def test_edge_detector_from_undef(circuit): """Test the edge detector's parameters u_rise, u_fall.""" setup = [] i = 0 for value in (False, True): for ur in (False, True, None): for uf in (False, True): for r in (False, True): dest = edzed.Input(f'test_event_{i}', initdef=None) edzed.Input(f"test_edge_{i}", on_output=edzed.Event(dest, efilter=edzed.Edge( rise=r, u_rise=ur, u_fall=uf)), initdef=value) if ur is None: ur = r event = ur if value else uf result = value if event else None setup.append((dest, result, (value, ur, uf, r))) i += 1 init(circuit) for dest, result, args in setup: assert dest.output is result, f"failed for (value, u_rise, u_fall, rise) = {args}"
def test_validators(circuit): """Test multiple validators.""" # order = check, allowed, schema inputs = [ edzed.Input('input1', allowed=[False, True], initdef=False), edzed.Input('input2', check=lambda x: isinstance(x, bool), allowed=[False, True], initdef=False), edzed.Input('input3', schema=bool, allowed=[False, True], initdef=False), edzed.Input('input4', schema=bool, initdef=False), ] init(circuit) VALUES = (None, 1, 99) ACCEPTED = [ (False, True, False), # because 1 == True, i.e. 1 is in allowed (False, False, False), # no value passes the strict type checking (False, True, False), # similar to input1 (True, True, True), # each value will be converted to bool ] OUTPUT = [ (False, 1, 1), (False, False, False), (False, True, True), (False, True, True), ] for inp, acc_list, out_list in zip(inputs, ACCEPTED, OUTPUT): inp.event('put', value=inp.initdef) # reset to default for val, acc, out in zip(VALUES, acc_list, out_list): assert inp.put(val) is acc assert inp.output == out
def test_no_circular_init_by_event(circuit): """Circular (recursive in general) events are forbidden.""" inp_a = edzed.Input('inp_a', on_output=edzed.Event('inp_b'), initdef='ok') inp_b = edzed.Input( 'inp_b', on_output=edzed.Event('inp_a')) # cannot send back to inp_a with pytest.raises(edzed.EdzedCircuitError, match="Forbidden recursive event"): init(circuit)
def test_no_cross_circuit_events(circuit): inp1 = edzed.Input('inp', comment="circuit 1", initdef=None) event = edzed.Event(inp1) init(circuit) event.send(inp1, value=1) assert inp1.output == 1 edzed.reset_circuit() inp2 = edzed.Input('inp', comment="circuit 2", initdef=None) init(circuit) with pytest.raises(edzed.EdzedCircuitError, match="not in the current circuit"): event.send(inp2, value=2)
def test_and_or(circuit): """Test unpack=False on AND/OR logical gates.""" inp0 = edzed.Input('inp0', initdef=False) inp1 = edzed.Input('inp1', initdef=False) and_gate = edzed.And('AND').connect(inp0, inp1, True) or_gate = edzed.Or('OR').connect(inp0, inp1, False) init(circuit) for v0, v1 in ((0, 0), (0, 1), (1, 0), (1, 1)): inp0.put(v0) inp1.put(v1) and_gate.eval_block() or_gate.eval_block() assert and_gate.output == bool(v0 and v1) assert or_gate.output == bool(v0 or v1)
def test_init_by_event(circuit): """Test initialization by an event.""" src = edzed.Input('src', on_output=edzed.Event('dest'), initdef='ok') dest = edzed.Input('dest', on_output=edzed.Event('mem')) # note: no inittdef=... ! mem = EventMemory('mem') init(circuit) assert src.output == dest.output == 'ok' assert mem.output == ('put', { 'source': 'dest', 'trigger': 'output', 'previous': edzed.UNDEF, 'value': 'ok' })
def test_on_every_output(circuit): """Test that on_output generates events.""" dest = EventMemory('dest') dest_every = EventMemory('dest_every') src = edzed.Input('src', on_output=edzed.Event(dest, etype='ev'), on_every_output=edzed.Event(dest_every, etype='ev'), initdef=None) init(circuit) CDATA = {'source': 'src', 'trigger': 'output'} src.put(0) assert dest.output == dest_every.output == ('ev', { **CDATA, 'previous': None, 'value': 0 }) src.put(911) assert dest.output == dest_every.output == ('ev', { **CDATA, 'previous': 0, 'value': 911 }) dest.event('ev', cleared=True) dest_every.event('ev', cleared=True) src.put(911) assert dest.output == ('ev', {'cleared': True}) assert dest_every.output == ('ev', { **CDATA, 'previous': 911, 'value': 911 })
def test_invert(circuit): """Shortcut '_not_blk' creates an inverter block connected to blk.""" src = edzed.Input('src', allowed=(True, False), initdef=False) id1 = edzed.FuncBlock('identity1', func=lambda x: x).connect('src') id2 = edzed.FuncBlock('identity2', func=lambda x: x).connect('_not_src') # verify that _not_src shortcut may be used multiple times id3 = edzed.FuncBlock('identity3', func=lambda x: x).connect('_not_src') # _not_src does not exist yet assert sum(1 for blk in circuit.getblocks()) == 4 with pytest.raises(KeyError): circuit.findblock('_not_src') init(circuit) # _not_src was just created automatically by finalize() assert sum(1 for blk in circuit.getblocks()) == 5 invert = circuit.findblock('_not_src') for value in (True, False, True, False): src.put(value) invert.eval_block() id1.eval_block() id2.eval_block() id3.eval_block() assert id1.output is value assert id2.output is id3.output is not value
async def output_async(circuit, *, log, t1=0.1, t2=0.0, test_error=False, mstop=True, on_error=None, **kwargs): async def wait120(arg): logger.put(f'start {arg}') if test_error: # pylint: disable=pointless-statement 1 / 0 # BOOM! await asyncio.sleep(0.12) logger.put(f'stop {arg}') return f'ok {arg}' try: inp = edzed.Input('inp', initdef='i1', on_output=edzed.Event('echo')) logger = TimeLogger('logger', mstop=mstop) edzed.OutputAsync('echo', coro=wait120, on_error=on_error, **kwargs) asyncio.create_task(circuit.run_forever()) await asyncio.sleep(t1) if circuit.is_ready(): # skip after an error, inp.put('i2') if t2 > 0.0: await asyncio.sleep(t2) inp.put('i3') await asyncio.sleep(0.05) await circuit.shutdown() logger.put("END") finally: logger.compare(log)
def test_chained_dataedit(circuit): """Chaining of event data edit functions is allowed.""" def check(expected): def _check(data): assert data == expected return data return _check src = edzed.Input( 'src', on_output=edzed.Event( 'dest', efilter=( edzed.not_from_undef, check({'source': 'src', 'trigger': 'output', 'previous': None, 'value': 'V'}), edzed.DataEdit \ .permit('source', 'trigger', 'value') \ .setdefault(source='fake') \ .copy('value', 'saved') \ .rename('source', 'src'), check( {'src': 'src', 'trigger': 'output', 'saved': 'V', 'value': 'V'}), edzed.DataEdit.permit('trigger', 'src') \ .modify('src', lambda x: x[::-1]) \ .add(a=1) )), initdef=None) dest = EventMemory('dest') init(circuit) src.put('V') assert dest.output == ('put', {'a': 1, 'src': 'crs', 'trigger': 'output'})
def test_multiple_events(circuit): """Test multiple events.""" dest1 = EventMemory('dest2') dest2 = EventMemory('dest1') src = edzed.Input('src', on_output=[ edzed.Event(dest=dest1, etype='ev1'), edzed.Event(dest=dest2, etype='ev2') ], initdef=None) init(circuit) CDATA = {'source': 'src', 'trigger': 'output'} src.put(0) assert dest1.output == ('ev1', {**CDATA, 'previous': None, 'value': 0}) assert dest2.output == ('ev2', {**CDATA, 'previous': None, 'value': 0}) src.put(911) assert dest1.output[1] == dest2.output[1] == { **CDATA, 'previous': 0, 'value': 911 } src.put('last') assert dest1.output[1] == dest2.output[1] == { **CDATA, 'previous': 911, 'value': 'last' }
async def output_func(circuit, *, log, v2=2, on_error=None, mstop=True, **kwargs): def worker(arg): v = 12 // arg logger.put(v) return 100 + v try: inp = edzed.Input('inp', initdef=6, on_output=edzed.Event('echo')) logger = TimeLogger('logger', mstop=mstop) edzed.OutputFunc('echo', func=worker, on_error=on_error, **kwargs) asyncio.create_task(circuit.run_forever()) await asyncio.sleep(0.05) inp.put(v2) if circuit.is_ready(): # skip after an error, await asyncio.sleep(0.05) inp.put(3) await circuit.shutdown() logger.put("END") finally: logger.compare(log)
def test_on_output(circuit): """Test that on_output generates events.""" dest = EventMemory('dest') src = edzed.Input('src', check=lambda x: x != 'NO!', on_output=edzed.Event(dest, etype='ev'), initdef=None) init(circuit) CDATA = {'source': 'src', 'trigger': 'output'} src.put(0) assert dest.output == ('ev', {**CDATA, 'previous': None, 'value': 0}) src.put(911) assert dest.output == ('ev', {**CDATA, 'previous': 0, 'value': 911}) src.put('string') assert dest.output == ('ev', {**CDATA, 'previous': 911, 'value': 'string'}) dest.event('clear') src.put('string') # same value, no change, no output event assert dest.output == ('clear', {}) src.put('NO!') # forbidden value (check=...), no output event assert dest.output == ('clear', {}) src.put(0) assert dest.output == ('ev', {**CDATA, 'previous': 'string', 'value': 0})
async def test_nostart_nopersistent(circuit): """Persistent data are not touched if the start fails.""" class NoStart(edzed.CBlock): def start(self): raise RuntimeError("failed start") def calc_output(self): return None NoStart('nostart') inp1 = edzed.Input('input1', persistent=True) edzed.Input('input2', persistent=True, initdef=0) pd = {inp1.key: 33} circuit.set_persistent_data(pd) with pytest.raises(RuntimeError): await circuit.run_forever() assert pd == {inp1.key: 33}
def test_not_from_undef(circuit): """Test the not_from_undef filter.""" dest = edzed.Input('dest', initdef=None) src = edzed.Input('src', on_output=edzed.Event(dest, efilter=edzed.not_from_undef), initdef=1) init(circuit) assert src.output == 1 assert dest.output is None # UNDEF -> 1 suppressed by the filter src.put(3) assert src.output == 3 assert dest.output == 3 src.put(5) assert src.output == 5 assert dest.output == 5
def test_expiration(circuit): """Test the internal state expiration""" inp1 = edzed.Input('inp1', initdef=91, persistent=True) inp2 = edzed.Input('inp2', initdef=92, persistent=True, expiration=None) inp3 = edzed.Input('inp3', initdef=93, persistent=True, expiration=10) inp4 = edzed.Input('inp4', initdef=94, persistent=True, expiration="1m30s") storage = { inp1.key: 1, inp2.key: 2, inp3.key: 3, inp4.key: 4, 'edzed-stop-time': time.time() - 12.0, # 12 seconds old } circuit.set_persistent_data(storage) init(circuit) assert inp1.output == 1 # no expiration assert inp2.output == 2 # no expiration assert inp3.output == 93 # state expired, init from default assert inp4.output == 4 # not expired
def test_schema(circuit): """schema validator test.""" inp = edzed.Input('input', schema=lambda x: int(x) + 100, initdef=23) init(circuit) assert inp.output == 123 # schema is applied also to the default assert inp.event('put', value='string') is False assert inp.output == 123 assert inp.event('put', value='68') is True assert inp.output == 168
def test_override(circuit): """Test the override block.""" SENTINEL = 999 inp = edzed.Input('inp', initdef=None) override = edzed.Input('ctrl', initdef=SENTINEL) out = edzed.Override('test', null_value=SENTINEL).connect(input=inp, override=override) init(circuit) TEST_VALUES = (17, 3.14, SENTINEL, True, False, None, "LAST") for value in TEST_VALUES: inp.event('put', value=value) out.eval_block() assert out.output == value for value in TEST_VALUES: override.event('put', value=value) out.eval_block() assert out.output == (value if value != SENTINEL else "LAST") # parenthesis required
def test_check(circuit): """check validator test.""" inp = edzed.Input('input', check=lambda x: x % 5 == 0, initdef=5) init(circuit) assert inp.output == 5 assert inp.put(25) is True assert inp.output == 25 assert inp.put(68) is False assert inp.output == 25
def test_delta(circuit): """Test the Delta filter.""" trace = [] def tee(data): trace.append(data['value']) return data dest = edzed.Input('dest', initdef=None) src = edzed.Input('src', on_output=edzed.Event(dest, efilter=[edzed.Delta(1.7), tee]), initdef=0) init(circuit) for num in (0, 1, 0, 2, 3.69, 3.71, 5, 6, 7, 8, 15, 13.5, 16.5, 12): src.put(num) assert trace == [0, 2, 3.71, 6, 8, 15, 12] assert dest.output == 12
def test_edge_detector(circuit): """Test the edge detector's parameters rise and fall.""" SEQ1 = (False, True, False, True, False, True, False, True) SEQ2 = (True, False, True, False, True) cnt = edzed.Counter('counter') i = 0 setup = [] for r in (False, True): for f in (False, True): inp1 = edzed.Input(f"test{i}_seq1", on_output=edzed.Event(cnt, 'inc', efilter=edzed.Edge( rise=r, fall=f)), initdef=SEQ1[0]) inp2 = edzed.Input(f"test{i}_seq2", on_output=edzed.Event(cnt, 'inc', efilter=edzed.Edge( rise=r, fall=f)), initdef=SEQ2[0]) s1 = s2 = 0 # s1, s2 = expected event count for SEQ1, SEQ2 if r: s1 += 4 # 4 rising edges s2 += 2 # 3 rising edges, but 1 of them is initial # and is counted immediately at the block creation if f: s1 += 3 # 4 falling edges, but 1 of them is initial # and is suppressed by uf=False (default) s2 += 2 # 2 falling edges setup.append((inp1, SEQ1, s1, (r, f))) setup.append((inp2, SEQ2, s2, (r, f))) i += 1 init(circuit) assert cnt.output == 2 # 2 times the initial rising edge of S2R1F10 and S2R1F1 for inp, seq, result, args in setup: cnt.put(0) assert cnt.output == 0 for val in seq: inp.put(val) assert cnt.output == result, f"failed for {inp.name}, (rise, fall) = {args}"
def test_save_state_sync(circuit): """By default the state is saved after each event (sync_state=True).""" storage = dict() inp = edzed.Input('ipers', initdef=99, persistent=True) circuit.set_persistent_data(storage) init(circuit) assert inp.output == 99 assert storage == {inp.key: 99} inp.event('put', value=3.14) assert storage == {inp.key: 3.14}
def test_load_state(circuit): """The saved state is loaded in preference to the default.""" inp = edzed.Input('ipers', initdef=99, persistent=True) storage = {inp.key: 'saved'} circuit.set_persistent_data(storage) init(circuit) assert storage == {inp.key: 'saved'} assert inp.output == 'saved' inp.event('put', value=3.14) assert storage == {inp.key: 3.14}
def test_error_checking(circuit): dest = edzed.Input('dest', initdef=0) init(circuit) with pytest.raises(TypeError, match="string"): dest.event(333) # event name must be a string or a EventType with pytest.raises(ValueError, match="empty"): dest.event('') with pytest.raises(edzed.EdzedUnknownEvent): dest.event("no_such_event") with pytest.raises(edzed.EdzedUnknownEvent): dest.event("no_such_event")
async def test_instability_2(circuit): """Test a stable circuit that becomes instable.""" ctrl = edzed.Input('ctrl', initdef=False) edzed.FuncBlock('xor', func=lambda a, b: bool(a) != bool(b)).connect(ctrl, 'xor') simtask = asyncio.create_task(circuit.run_forever()) await circuit.wait_init() assert ctrl.output is not edzed.UNDEF # so far so good, but now create an instability ctrl.put(True) with pytest.raises(edzed.EdzedCircuitError, match="instability"): await asyncio.wait_for(simtask, timeout=1.0)
def test_init(circuit): """Initial value is assigned on init.""" INITIAL = 'default_value' inp = edzed.Input('input', initdef=INITIAL) assert inp.output is edzed.UNDEF init(circuit) assert inp.output == INITIAL inp.event('put', value=3.14) assert inp.output == 3.14 inp.event('put', value=inp.initdef) # reset to default assert inp.output == INITIAL
def test_dataedit_add_output(circuit): """Test add_output.""" add = edzed.Input('add', initdef=12) dest = EventMemory('dest') src = edzed.Input('src', on_output=edzed.Event( dest, etype='ev', efilter=edzed.DataEdit.add_output('A', add).add_output( 'self', 'src'), ), initdef=3) init(circuit) CDATA = {'source': 'src', 'trigger': 'output'} src.put(4) assert dest.output[1] == { **CDATA, 'previous': 3, 'value': 4, 'self': 4, 'A': 12 } add.put('old') add.put('new') src.put(5) assert dest.output[1] == { **CDATA, 'previous': 4, 'value': 5, 'self': 5, 'A': 'new' } src.put(6) assert dest.output[1] == { **CDATA, 'previous': 5, 'value': 6, 'self': 6, 'A': 'new' }
def test_filter(circuit): """Test event filter.""" def even_numbers_only(data): return data.get('value', 1) % 2 == 0 dest = edzed.Input('dest', initdef=None) src = edzed.Input('src', on_output=edzed.Event(dest, efilter=even_numbers_only), initdef=0) init(circuit) assert src.output == 0 assert dest.output == 0 src.put(1) assert src.output == 1 assert dest.output == 0 src.put(8) assert src.output == 8 assert dest.output == 8 src.put(33) assert src.output == 33 assert dest.output == 8
def test_remove_unused(circuit): """ Verify that unused keys are removed. All 'edzed-*' keys are reserved for internal use and are preserved. """ inp = edzed.Input('ipers', initdef=1, persistent=True) storage = {inp.key: 2, 'wtf': 3, 'edzed-xyz': 4} circuit.set_persistent_data(storage) init(circuit) assert inp.output == 2 assert storage == {inp.key: 2, 'edzed-xyz': 4} # without the 'wtf' item
def test_allowed(circuit): """allowed validator test.""" ALLOWED = (False, 'YES', 2.5) NOT_ALLOWED = (True, None, '', 'hello', 99) inp = edzed.Input('input', allowed=ALLOWED, initdef=False) init(circuit) for v in ALLOWED: assert inp.event('put', value=v) is True assert inp.output == v last = inp.output for v in NOT_ALLOWED: assert inp.event('put', value=v) is False assert inp.output == last