crosshair-tool 0.0.99__cp312-cp312-macosx_10_13_x86_64.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-312-darwin.so +0 -0
- crosshair/__init__.py +42 -0
- crosshair/__main__.py +8 -0
- crosshair/_mark_stacks.h +790 -0
- crosshair/_preliminaries_test.py +18 -0
- crosshair/_tracers.h +94 -0
- crosshair/_tracers_pycompat.h +522 -0
- crosshair/_tracers_test.py +138 -0
- crosshair/abcstring.py +245 -0
- crosshair/auditwall.py +190 -0
- crosshair/auditwall_test.py +77 -0
- crosshair/codeconfig.py +113 -0
- crosshair/codeconfig_test.py +117 -0
- crosshair/condition_parser.py +1237 -0
- crosshair/condition_parser_test.py +497 -0
- crosshair/conftest.py +30 -0
- crosshair/copyext.py +155 -0
- crosshair/copyext_test.py +84 -0
- crosshair/core.py +1763 -0
- crosshair/core_and_libs.py +149 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +1316 -0
- crosshair/diff_behavior.py +314 -0
- crosshair/diff_behavior_test.py +261 -0
- crosshair/dynamic_typing.py +346 -0
- crosshair/dynamic_typing_test.py +210 -0
- crosshair/enforce.py +282 -0
- crosshair/enforce_test.py +182 -0
- crosshair/examples/PEP316/__init__.py +1 -0
- crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
- crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
- crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
- crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
- crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
- crosshair/examples/PEP316/correct_code/__init__.py +0 -0
- crosshair/examples/PEP316/correct_code/arith.py +60 -0
- crosshair/examples/PEP316/correct_code/chess.py +77 -0
- crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
- crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
- crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
- crosshair/examples/PEP316/correct_code/showcase.py +104 -0
- crosshair/examples/__init__.py +0 -0
- crosshair/examples/check_examples_test.py +146 -0
- crosshair/examples/deal/__init__.py +1 -0
- crosshair/examples/icontract/__init__.py +1 -0
- crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
- crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
- crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
- crosshair/examples/icontract/correct_code/__init__.py +0 -0
- crosshair/examples/icontract/correct_code/arith.py +51 -0
- crosshair/examples/icontract/correct_code/showcase.py +94 -0
- crosshair/fnutil.py +391 -0
- crosshair/fnutil_test.py +75 -0
- crosshair/fuzz_core_test.py +516 -0
- crosshair/libimpl/__init__.py +0 -0
- crosshair/libimpl/arraylib.py +161 -0
- 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 +23 -0
- crosshair/libimpl/builtinslib.py +5228 -0
- crosshair/libimpl/builtinslib_ch_test.py +1191 -0
- crosshair/libimpl/builtinslib_test.py +3735 -0
- crosshair/libimpl/codecslib.py +86 -0
- crosshair/libimpl/codecslib_test.py +86 -0
- crosshair/libimpl/collectionslib.py +264 -0
- crosshair/libimpl/collectionslib_ch_test.py +252 -0
- crosshair/libimpl/collectionslib_test.py +332 -0
- crosshair/libimpl/copylib.py +23 -0
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +2559 -0
- crosshair/libimpl/datetimelib_ch_test.py +354 -0
- crosshair/libimpl/datetimelib_test.py +112 -0
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/__init__.py +23 -0
- crosshair/libimpl/encodings/_encutil.py +187 -0
- crosshair/libimpl/encodings/ascii.py +44 -0
- crosshair/libimpl/encodings/latin_1.py +40 -0
- crosshair/libimpl/encodings/utf_8.py +93 -0
- crosshair/libimpl/encodings_ch_test.py +83 -0
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +34 -0
- crosshair/libimpl/functoolslib_test.py +56 -0
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +47 -0
- crosshair/libimpl/heapqlib_test.py +21 -0
- crosshair/libimpl/importliblib.py +18 -0
- crosshair/libimpl/importliblib_test.py +38 -0
- crosshair/libimpl/iolib.py +216 -0
- crosshair/libimpl/iolib_ch_test.py +128 -0
- crosshair/libimpl/iolib_test.py +19 -0
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib.py +44 -0
- crosshair/libimpl/itertoolslib_test.py +44 -0
- crosshair/libimpl/jsonlib.py +984 -0
- crosshair/libimpl/jsonlib_ch_test.py +42 -0
- crosshair/libimpl/jsonlib_test.py +51 -0
- crosshair/libimpl/mathlib.py +179 -0
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +67 -0
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +178 -0
- crosshair/libimpl/randomlib_test.py +120 -0
- crosshair/libimpl/relib.py +846 -0
- crosshair/libimpl/relib_ch_test.py +169 -0
- crosshair/libimpl/relib_test.py +493 -0
- crosshair/libimpl/timelib.py +72 -0
- crosshair/libimpl/timelib_test.py +82 -0
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib.py +75 -0
- crosshair/libimpl/unicodedatalib_test.py +42 -0
- crosshair/libimpl/urlliblib.py +23 -0
- crosshair/libimpl/urlliblib_test.py +19 -0
- 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 +261 -0
- crosshair/lsp_server_test.py +30 -0
- crosshair/main.py +973 -0
- crosshair/main_test.py +543 -0
- crosshair/objectproxy.py +376 -0
- crosshair/objectproxy_test.py +41 -0
- crosshair/opcode_intercept.py +601 -0
- crosshair/opcode_intercept_test.py +304 -0
- crosshair/options.py +218 -0
- crosshair/options_test.py +10 -0
- crosshair/patch_equivalence_test.py +75 -0
- crosshair/path_cover.py +209 -0
- crosshair/path_cover_test.py +138 -0
- crosshair/path_search.py +161 -0
- crosshair/path_search_test.py +52 -0
- crosshair/pathing_oracle.py +271 -0
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer.py +27 -0
- crosshair/pure_importer_test.py +16 -0
- crosshair/py.typed +0 -0
- crosshair/register_contract.py +273 -0
- crosshair/register_contract_test.py +190 -0
- crosshair/simplestructs.py +1165 -0
- crosshair/simplestructs_test.py +283 -0
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +1199 -0
- crosshair/statespace_test.py +108 -0
- crosshair/stubs_parser.py +352 -0
- crosshair/stubs_parser_test.py +43 -0
- crosshair/test_util.py +329 -0
- crosshair/test_util_test.py +26 -0
- crosshair/tools/__init__.py +0 -0
- crosshair/tools/check_help_in_doc.py +264 -0
- crosshair/tools/check_init_and_setup_coincide.py +119 -0
- crosshair/tools/generate_demo_table.py +127 -0
- crosshair/tracers.py +544 -0
- crosshair/tracers_test.py +154 -0
- crosshair/type_repo.py +151 -0
- crosshair/unicode_categories.py +589 -0
- crosshair/unicode_categories_test.py +27 -0
- crosshair/util.py +741 -0
- crosshair/util_test.py +173 -0
- crosshair/watcher.py +307 -0
- crosshair/watcher_test.py +107 -0
- crosshair/z3util.py +76 -0
- crosshair/z3util_test.py +11 -0
- crosshair_tool-0.0.99.dist-info/METADATA +144 -0
- crosshair_tool-0.0.99.dist-info/RECORD +176 -0
- crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
- crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
- crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
- crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
crosshair/main_test.py
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
from argparse import Namespace
|
|
7
|
+
from os.path import join, split
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from crosshair.fnutil import NotFound
|
|
12
|
+
from crosshair.main import (
|
|
13
|
+
DEFAULT_OPTIONS,
|
|
14
|
+
AnalysisKind,
|
|
15
|
+
AnalysisMessage,
|
|
16
|
+
AnalysisOptionSet,
|
|
17
|
+
List,
|
|
18
|
+
MessageType,
|
|
19
|
+
Path,
|
|
20
|
+
Tuple,
|
|
21
|
+
check,
|
|
22
|
+
describe_message,
|
|
23
|
+
diffbehavior,
|
|
24
|
+
set_debug,
|
|
25
|
+
textwrap,
|
|
26
|
+
unwalled_main,
|
|
27
|
+
watch,
|
|
28
|
+
)
|
|
29
|
+
from crosshair.test_util import simplefs
|
|
30
|
+
from crosshair.util import add_to_pypath, load_file
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture(autouse=True)
|
|
34
|
+
def rewind_modules():
|
|
35
|
+
defined_modules = list(sys.modules.keys())
|
|
36
|
+
yield None
|
|
37
|
+
for name, module in list(sys.modules.items()):
|
|
38
|
+
# Some standard library modules aren't happy with getting reloaded.
|
|
39
|
+
if name.startswith("multiprocessing"):
|
|
40
|
+
continue
|
|
41
|
+
if name not in defined_modules:
|
|
42
|
+
del sys.modules[name]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
def root() -> Path:
|
|
47
|
+
return Path(tempfile.mkdtemp())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def call_check(
|
|
51
|
+
files: List[str], options: AnalysisOptionSet = AnalysisOptionSet()
|
|
52
|
+
) -> Tuple[int, List[str], List[str]]:
|
|
53
|
+
stdbuf: io.StringIO = io.StringIO()
|
|
54
|
+
errbuf: io.StringIO = io.StringIO()
|
|
55
|
+
retcode = check(Namespace(target=files), options, stdbuf, errbuf)
|
|
56
|
+
stdlines = [ls for ls in stdbuf.getvalue().split("\n") if ls]
|
|
57
|
+
errlines = [ls for ls in errbuf.getvalue().split("\n") if ls]
|
|
58
|
+
return retcode, stdlines, errlines
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def call_diffbehavior(fn1: str, fn2: str) -> Tuple[int, List[str]]:
|
|
62
|
+
buf: io.StringIO = io.StringIO()
|
|
63
|
+
errbuf: io.StringIO = io.StringIO()
|
|
64
|
+
retcode = diffbehavior(
|
|
65
|
+
Namespace(fn1=fn1, fn2=fn2, exception_equivalence="type_and_message"),
|
|
66
|
+
DEFAULT_OPTIONS,
|
|
67
|
+
buf,
|
|
68
|
+
errbuf,
|
|
69
|
+
)
|
|
70
|
+
lines = [
|
|
71
|
+
ls for ls in buf.getvalue().split("\n") + errbuf.getvalue().split("\n") if ls
|
|
72
|
+
]
|
|
73
|
+
return retcode, lines
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
SIMPLE_FOO = {
|
|
77
|
+
"foo.py": """
|
|
78
|
+
def foofn(x: int) -> int:
|
|
79
|
+
''' post: _ == x '''
|
|
80
|
+
return x + 1
|
|
81
|
+
"""
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
FOO_CLASS = {
|
|
85
|
+
"foo.py": """
|
|
86
|
+
class Fooey:
|
|
87
|
+
def incr(self, x: int) -> int:
|
|
88
|
+
''' post: _ == x '''
|
|
89
|
+
return x + 1
|
|
90
|
+
"""
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
ASSERT_BASED_FOO = {
|
|
94
|
+
"foo.py": """
|
|
95
|
+
def foofn(x: int) -> int:
|
|
96
|
+
''' :raises KeyError: when input is 999 '''
|
|
97
|
+
assert x >= 100
|
|
98
|
+
x = x + 1
|
|
99
|
+
if x == 1000:
|
|
100
|
+
raise KeyError
|
|
101
|
+
assert x != 101
|
|
102
|
+
return x
|
|
103
|
+
"""
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
FOO_WITH_CONFIRMABLE_AND_PRE_UNSAT = {
|
|
107
|
+
"foo.py": """
|
|
108
|
+
def foo_confirmable(x: int) -> int:
|
|
109
|
+
''' post: _ > x '''
|
|
110
|
+
return x + 1
|
|
111
|
+
def foo_pre_unsat(x: int) -> int:
|
|
112
|
+
'''
|
|
113
|
+
pre: x != x
|
|
114
|
+
post: True
|
|
115
|
+
'''
|
|
116
|
+
return x
|
|
117
|
+
"""
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
FOO_STATIC_AND_CLASSMETHODS = {
|
|
121
|
+
"foo.py": """
|
|
122
|
+
class Fooey:
|
|
123
|
+
@staticmethod
|
|
124
|
+
def static_incr(x: int) -> int:
|
|
125
|
+
''' post: _ == x '''
|
|
126
|
+
return x + 1
|
|
127
|
+
@classmethod
|
|
128
|
+
def classmethod_incr(cls, x: int) -> int:
|
|
129
|
+
''' post: _ == x '''
|
|
130
|
+
assert cls == Fooey
|
|
131
|
+
return x + 1
|
|
132
|
+
"""
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
OUTER_INNER = {
|
|
136
|
+
"outer": {
|
|
137
|
+
"__init__.py": "",
|
|
138
|
+
"inner.py": """
|
|
139
|
+
def foofn(x: int) -> int:
|
|
140
|
+
''' post: _ == x '''
|
|
141
|
+
return x
|
|
142
|
+
""",
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
CIRCULAR_WITH_GUARD = {
|
|
147
|
+
"first.py": """
|
|
148
|
+
import typing
|
|
149
|
+
if typing.TYPE_CHECKING:
|
|
150
|
+
from second import Second
|
|
151
|
+
class First():
|
|
152
|
+
def __init__(self, f: "Second") -> None:
|
|
153
|
+
''' post: True '''
|
|
154
|
+
""",
|
|
155
|
+
"second.py": """
|
|
156
|
+
from first import First
|
|
157
|
+
class Second():
|
|
158
|
+
pass
|
|
159
|
+
""",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
DIRECTIVES_TREE = {
|
|
164
|
+
"outerpkg": {
|
|
165
|
+
"__init__.py": "# crosshair: off",
|
|
166
|
+
"outermod.py": textwrap.dedent(
|
|
167
|
+
"""\
|
|
168
|
+
def fn1():
|
|
169
|
+
assert True
|
|
170
|
+
raise Exception
|
|
171
|
+
"""
|
|
172
|
+
),
|
|
173
|
+
"innerpkg": {
|
|
174
|
+
"__init__.py": "# crosshair: on",
|
|
175
|
+
"innermod.py": textwrap.dedent(
|
|
176
|
+
"""\
|
|
177
|
+
# crosshair: off
|
|
178
|
+
def fn2():
|
|
179
|
+
# crosshair: on
|
|
180
|
+
assert True
|
|
181
|
+
raise Exception # this is the only function that's enabled
|
|
182
|
+
def fn3():
|
|
183
|
+
assert True
|
|
184
|
+
raise Exception
|
|
185
|
+
"""
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_load_file(root):
|
|
193
|
+
simplefs(root, SIMPLE_FOO)
|
|
194
|
+
module = load_file(join(root, "foo.py"))
|
|
195
|
+
assert module is not None
|
|
196
|
+
assert module.foofn(5) == 6
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_check_by_filename(root):
|
|
200
|
+
simplefs(root, SIMPLE_FOO)
|
|
201
|
+
retcode, lines, _ = call_check([str(root / "foo.py")])
|
|
202
|
+
assert retcode == 1
|
|
203
|
+
assert len(lines) == 1
|
|
204
|
+
assert "foo.py:3: error: false when calling foofn" in lines[0]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@pytest.mark.smoke
|
|
208
|
+
def test_check_by_class(root):
|
|
209
|
+
simplefs(root, FOO_CLASS)
|
|
210
|
+
with add_to_pypath(root):
|
|
211
|
+
retcode, lines, _ = call_check(["foo.Fooey"])
|
|
212
|
+
assert retcode == 1
|
|
213
|
+
assert len(lines) == 1
|
|
214
|
+
assert "foo.py:4: error: false when calling incr" in lines[0]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_check_failure_via_main(root):
|
|
218
|
+
simplefs(root, SIMPLE_FOO)
|
|
219
|
+
try:
|
|
220
|
+
sys.stdout = io.StringIO()
|
|
221
|
+
assert unwalled_main(["check", str(root / "foo.py")]) == 1
|
|
222
|
+
finally:
|
|
223
|
+
sys.stdout = sys.__stdout__
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_check_ok_via_main(root):
|
|
227
|
+
# contract is assert-based, but we do not analyze that type.
|
|
228
|
+
simplefs(root, ASSERT_BASED_FOO)
|
|
229
|
+
try:
|
|
230
|
+
sys.stdout = io.StringIO()
|
|
231
|
+
exitcode = unwalled_main(
|
|
232
|
+
[
|
|
233
|
+
"check",
|
|
234
|
+
str(root / "foo.py"),
|
|
235
|
+
"--analysis_kind=PEP316,icontract",
|
|
236
|
+
]
|
|
237
|
+
)
|
|
238
|
+
assert exitcode == 0
|
|
239
|
+
finally:
|
|
240
|
+
sys.stdout = sys.__stdout__
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_no_args_prints_usage(root):
|
|
244
|
+
try:
|
|
245
|
+
sys.stderr = io.StringIO()
|
|
246
|
+
exitcode = unwalled_main([])
|
|
247
|
+
finally:
|
|
248
|
+
out = sys.stderr.getvalue()
|
|
249
|
+
sys.stderr = sys.__stderr__
|
|
250
|
+
assert exitcode == 2
|
|
251
|
+
assert re.search(r"^usage", out)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def test_assert_mode_e2e(root, capsys: pytest.CaptureFixture[str]):
|
|
255
|
+
simplefs(root, ASSERT_BASED_FOO)
|
|
256
|
+
exitcode = unwalled_main(["check", str(root / "foo.py"), "--analysis_kind=asserts"])
|
|
257
|
+
(out, err) = capsys.readouterr()
|
|
258
|
+
assert err == ""
|
|
259
|
+
assert re.search(
|
|
260
|
+
r"foo.py\:8\: error\: AssertionError\: when calling foofn\(100\)", out
|
|
261
|
+
)
|
|
262
|
+
assert len([ls for ls in out.split("\n") if ls]) == 1
|
|
263
|
+
assert exitcode == 1
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_assert_without_checkable(root, capsys: pytest.CaptureFixture[str]):
|
|
267
|
+
simplefs(root, SIMPLE_FOO)
|
|
268
|
+
exitcode = unwalled_main(["check", str(root / "foo.py"), "--analysis_kind=asserts"])
|
|
269
|
+
(out, err) = capsys.readouterr()
|
|
270
|
+
assert (
|
|
271
|
+
err
|
|
272
|
+
== "WARNING: Targets found, but contain no checkable functions.\nHINT: Ensure that your functions to analyze lead with assert statements.\n"
|
|
273
|
+
)
|
|
274
|
+
assert out == ""
|
|
275
|
+
assert exitcode == 0
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_directives(root):
|
|
279
|
+
simplefs(root, DIRECTIVES_TREE)
|
|
280
|
+
ret, out, err = call_check(
|
|
281
|
+
[str(root)], AnalysisOptionSet(analysis_kind=[AnalysisKind.asserts])
|
|
282
|
+
)
|
|
283
|
+
assert err == []
|
|
284
|
+
assert ret == 1
|
|
285
|
+
assert re.search(r"innermod.py:5: error: Exception: when calling fn2()", out[0])
|
|
286
|
+
assert len(out) == 1
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_directives_on_check_with_linenumbers(root):
|
|
290
|
+
simplefs(root, DIRECTIVES_TREE)
|
|
291
|
+
ret, out, err = call_check(
|
|
292
|
+
[str(root / "outerpkg" / "innerpkg" / "innermod.py") + ":5"],
|
|
293
|
+
AnalysisOptionSet(analysis_kind=[AnalysisKind.asserts]),
|
|
294
|
+
)
|
|
295
|
+
assert err == []
|
|
296
|
+
assert ret == 1
|
|
297
|
+
assert re.search(r"innermod.py:5: error: Exception: when calling fn2()", out[0])
|
|
298
|
+
assert len(out) == 1
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_report_confirmation(root):
|
|
302
|
+
simplefs(root, FOO_WITH_CONFIRMABLE_AND_PRE_UNSAT)
|
|
303
|
+
retcode, lines, _ = call_check([str(root / "foo.py")])
|
|
304
|
+
assert retcode == 0
|
|
305
|
+
assert lines == []
|
|
306
|
+
# Now, turn on confirmations with the `--report_all` option:
|
|
307
|
+
retcode, lines, _ = call_check(
|
|
308
|
+
[str(root / "foo.py")], options=AnalysisOptionSet(report_all=True)
|
|
309
|
+
)
|
|
310
|
+
assert retcode == 0
|
|
311
|
+
assert len(lines) == 2
|
|
312
|
+
output_text = "\n".join(lines)
|
|
313
|
+
assert "foo.py:3: info: Confirmed over all paths." in output_text
|
|
314
|
+
assert "foo.py:7: info: Unable to meet precondition." in output_text
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def test_cover_static_and_classmethods(root: Path, capsys: pytest.CaptureFixture[str]):
|
|
318
|
+
simplefs(root, FOO_STATIC_AND_CLASSMETHODS)
|
|
319
|
+
ret = unwalled_main(["cover", str(root / "foo.py")])
|
|
320
|
+
(out, err) = capsys.readouterr()
|
|
321
|
+
assert err == ""
|
|
322
|
+
assert out == "static_incr(0)\nclassmethod_incr(Fooey, 0)\n"
|
|
323
|
+
assert ret == 0
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def test_check_nonexistent_filename(root):
|
|
327
|
+
simplefs(root, SIMPLE_FOO)
|
|
328
|
+
retcode, _, errlines = call_check([str(root / "notexisting.py")])
|
|
329
|
+
assert retcode == 2
|
|
330
|
+
assert len(errlines) == 1
|
|
331
|
+
assert "File not found" in errlines[0]
|
|
332
|
+
assert "notexisting.py" in errlines[0]
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
@pytest.mark.smoke
|
|
336
|
+
def test_check_by_module(root):
|
|
337
|
+
simplefs(root, SIMPLE_FOO)
|
|
338
|
+
with add_to_pypath(root):
|
|
339
|
+
retcode, lines, _ = call_check(["foo"])
|
|
340
|
+
assert retcode == 1
|
|
341
|
+
assert len(lines) == 1
|
|
342
|
+
assert "foo.py:3: error: false when calling foofn" in lines[0]
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_check_nonexistent_module(root):
|
|
346
|
+
simplefs(root, SIMPLE_FOO)
|
|
347
|
+
retcode, _, errlines = call_check(["notexisting"])
|
|
348
|
+
assert retcode == 2
|
|
349
|
+
assert (
|
|
350
|
+
errlines[-1] == "crosshair.fnutil.NotFound: Module 'notexisting' was not found"
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@pytest.mark.smoke
|
|
355
|
+
def test_check_by_package(root):
|
|
356
|
+
simplefs(root, OUTER_INNER)
|
|
357
|
+
with add_to_pypath(root):
|
|
358
|
+
retcode, lines, _ = call_check(["outer.inner.foofn"])
|
|
359
|
+
assert retcode == 0
|
|
360
|
+
assert len(lines) == 0
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def test_check_nonexistent_member(root):
|
|
364
|
+
simplefs(root, OUTER_INNER)
|
|
365
|
+
with add_to_pypath(root), pytest.raises(NotFound):
|
|
366
|
+
call_check(["outer.inner.nonexistent"])
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def test_check_circular_with_guard(root):
|
|
370
|
+
simplefs(root, CIRCULAR_WITH_GUARD)
|
|
371
|
+
with add_to_pypath(root):
|
|
372
|
+
retcode, lines, _ = call_check([str(root / "first.py")])
|
|
373
|
+
assert retcode == 0
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def test_check_not_deterministic(root) -> None:
|
|
377
|
+
NOT_DETERMINISTIC_FOO = {
|
|
378
|
+
"foo.py": """
|
|
379
|
+
_GLOBAL_THING = [42]
|
|
380
|
+
|
|
381
|
+
def wonky_foo(i: int) -> int:
|
|
382
|
+
'''post: True'''
|
|
383
|
+
_GLOBAL_THING[0] += 1
|
|
384
|
+
if i > _GLOBAL_THING[0]:
|
|
385
|
+
pass
|
|
386
|
+
return True
|
|
387
|
+
_GLOBAL_THING = [True]
|
|
388
|
+
|
|
389
|
+
def regular_foo(i: int) -> int:
|
|
390
|
+
'''post: True'''
|
|
391
|
+
return i
|
|
392
|
+
"""
|
|
393
|
+
}
|
|
394
|
+
simplefs(root, NOT_DETERMINISTIC_FOO)
|
|
395
|
+
with add_to_pypath(root):
|
|
396
|
+
retcode, lines, errlines = call_check([str(root / "foo.py")])
|
|
397
|
+
assert errlines == []
|
|
398
|
+
assert (
|
|
399
|
+
"NotDeterministic: Found a different execution paths after making the same decisions"
|
|
400
|
+
in lines[0]
|
|
401
|
+
)
|
|
402
|
+
assert retcode == 1
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def test_watch(root):
|
|
406
|
+
# Just to make sure nothing explodes
|
|
407
|
+
simplefs(root, SIMPLE_FOO)
|
|
408
|
+
retcode = watch(
|
|
409
|
+
Namespace(directory=[str(root)]),
|
|
410
|
+
AnalysisOptionSet(),
|
|
411
|
+
max_watch_iterations=2,
|
|
412
|
+
)
|
|
413
|
+
assert retcode == 0
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def test_diff_behavior_same(root):
|
|
417
|
+
simplefs(root, SIMPLE_FOO)
|
|
418
|
+
with add_to_pypath(root):
|
|
419
|
+
retcode, lines = call_diffbehavior("foo.foofn", str(root / "foo.py:2"))
|
|
420
|
+
assert lines == [
|
|
421
|
+
"No differences found. (attempted 2 iterations)",
|
|
422
|
+
"All paths exhausted, functions are likely the same!",
|
|
423
|
+
]
|
|
424
|
+
assert retcode == 0
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def test_diff_behavior_different(root):
|
|
428
|
+
simplefs(
|
|
429
|
+
root,
|
|
430
|
+
{
|
|
431
|
+
"foo.py": """
|
|
432
|
+
def add(x: int, y: int) -> int:
|
|
433
|
+
return x + y
|
|
434
|
+
def faultyadd(x: int, y: int) -> int:
|
|
435
|
+
return 42 if (x, y) == (10, 10) else x + y
|
|
436
|
+
"""
|
|
437
|
+
},
|
|
438
|
+
)
|
|
439
|
+
with add_to_pypath(root):
|
|
440
|
+
retcode, lines = call_diffbehavior("foo.add", "foo.faultyadd")
|
|
441
|
+
assert retcode == 1
|
|
442
|
+
assert lines == [
|
|
443
|
+
"Given: (x=10, y=10),",
|
|
444
|
+
" foo.add : returns 20",
|
|
445
|
+
" foo.faultyadd : returns 42",
|
|
446
|
+
]
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def test_diff_behavior_error(root):
|
|
450
|
+
retcode, lines = call_diffbehavior("foo.unknown", "foo.unknown")
|
|
451
|
+
assert retcode == 2
|
|
452
|
+
assert re.search(".*NotFound", lines[0])
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def test_diff_behavior_targeting_error(root):
|
|
456
|
+
simplefs(root, SIMPLE_FOO)
|
|
457
|
+
with add_to_pypath(root):
|
|
458
|
+
retcode, lines = call_diffbehavior("foo.foofn", "foo")
|
|
459
|
+
assert retcode == 2
|
|
460
|
+
assert lines == ['"foo" does not target a function.']
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@pytest.mark.smoke
|
|
464
|
+
def test_diff_behavior_via_main(root):
|
|
465
|
+
simplefs(root, SIMPLE_FOO)
|
|
466
|
+
sys.stdout = io.StringIO()
|
|
467
|
+
try:
|
|
468
|
+
with add_to_pypath(root):
|
|
469
|
+
assert unwalled_main(["diffbehavior", "foo.foofn", "foo.foofn"]) == 0
|
|
470
|
+
finally:
|
|
471
|
+
out = sys.stdout.getvalue()
|
|
472
|
+
sys.stdout = sys.__stdout__
|
|
473
|
+
import re
|
|
474
|
+
|
|
475
|
+
assert re.search("No differences found", out)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def test_cover(tmp_path: Path, capsys: pytest.CaptureFixture[str]):
|
|
479
|
+
simplefs(tmp_path, SIMPLE_FOO)
|
|
480
|
+
with add_to_pypath(str(tmp_path)):
|
|
481
|
+
assert unwalled_main(["cover", "foo.foofn"]) == 0
|
|
482
|
+
assert capsys.readouterr().out == "foofn(0)\n"
|
|
483
|
+
with add_to_pypath(str(tmp_path)):
|
|
484
|
+
args = ["cover", "foo.foofn", "--example_output_format=arg_dictionary"]
|
|
485
|
+
assert unwalled_main(args) == 0
|
|
486
|
+
assert capsys.readouterr().out == '{"x": 0}\n'
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def test_search(tmp_path: Path, capsys: pytest.CaptureFixture[str]):
|
|
490
|
+
simplefs(tmp_path, SIMPLE_FOO)
|
|
491
|
+
with add_to_pypath(str(tmp_path)):
|
|
492
|
+
assert unwalled_main(["search", "foo.foofn"]) == 0
|
|
493
|
+
assert capsys.readouterr().out == '{"x": 0}\n'
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@pytest.mark.smoke
|
|
497
|
+
def test_main_as_subprocess(tmp_path: Path):
|
|
498
|
+
# This helps check things like addaudithook() which we don't want to run inside
|
|
499
|
+
# the testing process.
|
|
500
|
+
simplefs(tmp_path, SIMPLE_FOO)
|
|
501
|
+
completion = subprocess.run(
|
|
502
|
+
[sys.executable, "-Werror", "-m", "crosshair", "check", str(tmp_path)],
|
|
503
|
+
stdin=subprocess.DEVNULL,
|
|
504
|
+
capture_output=True,
|
|
505
|
+
text=True,
|
|
506
|
+
)
|
|
507
|
+
assert completion.returncode == 1
|
|
508
|
+
assert completion.stderr == ""
|
|
509
|
+
assert "foo.py:3: error: false when calling foofn" in completion.stdout
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def test_mypycrosshair_command():
|
|
513
|
+
example_file = join(
|
|
514
|
+
split(__file__)[0], "examples", "PEP316", "bugs_detected", "showcase.py"
|
|
515
|
+
)
|
|
516
|
+
completion = subprocess.run(
|
|
517
|
+
[
|
|
518
|
+
sys.executable,
|
|
519
|
+
f"-c",
|
|
520
|
+
f"import crosshair.main;"
|
|
521
|
+
+ f"crosshair.main.mypy_and_check([r'{example_file}'])",
|
|
522
|
+
],
|
|
523
|
+
stdin=subprocess.DEVNULL,
|
|
524
|
+
capture_output=True,
|
|
525
|
+
text=True,
|
|
526
|
+
)
|
|
527
|
+
assert completion.stderr.strip() == "", f"stderr was '''{completion.stderr}'''"
|
|
528
|
+
if completion.returncode != 1:
|
|
529
|
+
print(completion.stdout)
|
|
530
|
+
assert False, f"Return code was {completion.returncode}"
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def test_describe_message():
|
|
534
|
+
msg = AnalysisMessage(MessageType.PRE_UNSAT, "unsat", "filename", 1, 1, "traceback")
|
|
535
|
+
opts = DEFAULT_OPTIONS.overlay(report_all=True, report_verbose=True)
|
|
536
|
+
assert describe_message(msg, opts).split("\n") == [
|
|
537
|
+
"traceback",
|
|
538
|
+
"\x1b[91mI am having trouble finding any inputs that meet your preconditions.\x1b[0m",
|
|
539
|
+
"filename:1:",
|
|
540
|
+
"",
|
|
541
|
+
"unsat",
|
|
542
|
+
"",
|
|
543
|
+
]
|