crosshair-tool 0.0.83__cp39-cp39-macosx_10_9_universal2.whl → 0.0.85__cp39-cp39-macosx_10_9_universal2.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.

Potentially problematic release.


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

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