def test_multifirst(): def broadcaster(value): yield tap.Broadcast('key', value) def receiver(wait_value): value = yield tap.Receive('key', lambda x: x == wait_value) return value def fn(): strand_1 = yield tap.CallFork(receiver, (1, )) strand_2 = yield tap.CallFork(receiver, (2, )) strand_3 = yield tap.CallFork(receiver, (3, )) results = yield tap.Fork([ tap.First([strand_1, strand_2, strand_3]), tap.First([strand_2, strand_1]), ]) yield tap.Call(broadcaster, (5, )) yield tap.Call(broadcaster, (3, )) yield tap.Call(broadcaster, (1, )) value = yield tap.Join(results) return value # the first race resolves first, thus cancelling strands 1 and 2, preventing the second from ever finishing with pytest.raises(tap.TapystryError) as x: tap.run(fn) assert str(x.value).startswith("Hanging strands")
def test_lock_cancel_mid_acquire(): a = 0 lock = tap.Lock() def acquire(): release = yield lock.Acquire() nonlocal a a += 5 yield tap.Receive("unlock") yield release def fn(): yield tap.CallFork(acquire) t = yield tap.CallFork(acquire) yield tap.CallFork(acquire) yield tap.Sleep(0) assert a == 5 yield tap.Cancel(t) yield tap.Broadcast("unlock") yield tap.Sleep(0.01) assert a == 10 yield tap.Broadcast("unlock") yield tap.Sleep(0.001) assert a == 10 tap.run(fn)
def test_call_thread(): a = 0 cv = threading.Condition() def thread_fn(): nonlocal a for _ in range(2): with cv: cv.wait() a += 1 with cv: cv.notify() return "done" def fn(): t = yield tap.Fork(tap.CallThread(thread_fn)) yield tap.Sleep(0.01) assert a == 0 with cv: cv.notify() cv.wait() assert a == 1 with cv: cv.notify() cv.wait() assert a == 2 with cv: cv.notify() assert a == 2 assert (yield tap.Join(t)) == "done" tap.run(fn)
def test_multifirst_canceled(): def broadcaster(value): yield tap.Broadcast('key', value) def receiver(wait_value): value = yield tap.Receive('key', lambda x: x == wait_value) return value def fn(): strand_1 = yield tap.CallFork(receiver, (1, )) strand_2 = yield tap.CallFork(receiver, (2, )) strand_3 = yield tap.CallFork(receiver, (3, )) results = yield tap.Fork([ tap.First([strand_1, strand_2], name="1v2"), tap.First([strand_2, strand_3], name="2v3"), ]) yield tap.Call(broadcaster, (5, )) yield tap.Call(broadcaster, (1, )) yield tap.Call(broadcaster, (3, )) value = yield tap.Join(results, name="joinfork") yield tap.Join(strand_2, name="joincanceled") return value with pytest.raises(tap.TapystryError) as x: tap.run(fn) # TODO: always do the child strand assert str(x.value).startswith( "Hanging strands detected waiting for Race(Join(joincanceled))" ) or str(x.value).startswith("Hanging strands detected waiting for Join")
def test_intercept_nontest(): def fn(): yield tap.Intercept() with pytest.raises(tap.TapystryError) as x: tap.run(fn) assert str(x.value).startswith("Cannot intercept outside of test mode")
def test_race_threads(): def fn(): winner, r = yield tap.Race( dict(long=tap.Sleep(0.03), short=tap.Sleep(0.02))) assert winner == "short" tap.run(fn)
def test_intercept_cancel_too_late(): def broadcaster(value): yield tap.Broadcast('key', value) def receiver(): value = yield tap.Receive('key') return value def intercepter(): (effect, inject) = yield tap.Intercept(lambda e: isinstance(e, tap.Receive)) assert effect.key == "key" yield inject("real") def fn(): intercept_strand = yield tap.CallFork(intercepter) yield tap.Cancel(intercept_strand) recv_strand = yield tap.CallFork(receiver) # even though this is forked, it doesn't end up hanging yield tap.Call(broadcaster, (5, )) # this gets intercepted and never gets injected yield tap.Join(recv_strand) with pytest.raises(tap.TapystryError) as x: tap.run(fn, test_mode=True) # print(x.value) assert str(x.value).startswith( "Hanging strands detected waiting for Race(Join") or str( x.value ).startswith("Hanging strands detected waiting for Join") or str( x.value).startswith("Hanging strands detected waiting for Receive")
def test_error_stack(): def broadcaster(value): yield tap.Broadcast('key', value) def receiver(): value = yield tap.Receive('key') if value < 10: broadcast_strand = yield tap.CallFork(broadcaster, (value + 1, )) receive_strand = yield tap.CallFork(receiver) yield tap.Join([broadcast_strand, receive_strand]) raise Exception("too large") return value def fn(): # fork in apparently wrong order! broadcast_strand = yield tap.CallFork(broadcaster, (5, )) recv_strand = yield tap.CallFork(receiver) yield tap.Join(broadcast_strand) value = yield tap.Join(recv_strand) return value with pytest.raises(tap.TapystryError) as x: tap.run(fn) # print(x.value) assert str(x.value).startswith("Exception caught at") assert str(x.value).count(", in receiver\n") == 6
def test_queues_get_then_put(): q = tap.Queue(buffer_size=1) a = 0 def pop_and_add(): nonlocal a b = yield q.Get() a += b def fn(): assert not q.has_work() t1 = yield tap.CallFork(pop_and_add) t2 = yield tap.CallFork(pop_and_add) t3 = yield tap.CallFork(pop_and_add) yield tap.Sleep(0) assert a == 0 yield q.Put(3) assert a == 3 yield tap.Cancel(t2) yield q.Put(5) assert a == 8 yield q.Put(5) assert a == 8 t4 = yield tap.CallFork(pop_and_add) yield tap.Sleep(0) assert a == 13 yield tap.Join([t1, t3, t4]) tap.run(fn)
def test_bad_yield(): def fn(): yield 3 with pytest.raises(tap.TapystryError) as x: tap.run(fn) assert str(x.value).startswith("Strand yielded non-effect")
def test_immediate_thread(): def fn(): def fast(): pass yield tap.Race([tap.CallThread(fast)]) tap.run(fn)
def test_sleep(): def fn(): t = time.time() yield tap.Sleep(0.01) assert time.time() - t > 0.01 t = time.time() yield tap.Sleep(0) assert time.time() - t < 0.01 tap.run(fn)
def test_no_arg(): def broadcaster(value): yield tap.Broadcast('key', value) def fn(): yield tap.CallFork(broadcaster) return with pytest.raises(TypeError): tap.run(fn)
def test_tricky_cancel(): def wake_and_fork(): yield tap.Fork(tap.Broadcast("wake")) yield tap.Fork(tap.Receive("hmm")) def fn(): task = yield tap.CallFork(wake_and_fork) yield tap.Receive("wake") yield tap.Cancel(task) tap.run(fn)
def test_lock_hang(): lock = tap.Lock() def fn(): yield lock.Acquire() yield lock.Acquire() with pytest.raises(tap.TapystryError) as x: tap.run(fn) # TODO fix assert str(x.value).startswith("Hanging strands detected waiting for Receive(lock") or \ str(x.value).startswith("Hanging strands detected waiting for Call(Acquire")
def test_queues_put_then_get_no_buffer(): q = tap.Queue(buffer_size=0) def fn(): assert not q.has_work() t = yield tap.Fork(q.Put(3)) yield tap.Sleep(0.01) assert q.has_work() assert (yield q.Get()) == 3 yield tap.Join(t) tap.run(fn)
def test_create_acquires_out_of_order(): lock = tap.Lock() def fn(): a1 = lock.Acquire() a2 = lock.Acquire() r2 = yield a2 yield r2 r1 = yield a1 yield r1 tap.run(fn)
def test_queues_put_then_get(): q = tap.Queue(buffer_size=2) def fn(): assert not q.has_work() # we have buffer so a put is fine yield q.Put(3) yield q.Put(5) assert q.has_work() assert (yield q.Get()) == 3 assert (yield q.Get()) == 5 yield q.Put(3) yield q.Put(5) tap.run(fn)
def test_call_trivial(): def random(value): return 10 def fn(): x = yield tap.Call(random, (5, )) return x def fork_fn(): strand = yield tap.CallFork(random, (5, )) x = yield tap.Join(strand) return x assert tap.run(fn) == 10 assert tap.run(fork_fn) == 10 assert tap.run(random, args=(5, )) == 10
def test_intercept_cancel(): def broadcaster(value): yield tap.Broadcast('key', value) def receiver(): value = yield tap.Receive('key') return value def intercepter(): (effect, inject) = yield tap.Intercept(lambda e: False) (effect, inject) = yield tap.Intercept(lambda e: isinstance(e, tap.Receive)) assert effect.key == "key" yield inject("real") def fn(): intercept_strand = yield tap.CallFork(intercepter) yield tap.Cancel(intercept_strand) recv_strand = yield tap.CallFork(receiver) # even though this is forked, it doesn't end up hanging yield tap.Call(broadcaster, (5, )) value = yield tap.Join(recv_strand) assert value == 5 # did *not* get intercepted return value assert tap.run(fn, test_mode=True) == 5 # got intercepted
def test_immediate_return(): def fn(): if False: yield return 3 assert tap.run(fn) == 3
def test_multifirst_no_cancel(): def broadcaster(value): yield tap.Broadcast('key', value) def receiver(wait_value): value = yield tap.Receive('key', lambda x: x == wait_value) return value def fn(): strand_1 = yield tap.CallFork(receiver, (1, )) strand_2 = yield tap.CallFork(receiver, (2, )) strand_3 = yield tap.CallFork(receiver, (3, )) results = yield tap.Fork([ tap.First([strand_1, strand_2], name="1v2", cancel_losers=False), tap.First([strand_2, strand_3], name="2v3", cancel_losers=False), ]) yield tap.Call(broadcaster, (5, )) yield tap.Call(broadcaster, (1, )) yield tap.Call(broadcaster, (3, )) value = yield tap.Join(results, name="joinfork") yield tap.Call(broadcaster, (2, )) yield tap.Join(strand_2, name="joincanceled") return value assert tap.run(fn) == [ (0, 1), (1, 3), ]
def test_yield_from(): def fn1(value): yield tap.Broadcast('key1', value) yield tap.Broadcast('key2', value) return 1 def fn2(value): yield tap.Broadcast('key3', value) yield tap.Broadcast('key4', value) return 2 def fn(): v1 = yield from fn1(3) v2 = yield from fn2(4) assert v1 == 1 assert v2 == 2 tap.run(fn)
def test_release_twice(): lock = tap.Lock() def dummy(eff): yield eff def fn(): release = yield lock.Acquire() yield tap.Call(dummy, (release, )) yield release with pytest.raises(tap.TapystryError) as x: tap.run(fn) # print(x.value) assert str(x.value).startswith("Exception caught at") assert str(x.value).count("in test_release_twice\n") == 1 assert str(x.value).count("in Acquire\n") == 1 assert str(x.value).count("Yielded same lock release multiple times?") == 1
def test_never_receive(): def broadcaster(value): yield tap.Broadcast('key', value) def receiver(): value = yield tap.Receive('key2') return value def fn(): recv_strand = yield tap.CallFork(receiver) broadcast_strand = yield tap.CallFork(broadcaster, (5, )) yield tap.Join(broadcast_strand) value = yield tap.Join(recv_strand) return value with pytest.raises(tap.TapystryError) as x: tap.run(fn) assert str(x.value).startswith("Hanging strands")
def test_call(): def random(value): yield tap.Broadcast('key', value) return 10 def fn(): x = yield tap.Call(random, (5, )) return x assert tap.run(fn) == 10
def test_never_join(): def broadcaster(value): yield tap.Broadcast('key', value) yield tap.Broadcast('key2', value) def fn(): yield tap.CallFork(broadcaster, (5, )) return assert tap.run(fn) is None
def test_lock_cancel_after_acquire(): lock = tap.Lock() def acquire(): release = yield lock.Acquire() yield tap.Receive("unlock") yield release def fn(): t = yield tap.CallFork(acquire) yield tap.Sleep(0.001) yield tap.Cancel(t) t = yield tap.CallFork(acquire) yield tap.Sleep(0.001) with pytest.raises(tap.TapystryError) as x: tap.run(fn) # TODO fix assert str(x.value).startswith("Hanging strands detected waiting for Receive(lock") or \ str(x.value).startswith("Hanging strands detected waiting for Call(Acquire")
def test_lock(): a = 0 lock = tap.Lock() def waits(): yield tap.Receive("msg") nonlocal a a += 1 release = yield lock.Acquire() a += 2 yield release def nowaits(): release = yield lock.Acquire() nonlocal a a += 5 yield tap.Receive("unlock") yield release def fn(): yield tap.CallFork(waits) yield tap.CallFork(nowaits) yield tap.CallFork(nowaits) yield tap.Sleep(0) assert a == 5 # the waiting strand finally gets to acquire lock, but it is the latest yield tap.Broadcast("msg") yield tap.Sleep(0) assert a == 6 yield tap.Broadcast("unlock") yield tap.Sleep(0.01) assert a == 11 yield tap.Broadcast("unlock") yield tap.Sleep(0.01) assert a == 13 tap.run(fn)
def test_lock_cancel_mid_acquire_trickier(): a = 0 lock = tap.Lock() def acquire(x, to_cancel): yield tap.Receive(str(x)) release = yield lock.Acquire() nonlocal a a += 5 yield tap.Sequence([ tap.Receive(str(x)), tap.Sequence([ tap.Cancel(to_cancel), release, ]) if to_cancel is not None else release ]) def fn(): t1 = yield tap.CallFork(acquire, (1, None)) t2 = yield tap.CallFork(acquire, (2, t1)) t3 = yield tap.CallFork(acquire, (3, None)) yield tap.Broadcast("2") yield tap.Broadcast("1") yield tap.Sleep(0.001) assert a == 5 yield tap.Broadcast("3") yield tap.Broadcast( "2") # this simultaneously tries to cancel 1 and unlocks yield tap.Sleep(0.01) assert a == 10 yield tap.Broadcast("1") # 1 gets canceled after the acquire, so it's too late to release yield tap.Sleep(0.001) assert a == 10 # yield tap.Join([t1, t2, t3]) yield tap.Cancel(t3) tap.run(fn)