test_scopes.py (3755B)
1 #!/usr/bin/env python3 2 """Ownership scopes and curried application against the public native binding.""" 3 from pathlib import Path 4 import sys 5 from tricu import Library, TricuError 6 7 8 def invalid(ctx, handle): 9 try: 10 ctx.kind(handle) 11 except TricuError as error: 12 assert error.status == 2, error 13 else: 14 raise AssertionError('temporary root survived its scope') 15 16 17 with Library(sys.argv[1]).context() as ctx: 18 worker = ctx.load_bundle((Path(sys.argv[2])/'id.arboricx').read_bytes(), 'id') 19 with ctx.scope() as roots: 20 value = roots.string('scoped 🌳\0value') 21 result = roots.apply_many(worker, value) 22 assert ctx.to_string(result) == 'scoped 🌳\0value' 23 leaf = roots.leaf() 24 stem = roots.stem(leaf) 25 child = roots.child(stem, 0) 26 retained = roots.retain(child) 27 assert ctx.to_bool(stem) is True 28 roots.release(retained) 29 invalid(ctx, retained) 30 try: 31 roots.own(child) 32 except ValueError: 33 pass 34 else: 35 raise AssertionError('duplicate adoption accepted') 36 detached = roots.detach(value) 37 for handle in (result, leaf, stem, child): 38 invalid(ctx, handle) 39 assert ctx.to_string(detached) == 'scoped 🌳\0value' 40 ctx.release(detached) 41 roots.close() 42 try: 43 roots.leaf() 44 except RuntimeError: 45 pass 46 else: 47 raise AssertionError('closed scope accepted a constructor') 48 # Zero-argument application returns a new owned root, borrowing the function. 49 alias = ctx.apply_many(worker) 50 assert alias != worker 51 ctx.release(alias) 52 with ctx.scope() as roots: 53 argument = roots.number(42) 54 assert ctx.to_number(roots.apply_many(worker, argument)) == 42 55 # A late argument failure must release each intermediate application. 56 made = [] 57 original_apply = ctx.apply 58 def capture(function, value): 59 result = original_apply(function, value) 60 made.append(result) 61 return result 62 ctx.apply = capture 63 try: 64 ctx.apply_many(worker, argument, 0) 65 except TricuError as error: 66 assert error.status == 2 67 else: 68 raise AssertionError('invalid late argument accepted') 69 finally: 70 ctx.apply = original_apply 71 assert made 72 for handle in made: 73 invalid(ctx, handle) 74 assert ctx.to_number(argument) == 42 75 try: 76 with ctx.scope() as roots: 77 temporary = roots.leaf() 78 raise ValueError('application failure') 79 except ValueError as error: 80 assert str(error) == 'application failure' 81 invalid(ctx, temporary) 82 # Cleanup attempts every root, and preserves an existing body exception. 83 try: 84 with ctx.scope() as roots: 85 first = roots.leaf() 86 last = roots.leaf() 87 ctx.release(last) 88 raise ValueError('original failure') 89 except ValueError as error: 90 assert str(error) == 'original failure' 91 invalid(ctx, first) 92 # A scope adopts both IO result roots, including an empty cleanup list. 93 with ctx.scope() as roots: 94 program = roots.fork(roots.string('tricuIO'), roots.fork(roots.number(2), roots.fork(roots.number(0), roots.number(7)))) 95 outcome, result, cleanup = roots.run_io(program) 96 assert outcome == 0 and ctx.to_number(result) == 7 and ctx.kind(cleanup) == 0 97 for handle in (program, result, cleanup): 98 invalid(ctx, handle) 99 assert ctx.kind(worker) == 2 100 with ctx.scope() as roots: 101 roots.leaf() 102 ctx.close() # Context destruction already releases every scope root. 103 print('Python handle scopes passed')