def test_execute_parallel_drone_commands(self): parallel_commands = [ ParallelDroneCommands( [[ SingleDroneCommand("name1", Command.takeoff()), SingleDroneCommand("name1", Command.land()) ], [ SingleDroneCommand("name2", Command.takeoff()), SingleDroneCommand("name2", Command.land()) ]]) ] with patch( 'xdrone.command_converters.dji_tello_edu_drone_executor.FlyTello', return_value=self.fly_tello): executor = DJITelloEduExecutor(self.name_id_map) executor.execute_drone_commands(parallel_commands) calls = [ call.wait_sync(), call.takeoff(tello=1), call.land(tello=1), call.takeoff(tello=2), call.land(tello=2) ] self.fly.assert_has_calls(calls)
def test_parallel_with_different_drones_should_give_correct_commands(self): drone_config_map = { "DRONE1": DefaultDroneConfig(), "DRONE2": DefaultDroneConfig(), "DRONE3": DefaultDroneConfig() } actual = generate_commands(""" main() { {DRONE1.takeoff();} || {DRONE2.takeoff();} || {DRONE3.takeoff();}; { DRONE1.land(); } || { {DRONE2.land();} || {DRONE3.land();}; }; } """, drone_config_map=drone_config_map) expected = [ ParallelDroneCommands( [[SingleDroneCommand("DRONE1", Command.takeoff())], [SingleDroneCommand("DRONE2", Command.takeoff())], [SingleDroneCommand("DRONE3", Command.takeoff())]]), ParallelDroneCommands( [[SingleDroneCommand("DRONE1", Command.land())], [ ParallelDroneCommands( [[SingleDroneCommand("DRONE2", Command.land())], [SingleDroneCommand("DRONE3", Command.land())]]) ]]) ] self.assertEqual(expected, actual)
def test_check_takeoff_land_takeoff_land_should_not_give_error(self): drone_commands = [ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.land()), SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.land()), ] BoundaryChecker(self.boundary_config).check(drone_commands, self.state_updater_map)
def test_eq(self): commands1 = [ None, Command.takeoff(), Command.land(), Command.up(1), Command.down(1), Command.left(1), Command.right(1), Command.forward(1), Command.backward(1), Command.rotate_left(1), Command.rotate_right(1), Command.wait(1), Command.up(2), Command.down(2), Command.left(2), Command.right(2), Command.forward(2), Command.backward(2), Command.rotate_left(2), Command.rotate_right(2), Command.wait(2) ] commands2 = [ None, Command.takeoff(), Command.land(), Command.up(1), Command.down(1), Command.left(1), Command.right(1), Command.forward(1), Command.backward(1), Command.rotate_left(1), Command.rotate_right(1), Command.wait(1), Command.up(2), Command.down(2), Command.left(2), Command.right(2), Command.forward(2), Command.backward(2), Command.rotate_left(2), Command.rotate_right(2), Command.wait(2) ] for i in range(len(commands1)): for j in range(len(commands2)): if i == j: self.assertEqual(commands1[i], commands2[j]) else: self.assertNotEqual(commands1[i], commands2[j])
def test_check_parallel_drone_command_should_detect_collision_and_give_error(self): drone_commands = [SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE2", Command.takeoff()), ParallelDroneCommands([ [SingleDroneCommand("DRONE1", Command.right(1))], [SingleDroneCommand("DRONE2", Command.left(1))] ]), SingleDroneCommand("DRONE1", Command.land()), SingleDroneCommand("DRONE2", Command.land())] with self.assertRaises(SafetyCheckError) as context: self.collision_checker.check(drone_commands, self.state_updater_map) self.assertTrue("Collisions might happen!\nCollision might happen between DRONE1 and DRONE2" in str(context.exception))
def test_basic_program(self): commands = "main() {takeoff(); land();}" expected = [ SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.land()) ] self.assertEqual(expected, generate_commands(commands))
def test_dji_tello_executor_if_receive_error_should_print(self) -> None: mocked_socket = Mock() mocked_socket.recvfrom = Mock(side_effect=Exception('timed out')) mocked_socket.sendto = Mock() commands = [Command.takeoff(), Command.land()] executor = DJITelloExecutor() executor.sock.close() executor.sock = mocked_socket with self.assertLogs(logging.getLogger()) as log: executor.execute_commands(commands) calls = [ call(b"command", ('192.168.10.1', 8889)), call(b"takeoff", ('192.168.10.1', 8889)), call(b"land", ('192.168.10.1', 8889)) ] mocked_socket.sendto.assert_has_calls(calls) expected_log = [ "INFO:root:sent message: b'command'", "ERROR:root:Error met when receiving response: timed out", "INFO:root:sent message: b'takeoff'", "ERROR:root:Error met when receiving response: timed out", "INFO:root:sent message: b'land'", "ERROR:root:Error met when receiving response: timed out" ] self.assertEqual(expected_log, log.output)
def test_function_procedure_in_function(self): commands = generate_commands(""" function func(int i) return int { while i < 10 { i <- i + 1; } return i; } procedure proc(int i) { forward(i * i); } function func2(int i) return int { proc(func(i)); return func(i) * func(i); } main () { takeoff(); forward(func2(1)); land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.forward(100)), SingleDroneCommand("DEFAULT", Command.forward(100)), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, commands)
def test_multiple_commands_should_return_correct_command(self): actual = generate_commands(""" main() { takeoff(); up(1); down(2); left(3); right(4); forward(5); backward(6); rotate_left(7); rotate_right(8); wait(9); land(); } """) expected = [ SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.up(1)), SingleDroneCommand("DEFAULT", Command.down(2)), SingleDroneCommand("DEFAULT", Command.left(3)), SingleDroneCommand("DEFAULT", Command.right(4)), SingleDroneCommand("DEFAULT", Command.forward(5)), SingleDroneCommand("DEFAULT", Command.backward(6)), SingleDroneCommand("DEFAULT", Command.rotate_left(7)), SingleDroneCommand("DEFAULT", Command.rotate_right(8)), SingleDroneCommand("DEFAULT", Command.wait(9)), SingleDroneCommand("DEFAULT", Command.land()) ] self.assertEqual(expected, actual)
def test_dji_tello_executor_if_interrupt_error_should_stop_and_emergency( self) -> None: mocked_socket = Mock() mocked_socket.recvfrom = Mock(return_value=(b'ok', ('192.168.10.1', 8889))) mocked_socket.sendto = Mock() def mocked_sendto(msg, _): if msg == b"takeoff": raise KeyboardInterrupt mocked_socket.sendto.side_effect = mocked_sendto commands = [Command.takeoff(), Command.land()] executor = DJITelloExecutor() executor.sock.close() executor.sock = mocked_socket with self.assertLogs(logging.getLogger()) as log: executor.execute_commands(commands) calls = [ call(b"command", ('192.168.10.1', 8889)), call(b"takeoff", ('192.168.10.1', 8889)), call(b"emergency", ('192.168.10.1', 8889)) ] mocked_socket.sendto.assert_has_calls(calls) expected_log = [ "INFO:root:sent message: b'command'", "INFO:root:received response: ok", "INFO:root:KeyboardInterrupt received. Forced stop." ] self.assertEqual(expected_log, log.output)
def test_update_land_should_update_state(self): state = State(has_taken_off=True, z_meters=3) actual = self.state_updater.update(Command.land(), state) expected = State(has_taken_off=False, time_used_seconds=3 / 0.5, z_meters=0) self.assertEqual(expected, actual)
def test_eq(self): self.assertEqual(SingleDroneCommand("abc", Command.takeoff()), SingleDroneCommand("abc", Command.takeoff())) self.assertNotEqual(SingleDroneCommand("", Command.takeoff()), SingleDroneCommand("abc", Command.takeoff())) self.assertNotEqual(SingleDroneCommand("abc", Command.takeoff()), SingleDroneCommand("abc", Command.land())) self.assertNotEqual(None, SingleDroneCommand("abc", Command.takeoff()))
def test_land_should_return_correct_command(self): actual = generate_commands(""" main() { takeoff(); land(); } """) expected = [ SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.land()) ] self.assertEqual(expected, actual)
def test_check_bad_collision_config_should_not_give_error(self): collision_config = CollisionConfig(collision_meters=0.3, time_interval_seconds=0.001) collision_checker = CollisionChecker(self.drone_config_map, collision_config) drone_commands = [SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.land())] with self.assertRaises(SafetyCheckError) as context: collision_checker.check(drone_commands, self.state_updater_map) self.assertTrue("Error occurred during collision check, please retry with better the collision_config." in str(context.exception))
def test_backward_with_int_parameter_should_return_correct_command(self): actual = generate_commands(""" main() { takeoff(); backward(1); land(); } """) expected = [ SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.backward(1)), SingleDroneCommand("DEFAULT", Command.land()) ] self.assertEqual(expected, actual)
def test_up_with_decimal_parameter_should_return_correct_command(self): actual = generate_commands(""" main() { takeoff(); up(1.0); land(); } """) expected = [ SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.up(1.0)), SingleDroneCommand("DEFAULT", Command.land()) ] self.assertEqual(expected, actual)
def test_land_with_drone_name_should_return_correct_command(self): actual = generate_commands( """ main() { DRONE1.takeoff(); DRONE1.land(); } """, drone_config_map={"DRONE1": DefaultDroneConfig()}) expected = [ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.land()) ] self.assertEqual(expected, actual)
def test_movement_with_no_name_specified_if_only_one_drone_should_use_that_drone( self): drone_config_map = {"THE_ONE": DefaultDroneConfig()} actual = generate_commands(""" main() { takeoff(); land(); } """, drone_config_map=drone_config_map) expected = [ SingleDroneCommand("THE_ONE", Command.takeoff()), SingleDroneCommand("THE_ONE", Command.land()) ] self.assertEqual(expected, actual)
def test_check_single_drone_command_should_check_state_and_catch_error( self): boundary_checker = BoundaryChecker(self.boundary_config) boundary_checker.boundary_config.check_state = Mock( side_effect=SafetyCheckError) drone_commands = [ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.land()) ] with self.assertRaises(SafetyCheckError) as context: BoundaryChecker(self.boundary_config).check( drone_commands, self.state_updater_map)
def test_check_parallel_drone_commands_should_check_state(self): boundary_checker = BoundaryChecker(self.boundary_config) boundary_checker.boundary_config.check_state = Mock() drone_commands = [ ParallelDroneCommands( [[ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.land()) ], [ SingleDroneCommand("DRONE2", Command.takeoff()), SingleDroneCommand("DRONE2", Command.land()) ]]) ] BoundaryChecker(self.boundary_config).check(drone_commands, self.state_updater_map) last_two_calls = [ call("DRONE1", State(time_used_seconds=2)), call("DRONE2", State(time_used_seconds=2, x_meters=1)) ] boundary_checker.boundary_config.check_state.assert_has_calls( last_two_calls)
def test_if_with_error_commands_not_entering_should_not_give_error(self): actual_commands = generate_commands(""" main () { takeoff(); if false { int a <- "error"; forward(1); } land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, actual_commands)
def test_repeat_should_run_correct_commands(self): actual_commands = generate_commands(""" main () { takeoff(); repeat 4 times { forward(1); } land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff())] + \ [SingleDroneCommand("DEFAULT", Command.forward(1)) for _ in range(4)] + \ [SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, actual_commands)
def test_call_procedure_with_parameter_should_shadow_constant(self): commands = generate_commands(""" procedure proc(drone DRONE2) { DRONE2.up(1); } main () { DRONE1.takeoff(); proc(DRONE1); DRONE1.land(); } """, drone_config_map={"DRONE1": DefaultDroneConfig(), "DRONE2": DefaultDroneConfig()}) expected_commands = [SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.up(1)), SingleDroneCommand("DRONE1", Command.land())] self.assertEqual(expected_commands, commands)
def test_check_single_drone_command_should_check_state(self): boundary_checker = BoundaryChecker(self.boundary_config) boundary_checker.boundary_config.check_state = Mock() drone_commands = [ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.land()) ] BoundaryChecker(self.boundary_config).check(drone_commands, self.state_updater_map) calls = [ call("DRONE1", State(has_taken_off=True, time_used_seconds=1, z_meters=1)), call("DRONE2", State(time_used_seconds=1, x_meters=1)), call("DRONE1", State(time_used_seconds=2)), call("DRONE2", State(time_used_seconds=2, x_meters=1)) ] boundary_checker.boundary_config.check_state.assert_has_calls(calls)
def test_call_procedure_should_run_commands(self): commands = generate_commands(""" procedure proc(int i, int j) { up(i + j); down(i); } main () { takeoff(); proc(100, 200); land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.up(300)), SingleDroneCommand("DEFAULT", Command.down(100)), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, commands)
def test_recursion(self): commands = generate_commands(""" function func(int i) return int { if i >= 10 { return 10; } return func(i + 1); } main () { takeoff(); forward(func(1)); land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.forward(10)), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, commands)
def test_if_true_without_else_should_run_correct_commands(self): actual_st = SymbolTable() actual_commands = generate_commands(""" main () { takeoff(); if true { int a <- 1; forward(a); } land(); } """, symbol_table=actual_st) expected_st = SymbolTable() self.assertEqual(expected_st, actual_st) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.forward(1)), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, actual_commands)
def test_loops_in_function(self): commands = generate_commands(""" function func(int i) return int { while i < 10 { i <- i + 1; } return i; } main () { takeoff(); forward(func(1)); land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.forward(10)), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, commands)
def test_loops_in_function(self): commands = generate_commands(""" procedure proc(int i) { while i < 10 { i <- i + 1; } forward(i); } main () { takeoff(); proc(1); land(); } """) expected_commands = [SingleDroneCommand("DEFAULT", Command.takeoff()), SingleDroneCommand("DEFAULT", Command.forward(10)), SingleDroneCommand("DEFAULT", Command.land())] self.assertEqual(expected_commands, commands)
def test_execute_single_drone_command(self): single_commands = [ SingleDroneCommand("name1", Command.takeoff()), SingleDroneCommand("name1", Command.land()), SingleDroneCommand("name2", Command.up(1.1)), SingleDroneCommand("name2", Command.down(1.2)), SingleDroneCommand("name2", Command.left(1.3)), SingleDroneCommand("name2", Command.right(1.4)), SingleDroneCommand("name2", Command.forward(1.5)), SingleDroneCommand("name2", Command.backward(1.6)), SingleDroneCommand("name2", Command.rotate_left(91)), SingleDroneCommand("name2", Command.rotate_right(92)), SingleDroneCommand("name2", Command.wait(1)) ] with patch( 'xdrone.command_converters.dji_tello_edu_drone_executor.FlyTello', return_value=self.fly_tello): executor = DJITelloEduExecutor(self.name_id_map) executor.execute_drone_commands(single_commands) calls = [ call.wait_sync(), call.takeoff(tello=1), call.wait_sync(), call.land(tello=1), call.wait_sync(), call.up(110, tello=2), call.wait_sync(), call.down(120, tello=2), call.wait_sync(), call.left(130, tello=2), call.wait_sync(), call.right(140, tello=2), call.wait_sync(), call.forward(150, tello=2), call.wait_sync(), call.back(160, tello=2), call.wait_sync(), call.rotate_ccw(91, tello=2), call.wait_sync(), call.rotate_cw(92, tello=2), call.wait_sync(), call.pause(1) ] self.fly.assert_has_calls(calls)