diff --git a/.circleci/config.yml b/.circleci/config.yml index 44bffeafb2..15d3624075 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ jobs: auth: username: $DOCKERHUB_USERNAME password: $DOCKERHUB_PASSWORD - parallelism: 5 + parallelism: 6 steps: - checkout - run: | @@ -19,7 +19,8 @@ jobs: 1) component=components/notifier;; 2) component=components/api_server;; 3) component=components/shared_code;; - 4) component=tests/feature_tests;; + 4) component=tests/application_tests;; + 5) component=tests/feature_tests;; esac cd $component mkdir -p build @@ -36,6 +37,10 @@ jobs: path: components/api_server/build - store_artifacts: path: components/shared_code/build + - store_artifacts: + path: components/application_tests/build + - store_artifacts: + path: components/feature_tests/build unittest_frontend: docker: @@ -65,6 +70,18 @@ jobs: ci/unittest.sh ci/quality.sh + unittest_release: + machine: + image: default + steps: + - checkout + - run: | + cd release + python3 -m venv venv + . venv/bin/activate + ci/pip-install.sh + ci/quality.sh + application_tests: machine: image: default @@ -119,6 +136,8 @@ workflows: context: QualityTime - unittest_docs: context: QualityTime + - unittest_release: + context: QualityTime - docker/hadolint: context: QualityTime dockerfiles: "components/collector/Dockerfile:components/database/Dockerfile:\ diff --git a/.github/workflows/application-tests-quality.yml b/.github/workflows/application-tests-quality.yml new file mode 100644 index 0000000000..0e430c4dfa --- /dev/null +++ b/.github/workflows/application-tests-quality.yml @@ -0,0 +1,27 @@ +name: Application tests quality + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.7 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies + run: | + cd tests/application_tests + ci/pip-install.sh + - name: Test + run: | + cd tests/application_tests + ci/unittest.sh + - name: Quality + run: | + cd tests/application_tests + ci/quality.sh diff --git a/.github/workflows/release-quality.yml b/.github/workflows/release-quality.yml new file mode 100644 index 0000000000..8b37655c49 --- /dev/null +++ b/.github/workflows/release-quality.yml @@ -0,0 +1,22 @@ +name: Release script quality + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.7 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies and run quality checks + run: | + cd release + python -m venv venv + . venv/bin/activate + ci/pip-install.sh + ci/quality.sh diff --git a/components/api_server/ci/quality.sh b/components/api_server/ci/quality.sh index 12a07f1a48..a759e2ebad 100755 --- a/components/api_server/ci/quality.sh +++ b/components/api_server/ci/quality.sh @@ -15,15 +15,6 @@ run pipx run `spec mypy` --python-executable=$(which python) src # pip-audit run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements.txt -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/components/api_server/pyproject.toml b/components/api_server/pyproject.toml index cdb611f7dd..1d6317ea68 100644 --- a/components/api_server/pyproject.toml +++ b/components/api_server/pyproject.toml @@ -29,7 +29,6 @@ tools = [ "mypy==1.10.0", "pip-audit==2.7.3", "ruff==0.4.8", - "safety==3.2.3", "vulture==2.11" ] diff --git a/components/collector/ci/quality.sh b/components/collector/ci/quality.sh index 8933d67612..089f01ea42 100755 --- a/components/collector/ci/quality.sh +++ b/components/collector/ci/quality.sh @@ -16,15 +16,6 @@ run pipx run `spec mypy` --python-executable=$(which python) src tests # See https://github.com/aio-libs/aiohttp/issues/6772 for why we ignore the CVE run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements.txt -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/components/collector/pyproject.toml b/components/collector/pyproject.toml index b15d759577..593b5b7b9e 100644 --- a/components/collector/pyproject.toml +++ b/components/collector/pyproject.toml @@ -29,7 +29,6 @@ tools = [ "mypy==1.10.0", "pip-audit==2.7.3", "ruff==0.4.8", - "safety==3.2.3", "vulture==2.11" ] diff --git a/components/notifier/ci/quality.sh b/components/notifier/ci/quality.sh index e252158282..683d5d1682 100755 --- a/components/notifier/ci/quality.sh +++ b/components/notifier/ci/quality.sh @@ -16,15 +16,6 @@ run pipx run `spec mypy` --python-executable=$(which python) src # See https://github.com/aio-libs/aiohttp/issues/6772 for why we ignore the CVE run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements.txt -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/components/notifier/pyproject.toml b/components/notifier/pyproject.toml index ffee5867fc..8bd32795eb 100644 --- a/components/notifier/pyproject.toml +++ b/components/notifier/pyproject.toml @@ -22,7 +22,6 @@ tools = [ "mypy==1.10.0", "pip-audit==2.7.3", "ruff==0.4.8", - "safety==3.2.3", "vulture==2.11" ] diff --git a/components/shared_code/ci/quality.sh b/components/shared_code/ci/quality.sh index 599ef9a839..b87b7a8cc0 100755 --- a/components/shared_code/ci/quality.sh +++ b/components/shared_code/ci/quality.sh @@ -18,15 +18,6 @@ run $PIPX_BIN_DIR/mypy src --python-executable=$(which python) # pip-audit run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/components/shared_code/pyproject.toml b/components/shared_code/pyproject.toml index 756ee5d689..0ddfa708c9 100644 --- a/components/shared_code/pyproject.toml +++ b/components/shared_code/pyproject.toml @@ -26,7 +26,6 @@ tools = [ "pip-audit==2.7.3", "pydantic==2.7.4", # Needed because pipx needs to inject Pydantic into the mpyp venv, see ci/quality.sh "ruff==0.4.8", - "safety==3.2.3", "vulture==2.11" ] diff --git a/docs/ci/quality.sh b/docs/ci/quality.sh index 1745cbb158..e434ca8c78 100755 --- a/docs/ci/quality.sh +++ b/docs/ci/quality.sh @@ -18,6 +18,9 @@ run pipx install --force `spec mypy` # --force works around this bug: https://g run pipx inject mypy `spec pydantic` run $PIPX_BIN_DIR/mypy src --python-executable=$(which python) +# Pyproject-fmt +run pipx run `spec pyproject-fmt` --check pyproject.toml + # Vale run pipx run `spec vale` sync run pipx run `spec vale` --no-wrap src/*.md @@ -25,8 +28,8 @@ run pipx run `spec vale` --no-wrap src/*.md # pip-audit run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt -# Safety -run pipx run `spec bandit` --quiet --recursive src/ +# Bandit +run pipx run `spec bandit` --configfile pyproject.toml --quiet --recursive src/ # Vulture run pipx run `spec vulture` --min-confidence 0 src/ tests/ .vulture_ignore_list.py diff --git a/docs/pyproject.toml b/docs/pyproject.toml index c6fa866de5..0dd5e5fe26 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -1,36 +1,94 @@ [project] name = "docs" version = "5.13.0" +requires-python = ">=3.12" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", +] dependencies = [ "furo==2023.9.10", "gitpython==3.1.43", "myst-parser==2.0.0", - "pydantic==2.7.4", # Needed for generating the reference docs from the data model - "Sphinx==7.2.6", + "pydantic==2.7.4", # Needed for generating the reference docs from the data model + "sphinx==7.2.6", "sphinx-copybutton==0.5.2", - "sphinx_design==0.5.0" + "sphinx-design==0.5.0", ] - -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "coverage==7.3.4", "pip==24.0", + "pip-tools==7.4.1", # To add hashes to requirements "pipx==1.6.0", - "pip-tools==7.4.1", # To add hashes to requirements - "unittest-xml-reporting==3.2.0", # Needed to generate JUnit XML output for Sonarcloud.io + "unittest-xml-reporting==3.2.0", # Needed to generate JUnit XML output for Sonarcloud.io ] -tools = [ +optional-dependencies.tools = [ "bandit==1.7.9", "fixit==2.1.0", "mypy==1.10.0", "pip-audit==2.7.3", - "pydantic==2.7.4", # Needed because pipx needs to inject Pydantic into the mpyp venv, see ci/quality.sh + "pydantic==2.7.4", # Needed because pipx needs to inject Pydantic into the mpyp venv, see ci/quality.sh + "pyproject-fmt==2.1.3", "ruff==0.4.8", - "safety==3.2.3", - "vale==3.0.3.0", # Documentation grammar and style checker - "vulture==2.11" + "vale==3.0.3.0", # Documentation grammar and style checker + "vulture==2.11", ] +[tool.ruff] +target-version = "py312" +line-length = 120 +src = [ + "src", +] +lint.select = [ + "ALL", +] +lint.ignore = [ + "ANN101", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - type checkers can infer the type of `self`, so annotating it is superfluous + "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter + "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` + "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` + "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" + "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter + "PD", # https://docs.astral.sh/ruff/rules/#pandas-vet-pd - pandas isn't used + "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - pytest isn't used +] +lint.per-file-ignores.".vulture_ignore_list.py" = [ + "ALL", +] +lint.per-file-ignores."__init__.py" = [ + "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files +] +lint.per-file-ignores."src/conf.py" = [ + "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is a configuration file +] +lint.per-file-ignores."src/create_reference_md.py" = [ + "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is a script +] +lint.per-file-ignores."tests/**/*.py" = [ + "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - don't require test functions to have return types +] +lint.isort.section-order = [ + "future", + "standard-library", + "third-party", + "second-party", + "first-party", + "tests", + "local-folder", +] +lint.isort.sections.second-party = [ + "shared", + "shared_data_model", +] +lint.isort.sections.tests = [ + "tests", +] + +[tool.pyproject-fmt] +indent = 4 +keep_full_version = true # Remove trailing zero's from version specifiers? + [tool.mypy] plugins = "pydantic.mypy" ignore_missing_imports = false @@ -46,43 +104,3 @@ generate_hashes = true quiet = true strip_extras = true upgrade = true - -[tool.ruff] -target-version = "py312" -line-length = 120 -src = ["src"] - -[tool.ruff.lint] -select = ["ALL"] -ignore = [ - "ANN101", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - type checkers can infer the type of `self`, so annotating it is superfluous - "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter - "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` - "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` - "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" - "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter - "PD", # https://docs.astral.sh/ruff/rules/#pandas-vet-pd - pandas isn't used - "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - pytest isn't used -] - -[tool.ruff.lint.isort] -section-order = ["future", "standard-library", "third-party", "second-party", "first-party", "tests", "local-folder"] - -[tool.ruff.lint.isort.sections] -"second-party" = ["shared", "shared_data_model"] -"tests" = ["tests"] - -[tool.ruff.lint.per-file-ignores] -".vulture_ignore_list.py" = ["ALL"] -"__init__.py" = [ - "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files -] -"src/conf.py" = [ - "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is a configuration file -] -"src/create_reference_md.py" = [ - "INP001", # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ - false positive because this is a script -] -"tests/**/*.py" = [ - "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - don't require test functions to have return types -] diff --git a/release/ci/quality.sh b/release/ci/quality.sh new file mode 100755 index 0000000000..989e72fd71 --- /dev/null +++ b/release/ci/quality.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +source ../ci/base.sh + +# Ruff +run pipx run `spec ruff` check . +run pipx run `spec ruff` format --check . + +# Fixit +run pipx run `spec fixit` lint *.py + +# Mypy +run pipx run `spec mypy` --python-executable=$(which python) *.py + +# Pyproject-fmt +run pipx run `spec pyproject-fmt` --check pyproject.toml + +# pip-audit +run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt + +# Bandit +run pipx run `spec bandit` --configfile pyproject.toml --quiet --recursive *.py + +# Vulture +run pipx run `spec vulture` --min-confidence 0 *.py diff --git a/release/pyproject.toml b/release/pyproject.toml index 4300cb70b2..6da6cf2906 100644 --- a/release/pyproject.toml +++ b/release/pyproject.toml @@ -1,17 +1,50 @@ [project] name = "release" version = "5.13.0" +requires-python = ">=3.12" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", +] dependencies = [ "bump-my-version==0.22.0.post197.dev9", "gitpython==3.1.43", ] - -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "pip==24.0", - "pip-tools==7.4.1" # To add hashes to requirements + "pip-tools==7.4.1", # To add hashes to requirements +] +optional-dependencies.tools = [ + "bandit==1.7.9", + "fixit==2.1.0", + "mypy==1.10.0", + "pip-audit==2.7.3", + "pydantic==2.7.4", # Needed because pipx needs to inject Pydantic into the mpyp venv, see ci/quality.sh + "pyproject-fmt==2.1.3", + "ruff==0.4.8", + "vulture==2.11", ] +[tool.ruff] +target-version = "py312" +line-length = 120 +src = [ + "src", +] +lint.select = [ + "ALL", +] +lint.ignore = [ + "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter + "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` + "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` + "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter +] + +[tool.pyproject-fmt] +indent = 4 +keep_full_version = true # Remove trailing zero's from version specifiers? + [tool.bumpversion] current_version = "5.13.0" parse = """(?x) @@ -33,7 +66,10 @@ commit = true tag = true [tool.bumpversion.parts.pre_release_label] -values = ["rc", "final"] +values = [ + "rc", + "final", +] optional_value = "final" [[tool.bumpversion.files]] @@ -76,6 +112,12 @@ glob = "../**/pyproject.toml" search = 'version = "{current_version}"' replace = 'version = "{new_version}"' +[tool.bandit] +skips = [ + "B404", # Consider possible security implications associated with the subprocess module. + "B603", # subprocess call - check for execution of untrusted input. +] + [tool.pip-tools] allow_unsafe = true generate_hashes = true diff --git a/release/release.py b/release/release.py index 2faddc037c..423efaa5b2 100755 --- a/release/release.py +++ b/release/release.py @@ -26,9 +26,9 @@ def get_version() -> str: with pathlib.Path(release_folder / "pyproject.toml").open(mode="rb") as py_project_toml_fp: py_project_toml = tomllib.load(py_project_toml_fp) version_re = py_project_toml["tool"]["bumpversion"]["parse"] - version_tags = [tag for tag in repo.tags if re.match(version_re, tag.tag.tag.strip("v"), re.MULTILINE)] + version_tags = [tag for tag in repo.tags if tag.tag and re.match(version_re, tag.tag.tag.strip("v"), re.MULTILINE)] latest_tag = sorted(version_tags, key=lambda tag: tag.commit.committed_datetime)[-1] - return latest_tag.tag.tag.strip("v") + return latest_tag.tag.tag.strip("v") if latest_tag.tag else "?" def parse_arguments() -> tuple[str, str, bool]: @@ -44,13 +44,26 @@ def parse_arguments() -> tuple[str, str, bool]: - the changelog has an '[Unreleased]' header - the changelog contains no release candidates - the new release has been added to the version overview""" - parser = ArgumentParser(description=description, epilog=epilog, formatter_class=RawDescriptionHelpFormatter) - allowed_bumps_in_rc_mode = ["rc", "rc-major", "rc-minor", "rc-patch", "drop-rc"] # rc = release candidate + parser = ArgumentParser( + description=description, + epilog=epilog, + formatter_class=RawDescriptionHelpFormatter, + ) + allowed_bumps_in_rc_mode = [ + "rc", + "rc-major", + "rc-minor", + "rc-patch", + "drop-rc", + ] # rc = release candidate allowed_bumps = ["rc-patch", "rc-minor", "rc-major", "patch", "minor", "major"] bumps = allowed_bumps_in_rc_mode if "rc" in current_version else allowed_bumps parser.add_argument("bump", choices=bumps) parser.add_argument( - "-c", "--check-preconditions-only", action="store_true", help="only check the preconditions and then exit" + "-c", + "--check-preconditions-only", + action="store_true", + help="only check the preconditions and then exit", ) arguments = parser.parse_args() return arguments.bump, current_version, arguments.check_preconditions_only @@ -117,7 +130,7 @@ def failed_preconditions_version_overview(current_version: str, root: pathlib.Pa for line in version_overview_lines: if line.startswith(f"| v{current_version} "): if previous_line.startswith("| v"): - today = datetime.date.today().isoformat() + today = utc_today().isoformat() release_date = previous_line.split(" | ")[1].strip() if release_date != today: # Second column is the release date column return [f"{missing} the release date. Expected today: '{today}', found: '{release_date}'."] @@ -127,13 +140,18 @@ def failed_preconditions_version_overview(current_version: str, root: pathlib.Pa return [f"{missing} the current version ({current_version})."] +def utc_today() -> datetime.date: + """Return today in UTC.""" + return datetime.datetime.now(tz=datetime.UTC).date() + + def main() -> None: """Create the release.""" - os.environ["RELEASE_DATE"] = datetime.date.today().isoformat() # Used by bump-my-version to update CHANGELOG.md + os.environ["RELEASE_DATE"] = utc_today().isoformat() # Used by bump-my-version to update CHANGELOG.md bump, current_version, check_preconditions_only = parse_arguments() check_preconditions(bump, current_version) if check_preconditions_only: - return + return # See https://github.com/callowayproject/bump-my-version?tab=readme-ov-file#add-support-for-pre-release-versions # for how bump-my-version deals with pre-release versions if bump.startswith("rc-"): @@ -142,8 +160,8 @@ def main() -> None: bump = "pre_release_label" # Bump the pre-release label from "rc" to "final" (which is optional and omitted) elif bump == "rc": bump = "pre_release_number" # Bump the release candidate number - subprocess.run(("bump-my-version", "bump", bump), check=True) - subprocess.run(("git", "push", "--follow-tags"), check=True) + subprocess.run(("bump-my-version", "bump", bump), check=True) # noqa: S603 + subprocess.run(("git", "push", "--follow-tags"), check=True) # noqa: S603 if __name__ == "__main__": diff --git a/tests/application_tests/ci/quality.sh b/tests/application_tests/ci/quality.sh new file mode 100755 index 0000000000..2b91923801 --- /dev/null +++ b/tests/application_tests/ci/quality.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +source ../../ci/base.sh + +# Ruff +run pipx run `spec ruff` check . +run pipx run `spec ruff` format --check . + +# Fixit +run pipx run `spec fixit` lint src + +# Mypy +run pipx run `spec mypy` --python-executable=$(which python) src + +# Pyproject-fmt +run pipx run `spec pyproject-fmt` --check pyproject.toml + +# pip-audit +run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements.txt -r requirements/requirements-dev.txt + +# Bandit +run pipx run `spec bandit` --configfile pyproject.toml --quiet --recursive src + +# Vulture +run pipx run `spec vulture` --min-confidence 0 src diff --git a/tests/application_tests/ci/unittest.sh b/tests/application_tests/ci/unittest.sh new file mode 100755 index 0000000000..9a4802fb92 --- /dev/null +++ b/tests/application_tests/ci/unittest.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +# Dummy unittest.sh so we can run the same steps for different components. diff --git a/tests/application_tests/pyproject.toml b/tests/application_tests/pyproject.toml index d4f018d2ee..d07fc42aca 100644 --- a/tests/application_tests/pyproject.toml +++ b/tests/application_tests/pyproject.toml @@ -1,17 +1,75 @@ [project] -name = "application_tests" +name = "application-tests" version = "5.13.0" +requires-python = ">=3.12" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", +] dependencies = [ "axe-selenium-python==2.1.6", "requests==2.32.3", - "selenium==4.21.0" + "selenium==4.21.0", ] - -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "pip==24.0", - "pip-tools==7.4.1" # To add hashes to requirements + "pip-tools==7.4.1", # To add hashes to requirements + "types-requests==2.32.0.20240602", +] +optional-dependencies.tools = [ + "bandit==1.7.9", + "fixit==2.1.0", + "mypy==1.10.0", + "pip-audit==2.7.3", + "pyproject-fmt==2.1.3", + "ruff==0.4.8", + "vulture==2.11", +] + +[tool.ruff] +target-version = "py312" +line-length = 120 +src = [ + "src", +] +lint.select = [ + "ALL", +] +lint.ignore = [ + "ANN001", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - too many untyped arguments atm to turn this rule on + "ANN101", # https://docs.astral.sh/ruff/rules/missing-type-function-argument/ - type checkers can infer the type of `self`, so annotating it is superfluous + "ANN201", # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/ - too many untyped return values atm to turn this rule on + "ANN204", # https://docs.astral.sh/ruff/rules/missing-return-type-special-method/ - typing classes that inherit from set and list correctly is surprisingly hard + "COM812", # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - this rule may cause conflicts when used with the ruff formatter + "D107", # https://docs.astral.sh/ruff/rules/undocumented-public-init/ - requiring __init__() methods to have docstrings seems a bit much + "D203", # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/ - prevent warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class` + "D213", # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/ - prevent warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line` + "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - not sure of the value of preventing "boolean traps" + "ISC001", # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ - this rule may cause conflicts when used with the ruff formatter + "PD", # https://docs.astral.sh/ruff/rules/#pandas-vet-pd - pandas isn't used + "PT", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - pytest isn't used +] +lint.per-file-ignores."__init__.py" = [ + "D104", # https://docs.astral.sh/ruff/rules/undocumented-public-package/ - don't require doc strings in __init__.py files +] + +[tool.pyproject-fmt] +indent = 4 +keep_full_version = true # Remove trailing zero's from version specifiers? + +[tool.mypy] +ignore_missing_imports = false +incremental = false +warn_redundant_casts = true +warn_return_any = true +warn_unreachable = true +warn_unused_ignores = true + +[[tool.mypy.overrides]] +module = [ + "axe_selenium_python", ] +ignore_missing_imports = true [tool.pip-tools] allow_unsafe = true diff --git a/tests/application_tests/requirements/requirements-dev.txt b/tests/application_tests/requirements/requirements-dev.txt index 6ee13d3e1d..d6d2a4e733 100644 --- a/tests/application_tests/requirements/requirements-dev.txt +++ b/tests/application_tests/requirements/requirements-dev.txt @@ -195,6 +195,10 @@ trio-websocket==0.11.1 \ --hash=sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f \ --hash=sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638 # via selenium +types-requests==2.32.0.20240602 \ + --hash=sha256:3f98d7bbd0dd94ebd10ff43a7fbe20c3b8528acace6d8efafef0b6a184793f06 \ + --hash=sha256:ed3946063ea9fbc6b5fc0c44fa279188bae42d582cb63760be6cb4b9d06c3de8 + # via application_tests (pyproject.toml) typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 @@ -205,6 +209,7 @@ urllib3==2.2.1 \ # via # requests # selenium + # types-requests wheel==0.43.0 \ --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 diff --git a/tests/application_tests/src/test_api.py b/tests/application_tests/src/test_api.py index 9ac89832f1..0897e53d95 100644 --- a/tests/application_tests/src/test_api.py +++ b/tests/application_tests/src/test_api.py @@ -11,5 +11,5 @@ class ApiTest(unittest.TestCase): def test_documentation(self): """Test that the documentation API is available.""" apis = requests.get("http://www:8080/api", timeout=10).json().keys() - self.assertTrue("/api/internal/login" in apis) - self.assertTrue("/api/v3/login" in apis) + self.assertIn("/api/internal/login", apis) + self.assertIn("/api/v3/login", apis) diff --git a/tests/application_tests/src/test_report.py b/tests/application_tests/src/test_report.py index 1d318fc9fd..8ed3e00a6a 100644 --- a/tests/application_tests/src/test_report.py +++ b/tests/application_tests/src/test_report.py @@ -10,29 +10,31 @@ from selenium.webdriver.support.ui import WebDriverWait -class element_has_no_css_class: - """An expectation for checking that an element has no css class. +class ElementHasNoCCSClass: + """An expectation for checking that an element has no CSS class. locator - used to find the element - returns the WebElement once it has the particular css class + returns the WebElement once it has no particular CSS class """ - def __init__(self, locator): + def __init__(self, locator) -> None: self.locator = locator def __call__(self, driver): + """Return the element if it no longer has a CSS class, otherwise False.""" element = driver.find_element(*self.locator) return element if len(element.get_attribute("class")) == 0 else False -class nr_elements: +class NrElements: """An expectation for the number of matching elements.""" - def __init__(self, locator, expected_nr: int): + def __init__(self, locator, expected_nr: int) -> None: self.locator = locator self.expected_nr = expected_nr def __call__(self, driver): + """Return the element if it has the expected number of elements, otherwise False.""" elements = driver.find_elements(*self.locator) return elements if len(elements) == self.expected_nr else False @@ -60,7 +62,7 @@ def login(self): login_form.find_element(By.NAME, "username").send_keys("jadoe") login_form.find_element(By.NAME, "password").send_keys("secret") login_form.find_element(By.CLASS_NAME, "button").click() - self.wait.until(element_has_no_css_class((By.TAG_NAME, "body"))) # Wait for body dimmer to disappear + self.wait.until(ElementHasNoCCSClass((By.TAG_NAME, "body"))) # Wait for body dimmer to disappear def test_title(self): """Test the title.""" @@ -90,7 +92,7 @@ def test_add_report(self): self.login() nr_reports = len(self.driver.find_elements(By.CLASS_NAME, "card")) self.driver.find_element(By.CLASS_NAME, "button.primary").click() - self.wait.until(nr_elements((By.CLASS_NAME, "card"), nr_reports + 1)) + self.wait.until(NrElements((By.CLASS_NAME, "card"), nr_reports + 1)) def test_report_axe_accessibility(self): """Run axe accessibility check on a report.""" @@ -104,15 +106,16 @@ def test_report_axe_accessibility(self): # Process axe results violation_results = results1["violations"] - axe.write_results(results1, '../../build/a11y.json') + axe.write_results(results1, "../../build/a11y.json") readable_report = axe.report(violation_results) - filename = pathlib.Path('../../build/a11y_violations.txt') + filename = pathlib.Path("../../build/a11y_violations.txt") try: - with open(filename, "w", encoding="utf8") as report_file: + with filename.open("w", encoding="utf8") as report_file: report_file.write(readable_report) except OSError: - print("Could not write axe violations report") + self.fail("Could not write axe violations report") - # If there are violations, output the readable report data - # TODO - assertEqual 0 in https://github.com/ICTU/quality-time/issues/6354 - self.assertTrue(6 >= len(violation_results), readable_report) + # If there are moe violations than expected, output the readable report data + # Fixing the axe violations is on the backlog: https://github.com/ICTU/quality-time/issues/6354 + current_number_of_axe_violations = 6 + self.assertLessEqual(len(violation_results), current_number_of_axe_violations, readable_report) diff --git a/tests/feature_tests/ci/quality.sh b/tests/feature_tests/ci/quality.sh index 686861329f..4634fed717 100755 --- a/tests/feature_tests/ci/quality.sh +++ b/tests/feature_tests/ci/quality.sh @@ -15,15 +15,6 @@ run pipx run `spec mypy` --python-executable=$(which python) src # pip-audit run pipx run `spec pip-audit` --strict --progress-spinner=off -r requirements/requirements-dev.txt -# Safety -# Vulnerability ID: 67599 -# ADVISORY: ** DISPUTED ** An issue was discovered in pip (all versions) because it installs the version with the -# highest version number, even if the user had intended to obtain a private package from a private index. This only -# affects use of the --extra-index-url option, and exploitation requires that the... -# CVE-2018-20225 -# For more information about this vulnerability, visit https://data.safetycli.com/v/67599/97c -run pipx run `spec safety` check --bare --ignore 67599 -r requirements/requirements-dev.txt - # Bandit run pipx run `spec bandit` --quiet --recursive src/ diff --git a/tests/feature_tests/pyproject.toml b/tests/feature_tests/pyproject.toml index 97494d7e75..605d6cd4b5 100644 --- a/tests/feature_tests/pyproject.toml +++ b/tests/feature_tests/pyproject.toml @@ -25,7 +25,6 @@ tools = [ "mypy==1.10.0", "pip-audit==2.7.3", "ruff==0.4.8", - "safety==3.2.3", "vulture==2.11" ]