Skip to content

Commit f703dbc

Browse files
authored
Merge pull request #156 from posit-dev/ci-gdg-improvements
ci: GDG improvements
2 parents 1d29d16 + a3effc8 commit f703dbc

282 files changed

Lines changed: 3099 additions & 585 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ addopts = [
9494
"--strict-markers",
9595
"--strict-config",
9696
]
97+
markers = [
98+
"dedicated: test targets one or more specific gdtest_* packages (not a generic sweep)",
99+
]
97100

98101
[tool.coverage.run]
99102
source = ["great_docs"]
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
"""
2+
Batch script: Add coverage_exclude to all spec files that don't have it.
3+
4+
For each spec, determines which coverage levels the package structurally cannot
5+
pass (based on its expected dict and features), then injects coverage_exclude
6+
into the spec's expected dict.
7+
8+
DED is never auto-excluded as it's kept as an aspirational target.
9+
10+
Run from repo root:
11+
python test-packages/apply_coverage_excludes.py [--dry-run] [--batch N]
12+
"""
13+
14+
import re
15+
import sys
16+
from pathlib import Path
17+
18+
sys.path.insert(0, str(Path(__file__).parent))
19+
20+
from render_all import (
21+
_COVERAGE_LEVELS,
22+
ALL_PACKAGES,
23+
_compute_coverage,
24+
_spec_file_exists,
25+
get_spec,
26+
)
27+
28+
SPEC_DIR = Path(__file__).parent / "synthetic" / "specs"
29+
30+
# DED is aspirational — never auto-exclude
31+
NEVER_EXCLUDE = {"DED"}
32+
33+
34+
def determine_exclusions(name: str) -> list[str]:
35+
"""Determine which levels should be excluded for a package."""
36+
coverage = _compute_coverage(name)
37+
return [
38+
level
39+
for level in _COVERAGE_LEVELS
40+
if not coverage.get(level) and level not in NEVER_EXCLUDE
41+
]
42+
43+
44+
def inject_coverage_exclude(spec_path: Path, exclusions: list[str]) -> bool:
45+
"""Inject coverage_exclude into a spec file's expected dict.
46+
47+
Returns `True` if the file was modified, `False` if skipped.
48+
"""
49+
content = spec_path.read_text(encoding="utf-8")
50+
51+
# Skip if already has coverage_exclude or coverage_include
52+
if "coverage_exclude" in content or "coverage_include" in content:
53+
return False
54+
55+
if not exclusions:
56+
return False
57+
58+
# Build the exclude line
59+
exclude_str = repr(exclusions)
60+
61+
# Find the closing of the "expected" dict: look for the pattern
62+
# where "expected": { ... } ends with },\n} or just \n },\n}
63+
# Strategy: find `"expected": {` then find its closing `}`
64+
65+
# Try to find the last key-value in expected dict and add after it
66+
# Pattern: look for the last line before the closing `},` or `}` of expected
67+
68+
# Find "expected": { ... }
69+
# We'll use a regex to find the expected dict and insert before its closing brace
70+
# The expected dict typically ends with:
71+
# "some_key": some_value,
72+
# },
73+
# }
74+
75+
# Find the "expected" key
76+
expected_match = re.search(r'"expected"\s*:\s*\{', content)
77+
if not expected_match:
78+
# No expected dict — need to add one
79+
# Find the closing `}` of the SPEC dict and add expected before it
80+
# For now, skip these (19 packages without expected)
81+
return False
82+
83+
# Find the matching closing brace for the expected dict
84+
start = expected_match.end()
85+
brace_depth = 1
86+
pos = start
87+
while pos < len(content) and brace_depth > 0:
88+
if content[pos] == "{":
89+
brace_depth += 1
90+
elif content[pos] == "}":
91+
brace_depth -= 1
92+
pos += 1
93+
94+
# pos is now just past the closing } of expected
95+
closing_brace_pos = pos - 1
96+
97+
# Find the last non-whitespace before the closing brace
98+
before_close = content[:closing_brace_pos].rstrip()
99+
100+
# Determine indentation (look at lines inside expected)
101+
lines_in_expected = content[expected_match.start() : closing_brace_pos].split("\n")
102+
# Find a typical key line for indentation reference
103+
indent = " " # default 8 spaces
104+
for line in lines_in_expected[1:]:
105+
stripped = line.lstrip()
106+
if stripped.startswith('"') and ":" in stripped:
107+
indent = line[: len(line) - len(stripped)]
108+
break
109+
110+
# Build the new line to insert
111+
new_line = f'{indent}"coverage_exclude": {exclude_str},'
112+
113+
# Insert before the closing brace
114+
# Check if the last content line before } has a trailing comma
115+
if before_close.endswith(","):
116+
# Good, just add our line
117+
new_content = before_close + "\n" + new_line + "\n" + content[closing_brace_pos:]
118+
else:
119+
# Add a comma to the previous line
120+
new_content = before_close + ",\n" + new_line + "\n" + content[closing_brace_pos:]
121+
122+
spec_path.write_text(new_content, encoding="utf-8")
123+
return True
124+
125+
126+
def main():
127+
import argparse
128+
129+
parser = argparse.ArgumentParser(description="Add coverage_exclude to spec files")
130+
parser.add_argument("--dry-run", action="store_true", help="Show what would be done")
131+
parser.add_argument("--batch", type=int, default=0, help="Process only N specs (0=all)")
132+
parser.add_argument("--start", type=int, default=0, help="Start at this index")
133+
args = parser.parse_args()
134+
135+
modified = 0
136+
skipped = 0
137+
errors = []
138+
139+
packages = ALL_PACKAGES[args.start :]
140+
if args.batch > 0:
141+
packages = packages[: args.batch]
142+
143+
for name in packages:
144+
if not _spec_file_exists(name):
145+
continue
146+
147+
spec = get_spec(name)
148+
exp = spec.get("expected", {})
149+
150+
# Skip if already configured
151+
if exp.get("coverage_exclude") or exp.get("coverage_include"):
152+
skipped += 1
153+
continue
154+
155+
exclusions = determine_exclusions(name)
156+
if not exclusions:
157+
skipped += 1
158+
continue
159+
160+
spec_path = SPEC_DIR / f"{name}.py"
161+
if not spec_path.exists():
162+
errors.append(f"{name}: spec file not found")
163+
continue
164+
165+
if args.dry_run:
166+
print(f" {name}: would exclude {exclusions}")
167+
modified += 1
168+
else:
169+
try:
170+
if inject_coverage_exclude(spec_path, exclusions):
171+
modified += 1
172+
print(f" ✓ {name}: {len(exclusions)} levels excluded")
173+
else:
174+
skipped += 1
175+
except Exception as e:
176+
errors.append(f"{name}: {e}")
177+
178+
print(f"\n{'[DRY RUN] ' if args.dry_run else ''}Modified: {modified}, Skipped: {skipped}")
179+
if errors:
180+
print(f"Errors ({len(errors)}):")
181+
for e in errors:
182+
print(f" ✗ {e}")
183+
184+
185+
if __name__ == "__main__":
186+
main()

0 commit comments

Comments
 (0)