Files
codeql/python/ql/test/library-tests/ControlFlow/evaluation-order/timer.py
Taus 795b138584 Python: Add self-validating CFG tests
These tests consist of various Python constructions (hopefully a
somewhat comprehensive set) with specific timestamp annotations
scattered throughout. When the tests are run using the Python 3
interpreter, these annotations are checked and compared to the "current
timestamp" to see that they are in agreement. This is what makes the
tests "self-validating".

There are a few different kinds of annotations: the basic `t[4]` style
(meaning this is executed at timestamp 4), the `t[dead(4)]` variant
(meaning this _would_ happen at timestamp 4, but it is in a dead
branch), and `t[never]` (meaning this is never executed at all).

In addition to this, there is a query, MissingAnnotations, which checks
whether we have applied these annotations maximally. Many expression
nodes are not actually annotatable, so there is a sizeable list of
excluded nodes for that query.
2026-04-28 15:38:04 +00:00

190 lines
5.2 KiB
Python

"""Abstract timer for self-validating CFG evaluation-order tests.
Provides a Timer context manager and a @test decorator for writing tests
that verify the order in which Python evaluates expressions.
Usage with @test decorator (preferred):
from timer import test, dead, never
@test
def test_sequential(t):
x = 1 @ t[0]
y = 2 @ t[1]
z = (x + y) @ t[2]
Annotation forms:
t[n] - assert current timestamp is n, return marker
t[n, m, ...] - assert current timestamp is one of {n, m, ...}
t[dead(n)] - mark timestamp n as dead (fails if evaluated)
t[dead(n), m] - dead at n, live at m
t[never] - mark as never evaluated (fails if evaluated)
t["label"] - record current timestamp under label (development aid)
t(value, n) - equivalent to: value @ t[n]
Run a test file directly to self-validate: python test_file.py
"""
import atexit
import sys
_results = []
class _Check:
"""Marker returned by t[n] — asserts the current timestamp.
Receives the raw subscript elements: plain ints are live timestamps,
dead(n) markers are dead timestamps, and `never` means any evaluation
is an error.
"""
__slots__ = ("_timer", "_live", "_dead", "_never")
def __init__(self, timer, elements):
self._timer = timer
self._live = set()
self._dead = set()
self._never = False
for e in elements:
if isinstance(e, int):
self._live.add(e)
elif isinstance(e, _DeadMarker):
self._dead.add(e.timestamp)
elif isinstance(e, _NeverSentinel):
self._never = True
def __rmatmul__(self, value):
ts = self._timer._tick()
if self._never:
self._timer._error(
f"expression annotated with t[never] was evaluated (timestamp {ts})"
)
elif ts in self._dead:
self._timer._error(
f"timestamp {ts} is marked dead but was evaluated"
)
elif ts not in self._live:
self._timer._error(
f"expected {sorted(self._live)}, got {ts}"
)
return value
class _Label:
"""Marker returned by t["name"] — records the timestamp under a label."""
__slots__ = ("_timer", "_name")
def __init__(self, timer, name):
self._timer = timer
self._name = name
def __rmatmul__(self, value):
ts = self._timer._tick()
self._timer._labels.setdefault(self._name, []).append(ts)
return value
class _DeadMarker:
"""Marker returned by dead(n) — used inside t[...] to mark a timestamp as dead."""
def __init__(self, timestamp):
self.timestamp = timestamp
def dead(n):
"""Mark timestamp `n` as dead code inside a timer subscript: t[dead(1), 2]."""
return _DeadMarker(n)
class _NeverSentinel:
"""Sentinel for never-evaluated annotations: t[never]."""
pass
never = _NeverSentinel()
class Timer:
"""Context manager tracking abstract evaluation timestamps.
Each Timer instance maintains a counter starting at 0. Every time an
annotation (@ t[n] or t(value, n)) is encountered, the counter is
compared against the expected value and then incremented.
"""
def __init__(self, name="<unnamed>"):
self._name = name
self._counter = 0
self._errors = []
self._labels = {}
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._labels:
for name, timestamps in sorted(self._labels.items()):
print(f" {name}: {', '.join(map(str, timestamps))}")
_results.append((self._name, list(self._errors)))
if self._errors:
print(f"{self._name}: FAIL")
for err in self._errors:
print(f" {err}")
else:
print(f"{self._name}: ok")
return False
def _tick(self):
ts = self._counter
self._counter += 1
return ts
def _error(self, msg):
self._errors.append(msg)
def __getitem__(self, key):
if isinstance(key, str):
return _Label(self, key)
elif isinstance(key, tuple):
return _Check(self, key)
else:
return _Check(self, [key])
def __call__(self, value, key):
"""Alternative to @ operator: t(value, 4) or t(value, [1, 2, 3])."""
if isinstance(key, list):
key = tuple(key)
marker = self[key]
return marker.__rmatmul__(value)
def test(fn):
"""Decorator that creates a Timer and runs the test function immediately.
The function receives a fresh Timer as its sole argument. Errors are
collected (not raised) and reported after the function completes.
"""
with Timer(fn.__name__) as t:
try:
fn(t)
except Exception as e:
t._error(f"exception: {type(e).__name__}: {e}")
return fn
def _report():
"""Print summary at interpreter exit."""
if not _results:
return
total = len(_results)
passed = sum(1 for _, errors in _results if not errors)
print("---")
print(f"{passed}/{total} tests passed")
if passed < total:
sys.exit(1)
atexit.register(_report)