Skip to content

Commit a821a59

Browse files
committed
Add Python test coverage and extend unit tests
- Add make test-python-coverage (fail under 90%), CI Python coverage job, coverage in requirements-lint and .gitignore. Ci runs test-python-coverage. - Raise c8 branches to 90%; add c8 ignore in heading-numbering for unreachable branch. - Extend test_verify_markdownlint_fixtures tests (find_markdownlint_cmd, load_expected_errors, parse output, to_count_map, verify_file edge cases, main return/verbose). Add rule unit tests for edge cases and coverage paths.
1 parent 39d68e2 commit a821a59

21 files changed

Lines changed: 2440 additions & 1953 deletions

‎.github/workflows/python-tests.yml‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: Python tests
22

3-
# Unit tests for test-scripts/*.py (unittest). NOTE: Keep in sync with 'test-python' Makefile target.
3+
# Unit tests and coverage for test-scripts/*.py. Keep in sync with 'test-python' and
4+
# 'test-python-coverage' Makefile targets.
45

56
on:
67
push:
@@ -24,3 +25,21 @@ jobs:
2425

2526
- name: Run Python unit tests
2627
run: make test-python
28+
29+
coverage:
30+
name: Python coverage
31+
runs-on: ubuntu-latest
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@v6
35+
36+
- name: Set up Python
37+
uses: actions/setup-python@v6
38+
with:
39+
python-version: '3.11'
40+
41+
- name: Install coverage
42+
run: pip install coverage
43+
44+
- name: Run Python coverage (fail if < 90%)
45+
run: make test-python-coverage

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ tmp/
3737

3838
# Ignore coverage files
3939
coverage.*
40+
.coverage
4041

4142
# Ignore Python cache files
4243
**/__pycache__/

‎.markdownlint-rules/heading-numbering.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ function getSiblings(sorted, parentIndex, i) {
5353
if (parentIndex[j] !== parentIndex[i]) {
5454
continue;
5555
}
56+
/* c8 ignore start -- same parent implies same level by tree construction */
5657
if (sorted[j].level !== h.level) {
5758
continue;
5859
}
60+
/* c8 ignore stop */
5961
siblings.push({ index: j, ...sorted[j] });
6062
}
6163
siblings.sort((a, b) => a.lineNumber - b.lineNumber);
@@ -76,17 +78,21 @@ function getExpectedNumberInSection(sorted, parentIndex, i) {
7678

7779
const siblings = getSiblings(sorted, parentIndex, i);
7880
const myIdx = siblings.findIndex((s) => s.lineNumber === h.lineNumber);
81+
/* c8 ignore start -- current heading is always in its sibling list */
7982
if (myIdx < 0) {
8083
return null;
8184
}
85+
/* c8 ignore stop */
8286

8387
const firstNumbered = siblings.find((s) =>
8488
parseHeadingNumberPrefix(s.rawText).numbering != null
8589
);
90+
/* c8 ignore start -- sectionUsesNum ensures at least one numbered sibling */
8691
const firstNumbering =
8792
firstNumbered != null
8893
? parseHeadingNumberPrefix(firstNumbered.rawText).numbering
8994
: null;
95+
/* c8 ignore stop */
9096
const startAtZero = firstNumbering === "0";
9197
const nextNum = startAtZero ? myIdx : myIdx + 1;
9298
const prefix = parentNum ? parentNum + "." : "";
@@ -111,9 +117,11 @@ function getSectionPeriodStyle(sorted, parentIndex, i) {
111117
const firstNumbered = siblings.find((s) =>
112118
parseHeadingNumberPrefix(s.rawText).numbering != null
113119
);
120+
/* c8 ignore start -- getPeriodStyleError only called for numbered headings */
114121
if (firstNumbered == null) {
115122
return null;
116123
}
124+
/* c8 ignore stop */
117125
return parseHeadingNumberPrefix(firstNumbered.rawText).hasH2Dot;
118126
}
119127

‎Makefile‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
.PHONY: ci lint-js lint-readmes lint-python test-markdownlint test-python test-rules test-rules-coverage venv
1+
.PHONY: ci lint-js lint-readmes lint-python test-markdownlint test-python test-python-coverage test-rules test-rules-coverage venv
22

33
# Run all CI checks (same as GitHub Actions workflows). Run after 'npm install' and optionally 'make venv'.
4-
ci: lint-js test-rules-coverage test-markdownlint lint-python test-python lint-readmes
4+
ci: lint-js test-rules-coverage test-markdownlint lint-python test-python-coverage test-python lint-readmes
55

66
# README linting - performs same checks as GitHub Actions workflow
77
# NOTE: This target must be kept in sync with .github/workflows/lint-readmes.yml.
@@ -158,6 +158,21 @@ test-python:
158158
}
159159
@python3 -m unittest discover -s test-scripts -p "test_*.py" -v
160160

161+
# Python unit test coverage - runs tests with coverage, fails if coverage < 90%.
162+
# Requires: pip install coverage (or make venv). Sources: test-scripts/*.py (excl. test_*.py).
163+
test-python-coverage:
164+
@command -v python3 >/dev/null 2>&1 || { \
165+
echo "Error: python3 not found. Install Python 3 to run coverage."; \
166+
exit 1; \
167+
}
168+
@command -v coverage >/dev/null 2>&1 || [ -x .venv/bin/coverage ] || { \
169+
echo "Error: coverage not found. Install with: pip install coverage or run 'make venv'"; \
170+
exit 1; \
171+
}
172+
@if [ -d .venv ]; then PATH="$(CURDIR)/.venv/bin:$$PATH"; export PATH; fi; \
173+
coverage run -m unittest discover -s test-scripts -p "test_*.py" && \
174+
coverage report --include="test-scripts/*.py" --omit="test-scripts/test_*.py" --fail-under=90
175+
161176
# Python venv for lint tooling - creates .venv and installs test-scripts/requirements-lint.txt
162177
# Run once (or after adding/updating test-scripts/requirements-lint.txt) so make lint-python uses the venv.
163178
# Usage: make venv

0 commit comments

Comments
 (0)