crosshair-tool 0.0.99__cp312-cp312-macosx_10_13_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _crosshair_tracers.cpython-312-darwin.so +0 -0
- crosshair/__init__.py +42 -0
- crosshair/__main__.py +8 -0
- crosshair/_mark_stacks.h +790 -0
- crosshair/_preliminaries_test.py +18 -0
- crosshair/_tracers.h +94 -0
- crosshair/_tracers_pycompat.h +522 -0
- crosshair/_tracers_test.py +138 -0
- crosshair/abcstring.py +245 -0
- crosshair/auditwall.py +190 -0
- crosshair/auditwall_test.py +77 -0
- crosshair/codeconfig.py +113 -0
- crosshair/codeconfig_test.py +117 -0
- crosshair/condition_parser.py +1237 -0
- crosshair/condition_parser_test.py +497 -0
- crosshair/conftest.py +30 -0
- crosshair/copyext.py +155 -0
- crosshair/copyext_test.py +84 -0
- crosshair/core.py +1763 -0
- crosshair/core_and_libs.py +149 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +1316 -0
- crosshair/diff_behavior.py +314 -0
- crosshair/diff_behavior_test.py +261 -0
- crosshair/dynamic_typing.py +346 -0
- crosshair/dynamic_typing_test.py +210 -0
- crosshair/enforce.py +282 -0
- crosshair/enforce_test.py +182 -0
- crosshair/examples/PEP316/__init__.py +1 -0
- crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
- crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
- crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
- crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
- crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
- crosshair/examples/PEP316/correct_code/__init__.py +0 -0
- crosshair/examples/PEP316/correct_code/arith.py +60 -0
- crosshair/examples/PEP316/correct_code/chess.py +77 -0
- crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
- crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
- crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
- crosshair/examples/PEP316/correct_code/showcase.py +104 -0
- crosshair/examples/__init__.py +0 -0
- crosshair/examples/check_examples_test.py +146 -0
- crosshair/examples/deal/__init__.py +1 -0
- crosshair/examples/icontract/__init__.py +1 -0
- crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
- crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
- crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
- crosshair/examples/icontract/correct_code/__init__.py +0 -0
- crosshair/examples/icontract/correct_code/arith.py +51 -0
- crosshair/examples/icontract/correct_code/showcase.py +94 -0
- crosshair/fnutil.py +391 -0
- crosshair/fnutil_test.py +75 -0
- crosshair/fuzz_core_test.py +516 -0
- crosshair/libimpl/__init__.py +0 -0
- crosshair/libimpl/arraylib.py +161 -0
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +23 -0
- crosshair/libimpl/builtinslib.py +5228 -0
- crosshair/libimpl/builtinslib_ch_test.py +1191 -0
- crosshair/libimpl/builtinslib_test.py +3735 -0
- crosshair/libimpl/codecslib.py +86 -0
- crosshair/libimpl/codecslib_test.py +86 -0
- crosshair/libimpl/collectionslib.py +264 -0
- crosshair/libimpl/collectionslib_ch_test.py +252 -0
- crosshair/libimpl/collectionslib_test.py +332 -0
- crosshair/libimpl/copylib.py +23 -0
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +2559 -0
- crosshair/libimpl/datetimelib_ch_test.py +354 -0
- crosshair/libimpl/datetimelib_test.py +112 -0
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/__init__.py +23 -0
- crosshair/libimpl/encodings/_encutil.py +187 -0
- crosshair/libimpl/encodings/ascii.py +44 -0
- crosshair/libimpl/encodings/latin_1.py +40 -0
- crosshair/libimpl/encodings/utf_8.py +93 -0
- crosshair/libimpl/encodings_ch_test.py +83 -0
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +34 -0
- crosshair/libimpl/functoolslib_test.py +56 -0
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +47 -0
- crosshair/libimpl/heapqlib_test.py +21 -0
- crosshair/libimpl/importliblib.py +18 -0
- crosshair/libimpl/importliblib_test.py +38 -0
- crosshair/libimpl/iolib.py +216 -0
- crosshair/libimpl/iolib_ch_test.py +128 -0
- crosshair/libimpl/iolib_test.py +19 -0
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib.py +44 -0
- crosshair/libimpl/itertoolslib_test.py +44 -0
- crosshair/libimpl/jsonlib.py +984 -0
- crosshair/libimpl/jsonlib_ch_test.py +42 -0
- crosshair/libimpl/jsonlib_test.py +51 -0
- crosshair/libimpl/mathlib.py +179 -0
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +67 -0
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +178 -0
- crosshair/libimpl/randomlib_test.py +120 -0
- crosshair/libimpl/relib.py +846 -0
- crosshair/libimpl/relib_ch_test.py +169 -0
- crosshair/libimpl/relib_test.py +493 -0
- crosshair/libimpl/timelib.py +72 -0
- crosshair/libimpl/timelib_test.py +82 -0
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib.py +75 -0
- crosshair/libimpl/unicodedatalib_test.py +42 -0
- crosshair/libimpl/urlliblib.py +23 -0
- crosshair/libimpl/urlliblib_test.py +19 -0
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +261 -0
- crosshair/lsp_server_test.py +30 -0
- crosshair/main.py +973 -0
- crosshair/main_test.py +543 -0
- crosshair/objectproxy.py +376 -0
- crosshair/objectproxy_test.py +41 -0
- crosshair/opcode_intercept.py +601 -0
- crosshair/opcode_intercept_test.py +304 -0
- crosshair/options.py +218 -0
- crosshair/options_test.py +10 -0
- crosshair/patch_equivalence_test.py +75 -0
- crosshair/path_cover.py +209 -0
- crosshair/path_cover_test.py +138 -0
- crosshair/path_search.py +161 -0
- crosshair/path_search_test.py +52 -0
- crosshair/pathing_oracle.py +271 -0
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer.py +27 -0
- crosshair/pure_importer_test.py +16 -0
- crosshair/py.typed +0 -0
- crosshair/register_contract.py +273 -0
- crosshair/register_contract_test.py +190 -0
- crosshair/simplestructs.py +1165 -0
- crosshair/simplestructs_test.py +283 -0
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +1199 -0
- crosshair/statespace_test.py +108 -0
- crosshair/stubs_parser.py +352 -0
- crosshair/stubs_parser_test.py +43 -0
- crosshair/test_util.py +329 -0
- crosshair/test_util_test.py +26 -0
- crosshair/tools/__init__.py +0 -0
- crosshair/tools/check_help_in_doc.py +264 -0
- crosshair/tools/check_init_and_setup_coincide.py +119 -0
- crosshair/tools/generate_demo_table.py +127 -0
- crosshair/tracers.py +544 -0
- crosshair/tracers_test.py +154 -0
- crosshair/type_repo.py +151 -0
- crosshair/unicode_categories.py +589 -0
- crosshair/unicode_categories_test.py +27 -0
- crosshair/util.py +741 -0
- crosshair/util_test.py +173 -0
- crosshair/watcher.py +307 -0
- crosshair/watcher_test.py +107 -0
- crosshair/z3util.py +76 -0
- crosshair/z3util_test.py +11 -0
- crosshair_tool-0.0.99.dist-info/METADATA +144 -0
- crosshair_tool-0.0.99.dist-info/RECORD +176 -0
- crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
- crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
- crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
- crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
|
@@ -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)
|
|
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)
|