diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..25514dfec --- /dev/null +++ b/.coveragerc @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: Copyright (C) 2025 Opal Health Informatics Group at the Research Institute of the McGill University Health Centre +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +[run] +source = opal +; include = opal/* +omit = + .*, + **/tests/*, + # **/migrations/*, + opal/wsgi.py, + opal/asgi.py, + # omit prod settings + opal/settings_prod.py, + # omit sidebar menu + opal/templates/sidebar_menu.html, + opal/templates/components/menu_group.html, + opal/templates/components/menu_item.html +branch = True +plugins = + django_coverage_plugin + +[report] +fail_under = 100 +precision = 2 +show_missing = True +skip_empty = True +skip_covered = True +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + # Don't require coverage on TYPE_CHECKING imports + if TYPE_CHECKING: + + # Don't complain if non-runnable code isn't run: + if __name__ == .__main__.: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b708fc40f..01fa32911 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -164,13 +164,17 @@ jobs: # generate secret key SECRET_KEY=$(python -c "import secrets; print(secrets.token_urlsafe())") sed -i "s/^SECRET_KEY=.*/SECRET_KEY=$SECRET_KEY/" .env - MYSQL_PWD=$DB_ROOT_PASSWORD mariadb -u root -h db --skip-ssl -e "GRANT ALL PRIVILEGES ON \`test_OpalDB\`.* TO \`$DB_USER\`@\`%\`;" - MYSQL_PWD=$DB_ROOT_PASSWORD mariadb -u root -h db --skip-ssl -e "GRANT ALL PRIVILEGES ON \`test_QuestionnaireDB\`.* TO \`$DB_USER\`@\`%\`;" + MYSQL_PWD=$DB_ROOT_PASSWORD mariadb -u root -h db --skip-ssl -e "GRANT ALL PRIVILEGES ON \`test_opal%\`.* TO \`$DB_USER\`@\`%\`;" + MYSQL_PWD=$DB_ROOT_PASSWORD mariadb -u root -h db --skip-ssl -e "GRANT ALL PRIVILEGES ON \`test_OpalDB%\`.* TO \`$DB_USER\`@\`%\`;" + MYSQL_PWD=$DB_ROOT_PASSWORD mariadb -u root -h db --skip-ssl -e "GRANT ALL PRIVILEGES ON \`test_QuestionnaireDB%\`.* TO \`$DB_USER\`@\`%\`;" - name: Run pytest + env: + # https://github.com/zupo/awesome-pytest-speedup#pythondontwritebytecode + PYTHONDONTWRITEBYTECODE: 1 run: | uv run pytest --version # -m "" runs all tests, even the ones marked as slow - uv run coverage run -m pytest -m "" -v --junitxml=test-report.xml + uv run pytest --cov=opal -n auto -m "" --junitxml=test-report.xml # see: https://github.com/dorny/test-reporter/issues/244 # - name: Publish Test Results # uses: dorny/test-reporter@v1.9.1 @@ -237,6 +241,6 @@ jobs: permissions: contents: read packages: write - uses: opalmedapps/.github/.github/workflows/docker-build.yaml@fix-build + uses: opalmedapps/.github/.github/workflows/docker-build.yaml@main with: test-command: python manage.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15145dd00..bbc6503fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,6 @@ jobs: permissions: contents: read packages: write - uses: opalmedapps/.github/.github/workflows/docker-build.yaml@fix-build + uses: opalmedapps/.github/.github/workflows/docker-build.yaml@main with: test-command: python manage.py diff --git a/pyproject.toml b/pyproject.toml index b61a65584..45ae8b1c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,11 +63,14 @@ dev = [ "pandas-stubs==2.2.3.250308", "pre-commit==4.2.0", "pytest==8.3.5", + # for proper coverage support with pytest-xdist + "pytest-cov==6.1.1", "pytest-django==4.11.1", "pytest-mock==3.14.0", "pytest-randomly==3.16.0", "pytest-socket==0.7.0", "pytest-sugar==1.0.0", + "pytest-xdist==3.6.1", "ruff==0.11.4", "types-beautifulsoup4==4.12.0.20250204", "types-fpdf2==2.8.2.20250318", diff --git a/setup.cfg b/setup.cfg index 5157ed08f..1c59fd834 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,37 +44,3 @@ warn_unreachable = False [mypy-*.factories] disallow_untyped_calls = False - -[coverage:run] -source = opal -; include = opal/* -omit = - .*, - **/tests/*, - # **/migrations/*, - opal/wsgi.py, - opal/asgi.py, - # omit prod settings - opal/settings_prod.py, - # omit sidebar menu - opal/templates/sidebar_menu.html, - opal/templates/components/menu_group.html, - opal/templates/components/menu_item.html -branch = True -plugins = - django_coverage_plugin - -[coverage:report] -fail_under = 100 -precision = 2 -show_missing = True -skip_empty = True -skip_covered = True -exclude_lines = - # Have to re-enable the standard pragma - pragma: no cover - # Don't require coverage on TYPE_CHECKING imports - if TYPE_CHECKING: - - # Don't complain if non-runnable code isn't run: - if __name__ == .__main__.: diff --git a/uv.lock b/uv.lock index 8b27b8990..602700daf 100644 --- a/uv.lock +++ b/uv.lock @@ -56,11 +56,13 @@ dev = [ { name = "pandas-stubs" }, { name = "pre-commit" }, { name = "pytest" }, + { name = "pytest-cov" }, { name = "pytest-django" }, { name = "pytest-mock" }, { name = "pytest-randomly" }, { name = "pytest-socket" }, { name = "pytest-sugar" }, + { name = "pytest-xdist" }, { name = "ruff" }, { name = "types-beautifulsoup4" }, { name = "types-fpdf2" }, @@ -129,11 +131,13 @@ dev = [ { name = "pandas-stubs", specifier = "==2.2.3.250308" }, { name = "pre-commit", specifier = "==4.2.0" }, { name = "pytest", specifier = "==8.3.5" }, + { name = "pytest-cov", specifier = "==6.1.1" }, { name = "pytest-django", specifier = "==4.11.1" }, { name = "pytest-mock", specifier = "==3.14.0" }, { name = "pytest-randomly", specifier = "==3.16.0" }, { name = "pytest-socket", specifier = "==0.7.0" }, { name = "pytest-sugar", specifier = "==1.0.0" }, + { name = "pytest-xdist", specifier = "==3.6.1" }, { name = "ruff", specifier = "==0.11.4" }, { name = "types-beautifulsoup4", specifier = "==4.12.0.20250204" }, { name = "types-fpdf2", specifier = "==2.8.2.20250318" }, @@ -768,6 +772,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, ] +[[package]] +name = "execnet" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, +] + [[package]] name = "factory-boy" version = "3.3.3" @@ -1549,6 +1562,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] +[[package]] +name = "pytest-cov" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 }, +] + [[package]] name = "pytest-django" version = "4.11.1" @@ -1611,6 +1637,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/92/fb/889f1b69da2f13691de09a111c16c4766a433382d44aa0ecf221deded44a/pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd", size = 10171 }, ] +[[package]] +name = "pytest-xdist" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0"