Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests for tasks #45

Merged
merged 6 commits into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions .github/workflows/_local_ci_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,56 @@ jobs:
name: Call reusable workflow
uses: ./.github/workflows/ci_tests.yml
with:
# general
python_version: "3.9"
install_extras: "[dev,docs]"
skip_pre-commit_hooks: pylint
pylint_options: --rcfile=pyproject.toml
pylint_targets: ci_cd
install_extras: "[dev,docs,testing]"

# pre-commit
skip_pre-commit_hooks: pylint,pylint-tests

# pylint
pylint_runs: |
--rcfile=pyproject.toml ci_cd
--rcfile=pyproject.toml --disable=import-outside-toplevel,redefined-outer-name tests

# build dist
build_libs: flit
build_cmd: flit build

# build docs
update_python_api_ref: false
update_docs_landing_page: false
debug: false

pytest:
name: pytest
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version}}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version}}

- name: Install Python dependencies
run: |
python -m pip install -U pip
pip install -U setuptools wheel flit
pip install -e .[testing]

- name: Test with pytest
run: pytest -vvv --cov=ci_cd --cov-report=xml

- name: Upload coverage to Codecov
if: matrix.python-version == '3.9' && github.repository == 'SINTEF/ci-cd'
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ __pycache__/
*.egg*/
build/

# Documentation
site/

# Coverage
.coverage
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ repos:
hooks:
- id: bandit
args: ["-r"]
exclude: ^tests/.*$

# mypy is a static typing linter
# The main code repository can be found at:
Expand All @@ -62,6 +63,23 @@ repos:
- id: pylint
name: pylint
entry: pylint
args: ["--rcfile=pyproject.toml"]
language: python
types: [python]
require_serial: true
exclude: ^tests/.*$
# pylint is a Python linter
# It is run through the local environment to ensure external packages can be
# imported without issue.
# For more information about pylint see its documentation at:
# https://pylint.pycqa.org/en/latest/
- id: pylint-tests
name: pylint - tests
entry: pylint
args:
- "--rcfile=pyproject.toml"
- "--disable=import-outside-toplevel,redefined-outer-name"
language: python
types: [python]
require_serial: true
files: ^tests/.*$
16 changes: 10 additions & 6 deletions ci_cd/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,10 +401,8 @@ def update_deps( # pylint: disable=too-many-branches,too-many-locals,too-many-s

# Update pyproject.toml
updated_version = ".".join(latest_version[: len(version.split("."))])
escaped_full_dependency_name = full_dependency_name.replace(
"[", "\[" # pylint: disable=anomalous-backslash-in-string
).replace(
"]", "\]" # pylint: disable=anomalous-backslash-in-string
escaped_full_dependency_name = full_dependency_name.replace("[", r"\[").replace(
"]", r"\]"
)
update_file(
pyproject_path,
Expand Down Expand Up @@ -500,6 +498,8 @@ def create_api_reference_docs( # pylint: disable=too-many-locals,too-many-branc
unwanted_folder: list[str] = ["__pycache__"]
if not unwanted_file:
unwanted_file: list[str] = ["__init__.py"]
if not full_docs_folder:
full_docs_folder: list[str] = []

def write_file(full_path: Path, content: str) -> None:
"""Write file with `content` to `full_path`"""
Expand Down Expand Up @@ -604,9 +604,13 @@ def write_file(full_path: Path, content: str) -> None:

basename = filename[: -len(".py")]
py_path = (
f"{package_dir.name}/{relpath}/{basename}".replace("/", ".")
f"{package_dir.relative_to(root_repo_path)}/{relpath}/{basename}".replace(
"/", "."
)
if str(relpath) != "."
else f"{package_dir.name}/{basename}".replace("/", ".")
else f"{package_dir.relative_to(root_repo_path)}/{basename}".replace(
"/", "."
)
)
md_filename = filename.replace(".py", ".md")
if debug:
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ docs = [
"mkdocs-material ~=8.4",
"mkdocstrings[python] ~=0.19.0",
]
testing = [
"pytest ~=7.1",
"pytest-cov ~=3.0",
]
dev = [
"pre-commit ~=2.20",
"pylint ~=2.13",
Expand Down
200 changes: 200 additions & 0 deletions tests/test_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
"""Test `ci_cd/tasks.py`."""
# pylint: disable=too-many-locals
import pytest


def test_create_api_reference() -> None:
"""Check create_api_reference_docs runs with defaults."""
import os
import shutil
from pathlib import Path
from tempfile import TemporaryDirectory

from invoke import MockContext

from ci_cd.tasks import create_api_reference_docs

with TemporaryDirectory() as tmpdir:
root_path = Path(tmpdir).resolve()
package_dir = root_path / "ci_cd"
shutil.copytree(
src=Path(__file__).resolve().parent.parent / "ci_cd",
dst=package_dir,
)

docs_folder = root_path / "docs"
docs_folder.mkdir()

create_api_reference_docs(
MockContext(),
str(package_dir),
root_repo_path=str(root_path),
)

api_reference_folder = docs_folder / "api_reference"

assert docs_folder.exists(), f"Parent content: {os.listdir(docs_folder.parent)}"
assert (
api_reference_folder.exists()
), f"Parent content: {os.listdir(api_reference_folder.parent)}"
assert {".pages", "main.md", "tasks.md"} == set(
os.listdir(api_reference_folder)
)

assert (api_reference_folder / ".pages").read_text(
encoding="utf8"
) == 'title: "API Reference"\n'
assert (api_reference_folder / "main.md").read_text(
encoding="utf8"
) == "# main\n\n::: ci_cd.main\n"
assert (api_reference_folder / "tasks.md").read_text(
encoding="utf8"
) == "# tasks\n\n::: ci_cd.tasks\n"


def test_api_reference_nested_package() -> None:
"""Check create_api_reference_docs generates correct link to sub-nested package
directory."""
import os
import shutil
from pathlib import Path
from tempfile import TemporaryDirectory

from invoke import MockContext

from ci_cd.tasks import create_api_reference_docs

with TemporaryDirectory() as tmpdir:
root_path = Path(tmpdir).resolve()
package_dir = root_path / "src" / "ci_cd" / "ci_cd"
shutil.copytree(
src=Path(__file__).resolve().parent.parent / "ci_cd",
dst=package_dir,
)

docs_folder = root_path / "docs"
docs_folder.mkdir()

create_api_reference_docs(
MockContext(),
str(package_dir),
root_repo_path=str(root_path),
)

api_reference_folder = docs_folder / "api_reference"

assert docs_folder.exists(), f"Parent content: {os.listdir(docs_folder.parent)}"
assert (
api_reference_folder.exists()
), f"Parent content: {os.listdir(api_reference_folder.parent)}"
assert {".pages", "main.md", "tasks.md"} == set(
os.listdir(api_reference_folder)
)

assert (api_reference_folder / ".pages").read_text(
encoding="utf8"
) == 'title: "API Reference"\n'
assert (api_reference_folder / "main.md").read_text(
encoding="utf8"
) == "# main\n\n::: src.ci_cd.ci_cd.main\n"
assert (api_reference_folder / "tasks.md").read_text(
encoding="utf8"
) == "# tasks\n\n::: src.ci_cd.ci_cd.tasks\n"


def test_update_deps() -> None:
"""Check update_deps runs with defaults."""
import re
from pathlib import Path
from tempfile import TemporaryDirectory

import tomlkit
from invoke import MockContext

from ci_cd.tasks import update_deps

original_dependencies = {
"invoke": "1.7",
"tomlkit": "0.11.4",
"mike": "1.1",
"pytest": "7.1",
"pytest-cov": "3.0",
"pre-commit": "2.20",
"pylint": "2.13",
}

with TemporaryDirectory() as tmpdir:
root_path = Path(tmpdir).resolve()
pyproject_file = root_path / "pyproject.toml"
pyproject_file.write_text(
data=f"""
[project]
requires-python = "~=3.7"

dependencies = [
"invoke ~={original_dependencies['invoke']}",
"tomlkit[test,docs] ~={original_dependencies['tomlkit']}",
]

[project.optional-dependencies]
docs = [
"mike >={original_dependencies['mike']},<3",
]
testing = [
"pytest ~={original_dependencies['pytest']}",
"pytest-cov ~={original_dependencies['pytest-cov']}",
]
dev = [
"mike >={original_dependencies['mike']},<3",
"pytest ~={original_dependencies['pytest']}",
"pytest-cov ~={original_dependencies['pytest-cov']}",
"pre-commit ~={original_dependencies['pre-commit']}",
"pylint ~={original_dependencies['pylint']}",
]
""",
encoding="utf8",
)

context = MockContext(
run={
re.compile(r".*invoke$"): "invoke (1.7.1)\n",
re.compile(r".*tomlkit$"): "tomlkit (1.0.0)",
re.compile(r".*mike$"): "mike (1.0.1)",
re.compile(r".*pytest$"): "pytest (7.1.0)",
re.compile(r".*pytest-cov$"): "pytest-cov (3.1.0)",
re.compile(r".*pre-commit$"): "pre-commit (2.20.0)",
re.compile(r".*pylint$"): "pylint (2.14.0)",
}
)

update_deps(
context,
root_repo_path=str(root_path),
)

pyproject = tomlkit.loads(pyproject_file.read_bytes())

dependencies: list[str] = pyproject.get("project", {}).get("dependencies", [])
for optional_deps in (
pyproject.get("project", {}).get("optional-dependencies", {}).values()
):
dependencies.extend(optional_deps)

for line in dependencies:
if any(
package_name in line
for package_name in ["invoke ", "pytest ", "pre-commit "]
):
package_name = line.split(maxsplit=1)[0]
assert line == f"{package_name} ~={original_dependencies[package_name]}"
elif "tomlkit" in line:
# Should be three version digits, since the original dependency had three.
assert line == "tomlkit[test,docs] ~=1.0.0"
elif "mike" in line:
assert line == "mike >=1.0,<3"
elif "pytest-cov" in line:
assert line == "pytest-cov ~=3.1"
elif "pylint" in line:
assert line == "pylint ~=2.14"
else:
pytest.fail(f"Unknown package in line: {line}")