import shutil import sys import os import subprocess class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def color_text(msg, msg_type): if msg_type == "success": msg = bcolors.BOLD + msg + bcolors.ENDC if msg_type == "fail": msg = bcolors.FAIL + msg + bcolors.ENDC if msg_type == "warn": msg = bcolors.WARNING + msg + bcolors.ENDC if msg_type == "ok": msg = bcolors.OKGREEN + msg + bcolors.ENDC if msg_type == "user": msg = bcolors.HEADER + msg + bcolors.ENDC if msg_type == "problem": msg = bcolors.OKBLUE + msg + bcolors.ENDC return msg class SHPCLogger: def __init__(self, out=sys.stderr): self.out = out self.tabs = 0 def indent(self): self.tabs += 1 def deindent(self): self.tabs -= 1 def success(self, msg=""): tabs = "\t"*self.tabs print(color_text(f"{tabs}[PASS] {msg}", "success"), file=self.out) def fail(self, msg=""): tabs = "\t"*self.tabs print(color_text(f"{tabs}[FAIL] {msg}", "warn"), file=self.out) def not_found(self, msg=""): tabs = "\t"*self.tabs print(color_text(f"{tabs}[NOT FOUND] {msg}", "fail"), file=self.out) def compile_error(self, msg=""): tabs = "\t"*self.tabs print(color_text(f"{tabs}[COMPILE ERROR] {msg}", "fail"), file=self.out) def runtime_error(self, msg=""): tabs = "\t"*self.tabs print(color_text(f"{tabs}[RUNTIME ERROR] {msg}", "fail"), file=self.out) def user(self, msg=""): tabs = "\t"*self.tabs print(color_text(f"{tabs}(USER) {msg}", "user"), file=self.out) def problem(self, msg=""): tabs = "\t"*self.tabs print(color_text(f"{tabs} {msg}", "problem"), file=self.out) class SHPCTestCase: def __init__(self, test_command, _eval): self.test_command = test_command self.eval = _eval def run(self, cwd): result = subprocess.check_output(self.test_command.split(), cwd=cwd, stderr=subprocess.DEVNULL).decode('utf-8') return result class SHPCProblem: def __init__(self, name, hw, wd, required_files, test_cases): self.name = name self.hw = hw self.wd = wd self.required_files = required_files self.test_cases = test_cases def compile(self, user, submission_dir): no_file = False # Remove the file already existing in the directory for fname in self.required_files: file_path = f"{self.wd}/{fname}" if os.path.exists(file_path): os.remove(f"{self.wd}/{fname}") # Check if all required files exist for fname in self.required_files: user_file_path = f"{submission_dir}/{user}/{self.hw}/{fname}" if not os.path.exists(user_file_path): print(user_file_path) raise Exception(f"Not exist the required file {fname} in for {user}") # Copy all required files for fname in self.required_files: file_path = f"{self.wd}/{fname}" user_file_path = f"{submission_dir}/{user}/{self.hw}/{fname}" shutil.copy(user_file_path, file_path) fnull = open(os.devnull, 'wb') subprocess.run(["make", "clean"], cwd=f"{self.wd}", stdout=fnull, stderr=fnull) compile_fail = subprocess.run(["make"], cwd=f"{self.wd}", stdout=fnull, stderr=fnull).returncode if compile_fail: raise Exception(f"Cannot compile for {user}") class SHPCGrader: def __init__(self, submission_dir, problems, users, out=sys.stderr): self.submission_dir = submission_dir self.problems = problems self.users = users self.logger = SHPCLogger(out) # user -> prob -> list of scores score_dict = [[]] * len(users) for i in range(len(users)): score_dict[i] = [0] * len(problems) self.score_dict = score_dict def grade(self): for i, user in enumerate(self.users): self.logger.user(f"{user}") self.logger.indent() for j, problem in enumerate(self.problems): self.logger.problem(f"{problem.name}") self.logger.indent() try: problem.compile(user, self.submission_dir) except Exception as ex: self.logger.compile_error(f"{ex}") self.logger.deindent() continue for test_case in problem.test_cases: try: submitted = test_case.run(f"{problem.wd}") except Exception as ex: self.logger.runtime_error(f"{ex}") continue score = test_case.eval(submitted) self.score_dict[i][j] += score if score > 0: self.logger.success(f"{test_case.test_command}") else: self.logger.fail(f"{test_case.test_command}") self.logger.deindent() self.logger.deindent() def to_csv(self, sep="\t", outfile=sys.stdout): outfile.write(sep) for prob in self.problems: outfile.write(f"{prob.name}{sep}") outfile.write("\n") for i, user in enumerate(self.users): outfile.write(f"{user}{sep}") for j, _ in enumerate(self.problems): score = self.score_dict[i][j] outfile.write(f"{score}{sep}") outfile.write("\n")