pretense-py 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pretense/__init__.py +82 -0
- pretense/cli.py +35 -0
- pretense/collections.py +206 -0
- pretense/compiler.py +47 -0
- pretense/context.py +9 -0
- pretense/fake.py +5 -0
- pretense/parts/autofab.toml +4 -0
- pretense/parts/pretense/__init__.py +1 -0
- pretense/parts/pretense/description.md +20 -0
- pretense/parts/pretense/high/pretense.md +23 -0
- pretense/parts/pretense/high/pretense_impl.md +23 -0
- pretense/parts/pretense/lib/pretense.py +1303 -0
- pretense/parts/pretense/lib/pretense_impl.py +806 -0
- pretense/parts/pretense/low/pretense.pyi +422 -0
- pretense/parts/pretense/low/pretense_impl.pyi +86 -0
- pretense/parts/pretense/planning/pretense.md +72 -0
- pretense/parts/pretense/planning/pretense_impl.md +59 -0
- pretense/parts/pretense/pretense/pretense_impl_pretense.py +218 -0
- pretense/parts/pretense/scenarios/pretense.md +76 -0
- pretense/parts/pretense/scenarios/pretense_impl.md +58 -0
- pretense/parts/pretense/tests/pretense_impl_test.py +895 -0
- pretense/proto.py +9 -0
- pretense/sentinels.py +25 -0
- pretense/support/tests/counterexample_synthesis_test.py +97 -0
- pretense/support/tests/pretense_core_test.py +244 -0
- pretense/synthesis.py +44 -0
- pretense/transition.py +11 -0
- pretense_py-0.2.0.dist-info/METADATA +44 -0
- pretense_py-0.2.0.dist-info/RECORD +31 -0
- pretense_py-0.2.0.dist-info/WHEEL +4 -0
- pretense_py-0.2.0.dist-info/entry_points.txt +2 -0
pretense/__init__.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Pretense: Unified State-Machine Verification & Fake Synthesis Engine."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
_curr = Path(__file__).resolve().parent
|
|
8
|
+
_REPO_ROOT = _curr
|
|
9
|
+
while _curr and _curr != _curr.parent:
|
|
10
|
+
if (_curr / "pyproject.toml").is_file():
|
|
11
|
+
_REPO_ROOT = _curr
|
|
12
|
+
break
|
|
13
|
+
_curr = _curr.parent
|
|
14
|
+
|
|
15
|
+
for _p in (_REPO_ROOT, _REPO_ROOT / "autofab_python", _REPO_ROOT / "autofab", _REPO_ROOT / "pretense"):
|
|
16
|
+
_str_p = str(_p)
|
|
17
|
+
if _p.is_dir() and _str_p not in sys.path:
|
|
18
|
+
sys.path.insert(0, _str_p)
|
|
19
|
+
|
|
20
|
+
from pretense.parts.pretense.lib.pretense import (
|
|
21
|
+
AmbiguousTransitionError,
|
|
22
|
+
Domain,
|
|
23
|
+
EnsuresCheck,
|
|
24
|
+
FakeAdapter,
|
|
25
|
+
NoMatchingTransitionError,
|
|
26
|
+
NotBound,
|
|
27
|
+
NotBoundType,
|
|
28
|
+
Outcome,
|
|
29
|
+
PretenseEngine,
|
|
30
|
+
PretenseError,
|
|
31
|
+
PretenseRunResult,
|
|
32
|
+
PretenseRunnerConfig,
|
|
33
|
+
PretenseSpecificationError,
|
|
34
|
+
Raised,
|
|
35
|
+
RequiresCheck,
|
|
36
|
+
StateMachine,
|
|
37
|
+
StateMachineProto,
|
|
38
|
+
TraceStep,
|
|
39
|
+
Transition,
|
|
40
|
+
TransitionContext,
|
|
41
|
+
Value,
|
|
42
|
+
transition,
|
|
43
|
+
)
|
|
44
|
+
from pretense.collections import PretenseDict, PretenseSet, choose_weighted, make_tup
|
|
45
|
+
from pretense.context import get_current_draw, with_draw
|
|
46
|
+
from pretense.compiler import compile_hypothesis_test_case
|
|
47
|
+
from pretense.synthesis import synthesize_unit_test
|
|
48
|
+
|
|
49
|
+
__version__ = "0.2.0"
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"AmbiguousTransitionError",
|
|
53
|
+
"Domain",
|
|
54
|
+
"EnsuresCheck",
|
|
55
|
+
"FakeAdapter",
|
|
56
|
+
"NoMatchingTransitionError",
|
|
57
|
+
"NotBound",
|
|
58
|
+
"NotBoundType",
|
|
59
|
+
"Outcome",
|
|
60
|
+
"PretenseDict",
|
|
61
|
+
"PretenseEngine",
|
|
62
|
+
"PretenseError",
|
|
63
|
+
"PretenseRunResult",
|
|
64
|
+
"PretenseRunnerConfig",
|
|
65
|
+
"PretenseSet",
|
|
66
|
+
"PretenseSpecificationError",
|
|
67
|
+
"Raised",
|
|
68
|
+
"RequiresCheck",
|
|
69
|
+
"StateMachine",
|
|
70
|
+
"StateMachineProto",
|
|
71
|
+
"TraceStep",
|
|
72
|
+
"Transition",
|
|
73
|
+
"TransitionContext",
|
|
74
|
+
"Value",
|
|
75
|
+
"choose_weighted",
|
|
76
|
+
"compile_hypothesis_test_case",
|
|
77
|
+
"get_current_draw",
|
|
78
|
+
"make_tup",
|
|
79
|
+
"synthesize_unit_test",
|
|
80
|
+
"transition",
|
|
81
|
+
"with_draw",
|
|
82
|
+
]
|
pretense/cli.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""CLI runner for Pretense state machines (uv run pretense)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
_curr = Path(__file__).resolve().parent
|
|
8
|
+
_REPO_ROOT = _curr
|
|
9
|
+
while _curr and _curr != _curr.parent:
|
|
10
|
+
if (_curr / "pyproject.toml").is_file():
|
|
11
|
+
_REPO_ROOT = _curr
|
|
12
|
+
break
|
|
13
|
+
_curr = _curr.parent
|
|
14
|
+
|
|
15
|
+
for _p in (_REPO_ROOT, _REPO_ROOT / "autofab_python", _REPO_ROOT / "autofab", _REPO_ROOT / "pretense"):
|
|
16
|
+
_str_p = str(_p)
|
|
17
|
+
if _p.is_dir() and _str_p not in sys.path:
|
|
18
|
+
sys.path.insert(0, _str_p)
|
|
19
|
+
|
|
20
|
+
# Also discover support/lib if run within a role workspace
|
|
21
|
+
_curr = Path.cwd().resolve()
|
|
22
|
+
while _curr and _curr != _curr.parent:
|
|
23
|
+
if (_curr / "support" / "lib").is_dir():
|
|
24
|
+
_sup = str(_curr / "support" / "lib")
|
|
25
|
+
if _sup not in sys.path:
|
|
26
|
+
sys.path.insert(0, _sup)
|
|
27
|
+
break
|
|
28
|
+
_curr = _curr.parent
|
|
29
|
+
|
|
30
|
+
from pretense.parts.pretense.lib.pretense_impl import main, run_pretense_cli
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
sys.exit(main())
|
|
34
|
+
|
|
35
|
+
__all__ = ["main", "run_pretense_cli"]
|
pretense/collections.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Bidirectional collection helpers and recognizers/generators for Pretense."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from collections import deque
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, TypeVar, Union
|
|
6
|
+
|
|
7
|
+
from pretense.context import get_current_draw
|
|
8
|
+
from pretense.sentinels import NotBound, NotBoundType
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
K = TypeVar("K")
|
|
12
|
+
V = TypeVar("V")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_tup(*choosers: Callable[..., Any]) -> Callable[..., Optional[Tuple[Any, ...]]]:
|
|
16
|
+
"""Compose multiple argument recognizers/generators into a single joint tuple chooser.
|
|
17
|
+
|
|
18
|
+
If any individual chooser returns None, the combined chooser returns None (rejection).
|
|
19
|
+
"""
|
|
20
|
+
def combined(state: Any, this: Any, *args: Any) -> Optional[Tuple[Any, ...]]:
|
|
21
|
+
res: List[Any] = []
|
|
22
|
+
for chooser, arg in zip(choosers, args):
|
|
23
|
+
val = chooser(state, this, arg)
|
|
24
|
+
if val is None:
|
|
25
|
+
return None
|
|
26
|
+
res.append(val)
|
|
27
|
+
return tuple(res)
|
|
28
|
+
return combined
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def choose_weighted(*weighted_options: Tuple[float, Callable[[Any], Any]]) -> Any:
|
|
32
|
+
"""Statistical selection across weighted alternatives.
|
|
33
|
+
|
|
34
|
+
Under Hypothesis, uses st.frequency() to allow full byte-stream shrinking.
|
|
35
|
+
Outside Hypothesis, falls back to random.choices.
|
|
36
|
+
"""
|
|
37
|
+
draw = get_current_draw()
|
|
38
|
+
if draw is not None:
|
|
39
|
+
try:
|
|
40
|
+
import hypothesis.strategies as st
|
|
41
|
+
st_mod: Any = st
|
|
42
|
+
freq_strategies = []
|
|
43
|
+
for weight, builder in weighted_options:
|
|
44
|
+
int_weight = max(1, int(round(weight * 100)))
|
|
45
|
+
try:
|
|
46
|
+
strat = builder(st_mod)
|
|
47
|
+
if isinstance(strat, st.SearchStrategy):
|
|
48
|
+
freq_strategies.append((int_weight, strat))
|
|
49
|
+
else:
|
|
50
|
+
freq_strategies.append((int_weight, st.just(strat)))
|
|
51
|
+
except Exception:
|
|
52
|
+
freq_strategies.append((int_weight, st.just(builder(random))))
|
|
53
|
+
return draw(st_mod.frequency(freq_strategies))
|
|
54
|
+
except ImportError:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
# Deterministic fallback / PRNG
|
|
58
|
+
weights = [w for w, _ in weighted_options]
|
|
59
|
+
builders = [b for _, b in weighted_options]
|
|
60
|
+
chosen_builder = random.choices(builders, weights=weights, k=1)[0]
|
|
61
|
+
return chosen_builder(random)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PretenseSet(Set[T]):
|
|
65
|
+
"""State collection tracking rolling tombstones and providing bidirectional helpers."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, elements: Any = ()) -> None:
|
|
68
|
+
super().__init__(elements)
|
|
69
|
+
self._tombstones: deque[T] = deque(maxlen=50)
|
|
70
|
+
|
|
71
|
+
def remove(self, element: T) -> None:
|
|
72
|
+
super().remove(element)
|
|
73
|
+
self._tombstones.append(element)
|
|
74
|
+
|
|
75
|
+
def discard(self, element: object) -> None:
|
|
76
|
+
if element in self:
|
|
77
|
+
super().discard(element)
|
|
78
|
+
self._tombstones.append(element) # type: ignore
|
|
79
|
+
|
|
80
|
+
def get_in(self, val: Union[T, NotBoundType]) -> Optional[T]:
|
|
81
|
+
"""Recognize membership or generate an existing element from the set."""
|
|
82
|
+
if isinstance(val, NotBoundType):
|
|
83
|
+
if not self:
|
|
84
|
+
return None
|
|
85
|
+
draw = get_current_draw()
|
|
86
|
+
if draw is not None:
|
|
87
|
+
try:
|
|
88
|
+
import hypothesis.strategies as st
|
|
89
|
+
return draw(st.sampled_from(list(self)))
|
|
90
|
+
except ImportError:
|
|
91
|
+
pass
|
|
92
|
+
return next(iter(self))
|
|
93
|
+
return val if val in self else None
|
|
94
|
+
|
|
95
|
+
def get_not_in(self, val: Union[T, NotBoundType], prefix: str = "item_") -> Optional[T]:
|
|
96
|
+
"""Recognize non-membership or generate an absent element across 4 tiers."""
|
|
97
|
+
if isinstance(val, NotBoundType):
|
|
98
|
+
draw = get_current_draw()
|
|
99
|
+
if draw is not None:
|
|
100
|
+
try:
|
|
101
|
+
import hypothesis.strategies as st
|
|
102
|
+
# Tier 1: The Graveyard (Tombstone History) - 40% probability if available
|
|
103
|
+
if self._tombstones and draw(st.booleans()) and draw(st.booleans()):
|
|
104
|
+
candidate = draw(st.sampled_from(list(self._tombstones)))
|
|
105
|
+
if candidate not in self:
|
|
106
|
+
return candidate
|
|
107
|
+
|
|
108
|
+
# Tier 2: Near-Miss Mutations - 30% probability if string elements exist
|
|
109
|
+
if self and all(isinstance(x, str) for x in self) and draw(st.booleans()):
|
|
110
|
+
base = str(draw(st.sampled_from(list(self))))
|
|
111
|
+
mutation_type = draw(st.integers(0, 3))
|
|
112
|
+
if mutation_type == 0:
|
|
113
|
+
candidate_str = base.upper() if base.islower() else base.lower()
|
|
114
|
+
elif mutation_type == 1:
|
|
115
|
+
candidate_str = base + " "
|
|
116
|
+
elif mutation_type == 2:
|
|
117
|
+
candidate_str = "_" + base
|
|
118
|
+
else:
|
|
119
|
+
candidate_str = base + "_alt"
|
|
120
|
+
if candidate_str not in self:
|
|
121
|
+
return candidate_str # type: ignore
|
|
122
|
+
|
|
123
|
+
# Tier 3: Open Universe Fuzzing
|
|
124
|
+
if not self or all(isinstance(x, str) for x in self):
|
|
125
|
+
fresh_str = draw(st.text(min_size=1, max_size=10).filter(lambda s: s not in self))
|
|
126
|
+
return fresh_str # type: ignore
|
|
127
|
+
except ImportError:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
# Tier 4: Deterministic Sequential Fallback
|
|
131
|
+
idx = len(self)
|
|
132
|
+
candidate = f"{prefix}{idx}"
|
|
133
|
+
while candidate in self:
|
|
134
|
+
idx += 1
|
|
135
|
+
candidate = f"{prefix}{idx}"
|
|
136
|
+
return candidate # type: ignore
|
|
137
|
+
|
|
138
|
+
return val if val not in self else None
|
|
139
|
+
|
|
140
|
+
def get_subset(self, val: Union[Set[T], NotBoundType]) -> Optional[Set[T]]:
|
|
141
|
+
"""Recognize a subset or generate a valid subset from this set."""
|
|
142
|
+
if isinstance(val, NotBoundType):
|
|
143
|
+
draw = get_current_draw()
|
|
144
|
+
if draw is not None:
|
|
145
|
+
try:
|
|
146
|
+
import hypothesis.strategies as st
|
|
147
|
+
return set(draw(st.lists(st.sampled_from(list(self)), unique=True))) if self else set()
|
|
148
|
+
except ImportError:
|
|
149
|
+
pass
|
|
150
|
+
return set()
|
|
151
|
+
return val if isinstance(val, (set, frozenset)) and val.issubset(self) else None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class PretenseDict(Dict[K, V]):
|
|
155
|
+
"""Dictionary collection tracking key tombstones for bidirectional key recognition/generation."""
|
|
156
|
+
|
|
157
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
158
|
+
super().__init__(*args, **kwargs)
|
|
159
|
+
self._tombstones: deque[K] = deque(maxlen=50)
|
|
160
|
+
|
|
161
|
+
def __delitem__(self, key: K) -> None:
|
|
162
|
+
super().__delitem__(key)
|
|
163
|
+
self._tombstones.append(key)
|
|
164
|
+
|
|
165
|
+
def pop(self, key: K, *args: Any) -> V:
|
|
166
|
+
res = super().pop(key, *args)
|
|
167
|
+
self._tombstones.append(key)
|
|
168
|
+
return res
|
|
169
|
+
|
|
170
|
+
def get_key_in(self, key: Union[K, NotBoundType]) -> Optional[K]:
|
|
171
|
+
"""Recognize key existence or generate an existing key."""
|
|
172
|
+
if isinstance(key, NotBoundType):
|
|
173
|
+
if not self:
|
|
174
|
+
return None
|
|
175
|
+
draw = get_current_draw()
|
|
176
|
+
if draw is not None:
|
|
177
|
+
try:
|
|
178
|
+
import hypothesis.strategies as st
|
|
179
|
+
return draw(st.sampled_from(list(self.keys())))
|
|
180
|
+
except ImportError:
|
|
181
|
+
pass
|
|
182
|
+
return next(iter(self.keys()))
|
|
183
|
+
return key if key in self else None
|
|
184
|
+
|
|
185
|
+
def get_key_not_in(self, key: Union[K, NotBoundType], prefix: str = "key_") -> Optional[K]:
|
|
186
|
+
"""Recognize key absence or generate an absent key."""
|
|
187
|
+
if isinstance(key, NotBoundType):
|
|
188
|
+
draw = get_current_draw()
|
|
189
|
+
if draw is not None:
|
|
190
|
+
try:
|
|
191
|
+
import hypothesis.strategies as st
|
|
192
|
+
if self._tombstones and draw(st.booleans()):
|
|
193
|
+
cand = draw(st.sampled_from(list(self._tombstones)))
|
|
194
|
+
if cand not in self:
|
|
195
|
+
return cand
|
|
196
|
+
if not self or all(isinstance(k, str) for k in self.keys()):
|
|
197
|
+
return draw(st.text(min_size=1, max_size=10).filter(lambda s: s not in self)) # type: ignore
|
|
198
|
+
except ImportError:
|
|
199
|
+
pass
|
|
200
|
+
idx = len(self)
|
|
201
|
+
cand_str = f"{prefix}{idx}"
|
|
202
|
+
while cand_str in self:
|
|
203
|
+
idx += 1
|
|
204
|
+
cand_str = f"{prefix}{idx}"
|
|
205
|
+
return cand_str # type: ignore
|
|
206
|
+
return key if key not in self else None
|
pretense/compiler.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Hypothesis stateful test case generation for Pretense prototypes."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
|
+
import pretense.parts.pretense.lib.pretense_impl as _impl
|
|
5
|
+
|
|
6
|
+
_ACTIVE_STEP: Any = None
|
|
7
|
+
_ACTIVE_TRACE: List[Any] = []
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_active_step() -> Any:
|
|
11
|
+
global _ACTIVE_STEP
|
|
12
|
+
if _ACTIVE_STEP is not None:
|
|
13
|
+
return _ACTIVE_STEP
|
|
14
|
+
return _impl.get_active_step()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_active_trace() -> List[Any]:
|
|
18
|
+
global _ACTIVE_TRACE
|
|
19
|
+
if _ACTIVE_TRACE:
|
|
20
|
+
return list(_ACTIVE_TRACE)
|
|
21
|
+
return _impl.get_active_trace()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def format_active_step_diagnostic(*args: Any, **kwargs: Any) -> Optional[str]:
|
|
25
|
+
global _ACTIVE_STEP, _ACTIVE_TRACE
|
|
26
|
+
if _ACTIVE_STEP is not None:
|
|
27
|
+
lines = [
|
|
28
|
+
f"Active Pretense Transition: '{_ACTIVE_STEP.transition_name}' (method: {_ACTIVE_STEP.target_name or '<seeding>'})",
|
|
29
|
+
f"Arguments: {_ACTIVE_STEP.args!r}",
|
|
30
|
+
]
|
|
31
|
+
if len(_ACTIVE_TRACE) > 1:
|
|
32
|
+
lines.append(f"Preceding Steps ({len(_ACTIVE_TRACE) - 1}):")
|
|
33
|
+
for i, step in enumerate(_ACTIVE_TRACE[:-1], 1):
|
|
34
|
+
args_str = ", ".join(repr(a) for a in step.args)
|
|
35
|
+
lines.append(f" {i}. {step.transition_name}({args_str})")
|
|
36
|
+
return "\n".join(lines)
|
|
37
|
+
return _impl.format_active_step_diagnostic(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
compile_hypothesis_test_case = _impl.compile_hypothesis_test_case
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"compile_hypothesis_test_case",
|
|
44
|
+
"format_active_step_diagnostic",
|
|
45
|
+
"get_active_step",
|
|
46
|
+
"get_active_trace",
|
|
47
|
+
]
|
pretense/context.py
ADDED
pretense/fake.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Pretense part package marker.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Pretense Part (`update_python_with_ai/parts/pretense`)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The **pretense** part provides Cleanroom's unified state-machine-driven testing, verification, and fake synthesis engine.
|
|
6
|
+
|
|
7
|
+
Software verification driven purely by example-based testing or AST statement coverage suffers from severe blind spots in LLM-generated code: high statement coverage often greenlights state desynchronization, omission bugs, and boundary condition regressions. Pretense solves this by expressing behavioral specifications as executable state transition models. Powered by property-based conjecture search and minimal counterexample shrinking, Pretense verifies state-transition invariants, synthesizes zero-maintenance interface fakes, enforces execution watchdogs against infinite loops, and drives differential testing between specifications and implementations.
|
|
8
|
+
|
|
9
|
+
## Key Components & Specifications
|
|
10
|
+
|
|
11
|
+
- [`pretense.md`](file:///Users/seanmcdirmid/projects/cleanroom/update_python_with_ai/parts/pretense/high/pretense.md): Defines the core state machine abstractions, sentinels, outcome descriptors, transition models, stateful collection structures, hypothesis draw contexts, fake adapter contracts, and the `PretenseEngine` interface.
|
|
12
|
+
- [`pretense_impl.md`](file:///Users/seanmcdirmid/projects/cleanroom/update_python_with_ai/parts/pretense/high/pretense_impl.md): Realizes the dynamic Hypothesis state machine compiler, active step diagnostic tracking, minimal counterexample unit test synthesis, watchdog timeout enforcement, test discovery, and the `pretense` command-line test runner.
|
|
13
|
+
|
|
14
|
+
## How It Is Used by Other Parts
|
|
15
|
+
|
|
16
|
+
Pretense serves as Cleanroom's primary state-machine testing and contract verification engine:
|
|
17
|
+
- **`cleanroom_roles.toml`**: Configures the `pretense`, `pretense_qa`, and `pretense_coverage` roles to invoke `uv run pretense ...` across all parts.
|
|
18
|
+
- **`update_python_with_ai/support`**: `pretense_lint.py` enforces structural rules on `*_pretense.py` files and validates that state machines conform to Pretense contracts.
|
|
19
|
+
- **`benchmark`**: Provisions `.env` watchdogs (`PYTHONFAULTHANDLER=1`, `PRETENSE_TIMEOUT=45.0`) and provides baseline developer models with Pretense verification tools.
|
|
20
|
+
- **All constituent parts**: Every Cleanroom part maintains executable state machine specifications under `parts/<name>/pretense/` targeting the part's implementation modules.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# pretense interface component
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The pretense interface component defines the declarative state machine abstractions, sentinels, collection models, transition specifications, fake adapters, and verification engine contracts for stateful software testing in Cleanroom.
|
|
6
|
+
|
|
7
|
+
Example-based testing and AST statement coverage frequently miss critical state desynchronizations, boundary regressions, and omitted invariant logic in neural-generated code. By defining stateful systems as executable state transition models rather than static unit test sequences, this component enables automated property-based exploration, invariant checking across arbitrary operation orderings, and automatic fake synthesis. Centralizing these state abstractions prevents fragmentation across test harnesses and ensures that specification contracts remain mathematically verifiable.
|
|
8
|
+
|
|
9
|
+
**Out of scope:** Subprocess invocation, filesystem scanning, and shell argument parsing; these are handled by other components.
|
|
10
|
+
|
|
11
|
+
## Types and Behavior
|
|
12
|
+
|
|
13
|
+
A system's *pretense engine* coordinates state machine compilation, test suite execution, and fake generation.
|
|
14
|
+
|
|
15
|
+
A *pretense sentinel* distinguishes unprovided inputs and specific runtime states from standard program values. The sentinel *not bound* indicates that a parameter was omitted by a caller. An *execution outcome* encapsulates the result of evaluating an operation against an implementation under test. A *value outcome* wraps a successful return value. A *raised outcome* wraps an expected or intercepted exception instance. A *specification error* indicates that a state machine contract, transition invariant, or antecedent predicate was violated.
|
|
16
|
+
|
|
17
|
+
A *draw context* provides a dynamically bound generator for sampling values during property-based test exploration. A *pretense collection* provides an observable state container that tracks mutations and verifies invariants. A *pretense dictionary* provides an observable key-value mapping with existence assertions and weighted key selection. A *pretense set* provides an observable unique item container with element sampling and existence assertions.
|
|
18
|
+
|
|
19
|
+
A *state transition* defines a single atomic operation step within a state machine. Each state transition specifies a *requires* condition that must hold before the operation executes, an *argument binder* that generates or recognizes operation arguments, an expected return outcome or contracted exception, an *ensures* postcondition verified against the concrete singleton under test during real test execution, and an *update* condition that applies reference model state mutations. An *argument domain* unifies generative sampling with analytical validation for an operation parameter. A domain produces randomized values conforming to its specification during property fuzzing and validates whether arbitrary candidate values belong to its domain during adapter dispatch. Argument domains support scalar categoricals, discrete ranges, lexical patterns, structural data types, dependent parameter domains whose boundaries derive dynamically from preceding arguments, and constructive perturbations that derive candidate values from anchor values via targeted near-boundary mutations. An argument binder binds transition arguments exclusively from explicit domains, native collections, ranges, patterns, data types, dependent domain functions, and keyword domain mappings. An unresolved domain placeholder enforces explicit domain definition by failing at runtime if left unspecialized. An argument binder generates operation arguments from direct values, callables, domains, or property-based search strategies treated directly as values, automatically drawing randomized instances from active draw contexts during property fuzzing while drawing default examples when evaluated outside fuzzing contexts. A *transition context* provides methods for specifying requires preconditions, argument binders, expected returns, contracted exceptions, ensures postconditions, and model updates for a single transition method. A transition context provides a *check helper* for evaluating requirements without exception overhead and evaluating postconditions against immediate and collaborating singletons under test during real test runs while omitting ensures verification during fake adapter dispatch. A transition context strictly enforces mutual exclusivity between expected returns and contracted exceptions: declaring both on the same transition context raises an invalid transition definition error. When synthesizing fake exceptions from contracted exception types, the transition context safely instantiates exception classes that declare mandatory positional parameters. A *transition decorator* registers a transition method against a target singleton class operation.
|
|
20
|
+
|
|
21
|
+
A *state machine* declares the complete behavioral contract of an interface or implementation through a collection of state transitions and system invariants. A state machine stores singletons under test as direct references in its state representation to drive implementation logic under test, or provides synthesized in-memory fake adapters backed by the shared reference model. A *fake adapter* acts as a dynamic test double conforming to an interface protocol, delegating method invocations directly to matching state transitions and verifying preconditions. A *trace step* records the operation name, arguments, and outcome of an executed transition within a state machine run.
|
|
22
|
+
|
|
23
|
+
A *runner configuration* specifies test execution parameters including target paths, fuzzing iterations, statement coverage thresholds, execution timeout durations, and active step diagnostic reporting. A *run result* reports execution metrics, including passed test counts, failure counts, statement coverage percentages, and synthesized counterexamples.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# pretense_impl implementation component
|
|
2
|
+
|
|
3
|
+
implements: pretense
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The pretense_impl implementation component realizes the state machine compiler, diagnostic tracer, fake adapter proxy, execution watchdog, and test runner for the pretense engine.
|
|
8
|
+
|
|
9
|
+
Executing stateful test suites against neural implementations requires robust test case synthesis, minimal counterexample shrinking, and failsafe execution bounds. Without automatic shrinking and active step tracing, developers and models struggle to debug complex multi-step failures. Without execution watchdogs, unbounded loops or blocking operations hang test runners indefinitely. This component bridges abstract state models to property-based test engines, synthesizes reproducible unit test files from minimized counterexamples, and guarantees execution termination through signal-based watchdogs.
|
|
10
|
+
|
|
11
|
+
**Out of scope:** Static type checking, code generation templates, and language server protocols; these are handled by other components.
|
|
12
|
+
|
|
13
|
+
## Types and Behavior
|
|
14
|
+
|
|
15
|
+
The *pretense engine realization* implements the pretense engine service on the system tier.
|
|
16
|
+
|
|
17
|
+
A *state machine compiler* translates declarative state machine specifications into executable property-based test suites. The compiler inspects each declared state transition on a state machine, binding requires guards to test generation preconditions, and evaluating ensures postconditions against the concrete system under test after invocation and outcome recognition, while applying model update mutations to backing state. When a postcondition or invariant fails during property search, the compiler shrinks the failure sequence to a minimal sequence of trace steps and formats an *active step diagnostic* detailing the active transition, input arguments, and current state snapshot. When the system under test produces an outcome rejected by the transition's return recognizer, the active step diagnostic details whether the transition was configured expecting an exception or a return value.
|
|
18
|
+
|
|
19
|
+
A *unit test synthesizer* generates a standalone, reproducible Python test function from a shrunken counterexample sequence. The synthesized test instantiates the system under test, applies each step in sequence with literal arguments, and verifies that the failure reproduces deterministically.
|
|
20
|
+
|
|
21
|
+
A *watchdog monitor* oversees test execution using a dual-layer timeout mechanism. A software timer emits a warning before expiration, dumping active step diagnostics to standard error. Concurrently, a system-level fault handler registers an abrupt termination alarm that interrupts blocking system calls or infinite loops, exiting with a non-zero status code to prevent test runner hangs.
|
|
22
|
+
|
|
23
|
+
A *test runner coordinator* scans target directories for state machine test files, rejecting loading errors or missing test targets with diagnostics and a non-zero exit status code rather than reporting success. The coordinator executes test cases within configured execution phases, verifies statement coverage thresholds against the corresponding implementation modules under test (excluding abstract interface stubs), suppresses property fuzzing during coverage evaluation to bound execution to deterministic test suites while reserving fuzzing for QA verification, formats and reports all failure and error details across all executed test methods in failing test targets to prevent defect masking, formats auditor guidance reminders upon test failures and coverage deficits directing the agent to formulate blame feedback strictly in terms of how the blamed file is not compliant with the common source of truth specification, permitting line numbers only within the blamed file while strictly forbidding exposing details of other files, and reports aggregated test outcomes through a run result payload, exiting with a non-zero exit status code whenever any test fails, any test target fails to load, zero test targets are executed, or coverage thresholds are not met.
|