From d4d8cedb370f32998585e422e79ecbfb08d8055e Mon Sep 17 00:00:00 2001 From: Robin De Schepper <robin.deschepper93@gmail.com> Date: Wed, 13 Oct 2021 10:36:51 +0200 Subject: [PATCH] Automatic test discovery sans boilerplate (#1693) --- .github/workflows/basic.yml | 6 +- .gitignore | 1 + doc/contrib/test.rst | 15 +- python/test/cases.py | 19 ++ python/test/fixtures.py | 219 ++++++++++++++++++ python/test/options.py | 18 -- python/test/readme.md | 79 ++----- python/test/unit/runner.py | 74 ------ python/test/unit/test_cable_probes.py | 25 +- python/test/unit/test_catalogues.py | 37 +-- python/test/unit/test_clear_samplers.py | 94 +------- python/test/unit/test_contexts.py | 25 +- python/test/unit/test_decor.py | 24 +- .../test/unit/test_domain_decompositions.py | 25 +- python/test/unit/test_event_generators.py | 25 +- python/test/unit/test_identifiers.py | 25 +- python/test/unit/test_morphology.py | 25 +- python/test/unit/test_profiling.py | 24 +- python/test/unit/test_schedules.py | 34 +-- python/test/unit/test_spikes.py | 69 +----- python/test/unit_distributed/__init__.py | 3 - python/test/unit_distributed/runner.py | 81 ------- .../unit_distributed/test_contexts_arbmpi.py | 47 +--- .../unit_distributed/test_contexts_mpi4py.py | 38 +-- .../test_domain_decompositions.py | 45 +--- .../test/unit_distributed/test_simulator.py | 45 +--- 26 files changed, 321 insertions(+), 801 deletions(-) create mode 100644 python/test/cases.py create mode 100644 python/test/fixtures.py delete mode 100644 python/test/options.py delete mode 100644 python/test/unit/runner.py delete mode 100644 python/test/unit_distributed/__init__.py delete mode 100644 python/test/unit_distributed/runner.py diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 5e22c96e..8b7f7373 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -172,12 +172,10 @@ jobs: run: scripts/run_cpp_examples.sh "mpirun -n 4 -oversubscribe" - name: Run python tests run: | - cd build - python ../python/test/unit/runner.py -v2 - cd - + python3 -m unittest discover -v -s python - if: ${{ matrix.config.mpi == 'ON' }} name: Run python+MPI tests - run: mpirun -n 4 -oversubscribe python python/test/unit_distributed/runner.py -v2 + run: mpirun -n 4 -oversubscribe python3 -m unittest discover -v -s python - name: Run Python examples run: scripts/run_python_examples.sh - name: Build a catalogue diff --git a/.gitignore b/.gitignore index f2c26f5f..ba311b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.so # intermediate python files +__pycache__ *.pyc # python dev env files diff --git a/doc/contrib/test.rst b/doc/contrib/test.rst index bddc4191..e447931c 100644 --- a/doc/contrib/test.rst +++ b/doc/contrib/test.rst @@ -1,7 +1,20 @@ .. _contribtest: Tests -============ +===== C++ tests are located in ``/tests`` and Python (binding) tests in ``/python/test``. See the documentation on :ref:`building <building>` for the C++ tests and ``/python/test/readme.md`` for the latter. + +Python tests +============ + +The Python tests uses the `unittest +<https://docs.python.org/3/library/unittest.html>`_ and its test discovery +mechanism. For tests to be discovered they must meet the following criteria: + +* Located in an importable code folder starting from the ``python/test`` root. + If you introduce subfolders they must all contain a ``__init__.py`` file. +* The filenames must start with ``test_``. +* The test case classes must begin with ``Test``. +* The test functions inside the cases must begin with ``test_``. diff --git a/python/test/cases.py b/python/test/cases.py new file mode 100644 index 00000000..4c21056a --- /dev/null +++ b/python/test/cases.py @@ -0,0 +1,19 @@ +import unittest +import arbor +from . import fixtures + +_mpi_enabled = arbor.__config__ + +@fixtures.context +def skipIfNotDistributed(context): + skipSingleNode = unittest.skipIf(context.ranks < 2, "Skipping distributed test on single node.") + skipNotEnabled = unittest.skipIf(not _mpi_enabled, "Skipping distributed test, no MPI support in arbor.") + def skipper(f): + return skipSingleNode(skipNotEnabled(f)) + + return skipper + + +@fixtures.context +def skipIfDistributed(context): + return unittest.skipIf(context.ranks > 1, "Skipping single node test on multiple nodes.") diff --git a/python/test/fixtures.py b/python/test/fixtures.py new file mode 100644 index 00000000..64145597 --- /dev/null +++ b/python/test/fixtures.py @@ -0,0 +1,219 @@ +import arbor +import functools +from functools import lru_cache as cache +import unittest +from pathlib import Path +import subprocess +import warnings +import atexit + +_mpi_enabled = arbor.__config__["mpi"] +_mpi4py_enabled = arbor.__config__["mpi4py"] + +# The API of `functools`'s caches went through a bunch of breaking changes from +# 3.6 to 3.9. Patch them up in a local `cache` function. +try: + cache(lambda: None) +except TypeError: + # If `lru_cache` does not accept user functions as first arg, it expects + # the max cache size as first arg, we pass None to produce a cache decorator + # without max size. + cache = cache(None) + +def _fix(param_name, fixture, func): + """ + Decorates `func` to inject the `fixture` callable result as `param_name`. + """ + @functools.wraps(func) + def wrapper(*args, **kwargs): + kwargs[param_name] = fixture() + return func(*args, **kwargs) + + return wrapper + +def _fixture(decorator): + @functools.wraps(decorator) + def fixture_decorator(func): + return _fix(decorator.__name__, decorator, func) + + return fixture_decorator + +def _singleton_fixture(f): + return _fixture(cache(f)) + + +@_fixture +def repo_path(): + """ + Fixture that returns the repo root path. + """ + return Path(__file__).parent.parent.parent + + +def _finalize_mpi(): + print("Context fixture finalizing mpi") + arbor.mpi_finalize() + + +@_fixture +def context(): + """ + Fixture that produces an MPI sensitive `arbor.context` + """ + args = [arbor.proc_allocation()] + if _mpi_enabled: + if not arbor.mpi_is_initialized(): + print("Context fixture initializing mpi", flush=True) + arbor.mpi_init() + atexit.register(_finalize_mpi) + if _mpi4py_enabled: + from mpi4py.MPI import COMM_WORLD as comm + else: + comm = arbor.mpi_comm() + args.append(comm) + return arbor.context(*args) + + +class _BuildCatError(Exception): pass + + +def _build_cat_local(name, path): + try: + subprocess.run(["build-catalogue", name, str(path)], check=True, stderr=subprocess.PIPE) + except subprocess.CalledProcessError as e: + raise _BuildCatError("Tests can't build catalogues:\n" + e.stderr.decode()) from None + + +def _build_cat_distributed(comm, name, path): + # Control flow explanation: + # * `build_err` starts out as `None` + # * Rank 1 to N wait for a broadcast from rank 0 to receive the new value + # for `build_err` + # * Rank 0 splits off from the others and executes the build. + # * If it builds correctly it finishes the collective `build_err` + # broadcast with the initial value `None`: all nodes continue. + # * If it errors, it finishes the collective broadcast with the caught err + # + # All MPI ranks either continue or raise the same err. (prevents stalling) + build_err = None + if not comm.Get_rank(): + try: + _build_cat_local(name, path) + except Exception as e: + build_err = e + build_err = comm.bcast(build_err, root=0) + if build_err: + raise build_err + +@context +def _build_cat(name, path, context): + if context.has_mpi: + try: + from mpi4py.MPI import COMM_WORLD as comm + except ImportError: + raise _BuildCatError( + "Building catalogue in an MPI context, but `mpi4py` not found." + + " Concurrent identical catalogue builds might occur." + ) from None + + _build_cat_distributed(comm, name, path) + else: + _build_cat_local(name, path) + return Path.cwd() / (name + "-catalogue.so") + + +@_singleton_fixture +@repo_path +def dummy_catalogue(repo_path): + """ + Fixture that returns a dummy `arbor.catalogue` + which contains the `dummy` mech. + """ + path = repo_path / "test" / "unit" / "dummy" + cat_path = _build_cat("dummy", path) + return arbor.load_catalogue(str(cat_path)) + +@_fixture +class empty_recipe(arbor.recipe): + """ + Blank recipe fixture. + """ + pass + + +@_fixture +def cable_cell(): + # (1) Create a morphology with a single (cylindrical) segment of length=diameter=6 μm + tree = arbor.segment_tree() + tree.append( + arbor.mnpos, + arbor.mpoint(-3, 0, 0, 3), + arbor.mpoint(3, 0, 0, 3), + tag=1, + ) + + # (2) Define the soma and its midpoint + labels = arbor.label_dict({'soma': '(tag 1)', + 'midpoint': '(location 0 0.5)'}) + + # (3) Create cell and set properties + decor = arbor.decor() + decor.set_property(Vm=-40) + decor.paint('"soma"', 'hh') + decor.place('"midpoint"', arbor.iclamp( 10, 2, 0.8), "iclamp") + decor.place('"midpoint"', arbor.spike_detector(-10), "detector") + return arbor.cable_cell(tree, labels, decor) + +@_fixture +class art_spiker_recipe(arbor.recipe): + """ + Recipe fixture with 3 artificial spiking cells. + """ + def __init__(self): + super().__init__() + self.the_props = arbor.neuron_cable_properties() + self.trains = [ + [0.8, 2, 2.1, 3], + [0.4, 2, 2.2, 3.1, 4.5], + [0.2, 2, 2.8, 3]] + + def num_cells(self): + return 4 + + def cell_kind(self, gid): + if gid < 3: + return arbor.cell_kind.spike_source + else: + return arbor.cell_kind.cable + + def connections_on(self, gid): + return [] + + def event_generators(self, gid): + return [] + + def global_properties(self, kind): + return self.the_props + + def probes(self, gid): + if gid < 3: + return [] + else: + return [arbor.cable_probe_membrane_voltage('"midpoint"')] + + @cable_cell + def _cable_cell(self, cable_cell): + return cable_cell + + def cell_description(self, gid): + if gid < 3: + return arbor.spike_source_cell("src", arbor.explicit_schedule(self.trains[gid])) + else: + return self._cable_cell() + +@_fixture +@context +@art_spiker_recipe +def art_spiking_sim(context, art_spiker_recipe): + dd = arbor.partition_load_balance(art_spiker_recipe, context) + return arbor.simulation(art_spiker_recipe, dd, context) diff --git a/python/test/options.py b/python/test/options.py deleted file mode 100644 index 9e191c1d..00000000 --- a/python/test/options.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# -# options.py - -import argparse - -import os - -import arbor as arb - -def parse_arguments(args=None, namespace=None): - parser = argparse.ArgumentParser() - - # add arguments as needed (e.g. -d, --dryrun Number of dry run ranks) - parser.add_argument("-v", "--verbosity", nargs='?', const=0, type=int, choices=[0, 1, 2], default=0, help="increase output verbosity") - #parser.add_argument("-d", "--dryrun", type=int, default=100 , help="number of dry run ranks") - args = parser.parse_args() - return args diff --git a/python/test/readme.md b/python/test/readme.md index dee3e090..515366df 100644 --- a/python/test/readme.md +++ b/python/test/readme.md @@ -1,87 +1,44 @@ ## Directory Structure ``` |- test\ - |- options.py |- unit\ - |- runner.py |- test_contexts.py |- ... |- unit-distributed\ - |- runner.py |- test_contexts_arbmpi.py |- test_contexts_mpi4py.py |- ... ``` -In parent folder `test`: -- `options.py`: set global options (define arg parser) - -In subfolders `unit`/`unit_distributed`: -- `test_xxxs.py`: define unittest class with test methods and own test suite (named: test module) -- `runner.py`: run all tests in this subfolder (which are defined as suite in test modules) +In subfolders `unit`/`unit_distributed`: +- `test_xxxs.py`: define `TestMyTestCase(unittest.TestCase)` classes with + test methods ## Usage -### with `unittest` from SUBFOLDER: -* to run all tests in subfolder: +* to run all tests: + ``` -python -m unittest [-v] +[mpiexec -n X] python -m unittest discover [-v] -s python ``` -* to run module: -``` -python -m unittest module [-v] -``` -, e.g. in `test/unit` use `python -m unittest test_contexts -v` -* to run class in module: -``` -python -m unittest module.class [-v] -``` -, eg. in `test/unit` use `python -m unittest test_contexts.Contexts -v` -* to run method in class in module: -``` -python -m unittest module.class.method [-v] -``` -, eg. in `test/unit` use `python -m unittest test_contexts.Contexts.test_context -v` - -### with `runner.py` and argument(s) `-v {0,1,2}` from SUBFOLDER: -* to run all tests in subfolder: -``` -python -m runner[-v2] -``` -or `python runner.py [-v2]` -* to run module: -``` -python -m test_xxxs [-v2] -``` -or `python test_xxxs.py [-v2]` -* running classes or methods not possible this way +* to run pattern matched test file(s): -### from any other folder: - -* to run all tests: -``` -python path/to/runner.py [-v2] +``` +[mpiexec -n X] python -m unittest discover [-v] -s python -p test_some_file.py +[mpiexec -n X] python -m unittest discover [-v] -s python -p test_some_*.py ``` -* to run module: -``` -python path/to/test_xxxs.py [-v2] -``` + ## Adding new tests -1. In suitable folder `test/unit` (no MPI) or `test/unit_distributed` (MPI), create `test_xxxs.py` file -2. In `test_xxxs.py` file, define - a) a unittest `class Xxxs(unittest.TestCase)` with test methods `test_yyy` - b) a suite function `suite()` consisting of all desired tests returning a unittest suite `unittest.makeSuite(Xxxs, ('test'))` (for all defined tests, tuple of selected tests possible); steering of which tests to include happens here! - c) a run function `run()` with a unittest runner `unittest.TextTestRunner` running the `suite()` via `runner.run(suite())` - d) a `if __name__ == "__main__":` calling `run()` -3. Add module to `runner.py` in subfolder by adding `test_xxxs` - a) to import: in `try` add `import test_xxxs`, in `except` add `from test.subfolder import test_xxxs` - b) to `test_modules` list +1. In suitable folder `test/unit` (no MPI) or `test/unit_distributed` (MPI), + create `test_xxxs.py` file +1. Create tests suitable for local and distributed + testing, or mark with the appropriate `cases.skipIf(Not)Distributed` decorator ## Naming convention -- modules: `test_xxxs` (all lower case, ending with `s` since module can consist of multiple classes) -- class(es): `Xxxs` (first letter upper case, ending with `s` since class can consist of multiple test functions) -- functions: `test_yyy` (always starting with `test`since suite is build from all methods starting with `test`) +- modules: `test_xxxs` (ending with `s` since module can consist of multiple classes) +- class(es): `TestXxxs` (ending with `s` since class can consist of multiple test functions) +- functions: `test_yyy` diff --git a/python/test/unit/runner.py b/python/test/unit/runner.py deleted file mode 100644 index 224f1957..00000000 --- a/python/test/unit/runner.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -# -# runner.py - -import unittest - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options - import test_cable_probes - import test_catalogues - import test_clear_samplers - import test_contexts - import test_decor - import test_domain_decomposition - import test_event_generators - import test_identifiers - import test_morphology - import test_profiling - import test_schedules - import test_spikes - import test_tests - # add more if needed -except ModuleNotFoundError: - from test import options - from test.unit import test_cable_probes - from test.unit import test_catalogues - from test.unit import test_clear_samplers - from test.unit import test_contexts - from test.unit import test_decor - from test.unit import test_domain_decompositions - from test.unit import test_event_generators - from test.unit import test_identifiers - from test.unit import test_morphology - from test.unit import test_profiling - from test.unit import test_schedules - from test.unit import test_spikes - # add more if needed - -test_modules = [\ - test_cable_probes,\ - test_catalogues, \ - test_clear_samplers, \ - test_contexts,\ - test_decor,\ - test_domain_decompositions,\ - test_event_generators,\ - test_identifiers,\ - test_morphology,\ - test_profiling,\ - test_schedules,\ - test_spikes,\ -] # add more if needed - -def suite(): - loader = unittest.TestLoader() - - suites = [] - for test_module in test_modules: - test_module_suite = test_module.suite() - suites.append(test_module_suite) - - suite = unittest.TestSuite(suites) - - return suite - -if __name__ == "__main__": - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - result = runner.run(suite()) - sys.exit(not(result.wasSuccessful())) diff --git a/python/test/unit/test_cable_probes.py b/python/test/unit/test_cable_probes.py index 33d2c254..b992b4a6 100644 --- a/python/test/unit/test_cable_probes.py +++ b/python/test/unit/test_cable_probes.py @@ -2,15 +2,7 @@ import unittest import arbor as A - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ tests for cable probe wrappers @@ -91,7 +83,7 @@ class cc_recipe(A.recipe): def cell_description(self, gid): return self.cell -class CableProbes(unittest.TestCase): +class TestCableProbes(unittest.TestCase): def test_probe_addr_metadata(self): recipe = cc_recipe() context = A.context() @@ -172,16 +164,3 @@ class CableProbes(unittest.TestCase): m = sim.probe_metadata((0, 16)) self.assertEqual(1, len(m)) self.assertEqual(all_cv_cables, m[0]) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(CableProbes, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_catalogues.py b/python/test/unit/test_catalogues.py index 00cc2ff8..f94dc737 100644 --- a/python/test/unit/test_catalogues.py +++ b/python/test/unit/test_catalogues.py @@ -1,16 +1,7 @@ +from .. import fixtures import unittest - import arbor as arb -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options - """ tests for (dynamically loaded) catalogues """ @@ -46,17 +37,14 @@ class recipe(arb.recipe): return self.cell -class Catalogues(unittest.TestCase): +class TestCatalogues(unittest.TestCase): def test_nonexistent(self): with self.assertRaises(FileNotFoundError): arb.load_catalogue("_NO_EXIST_.so") - def test_shared_catalogue(self): - try: - cat = arb.load_catalogue("lib/dummy-catalogue.so") - except: - print("BBP catalogue not found. Are you running from build directory?") - raise + @fixtures.dummy_catalogue + def test_shared_catalogue(self, dummy_catalogue): + cat = dummy_catalogue nms = [m for m in cat] self.assertEqual(nms, ['dummy'], "Expected equal names.") for nm in nms: @@ -94,18 +82,3 @@ class Catalogues(unittest.TestCase): cat = arb.catalogue() cat.extend(other, "prefix/") self.assertNotEqual(hash_(other), hash_(cat), "Extending empty with prefixed cat should not yield cat") - - - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(Catalogues, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_clear_samplers.py b/python/test/unit/test_clear_samplers.py index c54db908..bb7c2018 100644 --- a/python/test/unit/test_clear_samplers.py +++ b/python/test/unit/test_clear_samplers.py @@ -8,87 +8,18 @@ import numpy as np # to be able to run .py file from child directory import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures, cases """ all tests for the simulator wrapper """ -def make_cable_cell(): - # (1) Create a morphology with a single (cylindrical) segment of length=diameter=6 μm - tree = A.segment_tree() - tree.append(A.mnpos, A.mpoint(-3, 0, 0, 3), A.mpoint(3, 0, 0, 3), tag=1) - - # (2) Define the soma and its midpoint - labels = A.label_dict({'soma': '(tag 1)', - 'midpoint': '(location 0 0.5)'}) - - # (3) Create cell and set properties - decor = A.decor() - decor.set_property(Vm=-40) - decor.paint('"soma"', 'hh') - decor.place('"midpoint"', A.iclamp( 10, 2, 0.8), "iclamp") - decor.place('"midpoint"', A.spike_detector(-10), "detector") - return A.cable_cell(tree, labels, decor) - -# Test recipe art_spiker_recipe comprises three artificial spiking cells -class art_spiker_recipe(A.recipe): - def __init__(self): - A.recipe.__init__(self) - self.the_props = A.neuron_cable_properties() - self.trains = [ - [0.8, 2, 2.1, 3], - [0.4, 2, 2.2, 3.1, 4.5], - [0.2, 2, 2.8, 3]] - - def num_cells(self): - return 4 - - def cell_kind(self, gid): - if gid < 3: - return A.cell_kind.spike_source - else: - return A.cell_kind.cable - - def connections_on(self, gid): - return [] - - def event_generators(self, gid): - return [] - - def global_properties(self, kind): - return self.the_props - - def probes(self, gid): - if gid < 3: - return [] - else: - return [A.cable_probe_membrane_voltage('"midpoint"')] - - def cell_description(self, gid): - if gid < 3: - return A.spike_source_cell("src", A.explicit_schedule(self.trains[gid])) - else: - return make_cable_cell() - - - -class Clear_samplers(unittest.TestCase): - # Helper for constructing a simulation from a recipe using default context and domain decomposition. - def init_sim(self, recipe): - context = A.context() - dd = A.partition_load_balance(recipe, context) - return A.simulation(recipe, dd, context) - +@cases.skipIfDistributed() +class TestClearSamplers(unittest.TestCase): # test that all spikes are sorted by time then by gid - def test_spike_clearing(self): - - sim = self.init_sim(art_spiker_recipe()) + @fixtures.art_spiking_sim + def test_spike_clearing(self, art_spiking_sim): + sim = art_spiking_sim sim.record(A.spike_recording.all) handle = sim.sample((3, 0), A.regular_schedule(0.1)) @@ -133,16 +64,3 @@ class Clear_samplers(unittest.TestCase): self.assertEqual(times_t, times) self.assertEqual(list(data[:, 0]), list(data_t[:, 0])) self.assertEqual(list(data[:, 1]), list(data_t[:, 1])) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(Clear_samplers, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_contexts.py b/python/test/unit/test_contexts.py index a445bb15..d5728d82 100644 --- a/python/test/unit/test_contexts.py +++ b/python/test/unit/test_contexts.py @@ -5,21 +5,13 @@ import unittest import arbor as arb - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ all tests for non-distributed arb.context """ -class Contexts(unittest.TestCase): +class TestContexts(unittest.TestCase): def test_default_allocation(self): alloc = arb.proc_allocation() @@ -86,16 +78,3 @@ class Contexts(unittest.TestCase): self.assertEqual(ctx.has_gpu, alloc.has_gpu) self.assertEqual(ctx.ranks, 1) self.assertEqual(ctx.rank, 0) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(Contexts, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_decor.py b/python/test/unit/test_decor.py index 2ec68d75..cb835673 100644 --- a/python/test/unit/test_decor.py +++ b/python/test/unit/test_decor.py @@ -3,21 +3,14 @@ import unittest import arbor as A -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ Tests for decor and decoration wrappers. TODO: Coverage for more than just iclamp. """ -class DecorClasses(unittest.TestCase): +class TestDecorClasses(unittest.TestCase): def test_iclamp(self): # Constant amplitude iclamp: clamp = A.iclamp(10); @@ -46,16 +39,3 @@ class DecorClasses(unittest.TestCase): clamp = A.iclamp(envelope, frequency=7); self.assertEqual(7, clamp.frequency) self.assertEqual(envelope, clamp.envelope) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(DecorClasses, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_domain_decompositions.py b/python/test/unit/test_domain_decompositions.py index eae6b2d4..9dd86ae8 100644 --- a/python/test/unit/test_domain_decompositions.py +++ b/python/test/unit/test_domain_decompositions.py @@ -5,15 +5,7 @@ import unittest import arbor as arb - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures # check Arbor's configuration of mpi and gpu gpu_enabled = arb.__config__["gpu"] @@ -56,7 +48,7 @@ class hetero_recipe (arb.recipe): else: return arb.cell_kind.cable -class Domain_Decompositions(unittest.TestCase): +class TestDomain_Decompositions(unittest.TestCase): # 1 cpu core, no gpus; assumes all cells will be put into cell groups of size 1 def test_domain_decomposition_homogenous_CPU(self): n_cells = 10 @@ -242,16 +234,3 @@ class Domain_Decompositions(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "unable to perform load balancing because cell_kind::spike_source has invalid suggested gpu_cell_group size of 0"): decomp = arb.partition_load_balance(recipe, context, hints) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(Domain_Decompositions, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_event_generators.py b/python/test/unit/test_event_generators.py index c45e591f..2ea56490 100644 --- a/python/test/unit/test_event_generators.py +++ b/python/test/unit/test_event_generators.py @@ -5,21 +5,13 @@ import unittest import arbor as arb - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ all tests for event generators (regular, explicit, poisson) """ -class EventGenerator(unittest.TestCase): +class TestEventGenerator(unittest.TestCase): def test_event_generator_regular_schedule(self): cm = arb.cell_local_label("tgt0") @@ -43,16 +35,3 @@ class EventGenerator(unittest.TestCase): self.assertEqual(pg.target.label, "tgt2") self.assertEqual(pg.target.policy, arb.selection_policy.univalent) self.assertEqual(pg.weight, 42.) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class EventGenerator - suite = unittest.makeSuite(EventGenerator, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_identifiers.py b/python/test/unit/test_identifiers.py index ba425be2..61469cb3 100644 --- a/python/test/unit/test_identifiers.py +++ b/python/test/unit/test_identifiers.py @@ -5,21 +5,13 @@ import unittest import arbor as arb - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ all tests for identifiers, indexes, kinds """ -class CellMembers(unittest.TestCase): +class TestCellMembers(unittest.TestCase): def test_gid_index_ctor_cell_member(self): cm = arb.cell_member(17,42) @@ -32,16 +24,3 @@ class CellMembers(unittest.TestCase): cm.index = 23 self.assertEqual(cm.gid, 13) self.assertEqual(cm.index, 23) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(CellMembers, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_morphology.py b/python/test/unit/test_morphology.py index bbc37da9..4c271f2d 100644 --- a/python/test/unit/test_morphology.py +++ b/python/test/unit/test_morphology.py @@ -6,15 +6,7 @@ import unittest import arbor as A import numpy as N import math - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ tests for morphology-related classes @@ -24,7 +16,7 @@ def as_matrix(iso): trans = N.array(iso((0, 0, 0))) return N.c_[N.array([iso(v) for v in [(1,0,0),(0,1,0),(0,0,1)]]).transpose()-N.c_[trans, trans, trans], trans] -class PlacePwlin(unittest.TestCase): +class TestPlacePwlin(unittest.TestCase): def test_identity(self): self.assertTrue(N.isclose(as_matrix(A.isometry()), N.eye(3, 4)).all()) @@ -130,16 +122,3 @@ class PlacePwlin(unittest.TestCase): Chalf_all = [(s.prox, s.dist) for s in place.all_segments([A.cable(0, 0., 0.5)])] self.assertEqual([(x0p, x0d), (x1p, x1p)], Chalf_all) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(PlacePwlin, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_profiling.py b/python/test/unit/test_profiling.py index bd0a99b5..41f406c0 100644 --- a/python/test/unit/test_profiling.py +++ b/python/test/unit/test_profiling.py @@ -6,15 +6,7 @@ import unittest import arbor as arb import functools - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ all tests for profiling @@ -91,17 +83,3 @@ class TestProfiling(unittest.TestCase): summary = arb.profiler_summary() self.assertEqual(str, type(summary), 'profiler summary must be str') self.assertTrue(summary, 'empty summary') - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from classes RegularSchedule, ExplicitSchedule and PoissonSchedule - suite = unittest.TestSuite() - suite.addTests(unittest.makeSuite(TestProfiling, ('test'))) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit/test_schedules.py b/python/test/unit/test_schedules.py index 44eec19d..3742fe20 100644 --- a/python/test/unit/test_schedules.py +++ b/python/test/unit/test_schedules.py @@ -5,21 +5,13 @@ import unittest import arbor as arb - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ all tests for schedules (regular, explicit, poisson) """ -class RegularSchedule(unittest.TestCase): +class TestRegularSchedule(unittest.TestCase): def test_none_ctor_regular_schedule(self): rs = arb.regular_schedule(tstart=0, dt=0.1, tstop=None) self.assertEqual(rs.dt, 0.1) @@ -73,7 +65,7 @@ class RegularSchedule(unittest.TestCase): rs = arb.regular_schedule(0., 1., 10.) rs.events(0, -10) -class ExplicitSchedule(unittest.TestCase): +class TestExplicitSchedule(unittest.TestCase): def test_times_contor_explicit_schedule(self): es = arb.explicit_schedule([1, 2, 3, 4.5]) self.assertEqual(es.times, [1, 2, 3, 4.5]) @@ -108,7 +100,7 @@ class ExplicitSchedule(unittest.TestCase): rs = arb.regular_schedule(0.1) rs.events(1., -1.) -class PoissonSchedule(unittest.TestCase): +class TestPoissonSchedule(unittest.TestCase): def test_freq_poisson_schedule(self): ps = arb.poisson_schedule(42.) self.assertEqual(ps.freq, 42.) @@ -181,20 +173,4 @@ class PoissonSchedule(unittest.TestCase): def test_tstop_poisson_schedule(self): tstop = 50 events = arb.poisson_schedule(0., 1, 0, tstop).events(0, 100) - self.assertTrue(max(events) < tstop) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from classes RegularSchedule, ExplicitSchedule and PoissonSchedule - suite = unittest.TestSuite() - suite.addTests(unittest.makeSuite(RegularSchedule, ('test'))) - suite.addTests(unittest.makeSuite(ExplicitSchedule, ('test'))) - suite.addTests(unittest.makeSuite(PoissonSchedule, ('test'))) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() + self.assertTrue(max(events) < tstop) \ No newline at end of file diff --git a/python/test/unit/test_spikes.py b/python/test/unit/test_spikes.py index 8211f5de..d7b32d34 100644 --- a/python/test/unit/test_spikes.py +++ b/python/test/unit/test_spikes.py @@ -4,63 +4,17 @@ import unittest import arbor as A - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures """ all tests for the simulator wrapper """ -# Test recipe art_spiker_recipe comprises three artificial spiking cells - -class art_spiker_recipe(A.recipe): - def __init__(self): - A.recipe.__init__(self) - self.props = A.neuron_cable_properties() - self.trains = [ - [0.8, 2, 2.1, 3], - [0.4, 2, 2.2, 3.1, 4.5], - [0.2, 2, 2.8, 3]] - - def num_cells(self): - return 3 - - def cell_kind(self, gid): - return A.cell_kind.spike_source - - def connections_on(self, gid): - return [] - - def event_generators(self, gid): - return [] - - def global_properties(self, kind): - return self.the_props - - def probes(self, gid): - return [] - - def cell_description(self, gid): - return A.spike_source_cell("src", A.explicit_schedule(self.trains[gid])) - - -class Spikes(unittest.TestCase): - # Helper for constructing a simulation from a recipe using default context and domain decomposition. - def init_sim(self, recipe): - context = A.context() - dd = A.partition_load_balance(recipe, context) - return A.simulation(recipe, dd, context) - +class TestSpikes(unittest.TestCase): # test that all spikes are sorted by time then by gid - def test_spikes_sorted(self): - sim = self.init_sim(art_spiker_recipe()) + @fixtures.art_spiking_sim + def test_spikes_sorted(self, art_spiking_sim): + sim = art_spiking_sim sim.record(A.spike_recording.all) # run simulation in 5 steps, forcing 5 epochs sim.run(1, 0.01) @@ -75,16 +29,3 @@ class Spikes(unittest.TestCase): self.assertEqual([2, 1, 0, 0, 1, 2, 0, 1, 2, 0, 2, 1, 1], gids) self.assertEqual([0.2, 0.4, 0.8, 2., 2., 2., 2.1, 2.2, 2.8, 3., 3., 3.1, 4.5], times) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(Spikes, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - runner = unittest.TextTestRunner(verbosity = v) - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit_distributed/__init__.py b/python/test/unit_distributed/__init__.py deleted file mode 100644 index e08cb8eb..00000000 --- a/python/test/unit_distributed/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -# -# __init__.py diff --git a/python/test/unit_distributed/runner.py b/python/test/unit_distributed/runner.py deleted file mode 100644 index 48c6c3a5..00000000 --- a/python/test/unit_distributed/runner.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# -# runner.py - -import unittest -import arbor as arb - -# check Arbor's configuration of mpi -mpi_enabled = arb.__config__["mpi"] -mpi4py_enabled = arb.__config__["mpi4py"] - -if (mpi_enabled and mpi4py_enabled): - import mpi4py.MPI as mpi - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options - import test_contexts_arbmpi - import test_contexts_mpi4py - import test_domain_decompositions - import test_simulator - # add more if needed -except ModuleNotFoundError: - from test import options - from test.unit_distributed import test_contexts_arbmpi - from test.unit_distributed import test_contexts_mpi4py - from test.unit_distributed import test_domain_decompositions - from test.unit_distributed import test_simulator - # add more if needed - -test_modules = [\ - test_contexts_arbmpi,\ - test_contexts_mpi4py,\ - test_domain_decompositions,\ - test_simulator\ -] # add more if needed - -def suite(): - loader = unittest.TestLoader() - - suites = [] - for test_module in test_modules: - test_module_suite = test_module.suite() - suites.append(test_module_suite) - - suite = unittest.TestSuite(suites) - - return suite - - -if __name__ == "__main__": - v = options.parse_arguments().verbosity - - if not arb.mpi_is_initialized(): - print(" Runner initializing mpi") - arb.mpi_init() - - if mpi4py_enabled: - comm = arb.mpi_comm(mpi.COMM_WORLD) - elif mpi_enabled: - comm = arb.mpi_comm() - - alloc = arb.proc_allocation() - ctx = arb.context(alloc, comm) - rank = ctx.rank - - if rank == 0: - runner = unittest.TextTestRunner(verbosity = v) - else: - sys.stdout = open(os.devnull, 'w') - runner = unittest.TextTestRunner(stream=sys.stdout) - - result = runner.run(suite()) - - if not arb.mpi_is_finalized(): - arb.mpi_finalize() - - sys.exit(not(result.wasSuccessful())) diff --git a/python/test/unit_distributed/test_contexts_arbmpi.py b/python/test/unit_distributed/test_contexts_arbmpi.py index 30328cc7..d7165a90 100644 --- a/python/test/unit_distributed/test_contexts_arbmpi.py +++ b/python/test/unit_distributed/test_contexts_arbmpi.py @@ -5,24 +5,13 @@ import unittest import arbor as arb - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options - -# check Arbor's configuration of mpi -mpi_enabled = arb.__config__["mpi"] +from .. import fixtures, cases """ all tests for distributed arb.context using arbor mpi wrappers """ -@unittest.skipIf(mpi_enabled == False, "MPI not enabled") -class Contexts_arbmpi(unittest.TestCase): +@cases.skipIfNotDistributed() +class TestContexts_arbmpi(unittest.TestCase): # Initialize mpi only once in this class (when adding classes move initialization to setUpModule() @classmethod def setUpClass(self): @@ -74,33 +63,3 @@ class Contexts_arbmpi(unittest.TestCase): def test_finalized_arbmpi(self): self.assertFalse(arb.mpi_is_finalized()) - -def suite(): - # specify class and test functions as tuple (here: all tests starting with 'test' from class Contexts_arbmpi - suite = unittest.makeSuite(Contexts_arbmpi, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - - if not arb.mpi_is_initialized(): - arb.mpi_init() - - comm = arb.mpi_comm() - alloc = arb.proc_allocation() - ctx = arb.context(alloc, comm) - rank = ctx.rank - - if rank == 0: - runner = unittest.TextTestRunner(verbosity = v) - else: - sys.stdout = open(os.devnull, 'w') - runner = unittest.TextTestRunner(stream=sys.stdout) - - runner.run(suite()) - - if not arb.mpi_is_finalized(): - arb.mpi_finalize() - -if __name__ == "__main__": - run() diff --git a/python/test/unit_distributed/test_contexts_mpi4py.py b/python/test/unit_distributed/test_contexts_mpi4py.py index 82b3c345..a22d065c 100644 --- a/python/test/unit_distributed/test_contexts_mpi4py.py +++ b/python/test/unit_distributed/test_contexts_mpi4py.py @@ -5,15 +5,7 @@ import unittest import arbor as arb - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures, cases # check Arbor's configuration of mpi mpi_enabled = arb.__config__["mpi"] @@ -26,8 +18,8 @@ if (mpi_enabled and mpi4py_enabled): all tests for distributed arb.context using mpi4py """ # Only test class if env var ARB_WITH_MPI4PY=ON -@unittest.skipIf(mpi_enabled == False or mpi4py_enabled == False, "MPI/mpi4py not enabled") -class Contexts_mpi4py(unittest.TestCase): +@cases.skipIfNotDistributed() +class TestContexts_mpi4py(unittest.TestCase): def test_initialized_mpi4py(self): # test mpi initialization (automatically when including mpi4py: https://mpi4py.readthedocs.io/en/stable/mpi4py.run.html) self.assertTrue(mpi.Is_initialized()) @@ -68,27 +60,3 @@ class Contexts_mpi4py(unittest.TestCase): def test_finalized_mpi4py(self): # test mpi finalization (automatically when including mpi4py, but only just before the Python process terminates) self.assertFalse(mpi.Is_finalized()) - -def suite(): - # specify class and test functions as tuple (here: all tests starting with 'test' from class Contexts_mpi4py - suite = unittest.makeSuite(Contexts_mpi4py, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - - comm = arb.mpi_comm(mpi.COMM_WORLD) - alloc = arb.proc_allocation() - ctx = arb.context(alloc, comm) - rank = ctx.rank - - if rank == 0: - runner = unittest.TextTestRunner(verbosity = v) - else: - sys.stdout = open(os.devnull, 'w') - runner = unittest.TextTestRunner(stream=sys.stdout) - - runner.run(suite()) - -if __name__ == "__main__": - run() diff --git a/python/test/unit_distributed/test_domain_decompositions.py b/python/test/unit_distributed/test_domain_decompositions.py index 439fd425..3c125c75 100644 --- a/python/test/unit_distributed/test_domain_decompositions.py +++ b/python/test/unit_distributed/test_domain_decompositions.py @@ -5,15 +5,7 @@ import unittest import arbor as arb - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures, cases # check Arbor's configuration of mpi and gpu mpi_enabled = arb.__config__["mpi"] @@ -145,8 +137,8 @@ class gj_non_symmetric (arb.recipe): else: return [] -@unittest.skipIf(mpi_enabled == False, "MPI not enabled") -class Domain_Decompositions_Distributed(unittest.TestCase): +@cases.skipIfNotDistributed() +class TestDomain_Decompositions_Distributed(unittest.TestCase): # Initialize mpi only once in this class (when adding classes move initialization to setUpModule() @classmethod def setUpClass(self): @@ -432,34 +424,3 @@ class Domain_Decompositions_Distributed(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "unable to perform load balancing because cell_kind::cable has invalid suggested gpu_cell_group size of 0"): decomp2 = arb.partition_load_balance(recipe, context, hints2) - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(Domain_Decompositions_Distributed, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - - if not arb.mpi_is_initialized(): - arb.mpi_init() - - comm = arb.mpi_comm() - - alloc = arb.proc_allocation() - ctx = arb.context(alloc, comm) - rank = ctx.rank - - if rank == 0: - runner = unittest.TextTestRunner(verbosity = v) - else: - sys.stdout = open(os.devnull, 'w') - runner = unittest.TextTestRunner(stream=sys.stdout) - - runner.run(suite()) - - if not arb.mpi_is_finalized(): - arb.mpi_finalize() - -if __name__ == "__main__": - run() diff --git a/python/test/unit_distributed/test_simulator.py b/python/test/unit_distributed/test_simulator.py index 0dcf1374..249d5149 100644 --- a/python/test/unit_distributed/test_simulator.py +++ b/python/test/unit_distributed/test_simulator.py @@ -5,15 +5,7 @@ import unittest import numpy as np import arbor as A - -# to be able to run .py file from child directory -import sys, os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) - -try: - import options -except ModuleNotFoundError: - from test import options +from .. import fixtures, cases mpi_enabled = A.__config__["mpi"] @@ -56,8 +48,8 @@ class lifN_recipe(A.recipe): c.t_ref = 4 return c -@unittest.skipIf(mpi_enabled == False, "MPI not enabled") -class Simulator(unittest.TestCase): +@cases.skipIfNotDistributed() +class TestSimulator(unittest.TestCase): def init_sim(self): comm = A.mpi_comm() context = A.context(threads=1, gpu_id=None, mpi=A.mpi_comm()) @@ -98,34 +90,3 @@ class Simulator(unittest.TestCase): expected = [((s, 0), t) for s in range(0, self.ranks) for t in ([0, 2, 4, 6, 8] if s%2==0 else [0, 4, 8])] self.assertEqual(expected, sorted(spikes)) - - -def suite(): - # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts - suite = unittest.makeSuite(Simulator, ('test')) - return suite - -def run(): - v = options.parse_arguments().verbosity - - if not A.mpi_is_initialized(): - A.mpi_init() - - comm = A.mpi_comm() - alloc = A.proc_allocation() - ctx = A.context(alloc, comm) - rank = ctx.rank - - if rank == 0: - runner = unittest.TextTestRunner(verbosity = v) - else: - sys.stdout = open(os.devnull, 'w') - runner = unittest.TextTestRunner(stream=sys.stdout) - - runner.run(suite()) - - if not A.mpi_is_finalized(): - A.mpi_finalize() - -if __name__ == "__main__": - run() -- GitLab