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
crosshair/core_test.py CHANGED
@@ -1,15 +1,15 @@
1
1
  import dataclasses
2
+ import importlib
2
3
  import inspect
3
4
  import re
4
5
  import sys
5
6
  import time
6
- import unittest
7
7
  from typing import *
8
8
 
9
9
  import pytest # type: ignore
10
10
 
11
11
  import crosshair
12
- from crosshair import type_repo
12
+ from crosshair import core_and_libs, type_repo
13
13
  from crosshair.core import (
14
14
  deep_realize,
15
15
  get_constructor_signature,
@@ -28,7 +28,7 @@ from crosshair.core_and_libs import (
28
28
  standalone_statespace,
29
29
  )
30
30
  from crosshair.fnutil import FunctionInfo, walk_qualname
31
- from crosshair.libimpl.builtinslib import SymbolicInt
31
+ from crosshair.libimpl.builtinslib import LazyIntSymbolicStr, SymbolicInt
32
32
  from crosshair.options import DEFAULT_OPTIONS, AnalysisOptionSet
33
33
  from crosshair.statespace import (
34
34
  CANNOT_CONFIRM,
@@ -38,19 +38,14 @@ from crosshair.statespace import (
38
38
  POST_FAIL,
39
39
  )
40
40
  from crosshair.test_util import check_exec_err, check_messages, check_states
41
- from crosshair.tracers import NoTracing, is_tracing
42
- from crosshair.util import CrosshairInternal, set_debug
41
+ from crosshair.tracers import NoTracing, ResumedTracing, is_tracing
42
+ from crosshair.util import CrossHairInternal, set_debug
43
43
 
44
44
  try:
45
45
  import icontract
46
46
  except Exception:
47
47
  icontract = None # type: ignore
48
48
 
49
- try:
50
- import hypothesis
51
- except Exception:
52
- hypothesis = None # type: ignore
53
-
54
49
 
55
50
  @pytest.fixture(autouse=True)
56
51
  def check_tracer_state():
@@ -301,13 +296,12 @@ class RegularInt:
301
296
  return num
302
297
 
303
298
 
304
- class UnitTests(unittest.TestCase):
305
- def test_get_constructor_signature_with_new(self):
306
- self.assertIs(RegularInt(7), 7)
307
- params = get_constructor_signature(RegularInt).parameters
308
- self.assertEqual(len(params), 1)
309
- self.assertEqual(params["num"].name, "num")
310
- self.assertEqual(params["num"].annotation, int)
299
+ def test_get_constructor_signature_with_new():
300
+ assert RegularInt(7) == 7
301
+ params = get_constructor_signature(RegularInt).parameters
302
+ assert len(params) == 1
303
+ assert params["num"].name == "num"
304
+ assert params["num"].annotation == int
311
305
 
312
306
 
313
307
  def test_proxy_alone() -> None:
@@ -387,394 +381,451 @@ def test_exc_handling_doesnt_catch_crosshair_timeout():
387
381
  )
388
382
 
389
383
 
390
- class ObjectsTest(unittest.TestCase):
391
- def test_obj_member_fail(self) -> None:
392
- def f(foo: Pokeable) -> int:
393
- """
394
- pre: 0 <= foo.x <= 4
395
- post[foo]: _ < 5
396
- """
397
- foo.poke()
398
- foo.poke()
399
- return foo.x
384
+ def test_obj_member_fail() -> None:
385
+ def f(foo: Pokeable) -> int:
386
+ """
387
+ pre: 0 <= foo.x <= 4
388
+ post[foo]: _ < 5
389
+ """
390
+ foo.poke()
391
+ foo.poke()
392
+ return foo.x
400
393
 
401
- check_states(f, POST_FAIL)
394
+ check_states(f, POST_FAIL)
402
395
 
403
- def test_obj_member_nochange_ok(self) -> None:
404
- def f(foo: Pokeable) -> int:
405
- """post: _ == foo.x"""
406
- return foo.x
407
396
 
408
- check_states(f, CONFIRMED)
397
+ def test_obj_member_nochange_ok() -> None:
398
+ def f(foo: Pokeable) -> int:
399
+ """post: _ == foo.x"""
400
+ return foo.x
409
401
 
410
- def test_obj_member_change_ok(self) -> None:
411
- def f(foo: Pokeable) -> int:
412
- """
413
- pre: foo.x >= 0
414
- post[foo]: foo.x >= 2
415
- """
416
- foo.poke()
417
- foo.poke()
418
- return foo.x
402
+ check_states(f, CONFIRMED)
419
403
 
420
- check_states(f, CONFIRMED)
421
404
 
422
- def test_obj_member_change_detect(self) -> None:
423
- def f(foo: Pokeable) -> int:
424
- """
425
- pre: foo.x > 0
426
- post[]: True
427
- """
428
- foo.poke()
429
- return foo.x
405
+ def test_obj_member_change_ok() -> None:
406
+ def f(foo: Pokeable) -> int:
407
+ """
408
+ pre: foo.x >= 0
409
+ post[foo]: foo.x >= 2
410
+ """
411
+ foo.poke()
412
+ foo.poke()
413
+ return foo.x
430
414
 
431
- check_states(f, POST_ERR)
415
+ check_states(f, CONFIRMED)
432
416
 
433
- def test_example_second_largest(self) -> None:
434
- def second_largest(items: List[int]) -> int:
435
- """
436
- pre: len(items) == 3 # (length is to cap runtime)
437
- post: _ == sorted(items)[-2]
438
- """
439
- next_largest, largest = items[:2]
440
- if largest < next_largest:
441
- next_largest, largest = largest, next_largest
442
-
443
- for item in items[2:]:
444
- if item > largest:
445
- largest, next_largest = (item, largest)
446
- elif item > next_largest:
447
- next_largest = item
448
- return next_largest
449
-
450
- check_states(second_largest, CONFIRMED)
451
-
452
- def test_pokeable_class(self) -> None:
453
- messages = analyze_class(Pokeable)
454
- line = Pokeable.wild_pokeby.__code__.co_firstlineno
455
- self.assertEqual(
456
- *check_messages(messages, state=MessageType.POST_FAIL, line=line, column=0)
457
- )
458
417
 
459
- def test_person_class(self) -> None:
460
- messages = analyze_class(Person)
461
- self.assertEqual(*check_messages(messages, state=MessageType.CONFIRMED))
462
-
463
- def test_methods_directly(self) -> None:
464
- # Running analysis on individual methods directly works a little
465
- # differently, especially for staticmethod/classmethod. Confirm these
466
- # don't explode:
467
- messages = analyze_any(
468
- walk_qualname(Person, "a_regular_method"),
469
- AnalysisOptionSet(),
470
- )
471
- self.assertEqual(*check_messages(messages, state=MessageType.CONFIRMED))
418
+ def test_obj_member_change_detect() -> None:
419
+ def f(foo: Pokeable) -> int:
420
+ """
421
+ pre: foo.x > 0
422
+ post[]: True
423
+ """
424
+ foo.poke()
425
+ return foo.x
472
426
 
473
- def test_class_method(self) -> None:
474
- messages = analyze_any(
475
- walk_qualname(Person, "a_class_method"),
476
- AnalysisOptionSet(),
477
- )
478
- self.assertEqual(*check_messages(messages, state=MessageType.CONFIRMED))
427
+ check_states(f, POST_ERR)
479
428
 
480
- def test_static_method(self) -> None:
481
- messages = analyze_any(
482
- walk_qualname(Person, "a_static_method"),
483
- AnalysisOptionSet(),
484
- )
485
- self.assertEqual(*check_messages(messages, state=MessageType.CONFIRMED))
486
429
 
487
- def test_extend_namedtuple(self) -> None:
488
- def f(p: PersonTuple) -> PersonTuple:
489
- """
490
- post: _.age != 222
491
- """
492
- return PersonTuple(p.name, p.age + 1)
430
+ def test_example_second_largest() -> None:
431
+ def second_largest(items: List[int]) -> int:
432
+ """
433
+ pre: len(items) == 3 # (length is to cap runtime)
434
+ post: _ == sorted(items)[-2]
435
+ """
436
+ next_largest, largest = items[:2]
437
+ if largest < next_largest:
438
+ next_largest, largest = largest, next_largest
439
+
440
+ for item in items[2:]:
441
+ if item > largest:
442
+ largest, next_largest = (item, largest)
443
+ elif item > next_largest:
444
+ next_largest = item
445
+ return next_largest
446
+
447
+ check_states(second_largest, CONFIRMED)
448
+
449
+
450
+ def test_pokeable_class() -> None:
451
+ messages = analyze_class(Pokeable)
452
+ line = Pokeable.wild_pokeby.__code__.co_firstlineno
453
+ actual, expected = check_messages(
454
+ messages, state=MessageType.POST_FAIL, line=line, column=0
455
+ )
456
+ assert actual == expected
457
+
458
+
459
+ def test_person_class() -> None:
460
+ messages = analyze_class(Person)
461
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
462
+ assert actual == expected
463
+
464
+
465
+ def test_methods_directly() -> None:
466
+ # Running analysis on individual methods directly works a little
467
+ # differently, especially for staticmethod/classmethod. Confirm these
468
+ # don't explode:
469
+ messages = analyze_any(
470
+ walk_qualname(Person, "a_regular_method"),
471
+ AnalysisOptionSet(),
472
+ )
473
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
474
+ assert actual == expected
475
+
476
+
477
+ def test_class_method() -> None:
478
+ messages = analyze_any(
479
+ walk_qualname(Person, "a_class_method"),
480
+ AnalysisOptionSet(),
481
+ )
482
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
483
+ assert actual == expected
484
+
485
+
486
+ def test_static_method() -> None:
487
+ messages = analyze_any(
488
+ walk_qualname(Person, "a_static_method"),
489
+ AnalysisOptionSet(),
490
+ )
491
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
492
+ assert actual == expected
493
+
494
+
495
+ def test_extend_namedtuple() -> None:
496
+ def f(p: PersonTuple) -> PersonTuple:
497
+ """
498
+ post: _.age != 222
499
+ """
500
+ return PersonTuple(p.name, p.age + 1)
501
+
502
+ check_states(f, POST_FAIL)
503
+
504
+
505
+ def test_without_typed_attributes() -> None:
506
+ def f(p: PersonWithoutAttributes) -> PersonWithoutAttributes:
507
+ """
508
+ post: _.age != 222
509
+ """
510
+ return PersonTuple(p.name, p.age + 1) # type: ignore
511
+
512
+ check_states(f, POST_FAIL)
513
+
514
+
515
+ def test_property() -> None:
516
+ def f(p: Person) -> None:
517
+ """
518
+ pre: 0 <= p.age < 100
519
+ post[p]: p.birth + p.age == NOW
520
+ """
521
+ assert p.age == NOW - p.birth
522
+ oldbirth = p.birth
523
+ p.age = p.age + 1
524
+ assert oldbirth == p.birth + 1
525
+
526
+ check_states(f, CONFIRMED)
527
+
528
+
529
+ def test_readonly_property_contract() -> None:
530
+ class Clock:
531
+ @property
532
+ def time(self) -> int:
533
+ """post: _ == self.time"""
534
+ return 120
535
+
536
+ messages = analyze_class(Clock)
537
+ actual, expected = check_messages(messages, state=MessageType.CONFIRMED)
538
+ assert actual == expected
539
+
493
540
 
494
- check_states(f, POST_FAIL)
541
+ def test_typevar_basic() -> None:
542
+ T = TypeVar("T")
495
543
 
496
- def test_without_typed_attributes(self) -> None:
497
- def f(p: PersonWithoutAttributes) -> PersonWithoutAttributes:
544
+ @dataclasses.dataclass
545
+ class MaybePair(Generic[T]):
546
+ """
547
+ inv: (self.left is None) == (self.right is None)
548
+ """
549
+
550
+ left: Optional[T]
551
+ right: Optional[T]
552
+
553
+ def setpair(self, left: Optional[T], right: Optional[T]):
554
+ """post[self]: True"""
555
+ if (left is None) ^ (right is None):
556
+ raise ValueError("Populate both values or neither value in the pair")
557
+ self.left, self.right = left, right
558
+
559
+ messages = analyze_function(
560
+ FunctionInfo(MaybePair, "setpair", MaybePair.__dict__["setpair"])
561
+ )
562
+ actual, expected = check_messages(messages, state=MessageType.EXEC_ERR)
563
+ assert actual == expected
564
+
565
+
566
+ def test_bad_invariant():
567
+ class WithBadInvariant:
568
+ """
569
+ inv: self.item == 7
570
+ """
571
+
572
+ def do_a_thing(self) -> None:
573
+ pass
574
+
575
+ actual, expected = check_messages(
576
+ analyze_class(WithBadInvariant), state=MessageType.PRE_UNSAT
577
+ )
578
+ assert actual == expected
579
+
580
+
581
+ def test_expr_name_resolution():
582
+ """
583
+ dataclass() generates several methods. It can be tricky to ensure
584
+ that invariants for these methods can resolve names in the
585
+ correct namespace.
586
+ """
587
+ actual, expected = check_messages(
588
+ analyze_class(ReferenceHoldingClass), state=MessageType.CONFIRMED
589
+ )
590
+ assert actual == expected
591
+
592
+
593
+ def test_inheritance_base_class_ok():
594
+ actual, expected = check_messages(
595
+ analyze_class(SmokeDetector), state=MessageType.CONFIRMED
596
+ )
597
+ assert actual == expected
598
+
599
+
600
+ def test_super():
601
+ class FooDetector(SmokeDetector):
602
+ def signaling_alarm(self, air_samples: List[int]):
603
+ return super().signaling_alarm(air_samples)
604
+
605
+ actual, expected = check_messages(
606
+ analyze_class(FooDetector), state=MessageType.CONFIRMED
607
+ )
608
+ assert actual == expected
609
+
610
+
611
+ def test_use_inherited_postconditions():
612
+ class CarbonMonoxideDetector(SmokeDetector):
613
+ def signaling_alarm(self, air_samples: List[int]) -> bool:
498
614
  """
499
- post: _.age != 222
615
+ post: implies(AirSample.CO2 in air_samples, _ == True)
500
616
  """
501
- return PersonTuple(p.name, p.age + 1) # type: ignore
617
+ return AirSample.CO2 in air_samples # fails: does not detect smoke
502
618
 
503
- check_states(f, POST_FAIL)
619
+ actual, expected = check_messages(
620
+ analyze_class(CarbonMonoxideDetector), state=MessageType.POST_FAIL
621
+ )
622
+ assert actual == expected
623
+
624
+
625
+ def test_inherited_preconditions_overridable():
626
+ @dataclasses.dataclass
627
+ class SmokeDetectorWithBattery(SmokeDetector):
628
+ _battery_power: int
504
629
 
505
- def test_property(self) -> None:
506
- def f(p: Person) -> None:
630
+ def signaling_alarm(self, air_samples: List[int]) -> bool:
507
631
  """
508
- pre: 0 <= p.age < 100
509
- post[p]: p.birth + p.age == NOW
632
+ pre: self._battery_power > 0 or self._is_plugged_in
633
+ post: self._battery_power > 0
510
634
  """
511
- assert p.age == NOW - p.birth
512
- oldbirth = p.birth
513
- p.age = p.age + 1
514
- assert oldbirth == p.birth + 1
635
+ return AirSample.SMOKE in air_samples
515
636
 
516
- check_states(f, CONFIRMED)
637
+ actual, expected = check_messages(
638
+ analyze_class(SmokeDetectorWithBattery), state=MessageType.POST_FAIL
639
+ )
640
+ assert actual == expected
517
641
 
518
- def test_readonly_property_contract(self) -> None:
519
- class Clock:
520
- @property
521
- def time(self) -> int:
522
- """post: _ == self.time"""
523
- return 120
524
642
 
525
- messages = analyze_class(Clock)
526
- self.assertEqual(*check_messages(messages, state=MessageType.CONFIRMED))
643
+ def test_use_subclasses_of_arguments():
644
+ # Even though the argument below is typed as the base class, the fact
645
+ # that a faulty implementation exists is enough to produce a
646
+ # counterexample:
647
+ def f(foo: Cat) -> int:
648
+ """post: _ == 1"""
649
+ return foo.size()
527
650
 
528
- def test_typevar(self) -> None:
529
- T = TypeVar("T")
651
+ # Type repo doesn't load crosshair classes by default; load manually:
652
+ type_repo._add_class(Cat)
653
+ type_repo._add_class(BiggerCat)
654
+ check_states(f, POST_FAIL)
530
655
 
531
- @dataclasses.dataclass
532
- class MaybePair(Generic[T]):
533
- """
534
- inv: (self.left is None) == (self.right is None)
535
- """
536
656
 
537
- left: Optional[T]
538
- right: Optional[T]
657
+ def test_does_not_report_with_actual_repr():
658
+ def f(foo: BiggerCat) -> int:
659
+ """post: False"""
660
+ return foo.size()
539
661
 
540
- def setpair(self, left: Optional[T], right: Optional[T]):
541
- """post[self]: True"""
542
- if (left is None) ^ (right is None):
543
- raise ValueError(
544
- "Populate both values or neither value in the pair"
545
- )
546
- self.left, self.right = left, right
662
+ (actual, expected) = check_messages(
663
+ analyze_function(f),
664
+ state=MessageType.POST_FAIL,
665
+ message="false when calling f(BiggerCat()) " "(which returns 2)",
666
+ )
667
+ assert expected == actual
547
668
 
548
- messages = analyze_function(
549
- FunctionInfo(MaybePair, "setpair", MaybePair.__dict__["setpair"])
550
- )
551
- self.assertEqual(*check_messages(messages, state=MessageType.EXEC_ERR))
552
669
 
553
- def test_bad_invariant(self):
554
- class WithBadInvariant:
670
+ def test_check_parent_conditions():
671
+ # Ensure that conditions of parent classes are checked in children
672
+ # even when not overridden.
673
+ class Parent:
674
+ def size(self) -> int:
675
+ return 1
676
+
677
+ def amount_smaller(self, other_size: int) -> int:
555
678
  """
556
- inv: self.item == 7
679
+ pre: other_size >= 1
680
+ post: _ >= 0
557
681
  """
682
+ return other_size - self.size()
558
683
 
559
- def do_a_thing(self) -> None:
560
- pass
684
+ class Child(Parent):
685
+ def size(self) -> int:
686
+ return 2
561
687
 
562
- self.assertEqual(
563
- *check_messages(
564
- analyze_class(WithBadInvariant), state=MessageType.PRE_UNSAT
565
- )
566
- )
688
+ messages = analyze_class(Child)
689
+ actual, expected = check_messages(messages, state=MessageType.POST_FAIL)
690
+ assert actual == expected
691
+
692
+
693
+ def test_final_with_concrete_proxy():
694
+ from typing import Final
695
+
696
+ class FinalCat:
697
+ legs: Final[int] = 4
567
698
 
568
- def test_expr_name_resolution(self):
699
+ def __repr__(self):
700
+ return f"FinalCat with {self.legs} legs"
701
+
702
+ def f(cat: FinalCat, strides: int) -> int:
569
703
  """
570
- dataclass() generates several methods. It can be tricky to ensure
571
- that invariants for these methods can resolve names in the
572
- correct namespace.
704
+ pre: strides > 0
705
+ post: __return__ >= 4
573
706
  """
574
- self.assertEqual(
575
- *check_messages(
576
- analyze_class(ReferenceHoldingClass), state=MessageType.CONFIRMED
577
- )
578
- )
707
+ return strides * cat.legs
579
708
 
580
- def test_inheritance_base_class_ok(self):
581
- self.assertEqual(
582
- *check_messages(analyze_class(SmokeDetector), state=MessageType.CONFIRMED)
583
- )
709
+ check_states(f, CONFIRMED)
584
710
 
585
- def test_super(self):
586
- class FooDetector(SmokeDetector):
587
- def signaling_alarm(self, air_samples: List[int]):
588
- return super().signaling_alarm(air_samples)
589
711
 
590
- self.assertEqual(
591
- *check_messages(analyze_class(FooDetector), state=MessageType.CONFIRMED)
592
- )
712
+ # TODO: precondition strengthening check
713
+ def TODO_test_cannot_strengthen_inherited_preconditions():
714
+ class PowerHungrySmokeDetector(SmokeDetector):
715
+ _battery_power: int
593
716
 
594
- def test_use_inherited_postconditions(self):
595
- class CarbonMonoxideDetector(SmokeDetector):
596
- def signaling_alarm(self, air_samples: List[int]) -> bool:
597
- """
598
- post: implies(AirSample.CO2 in air_samples, _ == True)
599
- """
600
- return AirSample.CO2 in air_samples # fails: does not detect smoke
601
-
602
- self.assertEqual(
603
- *check_messages(
604
- analyze_class(CarbonMonoxideDetector), state=MessageType.POST_FAIL
605
- )
606
- )
717
+ def signaling_alarm(self, air_samples: List[int]) -> bool:
718
+ """
719
+ pre: self._is_plugged_in
720
+ pre: self._battery_power > 0
721
+ """
722
+ return AirSample.SMOKE in air_samples
607
723
 
608
- def test_inherited_preconditions_overridable(self):
609
- @dataclasses.dataclass
610
- class SmokeDetectorWithBattery(SmokeDetector):
611
- _battery_power: int
612
-
613
- def signaling_alarm(self, air_samples: List[int]) -> bool:
614
- """
615
- pre: self._battery_power > 0 or self._is_plugged_in
616
- post: self._battery_power > 0
617
- """
618
- return AirSample.SMOKE in air_samples
619
-
620
- self.assertEqual(
621
- *check_messages(
622
- analyze_class(SmokeDetectorWithBattery), state=MessageType.POST_FAIL
623
- )
624
- )
724
+ actual, expected = check_messages(
725
+ analyze_class(PowerHungrySmokeDetector), state=MessageType.PRE_INVALID
726
+ )
727
+ assert actual == expected
625
728
 
626
- def test_use_subclasses_of_arguments(self):
627
- # Even though the argument below is typed as the base class, the fact
628
- # that a faulty implementation exists is enough to produce a
629
- # counterexample:
630
- def f(foo: Cat) -> int:
631
- """post: _ == 1"""
632
- return foo.size()
633
-
634
- # Type repo doesn't load crosshair classes by default; load manually:
635
- type_repo._add_class(Cat)
636
- type_repo._add_class(BiggerCat)
637
- check_states(f, POST_FAIL)
638
-
639
- def test_does_not_report_with_actual_repr(self):
640
- def f(foo: BiggerCat) -> int:
641
- """post: False"""
642
- return foo.size()
643
-
644
- (actual, expected) = check_messages(
645
- analyze_function(f),
646
- state=MessageType.POST_FAIL,
647
- message="false when calling f(BiggerCat()) " "(which returns 2)",
648
- )
649
- assert expected == actual
650
729
 
651
- def test_check_parent_conditions(self):
652
- # Ensure that conditions of parent classes are checked in children
653
- # even when not overridden.
654
- class Parent:
655
- def size(self) -> int:
656
- return 1
730
+ def test_newtype() -> None:
731
+ T = TypeVar("T")
732
+ Number = NewType("Number", int)
733
+ with standalone_statespace:
734
+ x = proxy_for_type(Number, "x", allow_subtypes=False)
735
+ assert isinstance(x, SymbolicInt)
657
736
 
658
- def amount_smaller(self, other_size: int) -> int:
659
- """
660
- pre: other_size >= 1
661
- post: _ >= 0
662
- """
663
- return other_size - self.size()
664
737
 
665
- class Child(Parent):
666
- def size(self) -> int:
667
- return 2
738
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
739
+ def test_type_statement() -> None:
740
+ env: dict[str, Any] = {}
741
+ exec("type MyIntNew = int\n", env)
742
+ assert "MyIntNew" in env
743
+ MyIntNew = env["MyIntNew"]
744
+ with standalone_statespace:
745
+ x = proxy_for_type(MyIntNew, "x")
746
+ assert isinstance(x, SymbolicInt)
668
747
 
669
- messages = analyze_class(Child)
670
- self.assertEqual(*check_messages(messages, state=MessageType.POST_FAIL))
671
748
 
672
- @pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.8+ required")
673
- def test_final_with_concrete_proxy(self):
674
- from typing import Final
749
+ @pytest.mark.skipif(sys.version_info < (3, 12), reason="type statements added in 3.12")
750
+ def test_parameterized_type_statement() -> None:
751
+ env: dict[str, Any] = {}
752
+ exec("type Pair[A, B] = tuple[B, A]\n", env)
753
+ assert "Pair" in env
754
+ Pair = env["Pair"]
755
+ with standalone_statespace:
756
+ x = proxy_for_type(Pair[int, str], "x")
757
+ assert isinstance(x[0], LazyIntSymbolicStr)
758
+ assert isinstance(x[1], SymbolicInt)
675
759
 
676
- class FinalCat:
677
- legs: Final[int] = 4
678
760
 
679
- def __repr__(self):
680
- return f"FinalCat with {self.legs} legs"
761
+ def test_container_typevar() -> None:
762
+ T = TypeVar("T")
681
763
 
682
- def f(cat: FinalCat, strides: int) -> int:
683
- """
684
- pre: strides > 0
685
- post: __return__ >= 4
686
- """
687
- return strides * cat.legs
764
+ def f(s: Sequence[T]) -> Dict[T, T]:
765
+ """post: len(_) == len(s)"""
766
+ return dict(zip(s, s))
688
767
 
689
- check_states(f, CONFIRMED)
768
+ # (sequence could contain duplicate items)
769
+ check_states(f, POST_FAIL)
690
770
 
691
- # TODO: precondition strengthening check
692
- def TODO_test_cannot_strengthen_inherited_preconditions(self):
693
- class PowerHungrySmokeDetector(SmokeDetector):
694
- _battery_power: int
695
-
696
- def signaling_alarm(self, air_samples: List[int]) -> bool:
697
- """
698
- pre: self._is_plugged_in
699
- pre: self._battery_power > 0
700
- """
701
- return AirSample.SMOKE in air_samples
702
-
703
- self.assertEqual(
704
- *check_messages(
705
- analyze_class(PowerHungrySmokeDetector), state=MessageType.PRE_INVALID
706
- )
707
- )
708
771
 
709
- def test_newtype(self) -> None:
710
- T = TypeVar("T")
711
- Number = NewType("Number", int)
712
- with standalone_statespace:
713
- x = proxy_for_type(Number, "x", allow_subtypes=False)
714
- assert isinstance(x, SymbolicInt)
772
+ def test_typevar_bounds_fail() -> None:
773
+ T = TypeVar("T")
715
774
 
716
- def test_container_typevar(self) -> None:
717
- T = TypeVar("T")
775
+ def f(x: T) -> int:
776
+ """post:True"""
777
+ return x + 1 # type: ignore
718
778
 
719
- def f(s: Sequence[T]) -> Dict[T, T]:
720
- """post: len(_) == len(s)"""
721
- return dict(zip(s, s))
779
+ check_states(f, EXEC_ERR)
722
780
 
723
- # (sequence could contain duplicate items)
724
- check_states(f, POST_FAIL)
725
781
 
726
- def test_typevar_bounds_fail(self) -> None:
727
- T = TypeVar("T")
782
+ def test_typevar_bounds_ok() -> None:
783
+ B = TypeVar("B", bound=int)
728
784
 
729
- def f(x: T) -> int:
730
- """post:True"""
731
- return x + 1 # type: ignore
785
+ def f(x: B) -> int:
786
+ """post:True"""
787
+ return x + 1
732
788
 
733
- check_states(f, EXEC_ERR)
789
+ check_states(f, CONFIRMED)
734
790
 
735
- def test_typevar_bounds_ok(self) -> None:
736
- B = TypeVar("B", bound=int)
737
791
 
738
- def f(x: B) -> int:
739
- """post:True"""
740
- return x + 1
792
+ def test_any() -> None:
793
+ def f(x: Any) -> bool:
794
+ """post: True"""
795
+ return x is None
741
796
 
742
- check_states(f, CONFIRMED)
797
+ check_states(f, CONFIRMED)
743
798
 
744
- def test_any(self) -> None:
745
- def f(x: Any) -> bool:
746
- """post: True"""
747
- return x is None
748
799
 
749
- check_states(f, CONFIRMED)
800
+ def test_meeting_class_preconditions() -> None:
801
+ def f() -> int:
802
+ """
803
+ post: _ == -1
804
+ """
805
+ pokeable = Pokeable(0)
806
+ pokeable.safe_pokeby(-1)
807
+ return pokeable.x
750
808
 
751
- def test_meeting_class_preconditions(self) -> None:
752
- def f() -> int:
753
- """
754
- post: _ == -1
755
- """
756
- pokeable = Pokeable(0)
757
- pokeable.safe_pokeby(-1)
758
- return pokeable.x
809
+ analyze_function(f)
810
+ # TODO: this doesn't test anything?
759
811
 
760
- analyze_function(f)
761
- # TODO: this doesn't test anything?
762
812
 
763
- def test_enforced_fn_preconditions(self) -> None:
764
- def f(x: int) -> bool:
765
- """post: _ == True"""
766
- return bool(fibb(x)) or True
813
+ def test_enforced_fn_preconditions() -> None:
814
+ def f(x: int) -> bool:
815
+ """post: _ == True"""
816
+ return bool(fibb(x)) or True
767
817
 
768
- check_states(f, EXEC_ERR)
818
+ check_states(f, EXEC_ERR)
769
819
 
770
- def test_generic_object(self) -> None:
771
- def f(thing: object):
772
- """post: True"""
773
- if isinstance(thing, SmokeDetector):
774
- return thing._is_plugged_in
775
- return False
776
820
 
777
- check_states(f, CANNOT_CONFIRM)
821
+ def test_generic_object() -> None:
822
+ def f(thing: object):
823
+ """post: True"""
824
+ if isinstance(thing, SmokeDetector):
825
+ return thing._is_plugged_in
826
+ return False
827
+
828
+ check_states(f, CANNOT_CONFIRM)
778
829
 
779
830
 
780
831
  def get_natural_number() -> int:
@@ -811,233 +862,242 @@ def test_access_class_method_on_symbolic_type():
811
862
  person.a_class_method(42) # Just check that this don't explode
812
863
 
813
864
 
814
- class BehaviorsTest(unittest.TestCase):
815
- def test_syntax_error(self) -> None:
816
- def f(x: int):
817
- """pre: x && x"""
865
+ def test_syntax_error() -> None:
866
+ def f(x: int):
867
+ """pre: x && x"""
818
868
 
819
- self.assertEqual(
820
- *check_messages(analyze_function(f), state=MessageType.SYNTAX_ERR)
821
- )
869
+ actual, expected = check_messages(analyze_function(f), state=MessageType.SYNTAX_ERR)
870
+ assert actual == expected
822
871
 
823
- def test_raises_ok(self) -> None:
824
- def f() -> bool:
825
- """
826
- raises: IndexError, NameError
827
- post: __return__
828
- """
829
- raise IndexError()
830
- return True
831
872
 
832
- check_states(f, CONFIRMED)
873
+ def test_raises_ok() -> None:
874
+ def f() -> bool:
875
+ """
876
+ raises: IndexError, NameError
877
+ post: __return__
878
+ """
879
+ raise IndexError()
880
+ return True
833
881
 
834
- def test_optional_can_be_none_fail(self) -> None:
835
- def f(n: Optional[Pokeable]) -> bool:
836
- """post: _"""
837
- return isinstance(n, Pokeable)
882
+ check_states(f, CONFIRMED)
838
883
 
839
- check_states(f, POST_FAIL)
840
884
 
841
- def test_implicit_heapref_conversions(self) -> None:
842
- def f(foo: List[List]) -> None:
843
- """
844
- pre: len(foo) > 0
845
- post: True
846
- """
847
- foo[0].append(42)
885
+ def test_optional_can_be_none_fail() -> None:
886
+ def f(n: Optional[Pokeable]) -> bool:
887
+ """post: _"""
888
+ return isinstance(n, Pokeable)
848
889
 
849
- check_states(f, CONFIRMED)
890
+ check_states(f, POST_FAIL)
850
891
 
851
- def test_nonuniform_list_types_1(self) -> None:
852
- def f(a: List[object], b: List[int]) -> List[object]:
853
- """
854
- pre: len(b) == 5 # constraint for performance
855
- post: b[0] not in _
856
- """
857
- ret = a + b[1:] # type: ignore
858
- return ret
859
892
 
860
- check_states(f, POST_FAIL)
893
+ def test_implicit_heapref_conversions() -> None:
894
+ def f(foo: List[List]) -> None:
895
+ """
896
+ pre: len(foo) > 0
897
+ post: True
898
+ """
899
+ foo[0].append(42)
861
900
 
862
- def test_nonuniform_list_types_2(self) -> None:
863
- def f(a: List[object], b: List[int]) -> List[object]:
864
- """
865
- pre: len(b) == 5 # constraint for performance
866
- post: b[-1] not in _
867
- """
868
- return a + b[:-1] # type: ignore
901
+ check_states(f, CONFIRMED)
869
902
 
870
- check_states(f, POST_FAIL)
871
903
 
872
- def test_varargs_fail(self) -> None:
873
- def f(x: int, *a: str, **kw: bool) -> int:
874
- """post: _ > x"""
875
- return x + len(a) + (42 if kw else 0)
904
+ def test_nonuniform_list_types_1() -> None:
905
+ def f(a: List[object], b: List[int]) -> List[object]:
906
+ """
907
+ pre: len(b) == 5 # constraint for performance
908
+ post: b[0] not in _
909
+ """
910
+ ret = a + b[1:] # type: ignore
911
+ return ret
876
912
 
877
- check_states(f, POST_FAIL)
913
+ check_states(f, POST_FAIL)
878
914
 
879
- def test_varargs_ok(self) -> None:
880
- def f(x: int, *a: str, **kw: bool) -> int:
881
- """post: _ >= x"""
882
- return x + len(a) + (42 if kw else 0)
883
915
 
884
- check_states(f, CANNOT_CONFIRM)
916
+ def test_nonuniform_list_types_2() -> None:
917
+ def f(a: List[object], b: List[int]) -> List[object]:
918
+ """
919
+ pre: len(b) == 5 # constraint for performance
920
+ post: b[-1] not in _
921
+ """
922
+ return a + b[:-1] # type: ignore
885
923
 
886
- def test_recursive_fn_fail(self) -> None:
887
- check_states(fibb, POST_FAIL)
924
+ check_states(f, POST_FAIL)
888
925
 
889
- def test_recursive_fn_ok(self) -> None:
890
- check_states(recursive_example, CONFIRMED)
891
926
 
892
- def test_recursive_postcondition_ok(self) -> None:
893
- def f(x: int) -> int:
894
- """post: _ == f(-x)"""
895
- return x * x
927
+ def test_varargs_fail() -> None:
928
+ def f(x: int, *a: str, **kw: bool) -> int:
929
+ """post: _ > x"""
930
+ return x + len(a) + (42 if kw else 0)
896
931
 
897
- check_states(f, CONFIRMED)
932
+ check_states(f, POST_FAIL)
898
933
 
899
- def test_reentrant_precondition(self) -> None:
900
- # Really, we're just ensuring that we don't stack-overflow here.
901
- check_states(reentrant_precondition, CONFIRMED)
902
-
903
- def test_recursive_postcondition_enforcement_suspension(self) -> None:
904
- messages = analyze_class(Measurer)
905
- self.assertEqual(*check_messages(messages, state=MessageType.POST_FAIL))
906
-
907
- def test_short_circuiting(self) -> None:
908
- # Some operations are hard to deal with symbolically, like hashes.
909
- # CrossHair will sometimes "short-circuit" functions, in hopes that the
910
- # function body isn't required to prove the postcondition.
911
- # This is an example of such a case.
912
- def f(x: str) -> int:
913
- """post: _ == 0"""
914
- a = hash(x)
915
- b = 7
916
- # This is zero no matter what the hashes are:
917
- return (a + b) - (b + a)
918
934
 
919
- check_states(f, CONFIRMED)
935
+ def test_varargs_ok() -> None:
936
+ def f(x: int, *a: str, **kw: bool) -> int:
937
+ """post: _ >= x"""
938
+ return x + len(a) + (42 if kw else 0)
920
939
 
921
- def test_error_message_in_unrelated_method(self) -> None:
922
- messages = analyze_class(OverloadedContainer)
923
- line = ShippingContainer.total_weight.__code__.co_firstlineno + 1
924
- self.assertEqual(
925
- *check_messages(
926
- messages,
927
- state=MessageType.POST_FAIL,
928
- message="false when calling total_weight(OverloadedContainer()) (which returns 13)",
929
- line=line,
930
- )
931
- )
940
+ check_states(f, CANNOT_CONFIRM)
932
941
 
933
- def test_error_message_has_unmodified_args(self) -> None:
934
- def f(foo: List[Pokeable]) -> None:
935
- """
936
- pre: len(foo) == 1
937
- pre: foo[0].x == 10
938
- post[foo]: foo[0].x == 12
939
- """
940
- foo[0].poke()
941
942
 
942
- self.assertEqual(
943
- *check_messages(
944
- analyze_function(f),
945
- state=MessageType.POST_FAIL,
946
- message="false when calling f([Pokeable(x=10)])",
947
- )
948
- )
943
+ def test_recursive_fn_fail() -> None:
944
+ check_states(fibb, POST_FAIL)
949
945
 
950
- # TODO: List[List] involves no HeapRefs
951
- def TODO_test_potential_circular_references(self) -> None:
952
- # TODO?: potential aliasing of input argument data?
953
- def f(foo: List[List], thing: object) -> None:
954
- """
955
- pre: len(foo) == 2
956
- pre: len(foo[0]) == 1
957
- pre: len(foo[1]) == 1
958
- post: len(foo[1]) == 1
959
- """
960
- foo[0].append(object()) # TODO: using 42 yields a z3 sort error
961
946
 
962
- check_states(f, CONFIRMED)
947
+ def test_recursive_fn_ok() -> None:
948
+ check_states(recursive_example, CONFIRMED)
963
949
 
964
- def test_nonatomic_comparison(self) -> None:
965
- def f(x: int, ls: List[str]) -> bool:
966
- """post: not _"""
967
- return ls == x
968
950
 
969
- check_states(f, CONFIRMED)
951
+ def test_recursive_postcondition_ok() -> None:
952
+ def f(x: int) -> int:
953
+ """post: _ == f(-x)"""
954
+ return x * x
970
955
 
971
- def test_difficult_equality(self) -> None:
972
- def f(x: Dict[FrozenSet[float], int]) -> bool:
973
- """post: not _"""
974
- return x == {frozenset({10.0}): 1}
956
+ check_states(f, CONFIRMED)
975
957
 
976
- check_states(f, POST_FAIL)
977
958
 
978
- def test_nondeterminisim_detected(self) -> None:
979
- _GLOBAL_THING = [True]
959
+ def test_reentrant_precondition() -> None:
960
+ # Really, we're just ensuring that we don't stack-overflow here.
961
+ check_states(reentrant_precondition, CONFIRMED)
980
962
 
981
- def f(i: int) -> int:
982
- """post: True"""
983
- _GLOBAL_THING[0] = not _GLOBAL_THING[0]
984
- if _GLOBAL_THING[0]:
985
- return -i if i < 0 else i
986
- else:
987
- return -i if i < 0 else i
988
963
 
989
- self.assertEqual(*check_exec_err(f, "NotDeterministic"))
964
+ def test_recursive_postcondition_enforcement_suspension() -> None:
965
+ messages = analyze_class(Measurer)
966
+ actual, expected = check_messages(messages, state=MessageType.POST_FAIL)
967
+ assert actual == expected
990
968
 
991
- def test_old_works_in_invariants(self) -> None:
992
- @dataclasses.dataclass
993
- class FrozenApples:
994
- """inv: self.count == __old__.self.count"""
995
969
 
996
- count: int
970
+ def test_short_circuiting() -> None:
971
+ # Some operations are hard to deal with symbolically, like hashes.
972
+ # CrossHair will sometimes "short-circuit" functions, in hopes that the
973
+ # function body isn't required to prove the postcondition.
974
+ # This is an example of such a case.
975
+ def f(x: str) -> int:
976
+ """post: _ == 0"""
977
+ a = hash(x)
978
+ b = 7
979
+ # This is zero no matter what the hashes are:
980
+ return (a + b) - (b + a)
997
981
 
998
- def add_one(self):
999
- self.count += 1
982
+ check_states(f, CONFIRMED)
1000
983
 
1001
- messages = analyze_class(FrozenApples)
1002
- self.assertEqual(*check_messages(messages, state=MessageType.POST_FAIL))
1003
984
 
1004
- # Also confirm we can create one as an argument:
1005
- def f(a: FrozenApples) -> int:
1006
- """post: True"""
1007
- return 0
985
+ def test_error_message_in_unrelated_method() -> None:
986
+ messages = analyze_class(OverloadedContainer)
987
+ line = ShippingContainer.total_weight.__code__.co_firstlineno + 1
988
+ actual, expected = check_messages(
989
+ messages,
990
+ state=MessageType.POST_FAIL,
991
+ message="false when calling total_weight(OverloadedContainer()) (which returns 13)",
992
+ line=line,
993
+ )
994
+ assert actual == expected
995
+
1008
996
 
1009
- check_states(f, CONFIRMED)
997
+ def test_error_message_has_unmodified_args() -> None:
998
+ def f(foo: List[Pokeable]) -> None:
999
+ """
1000
+ pre: len(foo) == 1
1001
+ pre: foo[0].x == 10
1002
+ post[foo]: foo[0].x == 12
1003
+ """
1004
+ foo[0].poke()
1005
+
1006
+ actual, expected = check_messages(
1007
+ analyze_function(f),
1008
+ state=MessageType.POST_FAIL,
1009
+ message="false when calling f([Pokeable(x=10)])",
1010
+ )
1011
+ assert actual == expected
1012
+
1013
+
1014
+ # TODO: List[List] involves no HeapRefs
1015
+ def TODO_test_potential_circular_references() -> None:
1016
+ # TODO?: potential aliasing of input argument data?
1017
+ def f(foo: List[List], thing: object) -> None:
1018
+ """
1019
+ pre: len(foo) == 2
1020
+ pre: len(foo[0]) == 1
1021
+ pre: len(foo[1]) == 1
1022
+ post: len(foo[1]) == 1
1023
+ """
1024
+ foo[0].append(object()) # TODO: using 42 yields a z3 sort error
1010
1025
 
1011
- def test_class_patching_is_undone(self) -> None:
1012
- # CrossHair does a lot of monkey matching of classes
1013
- # with contracts. Ensure that gets undone.
1014
- original_container = ShippingContainer.__dict__.copy()
1015
- original_overloaded = OverloadedContainer.__dict__.copy()
1016
- run_checkables(analyze_class(OverloadedContainer))
1017
- for k, v in original_container.items():
1018
- self.assertIs(ShippingContainer.__dict__[k], v)
1019
- for k, v in original_overloaded.items():
1020
- self.assertIs(OverloadedContainer.__dict__[k], v)
1021
-
1022
- def test_fallback_when_smt_values_out_themselves(self) -> None:
1023
- def f(items: List[str]) -> str:
1024
- """post: True"""
1025
- return ",".join(items)
1026
-
1027
- check_states(f, CANNOT_CONFIRM)
1028
-
1029
- def test_unrelated_regex(self) -> None:
1030
- def f(s: str) -> bool:
1031
- """post: True"""
1032
- return bool(re.match(r"(\d+)", s))
1033
-
1034
- check_states(f, CANNOT_CONFIRM)
1035
-
1036
- @pytest.mark.skip("Python 3.9+ is not supported yet")
1037
- def test_new_style_type_hints(self):
1026
+ check_states(f, CONFIRMED)
1027
+
1028
+
1029
+ def test_nonatomic_comparison() -> None:
1030
+ def f(x: int, ls: List[str]) -> bool:
1031
+ """post: not _"""
1032
+ return ls == x
1033
+
1034
+ check_states(f, CONFIRMED)
1035
+
1036
+
1037
+ def test_difficult_equality() -> None:
1038
+ def f(x: Dict[FrozenSet[float], int]) -> bool:
1039
+ """post: not _"""
1040
+ return x == {frozenset({10.0}): 1}
1041
+
1042
+ check_states(f, POST_FAIL)
1043
+
1044
+
1045
+ def test_old_works_in_invariants() -> None:
1046
+ @dataclasses.dataclass
1047
+ class FrozenApples:
1048
+ """inv: self.count == __old__.self.count"""
1049
+
1050
+ count: int
1051
+
1052
+ def add_one(self):
1053
+ self.count += 1
1054
+
1055
+ messages = analyze_class(FrozenApples)
1056
+ actual, expected = check_messages(messages, state=MessageType.POST_FAIL)
1057
+ assert actual == expected
1058
+
1059
+ # Also confirm we can create one as an argument:
1060
+ def f(a: FrozenApples) -> int:
1061
+ """post: True"""
1062
+ return 0
1063
+
1064
+ check_states(f, CONFIRMED)
1065
+
1066
+
1067
+ def test_class_patching_is_undone() -> None:
1068
+ # CrossHair does a lot of monkey matching of classes
1069
+ # with contracts. Ensure that gets undone.
1070
+ original_container = ShippingContainer.__dict__.copy()
1071
+ original_overloaded = OverloadedContainer.__dict__.copy()
1072
+ run_checkables(analyze_class(OverloadedContainer))
1073
+ for k, v in original_container.items():
1074
+ assert ShippingContainer.__dict__[k] is v
1075
+ for k, v in original_overloaded.items():
1076
+ assert OverloadedContainer.__dict__[k] is v
1077
+
1078
+
1079
+ def test_fallback_when_smt_values_out_themselves() -> None:
1080
+ def f(items: List[str]) -> str:
1081
+ """post: True"""
1082
+ return ",".join(items)
1083
+
1084
+ check_states(f, CANNOT_CONFIRM)
1085
+
1086
+
1087
+ def test_unrelated_regex() -> None:
1088
+ def f(s: str) -> bool:
1089
+ """post: True"""
1090
+ return bool(re.match(r"(\d+)", s))
1091
+
1092
+ check_states(f, CANNOT_CONFIRM)
1093
+
1094
+
1095
+ if sys.version_info >= (3, 9):
1096
+
1097
+ def test_new_style_type_hints():
1038
1098
  def f(ls: list[int]) -> List[int]:
1039
1099
  """
1040
- pre: len(ls) == 2
1100
+ pre: len(ls) == 1
1041
1101
  post: _[0] != 'a'
1042
1102
  """
1043
1103
  return ls
@@ -1045,169 +1105,125 @@ class BehaviorsTest(unittest.TestCase):
1045
1105
  check_states(f, CONFIRMED)
1046
1106
 
1047
1107
 
1048
- if icontract:
1108
+ def test_nondeterministic_detected_via_condition() -> None:
1109
+ _GLOBAL_THING = [42]
1049
1110
 
1050
- class TestIcontract(unittest.TestCase):
1051
- def test_icontract_basic(self):
1052
- @icontract.ensure(lambda result, x: result > x)
1053
- def some_func(x: int, y: int = 5) -> int:
1054
- return x - y
1111
+ def f(i: int) -> int:
1112
+ """post: True"""
1113
+ _GLOBAL_THING[0] += 1
1114
+ if i > _GLOBAL_THING[0]:
1115
+ pass
1116
+ return True
1055
1117
 
1056
- check_states(some_func, POST_FAIL)
1118
+ actual, expected = check_exec_err(f, "NotDeterministic")
1119
+ assert actual == expected
1057
1120
 
1058
- def test_icontract_snapshots(self):
1059
- messages = analyze_function(
1060
- icontract_appender,
1061
- DEFAULT_OPTIONS,
1062
- )
1063
- line = icontract_appender.__wrapped__.__code__.co_firstlineno + 1
1064
- self.assertEqual(
1065
- *check_messages(
1066
- messages, state=MessageType.POST_FAIL, line=line, column=0
1067
- )
1068
- )
1069
1121
 
1070
- def test_icontract_weaken(self):
1071
- @icontract.require(lambda x: x in (2, 3))
1072
- @icontract.ensure(lambda: True)
1073
- def trynum(x: int):
1074
- IcontractB().weakenedfunc(x)
1075
-
1076
- check_states(trynum, CONFIRMED)
1077
-
1078
- def test_icontract_class(self):
1079
- messages = run_checkables(
1080
- analyze_class(
1081
- IcontractB,
1082
- # TODO: why is this required?
1083
- DEFAULT_OPTIONS.overlay(analysis_kind=[AnalysisKind.icontract]),
1084
- )
1085
- )
1086
- messages = {
1087
- (m.state, m.line, m.message)
1088
- for m in messages
1089
- if m.state != MessageType.CONFIRMED
1090
- }
1091
- line_gt0 = (
1092
- IcontractB.break_parent_invariant.__wrapped__.__code__.co_firstlineno
1093
- )
1094
- line_lt100 = (
1095
- IcontractB.break_my_invariant.__wrapped__.__code__.co_firstlineno
1096
- )
1097
- self.assertEqual(
1098
- messages,
1099
- {
1100
- (
1101
- MessageType.POST_FAIL,
1102
- line_gt0,
1103
- '"@icontract.invariant(lambda self: self.x > 0)" yields false '
1104
- "when calling break_parent_invariant(IcontractB())",
1105
- ),
1106
- (
1107
- MessageType.POST_FAIL,
1108
- line_lt100,
1109
- '"@icontract.invariant(lambda self: self.x < 100)" yields false '
1110
- "when calling break_my_invariant(IcontractB())",
1111
- ),
1112
- },
1113
- )
1122
+ def test_nondeterministic_detected_in_detached_path() -> None:
1123
+ _GLOBAL_THING = [True]
1114
1124
 
1115
- def test_icontract_nesting(self):
1116
- @icontract.require(lambda name: name.startswith("a"))
1117
- def innerfn(name: str):
1118
- pass
1119
-
1120
- @icontract.ensure(lambda: True)
1121
- @icontract.require(lambda name: len(name) > 0)
1122
- def outerfn(name: str):
1123
- innerfn("00" + name)
1124
-
1125
- self.assertEqual(
1126
- *check_exec_err(
1127
- outerfn,
1128
- message_prefix="PreconditionFailed",
1129
- )
1130
- )
1125
+ def f(i: int) -> int:
1126
+ """post: True"""
1127
+ _GLOBAL_THING[0] = not _GLOBAL_THING[0]
1128
+ if _GLOBAL_THING[0]:
1129
+ raise Exception
1130
+ else:
1131
+ return -i if i < 0 else i
1131
1132
 
1133
+ actual, expected = check_exec_err(f, "NotDeterministic")
1134
+ assert actual == expected
1132
1135
 
1133
- if hypothesis:
1134
- from hypothesis.database import InMemoryExampleDatabase
1135
1136
 
1136
- @hypothesis.given(hypothesis.strategies.booleans())
1137
- def foo(x):
1138
- assert x
1137
+ if icontract:
1139
1138
 
1140
- def test_hypothesis_counterexample_text():
1139
+ def test_icontract_basic():
1140
+ @icontract.ensure(lambda result, x: result > x)
1141
+ def some_func(x: int, y: int = 5) -> int:
1142
+ return x - y
1143
+
1144
+ check_states(some_func, POST_FAIL)
1145
+
1146
+ def test_icontract_snapshots():
1141
1147
  messages = analyze_function(
1142
- foo,
1143
- DEFAULT_OPTIONS.overlay(
1144
- analysis_kind=[AnalysisKind.hypothesis],
1145
- max_iterations=10,
1146
- ),
1148
+ icontract_appender,
1149
+ DEFAULT_OPTIONS,
1147
1150
  )
1151
+ line = icontract_appender.__wrapped__.__code__.co_firstlineno + 1
1148
1152
  actual, expected = check_messages(
1149
- messages,
1150
- state=MessageType.EXEC_ERR,
1151
- message="AssertionError: assert False when calling foo(False)",
1153
+ messages, state=MessageType.POST_FAIL, line=line, column=0
1152
1154
  )
1153
1155
  assert actual == expected
1154
1156
 
1155
- db = InMemoryExampleDatabase()
1156
- called = False
1157
-
1158
- @hypothesis.given(hypothesis.strategies.booleans())
1159
- @hypothesis.settings(database=db, phases=[hypothesis.Phase.reuse])
1160
- def bar(x):
1161
- global called
1162
- called = True
1163
- assert x
1164
-
1165
- def test_hypothesis_database_interaction():
1166
- # Before we do anything, the wrapped test hasn't been called and the db is empty
1167
- global called
1168
- assert not called
1169
- assert not db.data
1157
+ def test_icontract_weaken():
1158
+ @icontract.require(lambda x: x in (2, 3))
1159
+ @icontract.ensure(lambda: True)
1160
+ def trynum(x: int):
1161
+ IcontractB().weakenedfunc(x)
1170
1162
 
1171
- # Calling the test doesn't change that, because it only replays examples
1172
- bar()
1173
- assert not called
1174
- assert not db.data
1163
+ check_states(trynum, CONFIRMED)
1175
1164
 
1176
- # Running CrossHair *does* call it, and write to the database
1177
- messages = analyze_function(
1178
- bar,
1179
- DEFAULT_OPTIONS.overlay(
1180
- analysis_kind=[AnalysisKind.hypothesis],
1181
- max_iterations=10,
1182
- ),
1183
- )
1184
- actual, expected = check_messages(
1185
- messages,
1186
- state=MessageType.EXEC_ERR,
1165
+ @pytest.mark.skip(
1166
+ reason="Temporary disable: RecursionError on 3.11.13 - icontract's _IN_PROGRESS + CrossHair laziness cross-test leaks"
1167
+ )
1168
+ def test_icontract_class():
1169
+ messages = run_checkables(
1170
+ analyze_class(
1171
+ IcontractB,
1172
+ # TODO: why is this required?
1173
+ DEFAULT_OPTIONS.overlay(analysis_kind=[AnalysisKind.icontract]),
1174
+ )
1187
1175
  )
1188
- assert called
1189
- assert db.data
1176
+ messages = {
1177
+ (m.state, m.line, m.message)
1178
+ for m in messages
1179
+ if m.state != MessageType.CONFIRMED
1180
+ }
1181
+ line_gt0 = IcontractB.break_parent_invariant.__wrapped__.__code__.co_firstlineno
1182
+ line_lt100 = IcontractB.break_my_invariant.__wrapped__.__code__.co_firstlineno
1183
+ assert messages == {
1184
+ (
1185
+ MessageType.POST_FAIL,
1186
+ line_gt0,
1187
+ '"@icontract.invariant(lambda self: self.x > 0)" yields false '
1188
+ "when calling break_parent_invariant(IcontractB())",
1189
+ ),
1190
+ (
1191
+ MessageType.POST_FAIL,
1192
+ line_lt100,
1193
+ '"@icontract.invariant(lambda self: self.x < 100)" yields false '
1194
+ "when calling break_my_invariant(IcontractB())",
1195
+ ),
1196
+ }
1190
1197
 
1191
- # Now, if we call the function again it'll pick that up and fail!
1192
- called = False
1193
- with pytest.raises(AssertionError):
1194
- bar()
1195
- assert called
1198
+ def test_icontract_nesting():
1199
+ @icontract.require(lambda name: name.startswith("a"))
1200
+ def innerfn(name: str):
1201
+ pass
1196
1202
 
1203
+ @icontract.ensure(lambda: True)
1204
+ @icontract.require(lambda name: len(name) > 0)
1205
+ def outerfn(name: str):
1206
+ innerfn("00" + name)
1197
1207
 
1198
- class TestAssertsMode(unittest.TestCase):
1199
- def test_asserts(self):
1200
- messages = analyze_function(
1201
- remove_smallest_with_asserts,
1202
- DEFAULT_OPTIONS.overlay(
1203
- analysis_kind=[AnalysisKind.asserts],
1204
- max_iterations=10,
1205
- ),
1206
- )
1207
- line = remove_smallest_with_asserts.__code__.co_firstlineno + 4
1208
- self.assertEqual(
1209
- *check_messages(messages, state=MessageType.EXEC_ERR, line=line, column=0)
1208
+ actual, expected = check_exec_err(
1209
+ outerfn,
1210
+ message_prefix="PreconditionFailed",
1210
1211
  )
1212
+ assert actual == expected
1213
+
1214
+
1215
+ def test_asserts():
1216
+ messages = analyze_function(
1217
+ remove_smallest_with_asserts,
1218
+ DEFAULT_OPTIONS.overlay(
1219
+ analysis_kind=[AnalysisKind.asserts],
1220
+ ),
1221
+ )
1222
+ line = remove_smallest_with_asserts.__code__.co_firstlineno + 4
1223
+ expected, actual = check_messages(
1224
+ messages, state=MessageType.EXEC_ERR, line=line, column=0
1225
+ )
1226
+ assert expected == actual
1211
1227
 
1212
1228
 
1213
1229
  def test_unpickable_args() -> None:
@@ -1227,10 +1243,21 @@ def test_unpickable_args() -> None:
1227
1243
  check_states(dothing, POST_FAIL)
1228
1244
 
1229
1245
 
1246
+ def test_kwargs(space):
1247
+ def callme(lu=3, **kw):
1248
+ return 42
1249
+
1250
+ kwargs = proxy_for_type(Dict[str, int], "kwargs")
1251
+ with ResumedTracing():
1252
+ space.add(kwargs.__len__() == 1)
1253
+ assert callme(**kwargs) == 42 # (this is a CALL_FUNCTION_EX opcode)
1254
+
1255
+
1230
1256
  @pytest.mark.smoke
1231
1257
  def test_deep_realize(space):
1232
1258
  x = proxy_for_type(int, "x")
1233
- space.add(x.var == 4)
1259
+ with ResumedTracing():
1260
+ space.add(x == 4)
1234
1261
 
1235
1262
  @dataclasses.dataclass
1236
1263
  class Woo:
@@ -1257,6 +1284,10 @@ def test_is_not_deeply_immutable(o):
1257
1284
  assert not is_deeply_immutable(o)
1258
1285
 
1259
1286
 
1287
+ def test_crosshair_modules_can_be_reloaded():
1288
+ importlib.reload(core_and_libs)
1289
+
1290
+
1260
1291
  def profile():
1261
1292
  # This is a scratch area to run quick profiles.
1262
1293
  def f(x: int) -> int:
@@ -1283,5 +1314,3 @@ if __name__ == "__main__":
1283
1314
  import cProfile
1284
1315
 
1285
1316
  cProfile.run("profile()", "out.pprof")
1286
- else:
1287
- unittest.main()