def test_interruption_in_machines_with_sequential_state(capsys): ws1 = WaitState("ws1", 0.2) ws2 = WaitState("ws2", 0.2) ps1 = PrintState("ps1", "Print1") es = IdleState("es") iss = IdleState("iss") sm = SequentialState("sm", children=[ws1, ws2, ps1]) sm.add_transition_on_success(es) sm.add_transition(lambda s, b: s._curr_child.checkName('ws2'), iss) exe = Machine("exe", sm, ["es", "iss"], rate=100) exe.run() assert exe._exception_raised_state_name == "" assert exe._internal_exception is None assert exe._status == StateStatus.SUCCESS assert not exe._run_thread.is_alive() assert exe._curr_state._name == 'iss' assert exe.is_end() assert capsys.readouterr().out == "" assert not sm._run_thread.is_alive() assert not ws1._run_thread.is_alive() assert not ws2._run_thread.is_alive() assert sm._status == StateStatus.INTERRUPTED assert ws2._status == StateStatus.INTERRUPTED assert ws1._status == StateStatus.SUCCESS assert ps1._status == StateStatus.UNKNOWN assert ws2.checkStatus(StateStatus.INTERRUPTED) assert ws1.checkStatus(StateStatus.SUCCESS)
def test_object_set_get(capsys): class SetState(State): def execute(self, board: Board): obj = {'hello': [1, 2, 3], 'name': {'first': 'test'}} board.set('obj', obj) obj['name'] = {} return StateStatus.SUCCESS class GetState(State): def execute(self, board): obj = board.get('obj') assert obj['hello'] == [1, 2, 3] assert obj['name']['first'] == 'test' return StateStatus.SUCCESS s = SetState('s') g = GetState('g') w = WaitState('w', 1) s.add_transition_on_success(w) w.add_transition_on_success(g) exe = Machine('xe', s, end_state_ids=['g']) exe.run() assert exe.is_end() assert exe._curr_state._status == StateStatus.SUCCESS
def test_atleastone_interrupt(capsys): interrupted = False class WaitAndPrint(State): def execute(self, board: Board) -> typing.Optional[StateStatus]: time.sleep(0.5) if self.is_interrupted(): nonlocal interrupted interrupted = True return StateStatus.INTERRUPTED print("HelloWorld") return StateStatus.SUCCESS one = AtLeastOneState("one", children=[ PrintState('p5', "ps5"), WaitAndPrint("ws") ]) es = IdleState("endState") one.add_transition_on_success(es) exe = Machine("xe", one, end_state_ids=["endState"], rate=10) exe.run() assert capsys.readouterr().out == "ps5\n" assert interrupted
def test_end_case(): ps1 = PrintState("ps1", "Hello World") es = DummyState("endState") ps1.add_transition_on_success(es) exe = Machine("xe", ps1, end_state_ids=["endState"], rate=10) exe.run() assert exe.is_end()
def test_interrupt_machine(capsys): s1 = WaitState('s1', 1.1) s2 = DummyState('s2') s1.add_transition_on_success(s2) mac = Machine("mac", s1, ["s2"], debug=True, rate=1) mac.start(None) assert mac.interrupt()
def test_simple_machine(capsys): ps1 = PrintState("ps1", "print1") ps2 = PrintState("ps2", "print2") ps1.add_transition_on_success(ps2) exe = Machine("xe", ps1, end_state_ids=["ps2"], rate=10) b = Board() exe.run(b) assert exe.is_end() assert capsys.readouterr().out == "print1\nprint2\n"
def test_print_state(capsys): print_text = "this is a print_text" ps = PrintState("p1", print_text) es = IdleState("endState") ps.add_transition_on_success(es) exe = Machine("xe", ps, end_state_ids=["endState"], rate=10) exe.run() captured = capsys.readouterr() assert captured.out == print_text + '\n'
def test_machine_rate_fast(): ps1 = PrintState("ps1", "print1") # execute at second 0 ps2 = PrintState("ps1", "print2") # execute at second 0.1 es = DummyState("endState") # execute at second 0.2 ps1.add_transition_on_success(ps2) ps2.add_transition_on_success(es) exe = Machine("xe", ps1, end_state_ids=["endState"], rate=10) start_time = time.time() exe.run() duration = time.time() - start_time assert 0.2 == pytest.approx(duration, abs=1e-2)
def test_parse_debug_info(): from behavior_machine.library import SequentialState, IdleState, WaitState from behavior_machine.core import Machine s1 = IdleState('s1') exe = Machine('exe', s1) info = exe.get_debug_info() parse_str = logging.parse_debug_info(info) assert parse_str[0] == 'exe(Machine) -- UNKNOWN' assert parse_str[1] == ' -> s1(IdleState) -- UNKNOWN'
def test_wait_state(): s1 = WaitState("s1", 2) s2 = IdleState("s2") s1.add_transition_on_success(s2) exe = Machine("test", s1, end_state_ids=['s2'], rate=10) start_time = time.time() exe.run() duration = time.time() - start_time # Because the waut these are executed, its hard to know the margin assert duration == pytest.approx(2, rel=0.1)
def test_chain_case(): s1 = DummyState("s1") s2 = DummyState("s2") s1.add_transition_on_success(s2) s3 = DummyState("s3") s2.add_transition_on_success(s3) exe = Machine("xe", s1, end_state_ids=["s3"], rate=10) b = Board() exe.start(b, manual_exec=True) exe.update(b, True) assert not exe.is_end() exe.update(b, True) assert exe.is_end()
def test_end_case_delay(capsys): ps1 = PrintState("ps1", "Hello World") class EndState(State): def execute(self, board): time.sleep(0.5) print('completed') return StateStatus.SUCCESS es = EndState('endState') ps1.add_transition_on_success(es) exe = Machine("xe", ps1, end_state_ids=["endState"], rate=10) exe.run() assert exe.is_end() assert capsys.readouterr().out == "Hello World\ncompleted\n"
def test_machine_rate_fast(): w1 = WaitState("w1", 0.05) # execute at second 0 w2 = WaitState("w2", 0.05) # execute at second 0.1s es = DummyState("endState") # execute at second 0.2 w1.add_transition_on_success(w2) w2.add_transition_on_success(es) exe = Machine("xe", w1, end_state_ids=["endState"], rate=10) start_time = time.time() exe.run() duration = time.time() - start_time assert pytest.approx(duration, abs=1e-2) == 0.2 assert w1._status == StateStatus.SUCCESS assert w2._status == StateStatus.SUCCESS
def test_machine_with_exception_in_transition(capsys): is1 = DummyState('d1') is2 = DummyState('d2') is1.add_transition(lambda s, b: s.unknown(), is2) mac = Machine("mac", is1, ["is2"]) mac.run() assert mac._status == StateStatus.EXCEPTION assert not mac._run_thread.is_alive() assert not is1._run_thread.is_alive() assert is2._run_thread is None # Never reach is2
def test_nested_sequential_state(capsys): ps1 = PrintState("ps1", "Print1") ps2 = PrintState("ps2", "Print2") ps3 = PrintState("ps3", "Print3") ps4 = PrintState("ps4", "Print4") es = IdleState("endState") sm = SequentialState("sm", children=[ps3, ps2]) sm2 = SequentialState("sm2", children=[ps4, sm, ps1]) sm2.add_transition_on_success(es) mach = Machine("xe", sm2, end_state_ids=['endState'], rate=10) mach.run() assert capsys.readouterr().out == "Print4\nPrint3\nPrint2\nPrint1\n"
def test_parallel_one_state_exception(capsys): class ExceptionAfter1SecState(State): def execute(self, board): time.sleep(1) return StateStatus.EXCEPTION ws = WaitState("ws1", 5) fs = ExceptionAfter1SecState("fs") es = IdleState("es") fes = IdleState("fs-terminal") pm = ParallelState('pm', [ws, fs]) pm.add_transition_on_success(es) pm.add_transition_on_failed(fes) exe = Machine("main_machine", pm, end_state_ids=['es', 'fs-terminal'], rate=10) # run machine and see how it reacts exe.start(None) # wait for 0.5 second to see it is still running assert not exe.wait(0.5) assert exe.check_status(StateStatus.RUNNING) assert exe._curr_state.check_name('pm') # at this point, it should throw or raise the exception # wait another 1.5 seconds assert exe.wait(1.5) assert exe.check_status(StateStatus.EXCEPTION) assert not pm._run_thread.is_alive()
def test_debugging_machine(capsys): from behavior_machine import logging logging.add_fs('capsys', sys.stdout) s1 = WaitState('s1', 1.1) s2 = DummyState('s2') s1.add_transition_on_success(s2) mac = Machine("mac", s1, ["s2"], debug=True, rate=1) mac.run() assert mac.is_end() assert capsys.readouterr().out == ("[Base] mac(Machine) -- RUNNING\n" " -> s1(WaitState) -- RUNNING\n" "[Base] mac(Machine) -- RUNNING\n" " -> s2(DummyState) -- SUCCESS\n")
def test_interrupt_in_parallel_state(capsys): ws = WaitState("ws1", 1) ws2 = WaitState("ws2", 2) es = IdleState("es") pm = ParallelState('pm', [ws, ws2]) pm.add_transition_on_success(es) pm.add_transition_after_elapsed(es, 0.1) exe = Machine("main_machine", pm, end_state_ids=['es'], rate=10) # run machine exe.start(None) # because of the elapsed transition, the machine will immediate transition to the end state in 0.1 seconds assert exe.wait(0.2) assert ws._status == StateStatus.INTERRUPTED assert ws2._status == StateStatus.INTERRUPTED assert not pm._run_thread.is_alive()
def test_flow_into_machine(capsys): test_phrase = "test_flow_into_machine" class OnlyState(State): def execute(self, board): assert self.flow_in == test_phrase print("only-state") return StateStatus.SUCCESS os = OnlyState("only") mac = Machine("mac", os, ["only"]) mac.run(flow_in=test_phrase) assert mac.is_end() assert capsys.readouterr().out == "only-state\n"
def test_machine_with_exception_in_transition_with_zombie_states(capsys): ws1 = WaitState('ws1', 10) is2 = DummyState('d2') ws1.add_transition(lambda s, b: s.unknown(), is2) mac = Machine("mac", ws1, ["is2"]) mac.run() assert mac._status == StateStatus.EXCEPTION # this is an interrupted, because exception happen at higher level assert ws1._status == StateStatus.INTERRUPTED assert not mac._run_thread.is_alive() assert not ws1._run_thread.is_alive() assert is2._run_thread is None # Never reach it
def test_transition_on_failed(capsys): class failedState(State): def execute(self, board): return StateStatus.FAILED fs = failedState('fs') ps1 = PrintState("ps1", "success") ps2 = PrintState("ps2", "failed") fs.add_transition_on_success(ps1) fs.add_transition_on_failed(ps2) exe = Machine("xe", fs, end_state_ids=["ps1", "ps2"], rate=10) exe.run(None) assert exe.is_end() assert exe._curr_state.check_name("ps2") assert capsys.readouterr().out == "failed\n"
def test_sequential_state(capsys): ps1 = PrintState("ps1", "Print1") ps2 = PrintState("ps1", "Print2") ps3 = PrintState("ps1", "Print3") es = IdleState("endState") sm = SequentialState("sm", children=[ps2, ps3]) sm.add_children(ps1) sm.add_transition_on_success(es) exe = Machine("xe", sm, end_state_ids=["endState"], rate=10) exe.run() assert capsys.readouterr().out == "Print2\nPrint3\nPrint1\n"
def test_interrupt_in_parallel_state(capsys): ws = WaitState("ws1", 1) ws2 = WaitState("ws2", 2) es = IdleState("es") pm = ParallelState('pm', [ws, ws2]) pm.add_transition_on_success(es) pm.add_transition(lambda x, y: True, es) exe = Machine("main_machine", pm, end_state_ids=['es'], rate=10) # run machine exe.start(None) # because of the always transition, it should happen in about 10 assert exe.wait(0.2) assert ws.checkStatus(StateStatus.INTERRUPTED) assert ws2.checkStatus(StateStatus.INTERRUPTED) assert not pm._run_thread.is_alive()
def test_sequential_state_success(capsys): ps1 = PrintState("ps1", "Print1") ps2 = PrintState("ps2", "Print2") es = IdleState("es") seqs = SequentialState("sm", children=[ps1, ps2]) seqs.add_transition_on_success(es) exe = Machine("m1", seqs, ['es']) exe.run() assert capsys.readouterr().out == "Print1\nPrint2\n" assert exe.is_end() assert exe._curr_state == es assert seqs._status == StateStatus.SUCCESS assert ps1._status == StateStatus.SUCCESS assert ps2._status == StateStatus.SUCCESS
def make_machine(name): s1 = IdleState("s1") s2 = IdleState("s2") s1.add_transition_on_success(s2) return Machine(name, s1)
def test_machine_with_exception(capsys): ps1 = PrintState("ps1", "p1") re1 = RaiseExceptionState('re1') ps2 = PrintState("ps2", "p2") ps1.add_transition_on_success(re1) re1.add_transition_on_success(ps2) mac = Machine("mac", ps1, ["ps2"]) mac.run() assert capsys.readouterr().out == 'p1\n' assert mac.check_status(StateStatus.EXCEPTION) assert str(mac._internal_exception) == "raiseException" assert mac._exception_raised_state_name == "mac.re1"
def test_transition_on_complete(capsys): class NothingState(State): def execute(self, board): print("hello") ds1 = DummyState("d1") ns = NothingState("ns") ds2 = DummyState("ds2") ds1.add_transition_on_success(ns) ns.add_transition_on_complete(ds2) exe = Machine("xe", ds1, end_state_ids=["ds2"], rate=10) exe.run(None) assert exe.is_end() assert exe._curr_state.check_name("ds2") assert capsys.readouterr().out == "hello\n"
def test_parallel_one_state_fails(capsys): class FailAfter1SecState(State): def execute(self, board): time.sleep(1) return StateStatus.FAILED ws = WaitState("ws1", 5) fs = FailAfter1SecState("fs") es = IdleState("es") fes = IdleState("fs-terminal") pm = ParallelState('pm', [ws, fs]) pm.add_transition_on_success(es) pm.add_transition_on_failed(fes) exe = Machine("main_machine", pm, end_state_ids=['es', 'fs-terminal'], rate=10) # run machine and see how it reacts exe.start(None) # wait for one second assert not exe.wait(0.5) assert exe.check_status(StateStatus.RUNNING) assert exe._curr_state.check_name('pm') # at this point ws should be done but ws2 is still going # wait another one seconds assert exe.wait(2) assert exe._curr_state == fes assert not pm._run_thread.is_alive()
def test_atleastone_state(capsys): ps1 = PrintState('p1', "ps1") ws1 = WaitState("w1", 0.5) ps2 = PrintState('p2', "ps2") one = AtLeastOneState("one", children=[ ps2, SequentialState("seq", children=[ ws1, ps1 ]) ]) es = IdleState("endState") one.add_transition_on_success(es) exe = Machine("xe", one, end_state_ids=["endState"], rate=10) exe.run() assert capsys.readouterr().out == "ps2\n"
def test_debugging_machine(caplog): import logging logging.basicConfig(level=logging.DEBUG) caplog.set_level(logging.DEBUG) logger = logging.getLogger(__name__) s1 = WaitState('s1', 1.1) s2 = DummyState('s2') s1.add_transition_on_success(s2) mac = Machine("mac", s1, ["s2"], debug=True, rate=1, logger=logger) mac.run() assert mac.is_end() assert len(caplog.records) == 3 assert caplog.records[ 0].message == "[Base] mac(Machine) -- RUNNING\n -> s1(WaitState) -- RUNNING" # This is at t=0 assert caplog.records[ 1].message == "[Base] mac(Machine) -- RUNNING\n -> s1(WaitState) -- RUNNING" # This is at t=1 assert caplog.records[ 2].message == "[Base] mac(Machine) -- RUNNING\n -> s2(DummyState) -- SUCCESS" # At the end