Skip to content

Commit a427012

Browse files
committed
Devops: Add tests for the minimal install
A new job `tests-minimal-install` is added to the CI and CD workflows. The job installs the package with minimal extra dependencies (just the `tests` extras are installed so that `pytest` is available`. The job then invokes `pytest` with the `-m minimal_install` option. The `minimal_install` marker is added to run only those tests that should run with a minimal install. For now, the minimal install tests simply check that all modules are importable, except for the workflow plugin implementations, and that all CLI commands can be called with the `--help` option.
1 parent 905d92d commit a427012

16 files changed

+306
-220
lines changed

.github/workflows/cd.yml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,36 @@ jobs:
8383
- name: Run pytest
8484
run: pytest -sv tests
8585

86+
tests-minimal-install:
87+
88+
runs-on: ubuntu-latest
89+
timeout-minutes: 30
90+
91+
strategy:
92+
matrix:
93+
python-version: ['3.9']
94+
95+
steps:
96+
- uses: actions/checkout@v2
97+
98+
- name: Install Python ${{ matrix.python-version }}
99+
uses: actions/setup-python@v4
100+
with:
101+
python-version: ${{ matrix.python-version }}
102+
cache: pip
103+
cache-dependency-path: pyproject.toml
104+
105+
- name: Install Python package without any extras
106+
run: pip install -e .[tests]
107+
108+
- name: Run pytest
109+
run: pytest -sv tests -m minimal_install
110+
111+
86112
publish:
87113

88114
name: Publish to PyPI
89-
needs: [pre-commit, tests]
115+
needs: [pre-commit, tests, tests-minimal-install]
90116
runs-on: ubuntu-latest
91117

92118
steps:

.github/workflows/ci.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,28 @@ jobs:
6262
env:
6363
AIIDA_WARN_v3: true
6464
run: pytest -sv tests
65+
66+
tests-minimal-install:
67+
68+
runs-on: ubuntu-latest
69+
timeout-minutes: 30
70+
71+
strategy:
72+
matrix:
73+
python-version: ['3.9']
74+
75+
steps:
76+
- uses: actions/checkout@v2
77+
78+
- name: Install Python ${{ matrix.python-version }}
79+
uses: actions/setup-python@v4
80+
with:
81+
python-version: ${{ matrix.python-version }}
82+
cache: pip
83+
cache-dependency-path: pyproject.toml
84+
85+
- name: Install Python package without any extras
86+
run: pip install -e .[tests]
87+
88+
- name: Run pytest
89+
run: pytest -sv tests -m minimal_install

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ filterwarnings = [
164164
'ignore:Creating AiiDA configuration folder.*:UserWarning',
165165
'ignore:Object of type .* not in session, .* operation along .* will not proceed:sqlalchemy.exc.SAWarning'
166166
]
167+
markers = [
168+
'minimal_install: mark test as relevant for minimal install.'
169+
]
167170
testpaths = [
168171
'tests'
169172
]

tests/cli/test_root.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def recurse_commands(command: click.Command, parents: list[str] | None = None):
3232

3333
@pytest.mark.parametrize('command', recurse_commands(cmd_root))
3434
@pytest.mark.parametrize('help_option', ('--help', '-h'))
35+
@pytest.mark.minimal_install
3536
def test_commands_help_option(command, help_option):
3637
"""Test the help options for all subcommands of the CLI.
3738

tests/test_minimal_install.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Tests for a minimal install of the package without any extra dependencies.
2+
3+
The unit tests in this module should be run against a minimal install of the package without any extra dependencies
4+
installed. This guarantees that most of the code can be imported without any plugin packages being installed.
5+
"""
6+
import pytest
7+
8+
9+
@pytest.mark.minimal_install
10+
def test_imports():
11+
"""The following modules should be importable without any plugin packages installed."""
12+
import aiida_common_workflows.cli
13+
import aiida_common_workflows.common
14+
import aiida_common_workflows.generators
15+
import aiida_common_workflows.plugins
16+
import aiida_common_workflows.protocol
17+
import aiida_common_workflows.utils
18+
import aiida_common_workflows.workflows
19+
import aiida_common_workflows.workflows.dissociation
20+
import aiida_common_workflows.workflows.eos # noqa: F401

tests/workflows/relax/test_abinit.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"""Tests for the :mod:`aiida_common_workflows.workflows.relax.abinit` module."""
2-
32
import pytest
43
from aiida import engine, plugins
54

6-
WORKCHAIN = plugins.WorkflowFactory('common_workflows.relax.abinit')
7-
GENERATOR = WORKCHAIN.get_input_generator()
5+
6+
@pytest.fixture
7+
def generator():
8+
return plugins.WorkflowFactory('common_workflows.relax.abinit').get_input_generator()
89

910

1011
@pytest.fixture
@@ -22,52 +23,52 @@ def default_builder_inputs(generate_code, generate_structure):
2223

2324

2425
@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
25-
def test_get_builder(default_builder_inputs):
26+
def test_get_builder(generator, default_builder_inputs):
2627
"""Test the ``get_builder`` with default arguments."""
27-
builder = GENERATOR.get_builder(**default_builder_inputs)
28+
builder = generator.get_builder(**default_builder_inputs)
2829
assert isinstance(builder, engine.ProcessBuilder)
2930

3031

3132
@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
3233
@pytest.mark.skip('Running this test will fail with an `UnroutableError` in `kiwipy`.')
33-
def test_submit(default_builder_inputs):
34+
def test_submit(generator, default_builder_inputs):
3435
"""Test submitting the builder returned by ``get_builder`` called with default arguments.
3536
3637
This will actually create the ``WorkChain`` instance, so if it doesn't raise, that means the input spec was valid.
3738
"""
38-
builder = GENERATOR.get_builder(**default_builder_inputs)
39+
builder = generator.get_builder(**default_builder_inputs)
3940
engine.submit(builder)
4041

4142

4243
@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
43-
def test_supported_electronic_types(default_builder_inputs):
44+
def test_supported_electronic_types(generator, default_builder_inputs):
4445
"""Test calling ``get_builder`` for the supported ``electronic_types``."""
4546
inputs = default_builder_inputs
4647

47-
for electronic_type in GENERATOR.spec().inputs['electronic_type'].choices:
48+
for electronic_type in generator.spec().inputs['electronic_type'].choices:
4849
inputs['electronic_type'] = electronic_type
49-
builder = GENERATOR.get_builder(**inputs)
50+
builder = generator.get_builder(**inputs)
5051
assert isinstance(builder, engine.ProcessBuilder)
5152

5253

5354
@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
54-
def test_supported_relax_types(default_builder_inputs):
55+
def test_supported_relax_types(generator, default_builder_inputs):
5556
"""Test calling ``get_builder`` for the supported ``relax_types``."""
5657
inputs = default_builder_inputs
5758

58-
for relax_type in GENERATOR.spec().inputs['relax_type'].choices:
59+
for relax_type in generator.spec().inputs['relax_type'].choices:
5960
inputs['relax_type'] = relax_type
60-
builder = GENERATOR.get_builder(**inputs)
61+
builder = generator.get_builder(**inputs)
6162
assert isinstance(builder, engine.ProcessBuilder)
6263

6364

6465
@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
6566
@pytest.mark.filterwarnings('ignore: input magnetization per site was None')
66-
def test_supported_spin_types(default_builder_inputs):
67+
def test_supported_spin_types(generator, default_builder_inputs):
6768
"""Test calling ``get_builder`` for the supported ``spin_types``."""
6869
inputs = default_builder_inputs
6970

70-
for spin_type in GENERATOR.spec().inputs['spin_type'].choices:
71+
for spin_type in generator.spec().inputs['spin_type'].choices:
7172
inputs['spin_type'] = spin_type
72-
builder = GENERATOR.get_builder(**inputs)
73+
builder = generator.get_builder(**inputs)
7374
assert isinstance(builder, engine.ProcessBuilder)

tests/workflows/relax/test_bigdft.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"""Tests for the :mod:`aiida_common_workflows.workflows.relax.bigdft` module."""
2-
32
import pytest
43
from aiida import engine, plugins
54

6-
WORKCHAIN = plugins.WorkflowFactory('common_workflows.relax.bigdft')
7-
GENERATOR = WORKCHAIN.get_input_generator()
5+
6+
@pytest.fixture
7+
def generator():
8+
return plugins.WorkflowFactory('common_workflows.relax.bigdft').get_input_generator()
89

910

1011
@pytest.fixture
@@ -21,47 +22,47 @@ def default_builder_inputs(generate_code, generate_structure):
2122
}
2223

2324

24-
def test_get_builder(default_builder_inputs):
25+
def test_get_builder(generator, default_builder_inputs):
2526
"""Test the ``get_builder`` with default arguments."""
26-
builder = GENERATOR.get_builder(**default_builder_inputs)
27+
builder = generator.get_builder(**default_builder_inputs)
2728
assert isinstance(builder, engine.ProcessBuilder)
2829

2930

3031
@pytest.mark.skip('Running this test will fail with an `UnroutableError` in `kiwipy`.')
31-
def test_submit(default_builder_inputs):
32+
def test_submit(generator, default_builder_inputs):
3233
"""Test submitting the builder returned by ``get_builder`` called with default arguments.
3334
3435
This will actually create the ``WorkChain`` instance, so if it doesn't raise, that means the input spec was valid.
3536
"""
36-
builder = GENERATOR.get_builder(**default_builder_inputs)
37+
builder = generator.get_builder(**default_builder_inputs)
3738
engine.submit(builder)
3839

3940

40-
def test_supported_electronic_types(default_builder_inputs):
41+
def test_supported_electronic_types(generator, default_builder_inputs):
4142
"""Test calling ``get_builder`` for the supported ``electronic_types``."""
4243
inputs = default_builder_inputs
4344

44-
for electronic_type in GENERATOR.spec().inputs['electronic_type'].choices:
45+
for electronic_type in generator.spec().inputs['electronic_type'].choices:
4546
inputs['electronic_type'] = electronic_type
46-
builder = GENERATOR.get_builder(**inputs)
47+
builder = generator.get_builder(**inputs)
4748
assert isinstance(builder, engine.ProcessBuilder)
4849

4950

50-
def test_supported_relax_types(default_builder_inputs):
51+
def test_supported_relax_types(generator, default_builder_inputs):
5152
"""Test calling ``get_builder`` for the supported ``relax_types``."""
5253
inputs = default_builder_inputs
5354

54-
for relax_type in GENERATOR.spec().inputs['relax_type'].choices:
55+
for relax_type in generator.spec().inputs['relax_type'].choices:
5556
inputs['relax_type'] = relax_type
56-
builder = GENERATOR.get_builder(**inputs)
57+
builder = generator.get_builder(**inputs)
5758
assert isinstance(builder, engine.ProcessBuilder)
5859

5960

60-
def test_supported_spin_types(default_builder_inputs):
61+
def test_supported_spin_types(generator, default_builder_inputs):
6162
"""Test calling ``get_builder`` for the supported ``spin_types``."""
6263
inputs = default_builder_inputs
6364

64-
for spin_type in GENERATOR.spec().inputs['spin_type'].choices:
65+
for spin_type in generator.spec().inputs['spin_type'].choices:
6566
inputs['spin_type'] = spin_type
66-
builder = GENERATOR.get_builder(**inputs)
67+
builder = generator.get_builder(**inputs)
6768
assert isinstance(builder, engine.ProcessBuilder)

0 commit comments

Comments
 (0)