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.
- _crosshair_tracers.cpython-39-darwin.so +0 -0
- crosshair/__init__.py +1 -1
- crosshair/_mark_stacks.h +51 -24
- crosshair/_tracers.h +9 -5
- crosshair/_tracers_test.py +19 -9
- crosshair/auditwall.py +9 -8
- crosshair/auditwall_test.py +31 -19
- crosshair/codeconfig.py +3 -2
- crosshair/condition_parser.py +17 -133
- crosshair/condition_parser_test.py +54 -96
- crosshair/conftest.py +1 -1
- crosshair/copyext.py +91 -22
- crosshair/copyext_test.py +33 -0
- crosshair/core.py +259 -203
- crosshair/core_and_libs.py +20 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +693 -664
- crosshair/diff_behavior.py +76 -21
- crosshair/diff_behavior_test.py +132 -23
- crosshair/dynamic_typing.py +128 -18
- crosshair/dynamic_typing_test.py +91 -4
- crosshair/enforce.py +1 -6
- crosshair/enforce_test.py +15 -23
- crosshair/examples/check_examples_test.py +2 -1
- crosshair/fnutil.py +2 -3
- crosshair/fnutil_test.py +0 -7
- crosshair/fuzz_core_test.py +70 -83
- crosshair/libimpl/arraylib.py +10 -7
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +5 -5
- crosshair/libimpl/builtinslib.py +1002 -682
- crosshair/libimpl/builtinslib_ch_test.py +108 -30
- crosshair/libimpl/builtinslib_test.py +431 -143
- crosshair/libimpl/codecslib.py +22 -2
- crosshair/libimpl/codecslib_test.py +41 -9
- crosshair/libimpl/collectionslib.py +44 -8
- crosshair/libimpl/collectionslib_test.py +108 -20
- crosshair/libimpl/copylib.py +1 -1
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +84 -67
- crosshair/libimpl/datetimelib_ch_test.py +12 -7
- crosshair/libimpl/datetimelib_test.py +5 -6
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/_encutil.py +21 -11
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +19 -7
- crosshair/libimpl/functoolslib_test.py +22 -6
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +32 -5
- crosshair/libimpl/heapqlib_test.py +15 -12
- crosshair/libimpl/iolib.py +7 -4
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib_test.py +1 -1
- crosshair/libimpl/mathlib.py +165 -2
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +59 -16
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +1 -0
- crosshair/libimpl/randomlib_test.py +6 -4
- crosshair/libimpl/relib.py +180 -59
- crosshair/libimpl/relib_ch_test.py +26 -2
- crosshair/libimpl/relib_test.py +77 -14
- crosshair/libimpl/timelib.py +35 -13
- crosshair/libimpl/timelib_test.py +13 -3
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib_test.py +3 -3
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +21 -10
- crosshair/main.py +48 -28
- crosshair/main_test.py +59 -14
- crosshair/objectproxy.py +39 -14
- crosshair/objectproxy_test.py +27 -13
- crosshair/opcode_intercept.py +212 -24
- crosshair/opcode_intercept_test.py +172 -18
- crosshair/options.py +0 -1
- crosshair/patch_equivalence_test.py +5 -21
- crosshair/path_cover.py +7 -5
- crosshair/path_search.py +6 -4
- crosshair/path_search_test.py +1 -2
- crosshair/pathing_oracle.py +53 -10
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer_test.py +5 -21
- crosshair/register_contract.py +16 -6
- crosshair/register_contract_test.py +2 -14
- crosshair/simplestructs.py +154 -85
- crosshair/simplestructs_test.py +16 -2
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +319 -196
- crosshair/statespace_test.py +45 -0
- crosshair/stubs_parser.py +0 -2
- crosshair/test_util.py +87 -25
- crosshair/test_util_test.py +26 -0
- crosshair/tools/check_init_and_setup_coincide.py +0 -3
- crosshair/tools/generate_demo_table.py +2 -2
- crosshair/tracers.py +141 -49
- crosshair/type_repo.py +11 -4
- crosshair/unicode_categories.py +1 -0
- crosshair/util.py +158 -76
- crosshair/util_test.py +13 -20
- crosshair/watcher.py +4 -4
- crosshair/z3util.py +1 -1
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/METADATA +45 -36
- crosshair_tool-0.0.100.dist-info/RECORD +176 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/WHEEL +2 -1
- crosshair/examples/hypothesis/__init__.py +0 -2
- crosshair/examples/hypothesis/bugs_detected/simple_strategies.py +0 -74
- crosshair_tool-0.0.56.dist-info/RECORD +0 -152
- /crosshair/{examples/hypothesis/bugs_detected/__init__.py → py.typed} +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info}/entry_points.txt +0 -0
- {crosshair_tool-0.0.56.dist-info → crosshair_tool-0.0.100.dist-info/licenses}/LICENSE +0 -0
- {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
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
391
|
-
def
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
541
|
+
def test_typevar_basic() -> None:
|
|
542
|
+
T = TypeVar("T")
|
|
495
543
|
|
|
496
|
-
|
|
497
|
-
|
|
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:
|
|
615
|
+
post: implies(AirSample.CO2 in air_samples, _ == True)
|
|
500
616
|
"""
|
|
501
|
-
return
|
|
617
|
+
return AirSample.CO2 in air_samples # fails: does not detect smoke
|
|
502
618
|
|
|
503
|
-
|
|
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
|
-
|
|
506
|
-
def f(p: Person) -> None:
|
|
630
|
+
def signaling_alarm(self, air_samples: List[int]) -> bool:
|
|
507
631
|
"""
|
|
508
|
-
pre: 0
|
|
509
|
-
post
|
|
632
|
+
pre: self._battery_power > 0 or self._is_plugged_in
|
|
633
|
+
post: self._battery_power > 0
|
|
510
634
|
"""
|
|
511
|
-
|
|
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
|
-
|
|
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
|
-
|
|
526
|
-
|
|
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
|
-
|
|
529
|
-
|
|
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
|
-
|
|
538
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
554
|
-
|
|
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
|
-
|
|
679
|
+
pre: other_size >= 1
|
|
680
|
+
post: _ >= 0
|
|
557
681
|
"""
|
|
682
|
+
return other_size - self.size()
|
|
558
683
|
|
|
559
|
-
|
|
560
|
-
|
|
684
|
+
class Child(Parent):
|
|
685
|
+
def size(self) -> int:
|
|
686
|
+
return 2
|
|
561
687
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
699
|
+
def __repr__(self):
|
|
700
|
+
return f"FinalCat with {self.legs} legs"
|
|
701
|
+
|
|
702
|
+
def f(cat: FinalCat, strides: int) -> int:
|
|
569
703
|
"""
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
correct namespace.
|
|
704
|
+
pre: strides > 0
|
|
705
|
+
post: __return__ >= 4
|
|
573
706
|
"""
|
|
574
|
-
|
|
575
|
-
*check_messages(
|
|
576
|
-
analyze_class(ReferenceHoldingClass), state=MessageType.CONFIRMED
|
|
577
|
-
)
|
|
578
|
-
)
|
|
707
|
+
return strides * cat.legs
|
|
579
708
|
|
|
580
|
-
|
|
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
|
-
|
|
591
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
-
|
|
680
|
-
|
|
761
|
+
def test_container_typevar() -> None:
|
|
762
|
+
T = TypeVar("T")
|
|
681
763
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
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
|
-
|
|
710
|
-
|
|
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
|
|
717
|
-
|
|
775
|
+
def f(x: T) -> int:
|
|
776
|
+
"""post:True"""
|
|
777
|
+
return x + 1 # type: ignore
|
|
718
778
|
|
|
719
|
-
|
|
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
|
-
|
|
727
|
-
|
|
782
|
+
def test_typevar_bounds_ok() -> None:
|
|
783
|
+
B = TypeVar("B", bound=int)
|
|
728
784
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
785
|
+
def f(x: B) -> int:
|
|
786
|
+
"""post:True"""
|
|
787
|
+
return x + 1
|
|
732
788
|
|
|
733
|
-
|
|
789
|
+
check_states(f, CONFIRMED)
|
|
734
790
|
|
|
735
|
-
def test_typevar_bounds_ok(self) -> None:
|
|
736
|
-
B = TypeVar("B", bound=int)
|
|
737
791
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
792
|
+
def test_any() -> None:
|
|
793
|
+
def f(x: Any) -> bool:
|
|
794
|
+
"""post: True"""
|
|
795
|
+
return x is None
|
|
741
796
|
|
|
742
|
-
|
|
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
|
-
|
|
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
|
-
|
|
752
|
-
|
|
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
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
815
|
-
def
|
|
816
|
-
|
|
817
|
-
"""pre: x && x"""
|
|
865
|
+
def test_syntax_error() -> None:
|
|
866
|
+
def f(x: int):
|
|
867
|
+
"""pre: x && x"""
|
|
818
868
|
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
943
|
-
|
|
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
|
-
|
|
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
|
-
|
|
951
|
+
def test_recursive_postcondition_ok() -> None:
|
|
952
|
+
def f(x: int) -> int:
|
|
953
|
+
"""post: _ == f(-x)"""
|
|
954
|
+
return x * x
|
|
970
955
|
|
|
971
|
-
|
|
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
|
-
|
|
979
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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) ==
|
|
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
|
-
|
|
1108
|
+
def test_nondeterministic_detected_via_condition() -> None:
|
|
1109
|
+
_GLOBAL_THING = [42]
|
|
1049
1110
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1071
|
-
|
|
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
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
-
|
|
1137
|
-
def foo(x):
|
|
1138
|
-
assert x
|
|
1137
|
+
if icontract:
|
|
1139
1138
|
|
|
1140
|
-
def
|
|
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
|
-
|
|
1143
|
-
DEFAULT_OPTIONS
|
|
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
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
1172
|
-
bar()
|
|
1173
|
-
assert not called
|
|
1174
|
-
assert not db.data
|
|
1163
|
+
check_states(trynum, CONFIRMED)
|
|
1175
1164
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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
|
-
|
|
1189
|
-
|
|
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
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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
|
-
|
|
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()
|