From d64b2f747cb80bfc58677b304679d01afd95db77 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 13 Nov 2025 13:20:31 +0100 Subject: [PATCH] [ty] Add filtering option for mdtest runner (#21422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This change to the mdtest runner makes it easy to run on a subset of tests/files. For example: ``` ▶ uv run crates/ty_python_semantic/mdtest.py implicit running 1 test test mdtest__implicit_type_aliases ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 281 filtered out; finished in 0.83s Ready to watch for changes... ``` Subsequent changes to either that test file or the Rust source code will also only rerun the `implicit_type_aliases` test. Multiple arguments can be provided, and filters can either be partial file paths (`loops/for.md`, `loops/for`, `for`) or mangled test names (`loops_for`): ``` ▶ uv run crates/ty_python_semantic/mdtest.py implicit binary/union running 2 tests test mdtest__binary_unions ... ok test mdtest__implicit_type_aliases ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 280 filtered out; finished in 0.85s Ready to watch for changes... ``` ## Test Plan Tested it interactively for a while --- crates/ty_python_semantic/mdtest.py | 38 ++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/crates/ty_python_semantic/mdtest.py b/crates/ty_python_semantic/mdtest.py index 8eb272f39d..1f7ef89797 100644 --- a/crates/ty_python_semantic/mdtest.py +++ b/crates/ty_python_semantic/mdtest.py @@ -9,11 +9,12 @@ from __future__ import annotations +import argparse import json import os import subprocess from pathlib import Path -from typing import Final, Literal, Never, assert_never +from typing import Final, Literal, assert_never from rich.console import Console from watchfiles import Change, watch @@ -32,10 +33,15 @@ MDTEST_DIR: Final = CRATE_ROOT / "resources" / "mdtest" class MDTestRunner: mdtest_executable: Path | None console: Console + filters: list[str] - def __init__(self) -> None: + def __init__(self, filters: list[str] | None = None) -> None: self.mdtest_executable = None self.console = Console() + self.filters = [ + f.removesuffix(".md").replace("/", "_").replace("-", "_") + for f in (filters or []) + ] def _run_cargo_test(self, *, message_format: Literal["human", "json"]) -> str: return subprocess.check_output( @@ -117,13 +123,16 @@ class MDTestRunner: check=False, ) - def _run_mdtests_for_file(self, markdown_file: Path) -> None: - path_mangled = ( + def _mangle_path(self, markdown_file: Path) -> str: + return ( markdown_file.as_posix() .replace("/", "_") .replace("-", "_") .removesuffix(".md") ) + + def _run_mdtests_for_file(self, markdown_file: Path) -> None: + path_mangled = self._mangle_path(markdown_file) test_name = f"mdtest__{path_mangled}" output = self._run_mdtest(["--exact", test_name], capture_output=True) @@ -165,9 +174,9 @@ class MDTestRunner: print(line) - def watch(self) -> Never: + def watch(self): self._recompile_tests("Compiling tests...", message_on_success=False) - self._run_mdtest() + self._run_mdtest(self.filters) self.console.print("[dim]Ready to watch for changes...[/dim]") for changes in watch(*DIRS_TO_WATCH): @@ -214,12 +223,12 @@ class MDTestRunner: if rust_code_has_changed: if self._recompile_tests("Rust code has changed, recompiling tests..."): - self._run_mdtest() + self._run_mdtest(self.filters) elif vendored_typeshed_has_changed: if self._recompile_tests( "Vendored typeshed has changed, recompiling tests..." ): - self._run_mdtest() + self._run_mdtest(self.filters) elif new_md_files: files = " ".join(file.as_posix() for file in new_md_files) self._recompile_tests( @@ -231,8 +240,19 @@ class MDTestRunner: def main() -> None: + parser = argparse.ArgumentParser( + description="A runner for Markdown-based tests for ty" + ) + parser.add_argument( + "filters", + nargs="*", + help="Partial paths or mangled names, e.g., 'loops/for.md' or 'loops_for'", + ) + + args = parser.parse_args() + try: - runner = MDTestRunner() + runner = MDTestRunner(filters=args.filters) runner.watch() except KeyboardInterrupt: print()