def test_synchronization_coordinator_with_multiobject_exception(): mo = MultiObject(range(3)) executed = [] class MyException(Exception): pass def foo(i, _sync=SYNC): def execute(caption): executed.append((i, caption)) _sync.wait_for_everyone() execute('after wait') if i == 2: raise MyException _sync.wait_for_everyone() execute('after wait/abandon') with pytest.raises(MultiException) as exc: mo.call(foo) assert exc.value.count == 1 assert exc.value.common_type is MyException verify_concurrent_order(executed, {(i, 'after wait') for i in range(3)}, {(i, 'after wait/abandon') for i in range(2)})
def test_synchronization_coordinator_abandon(): mo = MultiObject(range(3)) sync = SynchronizationCoordinator(len(mo)) executed = [] def foo(i): def execute(caption): executed.append((i, caption)) sync.wait_for_everyone() execute('after wait 1') if i == 2: sync.abandon() return # Only two waiters should reach here sync.wait_for_everyone() execute('after wait 2') # Even without explicit call to abandon, sync should only wait for two waiters sync.wait_for_everyone() execute('after wait 3') mo.call(foo) verify_concurrent_order(executed, {(i, 'after wait 1') for i in range(3)}, {(i, 'after wait 2') for i in range(2)}, {(i, 'after wait 3') for i in range(2)})
def test_synchronization_coordinator_with_multiobject(): mo = MultiObject(range(3)) executed = [] def foo(i, _sync=SYNC): def execute(caption): executed.append((i, caption)) sleep(i / 10) _sync.wait_for_everyone() execute('after wait') def func_to_call_once(param): executed.append('params = %s' % sorted(param)) return sum(param) result = _sync.collect_and_call_once(i + 1, func_to_call_once) execute('result is %s' % result) foo(10) assert executed == [(10, 'after wait'), 'params = [11]', (10, 'result is 11')] executed.clear() mo.call(foo) verify_concurrent_order(executed, {(i, 'after wait') for i in range(3)}, {'params = [1, 2, 3]'}, {(i, 'result is 6') for i in range(3)})
def test_synchronization_coordinator_collect_and_call_once(): mo = MultiObject(range(3)) sync = SynchronizationCoordinator(len(mo)) executed = [] def foo(i): def execute(caption): executed.append((i, caption)) sleep(i / 10) def func_to_call_once(param): executed.append('params = %s' % sorted(param)) return sum(param) result = sync.collect_and_call_once(i + 1, func_to_call_once) execute('result is %s' % result) assert sync.collect_and_call_once( i, len) == 3, 'parameters remain from previous call' mo.call(foo) verify_concurrent_order(executed, {'params = [1, 2, 3]'}, {(i, 'result is 6') for i in range(3)})
def test_synchronization_coordinator_wait_for_everyone(): mo = MultiObject(range(3)) sync = SynchronizationCoordinator(len(mo)) executed = [] def foo(i): def execute(caption): executed.append((i, caption)) sleep(i / 10) execute('after sleep') sync.wait_for_everyone() execute('after wait') sync.wait_for_everyone() sleep(i / 10) execute('after sleep 2') sync.wait_for_everyone() execute('after wait 2') mo.call(foo) verify_concurrent_order(executed, {(i, 'after sleep') for i in range(3)}, {(i, 'after wait') for i in range(3)}, {(i, 'after sleep 2') for i in range(3)}, {(i, 'after wait 2') for i in range(3)})
def test_synchronization_coordinator_timeout(): mo = MultiObject(range(3)) def foo(i, _sync=SYNC): sleep(i / 10) _sync.wait_for_everyone(timeout=0.1) with pytest.raises(MultiException) as exc: mo.call(foo) assert exc.value.count == len(mo) assert exc.value.common_type is threading.BrokenBarrierError
def test_multiobject_logging(): m = MultiObject(range(4), log_ctx="abcd", initial_log_interval=0.1) def check(i): sleep(.2) # we'll mock the logger so we can ensure it logged with patch("easypy.concurrency._logger") as _logger: m.call(check) args_list = (c[0] for c in _logger.info.call_args_list) for args in args_list: assert "test_multiobject_logging.<locals>.check" == args[2] assert "easypy/tests/test_concurrency.py" in args[4]
def test_synchronization_coordinator_with_context_manager(): mo = MultiObject(range(3)) executed = [] @contextmanager def foo(i, _sync=SYNC): def execute(caption): executed.append((i, caption)) sleep(i / 10) execute('after sleep') _sync.wait_for_everyone() execute('before yield') yield _sync.wait_for_everyone() execute('after yield') with mo.call(foo): executed.append('with body') verify_concurrent_order(executed, {(i, 'after sleep') for i in range(3)}, {(i, 'before yield') for i in range(3)}, {'with body'}, {(i, 'after yield') for i in range(3)})
def test_multiobject_1(): m = MultiObject(range(10)) def mul(a, b, *c): return a * b + sum(c) assert sum(m.call(mul, 2)) == 90 assert sum(m.call(mul, b=10)) == 450 assert sum(m.call(mul, 1, 1, 1)) == 65 assert m.filter(None).T == (1, 2, 3, 4, 5, 6, 7, 8, 9) assert sum(m.denominator) == 10 with pytest.raises(MultiException) as info: m.call(lambda i: 1 / (i % 2)) assert info.value.count == 5 assert info.value.common_type == ZeroDivisionError assert not info.value.complete
def test_synchronization_coordinator_exception_in_collect_and_call_once(): mo = MultiObject(range(3)) sync = SynchronizationCoordinator(len(mo)) times_called = 0 class MyException(Exception): pass def foo(i): def func_to_call_once(_): nonlocal times_called times_called += 1 raise MyException with pytest.raises(MyException): sync.collect_and_call_once(i, func_to_call_once) assert sync.collect_and_call_once(i + 1, sum) == 6 mo.call(foo) assert times_called == 1, 'collect_and_call_once with exception called the function more than once'
def test_synchronization_coordinator_with_multiobject_early_return(): mo = MultiObject(range(3)) executed = [] def foo(i, _sync=SYNC): def execute(caption): executed.append((i, caption)) _sync.wait_for_everyone() execute('after wait') if i == 2: return _sync.wait_for_everyone() execute('after wait/abandon') mo.call(foo) verify_concurrent_order(executed, {(i, 'after wait') for i in range(3)}, {(i, 'after wait/abandon') for i in range(2)})