Skip to content

Commit

Permalink
Devops: Add tests for the minimal install
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sphuber committed Feb 28, 2024
1 parent 905d92d commit 34e8858
Show file tree
Hide file tree
Showing 16 changed files with 307 additions and 220 deletions.
28 changes: 27 additions & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,36 @@ jobs:
- name: Run pytest
run: pytest -sv tests

tests-minimal-install:

runs-on: ubuntu-latest
timeout-minutes: 30

strategy:
matrix:
python-version: ['3.9']

steps:
- uses: actions/checkout@v2

- name: Install Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml

- name: Install Python package without any extras
run: pip install -e .[tests]

- name: Run pytest
run: pytest -sv tests -m minimal_install


publish:

name: Publish to PyPI
needs: [pre-commit, tests]
needs: [pre-commit, tests, tests-minimal-install]
runs-on: ubuntu-latest

steps:
Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,28 @@ jobs:
env:
AIIDA_WARN_v3: true
run: pytest -sv tests

tests-minimal-install:

runs-on: ubuntu-latest
timeout-minutes: 30

strategy:
matrix:
python-version: ['3.9']

steps:
- uses: actions/checkout@v2

- name: Install Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml

- name: Install Python package without any extras
run: pip install -e .[tests]

- name: Run pytest
run: pytest -sv tests -m minimal_install
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ filterwarnings = [
'ignore:Creating AiiDA configuration folder.*:UserWarning',
'ignore:Object of type .* not in session, .* operation along .* will not proceed:sqlalchemy.exc.SAWarning'
]
markers = [
'minimal_install: mark test as relevant for minimal install.'
]
testpaths = [
'tests'
]
Expand Down
1 change: 1 addition & 0 deletions tests/cli/test_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def recurse_commands(command: click.Command, parents: list[str] | None = None):

@pytest.mark.parametrize('command', recurse_commands(cmd_root))
@pytest.mark.parametrize('help_option', ('--help', '-h'))
@pytest.mark.minimal_install
def test_commands_help_option(command, help_option):
"""Test the help options for all subcommands of the CLI.
Expand Down
20 changes: 20 additions & 0 deletions tests/test_minimal_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Tests for a minimal install of the package without any extra dependencies.
The unit tests in this module should be run against a minimal install of the package without any extra dependencies
installed. This guarantees that most of the code can be imported without any plugin packages being installed.
"""
import pytest


@pytest.mark.minimal_install
def test_imports():
"""The following modules should be importable without any plugin packages installed."""
import aiida_common_workflows.cli
import aiida_common_workflows.common
import aiida_common_workflows.generators
import aiida_common_workflows.plugins
import aiida_common_workflows.protocol
import aiida_common_workflows.utils
import aiida_common_workflows.workflows
import aiida_common_workflows.workflows.dissociation
import aiida_common_workflows.workflows.eos # noqa: F401
33 changes: 17 additions & 16 deletions tests/workflows/relax/test_abinit.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Tests for the :mod:`aiida_common_workflows.workflows.relax.abinit` module."""

import pytest
from aiida import engine, plugins

WORKCHAIN = plugins.WorkflowFactory('common_workflows.relax.abinit')
GENERATOR = WORKCHAIN.get_input_generator()

@pytest.fixture
def generator():
return plugins.WorkflowFactory('common_workflows.relax.abinit').get_input_generator()


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


@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
def test_get_builder(default_builder_inputs):
def test_get_builder(generator, default_builder_inputs):
"""Test the ``get_builder`` with default arguments."""
builder = GENERATOR.get_builder(**default_builder_inputs)
builder = generator.get_builder(**default_builder_inputs)
assert isinstance(builder, engine.ProcessBuilder)


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


@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
def test_supported_electronic_types(default_builder_inputs):
def test_supported_electronic_types(generator, default_builder_inputs):
"""Test calling ``get_builder`` for the supported ``electronic_types``."""
inputs = default_builder_inputs

for electronic_type in GENERATOR.spec().inputs['electronic_type'].choices:
for electronic_type in generator.spec().inputs['electronic_type'].choices:
inputs['electronic_type'] = electronic_type
builder = GENERATOR.get_builder(**inputs)
builder = generator.get_builder(**inputs)
assert isinstance(builder, engine.ProcessBuilder)


@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
def test_supported_relax_types(default_builder_inputs):
def test_supported_relax_types(generator, default_builder_inputs):
"""Test calling ``get_builder`` for the supported ``relax_types``."""
inputs = default_builder_inputs

for relax_type in GENERATOR.spec().inputs['relax_type'].choices:
for relax_type in generator.spec().inputs['relax_type'].choices:
inputs['relax_type'] = relax_type
builder = GENERATOR.get_builder(**inputs)
builder = generator.get_builder(**inputs)
assert isinstance(builder, engine.ProcessBuilder)


@pytest.mark.usefixtures('pseudo_dojo_jthxml_family')
@pytest.mark.filterwarnings('ignore: input magnetization per site was None')
def test_supported_spin_types(default_builder_inputs):
def test_supported_spin_types(generator, default_builder_inputs):
"""Test calling ``get_builder`` for the supported ``spin_types``."""
inputs = default_builder_inputs

for spin_type in GENERATOR.spec().inputs['spin_type'].choices:
for spin_type in generator.spec().inputs['spin_type'].choices:
inputs['spin_type'] = spin_type
builder = GENERATOR.get_builder(**inputs)
builder = generator.get_builder(**inputs)
assert isinstance(builder, engine.ProcessBuilder)
33 changes: 17 additions & 16 deletions tests/workflows/relax/test_bigdft.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Tests for the :mod:`aiida_common_workflows.workflows.relax.bigdft` module."""

import pytest
from aiida import engine, plugins

WORKCHAIN = plugins.WorkflowFactory('common_workflows.relax.bigdft')
GENERATOR = WORKCHAIN.get_input_generator()

@pytest.fixture
def generator():
return plugins.WorkflowFactory('common_workflows.relax.bigdft').get_input_generator()


@pytest.fixture
Expand All @@ -21,47 +22,47 @@ def default_builder_inputs(generate_code, generate_structure):
}


def test_get_builder(default_builder_inputs):
def test_get_builder(generator, default_builder_inputs):
"""Test the ``get_builder`` with default arguments."""
builder = GENERATOR.get_builder(**default_builder_inputs)
builder = generator.get_builder(**default_builder_inputs)
assert isinstance(builder, engine.ProcessBuilder)


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


def test_supported_electronic_types(default_builder_inputs):
def test_supported_electronic_types(generator, default_builder_inputs):
"""Test calling ``get_builder`` for the supported ``electronic_types``."""
inputs = default_builder_inputs

for electronic_type in GENERATOR.spec().inputs['electronic_type'].choices:
for electronic_type in generator.spec().inputs['electronic_type'].choices:
inputs['electronic_type'] = electronic_type
builder = GENERATOR.get_builder(**inputs)
builder = generator.get_builder(**inputs)
assert isinstance(builder, engine.ProcessBuilder)


def test_supported_relax_types(default_builder_inputs):
def test_supported_relax_types(generator, default_builder_inputs):
"""Test calling ``get_builder`` for the supported ``relax_types``."""
inputs = default_builder_inputs

for relax_type in GENERATOR.spec().inputs['relax_type'].choices:
for relax_type in generator.spec().inputs['relax_type'].choices:
inputs['relax_type'] = relax_type
builder = GENERATOR.get_builder(**inputs)
builder = generator.get_builder(**inputs)
assert isinstance(builder, engine.ProcessBuilder)


def test_supported_spin_types(default_builder_inputs):
def test_supported_spin_types(generator, default_builder_inputs):
"""Test calling ``get_builder`` for the supported ``spin_types``."""
inputs = default_builder_inputs

for spin_type in GENERATOR.spec().inputs['spin_type'].choices:
for spin_type in generator.spec().inputs['spin_type'].choices:
inputs['spin_type'] = spin_type
builder = GENERATOR.get_builder(**inputs)
builder = generator.get_builder(**inputs)
assert isinstance(builder, engine.ProcessBuilder)
Loading

0 comments on commit 34e8858

Please sign in to comment.