From 9aa1c90479abd502ef6bbcaa2d50451c4d2b4af3 Mon Sep 17 00:00:00 2001 From: Tue Herlau <tuhe@dtu.dk> Date: Fri, 10 Mar 2023 11:26:30 +0100 Subject: [PATCH] fix of Cache and unicode --- src/unitgrade.egg-info/PKG-INFO | 2 +- src/unitgrade/artifacts.py | 2 +- src/unitgrade/evaluate.py | 11 ++++++--- src/unitgrade/framework.py | 32 ++++++++++++++++++------- src/unitgrade/runners.py | 25 ++++++++++++++++---- src/unitgrade/utils.py | 41 ++++++++++++++++++++++----------- src/unitgrade/version.py | 2 +- 7 files changed, 83 insertions(+), 32 deletions(-) diff --git a/src/unitgrade.egg-info/PKG-INFO b/src/unitgrade.egg-info/PKG-INFO index 601a473..b3cd176 100644 --- a/src/unitgrade.egg-info/PKG-INFO +++ b/src/unitgrade.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: unitgrade -Version: 0.1.30.5 +Version: 0.1.30.6 Summary: A student homework/exam evaluation framework build on pythons unittest framework. Home-page: https://lab.compute.dtu.dk/tuhe/unitgrade Author: Tue Herlau diff --git a/src/unitgrade/artifacts.py b/src/unitgrade/artifacts.py index 7b00750..1294fc4 100644 --- a/src/unitgrade/artifacts.py +++ b/src/unitgrade/artifacts.py @@ -58,7 +58,7 @@ class DummyPipe: self.type = type self.std_ = std_out_or_err self.queue = queue - self.mute = False + self.mute = mute def write(self, message): if not self.mute: diff --git a/src/unitgrade/evaluate.py b/src/unitgrade/evaluate.py index 728577e..0e1aba3 100644 --- a/src/unitgrade/evaluate.py +++ b/src/unitgrade/evaluate.py @@ -43,7 +43,9 @@ parser.add_argument('--noprogress', action="store_true", help='Disable progres def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False, show_privisional=True, noprogress=None, - generate_artifacts=True): + generate_artifacts=True, + show_errors_in_grade_mode=True # This is included for debugging purpose. Should always be True. + ): args = parser.parse_args() if noprogress is None: noprogress = args.noprogress @@ -66,7 +68,7 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute and not noprogress, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute, show_tol_err=show_tol_err, - generate_artifacts=generate_artifacts) + generate_artifacts=generate_artifacts, show_errors_in_grade_mode=show_errors_in_grade_mode) if question is None and show_privisional: @@ -109,7 +111,9 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa show_progress_bar=True, show_tol_err=False, generate_artifacts=True, # Generate the artifact .json files. These are exclusively used by the dashboard. - big_header=True): + big_header=True, + show_errors_in_grade_mode=True # Show test error during grading. + ): from unitgrade.version import __version__ now = datetime.now() @@ -152,6 +156,7 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa UTextResult.number = n UTextResult.nL = report.nL UTextResult.unmute = unmute # Hacky as well. + UTextResult.show_errors_in_grade_mode = show_errors_in_grade_mode UTextResult.setUpClass_time = q._cache.get(((q.__name__, 'setUpClass'), 'time'), 3) if hasattr(q, '_cache') and q._cache is not None else 3 diff --git a/src/unitgrade/framework.py b/src/unitgrade/framework.py index 864d315..d482ec0 100644 --- a/src/unitgrade/framework.py +++ b/src/unitgrade/framework.py @@ -120,6 +120,9 @@ class Report: relative_path = os.path.relpath(self._file(), root_dir) modules = os.path.normpath(relative_path[:-3]).split(os.sep) relative_path = relative_path.replace("\\", "/") + if relative_path.startswith(".."): + raise Exception("Bad relative path. setup failed. ", root_dir, self._file()) + return root_dir, relative_path, modules def __init__(self, strict=False, payload=None): @@ -351,6 +354,16 @@ class UTestCase(unittest.TestCase): # print("Run called in test framework...", self._generate_artifacts) if not self._generate_artifacts: return super().run(result) + + print(result) + mute = False + if isinstance(result, UTextResult): + print(result.show_errors_in_grade_mode) + mute = not result.show_errors_in_grade_mode + else: + pass + # print("this' not a text result.") + # print(result.show_errors_in_grade_mode) from unitgrade.artifacts import StdCapturing from unitgrade.utils import DKPupDB self._error_fed_during_run = [] # Initialize this to be empty. @@ -362,8 +375,8 @@ class UTestCase(unittest.TestCase): _stdout = sys.stdout _stderr = sys.stderr - - std_capture = StdCapturing(stdout=sys.stdout, stderr=sys.stderr, db=db, mute=False) + # mute = True + std_capture = StdCapturing(stdout=sys.stdout, stderr=sys.stderr, db=db, mute=mute) state_ = None try: @@ -376,17 +389,13 @@ class UTestCase(unittest.TestCase): # db.get('stdout') # db.get('stderr') # db.get("history") - - result_ = TestCase.run(self, result) from werkzeug.debug.tbtools import DebugTraceback, _process_traceback - # What could be nice to upload? # When the files are edited? # When tests are run? # how to register it? (report, question, user...) - # # print(result_._excinfo[0]) actual_errors = [] for test, err in self._error_fed_during_run: @@ -467,7 +476,7 @@ class UTestCase(unittest.TestCase): for ll in data.contexts_by_lineno(file): l = ll-1 - print(l, lines2[l]) + # print(l, lines2[l]) if l < len(lines2) and lines2[l].strip() == garb: print("Got one.") rel = os.path.relpath(child, root) @@ -858,12 +867,19 @@ class NotebookTestCase(UTestCase): _nb = None @classmethod def setUpClass(cls) -> None: + f = cls._cache_file() + # print(f) + file = os.path.dirname(os.path.dirname(f)) + "/" + cls.notebook + # print(f"{f=}, {file=}, {cls.notebook=}") + # print(__file__) + print(os.path.curdir) + # print("cwd", os.getcwd()) with Capturing(): # print(__file__) f = cls._cache_file() # print(f) file = os.path.dirname(os.path.dirname(f)) + "/" + cls.notebook - cls._nb = importnb.Notebook.load_file(file) + cls._nb = importnb.Notebook.load_argv(file + " -nograde") @property def nb(self): diff --git a/src/unitgrade/runners.py b/src/unitgrade/runners.py index dfd1c46..1f5e5da 100644 --- a/src/unitgrade/runners.py +++ b/src/unitgrade/runners.py @@ -14,6 +14,7 @@ class UTextResult(unittest.TextTestResult): unmute = False # Whether to redirect stdout. cc = None setUpClass_time = 3 # Estimated time to run setUpClass in TestCase. Must be set externally. See key (("ClassName", "setUpClass"), "time") in _cache. + show_errors_in_grade_mode = True def __init__(self, stream, descriptions, verbosity): super().__init__(stream, descriptions, verbosity) @@ -24,6 +25,19 @@ class UTextResult(unittest.TextTestResult): self.printErrorList('ERROR', [(test, res['stderr']) for test, res in self.errors]) self.printErrorList('FAIL', [(test, res['stderr']) for test, res in self.failures]) + def printErrorList(self, flavour, errors): + if not self.show_errors_in_grade_mode: + return + else: + super().printErrorList(flavour, errors) + # + # for test, err in errors: + # self.stream.writeln(self.separator1) + # self.stream.writeln("%s: %s" % (flavour,self.getDescription(test))) + # self.stream.writeln(self.separator2) + # self.stream.writeln("%s" % err) + # self.stream.flush() + def addError(self, test, err): super(unittest.TextTestResult, self).addError(test, err) err = self.errors[-1][1] @@ -42,7 +56,6 @@ class UTextResult(unittest.TextTestResult): if self.item_title_print is None: # In case the short description is not set either... self.item_title_print = test.id() - self.cc_terminate(success=False) def addFailure(self, test, err): @@ -111,18 +124,21 @@ class UTextResult(unittest.TextTestResult): # if self.show_progress_bar or True: estimated_time = test.__class__._cache.get(((name, test._testMethodName), 'time'), 100) if hasattr(test.__class__, '_cache') else 4 self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar) - # else: - # print(self.item_title_print + ('.' * max(0, self.nL - 4 - len(self.item_title_print))), end="") self._test = test - # if not self.unmute: self._stdout = sys.stdout # Redundant. remove later. from unitgrade.utils import Logger sys.stdout = Logger(io.StringIO(), write_to_stdout=self.unmute) + if not self.show_errors_in_grade_mode: + # print("Trying to hide the errors....", self.show_errors_in_grade_mode) + self._stderr = sys.stderr + sys.stderr = Logger(io.StringIO(), write_to_stdout=False) def stopTest(self, test): # if not self.unmute: buff = sys.stdout.log sys.stdout = self._stdout # redundant. + if not self.show_errors_in_grade_mode: + sys.stderr = self._stderr buff.close() from unitgrade.utils import Logger super().stopTest(test) @@ -139,6 +155,7 @@ class UTextResult(unittest.TextTestResult): self.cc = cc + def _restoreStdout(self): # Used when setting up the test. if self._previousTestClass is None: q_time = self.cc.terminate() diff --git a/src/unitgrade/utils.py b/src/unitgrade/utils.py index 03b0f58..4160451 100644 --- a/src/unitgrade/utils.py +++ b/src/unitgrade/utils.py @@ -137,7 +137,7 @@ class ActiveProgress(): def terminate(self): if not self._running: - print("Stopping a progress bar which is not running (class unitgrade.utils.ActiveProgress") + # print("Stopping a progress bar which is not running (class unitgrade.utils.ActiveProgress") pass # raise Exception("Stopping a stopped progress bar. ") self._running = False @@ -152,10 +152,7 @@ class ActiveProgress(): if self.mute_stdout: import io - # from unitgrade.utils import Logger - sys.stdout = self._stdout #= sys.stdout - - # sys.stdout = Logger(io.StringIO(), write_to_stdout=False) + sys.stdout = self._stdout return time.time() - self.time_started @@ -308,20 +305,28 @@ def load_token(file_in): ## Key/value store related. class DKPupDB: + DISABLE_DB = False """ This key/value store store artifacts (associated with a specific question) in a dictionary. """ def __init__(self, artifact_file, use_pupdb=False, register_ephemeral=False): # Make a double-headed disk cache thingy. - self.dk = Cache(os.path.dirname(artifact_file)) # Start in this directory. - self.name_ = os.path.basename(artifact_file[:-5]) - if self.name_ not in self.dk: - self.dk[self.name_] = dict() - self.use_pupdb = use_pupdb - self.register_ephemeral = register_ephemeral - if self.use_pupdb: - from pupdb.core import PupDB - self.db_ = PupDB(artifact_file) + try: + self.dk = Cache(os.path.dirname(artifact_file)) # Start in this directory. + self.name_ = os.path.basename(artifact_file[:-5]) + if self.name_ not in self.dk: + self.dk[self.name_] = dict() + self.use_pupdb = use_pupdb + self.register_ephemeral = register_ephemeral + if self.use_pupdb: + from pupdb.core import PupDB + self.db_ = PupDB(artifact_file) + except Exception as e: + # print("Artifact file not readable. Possibly ") + self.DISABLE_DB = True + pass def __setitem__(self, key, value): + if self.DISABLE_DB: + return if self.use_pupdb: self.db_.set(key, value) with self.dk.transact(): @@ -332,6 +337,8 @@ class DKPupDB: self.dk[self.name_ + "-updated"] = True def __getitem__(self, item): + if self.DISABLE_DB: + return "db disabled due to bad artifact file." v = self.dk[self.name_][item] if self.use_pupdb: v2 = self.db_.get(item) @@ -344,11 +351,17 @@ class DKPupDB: # return self.db_.keys() def set(self, item, value): # This one is deprecated. + if self.DISABLE_DB: + return self[item] = value def get(self, item, default=None): + if self.DISABLE_DB: + return "artifact file not loaded." return self[item] if item in self else default def __contains__(self, item): + if self.DISABLE_DB: + return False return item in self.dk[self.name_] #keys() # return item in self.dk diff --git a/src/unitgrade/version.py b/src/unitgrade/version.py index 1287186..a4d669c 100644 --- a/src/unitgrade/version.py +++ b/src/unitgrade/version.py @@ -1 +1 @@ -__version__ = "0.1.30.6" +__version__ = "0.1.30.8" -- GitLab