Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/typedpython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
uv pip install --python .venv/bin/python "pyrefly==1.3.2" pytest

- name: Behaviour tests
run: .venv/bin/python -m pytest -q -m "not measurement"
run: .venv/bin/python -m pytest -q -m "not measurement and not race_mutant"

- name: Measurements (recorded, sanity bounds only)
# A shared runner is not an idle machine (AGENTS.md §8): read ratios, not absolute times.
Expand Down Expand Up @@ -77,7 +77,7 @@ jobs:
# only (tools/sanitizers.py env prints it); the gate's pyrefly runs without it.
run: |
env $(.venv/bin/python tools/sanitizers.py env) \
.venv/bin/python -m pytest -q -m "not measurement" \
.venv/bin/python -m pytest -q -m "not measurement and not race_mutant" \
src/test/python/test_cgen.py src/test/python/test_runtime.py src/test/python/test_pipeline.py

app-fixture:
Expand Down
5 changes: 4 additions & 1 deletion python-multiplatform-ksp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ test = ["pytest"]
testpaths = ["src/test/python"]
pythonpath = ["src/main/python"]
addopts = "--basetemp=.pytest_tmp"
markers = ["measurement: records cost; asserts only sanity bounds"]
markers = [
"measurement: records cost; asserts only sanity bounds",
"race_mutant: shows a concurrency test catches a mutant; depends on the race firing, so local only (not in CI)",
]
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ def _mutant(built, tmp: Path, drop: str) -> Path:
return cbuild.build("\n".join(kept), "ftmod", tmp / drop, ext_suffix=None)


@pytest.mark.race_mutant
def test_mutant_without_the_array_critical_section_is_caught(built, tmp_path):
so = _mutant(built, tmp_path, "tp_cs_begin")
detected = False
Expand Down Expand Up @@ -588,6 +589,7 @@ def _racy_runtime(tmp: Path, *, drop_dict_lock: bool, plain_epoch: bool) -> Path
return d


@pytest.mark.race_mutant
def test_mutant_without_the_dict_critical_section_is_caught(built, tmp_path):
"""Without the critical section around the epoch read and the global lookup, a thread can
remember a binding the rebinding thread has already replaced (the watcher fires before the store)."""
Expand Down Expand Up @@ -670,7 +672,12 @@ def _tsan_reports(built, tmp: Path, launcher: Path, rt: Path | None) -> tuple[in
run = subprocess.run([str(launcher), "-c", LOADER + textwrap.dedent(TSAN_SCRIPT), str(so), "1"],
capture_output=True, text=True, env=env, timeout=600)
out = run.stdout + run.stderr
return out.count("WARNING: ThreadSanitizer"), "TSAN-RUN-DONE" in out
# The interpreter is not instrumented, so TSan also reports races between two accesses inside
# CPython itself (seen on Linux CI). Count only reports with a frame of the extension or the
# header: those are the accesses this test is about.
blocks = out.split("WARNING: ThreadSanitizer")[1:]
ours = [b for b in blocks if "ftmod" in b or " tp_" in b]
return len(ours), "TSAN-RUN-DONE" in out


@pytest.fixture(scope="module")
Expand Down
6 changes: 4 additions & 2 deletions python-multiplatform-ksp/src/test/python/test_importgate.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ def test_clean_module_imports_and_is_cached_by_content(project, calls):
assert len(calls) == 1, "an unchanged module must not be checked again"

del sys.modules["good_mod"]
path.write_text("VALUE: int = 2\n")
assert importlib.import_module("good_mod").VALUE == 2
# a different length: CPython's .pyc check compares mtime in whole seconds and the size, so a
# same-size rewrite within one second would run the stale bytecode (CPython's own behaviour)
path.write_text("VALUE: int = 22\n")
assert importlib.import_module("good_mod").VALUE == 22
assert len(calls) == 2, "changed content must be re-checked"


Expand Down