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
@@ -0,0 +1,77 @@
1
+ import dataclasses
2
+
3
+
4
+ @dataclasses.dataclass(init=False)
5
+ class ChessPiece:
6
+ """
7
+ A base class for chess pieces.
8
+
9
+ inv: 0 <= self.x < 8
10
+ inv: 0 <= self.y < 8
11
+ """
12
+
13
+ x: int
14
+ y: int
15
+
16
+ def __init__(self, x: int, y: int):
17
+ """
18
+ :raises: ValueError
19
+ """
20
+ if not (0 <= x < 8):
21
+ raise ValueError(f'x position "{x}" is invalid')
22
+ if not (0 <= y < 8):
23
+ raise ValueError(f'y position "{y}" is invalid')
24
+ self.x = x
25
+ self.y = y
26
+
27
+ def can_move_to(self, x: int, y: int) -> bool:
28
+ """
29
+ Determine whether this piece can move to the given position (in a single turn).
30
+
31
+ pre: (0 <= x < 8) and (0 <= y < 8)
32
+
33
+ # It's never valid to "move" to your present location:
34
+ post: implies((x, y) == (self.x, self.y), not __return__)
35
+ """
36
+ raise NotImplementedError
37
+
38
+
39
+ class FreeChessPiece(ChessPiece):
40
+ def can_move_to(self, x: int, y: int) -> bool:
41
+ """
42
+ Most pieces (except the pawn) can move back to their
43
+ starting position after moving.
44
+ post: implies(_, type(self)(x, y).can_move_to(self.x, self.y))
45
+ """
46
+ raise NotImplementedError
47
+
48
+
49
+ def _board_is_symmetric(piece: ChessPiece, x: int, y: int):
50
+ """
51
+ A method just for testing. (put this is a test file if you like)
52
+
53
+ If the given piece can move to (x,y), then the equivalent
54
+ opponent's piece should be able to move to the mirrored position.
55
+
56
+ pre: piece.can_move_to(x, y)
57
+ post: piece.can_move_to(7 - x, 7 - y)
58
+ """
59
+ piece.x = 7 - piece.x
60
+ piece.y = 7 - piece.y
61
+
62
+
63
+ class Pawn(ChessPiece):
64
+ def can_move_to(self, x: int, y: int) -> bool:
65
+ return (x == self.x) and (y == 3) and (x, y) != (self.x, self.y)
66
+
67
+
68
+ class Rook(FreeChessPiece):
69
+ def can_move_to(self, x: int, y: int) -> bool:
70
+ return (x == self.x) ^ (y == self.y)
71
+
72
+
73
+ class King(FreeChessPiece):
74
+ def can_move_to(self, x: int, y: int) -> bool:
75
+ return (
76
+ abs(x - self.x) <= 1 and abs(y - self.y) <= 1 and (x, y) != (self.x, self.y)
77
+ )
@@ -0,0 +1,17 @@
1
+ from typing import Optional, Tuple
2
+
3
+
4
+ def mydiv(x: int, y: int) -> Optional[float]:
5
+ """
6
+ pre: y != 0
7
+ post: isinstance(_, float)
8
+ """
9
+ return None if y == 0 else x / y
10
+
11
+
12
+ def myavg(t: Tuple[int, ...]) -> Optional[float]:
13
+ """
14
+ pre: len(t) > 0
15
+ post: isinstance(_, float)
16
+ """
17
+ return mydiv(sum(t), len(t))
@@ -0,0 +1,132 @@
1
+ from typing import Tuple, Type
2
+
3
+ import numpy as np
4
+ from numpy.lib.mixins import NDArrayOperatorsMixin
5
+
6
+ from crosshair import (
7
+ IgnoreAttempt,
8
+ SymbolicFactory,
9
+ deep_realize,
10
+ realize,
11
+ register_type,
12
+ )
13
+ from crosshair.util import CrosshairUnsupported
14
+
15
+ #
16
+ # Classes implemented in C generally cannot be simulated symbolically by
17
+ # CrossHair.
18
+ # However, you can install hooks to produce custom symbolic values.
19
+ # Here, we provide a lazy version of the numpy's array class: this has
20
+ # a symbolic shape and data type.
21
+ # When an actual operation needs to be performed, we'll construct the
22
+ # actual array.
23
+ #
24
+
25
+
26
+ class SymbolicNdarray(NDArrayOperatorsMixin):
27
+ def __init__(self, creator: SymbolicFactory):
28
+ # Our callback gets a SymbolicFactory instance which can produce more
29
+ # symbolic values when called with a type.
30
+ self.shape = creator(Tuple[int, ...], "_shape")
31
+ # Note that we avoid the builtin len() - symbolic creation hooks do not run
32
+ # under the monkeypatched environment, and calling the real len() would
33
+ # realize the shape's length.
34
+ self.ndim = self.shape.__len__()
35
+ self.dtype = np.dtype(realize(creator(Type[np.number], "_dtype")))
36
+
37
+ @property
38
+ def size(self):
39
+ totalsize = 1
40
+ for size in self.shape:
41
+ if size < 0:
42
+ raise IgnoreAttempt("ndarray disallows negative dimensions")
43
+ totalsize *= size
44
+ return totalsize
45
+
46
+ def __repr__(self):
47
+ return repr(self.__array__())
48
+
49
+ def __array_function__(self, func, types, args, kwargs):
50
+ return func(*deep_realize(args), **kwargs)
51
+
52
+ def __array_ufunc__(self, ufunc, method, *args, **kwargs):
53
+ if method == "__call__":
54
+ return ufunc(*deep_realize(args), **kwargs)
55
+ else:
56
+ return NotImplemented
57
+
58
+ def __ch_realize__(self):
59
+ # CrossHair looks for this magic method when it needs to make a value concrete:
60
+ return self.__array__()
61
+
62
+ def __array__(self):
63
+ # numpy looks for this magic method when it needs a real numpy array:
64
+ if any(size < 0 for size in self.shape):
65
+ raise IgnoreAttempt("ndarray disallows negative dimensions")
66
+ self.dtype = realize(self.dtype)
67
+ self.shape = deep_realize(self.shape)
68
+ if self.size * self.dtype.itemsize > 10 * 1024 * 1024:
69
+ raise CrosshairUnsupported("Will not realize numpy arrays over 10MB")
70
+ # For the contents, we just construct it with ones. This makes it much
71
+ # less complete in terms of finding counterexamples, but is sufficient
72
+ # for array dimension and type reasoning. If we were more ambitious,
73
+ # we would rewrite a (slow) implementation of numpy in terms of native
74
+ # Python types.
75
+ return np.ones(self.shape, self.dtype)
76
+
77
+
78
+ # Make crosshair use our custom class whenever it needs a symbolic
79
+ # ndarray instance:
80
+ register_type(np.ndarray, SymbolicNdarray)
81
+
82
+
83
+ def matrix_multiply(image1: np.ndarray, image2: np.ndarray) -> np.ndarray:
84
+ """
85
+ pre: image1.dtype == image2.dtype == np.float64
86
+ pre: len(image1.shape) == len(image2.shape) == 2
87
+ pre: image1.shape[1] == image2.shape[0]
88
+ post: __return__.shape == (image1.shape[0], image2.shape[1])
89
+ """
90
+ return image1 @ image2
91
+
92
+
93
+ def unit_normalize(a: np.ndarray) -> np.ndarray:
94
+ """
95
+ Normalize the given array values into the [0,1] range.
96
+
97
+ >>> unit_normalize(np.arange(-1, 2))
98
+ array([0. , 0.5, 1. ])
99
+
100
+ pre: a.size > 0
101
+ pre: a.dtype == np.float64
102
+ pre: np.ptp(a) > 0
103
+ post: np.max(_) <= 1.0
104
+ post: np.min(_) >= 0.0
105
+ """
106
+ return (a - np.min(a)) / np.ptp(a)
107
+
108
+
109
+ def threshold_image(image: np.ndarray, threshold: float) -> np.ndarray:
110
+ """
111
+ >>> threshold_image(np.array([[0.0, 0.3], [0.6, 1.0]], dtype=np.float64), 0.5)
112
+ array([[0.5, 0.5],
113
+ [0.6, 1. ]])
114
+
115
+ pre: len(image.shape) == 2
116
+ pre: image.dtype == np.float64
117
+ pre: image.size > 0
118
+ pre: threshold > 0
119
+ post: _.shape == image.shape
120
+ post: image.dtype == _.dtype
121
+ post: np.min(_) >= threshold
122
+ """
123
+ return np.where(image > threshold, image, threshold)
124
+
125
+
126
+ def repeat_array(src: np.ndarray, count: int) -> np.ndarray:
127
+ """
128
+ pre: src.shape[0] > 0
129
+ pre: count > 0
130
+ post: _.shape == (src.shape[0] * count, *src.shape[1:])
131
+ """
132
+ return np.concatenate([src] * count)
@@ -0,0 +1,35 @@
1
+ import dataclasses
2
+ from typing import List
3
+
4
+
5
+ @dataclasses.dataclass
6
+ class AverageableStack:
7
+ """
8
+ A stack of numbers with a O(1) average() operation.
9
+
10
+ inv: self._total == sum(self._values)
11
+ """
12
+
13
+ _values: List[int]
14
+ _total: int
15
+
16
+ def __init__(self):
17
+ self._values = []
18
+ self._total = 0
19
+
20
+ def push(self, val: int):
21
+ """post: True"""
22
+ self._values.append(val)
23
+ self._total += val
24
+
25
+ def pop(self) -> int:
26
+ """
27
+ pre: self._values
28
+ """
29
+ val = self._values.pop()
30
+ self._total -= val
31
+ return val
32
+
33
+ def average(self) -> float:
34
+ """pre: self._values"""
35
+ return self._total / len(self._values)
@@ -0,0 +1,104 @@
1
+ import statistics
2
+ from typing import Iterable, List, Sequence, Tuple, TypeVar
3
+
4
+ T = TypeVar("T")
5
+ U = TypeVar("U")
6
+
7
+
8
+ def average(numbers: List[float]) -> float:
9
+ """
10
+ pre: len(numbers) > 0
11
+ post: min(numbers) <= __return__ <= max(numbers)
12
+ """
13
+ return sum(numbers) / len(numbers)
14
+
15
+
16
+ def duplicate_list(a: List[T]) -> List[T]:
17
+ """
18
+ post: len(__return__) == 2 * len(a)
19
+ post: __return__[:len(a)] == a
20
+ post: __return__[-len(a):] == a
21
+ """
22
+ return a + a
23
+
24
+
25
+ def compute_grade(homework_scores: List[float], exam_scores: List[float]) -> float:
26
+ """
27
+ pre: homework_scores or exam_scores
28
+ pre: all(0 <= s <= 1.0 for s in homework_scores + exam_scores)
29
+ post: 0 <= __return__ <= 1.0
30
+ """
31
+ # make exams matter more by counting them twice:
32
+ all_scores = homework_scores + exam_scores + exam_scores
33
+ return sum(all_scores) / len(all_scores)
34
+
35
+
36
+ def make_csv_line(objects: Sequence) -> str:
37
+ """
38
+ pre: len(objects) > 0
39
+ pre: all(',' not in str(o) for o in objects)
40
+ post: __return__.split(',') == list(map(str, objects))
41
+ """
42
+ return ",".join(map(str, objects))
43
+
44
+
45
+ def csv_first_column(lines: List[str]) -> List[str]:
46
+ """
47
+ pre: all(',' in line for line in lines)
48
+ post: __return__ == [line.split(',')[0] for line in lines]
49
+ """
50
+ return [line[: line.index(",")] for line in lines]
51
+
52
+
53
+ def zip_exact(a: Iterable[T], b: Iterable[U]) -> List[Tuple[T, U]]:
54
+ """
55
+ pre: len(a) == len(b)
56
+ post: len(__return__) == len(a) == len(b)
57
+ """
58
+ return list(zip(a, b))
59
+
60
+
61
+ def zipped_pairs(x: List[T]) -> List[Tuple[T, T]]:
62
+ """
63
+ post: len(__return__) == max(0, len(x) - 1)
64
+ """
65
+ return zip_exact(x[:-1], x[1:])
66
+
67
+
68
+ def even_fibb(n: int) -> List[int]:
69
+ """
70
+ Return a list of the first N even fibbonacci numbers.
71
+
72
+ >>> even_fibb(2)
73
+ [2, 8]
74
+
75
+ pre: n >= 0
76
+ post: len(__return__) == n
77
+ """
78
+ prev = 1
79
+ cur = 1
80
+ result = []
81
+ while n > 0:
82
+ prev, cur = cur, prev + cur
83
+ if cur % 2 == 0:
84
+ result.append(cur)
85
+ n -= 1
86
+ return result
87
+
88
+
89
+ def remove_outliers(numbers: List[float], num_deviations: float = 3):
90
+ """
91
+ >>> remove_outliers([0, 1, 2, 3, 4, 5, 50, 6, 7, 8, 9], num_deviations=1)
92
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
93
+
94
+ post: len(_) <= len(numbers)
95
+ post: not numbers or max(_) <= max(numbers)
96
+ post: not numbers or min(_) >= min(numbers)
97
+ post: all(x in numbers for x in _)
98
+ """
99
+ if len(numbers) < 2:
100
+ return numbers
101
+ avg = statistics.mean(numbers)
102
+ allowed_range = statistics.stdev(numbers) * num_deviations
103
+ min_val, max_val = avg - allowed_range, avg + allowed_range
104
+ return [num for num in numbers if min_val <= num <= max_val]
File without changes
@@ -0,0 +1,146 @@
1
+ """Run functional tests of the tool on all the examples."""
2
+
3
+ import argparse
4
+ import os
5
+ import pathlib
6
+ import re
7
+ import shlex
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Iterable, List
12
+
13
+ import pytest
14
+
15
+
16
+ def extract_linenums(text: str) -> List[int]:
17
+ r"""
18
+ Pull ordered line numbers out of crosshair output.
19
+
20
+ >>> extract_linenums("foo:34:bar\nfoo:64:bar\n")
21
+ [34, 64]
22
+ """
23
+ return list(map(int, re.compile(r":(\d+)\:").findall(text)))
24
+
25
+
26
+ def find_examples() -> Iterable[Path]:
27
+ examples_dir = pathlib.Path(os.path.realpath(__file__)).parent
28
+ for path in sorted(examples_dir.glob("*/**/*.py")):
29
+ if path.stem != "__init__":
30
+ yield path
31
+
32
+
33
+ def main(argv: List[str]) -> int:
34
+ """Execute the main routine."""
35
+ parser = argparse.ArgumentParser(description=__doc__)
36
+ parser.add_argument(
37
+ "--overwrite", help="If set, overwrite the golden files", action="store_true"
38
+ )
39
+ args = parser.parse_args(argv)
40
+
41
+ overwrite = bool(args.overwrite)
42
+
43
+ success = True
44
+
45
+ for pth in find_examples():
46
+ success &= run_on_file(pth, overwrite)
47
+
48
+ if not success:
49
+ print("One or more functional tests failed. Please see above.")
50
+ return 1
51
+
52
+ print("The functional tests passed.")
53
+ return 0
54
+
55
+
56
+ def run_on_file(pth: Path, overwrite: bool) -> bool:
57
+ cmd = [
58
+ sys.executable,
59
+ "-m",
60
+ "crosshair",
61
+ "check",
62
+ str(pth),
63
+ ]
64
+
65
+ cmd_as_string = " ".join(shlex.quote(part) for part in cmd)
66
+
67
+ print(f"Running: {cmd_as_string}")
68
+
69
+ proc = subprocess.Popen(
70
+ cmd,
71
+ stdin=subprocess.DEVNULL,
72
+ stdout=subprocess.PIPE,
73
+ stderr=subprocess.PIPE,
74
+ encoding="utf-8",
75
+ )
76
+
77
+ stdout, stderr = proc.communicate()
78
+
79
+ assert isinstance(stdout, str)
80
+ assert isinstance(stderr, str)
81
+
82
+ # We see empty output if, and only if, the process succeeds:
83
+ if (proc.returncode == 0) != (stdout == "" and stderr == ""):
84
+ print(
85
+ f"The return code does not correspond to the output.\n\n"
86
+ f"The command was:\n"
87
+ f"{cmd_as_string}\n\n"
88
+ f"The return code was: {proc.returncode}\n"
89
+ f"The captured stdout was:\n"
90
+ f"{stdout}\n\n"
91
+ f"The captured stderr:\n"
92
+ f"{stderr}\n\n"
93
+ )
94
+ return False
95
+
96
+ expected_stdout_pth = pth.parent / (pth.stem + ".out")
97
+
98
+ ##
99
+ # Replace the absolute path to the examples directory
100
+ # with a place holder to make these tests machine agnostic.
101
+ ##
102
+
103
+ path_re = re.compile(r"^.*[/\\]([_\w]+\.py):", re.MULTILINE)
104
+ stdout, _ = path_re.subn(r"\1:", stdout)
105
+
106
+ if overwrite:
107
+ if expected_stdout_pth.exists():
108
+ expected_stdout_pth.unlink()
109
+ if stdout:
110
+ expected_stdout_pth.write_text(stdout)
111
+ else:
112
+ if expected_stdout_pth.exists():
113
+ expected_stdout = expected_stdout_pth.read_text()
114
+ else:
115
+ expected_stdout = ""
116
+
117
+ # We only check line numbers, as error messages aren't stable.
118
+ if extract_linenums(expected_stdout) != extract_linenums(stdout):
119
+ print(
120
+ f"The output was different than expected.\n\n"
121
+ f"The command was:\n"
122
+ f"{cmd_as_string}\n\n"
123
+ f"The captured stdout was:\n"
124
+ f"{stdout}\n\n"
125
+ f"The expected stdout:\n"
126
+ f"{expected_stdout}\n\n"
127
+ )
128
+ if stderr:
129
+ print(f"The captured stderr was:\n" f"{stderr}\n\n")
130
+ return False
131
+ return True
132
+
133
+
134
+ @pytest.mark.skipif(
135
+ sys.version_info < (3, 8),
136
+ reason="only test 3rd party libs under new python versions",
137
+ )
138
+ @pytest.mark.parametrize("path", find_examples(), ids=lambda p: "_".join(p.parts[-3:]))
139
+ def test_examples(path: Path):
140
+ # TODO: "unable to meet precondition" and non-deterministic problems aren't
141
+ # surfaced. Reconsider.
142
+ assert run_on_file(path, overwrite=False)
143
+
144
+
145
+ if __name__ == "__main__":
146
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1 @@
1
+ # crosshair: analysis_kind=deal
@@ -0,0 +1 @@
1
+ # crosshair: analysis_kind=icontract
File without changes
@@ -0,0 +1,41 @@
1
+ from typing import Callable, Dict, List, Sequence, Tuple, TypeVar
2
+
3
+ from icontract import ensure, require, snapshot
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ @require(lambda x: x > 0)
9
+ @ensure(lambda result: result > 0)
10
+ def some_func(x: int) -> int:
11
+ # Bug when the constant makes the result negative.
12
+ return x - 1000
13
+
14
+
15
+ @ensure(lambda s, result: len(result) == len(s))
16
+ def list_to_dict(s: Sequence[T]) -> Dict[T, T]:
17
+ # CrossHair finds a counterexample with duplicate values in the input.
18
+ return dict(zip(s, s))
19
+
20
+
21
+ @ensure(lambda x, result: len(result) == len(x) - 1)
22
+ def consecutive_pairs(x: List[T]) -> List[Tuple[T, T]]:
23
+ # Bug on an empty input list
24
+ return [(x[i], x[i + 1]) for i in range(len(x) - 1)]
25
+
26
+
27
+ @ensure(lambda result: result != 42)
28
+ def higher_order(fn: Callable[[int], int]) -> int:
29
+ # Crosshair can find models for pure callables over atomic types.
30
+ # Bug when given something like lambda a: 42 if (a == 0) else 0.
31
+ return fn(fn(100))
32
+
33
+
34
+ @snapshot(lambda lists: lists[:])
35
+ @ensure(
36
+ lambda lists, OLD: all(len(x) == len(OLD.lists[i]) + 1 for i, x in enumerate(lists))
37
+ )
38
+ def append_fourtytwo_to_each(lists: List[List[int]]):
39
+ # Bug when two elements of the input are the SAME list!
40
+ for ls in lists:
41
+ ls.append(42)
@@ -0,0 +1,8 @@
1
+ import icontract
2
+
3
+
4
+ @icontract.require(lambda x: x > 0)
5
+ @icontract.ensure(lambda result: result > 0)
6
+ def some_func(x: int) -> int:
7
+ # The constant makes the result negative.
8
+ return x - 1000
File without changes
@@ -0,0 +1,51 @@
1
+ from typing import List, Optional, Tuple
2
+
3
+ import icontract
4
+
5
+
6
+ @icontract.require(lambda ln, w: ln > 0 and w > 0)
7
+ @icontract.ensure(
8
+ lambda ln, w, result: result > ln and result > w,
9
+ "The perimeter of a rectangle is longer than any single side.",
10
+ )
11
+ def perimiter_length(length: int, width: int) -> int:
12
+ return 2 * length + 2 * width
13
+
14
+
15
+ @icontract.ensure(lambda things, result: result[0] == things[1])
16
+ @icontract.ensure(lambda things, result: result[1] == things[0])
17
+ def swap(things: Tuple[int, int]) -> Tuple[int, int]:
18
+ """
19
+ Swap the arguments.
20
+ """
21
+ return (things[1], things[0])
22
+
23
+
24
+ @icontract.ensure(lambda items, result: len(result) == len(items) * 2)
25
+ def double(items: List[str]) -> List[str]:
26
+ """
27
+ Return a new list that is the input list, repeated twice.
28
+ """
29
+ return items + items
30
+
31
+
32
+ # NOTE: This is an example of contracts on recursive functions.
33
+
34
+
35
+ @icontract.require(lambda numbers: len(numbers) > 0)
36
+ @icontract.ensure(
37
+ lambda numbers, result: result[0] == min(numbers),
38
+ "The left return value is always the smallest.",
39
+ )
40
+ def smallest_two(numbers: Tuple[int, ...]) -> Tuple[Optional[int], Optional[int]]:
41
+ """Find the two smallest numbers."""
42
+ if len(numbers) == 1:
43
+ return (numbers[0], None)
44
+ (smallest, second) = smallest_two(numbers[1:])
45
+ n = numbers[0]
46
+ if smallest is None or n < smallest:
47
+ return (n, smallest)
48
+ elif second is None or n < second:
49
+ return (smallest, n)
50
+ else:
51
+ return (smallest, second)