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.
Files changed (176) hide show
  1. _crosshair_tracers.cpython-312-darwin.so +0 -0
  2. crosshair/__init__.py +42 -0
  3. crosshair/__main__.py +8 -0
  4. crosshair/_mark_stacks.h +790 -0
  5. crosshair/_preliminaries_test.py +18 -0
  6. crosshair/_tracers.h +94 -0
  7. crosshair/_tracers_pycompat.h +522 -0
  8. crosshair/_tracers_test.py +138 -0
  9. crosshair/abcstring.py +245 -0
  10. crosshair/auditwall.py +190 -0
  11. crosshair/auditwall_test.py +77 -0
  12. crosshair/codeconfig.py +113 -0
  13. crosshair/codeconfig_test.py +117 -0
  14. crosshair/condition_parser.py +1237 -0
  15. crosshair/condition_parser_test.py +497 -0
  16. crosshair/conftest.py +30 -0
  17. crosshair/copyext.py +155 -0
  18. crosshair/copyext_test.py +84 -0
  19. crosshair/core.py +1763 -0
  20. crosshair/core_and_libs.py +149 -0
  21. crosshair/core_regestered_types_test.py +82 -0
  22. crosshair/core_test.py +1316 -0
  23. crosshair/diff_behavior.py +314 -0
  24. crosshair/diff_behavior_test.py +261 -0
  25. crosshair/dynamic_typing.py +346 -0
  26. crosshair/dynamic_typing_test.py +210 -0
  27. crosshair/enforce.py +282 -0
  28. crosshair/enforce_test.py +182 -0
  29. crosshair/examples/PEP316/__init__.py +1 -0
  30. crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
  31. crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
  32. crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
  33. crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
  34. crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
  35. crosshair/examples/PEP316/correct_code/__init__.py +0 -0
  36. crosshair/examples/PEP316/correct_code/arith.py +60 -0
  37. crosshair/examples/PEP316/correct_code/chess.py +77 -0
  38. crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
  39. crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
  40. crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
  41. crosshair/examples/PEP316/correct_code/showcase.py +104 -0
  42. crosshair/examples/__init__.py +0 -0
  43. crosshair/examples/check_examples_test.py +146 -0
  44. crosshair/examples/deal/__init__.py +1 -0
  45. crosshair/examples/icontract/__init__.py +1 -0
  46. crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
  47. crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
  48. crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
  49. crosshair/examples/icontract/correct_code/__init__.py +0 -0
  50. crosshair/examples/icontract/correct_code/arith.py +51 -0
  51. crosshair/examples/icontract/correct_code/showcase.py +94 -0
  52. crosshair/fnutil.py +391 -0
  53. crosshair/fnutil_test.py +75 -0
  54. crosshair/fuzz_core_test.py +516 -0
  55. crosshair/libimpl/__init__.py +0 -0
  56. crosshair/libimpl/arraylib.py +161 -0
  57. crosshair/libimpl/binascii_ch_test.py +30 -0
  58. crosshair/libimpl/binascii_test.py +67 -0
  59. crosshair/libimpl/binasciilib.py +150 -0
  60. crosshair/libimpl/bisectlib_test.py +23 -0
  61. crosshair/libimpl/builtinslib.py +5228 -0
  62. crosshair/libimpl/builtinslib_ch_test.py +1191 -0
  63. crosshair/libimpl/builtinslib_test.py +3735 -0
  64. crosshair/libimpl/codecslib.py +86 -0
  65. crosshair/libimpl/codecslib_test.py +86 -0
  66. crosshair/libimpl/collectionslib.py +264 -0
  67. crosshair/libimpl/collectionslib_ch_test.py +252 -0
  68. crosshair/libimpl/collectionslib_test.py +332 -0
  69. crosshair/libimpl/copylib.py +23 -0
  70. crosshair/libimpl/copylib_test.py +18 -0
  71. crosshair/libimpl/datetimelib.py +2559 -0
  72. crosshair/libimpl/datetimelib_ch_test.py +354 -0
  73. crosshair/libimpl/datetimelib_test.py +112 -0
  74. crosshair/libimpl/decimallib.py +5257 -0
  75. crosshair/libimpl/decimallib_ch_test.py +78 -0
  76. crosshair/libimpl/decimallib_test.py +76 -0
  77. crosshair/libimpl/encodings/__init__.py +23 -0
  78. crosshair/libimpl/encodings/_encutil.py +187 -0
  79. crosshair/libimpl/encodings/ascii.py +44 -0
  80. crosshair/libimpl/encodings/latin_1.py +40 -0
  81. crosshair/libimpl/encodings/utf_8.py +93 -0
  82. crosshair/libimpl/encodings_ch_test.py +83 -0
  83. crosshair/libimpl/fractionlib.py +16 -0
  84. crosshair/libimpl/fractionlib_test.py +80 -0
  85. crosshair/libimpl/functoolslib.py +34 -0
  86. crosshair/libimpl/functoolslib_test.py +56 -0
  87. crosshair/libimpl/hashliblib.py +30 -0
  88. crosshair/libimpl/hashliblib_test.py +18 -0
  89. crosshair/libimpl/heapqlib.py +47 -0
  90. crosshair/libimpl/heapqlib_test.py +21 -0
  91. crosshair/libimpl/importliblib.py +18 -0
  92. crosshair/libimpl/importliblib_test.py +38 -0
  93. crosshair/libimpl/iolib.py +216 -0
  94. crosshair/libimpl/iolib_ch_test.py +128 -0
  95. crosshair/libimpl/iolib_test.py +19 -0
  96. crosshair/libimpl/ipaddresslib.py +8 -0
  97. crosshair/libimpl/itertoolslib.py +44 -0
  98. crosshair/libimpl/itertoolslib_test.py +44 -0
  99. crosshair/libimpl/jsonlib.py +984 -0
  100. crosshair/libimpl/jsonlib_ch_test.py +42 -0
  101. crosshair/libimpl/jsonlib_test.py +51 -0
  102. crosshair/libimpl/mathlib.py +179 -0
  103. crosshair/libimpl/mathlib_ch_test.py +44 -0
  104. crosshair/libimpl/mathlib_test.py +67 -0
  105. crosshair/libimpl/oslib.py +7 -0
  106. crosshair/libimpl/pathliblib_test.py +10 -0
  107. crosshair/libimpl/randomlib.py +178 -0
  108. crosshair/libimpl/randomlib_test.py +120 -0
  109. crosshair/libimpl/relib.py +846 -0
  110. crosshair/libimpl/relib_ch_test.py +169 -0
  111. crosshair/libimpl/relib_test.py +493 -0
  112. crosshair/libimpl/timelib.py +72 -0
  113. crosshair/libimpl/timelib_test.py +82 -0
  114. crosshair/libimpl/typeslib.py +15 -0
  115. crosshair/libimpl/typeslib_test.py +36 -0
  116. crosshair/libimpl/unicodedatalib.py +75 -0
  117. crosshair/libimpl/unicodedatalib_test.py +42 -0
  118. crosshair/libimpl/urlliblib.py +23 -0
  119. crosshair/libimpl/urlliblib_test.py +19 -0
  120. crosshair/libimpl/weakreflib.py +13 -0
  121. crosshair/libimpl/weakreflib_test.py +69 -0
  122. crosshair/libimpl/zliblib.py +15 -0
  123. crosshair/libimpl/zliblib_test.py +13 -0
  124. crosshair/lsp_server.py +261 -0
  125. crosshair/lsp_server_test.py +30 -0
  126. crosshair/main.py +973 -0
  127. crosshair/main_test.py +543 -0
  128. crosshair/objectproxy.py +376 -0
  129. crosshair/objectproxy_test.py +41 -0
  130. crosshair/opcode_intercept.py +601 -0
  131. crosshair/opcode_intercept_test.py +304 -0
  132. crosshair/options.py +218 -0
  133. crosshair/options_test.py +10 -0
  134. crosshair/patch_equivalence_test.py +75 -0
  135. crosshair/path_cover.py +209 -0
  136. crosshair/path_cover_test.py +138 -0
  137. crosshair/path_search.py +161 -0
  138. crosshair/path_search_test.py +52 -0
  139. crosshair/pathing_oracle.py +271 -0
  140. crosshair/pathing_oracle_test.py +21 -0
  141. crosshair/pure_importer.py +27 -0
  142. crosshair/pure_importer_test.py +16 -0
  143. crosshair/py.typed +0 -0
  144. crosshair/register_contract.py +273 -0
  145. crosshair/register_contract_test.py +190 -0
  146. crosshair/simplestructs.py +1165 -0
  147. crosshair/simplestructs_test.py +283 -0
  148. crosshair/smtlib.py +24 -0
  149. crosshair/smtlib_test.py +14 -0
  150. crosshair/statespace.py +1199 -0
  151. crosshair/statespace_test.py +108 -0
  152. crosshair/stubs_parser.py +352 -0
  153. crosshair/stubs_parser_test.py +43 -0
  154. crosshair/test_util.py +329 -0
  155. crosshair/test_util_test.py +26 -0
  156. crosshair/tools/__init__.py +0 -0
  157. crosshair/tools/check_help_in_doc.py +264 -0
  158. crosshair/tools/check_init_and_setup_coincide.py +119 -0
  159. crosshair/tools/generate_demo_table.py +127 -0
  160. crosshair/tracers.py +544 -0
  161. crosshair/tracers_test.py +154 -0
  162. crosshair/type_repo.py +151 -0
  163. crosshair/unicode_categories.py +589 -0
  164. crosshair/unicode_categories_test.py +27 -0
  165. crosshair/util.py +741 -0
  166. crosshair/util_test.py +173 -0
  167. crosshair/watcher.py +307 -0
  168. crosshair/watcher_test.py +107 -0
  169. crosshair/z3util.py +76 -0
  170. crosshair/z3util_test.py +11 -0
  171. crosshair_tool-0.0.99.dist-info/METADATA +144 -0
  172. crosshair_tool-0.0.99.dist-info/RECORD +176 -0
  173. crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
  174. crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
  175. crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
  176. 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
+ ]