def test_exception_under_gather(): """Test exception propagation under asyncio.gather. This expands upon test_concurrency_trivial. Here, each child is still a coroutine, not a separate unit. Each will sleep and then call for its unit to fail. The expected result is that the first child will complete its work and the second child will be cancelled while asleep, because the unit has failed. """ async def pseudochild(unit, key=None, value=None): unit.state.outputs[f'{key}0'] = value await asyncio.sleep(value) unit.state.outputs[f'{key}1'] = value unit.fail() async def parent_work(unit=None): await asyncio.gather(pseudochild(unit=unit, key='A', value=0), pseudochild(unit=unit, key='B', value=1)) unit = Unit(Unit.Scaffold(work=parent_work)) asyncio.run(unit()) assert not unit assert unit.state.outputs == dict(A0=0, A1=0, B0=1)
def test_boolean_notstarted_false(): """Check that an unstarted unit is considered false.""" async def work(unit, **_): pass unit = Unit(Unit.Scaffold(work=work)) assert not unit
def test_boolean_noop_true(): """Check that a started no-op unit is considered true.""" async def work(unit, **_): pass unit = Unit(Unit.Scaffold(work=work)) asyncio.run(unit()) assert unit.state.time_started assert unit.state.time_stopped assert not unit.state.failure assert unit
def test_concurrency_trivial(): """Check the work of a parent with two concurrent children.""" async def pseudochild(unit, key=None, value=None): unit.state.outputs[key] = value async def parent_work(unit=None): await asyncio.gather(pseudochild(unit=unit, key='a', value=1), pseudochild(unit=unit, key='b', value=2)) unit = Unit(Unit.Scaffold(work=parent_work)) asyncio.run(unit()) assert unit assert unit.state.outputs == dict(a=1, b=2)
def test_parent_of_failure(): """Check the effect on a family of a child calling fail(). The child calling the method should be cut short, but the parent should continue thereafter. Neither should be truthy when stopped. """ @permissive() async def a(unit: Unit): unit.state.outputs.update(before=True) unit.fail() unit.state.outputs.update(after=True) @permissive() async def b(unit: Unit): unit.state.outputs.update(before=True) await unit.new_child(a)() unit.state.outputs.update(after=True) root = Unit(b) asyncio.run(root()) assert not root assert root.state.outputs == dict(before=True, after=True) assert not root.state.failure assert not root.state.error assert len(root.children) == 1 assert not root.children[0] assert root.children[0].state.outputs == dict(before=True) assert root.children[0].state.failure assert not root.children[0].state.error
def test_concurrency_with_failure(): """Check the consequences of a middle child failing.""" @permissive() async def a(unit: Unit): await asyncio.sleep(0.01) @permissive() async def b(unit: Unit): unit.fail() @permissive() async def c(unit: Unit): a0 = unit.new_child(a) b0 = unit.new_child(b) a1 = unit.new_child(a) await asyncio.gather(a0(), b0(), a1()) unit.state.outputs.update(after=True) root = Unit(c) asyncio.run(root()) assert not root assert root.state.outputs == dict(after=True) assert not root.state.failure assert not root.state.error assert len(root.children) == 3 assert root.children[0] assert not root.children[1] assert root.children[1].state.failure assert not root.children[1].state.error assert root.children[2]
def test_parent_of_unacknowledged_caught_error(): """Check the effect on a family of a child dividing by zero. In this variation on the above, the parent anticipates the error. """ @permissive() async def a(unit: Unit): unit.state.outputs.update(before=True) 1 / 0 unit.state.outputs.update(after=True) @permissive() async def b(unit: Unit): unit.state.outputs.update(before=True) try: await unit.new_child(a)() except ZeroDivisionError: pass unit.state.outputs.update(after=True) root = Unit(b) asyncio.run(root()) assert not root assert root.state.outputs == dict(before=True, after=True) assert not root.state.failure assert not root.state.error assert len(root.children) == 1 assert not root.children[0] assert root.children[0].state.outputs == dict(before=True) assert root.children[0].state.error
def test_parent_of_unacknowledged_uncaught_error(): """Check the effect on a family of a child dividing by zero. The consequences should resemble those of an acknowledged error, but the child’s exception propagates directly. """ @permissive() async def a(unit: Unit): unit.state.outputs.update(before=True) 1 / 0 unit.state.outputs.update(after=True) @permissive() async def b(unit: Unit): unit.state.outputs.update(before=True) await unit.new_child(a)() unit.state.outputs.update(after=True) root = Unit(b) with pytest.raises(ZeroDivisionError): asyncio.run(root()) assert not root assert root.state.outputs == dict(before=True) assert root.state.error assert len(root.children) == 1 assert not root.children[0] assert root.children[0].state.outputs == dict(before=True) assert root.children[0].state.error
def test_parent_of_acknowledged_error(): """Check the effect on a family of a child calling panic(). The child calling the method should be cut short, and the parent too. Neither should be truthy when stopped. """ @permissive() async def a(unit: Unit): unit.state.outputs.update(before=True) unit.panic() unit.state.outputs.update(after=True) @permissive() async def b(unit: Unit): unit.state.outputs.update(before=True) await unit.new_child(a)() unit.state.outputs.update(after=True) root = Unit(b) with pytest.raises(root.ConclusionSignal): asyncio.run(root()) assert not root assert root.state.outputs == dict(before=True) assert not root.state.error assert len(root.children) == 1 assert not root.children[0] assert root.children[0].state.outputs == dict(before=True) assert root.children[0].state.error
def test_parent_trivial(): """Check that a unit can easily create and run another.""" @permissive() async def a(unit: Unit): pass @permissive() async def b(unit: Unit): await unit.new_child(a)() root = Unit(b) asyncio.run(root()) assert root assert root._work is b.work assert len(root.children) == 1 assert root.children[0] assert root.children[0]._work is a.work
def test_concurrency_simple(): """Check the consequences of well-behaved concurrent children.""" @permissive() async def a(unit: Unit): await asyncio.sleep(0.01) @permissive() async def b(unit: Unit): a0 = unit.new_child(a) a1 = unit.new_child(a) await asyncio.gather(a0(), a1()) root = Unit(b) asyncio.run(root()) assert root assert len(root.children) == 2 a0, a1 = root.children assert a0.state.time_started < a1.state.time_started assert a0.state.time_stopped > a1.state.time_started # Overlap. assert a0.state.time_stopped < a1.state.time_stopped
async def a(unit: Unit): unit.state.outputs.update(before=True) unit.fail() unit.state.outputs.update(after=True)
async def c(unit: Unit): a0 = unit.new_child(a) b0 = unit.new_child(b) a1 = unit.new_child(a) await asyncio.gather(a0(), b0(), a1()) unit.state.outputs.update(after=True)
async def b(unit: Unit): unit.fail()
async def b(unit: Unit): a0 = unit.new_child(a) a1 = unit.new_child(a) await asyncio.gather(a0(), a1())