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