Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ tasks:
# --- Testing ---

test:
desc: Run unit and acceptance tests
desc: Run unit, acceptance, and doctests
# Run the legs as sequential cmds (not parallel deps) so the concatenating
# cat can be a `defer`, which runs even when a leg fails. When the legs are
# deps, the cat in cmds is skipped on failure, so test-output.json is never
Expand All @@ -412,11 +412,19 @@ tasks:
- test-output.json
cmds:
- defer: cat test-output-unit.json test-output-acc.json > test-output.json
- task: doctest
- task: test-unit
- task: test-acc
vars:
ACCEPTANCE_TEST_FILTER: "{{.ACCEPTANCE_TEST_FILTER}}"

doctest:
desc: Run doctests for all Python files in the repo
sources:
- "**/*.py"
cmds:
- 'uv run -p ">=3.11" --no-project python tools/run_doctests.py'

test-unit:
desc: Run unit tests across all Go modules (root, tools, codegen)
sources:
Expand Down
2 changes: 1 addition & 1 deletion acceptance/bin/json_in_json_normalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def normalize_json_field(line, fields):
Normalize JSON in a field if the field name matches.

>>> normalize_json_field(' "foo": "{\\"a\\": 1}",', {'foo'})
' "foo": "{\\"a\\":1}",'
' "foo": "{\\"a\\":1}",\n'
>>> normalize_json_field(' "foo": "{\\"a\\": 1}",', {'bar'})
' "foo": "{\\"a\\": 1}",'
>>> normalize_json_field(' "other": "plain string",', {'foo'})
Expand Down
45 changes: 45 additions & 0 deletions tools/run_doctests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Run doctests for every Python file in the repo that has them.

Most Python files here are scripts with no doctests, and many can't even be
imported standalone (SDK-generated modules, notebooks using injected globals,
scripts with import-time side effects). So we git grep for files that actually
contain a `>>>` prompt and only run the doctest runner on those; a file with
none produces no tests anyway. --untracked also scans new files not yet
committed while still honoring .gitignore, so generated/ignored files stay out.
Each file runs in its own subprocess with cwd set to the file's directory, so
sibling imports (e.g. gron.py's `from print_requests import ...`) resolve as
they do when the script is invoked normally.
"""

import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent


def main() -> int:
out = subprocess.check_output(
["git", "grep", "-l", "--untracked", "--no-color", "-F", ">>>", "--", "*.py"],
cwd=ROOT,
text=True,
)
files = sorted(ROOT / line for line in out.splitlines())
failed = []
for path in files:
result = subprocess.run(
[sys.executable, "-m", "doctest", path.name],
cwd=path.parent,
)
if result.returncode != 0:
failed.append(path.relative_to(ROOT))

print(f"\n{len(files)} files with doctests, {len(failed)} failed")
for rel in failed:
print(f"FAIL {rel}")
return 1 if failed else 0


if __name__ == "__main__":
sys.exit(main())
Loading