Skip to content

Commit 29ef4bc

Browse files
committed
Update tests
1 parent c73227b commit 29ef4bc

14 files changed

Lines changed: 520 additions & 511 deletions

‎canopen/async_guard.py‎

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,3 @@ def async_guard_wrap(*args, **kwargs):
6666
if fn is not None:
6767
return decorator(fn)
6868
return decorator
69-
70-
71-
class AllowBlocking:
72-
""" Context manager to pause async guard """
73-
74-
def __enter__(self):
75-
self._enabled = is_async_guarded()
76-
enable_async_guard(False)
77-
return self
78-
79-
def __exit__(self, exc_type, exc_value, traceback):
80-
enable_async_guard(self._enabled)

‎test/README.md‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Canopen unit tests
2+
3+
This directory contains the unittests for the canopoen library. canopen use
4+
`unittest` as the framework for testing.
5+
6+
7+
## Testing without async
8+
9+
If writing a test that doesn't require or depend on async features, tests
10+
can be written as.
11+
12+
```python
13+
class TestVariable(unittest.TestCase):
14+
...
15+
```
16+
17+
See [`test_variable.py`](`test_variable.py`) as an example
18+
19+
20+
## Testing with async
21+
22+
Since this library supports usage with async support and with regular blocking
23+
calls, the unit tests must test both cases. This does requre a little bit more
24+
setup in the testing.
25+
26+
First create a base class that is intended to be run twice, once without async
27+
and once with async enabled.
28+
29+
```python
30+
from .async_tests import DualSyncAsyncTestCase
31+
32+
class TestEmcy(DualSyncAsyncTestCase):
33+
__test__ = False # This is a base class that shall not run directly
34+
35+
# The following attrobutes are available:
36+
# async_test: bool # Flag if async testing is running
37+
# loop: Optional[asyncio.AbstractEventLoop] # The loop in async mode,
38+
# # `None` in regular mode.
39+
40+
def setUp(self):
41+
super().setUp() # Make sure this is called when overriding `setUp`
42+
# ... do your setup
43+
44+
# Any tests that doesn't depend on async, can be written as regular
45+
# test methods
46+
def test_emcy_error(self):
47+
self.assertEqual(...)
48+
49+
# Any tests that requre async, use `async def`
50+
async def test_method(self):
51+
if self.async_test:
52+
# This is when async is enabled.
53+
await some_async_command()
54+
else:
55+
# This is when async is not running
56+
some_regular_command()
57+
```
58+
59+
To run this class, two instances of the test class must be created. One with
60+
async and one without:
61+
62+
```python
63+
class TestEmcySync(TestEmcy):
64+
"""Run the tests in non-asynchronous mode."""
65+
__test__ = True # This is test to run
66+
async_test = False # Not async mode
67+
68+
class TestEmcyAsync(TestEmcy):
69+
"""Run the tests in asynchronous mode."""
70+
__test__ = True # This is tests to run
71+
async_test = True # In async mode
72+
```
73+
74+
Note that there is no internal difference between the `Sync` and `Async` runs
75+
except for the changes in `self.async_test` and `self.loop`. It is the
76+
responsibility of the tests to setup the unit test with and without async.
77+
78+
79+
### Setting up a Network instance in async
80+
81+
When setting up a `Network()` instance in async, it is important that its async
82+
context is entered. If `setUp()` contains `self.network = Network(loop=self.loop)`
83+
then the following can be added:
84+
85+
```python
86+
async def asyncSetUp(self):
87+
if self.async_test:
88+
await self.network.__aenter__()
89+
90+
async def asyncTearDown(self):
91+
if self.async_test:
92+
await self.network.__aexit__(None, None, None)
93+
```
94+
95+
96+
### Excluding async from a test
97+
98+
The easiest way is to do:
99+
100+
```python
101+
def test_something(self):
102+
if self.async_test:
103+
self.skipTest("Async is not supported because ...")
104+
```

‎test/async_tests.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import asyncio
2+
import unittest
3+
from typing import Optional
4+
5+
from canopen.async_guard import enable_async_guard
6+
7+
8+
class DualSyncAsyncTestCase(unittest.IsolatedAsyncioTestCase):
9+
"""Base class for async test cases."""
10+
11+
__test__ = False # This is a base class, tests should not be run directly.
12+
13+
async_test: bool
14+
"""Flag to indicate the test mode. If True, the test will run in async
15+
mode, otherwise it will run in sync mode."""
16+
17+
loop: Optional[asyncio.AbstractEventLoop]
18+
"""The event loop to use for async tests. This will be set in the setUp
19+
method if async_test is True, otherwise it will be None."""
20+
21+
def setUp(self):
22+
"""Set up an object for async testing."""
23+
enable_async_guard(self.async_test)
24+
loop = None
25+
if self.async_test:
26+
loop = asyncio.get_event_loop()
27+
self.loop = loop
28+
29+
# Add a cleanup to disable the async guard after the test
30+
self.addCleanup(enable_async_guard, False)

‎test/conftest.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
import pytest
3+
4+
import canopen
5+
6+
7+
@pytest.fixture(scope="session", autouse=True)
8+
def enable_network_exceptions():
9+
"""Fixture to enable exceptions in the reception threads.
10+
11+
This makes sure exceptions are not swallowed in the reception threads,
12+
which is useful for debugging and testing.
13+
"""
14+
canopen.Network.FILTER_ERRORS = False
15+
yield

0 commit comments

Comments
 (0)