PageRenderTime 25ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/5 - Classification/autograder.py

https://bitbucket.org/wesleyto/cs188
Python | 348 lines | 335 code | 2 blank | 11 comment | 1 complexity | 2983db362c7c176cd21d36ef68322c46 MD5 | raw file
  1. # autograder.py
  2. # -------------
  3. # Licensing Information: Please do not distribute or publish solutions to this
  4. # project. You are free to use and extend these projects for educational
  5. # purposes. The Pacman AI projects were developed at UC Berkeley, primarily by
  6. # John DeNero (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
  7. # Student side autograding was added by Brad Miller, Nick Hay, and Pieter
  8. # Abbeel in Spring 2013.
  9. # For more info, see http://inst.eecs.berkeley.edu/~cs188/pacman/pacman.html
  10. # imports from python standard library
  11. import grading
  12. import imp
  13. import optparse
  14. import os
  15. import re
  16. import sys
  17. import projectParams
  18. import random
  19. random.seed(0)
  20. # register arguments and set default values
  21. def readCommand(argv):
  22. parser = optparse.OptionParser(description = 'Run public tests on student code')
  23. parser.set_defaults(generateSolutions=False, edxOutput=False, muteOutput=False, printTestCase=False, noGraphics=False, graphics=False)
  24. parser.add_option('--test-directory',
  25. dest = 'testRoot',
  26. default = 'test_cases',
  27. help = 'Root test directory which contains subdirectories corresponding to each question')
  28. parser.add_option('--student-code',
  29. dest = 'studentCode',
  30. default = projectParams.STUDENT_CODE_DEFAULT,
  31. help = 'comma separated list of student code files')
  32. parser.add_option('--code-directory',
  33. dest = 'codeRoot',
  34. default = "",
  35. help = 'Root directory containing the student and testClass code')
  36. parser.add_option('--test-case-code',
  37. dest = 'testCaseCode',
  38. default = projectParams.PROJECT_TEST_CLASSES,
  39. help = 'class containing testClass classes for this project')
  40. parser.add_option('--generate-solutions',
  41. dest = 'generateSolutions',
  42. action = 'store_true',
  43. help = 'Write solutions generated to .solution file')
  44. parser.add_option('--edx-output',
  45. dest = 'edxOutput',
  46. action = 'store_true',
  47. help = 'Generate edX output files')
  48. parser.add_option('--mute',
  49. dest = 'muteOutput',
  50. action = 'store_true',
  51. help = 'Mute output from executing tests')
  52. parser.add_option('--print-tests', '-p',
  53. dest = 'printTestCase',
  54. action = 'store_true',
  55. help = 'Print each test case before running them.')
  56. parser.add_option('--test', '-t',
  57. dest = 'runTest',
  58. default = None,
  59. help = 'Run one particular test. Relative to test root.')
  60. parser.add_option('--question', '-q',
  61. dest = 'gradeQuestion',
  62. default = None,
  63. help = 'Grade one particular question.')
  64. parser.add_option('--no-graphics',
  65. dest = 'noGraphics',
  66. action = 'store_true',
  67. help = 'No graphics display for pacman games.')
  68. parser.add_option('--graphics',
  69. dest = 'graphics',
  70. action = 'store_true',
  71. help = 'Display graphics for pacman games.')
  72. (options, args) = parser.parse_args(argv)
  73. return options
  74. # confirm we should author solution files
  75. def confirmGenerate():
  76. print 'WARNING: this action will overwrite any solution files.'
  77. print 'Are you sure you want to proceed? (yes/no)'
  78. while True:
  79. ans = sys.stdin.readline().strip()
  80. if ans == 'yes':
  81. break
  82. elif ans == 'no':
  83. sys.exit(0)
  84. else:
  85. print 'please answer either "yes" or "no"'
  86. # TODO: Fix this so that it tracebacks work correctly
  87. # Looking at source of the traceback module, presuming it works
  88. # the same as the intepreters, it uses co_filename. This is,
  89. # however, a readonly attribute.
  90. def setModuleName(module, filename):
  91. functionType = type(confirmGenerate)
  92. classType = type(optparse.Option)
  93. for i in dir(module):
  94. o = getattr(module, i)
  95. if hasattr(o, '__file__'): continue
  96. if type(o) == functionType:
  97. setattr(o, '__file__', filename)
  98. elif type(o) == classType:
  99. setattr(o, '__file__', filename)
  100. # TODO: assign member __file__'s?
  101. #print i, type(o)
  102. #from cStringIO import StringIO
  103. def loadModuleString(moduleSource):
  104. # Below broken, imp doesn't believe its being passed a file:
  105. # ValueError: load_module arg#2 should be a file or None
  106. #
  107. #f = StringIO(moduleCodeDict[k])
  108. #tmp = imp.load_module(k, f, k, (".py", "r", imp.PY_SOURCE))
  109. tmp = imp.new_module(k)
  110. exec moduleCodeDict[k] in tmp.__dict__
  111. setModuleName(tmp, k)
  112. return tmp
  113. import py_compile
  114. def loadModuleFile(moduleName, filePath):
  115. with open(filePath, 'r') as f:
  116. return imp.load_module(moduleName, f, "%s.py" % moduleName, (".py", "r", imp.PY_SOURCE))
  117. def readFile(path, root=""):
  118. "Read file from disk at specified path and return as string"
  119. with open(os.path.join(root, path), 'r') as handle:
  120. return handle.read()
  121. #######################################################################
  122. # Error Hint Map
  123. #######################################################################
  124. # TODO: use these
  125. ERROR_HINT_MAP = {
  126. 'q1': {
  127. "<type 'exceptions.IndexError'>": """
  128. We noticed that your project threw an IndexError on q1.
  129. While many things may cause this, it may have been from
  130. assuming a certain number of successors from a state space
  131. or assuming a certain number of actions available from a given
  132. state. Try making your code more general (no hardcoded indices)
  133. and submit again!
  134. """
  135. },
  136. 'q3': {
  137. "<type 'exceptions.AttributeError'>": """
  138. We noticed that your project threw an AttributeError on q3.
  139. While many things may cause this, it may have been from assuming
  140. a certain size or structure to the state space. For example, if you have
  141. a line of code assuming that the state is (x, y) and we run your code
  142. on a state space with (x, y, z), this error could be thrown. Try
  143. making your code more general and submit again!
  144. """
  145. }
  146. }
  147. import pprint
  148. def splitStrings(d):
  149. d2 = dict(d)
  150. for k in d:
  151. if k[0:2] == "__":
  152. del d2[k]
  153. continue
  154. if d2[k].find("\n") >= 0:
  155. d2[k] = d2[k].split("\n")
  156. return d2
  157. def printTest(testDict, solutionDict):
  158. pp = pprint.PrettyPrinter(indent=4)
  159. print "Test case:"
  160. for line in testDict["__raw_lines__"]:
  161. print " |", line
  162. print "Solution:"
  163. for line in solutionDict["__raw_lines__"]:
  164. print " |", line
  165. def runTest(testName, moduleDict, printTestCase=False, display=None):
  166. import testParser
  167. import testClasses
  168. for module in moduleDict:
  169. setattr(sys.modules[__name__], module, moduleDict[module])
  170. testDict = testParser.TestParser(testName + ".test").parse()
  171. solutionDict = testParser.TestParser(testName + ".solution").parse()
  172. test_out_file = os.path.join('%s.test_output' % testName)
  173. testDict['test_out_file'] = test_out_file
  174. testClass = getattr(projectTestClasses, testDict['class'])
  175. questionClass = getattr(testClasses, 'Question')
  176. question = questionClass({'max_points': 0}, display)
  177. testCase = testClass(question, testDict)
  178. if printTestCase:
  179. printTest(testDict, solutionDict)
  180. # This is a fragile hack to create a stub grades object
  181. grades = grading.Grades(projectParams.PROJECT_NAME, [(None,0)])
  182. testCase.execute(grades, moduleDict, solutionDict)
  183. # returns all the tests you need to run in order to run question
  184. def getDepends(testParser, testRoot, question):
  185. allDeps = [question]
  186. questionDict = testParser.TestParser(os.path.join(testRoot, question, 'CONFIG')).parse()
  187. if 'depends' in questionDict:
  188. depends = questionDict['depends'].split()
  189. for d in depends:
  190. # run dependencies first
  191. allDeps = getDepends(testParser, testRoot, d) + allDeps
  192. return allDeps
  193. # get list of questions to grade
  194. def getTestSubdirs(testParser, testRoot, questionToGrade):
  195. problemDict = testParser.TestParser(os.path.join(testRoot, 'CONFIG')).parse()
  196. if questionToGrade != None:
  197. questions = getDepends(testParser, testRoot, questionToGrade)
  198. if len(questions) > 1:
  199. print 'Note: due to dependencies, the following tests will be run: %s' % ' '.join(questions)
  200. return questions
  201. if 'order' in problemDict:
  202. return problemDict['order'].split()
  203. return sorted(os.listdir(testRoot))
  204. # evaluate student code
  205. def evaluate(generateSolutions, testRoot, moduleDict, exceptionMap=ERROR_HINT_MAP, edxOutput=False, muteOutput=False,
  206. printTestCase=False, questionToGrade=None, display=None):
  207. # imports of testbench code. note that the testClasses import must follow
  208. # the import of student code due to dependencies
  209. import testParser
  210. import testClasses
  211. for module in moduleDict:
  212. setattr(sys.modules[__name__], module, moduleDict[module])
  213. questions = []
  214. questionDicts = {}
  215. test_subdirs = getTestSubdirs(testParser, testRoot, questionToGrade)
  216. for q in test_subdirs:
  217. subdir_path = os.path.join(testRoot, q)
  218. if not os.path.isdir(subdir_path) or q[0] == '.':
  219. continue
  220. # create a question object
  221. questionDict = testParser.TestParser(os.path.join(subdir_path, 'CONFIG')).parse()
  222. questionClass = getattr(testClasses, questionDict['class'])
  223. question = questionClass(questionDict, display)
  224. questionDicts[q] = questionDict
  225. # load test cases into question
  226. tests = filter(lambda t: re.match('[^#~.].*\.test\Z', t), os.listdir(subdir_path))
  227. tests = map(lambda t: re.match('(.*)\.test\Z', t).group(1), tests)
  228. for t in sorted(tests):
  229. test_file = os.path.join(subdir_path, '%s.test' % t)
  230. solution_file = os.path.join(subdir_path, '%s.solution' % t)
  231. test_out_file = os.path.join(subdir_path, '%s.test_output' % t)
  232. testDict = testParser.TestParser(test_file).parse()
  233. if testDict.get("disabled", "false").lower() == "true":
  234. continue
  235. testDict['test_out_file'] = test_out_file
  236. testClass = getattr(projectTestClasses, testDict['class'])
  237. testCase = testClass(question, testDict)
  238. def makefun(testCase, solution_file):
  239. if generateSolutions:
  240. # write solution file to disk
  241. return lambda grades: testCase.writeSolution(moduleDict, solution_file)
  242. else:
  243. # read in solution dictionary and pass as an argument
  244. testDict = testParser.TestParser(test_file).parse()
  245. solutionDict = testParser.TestParser(solution_file).parse()
  246. if printTestCase:
  247. return lambda grades: printTest(testDict, solutionDict) or testCase.execute(grades, moduleDict, solutionDict)
  248. else:
  249. return lambda grades: testCase.execute(grades, moduleDict, solutionDict)
  250. question.addTestCase(testCase, makefun(testCase, solution_file))
  251. # Note extra function is necessary for scoping reasons
  252. def makefun(question):
  253. return lambda grades: question.execute(grades)
  254. setattr(sys.modules[__name__], q, makefun(question))
  255. questions.append((q, question.getMaxPoints()))
  256. grades = grading.Grades(projectParams.PROJECT_NAME, questions, edxOutput=edxOutput, muteOutput=muteOutput)
  257. if questionToGrade == None:
  258. for q in questionDicts:
  259. for prereq in questionDicts[q].get('depends', '').split():
  260. grades.addPrereq(q, prereq)
  261. grades.grade(sys.modules[__name__], bonusPic = projectParams.BONUS_PIC)
  262. return grades.points
  263. def getDisplay(graphicsByDefault, options=None):
  264. graphics = graphicsByDefault
  265. if options != None:
  266. if options.graphics:
  267. graphics = True
  268. if options.noGraphics:
  269. graphics = False
  270. if graphics:
  271. import graphicsDisplay
  272. return graphicsDisplay.PacmanGraphics(1, frameTime=.05)
  273. else:
  274. import textDisplay
  275. return textDisplay.NullGraphics()
  276. if __name__ == '__main__':
  277. options = readCommand(sys.argv)
  278. if options.generateSolutions:
  279. confirmGenerate()
  280. codePaths = options.studentCode.split(',')
  281. # moduleCodeDict = {}
  282. # for cp in codePaths:
  283. # moduleName = re.match('.*?([^/]*)\.py', cp).group(1)
  284. # moduleCodeDict[moduleName] = readFile(cp, root=options.codeRoot)
  285. # moduleCodeDict['projectTestClasses'] = readFile(options.testCaseCode, root=options.codeRoot)
  286. # moduleDict = loadModuleDict(moduleCodeDict)
  287. moduleDict = {}
  288. for cp in codePaths:
  289. moduleName = re.match('.*?([^/]*)\.py', cp).group(1)
  290. moduleDict[moduleName] = loadModuleFile(moduleName, os.path.join(options.codeRoot, cp))
  291. moduleName = re.match('.*?([^/]*)\.py', options.testCaseCode).group(1)
  292. moduleDict['projectTestClasses'] = loadModuleFile(moduleName, os.path.join(options.codeRoot, options.testCaseCode))
  293. if options.runTest != None:
  294. runTest(options.runTest, moduleDict, printTestCase=options.printTestCase, display=getDisplay(True, options))
  295. else:
  296. evaluate(options.generateSolutions, options.testRoot, moduleDict,
  297. edxOutput=options.edxOutput, muteOutput=options.muteOutput, printTestCase=options.printTestCase,
  298. questionToGrade=options.gradeQuestion, display=getDisplay(options.gradeQuestion!=None, options))