crosshair-tool 0.0.83__tar.gz → 0.0.85__tar.gz

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.

Potentially problematic release.


This version of crosshair-tool might be problematic. Click here for more details.

Files changed (181) hide show
  1. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/PKG-INFO +2 -2
  2. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/README.md +1 -1
  3. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/__init__.py +1 -1
  4. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_mark_stacks.h +0 -25
  5. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_tracers.c +92 -15
  6. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_tracers.h +2 -0
  7. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_tracers_test.py +8 -2
  8. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/auditwall.py +0 -1
  9. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/auditwall_test.py +5 -0
  10. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/condition_parser.py +5 -5
  11. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/condition_parser_test.py +50 -63
  12. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/copyext.py +23 -7
  13. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/copyext_test.py +11 -1
  14. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/core.py +23 -17
  15. crosshair-tool-0.0.85/crosshair/core_test.py +1290 -0
  16. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/diff_behavior_test.py +14 -21
  17. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/dynamic_typing.py +90 -1
  18. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/dynamic_typing_test.py +73 -1
  19. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/enforce_test.py +15 -22
  20. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/fnutil_test.py +4 -8
  21. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/arraylib.py +2 -5
  22. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/binasciilib.py +2 -3
  23. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/builtinslib.py +28 -21
  24. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/builtinslib_test.py +1 -8
  25. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/collectionslib.py +18 -3
  26. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/collectionslib_test.py +89 -15
  27. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/_encutil.py +8 -3
  28. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/mathlib_test.py +0 -7
  29. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/relib_ch_test.py +2 -2
  30. crosshair-tool-0.0.85/crosshair/libimpl/timelib.py +72 -0
  31. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/timelib_test.py +12 -2
  32. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/lsp_server.py +1 -1
  33. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/main.py +3 -1
  34. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/objectproxy_test.py +7 -11
  35. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/opcode_intercept.py +24 -8
  36. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/opcode_intercept_test.py +13 -2
  37. crosshair-tool-0.0.85/crosshair/tools/__init__.py +0 -0
  38. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tracers.py +27 -9
  39. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/type_repo.py +2 -2
  40. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/unicode_categories.py +1 -0
  41. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/util.py +45 -16
  42. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/watcher.py +2 -2
  43. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/PKG-INFO +2 -2
  44. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/SOURCES.txt +1 -0
  45. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/setup.cfg +2 -2
  46. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/setup.py +2 -2
  47. crosshair-tool-0.0.83/crosshair/core_test.py +0 -1249
  48. crosshair-tool-0.0.83/crosshair/libimpl/timelib.py +0 -53
  49. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/LICENSE +0 -0
  50. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/__main__.py +0 -0
  51. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_preliminaries_test.py +0 -0
  52. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/_tracers_pycompat.h +0 -0
  53. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/abcstring.py +0 -0
  54. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/codeconfig.py +0 -0
  55. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/codeconfig_test.py +0 -0
  56. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/conftest.py +0 -0
  57. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/core_and_libs.py +0 -0
  58. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/core_regestered_types_test.py +0 -0
  59. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/diff_behavior.py +0 -0
  60. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/enforce.py +0 -0
  61. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/__init__.py +0 -0
  62. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
  63. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/getattr_magic.py +0 -0
  64. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +0 -0
  65. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/shopping_cart.py +0 -0
  66. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/bugs_detected/showcase.py +0 -0
  67. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/__init__.py +0 -0
  68. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/arith.py +0 -0
  69. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/chess.py +0 -0
  70. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/nesting_inference.py +0 -0
  71. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/numpy_examples.py +0 -0
  72. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/rolling_average.py +0 -0
  73. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/PEP316/correct_code/showcase.py +0 -0
  74. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/__init__.py +0 -0
  75. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/check_examples_test.py +0 -0
  76. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/deal/__init__.py +0 -0
  77. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/__init__.py +0 -0
  78. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
  79. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/bugs_detected/showcase.py +0 -0
  80. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/bugs_detected/wrong_sign.py +0 -0
  81. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/correct_code/__init__.py +0 -0
  82. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/correct_code/arith.py +0 -0
  83. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/examples/icontract/correct_code/showcase.py +0 -0
  84. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/fnutil.py +0 -0
  85. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/fuzz_core_test.py +0 -0
  86. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/__init__.py +0 -0
  87. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/binascii_ch_test.py +0 -0
  88. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/binascii_test.py +0 -0
  89. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/bisectlib_test.py +0 -0
  90. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/builtinslib_ch_test.py +0 -0
  91. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/codecslib.py +0 -0
  92. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/codecslib_test.py +0 -0
  93. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/collectionslib_ch_test.py +0 -0
  94. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/copylib.py +0 -0
  95. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/datetimelib.py +0 -0
  96. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/datetimelib_ch_test.py +0 -0
  97. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/datetimelib_test.py +0 -0
  98. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/decimallib.py +0 -0
  99. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/decimallib_ch_test.py +0 -0
  100. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/decimallib_test.py +0 -0
  101. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/__init__.py +0 -0
  102. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/ascii.py +0 -0
  103. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/latin_1.py +0 -0
  104. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings/utf_8.py +0 -0
  105. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/encodings_ch_test.py +0 -0
  106. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/fractionlib.py +0 -0
  107. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/fractionlib_test.py +0 -0
  108. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/functoolslib.py +0 -0
  109. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/functoolslib_test.py +0 -0
  110. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/hashliblib.py +0 -0
  111. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/hashliblib_test.py +0 -0
  112. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/heapqlib.py +0 -0
  113. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/heapqlib_test.py +0 -0
  114. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/importliblib.py +0 -0
  115. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/importliblib_test.py +0 -0
  116. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/iolib.py +0 -0
  117. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/iolib_ch_test.py +0 -0
  118. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/iolib_test.py +0 -0
  119. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/ipaddresslib.py +0 -0
  120. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/itertoolslib.py +0 -0
  121. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/itertoolslib_test.py +0 -0
  122. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/jsonlib.py +0 -0
  123. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/jsonlib_ch_test.py +0 -0
  124. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/jsonlib_test.py +0 -0
  125. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/mathlib.py +0 -0
  126. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/mathlib_ch_test.py +0 -0
  127. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/oslib.py +0 -0
  128. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/pathliblib_test.py +0 -0
  129. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/randomlib.py +0 -0
  130. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/randomlib_test.py +0 -0
  131. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/relib.py +0 -0
  132. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/relib_test.py +0 -0
  133. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/typeslib.py +0 -0
  134. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/typeslib_test.py +0 -0
  135. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/unicodedatalib.py +0 -0
  136. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/unicodedatalib_test.py +0 -0
  137. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/urlliblib.py +0 -0
  138. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/urlliblib_test.py +0 -0
  139. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/weakreflib.py +0 -0
  140. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/weakreflib_test.py +0 -0
  141. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/zliblib.py +0 -0
  142. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/libimpl/zliblib_test.py +0 -0
  143. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/lsp_server_test.py +0 -0
  144. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/main_test.py +0 -0
  145. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/objectproxy.py +0 -0
  146. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/options.py +0 -0
  147. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/options_test.py +0 -0
  148. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/patch_equivalence_test.py +0 -0
  149. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/path_cover.py +0 -0
  150. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/path_cover_test.py +0 -0
  151. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/path_search.py +0 -0
  152. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/path_search_test.py +0 -0
  153. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/pathing_oracle.py +0 -0
  154. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/pure_importer.py +0 -0
  155. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/pure_importer_test.py +0 -0
  156. /crosshair-tool-0.0.83/crosshair/tools/__init__.py → /crosshair-tool-0.0.85/crosshair/py.typed +0 -0
  157. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/register_contract.py +0 -0
  158. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/register_contract_test.py +0 -0
  159. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/simplestructs.py +0 -0
  160. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/simplestructs_test.py +0 -0
  161. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/smtlib.py +0 -0
  162. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/smtlib_test.py +0 -0
  163. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/statespace.py +0 -0
  164. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/statespace_test.py +0 -0
  165. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/stubs_parser.py +0 -0
  166. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/stubs_parser_test.py +0 -0
  167. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/test_util.py +0 -0
  168. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/test_util_test.py +0 -0
  169. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tools/check_help_in_doc.py +0 -0
  170. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tools/check_init_and_setup_coincide.py +0 -0
  171. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tools/generate_demo_table.py +0 -0
  172. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/tracers_test.py +0 -0
  173. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/unicode_categories_test.py +0 -0
  174. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/util_test.py +0 -0
  175. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/watcher_test.py +0 -0
  176. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/z3util.py +0 -0
  177. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair/z3util_test.py +0 -0
  178. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/dependency_links.txt +0 -0
  179. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/entry_points.txt +0 -0
  180. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/requires.txt +0 -0
  181. {crosshair-tool-0.0.83 → crosshair-tool-0.0.85}/crosshair_tool.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crosshair-tool
3
- Version: 0.0.83
3
+ Version: 0.0.85
4
4
  Summary: Analyze Python code for correctness using symbolic execution.
5
5
  Home-page: https://github.com/pschanely/CrossHair
6
6
  Author: Phillip Schanely
@@ -30,7 +30,7 @@ License-File: LICENSE
30
30
  # CrossHair
31
31
 
32
32
  [![Join the chat on Discord](https://img.shields.io/discord/1346219754519789719?logo=discord&logoColor=white)](https://discord.gg/rUeTaYTWbb)
33
- [![Check status](https://github.com/pschanely/CrossHair/workflows/Check/badge.svg)](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck)
33
+ [![Check status](https://github.com/pschanely/CrossHair/actions/workflows/check.yml/badge.svg?branch=main&event=push)](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck+event%3Apush)
34
34
  [![Downloads](https://static.pepy.tech/badge/crosshair-tool/week)](https://pepy.tech/project/crosshair-tool)
35
35
 
36
36
  An analysis tool for Python that blurs the line between testing and
@@ -3,7 +3,7 @@
3
3
  # CrossHair
4
4
 
5
5
  [![Join the chat on Discord](https://img.shields.io/discord/1346219754519789719?logo=discord&logoColor=white)](https://discord.gg/rUeTaYTWbb)
6
- [![Check status](https://github.com/pschanely/CrossHair/workflows/Check/badge.svg)](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck)
6
+ [![Check status](https://github.com/pschanely/CrossHair/actions/workflows/check.yml/badge.svg?branch=main&event=push)](https://github.com/pschanely/CrossHair/actions?query=workflow%3ACheck+event%3Apush)
7
7
  [![Downloads](https://static.pepy.tech/badge/crosshair-tool/week)](https://pepy.tech/project/crosshair-tool)
8
8
 
9
9
  An analysis tool for Python that blurs the line between testing and
@@ -15,7 +15,7 @@ from crosshair.statespace import StateSpace
15
15
  from crosshair.tracers import NoTracing, ResumedTracing
16
16
  from crosshair.util import IgnoreAttempt, debug
17
17
 
18
- __version__ = "0.0.83" # Do not forget to update in setup.py!
18
+ __version__ = "0.0.85" # Do not forget to update in setup.py!
19
19
  __author__ = "Phillip Schanely"
20
20
  __license__ = "MIT"
21
21
  __status__ = "Alpha"
@@ -538,31 +538,6 @@ static const uint8_t _ch_DE_INSTRUMENT[256] = {
538
538
  #endif
539
539
  #endif
540
540
 
541
- static const uint8_t _ch_TRACABLE_INSTRUCTIONS[256] = {
542
- // This must be manually kept in sync the the various
543
- // instructions that we care about on the python side.
544
- [MAP_ADD] = 1,
545
- [BINARY_SUBSCR] = 1,
546
- [BINARY_SLICE] = 1,
547
- [CONTAINS_OP] = 1,
548
- [BUILD_STRING] = 1,
549
- #if PY_VERSION_HEX < 0x030D0000
550
- // <= 3.12
551
- [FORMAT_VALUE] = 1,
552
- #elif PY_VERSION_HEX < 0x030E0000
553
- // 3.13
554
- [CALL_KW] = 1,
555
- [CONVERT_VALUE] = 1,
556
- #endif
557
- [UNARY_NOT] = 1,
558
- [SET_ADD] = 1,
559
- [IS_OP] = 1,
560
- [BINARY_OP] = 1,
561
- [CALL] = 1,
562
- [CALL_FUNCTION_EX] = 1,
563
- };
564
-
565
-
566
541
  /* Get the underlying opcode, stripping instrumentation */
567
542
  int _ch_Py_GetBaseOpcode(PyCodeObject *code, int i)
568
543
  {
@@ -17,20 +17,69 @@
17
17
  #include <opcode.h>
18
18
 
19
19
  #define Py_BUILD_CORE
20
+ #define NEED_OPCODE_METADATA
20
21
 
21
- #if PY_VERSION_HEX >= 0x030C0000
22
- #include "_mark_stacks.h"
23
- #endif
24
22
 
25
23
  #include "_tracers_pycompat.h"
24
+ #include "internal/pycore_code.h"
25
+
26
+ #if PY_VERSION_HEX >= 0x030D0000
27
+ // Python 3.13+
28
+ #include "internal/pycore_opcode_metadata.h"
29
+ #endif
30
+
26
31
  #include "_tracers.h"
27
32
 
28
33
  #include "frameobject.h"
29
34
 
30
35
  #if PY_VERSION_HEX >= 0x030B0000
36
+ // Python 3.11+
31
37
  #include "internal/pycore_frame.h"
32
38
  #endif
33
39
 
40
+
41
+ #if PY_VERSION_HEX >= 0x030C0000
42
+ // Python 3.12+
43
+ const uint8_t _ch_TRACABLE_INSTRUCTIONS[256] = {
44
+ // This must be manually kept in sync the the various
45
+ // instructions that we care about on the python side.
46
+ [MAP_ADD] = 1,
47
+ [BINARY_SLICE] = 1,
48
+ [CONTAINS_OP] = 1,
49
+ [BUILD_STRING] = 1,
50
+ #if PY_VERSION_HEX < 0x030D0000
51
+ // <= 3.12
52
+ [FORMAT_VALUE] = 1,
53
+ [BINARY_SUBSCR] = 1,
54
+ #elif PY_VERSION_HEX < 0x030E0000
55
+ // == 3.13
56
+ [CALL_KW] = 1,
57
+ [CONVERT_VALUE] = 1,
58
+ [BINARY_SUBSCR] = 1,
59
+ #elif PY_VERSION_HEX < 0x030F0000
60
+ // == 3.14
61
+ [CALL_KW] = 1,
62
+ [CONVERT_VALUE] = 1,
63
+ #endif
64
+ [UNARY_NOT] = 1,
65
+ [SET_ADD] = 1,
66
+ [IS_OP] = 1,
67
+ [BINARY_OP] = 1,
68
+ [CALL] = 1,
69
+ [CALL_FUNCTION_EX] = 1,
70
+ };
71
+ #endif
72
+
73
+
74
+ #if PY_VERSION_HEX >= 0x030C0000
75
+ #if PY_VERSION_HEX < 0x030E0000
76
+ // Python 3.12 & 3.13
77
+ #define CH_STACK_COMPUTATION 1
78
+ #include "_mark_stacks.h"
79
+ #endif
80
+ #endif
81
+
82
+
34
83
  static int
35
84
  pyint_as_int(PyObject * pyint, int *pint)
36
85
  {
@@ -92,8 +141,7 @@ CTracer_dealloc(CTracer *self)
92
141
  Py_TYPE(self)->tp_free((PyObject*)self);
93
142
  }
94
143
 
95
- #if PY_VERSION_HEX >= 0x030C0000
96
- // Python 3.12
144
+ #if CH_STACK_COMPUTATION
97
145
 
98
146
  #define _CODE_STACK_CACHE_CAPACITY 64
99
147
  static CodeAndStacks _CODE_STACK_CACHE[_CODE_STACK_CACHE_CAPACITY];
@@ -183,7 +231,7 @@ CTracer_push_module(CTracer *self, PyObject *args)
183
231
  continue;
184
232
  }
185
233
  #if PY_VERSION_HEX >= 0x030C0000
186
- // Python 3.12
234
+ // Python 3.12+
187
235
  if (! _ch_TRACABLE_INSTRUCTIONS[opcode]) {
188
236
  self->trace_all_opcodes = TRUE;
189
237
  // sys.monitoring also will need to be reset, but that happens at the python layer above
@@ -293,12 +341,36 @@ static int
293
341
  CTracer_handle_opcode(CTracer *self, PyCodeObject* pCode, int lasti)
294
342
  {
295
343
 
296
- #if PY_VERSION_HEX >= 0x030C0000
297
- // Python 3.12
344
+ #if CH_STACK_COMPUTATION
345
+ if (!self->trace_all_opcodes) {
346
+ int64_t *stacks = _ch_get_stacks(pCode);
347
+ uint8_t at_enabled_position = stacks[lasti / 2] & 1;
348
+ if (!at_enabled_position) {
349
+ return RET_DISABLE_TRACING;
350
+ }
351
+ }
352
+ #elif PY_VERSION_HEX >= 0x030E0000
353
+ // Python 3.14+
298
354
  if (!self->trace_all_opcodes) {
299
- int64_t *stacks = _ch_get_stacks(pCode);
300
- uint8_t at_enabled_position = stacks[lasti / 2] & 1;
301
- if (!at_enabled_position) {
355
+ PyBytesObject *code_bytes = PyCode_GetCode(pCode);
356
+ int opcode = code_bytes->ob_sval[lasti];
357
+ int last_opcode = 255;
358
+ uint8_t at_enabled_position = _ch_TRACABLE_INSTRUCTIONS[opcode];
359
+ uint8_t last_instr_enabled = 0;
360
+ if (lasti > 1) {
361
+ // TODO: This seems wrong; it doesn't account for extended args or cache entries;
362
+ last_opcode = code_bytes->ob_sval[lasti - 2];
363
+ last_instr_enabled = _ch_TRACABLE_INSTRUCTIONS[last_opcode];
364
+ }
365
+ // printf("lasti: %d, func: %s, opcode: %s, last opcode (%s) enabled: %d -> %d\n", lasti,
366
+ // PyUnicode_AsUTF8(pCode->co_name) ? PyUnicode_AsUTF8(pCode->co_name) : "<unknown>",
367
+ // _PyOpcode_OpName[opcode] ? _PyOpcode_OpName[opcode] : "<unknown>",
368
+ // _PyOpcode_OpName[last_opcode] ? _PyOpcode_OpName[last_opcode] : "<unknown>",
369
+ // last_instr_enabled,
370
+ // at_enabled_position
371
+ // );
372
+
373
+ if ((!last_instr_enabled) && (!at_enabled_position)) {
302
374
  return RET_DISABLE_TRACING;
303
375
  }
304
376
  }
@@ -860,7 +932,12 @@ TraceSwapType = {
860
932
 
861
933
 
862
934
  static PyObject **crosshair_tracers_stack_lookup(PyFrameObject *frame, int index) {
863
- #if PY_VERSION_HEX >= 0x030C0000
935
+ #if PY_VERSION_HEX >= 0x030E0000
936
+ // Python 3.14+
937
+ PyCodeObject* code = _PyFrame_GetCodeBorrow(frame);
938
+ _PyInterpreterFrame* interpreterFrame = frame->f_frame;
939
+ return &(interpreterFrame->stackpointer[index]);
940
+ #elif PY_VERSION_HEX >= 0x030C0000
864
941
  // Python 3.12
865
942
  PyCodeObject* code = _PyFrame_GetCodeBorrow(frame);
866
943
  _PyInterpreterFrame* interpreterFrame = frame->f_frame;
@@ -900,7 +977,7 @@ static PyObject *crosshair_tracers_stack_read(PyObject *self, PyObject *args)
900
977
  return NULL;
901
978
  }
902
979
  PyObject *ret = *retaddr;
903
- if (ret == NULL) {
980
+ if (ret == NULL || ret == 1) { // starting in 3.14, we sometimes see a sentinal 0x1 pointer (?)
904
981
  PyErr_SetString(PyExc_ValueError, "No stack value is present");
905
982
  return NULL;
906
983
  } else {
@@ -937,7 +1014,7 @@ static PyObject* crosshair_tracers_code_stack_depths(PyObject *self, PyObject *a
937
1014
  return NULL;
938
1015
  }
939
1016
 
940
- #if PY_VERSION_HEX >= 0x030C0000
1017
+ #if CH_STACK_COMPUTATION
941
1018
  // Python 3.12
942
1019
  int64_t *stacks = _ch_get_stacks(code);
943
1020
  int codelen = (int)Py_SIZE(code);
@@ -961,7 +1038,7 @@ static PyObject* crosshair_tracers_supported_opcodes(PyObject *self, PyObject *a
961
1038
  return NULL;
962
1039
  }
963
1040
  #if PY_VERSION_HEX >= 0x030C0000
964
- // Python 3.12
1041
+ // Python 3.12+
965
1042
  PyObject* python_val = PyList_New(0);
966
1043
  for (int i = 0; i < 256; i++) {
967
1044
  if (_ch_TRACABLE_INSTRUCTIONS[i]) {
@@ -88,4 +88,6 @@ typedef struct TraceSwap {
88
88
 
89
89
  extern PyTypeObject TraceSwapType;
90
90
 
91
+ extern const uint8_t _ch_TRACABLE_INSTRUCTIONS[256];
92
+
91
93
  #endif /* _COVERAGE_TRACER_H */
@@ -89,7 +89,10 @@ def _log_execution_stacks(fn, *a, **kw):
89
89
  return stacks
90
90
 
91
91
 
92
- @pytest.mark.skipif(sys.version_info < (3, 12), reason="stack depth on 3.12+")
92
+ @pytest.mark.skipif(
93
+ sys.version_info < (3, 12) or sys.version_info >= (3, 14),
94
+ reason="stack depths only in 3.12 & 3.13",
95
+ )
93
96
  def test_one_function_stack_depth():
94
97
  _E = (TypeError, KeyboardInterrupt)
95
98
 
@@ -100,7 +103,10 @@ def test_one_function_stack_depth():
100
103
  _log_execution_stacks(a, 4)
101
104
 
102
105
 
103
- @pytest.mark.skipif(sys.version_info < (3, 12), reason="stack depth on 3.12+")
106
+ @pytest.mark.skipif(
107
+ sys.version_info < (3, 12) or sys.version_info >= (3, 14),
108
+ reason="stack depths only in 3.12 & 3.13",
109
+ )
104
110
  def test_stack_get():
105
111
  def to_be_traced(x):
106
112
  r = 8 - x
@@ -135,7 +135,6 @@ def make_handler(event: str) -> Callable[[str, Tuple], None]:
135
135
  "imaplib",
136
136
  "msvcrt",
137
137
  "nntplib",
138
- "os",
139
138
  "pathlib",
140
139
  "poplib",
141
140
  "shutil",
@@ -42,6 +42,10 @@ def test_popen_disallowed():
42
42
  assert call([pyexec, __file__, "popen", "withwall"]) == 10
43
43
 
44
44
 
45
+ def test_chdir_allowed():
46
+ assert call([pyexec, __file__, "chdir", "withwall"]) == 0
47
+
48
+
45
49
  @pytest.mark.skipif(sys.version_info < (3, 9), reason="Python 3.9+ required")
46
50
  def test_popen_via_platform_allowed():
47
51
  assert call([pyexec, __file__, "popen_via_platform", "withwall"]) == 0
@@ -58,6 +62,7 @@ _ACTIONS = {
58
62
  "popen_via_platform": lambda: platform._syscmd_ver( # type: ignore
59
63
  supported_platforms=(sys.platform,)
60
64
  ),
65
+ "chdir": lambda: os.chdir("."),
61
66
  }
62
67
 
63
68
  if __name__ == "__main__":
@@ -47,6 +47,7 @@ from crosshair.options import AnalysisKind
47
47
  from crosshair.register_contract import get_contract
48
48
  from crosshair.tracers import NoTracing
49
49
  from crosshair.util import (
50
+ CrossHairInternal,
50
51
  DynamicScopeVar,
51
52
  EvalFriendlyReprContext,
52
53
  IdKeyedDict,
@@ -485,6 +486,7 @@ class ConcreteConditionParser(ConditionParser):
485
486
  method = cls.__dict__.get(method_name, None)
486
487
  super_method_conditions = super_methods.get(method_name)
487
488
  if super_method_conditions is not None:
489
+ # Re-type the super's `self` argument to be this class:
488
490
  revised_sig = set_first_arg_type(super_method_conditions.sig, cls)
489
491
  super_method_conditions = replace(
490
492
  super_method_conditions, sig=revised_sig
@@ -511,17 +513,15 @@ class ConcreteConditionParser(ConditionParser):
511
513
  final_pre = list(conditions.pre)
512
514
  final_post = list(conditions.post)
513
515
  if method_name in (
514
- "__new__", # isn't passed a concrete instance.
516
+ "__new__", # a staticmethod, but not isinstance(staticmethod)
515
517
  "__repr__", # is itself required for reporting problems with invariants.
516
518
  # [set/del]attr can do anything; we can't resonably enforce invariants:
517
519
  "__setattr__",
518
520
  "__delattr__",
521
+ "__replace__", # Will raise an exception with most arbitrary **kwargs.
522
+ "__annotate__", # a staticmethod, but not isinstance(staticmethod)
519
523
  ):
520
524
  pass
521
- elif method_name == "__replace__":
522
- # TODO: remove this case when fixed in 3.13
523
- # see https://github.com/python/cpython/issues/114198
524
- pass
525
525
  elif method_name == "__del__":
526
526
  final_pre.extend(inv)
527
527
  elif method_name == "__init__":
@@ -133,41 +133,32 @@ def test_parse_sphinx_raises() -> None:
133
133
  assert parse_sphinx_raises(sphinx_raises) == {LocallyDefiendException}
134
134
 
135
135
 
136
- class Pep316ParserTest(unittest.TestCase):
136
+ class TestPep316Parser:
137
137
  def test_class_parse(self) -> None:
138
138
  class_conditions = Pep316Parser().get_class_conditions(Foo)
139
- self.assertEqual(
140
- set([c.expr_source for c in class_conditions.inv]),
141
- set(["self.x >= 0", "self.y >= 0"]),
142
- )
143
- self.assertEqual(
144
- set(class_conditions.methods.keys()), set(["isready", "__init__"])
145
- )
139
+ assert set([c.expr_source for c in class_conditions.inv]) == {
140
+ "self.x >= 0",
141
+ "self.y >= 0",
142
+ }
143
+ assert {"isready", "__init__"} <= set(class_conditions.methods.keys())
146
144
  method = class_conditions.methods["isready"]
147
- self.assertEqual(
148
- set([c.expr_source for c in method.pre]),
149
- set(["self.x >= 0", "self.y >= 0"]),
150
- )
145
+ assert set([c.expr_source for c in method.pre]) == {
146
+ "self.x >= 0",
147
+ "self.y >= 0",
148
+ }
151
149
  startlineno = inspect.getsourcelines(Foo)[1]
152
- self.assertEqual(
153
- set([(c.expr_source, c.line) for c in method.post]),
154
- set(
155
- [
156
- ("self.x >= 0", startlineno + 7),
157
- ("self.y >= 0", startlineno + 12),
158
- ("__return__ == (self.x == 0)", startlineno + 24),
159
- ]
160
- ),
161
- )
150
+ assert set([(c.expr_source, c.line) for c in method.post]) == {
151
+ ("self.x >= 0", startlineno + 7),
152
+ ("self.y >= 0", startlineno + 12),
153
+ ("__return__ == (self.x == 0)", startlineno + 24),
154
+ }
162
155
 
163
156
  def test_single_line_condition(self) -> None:
164
157
  conditions = Pep316Parser().get_fn_conditions(
165
158
  FunctionInfo.from_fn(single_line_condition)
166
159
  )
167
160
  assert conditions is not None
168
- self.assertEqual(
169
- set([c.expr_source for c in conditions.post]), set(["__return__ >= x"])
170
- )
161
+ assert set([c.expr_source for c in conditions.post]) == {"__return__ >= x"}
171
162
 
172
163
  def test_implies_condition(self):
173
164
  conditions = Pep316Parser().get_fn_conditions(
@@ -182,37 +173,35 @@ class Pep316ParserTest(unittest.TestCase):
182
173
  FunctionInfo.from_fn(locally_defined_raises_condition)
183
174
  )
184
175
  assert conditions is not None
185
- self.assertEqual([], list(conditions.syntax_messages()))
186
- self.assertEqual(set([LocallyDefiendException]), conditions.raises)
176
+ assert [] == list(conditions.syntax_messages())
177
+ assert set([LocallyDefiendException]) == conditions.raises
187
178
 
188
179
  def test_tricky_raises_condition(self) -> None:
189
180
  conditions = Pep316Parser().get_fn_conditions(
190
181
  FunctionInfo.from_fn(tricky_raises_condition)
191
182
  )
192
183
  assert conditions is not None
193
- self.assertEqual([], list(conditions.syntax_messages()))
194
- self.assertEqual(conditions.raises, set([KeyError, json.JSONDecodeError]))
184
+ assert [] == list(conditions.syntax_messages())
185
+ assert conditions.raises == {KeyError, json.JSONDecodeError}
195
186
 
196
187
  def test_invariant_is_inherited(self) -> None:
197
188
  class_conditions = Pep316Parser().get_class_conditions(SubClassExample)
198
- self.assertEqual(set(class_conditions.methods.keys()), set(["foo", "__init__"]))
189
+ assert set(class_conditions.methods.keys()) == {"foo", "__init__"}
199
190
  method = class_conditions.methods["foo"]
200
- self.assertEqual(len(method.pre), 1)
201
- self.assertEqual(set([c.expr_source for c in method.pre]), set(["True"]))
202
- self.assertEqual(len(method.post), 2)
203
- self.assertEqual(
204
- set([c.expr_source for c in method.post]), set(["True", "False"])
205
- )
191
+ assert len(method.pre) == 1
192
+ assert set([c.expr_source for c in method.pre]) == {"True"}
193
+ assert len(method.post) == 2
194
+ assert set([c.expr_source for c in method.post]) == {"True", "False"}
206
195
 
207
196
  def test_invariant_applies_to_init(self) -> None:
208
197
  class_conditions = Pep316Parser().get_class_conditions(BaseClassExample)
209
- self.assertEqual(set(class_conditions.methods.keys()), set(["__init__", "foo"]))
198
+ assert set(class_conditions.methods.keys()) == {"__init__", "foo"}
210
199
 
211
200
  @pytest.mark.skipif(
212
201
  sys.version_info >= (3, 13), reason="builtins have signatures in 3.13"
213
202
  )
214
203
  def test_builtin_conditions_are_null(self) -> None:
215
- self.assertIsNone(Pep316Parser().get_fn_conditions(FunctionInfo.from_fn(zip)))
204
+ assert Pep316Parser().get_fn_conditions(FunctionInfo.from_fn(zip)) is None
216
205
 
217
206
  def test_conditions_with_closure_references_and_string_type(self) -> None:
218
207
  # This is a function that refers to something in its closure.
@@ -228,7 +217,7 @@ class Pep316ParserTest(unittest.TestCase):
228
217
 
229
218
 
230
219
  @pytest.mark.skipif(not icontract, reason="icontract is not installed")
231
- class IcontractParserTest(unittest.TestCase):
220
+ class TestIcontractParser:
232
221
  def test_simple_parse(self):
233
222
  @icontract.require(lambda ls: len(ls) > 0)
234
223
  @icontract.ensure(lambda ls, result: min(ls) <= result <= max(ls))
@@ -237,17 +226,17 @@ class IcontractParserTest(unittest.TestCase):
237
226
 
238
227
  conditions = IcontractParser().get_fn_conditions(FunctionInfo.from_fn(avg))
239
228
  assert conditions is not None
240
- self.assertEqual(len(conditions.pre), 1)
241
- self.assertEqual(len(conditions.post), 1)
242
- self.assertEqual(conditions.pre[0].evaluate({"ls": []}), False)
229
+ assert len(conditions.pre) == 1
230
+ assert len(conditions.post) == 1
231
+ assert conditions.pre[0].evaluate({"ls": []}) is False
243
232
  post_args = {
244
233
  "ls": [42, 43],
245
234
  "__old__": AttributeHolder({}),
246
235
  "__return__": 40,
247
236
  "_": 40,
248
237
  }
249
- self.assertEqual(conditions.post[0].evaluate(post_args), False)
250
- self.assertEqual(len(post_args), 4) # (check args are unmodified)
238
+ assert conditions.post[0].evaluate(post_args) is False
239
+ assert len(post_args) == 4 # (check args are unmodified)
251
240
 
252
241
  def test_simple_class_parse(self):
253
242
  @icontract.invariant(lambda self: self.i >= 0)
@@ -268,14 +257,14 @@ class IcontractParserTest(unittest.TestCase):
268
257
  self.i -= 1
269
258
 
270
259
  conditions = IcontractParser().get_class_conditions(Counter)
271
- self.assertEqual(len(conditions.inv), 1)
260
+ assert len(conditions.inv) == 1
272
261
 
273
262
  decr_conditions = conditions.methods["decr"]
274
- self.assertEqual(len(decr_conditions.pre), 2)
263
+ assert len(decr_conditions.pre) == 2
275
264
  # decr() precondition: count > 0
276
- self.assertEqual(decr_conditions.pre[0].evaluate({"self": Counter()}), False)
265
+ assert decr_conditions.pre[0].evaluate({"self": Counter()}) is False
277
266
  # invariant: count >= 0
278
- self.assertEqual(decr_conditions.pre[1].evaluate({"self": Counter()}), True)
267
+ assert decr_conditions.pre[1].evaluate({"self": Counter()}) is True
279
268
 
280
269
  class TruncatedCounter(Counter):
281
270
  @icontract.require(
@@ -287,21 +276,19 @@ class IcontractParserTest(unittest.TestCase):
287
276
 
288
277
  conditions = IcontractParser().get_class_conditions(TruncatedCounter)
289
278
  decr_conditions = conditions.methods["decr"]
290
- self.assertEqual(
291
- decr_conditions.pre[0].evaluate({"self": TruncatedCounter()}), True
292
- )
279
+ assert decr_conditions.pre[0].evaluate({"self": TruncatedCounter()}) is True
293
280
 
294
281
  # check the weakened precondition
295
- self.assertEqual(
296
- len(decr_conditions.pre), 2
282
+ assert (
283
+ len(decr_conditions.pre) == 2
297
284
  ) # one for the invariant, one for the disjunction
298
285
  ctr = TruncatedCounter()
299
286
  ctr.i = 1
300
- self.assertEqual(decr_conditions.pre[1].evaluate({"self": ctr}), True)
301
- self.assertEqual(decr_conditions.pre[0].evaluate({"self": ctr}), True)
287
+ assert decr_conditions.pre[1].evaluate({"self": ctr}) is True
288
+ assert decr_conditions.pre[0].evaluate({"self": ctr}) is True
302
289
  ctr.i = 0
303
- self.assertEqual(decr_conditions.pre[1].evaluate({"self": ctr}), True)
304
- self.assertEqual(decr_conditions.pre[0].evaluate({"self": ctr}), True)
290
+ assert decr_conditions.pre[1].evaluate({"self": ctr}) is True
291
+ assert decr_conditions.pre[0].evaluate({"self": ctr}) is True
305
292
 
306
293
 
307
294
  @pytest.mark.skipif(not deal, reason="deal is not installed")
@@ -395,20 +382,20 @@ def fn_with_docstring_comments_and_assert(numbers: List[int]) -> None:
395
382
  assert min(numbers) > smallest
396
383
 
397
384
 
398
- class AssertsParserTest(unittest.TestCase):
385
+ class TestAssertsParser:
399
386
  def tests_simple_parse(self) -> None:
400
387
  conditions = AssertsParser().get_fn_conditions(
401
388
  FunctionInfo.from_fn(avg_with_asserts)
402
389
  )
403
390
  assert conditions is not None
404
391
  conditions.fn([])
405
- self.assertEqual(conditions.fn([2.2]), 2.2)
406
- with self.assertRaises(AssertionError):
392
+ assert conditions.fn([2.2]) == 2.2
393
+ with pytest.raises(AssertionError):
407
394
  conditions.fn([9.2, 17.8])
408
395
 
409
396
  def tests_empty_parse(self) -> None:
410
397
  conditions = AssertsParser().get_fn_conditions(FunctionInfo.from_fn(debug))
411
- self.assertEqual(conditions, None)
398
+ assert conditions is None
412
399
 
413
400
  def tests_extra_ast_nodes(self) -> None:
414
401
  conditions = AssertsParser().get_fn_conditions(
@@ -422,10 +409,10 @@ class AssertsParserTest(unittest.TestCase):
422
409
  # normal, passing case:
423
410
  nums = [3, 1, 2]
424
411
  conditions.fn(nums)
425
- self.assertEqual(nums, [3, 2])
412
+ assert nums == [3, 2]
426
413
 
427
414
  # Failing case (duplicate minimum values):
428
- with self.assertRaises(AssertionError):
415
+ with pytest.raises(AssertionError):
429
416
  nums = [3, 1, 1, 2]
430
417
  conditions.fn(nums)
431
418
 
@@ -1,4 +1,9 @@
1
- from copy import _deepcopy_atomic # type: ignore
1
+ import sys
2
+
3
+ if sys.version_info >= (3, 14):
4
+ from copy import _atomic_types
5
+ else:
6
+ from copy import _deepcopy_atomic # type: ignore
2
7
  from copy import _deepcopy_dict # type: ignore
3
8
  from copy import _deepcopy_dispatch # type: ignore
4
9
  from copy import _deepcopy_list # type: ignore
@@ -9,7 +14,7 @@ from copy import Error
9
14
  from copyreg import dispatch_table # type: ignore
10
15
  from enum import Enum
11
16
  from types import MappingProxyType
12
- from typing import Any, Dict, Tuple
17
+ from typing import Any, Callable, Dict, Tuple
13
18
 
14
19
  from crosshair.tracers import ResumedTracing
15
20
  from crosshair.util import (
@@ -85,17 +90,28 @@ def deepcopyext(obj: object, mode: CopyMode, memo: Dict) -> Any:
85
90
  return cpy
86
91
 
87
92
 
93
+ if sys.version_info >= (3, 14):
94
+
95
+ def lookup_dispatch(cls: type) -> Callable:
96
+ if cls in _atomic_types:
97
+ return lambda obj, memo: obj
98
+ return _deepcopy_dispatch.get(cls)
99
+
100
+ else:
101
+
102
+ def lookup_dispatch(cls: type) -> Callable:
103
+ return _deepcopy_dispatch.get(cls)
104
+
105
+
88
106
  def _deepconstruct(obj: object, mode: CopyMode, memo: Dict):
89
107
  cls = type(obj)
90
108
 
91
109
  def subdeepcopy(obj: object, memo: Dict):
92
110
  return deepcopyext(obj, mode, memo)
93
111
 
94
- if cls in _deepcopy_dispatch:
95
- creator = _deepcopy_dispatch[cls]
96
- if creator is _deepcopy_atomic:
97
- return obj
98
- elif creator in (_deepcopy_dict, _deepcopy_list, _deepcopy_tuple):
112
+ creator = lookup_dispatch(cls)
113
+ if creator is not None:
114
+ if creator in (_deepcopy_dict, _deepcopy_list, _deepcopy_tuple):
99
115
  return creator(obj, memo, deepcopy=subdeepcopy)
100
116
  else:
101
117
  # TODO: We loose subdeepcopy in this case - won't
@@ -7,7 +7,7 @@ import pytest
7
7
  from crosshair.copyext import CopyMode, deepcopyext
8
8
  from crosshair.core_and_libs import proxy_for_type, standalone_statespace
9
9
  from crosshair.libimpl.builtinslib import SymbolicInt
10
- from crosshair.tracers import NoTracing, ResumedTracing
10
+ from crosshair.tracers import NoTracing
11
11
 
12
12
 
13
13
  def test_deepcopyext_best_effort():
@@ -33,6 +33,16 @@ def test_deepcopyext_symbolic_set():
33
33
  deepcopyext(s, CopyMode.REALIZE, {})
34
34
 
35
35
 
36
+ def test_deepcopyext_realize_simple(space):
37
+ x = SymbolicInt("x")
38
+ input = (x,)
39
+ output = deepcopyext(input, CopyMode.REALIZE, {})
40
+ assert input is not output
41
+ assert input[0] is not output[0]
42
+ assert type(input[0]) is SymbolicInt
43
+ assert type(output[0]) is int
44
+
45
+
36
46
  def test_deepcopyext_realize(space):
37
47
  x = SymbolicInt("x")
38
48
  lock = RLock()