Skip to content

Commit

Permalink
migrate test suite from nose to pytest
Browse files Browse the repository at this point in the history
  • Loading branch information
apdavison committed Oct 14, 2022
1 parent d76b4d0 commit d2d72ce
Show file tree
Hide file tree
Showing 45 changed files with 684 additions and 797 deletions.
9 changes: 3 additions & 6 deletions .github/workflows/full-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: Install basic Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install coverage coveralls nose-testconfig
python -m pip install coveralls pytest pytest-cov
python -m pip install mpi4py
python -m pip install -r requirements.txt
- name: Install Brian 2
Expand All @@ -54,9 +54,6 @@ jobs:
- name: Install PyNN itself
run: |
python setup.py install
- name: Run unit tests
- name: Run unit and system tests
run: |
nosetests --nologcapture --where=test/unittests --verbosity=2 test_assembly.py test_brian.py test_connectors_parallel.py test_connectors_serial.py test_core.py test_descriptions.py test_files.py test_idmixin.py test_lowlevelapi.py test_nest.py test_neuron.py test_parameters.py test_population.py test_populationview.py test_projection.py test_random.py test_recording.py test_simulation_control.py test_space.py test_standardmodels.py test_utility_functions.py
- name: Run system tests
run: |
nosetests --nologcapture --where=test/system --verbosity=2 test_nest.py test_brian2.py test_neuron.py
pytest -v --cov=pyNN test
4 changes: 2 additions & 2 deletions ci/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
set -e # stop execution in case of errors

pip install -r requirements.txt
pip install coverage coveralls
pip install nose-testconfig
pip install coveralls
pip install pytest pytest-cov
source ci/install_brian.sh
source ci/install_nest.sh
source ci/install_neuron.sh
Expand Down
4 changes: 2 additions & 2 deletions ci/test_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
set -e # stop execution in case of errors

if [ "$TRAVIS_PYTHON_VERSION" == "3.9" ]; then
python setup.py nosetests --verbose --nologcapture --with-coverage --cover-package=pyNN --tests=test;
pytest --verbose --cov=pyNN
else
python setup.py nosetests --verbose --nologcapture -e backends --tests=test/unittests
pytest --verbose test/unittests
fi
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class MockNESTModule(mock.Mock):
# The short X.Y version.
version = '0.10'
# The full version, including alpha/beta/rc tags.
release = '0.10.1'
release = '0.10.2.dev'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
43 changes: 16 additions & 27 deletions doc/developers/contributing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Requirements
In addition to the requirements listed in :doc:`../installation`, you will need to
install:

* nose_
* pytest_
* mock_
* coverage_

Expand Down Expand Up @@ -86,15 +86,14 @@ We try to stay fairly close to PEP8_. Please note in particular:
Testing
=======

Running the PyNN test suite requires the *nose_*, *mock_* and *nose-testconfig* packages,
and optionally the *coverage_* package. To run the entire test suite, in the
``test`` subdirectory of the source tree::
Running the PyNN test suite requires the *pytest_* package,
and optionally the *pytest-cov* package. To run the entire test suite::

$ nosetests
$ pytest

To see how well the codebase is covered by the tests, run::

$ nosetests --with-coverage --cover-package=pyNN --cover-erase --cover-html
$ pytest --cov=pyNN --cov-report term --cov-report html

There are currently two sorts of tests, unit tests, which aim to exercise
small pieces of code such as individual functions and methods, and system tests,
Expand All @@ -110,31 +109,21 @@ Except when testing a specific simulator interface, unit tests should be able to
run without a simulator installed.

System tests should be written so that they can run with any of the simulators.
The suggested way to do this is to write test functions, in a separate file,
that take a simulator module as an argument, and then call these functions from
``test_neuron.py``, ``test_nest.py``, etc.
The suggested way to do this is to write test functions
that take a simulator module as an argument, and then wrap these functions
with the following decorator::

System tests defined in the scenarios directory are treated as a single test
(test_scenarios()) while running nosetests. To run only the tests within a file
@pytest.mark.parametrize("sim", (pyNN.nest, pyNN.neuron, pyNN.brian2))

To run only the tests within a file
named 'test_electrodes' located inside system/scenarios, use::

$ nosetests -s --tc=testFile:test_electrodes test_nest.py
$ pytest test/system/scenarios/test_electrodes.py

To run a single specific test named 'test_changing_electrode' located within
some file (and added to registry) inside system/scenarios, use::

$ nosetests -s --tc=testName:test_changing_electrode test_nest.py

Note that this would also run the tests specified within the simulator specific
files such as test_brian.py, test_nest.py and test_neuron.py. To avoid
this, specify the 'test_scenarios function' on the command line::

$ nosetests -s --tc=testName:test_changing_electrode test_nest.py:test_scenarios

The ``test/unsorted`` directory contains a number of old tests that are either
no longer useful or have not yet been adapted to the nose framework. These are
not part of the test suite, but we are gradually adapting those tests that are
useful and deleting the others.
$ pytest test/system/scenarios/test_electrodes.py::test_changing_electrode


Submitting code
Expand Down Expand Up @@ -190,11 +179,11 @@ in becoming release manager for PyNN, please contact us via the `mailing list`_.
When you think a release is ready, run through the following checklist one
last time:

* do all the tests pass? This means running :command:`nosetests` in
* do all the tests pass? This means running :command:`pytest` in
:file:`test/unittests` and :file:`test/system` and running :command:`make doctest` in
:file:`doc`. You should do this on at least two Linux systems -- one a very
recent version and one at least a year old, and on at least one version of
Mac OS X. You should also do this with Python 2.7 and 3.4, 3.5 or 3.6.
Mac OS X. You should also do this with multiple Python versions (3.7+).
* do all the example scripts generate the correct output? Run the
:file:`run_all_examples.py` script in :file:`examples/tools` and then visually
check the :file:`.png` files generated in :file:`examples/tools/Results`. Again,
Expand Down Expand Up @@ -254,7 +243,7 @@ If this is a final release, there are a few more steps:

.. _Sphinx: http://sphinx-doc.org/
.. _PEP8: http://www.python.org/dev/peps/pep-0008/
.. _nose: https://nose.readthedocs.org/
.. _pytest: https://docs.pytest.org
.. _mock: http://www.voidspace.org.uk/python/mock/
.. _coverage: http://nedbatchelder.com/code/coverage/
.. _`Python Package Index`: http://pypi.python.org/
Expand Down
2 changes: 1 addition & 1 deletion pyNN/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
:license: CeCILL, see LICENSE for details.
"""

__version__ = '0.10.1'
__version__ = '0.10.2.dev'
__all__ = ["common", "random", "nest", "neuron", "brian2",
"recording", "errors", "space", "descriptions",
"standardmodels", "parameters", "core", "serialization"]
11 changes: 5 additions & 6 deletions pyNN/common/populations.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def sample(self, n, rng=None):
assert isinstance(n, int)
if not rng:
rng = random.NumpyRNG()
indices = rng.permutation(np.arange(len(self), dtype=np.int))[0:n]
indices = rng.permutation(np.arange(len(self), dtype=int))[0:n]
logger.debug("The %d cells selected have indices %s" % (n, indices))
logger.debug("%s.sample(%s)", self.label, n)
return self._get_view(indices)
Expand Down Expand Up @@ -658,7 +658,6 @@ def __init__(self, size, cellclass, cellparams=None, structure=None,
else:
raise Exception(
"A maximum of 3 dimensions is allowed. What do you think this is, string theory?")
# NEST doesn't like np.int, so to be safe we cast to Python int
size = int(reduce(operator.mul, size))
self.size = size
self.label = label or 'population%d' % Population._nPop
Expand Down Expand Up @@ -718,7 +717,7 @@ def id_to_index(self, id):
if (self.first_id > id.min()) or (self.last_id < id.max()):
raise ValueError("ids should be in the range [%d,%d], actually [%d, %d]" % (
self.first_id, self.last_id, id.min(), id.max()))
return (id - self.first_id).astype(np.int) # this assumes ids are consecutive
return (id - self.first_id).astype(int) # this assumes ids are consecutive

def id_to_local_index(self, id):
"""
Expand Down Expand Up @@ -906,7 +905,7 @@ def id_to_index(self, id):
if self._is_sorted:
return np.searchsorted(self.all_cells, id)
else:
result = np.array([], dtype=np.int)
result = np.array([], dtype=int)
for item in id:
data = np.where(self.all_cells == item)[0]
if len(data) == 0:
Expand Down Expand Up @@ -1159,7 +1158,7 @@ def id_to_index(self, id):
if self._is_sorted:
return np.searchsorted(all_cells, id)
else:
result = np.array([], dtype=np.int)
result = np.array([], dtype=int)
for item in id:
data = np.where(all_cells == item)[0]
if len(data) == 0:
Expand Down Expand Up @@ -1261,7 +1260,7 @@ def sample(self, n, rng=None):
assert isinstance(n, int)
if not rng:
rng = random.NumpyRNG()
indices = rng.permutation(np.arange(len(self), dtype=np.int))[0:n]
indices = rng.permutation(np.arange(len(self), dtype=int))[0:n]
logger.debug("The %d cells recorded have indices %s" % (n, indices))
logger.debug("%s.sample(%s)", self.label, n)
return self[indices]
Expand Down
3 changes: 2 additions & 1 deletion pyNN/connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from pyNN.recording import files
from pyNN.parameters import LazyArray
from pyNN.standardmodels import StandardSynapseType
from pyNN.common import Population
import numpy as np
from itertools import repeat
import logging
Expand Down Expand Up @@ -252,6 +251,7 @@ def _connect_with_map(self, projection, connection_map, distance_map=None):
self._standard_connect(projection, connection_map.by_column, distance_map)

def _get_connection_map_no_self_connections(self, projection):
from pyNN.common import Population
if (isinstance(projection.pre, Population)
and isinstance(projection.post, Population)
and projection.pre == projection.post):
Expand All @@ -267,6 +267,7 @@ def _get_connection_map_no_self_connections(self, projection):
return connection_map

def _get_connection_map_no_mutual_connections(self, projection):
from pyNN.common import Population
if (isinstance(projection.pre, Population)
and isinstance(projection.post, Population)
and projection.pre == projection.post):
Expand Down
2 changes: 1 addition & 1 deletion pyNN/nest/recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _get_data_arrays(self, variable, clear=False):
if variable == "times":
values = times
else:
# I'm hoping numpy optimises for the case where scale_factor = 1,
# I'm hoping numpy optimises for the case where scale_factor = 1,
# otherwise should avoid this multiplication in that case
values = events[nest_variable] * scale_factor

Expand Down
2 changes: 1 addition & 1 deletion pyNN/neuron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, **extra_params
simulator.state.min_delay = min_delay
simulator.state.max_delay = extra_params.get('max_delay', DEFAULT_MAX_DELAY)
if 'use_cvode' in extra_params:
simulator.state.record_sample_times = extra_params['use_cvode']
simulator.state.record_sample_times = extra_params['use_cvode']
simulator.state.cvode.active(int(extra_params['use_cvode']))
if 'rtol' in extra_params:
simulator.state.cvode.rtol(float(extra_params['rtol']))
Expand Down
5 changes: 1 addition & 4 deletions pyNN/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
"""

import numpy as np
try:
from collections import Sized
except ImportError:
from collections.abc import Sized
from collections.abc import Sized
from pyNN.core import is_listlike
from pyNN import errors
from pyNN.random import RandomDistribution, NativeRNG
Expand Down
4 changes: 2 additions & 2 deletions pyNN/recording/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,9 @@ def _get_current_segment(self, filter_ids=None, variables='all', clear=False):
times = times[mask]
id_array = id_array[mask]
segment.spiketrains = neo.spiketrainlist.SpikeTrainList.from_spike_time_array(
times, id_array,
times, id_array,
np.array(sids, dtype=int),
t_stop=t_stop,
t_stop=t_stop,
units="ms",
t_start=self._recording_start_time,
source_population=self.population.label
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ lazyarray>=0.5.2
neo>=0.11.0
#git+https://github.com/NeuralEnsemble/python-neo.git@master#egg=neo
setuptools>=20.5
nose
h5py
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def find(self, command):

setup(
name="PyNN",
version="0.10.1",
version="0.10.2.dev",
packages=['pyNN', 'pyNN.nest', 'pyNN.neuron',
'pyNN.brian2', 'pyNN.common', 'pyNN.mock', 'pyNN.neuroml',
'pyNN.recording', 'pyNN.standardmodels', 'pyNN.descriptions',
Expand Down
24 changes: 0 additions & 24 deletions test/system/scenarios/__init__.py
Original file line number Diff line number Diff line change
@@ -1,24 +0,0 @@
# encoding: utf-8
from testconfig import config


if 'testFile' in config:
file_name = config['testFile']
exec("from . import ( %s )" % file_name)
else:
from . import (scenario1,
scenario2,
scenario3,
ticket166,
test_simulation_control,
test_recording,
test_cell_types,
test_electrodes,
scenario4,
test_parameter_handling,
test_procedural_api,
issue274,
test_connectors,
issue231,
test_connection_handling,
test_synapse_types)
13 changes: 7 additions & 6 deletions test/system/scenarios/issue231.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
min_delay should be calculated automatically if not set
"""

from nose.tools import assert_equal
from .registry import register
import pyNN.nest
import pyNN.brian2
import pytest


# for NEURON, this only works when run with MPI and more than one process
@register(exclude="neuron")
def issue231(sim):
@pytest.mark.parametrize("sim", (pyNN.nest, pyNN.brian2))
def test_issue231(sim):
sim.setup(min_delay='auto')

p1 = sim.Population(13, sim.IF_cond_exp())
Expand All @@ -18,11 +19,11 @@ def issue231(sim):
synapse = sim.StaticSynapse(delay=0.5)
prj = sim.Projection(p1, p2, connector, synapse)
sim.run(100.0)
assert_equal(sim.get_min_delay(), 0.5)
assert sim.get_min_delay() == 0.5
sim.end()


if __name__ == '__main__':
from pyNN.utility import get_simulator
sim, args = get_simulator()
issue231(sim)
test_issue231(sim)
11 changes: 7 additions & 4 deletions test/system/scenarios/issue274.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from .registry import register

from pyNN.random import RandomDistribution as rnd
import pyNN.nest
import pyNN.neuron
import pyNN.brian2
import pytest


@register()
def issue274(sim):
@pytest.mark.parametrize("sim", (pyNN.nest, pyNN.neuron, pyNN.brian2))
def test_issue274(sim):
"""Issue with offset in GIDs"""
sim.setup(min_delay=0.5)

Expand All @@ -24,4 +27,4 @@ def issue274(sim):
if __name__ == '__main__':
from pyNN.utility import get_simulator
sim, args = get_simulator()
issue274(sim)
test_issue274(sim)
12 changes: 0 additions & 12 deletions test/system/scenarios/registry.py

This file was deleted.

0 comments on commit d2d72ce

Please sign in to comment.