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/main.py
CHANGED
|
@@ -36,7 +36,7 @@ from crosshair.core_and_libs import (
|
|
|
36
36
|
installed_plugins,
|
|
37
37
|
run_checkables,
|
|
38
38
|
)
|
|
39
|
-
from crosshair.diff_behavior import diff_behavior
|
|
39
|
+
from crosshair.diff_behavior import ExceptionEquivalenceType, diff_behavior
|
|
40
40
|
from crosshair.fnutil import (
|
|
41
41
|
FUNCTIONINFO_DESCRIPTOR_TYPES,
|
|
42
42
|
FunctionInfo,
|
|
@@ -60,9 +60,9 @@ from crosshair.path_cover import (
|
|
|
60
60
|
from crosshair.path_search import OptimizationKind, path_search
|
|
61
61
|
from crosshair.pure_importer import prefer_pure_python_imports
|
|
62
62
|
from crosshair.register_contract import REGISTERED_CONTRACTS
|
|
63
|
-
from crosshair.statespace import NotDeterministic
|
|
64
63
|
from crosshair.util import (
|
|
65
64
|
ErrorDuringImport,
|
|
65
|
+
NotDeterministic,
|
|
66
66
|
add_to_pypath,
|
|
67
67
|
debug,
|
|
68
68
|
format_boundargs,
|
|
@@ -85,14 +85,6 @@ def analysis_kind(argstr: str) -> Sequence[AnalysisKind]:
|
|
|
85
85
|
ret = [AnalysisKind[part.strip()] for part in argstr.split(",")]
|
|
86
86
|
except KeyError:
|
|
87
87
|
raise ValueError
|
|
88
|
-
if AnalysisKind.hypothesis in ret:
|
|
89
|
-
try:
|
|
90
|
-
import hypothesis
|
|
91
|
-
|
|
92
|
-
if hypothesis.__version_info__ < (6, 0, 0):
|
|
93
|
-
raise Exception("CrossHair requires hypothesis version >= 6.0.0")
|
|
94
|
-
except ImportError as e:
|
|
95
|
-
raise Exception("Unable to import the hypothesis library") from e
|
|
96
88
|
return ret
|
|
97
89
|
|
|
98
90
|
|
|
@@ -273,6 +265,21 @@ def command_line_parser() -> argparse.ArgumentParser:
|
|
|
273
265
|
type=str,
|
|
274
266
|
help="second fully-qualified function to compare",
|
|
275
267
|
)
|
|
268
|
+
diffbehavior_parser.add_argument(
|
|
269
|
+
"--exception_equivalence",
|
|
270
|
+
metavar="EXCEPTION_EQUIVALENCE",
|
|
271
|
+
type=ExceptionEquivalenceType,
|
|
272
|
+
default=ExceptionEquivalenceType.TYPE_AND_MESSAGE,
|
|
273
|
+
choices=ExceptionEquivalenceType.__members__.values(),
|
|
274
|
+
help=textwrap.dedent(
|
|
275
|
+
"""\
|
|
276
|
+
Decide how to treat exceptions, while searching for a counter-example.
|
|
277
|
+
`ALL` treats all exceptions as equivalent,
|
|
278
|
+
`SAME_TYPE`, considers matches on the type.
|
|
279
|
+
`TYPE_AND_MESSAGE` matches for the same type and message.
|
|
280
|
+
"""
|
|
281
|
+
),
|
|
282
|
+
)
|
|
276
283
|
cover_parser = subparsers.add_parser(
|
|
277
284
|
"cover",
|
|
278
285
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
@@ -413,7 +420,6 @@ def command_line_parser() -> argparse.ArgumentParser:
|
|
|
413
420
|
PEP316 : check PEP316 contracts (docstring-based)
|
|
414
421
|
icontract : check icontract contracts (decorator-based)
|
|
415
422
|
deal : check deal contracts (decorator-based)
|
|
416
|
-
hypothesis : check hypothesis tests
|
|
417
423
|
"""
|
|
418
424
|
),
|
|
419
425
|
)
|
|
@@ -442,7 +448,9 @@ def run_watch_loop(
|
|
|
442
448
|
active_messages = {}
|
|
443
449
|
else:
|
|
444
450
|
time.sleep(0.1)
|
|
445
|
-
max_uninteresting_iterations
|
|
451
|
+
max_uninteresting_iterations = min(
|
|
452
|
+
max_uninteresting_iterations * 2, 100_000_000
|
|
453
|
+
)
|
|
446
454
|
for curstats, messages in watcher.run_iteration(max_uninteresting_iterations):
|
|
447
455
|
messages = [m for m in messages if m.state > MessageType.PRE_UNSAT]
|
|
448
456
|
stats.update(curstats)
|
|
@@ -529,10 +537,10 @@ def messages_merged(
|
|
|
529
537
|
_MOTD = [
|
|
530
538
|
"Did I miss a counterexample? Let me know: https://github.com/pschanely/CrossHair/issues/new",
|
|
531
539
|
"Help me be faster! Add to my benchmark suite: https://github.com/pschanely/crosshair-benchmark",
|
|
532
|
-
"
|
|
540
|
+
"Please consider sharing your CrossHair experience with others on social media.",
|
|
533
541
|
"Questions? Ask at https://github.com/pschanely/CrossHair/discussions/new?category=q-a",
|
|
534
542
|
"Consider signing up for CrossHair updates at https://pschanely.github.io",
|
|
535
|
-
|
|
543
|
+
"Come say hello in the discord chat; we are friendly! https://discord.gg/rUeTaYTWbb",
|
|
536
544
|
]
|
|
537
545
|
|
|
538
546
|
|
|
@@ -688,10 +696,11 @@ def diffbehavior(
|
|
|
688
696
|
(fn_name1, fn_name2) = (args.fn1, args.fn2)
|
|
689
697
|
fn1 = checked_fn_load(fn_name1, stderr)
|
|
690
698
|
fn2 = checked_fn_load(fn_name2, stderr)
|
|
699
|
+
exception_equivalence = args.exception_equivalence
|
|
691
700
|
if fn1 is None or fn2 is None:
|
|
692
701
|
return 2
|
|
693
702
|
options.stats = Counter()
|
|
694
|
-
diffs = diff_behavior(fn1, fn2, options)
|
|
703
|
+
diffs = diff_behavior(fn1, fn2, options, exception_equivalence)
|
|
695
704
|
debug("stats", options.stats)
|
|
696
705
|
if isinstance(diffs, str):
|
|
697
706
|
print(diffs, file=stderr)
|
|
@@ -765,9 +774,11 @@ def cover(
|
|
|
765
774
|
ctxfn,
|
|
766
775
|
options,
|
|
767
776
|
args.coverage_type,
|
|
768
|
-
arg_formatter=
|
|
769
|
-
|
|
770
|
-
|
|
777
|
+
arg_formatter=(
|
|
778
|
+
format_boundargs_as_dictionary
|
|
779
|
+
if example_output_format == ExampleOutputFormat.ARG_DICTIONARY
|
|
780
|
+
else format_boundargs
|
|
781
|
+
),
|
|
771
782
|
)
|
|
772
783
|
except NotDeterministic:
|
|
773
784
|
print(
|
|
@@ -851,16 +862,24 @@ def check(
|
|
|
851
862
|
if isinstance(entities, int):
|
|
852
863
|
return entities
|
|
853
864
|
full_options = DEFAULT_OPTIONS.overlay(report_verbose=False).overlay(options)
|
|
854
|
-
for
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
865
|
+
checkables = [c for e in entities for c in analyze_any(e, options)]
|
|
866
|
+
if not checkables:
|
|
867
|
+
extra_help = ""
|
|
868
|
+
if full_options.analysis_kind == [AnalysisKind.asserts]:
|
|
869
|
+
extra_help = "\nHINT: Ensure that your functions to analyze lead with assert statements."
|
|
870
|
+
print(
|
|
871
|
+
"WARNING: Targets found, but contain no checkable functions." + extra_help,
|
|
872
|
+
file=stderr,
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
for message in run_checkables(checkables):
|
|
876
|
+
line = describe_message(message, full_options)
|
|
877
|
+
if line is None:
|
|
878
|
+
continue
|
|
879
|
+
stdout.write(line + "\n")
|
|
880
|
+
debug("Traceback for output message:\n", message.traceback)
|
|
881
|
+
if message.state > MessageType.PRE_UNSAT:
|
|
882
|
+
any_problems = True
|
|
864
883
|
return 1 if any_problems else 0
|
|
865
884
|
|
|
866
885
|
|
|
@@ -936,6 +955,7 @@ def mypy_and_check(cmd_args: Optional[List[str]] = None) -> None:
|
|
|
936
955
|
_mypy_out, mypy_err, mypy_ret = api.run(mypy_cmd_args)
|
|
937
956
|
print(mypy_err, file=sys.stderr)
|
|
938
957
|
if mypy_ret != 0:
|
|
958
|
+
print(_mypy_out, file=sys.stdout)
|
|
939
959
|
sys.exit(mypy_ret)
|
|
940
960
|
engage_auditwall()
|
|
941
961
|
debug("Running crosshair with these args:", check_args)
|
crosshair/main_test.py
CHANGED
|
@@ -61,7 +61,12 @@ def call_check(
|
|
|
61
61
|
def call_diffbehavior(fn1: str, fn2: str) -> Tuple[int, List[str]]:
|
|
62
62
|
buf: io.StringIO = io.StringIO()
|
|
63
63
|
errbuf: io.StringIO = io.StringIO()
|
|
64
|
-
retcode = diffbehavior(
|
|
64
|
+
retcode = diffbehavior(
|
|
65
|
+
Namespace(fn1=fn1, fn2=fn2, exception_equivalence="type_and_message"),
|
|
66
|
+
DEFAULT_OPTIONS,
|
|
67
|
+
buf,
|
|
68
|
+
errbuf,
|
|
69
|
+
)
|
|
65
70
|
lines = [
|
|
66
71
|
ls for ls in buf.getvalue().split("\n") + errbuf.getvalue().split("\n") if ls
|
|
67
72
|
]
|
|
@@ -246,19 +251,28 @@ def test_no_args_prints_usage(root):
|
|
|
246
251
|
assert re.search(r"^usage", out)
|
|
247
252
|
|
|
248
253
|
|
|
249
|
-
def
|
|
254
|
+
def test_assert_mode_e2e(root, capsys: pytest.CaptureFixture[str]):
|
|
250
255
|
simplefs(root, ASSERT_BASED_FOO)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
finally:
|
|
255
|
-
out = sys.stdout.getvalue()
|
|
256
|
-
sys.stdout = sys.__stdout__
|
|
257
|
-
assert exitcode == 1
|
|
256
|
+
exitcode = unwalled_main(["check", str(root / "foo.py"), "--analysis_kind=asserts"])
|
|
257
|
+
(out, err) = capsys.readouterr()
|
|
258
|
+
assert err == ""
|
|
258
259
|
assert re.search(
|
|
259
|
-
r"foo.py\:8\: error\: AssertionError\: when calling foofn\(
|
|
260
|
+
r"foo.py\:8\: error\: AssertionError\: when calling foofn\(100\)", out
|
|
260
261
|
)
|
|
261
262
|
assert len([ls for ls in out.split("\n") if ls]) == 1
|
|
263
|
+
assert exitcode == 1
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_assert_without_checkable(root, capsys: pytest.CaptureFixture[str]):
|
|
267
|
+
simplefs(root, SIMPLE_FOO)
|
|
268
|
+
exitcode = unwalled_main(["check", str(root / "foo.py"), "--analysis_kind=asserts"])
|
|
269
|
+
(out, err) = capsys.readouterr()
|
|
270
|
+
assert (
|
|
271
|
+
err
|
|
272
|
+
== "WARNING: Targets found, but contain no checkable functions.\nHINT: Ensure that your functions to analyze lead with assert statements.\n"
|
|
273
|
+
)
|
|
274
|
+
assert out == ""
|
|
275
|
+
assert exitcode == 0
|
|
262
276
|
|
|
263
277
|
|
|
264
278
|
def test_directives(root):
|
|
@@ -359,6 +373,35 @@ def test_check_circular_with_guard(root):
|
|
|
359
373
|
assert retcode == 0
|
|
360
374
|
|
|
361
375
|
|
|
376
|
+
def test_check_not_deterministic(root) -> None:
|
|
377
|
+
NOT_DETERMINISTIC_FOO = {
|
|
378
|
+
"foo.py": """
|
|
379
|
+
_GLOBAL_THING = [42]
|
|
380
|
+
|
|
381
|
+
def wonky_foo(i: int) -> int:
|
|
382
|
+
'''post: True'''
|
|
383
|
+
_GLOBAL_THING[0] += 1
|
|
384
|
+
if i > _GLOBAL_THING[0]:
|
|
385
|
+
pass
|
|
386
|
+
return True
|
|
387
|
+
_GLOBAL_THING = [True]
|
|
388
|
+
|
|
389
|
+
def regular_foo(i: int) -> int:
|
|
390
|
+
'''post: True'''
|
|
391
|
+
return i
|
|
392
|
+
"""
|
|
393
|
+
}
|
|
394
|
+
simplefs(root, NOT_DETERMINISTIC_FOO)
|
|
395
|
+
with add_to_pypath(root):
|
|
396
|
+
retcode, lines, errlines = call_check([str(root / "foo.py")])
|
|
397
|
+
assert errlines == []
|
|
398
|
+
assert (
|
|
399
|
+
"NotDeterministic: Found a different execution paths after making the same decisions"
|
|
400
|
+
in lines[0]
|
|
401
|
+
)
|
|
402
|
+
assert retcode == 1
|
|
403
|
+
|
|
404
|
+
|
|
362
405
|
def test_watch(root):
|
|
363
406
|
# Just to make sure nothing explodes
|
|
364
407
|
simplefs(root, SIMPLE_FOO)
|
|
@@ -456,7 +499,7 @@ def test_main_as_subprocess(tmp_path: Path):
|
|
|
456
499
|
# the testing process.
|
|
457
500
|
simplefs(tmp_path, SIMPLE_FOO)
|
|
458
501
|
completion = subprocess.run(
|
|
459
|
-
[sys.executable, "-m", "crosshair", "check", str(tmp_path)],
|
|
502
|
+
[sys.executable, "-Werror", "-m", "crosshair", "check", str(tmp_path)],
|
|
460
503
|
stdin=subprocess.DEVNULL,
|
|
461
504
|
capture_output=True,
|
|
462
505
|
text=True,
|
|
@@ -468,7 +511,7 @@ def test_main_as_subprocess(tmp_path: Path):
|
|
|
468
511
|
|
|
469
512
|
def test_mypycrosshair_command():
|
|
470
513
|
example_file = join(
|
|
471
|
-
split(__file__)[0], "examples", "
|
|
514
|
+
split(__file__)[0], "examples", "PEP316", "bugs_detected", "showcase.py"
|
|
472
515
|
)
|
|
473
516
|
completion = subprocess.run(
|
|
474
517
|
[
|
|
@@ -481,8 +524,10 @@ def test_mypycrosshair_command():
|
|
|
481
524
|
capture_output=True,
|
|
482
525
|
text=True,
|
|
483
526
|
)
|
|
484
|
-
assert completion.stderr.strip() == ""
|
|
485
|
-
|
|
527
|
+
assert completion.stderr.strip() == "", f"stderr was '''{completion.stderr}'''"
|
|
528
|
+
if completion.returncode != 1:
|
|
529
|
+
print(completion.stdout)
|
|
530
|
+
assert False, f"Return code was {completion.returncode}"
|
|
486
531
|
|
|
487
532
|
|
|
488
533
|
def test_describe_message():
|
crosshair/objectproxy.py
CHANGED
|
@@ -10,11 +10,36 @@ from crosshair.tracers import NoTracing
|
|
|
10
10
|
# (which is BSD licenced)
|
|
11
11
|
#
|
|
12
12
|
|
|
13
|
+
_MISSING = object()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def proxy_inplace_op(proxy, op, *args):
|
|
17
|
+
my_original_value = proxy._wrapped()
|
|
18
|
+
my_new_value = op(my_original_value, *args)
|
|
19
|
+
# We need to return our own identity if (and only if!) the underlying value does.
|
|
20
|
+
if my_new_value is my_original_value:
|
|
21
|
+
return proxy
|
|
22
|
+
else:
|
|
23
|
+
object.__setattr__(proxy, "_inner", my_new_value)
|
|
24
|
+
return my_new_value
|
|
25
|
+
|
|
13
26
|
|
|
14
27
|
class ObjectProxy:
|
|
15
|
-
def
|
|
28
|
+
def _realize(self):
|
|
16
29
|
raise NotImplementedError
|
|
17
30
|
|
|
31
|
+
def _wrapped(self):
|
|
32
|
+
with NoTracing():
|
|
33
|
+
inner = _MISSING
|
|
34
|
+
try:
|
|
35
|
+
inner = object.__getattribute__(self, "_inner")
|
|
36
|
+
except AttributeError:
|
|
37
|
+
pass
|
|
38
|
+
if inner is _MISSING:
|
|
39
|
+
inner = self._realize()
|
|
40
|
+
object.__setattr__(self, "_inner", inner)
|
|
41
|
+
return inner
|
|
42
|
+
|
|
18
43
|
def __get_module__(self) -> str:
|
|
19
44
|
return self._wrapped().__module__
|
|
20
45
|
|
|
@@ -233,40 +258,40 @@ class ObjectProxy:
|
|
|
233
258
|
return other | self._wrapped()
|
|
234
259
|
|
|
235
260
|
def __iadd__(self, other):
|
|
236
|
-
return operator.iadd
|
|
261
|
+
return proxy_inplace_op(self, operator.iadd, other)
|
|
237
262
|
|
|
238
263
|
def __isub__(self, other):
|
|
239
|
-
return operator.isub
|
|
264
|
+
return proxy_inplace_op(self, operator.isub, other)
|
|
240
265
|
|
|
241
266
|
def __imul__(self, other):
|
|
242
|
-
return operator.imul
|
|
267
|
+
return proxy_inplace_op(self, operator.imul, other)
|
|
243
268
|
|
|
244
269
|
def __itruediv__(self, other):
|
|
245
|
-
return operator.itruediv
|
|
270
|
+
return proxy_inplace_op(self, operator.itruediv, other)
|
|
246
271
|
|
|
247
272
|
def __ifloordiv__(self, other):
|
|
248
|
-
return
|
|
273
|
+
return proxy_inplace_op(self, operator.ifloordiv, other)
|
|
249
274
|
|
|
250
275
|
def __imod__(self, other):
|
|
251
|
-
return operator.imod
|
|
276
|
+
return proxy_inplace_op(self, operator.imod, other)
|
|
252
277
|
|
|
253
278
|
def __ipow__(self, other, *args):
|
|
254
|
-
return operator.ipow
|
|
279
|
+
return proxy_inplace_op(self, operator.ipow, other, *args)
|
|
255
280
|
|
|
256
281
|
def __ilshift__(self, other):
|
|
257
|
-
return operator.ilshift
|
|
282
|
+
return proxy_inplace_op(self, operator.ilshift, other)
|
|
258
283
|
|
|
259
284
|
def __irshift__(self, other):
|
|
260
|
-
return operator.irshift
|
|
285
|
+
return proxy_inplace_op(self, operator.irshift, other)
|
|
261
286
|
|
|
262
287
|
def __iand__(self, other):
|
|
263
|
-
return operator.iand
|
|
288
|
+
return proxy_inplace_op(self, operator.iand, other)
|
|
264
289
|
|
|
265
290
|
def __ixor__(self, other):
|
|
266
|
-
return operator.ixor
|
|
291
|
+
return proxy_inplace_op(self, operator.ixor, other)
|
|
267
292
|
|
|
268
293
|
def __ior__(self, other):
|
|
269
|
-
return operator.ior
|
|
294
|
+
return proxy_inplace_op(self, operator.ior, other)
|
|
270
295
|
|
|
271
296
|
def __neg__(self):
|
|
272
297
|
return -self._wrapped()
|
|
@@ -335,7 +360,7 @@ class ObjectProxy:
|
|
|
335
360
|
return copy.copy(self._wrapped())
|
|
336
361
|
|
|
337
362
|
def __deepcopy__(self, memo):
|
|
338
|
-
ret = copy.deepcopy(self._wrapped())
|
|
363
|
+
ret = copy.deepcopy(self._wrapped(), memo)
|
|
339
364
|
memo[id(self)] = ret
|
|
340
365
|
return ret
|
|
341
366
|
|
crosshair/objectproxy_test.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pytest
|
|
2
2
|
|
|
3
3
|
from crosshair.objectproxy import ObjectProxy
|
|
4
4
|
|
|
@@ -7,21 +7,35 @@ class ObjectWrap(ObjectProxy):
|
|
|
7
7
|
def __init__(self, obj):
|
|
8
8
|
object.__setattr__(self, "_o", obj)
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def _realize(self):
|
|
11
11
|
return object.__getattribute__(self, "_o")
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
15
|
-
def
|
|
14
|
+
class TestObjectProxy:
|
|
15
|
+
def test_object_proxy_over_list(self) -> None:
|
|
16
16
|
i = [1, 2, 3]
|
|
17
17
|
proxy = ObjectWrap(i)
|
|
18
|
-
|
|
18
|
+
assert i == proxy
|
|
19
19
|
proxy.append(4)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
assert [1, 2, 3, 4] == proxy
|
|
21
|
+
assert [1, 2, 3, 4, 5] == proxy + [5]
|
|
22
|
+
assert [2, 3] == proxy[1:3]
|
|
23
|
+
assert [1, 2, 3, 4] == proxy
|
|
24
|
+
|
|
25
|
+
def test_inplace_identities(self) -> None:
|
|
26
|
+
proxy = ObjectWrap(3.0)
|
|
27
|
+
orig_proxy = proxy
|
|
28
|
+
proxy += 1.0
|
|
29
|
+
assert proxy is not orig_proxy
|
|
30
|
+
proxy = ObjectWrap([1, 2])
|
|
31
|
+
orig_proxy = proxy
|
|
32
|
+
proxy += [3, 4]
|
|
33
|
+
assert proxy is orig_proxy
|
|
34
|
+
|
|
35
|
+
def test_object_proxy_over_float(self) -> None:
|
|
36
|
+
proxy = ObjectWrap(4.5)
|
|
37
|
+
proxy //= 2.0
|
|
38
|
+
assert 2.0 == proxy
|
|
39
|
+
proxy = ObjectWrap(5.0)
|
|
40
|
+
proxy /= 2.0
|
|
41
|
+
assert 2.5 == proxy
|