diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0a5f70cb..3d798739 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,14 +75,34 @@ jobs: poetry run mypy . - name: Tests with Pytest + id: pytest + continue-on-error: true run: | source venv/Scripts/activate - PYTHONPATH=${PWD} poetry run coverage run -m pytest --verbose - - - name: Report coverage - run: | - source venv/Scripts/activate - poetry run coverage report + PYTHONPATH=${PWD} poetry run pytest \ + -n auto \ + --dist loadscope \ + --cov=openseries \ + --cov-report=term \ + --cov-report=term-missing \ + --cov-report=xml \ + --junitxml=junit.xml + + - name: Create GitHub issue on failure + if: ${{ steps.pytest.outcome == 'failure' }} + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + await github.rest.issues.create({ + owner: 'CaptorAB', + repo: 'openseries', + title: `Tests failed on ${new Date().toDateString()}`, + body: `See the full logs here: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`, + }) + + - name: Fail job if tests failed + if: ${{ steps.pytest.outcome == 'failure' }} + run: exit 1 - name: Package and check run: | @@ -154,14 +174,34 @@ jobs: poetry run mypy . - name: Tests with Pytest + id: pytest + continue-on-error: true run: | source venv/bin/activate - PYTHONPATH=${PWD} poetry run coverage run -m pytest --verbose - - - name: Report coverage - run: | - source venv/bin/activate - poetry run coverage report + PYTHONPATH=${PWD} poetry run pytest \ + -n auto \ + --dist loadscope \ + --cov=openseries \ + --cov-report=term \ + --cov-report=term-missing \ + --cov-report=xml \ + --junitxml=junit.xml + + - name: Create GitHub issue on failure + if: ${{ steps.pytest.outcome == 'failure' }} + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + await github.rest.issues.create({ + owner: 'CaptorAB', + repo: 'openseries', + title: `Tests failed on ${new Date().toDateString()}`, + body: `See the full logs here: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`, + }) + + - name: Fail job if tests failed + if: ${{ steps.pytest.outcome == 'failure' }} + run: exit 1 - name: Package and check run: | @@ -232,14 +272,27 @@ jobs: poetry run mypy . - name: Tests with Pytest + id: pytest + continue-on-error: true run: | source venv/bin/activate - PYTHONPATH=${PWD} poetry run coverage run -m pytest --verbose + PYTHONPATH=${PWD} poetry run pytest --cov=openseries --dist loadscope -n auto - - name: Report coverage - run: | - source venv/bin/activate - poetry run coverage report + - name: Create GitHub issue on failure + if: ${{ steps.pytest.outcome == 'failure' }} + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + await github.rest.issues.create({ + owner: 'CaptorAB', + repo: 'openseries', + title: `Tests failed on ${new Date().toDateString()}`, + body: `See the full logs here: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`, + }) + + - name: Fail job if tests failed + if: ${{ steps.pytest.outcome == 'failure' }} + run: exit 1 - name: Package and check run: | diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ae6484b2..03e52ae6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -61,9 +61,18 @@ jobs: poetry run mypy . - name: Tests with Pytest + id: pytest + continue-on-error: true run: | source venv/bin/activate - PYTHONPATH=${PWD} poetry run coverage run -m pytest --verbose + PYTHONPATH=${PWD} poetry run pytest \ + -n auto \ + --dist loadscope \ + --cov=openseries \ + --cov-report=term \ + --cov-report=term-missing \ + --cov-report=xml \ + --junitxml=junit.xml - name: Create GitHub issue on failure if: ${{ steps.pytest.outcome == 'failure' }} @@ -81,19 +90,20 @@ jobs: if: ${{ steps.pytest.outcome == 'failure' }} run: exit 1 - - name: Report coverage - run: | - source venv/bin/activate - poetry run coverage xml --quiet - poetry run coverage report + - name: Upload test results to Codecov + if: ${{ github.ref_name == 'master' }} + uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: junit.xml + verbose: true - name: Upload coverage to Codecov.io + if: ${{ github.ref_name == 'master' }} uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: token: ${{ secrets.CODECOV_TOKEN }} slug: CaptorAB/openseries - fail_ci_if_error: true - files: ./coverage.xml verbose: true - name: Create Git Tag with version from pyproject.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 463fbaa6..dae4a145 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -90,7 +90,14 @@ jobs: continue-on-error: true run: | source venv/bin/activate - PYTHONPATH=${PWD} poetry run coverage run -m pytest --verbose + PYTHONPATH=${PWD} poetry run pytest \ + -n auto \ + --dist loadscope \ + --cov=openseries \ + --cov-report=term \ + --cov-report=term-missing \ + --cov-report=xml \ + --junitxml=junit.xml - name: Create GitHub issue on failure if: ${{ steps.pytest.outcome == 'failure' }} @@ -108,17 +115,18 @@ jobs: if: ${{ steps.pytest.outcome == 'failure' }} run: exit 1 - - name: Report coverage - run: | - source venv/bin/activate - poetry run coverage xml --quiet - poetry run coverage report + - name: Upload test results to Codecov + if: ${{ github.ref_name == 'master' }} + uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: junit.xml + verbose: true - name: Upload coverage to Codecov.io + if: ${{ github.ref_name == 'master' }} uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: token: ${{ secrets.CODECOV_TOKEN }} slug: CaptorAB/openseries - fail_ci_if_error: true - files: ./coverage.xml verbose: true diff --git a/.github/zizmor.yml b/.github/zizmor.yml index e2c514f8..6737eded 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -20,3 +20,4 @@ rules: - softprops/action-gh-release - astral-sh/setup-uv - codecov/codecov-action + - codecov/test-results-action diff --git a/.gitignore b/.gitignore index d7602918..fa696127 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__/ # Coverage files .coverage +coverage.xml # Environments /venv/ diff --git a/Makefile b/Makefile index 178204b9..71ff06e8 100644 --- a/Makefile +++ b/Makefile @@ -14,9 +14,7 @@ install: poetry run pre-commit install test: - poetry run coverage run -m pytest --verbose - poetry run coverage xml --quiet - poetry run coverage report + poetry run pytest --cov=openseries --cov-report=term --cov-report=term-missing -n auto lint: poetry run ruff check . --fix --exit-non-zero-on-fix diff --git a/README.md b/README.md index 2963ce81..77d0abf1 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ![Platform](https://img.shields.io/badge/platforms-Windows%20%7C%20macOS%20%7C%20Linux-blue) [![Python version](https://img.shields.io/pypi/pyversions/openseries.svg)](https://www.python.org/) [![GitHub Action Test Suite](https://github.com/CaptorAB/openseries/actions/workflows/test.yml/badge.svg)](https://github.com/CaptorAB/openseries/actions/workflows/test.yml) -[![codecov](https://img.shields.io/codecov/c/gh/CaptorAB/openseries)](https://codecov.io/gh/CaptorAB/openseries/branch/master) +[![codecov](https://img.shields.io/codecov/c/gh/CaptorAB/openseries?logo=codecov)](https://codecov.io/gh/CaptorAB/openseries/branch/master) [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://beta.ruff.rs/docs/) [![GitHub License](https://img.shields.io/github/license/CaptorAB/openseries)](https://github.com/CaptorAB/openseries/blob/master/LICENSE.md) @@ -54,7 +54,7 @@ _,_=series.plot_series() ### Sample output using the report_html() function: -Two Assets Compared +Two Assets Compared ## Development Instructions diff --git a/coverage.xml b/coverage.xml deleted file mode 100644 index 776fc4a6..00000000 --- a/coverage.xml +++ /dev/null @@ -1,1987 +0,0 @@ - - - - - - /Users/martinkarrin/workgit/openseries - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/make.ps1 b/make.ps1 index d410a2ea..80a728fa 100644 --- a/make.ps1 +++ b/make.ps1 @@ -95,10 +95,7 @@ switch ($task) { } "test" { - poetry run coverage run -m pytest --verbose - poetry run coverage xml --quiet - poetry run coverage report - poetry run genbadge coverage --silent --local --input-file coverage.xml --output-file coverage.svg + poetry run pytest --cov=openseries --cov-report=term --cov-report=term-missing -n auto } "lint" { diff --git a/poetry.lock b/poetry.lock index 32c1d104..b2063f1d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -61,14 +61,14 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2025.4.26" +version = "2025.6.15" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, - {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, + {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, + {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, ] [[package]] @@ -290,6 +290,9 @@ files = [ {file = "coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec"}, ] +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] @@ -359,6 +362,21 @@ tzdata = "*" [package.extras] dev = ["flake8", "hypothesis", "pip-tools", "pytest", "pytest-benchmark", "pytest-xdist"] +[[package]] +name = "execnet" +version = "2.1.1" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + [[package]] name = "filelock" version = "3.18.0" @@ -848,14 +866,14 @@ virtualenv = ">=20.10.0" [[package]] name = "pydantic" -version = "2.11.6" +version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.11.6-py3-none-any.whl", hash = "sha256:a24478d2be1b91b6d3bc9597439f69ed5e87f68ebd285d86f7c7932a084b72e7"}, - {file = "pydantic-2.11.6.tar.gz", hash = "sha256:12b45cfb4af17e555d3c6283d0b55271865fb0b43cc16dd0d52749dc7abf70e7"}, + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] [package.dependencies] @@ -1035,6 +1053,47 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "6.2.1" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, + {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, +] + +[package.dependencies] +coverage = {version = ">=7.5", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=6.2.5" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-xdist" +version = "3.7.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_xdist-3.7.0-py3-none-any.whl", hash = "sha256:7d3fbd255998265052435eb9daa4e99b62e6fb9cfb6efd1f858d4d8c0c7f0ca0"}, + {file = "pytest_xdist-3.7.0.tar.gz", hash = "sha256:f9248c99a7c15b7d2f90715df93610353a485827bc06eefb6566d23f6400f126"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1490,4 +1549,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.14" -content-hash = "f0df58f377413e2897bc9e2bbfa780b50127cc3ffd8602d08bb877ae6fa071f5" +content-hash = "5a2ac8af80bc5cddee878e6f972cadfaab4be86b18283d5996b607582f19e583" diff --git a/pyproject.toml b/pyproject.toml index a5ff4f7c..c7722975 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,11 +61,12 @@ dependencies = [ [tool.poetry.group.dev.dependencies] black = ">=24.4.2,<27.0.0" -coverage = ">=7.2.7,<9.0.0" mypy = "1.16.0" pandas-stubs = ">=2.1.2,<3.0.0" pre-commit = ">=3.7.1,<6.0.0" pytest = ">=8.2.2,<9.0.0" +pytest-cov = ">=5.0.0,<7.0.0" +pytest-xdist = ">=3.3.1,<5.0.0" ruff = "0.11.13" types-openpyxl = ">=3.1.2,<5.0.0" types-python-dateutil = ">=2.8.2,<4.0.0" @@ -78,7 +79,6 @@ build-backend = "poetry.core.masonry.api" [tool.coverage.run] branch = true omit = ["venv/*", "tests/*", "**/__init__.py"] -include = ["openseries/*"] [tool.coverage.report] omit = ["**/__init__.py"]