diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml new file mode 100644 index 00000000..63f594ef --- /dev/null +++ b/.github/workflows/CI.yaml @@ -0,0 +1,210 @@ +name: CI + +on: + push: + branches: [main] + pull_request: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_COLOR: "1" + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_PYTHON_VERSION_WARNING: "1" + +jobs: + check-manifest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: pipx run check-manifest + + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version-default + - uses: pre-commit/action@v3.0.1 + + typing: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version-default + - name: Install tox-uv + run: python -Im pip install tox-uv + - name: type-check + run: python -Im tox run -e mypy + + docs: + name: Build docs and run doctests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + # Keep in sync with tox/docs and .readthedocs.yaml. + python-version: "3.12" + cache: pip + + - name: Install tox-uv + run: python -Im pip install tox-uv + - name: Build docs + run: python -Im tox run -e docs,changelog + + - uses: actions/upload-artifact@v4 + with: + name: docs + path: docs/_build/ + + install-dev: + name: Verify install env + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version-default + cache: pip + + - name: Install in dev mode & import + run: | + python -Im pip install -e .[dev] + python -Ic 'import subliminal; print(subliminal.__version__)' + + build-package: + name: Build & verify package + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: hynek/build-and-inspect-python-package@v2 + id: baipp + + outputs: + # Used to define the matrix for tests below. The value is based on + # packaging metadata (trove classifiers). + supported-python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }} + + test: + name: Test + runs-on: ${{ matrix.os }} + needs: build-package + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + # Created by the build-and-inspect-python-package action above. + python-version: ${{ fromJson(needs.build-package.outputs.supported-python-versions) }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + allow-prereleases: true + - name: Install tox-uv + run: python -Im pip install tox-uv + + - name: Run tests + env: + # Make sure to add `passenv=COVERAGE_FILE` to `[testenv]` in tox.ini + COVERAGE_FILE: ".coverage.${{ matrix.os }}.${{ matrix.python-version }}" + run: >- + uvx --with=tox-uv + tox run + -e py${{ matrix.python-version }}-coverage + + - name: Store coverage file + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.os }}-${{ matrix.python-version }} + path: .coverage* + include-hidden-files: true + if-no-files-found: error + + coverage: + name: Coverage + runs-on: ubuntu-latest + needs: test + permissions: + pull-requests: write + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version-default + - uses: hynek/setup-cached-uv@v2 + + - name: Download coverage data + uses: actions/download-artifact@v4 + with: + pattern: coverage-* + merge-multiple: true + + - name: Combine coverage + run: | + uv tool install 'coverage[toml]>7' + + coverage combine + coverage html --skip-covered --skip-empty + + # Report and write to summary. + coverage report --skip-covered --skip-empty --show-missing --format=markdown >> $GITHUB_STEP_SUMMARY + + ## Report again and fail if under 100%. + #coverage report --fail-under=100 + + - name: Upload HTML report if check failed. + uses: actions/upload-artifact@v4 + with: + name: html-report + path: htmlcov + #if: ${{ failure() }} + + - name: Coverage comment + id: coverage_comment + uses: py-cov-action/python-coverage-comment-action@v3 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Store Pull Request comment to be posted + uses: actions/upload-artifact@v4 + if: steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true' + with: + name: python-coverage-comment-action + path: python-coverage-comment-action.txt + + + # Ensure everything required is passing for branch protection. + required-checks-pass: + if: always() + + needs: + - coverage + - docs + - install-dev + - typing + - pre-commit + - check-manifest + + runs-on: ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml deleted file mode 100644 index 2f3376ca..00000000 --- a/.github/workflows/check.yaml +++ /dev/null @@ -1,130 +0,0 @@ -name: CI - -on: - push: - branches: [main] - pull_request: {} - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - check-manifest: - if: github.event_name != 'schedule' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: pipx run check-manifest - - pre-commit: - if: github.event_name != 'schedule' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - uses: pre-commit/action@v3.0.1 - - typing: - if: github.event_name != 'schedule' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: install - run: | - python -m pip install -U pip - python -m pip install -e ".[test]" - - name: type-check - run: | - mypy --install-types --non-interactive --config-file pyproject.toml subliminal tests - - docs: - if: github.event_name != 'schedule' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - run: | - python -m venv venv - source ./venv/bin/activate - python -m pip install -e .[docs] - sphinx-build -b html docs/ docs/_build - - uses: actions/upload-artifact@v4 - with: - name: docs - path: docs/_build/ - - test: - name: Test - if: github.event_name != 'schedule' - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - exclude: - # macos-latest only work with arm64 - - os: macos-latest - python-version: ["3.8", "3.9"] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - - name: install - run: | - python -m pip install --upgrade pip - pip install -e .[test] - - name: run pytest --cov - env: - COVERAGE_FILE: ".coverage.${{ matrix.os }}.${{ matrix.python-version }}" - # Alternatively you can run coverage with the --parallel flag or add - # `parallel = True` in the coverage config file. - # If using pytest-cov, you can also add the `--cov-append` flag - # directly or through PYTEST_ADD_OPTS. - run: | - pytest --cov --cov-report=xml - - name: Store coverage file - uses: actions/upload-artifact@v4 - with: - name: coverage-${{ matrix.os }}-${{ matrix.python-version }} - path: .coverage.${{ matrix.os }}.${{ matrix.python-version }} - - coverage: - name: Coverage - runs-on: ubuntu-latest - needs: test - permissions: - pull-requests: write - contents: write - steps: - - uses: actions/checkout@v4 - - - uses: actions/download-artifact@v4 - id: download - with: - pattern: coverage-* - merge-multiple: true - - - name: Coverage comment - id: coverage_comment - uses: py-cov-action/python-coverage-comment-action@v3 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MERGE_COVERAGE_FILES: true - - - name: Store Pull Request comment to be posted - uses: actions/upload-artifact@v4 - if: steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true' - with: - name: python-coverage-comment-action - path: python-coverage-comment-action.txt diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml deleted file mode 100644 index 42c8a279..00000000 --- a/.github/workflows/coverage.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: Post coverage comment - -on: - workflow_run: - workflows: ["CI"] - types: - - completed - -jobs: - test: - name: Run tests & display coverage - runs-on: ubuntu-latest - if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' - permissions: - # Gives the action the necessary permissions for publishing new - # comments in pull requests. - pull-requests: write - # Gives the action the necessary permissions for editing existing - # comments (to avoid publishing multiple comments in the same PR) - contents: write - # Gives the action the necessary permissions for looking up the - # workflow that launched this workflow, and download the related - # artifact that contains the comment to be published - actions: read - steps: - # DO NOT run actions/checkout here, for security reasons - # For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - - name: Post comment - uses: py-cov-action/python-coverage-comment-action@v3 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }} - # Update those if you changed the default values: - # COMMENT_ARTIFACT_NAME: python-coverage-comment-action - # COMMENT_FILENAME: python-coverage-comment-action.txt diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/schedule-tests.yaml similarity index 88% rename from .github/workflows/run-tests.yaml rename to .github/workflows/schedule-tests.yaml index fc7370e1..2e8c24f5 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/schedule-tests.yaml @@ -11,7 +11,7 @@ concurrency: jobs: test-api: name: Test APIs - if: github.event_name == 'schedule' + if: github.repository == 'Diaoul/subliminal' && github.event_name == 'schedule' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 34bbb323..0082f1ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,12 +62,15 @@ docs = [ "towncrier", ] test = [ + "coverage[toml]>=7", "mypy", + "types-requests", + "types-beautifulsoup4", "pytest>=6.0", "pytest-cov", "pytest-xdist", "sympy", - "vcrpy>=1.6.1", + "vcrpy>=5", "importlib_metadata>=4.6; python_version<'3.10'", ] dev = [ @@ -159,6 +162,9 @@ omit = [ "subliminal/cli.py", ] +[tool.coverage.paths] +source = [".tox/py*/**/site-packages"] + [tool.coverage.run] source = ["subliminal"] branch = true diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 0770df7b..6f162ead 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -77,7 +77,7 @@ def test_registrable_extension_manager_register(): eps = manager.list_entry_points() ep_names = [ep.name for ep in eps] - assert ep_names == ['addic7ed', 'opensubtitles', 'de7cidda'] + assert ep_names == ['addic7ed', 'bsplayer', 'opensubtitles', 'de7cidda'] # Raise ValueError on same entry point with pytest.raises(ValueError, match='Extension already registered'): diff --git a/tests/test_video.py b/tests/test_video.py index a1d076a4..b630edfc 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -14,7 +14,8 @@ def test_video_exists_age(movies, tmpdir, monkeypatch): video = movies['man_of_steel'] tmpdir.ensure(video.name).setmtime(timestamp(datetime.now(timezone.utc) - timedelta(days=3))) assert video.exists - assert timedelta(days=3) <= video.age < timedelta(days=3, seconds=1) + with pytest.deprecated_call(): + assert timedelta(days=3) <= video.age < timedelta(days=3, seconds=1) def test_video_age(movies): diff --git a/tox.ini b/tox.ini index 32f48887..e6fcd8c3 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,6 @@ env_list = docs, changelog, - [testenv:.pkg] pass_env = SETUPTOOLS_SCM_PRETEND_VERSION @@ -17,12 +16,26 @@ pass_env = SETUPTOOLS_SCM_PRETEND_VERSION package = wheel wheel_build_env = .pkg extras = test +pass_env = + COVERAGE_FILE +allowlist_externals = + echo + false +# This and the next few testenvs are a workaround for +# https://github.com/tox-dev/tox/issues/2858. +commands = + echo "Unrecognized environment name {envname}" + false [testenv:tests] commands = pytest {posargs:-n auto} +[testenv:py3{,.}{8,9,10,11,12}-tests] +commands = {[testenv:tests]commands} + + [testenv:mypy] commands = mypy --install-types --non-interactive {posargs:subliminal tests} @@ -32,7 +45,14 @@ usedevelop = true deps = coverage[toml]>=7 set_env = COVERAGE_PROCESS_START = pyproject.toml -commands = pytest --cov --cov-report=term-missing:skip-covered {posargs:-n auto} +commands = + python -m pytest --cov=subliminal --cov-report= --cov-fail-under=0 {posargs:-n auto} + coverage report --skip-covered --show-missing --fail-under=80 + + +[testenv:py3{,.}{8,9,10,11,12}-coverage] +commands = {[testenv:coverage]commands} + [testenv:coverage-core] usedevelop = true @@ -40,10 +60,18 @@ deps = coverage[toml]>=7 set_env = COVERAGE_PROCESS_START = pyproject.toml commands = - pytest --ignore=tests/refiners --ignore=tests/providers --cov --cov-report= --cov-fail-under=0 {posargs:-n auto} + python -m pytest \ + --ignore=tests/refiners --ignore=tests/providers \ + --cov=subliminal --cov-report= --cov-fail-under=0 \ + {posargs:-n auto} coverage report \ --omit='subliminal/cli.py,subliminal/converters/*,subliminal/providers/*,subliminal/refiners/*' \ - --skip-covered --fail-under=100 + --skip-covered --show-missing --fail-under=100 + + +[testenv:py3{,.}{8,9,10,11,12}-coverage-core] +commands = {[testenv:coverage-core]commands} + [testenv:pre-commit] description = @@ -58,8 +86,6 @@ setenv = [testenv:docs] -package = wheel -wheel_build_env = .pkg # Keep base_python in-sync with check.yaml/docs and .readthedocs.yaml. base_python = py312 extras = docs @@ -73,5 +99,4 @@ commands = [testenv:changelog] extras = docs allowlist_externals = towncrier -skip_install = true commands = towncrier build --version main --draft