From 393e28c2a621223e151bd4b6e085980f33840a30 Mon Sep 17 00:00:00 2001 From: Ioannis Filippidis Date: Fri, 7 May 2021 12:54:04 +0200 Subject: [PATCH] TST: use `pytest`, instead of `nose` for compatibility with Python 3.10. - REL: add `pytest >= 4.6.11` to the argument `tests_require` of the function `setuptools.setup` in the script `setup.py`. This lower bound has been selected to ensure compatibility with both Python 3 and Python 2.7. See below for details. - REL: add `pytest >= 4.6.11` to file `requirements.txt` - REL: remove `nose` from argument `tests_require` of the function `setuptools.setup` in script `setup.py` - REL: remove `nose` from file `requirements.txt` - CI: update file `.travis.yml` - CI: remove the collection of coverage measurements on Travis CI, because these measurements do not collect Cython coverage with the build configuration currently used for CI testing. - DEV: remove `coveralls` from the file `requirements.txt`, because the package `coveralls` was used only on Travis CI. - TST: add a configuration file `tests/pytest.ini` and include it in `MANIFEST.in` - GIT: ignore `.pytest_cache/` in `.gitignore` ## Motivation The change from [`nose == 1.3.7`]( https://pypi.org/project/nose/1.3.7/#history) to [`pytest`]( https://pypi.org/project/pytest) is motivated by compatibility with Python 3.10. ## `nose` is incompatible with Python 3.10 The package `nose`, which was used to run the tests of `dd`, is not compatible with Python 3.10 (for details, read the last section below). Also, `nose` uses the `imp` module from CPython's standard library. The module `imp` [is deprecated]( https://docs.python.org/3.10/library/imp.html), so it may be removed in some future Python version. ## Summary of transition to `pytest` In summary, using `pytest` with the existing tests requires adding a [configuration file `tests/pytest.ini`]( https://docs.pytest.org/en/latest/reference/customize.html#configuration-file-formats) to tell `pytest` which functions, classes, and methods to collect tests from (called "discovery" of tests). The [parameter `--continue-on-collection-errors`]( https://docs.pytest.org/en/latest/reference/reference.html#command-line-flags) tells `pytest` to not stop in case any test module fails to import, and to continue with running the tests. The ability to run the tests when some `dd` C extension modules are not installed is necessary. After transitioning the tests to `pytest`, the tests have been confirmed to run successfully: - on Python 2.7 with `pytest == 4.6.11`, and - on Python 3.9 with `pytest == 6.2.4`. ## Considering using `unittest` For writing `dd` tests, the module [`unittest`]( https://docs.python.org/3/library/unittest.html) (part of CPython's standard library) suffices. For *discovering* the tests, `unittest` seems to require that tests be methods of subclasses of the class `unittest.TestCase`. This is not the case in the tests of `dd` tests. Using `pytest` allows changing the test runner from `nosetests` with minimal changes to the tests themselves. Test discovery using `unittest` could possibly be implemented by adding a file `tests/__init__.py`, and defining in that file a function `load_tests`, following the [documentation of `unittest`]( https://docs.python.org/3/library/unittest.html#unittest.TestLoader.discover). In any case, it is simpler to use `pytest`, which requires only a configuration file. If `unittest` encounters an `ImportError` during collection of the tests (i.e., when `unittest` tries to import test modules), then it stops. There does not appear to be any way to tell `unittest` to continue and run the rest of the test modules (those that *could* be imported). ## Usage of `nose` The dependence on `nose` was minimal. Only one function was used from `nose`: the function `nose.tools.assert_raises`. The function `assert_raises` is dynamically defined [in the module `nose.tools.trivial`]( https://github.com/nose-devs/nose/blob/release_1.3.7/nose/tools/trivial.py#L32-L54) by instantiating the class `unittest.TestCase`, and setting `assert_raises` to equal the [bound method `assertRaises`]( https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises) of the instance of `TestCase`. So the function `assert_raises` from `nose` is a PEP8-compliant binding for the method `unittest.TestCase.assertRaises`. Reading the code of `unittest`: - - it follows that the usage: ```python with nose.tools.assert_raises(AssertionError): foo(1, 2) ``` is equivalent to the following code (`AssertionError` here is used as an example): ```python with unittest.TestCase().assertRaises(AssertionError): foo(1, 2) ``` ## Replacing usage of `nose` with `pytest` in test code The [context manager `pytest.raises`]( https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises) is a [drop-in replacement]( https://en.wikipedia.org/wiki/Drop-in_replacement) for the function `nose.tools.assert_raises`: ```python with pytest.raises(AssertionError): foo(1, 2) ``` Also, the tests can still be run with `nosetests` on Python versions where `nose` is still available. ## Replacing the test runner `nosetests` with `pytest` - `pytest` correctly recognized the test files by default - `pytest` does not recognize by default methods of classes that do not start with "Test" as test methods. The configuration file is necessary for changing this behavior of `pytest` (in particular, the command-line parameter `-k` did not seem to work for classes). Relevant documentation: - - - - - - - The call `pytest tests/foo_test.py` imports the package `dd` from `site-packages` (assuming that the module `foo_test.py` contains the statement `import dd`). So the default behavior of `pytest` is as intended. In contrast, `nosetests tests/foo_test.py` imports the package `dd` from the local directory `dd/`, even though `dd` *is* installed under `site-packages`. In any case, `pytest` is called from within the directory `tests/`, as was done for `nosetests`. Both: - `python -m pytest tests/foo_test.py` and - `PYTHONPATH=. pytest tests/foo_test.py` result in importing `dd` from the local directory `dd/`. Relevant documentation: - - As remarked above, the parameter [`--continue-on-collection-errors`]( https://docs.pytest.org/en/latest/reference/reference.html#command-line-flags) of `pytest` needs to be used for running the tests when some of the C extension modules are not installed. For example: ``` cd tests/ pytest -v --continue-on-collection-errors . ``` To activate [Python's development mode]( https://docs.python.org/3/library/devmode.html): ``` cd tests/ python -X dev -m pytest -vvv --continue-on-collection-errors . ``` ## Further remarks Observations about `pytest`: - detects assertions that failed, and marks their source lines - avoids the deprecated `imp` module (of the standard library) that `nose` uses (and thus the associated `DeprecationWarning`) - running the tests of `dd` with `pytest` revealed several `DeprecationWarning`s that were previously not shown by `nose` (these warnings were about invalid escape sequences due to backslashes appearing in non-raw strings). [`pytest == 6.2.4`](https://pypi.org/project/pytest/6.2.4) is not compatible with Python 2.7. [`pytest == 4.6.11`](https://pypi.org/project/pytest/4.6.11/) is the latest version of `pytest` that is compatible with Python 2.7 (released on June 5, 2020). `pytest` specifies `python_requires` [PEP 345]( https://www.python.org/dev/peps/pep-0345/#requires-python), [PEP 503]( https://www.python.org/dev/peps/pep-0503/): - - So including `pytest>=4.6.11` in the file `requirements.txt` suffices to install, on each Python version, the latest version of `pytest` that is compatible with that Python version. This simplifies testing on CI, and packaging. In other words, conditional installations in the file `.travis.yml` are not needed for `pytest`, neither conditional definition of `tests_require` in the script `setup.py`. This approach leaves implicit the upper bound on `pytest` in `tests_require`. This upper bound is specified explicitly by `pytest` itself, depending on the Python version of the interpreter. It appears that `pip == 9.0.0` and `setuptools == 24.2.0` are required, to correctly implement `python_requires`: - - ## How replacing usage of `nose` with `unittest` would have looked like A way to replace `nose` could have been to add a module `tests/utils.py` containing: ```python """Common functionality for tests.""" import unittest _test_case = unittest.TestCase() assert_raises = _test_case.assertRaises ``` which is close to what `nose` does. The function `assert_raises` could then be imported from the module `utils` in test modules, and used. Using `pytest` avoids the need for this workaround. ## Details about the incompatibility of `nose` with Python 3.10 [`nose == 1.3.7`]( https://pypi.org/project/nose/1.3.7/#history) imports in Python 3.10. Running `nosetests` fails, due to imports from the module `collections` of classes that have moved to the module `collections.abc`. Relevant information about CPython changes in `collections` (removal of ABCs): - - - - Deprecation since Python 3.3, present until Python 3.9: - Removed in Python 3.10: ## About skipping tests The decorator `unittest.skipIf` is recognized by `pytest`, and skipped tests are correctly recorded and reported. In any case, note also the `pytest` test-skipping facilities: - - - ## About passing `-X dev` to `python` in the `Makefile` The argument `dev` is available for the `python` option [`-X` only on Python 3.7 and higher]( https://docs.python.org/3/library/devmode.html#devmode). So the `Makefile` rules where `-X dev` appears are not compatible with ealier Python versions supported by `dd`. This is not an issue: the development environment is intended to be Python 3.9 or higher, so there is no issue with using `-X dev`. ## Avoiding interaction between tests via class attributes Avoid class attributes in test classes. Use [data attributes][1] instead. Initialize the data attributes in setup methods of the test classes, as is common practice. This approach avoids interaction (via class attributes) between test scripts that import the same modules of common tests. With `nose`, this kind of interaction apparently did not occur, as observed by test failures that were expected to happen. However, `pytest` apparently runs tests in a way that changes to imported modules (e.g., class attributes) persist between different test scripts. This `pytest` behavior was observed by the disappearance of test failures when running with `pytest` (the test failures were observable with `pytest` only when telling `pytest` to run individual test scripts, instead of collecting tests from all test scripts. The cause of the issue with `pytest` was the modification of class attributes (not [data attributes][1]) from the importing module, of classes in the imported module. The modifications were done by setting the class attribute `DD` that defines the BDD or ZDD manager class. Both the scripts `cudd_test.py` and `autoref_test.py` made modifications. The end result was `autoref` tests being run using the class `dd.cudd.BDD`. Using data attributes, instead of class attributes, and subclassing, avoids this kind of erroneous testing. This approach is explicit [PEP 20][4]. [1]: https://docs.python.org/3/tutorial/classes.html#instance-objects [2]: https://github.com/pytest-dev/pytest-xdist [3]: https://github.com/pytest-dev/pytest-forked [4]: https://www.python.org/dev/peps/pep-0020/ --- .gitignore | 1 + .travis.yml | 5 +---- CHANGES.md | 2 ++ MANIFEST.in | 1 + Makefile | 23 ++++++++++++-------- README.md | 8 +++---- requirements.txt | 3 +-- setup.py | 2 +- tests/autoref_test.py | 15 ++++++++----- tests/bdd_test.py | 48 +++++++++++++++++++++--------------------- tests/common.py | 19 +++++++++-------- tests/common_bdd.py | 5 ++--- tests/common_cudd.py | 19 +++++++++-------- tests/cudd_test.py | 34 +++++++++++++++++++----------- tests/cudd_zdd_test.py | 20 ++++++++++++------ tests/dddmp_test.py | 6 +++--- tests/pytest.ini | 6 ++++++ 17 files changed, 124 insertions(+), 93 deletions(-) create mode 100644 tests/pytest.ini diff --git a/.gitignore b/.gitignore index 8d2e6e7e..dc881e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .coverage .cache +.pytest_cache/ todo.txt dd/_version.py cython_debug/* diff --git a/.travis.yml b/.travis.yml index ce3d50c8..67b10fca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,7 +43,4 @@ install: script: - cd tests/ - - nosetests --with-coverage --cover-package=dd - -after_success: - - coveralls + - pytest -v --continue-on-collection-errors . diff --git a/CHANGES.md b/CHANGES.md index 7dff311c..ea49b9f0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ ## 0.5.7 +- require `pytest >= 4.6.11`, instead of `nose`, for Python 3.10 compatibility + API: - return memory size in bytes from methods `dd.cudd.BDD.statistics` and diff --git a/MANIFEST.in b/MANIFEST.in index bb1ef288..a5be9876 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ include AUTHORS include requirements.txt include download.py include tests/README.md +include tests/pytest.ini include tests/inspect_cython_signatures.py include tests/common.py include tests/common_bdd.py diff --git a/Makefile b/Makefile index b34b9899..b69b2a33 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,3 @@ -# The script `rtests.py` below is an adaptation of: -# https://github.com/tulip-control/tulip-control/blob/master/run_tests.py - wheel_file := $(wildcard dist/*.whl) .PHONY: cudd install test @@ -12,15 +9,16 @@ build_cudd: clean cudd install test build_sylvan: clean -pip uninstall -y dd python setup.py install --sylvan - rtests.py --rednose + pip install pytest + make test sdist_test: clean python setup.py sdist --cudd --buddy cd dist; \ pip install dd*.tar.gz; \ tar -zxf dd*.tar.gz - pip install nose rednose - rtests.py --rednose + pip install pytest + make -C dist/dd*/ -f ../../Makefile test sdist_test_cudd: clean pip install cython ply @@ -30,8 +28,8 @@ sdist_test_cudd: clean tar -zxf dd*.tar.gz; \ cd dd*; \ python setup.py install --fetch --cudd - pip install nose rednose - rtests.py --rednose + pip install pytest + make -C dist/dd*/ -f ../../Makefile test # use to create source distributions for PyPI sdist: clean @@ -69,7 +67,14 @@ develop: test: cd tests/; \ - nosetests -v + python -X dev -m pytest -v --continue-on-collection-errors . +# `pytest -Werror` turns all warnings into errors +# https://docs.pytest.org/en/latest/how-to/capture-warnings.html +# including pytest warnings about unraisable exceptions: +# https://docs.pytest.org/en/latest/how-to/failures.html +# #warning-about-unraisable-exceptions-and-unhandled-thread-exceptions +# https://docs.pytest.org/en/latest/reference/reference.html +# #pytest.PytestUnraisableExceptionWarning test_abc: python -X dev tests/inspect_cython_signatures.py diff --git a/README.md b/README.md index a3c16249..5195bf63 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ [![Build Status][build_img]][travis] -[![Coverage Status][coverage]][coveralls] About @@ -324,14 +323,15 @@ The modules `dd.cudd` and `dd.cudd_zdd` in the wheel dynamically link to the: Tests ===== -Require [`nose`](https://pypi.python.org/pypi/nose). Run with: +Use [`pytest`](https://pypi.org/project/pytest). Run with: ```shell cd tests/ -nosetests +pytest -v --continue-on-collection-errors . ``` Tests of Cython modules that were not installed will fail. +The code is covered well by tests. License @@ -341,5 +341,3 @@ License [build_img]: https://travis-ci.com/tulip-control/dd.svg?branch=master [travis]: https://travis-ci.com/tulip-control/dd -[coverage]: https://coveralls.io/repos/tulip-control/dd/badge.svg?branch=master -[coveralls]: https://coveralls.io/r/tulip-control/dd?branch=master diff --git a/requirements.txt b/requirements.txt index 762c701e..52e785e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ cython==0.29.24 # dev -nose==1.3.7 +pytest>=4.6.11 gitpython grip -coveralls diff --git a/setup.py b/setup.py index 8a1d7f08..d8efce75 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ 'pydot >= 1.2.2', 'setuptools >= 19.6'] TESTS_REQUIRE = [ - 'nose >= 1.3.4'] + 'pytest >= 4.6.11'] CLASSIFIERS = [ 'Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', diff --git a/tests/autoref_test.py b/tests/autoref_test.py index c5e26e19..79fe30f0 100644 --- a/tests/autoref_test.py +++ b/tests/autoref_test.py @@ -3,17 +3,22 @@ from dd import autoref as _bdd import dd.bdd -from nose.tools import assert_raises -from common import Tests -from common_bdd import Tests as BDDTests +import common +import common_bdd logging.getLogger('astutils').setLevel('ERROR') -Tests.DD = _bdd.BDD -BDDTests.DD = _bdd.BDD +class Tests(common.Tests): + def setup_method(self): + self.DD = _bdd.BDD + + +class BDDTests(common_bdd.Tests): + def setup_method(self): + self.DD = _bdd.BDD def test_str(): diff --git a/tests/bdd_test.py b/tests/bdd_test.py index cdf17ae1..210770f2 100644 --- a/tests/bdd_test.py +++ b/tests/bdd_test.py @@ -8,7 +8,7 @@ from dd import bdd as _bdd import networkx as nx import networkx.algorithms.isomorphism as iso -from nose.tools import assert_raises +import pytest class BDD(_BDD): @@ -60,9 +60,9 @@ def test_add_var(): assert b.vars['y'] == 5, b.vars assert j == 5, j # attempt to add var at an existing level - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.add_var('z', level=35) - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.add_var('z', level=5) # # mixing automated and @@ -75,14 +75,14 @@ def test_add_var(): assert 'y' in b.vars, b.vars assert b.vars['x'] == 2, b.vars assert b.vars['y'] == 1, b.vars - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.add_var('z') b.add_var('z', level=0) def test_var(): b = BDD() - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.var('x') j = b.add_var('x') u = b.var('x') @@ -99,17 +99,17 @@ def test_assert_consistent(): g = x_or_y() assert g.assert_consistent() g._succ[2] = (5, 1, 2) - with assert_raises(AssertionError): + with pytest.raises(AssertionError): g.assert_consistent() g = x_or_y() g.roots.add(2) g._succ[4] = (0, 10, 1) - with assert_raises(AssertionError): + with pytest.raises(AssertionError): g.assert_consistent() g = x_or_y() g.roots.add(2) g._succ[1] = (2, None, 1) - with assert_raises(AssertionError): + with pytest.raises(AssertionError): g.assert_consistent() g = x_and_y() assert g.assert_consistent() @@ -120,7 +120,7 @@ def test_level_to_variable(): g = BDD(ordering) assert g.var_at_level(0) == 'x' assert g.var_at_level(1) == 'y' - with assert_raises(AssertionError): + with pytest.raises(AssertionError): g.var_at_level(10) @@ -209,7 +209,7 @@ def test_count(): assert r == 6, r r = g.count(-4, 3) assert r == 2, r - with assert_raises(Exception): + with pytest.raises(Exception): g.count() r = g.count(4) assert r == 3, r @@ -472,10 +472,10 @@ def test_find_or_add(): assert refv == refv_, (refv, refv_) assert refw == refw_, (refw, refw_) # only non-terminals can be added - with assert_raises(AssertionError): + with pytest.raises(AssertionError): g.find_or_add(2, -1, 1) # low and high must already exist - with assert_raises(AssertionError): + with pytest.raises(AssertionError): g.find_or_add(0, 3, 4) # canonicity of complemented edges # v < 0, w > 0 @@ -527,7 +527,7 @@ def test_next_free_int(): # full g._succ = {1, 2, 3} g.max_nodes = 3 - with assert_raises(Exception): + with pytest.raises(Exception): g._next_free_int(start=1) @@ -663,7 +663,7 @@ def test_cofactor(): ordering = {'x': 0, 'y': 1, 'z': 2} g = BDD(ordering) # u not in g - with assert_raises(AssertionError): + with pytest.raises(AssertionError): g.let({'x': False, 'y': True, 'z': False}, 5) # x /\ y e = g.add_expr(r'x /\ y') @@ -799,7 +799,7 @@ def test_request_reordering(): ctx._last_len = 1 ctx.length = 3 # >= 2 = 2 * _last_len # large growth - with assert_raises(_bdd._NeedsReordering): + with pytest.raises(_bdd._NeedsReordering): _bdd._request_reordering(ctx) ctx._last_len = 2 ctx.length = 3 # < 4 = 2 * _last_len @@ -817,20 +817,20 @@ def test_reordering_context(): ctx.assert_(False) # nested context ctx._reordering_context = True - with assert_raises(_bdd._NeedsReordering): + with pytest.raises(_bdd._NeedsReordering): with _bdd._ReorderingContext(ctx): ctx.assert_(True) raise _bdd._NeedsReordering() ctx.assert_(True) # other exception ctx._reordering_context = False - with assert_raises(AssertionError): + with pytest.raises(AssertionError): with _bdd._ReorderingContext(ctx): ctx.assert_(True) raise AssertionError() ctx.assert_(False) ctx._reordering_context = True - with assert_raises(Exception): + with pytest.raises(Exception): with _bdd._ReorderingContext(ctx): raise Exception() ctx.assert_(True) @@ -944,7 +944,7 @@ def test_undeclare_vars(): bdd = BDD() bdd.declare('x', 'y', 'z', 'w') u = bdd.add_expr(r'y /\ w') - with assert_raises(AssertionError): + with pytest.raises(AssertionError): bdd.undeclare_vars('z', 'y') @@ -1100,7 +1100,7 @@ def test_rename(): assert r == r_, (r, r_) # u not in bdd dvars = {'x': 'xp'} - with assert_raises(AssertionError): + with pytest.raises(AssertionError): g.let(dvars, 1000) # y essential for u dvars = {'x': 'y'} @@ -1153,16 +1153,16 @@ def test_image_rename_map_checks(): assert r == 1, r # overlapping keys and values rename = {0: 1, 1: 2} - with assert_raises(AssertionError): + with pytest.raises(AssertionError): _bdd.image(1, 1, rename, qvars, bdd) - with assert_raises(AssertionError): + with pytest.raises(AssertionError): _bdd.preimage(1, 1, rename, qvars, bdd) # may be in support after quantification ? trans = bdd.add_expr('x => xp') source = bdd.add_expr(r'x /\ y') qvars = {0} rename = {1: 0, 3: 2} - with assert_raises(AssertionError): + with pytest.raises(AssertionError): _bdd.image(trans, source, rename, qvars, bdd) # in support of `target` ? qvars = set() @@ -1237,7 +1237,7 @@ def test_assert_valid_ordering(): ordering = {'x': 0, 'y': 1} _bdd._assert_valid_ordering(ordering) incorrect_ordering = {'x': 0, 'y': 2} - with assert_raises(AssertionError): + with pytest.raises(AssertionError): _bdd._assert_valid_ordering(incorrect_ordering) diff --git a/tests/common.py b/tests/common.py index d4efe018..fb37c6aa 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,10 +1,11 @@ """Common tests for `autoref`, `cudd`, `cudd_zdd`.""" -from nose.tools import assert_raises +import pytest class Tests(object): - DD = None # `autoref.BDD` or `cudd.BDD` or - # `cudd_zdd.ZDD` + def setup_method(self): + self.DD = None # `autoref.BDD` or `cudd.BDD` or + # `cudd_zdd.ZDD` def test_true_false(self): bdd = self.DD() @@ -80,7 +81,7 @@ def test_contains(self): # undefined `__contains__` other_bdd = self.DD() other_true = other_bdd.true - with assert_raises(AssertionError): + with pytest.raises(AssertionError): other_true in bdd def test_var_levels(self): @@ -260,7 +261,7 @@ def test_count(self): # x b.declare('x') u = b.add_expr('x') - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.count(u, 0) n = b.count(u, 1) assert n == 1, n @@ -273,9 +274,9 @@ def test_count(self): # x /\ y b.declare('y') u = b.add_expr(r'x /\ y') - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.count(u, 0) - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.count(u, 1) n = b.count(u, 2) assert n == 1, n @@ -287,9 +288,9 @@ def test_count(self): assert n == 1, n # x \/ ~ y u = b.add_expr(r'x \/ ~ y') - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.count(u, 0) - with assert_raises(AssertionError): + with pytest.raises(AssertionError): b.count(u, 1) n = b.count(u, 2) assert n == 3, n diff --git a/tests/common_bdd.py b/tests/common_bdd.py index 512127eb..2ea2a23d 100644 --- a/tests/common_bdd.py +++ b/tests/common_bdd.py @@ -1,11 +1,10 @@ """Common tests for `autoref`, `cudd`.""" import os -from nose.tools import assert_raises - class Tests(object): - DD = None # `autoref.BDD` or `cudd.BDD` + def setup_method(self): + self.DD = None # `autoref.BDD` or `cudd.BDD` def test_succ(self): bdd = self.DD() diff --git a/tests/common_cudd.py b/tests/common_cudd.py index 6ff575d4..47c4c854 100644 --- a/tests/common_cudd.py +++ b/tests/common_cudd.py @@ -1,10 +1,11 @@ """Common tests for `cudd`, `cudd_zdd`.""" -from nose.tools import assert_raises +import pytest class Tests(object): - DD = None # `cudd.BDD` or `cudd_zdd.ZDD` - MODULE = None # `cudd` or `cudd_zdd` + def setup_method(self): + self.DD = None # `cudd.BDD` or `cudd_zdd.ZDD` + self.MODULE = None # `cudd` or `cudd_zdd` def test_add_var(self): bdd = self.DD() @@ -119,7 +120,7 @@ def test_double_incref_decref_locally_inconsistent(self): bdd.incref(u) # ref cnt = 3 bdd.incref(u) # ref cnt = 4 bdd.decref(v) # ref cnt = 3 - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): bdd.decref(v) del u, v # ref cnt = 2 assert len(bdd) > 0, len(bdd) @@ -180,7 +181,7 @@ def test_decref(self): # No need for `recursive=True`, # because this call should have # no effect at all. - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): bdd.decref(u) # safe to access the CUDD BDD/ZDD node's # reference count because garbage collection @@ -218,14 +219,14 @@ def test_decref_ref_lower_bound(self): # # Again, `u` is not used in any way # that could access deallocated memory. - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): bdd.decref(u) assert u._ref == 0, u._ref # check also with `recursive=True` - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): bdd.decref(u, recursive=True) # check also `incref` - with assert_raises(RuntimeError): + with pytest.raises(RuntimeError): bdd.incref(u) assert len(bdd) == 0, len(bdd) @@ -236,7 +237,7 @@ def test_dealloc_wrong_ref_lower_bound(self): # make an erroneous external modification assert u.ref == 1, u.ref u._ref = -1 # erroneous value - with assert_raises(AssertionError): + with pytest.raises(AssertionError): self.MODULE._test_call_dealloc(u) assert u.ref == 1, u.ref assert u._ref == -1, u._ref diff --git a/tests/cudd_test.py b/tests/cudd_test.py index afff20d2..b6f3783a 100644 --- a/tests/cudd_test.py +++ b/tests/cudd_test.py @@ -2,25 +2,35 @@ import logging from dd import cudd -from nose.tools import assert_raises, assert_warns +import pytest -from common import Tests -from common_bdd import Tests as BDDTests -from common_cudd import Tests as CuddTests +import common +import common_bdd +import common_cudd logging.getLogger('astutils').setLevel('ERROR') -Tests.DD = cudd.BDD -BDDTests.DD = cudd.BDD -CuddTests.DD = cudd.BDD -CuddTests.MODULE = cudd +class Tests(common.Tests): + def setup_method(self): + self.DD = cudd.BDD + + +class BDDTests(common_bdd.Tests): + def setup_method(self): + self.DD = cudd.BDD + + +class CuddTests(common_cudd.Tests): + def setup_method(self): + self.DD = cudd.BDD + self.MODULE = cudd def test_str(): bdd = cudd.BDD() - with assert_warns(UserWarning): + with pytest.warns(UserWarning): s = str(bdd) s + 'must be a string' @@ -142,7 +152,7 @@ def test_swap(): # not checked for overlapping key-value pairs. u = bdd.apply('and', x, ~ y) d = dict(x='y', y='x') - with assert_raises(AssertionError): + with pytest.raises(AssertionError): f = bdd._swap(u, d) # f_ = bdd.apply('and', ~ x, y) # assert f == f_, (f, f_) @@ -170,7 +180,7 @@ def test_swap(): z = bdd.var('z') u = bdd.apply('and', x, ~ y) d = dict(x='y', y='z') - with assert_raises(AssertionError): + with pytest.raises(AssertionError): f = bdd._swap(u, d) # The following result is obtained if the # assertions are removed from `BDD._swap`. @@ -180,7 +190,7 @@ def test_swap(): # 2) each value appears once among values u = bdd.apply('and', x, ~ y) d = dict(x='y', z='y') - with assert_raises(AssertionError): + with pytest.raises(AssertionError): f = bdd._swap(u, d) # The following result is obtained if the # assertions are removed from `BDD._swap`. diff --git a/tests/cudd_zdd_test.py b/tests/cudd_zdd_test.py index a90b683f..0b1c48f2 100644 --- a/tests/cudd_zdd_test.py +++ b/tests/cudd_zdd_test.py @@ -4,20 +4,26 @@ from dd import cudd from dd import cudd_zdd from dd import _copy -from nose.tools import assert_warns +import pytest -from common import Tests as Tests -from common_cudd import Tests as CuddTests +import common +import common_cudd -Tests.DD = cudd_zdd.ZDD -CuddTests.DD = cudd_zdd.ZDD -CuddTests.MODULE = cudd_zdd +class Tests(common.Tests): + def setup_method(self): + self.DD = cudd_zdd.ZDD + + +class CuddTests(common_cudd.Tests): + def setup_method(self): + self.DD = cudd_zdd.ZDD + self.MODULE = cudd_zdd def test_str(): bdd = cudd_zdd.ZDD() - with assert_warns(UserWarning): + with pytest.warns(UserWarning): s = str(bdd) s + 'must be a string' diff --git a/tests/dddmp_test.py b/tests/dddmp_test.py index b3208979..199ba7e0 100644 --- a/tests/dddmp_test.py +++ b/tests/dddmp_test.py @@ -4,7 +4,7 @@ from dd.dddmp import Lexer, Parser, load, _rewrite_tables import networkx as nx -from nose.tools import assert_raises +import pytest logging.getLogger('dd.dddmp.parser_logger').setLevel(logging.ERROR) @@ -17,13 +17,13 @@ def test_lexer(): lexer.lexer.input(s) tok = lexer.lexer.token() assert tok.value == '.ghs' - with assert_raises(Exception): + with pytest.raises(Exception): lexer.lexer.token() def test_parser(): parser = Parser() - with assert_raises(Exception): + with pytest.raises(Exception): parser.parser.parse(input='.mode C', lexer=parser.lexer.lexer) diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 00000000..ed7f9f25 --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,6 @@ +# configuration file for package `pytest` +[pytest] +filterwarnings = error +python_files = *_test.py +python_classes = *Tests +python_functions = test_*