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/core_test.py ADDED
@@ -0,0 +1,1316 @@
1
+ import dataclasses
2
+ import importlib
3
+ import inspect
4
+ import re
5
+ import sys
6
+ import time
7
+ from typing import *
8
+
9
+ import pytest # type: ignore
10
+
11
+ import crosshair
12
+ from crosshair import core_and_libs, type_repo
13
+ from crosshair.core import (
14
+ deep_realize,
15
+ get_constructor_signature,
16
+ is_deeply_immutable,
17
+ proxy_for_class,
18
+ proxy_for_type,
19
+ run_checkables,
20
+ )
21
+ from crosshair.core_and_libs import (
22
+ AnalysisKind,
23
+ List,
24
+ MessageType,
25
+ analyze_any,
26
+ analyze_class,
27
+ analyze_function,
28
+ standalone_statespace,
29
+ )
30
+ from crosshair.fnutil import FunctionInfo, walk_qualname
31
+ from crosshair.libimpl.builtinslib import LazyIntSymbolicStr, SymbolicInt
32
+ from crosshair.options import DEFAULT_OPTIONS, AnalysisOptionSet
33
+ from crosshair.statespace import (
34
+ CANNOT_CONFIRM,
35
+ CONFIRMED,
36
+ EXEC_ERR,
37
+ POST_ERR,
38
+ POST_FAIL,
39
+ )
40
+ from crosshair.test_util import check_exec_err, check_messages, check_states
41
+ from crosshair.tracers import NoTracing, ResumedTracing, is_tracing
42
+ from crosshair.util import CrossHairInternal, set_debug
43
+
44
+ try:
45
+ import icontract
46
+ except Exception:
47
+ icontract = None # type: ignore
48
+
49
+
50
+ @pytest.fixture(autouse=True)
51
+ def check_tracer_state():
52
+ assert not is_tracing()
53
+ yield None
54
+ assert not is_tracing()
55
+
56
+
57
+ @dataclasses.dataclass
58
+ class Pokeable:
59
+ """
60
+ inv: self.x >= 0
61
+ """
62
+
63
+ x: int = 1
64
+
65
+ def poke(self) -> None:
66
+ """
67
+ post[self]: True
68
+ """
69
+ self.x += 1
70
+
71
+ def wild_pokeby(self, amount: int) -> None:
72
+ """
73
+ post[self]: True
74
+ """
75
+ self.x += amount
76
+
77
+ def safe_pokeby(self, amount: int) -> None:
78
+ """
79
+ pre: amount >= 0
80
+ post[self]: True
81
+ """
82
+ self.x += amount
83
+
84
+
85
+ def remove_smallest_with_asserts(numbers: List[int]) -> None:
86
+ assert len(numbers) > 0
87
+ smallest = min(numbers)
88
+ numbers.remove(smallest)
89
+ assert len(numbers) == 0 or min(numbers) > smallest
90
+
91
+
92
+ if icontract:
93
+
94
+ @icontract.snapshot(lambda lst: lst[:])
95
+ @icontract.ensure(lambda OLD, lst, value: lst == OLD.lst + [value])
96
+ def icontract_appender(lst: List[int], value: int) -> None:
97
+ lst.append(value)
98
+ lst.append(1984) # bug
99
+
100
+ @icontract.invariant(lambda self: self.x > 0)
101
+ class IcontractA(icontract.DBC):
102
+ def __init__(self) -> None:
103
+ self.x = 10
104
+
105
+ @icontract.require(lambda x: x % 2 == 0)
106
+ def weakenedfunc(self, x: int) -> None:
107
+ pass
108
+
109
+ @icontract.invariant(lambda self: self.x < 100)
110
+ class IcontractB(IcontractA):
111
+ def break_parent_invariant(self):
112
+ self.x = -1
113
+
114
+ def break_my_invariant(self):
115
+ self.x = 101
116
+
117
+ @icontract.require(lambda x: x % 3 == 0)
118
+ def weakenedfunc(self, x: int) -> None:
119
+ pass
120
+
121
+
122
+ class ShippingContainer:
123
+ container_weight = 4
124
+
125
+ def total_weight(self) -> int:
126
+ """post: _ < 10"""
127
+ return self.cargo_weight() + self.container_weight
128
+
129
+ def cargo_weight(self) -> int:
130
+ return 0
131
+
132
+ def __repr__(self):
133
+ return type(self).__name__ + "()"
134
+
135
+
136
+ class OverloadedContainer(ShippingContainer):
137
+ """
138
+ We use this example to demonstrate messaging when an override breaks
139
+ the contract of a different method.
140
+ """
141
+
142
+ def cargo_weight(self) -> int:
143
+ return 9
144
+
145
+
146
+ class Cat:
147
+ def size(self) -> int:
148
+ return 1
149
+
150
+
151
+ class BiggerCat(Cat):
152
+ def size(self) -> int:
153
+ return 2
154
+
155
+
156
+ class PersonTuple(NamedTuple):
157
+ name: str
158
+ age: int
159
+
160
+
161
+ class PersonWithoutAttributes:
162
+ def __init__(self, name: str, age: int):
163
+ self.name = name
164
+ self.age = age
165
+
166
+
167
+ NOW = 1000
168
+
169
+
170
+ @dataclasses.dataclass
171
+ class Person:
172
+ """
173
+ Contains various features that we expect to be successfully checkable.
174
+
175
+ inv: True
176
+ """
177
+
178
+ name: str
179
+ birth: int
180
+
181
+ def _getage(self):
182
+ return NOW - self.birth
183
+
184
+ def _setage(self, newage):
185
+ self.birth = NOW - newage
186
+
187
+ def _delage(self):
188
+ del self.birth
189
+
190
+ age = property(_getage, _setage, _delage, "Age of person")
191
+
192
+ def abstract_operation(self):
193
+ """
194
+ post: False # doesn't error because the method is "abstract"
195
+ """
196
+ raise NotImplementedError
197
+
198
+ def a_regular_method(self):
199
+ """post: True"""
200
+
201
+ @classmethod
202
+ def a_class_method(cls, x):
203
+ """post: cls == Person"""
204
+
205
+ @staticmethod
206
+ def a_static_method():
207
+ """post: True"""
208
+
209
+
210
+ class AirSample:
211
+ # NOTE: we don't use an enum here because we want to use pure symbolic containers
212
+ # in our tests.
213
+ CLEAN = 0
214
+ SMOKE = 1
215
+ CO2 = 2
216
+
217
+
218
+ @dataclasses.dataclass
219
+ class SmokeDetector:
220
+ """inv: not (self._is_plugged_in and self._in_original_packaging)"""
221
+
222
+ _in_original_packaging: bool
223
+ _is_plugged_in: bool
224
+
225
+ def signaling_alarm(self, air_samples: List[int]) -> bool:
226
+ """
227
+ pre: self._is_plugged_in
228
+ post: implies(AirSample.SMOKE in air_samples, _ == True)
229
+ """
230
+ return AirSample.SMOKE in air_samples
231
+
232
+
233
+ class Measurer:
234
+ def measure(self, x: int) -> str:
235
+ """
236
+ post: _ == self.measure(-x)
237
+ """
238
+ return "small" if x <= 10 else "large"
239
+
240
+
241
+ def _unused_fn(x: int) -> "ClassWithExplicitSignature":
242
+ raise NotImplementedError
243
+
244
+
245
+ class ClassWithExplicitSignature:
246
+ __signature__ = inspect.signature(_unused_fn)
247
+
248
+ def __init__(self, *a):
249
+ self.x = a[0]
250
+
251
+
252
+ A_REFERENCED_THING = 42
253
+
254
+
255
+ @dataclasses.dataclass(repr=False)
256
+ class ReferenceHoldingClass:
257
+ """
258
+ inv: self.item != A_REFERENCED_THING
259
+ """
260
+
261
+ item: str
262
+
263
+
264
+ def fibb(x: int) -> int:
265
+ """
266
+ pre: x>=0
267
+ post[]: _ < 3
268
+ """
269
+ if x <= 2:
270
+ return 1
271
+ r1, r2 = fibb(x - 1), fibb(x - 2)
272
+ ret = r1 + r2
273
+ return ret
274
+
275
+
276
+ def reentrant_precondition(minx: int):
277
+ """pre: reentrant_precondition(minx - 1)"""
278
+ return minx <= 10
279
+
280
+
281
+ def recursive_example(x: int) -> bool:
282
+ """
283
+ pre: x >= 0
284
+ post[]:
285
+ __old__.x >= 0 # just to confirm __old__ works in recursive cases
286
+ _ == True
287
+ """
288
+ if x == 0:
289
+ return True
290
+ else:
291
+ return recursive_example(x - 1)
292
+
293
+
294
+ class RegularInt:
295
+ def __new__(self, num: "int"):
296
+ return num
297
+
298
+
299
+ def test_get_constructor_signature_with_new():
300
+ assert RegularInt(7) == 7
301
+ params = get_constructor_signature(RegularInt).parameters
302
+ assert len(params) == 1
303
+ assert params["num"].name == "num"
304
+ assert params["num"].annotation == int
305
+
306
+
307
+ def test_proxy_alone() -> None:
308
+ def f(pokeable: Pokeable) -> None:
309
+ """
310
+ post[pokeable]: pokeable.x > 0
311
+ """
312
+ pokeable.poke()
313
+
314
+ check_states(f, CONFIRMED)
315
+
316
+
317
+ def test_proxy_in_list() -> None:
318
+ def f(pokeables: List[Pokeable]) -> None:
319
+ """
320
+ pre: len(pokeables) == 1
321
+ post: all(p.x > 0 for p in pokeables)
322
+ """
323
+ for pokeable in pokeables:
324
+ pokeable.poke()
325
+
326
+ check_states(f, CONFIRMED)
327
+
328
+
329
+ def test_class_with_explicit_signature() -> None:
330
+ def f(c: ClassWithExplicitSignature) -> int:
331
+ """post: _ != 42"""
332
+ return c.x
333
+
334
+ # pydantic sets __signature__ on the class, so we look for that as well as on
335
+ # __init__ (see https://github.com/samuelcolvin/pydantic/pull/1034)
336
+ check_states(f, POST_FAIL)
337
+
338
+
339
+ def test_preconditioned_init():
340
+ class Penguin:
341
+ _age: int
342
+
343
+ def __init__(self, age: int):
344
+ """pre: age >= 1"""
345
+ self._age = age
346
+
347
+ def f(p: Penguin) -> int:
348
+ """post: _ != 0"""
349
+ return p._age
350
+
351
+ check_states(f, CONFIRMED)
352
+
353
+
354
+ def test_class_proxies_are_created_through_constructor():
355
+ class Penguin:
356
+ can_swim: bool
357
+
358
+ def __init__(self):
359
+ self.can_swim = True
360
+
361
+ with standalone_statespace as space:
362
+ with NoTracing(): # (because following function resumes tracing)
363
+ p = proxy_for_class(Penguin, "p")
364
+ # `can_swim` is locked to True
365
+ assert p.can_swim is True
366
+
367
+
368
+ def test_exc_handling_doesnt_catch_crosshair_timeout():
369
+ def f(x: int, y: int):
370
+ """post: True"""
371
+ try:
372
+ while x + y <= x + y:
373
+ x += 1
374
+ except Exception as exc:
375
+ raise ValueError(f"I gobble exceptions!")
376
+
377
+ check_states(
378
+ f,
379
+ CANNOT_CONFIRM,
380
+ AnalysisOptionSet(per_condition_timeout=0.5, per_path_timeout=0.5),
381
+ )
382
+
383
+
384
+ def test_obj_member_fail() -> None:
385
+ def f(foo: Pokeable) -> int:
386
+ """
387
+ pre: 0 <= foo.x <= 4
388
+ post[foo]: _ < 5
389
+ """
390
+ foo.poke()
391
+ foo.poke()
392
+ return foo.x
393
+
394
+ check_states(f, POST_FAIL)
395
+
396
+
397
+ def test_obj_member_nochange_ok() -> None:
398
+ def f(foo: Pokeable) -> int:
399
+ """post: _ == foo.x"""
400
+ return foo.x
401
+
402
+ check_states(f, CONFIRMED)
403
+
404
+
405
+ def test_obj_member_change_ok() -> None:
406
+ def f(foo: Pokeable) -> int:
407
+ """
408
+ pre: foo.x >= 0
409
+ post[foo]: foo.x >= 2
410
+ """
411
+ foo.poke()
412
+ foo.poke()
413
+ return foo.x
414
+
415
+ check_states(f, CONFIRMED)
416
+
417
+
418
+ def test_obj_member_change_detect() -> None:
419
+ def f(foo: Pokeable) -> int:
420
+ """
421
+ pre: foo.x > 0
422
+ post[]: True
423
+ """
424
+ foo.poke()
425
+ return foo.x
426
+
427
+ check_states(f, POST_ERR)
428
+
429
+
430
+ def test_example_second_largest() -> None:
431
+ def second_largest(items: List[int]) -> int:
432
+ """
433
+ pre: len(items) == 3 # (length is to cap runtime)
434
+ post: _ == sorted(items)[-2]
435
+ """
436
+ next_largest, largest = items[:2]
437
+ if largest < next_largest:
438
+ next_largest, largest = largest, next_largest
439
+
440
+ for item in items[2:]:
441
+ if item > largest:
442
+ largest, next_largest = (item, largest)
443
+ elif item > next_largest:
444
+ next_largest = item
445
+ return next_largest
446
+
447
+ check_states(second_largest, CONFIRMED)
448
+
449
+
450
+ def test_pokeable_class() -> None:
451
+ messages = analyze_class(Pokeable)
452
+ line = Pokeable.wild_pokeby.__code__.co_firstlineno
453
+ actual, expected = check_messages(
454
+ messages, state=MessageType.POST_FAIL, line=line, column=0
455
+ )
456
+ assert actual == expected
457
+
458
+
459
+ def test_person_class() -> None:
460
+ messages = analyze_class(Person)
461
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
462
+ assert actual == expected
463
+
464
+
465
+ def test_methods_directly() -> None:
466
+ # Running analysis on individual methods directly works a little
467
+ # differently, especially for staticmethod/classmethod. Confirm these
468
+ # don't explode:
469
+ messages = analyze_any(
470
+ walk_qualname(Person, "a_regular_method"),
471
+ AnalysisOptionSet(),
472
+ )
473
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
474
+ assert actual == expected
475
+
476
+
477
+ def test_class_method() -> None:
478
+ messages = analyze_any(
479
+ walk_qualname(Person, "a_class_method"),
480
+ AnalysisOptionSet(),
481
+ )
482
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
483
+ assert actual == expected
484
+
485
+
486
+ def test_static_method() -> None:
487
+ messages = analyze_any(
488
+ walk_qualname(Person, "a_static_method"),
489
+ AnalysisOptionSet(),
490
+ )
491
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
492
+ assert actual == expected
493
+
494
+
495
+ def test_extend_namedtuple() -> None:
496
+ def f(p: PersonTuple) -> PersonTuple:
497
+ """
498
+ post: _.age != 222
499
+ """
500
+ return PersonTuple(p.name, p.age + 1)
501
+
502
+ check_states(f, POST_FAIL)
503
+
504
+
505
+ def test_without_typed_attributes() -> None:
506
+ def f(p: PersonWithoutAttributes) -> PersonWithoutAttributes:
507
+ """
508
+ post: _.age != 222
509
+ """
510
+ return PersonTuple(p.name, p.age + 1) # type: ignore
511
+
512
+ check_states(f, POST_FAIL)
513
+
514
+
515
+ def test_property() -> None:
516
+ def f(p: Person) -> None:
517
+ """
518
+ pre: 0 <= p.age < 100
519
+ post[p]: p.birth + p.age == NOW
520
+ """
521
+ assert p.age == NOW - p.birth
522
+ oldbirth = p.birth
523
+ p.age = p.age + 1
524
+ assert oldbirth == p.birth + 1
525
+
526
+ check_states(f, CONFIRMED)
527
+
528
+
529
+ def test_readonly_property_contract() -> None:
530
+ class Clock:
531
+ @property
532
+ def time(self) -> int:
533
+ """post: _ == self.time"""
534
+ return 120
535
+
536
+ messages = analyze_class(Clock)
537
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
538
+ assert actual == expected
539
+
540
+
541
+ def test_typevar_basic() -> None:
542
+ T = TypeVar("T")
543
+
544
+ @dataclasses.dataclass
545
+ class MaybePair(Generic[T]):
546
+ """
547
+ inv: (self.left is None) == (self.right is None)
548
+ """
549
+
550
+ left: Optional[T]
551
+ right: Optional[T]
552
+
553
+ def setpair(self, left: Optional[T], right: Optional[T]):
554
+ """post[self]: True"""
555
+ if (left is None) ^ (right is None):
556
+ raise ValueError("Populate both values or neither value in the pair")
557
+ self.left, self.right = left, right
558
+
559
+ messages = analyze_function(
560
+ FunctionInfo(MaybePair, "setpair", MaybePair.__dict__["setpair"])
561
+ )
562
+ actual, expected = check_messages(messages, state=MessageType.EXEC_ERR)
563
+ assert actual == expected
564
+
565
+
566
+ def test_bad_invariant():
567
+ class WithBadInvariant:
568
+ """
569
+ inv: self.item == 7
570
+ """
571
+
572
+ def do_a_thing(self) -> None:
573
+ pass
574
+
575
+ actual, expected = check_messages(
576
+ analyze_class(WithBadInvariant), state=MessageType.PRE_UNSAT
577
+ )
578
+ assert actual == expected
579
+
580
+
581
+ def test_expr_name_resolution():
582
+ """
583
+ dataclass() generates several methods. It can be tricky to ensure
584
+ that invariants for these methods can resolve names in the
585
+ correct namespace.
586
+ """
587
+ actual, expected = check_messages(
588
+ analyze_class(ReferenceHoldingClass), state=MessageType.CONFIRMED
589
+ )
590
+ assert actual == expected
591
+
592
+
593
+ def test_inheritance_base_class_ok():
594
+ actual, expected = check_messages(
595
+ analyze_class(SmokeDetector), state=MessageType.CONFIRMED
596
+ )
597
+ assert actual == expected
598
+
599
+
600
+ def test_super():
601
+ class FooDetector(SmokeDetector):
602
+ def signaling_alarm(self, air_samples: List[int]):
603
+ return super().signaling_alarm(air_samples)
604
+
605
+ actual, expected = check_messages(
606
+ analyze_class(FooDetector), state=MessageType.CONFIRMED
607
+ )
608
+ assert actual == expected
609
+
610
+
611
+ def test_use_inherited_postconditions():
612
+ class CarbonMonoxideDetector(SmokeDetector):
613
+ def signaling_alarm(self, air_samples: List[int]) -> bool:
614
+ """
615
+ post: implies(AirSample.CO2 in air_samples, _ == True)
616
+ """
617
+ return AirSample.CO2 in air_samples # fails: does not detect smoke
618
+
619
+ actual, expected = check_messages(
620
+ analyze_class(CarbonMonoxideDetector), state=MessageType.POST_FAIL
621
+ )
622
+ assert actual == expected
623
+
624
+
625
+ def test_inherited_preconditions_overridable():
626
+ @dataclasses.dataclass
627
+ class SmokeDetectorWithBattery(SmokeDetector):
628
+ _battery_power: int
629
+
630
+ def signaling_alarm(self, air_samples: List[int]) -> bool:
631
+ """
632
+ pre: self._battery_power > 0 or self._is_plugged_in
633
+ post: self._battery_power > 0
634
+ """
635
+ return AirSample.SMOKE in air_samples
636
+
637
+ actual, expected = check_messages(
638
+ analyze_class(SmokeDetectorWithBattery), state=MessageType.POST_FAIL
639
+ )
640
+ assert actual == expected
641
+
642
+
643
+ def test_use_subclasses_of_arguments():
644
+ # Even though the argument below is typed as the base class, the fact
645
+ # that a faulty implementation exists is enough to produce a
646
+ # counterexample:
647
+ def f(foo: Cat) -> int:
648
+ """post: _ == 1"""
649
+ return foo.size()
650
+
651
+ # Type repo doesn't load crosshair classes by default; load manually:
652
+ type_repo._add_class(Cat)
653
+ type_repo._add_class(BiggerCat)
654
+ check_states(f, POST_FAIL)
655
+
656
+
657
+ def test_does_not_report_with_actual_repr():
658
+ def f(foo: BiggerCat) -> int:
659
+ """post: False"""
660
+ return foo.size()
661
+
662
+ (actual, expected) = check_messages(
663
+ analyze_function(f),
664
+ state=MessageType.POST_FAIL,
665
+ message="false when calling f(BiggerCat()) " "(which returns 2)",
666
+ )
667
+ assert expected == actual
668
+
669
+
670
+ def test_check_parent_conditions():
671
+ # Ensure that conditions of parent classes are checked in children
672
+ # even when not overridden.
673
+ class Parent:
674
+ def size(self) -> int:
675
+ return 1
676
+
677
+ def amount_smaller(self, other_size: int) -> int:
678
+ """
679
+ pre: other_size >= 1
680
+ post: _ >= 0
681
+ """
682
+ return other_size - self.size()
683
+
684
+ class Child(Parent):
685
+ def size(self) -> int:
686
+ return 2
687
+
688
+ messages = analyze_class(Child)
689
+ actual, expected = check_messages(messages, state=MessageType.POST_FAIL)
690
+ assert actual == expected
691
+
692
+
693
+ def test_final_with_concrete_proxy():
694
+ from typing import Final
695
+
696
+ class FinalCat:
697
+ legs: Final[int] = 4
698
+
699
+ def __repr__(self):
700
+ return f"FinalCat with {self.legs} legs"
701
+
702
+ def f(cat: FinalCat, strides: int) -> int:
703
+ """
704
+ pre: strides > 0
705
+ post: __return__ >= 4
706
+ """
707
+ return strides * cat.legs
708
+
709
+ check_states(f, CONFIRMED)
710
+
711
+
712
+ # TODO: precondition strengthening check
713
+ def TODO_test_cannot_strengthen_inherited_preconditions():
714
+ class PowerHungrySmokeDetector(SmokeDetector):
715
+ _battery_power: int
716
+
717
+ def signaling_alarm(self, air_samples: List[int]) -> bool:
718
+ """
719
+ pre: self._is_plugged_in
720
+ pre: self._battery_power > 0
721
+ """
722
+ return AirSample.SMOKE in air_samples
723
+
724
+ actual, expected = check_messages(
725
+ analyze_class(PowerHungrySmokeDetector), state=MessageType.PRE_INVALID
726
+ )
727
+ assert actual == expected
728
+
729
+
730
+ def test_newtype() -> None:
731
+ T = TypeVar("T")
732
+ Number = NewType("Number", int)
733
+ with standalone_statespace:
734
+ x = proxy_for_type(Number, "x", allow_subtypes=False)
735
+ assert isinstance(x, SymbolicInt)
736
+
737
+
738
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
739
+ def test_type_statement() -> None:
740
+ env: dict[str, Any] = {}
741
+ exec("type MyIntNew = int\n", env)
742
+ assert "MyIntNew" in env
743
+ MyIntNew = env["MyIntNew"]
744
+ with standalone_statespace:
745
+ x = proxy_for_type(MyIntNew, "x")
746
+ assert isinstance(x, SymbolicInt)
747
+
748
+
749
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
750
+ def test_parameterized_type_statement() -> None:
751
+ env: dict[str, Any] = {}
752
+ exec("type Pair[A, B] = tuple[B, A]\n", env)
753
+ assert "Pair" in env
754
+ Pair = env["Pair"]
755
+ with standalone_statespace:
756
+ x = proxy_for_type(Pair[int, str], "x")
757
+ assert isinstance(x[0], LazyIntSymbolicStr)
758
+ assert isinstance(x[1], SymbolicInt)
759
+
760
+
761
+ def test_container_typevar() -> None:
762
+ T = TypeVar("T")
763
+
764
+ def f(s: Sequence[T]) -> Dict[T, T]:
765
+ """post: len(_) == len(s)"""
766
+ return dict(zip(s, s))
767
+
768
+ # (sequence could contain duplicate items)
769
+ check_states(f, POST_FAIL)
770
+
771
+
772
+ def test_typevar_bounds_fail() -> None:
773
+ T = TypeVar("T")
774
+
775
+ def f(x: T) -> int:
776
+ """post:True"""
777
+ return x + 1 # type: ignore
778
+
779
+ check_states(f, EXEC_ERR)
780
+
781
+
782
+ def test_typevar_bounds_ok() -> None:
783
+ B = TypeVar("B", bound=int)
784
+
785
+ def f(x: B) -> int:
786
+ """post:True"""
787
+ return x + 1
788
+
789
+ check_states(f, CONFIRMED)
790
+
791
+
792
+ def test_any() -> None:
793
+ def f(x: Any) -> bool:
794
+ """post: True"""
795
+ return x is None
796
+
797
+ check_states(f, CONFIRMED)
798
+
799
+
800
+ def test_meeting_class_preconditions() -> None:
801
+ def f() -> int:
802
+ """
803
+ post: _ == -1
804
+ """
805
+ pokeable = Pokeable(0)
806
+ pokeable.safe_pokeby(-1)
807
+ return pokeable.x
808
+
809
+ analyze_function(f)
810
+ # TODO: this doesn't test anything?
811
+
812
+
813
+ def test_enforced_fn_preconditions() -> None:
814
+ def f(x: int) -> bool:
815
+ """post: _ == True"""
816
+ return bool(fibb(x)) or True
817
+
818
+ check_states(f, EXEC_ERR)
819
+
820
+
821
+ def test_generic_object() -> None:
822
+ def f(thing: object):
823
+ """post: True"""
824
+ if isinstance(thing, SmokeDetector):
825
+ return thing._is_plugged_in
826
+ return False
827
+
828
+ check_states(f, CANNOT_CONFIRM)
829
+
830
+
831
+ def get_natural_number() -> int:
832
+ """post: _ >= 0"""
833
+ # crosshair: specs_complete=True
834
+ return 1
835
+
836
+
837
+ @pytest.mark.smoke
838
+ def test_specs_complete():
839
+ def f() -> int:
840
+ """post: _"""
841
+ return get_natural_number()
842
+
843
+ (actual, expected) = check_messages(
844
+ analyze_function(f),
845
+ state=MessageType.POST_FAIL,
846
+ message="false when calling f() "
847
+ "with crosshair.patch_to_return({"
848
+ "crosshair.core_test.get_natural_number: [0]}) "
849
+ "(which returns 0)",
850
+ )
851
+ assert actual == expected
852
+
853
+ # also check that it reproduces!:
854
+ assert get_natural_number() == 1
855
+ with crosshair.patch_to_return({crosshair.core_test.get_natural_number: [0]}):
856
+ assert get_natural_number() == 0
857
+
858
+
859
+ def test_access_class_method_on_symbolic_type():
860
+ with standalone_statespace as space:
861
+ person = proxy_for_type(Type[Person], "p")
862
+ person.a_class_method(42) # Just check that this don't explode
863
+
864
+
865
+ def test_syntax_error() -> None:
866
+ def f(x: int):
867
+ """pre: x && x"""
868
+
869
+ actual, expected = check_messages(analyze_function(f), state=MessageType.SYNTAX_ERR)
870
+ assert actual == expected
871
+
872
+
873
+ def test_raises_ok() -> None:
874
+ def f() -> bool:
875
+ """
876
+ raises: IndexError, NameError
877
+ post: __return__
878
+ """
879
+ raise IndexError()
880
+ return True
881
+
882
+ check_states(f, CONFIRMED)
883
+
884
+
885
+ def test_optional_can_be_none_fail() -> None:
886
+ def f(n: Optional[Pokeable]) -> bool:
887
+ """post: _"""
888
+ return isinstance(n, Pokeable)
889
+
890
+ check_states(f, POST_FAIL)
891
+
892
+
893
+ def test_implicit_heapref_conversions() -> None:
894
+ def f(foo: List[List]) -> None:
895
+ """
896
+ pre: len(foo) > 0
897
+ post: True
898
+ """
899
+ foo[0].append(42)
900
+
901
+ check_states(f, CONFIRMED)
902
+
903
+
904
+ def test_nonuniform_list_types_1() -> None:
905
+ def f(a: List[object], b: List[int]) -> List[object]:
906
+ """
907
+ pre: len(b) == 5 # constraint for performance
908
+ post: b[0] not in _
909
+ """
910
+ ret = a + b[1:] # type: ignore
911
+ return ret
912
+
913
+ check_states(f, POST_FAIL)
914
+
915
+
916
+ def test_nonuniform_list_types_2() -> None:
917
+ def f(a: List[object], b: List[int]) -> List[object]:
918
+ """
919
+ pre: len(b) == 5 # constraint for performance
920
+ post: b[-1] not in _
921
+ """
922
+ return a + b[:-1] # type: ignore
923
+
924
+ check_states(f, POST_FAIL)
925
+
926
+
927
+ def test_varargs_fail() -> None:
928
+ def f(x: int, *a: str, **kw: bool) -> int:
929
+ """post: _ > x"""
930
+ return x + len(a) + (42 if kw else 0)
931
+
932
+ check_states(f, POST_FAIL)
933
+
934
+
935
+ def test_varargs_ok() -> None:
936
+ def f(x: int, *a: str, **kw: bool) -> int:
937
+ """post: _ >= x"""
938
+ return x + len(a) + (42 if kw else 0)
939
+
940
+ check_states(f, CANNOT_CONFIRM)
941
+
942
+
943
+ def test_recursive_fn_fail() -> None:
944
+ check_states(fibb, POST_FAIL)
945
+
946
+
947
+ def test_recursive_fn_ok() -> None:
948
+ check_states(recursive_example, CONFIRMED)
949
+
950
+
951
+ def test_recursive_postcondition_ok() -> None:
952
+ def f(x: int) -> int:
953
+ """post: _ == f(-x)"""
954
+ return x * x
955
+
956
+ check_states(f, CONFIRMED)
957
+
958
+
959
+ def test_reentrant_precondition() -> None:
960
+ # Really, we're just ensuring that we don't stack-overflow here.
961
+ check_states(reentrant_precondition, CONFIRMED)
962
+
963
+
964
+ def test_recursive_postcondition_enforcement_suspension() -> None:
965
+ messages = analyze_class(Measurer)
966
+ actual, expected = check_messages(messages, state=MessageType.POST_FAIL)
967
+ assert actual == expected
968
+
969
+
970
+ def test_short_circuiting() -> None:
971
+ # Some operations are hard to deal with symbolically, like hashes.
972
+ # CrossHair will sometimes "short-circuit" functions, in hopes that the
973
+ # function body isn't required to prove the postcondition.
974
+ # This is an example of such a case.
975
+ def f(x: str) -> int:
976
+ """post: _ == 0"""
977
+ a = hash(x)
978
+ b = 7
979
+ # This is zero no matter what the hashes are:
980
+ return (a + b) - (b + a)
981
+
982
+ check_states(f, CONFIRMED)
983
+
984
+
985
+ def test_error_message_in_unrelated_method() -> None:
986
+ messages = analyze_class(OverloadedContainer)
987
+ line = ShippingContainer.total_weight.__code__.co_firstlineno + 1
988
+ actual, expected = check_messages(
989
+ messages,
990
+ state=MessageType.POST_FAIL,
991
+ message="false when calling total_weight(OverloadedContainer()) (which returns 13)",
992
+ line=line,
993
+ )
994
+ assert actual == expected
995
+
996
+
997
+ def test_error_message_has_unmodified_args() -> None:
998
+ def f(foo: List[Pokeable]) -> None:
999
+ """
1000
+ pre: len(foo) == 1
1001
+ pre: foo[0].x == 10
1002
+ post[foo]: foo[0].x == 12
1003
+ """
1004
+ foo[0].poke()
1005
+
1006
+ actual, expected = check_messages(
1007
+ analyze_function(f),
1008
+ state=MessageType.POST_FAIL,
1009
+ message="false when calling f([Pokeable(x=10)])",
1010
+ )
1011
+ assert actual == expected
1012
+
1013
+
1014
+ # TODO: List[List] involves no HeapRefs
1015
+ def TODO_test_potential_circular_references() -> None:
1016
+ # TODO?: potential aliasing of input argument data?
1017
+ def f(foo: List[List], thing: object) -> None:
1018
+ """
1019
+ pre: len(foo) == 2
1020
+ pre: len(foo[0]) == 1
1021
+ pre: len(foo[1]) == 1
1022
+ post: len(foo[1]) == 1
1023
+ """
1024
+ foo[0].append(object()) # TODO: using 42 yields a z3 sort error
1025
+
1026
+ check_states(f, CONFIRMED)
1027
+
1028
+
1029
+ def test_nonatomic_comparison() -> None:
1030
+ def f(x: int, ls: List[str]) -> bool:
1031
+ """post: not _"""
1032
+ return ls == x
1033
+
1034
+ check_states(f, CONFIRMED)
1035
+
1036
+
1037
+ def test_difficult_equality() -> None:
1038
+ def f(x: Dict[FrozenSet[float], int]) -> bool:
1039
+ """post: not _"""
1040
+ return x == {frozenset({10.0}): 1}
1041
+
1042
+ check_states(f, POST_FAIL)
1043
+
1044
+
1045
+ def test_old_works_in_invariants() -> None:
1046
+ @dataclasses.dataclass
1047
+ class FrozenApples:
1048
+ """inv: self.count == __old__.self.count"""
1049
+
1050
+ count: int
1051
+
1052
+ def add_one(self):
1053
+ self.count += 1
1054
+
1055
+ messages = analyze_class(FrozenApples)
1056
+ actual, expected = check_messages(messages, state=MessageType.POST_FAIL)
1057
+ assert actual == expected
1058
+
1059
+ # Also confirm we can create one as an argument:
1060
+ def f(a: FrozenApples) -> int:
1061
+ """post: True"""
1062
+ return 0
1063
+
1064
+ check_states(f, CONFIRMED)
1065
+
1066
+
1067
+ def test_class_patching_is_undone() -> None:
1068
+ # CrossHair does a lot of monkey matching of classes
1069
+ # with contracts. Ensure that gets undone.
1070
+ original_container = ShippingContainer.__dict__.copy()
1071
+ original_overloaded = OverloadedContainer.__dict__.copy()
1072
+ run_checkables(analyze_class(OverloadedContainer))
1073
+ for k, v in original_container.items():
1074
+ assert ShippingContainer.__dict__[k] is v
1075
+ for k, v in original_overloaded.items():
1076
+ assert OverloadedContainer.__dict__[k] is v
1077
+
1078
+
1079
+ def test_fallback_when_smt_values_out_themselves() -> None:
1080
+ def f(items: List[str]) -> str:
1081
+ """post: True"""
1082
+ return ",".join(items)
1083
+
1084
+ check_states(f, CANNOT_CONFIRM)
1085
+
1086
+
1087
+ def test_unrelated_regex() -> None:
1088
+ def f(s: str) -> bool:
1089
+ """post: True"""
1090
+ return bool(re.match(r"(\d+)", s))
1091
+
1092
+ check_states(f, CANNOT_CONFIRM)
1093
+
1094
+
1095
+ if sys.version_info >= (3, 9):
1096
+
1097
+ def test_new_style_type_hints():
1098
+ def f(ls: list[int]) -> List[int]:
1099
+ """
1100
+ pre: len(ls) == 1
1101
+ post: _[0] != 'a'
1102
+ """
1103
+ return ls
1104
+
1105
+ check_states(f, CONFIRMED)
1106
+
1107
+
1108
+ def test_nondeterministic_detected_via_condition() -> None:
1109
+ _GLOBAL_THING = [42]
1110
+
1111
+ def f(i: int) -> int:
1112
+ """post: True"""
1113
+ _GLOBAL_THING[0] += 1
1114
+ if i > _GLOBAL_THING[0]:
1115
+ pass
1116
+ return True
1117
+
1118
+ actual, expected = check_exec_err(f, "NotDeterministic")
1119
+ assert actual == expected
1120
+
1121
+
1122
+ def test_nondeterministic_detected_in_detached_path() -> None:
1123
+ _GLOBAL_THING = [True]
1124
+
1125
+ def f(i: int) -> int:
1126
+ """post: True"""
1127
+ _GLOBAL_THING[0] = not _GLOBAL_THING[0]
1128
+ if _GLOBAL_THING[0]:
1129
+ raise Exception
1130
+ else:
1131
+ return -i if i < 0 else i
1132
+
1133
+ actual, expected = check_exec_err(f, "NotDeterministic")
1134
+ assert actual == expected
1135
+
1136
+
1137
+ if icontract:
1138
+
1139
+ def test_icontract_basic():
1140
+ @icontract.ensure(lambda result, x: result > x)
1141
+ def some_func(x: int, y: int = 5) -> int:
1142
+ return x - y
1143
+
1144
+ check_states(some_func, POST_FAIL)
1145
+
1146
+ def test_icontract_snapshots():
1147
+ messages = analyze_function(
1148
+ icontract_appender,
1149
+ DEFAULT_OPTIONS,
1150
+ )
1151
+ line = icontract_appender.__wrapped__.__code__.co_firstlineno + 1
1152
+ actual, expected = check_messages(
1153
+ messages, state=MessageType.POST_FAIL, line=line, column=0
1154
+ )
1155
+ assert actual == expected
1156
+
1157
+ def test_icontract_weaken():
1158
+ @icontract.require(lambda x: x in (2, 3))
1159
+ @icontract.ensure(lambda: True)
1160
+ def trynum(x: int):
1161
+ IcontractB().weakenedfunc(x)
1162
+
1163
+ check_states(trynum, CONFIRMED)
1164
+
1165
+ @pytest.mark.skip(
1166
+ reason="Temporary disable: RecursionError on 3.11.13 - icontract's _IN_PROGRESS + CrossHair laziness cross-test leaks"
1167
+ )
1168
+ def test_icontract_class():
1169
+ messages = run_checkables(
1170
+ analyze_class(
1171
+ IcontractB,
1172
+ # TODO: why is this required?
1173
+ DEFAULT_OPTIONS.overlay(analysis_kind=[AnalysisKind.icontract]),
1174
+ )
1175
+ )
1176
+ messages = {
1177
+ (m.state, m.line, m.message)
1178
+ for m in messages
1179
+ if m.state != MessageType.CONFIRMED
1180
+ }
1181
+ line_gt0 = IcontractB.break_parent_invariant.__wrapped__.__code__.co_firstlineno
1182
+ line_lt100 = IcontractB.break_my_invariant.__wrapped__.__code__.co_firstlineno
1183
+ assert messages == {
1184
+ (
1185
+ MessageType.POST_FAIL,
1186
+ line_gt0,
1187
+ '"@icontract.invariant(lambda self: self.x > 0)" yields false '
1188
+ "when calling break_parent_invariant(IcontractB())",
1189
+ ),
1190
+ (
1191
+ MessageType.POST_FAIL,
1192
+ line_lt100,
1193
+ '"@icontract.invariant(lambda self: self.x < 100)" yields false '
1194
+ "when calling break_my_invariant(IcontractB())",
1195
+ ),
1196
+ }
1197
+
1198
+ def test_icontract_nesting():
1199
+ @icontract.require(lambda name: name.startswith("a"))
1200
+ def innerfn(name: str):
1201
+ pass
1202
+
1203
+ @icontract.ensure(lambda: True)
1204
+ @icontract.require(lambda name: len(name) > 0)
1205
+ def outerfn(name: str):
1206
+ innerfn("00" + name)
1207
+
1208
+ actual, expected = check_exec_err(
1209
+ outerfn,
1210
+ message_prefix="PreconditionFailed",
1211
+ )
1212
+ assert actual == expected
1213
+
1214
+
1215
+ def test_asserts():
1216
+ messages = analyze_function(
1217
+ remove_smallest_with_asserts,
1218
+ DEFAULT_OPTIONS.overlay(
1219
+ analysis_kind=[AnalysisKind.asserts],
1220
+ ),
1221
+ )
1222
+ line = remove_smallest_with_asserts.__code__.co_firstlineno + 4
1223
+ expected, actual = check_messages(
1224
+ messages, state=MessageType.EXEC_ERR, line=line, column=0
1225
+ )
1226
+ assert expected == actual
1227
+
1228
+
1229
+ def test_unpickable_args() -> None:
1230
+ from threading import RLock # RLock objects aren't copyable
1231
+
1232
+ @dataclasses.dataclass
1233
+ class WithUnpickleableArg:
1234
+ x: int
1235
+ lock: RLock
1236
+
1237
+ def dothing(foo: WithUnpickleableArg) -> int:
1238
+ """
1239
+ post: __return__ != 42
1240
+ """
1241
+ return foo.x
1242
+
1243
+ check_states(dothing, POST_FAIL)
1244
+
1245
+
1246
+ def test_kwargs(space):
1247
+ def callme(lu=3, **kw):
1248
+ return 42
1249
+
1250
+ kwargs = proxy_for_type(Dict[str, int], "kwargs")
1251
+ with ResumedTracing():
1252
+ space.add(kwargs.__len__() == 1)
1253
+ assert callme(**kwargs) == 42 # (this is a CALL_FUNCTION_EX opcode)
1254
+
1255
+
1256
+ @pytest.mark.smoke
1257
+ def test_deep_realize(space):
1258
+ x = proxy_for_type(int, "x")
1259
+ with ResumedTracing():
1260
+ space.add(x == 4)
1261
+
1262
+ @dataclasses.dataclass
1263
+ class Woo:
1264
+ stuff: Dict[str, int]
1265
+
1266
+ woo = Woo({"": x})
1267
+ assert type(woo.stuff[""]) is not int
1268
+ realized = deep_realize(woo)
1269
+ assert type(realized.stuff[""]) is int
1270
+ assert realized.stuff[""] == 4
1271
+
1272
+
1273
+ @pytest.mark.parametrize(
1274
+ "o", (4, "foo", 23.1, None, (12,), frozenset({1, 2}), ((), (4,)))
1275
+ )
1276
+ def test_is_deeply_immutable(o):
1277
+ with standalone_statespace:
1278
+ assert is_deeply_immutable(o)
1279
+
1280
+
1281
+ @pytest.mark.parametrize("o", ({}, {1: 2}, [], (3, []), ("foo", (3, []))))
1282
+ def test_is_not_deeply_immutable(o):
1283
+ with standalone_statespace:
1284
+ assert not is_deeply_immutable(o)
1285
+
1286
+
1287
+ def test_crosshair_modules_can_be_reloaded():
1288
+ importlib.reload(core_and_libs)
1289
+
1290
+
1291
+ def profile():
1292
+ # This is a scratch area to run quick profiles.
1293
+ def f(x: int) -> int:
1294
+ """
1295
+ post: _ != 123456
1296
+ """
1297
+ return hash(x)
1298
+
1299
+ check_states(f, AnalysisOptionSet(max_iterations=20)) == {
1300
+ MessageType.CANNOT_CONFIRM
1301
+ }
1302
+
1303
+
1304
+ if __name__ == "__main__":
1305
+ if ("-v" in sys.argv) or ("--verbose" in sys.argv):
1306
+ set_debug(True)
1307
+ if "-p" in sys.argv:
1308
+ import time
1309
+
1310
+ t0 = time.time()
1311
+ profile()
1312
+ print("check seconds:", time.time() - t0)
1313
+ elif "-t" in sys.argv:
1314
+ import cProfile
1315
+
1316
+ cProfile.run("profile()", "out.pprof")