|
17 | 17 | PROJECT_ROOT = Path(__file__).resolve().parent |
18 | 18 | DEFAULT_EXAMPLE_PYTHON = "3.11" |
19 | 19 | EXAMPLES_DIR = PROJECT_ROOT / "examples" |
| 20 | +DEFAULT_COVERAGE_CLASSES = ("PdfRestClient", "AsyncPdfRestClient") |
| 21 | + |
| 22 | + |
| 23 | +def _install_test_dependencies(session: nox.Session) -> None: |
| 24 | + _ = session.run_install( |
| 25 | + "uv", |
| 26 | + "sync", |
| 27 | + "--no-default-groups", |
| 28 | + "--group=dev", |
| 29 | + "--reinstall-package=pdfrest", |
| 30 | + f"--python={session.virtualenv.location}", |
| 31 | + env={"UV_PROJECT_ENVIRONMENT": session.virtualenv.location}, |
| 32 | + ) |
| 33 | + |
| 34 | + |
| 35 | +def _coverage_dir_for_session(session: nox.Session) -> Path: |
| 36 | + coverage_dir = PROJECT_ROOT / "coverage" / f"py{session.python}" |
| 37 | + coverage_dir.mkdir(parents=True, exist_ok=True) |
| 38 | + return coverage_dir |
| 39 | + |
| 40 | + |
| 41 | +def _pytest_args_from_session(session: nox.Session) -> list[str]: |
| 42 | + parser = argparse.ArgumentParser(add_help=False) |
| 43 | + _ = parser.add_argument("--no-parallel", action="store_true") |
| 44 | + _ = parser.add_argument("-n", "--workers", "--numprocesses") |
| 45 | + custom, remaining = parser.parse_known_args(session.posargs) |
| 46 | + |
| 47 | + pytest_args = list(remaining) |
| 48 | + |
| 49 | + if custom.no_parallel: |
| 50 | + return pytest_args |
| 51 | + if custom.workers: |
| 52 | + pytest_args[:0] = ["-n", custom.workers, "--maxschedchunk", "2"] |
| 53 | + else: |
| 54 | + pytest_args[:0] = ["-n", "8", "--maxschedchunk", "2"] |
| 55 | + |
| 56 | + return pytest_args |
| 57 | + |
| 58 | + |
| 59 | +def _run_pytest_with_coverage(session: nox.Session, pytest_args: Iterable[str]) -> Path: |
| 60 | + coverage_dir = _coverage_dir_for_session(session) |
| 61 | + htmlcov_dir = coverage_dir / "html" |
| 62 | + xml_report = coverage_dir / "coverage.xml" |
| 63 | + md_report = coverage_dir / "coverage.md" |
| 64 | + json_report = coverage_dir / "coverage.json" |
| 65 | + _ = session.run( |
| 66 | + "pytest", |
| 67 | + "--cov=pdfrest", |
| 68 | + "--cov-report=term-missing", |
| 69 | + f"--cov-report=html:{htmlcov_dir}", |
| 70 | + f"--cov-report=xml:{xml_report}", |
| 71 | + f"--cov-report=markdown:{md_report}", |
| 72 | + f"--cov-report=json:{json_report}", |
| 73 | + *pytest_args, |
| 74 | + ) |
| 75 | + return coverage_dir |
| 76 | + |
| 77 | + |
| 78 | +def _parse_class_values(values: Iterable[str]) -> list[str]: |
| 79 | + classes: list[str] = [] |
| 80 | + for value in values: |
| 81 | + if not value: |
| 82 | + continue |
| 83 | + for item in value.split(","): |
| 84 | + item = item.strip() |
| 85 | + if item: |
| 86 | + classes.append(item) |
| 87 | + return classes |
20 | 88 |
|
21 | 89 |
|
22 | 90 | @dataclass(frozen=True) |
@@ -162,48 +230,61 @@ def _infer_python_version_from_path(script: Path) -> str | None: |
162 | 230 |
|
163 | 231 | @nox.session(name="tests", python=python_versions, reuse_venv=True) |
164 | 232 | def tests(session: nox.Session) -> None: |
165 | | - # Define only custom flags |
| 233 | + pytest_args = _pytest_args_from_session(session) |
| 234 | + |
| 235 | + _install_test_dependencies(session) |
| 236 | + _ = _run_pytest_with_coverage(session, pytest_args) |
| 237 | + |
| 238 | + |
| 239 | +@nox.session(name="class-coverage", python=python_versions, reuse_venv=True) |
| 240 | +def class_coverage(session: nox.Session) -> None: |
166 | 241 | parser = argparse.ArgumentParser(add_help=False) |
167 | 242 | _ = parser.add_argument("--no-parallel", action="store_true") |
168 | | - _ = parser.add_argument( |
169 | | - "-n", "--workers", "--numprocesses" |
170 | | - ) # e.g., -n 4 to set workers |
| 243 | + _ = parser.add_argument("-n", "--workers", "--numprocesses") |
| 244 | + _ = parser.add_argument("--no-tests", action="store_true") |
| 245 | + _ = parser.add_argument("--coverage-json", type=Path, default=None) |
| 246 | + _ = parser.add_argument("--markdown-report", type=Path, default=None) |
| 247 | + _ = parser.add_argument("--fail-under", type=float, default=90.0) |
| 248 | + _ = parser.add_argument("--class", dest="classes", action="append", default=[]) |
| 249 | + _ = parser.add_argument("--classes", dest="classes_csv", default="") |
171 | 250 | custom, remaining = parser.parse_known_args(session.posargs) |
172 | 251 |
|
173 | 252 | pytest_args = list(remaining) |
| 253 | + if not custom.no_parallel: |
| 254 | + if custom.workers: |
| 255 | + pytest_args[:0] = ["-n", custom.workers, "--maxschedchunk", "2"] |
| 256 | + else: |
| 257 | + pytest_args[:0] = ["-n", "8", "--maxschedchunk", "2"] |
174 | 258 |
|
175 | | - # Default to parallel unless disabled or overridden |
176 | | - if custom.no_parallel: |
177 | | - pass |
178 | | - elif custom.workers: |
179 | | - pytest_args[:0] = ["-n", custom.workers, "--maxschedchunk", "2"] |
| 259 | + if custom.no_tests: |
| 260 | + coverage_dir = _coverage_dir_for_session(session) |
180 | 261 | else: |
181 | | - pytest_args[:0] = ["-n", "8", "--maxschedchunk", "2"] |
| 262 | + _install_test_dependencies(session) |
| 263 | + coverage_dir = _run_pytest_with_coverage(session, pytest_args) |
182 | 264 |
|
183 | | - _ = session.run_install( |
184 | | - "uv", |
185 | | - "sync", |
186 | | - "--no-default-groups", |
187 | | - "--group=dev", |
188 | | - "--reinstall-package=pdfrest", |
189 | | - f"--python={session.virtualenv.location}", |
190 | | - env={"UV_PROJECT_ENVIRONMENT": session.virtualenv.location}, |
191 | | - ) |
192 | | - coverage_dir = PROJECT_ROOT / "coverage" / f"py{session.python}" |
193 | | - coverage_dir.mkdir(parents=True, exist_ok=True) |
194 | | - htmlcov_dir = coverage_dir / "html" |
195 | | - xml_report = coverage_dir / "coverage.xml" |
196 | | - md_report = coverage_dir / "coverage.md" |
197 | | - _ = session.run( |
198 | | - "pytest", |
199 | | - "--cov=pdfrest", |
200 | | - "--cov-report=term-missing", |
201 | | - f"--cov-report=html:{htmlcov_dir}", |
202 | | - f"--cov-report=xml:{xml_report}", |
203 | | - f"--cov-report=markdown:{md_report}", |
204 | | - *pytest_args, |
| 265 | + coverage_json = custom.coverage_json or (coverage_dir / "coverage.json") |
| 266 | + markdown_report = custom.markdown_report or ( |
| 267 | + coverage_dir / "class-function-coverage.md" |
205 | 268 | ) |
206 | 269 |
|
| 270 | + classes = _parse_class_values([*custom.classes, custom.classes_csv]) |
| 271 | + if not classes: |
| 272 | + classes = list(DEFAULT_COVERAGE_CLASSES) |
| 273 | + |
| 274 | + script_args = [ |
| 275 | + "python", |
| 276 | + str(PROJECT_ROOT / "scripts" / "check_class_function_coverage.py"), |
| 277 | + str(coverage_json), |
| 278 | + "--fail-under", |
| 279 | + f"{custom.fail_under}", |
| 280 | + "--markdown-report", |
| 281 | + str(markdown_report), |
| 282 | + ] |
| 283 | + for class_name in classes: |
| 284 | + script_args.extend(["--class", class_name]) |
| 285 | + |
| 286 | + _ = session.run(*script_args) |
| 287 | + |
207 | 288 |
|
208 | 289 | @nox.session(name="examples", python=python_versions, reuse_venv=True) |
209 | 290 | def run_examples(session: nox.Session) -> None: |
|
0 commit comments