crosshair-tool 0.0.56__cp39-cp39-macosx_11_0_arm64.whl → 0.0.100__cp39-cp39-macosx_11_0_arm64.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.
- _crosshair_tracers.cpython-39-darwin.so +0 -0
- crosshair/__init__.py +1 -1
- crosshair/_mark_stacks.h +51 -24
- crosshair/_tracers.h +9 -5
- crosshair/_tracers_test.py +19 -9
- crosshair/auditwall.py +9 -8
- crosshair/auditwall_test.py +31 -19
- crosshair/codeconfig.py +3 -2
- crosshair/condition_parser.py +17 -133
- crosshair/condition_parser_test.py +54 -96
- crosshair/conftest.py +1 -1
- crosshair/copyext.py +91 -22
- crosshair/copyext_test.py +33 -0
- crosshair/core.py +259 -203
- crosshair/core_and_libs.py +20 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +693 -664
- crosshair/diff_behavior.py +76 -21
- crosshair/diff_behavior_test.py +132 -23
- crosshair/dynamic_typing.py +128 -18
- crosshair/dynamic_typing_test.py +91 -4
- crosshair/enforce.py +1 -6
- crosshair/enforce_test.py +15 -23
- crosshair/examples/check_examples_test.py +2 -1
- crosshair/fnutil.py +2 -3
- crosshair/fnutil_test.py +0 -7
- crosshair/fuzz_core_test.py +70 -83
- crosshair/libimpl/arraylib.py +10 -7
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +5 -5
- crosshair/libimpl/builtinslib.py +1002 -682
- crosshair/libimpl/builtinslib_ch_test.py +108 -30
- crosshair/libimpl/builtinslib_test.py +431 -143
- crosshair/libimpl/codecslib.py +22 -2
- crosshair/libimpl/codecslib_test.py +41 -9
- crosshair/libimpl/collectionslib.py +44 -8
- crosshair/libimpl/collectionslib_test.py +108 -20
- crosshair/libimpl/copylib.py +1 -1
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +84 -67
- crosshair/libimpl/datetimelib_ch_test.py +12 -7
- crosshair/libimpl/datetimelib_test.py +5 -6
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/_encutil.py +21 -11
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +19 -7
- crosshair/libimpl/functoolslib_test.py +22 -6
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +32 -5
- crosshair/libimpl/heapqlib_test.py +15 -12
- crosshair/libimpl/iolib.py +7 -4
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib_test.py +1 -1
- crosshair/libimpl/mathlib.py +165 -2
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +59 -16
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +1 -0
- crosshair/libimpl/randomlib_test.py +6 -4
- crosshair/libimpl/relib.py +180 -59
- crosshair/libimpl/relib_ch_test.py +26 -2
- crosshair/libimpl/relib_test.py +77 -14
- crosshair/libimpl/timelib.py +35 -13
- crosshair/libimpl/timelib_test.py +13 -3
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib_test.py +3 -3
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +21 -10
- crosshair/main.py +48 -28
- crosshair/main_test.py +59 -14
- crosshair/objectproxy.py +39 -14
- crosshair/objectproxy_test.py +27 -13
- crosshair/opcode_intercept.py +212 -24
- crosshair/opcode_intercept_test.py +172 -18
- crosshair/options.py +0 -1
- crosshair/patch_equivalence_test.py +5 -21
- crosshair/path_cover.py +7 -5
- crosshair/path_search.py +6 -4
- crosshair/path_search_test.py +1 -2
- crosshair/pathing_oracle.py +53 -10
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer_test.py +5 -21
- crosshair/register_contract.py +16 -6
- crosshair/register_contract_test.py +2 -14
- crosshair/simplestructs.py +154 -85
- crosshair/simplestructs_test.py +16 -2
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +319 -196
- crosshair/statespace_test.py +45 -0
- crosshair/stubs_parser.py +0 -2
- crosshair/test_util.py +87 -25
- crosshair/test_util_test.py +26 -0
- crosshair/tools/check_init_and_setup_coincide.py +0 -3
- crosshair/tools/generate_demo_table.py +2 -2
- crosshair/tracers.py +141 -49
- crosshair/type_repo.py +11 -4
- crosshair/unicode_categories.py +1 -0
- crosshair/util.py +158 -76
- crosshair/util_test.py +13 -20
- crosshair/watcher.py +4 -4
- crosshair/z3util.py +1 -1
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
- crosshair_tool-0.0.100.dist-info/RECORD +176 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
- crosshair/examples/hypothesis/__init__.py +0 -2
- crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
- crosshair_tool-0.0.56.dist-info/RECORD +0 -152
- /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/top_level.txt +0 -0
crosshair/condition_parser.py
CHANGED
|
@@ -10,7 +10,7 @@ import traceback
|
|
|
10
10
|
import types
|
|
11
11
|
from dataclasses import dataclass, replace
|
|
12
12
|
from functools import partial, wraps
|
|
13
|
-
from inspect import BoundArguments,
|
|
13
|
+
from inspect import BoundArguments, Signature
|
|
14
14
|
from itertools import chain
|
|
15
15
|
from typing import (
|
|
16
16
|
Any,
|
|
@@ -41,35 +41,22 @@ try:
|
|
|
41
41
|
except ModuleNotFoundError:
|
|
42
42
|
deal = None # type: ignore
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
import hypothesis
|
|
47
|
-
from hypothesis import strategies as st
|
|
48
|
-
from hypothesis.control import BuildContext
|
|
49
|
-
from hypothesis.database import ExampleDatabase
|
|
50
|
-
from hypothesis.internal.conjecture.data import ConjectureData
|
|
51
|
-
except ImportError:
|
|
52
|
-
hypothesis = None # type: ignore
|
|
53
|
-
ExampleDatabase = object # type: ignore
|
|
54
|
-
|
|
55
44
|
from crosshair.auditwall import opened_auditwall
|
|
56
45
|
from crosshair.fnutil import FunctionInfo, fn_globals, set_first_arg_type
|
|
57
46
|
from crosshair.options import AnalysisKind
|
|
58
47
|
from crosshair.register_contract import get_contract
|
|
59
48
|
from crosshair.tracers import NoTracing
|
|
60
49
|
from crosshair.util import (
|
|
50
|
+
CrossHairInternal,
|
|
61
51
|
DynamicScopeVar,
|
|
62
52
|
EvalFriendlyReprContext,
|
|
63
53
|
IdKeyedDict,
|
|
64
|
-
IgnoreAttempt,
|
|
65
|
-
UnexploredPath,
|
|
66
54
|
debug,
|
|
67
55
|
eval_friendly_repr,
|
|
68
56
|
format_boundargs,
|
|
69
57
|
frame_summary_for_fn,
|
|
70
58
|
is_pure_python,
|
|
71
59
|
sourcelines,
|
|
72
|
-
test_stack,
|
|
73
60
|
)
|
|
74
61
|
|
|
75
62
|
|
|
@@ -128,7 +115,7 @@ def get_doc_lines(thing: object) -> Iterable[Tuple[int, str]]:
|
|
|
128
115
|
if not isinstance(firstnode, ast.Expr):
|
|
129
116
|
return
|
|
130
117
|
strnode = firstnode.value
|
|
131
|
-
if not isinstance(strnode, ast.
|
|
118
|
+
if not (isinstance(strnode, ast.Constant) and isinstance(strnode.value, str)):
|
|
132
119
|
return
|
|
133
120
|
end_lineno = getattr(strnode, "end_lineno", None)
|
|
134
121
|
if end_lineno is not None:
|
|
@@ -457,7 +444,7 @@ class ConditionParser:
|
|
|
457
444
|
def get_class_conditions(self, cls: type) -> ClassConditions:
|
|
458
445
|
raise NotImplementedError
|
|
459
446
|
|
|
460
|
-
def class_can_have_conditions(
|
|
447
|
+
def class_can_have_conditions(self, cls: type) -> bool:
|
|
461
448
|
raise NotImplementedError
|
|
462
449
|
|
|
463
450
|
|
|
@@ -478,7 +465,7 @@ class ConcreteConditionParser(ConditionParser):
|
|
|
478
465
|
"""
|
|
479
466
|
raise NotImplementedError
|
|
480
467
|
|
|
481
|
-
def class_can_have_conditions(
|
|
468
|
+
def class_can_have_conditions(self, cls: type) -> bool:
|
|
482
469
|
# We can't get conditions/line numbers for classes written in C.
|
|
483
470
|
return is_pure_python(cls)
|
|
484
471
|
|
|
@@ -499,6 +486,7 @@ class ConcreteConditionParser(ConditionParser):
|
|
|
499
486
|
method = cls.__dict__.get(method_name, None)
|
|
500
487
|
super_method_conditions = super_methods.get(method_name)
|
|
501
488
|
if super_method_conditions is not None:
|
|
489
|
+
# Re-type the super's `self` argument to be this class:
|
|
502
490
|
revised_sig = set_first_arg_type(super_method_conditions.sig, cls)
|
|
503
491
|
super_method_conditions = replace(
|
|
504
492
|
super_method_conditions, sig=revised_sig
|
|
@@ -524,13 +512,16 @@ class ConcreteConditionParser(ConditionParser):
|
|
|
524
512
|
# Selectively add conditions inferred from invariants:
|
|
525
513
|
final_pre = list(conditions.pre)
|
|
526
514
|
final_post = list(conditions.post)
|
|
527
|
-
if method_name in (
|
|
528
|
-
#
|
|
529
|
-
#
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
#
|
|
515
|
+
if method_name in (
|
|
516
|
+
"__new__", # a staticmethod, but not isinstance(staticmethod)
|
|
517
|
+
"__repr__", # is itself required for reporting problems with invariants.
|
|
518
|
+
# [set/del]attr can do anything; we can't resonably enforce invariants:
|
|
519
|
+
"__setattr__",
|
|
520
|
+
"__delattr__",
|
|
521
|
+
"__replace__", # Will raise an exception with most arbitrary **kwargs.
|
|
522
|
+
"__annotate__", # a staticmethod, but not isinstance(staticmethod)
|
|
523
|
+
"__annotate_func__",
|
|
524
|
+
):
|
|
534
525
|
pass
|
|
535
526
|
elif method_name == "__del__":
|
|
536
527
|
final_pre.extend(inv)
|
|
@@ -1117,112 +1108,6 @@ class AssertsParser(ConcreteConditionParser):
|
|
|
1117
1108
|
return []
|
|
1118
1109
|
|
|
1119
1110
|
|
|
1120
|
-
class CrossHairDatabaseWrapper(ExampleDatabase):
|
|
1121
|
-
"""Save buffers to the underlying database but discard all other actions."""
|
|
1122
|
-
|
|
1123
|
-
def __init__(self, db: ExampleDatabase) -> None:
|
|
1124
|
-
super().__init__()
|
|
1125
|
-
self._db = db
|
|
1126
|
-
from crosshair.core import realize
|
|
1127
|
-
|
|
1128
|
-
self._realize = realize
|
|
1129
|
-
|
|
1130
|
-
def save(self, key: bytes, value: bytes) -> None:
|
|
1131
|
-
with NoTracing(), opened_auditwall():
|
|
1132
|
-
realkey = self._realize(key)
|
|
1133
|
-
realvalue = self._realize(value)
|
|
1134
|
-
self._db.save(realkey, realvalue)
|
|
1135
|
-
|
|
1136
|
-
def fetch(self, key: bytes) -> Iterable[bytes]:
|
|
1137
|
-
return ()
|
|
1138
|
-
|
|
1139
|
-
def delete(self, key: bytes, value: bytes) -> None:
|
|
1140
|
-
pass
|
|
1141
|
-
|
|
1142
|
-
def move(self, src: bytes, dest: bytes, value: bytes) -> None:
|
|
1143
|
-
pass
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
class HypothesisParser(ConcreteConditionParser):
|
|
1147
|
-
def __init__(self, toplevel_parser: Optional[ConditionParser] = None):
|
|
1148
|
-
super().__init__(toplevel_parser)
|
|
1149
|
-
|
|
1150
|
-
def _generate_args(self, payload: bytes, decorated_fn: Callable):
|
|
1151
|
-
given_kwargs = decorated_fn.hypothesis._given_kwargs # type: ignore
|
|
1152
|
-
strategy = st.fixed_dictionaries(given_kwargs)
|
|
1153
|
-
data = ConjectureData.for_buffer(payload)
|
|
1154
|
-
with BuildContext(data):
|
|
1155
|
-
return data.draw(strategy)
|
|
1156
|
-
|
|
1157
|
-
def _format_counterexample(
|
|
1158
|
-
self,
|
|
1159
|
-
fn: Callable,
|
|
1160
|
-
args: BoundArguments,
|
|
1161
|
-
return_val: object,
|
|
1162
|
-
repr_overrides: IdKeyedDict,
|
|
1163
|
-
) -> Tuple[str, str]:
|
|
1164
|
-
payload_bytes = args.arguments["payload"]
|
|
1165
|
-
kwargs = self._generate_args(payload_bytes, fn)
|
|
1166
|
-
sig = inspect.signature(fn.hypothesis.inner_test) # type: ignore
|
|
1167
|
-
real_args = sig.bind(**kwargs)
|
|
1168
|
-
return default_counterexample(fn.__name__, real_args, None, repr_overrides)
|
|
1169
|
-
|
|
1170
|
-
def get_fn_conditions(self, ctxfn: FunctionInfo) -> Optional[Conditions]:
|
|
1171
|
-
fn_and_sig = ctxfn.get_callable()
|
|
1172
|
-
if fn_and_sig is None:
|
|
1173
|
-
return None
|
|
1174
|
-
(fn, sig) = fn_and_sig
|
|
1175
|
-
if not getattr(fn, "is_hypothesis_test", False):
|
|
1176
|
-
return None
|
|
1177
|
-
handle = getattr(fn, "hypothesis", None)
|
|
1178
|
-
if handle is None:
|
|
1179
|
-
return None
|
|
1180
|
-
|
|
1181
|
-
# Mess with the settings to wrap whatever database we're using for CrossHair
|
|
1182
|
-
if hasattr(fn, "_hypothesis_internal_use_settings"):
|
|
1183
|
-
db = fn._hypothesis_internal_use_settings.database # type: ignore
|
|
1184
|
-
fn._hypothesis_internal_use_settings = hypothesis.settings( # type: ignore
|
|
1185
|
-
database=CrossHairDatabaseWrapper(db),
|
|
1186
|
-
phases=[hypothesis.Phase.generate],
|
|
1187
|
-
)
|
|
1188
|
-
fuzz_one = getattr(handle, "fuzz_one_input", None)
|
|
1189
|
-
if fuzz_one is None:
|
|
1190
|
-
return None
|
|
1191
|
-
|
|
1192
|
-
filename, first_line, _lines = sourcelines(fn)
|
|
1193
|
-
post = [
|
|
1194
|
-
ConditionExpr(
|
|
1195
|
-
POSTCONDITION,
|
|
1196
|
-
lambda _: True,
|
|
1197
|
-
filename,
|
|
1198
|
-
first_line,
|
|
1199
|
-
"",
|
|
1200
|
-
)
|
|
1201
|
-
]
|
|
1202
|
-
sig = inspect.Signature(
|
|
1203
|
-
parameters=[
|
|
1204
|
-
inspect.Parameter(
|
|
1205
|
-
"payload", inspect.Parameter.POSITIONAL_ONLY, annotation=bytes
|
|
1206
|
-
)
|
|
1207
|
-
]
|
|
1208
|
-
)
|
|
1209
|
-
|
|
1210
|
-
return Conditions(
|
|
1211
|
-
fuzz_one,
|
|
1212
|
-
fn,
|
|
1213
|
-
[], # (pre)
|
|
1214
|
-
post,
|
|
1215
|
-
raises=frozenset(),
|
|
1216
|
-
sig=sig,
|
|
1217
|
-
mutable_args=None,
|
|
1218
|
-
fn_syntax_messages=[],
|
|
1219
|
-
counterexample_description_maker=partial(self._format_counterexample, fn),
|
|
1220
|
-
)
|
|
1221
|
-
|
|
1222
|
-
def get_class_invariants(self, cls: type) -> List[ConditionExpr]:
|
|
1223
|
-
return []
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
1111
|
class RegisteredContractsParser(ConcreteConditionParser):
|
|
1227
1112
|
"""Parser for manually registered contracts."""
|
|
1228
1113
|
|
|
@@ -1310,7 +1195,7 @@ class RegisteredContractsParser(ConcreteConditionParser):
|
|
|
1310
1195
|
fn_syntax_messages=[],
|
|
1311
1196
|
)
|
|
1312
1197
|
|
|
1313
|
-
def class_can_have_conditions(
|
|
1198
|
+
def class_can_have_conditions(self, cls: type) -> bool:
|
|
1314
1199
|
# We might have registered contracts for classes written in C, so we don't want
|
|
1315
1200
|
# to skip evaluating conditions on the class methods.
|
|
1316
1201
|
return True
|
|
@@ -1325,7 +1210,6 @@ _PARSER_MAP = {
|
|
|
1325
1210
|
AnalysisKind.PEP316: Pep316Parser,
|
|
1326
1211
|
AnalysisKind.icontract: IcontractParser,
|
|
1327
1212
|
AnalysisKind.deal: DealParser,
|
|
1328
|
-
AnalysisKind.hypothesis: HypothesisParser,
|
|
1329
1213
|
}
|
|
1330
1214
|
|
|
1331
1215
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import json
|
|
3
3
|
import sys
|
|
4
|
-
import textwrap
|
|
5
|
-
import unittest
|
|
6
4
|
from typing import List
|
|
7
5
|
|
|
8
6
|
import pytest
|
|
@@ -11,7 +9,6 @@ from crosshair.condition_parser import (
|
|
|
11
9
|
AssertsParser,
|
|
12
10
|
CompositeConditionParser,
|
|
13
11
|
DealParser,
|
|
14
|
-
HypothesisParser,
|
|
15
12
|
IcontractParser,
|
|
16
13
|
Pep316Parser,
|
|
17
14
|
parse_sections,
|
|
@@ -31,11 +28,6 @@ try:
|
|
|
31
28
|
except ImportError:
|
|
32
29
|
deal = None # type: ignore
|
|
33
30
|
|
|
34
|
-
try:
|
|
35
|
-
import hypothesis # type: ignore
|
|
36
|
-
except ImportError:
|
|
37
|
-
hypothesis = None # type: ignore
|
|
38
|
-
|
|
39
31
|
|
|
40
32
|
class LocallyDefiendException(Exception):
|
|
41
33
|
pass
|
|
@@ -139,41 +131,32 @@ def test_parse_sphinx_raises() -> None:
|
|
|
139
131
|
assert parse_sphinx_raises(sphinx_raises) == {LocallyDefiendException}
|
|
140
132
|
|
|
141
133
|
|
|
142
|
-
class
|
|
134
|
+
class TestPep316Parser:
|
|
143
135
|
def test_class_parse(self) -> None:
|
|
144
136
|
class_conditions = Pep316Parser().get_class_conditions(Foo)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
set(class_conditions.methods.keys()), set(["isready", "__init__"])
|
|
151
|
-
)
|
|
137
|
+
assert set([c.expr_source for c in class_conditions.inv]) == {
|
|
138
|
+
"self.x >= 0",
|
|
139
|
+
"self.y >= 0",
|
|
140
|
+
}
|
|
141
|
+
assert {"isready", "__init__"} <= set(class_conditions.methods.keys())
|
|
152
142
|
method = class_conditions.methods["isready"]
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
143
|
+
assert set([c.expr_source for c in method.pre]) == {
|
|
144
|
+
"self.x >= 0",
|
|
145
|
+
"self.y >= 0",
|
|
146
|
+
}
|
|
157
147
|
startlineno = inspect.getsourcelines(Foo)[1]
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
("self.y >= 0", startlineno + 12),
|
|
164
|
-
("__return__ == (self.x == 0)", startlineno + 24),
|
|
165
|
-
]
|
|
166
|
-
),
|
|
167
|
-
)
|
|
148
|
+
assert set([(c.expr_source, c.line) for c in method.post]) == {
|
|
149
|
+
("self.x >= 0", startlineno + 7),
|
|
150
|
+
("self.y >= 0", startlineno + 12),
|
|
151
|
+
("__return__ == (self.x == 0)", startlineno + 24),
|
|
152
|
+
}
|
|
168
153
|
|
|
169
154
|
def test_single_line_condition(self) -> None:
|
|
170
155
|
conditions = Pep316Parser().get_fn_conditions(
|
|
171
156
|
FunctionInfo.from_fn(single_line_condition)
|
|
172
157
|
)
|
|
173
158
|
assert conditions is not None
|
|
174
|
-
|
|
175
|
-
set([c.expr_source for c in conditions.post]), set(["__return__ >= x"])
|
|
176
|
-
)
|
|
159
|
+
assert set([c.expr_source for c in conditions.post]) == {"__return__ >= x"}
|
|
177
160
|
|
|
178
161
|
def test_implies_condition(self):
|
|
179
162
|
conditions = Pep316Parser().get_fn_conditions(
|
|
@@ -188,37 +171,35 @@ class Pep316ParserTest(unittest.TestCase):
|
|
|
188
171
|
FunctionInfo.from_fn(locally_defined_raises_condition)
|
|
189
172
|
)
|
|
190
173
|
assert conditions is not None
|
|
191
|
-
|
|
192
|
-
|
|
174
|
+
assert [] == list(conditions.syntax_messages())
|
|
175
|
+
assert set([LocallyDefiendException]) == conditions.raises
|
|
193
176
|
|
|
194
177
|
def test_tricky_raises_condition(self) -> None:
|
|
195
178
|
conditions = Pep316Parser().get_fn_conditions(
|
|
196
179
|
FunctionInfo.from_fn(tricky_raises_condition)
|
|
197
180
|
)
|
|
198
181
|
assert conditions is not None
|
|
199
|
-
|
|
200
|
-
|
|
182
|
+
assert [] == list(conditions.syntax_messages())
|
|
183
|
+
assert conditions.raises == {KeyError, json.JSONDecodeError}
|
|
201
184
|
|
|
202
185
|
def test_invariant_is_inherited(self) -> None:
|
|
203
186
|
class_conditions = Pep316Parser().get_class_conditions(SubClassExample)
|
|
204
|
-
|
|
187
|
+
assert set(class_conditions.methods.keys()) == {"foo", "__init__"}
|
|
205
188
|
method = class_conditions.methods["foo"]
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
set([c.expr_source for c in method.post]), set(["True", "False"])
|
|
211
|
-
)
|
|
189
|
+
assert len(method.pre) == 1
|
|
190
|
+
assert set([c.expr_source for c in method.pre]) == {"True"}
|
|
191
|
+
assert len(method.post) == 2
|
|
192
|
+
assert set([c.expr_source for c in method.post]) == {"True", "False"}
|
|
212
193
|
|
|
213
194
|
def test_invariant_applies_to_init(self) -> None:
|
|
214
195
|
class_conditions = Pep316Parser().get_class_conditions(BaseClassExample)
|
|
215
|
-
|
|
196
|
+
assert set(class_conditions.methods.keys()) == {"__init__", "foo"}
|
|
216
197
|
|
|
217
198
|
@pytest.mark.skipif(
|
|
218
199
|
sys.version_info >= (3, 13), reason="builtins have signatures in 3.13"
|
|
219
200
|
)
|
|
220
201
|
def test_builtin_conditions_are_null(self) -> None:
|
|
221
|
-
|
|
202
|
+
assert Pep316Parser().get_fn_conditions(FunctionInfo.from_fn(zip)) is None
|
|
222
203
|
|
|
223
204
|
def test_conditions_with_closure_references_and_string_type(self) -> None:
|
|
224
205
|
# This is a function that refers to something in its closure.
|
|
@@ -234,7 +215,7 @@ class Pep316ParserTest(unittest.TestCase):
|
|
|
234
215
|
|
|
235
216
|
|
|
236
217
|
@pytest.mark.skipif(not icontract, reason="icontract is not installed")
|
|
237
|
-
class
|
|
218
|
+
class TestIcontractParser:
|
|
238
219
|
def test_simple_parse(self):
|
|
239
220
|
@icontract.require(lambda ls: len(ls) > 0)
|
|
240
221
|
@icontract.ensure(lambda ls, result: min(ls) <= result <= max(ls))
|
|
@@ -243,17 +224,17 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
243
224
|
|
|
244
225
|
conditions = IcontractParser().get_fn_conditions(FunctionInfo.from_fn(avg))
|
|
245
226
|
assert conditions is not None
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
227
|
+
assert len(conditions.pre) == 1
|
|
228
|
+
assert len(conditions.post) == 1
|
|
229
|
+
assert conditions.pre[0].evaluate({"ls": []}) is False
|
|
249
230
|
post_args = {
|
|
250
231
|
"ls": [42, 43],
|
|
251
232
|
"__old__": AttributeHolder({}),
|
|
252
233
|
"__return__": 40,
|
|
253
234
|
"_": 40,
|
|
254
235
|
}
|
|
255
|
-
|
|
256
|
-
|
|
236
|
+
assert conditions.post[0].evaluate(post_args) is False
|
|
237
|
+
assert len(post_args) == 4 # (check args are unmodified)
|
|
257
238
|
|
|
258
239
|
def test_simple_class_parse(self):
|
|
259
240
|
@icontract.invariant(lambda self: self.i >= 0)
|
|
@@ -274,14 +255,14 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
274
255
|
self.i -= 1
|
|
275
256
|
|
|
276
257
|
conditions = IcontractParser().get_class_conditions(Counter)
|
|
277
|
-
|
|
258
|
+
assert len(conditions.inv) == 1
|
|
278
259
|
|
|
279
260
|
decr_conditions = conditions.methods["decr"]
|
|
280
|
-
|
|
261
|
+
assert len(decr_conditions.pre) == 2
|
|
281
262
|
# decr() precondition: count > 0
|
|
282
|
-
|
|
263
|
+
assert decr_conditions.pre[0].evaluate({"self": Counter()}) is False
|
|
283
264
|
# invariant: count >= 0
|
|
284
|
-
|
|
265
|
+
assert decr_conditions.pre[1].evaluate({"self": Counter()}) is True
|
|
285
266
|
|
|
286
267
|
class TruncatedCounter(Counter):
|
|
287
268
|
@icontract.require(
|
|
@@ -293,21 +274,19 @@ class IcontractParserTest(unittest.TestCase):
|
|
|
293
274
|
|
|
294
275
|
conditions = IcontractParser().get_class_conditions(TruncatedCounter)
|
|
295
276
|
decr_conditions = conditions.methods["decr"]
|
|
296
|
-
|
|
297
|
-
decr_conditions.pre[0].evaluate({"self": TruncatedCounter()}), True
|
|
298
|
-
)
|
|
277
|
+
assert decr_conditions.pre[0].evaluate({"self": TruncatedCounter()}) is True
|
|
299
278
|
|
|
300
279
|
# check the weakened precondition
|
|
301
|
-
|
|
302
|
-
len(decr_conditions.pre)
|
|
280
|
+
assert (
|
|
281
|
+
len(decr_conditions.pre) == 2
|
|
303
282
|
) # one for the invariant, one for the disjunction
|
|
304
283
|
ctr = TruncatedCounter()
|
|
305
284
|
ctr.i = 1
|
|
306
|
-
|
|
307
|
-
|
|
285
|
+
assert decr_conditions.pre[1].evaluate({"self": ctr}) is True
|
|
286
|
+
assert decr_conditions.pre[0].evaluate({"self": ctr}) is True
|
|
308
287
|
ctr.i = 0
|
|
309
|
-
|
|
310
|
-
|
|
288
|
+
assert decr_conditions.pre[1].evaluate({"self": ctr}) is True
|
|
289
|
+
assert decr_conditions.pre[0].evaluate({"self": ctr}) is True
|
|
311
290
|
|
|
312
291
|
|
|
313
292
|
@pytest.mark.skipif(not deal, reason="deal is not installed")
|
|
@@ -401,20 +380,20 @@ def fn_with_docstring_comments_and_assert(numbers: List[int]) -> None:
|
|
|
401
380
|
assert min(numbers) > smallest
|
|
402
381
|
|
|
403
382
|
|
|
404
|
-
class
|
|
383
|
+
class TestAssertsParser:
|
|
405
384
|
def tests_simple_parse(self) -> None:
|
|
406
385
|
conditions = AssertsParser().get_fn_conditions(
|
|
407
386
|
FunctionInfo.from_fn(avg_with_asserts)
|
|
408
387
|
)
|
|
409
388
|
assert conditions is not None
|
|
410
389
|
conditions.fn([])
|
|
411
|
-
|
|
412
|
-
with
|
|
390
|
+
assert conditions.fn([2.2]) == 2.2
|
|
391
|
+
with pytest.raises(AssertionError):
|
|
413
392
|
conditions.fn([9.2, 17.8])
|
|
414
393
|
|
|
415
394
|
def tests_empty_parse(self) -> None:
|
|
416
395
|
conditions = AssertsParser().get_fn_conditions(FunctionInfo.from_fn(debug))
|
|
417
|
-
|
|
396
|
+
assert conditions is None
|
|
418
397
|
|
|
419
398
|
def tests_extra_ast_nodes(self) -> None:
|
|
420
399
|
conditions = AssertsParser().get_fn_conditions(
|
|
@@ -428,10 +407,10 @@ class AssertsParserTest(unittest.TestCase):
|
|
|
428
407
|
# normal, passing case:
|
|
429
408
|
nums = [3, 1, 2]
|
|
430
409
|
conditions.fn(nums)
|
|
431
|
-
|
|
410
|
+
assert nums == [3, 2]
|
|
432
411
|
|
|
433
412
|
# Failing case (duplicate minimum values):
|
|
434
|
-
with
|
|
413
|
+
with pytest.raises(AssertionError):
|
|
435
414
|
nums = [3, 1, 1, 2]
|
|
436
415
|
conditions.fn(nums)
|
|
437
416
|
|
|
@@ -492,18 +471,10 @@ def test_lines_with_trailing_comment():
|
|
|
492
471
|
|
|
493
472
|
def test_format_counterexample_positional_only():
|
|
494
473
|
if sys.version_info >= (3, 8):
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
'''
|
|
500
|
-
def foo(a=10, /, b=20):
|
|
501
|
-
"""post: True"""
|
|
502
|
-
'''
|
|
503
|
-
),
|
|
504
|
-
ns,
|
|
505
|
-
)
|
|
506
|
-
foo = ns["foo"]
|
|
474
|
+
|
|
475
|
+
def foo(a=10, /, b=20):
|
|
476
|
+
"""post: True"""
|
|
477
|
+
|
|
507
478
|
args = inspect.BoundArguments(inspect.signature(foo), {"a": 1, "b": 2})
|
|
508
479
|
conditions = Pep316Parser().get_fn_conditions(FunctionInfo.from_fn(foo))
|
|
509
480
|
assert conditions.format_counterexample(args, None, {}) == (
|
|
@@ -524,16 +495,3 @@ def test_format_counterexample_keyword_only():
|
|
|
524
495
|
"foo(1, b=2)",
|
|
525
496
|
"None",
|
|
526
497
|
)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
@pytest.mark.skipif(not hypothesis, reason="hypothesis is not installed")
|
|
530
|
-
def test_hypothesis_arg_regen():
|
|
531
|
-
@hypothesis.given(hypothesis.strategies.integers())
|
|
532
|
-
def hypothesis_fn_int(x):
|
|
533
|
-
pass
|
|
534
|
-
|
|
535
|
-
parser = HypothesisParser(None)
|
|
536
|
-
# NOTE: Enocding not stable across hypothesis versions.
|
|
537
|
-
# Byte string may need to be updated when our hypothesis dev version changes.
|
|
538
|
-
ret = parser._generate_args(b"\x01\x04T", hypothesis_fn_int)
|
|
539
|
-
assert ret == {"x": 42}
|