Skip to content

Commit e2ff29b

Browse files
authored
Merge pull request #697 from effigies/ci/test_python_versions
CI: Install package across Python versions and run pytest
2 parents 03d6328 + 32b694e commit e2ff29b

File tree

15 files changed

+214
-80
lines changed

15 files changed

+214
-80
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 116 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,85 +14,148 @@ jobs:
1414
build:
1515
if: "!contains(github.event.head_commit.message, '[skip ci]')"
1616
runs-on: ubuntu-latest
17+
strategy:
18+
matrix:
19+
python-version: ["3.10"]
20+
steps:
21+
- uses: actions/checkout@v3
22+
with:
23+
fetch-depth: 0
24+
- name: Set up Python ${{ matrix.python-version }}
25+
uses: actions/setup-python@v3
26+
with:
27+
python-version: ${{ matrix.python-version }}
28+
- name: Display Python version
29+
run: python -c "import sys; print(sys.version)"
30+
- name: Check python version and install build
31+
run: |
32+
python --version
33+
python -m pip install -U build twine
34+
- name: Build niworkflows
35+
run: python -m build
36+
- name: Check distributions
37+
run: twine check dist/*
38+
- uses: actions/upload-artifact@v3
39+
with:
40+
name: dist
41+
path: dist/
42+
43+
get_data:
44+
runs-on: ubuntu-latest
45+
steps:
46+
- name: Create test data directory
47+
run: mkdir -p $HOME/.cache/stanford-crn
48+
- name: Load test data cache
49+
uses: actions/cache@v2
50+
id: stanford-crn
51+
with:
52+
path: ~/.cache/stanford-crn/
53+
key: data-v0-${{ github.ref_name }}-${{ github.sha }}
54+
restore-keys: |
55+
data-v0-${{ github.ref_name }}-
56+
data-v0-
57+
- name: Install datalad
58+
run: |
59+
sudo apt-get update -y
60+
sudo apt-get install -y git-annex --no-install-recommends
61+
python -m pip install datalad==0.14.7 datalad-osf
62+
datalad wtf
63+
- name: Fetch test data
64+
run: |
65+
DS=$HOME/.cache/stanford-crn
66+
datalad install -r -s https://github.com/nipreps-data/niworkflows-data.git $DS
67+
cd $DS
68+
git -C BIDS-examples-1-enh-ds054 checkout enh/ds054
69+
datalad update -r --merge -d .
70+
datalad get -J 2 -r ds000003 ds000030/sub-10228/func
71+
72+
test:
73+
needs: [build, get_data]
74+
runs-on: ubuntu-latest
1775
strategy:
1876
matrix:
1977
python-version: [3.7, 3.8, 3.9]
78+
install: [repo]
79+
include:
80+
- python-version: 3.9
81+
install: sdist
82+
- python-version: 3.9
83+
install: wheel
84+
- python-version: 3.9
85+
install: editable
86+
env:
87+
INSTALL_TYPE: ${{ matrix.install }}
2088

2189
steps:
90+
- uses: actions/checkout@v3
91+
with:
92+
fetch-depth: 0
2293
- name: Set up Python ${{ matrix.python-version }}
23-
uses: actions/setup-python@v2
94+
uses: actions/setup-python@v3
2495
with:
2596
python-version: ${{ matrix.python-version }}
26-
- uses: actions/checkout@v2
97+
- name: Load test data cache
98+
uses: actions/cache@v2
99+
id: stanford-crn
27100
with:
28-
ssh-key: "${{ secrets.NIPREPS_DEPLOY }}"
29-
fetch-depth: 0
30-
- name: Build in confined, updated environment and interpolate version
101+
path: ~/.cache/stanford-crn/
102+
key: data-v0-${{ github.ref_name }}-${{ github.sha }}
103+
- name: Load TemplateFlow cache
104+
uses: actions/cache@v2
105+
id: templateflow
106+
with:
107+
path: ~/.cache/templateflow
108+
key: templateflow-v0-${{ github.ref_name }}-${{ strategy.job-index }}-${{ github.sha }}
109+
restore-keys: |
110+
templateflow-v0-${{ github.ref_name }}-
111+
templateflow-v0-
112+
- name: Fetch packages
113+
uses: actions/download-artifact@v3
114+
with:
115+
name: dist
116+
path: dist/
117+
- name: Select archive
118+
run: |
119+
if [ "$INSTALL_TYPE" = "sdist" ]; then
120+
ARCHIVE=$( ls dist/*.tar.gz )
121+
elif [ "$INSTALL_TYPE" = "wheel" ]; then
122+
ARCHIVE=$( ls dist/*.whl )
123+
elif [ "$INSTALL_TYPE" = "repo" ]; then
124+
ARCHIVE="."
125+
elif [ "$INSTALL_TYPE" = "editable" ]; then
126+
ARCHIVE="-e ."
127+
fi
128+
echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV
129+
- name: Install package
130+
run: python -m pip install $ARCHIVE
131+
- name: Check version
31132
run: |
32-
python -m venv /tmp/buildenv
33-
source /tmp/buildenv/bin/activate
34-
python -m pip install -U setuptools pip wheel twine docutils
35-
python setup.py sdist bdist_wheel
36-
python -m twine check dist/niworkflows*
37-
38133
# Interpolate version
39134
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
40135
TAG=${GITHUB_REF##*/}
41136
fi
42137
THISVERSION=$( python get_version.py )
43138
THISVERSION=${TAG:-$THISVERSION}
44-
echo "Expected VERSION: \"${THISVERSION}\""
45-
echo "THISVERSION=${THISVERSION}" >> ${GITHUB_ENV}
46-
47-
- name: Install in confined environment [sdist]
48-
run: |
49-
python -m venv /tmp/install_sdist
50-
source /tmp/install_sdist/bin/activate
51-
python -m pip install --upgrade pip wheel
52-
python -m pip install dist/niworkflows*.tar.gz
53139
INSTALLED_VERSION=$(python -c 'import niworkflows; print(niworkflows.__version__, end="")')
54140
echo "VERSION: \"${THISVERSION}\""
55141
echo "INSTALLED: \"${INSTALLED_VERSION}\""
56142
test "${INSTALLED_VERSION}" = "${THISVERSION}"
57-
58-
- name: Install in confined environment [wheel]
59-
run: |
60-
python -m venv /tmp/install_wheel
61-
source /tmp/install_wheel/bin/activate
62-
python -m pip install --upgrade pip wheel
63-
python -m pip install dist/niworkflows*.whl
64-
INSTALLED_VERSION=$(python -c 'import niworkflows; print(niworkflows.__version__, end="")')
65-
echo "INSTALLED: \"${INSTALLED_VERSION}\""
66-
test "${INSTALLED_VERSION}" = "${THISVERSION}"
67-
68-
- name: Install in confined environment [pip install .]
69-
run: |
70-
python -m venv /tmp/setup_install
71-
source /tmp/setup_install/bin/activate
72-
python -m pip install --upgrade pip wheel
73-
python -m pip install .
74-
INSTALLED_VERSION=$(python -c 'import niworkflows; print(niworkflows.__version__, end="")')
75-
echo "INSTALLED: \"${INSTALLED_VERSION}\""
76-
test "${INSTALLED_VERSION}" = "${THISVERSION}"
77-
78-
- name: Install in confined environment [pip install -e .]
79-
run: |
80-
python -m venv /tmp/setup_develop
81-
source /tmp/setup_develop/bin/activate
82-
python -m pip install pip
83-
python -m pip install --upgrade pip wheel
84-
python -m pip install -e .
85-
INSTALLED_VERSION=$(python -c 'import niworkflows; print(niworkflows.__version__, end="")')
86-
echo "INSTALLED: \"${INSTALLED_VERSION}\""
87-
test "${INSTALLED_VERSION}" = "${THISVERSION}"
143+
- name: Install test dependencies
144+
run: python -m pip install "niworkflows[tests]"
145+
- name: Run tests
146+
uses: GabrielBB/xvfb-action@v1
147+
with:
148+
run: pytest -sv --no-xvfb --doctest-modules --cov niworkflows niworkflows
149+
- uses: codecov/codecov-action@v2
150+
name: Submit to CodeCov
88151

89152
flake8:
90153
if: "!contains(github.event.head_commit.message, '[skip ci]')"
91154
runs-on: ubuntu-latest
92155
steps:
93-
- uses: actions/checkout@v2
156+
- uses: actions/checkout@v3
94157
- name: Set up Python 3.7
95-
uses: actions/setup-python@v1
158+
uses: actions/setup-python@v3
96159
with:
97160
python-version: 3.7
98161
- run: pip install flake8

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@
225225

226226
apidoc_module_dir = "../niworkflows"
227227
apidoc_output_dir = "api"
228-
apidoc_excluded_paths = ["conftest.py", "*/tests/*", "tests/*", "data/*"]
228+
apidoc_excluded_paths = ["conftest.py", "*/tests/*", "tests/*", "data/*", "testing.py"]
229229
apidoc_separate_modules = True
230230
apidoc_extra_args = ["--module-first", "-d 1", "-T"]
231231

niworkflows/conftest.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,10 @@
3232
import pkg_resources
3333

3434
from .utils.bids import collect_data
35-
36-
test_data_env = os.getenv(
37-
"TEST_DATA_HOME", str(Path.home() / ".cache" / "stanford-crn")
35+
from .testing import (
36+
test_data_env, test_output_dir, test_workdir, data_dir,
37+
data_env_canary, data_dir_canary
3838
)
39-
test_output_dir = os.getenv("TEST_OUTPUT_DIR")
40-
test_workdir = os.getenv("TEST_WORK_DIR")
41-
42-
data_dir = Path(test_data_env) / "BIDS-examples-1-enh-ds054"
4339

4440

4541
@pytest.fixture(autouse=True)
@@ -52,6 +48,7 @@ def add_np(doctest_namespace):
5248
doctest_namespace["pytest"] = pytest
5349
doctest_namespace["Path"] = Path
5450
doctest_namespace["datadir"] = data_dir
51+
doctest_namespace["data_dir_canary"] = data_dir_canary
5552
doctest_namespace["bids_collect_data"] = collect_data
5653
doctest_namespace["test_data"] = pkg_resources.resource_filename(
5754
"niworkflows", "tests/data"
@@ -82,6 +79,7 @@ def testdata_dir():
8279

8380
@pytest.fixture
8481
def ds000030_dir():
82+
data_env_canary()
8583
return Path(test_data_env) / "ds000030"
8684

8785

niworkflows/interfaces/bids.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ class BIDSInfo(SimpleInterface):
105105
This interface uses only the basename, not the path, to determine the
106106
subject, session, task, run, acquisition or reconstruction.
107107
108+
.. testsetup::
109+
110+
>>> data_dir_canary()
111+
108112
>>> bids_info = BIDSInfo(bids_dir=str(datadir / 'ds054'), bids_validate=False)
109113
>>> bids_info.inputs.in_file = '''\
110114
sub-01/func/ses-retest/sub-01_ses-retest_task-covertverbgeneration_bold.nii.gz'''
@@ -224,6 +228,10 @@ class BIDSDataGrabber(SimpleInterface):
224228
"""
225229
Collect files from a BIDS directory structure.
226230
231+
.. testsetup::
232+
233+
>>> data_dir_canary()
234+
227235
>>> bids_src = BIDSDataGrabber(anat_only=False)
228236
>>> bids_src.inputs.subject_data = bids_collect_data(
229237
... str(datadir / 'ds114'), '01', bids_validate=False)[0]
@@ -320,6 +328,10 @@ class DerivativesDataSink(SimpleInterface):
320328
Saves the ``in_file`` into a BIDS-Derivatives folder provided
321329
by ``base_directory``, given the input reference ``source_file``.
322330
331+
.. testsetup::
332+
333+
>>> data_dir_canary()
334+
323335
>>> import tempfile
324336
>>> tmpdir = Path(tempfile.mkdtemp())
325337
>>> tmpfile = tmpdir / 'a_temp_file.nii.gz'
@@ -732,6 +744,10 @@ class ReadSidecarJSON(SimpleInterface):
732744
"""
733745
Read JSON sidecar files of a BIDS tree.
734746
747+
.. testsetup::
748+
749+
>>> data_dir_canary()
750+
735751
>>> fmap = str(datadir / 'ds054' / 'sub-100185' / 'fmap' /
736752
... 'sub-100185_phasediff.nii.gz')
737753

niworkflows/interfaces/freesurfer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class StructuralReference(fs.RobustTemplate):
4848
"""
4949
Shortcut RobustTemplate with a copy of the source if a single volume is provided.
5050
51+
.. testsetup::
52+
53+
>>> data_dir_canary()
54+
5155
Examples
5256
--------
5357
>>> t1w = bids_collect_data(

niworkflows/interfaces/tests/test_bids.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from nipype.interfaces.base import Undefined
3333

3434
from .. import bids as bintfs
35+
from niworkflows.testing import needs_data_dir
3536

3637

3738
XFORM_CODES = {
@@ -608,6 +609,7 @@ def test_DerivativesDataSink_values(tmp_path, dtype):
608609
assert sha1(out_file.read_bytes()).hexdigest() == checksum
609610

610611

612+
@needs_data_dir
611613
@pytest.mark.parametrize("field", ["RepetitionTime", "UndefinedField"])
612614
def test_ReadSidecarJSON_connection(testdata_dir, field):
613615
"""

niworkflows/interfaces/tests/test_images.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,13 @@
2929
import pytest
3030

3131
from .. import images as im
32+
from niworkflows.testing import has_afni
3233

3334

3435
@pytest.mark.parametrize(
3536
"nvols, nmasks, ext, factor",
3637
[
37-
(500, 10, ".nii", 2),
38-
(500, 10, ".nii.gz", 5),
3938
(200, 3, ".nii", 1.1),
40-
(200, 3, ".nii.gz", 2),
41-
(200, 10, ".nii", 1.1),
42-
(200, 10, ".nii.gz", 2),
4339
],
4440
)
4541
def test_signal_extraction_equivalence(tmp_path, nvols, nmasks, ext, factor):
@@ -158,6 +154,7 @@ def test_conform_set_zooms(tmpdir):
158154
assert np.allclose(out_img.header.get_zooms(), conform.inputs.target_zooms)
159155

160156

157+
@pytest.mark.skipif(not has_afni, reason="Needs AFNI")
161158
@pytest.mark.parametrize("shape", [
162159
(10, 10, 10),
163160
(10, 10, 10, 1),

niworkflows/testing.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
from functools import wraps
3+
import os
4+
from pathlib import Path
5+
from nipype.interfaces import fsl, freesurfer as fs, afni
6+
7+
test_data_env = os.getenv(
8+
"TEST_DATA_HOME", str(Path.home() / ".cache" / "stanford-crn")
9+
)
10+
test_output_dir = os.getenv("TEST_OUTPUT_DIR")
11+
test_workdir = os.getenv("TEST_WORK_DIR")
12+
13+
data_dir = Path(test_data_env) / "BIDS-examples-1-enh-ds054"
14+
15+
16+
def create_canary(predicate, message):
17+
def canary():
18+
if predicate:
19+
pytest.skip(message)
20+
21+
def decorator(f):
22+
@wraps(f)
23+
def wrapper(*args, **kwargs):
24+
canary()
25+
return f(*args, **kwargs)
26+
return wrapper
27+
28+
return canary, decorator
29+
30+
31+
data_env_canary, needs_data_env = create_canary(
32+
not os.path.isdir(test_data_env),
33+
"Test data must be made available in ~/.cache/stanford-crn or in a "
34+
"directory referenced by the TEST_DATA_HOME environment variable.")
35+
36+
data_dir_canary, needs_data_dir = create_canary(
37+
not os.path.isdir(data_dir),
38+
"Test data must be made available in ~/.cache/stanford-crn or in a "
39+
"directory referenced by the TEST_DATA_HOME environment variable.")
40+
41+
has_fsl = fsl.Info.version() is not None
42+
has_freesurfer = fs.Info.version() is not None
43+
has_afni = afni.Info.version() is not None

0 commit comments

Comments
 (0)