def generate_commands(program, drone_config_map: Dict[str, DroneConfig] = None, boundary_checker: BoundaryChecker = None, collision_checker: CollisionChecker = None, has_checks: bool = True, symbol_table: SymbolTable = None, function_table: FunctionTable = None): if drone_config_map is None: drone_config_map = {"DEFAULT": DefaultDroneConfig()} if boundary_checker is None: boundary_checker = BoundaryChecker(BoundaryConfig.no_limit()) if collision_checker is None: collision_checker = CollisionChecker(drone_config_map, DefaultCollisionConfig()) if symbol_table is None: symbol_table = SymbolTable() if function_table is None: function_table = FunctionTable() state_updater_map = { name: StateUpdater(config) for name, config in drone_config_map.items() } tree = _parse_program(program) drone_commands = Compiler(drone_config_map, symbol_table, function_table).visit(tree) if has_checks: boundary_checker.check(drone_commands, state_updater_map) collision_checker.check(drone_commands, state_updater_map) return drone_commands
def test_check_should_call_update_states_and_check_drone_commands_with_correct_parameters( self): boundary_checker = BoundaryChecker(self.boundary_config) boundary_checker._update_states_and_check_drone_commands = Mock() drone_state_map = {"DRONE1": State(), "DRONE2": State(x_meters=1)} drones_involved = {"DRONE1", "DRONE2"} boundary_checker.check([], self.state_updater_map) boundary_checker._update_states_and_check_drone_commands.assert_called_once_with( [], self.state_updater_map, drone_state_map, drones_involved)
def test_check_parallel_drone_commands_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 = [ParallelDroneCommands([[], []])] with self.assertRaises(SafetyCheckError) as context: BoundaryChecker(self.boundary_config).check( drone_commands, self.state_updater_map)
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_in_the_end_not_land_should_give_error(self): with self.assertRaises(SafetyCheckError) as context: drone_commands = [SingleDroneCommand("DRONE1", Command.takeoff())] BoundaryChecker(self.boundary_config).check( drone_commands, self.state_updater_map) self.assertTrue( "Drone 'DRONE1' did not land in the end" in str(context.exception))
def test_check_parallel_drone_commands_nested_should_update_states_correctly( self): drone_commands = \ [ ParallelDroneCommands([ [SingleDroneCommand("DRONE1", Command.wait(5))], [SingleDroneCommand("DRONE2", Command.wait(3)), ParallelDroneCommands([ [SingleDroneCommand("DRONE3", Command.wait(4))], [SingleDroneCommand("DRONE4", Command.wait(1))] ])] ]), SingleDroneCommand("DRONE1", Command.wait(1)) ] drones_involved = {"DRONE1", "DRONE2", "DRONE3", "DRONE4", "DRONE5"} state_updaters = { name: StateUpdater(DroneConfig((1, 0, 0), 2, 180, 2)) for name in drones_involved } drone_state_map = {name: State() for name in drones_involved} BoundaryChecker( self.boundary_config)._update_states_and_check_drone_commands( drone_commands, state_updaters, drone_state_map, drones_involved) expected = { name: State(time_used_seconds=8) for name in drones_involved } self.assertEqual(expected, drone_state_map)
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_check_single_drone_command_wait_when_taken_off_should_not_give_error( self): drone_commands = [ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.wait(1)), SingleDroneCommand("DRONE1", Command.land()) ] BoundaryChecker(self.boundary_config).check(drone_commands, self.state_updater_map)
def generate_commands_with_config(program, config_json, has_checks): drone_config_map, boundary_config, collision_config = ConfigParser.parse( config_json) boundary_checker = BoundaryChecker(boundary_config) collision_checker = CollisionChecker(drone_config_map, collision_config) generated_commands = generate_commands(program, drone_config_map, boundary_checker, collision_checker, has_checks) return generated_commands, drone_config_map, boundary_config
def test_if_given_boundary_checker_should_use_it_to_check_safety(self): commands = "main() {takeoff(); up(1000); land();}" with self.assertRaises(SafetyCheckError) as context: generate_commands(commands, boundary_checker=BoundaryChecker( BoundaryConfig(max_seconds=10000, max_z_meters=1))) self.assertTrue( "Drone 'DEFAULT': the z coordinate 1001 will go beyond its upper limit 1" in str(context.exception))
def test_check_single_drone_command_takeoff_when_taken_off_should_give_error( self): with self.assertRaises(SafetyCheckError) as context: drone_commands = [ SingleDroneCommand("DRONE1", Command.takeoff()), SingleDroneCommand("DRONE1", Command.takeoff()) ] BoundaryChecker(self.boundary_config).check( drone_commands, self.state_updater_map) self.assertTrue( "'takeoff' command used when drone 'DRONE1' has already been taken off" in str(context.exception))
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_check_single_drone_command_should_update_state_correctly(self): drone_commands = [SingleDroneCommand("DRONE1", Command.takeoff())] drone_state_map = {"DRONE1": State(), "DRONE2": State(x_meters=1)} drones_involved = {"DRONE1", "DRONE2"} BoundaryChecker( self.boundary_config)._update_states_and_check_drone_commands( drone_commands, self.state_updater_map, drone_state_map, drones_involved) expected = { "DRONE1": State(has_taken_off=True, time_used_seconds=1, z_meters=1), "DRONE2": State(time_used_seconds=1, x_meters=1) } self.assertEqual(expected, drone_state_map)
def test_if_given_state_updater_should_use_it_to_update_state(self): commands = "main() {takeoff(); land();}" with self.assertRaises(SafetyCheckError) as context: generate_commands(commands, drone_config_map={ "DEFAULT": DroneConfig(init_position=(0, 0, 0), speed_mps=1, rotate_speed_dps=90, takeoff_height_meters=10) }, boundary_checker=BoundaryChecker( BoundaryConfig(max_seconds=10, max_z_meters=1))) self.assertTrue( "Drone 'DEFAULT': the z coordinate 10 will go beyond its upper limit 1" in str(context.exception))
def test_check_parallel_drone_commands_should_let_drone_not_involved_wait( self): drone_commands = [ ParallelDroneCommands( [[], [SingleDroneCommand("DRONE2", Command.wait(5))]]) ] drone_state_map = {"DRONE1": State(), "DRONE2": State(x_meters=1)} drones_involved = {"DRONE1", "DRONE2"} BoundaryChecker( self.boundary_config)._update_states_and_check_drone_commands( drone_commands, self.state_updater_map, drone_state_map, drones_involved) expected = { "DRONE1": State(time_used_seconds=5), "DRONE2": State(time_used_seconds=5, x_meters=1) } self.assertEqual(expected, drone_state_map)
def test_check_parallel_drone_commands_should_wait_for_slower_branch(self): drone_commands = [ ParallelDroneCommands( [[SingleDroneCommand("DRONE1", Command.takeoff())], [SingleDroneCommand("DRONE2", Command.wait(5))]]) ] drone_state_map = {"DRONE1": State(), "DRONE2": State(x_meters=1)} drones_involved = {"DRONE1", "DRONE2"} BoundaryChecker( self.boundary_config)._update_states_and_check_drone_commands( drone_commands, self.state_updater_map, drone_state_map, drones_involved) expected = { "DRONE1": State(has_taken_off=True, time_used_seconds=5, z_meters=1), "DRONE2": State(time_used_seconds=5, x_meters=1) } self.assertEqual(expected, drone_state_map)
def test_check_single_drone_command_other_command_when_not_taken_off_should_give_error( self): commands = [ Command.land(), Command.up(1), Command.down(1), Command.left(1), Command.right(1), Command.forward(1), Command.backward(1), Command.rotate_left(90), Command.rotate_right(90) ] for command in commands: with self.assertRaises(SafetyCheckError) as context: drone_commands = [SingleDroneCommand("DRONE1", command)] BoundaryChecker(self.boundary_config).check( drone_commands, self.state_updater_map) self.assertTrue( "'{}' command used when drone 'DRONE1' has not been taken off". format(command.opcode) in str(context.exception))
def test_if_not_given_state_updater_should_use_default_to_update_state( self): commands = "main() {takeoff(); land();}" generate_commands(commands, boundary_checker=BoundaryChecker( BoundaryConfig(max_seconds=10, max_z_meters=1)))