class Communicator(Singleton): '''Interface between listeners and the application.''' def _initialize(self): self._database = MemcachedDatabase() self._action_manager = ActionManager() self._population_control = PopulationControl() self._admin_handler = AdminHandler() def execute_command(self, password, command, args): '''Execute client's command.''' try: if command in ["born", "give_birth"]: result = self._population_control.execute_command(password, command, args) elif command == "map_data": result = self._admin_handler.execute_command(password, command, args) else: result = self._action_manager.do_action(password, command, args) # Committing or rollbacking all changes after the completion of execution. self._database.commit() return result except Exception: self._database.rollback() raise
def test_for_update(self): '''Tests of for_update flag works correctly.''' database = MemcachedDatabase() robot_id = "test_for_update_18762" robot = Robot(robot_id, "123") database.add_robot(robot, (10, 0)) database.commit() new_robot = database.get_robot(robot_id, for_update=True) # Testing lock with self.assertRaises(LockAlreadyAquiredError): database.get_robot(robot_id, for_update=True) # Testing commit. new_robot.set_alive(False) # It shouldn't be changed yet. new_robot_2 = database.get_robot(robot_id) self.assertNotEqual(new_robot.get_alive(), new_robot_2.get_alive()) # Actually committing changes. database.commit() new_robot_2 = database.get_robot(robot_id) self.assertEqual(new_robot.get_alive(), new_robot_2.get_alive()) # Lock should be freed. database.get_robot(robot_id, for_update=True) database.rollback()
def test_for_update(self): '''Tests for_update flag of get_square method.''' database = MemcachedDatabase() square = database.get_square((6, 1), for_update=True) # Testing the lock. with self.assertRaises(LockAlreadyAquiredError): database.get_square((6, 1), for_update=True) # Testing commit. square.set_robot_id("ujhqi981762yhdg67") # It shouldn't be changed yet. new_square = database.get_square((6, 1)) self.assertNotEqual(square.get_robot_id(), new_square.get_robot_id()) # Committing changes. database.commit() new_square = database.get_square((6, 1)) self.assertEqual(square.get_robot_id(), new_square.get_robot_id()) # Lock should be freed. new_square = database.get_square((6, 1), for_update=True) database.rollback()
def test_no_plant(self): '''Tests when there's no plant to eat.''' action_manager = ActionManager() database = MemcachedDatabase() with self.assertRaises(NoPlantToEat): action_manager.do_action("123", "eat", [TestEatAction.ROBOT_ID]) database.rollback()
def test_locked(self): '''Tests with a locked square.''' action_manager = ActionManager() database = MemcachedDatabase() database.get_square(TestEatAction.LOCATION, for_update=True) with self.assertRaises(LockAlreadyAquiredError): action_manager.do_action("123", "eat", [TestEatAction.ROBOT_ID]) database.rollback()
def test_locked_robot(self): '''Tests with a locked robot.''' population_control = PopulationControl() database = MemcachedDatabase() database.get_robot(TestGiveBirth.ROBOT_ID, for_update=True) with self.assertRaises(LockAlreadyAquiredError): population_control.execute_command("123", "give_birth", [TestGiveBirth.ROBOT_ID]) database.rollback()
def test_bad_argument(self): '''Tests when sending bad arguments to the action.''' action_manager = ActionManager() database = MemcachedDatabase() with self.assertRaises(InvalidArgumentsError): action_manager.do_action("123", "eat", [TestEatAction.ROBOT_ID, None]) database.rollback() with self.assertRaises(InvalidArgumentsError): action_manager.do_action("123", "eat", [TestEatAction.ROBOT_ID, "", 9]) database.rollback()
def test_rollback(self): '''Tests if calling rollback works correctly.''' database = MemcachedDatabase() new_robot = Robot("test_rollback_87162", "123") database.add_robot(new_robot, (1, 1)) database.rollback() database.commit() with self.assertRaises(RobotNotFoundError): database.get_robot("test_rollback_87162")
def test_rollback(self): '''Tests if database rolls back the changes correctly.''' database = MemcachedDatabase() square = database.get_square((6, 2), for_update=True) square.set_robot_id("iuwuyehdmn990198283") database.rollback() database.commit() new_square = database.get_square((6, 2)) self.assertNotEqual(square.get_robot_id(), new_square.get_robot_id())
def test_with_parent(self): '''Tests borning a robot with a parent.''' population_control = PopulationControl() database = MemcachedDatabase() database.add_password("oijdnnh76153WEd") robot = Robot("test_with_parent_18873", "123") database.add_robot(robot, (14, 1)) database.commit() population_control.execute_command("oijdnnh76153WEd", "born", ["test_with_parent_18873", "My Child"]) database.rollback()
def test_with_parent(self): '''Tests borning a robot with a parent.''' population_control = PopulationControl() database = MemcachedDatabase() database.add_password("oijdnnh76153WEd") robot = Robot("test_with_parent_18873", "123") database.add_robot(robot, (14, 1)) database.commit() population_control.execute_command( "oijdnnh76153WEd", "born", ["test_with_parent_18873", "My Child"]) database.rollback()
def test_bad_name(self): '''Tries to born a robot with an invalid name. Should be fail.''' population_control = PopulationControl() database = MemcachedDatabase() database.add_password("OIkdj981HJDJHcnm_1") database.add_password("OIkdj981HJDJHcnm_2") database.add_password("OIkdj981HJDJHcnm_3") database.add_password("OIkdj981HJDJHcnm_4") database.commit() long_name = "n" * (MAX_ROBOT_NAME + 1) with self.assertRaises(LongRobotNameError): population_control.execute_command("OIkdj981HJDJHcnm_1", "born", [None, long_name]) database.rollback() with self.assertRaises(LongRobotNameError): population_control.execute_command("OIkdj981HJDJHcnm_2", "born", [None, None]) database.rollback() with self.assertRaises(LongRobotNameError): population_control.execute_command("OIkdj981HJDJHcnm_3", "born", [None, b"some bytes"]) database.rollback() with self.assertRaises(LongRobotNameError): population_control.execute_command("OIkdj981HJDJHcnm_4", "born", [None, database]) database.rollback()
def test_not_enough_honor(self): '''Tests a robot with few honors, trying to give birth.''' population_control = PopulationControl() database = MemcachedDatabase() configs = Configs() robot = database.get_robot(TestGiveBirth.ROBOT_ID, for_update=True) robot.set_honor(configs.get_robots_birth_required_honor() - 1) database.commit() with self.assertRaises(NotEnoughHonorError): population_control.execute_command("123", "give_birth", [TestGiveBirth.ROBOT_ID]) database.rollback()
def test_bad_direction(self): '''Sends an invalid direction as arguments.''' robot_id = "test_bad_direction_18445" robot = Robot(robot_id, "123") world = World() action_manager = ActionManager() database = MemcachedDatabase() world.add_robot(robot, (12, 6)) database.commit() with self.assertRaises(InvalidArgumentsError): action_manager.do_action("123", "move", [robot_id, "U"]) database.rollback() with self.assertRaises(InvalidArgumentsError): action_manager.do_action("123", "move", [robot_id, 988]) database.rollback() with self.assertRaises(InvalidArgumentsError): action_manager.do_action("123", "move", [robot_id, None]) database.rollback() with self.assertRaises(InvalidArgumentsError): action_manager.do_action("123", "move", [robot_id]) database.rollback()
def test_moving_outside(self): '''Tests moving a robot to outside of the world.''' robot_id = "test_moving_outside_981165" robot = Robot(robot_id, "123") world = World() action_manager = ActionManager() database = MemcachedDatabase() world.add_robot(robot, (14, 2)) database.commit() with self.assertRaises(InvalidLocationError): action_manager.do_action("123", "move", [robot_id, "E"]) database.rollback()
def test_bad_arguments(self): '''Calls the action with invalid arguments.''' action = InfoAction() database = MemcachedDatabase() robot = Robot("test_info_action_1293", "123") with self.assertRaises(InvalidArgumentsError): action.do_action(robot, [robot.get_id(), None]) database.rollback() with self.assertRaises(InvalidArgumentsError): action.do_action(robot, [robot.get_id(), "", "09"]) database.rollback() with self.assertRaises(InvalidArgumentsError): action.do_action(robot, [robot.get_id(), []])
def test_locked_square(self): '''Tests with a already-locked square.''' database = MemcachedDatabase() robot = Robot("oi981872yuweu.9887", "123") robot.set_location((5, 0)) robot.set_has_water(True) database.get_square((5, 0), for_update=True) action = WaterAction() with self.assertRaises(LockAlreadyAquiredError): action.do_action(robot, ["oi981872yuweu.9887"]) # Freeing lock. database.rollback()
def test_lock(self): '''Tests if location is locked.''' robot_id = "test_move_lock_76120" robot = Robot(robot_id, "123") world = World() action_manager = ActionManager() database = MemcachedDatabase() world.add_robot(robot, (13, 6)) database.commit() database.get_square((13, 7), for_update=True) with self.assertRaises(LockAlreadyAquiredError): action_manager.do_action("123", "move", [robot_id, "S"]) database.rollback()
def test_no_plant_square(self): '''Tests watering a square without any plant.''' database = MemcachedDatabase() robot = Robot("098kk.ski87.99", "123") robot.set_location((6, 0)) robot.set_has_water(True) action = WaterAction() action.do_action(robot, ["098kk.ski87.99"]) self.assertFalse(robot.get_has_water()) # Honor shouldn't increase because this robot didn't really watered a plant. self.assertEqual(robot.get_honor(), 0) database.rollback()
def test_rollback(self): '''Tests if changes rolls back correctly.''' database = MemcachedDatabase() robot_id = "test_rollback_18098" robot = Robot(robot_id, "123") database.add_robot(robot, (11, 0)) database.commit() new_robot = database.get_robot(robot_id, for_update=True) new_robot.set_alive(False) database.rollback() database.commit() new_robot_2 = database.get_robot(robot_id) self.assertNotEqual(new_robot.get_alive(), new_robot_2.get_alive())
def tearDown(cls): database = MemcachedDatabase() database.rollback()
class TestActionManager(unittest.TestCase): def __init__(self, *args, **kwargs): super(TestActionManager, self).__init__(*args, **kwargs) self._action_manager = ActionManager() self._database = MemcachedDatabase() def tearDown(self): # Rolling back any remaining thing. self._database.rollback() def test_not_exist_robot(self): '''Test if ActionManager handles not-existed robot.''' with self.assertRaises(RobotNotFoundError): self._action_manager.do_action("123", "move", ["not_existed_robot_id"]) def test_bad_robot_id(self): '''Test invalid robot IDs.''' # Note that listeners checks if `args' is a list, so action manager won't receive another type. with self.assertRaises(actions.exceptions.InvalidArgumentsError): self._action_manager.do_action("123", "move", []) with self.assertRaises(actions.exceptions.InvalidArgumentsError): self._action_manager.do_action("123", "move", [None]) def test_invalid_password(self): '''Test if ActionManager authenticate passwords correctly.''' robot = Robot("test_invalid_password_95312", "andhue-ifue876-fkdnpw-1") self._database.add_robot(robot, (3, 1)) self._database.commit() with self.assertRaises(AuthenticationFailedError): self._action_manager.do_action("ieukjdf-ioquiwe-751io", "status", ["test_invalid_password_95312"]) def test_dead_robot(self): '''Test if ActionManager checks a dead robot.''' robot = Robot("test_dead_robot_98176", "1234") robot.set_alive(False) self._database.add_robot(robot, (3, 2)) self._database.commit() with self.assertRaises(AuthenticationFailedError): self._action_manager.do_action("1234", "status", ["test_dead_robot_98176"]) def test_bad_actions(self): '''Test wrong action IDs.''' robot = Robot("test_bad_actions_2376", "123") self._database.add_robot(robot, (4, 1)) self._database.commit() with self.assertRaises(actions.exceptions.InvalidActionError): self._action_manager.do_action("123", "not-exist-action", ["test_bad_actions_2376"]) self._database.rollback() with self.assertRaises(actions.exceptions.InvalidActionError): self._action_manager.do_action("123", 5432, ["test_bad_actions_2376"]) self._database.rollback() with self.assertRaises(actions.exceptions.InvalidActionError): self._action_manager.do_action("123", None, ["test_bad_actions_2376"]) self._database.rollback() with self.assertRaises(actions.exceptions.InvalidActionError): self._action_manager.do_action("123", "", ["test_bad_actions_2376"]) def test_ok(self): '''Execute a fine action.''' robot = Robot("test_ok_action_3278", "4467yrt-ddfjh-1u872-oiie") self._database.add_robot(robot, (3, 3)) self._database.commit() initial_energy = robot.get_energy() initial_age = robot.get_life() result = self._action_manager.do_action("4467yrt-ddfjh-1u872-oiie", "status", ["test_ok_action_3278"]) self.assertEqual(result['alive'], True) # Robot should lost energy and age. self._database.commit() robot = self._database.get_robot("test_ok_action_3278") self.assertEqual(robot.get_energy(), initial_energy - 1) self.assertEqual(robot.get_life(), initial_age - 1) def test_losing_energy_on_error(self): '''Tests if ActionManager reduces energy and age after an exception.''' robot = Robot("test_losing_energy_on_error_981", "091oikjdmncj") self._database.add_robot(robot, (5, 3)) self._database.commit() initial_energy = robot.get_energy() initial_age = robot.get_life() with self.assertRaises(actions.exceptions.InvalidActionError): self._action_manager.do_action("091oikjdmncj", "invalid_action", ["test_losing_energy_on_error_981"]) self._database.commit() robot = self._database.get_robot("test_losing_energy_on_error_981") self.assertEqual(robot.get_energy(), initial_energy - 1) self.assertEqual(robot.get_life(), initial_age - 1) # Robot shouldn't lose energy on authentication error. with self.assertRaises(AuthenticationFailedError): self._action_manager.do_action("wrong pass", "invalid_action", ["test_losing_energy_on_error_981"]) self._database.rollback() robot = self._database.get_robot("test_losing_energy_on_error_981") self.assertEqual(robot.get_energy(), initial_energy - 1) self.assertEqual(robot.get_life(), initial_age - 1) def test_delay(self): '''Tests delay between robot actions.''' robot = Robot("test_delay_1223", "09112345") self._database.add_robot(robot, (6, 3)) self._database.commit() self._action_manager.do_action("09112345", "sense", [robot.get_id()]) self._database.commit() start_time = time.time() self._action_manager.do_action("09112345", "sense", [robot.get_id()]) elapsed_time = time.time() - start_time self._database.commit() # one millisecond reduced from delay to cover error. self.assertGreater(elapsed_time, 0.029)
class ActionManager: def __init__(self): self._authenticator = Authenticator() self._database = MemcachedDatabase() configs = Configs() self._robots_action_delay = configs.get_robots_actions_delay() / 1000 self._actions = {'status': StatusAction(), 'sense': SenseAction(), 'move': MoveAction(), 'plant': PlantAction(), 'pick_water': PickWaterAction(), 'info': InfoAction(), 'eat': EatAction(), 'water': WaterAction()} def do_action(self, password, action_type, args): '''Will do the requested action.''' if len(args) == 0: raise exceptions.InvalidArgumentsError("`robot_id' should be passes as the first argument.") robot_id = args[0] if not isinstance(robot_id, str): raise exceptions.InvalidArgumentsError( "First argument (robot_id) should be a string, not {0}".format(type(robot_id))) robot = None try: robot = self._database.get_robot(robot_id, for_update=True) self._authenticator.authenticate_robot(robot, password) actions_delay = time.time() - robot.get_last_executed_action_time() if actions_delay < self._robots_action_delay: # Robot is sending actions too fast. Putting a delay between its actions. time.sleep(self._robots_action_delay - actions_delay) handler = self._get_action_handler(action_type) result = handler.do_action(robot, args) # Reducing age and energy. robot.set_energy(robot.get_energy() - 1) robot.set_life(robot.get_life() - 1) # Updating last executed action time. robot.set_last_executed_action_time(time.time()) return result except LockAlreadyAquiredError as error: # Logging all concurrency errors, so we can investigate them later. Logger().info("LockAlreadyAquiredError: {0}".format(error)) raise except AuthenticationFailedError: raise except BinarySkyException: # Concurrency exception is a fault of the server. Also, we ignored # authentication errors. Otherwise, robot should lose energy and age. # Note that we didn't handled unexpected errors (e.g. Exception class). # XXX: Dirty code. It shouldn't rollback and/or commit database changes. # But right now, there's no better way. self._database.rollback() if robot is None: # Seems we couldn't even get the robot. So, robot didn't do any actions # and shouldn't lose energy and age. raise robot = self._database.get_robot(robot_id, for_update=True) robot.set_energy(robot.get_energy() - 1) robot.set_life(robot.get_life() - 1) robot.set_last_executed_action_time(time.time()) self._database.commit() raise def _get_action_handler(self, action_type): '''Returns instance that should handle this action.''' if not isinstance(action_type, str): raise exceptions.InvalidActionError("Action type must be str, not {0}".format(type(action_type))) handler = self._actions.get(action_type) if handler is None: raise exceptions.InvalidActionError("Action {0} does not exists.".format(action_type)) return handler