crosshair-tool 0.0.56__cp39-cp39-macosx_11_0_arm64.whl → 0.0.100__cp39-cp39-macosx_11_0_arm64.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 (123) hide show
  1. _crosshair_tracers.cpython-39-darwin.so +0 -0
  2. crosshair/__init__.py +1 -1
  3. crosshair/_mark_stacks.h +51 -24
  4. crosshair/_tracers.h +9 -5
  5. crosshair/_tracers_test.py +19 -9
  6. crosshair/auditwall.py +9 -8
  7. crosshair/auditwall_test.py +31 -19
  8. crosshair/codeconfig.py +3 -2
  9. crosshair/condition_parser.py +17 -133
  10. crosshair/condition_parser_test.py +54 -96
  11. crosshair/conftest.py +1 -1
  12. crosshair/copyext.py +91 -22
  13. crosshair/copyext_test.py +33 -0
  14. crosshair/core.py +259 -203
  15. crosshair/core_and_libs.py +20 -0
  16. crosshair/core_regestered_types_test.py +82 -0
  17. crosshair/core_test.py +693 -664
  18. crosshair/diff_behavior.py +76 -21
  19. crosshair/diff_behavior_test.py +132 -23
  20. crosshair/dynamic_typing.py +128 -18
  21. crosshair/dynamic_typing_test.py +91 -4
  22. crosshair/enforce.py +1 -6
  23. crosshair/enforce_test.py +15 -23
  24. crosshair/examples/check_examples_test.py +2 -1
  25. crosshair/fnutil.py +2 -3
  26. crosshair/fnutil_test.py +0 -7
  27. crosshair/fuzz_core_test.py +70 -83
  28. crosshair/libimpl/arraylib.py +10 -7
  29. crosshair/libimpl/binascii_ch_test.py +30 -0
  30. crosshair/libimpl/binascii_test.py +67 -0
  31. crosshair/libimpl/binasciilib.py +150 -0
  32. crosshair/libimpl/bisectlib_test.py +5 -5
  33. crosshair/libimpl/builtinslib.py +1002 -682
  34. crosshair/libimpl/builtinslib_ch_test.py +108 -30
  35. crosshair/libimpl/builtinslib_test.py +431 -143
  36. crosshair/libimpl/codecslib.py +22 -2
  37. crosshair/libimpl/codecslib_test.py +41 -9
  38. crosshair/libimpl/collectionslib.py +44 -8
  39. crosshair/libimpl/collectionslib_test.py +108 -20
  40. crosshair/libimpl/copylib.py +1 -1
  41. crosshair/libimpl/copylib_test.py +18 -0
  42. crosshair/libimpl/datetimelib.py +84 -67
  43. crosshair/libimpl/datetimelib_ch_test.py +12 -7
  44. crosshair/libimpl/datetimelib_test.py +5 -6
  45. crosshair/libimpl/decimallib.py +5257 -0
  46. crosshair/libimpl/decimallib_ch_test.py +78 -0
  47. crosshair/libimpl/decimallib_test.py +76 -0
  48. crosshair/libimpl/encodings/_encutil.py +21 -11
  49. crosshair/libimpl/fractionlib.py +16 -0
  50. crosshair/libimpl/fractionlib_test.py +80 -0
  51. crosshair/libimpl/functoolslib.py +19 -7
  52. crosshair/libimpl/functoolslib_test.py +22 -6
  53. crosshair/libimpl/hashliblib.py +30 -0
  54. crosshair/libimpl/hashliblib_test.py +18 -0
  55. crosshair/libimpl/heapqlib.py +32 -5
  56. crosshair/libimpl/heapqlib_test.py +15 -12
  57. crosshair/libimpl/iolib.py +7 -4
  58. crosshair/libimpl/ipaddresslib.py +8 -0
  59. crosshair/libimpl/itertoolslib_test.py +1 -1
  60. crosshair/libimpl/mathlib.py +165 -2
  61. crosshair/libimpl/mathlib_ch_test.py +44 -0
  62. crosshair/libimpl/mathlib_test.py +59 -16
  63. crosshair/libimpl/oslib.py +7 -0
  64. crosshair/libimpl/pathliblib_test.py +10 -0
  65. crosshair/libimpl/randomlib.py +1 -0
  66. crosshair/libimpl/randomlib_test.py +6 -4
  67. crosshair/libimpl/relib.py +180 -59
  68. crosshair/libimpl/relib_ch_test.py +26 -2
  69. crosshair/libimpl/relib_test.py +77 -14
  70. crosshair/libimpl/timelib.py +35 -13
  71. crosshair/libimpl/timelib_test.py +13 -3
  72. crosshair/libimpl/typeslib.py +15 -0
  73. crosshair/libimpl/typeslib_test.py +36 -0
  74. crosshair/libimpl/unicodedatalib_test.py +3 -3
  75. crosshair/libimpl/weakreflib.py +13 -0
  76. crosshair/libimpl/weakreflib_test.py +69 -0
  77. crosshair/libimpl/zliblib.py +15 -0
  78. crosshair/libimpl/zliblib_test.py +13 -0
  79. crosshair/lsp_server.py +21 -10
  80. crosshair/main.py +48 -28
  81. crosshair/main_test.py +59 -14
  82. crosshair/objectproxy.py +39 -14
  83. crosshair/objectproxy_test.py +27 -13
  84. crosshair/opcode_intercept.py +212 -24
  85. crosshair/opcode_intercept_test.py +172 -18
  86. crosshair/options.py +0 -1
  87. crosshair/patch_equivalence_test.py +5 -21
  88. crosshair/path_cover.py +7 -5
  89. crosshair/path_search.py +6 -4
  90. crosshair/path_search_test.py +1 -2
  91. crosshair/pathing_oracle.py +53 -10
  92. crosshair/pathing_oracle_test.py +21 -0
  93. crosshair/pure_importer_test.py +5 -21
  94. crosshair/register_contract.py +16 -6
  95. crosshair/register_contract_test.py +2 -14
  96. crosshair/simplestructs.py +154 -85
  97. crosshair/simplestructs_test.py +16 -2
  98. crosshair/smtlib.py +24 -0
  99. crosshair/smtlib_test.py +14 -0
  100. crosshair/statespace.py +319 -196
  101. crosshair/statespace_test.py +45 -0
  102. crosshair/stubs_parser.py +0 -2
  103. crosshair/test_util.py +87 -25
  104. crosshair/test_util_test.py +26 -0
  105. crosshair/tools/check_init_and_setup_coincide.py +0 -3
  106. crosshair/tools/generate_demo_table.py +2 -2
  107. crosshair/tracers.py +141 -49
  108. crosshair/type_repo.py +11 -4
  109. crosshair/unicode_categories.py +1 -0
  110. crosshair/util.py +158 -76
  111. crosshair/util_test.py +13 -20
  112. crosshair/watcher.py +4 -4
  113. crosshair/z3util.py +1 -1
  114. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
  115. crosshair_tool-0.0.100.dist-info/RECORD +176 -0
  116. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
  117. crosshair/examples/hypothesis/__init__.py +0 -2
  118. crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
  119. crosshair_tool-0.0.56.dist-info/RECORD +0 -152
  120. /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
  121. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
  122. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
  123. {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,28 @@
1
1
  import codecs
2
2
 
3
3
  from crosshair import NoTracing, register_patch
4
- from crosshair.core import realize
4
+ from crosshair.core import class_with_realized_methods, realize, with_realized_args
5
5
  from crosshair.libimpl.encodings import codec_search
6
+ from crosshair.util import is_iterable
7
+
8
+
9
+ class RealizingCodecInfo(codecs.CodecInfo):
10
+ def __new__(cls, c: codecs.CodecInfo):
11
+ encode = with_realized_args(c.encode)
12
+ decode = with_realized_args(c.decode)
13
+ streamreader = class_with_realized_methods(c.streamreader)
14
+ streamwriter = class_with_realized_methods(c.streamwriter)
15
+ incrementaldecoder = class_with_realized_methods(c.incrementaldecoder)
16
+ incrementalencoder = class_with_realized_methods(c.incrementalencoder)
17
+ return codecs.CodecInfo(
18
+ encode,
19
+ decode,
20
+ streamreader=streamreader,
21
+ streamwriter=streamwriter,
22
+ incrementalencoder=incrementalencoder,
23
+ incrementaldecoder=incrementaldecoder,
24
+ name=c.name,
25
+ )
6
26
 
7
27
 
8
28
  def _decode(obj, encoding="utf-8", errors="strict"):
@@ -50,7 +70,7 @@ def _lookup(encoding: str) -> codecs.CodecInfo:
50
70
  try:
51
71
  return codecs.lookup("crosshair_" + encoding)
52
72
  except LookupError:
53
- return codecs.lookup(encoding)
73
+ return RealizingCodecInfo(codecs.lookup(encoding))
54
74
 
55
75
 
56
76
  def make_registrations() -> None:
@@ -1,8 +1,11 @@
1
1
  import codecs
2
+ import io
3
+ import sys
2
4
 
3
5
  import pytest
4
6
 
5
- from crosshair.core_and_libs import ResumedTracing
7
+ from crosshair.core import proxy_for_type
8
+ from crosshair.core_and_libs import ResumedTracing, standalone_statespace
6
9
  from crosshair.libimpl.builtinslib import LazyIntSymbolicStr, SymbolicBytes, SymbolicInt
7
10
  from crosshair.options import AnalysisOptionSet
8
11
  from crosshair.statespace import POST_FAIL, MessageType
@@ -22,27 +25,56 @@ def test_encode_utf8_literal(space):
22
25
 
23
26
  def test_encode_utf8_symbolic_char(space):
24
27
  cp = SymbolicInt("cp")
25
- space.add(cp.var >= ord("a"))
26
- space.add(cp.var <= ord("z"))
27
28
  with ResumedTracing():
29
+ space.add(cp >= ord("a"))
30
+ space.add(cp <= ord("z"))
28
31
  encoded = codecs.encode(chr(cp), "utf-8")
29
32
  assert isinstance(encoded, SymbolicBytes)
30
33
  byte_value = encoded[0]
31
- assert space.is_possible(byte_value.var == ord("a"))
32
- assert space.is_possible(byte_value.var == ord("b"))
34
+ with ResumedTracing():
35
+ assert space.is_possible(byte_value == ord("a"))
36
+ assert space.is_possible(byte_value == ord("b"))
33
37
 
34
38
 
35
39
  def test_decode_utf8_symbolic_char(space):
36
40
  cp = SymbolicInt("cp")
37
- space.add(cp.var >= ord("a"))
38
- space.add(cp.var <= ord("z"))
39
41
  with ResumedTracing():
42
+ space.add(cp >= ord("a"))
43
+ space.add(cp <= ord("z"))
40
44
  decoded = codecs.decode(SymbolicBytes([cp]), "utf-8")
41
45
  assert isinstance(decoded, LazyIntSymbolicStr)
42
- assert space.is_possible(decoded._codepoints[0].var == ord("a"))
43
- assert space.is_possible(decoded._codepoints[0].var == ord("b"))
46
+ with ResumedTracing():
47
+ assert space.is_possible(decoded._codepoints[0] == ord("a"))
48
+ assert space.is_possible(decoded._codepoints[0] == ord("b"))
49
+
50
+
51
+ def test_unsupported_codec_encode(space):
52
+ s = proxy_for_type(str, "s")
53
+ with ResumedTracing():
54
+ s.encode("cp858")
55
+
56
+
57
+ def test_unsupported_codec_streamwriter(space):
58
+ s = proxy_for_type(str, "s")
59
+ buf = bytearray()
60
+ with ResumedTracing():
61
+ codecs.getwriter("cp858")(io.BytesIO(buf)).write(s)
62
+
63
+
64
+ @pytest.mark.xfail(reason="not yet implemented")
65
+ def test_supported_codec_streamwriter(space):
66
+ s = proxy_for_type(str, "s")
67
+ with ResumedTracing():
68
+ space.add(len(s) == 1)
69
+ buf = bytearray()
70
+ codecs.getwriter("ascii")(io.BytesIO(buf)).write(s)
71
+ space.is_possible(buf[0] == ord("x"))
44
72
 
45
73
 
74
+ @pytest.mark.skipif(
75
+ sys.version_info >= (3, 13),
76
+ reason="Need to intercept UnicodeDecodeError.str in 3.13+",
77
+ )
46
78
  def test_decode_e2e():
47
79
  def f(byts: bytes) -> str:
48
80
  """
@@ -1,21 +1,25 @@
1
1
  import collections
2
+ import sys
2
3
  from typing import (
3
4
  Any,
4
5
  Callable,
5
6
  Dict,
6
7
  Generic,
7
8
  Iterable,
9
+ KeysView,
8
10
  List,
9
11
  Optional,
10
12
  Set,
11
13
  Tuple,
12
14
  TypeVar,
15
+ Union,
16
+ ValuesView,
13
17
  )
14
18
 
15
19
  from crosshair import register_type
16
- from crosshair.core import CrossHairValue, realize
20
+ from crosshair.core import realize
17
21
  from crosshair.tracers import NoTracing, ResumedTracing
18
- from crosshair.util import is_iterable
22
+ from crosshair.util import CrossHairValue, is_iterable
19
23
 
20
24
  T = TypeVar("T")
21
25
 
@@ -122,8 +126,19 @@ class ListBasedDeque(collections.abc.MutableSequence, CrossHairValue, Generic[T]
122
126
  prefix.reverse()
123
127
  self._contents = prefix + self._contents
124
128
 
125
- def index(self, item: T, *bounds) -> int:
126
- return self._contents.index(item, *bounds)
129
+ if sys.version_info >= (3, 14):
130
+
131
+ def index(self, item: T, *bounds) -> int:
132
+ try:
133
+ return self._contents.index(item, *bounds)
134
+ except ValueError as exc:
135
+ exc.args = ("deque.index(x): x not in deque",)
136
+ raise
137
+
138
+ else:
139
+
140
+ def index(self, item: T, *bounds) -> int:
141
+ return self._contents.index(item, *bounds)
127
142
 
128
143
  def insert(self, index: int, item: T) -> None:
129
144
  self._contents.insert(index, item)
@@ -203,11 +218,29 @@ def make_registrations():
203
218
  register_type(collections.abc.Mapping, lambda p, kt=Any, vt=Any: p(Dict[kt, vt])) # type: ignore
204
219
  register_type(collections.abc.MutableMapping, lambda p, kt=Any, vt=Any: p(Dict[kt, vt])) # type: ignore
205
220
  register_type(collections.OrderedDict, lambda p, kt=Any, vt=Any: collections.OrderedDict(p(Dict[kt, vt]))) # type: ignore
221
+ # TODO: This implementation of Counter probably over-realizes the symbolic map it is given:
206
222
  register_type(collections.Counter, lambda p, t=Any: collections.Counter(p(Dict[t, int]))) # type: ignore
207
223
  # TODO: MappingView is missing
208
- register_type(collections.abc.ItemsView, lambda p, kt=Any, vt=Any: p(Set[Tuple[kt, vt]])) # type: ignore
209
- register_type(collections.abc.KeysView, lambda p, t=Any: p(Set[t])) # type: ignore
210
- register_type(collections.abc.ValuesView, lambda p, t=Any: p(List[t])) # type: ignore
224
+ register_type(
225
+ collections.abc.ItemsView,
226
+ lambda p, k=Any, v=Any: p(Dict.__getitem__((k, v))).items(),
227
+ )
228
+ register_type(
229
+ collections.abc.MappingView,
230
+ lambda p, t=Any: p(
231
+ Union[
232
+ KeysView.__getitem__((t,)),
233
+ ValuesView.__getitem__((t,)),
234
+ ]
235
+ ),
236
+ )
237
+ register_type(
238
+ collections.abc.KeysView, lambda p, t=Any: p(Dict.__getitem__((t, Any))).keys()
239
+ )
240
+ register_type(
241
+ collections.abc.ValuesView,
242
+ lambda p, t=Any: p(Dict.__getitem__((Any, t))).values(),
243
+ )
211
244
 
212
245
  register_type(collections.abc.Container, lambda p, t=Any: p(Tuple[t, ...]))
213
246
  register_type(collections.abc.Collection, lambda p, t=Any: p(Tuple[t, ...]))
@@ -224,5 +257,8 @@ def make_registrations():
224
257
 
225
258
  register_type(collections.abc.MutableSet, lambda p, t=Any: p(Set[t])) # type: ignore
226
259
 
227
- register_type(collections.abc.ByteString, lambda p: p(bytes))
260
+ if sys.version_info < (3, 14):
261
+ register_type(collections.abc.ByteString, lambda p: p(bytes))
262
+ if sys.version_info >= (3, 12):
263
+ register_type(collections.abc.Buffer, lambda p: p(bytes))
228
264
  register_type(collections.abc.Hashable, lambda p: p(int))
@@ -1,14 +1,24 @@
1
- import collections
2
- from typing import DefaultDict, Deque, Tuple
1
+ import re
2
+ import sys
3
+ from collections import Counter, defaultdict, deque, namedtuple
4
+ from copy import deepcopy
5
+ from inspect import Parameter, Signature
6
+ from typing import Callable, Counter, DefaultDict, Deque, Dict, NamedTuple, Tuple
3
7
 
4
8
  import pytest
5
9
 
6
- from crosshair.core import proxy_for_type, realize, standalone_statespace
7
- from crosshair.libimpl.collectionslib import ListBasedDeque
10
+ from crosshair.core import (
11
+ deep_realize,
12
+ get_constructor_signature,
13
+ proxy_for_type,
14
+ realize,
15
+ standalone_statespace,
16
+ )
17
+ from crosshair.libimpl.collectionslib import ListBasedDeque, PureDefaultDict
8
18
  from crosshair.statespace import CANNOT_CONFIRM, CONFIRMED, POST_FAIL, MessageType
9
19
  from crosshair.test_util import check_states
10
- from crosshair.tracers import NoTracing
11
- from crosshair.util import set_debug
20
+ from crosshair.tracers import NoTracing, ResumedTracing
21
+ from crosshair.util import CrossHairValue
12
22
 
13
23
 
14
24
  @pytest.fixture
@@ -16,6 +26,20 @@ def test_list():
16
26
  return ListBasedDeque([1, 2, 3, 4, 5])
17
27
 
18
28
 
29
+ def test_counter_symbolic_deep(space):
30
+ d = proxy_for_type(Counter[int], "d")
31
+ with ResumedTracing():
32
+ deep_realize(d)
33
+ deepcopy(d)
34
+
35
+
36
+ def test_counter_deep(space):
37
+ d = Counter()
38
+ with ResumedTracing():
39
+ deep_realize(d)
40
+ deepcopy(d)
41
+
42
+
19
43
  def test_deque_appendleft(test_list) -> None:
20
44
  test_list.appendleft(0)
21
45
  assert test_list.popleft() == 0
@@ -75,7 +99,11 @@ def test_deque_index_with_start_index_throws_correct_exception(test_list) -> Non
75
99
  with pytest.raises(ValueError) as context:
76
100
  test_list.index(1, 2)
77
101
 
78
- assert context.match("1 is not in list")
102
+ if sys.version_info >= (3, 14):
103
+ # assert context.match(re.escape("list.index(x): x not in list"))
104
+ assert context.match(re.escape("deque.index(x): x not in deque"))
105
+ else:
106
+ assert context.match("1 is not in list")
79
107
 
80
108
 
81
109
  def test_deque_index_with_start_and_end_index(test_list) -> None:
@@ -89,7 +117,10 @@ def test_deque_index_with_start_and_end_index_throws_correct_exception(
89
117
  with pytest.raises(ValueError) as context:
90
118
  test_list.index(6, 0, 1)
91
119
 
92
- assert context.match("6 is not in list")
120
+ if sys.version_info >= (3, 14):
121
+ assert context.match(re.escape("deque.index(x): x not in deque"))
122
+ else:
123
+ assert context.match("6 is not in list")
93
124
 
94
125
 
95
126
  def test_deque_insert(test_list) -> None:
@@ -162,7 +193,7 @@ def test_deque_extendleft_method() -> None:
162
193
  """
163
194
  Can any deque be extended by itself and form this palindrome?
164
195
 
165
- post[ls]: ls != collections.deque([1, 2, 3, 3, 2, 1])
196
+ post[ls]: ls != deque([1, 2, 3, 3, 2, 1])
166
197
  """
167
198
  ls.extendleft(ls)
168
199
 
@@ -171,28 +202,28 @@ def test_deque_extendleft_method() -> None:
171
202
 
172
203
  def test_deque_add_symbolic_to_concrete():
173
204
  with standalone_statespace as space:
174
- d = ListBasedDeque([1, 2]) + collections.deque([3, 4])
205
+ d = ListBasedDeque([1, 2]) + deque([3, 4])
175
206
  assert list(d) == [1, 2, 3, 4]
176
207
 
177
208
 
178
209
  def test_deque_eq():
179
210
  with standalone_statespace as space:
180
211
  assert ListBasedDeque([1, 2]) == ListBasedDeque([1, 2])
181
- assert collections.deque([1, 2]) == ListBasedDeque([1, 2])
212
+ assert deque([1, 2]) == ListBasedDeque([1, 2])
182
213
  assert ListBasedDeque([1, 2]) != ListBasedDeque([1, 55])
183
- assert collections.deque([1, 2]) != ListBasedDeque([1, 55])
214
+ assert deque([1, 2]) != ListBasedDeque([1, 55])
184
215
 
185
216
 
186
217
  def test_defaultdict_repr_equiv(test_list) -> None:
187
218
  def f(symbolic: DefaultDict[int, int]) -> Tuple[dict, dict]:
188
219
  """post: _[0] == _[1]"""
189
- concrete = collections.defaultdict(symbolic.default_factory, symbolic.items())
220
+ concrete = defaultdict(symbolic.default_factory, symbolic.items())
190
221
  return (symbolic, concrete)
191
222
 
192
223
  check_states(f, CANNOT_CONFIRM)
193
224
 
194
225
 
195
- def test_defaultdict_basic_fail(test_list) -> None:
226
+ def test_defaultdict_basic_fail() -> None:
196
227
  def f(a: DefaultDict[int, int], k: int, v: int) -> None:
197
228
  """
198
229
  post[a]: a[42] != 42
@@ -215,10 +246,10 @@ def test_defaultdict_default_fail(test_list) -> None:
215
246
 
216
247
 
217
248
  def test_defaultdict_default_ok(test_list) -> None:
218
- def f(a: DefaultDict[int, int], k1: int, k2: int) -> DefaultDict[int, int]:
249
+ def f(a: DefaultDict[int, int], k: int) -> DefaultDict[int, int]:
219
250
  """
220
251
  pre: len(a) == 0 and a.default_factory is not None
221
- post: _[k1] == _[k2]
252
+ post: _[k] == _[k]
222
253
  """
223
254
  return a
224
255
 
@@ -229,16 +260,73 @@ def test_defaultdict_realize():
229
260
  with standalone_statespace:
230
261
  with NoTracing():
231
262
  d = proxy_for_type(DefaultDict[int, int], "d")
232
- assert type(realize(d)) is collections.defaultdict
263
+ assert type(realize(d)) is defaultdict
233
264
 
234
265
 
235
266
  #
236
- # We don't patch namedtuple, but namedtuple performs magic like dynamic type
267
+ # We don't patch namedtuple, but namedtuple performs magic dynamic type
237
268
  # generation, which can interfere with CrossHair.
238
269
  #
239
270
 
240
271
 
241
272
  def test_namedtuple_creation():
242
273
  with standalone_statespace:
243
- # Ensure type creation doesn't raise exception:
244
- Color = collections.namedtuple("Color", ("name", "hex"))
274
+ # Ensure type creation under trace doesn't raise exception:
275
+ Color = namedtuple("Color", ("name", "hex"))
276
+
277
+
278
+ def test_namedtuple_argument_detection_untyped():
279
+ UntypedColor = namedtuple("UntypedColor", ("name", "hex"))
280
+ expected_signature = Signature(
281
+ parameters=[
282
+ Parameter("name", Parameter.POSITIONAL_OR_KEYWORD),
283
+ Parameter("hex", Parameter.POSITIONAL_OR_KEYWORD),
284
+ ],
285
+ return_annotation=Signature.empty,
286
+ )
287
+ assert get_constructor_signature(UntypedColor) == expected_signature
288
+
289
+
290
+ def test_namedtuple_argument_detection_typed_with_subclass():
291
+ class ClassTypedColor(NamedTuple):
292
+ name: str
293
+ hex: int
294
+
295
+ expected_parameters = {
296
+ "name": Parameter("name", Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
297
+ "hex": Parameter("hex", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
298
+ }
299
+ assert get_constructor_signature(ClassTypedColor).parameters == expected_parameters
300
+
301
+
302
+ @pytest.mark.skipif(
303
+ sys.version_info < (3, 9),
304
+ reason="Functional namedtuple field types supported on Python >= 3.9",
305
+ )
306
+ def test_namedtuple_argument_detection_typed_functionally():
307
+ FunctionallyTypedColor = NamedTuple(
308
+ "FunctionallyTypedColor", [("name", str), ("hex", int)]
309
+ )
310
+ expected_parameters = {
311
+ "name": Parameter("name", Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
312
+ "hex": Parameter("hex", Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
313
+ }
314
+ assert (
315
+ get_constructor_signature(FunctionallyTypedColor).parameters
316
+ == expected_parameters
317
+ )
318
+
319
+
320
+ @pytest.mark.skipif(
321
+ sys.version_info < (3, 9),
322
+ reason="Functional namedtuple field types supported on Python >= 3.9",
323
+ )
324
+ def test_namedtuple_symbolic_creation(space):
325
+ UntypedColor = namedtuple("Color", "name hex")
326
+ Color = NamedTuple("Color", [("name", str), ("hex", int)])
327
+ untyped_color = proxy_for_type(UntypedColor, "color")
328
+ assert isinstance(untyped_color.hex, CrossHairValue)
329
+ color = proxy_for_type(Color, "color")
330
+ with ResumedTracing():
331
+ assert space.is_possible(color.hex == 5)
332
+ assert space.is_possible(color.hex == 10)
@@ -1,7 +1,7 @@
1
1
  from copy import copy, deepcopy
2
2
 
3
3
  from crosshair import NoTracing, register_patch
4
- from crosshair.core import CrossHairValue
4
+ from crosshair.util import CrossHairValue
5
5
 
6
6
 
7
7
  def _copy(x):
@@ -0,0 +1,18 @@
1
+ import copy
2
+ import datetime
3
+
4
+ from crosshair.core import proxy_for_type
5
+ from crosshair.statespace import StateSpace
6
+ from crosshair.tracers import ResumedTracing
7
+ from crosshair.util import debug
8
+
9
+
10
+ def test_date_copy(space: StateSpace) -> None:
11
+ concrete_date = datetime.date(2000, 2, 3)
12
+ symbolic_date = proxy_for_type(datetime.date, "d")
13
+ with ResumedTracing():
14
+ copied_symbolic = copy.deepcopy(symbolic_date)
15
+ copied_concrete = copy.deepcopy(concrete_date)
16
+ assert not space.is_possible(copied_concrete != concrete_date)
17
+ assert not space.is_possible(copied_concrete != datetime.date(2000, 2, 3))
18
+ assert not space.is_possible(copied_symbolic != symbolic_date)