From 7e820f223f4b9ce5cc9c87d56f0e203e26e467b1 Mon Sep 17 00:00:00 2001 From: Tue Herlau <tuhe@dtu.dk> Date: Thu, 2 Sep 2021 15:33:58 +0200 Subject: [PATCH] Updates --- LICENSE | 19 + MANIFEST.in | 1 - README.md | 36 +- cs101courseware_example/instructions.py | 2 +- dist/unitgrade-0.0.2-py3-none-any.whl | Bin 0 -> 17438 bytes dist/unitgrade-0.0.2.tar.gz | Bin 0 -> 17257 bytes dist/unitgrade-0.0.5.tar.gz | Bin 30274 -> 0 bytes pyproject.toml | 6 + setup.py | 43 +- {unitgrade2 => src/unitgrade2}/__init__.py | 3 +- .../__pycache__/__init__.cpython-38.pyc | Bin 0 -> 1306 bytes .../__pycache__/unitgrade2.cpython-38.pyc | Bin 0 -> 22713 bytes src/unitgrade2/unitgrade2.py | 705 ++++++++++++++ .../unitgrade2}/unitgrade_helpers2.py | 43 +- src/unitgrade2/version.py | 1 + unitgrade.egg-info/PKG-INFO | 10 - unitgrade.egg-info/SOURCES.txt | 21 - unitgrade.egg-info/dependency_links.txt | 1 - unitgrade.egg-info/requires.txt | 4 - unitgrade.egg-info/top_level.txt | 2 - unitgrade/Report_resources_do_not_hand_in.dat | Bin 64 -> 0 bytes unitgrade/version.py | 2 +- .../__pycache__/__init__.cpython-38.pyc | Bin 1373 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 1400 -> 0 bytes .../__pycache__/unitgrade2.cpython-38.pyc | Bin 29204 -> 0 bytes .../unitgrade_helpers2.cpython-38.pyc | Bin 6959 -> 0 bytes unitgrade2/__pycache__/version.cpython-38.pyc | Bin 167 -> 0 bytes unitgrade2/__pycache__/version.cpython-39.pyc | Bin 164 -> 0 bytes unitgrade2/unitgrade/ListQuestion.pkl | Bin 466 -> 0 bytes unitgrade2/unitgrade2.py | 891 ------------------ unitgrade2/version.py | 1 - 31 files changed, 793 insertions(+), 998 deletions(-) create mode 100644 LICENSE delete mode 100644 MANIFEST.in create mode 100644 dist/unitgrade-0.0.2-py3-none-any.whl create mode 100644 dist/unitgrade-0.0.2.tar.gz delete mode 100644 dist/unitgrade-0.0.5.tar.gz create mode 100644 pyproject.toml rename {unitgrade2 => src/unitgrade2}/__init__.py (90%) create mode 100644 src/unitgrade2/__pycache__/__init__.cpython-38.pyc create mode 100644 src/unitgrade2/__pycache__/unitgrade2.cpython-38.pyc create mode 100644 src/unitgrade2/unitgrade2.py rename {unitgrade2 => src/unitgrade2}/unitgrade_helpers2.py (84%) create mode 100644 src/unitgrade2/version.py delete mode 100644 unitgrade.egg-info/PKG-INFO delete mode 100644 unitgrade.egg-info/SOURCES.txt delete mode 100644 unitgrade.egg-info/dependency_links.txt delete mode 100644 unitgrade.egg-info/requires.txt delete mode 100644 unitgrade.egg-info/top_level.txt delete mode 100644 unitgrade/Report_resources_do_not_hand_in.dat delete mode 100644 unitgrade2/__pycache__/__init__.cpython-38.pyc delete mode 100644 unitgrade2/__pycache__/__init__.cpython-39.pyc delete mode 100644 unitgrade2/__pycache__/unitgrade2.cpython-38.pyc delete mode 100644 unitgrade2/__pycache__/unitgrade_helpers2.cpython-38.pyc delete mode 100644 unitgrade2/__pycache__/version.cpython-38.pyc delete mode 100644 unitgrade2/__pycache__/version.cpython-39.pyc delete mode 100644 unitgrade2/unitgrade/ListQuestion.pkl delete mode 100644 unitgrade2/unitgrade2.py delete mode 100644 unitgrade2/version.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..335ea9d --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 The Python Packaging Authority + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 822df1c..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include *.dat diff --git a/README.md b/README.md index 460eea5..c1a3fc8 100644 --- a/README.md +++ b/README.md @@ -4,47 +4,35 @@ Unitgrade is an automatic report and exam evaluation framework that enables inst Unitgrade is build on pythons `unittest` framework so that the tests can be specified in a familiar syntax and will integrate with any modern IDE. What it offers beyond `unittest` is the ability to collect tests in reports (for automatic evaluation) and an easy and 100% safe mechanism for verifying the students results and creating additional, hidden tests. A powerful cache system allows instructors to automatically create test-answers based on a working solution. - 100% Python `unittest` compatible - - No external configuration files: Just write a `unittest` - - No unnatural limitations: Use any package or framework. If you can `unittest` it, it works. + - No external configuration files, just write a `unittest` + - No unnatural limitations: If you can `unittest` it, it works. - Granular security model: - Students get public `unittests` for easy development of solutions - Students get a tamper-resistant file to create submissions which are uploaded - - Instructors can automatically verify the students solution using a Docker VM and run hidden tests + - Instructors can automatically verify the students solution using Docker VM and by running hidden tests + - Allow export of assignments to Autolab (no `make` file mysteries!) - Tests are quick to run and will integrate with your IDE ## Installation -Unitgrade can be installed through pip using +Unitgrade can be installed using `pip`: ``` -pip install git+https://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git +pip install unitgrade ``` This will install unitgrade in your site-packages directory. If you want to upgrade an old installation of unitgrade: ``` -pip install git+https://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade +pip install unitgrade --upgrade ``` If you are using anaconda+virtual environment you can install it as ``` source activate myenv conda install git pip -pip install git+https://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git +pip install unitgrade ``` -Alternatively, simply use git-clone of the sources and add unitgrade to your python path. When you are done, you should be able to import unitgrade: ``` import unitgrade ``` -## Testing installation -I have provided an example project which illustrates all main features in a self-contained manner and which should -work immediately upon installation. The source can be found here: https://lab.compute.dtu.dk/tuhe/unitgrade/-/tree/master/cs101courseware_example -To run the example, first start a python console: -``` -python -``` -Then run the code -``` -from cs101courseware_example import instructions -``` -This will print on-screen instructions for how to use the system tailored to your user-specific installation path. ## Evaluating a report Homework is broken down into **reports**. A report is a collection of questions which are individually scored, and each question may in turn involve multiple tests. Each report is therefore given an overall score based on a weighted average of how many tests are passed. @@ -83,12 +71,12 @@ To register your results, please run the file: >>> cs101report1_grade.py In the same manner as you ran this file. ``` -Once you are happy with the result, run the alternative, not-easy-to-tamper-with script called `cs101report1_grade.py`: +Once you are happy with the result run the script with the `_grade.py`-postfix, in this case `cs101report1_grade.py`: ``` python cs101report1_grade.py ``` -This runs the same tests, and generates a file `Report0_handin_18_of_18.token`. The file name indicates how many points you got. Upload this file to campusnet. +This runs the same tests, and generates a file `Report0_handin_18_of_18.token`. The file name indicates how many points you got. Upload this file to campusnet (and no other). ### Why are there two scripts? The reason why we use a standard test script, and one with the `_grade.py` extension, is because the tests should both be easy to debug, but at the same time we have to prevent accidential changes to the test scripts. Hence, we include two versions of the tests. @@ -97,7 +85,7 @@ The reason why we use a standard test script, and one with the `_grade.py` exten - **My non-grade script and the `_grade.py` script gives different number of points** Since the two scripts should contain the same code, the reason is nearly certainly that you have made an (accidental) change to the test scripts. Please ensure both scripts are up-to-date and if the problem persists, try to get support. - - **Why is there a `*_resources_do_not_hand_in.dat` file? Should I also upload it?** + - **Why is there a `unitgrade` directory with a bunch of pickle files? Should I also upload them?** No. The file contains the pre-computed test results your code is compared against. If you want to load this file manually, the unitgrade package contains helpful functions for doing so. - **I am worried you might think I cheated because I opened the '_grade.py' script/token file** @@ -135,7 +123,7 @@ Yes. That the script `report1_grade.py` is difficult to read is not the principle safety measure. Instead, it ensures there is no accidential tampering. If you muck around with these files and upload the result, we will very likely know. - **I have private data on my computer. Will this be read or uploaded?** -No. The code will look for and upload your solutions, but it will not read/look at other directories in your computer. In the example provided with this code, this means you should expect unitgrade to read/run all files in the `cs101courseware_example`-directory, but **no** other files on your computer (unless some code in this directory load other files). So as long as you keep your private files out of the base courseware directory, you should be fine. +No. The code will look for and upload your solutions, but it will not read/look at other directories in your computer. In the example provided with this code, this means you should expect unitgrade to read/run all files in the `cs101courseware_example`-directory, but **no** other files on your computer. So as long as you keep your private files out of the base courseware directory, you should be fine. - **Does this code install any spyware/etc.? Does it communicate with a website/online service?** No. Unitgrade makes no changes outside the courseware directory and it does not do anything tricky. It reads/runs code and write the `.token` file. diff --git a/cs101courseware_example/instructions.py b/cs101courseware_example/instructions.py index b6e149b..9786bd4 100644 --- a/cs101courseware_example/instructions.py +++ b/cs101courseware_example/instructions.py @@ -18,7 +18,7 @@ for d in os.listdir(wdir): if "__" not in d and d != "instructions.py": print("> ", d) print("") -fprint("The file homework1.py is the file you edit as part of the course; you are welcome to open it and inspect the content, but right now it consists of some simple programming tasks plus instructions.") +fprint("The file looping.py is the file you edit as part of the course; you are welcome to open it and inspect the content, but right now it consists of some simple programming tasks plus instructions.") fprint("The file cs101report1.py contains the actual tests of the program. All the tests are easily readable and the script will work with your debugger if you are using pycharm, however, we will run the script for the command line. ") fprint("To do so, open a console, and change directory to the cs103 main directory using e.g.:") tprint(f'cd "{wdir}"') diff --git a/dist/unitgrade-0.0.2-py3-none-any.whl b/dist/unitgrade-0.0.2-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..88df3c4dfc76af895eddef0e9e1a54c7a0cc5fc6 GIT binary patch literal 17438 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 literal 0 HcmV?d00001 diff --git a/dist/unitgrade-0.0.2.tar.gz b/dist/unitgrade-0.0.2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..24bc625650b200d9016cc5ada901d05c91e0706f GIT binary patch literal 17257 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 literal 0 HcmV?d00001 diff --git a/dist/unitgrade-0.0.5.tar.gz b/dist/unitgrade-0.0.5.tar.gz deleted file mode 100644 index debd32cec15ab13d6feb708942d2a2a9f1400e1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30274 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 Z!_V+D{0u+C&rihX{{dFZ^s)eu0RTHt$%Fs^ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b5a3c46 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.py b/setup.py index 29108c8..aaa1240 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,35 @@ -from setuptools import setup -from unitgrade.version import __version__ -setup( - name='unitgrade', +# Use this guide: +# https://packaging.python.org/tutorials/packaging-projects/ + +# from unitgrade2.version import __version__ +import setuptools +with open("src/unitgrade2/version.py", "r", encoding="utf-8") as fh: + __version__ = fh.read().split(" = ")[1].strip()[1:-1] +# long_description = fh.read() + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setuptools.setup( + name="unitgrade", version=__version__, - packages=['unitgrade', 'cs101courseware_example'], + author="Tue Herlau", + author_email="tuhe@dtu.dk", + description="A student homework/exam evaluation framework build on pythons unittest framework.", + long_description=long_description, + long_description_content_type="text/markdown", url='https://lab.compute.dtu.dk/tuhe/unitgrade', - license='Apache', - author='Tue Herlau', - author_email='tuhe@dtu.dk', - description='A lightweight student evaluation framework build on unittest', - include_package_data=True, - install_requires=['numpy', 'jinja2', 'tabulate', 'sklearn', 'compress_pickle', "pyfiglet"], + project_urls={ + "Bug Tracker": "https://lab.compute.dtu.dk/tuhe/unitgrade/issues", + }, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + package_dir={"": "src"}, + packages=setuptools.find_packages(where="src"), + python_requires=">=3.8", + license="MIT", + install_requires=['numpy', 'tabulate', 'tqdm', "pyfiglet", "colorama", "coverage"], ) diff --git a/unitgrade2/__init__.py b/src/unitgrade2/__init__.py similarity index 90% rename from unitgrade2/__init__.py rename to src/unitgrade2/__init__.py index 23b1c9d..d62d54d 100644 --- a/unitgrade2/__init__.py +++ b/src/unitgrade2/__init__.py @@ -1,4 +1,3 @@ -from unitgrade2.version import __version__ import os # DONT't import stuff here since install script requires __version__ @@ -34,4 +33,4 @@ def cache_read(file_name): else: return None -from unitgrade2.unitgrade2 import Hidden, myround, mfloor, msum, Capturing, ActiveProgress +from unitgrade2.unitgrade2 import myround, mfloor, msum, Capturing, ActiveProgress diff --git a/src/unitgrade2/__pycache__/__init__.cpython-38.pyc b/src/unitgrade2/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4429c41fb31c60f6c1074d20620caf2572fce98e GIT binary patch literal 1306 zcmZ9LO>f*p7{_PEue+O)@KO$?1k(eOQME#mkPxa6kb=Y|0-7qpT509=Og7%w9y2rU zc2^DuQgJ{ke1$|k^()Pl6JLQ7&tq>$=~({HGcV7~c>X^>zIAIzpx76mq~Cgk{DqBI zivx`>;niP*aKdRp62gbv;qEh<Q0{U68A%uqcnGxvGX{lQdWl#1iC+duP=-lZ4w3<d z+(#rH{SE7ogTo9LzAiol19<o0)fEV*oD}5TF?7K$ot9qFbGM7#*1e+oV?!@Jj3{ZD z`po#8we*5fVuF@(=YswD?zz`Gi?AiF$6f9{cPKd`pOPQQkK`D07Lem#Kt3mMsNg_R z*mb>gq;iv2^C(NRQ!yToEnCj4Tdd3UB~3<|ENdmS&Skas9?D957!U0Bb?>y!v$H~2 zCbf0z)STLY=c-CeVSVv5*G5~fJcBOl)he%yb!9Co>++_oW8eC6wh)=IgOj`v(=IJ| zBGgQ3p}-it<I2*Lmqfh<t$!Zue?EB(LwaJGQ!#lcv!)c4(UYdi&0M8iOti{wT6>e} zG>4(-bX>3OsQbX_5<XYp;`$vBLPNR*&yc!g2=yJP6%J&0IoAba^se)23j#wH^pdsY z3hku_v7^vV6`-K0F;&|b<FR{r8zQ^aAECdQ9q7>vgfMj*^nW|L(1DP4JN^N|h7P)+ z19n!wt@n)E63)Q;-E*K};k67Qea?;u+c^IM7<jZ{;E4SEyGx9JHdMb-AdQmst_ir; zlAfcU4&cc_N{}56vwm@o$xo1ZOez=2d6gc^)J);cC_M1`-l56`kBp28ner%&W}rPH z5n3FjW?g=F5}8x6Y1g$#mBx%2j5bD=smZgnC|1#2R6?aj@cYq9Hh^ap8MVTxTol5H zC^z>b2}`MEu0_P<vfAugiDd*BA+vGVf%KX^3OxkI;gELl&C^WO2Ii^}^u})Q91^15 z#A#H#ZsV}GQHhkJan~TREy@6)kLV8I6w<qnx`PXPHYit0HWjyic~VHJtXt})w1fS$ zHVyn1bNj|unaQ7s@0Fb6&lc0ec-WsAS?uuW7E5mbhx*?5CRFd?th*@hqwJ!5fN}$1 a7j+bd4@$|KLVST(>kuS#hojI7xBmrp{x(<u literal 0 HcmV?d00001 diff --git a/src/unitgrade2/__pycache__/unitgrade2.cpython-38.pyc b/src/unitgrade2/__pycache__/unitgrade2.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f772f63e4a0c6727d15b104e6cf981814c53043 GIT binary patch literal 22713 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ⅆ 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 literal 0 HcmV?d00001 diff --git a/src/unitgrade2/unitgrade2.py b/src/unitgrade2/unitgrade2.py new file mode 100644 index 0000000..a0290ca --- /dev/null +++ b/src/unitgrade2/unitgrade2.py @@ -0,0 +1,705 @@ +""" +git add . && git commit -m "Options" && git push && pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade +""" +import numpy as np +import sys +import re +import threading +import tqdm +import pickle +import os +from io import StringIO +import io +from unittest.runner import _WritelnDecorator +from typing import Any +import inspect +import textwrap +import colorama +from colorama import Fore +from functools import _make_key, RLock +from collections import namedtuple +import unittest +import time + +_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + +colorama.init(autoreset=True) # auto resets your settings after every output + +def gprint(s): + print(f"{Fore.GREEN}{s}") + +myround = lambda x: np.round(x) # required. +msum = lambda x: sum(x) +mfloor = lambda x: np.floor(x) + + +def setup_dir_by_class(C, base_dir): + name = C.__class__.__name__ + return base_dir, name + + +class Logger(object): + def __init__(self, buffer): + assert False + self.terminal = sys.stdout + self.log = buffer + + def write(self, message): + self.terminal.write(message) + self.log.write(message) + + def flush(self): + # this flush method is needed for python 3 compatibility. + pass + + +class Capturing(list): + def __init__(self, *args, stdout=None, unmute=False, **kwargs): + self._stdout = stdout + self.unmute = unmute + super().__init__(*args, **kwargs) + + def __enter__(self, capture_errors=True): # don't put arguments here. + self._stdout = sys.stdout if self._stdout == None else self._stdout + self._stringio = StringIO() + if self.unmute: + sys.stdout = Logger(self._stringio) + else: + sys.stdout = self._stringio + + if capture_errors: + self._sterr = sys.stderr + sys.sterr = StringIO() # memory hole it + self.capture_errors = capture_errors + return self + + def __exit__(self, *args): + self.extend(self._stringio.getvalue().splitlines()) + del self._stringio # free up some memory + sys.stdout = self._stdout + if self.capture_errors: + sys.sterr = self._sterr + + +class Capturing2(Capturing): + def __exit__(self, *args): + lines = self._stringio.getvalue().splitlines() + txt = "\n".join(lines) + numbers = extract_numbers(txt) + self.extend(lines) + del self._stringio # free up some memory + sys.stdout = self._stdout + if self.capture_errors: + sys.sterr = self._sterr + + self.output = txt + self.numbers = numbers + + +# @classmethod +# class OrderedClassMembers(type): +# def __prepare__(self, name, bases): +# assert False +# return collections.OrderedDict() +# +# def __new__(self, name, bases, classdict): +# ks = list(classdict.keys()) +# for b in bases: +# ks += b.__ordered__ +# classdict['__ordered__'] = [key for key in ks if key not in ('__module__', '__qualname__')] +# return type.__new__(self, name, bases, classdict) + + +class Report: + title = "report title" + version = None + questions = [] + pack_imports = [] + individual_imports = [] + nL = 120 # Maximum line width + + @classmethod + def reset(cls): + for (q, _) in cls.questions: + if hasattr(q, 'reset'): + q.reset() + + @classmethod + def mfile(clc): + return inspect.getfile(clc) + + def _file(self): + return inspect.getfile(type(self)) + + def _import_base_relative(self): + if hasattr(self.pack_imports[0], '__path__'): + root_dir = self.pack_imports[0].__path__._path[0] + else: + root_dir = self.pack_imports[0].__file__ + + root_dir = os.path.dirname(root_dir) + relative_path = os.path.relpath(self._file(), root_dir) + modules = os.path.normpath(relative_path[:-3]).split(os.sep) + return root_dir, relative_path, modules + + def __init__(self, strict=False, payload=None): + working_directory = os.path.abspath(os.path.dirname(self._file())) + self.wdir, self.name = setup_dir_by_class(self, working_directory) + # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat") + for (q, _) in self.questions: + q.nL = self.nL # Set maximum line length. + + if payload is not None: + self.set_payload(payload, strict=strict) + + def main(self, verbosity=1): + # Run all tests using standard unittest (nothing fancy). + loader = unittest.TestLoader() + for q, _ in self.questions: + start = time.time() # A good proxy for setup time is to + suite = loader.loadTestsFromTestCase(q) + unittest.TextTestRunner(verbosity=verbosity).run(suite) + total = time.time() - start + q.time = total + + def _setup_answers(self, with_coverage=False): + if with_coverage: + for q, _ in self.questions: + q._with_coverage = True + q._report = self + + self.main() # Run all tests in class just to get that out of the way... + report_cache = {} + for q, _ in self.questions: + # print(self.questions) + if hasattr(q, '_save_cache'): + q()._save_cache() + print("q is", q()) + q()._cache_put('time', q.time) # = q.time + report_cache[q.__qualname__] = q._cache2 + else: + report_cache[q.__qualname__] = {'no cache see _setup_answers in unitgrade2.py': True} + if with_coverage: + for q, _ in self.questions: + q._with_coverage = False + return report_cache + + def set_payload(self, payloads, strict=False): + for q, _ in self.questions: + q._cache = payloads[q.__qualname__] + + +def rm_progress_bar(txt): + # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols. + nlines = [] + for l in txt.splitlines(): + pct = l.find("%") + ql = False + if pct > 0: + i = l.find("|", pct + 1) + if i > 0 and l.find("|", i + 1) > 0: + ql = True + if not ql: + nlines.append(l) + return "\n".join(nlines) + + +def extract_numbers(txt): + # txt = rm_progress_bar(txt) + numeric_const_pattern = r'[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?' + rx = re.compile(numeric_const_pattern, re.VERBOSE) + all = rx.findall(txt) + all = [float(a) if ('.' in a or "e" in a) else int(a) for a in all] + if len(all) > 500: + print(txt) + raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all)) + return all + + +class ActiveProgress(): + def __init__(self, t, start=True, title="my progress bar", show_progress_bar=True, file=None): + if file == None: + file = sys.stdout + self.file = file + self.t = t + self._running = False + self.title = title + self.dt = 0.01 + self.n = int(np.round(self.t / self.dt)) + self.show_progress_bar = show_progress_bar + self.pbar = None + + if start: + self.start() + + def start(self): + self._running = True + if self.show_progress_bar: + self.thread = threading.Thread(target=self.run) + self.thread.start() + self.time_started = time.time() + + def terminate(self): + if not self._running: + raise Exception("Stopping a stopped progress bar. ") + self._running = False + if self.show_progress_bar: + self.thread.join() + if self.pbar is not None: + self.pbar.update(1) + self.pbar.close() + self.pbar = None + + self.file.flush() + return time.time() - self.time_started + + def run(self): + self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100, + bar_format='{l_bar}{bar}| [{elapsed}<{remaining}]') + + for _ in range(self.n - 1): # Don't terminate completely; leave bar at 99% done until terminate. + if not self._running: + self.pbar.close() + self.pbar = None + break + + time.sleep(self.dt) + self.pbar.update(1) + +def dprint(first, last, nL, extra = "", file=None, dotsym='.', color='white'): + if file == None: + file = sys.stdout + + # ss = self.item_title_print + # state = "PASS" if success else "FAILED" + dot_parts = (dotsym * max(0, nL - len(last) - len(first))) + # if self.show_progress_bar or True: + print(first + dot_parts, end="", file=file) + # else: + # print(dot_parts, end="", file=self.cc.file) + last += extra + # if tsecs >= 0.5: + # state += " (" + str(tsecs) + " seconds)" + print(last, file=file) + + +class UTextResult(unittest.TextTestResult): + nL = 80 + number = -1 # HAcky way to set question number. + show_progress_bar = True + cc = None + + def __init__(self, stream, descriptions, verbosity): + super().__init__(stream, descriptions, verbosity) + self.successes = [] + + def printErrors(self) -> None: + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + def addError(self, test, err): + super(unittest.TextTestResult, self).addFailure(test, err) + self.cc_terminate(success=False) + + def addFailure(self, test, err): + super(unittest.TextTestResult, self).addFailure(test, err) + self.cc_terminate(success=False) + + def addSuccess(self, test: unittest.case.TestCase) -> None: + self.successes.append(test) + self.cc_terminate() + + def cc_terminate(self, success=True): + if self.show_progress_bar or True: + tsecs = np.round(self.cc.terminate(), 2) + self.cc.file.flush() + ss = self.item_title_print + + state = "PASS" if success else "FAILED" + + dot_parts = ('.' * max(0, self.nL - len(state) - len(ss))) + if self.show_progress_bar or True: + print(self.item_title_print + dot_parts, end="", file=self.cc.file) + else: + print(dot_parts, end="", file=self.cc.file) + + if tsecs >= 0.5: + state += " (" + str(tsecs) + " seconds)" + print(state, file=self.cc.file) + + def startTest(self, test): + # j =self.testsRun + self.testsRun += 1 + # item_title = self.getDescription(test) + item_title = test.shortDescription() # Better for printing (get from cache). + if item_title == None: + # For unittest framework where getDescription may return None. + item_title = self.getDescription(test) + self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title) + estimated_time = 10 + if self.show_progress_bar or True: + self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar, file=sys.stdout) + else: + print(self.item_title_print + ('.' * max(0, self.nL - 4 - len(self.item_title_print))), end="") + + self._test = test + self._stdout = sys.stdout + sys.stdout = io.StringIO() + + def stopTest(self, test): + sys.stdout = self._stdout + super().stopTest(test) + + def _setupStdout(self): + if self._previousTestClass == None: + total_estimated_time = 1 + if hasattr(self.__class__, 'q_title_print'): + q_title_print = self.__class__.q_title_print + else: + q_title_print = "<unnamed test. See unitgrade.py>" + + cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar) + self.cc = cc + + def _restoreStdout(self): # Used when setting up the test. + if self._previousTestClass is None: + q_time = self.cc.terminate() + q_time = np.round(q_time, 2) + sys.stdout.flush() + if self.show_progress_bar: + print(self.cc.title, end="") + print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else "")) + + +class UTextTestRunner(unittest.TextTestRunner): + def __init__(self, *args, **kwargs): + stream = io.StringIO() + super().__init__(*args, stream=stream, **kwargs) + + def _makeResult(self): + # stream = self.stream # not you! + stream = sys.stdout + stream = _WritelnDecorator(stream) + return self.resultclass(stream, self.descriptions, self.verbosity) + + +def cache(foo, typed=False): + """ Magic cache wrapper + https://github.com/python/cpython/blob/main/Lib/functools.py + """ + maxsize = None + def wrapper(self, *args, **kwargs): + key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed))) + if not self._cache_contains(key): + value = foo(self, *args, **kwargs) + self._cache_put(key, value) + else: + value = self._cache_get(key) + return value + + return wrapper + + +def get_hints(ss): + if ss == None: + return None + try: + ss = textwrap.dedent(ss) + ss = ss.replace('''"""''', "").strip() + hints = ["hints:", ] + j = np.argmax([ss.lower().find(h) for h in hints]) + h = hints[j] + ss = ss[ss.find(h) + len(h) + 1:] + ss = "\n".join([l for l in ss.split("\n") if not l.strip().startswith(":")]) + ss = textwrap.dedent(ss) + ss = ss.strip() + return ss + except Exception as e: + print("bad hints", ss, e) + + +class UTestCase(unittest.TestCase): + _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache. + _cache = None # Read-only cache. Ensures method always produce same result. + _cache2 = None # User-written cache. + _with_coverage = False + _report = None # The report used. This is very, very hacky and should always be None. Don't rely on it! + + def capture(self): + if hasattr(self, '_stdout') and self._stdout is not None: + file = self._stdout + else: + # self._stdout = sys.stdout + # sys._stdout = io.StringIO() + file = sys.stdout + return Capturing2(stdout=file) + + @classmethod + def question_title(cls): + """ Return the question title """ + return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__ + + @classmethod + def reset(cls): + print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.") + cls._outcome = None + cls._cache = None + cls._cache2 = None + + def _callSetUp(self): + if self._with_coverage: + if not hasattr(self._report, 'covcache'): + self._report.covcache = {} + import coverage + self.cov = coverage.Coverage() + self.cov.start() + self.setUp() + + def _callTearDown(self): + self.tearDown() + if self._with_coverage: + from pathlib import Path + from snipper import snipper + self.cov.stop() + data = self.cov.get_data() + base, _, _ = self._report._import_base_relative() + for file in data.measured_files(): + file = os.path.normpath(file) + root = Path(base) + child = Path(file) + if root in child.parents: + with open(child, 'r') as f: + s = f.read() + lines = s.splitlines() + garb = 'GARBAGE' + + lines2 = snipper.censor_code(lines, keep=True) + assert len(lines) == len(lines2) + + for l in data.contexts_by_lineno(file): + if lines2[l].strip() == garb: + if self.cache_id() not in self._report.covcache: + self._report.covcache[self.cache_id()] = {} + + rel = os.path.relpath(child, root) + cc = self._report.covcache[self.cache_id()] + j = 0 + for j in range(l, -1, -1): + if "def" in lines2[j] or "class" in lines2[j]: + break + from snipper.snipper import gcoms + fun = lines2[j] + comments, _ = gcoms("\n".join(lines2[j:l])) + if rel not in cc: + cc[rel] = {} + cc[rel][fun] = (l, "\n".join(comments)) + self._cache_put((self.cache_id(), 'coverage'), self._report.covcache) + + def shortDescriptionStandard(self): + sd = super().shortDescription() + if sd is None: + sd = self._testMethodName + return sd + + def shortDescription(self): + sd = self.shortDescriptionStandard() + title = self._cache_get((self.cache_id(), 'title'), sd) + return title if title is not None else sd + + @property + def title(self): + return self.shortDescription() + + @title.setter + def title(self, value): + self._cache_put((self.cache_id(), 'title'), value) + + def _get_outcome(self): + if not (self.__class__, '_outcome') or self.__class__._outcome is None: + self.__class__._outcome = {} + return self.__class__._outcome + + def _callTestMethod(self, testMethod): + t = time.time() + self._ensure_cache_exists() # Make sure cache is there. + if self._testMethodDoc is not None: + self._cache_put((self.cache_id(), 'title'), self.shortDescriptionStandard()) + + self._cache2[(self.cache_id(), 'assert')] = {} + res = testMethod() + elapsed = time.time() - t + self._get_outcome()[self.cache_id()] = res + self._cache_put((self.cache_id(), "time"), elapsed) + + def cache_id(self): + c = self.__class__.__qualname__ + m = self._testMethodName + return c, m + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._load_cache() + self._assert_cache_index = 0 + + def _ensure_cache_exists(self): + if not hasattr(self.__class__, '_cache') or self.__class__._cache == None: + self.__class__._cache = dict() + if not hasattr(self.__class__, '_cache2') or self.__class__._cache2 == None: + self.__class__._cache2 = dict() + + def _cache_get(self, key, default=None): + self._ensure_cache_exists() + return self.__class__._cache.get(key, default) + + def _cache_put(self, key, value): + self._ensure_cache_exists() + self.__class__._cache2[key] = value + + def _cache_contains(self, key): + self._ensure_cache_exists() + return key in self.__class__._cache + + def wrap_assert(self, assert_fun, first, *args, **kwargs): + # sys.stdout = self._stdout + key = (self.cache_id(), 'assert') + if not self._cache_contains(key): + print("Warning, framework missing", key) + self.__class__._cache[ + key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable. + cache = self._cache_get(key) + id = self._assert_cache_index + if not id in cache: + print("Warning, framework missing cache index", key, "id =", id) + _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()") + + # The order of these calls is important. If the method assert fails, we should still store the correct result in cache. + cache[id] = first + self._cache_put(key, cache) + self._assert_cache_index += 1 + assert_fun(first, _expected, *args, **kwargs) + + def assertEqualC(self, first: Any, msg: Any = ...) -> None: + self.wrap_assert(self.assertEqual, first, msg) + + def _cache_file(self): + return os.path.dirname(inspect.getfile(self.__class__)) + "/unitgrade/" + self.__class__.__name__ + ".pkl" + + def _save_cache(self): + # get the class name (i.e. what to save to). + cfile = self._cache_file() + if not os.path.isdir(os.path.dirname(cfile)): + os.makedirs(os.path.dirname(cfile)) + + if hasattr(self.__class__, '_cache2'): + with open(cfile, 'wb') as f: + pickle.dump(self.__class__._cache2, f) + + # But you can also set cache explicitly. + def _load_cache(self): + if self._cache is not None: # Cache already loaded. We will not load it twice. + return + # raise Exception("Loaded cache which was already set. What is going on?!") + cfile = self._cache_file() + if os.path.exists(cfile): + try: + with open(cfile, 'rb') as f: + data = pickle.load(f) + self.__class__._cache = data + except Exception as e: + print("Bad cache", cfile) + print(e) + else: + print("Warning! data file not found", cfile) + + def _feedErrorsToResult(self, result, errors): + """ Use this to show hints on test failure. """ + if not isinstance(result, UTextResult): + er = [e for e, v in errors if v != None] + + if len(er) > 0: + hints = [] + key = (self.cache_id(), 'coverage') + if self._cache_contains(key): + CC = self._cache_get(key) + for id in CC: + if id == self.cache_id(): + cl, m = id + gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:") + for file in CC[id]: + rec = CC[id][file] + gprint(f"> * {file}") + for l in rec: + _, comments = CC[id][file][l] + hint = get_hints(comments) + + if hint != None: + hints.append(hint) + gprint(f"> - {l}") + + er = er[0] + doc = er._testMethodDoc + if doc is not None: + hint = get_hints(er._testMethodDoc) + if hint is not None: + hints = [hint] + hints + if len(hints) > 0: + gprint("> Hints:") + gprint(textwrap.indent("\n".join(hints), "> ")) + + super()._feedErrorsToResult(result, errors) + + def startTestRun(self): + # print("asdfsdaf 11", file=sys.stderr) + super().startTestRun() + # print("asdfsdaf") + + def _callTestMethod(self, method): + # print("asdfsdaf") + super()._callTestMethod(method) + + +def hide(func): + return func + + +def makeRegisteringDecorator(foreignDecorator): + """ + Returns a copy of foreignDecorator, which is identical in every + way(*), except also appends a .decorator property to the callable it + spits out. + """ + + def newDecorator(func): + # Call to newDecorator(method) + # Exactly like old decorator, but output keeps track of what decorated it + R = foreignDecorator(func) # apply foreignDecorator, like call to foreignDecorator(method) would have done + R.decorator = newDecorator # keep track of decorator + # R.original = func # might as well keep track of everything! + return R + + newDecorator.__name__ = foreignDecorator.__name__ + newDecorator.__doc__ = foreignDecorator.__doc__ + return newDecorator + +hide = makeRegisteringDecorator(hide) + +def methodsWithDecorator(cls, decorator): + """ + Returns all methods in CLS with DECORATOR as the + outermost decorator. + + DECORATOR must be a "registering decorator"; one + can make any decorator "registering" via the + makeRegisteringDecorator function. + + import inspect + ls = list(methodsWithDecorator(GeneratorQuestion, deco)) + for f in ls: + print(inspect.getsourcelines(f) ) # How to get all hidden questions. + """ + for maybeDecorated in cls.__dict__.values(): + if hasattr(maybeDecorated, 'decorator'): + if maybeDecorated.decorator == decorator: + print(maybeDecorated) + yield maybeDecorated +# 817 diff --git a/unitgrade2/unitgrade_helpers2.py b/src/unitgrade2/unitgrade_helpers2.py similarity index 84% rename from unitgrade2/unitgrade_helpers2.py rename to src/unitgrade2/unitgrade_helpers2.py index 8421562..d007fd2 100644 --- a/unitgrade2/unitgrade_helpers2.py +++ b/src/unitgrade2/unitgrade_helpers2.py @@ -2,19 +2,13 @@ import numpy as np from tabulate import tabulate from datetime import datetime import pyfiglet -from unitgrade2 import Hidden, myround, msum, mfloor, ActiveProgress -from unitgrade2 import __version__ +from unitgrade2 import msum import unittest -# from unitgrade2.unitgrade2 import MySuite from unitgrade2.unitgrade2 import UTextResult - import inspect import os import argparse -import sys import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: @@ -109,31 +103,27 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa show_tol_err=False, big_header=True): - from unitgrade2.version import __version__ + from src.unitgrade2.version import __version__ now = datetime.now() if big_header: ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) else: b = "Unitgrade" - print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) s = report.title if hasattr(report, "version") and report.version is not None: s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + print(s, "(use --help for options)" if show_help_flag else "") # print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] - nL = 80 t_start = time.time() score = {} loader = SequentialTestLoader() for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) if question is not None and n+1 != question: continue suite = loader.loadTestsFromTestCase(q) @@ -143,12 +133,11 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa q.possible = 0 q.obtained = 0 q_ = {} # Gather score in this class. - from unitgrade2.unitgrade2 import UTextTestRunner - # unittest.Te - # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_] + from src.unitgrade2.unitgrade2 import UTextTestRunner UTextResult.q_title_print = q_title_print # Hacky UTextResult.show_progress_bar = show_progress_bar # Hacky. UTextResult.number = n + UTextResult.nL = report.nL res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) @@ -157,20 +146,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun - # possible = int(ws @ possible) - # obtained = int(ws @ obtained) - # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0 - obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} q.obtained = obtained q.possible = possible - s1 = f"*** Question q{n+1}" + s1 = f" * q{n+1}) Total" s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) ws, possible, obtained = upack(score) possible = int( msum(possible) ) @@ -185,10 +170,12 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa seconds = dt - minutes*60 plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + from src.unitgrade2.unitgrade2 import dprint + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) results = {'total': (obtained, possible), 'details': score} return results, table_data - - diff --git a/src/unitgrade2/version.py b/src/unitgrade2/version.py new file mode 100644 index 0000000..a0235ce --- /dev/null +++ b/src/unitgrade2/version.py @@ -0,0 +1 @@ +__version__ = "0.0.2" \ No newline at end of file diff --git a/unitgrade.egg-info/PKG-INFO b/unitgrade.egg-info/PKG-INFO deleted file mode 100644 index cc1c911..0000000 --- a/unitgrade.egg-info/PKG-INFO +++ /dev/null @@ -1,10 +0,0 @@ -Metadata-Version: 1.0 -Name: unitgrade -Version: 0.0.5 -Summary: A lightweight student evaluation framework build on unittest -Home-page: https://lab.compute.dtu.dk/tuhe/unitgrade -Author: Tue Herlau -Author-email: tuhe@dtu.dk -License: Apache -Description: UNKNOWN -Platform: UNKNOWN diff --git a/unitgrade.egg-info/SOURCES.txt b/unitgrade.egg-info/SOURCES.txt deleted file mode 100644 index 9026e79..0000000 --- a/unitgrade.egg-info/SOURCES.txt +++ /dev/null @@ -1,21 +0,0 @@ -MANIFEST.in -README.md -setup.py -cs101courseware_example/Report0_resources_do_not_hand_in.dat -cs101courseware_example/Report1_resources_do_not_hand_in.dat -cs101courseware_example/Report2_resources_do_not_hand_in.dat -cs101courseware_example/__init__.py -cs101courseware_example/cs101report1.py -cs101courseware_example/cs101report1_grade.py -cs101courseware_example/cs101report2.py -cs101courseware_example/cs101report2_grade.py -cs101courseware_example/homework1.py -cs101courseware_example/instructions.py -unitgrade/__init__.py -unitgrade/unitgrade.py -unitgrade/unitgrade_helpers.py -unitgrade.egg-info/PKG-INFO -unitgrade.egg-info/SOURCES.txt -unitgrade.egg-info/dependency_links.txt -unitgrade.egg-info/requires.txt -unitgrade.egg-info/top_level.txt \ No newline at end of file diff --git a/unitgrade.egg-info/dependency_links.txt b/unitgrade.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/unitgrade.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/unitgrade.egg-info/requires.txt b/unitgrade.egg-info/requires.txt deleted file mode 100644 index b8b3f85..0000000 --- a/unitgrade.egg-info/requires.txt +++ /dev/null @@ -1,4 +0,0 @@ -jinja2 -tabulate -sklearn -compress_pickle diff --git a/unitgrade.egg-info/top_level.txt b/unitgrade.egg-info/top_level.txt deleted file mode 100644 index 5f808ad..0000000 --- a/unitgrade.egg-info/top_level.txt +++ /dev/null @@ -1,2 +0,0 @@ -cs101courseware_example -unitgrade diff --git a/unitgrade/Report_resources_do_not_hand_in.dat b/unitgrade/Report_resources_do_not_hand_in.dat deleted file mode 100644 index 9c15fa98decdca1da9d38c1989086f8c4c1ce960..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64 zcmexsUKJ6=z`*kC+7>q^21Q0O1_p)_{ill=8CV)vYp3V|xeT8qo;6HpUYNnaD9gG- Rw_&~fw$HVUAPJVpC;)i%6A1tS diff --git a/unitgrade/version.py b/unitgrade/version.py index a23ef3f..a68927d 100644 --- a/unitgrade/version.py +++ b/unitgrade/version.py @@ -1 +1 @@ -__version__ = "0.1.8" \ No newline at end of file +__version__ = "0.1.0" \ No newline at end of file diff --git a/unitgrade2/__pycache__/__init__.cpython-38.pyc b/unitgrade2/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index ac7b672193da207e651f61b4bee3e1791e27a656..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1373 zcmZ9LUyIx}5Wpq*@BX<Y4NY1~d-Wv{a0&ZxPzWKEhD)J+IojmHUCn~=TDm>&S+*m~ z+3e!WLof89koGIw(WiVRz4obJp--Lh?)K8iXfzs2ni<Vc&$qVv1lqmnPO@|e`3ooS zHwz};La(2K;DpnhNE%U!I?Gwa_<-Boc|#+MyWD$2BAffX2crWk`nfB;$di8L%U;xz z{irVo(LfHPA)LUTk#P7ooJ&G#266mSs5&c)INm=<(eI|Yp!A^cL9YuCoN|(rGfUGM zyRce%Nzd#~w_E#?)?aCQ;h;uI%hUtya@NvwMv3-X%B^$u$L%wxwPt=xS_h+lYf*AY zz9v7Br{oCt%wUgu-en0fdi#ok*X4~vmFcXQ25FL>h|y?d*nDE_d?}N6kU(0>N{Jd% zSUOM2LhOV+bA4ltt1Laug<)lF>?+YG#^aeP5-E%;US+kejU!LNWSmN6g*J9siNe^t zkyYp#x17vGs!i`W%SGI6^WeTFWi6Bk!5PL2LyzAPbqi+yJlK0Ueh$Ctv2ISp_(_>I zQWUx#H$|qWD&gWv+a1So2Ie>(RSQT1lZ)q&s=$k{KL$alPlxdJsZIJYeg>oZ1nenZ z%%-7^b?fRh1Vxh>y<jc5q;QLLKeUt&R)w`x0}vrqAEAyRuf%Q8%y1RL>Q-<;%|H;Q zJ_Y$dk2V;P-_GOjpsdNDYcjyI`gOUh?G|W*x^JHW2{Wf<+~W3Ic1YN|>VHE0pRMbE zNM8JA6YZY%)h`q%qhz_QJ?^w*#n6fjV97#4kQ)xGURy`xXV`f}3Y)`O*f-=DrX~tw zRq({f^3H+E1P^o><YmHxB$$9);SpeYgG4Xo566K%5$kzXiA2@7V*-C0UCKmfX_DuQ zU@8is5-s@sU{N-JW)Wz$z@=Q|LW>~N_k$9SQuC}90Wasp+G`=^!TQOCeh1M9<x$rl zTHqUkG`;U$rJ~Znw%P(~Xs;@V=qV(`Q1M|I`>Qf4k#H>Q1|&A53=q0UZvaj{{oGPt z;DLbn_ZjDayeAi`Yzl5%d7PJ}GPbN6X?lA}r5kt}Q*-Tcs<W5kM^#So!G-i7+=_1g zhxYDhot8pwR!JeO9Y38nbL+juSE|~^RbQeAQ0$<%gW@iVD@;4BW7U5oOWx$-8-%|` O7KhZg`vc$cum1~XJ4#Xj diff --git a/unitgrade2/__pycache__/__init__.cpython-39.pyc b/unitgrade2/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index e61f9803216fe17bf58c02cb47dd2fafcfca4760..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1400 zcmZ9LJCEEp5XVXCxsS6icAO?J*+l>a#_r_+11SO{fgK}h4ncgu!fFJq$lKLglw^wX zdKWquIk!eqq;QequJl)8n<`%+Rc55U_+kqDYdE52NY0PR-rkTvyYnbaesKu-3kO%5 z1%q#)*FS@B!f8$<jVMK%<t$=+#BJ`pqLIa2?!6+B&3!(Aw*xbVxhuWMlYZpOK{SxV zXedX~NRFd1L|~6eIQ|>rl8~BF96uMT&dMT=kB(E!d)ZtteCQ9L*S~^r%1KU6ElpSK z+-m6sJ+(XAZtV+Nf2rxYgBB$%Q(tSBvzDGQO0?HfZk@5;f>WopR(?xb2Uq{vqGSd; zvf()cyV(DV)XM`xyXOfg#CZvE+_P@yux{^2QCP`#f2J~>6^kHE(xsS8CWfu&#?H4g zc}t^FTFOd^8n?A|9+ri;9S+R(OK)6d>C;>oR@TO@61_AY&s32}VO;Sdt95N0`4n8n zsZ>^IW0#dEjLjQag|2bS`AVeP3{J9K#NBTmoZ-Bzg~Bs~;uMCSyd~-;jQ+VdU6xWz zb+Z)Hhh^GGQRsTw6q#PAgo}69;WUmjaL4ha+L%$dvv>{r6d1MM2O-p_V|a%28t8<o zTi{FaL@pcJSj4VqV=y#X(R0?43kt_ak3vg%;8j>yg$#yNVdQSDy?h8mGw##($MFGp zY6e1>`Uv!Y8EtT2pf2N|VC*oVJ4_(6`eS=v+by8iK>%2+oR)Em+ppP-uqz16$YTN! zJh}?Oj68c|6YXw(rvM)%+b^`ootE@y^>6@U7D6&3PaxW<_0oFwrhgxh15(%=;$pu9 z$S^fmxN-%zk8JN8t4#1fmqA`8JV=5$XirIiRSpupl|P;YdMS3}suGE+@sBxtZFDIU zoux^hZ-Rv=gi5sFcY{sY0HH;o)dr_>kqa$?Oy3Pk2&L9pEdpMyi(S@2tb^Tc4E>I! z_ll+VV6}ibEYb|Vf02qx1L6wn9NK*ykrj0Vr%~~KQHOm|l}I?2vBpXRQfy2a;B*L3 z4FRygQXhjKN&g|^9LV?NMwLy$jVn*`vQ);FbtBE-K~m`kZpgx1`!3blbMccZ7kKYN zdK~UWH~xeCaI)K$LW=rLA-Ww+T{d&;%H(&Z`UGcvit-uC=O_WnZ4|s<SEM7?|3;R) U$;ExV3N?~BroKHK`Hp}6UpOyG&Hw-a diff --git a/unitgrade2/__pycache__/unitgrade2.cpython-38.pyc b/unitgrade2/__pycache__/unitgrade2.cpython-38.pyc deleted file mode 100644 index 3492c9eca22889350f2a5707f0579dc18533a9c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29204 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 diff --git a/unitgrade2/__pycache__/unitgrade_helpers2.cpython-38.pyc b/unitgrade2/__pycache__/unitgrade_helpers2.cpython-38.pyc deleted file mode 100644 index efd8ce6f58cc6a9f0d3cd6104d4cbe6e9982131d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6959 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 diff --git a/unitgrade2/__pycache__/version.cpython-38.pyc b/unitgrade2/__pycache__/version.cpython-38.pyc deleted file mode 100644 index 1e804a3c2f2261406205db1c73302a4acb70b698..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmWIL<>g`kf~^UPiK0OIF^Gc<7=auIATH(r5-AK(3@MDk44O<;tOk0PdIo-)jJLSs z<I7TuiZk=`;^S8`6fptSfr(${&Q>v@#XzZ;lG2RS7?=Fy(%jU%lH!=syv&mHqQsO` d1j{G}YKC4x<t+}I-29Z%oK!oI<)49=0RZYhDn$SQ diff --git a/unitgrade2/__pycache__/version.cpython-39.pyc b/unitgrade2/__pycache__/version.cpython-39.pyc deleted file mode 100644 index 613bef2848d43d5e78803c81d3d366c27a5e2c62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmYe~<>g`k0wp)iL{T997{oyaj6jY95EpX*i4=w?h7`tN22G|aRs%gtJp(^Y##`L+ z@nxw+#hLke@$oAeikN`vz{D>T{fzwFRQ;0Dj8uJ>{N&Qy)Vz{n{nEV5lJug)lvD)E bNFQo|UP0w84x8Nkl+v73JCMzvftUdR<gh2T diff --git a/unitgrade2/unitgrade/ListQuestion.pkl b/unitgrade2/unitgrade/ListQuestion.pkl deleted file mode 100644 index b201d4b453d94b66ed44ac821d72534a90b09811..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466 zcmZo*nR=X&0Ss!VX!Mx*WEPhMmZlb$Waj7ThK2wc&WXjTx^PB*QdVkmiB$<uB0e*( zBsD#?sMyMIO6(L3Z)R^MZ^l$IjW7Zk(Zi8goRU_Yn36W7hsnrzN@@=evU{fV@S@l| z#halW$>t393=VHbun2}hAi1_FARn@pWR~QlPU#T<8KnU8f}Wn<lpdCn%-qx|?)D2) z7k~n!G=sf|2W(++Nl|8Adhryn2^qrfAXyN|5bV*d0D}ljpn&35-QtY=qLL{fGljtt Wx%ow@un;W5YP3-@UVz>FQau2t#ipwO diff --git a/unitgrade2/unitgrade2.py b/unitgrade2/unitgrade2.py deleted file mode 100644 index c094340..0000000 --- a/unitgrade2/unitgrade2.py +++ /dev/null @@ -1,891 +0,0 @@ -""" -git add . && git commit -m "Options" && git push && pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade - -""" -# from . import cache_read -import unittest -import numpy as np -import sys -from io import StringIO -import collections -import re -import threading -import tqdm -import time -import pickle -import itertools -import os - -myround = lambda x: np.round(x) # required. -msum = lambda x: sum(x) -mfloor = lambda x: np.floor(x) - -def setup_dir_by_class(C,base_dir): - name = C.__class__.__name__ - # base_dir = os.path.join(base_dir, name) - # if not os.path.isdir(base_dir): - # os.makedirs(base_dir) - return base_dir, name - -class Hidden: - def hide(self): - return True - -class Logger(object): - def __init__(self, buffer): - self.terminal = sys.stdout - self.log = buffer - - def write(self, message): - self.terminal.write(message) - self.log.write(message) - - def flush(self): - # this flush method is needed for python 3 compatibility. - pass - -class Capturing(list): - def __init__(self, *args, stdout=None, unmute=False, **kwargs): - self._stdout = stdout - self.unmute = unmute - super().__init__(*args, **kwargs) - - def __enter__(self, capture_errors=True): # don't put arguments here. - self._stdout = sys.stdout if self._stdout == None else self._stdout - self._stringio = StringIO() - if self.unmute: - sys.stdout = Logger(self._stringio) - else: - sys.stdout = self._stringio - - if capture_errors: - self._sterr = sys.stderr - sys.sterr = StringIO() # memory hole it - self.capture_errors = capture_errors - return self - - def __exit__(self, *args): - self.extend(self._stringio.getvalue().splitlines()) - del self._stringio # free up some memory - sys.stdout = self._stdout - if self.capture_errors: - sys.sterr = self._sterr - -class Capturing2(Capturing): - def __exit__(self, *args): - lines = self._stringio.getvalue().splitlines() - txt = "\n".join(lines) - numbers = extract_numbers(txt) - self.extend(lines) - del self._stringio # free up some memory - sys.stdout = self._stdout - if self.capture_errors: - sys.sterr = self._sterr - - self.output = txt - self.numbers = numbers - - -class QItem(unittest.TestCase): - title = None - testfun = None - tol = 0 - estimated_time = 0.42 - _precomputed_payload = None - _computed_answer = None # Internal helper to later get results. - weight = 1 # the weight of the question. - - def __init__(self, question=None, *args, **kwargs): - if self.tol > 0 and self.testfun is None: - self.testfun = self.assertL2Relative - elif self.testfun is None: - self.testfun = self.assertEqual - - self.name = self.__class__.__name__ - # self._correct_answer_payload = correct_answer_payload - self.question = question - - super().__init__(*args, **kwargs) - if self.title is None: - self.title = self.name - - def _safe_get_title(self): - if self._precomputed_title is not None: - return self._precomputed_title - return self.title - - def assertNorm(self, computed, expected, tol=None): - if tol == None: - tol = self.tol - diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat ) - nrm = np.sqrt(np.sum( diff ** 2)) - - self.error_computed = nrm - - if nrm > tol: - print(f"Not equal within tolerance {tol}; norm of difference was {nrm}") - print(f"Element-wise differences {diff.tolist()}") - self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}") - - def assertL2(self, computed, expected, tol=None): - if tol == None: - tol = self.tol - diff = np.abs( (np.asarray(computed) - np.asarray(expected)) ) - self.error_computed = np.max(diff) - - if np.max(diff) > tol: - print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}") - print(f"Element-wise differences {diff.tolist()}") - self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}") - - def assertL2Relative(self, computed, expected, tol=None): - if tol == None: - tol = self.tol - diff = np.abs( (np.asarray(computed) - np.asarray(expected)) ) - diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) ) - self.error_computed = np.max(np.abs(diff)) - if np.sum(diff > tol) > 0: - print(f"Not equal within tolerance {tol}") - print(f"Element-wise differences {diff.tolist()}") - self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}") - - def precomputed_payload(self): - return self._precomputed_payload - - def precompute_payload(self): - # Pre-compute resources to include in tests (useful for getting around rng). - pass - - def compute_answer(self, unmute=False): - raise NotImplementedError("test code here") - - def test(self, computed, expected): - self.testfun(computed, expected) - - def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs): - possible = 1 - computed = None - def show_computed_(computed): - print(">>> Your output:") - print(computed) - - def show_expected_(expected): - print(">>> Expected output (note: may have been processed; read text script):") - print(expected) - - correct = self._correct_answer_payload - try: - if unmute: # Required to not mix together print stuff. - print("") - computed = self.compute_answer(unmute=unmute) - except Exception as e: - if not passall: - if not silent: - print("\n=================================================================================") - print(f"When trying to run test class '{self.name}' your code threw an error:", e) - show_expected_(correct) - import traceback - print(traceback.format_exc()) - print("=================================================================================") - return (0, possible) - - if self._computed_answer is None: - self._computed_answer = computed - - if show_expected or show_computed: - print("\n") - if show_expected: - show_expected_(correct) - if show_computed: - show_computed_(computed) - try: - if not passall: - self.test(computed=computed, expected=correct) - except Exception as e: - if not silent: - print("\n=================================================================================") - print(f"Test output from test class '{self.name}' does not match expected result. Test error:") - print(e) - show_computed_(computed) - show_expected_(correct) - return (0, possible) - return (1, possible) - - def score(self): - try: - self.test() - except Exception as e: - return 0 - return 1 - -class QPrintItem(QItem): - def compute_answer_print(self): - """ - Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values - are send to process_output (see compute_answer below). In other words, the text generated is: - - res = compute_Answer_print() - txt = (any terminal output generated above) - numbers = (any numbers found in terminal-output txt) - - self.test(process_output(res, txt, numbers), <expected result>) - - :return: Optional values for comparison - """ - raise Exception("Generate output here. The output is passed to self.process_output") - - def process_output(self, res, txt, numbers): - return res - - def compute_answer(self, unmute=False): - with Capturing(unmute=unmute) as output: - res = self.compute_answer_print() - s = "\n".join(output) - s = rm_progress_bar(s) # Remove progress bar. - numbers = extract_numbers(s) - self._computed_answer = (res, s, numbers) - return self.process_output(res, s, numbers) - -class OrderedClassMembers(type): - @classmethod - def __prepare__(self, name, bases): - return collections.OrderedDict() - def __new__(self, name, bases, classdict): - ks = list(classdict.keys()) - for b in bases: - ks += b.__ordered__ - classdict['__ordered__'] = [key for key in ks if key not in ('__module__', '__qualname__')] - return type.__new__(self, name, bases, classdict) - -class QuestionGroup(metaclass=OrderedClassMembers): - title = "Untitled question" - partially_scored = False - t_init = 0 # Time spend on initialization (placeholder; set this externally). - estimated_time = 0.42 - has_called_init_ = False - _name = None - _items = None - - @property - def items(self): - if self._items == None: - self._items = [] - members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__ if gt not in ["__classcell__", "__init__"]] if inspect.isclass(gt) and issubclass(gt, QItem)] - for I in members: - self._items.append( I(question=self)) - return self._items - - @items.setter - def items(self, value): - self._items = value - - @property - def name(self): - if self._name == None: - self._name = self.__class__.__name__ - return self._name # - - @name.setter - def name(self, val): - self._name = val - - def init(self): - # Can be used to set resources relevant for this question instance. - pass - - def init_all_item_questions(self): - for item in self.items: - if not item.question.has_called_init_: - item.question.init() - item.question.has_called_init_ = True - - -class Report(): - title = "report title" - version = None - questions = [] - pack_imports = [] - individual_imports = [] - nL = 80 # Maximum line width - - @classmethod - def reset(cls): - for (q,_) in cls.questions: - if hasattr(q, 'reset'): - q.reset() - - @classmethod - def mfile(clc): - return inspect.getfile(clc) - - def _file(self): - return inspect.getfile(type(self)) - - def _import_base_relative(self): - root_dir = self.pack_imports[0].__path__._path[0] - root_dir = os.path.dirname(root_dir) - relative_path = os.path.relpath(self._file(), root_dir) - modules = os.path.normpath(relative_path[:-3]).split(os.sep) - return root_dir, relative_path, modules - - def __init__(self, strict=False, payload=None): - working_directory = os.path.abspath(os.path.dirname(self._file())) - - self.wdir, self.name = setup_dir_by_class(self, working_directory) - # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat") - for (q,_) in self.questions: - q.nL = self.nL # Set maximum line length. - - if payload is not None: - self.set_payload(payload, strict=strict) - # else: - # if os.path.isfile(self.computed_answers_file): - # self.set_payload(cache_read(self.computed_answers_file), strict=strict) - # else: - # s = f"> Warning: The pre-computed answer file, {os.path.abspath(self.computed_answers_file)} is missing. The framework will NOT work as intended. Reasons may be a broken local installation." - # if strict: - # raise Exception(s) - # else: - # print(s) - - def main(self, verbosity=1): - # Run all tests using standard unittest (nothing fancy). - import unittest - loader = unittest.TestLoader() - for q,_ in self.questions: - import time - start = time.time() # A good proxy for setup time is to - suite = loader.loadTestsFromTestCase(q) - unittest.TextTestRunner(verbosity=verbosity).run(suite) - total = time.time() - start - q.time = total - - def _setup_answers(self): - self.main() # Run all tests in class just to get that out of the way... - report_cache = {} - for q, _ in self.questions: - if hasattr(q, '_save_cache'): - q()._save_cache() - q._cache['time'] = q.time - report_cache[q.__qualname__] = q._cache - else: - report_cache[q.__qualname__] = {'no cache see _setup_answers in unitgrade2.py':True} - return report_cache - - def set_payload(self, payloads, strict=False): - for q, _ in self.questions: - q._cache = payloads[q.__qualname__] - -def rm_progress_bar(txt): - # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols. - nlines = [] - for l in txt.splitlines(): - pct = l.find("%") - ql = False - if pct > 0: - i = l.find("|", pct+1) - if i > 0 and l.find("|", i+1) > 0: - ql = True - if not ql: - nlines.append(l) - return "\n".join(nlines) - -def extract_numbers(txt): - # txt = rm_progress_bar(txt) - numeric_const_pattern = '[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?' - rx = re.compile(numeric_const_pattern, re.VERBOSE) - all = rx.findall(txt) - all = [float(a) if ('.' in a or "e" in a) else int(a) for a in all] - if len(all) > 500: - print(txt) - raise Exception("unitgrade.unitgrade.py: Warning, too many numbers!", len(all)) - return all - -class ActiveProgress(): - def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True): - self.t = t - self._running = False - self.title = title - self.dt = 0.1 - self.n = int(np.round(self.t / self.dt)) - self.show_progress_bar = show_progress_bar - - # self.pbar = tqdm.tqdm(total=self.n) - if start: - self.start() - - def start(self): - self._running = True - if self.show_progress_bar: - self.thread = threading.Thread(target=self.run) - self.thread.start() - self.time_started = time.time() - - def terminate(self): - if not self._running: - raise Exception("Stopping a stopped progress bar. ") - self._running = False - if self.show_progress_bar: - self.thread.join() - if hasattr(self, 'pbar') and self.pbar is not None: - self.pbar.update(1) - self.pbar.close() - self.pbar=None - - sys.stdout.flush() - return time.time() - self.time_started - - def run(self): - self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100, - bar_format='{l_bar}{bar}| [{elapsed}<{remaining}]') # , unit_scale=dt, unit='seconds'): - - for _ in range(self.n-1): # Don't terminate completely; leave bar at 99% done until terminate. - if not self._running: - self.pbar.close() - self.pbar = None - break - - time.sleep(self.dt) - self.pbar.update(1) - - - -from unittest.suite import _isnotsuite - -# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore. -# raise Exception("no suite") -# pass - -def instance_call_stack(instance): - s = "-".join(map(lambda x: x.__name__, instance.__class__.mro())) - return s - -def get_class_that_defined_method(meth): - for cls in inspect.getmro(meth.im_class): - if meth.__name__ in cls.__dict__: - return cls - return None - -def caller_name(skip=2): - """Get a name of a caller in the format module.class.method - - `skip` specifies how many levels of stack to skip while getting caller - name. skip=1 means "who calls me", skip=2 "who calls my caller" etc. - - An empty string is returned if skipped levels exceed stack height - """ - stack = inspect.stack() - start = 0 + skip - if len(stack) < start + 1: - return '' - parentframe = stack[start][0] - - name = [] - module = inspect.getmodule(parentframe) - # `modname` can be None when frame is executed directly in console - # TODO(techtonik): consider using __main__ - if module: - name.append(module.__name__) - # detect classname - if 'self' in parentframe.f_locals: - # I don't know any way to detect call from the object method - # XXX: there seems to be no way to detect static method call - it will - # be just a function call - name.append(parentframe.f_locals['self'].__class__.__name__) - codename = parentframe.f_code.co_name - if codename != '<module>': # top level usually - name.append( codename ) # function or a method - - ## Avoid circular refs and frame leaks - # https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack - del parentframe, stack - - return ".".join(name) - -def get_class_from_frame(fr): - import inspect - args, _, _, value_dict = inspect.getargvalues(fr) - # we check the first parameter for the frame function is - # named 'self' - if len(args) and args[0] == 'self': - # in that case, 'self' will be referenced in value_dict - instance = value_dict.get('self', None) - if instance: - # return its class - # isinstance(instance, Testing) # is the actual class instance. - - return getattr(instance, '__class__', None) - # return None otherwise - return None - -from typing import Any -import inspect, gc - -def giveupthefunc(): - frame = inspect.currentframe() - code = frame.f_code - globs = frame.f_globals - functype = type(lambda: 0) - funcs = [] - for func in gc.get_referrers(code): - if type(func) is functype: - if getattr(func, "__code__", None) is code: - if getattr(func, "__globals__", None) is globs: - funcs.append(func) - if len(funcs) > 1: - return None - return funcs[0] if funcs else None - - -from collections import defaultdict - -class UTextResult(unittest.TextTestResult): - nL = 80 - number = -1 # HAcky way to set question number. - show_progress_bar = True - def __init__(self, stream, descriptions, verbosity): - super().__init__(stream, descriptions, verbosity) - self.successes = [] - - def printErrors(self) -> None: - # if self.dots or self.showAll: - # self.stream.writeln() - # if hasattr(self, 'cc'): - # self.cc.terminate() - # self.cc_terminate(success=False) - self.printErrorList('ERROR', self.errors) - self.printErrorList('FAIL', self.failures) - - def addError(self, test, err): - super(unittest.TextTestResult, self).addFailure(test, err) - self.cc_terminate(success=False) - - def addFailure(self, test, err): - super(unittest.TextTestResult, self).addFailure(test, err) - self.cc_terminate(success=False) - # if self.showAll: - # self.stream.writeln("FAIL") - # elif self.dots: - # self.stream.write('F') - # self.stream.flush() - - def addSuccess(self, test: unittest.case.TestCase) -> None: - # super().addSuccess(test) - self.successes.append(test) - # super().addSuccess(test) - # hidden = issubclass(item.__class__, Hidden) - # # if not hidden: - # # print(ss, end="") - # # sys.stdout.flush() - # start = time.time() - # - # (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent) - # q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title} - # tsecs = np.round(time.time()-start, 2) - self.cc_terminate() - - - - def cc_terminate(self, success=True): - if self.show_progress_bar or True: - tsecs = np.round(self.cc.terminate(), 2) - sys.stdout.flush() - ss = self.item_title_print - print(self.item_title_print + ('.' * max(0, self.nL - 4 - len(ss))), end="") - # current = 1 - # possible = 1 - # current == possible - ss = "PASS" if success else "FAILED" - if tsecs >= 0.1: - ss += " (" + str(tsecs) + " seconds)" - print(ss) - - - def startTest(self, test): - # super().startTest(test) - j =self.testsRun - self.testsRun += 1 - # print("Starting the test...") - # show_progress_bar = True - n = UTextResult.number - - item_title = self.getDescription(test) - # item_title = item_title.split("\n")[0] - item_title = test.shortDescription() # Better for printing (get from cache). - if item_title == None: - # For unittest framework where getDescription may return None. - item_title = self.getDescription(test) - # test.countTestCases() - self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title) - estimated_time = 10 - nL = 80 - # - if self.show_progress_bar or True: - 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, nL - 4 - len(self.item_title_print))), end="") - - self._test = test - - def _setupStdout(self): - if self._previousTestClass == None: - total_estimated_time = 1 - if hasattr(self.__class__, 'q_title_print'): - q_title_print = self.__class__.q_title_print - else: - q_title_print = "<unnamed test. See unitgrade.py>" - - # q_title_print = "some printed title..." - cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar) - self.cc = cc - - def _restoreStdout(self): # Used when setting up the test. - if self._previousTestClass == None: - q_time = self.cc.terminate() - q_time = np.round(q_time, 2) - sys.stdout.flush() - print(self.cc.title, end="") - # start = 10 - # q_time = np.round(time.time() - start, 2) - nL = 80 - print(" " * max(0, nL - len(self.cc.title)) + ( - " (" + str(q_time) + " seconds)" if q_time >= 0.1 else "")) # if q.name in report.payloads else "") - # print("=" * nL) - -from unittest.runner import _WritelnDecorator -from io import StringIO - -class UTextTestRunner(unittest.TextTestRunner): - def __init__(self, *args, **kwargs): - from io import StringIO - stream = StringIO() - super().__init__(*args, stream=stream, **kwargs) - - def _makeResult(self): - # stream = self.stream # not you! - stream = sys.stdout - stream = _WritelnDecorator(stream) - return self.resultclass(stream, self.descriptions, self.verbosity) - -def wrapper(foo): - def magic(self): - s = "-".join(map(lambda x: x.__name__, self.__class__.mro())) - # print(s) - foo(self) - magic.__doc__ = foo.__doc__ - return magic - -from functools import update_wrapper, _make_key, RLock -from collections import namedtuple -_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) - -def cache(foo, typed=False): - """ Magic cache wrapper - https://github.com/python/cpython/blob/main/Lib/functools.py - """ - maxsize = None - def wrapper(self, *args, **kwargs): - key = (self.cache_id(), ("@cache", foo.__name__, _make_key(args, kwargs, typed)) ) - # key = (self.cache_id(), '@cache') - # if self._cache_contains[key] - - if not self._cache_contains(key): - value = foo(self, *args, **kwargs) - self._cache_put(key, value) - else: - value = self._cache_get(key) - return value - return wrapper - - -class UTestCase(unittest.TestCase): - _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache. - _cache = None # Read-only cache. Ensures method always produce same result. - _cache2 = None # User-written cache. - - def capture(self): - return Capturing2(stdout=self._stdout) - - @classmethod - def question_title(cls): - """ Return the question title """ - return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__ - - @classmethod - def reset(cls): - print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.") - cls._outcome = None - cls._cache = None - cls._cache2 = None - - def _callSetUp(self): - self._stdout = sys.stdout - import io - sys.stdout = io.StringIO() - super().setUp() - # print("Setting up...") - - def _callTearDown(self): - sys.stdout = self._stdout - super().tearDown() - # print("asdfsfd") - - def shortDescriptionStandard(self): - sd = super().shortDescription() - if sd == None: - sd = self._testMethodName - return sd - - def shortDescription(self): - # self._testMethodDoc.strip().splitlines()[0].strip() - sd = self.shortDescriptionStandard() - title = self._cache_get( (self.cache_id(), 'title'), sd ) - return title if title != None else sd - - @property - def title(self): - return self.shortDescription() - - @title.setter - def title(self, value): - self._cache_put((self.cache_id(), 'title'), value) - - def _get_outcome(self): - if not (self.__class__, '_outcome') or self.__class__._outcome == None: - self.__class__._outcome = {} - return self.__class__._outcome - - def _callTestMethod(self, testMethod): - t = time.time() - self._ensure_cache_exists() # Make sure cache is there. - if self._testMethodDoc != None: - # Ensure the cache is eventually updated with the right docstring. - self._cache_put((self.cache_id(), 'title'), self.shortDescriptionStandard() ) - # Fix temp cache here (for using the @cache decorator) - self._cache2[ (self.cache_id(), 'assert') ] = {} - - res = testMethod() - elapsed = time.time() - t - # self._cache_put( (self.cache_id(), 'title'), self.shortDescription() ) - - self._get_outcome()[self.cache_id()] = res - self._cache_put( (self.cache_id(), "time"), elapsed) - - # This is my base test class. So what is new about it? - def cache_id(self): - c = self.__class__.__qualname__ - m = self._testMethodName - return (c,m) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._load_cache() - self._assert_cache_index = 0 - # self.cache_indexes = defaultdict(lambda: 0) - - def _ensure_cache_exists(self): - if not hasattr(self.__class__, '_cache') or self.__class__._cache == None: - self.__class__._cache = dict() - if not hasattr(self.__class__, '_cache2') or self.__class__._cache2 == None: - self.__class__._cache2 = dict() - - def _cache_get(self, key, default=None): - self._ensure_cache_exists() - return self.__class__._cache.get(key, default) - - def _cache_put(self, key, value): - self._ensure_cache_exists() - self.__class__._cache2[key] = value - - def _cache_contains(self, key): - self._ensure_cache_exists() - return key in self.__class__._cache - - def wrap_assert(self, assert_fun, first, *args, **kwargs): - key = (self.cache_id(), 'assert') - if not self._cache_contains(key): - print("Warning, framework missing", key) - cache = self._cache_get(key, {}) - id = self._assert_cache_index - if not id in cache: - print("Warning, framework missing cache index", key, "id =", id) - _expected = cache.get(id, first) - assert_fun(first, _expected, *args, **kwargs) - cache[id] = first - self._cache_put(key, cache) - self._assert_cache_index += 1 - - def assertEqualC(self, first: Any, msg: Any = ...) -> None: - self.wrap_assert(self.assertEqual, first, msg) - - def _cache_file(self): - return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl" - - def _save_cache(self): - # get the class name (i.e. what to save to). - cfile = self._cache_file() - if not os.path.isdir(os.path.dirname(cfile)): - os.makedirs(os.path.dirname(cfile)) - - if hasattr(self.__class__, '_cache2'): - with open(cfile, 'wb') as f: - pickle.dump(self.__class__._cache2, f) - - # But you can also set cache explicitly. - def _load_cache(self): - if self._cache != None: # Cache already loaded. We will not load it twice. - return - # raise Exception("Loaded cache which was already set. What is going on?!") - cfile = self._cache_file() - # print("Loading cache from", cfile) - if os.path.exists(cfile): - with open(cfile, 'rb') as f: - data = pickle.load(f) - self.__class__._cache = data - else: - print("Warning! data file not found", cfile) - -def hide(func): - return func - -def makeRegisteringDecorator(foreignDecorator): - """ - Returns a copy of foreignDecorator, which is identical in every - way(*), except also appends a .decorator property to the callable it - spits out. - """ - def newDecorator(func): - # Call to newDecorator(method) - # Exactly like old decorator, but output keeps track of what decorated it - R = foreignDecorator(func) # apply foreignDecorator, like call to foreignDecorator(method) would have done - R.decorator = newDecorator # keep track of decorator - # R.original = func # might as well keep track of everything! - return R - - newDecorator.__name__ = foreignDecorator.__name__ - newDecorator.__doc__ = foreignDecorator.__doc__ - # (*)We can be somewhat "hygienic", but newDecorator still isn't signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it's not a big issue - return newDecorator - -hide = makeRegisteringDecorator(hide) - -def methodsWithDecorator(cls, decorator): - """ - Returns all methods in CLS with DECORATOR as the - outermost decorator. - - DECORATOR must be a "registering decorator"; one - can make any decorator "registering" via the - makeRegisteringDecorator function. - - import inspect - ls = list(methodsWithDecorator(GeneratorQuestion, deco)) - for f in ls: - print(inspect.getsourcelines(f) ) # How to get all hidden questions. - """ - for maybeDecorated in cls.__dict__.values(): - if hasattr(maybeDecorated, 'decorator'): - if maybeDecorated.decorator == decorator: - print(maybeDecorated) - yield maybeDecorated - diff --git a/unitgrade2/version.py b/unitgrade2/version.py deleted file mode 100644 index acb984f..0000000 --- a/unitgrade2/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.9.0" \ No newline at end of file -- GitLab