Skip to content

Commit

Permalink
Prepare a tests/integration directory that will contain tests of the …
Browse files Browse the repository at this point in the history
…integration between mathrace-interaction and turing
  • Loading branch information
francesco-ballarin committed Feb 13, 2024
1 parent da1a574 commit 3a40ea8
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 117 deletions.
40 changes: 36 additions & 4 deletions .github/workflows/mathrace_interaction_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ on:
schedule:
- cron: "0 0 * * MON"
workflow_dispatch:
inputs:
full_integration_tests:
description: "If yes, run integration tests with --all-journals. Default value is no"

jobs:
test:
Expand Down Expand Up @@ -75,18 +78,47 @@ jobs:
python3 -m isort --check --diff .
- name: Run mypy
run: |
python3 -m mypy .
python3 -m mypy --exclude=conftest.py .
python3 -m mypy tests/integration/conftest.py
python3 -m mypy tests/unit/conftest.py
- name: Run yamllint
run: |
python3 -m yamllint -d "{ignore: turing, extends: default, rules: {document-start: {present: false}, line-length: disable, truthy: {check-keys: false}}}" ../.github
- name: Run documentation generation
run: |
cd docs && python3 -m sphinx -W -b html . build/html
- name: Run tests
- name: Run unit tests
run: |
python3 -m coverage run --source=mathrace_interaction -m pytest -svv tests/unit
- name: Check test coverage
COVERAGE_FILE=.coverage_unit python3 -m coverage run --source=mathrace_interaction -m pytest tests/unit
- name: Determine integration tests options
id: determine_integration_tests_options
run: |
WITH_INTEGRATION_TESTS=yes
FULL_INTEGRATION_TESTS=${{ inputs.full_integration_tests }}
if [ "${{ github.event_name }}" == "schedule" ]; then
FULL_INTEGRATION_TESTS=yes
fi
if [ "${{ matrix.name }}" != "with-turing" ]; then
WITH_INTEGRATION_TESTS=no
FULL_INTEGRATION_TESTS=no
fi
if [ "${FULL_INTEGRATION_TESTS}" != "yes" ] && [ "${FULL_INTEGRATION_TESTS}" != "no" ]; then
FULL_INTEGRATION_TESTS=no
fi
echo "with_integration_tests=${WITH_INTEGRATION_TESTS}" >> ${GITHUB_OUTPUT}
echo "full_integration_tests=${FULL_INTEGRATION_TESTS}" >> ${GITHUB_OUTPUT}
- name: Run integration tests (basic)
if: steps.determine_integration_tests_options.outputs.with_integration_tests == 'yes' && steps.determine_integration_tests_options.outputs.full_integration_tests != 'yes'
run: |
COVERAGE_FILE=.coverage_integration python3 -m coverage run --source=mathrace_interaction -m pytest tests/integration
- name: Run integration tests (full)
if: steps.determine_integration_tests_options.outputs.with_integration_tests == 'yes' && steps.determine_integration_tests_options.outputs.full_integration_tests == 'yes'
run: |
COVERAGE_FILE=.coverage_integration python3 -m coverage run --source=mathrace_interaction -m pytest --all-journals tests/integration
- name: Combine coverage reports
if: steps.determine_integration_tests_options.outputs.with_integration_tests == 'yes'
run: |
python3 -m coverage combine .coverage*
python3 -m coverage report --fail-under=100 --show-missing --skip-covered
warn:
Expand Down
134 changes: 134 additions & 0 deletions mathrace_interaction/tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Copyright (C) 2024 by the Turing @ DMF authors
#
# This file is part of Turing @ DMF.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
"""pytest configuration file for integration tests."""

import pathlib

import _pytest
import pytest

_data_dir = pathlib.Path(__file__).parent.parent.parent / "data"

# Integration testing with chrome driver takes several seconds for each journal file.
# To keep the total runtime of tests under control, run tests by default on a subset of the available files.
# Classify journals as either part of basic testing (key equal to True),
# or only part of full testing (key equal to False)
_data_journals_is_basic_testing = {
# disfida: different formats across the years
_data_dir / "2013" / "disfida.journal": True,
_data_dir / "2014" / "disfida.journal": True,
_data_dir / "2015" / "disfida.journal": True,
_data_dir / "2016" / "disfida.journal": True,
_data_dir / "2017" / "disfida.journal": True,
_data_dir / "2018" / "disfida.journal": True,
_data_dir / "2019" / "disfida.journal": True,
_data_dir / "2020" / "disfida.journal": True,
_data_dir / "2022" / "disfida.journal": True,
_data_dir / "2023" / "disfida_legacy_format.journal": True,
_data_dir / "2023" / "disfida_new_format.journal": True,
# kangourou: different total duration, score keeps increasing to the end of the race
_data_dir / "2014" / "kangourou.journal": True,
_data_dir / "2015" / "kangourou.journal": True,
_data_dir / "2016" / "kangourou.journal": True,
# cesenatico: alternative race definition and/or guest teams
_data_dir / "2019" / "cesenatico_finale_femminile_formato_journal.journal": True,
_data_dir / "2019" / "cesenatico_finale_formato_extracted.journal": True,
_data_dir / "2019" / "cesenatico_finale_formato_journal.journal": True,
_data_dir / "2019" / "cesenatico_semifinale_A.journal": False,
_data_dir / "2019" / "cesenatico_semifinale_B.journal": False,
_data_dir / "2019" / "cesenatico_semifinale_C.journal": False,
_data_dir / "2019" / "cesenatico_semifinale_D.journal": False,
# cesenatico: standard race definition, no guest teams, but with bonus/superbonus specification
_data_dir / "2022" / "cesenatico_finale.journal": True,
_data_dir / "2022" / "cesenatico_finale_femminile.journal": True,
_data_dir / "2022" / "cesenatico_pubblico.journal": True,
_data_dir / "2022" / "cesenatico_semifinale_A.journal": False,
_data_dir / "2022" / "cesenatico_semifinale_B.journal": False,
_data_dir / "2022" / "cesenatico_semifinale_C.journal": False,
_data_dir / "2022" / "cesenatico_semifinale_D.journal": False,
# cesenatico: standard race definition, no guest teams, no bonus/superbonus specification
_data_dir / "2019" / "cesenatico_finale_femminile_formato_extracted.journal": False,
_data_dir / "2020" / "cesenatico_finale.journal": False,
_data_dir / "2020" / "cesenatico_finale_femminile.journal": False,
_data_dir / "2020" / "cesenatico_semifinale_A.journal": False,
_data_dir / "2020" / "cesenatico_semifinale_B.journal": False,
_data_dir / "2020" / "cesenatico_semifinale_C.journal": False,
_data_dir / "2020" / "cesenatico_semifinale_D.journal": False,
_data_dir / "2021" / "cesenatico_finale.journal": False,
_data_dir / "2021" / "cesenatico_finale_femminile.journal": False,
_data_dir / "2021" / "cesenatico_semifinale_A.journal": False,
_data_dir / "2021" / "cesenatico_semifinale_B.journal": False,
_data_dir / "2021" / "cesenatico_semifinale_C.journal": False,
_data_dir / "2021" / "cesenatico_semifinale_D.journal": False,
_data_dir / "2021" / "cesenatico_semifinale_E.journal": False,
_data_dir / "2021" / "cesenatico_semifinale_F.journal": False,
# qualificazione: standard race definition, no guest teams, but with bonus/superbonus specification
_data_dir / "2022" / "qualificazione_arezzo_cagliari_taranto_trento.journal": False,
_data_dir / "2022" / "qualificazione_brindisi_catania_forli_cesena_sassari.journal": False,
_data_dir / "2022" / "qualificazione_campobasso_collevaldelsa_pisa_napoli.journal": False,
_data_dir / "2022" / "qualificazione_femminile_1.journal": False,
_data_dir / "2022" / "qualificazione_femminile_2.journal": False,
_data_dir / "2022" / "qualificazione_femminile_3.journal": False,
_data_dir / "2022" / "qualificazione_firenze.journal": False,
_data_dir / "2022" / "qualificazione_foggia_lucca_nuoro_tricase.journal": False,
_data_dir / "2022" / "qualificazione_genova.journal": False,
_data_dir / "2022" / "qualificazione_milano.journal": False,
_data_dir / "2022" / "qualificazione_narni.journal": False,
_data_dir / "2022" / "qualificazione_parma.journal": False,
_data_dir / "2022" / "qualificazione_pordenone_udine.journal": False,
_data_dir / "2022" / "qualificazione_reggio_emilia.journal": False,
_data_dir / "2022" / "qualificazione_roma.journal": False,
_data_dir / "2022" / "qualificazione_torino.journal": False,
_data_dir / "2022" / "qualificazione_trieste.journal": False,
_data_dir / "2022" / "qualificazione_velletri.journal": False,
_data_dir / "2022" / "qualificazione_vicenza.journal": False,
# qualificazione femminale: standard race definition, no guest teams, but with team names
_data_dir / "2019" / "cesenatico_finale_formato_extracted_nomi_squadra.journal": True,
_data_dir / "2023" / "qualificazione_femminile_1.journal": True,
_data_dir / "2023" / "qualificazione_femminile_2.journal": True,
_data_dir / "2023" / "qualificazione_femminile_3.journal": True,
# additional tests: different timestamp format
_data_dir / "2024" / "february_9_short_run.journal": True,
}


def pytest_addoption(parser: pytest.Parser, pluginmanager: pytest.PytestPluginManager) -> None:
"""Add options to run tests on all journal files, rather than only the basic subset."""
parser.addoption("--all-journals", action="store_true", help="Run tests on all journal files")


def pytest_configure(config: pytest.Config) -> None:
"""Add a marker to be associated to the value of the command line option --all-journals."""
config.addinivalue_line("markers", "requires_all_journals: mark test as requiring all journals")


def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
"""Apply skip associated to the value of the command line option --all-journals."""
if not config.getoption("--all-journals"):
skip_requires_all_journals_tests = pytest.mark.skip(reason="need --all-journals option to run")
for item in items:
if "requires_all_journals" in item.keywords:
item.add_marker(skip_requires_all_journals_tests)


@pytest.fixture(scope="session")
def data_dir() -> pathlib.Path:
"""Return the data directory of mathrace-interaction."""
return _data_dir


@pytest.fixture(scope="session")
def data_journals(request: _pytest.fixtures.SubRequest) -> list[pathlib.Path]:
"""
Return journals files in the data directory on which tests should be run.
By default, only a subset of the journal files will be returned.
All journal files are return if calling pytest with the --all-journals option.
"""
if request.config.option.all_journals:
return list(_data_journals_is_basic_testing.keys())
else:
return [journal for (journal, is_basic_testing) in _data_journals_is_basic_testing.items() if is_basic_testing]
27 changes: 27 additions & 0 deletions mathrace_interaction/tests/integration/test_fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright (C) 2024 by the Turing @ DMF authors
#
# This file is part of Turing @ DMF.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Test fixtures defined in conftest.py."""

import pathlib

import pytest


@pytest.mark.requires_all_journals
def test_data_journal_fixture(data_dir: pathlib.Path, data_journals: list[pathlib.Path]) -> None:
"""Test that the data_journals fixture actually contains all journal files."""
data_journals_actual = set()
for entry in data_dir.rglob("*"):
assert entry.is_file() or entry.is_dir()
if entry.is_file():
if entry.suffix == ".journal":
data_journals_actual.add(entry)
elif entry.is_dir():
pass
else:
raise RuntimeError(f"Invalid {entry}")
data_journals_difference = data_journals_actual.symmetric_difference(data_journals)
assert len(data_journals_difference) == 0, f"Unlisted journals found {data_journals_difference}"
103 changes: 14 additions & 89 deletions mathrace_interaction/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,98 +10,23 @@
import pytest


@pytest.fixture
@pytest.fixture(scope="session")
def data_dir() -> pathlib.Path:
"""Return the data directory of mathrace-interaction."""
return pathlib.Path(__file__).parent.parent.parent / "data"


@pytest.fixture
def data_journals_basic(data_dir: pathlib.Path) -> list[pathlib.Path]:
"""Return a subset of the journals in the data directory."""
return [
# disfida
data_dir / "2013" / "disfida.journal",
data_dir / "2014" / "disfida.journal",
data_dir / "2015" / "disfida.journal",
data_dir / "2016" / "disfida.journal",
data_dir / "2017" / "disfida.journal",
data_dir / "2018" / "disfida.journal",
data_dir / "2019" / "disfida.journal",
data_dir / "2020" / "disfida.journal",
data_dir / "2022" / "disfida.journal",
data_dir / "2023" / "disfida_legacy_format.journal",
data_dir / "2023" / "disfida_new_format.journal",
# kangourou: different total duration, score keeps increasing to the end of the race
data_dir / "2014" / "kangourou.journal",
data_dir / "2015" / "kangourou.journal",
data_dir / "2016" / "kangourou.journal",
# cesenatico: alternative race definition and/or guest teams
data_dir / "2019" / "cesenatico_finale_femminile_formato_journal.journal",
data_dir / "2019" / "cesenatico_finale_formato_extracted.journal",
data_dir / "2019" / "cesenatico_finale_formato_journal.journal",
# cesenatico: standard race definition, no guest teams, but with bonus/superbonus specification
data_dir / "2022" / "cesenatico_finale.journal",
data_dir / "2022" / "cesenatico_finale_femminile.journal",
data_dir / "2022" / "cesenatico_pubblico.journal",
# qualificazione femminale: standard race definition, no guest teams, but with team names
data_dir / "2019" / "cesenatico_finale_formato_extracted_nomi_squadra.journal",
data_dir / "2023" / "qualificazione_femminile_1.journal",
data_dir / "2023" / "qualificazione_femminile_2.journal",
data_dir / "2023" / "qualificazione_femminile_3.journal",
# additional tests: different timestamp format
data_dir / "2024" / "february_9_short_run.journal",
]


@pytest.fixture
def data_journals_all(data_dir: pathlib.Path, data_journals_basic: list[pathlib.Path]) -> list[pathlib.Path]:
@pytest.fixture(scope="session")
def data_journals(data_dir: pathlib.Path) -> list[pathlib.Path]:
"""Return all journals in the data directory."""
return [
*data_journals_basic,
# cesenatico: alternative race definition and/or guest teams
data_dir / "2019" / "cesenatico_semifinale_A.journal",
data_dir / "2019" / "cesenatico_semifinale_B.journal",
data_dir / "2019" / "cesenatico_semifinale_C.journal",
data_dir / "2019" / "cesenatico_semifinale_D.journal",
# cesenatico: standard race definition, no guest teams, no bonus/superbonus specification
data_dir / "2019" / "cesenatico_finale_femminile_formato_extracted.journal",
data_dir / "2020" / "cesenatico_finale.journal",
data_dir / "2020" / "cesenatico_finale_femminile.journal",
data_dir / "2020" / "cesenatico_semifinale_A.journal",
data_dir / "2020" / "cesenatico_semifinale_B.journal",
data_dir / "2020" / "cesenatico_semifinale_C.journal",
data_dir / "2020" / "cesenatico_semifinale_D.journal",
data_dir / "2021" / "cesenatico_finale.journal",
data_dir / "2021" / "cesenatico_finale_femminile.journal",
data_dir / "2021" / "cesenatico_semifinale_A.journal",
data_dir / "2021" / "cesenatico_semifinale_B.journal",
data_dir / "2021" / "cesenatico_semifinale_C.journal",
data_dir / "2021" / "cesenatico_semifinale_D.journal",
data_dir / "2021" / "cesenatico_semifinale_E.journal",
data_dir / "2021" / "cesenatico_semifinale_F.journal",
# cesenatico: standard race definition, no guest teams, but with bonus/superbonus specification
data_dir / "2022" / "cesenatico_semifinale_A.journal",
data_dir / "2022" / "cesenatico_semifinale_B.journal",
data_dir / "2022" / "cesenatico_semifinale_C.journal",
data_dir / "2022" / "cesenatico_semifinale_D.journal",
data_dir / "2022" / "qualificazione_arezzo_cagliari_taranto_trento.journal",
data_dir / "2022" / "qualificazione_brindisi_catania_forli_cesena_sassari.journal",
data_dir / "2022" / "qualificazione_campobasso_collevaldelsa_pisa_napoli.journal",
data_dir / "2022" / "qualificazione_femminile_1.journal",
data_dir / "2022" / "qualificazione_femminile_2.journal",
data_dir / "2022" / "qualificazione_femminile_3.journal",
data_dir / "2022" / "qualificazione_firenze.journal",
data_dir / "2022" / "qualificazione_foggia_lucca_nuoro_tricase.journal",
data_dir / "2022" / "qualificazione_genova.journal",
data_dir / "2022" / "qualificazione_milano.journal",
data_dir / "2022" / "qualificazione_narni.journal",
data_dir / "2022" / "qualificazione_parma.journal",
data_dir / "2022" / "qualificazione_pordenone_udine.journal",
data_dir / "2022" / "qualificazione_reggio_emilia.journal",
data_dir / "2022" / "qualificazione_roma.journal",
data_dir / "2022" / "qualificazione_torino.journal",
data_dir / "2022" / "qualificazione_trieste.journal",
data_dir / "2022" / "qualificazione_velletri.journal",
data_dir / "2022" / "qualificazione_vicenza.journal",
]
data_journals_set = set()
for entry in data_dir.rglob("*"):
assert entry.is_file() or entry.is_dir()
if entry.is_file():
if entry.suffix == ".journal":
data_journals_set.add(entry)
elif entry.is_dir():
pass
else:
raise RuntimeError(f"Invalid {entry}")
return list(sorted(data_journals_set))
16 changes: 0 additions & 16 deletions mathrace_interaction/tests/unit/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,3 @@ def test_data_contains_only_journal_and_score_files(data_dir: pathlib.Path) -> N
pass
else:
raise RuntimeError(f"Invalid {entry}")


def test_data_journal_all_fixture(data_dir: pathlib.Path, data_journals_all: list[pathlib.Path]) -> None:
"""Test that the data_journals_all fixture actually contains all journal files."""
data_journals_actual = set()
for entry in data_dir.rglob("*"):
assert entry.is_file() or entry.is_dir()
if entry.is_file():
if entry.suffix == ".journal":
data_journals_actual.add(entry)
elif entry.is_dir():
pass
else:
raise RuntimeError(f"Invalid {entry}")
data_journals_difference = data_journals_actual.symmetric_difference(data_journals_all)
assert len(data_journals_difference) == 0, f"Unlisted journals found {data_journals_difference}"
Loading

0 comments on commit 3a40ea8

Please sign in to comment.