chore: import upstream snapshot with attribution
This commit is contained in:
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run mypy type checking on all livekit packages.
|
||||
|
||||
Auto-discovers all plugin packages in livekit-plugins/ and runs mypy on them.
|
||||
Uses mypy's incremental mode (.mypy_cache) for fast re-checks after the first run.
|
||||
Passes given arguments to mypy. Arguments after `--` passed to `uv run`.
|
||||
|
||||
Third-party type stubs are declared in the `typing` dependency group in
|
||||
pyproject.toml and locked in uv.lock. We intentionally do NOT use
|
||||
`mypy --install-types`: it requires pip, installs unpinned stubs,
|
||||
and requires a forward pass in most cases.
|
||||
|
||||
When a dependency introduces a stub not declared yet, mypy records
|
||||
the complete set of stub packages it wants in `.mypy_cache/missing_stubs`,
|
||||
the same list `--install-types` consumes.
|
||||
See https://github.com/python/mypy/issues/10600#issuecomment-2481074163.
|
||||
|
||||
We read that file for the full set, then fail with the
|
||||
exact `uv add` command to declare and lock them. The script never installs stubs
|
||||
itself, so every run is a single deterministic pass..
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Plugins to exclude from type checking
|
||||
EXCLUDED_PLUGINS = [
|
||||
"browser",
|
||||
"nvidia",
|
||||
"rtzr",
|
||||
]
|
||||
|
||||
# mypy records the full set of stub packages it wants here, one per line.
|
||||
_MISSING_STUBS = ".mypy_cache/missing_stubs"
|
||||
|
||||
|
||||
INSTALL_STUBS_MESSAGE = """
|
||||
check_types: mypy needs type stubs that aren't currently installed.
|
||||
|
||||
Make sure to add them to the `typing` group and lock them with:
|
||||
|
||||
uv add --group typing {}
|
||||
"""
|
||||
|
||||
|
||||
def get_packages(repo_root: Path) -> list[str]:
|
||||
"""Return all packages to type-check."""
|
||||
packages = ["livekit.agents"]
|
||||
|
||||
plugins_dir = repo_root / "livekit-plugins"
|
||||
for plugin_dir in sorted(plugins_dir.glob("livekit-plugins-*")):
|
||||
if plugin_dir.is_dir():
|
||||
# livekit-plugins-openai -> openai
|
||||
# livekit-plugins-turn-detector -> turn_detector
|
||||
plugin_name = plugin_dir.name.replace("livekit-plugins-", "").replace("-", "_")
|
||||
if plugin_name in EXCLUDED_PLUGINS:
|
||||
continue
|
||||
packages.append(f"livekit.plugins.{plugin_name}")
|
||||
|
||||
return packages
|
||||
|
||||
|
||||
def read_missing_stubs(repo_root: Path) -> list[str]:
|
||||
"""The full stub-package list mypy recorded in .mypy_cache/missing_stubs."""
|
||||
marker = repo_root / _MISSING_STUBS
|
||||
if not marker.exists():
|
||||
return []
|
||||
return sorted(set(map(str.strip, marker.read_text().splitlines())))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Command:
|
||||
`python scripts/check_types.py --verbose -- --no-sync`
|
||||
Translates to:
|
||||
`uv run --no-sync mypy ... --verbose`
|
||||
"""
|
||||
repo_root = Path(__file__).parent.parent
|
||||
packages = get_packages(repo_root)
|
||||
|
||||
try:
|
||||
mypy_args_end = sys.argv.index("--")
|
||||
except ValueError:
|
||||
mypy_args, uv_run_args = sys.argv[1:], []
|
||||
else:
|
||||
mypy_args, uv_run_args = sys.argv[1:mypy_args_end], sys.argv[mypy_args_end + 1 :]
|
||||
|
||||
pkg_args: list[str] = []
|
||||
for pkg in packages:
|
||||
pkg_args.extend(["-p", pkg])
|
||||
|
||||
command = [
|
||||
"uv",
|
||||
"run",
|
||||
"--group",
|
||||
"typing",
|
||||
*uv_run_args,
|
||||
"mypy",
|
||||
"--untyped-calls-exclude=smithy_aws_core",
|
||||
*pkg_args,
|
||||
*mypy_args,
|
||||
]
|
||||
print(*command, "\n")
|
||||
returncode = subprocess.run(command, cwd=repo_root).returncode
|
||||
|
||||
if returncode and (missing := read_missing_stubs(repo_root)):
|
||||
print(INSTALL_STUBS_MESSAGE.format(" ".join(missing)), file=sys.stderr)
|
||||
|
||||
sys.exit(returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a markdown summary from JUnit XML test results."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import textwrap
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def generate_summary(xml_path: str) -> str:
|
||||
"""Parse JUnit XML and generate markdown summary."""
|
||||
lines: list[str] = []
|
||||
|
||||
try:
|
||||
tree = ET.parse(xml_path)
|
||||
root = tree.getroot()
|
||||
|
||||
testsuite = root.find("testsuite") if root.tag != "testsuite" else root
|
||||
if testsuite is None:
|
||||
testsuite = root
|
||||
|
||||
tests = int(testsuite.get("tests", 0))
|
||||
failures = int(testsuite.get("failures", 0))
|
||||
errors = int(testsuite.get("errors", 0))
|
||||
skipped = int(testsuite.get("skipped", 0))
|
||||
time_taken = float(testsuite.get("time", 0))
|
||||
|
||||
passed = tests - failures - errors - skipped
|
||||
|
||||
if failures == 0 and errors == 0:
|
||||
status = "✓ All tests passed"
|
||||
else:
|
||||
status = "✗ Some tests failed"
|
||||
|
||||
lines.append("## STT Test Results\n")
|
||||
lines.append(f"**Status:** {status}\n")
|
||||
lines.append("| Metric | Count |")
|
||||
lines.append("|--------|-------|")
|
||||
lines.append(f"| ✓ Passed | {passed} |")
|
||||
lines.append(f"| ✗ Failed | {failures} |")
|
||||
lines.append(f"| × Errors | {errors} |")
|
||||
lines.append(f"| → Skipped | {skipped} |")
|
||||
lines.append(f"| ▣ Total | {tests} |")
|
||||
lines.append(f"| ⏱ Duration | {time_taken:.1f}s |")
|
||||
lines.append("")
|
||||
|
||||
if failures > 0 or errors > 0:
|
||||
lines.append("<details>")
|
||||
lines.append("<summary>Failed Tests</summary>\n")
|
||||
for testcase in testsuite.iter("testcase"):
|
||||
failure = testcase.find("failure")
|
||||
error = testcase.find("error")
|
||||
if failure is not None or error is not None:
|
||||
name = testcase.get("name", "unknown")
|
||||
classname = testcase.get("classname", "")
|
||||
elem = failure if failure is not None else error
|
||||
msg = elem.text if elem.text else elem.get("message", "")
|
||||
msg = msg[:2000]
|
||||
indented_msg = textwrap.indent(msg.strip(), " ")
|
||||
lines.append(f"- **{classname}::{name}**")
|
||||
lines.append(" ```")
|
||||
lines.append(indented_msg)
|
||||
lines.append(" ```")
|
||||
lines.append("</details>")
|
||||
|
||||
skipped_tests = [tc for tc in testsuite.iter("testcase") if tc.find("skipped") is not None]
|
||||
if skipped_tests:
|
||||
lines.append("<details>")
|
||||
lines.append("<summary>Skipped Tests</summary>\n")
|
||||
lines.append("| Test | Reason |")
|
||||
lines.append("|------|--------|")
|
||||
for testcase in skipped_tests[:20]:
|
||||
name = testcase.get("name", "unknown")
|
||||
classname = testcase.get("classname", "")
|
||||
skip = testcase.find("skipped")
|
||||
reason = skip.get("message", "") if skip is not None else ""
|
||||
test_name = f"{classname}::{name}".replace("|", "\\|")
|
||||
reason_escaped = reason.strip().replace("|", "\\|").replace("\n", " ")
|
||||
lines.append(f"| `{test_name}` | {reason_escaped} |")
|
||||
if len(skipped_tests) > 20:
|
||||
lines.append(f"| ... | ... and {len(skipped_tests) - 20} more |")
|
||||
lines.append("</details>")
|
||||
|
||||
except Exception as e:
|
||||
lines.append("## STT Test Results\n")
|
||||
lines.append(f"⚠ Could not parse test results: {e}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Generate test summary from JUnit XML")
|
||||
parser.add_argument(
|
||||
"xml_path",
|
||||
nargs="?",
|
||||
default="test-results.xml",
|
||||
help="Path to JUnit XML file (default: test-results.xml)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
default=None,
|
||||
help="Output file path (default: stdout)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
summary = generate_summary(args.xml_path)
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
f.write(summary)
|
||||
else:
|
||||
print(summary)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user