diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index 257ba50..7782705 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -36,3 +36,15 @@ jobs: run: | pre-commit install pre-commit run --all-files --show-diff-on-failure --verbose --color=always + + - name: Test + run: | + pytest --cov --cov-report=term --cov-report=markdown:coverage.md + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-report + path: coverage.md + retention-days: 30 diff --git a/.github/workflows/pr_check_comment.yml b/.github/workflows/pr_check_comment.yml new file mode 100644 index 0000000..0c42e59 --- /dev/null +++ b/.github/workflows/pr_check_comment.yml @@ -0,0 +1,83 @@ +name: PR Checks - Post Coverage + +on: + workflow_run: + workflows: ["PR Pre Checks"] + types: + - completed + workflow_dispatch: + inputs: + run_id: + description: "Workflow run ID to fetch artifacts from" + required: true + type: string + pr_number: + description: "PR number to update" + required: true + type: string + +permissions: + actions: read + contents: read + pull-requests: write + +jobs: + post-coverage: + runs-on: ubuntu-latest + if: > + github.event_name == 'workflow_dispatch' || + (github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success') + + steps: + - name: Download coverage artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: coverage-report + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event_name == 'workflow_dispatch' && inputs.run_id || github.event.workflow_run.id }} + + - name: Read and Validate PR number + id: pr_number + env: + PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.workflow_run.pull_requests[0].number }} + run: | + if [ -z "$PR_NUMBER" ] || ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then + echo "Error: Could not determine PR number: $PR_NUMBER" + exit 1 + fi + echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_OUTPUT" + - name: Validate coverage report + run: | + if [ ! -f coverage.md ]; then + echo "Error: coverage.md file not found." + exit 1 + fi + FILE_SIZE=$(stat -c%s coverage.md) + if [ "$FILE_SIZE" -gt 65536 ]; then + echo "Error: coverage.md file size exceeds 64KB." + exit 1 + fi + + - name: Update PR body with coverage + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ steps.pr_number.outputs.PR_NUMBER }} + REPOSITORY: ${{ github.repository }} + run: | + # Fetch the current PR body + CURRENT_BODY=$(gh pr view "$PR_NUMBER" --repo "$REPOSITORY" --json body --jq '.body') + + # Strip previous coverage section if it exists + CLEAN_BODY=$(printf '%s\n' "$CURRENT_BODY" | sed '//,$d') + + # Build New PR body with coverage appended at the bottom + { + printf '%s\n' "$CLEAN_BODY" + echo "" + echo "## Coverage Report" + echo "" + cat coverage.md + } > new_body.md + + gh pr edit "$PR_NUMBER" --repo "$REPOSITORY" --body-file new_body.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fbd228d..dad4483 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,15 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.0 + rev: 745be0411e3c150e246d527e531e5a221b8c3462 # v0.15.19 hooks: # Run the linter. - id: ruff-check args: [ --fix ] # Run the formatter. - id: ruff-format +- repo: https://github.com/shellcheck-py/shellcheck-py + rev: 745eface02aef23e168a8afb6b5737818efbea95 # v0.11.0.1 + hooks: + - id: shellcheck + args: [ "-S", "error" ] diff --git a/ciq_tag.py b/ciq_tag.py index 450b68c..033b85f 100644 --- a/ciq_tag.py +++ b/ciq_tag.py @@ -398,10 +398,10 @@ def add_tag( # Find the first property which is 'greater' than inserted_tag in the sense that it appears # later in the CiqTag enum dictating the order in which properties are expected to occur # in a message. The inserted_tag will be inserted right before it, if it exists, or right after - # the last existing property, if any exists, or at the begginging of the message body otherwise. + # the last existing property, if any exists, or at the beginning of the message body otherwise. first_greater = mit.first_true( range(len(self._tags)), - pred=lambda i: (inserted_tag.get_order_num() < self._tags[i]._tag_type.get_order_num()), + pred=lambda i: inserted_tag.get_order_num() < self._tags[i]._tag_type.get_order_num(), default=len(self._tags), ) self._tags.insert( diff --git a/pyproject.toml b/pyproject.toml index 6a2805f..5d73931 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,8 +20,9 @@ dependencies = [ dev = [ "pre-commit", "ruff", - "pytest", - ] + "pytest", + "pytest-cov", +] [tool.ruff] line-length = 120 @@ -43,3 +44,16 @@ include = ["kt*"] [project.scripts] kt = "kt.kt:main" + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-ra -q" + +[tool.coverage.run] +source = ["."] +omit = ["tests/*", "venv/*"] +branch = true + +[tool.coverage.report] +show_missing = true +skip_empty = true diff --git a/tests/kt/ktlib/test_config.py b/tests/kt/ktlib/test_config.py index 419a6d4..dc1cef9 100644 --- a/tests/kt/ktlib/test_config.py +++ b/tests/kt/ktlib/test_config.py @@ -10,12 +10,15 @@ '"kernels_dir": "~/ciq/kernels",' '"images_source_dir": "~/ciq/default_test_images",' '"images_dir": "~/ciq/tmp/virt-images",' - '"ssh_key": "~/ciq/id_ed25519_generic.pub"' + '"ssh_key": "~/ciq/id_ed25519_generic.pub",' + '"user": "testuser"' "}" ) -def test_config_load_default_fallback(): +def test_config_load_default_fallback(monkeypatch): + # Remove the environment variable if it exists when running local tests + monkeypatch.delenv("KTOOLS_CONFIG_FILE", raising=False) config = Config.load() assert config.base_path == DEFAULT_CONFIG["base_path"] @@ -47,7 +50,7 @@ def test_config_load_from_json_data_None(): def test_config_load_from_json_data_empty(): json_data = "{}" - with pytest.raises(TypeError, match="missing 5 required positional arguments:"): + with pytest.raises(TypeError, match="missing 6 required positional arguments:"): config = Config.from_json(json_data) # noqa F841 @@ -74,3 +77,8 @@ def test_config_load_from_json_proper_images_dir(): def test_config_load_from_json_proper_ssh_key(): config = Config.from_json(CONFIG_STR) assert config.ssh_key == Path("~/ciq/id_ed25519_generic.pub").expanduser() + + +def test_config_load_from_json_proper_user(): + config = Config.from_json(CONFIG_STR) + assert config.user == "testuser" diff --git a/tests/kt/ktlib/test_kernels.py b/tests/kt/ktlib/test_kernels.py index f502e12..1b9c2b3 100644 --- a/tests/kt/ktlib/test_kernels.py +++ b/tests/kt/ktlib/test_kernels.py @@ -12,6 +12,8 @@ "src_tree_branch": "src-branch", "dist_git_root": "dist-git-tree-cbr", "dist_git_branch": "dist-branch", + "mock_config": "test-mock-config", + "automated": True, } }