def test_water_level(self): '''Tests if water level increases after watering.''' database = MemcachedDatabase() world = World() robot = Robot("198.1287.fkdfjei", "123") robot.set_location((5, 0)) robot.set_has_water(True) plant = Plant() plant.set_water_level(30) world.plant(plant, (5, 0)) database.commit() action = WaterAction() action.do_action(robot, ["198.1287.fkdfjei"]) database.commit() updated_square = world.get_square((5, 0)) plant = updated_square.get_plant() # Checking if honor increased. self.assertEqual(robot.get_honor(), 1) self.assertEqual(plant.get_water_level(), 100) self.assertFalse(robot.get_has_water())
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_specific_point(self): '''Gets information of a specific point, and check its result.''' database = MemcachedDatabase() new_robot = Robot("oie982736hhjf", "lo098173635") new_robot.set_location((9, 4)) database.add_robot(new_robot, (9, 4)) database.commit() action_manager = ActionManager() info = action_manager.do_action(new_robot.get_password(), "sense", [new_robot.get_id()]) self.assertEqual(len(info), 9) self.assertEqual(info["9,4"], {"surface_type": MapSquareTypes.SOIL, "robot": True, "plant": None}) self.assertEqual(info["9,3"], {"surface_type": MapSquareTypes.WATER, "robot": False, "plant": None}) self.assertEqual(info["10,5"], {"surface_type": MapSquareTypes.SOIL, "robot": False, "plant": None}) self.assertEqual(info["8,4"], {"surface_type": MapSquareTypes.SOIL, "robot": False, "plant": None})
def setUpClass(cls): # Creating a robot that all the tests will use. database = MemcachedDatabase() world = World() robot = Robot(TestGiveBirth.ROBOT_ID, "123") world.add_robot(robot, (0, 14)) database.commit()
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()
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_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_invalid_location(self): '''Tests adding a robot to an invalid location.''' database = MemcachedDatabase() robot = Robot("invalid_location_robot_1863", "123") with self.assertRaises(InvalidLocationError): self._world.add_robot(robot, (19872, 1190)) database.commit()
def setUpClass(cls): # Addin a robot to the world. All the tests would use this robot. robot = Robot(cls.ROBOT_ID, "123") world = World() world.add_robot(robot, cls.LOCATION) database = MemcachedDatabase() database.commit()
def test_invalid_location(self): '''Tests if database checks for invalid locations.''' database = MemcachedDatabase() new_robot = Robot("test_invalid_location_19887", "123") with self.assertRaises(InvalidLocationError): database.add_robot(new_robot, (91872, 16652)) database.commit()
def test_duplicate_add(self): '''Adding two robots with same ID. Should be failed.''' database = MemcachedDatabase() new_robot = Robot("test_duplicate_add_", "123") database.add_robot(new_robot, (1, 1)) database.commit() robot2 = Robot("test_duplicate_add_", "123") with self.assertRaises(CannotAddObjectError): database.add_robot(robot2, (1, 2)) database.commit()
def test_blocked_location(self): '''Tests adding the robot to a blocked square.''' database = MemcachedDatabase() robot = Robot("test_blocked_location_91882", "123") # There's a rock here. self._world.add_robot(robot, (6, 1)) database.commit() received_robot = database.get_robot(robot.get_id()) self.assertNotEqual(received_robot.get_location(), (6, 1))
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_getting_data(self): robot = Robot("13329.12900.12213", "123", name="HappyBot") robot.set_energy(124) robot.set_honor(7) robot.set_life(3) robot.set_has_water(True) plant = Plant() plant.set_age(64) plant.set_water_level(98) database = MemcachedDatabase() database.add_robot(robot, (6, 11)) square = database.get_square((5, 11), for_update=True) square.set_plant(plant) database.commit() expected_result = { "5,11": { "surface_type": MapSquareTypes.SOIL, "plant": { "water_level": 98, "matured": True, "age": 64 }, "robot": None }, "6,11": { "surface_type": MapSquareTypes.SOIL, "plant": None, "robot": { "name": "HappyBot", "has_water": True, "energy": 124, "life": 3, "honor": 7 } }, "6,2": { "surface_type": MapSquareTypes.ROCK, "robot": None, "plant": None } } communicator = Communicator() result = communicator.execute_command("NhdEr32Qcmp0Iue3", "map_data", expected_result.keys()) self.assertCountEqual(result, expected_result) for expected_key, expected_value in expected_result.items(): self.assertDictEqual(result[expected_key], expected_value)
def test_blocking_object(self): '''Tests moving toward a blocking object.''' robot_id = "test_invalid_location_8765112" robot = Robot(robot_id, "123") world = World() action_manager = ActionManager() database = MemcachedDatabase() world.add_robot(robot, (11, 6)) database.commit() with self.assertRaises(LocationIsBlockedError): action_manager.do_action("123", "move", [robot_id, "W"])
def test_ok(self): '''Tests a good scenario.''' population_control = PopulationControl() database = MemcachedDatabase() database.add_password("iujeh87UYh6512ewQ") robot_info = population_control.execute_command("iujeh87UYh6512ewQ", "born", [None, "RDaniel"]) database.commit() gotted_robot = database.get_robot(robot_info['robot_id']) self.assertEqual(gotted_robot.get_name(), "RDaniel")
def test_duplicate(self): '''Tests adding duplicate robot.''' database = MemcachedDatabase() robot = Robot("world_duplicate_robot_8722", "123") self._world.add_robot(robot, (5, 1)) database.commit() robot_2 = Robot("world_duplicate_robot_8722", "1236") with self.assertRaises(CannotAddObjectError): self._world.add_robot(robot_2, (5, 2)) database.commit()
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_blind_point(self): '''Gets information of a point, but don't care about the result.''' database = MemcachedDatabase() new_robot = Robot("1873yudhNCbueio", "ueijdnchiop") new_robot.set_location((9, 7)) database.add_robot(new_robot, (9, 7)) database.commit() action_manager = ActionManager() info = action_manager.do_action(new_robot.get_password(), "sense", [new_robot.get_id()]) self.assertEqual(len(info), 9)
def test_corner(self): '''Tests getting a corner of the map.''' database = MemcachedDatabase() new_robot = Robot("0981kdjieu871", "oie987163") new_robot.set_location((0, 1)) database.add_robot(new_robot, (0, 1)) database.commit() action_manager = ActionManager() info = action_manager.do_action(new_robot.get_password(), "sense", [new_robot.get_id()]) self.assertEqual(len(info), 6)
def test_blind_point(self): '''Gets information of a point, but don't care about the result.''' database = MemcachedDatabase() new_robot = Robot("1873yudhNCbueio", "ueijdnchiop") new_robot.set_location((9, 7)) database.add_robot(new_robot, (9, 7)) database.commit() action_manager = ActionManager() info = action_manager.do_action(new_robot.get_password(), "sense", [new_robot.get_id()]) self.assertEqual(len(info), 9)
def test_corner(self): '''Tests getting a corner of the map.''' database = MemcachedDatabase() new_robot = Robot("0981kdjieu871", "oie987163") new_robot.set_location((0, 1)) database.add_robot(new_robot, (0, 1)) database.commit() action_manager = ActionManager() info = action_manager.do_action(new_robot.get_password(), "sense", [new_robot.get_id()]) self.assertEqual(len(info), 6)
def test_ok(self): '''Tests a good scenario.''' population_control = PopulationControl() database = MemcachedDatabase() database.add_password("iujeh87UYh6512ewQ") robot_info = population_control.execute_command( "iujeh87UYh6512ewQ", "born", [None, "RDaniel"]) database.commit() gotted_robot = database.get_robot(robot_info['robot_id']) self.assertEqual(gotted_robot.get_name(), "RDaniel")
def test_ok(self): '''Adds a good robot object to the world.''' database = MemcachedDatabase() robot = Robot("world_ok_robot_38364", "123") self._world.add_robot(robot, (5, 0)) database.commit() gotted_robot = database.get_robot(robot.get_id()) self.assertEqual(gotted_robot.get_alive(), robot.get_alive()) all_robots = database.get_all_robot_ids() self.assertIn(robot.get_id(), all_robots)
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_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_locked_location(self): '''Tests adding a robot to a locked location.''' database = MemcachedDatabase() robot = Robot("test_locked_location_0023", "123") # Locking 8,1 database.get_square((8, 1), for_update=True) self._world.add_robot(robot, (8, 1)) database.commit() received_robot = database.get_robot(robot.get_id()) self.assertNotEqual(received_robot.get_location(), (8, 1))
def test_out_of_water(self): '''Tests if plant die after running out of water.''' plant = Plant() world = World() database = MemcachedDatabase() world.plant(plant, (4, 8)) database.commit() # Waiting for 11 cycles. time.sleep(11 * 0.03) square = world.get_square((4, 8), for_update=False) self.assertIsNone(square.get_plant())
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_out_of_water(self): '''Tests if plant die after running out of water.''' plant = Plant() world = World() database = MemcachedDatabase() world.plant(plant, (4, 8)) database.commit() # Waiting for 11 cycles. time.sleep(11 * 0.03) square = world.get_square((4, 8), for_update=False) self.assertIsNone(square.get_plant())
def test_simple_add(self): '''Test adding a single robot to database.''' database = MemcachedDatabase() new_robot = Robot("test_simple_add_", "123") # No exception should be raise. database.add_robot(new_robot, (0, 0)) database.commit() gotted_robot = database.get_robot("test_simple_add_") self.assertEqual(gotted_robot.get_id(), new_robot.get_id()) self.assertEqual(gotted_robot.get_alive(), new_robot.get_alive()) self.assertEqual(gotted_robot.get_password(), new_robot.get_password())
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_locked(self): '''Tests with a locked square.''' database = MemcachedDatabase() world = World() robot = Robot("test_locked_robot_190083", "123") # Setting the energy to zero, so the updater tries to update the square too. robot.set_energy(0) world.add_robot(robot, (5, 9)) database.commit() world.get_square((5, 9), for_update=True) with self.assertRaises(LockAlreadyAquiredError): database.get_robot(robot.get_id(), for_update=True)
def test_locked(self): '''Tests with a locked square.''' database = MemcachedDatabase() world = World() robot = Robot("test_locked_robot_190083", "123") # Setting the energy to zero, so the updater tries to update the square too. robot.set_energy(0) world.add_robot(robot, (5, 9)) database.commit() world.get_square((5, 9), for_update=True) with self.assertRaises(LockAlreadyAquiredError): database.get_robot(robot.get_id(), for_update=True)
def test_out_of_energy_robot(self): '''Tests when a robot ran out of energy.''' database = MemcachedDatabase() world = World() robot = Robot("test_out_of_energy_robot_18773", "123") robot.set_energy(0) world.add_robot(robot, (1, 9)) database.commit() got_robot = database.get_robot("test_out_of_energy_robot_18773", for_update=False) self.assertFalse(got_robot.get_alive()) square = world.get_square((1, 9)) self.assertIsNone(square.get_robot_id())
def test_out_of_life_robot(self): '''Tests when a robot ran out of life.''' database = MemcachedDatabase() world = World() robot = Robot("test_out_of_life_robot_9022", "123") robot.set_life(0) world.add_robot(robot, (0, 9)) database.commit() received_robot = database.get_robot("test_out_of_life_robot_9022", for_update=False) self.assertFalse(received_robot.get_alive()) square = world.get_square((0, 9)) self.assertIsNone(square.get_robot_id())
def test_out_of_life_robot(self): '''Tests when a robot ran out of life.''' database = MemcachedDatabase() world = World() robot = Robot("test_out_of_life_robot_9022", "123") robot.set_life(0) world.add_robot(robot, (0, 9)) database.commit() received_robot = database.get_robot("test_out_of_life_robot_9022", for_update=False) self.assertFalse(received_robot.get_alive()) square = world.get_square((0, 9)) self.assertIsNone(square.get_robot_id())
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_locked_square(self): '''Tests updating a locked square.''' plant = Plant() world = World() database = MemcachedDatabase() world.plant(plant, (7, 8)) database.commit() # Locking the square. world.get_square((7, 8), for_update=True) # Sleeping one cycle. time.sleep(0.031) with self.assertRaises(LockAlreadyAquiredError): world.get_square((7, 8), for_update=False)
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 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 test_out_of_energy_robot(self): '''Tests when a robot ran out of energy.''' database = MemcachedDatabase() world = World() robot = Robot("test_out_of_energy_robot_18773", "123") robot.set_energy(0) world.add_robot(robot, (1, 9)) database.commit() got_robot = database.get_robot("test_out_of_energy_robot_18773", for_update=False) self.assertFalse(got_robot.get_alive()) square = world.get_square((1, 9)) self.assertIsNone(square.get_robot_id())
def test_locked_square(self): '''Tests updating a locked square.''' plant = Plant() world = World() database = MemcachedDatabase() world.plant(plant, (7, 8)) database.commit() # Locking the square. world.get_square((7, 8), for_update=True) # Sleeping one cycle. time.sleep(0.031) with self.assertRaises(LockAlreadyAquiredError): world.get_square((7, 8), for_update=False)
def test_one_cycle(self): '''Tests if plant updates correctly after one cycle.''' plant = Plant() world = World() database = MemcachedDatabase() world.plant(plant, (3, 8)) database.commit() # Sleeping one cycle. time.sleep(0.031) square = world.get_square((3, 8), for_update=False) plant = square.get_plant() self.assertEqual(plant.get_age(), 1) self.assertEqual(plant.get_water_level(), 90)
def test_maximum_age(self): '''Tests if plant die after maximum age.''' plant = Plant() world = World() database = MemcachedDatabase() world.plant(plant, (11, 8)) database.commit() square = world.get_square((11, 8), for_update=True) plant = square.get_plant() plant.set_age(40) database.commit() # Sleeping one cycle. time.sleep(0.031) square = world.get_square((11, 8), for_update=False) self.assertIsNone(square.get_plant())
def test_ok(self): '''Tests a valid situation.''' 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() new_password = population_control.execute_command( "123", "give_birth", [TestGiveBirth.ROBOT_ID]) database.commit() updated_robot = database.get_robot(TestGiveBirth.ROBOT_ID, for_update=False) self.assertEqual(updated_robot.get_honor(), 1) self.assertTrue(isinstance(new_password, str)) self.assertEqual(len(new_password), 16)
def test_no_cycle_passed(self): '''Tests if plant not changed if no cycle passed.''' world = World() database = MemcachedDatabase() plant = Plant() plant.set_age(2) plant.set_water_level(70) world.plant(plant, (6, 8)) database.commit() # Waiting just a little. time.sleep(0.01) square = world.get_square((6, 8), for_update=False) received_plant = square.get_plant() self.assertEqual(received_plant.get_age(), plant.get_age()) self.assertEqual(received_plant.get_water_level(), plant.get_water_level()) self.assertEqual(received_plant.get_last_update(), plant.get_last_update())
def test_concurrent_add_failure(self): '''Tests the behavior of Database class, when concurrent add fails.''' # Mocking `cas' method, making it always return False. def mocked_cas(*args): return False mc_connection = MemcachedConnection().get_connection() original_cas = mc_connection.cas mc_connection.cas = unittest.mock.Mock(side_effect=mocked_cas) try: new_robot = Robot("test_concurrent_add_failure_9865", "123") database = MemcachedDatabase() with self.assertRaises(CouldNotSetValueBecauseOfConcurrencyError): database.add_robot(new_robot, (1, 1)) database.commit() finally: # Setting back the original cas method. mc_connection.cas = original_cas # Checking to see added robot is clearly rolled back. self.assertFalse(mc_connection.get(new_robot.get_id()))
def test_eating_not_matured(self): '''Tests when robot eat a non matured plant.''' world = World() database = MemcachedDatabase() action_manager = ActionManager() plant = Plant() plant.set_age(1) world.plant(plant, TestEatAction.LOCATION) database.commit() robot = database.get_robot(TestEatAction.ROBOT_ID, for_update=True) robot.set_energy(10) database.commit() action_manager.do_action("123", "eat", [TestEatAction.ROBOT_ID]) database.commit() updated_robot = database.get_robot(TestEatAction.ROBOT_ID) self.assertEqual(updated_robot.get_energy(), robot.get_energy() - 1)
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
class ObjectUpdater(DatabaseHook): '''This class checks objects in the world, and updates them if required. Responsibilities of this class: * Killing and removing a robot from the world, if its energy reached zero or it ran out of life. * Removing a plant from the world if its water level reached zero or it became too old. * Maturing a plant if it reached a certain age. * Updating a plant's water level during time. ''' def __init__(self): self._database = MemcachedDatabase() self._configs = Configs() def robot_got(self, robot_object, locked_for_update): '''Checks and updates the specified robot's object.''' if robot_object.get_energy() <= 0 or robot_object.get_life() <= 0: if not locked_for_update: # This would call this method again, and robot will be updated. return self._database.get_robot(robot_object.get_id(), for_update=True) robot_object.set_alive(False) # Removing the robot from its location. try: square = self._database.get_square(robot_object.get_location(), for_update=True) except LockAlreadyAquiredError: # Trying one more time. time.sleep(0.02) square = self._database.get_square(robot_object.get_location(), for_update=True) square.set_robot_id(None) # XXX: Though it's a very dirty thing to do, we have to commit these changes, because # later this robot will face an Authentication Exception, and our changes will be # lost. self._database.commit() # Immediately, locking the robot object. It's not atomic, so there's a little chance # that concurrency happens. But, it shouldn't be a problem, since the robot is already # dead, and can't do anything anyway. self._database.get_lock(robot_object.get_id()) return robot_object def square_got(self, location, square_object, locked_for_update): '''Checks and updates specified square object.''' plant = square_object.get_plant() if plant is None: return square_object # Time passed from the last time this plant updated. last_update = time.time() - plant.get_last_update() passed_cycles = math.floor(last_update / (self._configs.get_plant_cylce() / 1000)) if passed_cycles <= 0: # No cycle passed, no need to be updated. return square_object if not locked_for_update: # This will call this method again. try: return self._database.get_square(location, for_update=True) except LockAlreadyAquiredError: # Trying one more time. time.sleep(0.02) return self._database.get_square(location, for_update=True) plant.set_age(plant.get_age() + passed_cycles) plant.set_water_level(plant.get_water_level() - (passed_cycles * self._configs.get_plant_lose_water_in_cycle())) if plant.get_age() > self._configs.get_plant_max_age( ) or plant.get_water_level() <= 0: # Plant is dead! Removing it from the world. square_object.set_plant(None) plant.set_last_update(time.time()) return square_object
def test_directions(self): '''Tests if moving in different directions works correctly.''' robot_id = "test_directions_154332" robot = Robot(robot_id, "123") world = World() database = MemcachedDatabase() world.add_robot(robot, (10, 2)) database.commit() action_manager = ActionManager() action_manager.do_action("123", "move", [robot_id, "N"]) database.commit() robot = database.get_robot(robot_id) self.assertEqual(robot.get_location(), (10, 1)) action_manager.do_action("123", "move", [robot_id, "NE"]) database.commit() robot = database.get_robot(robot_id) self.assertEqual(robot.get_location(), (11, 0)) action_manager.do_action("123", "move", [robot_id, "E"]) database.commit() robot = database.get_robot(robot_id) self.assertEqual(robot.get_location(), (12, 0)) action_manager.do_action("123", "move", [robot_id, "SE"]) database.commit() robot = database.get_robot(robot_id) self.assertEqual(robot.get_location(), (13, 1)) action_manager.do_action("123", "move", [robot_id, "S"]) database.commit() robot = database.get_robot(robot_id) self.assertEqual(robot.get_location(), (13, 2)) action_manager.do_action("123", "move", [robot_id, "SW"]) database.commit() robot = database.get_robot(robot_id) self.assertEqual(robot.get_location(), (12, 3)) action_manager.do_action("123", "move", [robot_id, "W"]) database.commit() robot = database.get_robot(robot_id) self.assertEqual(robot.get_location(), (11, 3)) action_manager.do_action("123", "move", [robot_id, "NW"]) database.commit() robot = database.get_robot(robot_id) self.assertEqual(robot.get_location(), (10, 2))
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)
def test_robot_simulation(self): '''This test simulates a full game scenario.''' database = MemcachedDatabase() world = World() configs = Configs() print() print("Simulating a robot playing the game. This may take a while.") # Creating a world for the robot to live in. # The world map is: # # 000000 # 000222 # 000144 # 000144 row01 = [ MapSquare(MapSquareTypes.SOIL, (0, 30)), MapSquare(MapSquareTypes.SOIL, (1, 30)), MapSquare(MapSquareTypes.SOIL, (2, 30)), MapSquare(MapSquareTypes.SOIL, (3, 30)), MapSquare(MapSquareTypes.SOIL, (4, 30)), MapSquare(MapSquareTypes.SOIL, (5, 30)) ] row02 = [ MapSquare(MapSquareTypes.SOIL, (0, 31)), MapSquare(MapSquareTypes.SOIL, (1, 31)), MapSquare(MapSquareTypes.SOIL, (2, 31)), MapSquare(MapSquareTypes.ROCK, (3, 31)), MapSquare(MapSquareTypes.ROCK, (4, 31)), MapSquare(MapSquareTypes.ROCK, (5, 31)) ] row03 = [ MapSquare(MapSquareTypes.SOIL, (0, 32)), MapSquare(MapSquareTypes.SOIL, (1, 32)), MapSquare(MapSquareTypes.SOIL, (2, 32)), MapSquare(MapSquareTypes.SAND, (3, 32)), MapSquare(MapSquareTypes.WATER, (4, 32)), MapSquare(MapSquareTypes.WATER, (5, 32)) ] row04 = [ MapSquare(MapSquareTypes.SOIL, (0, 33)), MapSquare(MapSquareTypes.SOIL, (1, 33)), MapSquare(MapSquareTypes.SOIL, (2, 33)), MapSquare(MapSquareTypes.SAND, (3, 33)), MapSquare(MapSquareTypes.WATER, (4, 33)), MapSquare(MapSquareTypes.WATER, (5, 33)) ] database.add_square_row(row01) database.add_square_row(row02) database.add_square_row(row03) database.add_square_row(row04) # Creating parent of our robot. parent_robot = Robot("parent_robot_1982.345", "123", name="Parent") parent_robot.set_honor(configs.get_robots_birth_required_honor() + 1) world.add_robot(parent_robot, (2, 31)) database.commit() # Giving birth to our hero. result = self.post_request({ 'command': 'give_birth', 'password': '******', 'args': ["parent_robot_1982.345"] }) self.assertEqual(result['status'], 200, result) born_password = result['result'] # Robot requests a born. result = self.post_request({ 'command': 'born', 'password': born_password, 'args': [parent_robot.get_id()] }) self.assertEqual(result['status'], 200) robot_id = result['result']['robot_id'] password = result['result']['password'] # Getting status. result = self.post_request({ 'command': 'status', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) self.assertTrue(result['result']['alive']) self.assertFalse(result['result']['has_water']) self.assertEqual(result['result']['location'], "2,30") # Moving somewhere to plan a corp. # Note that parent robot is on the south. So, we have to turn around it. result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'W'] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'S'] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'S'] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'E'] }) self.assertEqual(result['status'], 200, result) # We are at the location. Checking if its correct. result = self.post_request({ 'command': 'status', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) self.assertEqual(result['result']['location'], "2,32") # Planting a corp here. result = self.post_request({ 'command': 'plant', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) # Checking if it is planted. result = self.post_request({ 'command': 'sense', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) self.assertEqual(result['result']['2,32']['surface_type'], MapSquareTypes.SOIL) self.assertIsNotNone(result['result']['2,32']['plant']) # Going to pick water. result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'E'] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'E'] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'pick_water', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'status', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) self.assertTrue(result['result']['has_water']) # Getting back to the plant location. result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'W'] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'W'] }) self.assertEqual(result['status'], 200, result) # Watering result = self.post_request({ 'command': 'water', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) # Checking result = self.post_request({ 'command': 'sense', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) # It lost some water already. self.assertGreater(result['result']['2,32']['plant']['water_level'], 70) # Plant should be matured by now. Eating! result = self.post_request({ 'command': 'status', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) previous_energy = result['result']['energy'] result = self.post_request({ 'command': 'eat', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'status', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 200, result) self.assertGreater(result['result']['energy'], previous_energy) # Now, trying some bad moves! # Trying to plant on a sand. result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'E'] }) self.assertEqual(result['status'], 200, result) result = self.post_request({ 'command': 'plant', 'password': password, 'args': [robot_id] }) self.assertEqual(result['status'], 500, result) self.assertEqual(result['error_code'], 'CannotPlantHereError') # Trying to move to a rock. result = self.post_request({ 'command': 'move', 'password': password, 'args': [robot_id, 'N'] }) self.assertEqual(result['status'], 500, result) self.assertEqual(result['error_code'], 'LocationIsBlockedError')