Testing Suite PartA - Py
Testing Suite PartA - Py
import unittest
import multiprocessing as mproc
import traceback
import string
import sys
import copy
import io
try:
from warehouse import DeliveryPlanner_PartA
studentExc = None
except Exception as e:
studentExc = e
########################################################################
# For debugging this flag can be set to True to print state
# which could result in a timeout
########################################################################
VERBOSE_FLAG = False
########################################################################
# For debugging set the time limit to a big number (like 600 or more)
########################################################################
TIME_LIMIT = 5 # seconds
########################################################################
# If your debugger does not handle multiprocess debugging very easily
# then when debugging set the following flag true.
########################################################################
DEBUGGING_SINGLE_PROCESS = False
class Submission:
"""Student Submission.
Attributes:
submission_score(Queue): Student score of last executed plan.
submission_error(Queue): Error messages generated during last executed
plan.
"""
def __init__(self, fout=None):
if DEBUGGING_SINGLE_PROCESS:
import queue
self.submission_score = queue.Queue(1)
self.submission_error = queue.Queue(1)
self.logmsgs = queue.Queue(1)
else:
self.submission_score = mproc.Manager().Queue(1)
self.submission_error = mproc.Manager().Queue(1)
self.logmsgs = mproc.Manager().Queue(1)
self.fout = io.StringIO()
def _reset(self):
"""Reset submission results.
"""
while not self.submission_score.empty():
self.submission_score.get()
Args:
warehouse(list(list)): the warehouse map to test against.
boxes_todo(list): the order of boxes to deliver.
"""
self._reset()
state = State(warehouse)
try:
student_planner = DeliveryPlanner_PartA(copy.deepcopy(warehouse),
copy.deepcopy(boxes_todo))
action_list = student_planner.plan_delivery()
num_delivered = 0
next_box_to_deliver = boxes_todo[num_delivered]
state.update_according_to(action)
if VERBOSE_FLAG:
# print final state
self.log('\n\n')
self.log('Final State: ')
state.print_to_console( self.fout )
except:
self.submission_error.put(traceback.format_exc())
self.submission_score.put(float('inf'))
class State:
"""Current State.
Args:
warehouse(list(list)): the warehouse map.
Attributes:
boxes_delivered(list): the boxes successfully delivered to dropzone.
total_cost(int): the total cost of all moves executed.
warehouse_state(list(list): the current warehouse state.
dropzone(tuple(int, int)): the location of the dropzone.
boxes(list): the location of the boxes.
robot_position(tuple): the current location of the robot.
box_held(str): ID of current box held.
"""
ORTHOGONAL_MOVE_COST = 2
DIAGONAL_MOVE_COST = 3
BOX_LIFT_COST = 4
BOX_DOWN_COST = 2
ILLEGAL_MOVE_PENALTY = 100
MOVE_DIRECTIONS = {"n":(-1,0),"ne":(-1,1),"e":(0,1),"se":(1,1),
"s":(1,0),"sw":(1,-1),"w":(0,-1),"nw":(-1,-1)}
Args:
warehouse(list(list)): the warehouse map.
"""
rows = len(warehouse)
cols = len(warehouse[0])
for i in range(rows):
for j in range(cols):
this_square = warehouse[i][j]
if this_square == '.':
self.warehouse_state[i][j] = '.'
else: # a box
box_id = this_square
self.warehouse_state[i][j] = box_id
self.boxes[box_id] = (i, j)
self.robot_position = self.dropzone
self.box_held = None
Args:
action(str): action to execute.
Raises:
Exception: if improperly formatted action.
"""
if action_type == 'move':
direction = action[1]
self._attempt_move(direction)
else:
# improper move format: kill test
raise Exception("action type must be 'move','lift' or 'down':
{}".format(''.join(action)))
Args:
direction: direction in which to move to adjacent square
("n","ne","e","se","s","sw","w","nw")
Raises:
ValueError: if improperly formatted move destination.
IndexError: if move is outside of warehouse.
"""
try:
destination = self.MOVE_DIRECTIONS[direction][0] +
self.robot_position[0], \
self.MOVE_DIRECTIONS[direction][1] +
self.robot_position[1]
destination_is_adjacent = self._are_adjacent(self.robot_position,
destination)
destination_is_traversable = self._is_traversable(destination)
if is_legal_move:
self._move_robot_to(destination)
else:
self._increase_total_cost_by(self.ILLEGAL_MOVE_PENALTY)
except ValueError:
raise Exception("move direction must be
'n','ne','e','se','s','sw','w','nw' your move is: {}".format(direction))
except IndexError: # (row, col) not in warehouse
self._increase_total_cost_by(self.ILLEGAL_MOVE_PENALTY)
Args:
box_id(str): the id of the box to lift.
Raises:
KeyError: if invalid box id.
"""
try:
box_position = self.boxes[box_id]
box_is_adjacent = self._are_adjacent(self.robot_position,
box_position)
robot_has_box = self._robot_has_box()
except KeyError:
self._increase_total_cost_by(self.ILLEGAL_MOVE_PENALTY)
The robot may put a box down on an adjacent empty space ('.') or the
dropzone ('@') at a cost
of 2 (regardless of the direction in which the robot puts down the
box).
Illegal moves (100 cost):
attempting to put down a box on a nonadjacent, nonexistent, or
occupied space
attempting to put down a box while not holding one
Args:
direction: direction to adjacent square in which to set box down
("n","ne","e","se","s","sw","w","nw")
Raises:
ValueError: if improperly formatted down destination.
IndexError: if down location is outside of warehouse.
"""
try:
destination = self.MOVE_DIRECTIONS[direction][0] +
self.robot_position[0], \
self.MOVE_DIRECTIONS[direction][1] +
self.robot_position[1]
destination_is_adjacent = self._are_adjacent(self.robot_position,
destination)
destination_is_traversable = self._is_traversable(destination)
robot_has_box = self._robot_has_box()
except ValueError:
raise Exception("down direction must be
'n','ne','e','se','s','sw','w','nw' your move is: {}".format(direction))
except IndexError: # (row, col) not in warehouse
self._increase_total_cost_by(self.ILLEGAL_MOVE_PENALTY)
Args:
amount(int): amount to increase cost by.
"""
self.total_cost += amount
Returns:
True if within warehouse.
"""
i, j = coordinates
rows = len(self.warehouse_state)
cols = len(self.warehouse_state[0])
Args:
coordinates1(tuple(int, int)): first coordinate.
coordinates2(tuple(int, int)): second coordinate.
Returns:
True if adjacent in all directions.
"""
return (self._are_horizontally_adjacent(coordinates1, coordinates2) or
self._are_vertically_adjacent(coordinates1, coordinates2) or
self._are_diagonally_adjacent(coordinates1, coordinates2)
)
@staticmethod
def _are_horizontally_adjacent(coordinates1, coordinates2):
"""Verify if coordinates are horizontally adjacent.
Args:
coordinates1(tuple(int, int)): first coordinate.
coordinates2(tuple(int, int)): second coordinate.
Returns:
True if horizontally adjacent.
"""
row1, col1 = coordinates1
row2, col2 = coordinates2
@staticmethod
def _are_vertically_adjacent(coordinates1, coordinates2):
"""Verify if coordinates are vertically adjacent.
Args:
coordinates1(tuple(int, int)): first coordinate.
coordinates2(tuple(int, int)): second coordinate.
Returns:
True if vertically adjacent.
"""
row1, col1 = coordinates1
row2, col2 = coordinates2
@staticmethod
def _are_diagonally_adjacent(coordinates1, coordinates2):
"""Verify if coordinates are diagonally adjacent.
Args:
coordinates1(tuple(int, int)): first coordinate.
coordinates2(tuple(int, int)): second coordinate.
Returns:
True if diagonally adjacent.
"""
row1, col1 = coordinates1
row2, col2 = coordinates2
Args:
coordinates(tuple(int, int)): coordinate to check.
Return:
True if traversable.
"""
is_wall = self._is_wall(coordinates)
has_box = self._space_contains_box(coordinates)
Args:
coordinates(tuple(int, int)): coordinate to check.
Return:
True if wall.
"""
i, j = coordinates
Args:
coordinates(tuple(int, int)): coordinate to check.
Return:
True if space contains box.
"""
i, j = coordinates
def _robot_has_box(self):
"""Verify if robot has box.
Returns:
True if box is being held.
"""
return self.box_held is not None
Args:
destination(tuple(int, int)): location to set box down at.
"""
old_position = self.robot_position
self.robot_position = destination
i1, j1 = old_position
if self.dropzone == old_position:
self.warehouse_state[i1][j1] = '@'
else:
self.warehouse_state[i1][j1] = '.'
i2, j2 = destination
self.warehouse_state[i2][j2] = '*'
if self._are_diagonally_adjacent(old_position, destination):
self._increase_total_cost_by(self.DIAGONAL_MOVE_COST)
else:
self._increase_total_cost_by(self.ORTHOGONAL_MOVE_COST)
Args:
box_id(str): the id of the box to lift.
"""
i, j = self.boxes[box_id]
self.warehouse_state[i][j] = '.'
self.boxes.pop(box_id)
self.box_held = box_id
self._increase_total_cost_by(self.BOX_LIFT_COST)
Args:
destination(tuple(int, int)): location to set box down at.
"""
# - If a box is placed on the '@' space, it is considered delivered and
is removed from the ware-
# house.
i, j = destination
if self.warehouse_state[i][j] == '.':
self.warehouse_state[i][j] = self.box_held
self.boxes[self.box_held] = (i, j)
else:
self._deliver_box(self.box_held)
self.box_held = None
self._increase_total_cost_by(self.BOX_DOWN_COST)
Args:
box_id(str): id of box to mark delivered.
"""
self.boxes_delivered.append(box_id)
def get_boxes_delivered(self):
"""Get list of boxes delivered.
Returns:
List of boxes delivered.
"""
return self.boxes_delivered
def get_total_cost(self):
"""Get current total cost.
Returns:
Total cost of all executed moves.
"""
return self.total_cost
class PartATestCase(unittest.TestCase):
""" Test Part A.
"""
credit = []
totalCredit = 0
fout = None
@classmethod
def _log(cls, s):
(cls.fout or sys.stdout).write( s + '\n')
def setUp(self):
"""Initialize test setup.
"""
if studentExc:
self.credit.append( 0.0 )
self.results.append( "exception on import: %s" % str(studentExc) )
raise studentExc
self.student_submission = Submission( fout = self.__class__.fout )
def tearDown(self):
self.__class__.totalCredit = sum(self.__class__.credit)
@classmethod
def tearDownClass(cls):
"""Save student results at conclusion of test.
"""
# Prints results after all tests complete
for line in cls.results:
cls._log(line)
cls._log("\n-----------")
cls._log('\nTotal Credit: {:.2f}'.format(cls.totalCredit))
error_message = ''
cost = float('inf')
score = 0.0
logmsg = ''
if not self.student_submission.logmsgs.empty():
logmsg = self.student_submission.logmsgs.get()
if not self.student_submission.submission_score.empty():
cost = self.student_submission.submission_score.get()
if not self.student_submission.submission_error.empty():
error_message = self.student_submission.submission_error.get()
self.results.append(self.FAIL_TEMPLATE.format(message=error_message,
output = logmsg, **params))
else:
self.results.append(self.SCORE_TEMPLATE.format(cost=cost,
score=score, output = logmsg, **params))
self.credit.append(score)
self.assertFalse(error_message, error_message)
Args:
params(dict): a dictionary of test parameters.
"""
if DEBUGGING_SINGLE_PROCESS:
self.student_submission.execute_student_plan(params['warehouse'],
params['todo'])
else:
test_process =
mproc.Process(target=self.student_submission.execute_student_plan,
args=(params['warehouse'],
params['todo']))
if DEBUGGING_SINGLE_PROCESS :
self.check_results( params )
else:
logmsg = ''
try:
test_process.start()
test_process.join(TIME_LIMIT)
except Exception as exp:
error_message = exp
if test_process.is_alive():
test_process.terminate()
error_message = ('Test aborted due to timeout. ' +
'Test was expected to finish in fewer than {}
second(s).'.format(TIME_LIMIT))
if not self.student_submission.logmsgs.empty():
logmsg = self.student_submission.logmsgs.get()
self.results.append(self.FAIL_TEMPLATE.format(message=error_message, output =
logmsg, **params))
else:
self.check_results( params )
def test_case1(self):
params = {'test_case': 1,
'warehouse': ['1#2',
'.#.',
'..@'],
'todo': ['1', '2'],
'min_cost': 23}
self.run_with_params(params)
self.run_with_params(params)
def test_case3(self):
params = {'test_case': 3,
'warehouse': ['1.#@#.4',
'2#.#.#3'],
'todo': ['1', '2', '3', '4'],
'min_cost': 57}
self.run_with_params(params)
# *** CREDIT TO: Kowsalya Subramanian for adding this test case
def test_case4(self):
params = {'test_case': 4,
'warehouse': ['3#@',
'2#.',
'1..'],
'todo': ['1', '2', '3'],
'min_cost': 44}
self.run_with_params(params)
# *** CREDIT TO: Gideon Rossman for adding this test case
def test_case5(self):
params = {'test_case': 5,
'warehouse': ['..1.',
'..@.',
'....',
'2...'],
'todo': ['1', '2'],
'min_cost': 19}
self.run_with_params(params)
# *** CREDIT TO: venkatasatyanarayana kamisetti for adding this test case
def test_case6(self):
params = {'test_case': 6,
'warehouse': ['1..',
'...',
'@.2'],
'todo': ['1', '2'],
'min_cost': 16}
self.run_with_params(params)
# # *** CREDIT TO: Dana Johnson for adding this test case
def test_case7(self):
params = {'test_case': 7,
'warehouse': ['#J######',
'#I#2345#',
'#H#1##6#',
'#G#0@#7#',
'#F####8#',
'#EDCBA9#',
'########'],
'todo': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J'],
'min_cost': 636.0}
self.run_with_params(params)
# *** CREDIT TO: Dana Johnson for adding this test case
def test_case8(self):
params = {'test_case': 8,
'warehouse': ['#######2',
'#......1',
'#@......'],
'todo': ['1', '2'],
'min_cost': 47.0}
self.run_with_params(params)
def test_case9(self):
params = {'test_case': 9,
'warehouse': ['..#1..',
'......',
'..####',
'..#2.#',
'.....@'],
'todo': ['1', '2'],
'min_cost': 43.0}
self.run_with_params(params)
# Test Case 10
def test_case10(self):
params = {'test_case': 10,
'warehouse': ['..#1..',
'#....#',
'..##.#',
'..#2.#',
'#....@'],
'todo': ['1', '2'],
'min_cost': 30.0}
self.run_with_params(params)
# Only run all of the test automatically if this file was executed from the
command line.
# Otherwise, let Nose/py.test do it's own thing with the test cases.
if __name__ == "__main__":
PartATestCase.fout = sys.stdout
unittest.main()