Skip to content

Commit 1b278fe

Browse files
Franz-Fischbachtzanko-matev
authored andcommitted
feat(test-programs): add py_checklist py_console_logs py_sudoku_solver
1 parent 283efa2 commit 1b278fe

File tree

18 files changed

+1891
-0
lines changed

18 files changed

+1891
-0
lines changed

.agents/codebase-insights.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323
- `ct record` now prefers `CODETRACER_PYTHON_INTERPRETER`, `PYTHON_EXECUTABLE`, `PYTHONEXECUTABLE`, or PATH resolution to locate the Python runtime before delegating to the db backend, and `db-backend-record` expects the resolved interpreter via `--python-interpreter` when launching the recorder.
2424
- `ct record` resolves Python via env vars (`CODETRACER_PYTHON_INTERPRETER`, `PYTHON_EXECUTABLE`, `PYTHONEXECUTABLE`, `PYTHON`) before checking PATH. When falling back to PATH we now call `findExe(..., followSymlinks=false)` so virtualenv launchers keep their original location and `pyvenv.cfg` remains discoverable.
2525
- `ct record` verifies that `codetracer_python_recorder` is importable before launching the db backend and prints actionable guidance if the module is missing or broken.
26+
- Sudoku test-program datasets include intentionally invalid boards (e.g., examples #3 and #6) with duplicate digits inside a sub-grid; solvers should detect and report these gracefully.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Python Checklist Test Programs
2+
3+
This suite mirrors the `ruby_checklist` sample by collecting small,
4+
targeted probes that exercise diverse areas of the Python language and
5+
standard library. The code is intentionally verbose and heavily
6+
commented so that engineers working across multiple languages can
7+
quickly understand what a probe demonstrates and why it matters.
8+
9+
## Goals
10+
11+
- Capture representative call stacks for critical Python features so the
12+
debugger and tracer integrations can be validated.
13+
- Provide ready-made snippets that illustrate specific language
14+
behaviors, pitfalls, and idioms for reference during development.
15+
- Keep each probe independent and side-effect light so they can run in
16+
isolation without hidden dependencies.
17+
18+
## Organization
19+
20+
- Minimum Python version: **3.11**. This allows us to cover structural
21+
pattern matching, `except*` for exception groups, and other
22+
post-3.10 features without fallbacks.
23+
- Each feature cluster lives in its own module (for example,
24+
`basics.py`, `functions_exceptions.py`, …). Modules should export
25+
numbered `demo_*` functions that focus on a single behavior.
26+
- A top-level `run_all()` function inside each module executes its demos
27+
sequentially, printing numbered lines so missed events are easy to
28+
spot in traces.
29+
- Inline comments and docstrings must explain **what** the snippet does,
30+
**why** someone would use it, and any common gotchas.
31+
- Helpers that spin up threads or processes must protect their entry
32+
points with `if __name__ == "__main__":` to stay portable across
33+
platforms (especially Windows).
34+
35+
## Feature Coverage
36+
37+
| Area | Modules / Demos | Notes |
38+
| --- | --- | --- |
39+
| Literals, operators, control flow | `basics.run_all()` | Covers literals, slicing, unpacking, match/case, loop `else`. |
40+
| Functions, decorators, exceptions | `functions_exceptions.run_all()` | Demonstrates call signatures, mutable defaults, closures, decorator stacking, caching, chained exceptions, `ExceptionGroup`. |
41+
| Context managers & iterators | `contexts_iterators.run_all()` | Custom `__enter__/__exit__`, `contextlib`, iterator protocol, generators with `send`/`throw`/`close`. |
42+
| Async & concurrency | `async_concurrency.run_all()` | Async context/iter, `create_task`, contextvars, threading/TLS, `ThreadPoolExecutor`, guarded multiprocessing. |
43+
| Data model & classes | `data_model.run_all()` | `__repr__`, formatting, rich ops, properties with `__slots__`, attribute hooks, descriptors, subclass hooks, metaclass lifecycle. |
44+
| Collections & dataclasses | `collections_dataclasses.run_all()` | Dataclasses with slots/order, enums and flags, namedtuple/deque/defaultdict/Counter, mapping proxy, pattern matching with `__match_args__`. |
45+
| System utilities | `system_utils.run_all()` | `subprocess.run` with env overrides, tz-aware datetime math, `Decimal`/`Fraction`, deterministic random, regex capture groups. |
46+
| Introspection & dynamic code | `introspection.run_all()` | `inspect.signature`, dynamic attributes, globals/locals snapshots, `eval`/`exec`/`compile`, AST parse/dump (with security notes in comments). |
47+
| Import system | `imports_demo.run_all()` | Runtime module creation via `types.ModuleType`, adjusting `sys.path`, `importlib.import_module`, `__all__`, module caching notes. |
48+
| Logging, warnings, GC, typing | `advanced_runtime.run_all()` | Scoped logging config, forced warnings, GC cycle collection with weakrefs/finalizers, runtime protocol checks, generics. |
49+
| Miscellaneous constructs | `miscellaneous.run_all()` | `iter(callable, sentinel)`, `del`, custom mapping protocol, context manager without suppression. |
50+
51+
> **Planned additions:** Remaining entries from the original checklist
52+
> (I/O & serialization, subprocess variants, JSON/pickle, etc.) can be
53+
> layered in by following the same pattern—add `<topic>.py`, expose
54+
> `run_all()`, then register it in `__main__.py`.
55+
56+
## Running The Suite
57+
58+
Once all modules are implemented, you will be able to run the entire
59+
checklist from the project root:
60+
61+
```bash
62+
python -m test-programs.python_checklist
63+
```
64+
65+
During early development, it is fine to run individual modules directly
66+
with `python test-programs/python_checklist/<module>.py` to iterate on a
67+
specific feature cluster.
68+
69+
## Contribution Guidelines
70+
71+
- Follow the numbering scheme diligently so console output gaps surface
72+
immediately.
73+
- Prefer deterministic behavior (avoid randomness unless the purpose is
74+
to exercise the random module).
75+
- Clean up temporary files, subprocesses, and other resources within the
76+
probe itself. Each demo should leave the environment in the same state
77+
it found it.
78+
- If a snippet showcases risky behavior (e.g., `eval`), highlight the
79+
security implications in comments.
80+
81+
This structure keeps the checklist maintainable and allows future
82+
contributors to plug in additional probes without surprising teammates.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Entry point for the Python checklist test suite.
2+
3+
Running ``python -m test-programs.py_checklist`` executes every module's
4+
``run_all`` function in a stable order. Each module prints numbered lines, so
5+
missing console events are immediately obvious when reviewing traces.
6+
7+
To add a new probe:
8+
1. Create ``<topic>.py`` with numbered ``demo_*`` functions and a ``run_all``.
9+
2. Import ``run_all`` here and append it to ``MODULES``.
10+
3. Ensure any platform-specific code (e.g., multiprocessing) stays guarded by
11+
``if __name__ == "__main__"`` inside the module itself.
12+
"""
13+
14+
from __future__ import annotations
15+
16+
from importlib import import_module
17+
from typing import Callable
18+
19+
20+
MODULES: list[tuple[str, Callable[[], None]]] = []
21+
22+
def register(module_name: str) -> None:
23+
module = import_module(f"test-programs.py_checklist.{module_name}")
24+
MODULES.append((module_name, module.run_all))
25+
26+
27+
for name in [
28+
"basics",
29+
"functions_exceptions",
30+
"contexts_iterators",
31+
"async_concurrency",
32+
"data_model",
33+
"collections_dataclasses",
34+
"system_utils",
35+
"introspection",
36+
"imports_demo",
37+
"advanced_runtime",
38+
"miscellaneous",
39+
]:
40+
register(name)
41+
42+
43+
def main() -> None:
44+
for name, runner in MODULES:
45+
print(f"== Running {name} ==")
46+
runner()
47+
48+
49+
if __name__ == "__main__":
50+
main()
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""Runtime-related probes: logging, warnings, GC, weakrefs, typing."""
2+
3+
from __future__ import annotations
4+
5+
import gc
6+
import logging
7+
import warnings
8+
import weakref
9+
from typing import Generic, Literal, Protocol, TypeAlias, TypedDict, TypeVar, runtime_checkable
10+
11+
12+
def demo_1_logging_warnings() -> None:
13+
"""Configure a module-scoped logger and emit a warning.
14+
15+
Logging is often customized per subsystem, so we avoid modifying the
16+
global root logger. Warnings are promoted so that traces capture them.
17+
"""
18+
19+
logger = logging.getLogger("python_checklist.advanced")
20+
if not logger.handlers:
21+
handler = logging.StreamHandler()
22+
handler.setFormatter(logging.Formatter("LOG:%(levelname)s:%(message)s"))
23+
logger.addHandler(handler)
24+
logger.setLevel(logging.INFO)
25+
logger.propagate = False
26+
27+
logger.info("1a. logging info message")
28+
29+
warnings.simplefilter("always", ResourceWarning)
30+
warnings.warn("1b. sample resource warning", ResourceWarning)
31+
32+
print("1. logging/warnings: configured")
33+
34+
35+
def demo_2_gc_weakrefs() -> None:
36+
"""Show garbage collection handling cycles and weak references."""
37+
38+
class Node:
39+
def __init__(self, name: str) -> None:
40+
self.name = name
41+
self.ref: "Node | None" = None
42+
43+
def __repr__(self) -> str:
44+
return f"Node({self.name})"
45+
46+
node_a = Node("A")
47+
node_b = Node("B")
48+
node_a.ref = node_b
49+
node_b.ref = node_a # create a reference cycle.
50+
51+
weak_a = weakref.ref(node_a)
52+
finalizer = weakref.finalize(
53+
node_b,
54+
lambda name=node_b.name: print(f"2b. finalize called for Node {name}"),
55+
)
56+
57+
del node_a
58+
del node_b
59+
collected = gc.collect()
60+
weak_alive = weak_a() is not None
61+
62+
print(
63+
"2. gc/weakref:",
64+
{"collected": collected, "weak_alive": weak_alive, "finalizer_alive": finalizer.alive},
65+
)
66+
67+
68+
def demo_3_typing_runtime() -> None:
69+
"""Runtime checks for typing constructs; useful for validation."""
70+
71+
UserId: TypeAlias = int
72+
Mode = Literal["r", "w"]
73+
74+
class User(TypedDict):
75+
id: UserId
76+
name: str
77+
78+
@runtime_checkable
79+
class Greeter(Protocol):
80+
def greet(self) -> str:
81+
...
82+
83+
class Impl:
84+
def greet(self) -> str:
85+
return "hello"
86+
87+
T = TypeVar("T")
88+
89+
class Box(Generic[T]):
90+
def __init__(self, value: T, mode: Mode = "r") -> None:
91+
self.value = value
92+
self.mode = mode
93+
94+
user: User = {"id": 7, "name": "Ada"}
95+
greeter_ok = isinstance(Impl(), Greeter)
96+
boxed = Box(user, mode="w")
97+
print("3. typing:", user, greeter_ok, boxed.mode)
98+
99+
100+
def run_all() -> None:
101+
"""Execute advanced runtime demonstrations."""
102+
demo_1_logging_warnings()
103+
demo_2_gc_weakrefs()
104+
demo_3_typing_runtime()
105+
106+
107+
if __name__ == "__main__":
108+
run_all()
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""Asyncio, threading, futures, and multiprocessing probes."""
2+
3+
from __future__ import annotations
4+
5+
import asyncio
6+
import contextvars
7+
import multiprocessing as mp
8+
import os
9+
import threading
10+
from concurrent.futures import ThreadPoolExecutor
11+
from typing import Any, List
12+
13+
14+
async def demo_1_async_primitives() -> None:
15+
"""Exercise async context managers, async iterators, and tasks."""
16+
17+
class AsyncContext:
18+
async def __aenter__(self) -> "AsyncContext":
19+
print("1. async-context: enter")
20+
return self
21+
22+
async def __aexit__(self, exc_type, exc, tb) -> bool:
23+
print("1. async-context: exit", exc_type.__name__ if exc_type else None)
24+
return False # do not suppress errors.
25+
26+
class AsyncIter:
27+
def __init__(self) -> None:
28+
self.n = 0
29+
30+
def __aiter__(self) -> "AsyncIter":
31+
return self
32+
33+
async def __anext__(self) -> int:
34+
await asyncio.sleep(0)
35+
if self.n >= 2:
36+
raise StopAsyncIteration
37+
self.n += 1
38+
return self.n
39+
40+
async with AsyncContext():
41+
async for value in AsyncIter():
42+
print("1a. async-iter:", value)
43+
44+
async def compute() -> int:
45+
await asyncio.sleep(0)
46+
return 42
47+
48+
task = asyncio.create_task(compute())
49+
print("1b. task result:", await task)
50+
51+
52+
def demo_2_contextvars() -> None:
53+
"""Context variables isolate async task state."""
54+
var = contextvars.ContextVar("counter", default=0)
55+
56+
def bump() -> int:
57+
var.set(var.get() + 1)
58+
return var.get()
59+
60+
values = [bump(), bump()]
61+
print("2. contextvars:", values)
62+
63+
64+
def demo_3_threads() -> None:
65+
"""Threads with locks and thread-local storage."""
66+
lock = threading.Lock()
67+
counter = 0
68+
tls = threading.local()
69+
70+
def worker(name: str) -> None:
71+
nonlocal counter
72+
tls.calls = getattr(tls, "calls", 0) + 1
73+
for _ in range(100):
74+
with lock:
75+
counter += 1
76+
print(f"3. thread {name}: tls.calls={tls.calls}")
77+
78+
threads = [threading.Thread(target=worker, args=(f"t{i}",)) for i in range(2)]
79+
for t in threads:
80+
t.start()
81+
for t in threads:
82+
t.join()
83+
print("3b. threads counter:", counter)
84+
85+
86+
def demo_4_thread_pool() -> None:
87+
"""Use concurrent.futures ThreadPoolExecutor."""
88+
with ThreadPoolExecutor(max_workers=2) as executor:
89+
future = executor.submit(sum, [1, 2, 3])
90+
print("4. thread-pool result:", future.result())
91+
92+
93+
def child_process(queue: mp.Queue) -> None:
94+
"""Child process used by demo_5_multiprocessing."""
95+
queue.put(("pid", os.getpid()))
96+
97+
98+
def demo_5_multiprocessing() -> None:
99+
"""Start a process; guard with __name__ check for Windows safety."""
100+
queue: mp.Queue[Any] = mp.Queue()
101+
process = mp.Process(target=child_process, args=(queue,))
102+
process.start()
103+
message = queue.get()
104+
process.join()
105+
print("5. multiprocessing:", message)
106+
107+
108+
def run_all() -> None:
109+
"""Run all async/concurrency demos; skip multiprocessing if imported."""
110+
asyncio.run(demo_1_async_primitives())
111+
demo_2_contextvars()
112+
demo_3_threads()
113+
demo_4_thread_pool()
114+
if __name__ == "__main__":
115+
# On Windows, the spawn start method re-imports modules, so we only
116+
# launch child processes when the module is the main entry point.
117+
demo_5_multiprocessing()
118+
else:
119+
print("5. multiprocessing: skipped (imported module)")
120+
121+
122+
if __name__ == "__main__":
123+
run_all()

0 commit comments

Comments
 (0)