""" A Grader grades a ThingToGrade, using a: -- WhoToGrade -- RepoHelper (for checking out files) -- Tester -- Recorder. A ThingToGrade specifies the: -- Course (including Term) [and hence Course information like the students enrolled, etc] -- Project -- [optionally, NOT YET IMPLEMENTED] -- module(s) within the project, -- functions within module(s), and -- ??? Authors: David Mutchler, David Lam, Mark Hays and their colleagues. Version 1.1: May, 2015. """ import repo_helper import tester import recorder import main_for_testing class Grader(object): """ A Grader grades a ThingToGrade, for students expressed by a WhoToGrade, using a: -- RepoHelper (for checking out files) -- Tester -- Recorder. If who_to_grade is None: Grade all students in the course. If this_tester is None: Use a StandardTester. If this_recorderr is None: Use a StandardRecorder. If this_repo_helper is None: Use a StandardRepoHelper. :type what_to_grade: ThingToGrade :type who_to_grade: WhoToGrade :type tester: Tester :type recorder Recorder :type repo_helper RepoHelper """ def __init__(self, what_to_grade, who_to_grade=None, this_tester=None, this_recorder=None, this_repo_helper=None): self.what_to_grade = what_to_grade course = what_to_grade.course self.who_to_grade = WhoToGrade(who_to_grade, course) self.repo_helper = this_repo_helper or \ repo_helper.StandardRepoHelper(what_to_grade) self.tester = this_tester or \ tester.StandardTester(self.what_to_grade, self.who_to_grade, self.repo_helper.get_grading_folder()) self.recorder = this_recorder or recorder.StandardRecorder() def grade(self, include_original=True, include_solution=True): """ Grade this Grader's WhatToGrade, for the students/teams in this Grader's WhoToGrade, using this Grader's HowToGrade, recording results using this Grader's Recorder. """ what = self.what_to_grade students = self.who_to_grade.students # Checkout the specified students (usernames): result = self.repo_helper.checkout_for_grading(what, students) self._display_result_of_checkout(result) return # Checkout the original and solution projects: if include_original: original = self.what_to_grade.course.username_for_original self.repo_helper.checkout_for_grading(what, [original]) if include_solution: solution = self.what_to_grade.course.username_for_solution self.repo_helper.checkout_for_grading(what, [solution]) # Do the tests on the specified students (usernames) # and record the results: results = self.tester.do_tests_on_students() self.recorder.record_all_results(results) @staticmethod def _display_result_of_checkout(result): """ Display the given result, which is a 2-tuple containing: -- the list of failed checkouts -- the list of skipped checkouts :type result: tuple(list(str)) """ failures, skipped, _ = result # Ignore the 3rd item: successes if failures: print('Checkout FAILED for the following students:') print(' ', *failures, sep=' ') if skipped: print('Checkout SKIPPED the following students') print('because their projects were already checked out:') print(' ', *skipped, sep=' ') class ProjectGrader(Grader): # TODO: Move the part of Grader (above) that is specific to # grading a project to this class. # CONSIDER: Should a ProjectGrader use ModuleGraders? def __init__(self, what_to_grade): """ :type what_to_grade: ThingToGrade """ super().__init__(what_to_grade) class ModuleGrader(Grader): # CONSIDER: Is this subclass a good thing to implement? # If so, implement this. pass class FunctionGrader(Grader): # CONSIDER: Is this subclass a good thing to implement? # If so, implement this. pass class StandardGrader(ProjectGrader): """ A StandardGrader is a ProjectGrader that uses a StandardTester and StandardRecorder to grade all students in the ThingToGrade's Course. """ def __init__(self, what_to_grade): """ :type what_to_grade: ThingToGrade """ super().__init__(what_to_grade) class ThingToGrade(object): """ A ThingToGrade specifies the: -- Course (including Term, and also course information like the students enrolled, etc) -- Project -- (optionally) module(s) within the project """ # CONSIDER: Perhaps allow grading of other things too, # e.g. listing the functions/methods/classes within a project # to grade. def __init__(self, course, project, modules=None): """ Stores the given course, project and (optionally) modules. The project can be specified as either: -- a positive integer that is the session number, or -- the project name itself. :type course: Course :type project (int, str) :type modules list((int, str)) """ # NOTE: Throughout I have used extensions of the hint-notation # despite them not yet being implemented in PyDev. # In particular, PyDev does not yet allow (int, str). self.course = course self.project = project try: # project can be a session number self.project_name = course.projects[project] except: # or the project name itself self.project_name = project self.modules = modules or course.get_modules(self.project_name) def __repr__(self): return 'ThingToGrade({}, {}, {})'.format(self.course, self.project, self.modules) class WhoToGrade(object): """ """ # TODO: Put a comment in the above. def __init__(self, who_to_grade=None, course=None): """ If who_to_grade is: -- None or empty list: Grade all students in the course. -- An integer: Grade all students in that section. -- Sequence: Grade all students listed. -- Filename (string): Grade all students listed in that file. :type who_to_grade: (list, tuple, int) :type course: Course """ # CONSIDER: Allow other types for who_to_grade ??? # CONSIDER: Allow GROUP names in the sequence or file? self.who_to_grade = who_to_grade self.course = course if not self.who_to_grade: # None or empty list - grade all the students self.students = course.get_usernames() else: try: # integer - grade all students in that section self.students = course.get_usernames(who_to_grade) except: try: # filename - grade all students in that file with open(who_to_grade, 'r') as file: self.students = file.read().split() except: # who_to_grade is a sequence of students to grade self.students = who_to_grade def main(): main_for_testing.main() if __name__ == '__main__': main() # ---------------------------------------------------------------------- # Code below here was once useful but is no longer correct. # # CONSIDER: Maybe rejuventate it. # ---------------------------------------------------------------------- # def choose_testers(self): # for thing_to_grade in self.things_to_grade: # self.tester[thing_to_grade] = tester.Tester(thing_to_grade) # # (self.grader[thing_to_grade]).grade(thing_to_grade) # if not self.students_to_grade: # self.get_students_to_grade() # if not self.thing_to_grade: # self.get_thing_to_grade() # results = [] # for student in self.thing_to_grade.students: # for tester in self.testers: # result = tester.run_tests(self.thing_to_grade, student) # results.append(result) # self.recorder.record(results) # class ThingToGrade: # """ # Something to be graded: # -- A project, or a module in the project, or a function # in a module in the project, or a collection of these, # along with # -- a list of students (usernames, or perhaps team names) # whose submissions are to be graded. # """ # def __init__(self, # name_of_thing_to_grade=None, course=None, term=None) # """ # If the thing_to_grade is: # -- a list => grade all the things in the list # -- a number N => grade all of the Session N project # -- a (N, M) pair => grade module M in the Session N project # -- a (N, M, s) triple => grade function s in module M # in the Session N project # -- ... # """ # # # TODO: Handle situations other than the above. # self.parse_name(name_of_thing_to_grade) # self.course = course if course else self.default_course() # self.term = term if term else self.current_term() # # # def parse_name(self, name_of_thing_to_grade): # # All these can be a list # self.session # 1, 2, ... # self.project # Session01_..., Session02_..., ... # self.modules # # def default_course(self): # # TODO: Get from a defaults.txt file. # return 'csse120' # # def current_term(self): # # TODO: Use the current date. # return '201510' # # # def all_students(self): # return # # self.students = students # self.function_name = function_name # if recheckout: # self.checkout(True) # # def checkout(self, overwrite=False): # svn.checkout(self.project, self.students)