tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

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')