From bafbf14bebcaa8231614b2588c326db698964c26 Mon Sep 17 00:00:00 2001
From: Tue Herlau <tuhe@dtu.dk>
Date: Mon, 30 Aug 2021 08:32:28 +0200
Subject: [PATCH] updates

---
 autolab/autolab.py                            | 149 +++++++++++-------
 autolab/lab_template/Makefile                 |   4 +-
 autolab/lab_template/autograde-Makefile       |   6 +-
 autolab/lab_template/hello.rb                 |   4 +-
 autolab/lab_template/hello.yml                |  25 ++-
 autolab/lab_template/src/Makefile             |   5 +-
 autolab/lab_template/src/Makefile-handout     |   3 +-
 autolab/lab_template/src/driver.sh            |  36 ++---
 autolab/lab_template/src/driver_python.py     |  69 ++++----
 .../__pycache__/homework1.cpython-39.pyc      | Bin 0 -> 836 bytes
 .../instructor/cs102/report2.py               |   1 -
 .../cs101/Report1_handin_10_of_10.token       | Bin 70024 -> 74010 bytes
 .../__pycache__/report1_grade.cpython-39.pyc  | Bin 0 -> 61166 bytes
 .../instructor/cs101/report1_grade.py         | 110 +++++++------
 .../cs101/Report1_handin_0_of_10.token        | Bin 0 -> 73617 bytes
 .../cs101/Report1_handin_10_of_10.token       | Bin 70101 -> 0 bytes
 .../__pycache__/homework1.cpython-39.pyc      | Bin 835 -> 994 bytes
 .../__pycache__/report1_grade.cpython-39.pyc  | Bin 0 -> 61284 bytes
 .../example_simplest/students/cs101/deploy.py |  16 --
 .../students/cs101/homework1.py               |  13 +-
 .../students/cs101/report1.py                 |   3 +
 .../students/cs101/report1_grade.py           | 116 ++++++++------
 .../hidden_gather_upload.cpython-39.pyc       | Bin 3274 -> 4210 bytes
 unitgrade_private2/hidden_gather_upload.py    |  72 ++++++---
 24 files changed, 369 insertions(+), 263 deletions(-)
 create mode 100644 examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-39.pyc
 create mode 100644 examples/example_simplest/instructor/cs101/__pycache__/report1_grade.cpython-39.pyc
 create mode 100644 examples/example_simplest/students/cs101/Report1_handin_0_of_10.token
 delete mode 100644 examples/example_simplest/students/cs101/Report1_handin_10_of_10.token
 create mode 100644 examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-39.pyc
 delete mode 100644 examples/example_simplest/students/cs101/deploy.py

diff --git a/autolab/autolab.py b/autolab/autolab.py
index 50586f4..801602c 100644
--- a/autolab/autolab.py
+++ b/autolab/autolab.py
@@ -4,6 +4,7 @@ cd ~/Autolab && bundle exec rails s -p 8000 --binding=0.0.0.0
 To remove my shitty image:
 docker rmi tango_python_tue
 """
+import inspect
 from zipfile import ZipFile
 import os
 from os.path import basename
@@ -11,6 +12,10 @@ import os
 import shutil
 from jinja2 import Environment, FileSystemLoader
 import glob
+import pickle
+from unitgrade2.unitgrade2 import Report
+import inspect
+from unitgrade_private2 import docker_helpers
 
 COURSES_BASE = "/home/tuhe/Autolab/courses/AutoPopulated"
 TEMPLATE_BASE = "/home/tuhe/Documents/unitgrade_private/autolab/lab_template"
@@ -39,7 +44,6 @@ def jj_handout(source, dest, data):
 
 
 def zipFilesInDir(dirName, zipFileName, filter):
-   # create a ZipFile object
    with ZipFile(zipFileName, 'w') as zipObj:
        # Iterate over all the files in directory
        for folderName, subfolders, filenames in os.walk(dirName):
@@ -50,43 +54,98 @@ def zipFilesInDir(dirName, zipFileName, filter):
                    # Add file to zip
                    zipObj.write(filePath, basename(filePath))
 
+def paths2report(base_path, report_file):
+    mod = ".".join(os.path.relpath(report_file[:-3], base_path).split(os.sep))
+    # f2 = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101"
+    # spec1 = importlib.util.spec_from_file_location("cs101", f2)
+    # cs101 = importlib.util.module_from_spec(spec1)
+    # spec1.loader.exec_module(cs101)
+
+    from importlib.machinery import SourceFileLoader
+    foo = SourceFileLoader(mod, report_file).load_module()
+    # return foo.Report1
+    # spec = importlib.util.spec_from_file_location(mod, report_file)
+    # foo = importlib.util.module_from_spec(spec)
+    # spec.loader.exec_module(foo)
+    for name, obj in inspect.getmembers(foo):
+        if inspect.isclass(obj): # and obj.__module__ == foo:
+            if obj.__module__ == foo.__name__: # and issubclass(obj, Report):
+                if issubclass(obj, Report):
+                    report = getattr(foo, name)
+                    return report
+    return None
+
+def run_relative(file, base):
+    relative = os.path.relpath(file, base)
+    mod = os.path.normpath(relative)[:-3].split(os.sep)
+    os.system(f"cd {base} && python -m {'.'.join(mod)}")
+
+
+import inspect
+
+# class Example:
+#     @property
+#     def title(self):
+#         stack = inspect.stack()
+#         return stack[1].function.__doc__
+#     @title.setter
+#     def title(self, value):
+#         stack = inspect.stack()
+#         stack[1].function.__doc__ = value
+#         # self._title = value
+#
+#     def myfun(self):
+#         self.title = 234
+#         self.title
+#
+#         return 3
+
+
 def deploy_assignment(base_name):
-    docker_build_image()
 
-    # Ok so what should go on here?
+    docker_build_image()
     LAB_DEST = os.path.join(COURSES_BASE, base_name)
-    STUDENT_HANDOUT_DIR = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/cs101"
-    INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1_grade.py"
-    # STUDENT_TOKEN_FILE  = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/cs101"
 
-    from cs101.report1 import Report1 # The instructors report class.
-    StudentReportClass = Report1.__qualname__ + "_handin.token"
-    import inspect
-    # inspect.getfile(Report1.pack_imports[0])
-    m = Report1.pack_imports[0]
-    root, relative = Report1()._import_base_relative()
+    INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1_grade.py"
+    INSTRUCTOR_BASE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor"
 
+    STUDENT_BASE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students"
+    STUDENT_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/cs101/report1_grade.py"
 
-    z = 234
+    # STUDENT_HANDOUT_DIR = os.path.dirname(STUDENT_GRADE_FILE) #"/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/cs101"
+    # INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1.py"
+    # Make instructor token file.
 
+    # Get the instructor result file.
+    run_relative(INSTRUCTOR_GRADE_FILE, INSTRUCTOR_BASE)
+    f = glob.glob(os.path.dirname(INSTRUCTOR_GRADE_FILE) + "/*.token")[0]
+    with open(f, 'rb') as f:
+        res = pickle.load(f)
 
+    # Now we have the instructor token file. Let's get the student token file.
+    problems = [dict(name='Total', description='', max_score=res['total'][1], optional='false')]
+    for k, q in res['details'].items():
+        problems.append(dict(name=q['title'], description='', max_score=q['possible'], optional='true'))
+    print(problems)
 
+    sc = [('Total', res['total'][0])] + [(q['title'], q['obtained']) for k, q in res['details'].items()]
+    ss = ", ".join( [f'"{t}": {s}' for t, s in sc] )
+    scores = '{"scores": {' + ss + '}}'
+    print(scores)
 
     # Quickly make student .token file to upload:
     # os.system(f"cd {os.path.dirname(STUDENT_HANDOUT_DIR)} && python -m cs101.{os.path.basename(INSTRUCTOR_GRADE_FILE)[:-3]}")
-    os.system(f"cd {STUDENT_HANDOUT_DIR} && python {os.path.basename(INSTRUCTOR_GRADE_FILE)}")
-
-    STUDENT_TOKEN_FILE = glob.glob(STUDENT_HANDOUT_DIR + "/*.token")[0]
+    # os.system(f"cd {STUDENT_HANDOUT_DIR} && python {os.path.basename(INSTRUCTOR_GRADE_FILE)}")
+    # handin_filename = os.path.basename(STUDENT_TOKEN_FILE)
 
-    tname = os.path.basename(STUDENT_TOKEN_FILE)
+    run_relative(STUDENT_GRADE_FILE, STUDENT_BASE)
+    STUDENT_TOKEN_FILE = glob.glob(os.path.dirname(STUDENT_GRADE_FILE) + "/*.token")[0]
+    handin_filename = os.path.basename( STUDENT_TOKEN_FILE)
     for _ in range(3):
-        tname = tname[:tname.rfind("_")]
-    tname += ".token"
-    print("> Name of handin file", tname)
+        handin_filename = handin_filename[:handin_filename.rfind("_")]
+    handin_filename += ".token"
 
-    # Take student handout and unzip it.
-    # Take student token file, unzip it and merge files.
-    # This is the directory which is going to be handed out.
+    print("> Name of handin file", handin_filename)
     if os.path.exists(LAB_DEST):
         shutil.rmtree(LAB_DEST)
     os.mkdir(LAB_DEST)
@@ -94,61 +153,45 @@ def deploy_assignment(base_name):
 
     # Make the handout directory.
     # Start in the src directory. You should make the handout files first.
-    # jj("a", "b", data={})
-    src_dest = LAB_DEST + "/src"
-    # src_source = TEMPLATE_BASE + "/src"
-    os.mkdir(src_dest)
-
-    # unitgrade-docker
-    from unitgrade_private2 import docker_helpers
+    os.mkdir(LAB_DEST + "/src")
 
+    INSTRUCTOR_REPORT_FILE = INSTRUCTOR_GRADE_FILE[:-9] + ".py"
+    a = 234
+    # /home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1.py"
     data = {
             'base_name': base_name,
-            'nice_name': base_name + "please",
-            # 'autograde_image': 'autograde_image',
+            # 'nice_name': base_name + "please",
+            'display_name': paths2report(INSTRUCTOR_BASE, INSTRUCTOR_REPORT_FILE).title,
+            'handin_filename': handin_filename,
             'autograde_image': 'tango_python_tue',
-            'src_files_to_handout': ['driver_python.py','student_sources.zip', tname, os.path.basename(docker_helpers.__file__),
+            'src_files_to_handout': ['driver_python.py', 'student_sources.zip', handin_filename, os.path.basename(docker_helpers.__file__),
                                      os.path.basename(INSTRUCTOR_GRADE_FILE)], # Remove tname later; it is the upload.
-            'handin_filename': 'hello3.c', # the student token file.
-            'student_token_file': tname,
             'instructor_grade_file': os.path.basename(INSTRUCTOR_GRADE_FILE),
-            'grade_file_relative_destination': relative,
+            'grade_file_relative_destination': os.path.relpath(INSTRUCTOR_GRADE_FILE, INSTRUCTOR_BASE),
+            'problems': problems,
             }
 
     # shutil.copyfile(TEMPLATE_BASE + "/hello.yml", f"{LAB_DEST}/{base_name}.yml")
     jj_handout(TEMPLATE_BASE + "/src/README", LAB_DEST + "/src/README", data)
     jj_handout(TEMPLATE_BASE + "/src/driver_python.py", LAB_DEST + "/src/driver_python.py", data)
-    jj_handout(TEMPLATE_BASE + "/src/hello.c", LAB_DEST + "/src/hello3.c",data)
     jj_handout(TEMPLATE_BASE + "/src/Makefile", LAB_DEST + "/src/Makefile",data)
     jj_handout(TEMPLATE_BASE + "/src/driver.sh", LAB_DEST + "/src/driver.sh",data)
 
     jj(TEMPLATE_BASE + "/Makefile", LAB_DEST + "/Makefile", data)
-    shutil.copyfile(TEMPLATE_BASE + "/hello.yml", f"{LAB_DEST}/{base_name}.yml")
-    shutil.copyfile(TEMPLATE_BASE + "/autograde-Makefile", LAB_DEST + "/autograde-Makefile")
+    jj(TEMPLATE_BASE + "/autograde-Makefile", LAB_DEST + "/autograde-Makefile",data=data)
     jj(TEMPLATE_BASE + "/hello.yml", f"{LAB_DEST}/{base_name}.yml", data=data)
     jj(TEMPLATE_BASE + "/hello.rb", f"{LAB_DEST}/{base_name}.rb", data=data)
 
     # Copy the student grade file to remove.
     shutil.copyfile(INSTRUCTOR_GRADE_FILE, f"{LAB_DEST}/src/{os.path.basename(INSTRUCTOR_GRADE_FILE)}")
-    shutil.copyfile(STUDENT_TOKEN_FILE, f"{LAB_DEST}/src/{os.path.basename(STUDENT_TOKEN_FILE)}")
-    shutil.copyfile(STUDENT_TOKEN_FILE, f"{LAB_DEST}/src/{tname}")
-    # zipFilesInDir(STUDENT_HANDOUT_DIR, LAB_DEST + '/student_sources.zip', lambda name: True)
-    # Make a zip file of all the students (handed out) sources.
-    shutil.make_archive(LAB_DEST + '/src/student_sources', 'zip', root_dir=os.path.dirname(STUDENT_HANDOUT_DIR), base_dir='cs101')
-    # Take the (student) .token file and unpack sources into the student_sources.zip directory.
-    # docker_helpers
-
+    shutil.copyfile(STUDENT_TOKEN_FILE, f"{LAB_DEST}/src/{handin_filename}")
+    shutil.make_archive(LAB_DEST + '/src/student_sources', 'zip', root_dir=STUDENT_BASE, base_dir='cs101')
     shutil.copyfile(docker_helpers.__file__, f"{LAB_DEST}/src/{os.path.basename(docker_helpers.__file__)}")
-
-
     os.mkdir(LAB_DEST +"/handin")
     os.mkdir(LAB_DEST +"/test-autograder") # Otherwise make clean will screw up.
-
     os.system(f"cd {LAB_DEST} && make && cd {CURDIR}")
-
     print("Deploy", base_name)
 
 if __name__ == "__main__":
-    # print("Hello there handsome")
     print("Deploying to", COURSES_BASE)
-    deploy_assignment("hello3")
+    deploy_assignment("hello4")
diff --git a/autolab/lab_template/Makefile b/autolab/lab_template/Makefile
index 4178c87..48023fe 100644
--- a/autolab/lab_template/Makefile
+++ b/autolab/lab_template/Makefile
@@ -12,8 +12,8 @@ handout:
 	(rm -rf $(LAB)-handout; mkdir $(LAB)-handout)
 	cp -p src/Makefile-handout $(LAB)-handout/Makefile
 	cp -p src/README-handout $(LAB)-handout/README
-	cp -p src/hello3.c-handout $(LAB)-handout/hello3.c
-	cp -p src/driver.sh $(LAB)-handout
+	#	cp -p src/hello3.c-handout $(LAB)-handout/hello3.c
+	#cp -p src/driver.sh $(LAB)-handout
 {%- for f in src_files_to_handout %}
 	cp -p src/{{f}} $(LAB)-handout
 {% endfor %}
diff --git a/autolab/lab_template/autograde-Makefile b/autolab/lab_template/autograde-Makefile
index 8843390..55d8a7d 100644
--- a/autolab/lab_template/autograde-Makefile
+++ b/autolab/lab_template/autograde-Makefile
@@ -1,7 +1,7 @@
 all:
-	tar xvf autograde.tar
-	cp hello3.c hello3-handout
-	(cd hello3-handout; sh driver.sh)
+	tar xf autograde.tar
+	cp {{handin_filename}} {{base_name}}-handout
+	(cd {{base_name}}-handout; python3 driver_python.py)
 
 clean:
 	rm -rf *~ hello3-handout
diff --git a/autolab/lab_template/hello.rb b/autolab/lab_template/hello.rb
index a28c026..cb907a3 100644
--- a/autolab/lab_template/hello.rb
+++ b/autolab/lab_template/hello.rb
@@ -1,10 +1,10 @@
 require "AssessmentBase.rb"
 
-module Hello3
+module {{base_name|capitalize}}
   include AssessmentBase
 
   def assessmentInitialize(course)
-    super("hello3",course)
+    super("{{base_name}}",course)
     @problems = []
   end
 
diff --git a/autolab/lab_template/hello.yml b/autolab/lab_template/hello.yml
index b545df5..f5df4d4 100644
--- a/autolab/lab_template/hello.yml
+++ b/autolab/lab_template/hello.yml
@@ -1,24 +1,33 @@
 ---
+
 general:
   name: {{ base_name }}
   description: ''
-  display_name: Hello3
-  handin_filename: hello3.c
+  display_name: {{ display_name }}
+  handin_filename: {{ handin_filename }}
   handin_directory: handin
   max_grace_days: 0
-  handout: hello3-handout.tar
-  writeup: writeup/hello3.html
+  handout: {{ base_name }}-handout.tar
+  writeup: writeup/{{base_name}}.html
   max_submissions: -1
   disable_handins: false
   max_size: 2
   has_svn: false
   category_name: Lab
 problems:
-- name: Correctness
-  description: ''
-  max_score: 100.0
-  optional: false
+{% for p in problems %}
+  - name: {{ p.name }}
+    description: '{{p.description}}'
+    max_score: {{p.max_score}}
+    optional: {{p.optional}}
+{% endfor %}
 autograder:
   autograde_timeout: 180
   autograde_image: {{ autograde_image }}
   release_score: true
+
+# problems:
+# - name: Correctness
+#  description: ''
+#  max_score: 100.0
+#  optional: false
\ No newline at end of file
diff --git a/autolab/lab_template/src/Makefile b/autolab/lab_template/src/Makefile
index d815a12..6ca51a4 100644
--- a/autolab/lab_template/src/Makefile
+++ b/autolab/lab_template/src/Makefile
@@ -1,6 +1,7 @@
 # Makefile for the Hello Lab
-all: 
-	gcc hello3.c -o hello3
+all:
+	echo "Makefile called... it is empty so far. "
+	#gcc hello3.c -o hello3
 
 clean:
 	rm -rf *~ hello3
diff --git a/autolab/lab_template/src/Makefile-handout b/autolab/lab_template/src/Makefile-handout
index f68bf26..6c57d65 100644
--- a/autolab/lab_template/src/Makefile-handout
+++ b/autolab/lab_template/src/Makefile-handout
@@ -1,5 +1,6 @@
 # Student makefile for the Hello Lab
-all: 
+all:
+    echo "handout makefile called.."
 	gcc hello.c -o hello
 
 clean:
diff --git a/autolab/lab_template/src/driver.sh b/autolab/lab_template/src/driver.sh
index 2155ec8..1d6a6f7 100755
--- a/autolab/lab_template/src/driver.sh
+++ b/autolab/lab_template/src/driver.sh
@@ -10,25 +10,25 @@
 # python3 --version
 python3 driver_python.py
 
-(make clean; make)
-status=$?
-if [ ${status} -ne 0 ]; then
-    echo "Failure: Unable to compile hello3.c (return status = ${status})"
-    echo "{\"scores\": {\"Correctness\": 0}}"
-    exit
-fi
-
+#(make clean; make)
+#status=$?
+#if [ ${status} -ne 0 ]; then
+#    echo "Failure: Unable to compile hello3.c (return status = ${status})"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#    exit
+#fi
+#
 # Run the code
-echo "Running ./hello3"
-./hello3
-status=$?
-if [ ${status} -eq 0 ]; then
-    echo "Success: ./hello3 runs with an exit status of 0"
-    echo "{\"scores\": {\"Correctness\": 100}}"
-else
-    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
-    echo "{\"scores\": {\"Correctness\": 0}}"
-fi
+#echo "Running ./hello3"
+#./hello3
+#status=$?
+#if [ ${status} -eq 0 ]; then
+#    echo "Success: ./hello3 runs with an exit status of 0"
+#    echo "{\"scores\": {\"Correctness\": 100}}"
+#else
+#    echo "Failure: ./hello fails or returns nonzero exit status of ${status}"
+#    echo "{\"scores\": {\"Correctness\": 0}}"
+#fi
 
 exit
 
diff --git a/autolab/lab_template/src/driver_python.py b/autolab/lab_template/src/driver_python.py
index a879813..93ecb2c 100644
--- a/autolab/lab_template/src/driver_python.py
+++ b/autolab/lab_template/src/driver_python.py
@@ -1,19 +1,20 @@
-print("="*10)
-tag = "[driver_python.py]"
-print(tag, "I am going to have a chamor of a time grading your file!")
 import os
 import glob
-import shutil
 import sys
 import pickle
 # import io
+import subprocess
+import docker_helpers
 import time
+
+print("="*10)
+tag = "[driver_python.py]"
+print(tag, "I am going to have a chamor of a time evaluating your stuff")
+
 sys.stderr = sys.stdout
 wdir = os.getcwd()
 
 # print(os.system("cd"))
-
-
 def pfiles():
     print("> Files in dir:")
     for f in glob.glob(wdir + "/*"):
@@ -21,7 +22,7 @@ def pfiles():
     print("---")
 
 # shutil.unpack_archive("student_sources.zip")
-student_token_file = '{{student_token_file}}'
+student_token_file = '{{handin_filename}}'
 instructor_grade_script = '{{instructor_grade_file}}'
 grade_file_relative_destination = "{{grade_file_relative_destination}}"
 with open(student_token_file, 'rb') as f:
@@ -30,55 +31,49 @@ sources = results['sources'][0]
 pfiles()
 
 host_tmp_dir = wdir + "/tmp"
-import subprocess
-import docker_helpers
 print(f"{host_tmp_dir=}")
 print(f"{student_token_file=}")
 print(f"{instructor_grade_script=}")
 command, token = docker_helpers.student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination)
-command = f"cd tmp && {command}"
-
+command = f"cd tmp && {command} --noprogress --autolab"
 
 def rcom(cm):
-    print(f"running... ", cm)
-    start = time.time()
+    # print(f"running... ", cm)
+    # start = time.time()
     rs = subprocess.run(cm, capture_output=True, text=True, shell=True)
-    print(rs)
-    print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
-rcom("ls")
-rcom('python3 --version')
-rcom('python --version')
-
-
-
+    print(rs.stdout)
+    if len(rs.stderr) > 0:
+        print("There were errors in executing the file:")
+        print(rs.stderr)
+    # print(rs)
+    # print("result of running command was", rs.stdout, "err", rs.stderr, "time", time.time() - start)
 
 start = time.time()
 rcom(command)
-# print("Calling sub process...")
-# result = subprocess.run(command.split(), capture_output=True, text=True, shell=True).stdout
-# print("result of running command was", result, "time", time.time() - start)
-
-
-import time
-time.sleep(1)
-# print("> Files in dir:")
-pfiles()
-for f in glob.glob(host_tmp_dir + "/cs101/*"):
-    print("cs101/", f)
-print("---")
+# pfiles()
+# for f in glob.glob(host_tmp_dir + "/cs101/*"):
+#     print("cs101/", f)
+# print("---")
 
 print(f"{token=}")
 ls = glob.glob(token)
-print(ls)
+# print(ls)
 f = ls[0]
 with open(f, 'rb') as f:
     results = pickle.load(f)
-print("results")
+# print("results")
+# print(results.keys())
 print(results['total'])
-
 # if os.path.exists(host_tmp_dir):
 #     shutil.rmtree(host_tmp_dir)
 # with io.BytesIO(sources['zipfile']) as zb:
 #     with zipfile.ZipFile(zb) as zip:
 #         zip.extractall(host_tmp_dir
-print("="*10)
\ No newline at end of file
+# print("="*10)
+# print('{"scores": {"Correctness": 100,  "Problem 1": 4}}')
+
+sc = [('Total', results['total'][0])] + [(q['title'], q['obtained']) for k, q in results['details'].items()]
+ss = ", ".join([f'"{t}": {s}' for t, s in sc])
+scores = '{"scores": {' + ss + '}}'
+print('{"_presentation": "semantic"}')
+print(scores)
diff --git a/examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-39.pyc b/examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..59aec17144bee92c76e761ed5a8bcfd0b24ba30c
GIT binary patch
literal 836
zcmYe~<>g{vU|@Khtet4b#K7<v#6iZa3=9ko3=9m#P7DkTDGVu$Eeuf%DNHHMEeuhN
zsZ1#>*-S-tsmv*?sf;OXDQwM5vl-?xH8V0Yq%x<lr!u57q;M=^Y-Vg`Vq{3+3}(>e
zN@hed9>iv5U|?_t*}%iVz)-?a!dS!5%-GBr%%I6wwTMeWK|#Sivn(}FAyFYGv$#Y-
zJ+~4>t1A?xmXsFd6~n}n^Ye-`i%T-|(iQU46iPBu6^avcQx#HkQgc)DN{SUS^Aw6w
z%TkMqQx)=yQc{cb6kPSv^}yys#p6NdYD63A80i?tYAO_G<d^28K&?@THr6rHF^pBv
zNQBv_3DK;{bc+R~@fHWvm=sOMC^m>AS27fVeD^C}KO;XkRllS(BURreKe-g_BK^|5
z%#!q?#FW(df}+f_#FA9~)QZI1f}GT17$ZKdC^0v+JijPgKQphmq^LBxB)>>Mx!BOa
z2y7xq#!#=I@)i%&Zm<_Xfm+N23T4(JQ1m5(*&sdx0|ST+!r&0+U|?XVVW?pgX8?zG
z5;UYs%JUWSN^_G^i;5Kz5)%{>^HLNNk`fe3GBS%5(n|A^OEUBGVBu4onOl%k2@9r@
zjMU5`h2qj&D{#o#DI}((Xe8<+X)4$%Bx)xqfWt$R=@w(+Eyg4;2STtgFfiO=PE1Jw
zISJx;<{}XL7Ds%1ZenI$e0-I7WPWLpLQ!gQX-)|!j*S$w6^s;Y6<&h;QzaGzj$Kgv
zCn^-@Cg$XT(~Blc5h#Jb1jWNkP-whlXJBCP(_{e$4cv;n#N5>Q_*<+6MVWae5GR3j
eKv-bMaoFVMr<CTT+JS=?<TMU;4pt5(5k>&&SJ&kL

literal 0
HcmV?d00001

diff --git a/examples/example_framework/instructor/cs102/report2.py b/examples/example_framework/instructor/cs102/report2.py
index 832a117..1b00e84 100644
--- a/examples/example_framework/instructor/cs102/report2.py
+++ b/examples/example_framework/instructor/cs102/report2.py
@@ -16,7 +16,6 @@ class Week1(UTestCase):
         self.assertEqualC(reverse_list([1,2,3]))
 
 
-
 class Question2(UTestCase):
     """ Second problem """
     @cache
diff --git a/examples/example_simplest/instructor/cs101/Report1_handin_10_of_10.token b/examples/example_simplest/instructor/cs101/Report1_handin_10_of_10.token
index 26332275bf4a15800ec28f4e3cde99ee1c23b810..0a688a52f5136ace5fb186affec815bdeb67c268 100644
GIT binary patch
delta 8812
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

delta 6632
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
Whs%`dHx(Go_&MP*F{QM)R1W}41VuCe

diff --git a/examples/example_simplest/instructor/cs101/__pycache__/report1_grade.cpython-39.pyc b/examples/example_simplest/instructor/cs101/__pycache__/report1_grade.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..320b3cf336a65641c1c544822f0a7b397f2c8859
GIT binary patch
literal 61166
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
SfQie3gOi1kg^`5`q!Iuj=r%L}

literal 0
HcmV?d00001

diff --git a/examples/example_simplest/instructor/cs101/report1_grade.py b/examples/example_simplest/instructor/cs101/report1_grade.py
index d844649..1a8a049 100644
--- a/examples/example_simplest/instructor/cs101/report1_grade.py
+++ b/examples/example_simplest/instructor/cs101/report1_grade.py
@@ -40,8 +40,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -138,13 +136,24 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        testcase_methods = list(testCaseClass.__dict__.keys())
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    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)
@@ -157,17 +166,7 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
@@ -188,6 +187,8 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
         # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
         z = 234
@@ -262,14 +263,15 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        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 = w * int(obtained * 1.0 / possible )
+        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
@@ -368,38 +370,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -414,10 +433,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -428,7 +450,7 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            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)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         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."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)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\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            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)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        a ="234"\n        b = "234"\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        return root_dir, relative_path\n\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         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."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        # test.countTestCases()\n\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)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\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        testcase_methods = list(testCaseClass.__dict__.keys())\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
 report1_payload = '8004953f000000000000007d948c055765656b31947d948c2c6e6f20636163686520736565205f73657475705f616e737765727320696e20756e69746772616465322e7079948873732e'
 name="Report1"
 
diff --git a/examples/example_simplest/students/cs101/Report1_handin_0_of_10.token b/examples/example_simplest/students/cs101/Report1_handin_0_of_10.token
new file mode 100644
index 0000000000000000000000000000000000000000..f5d1790d62c080547dc0c0940d993d6e89efd745
GIT binary patch
literal 73617
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
C2=$8q

literal 0
HcmV?d00001

diff --git a/examples/example_simplest/students/cs101/Report1_handin_10_of_10.token b/examples/example_simplest/students/cs101/Report1_handin_10_of_10.token
deleted file mode 100644
index 4e5f827930cba675185c66c49d883c1a2cd35046..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 70101
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

diff --git a/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-39.pyc b/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-39.pyc
index 797cf46e4a10aa111b1af224256cb5ec15734be5..f4a5b8432432aa2fb0239a4f02152ac367b45b4c 100644
GIT binary patch
delta 554
zcmX@i_K2M?k(ZZ?fq{YHak6%zBJ)H(nR+({28I-dD25cq6s8u2D8>}#6qXi-D5g~A
z6xM8}BBxZA6t+~R6!sMMW~SK;bD5eM85vSpQaDl>(-~4Y7cn+7HZw6Yq;Lf@XmVGj
zbGcR|<`(3nDioKLrljVTC?w~nr0OYzWMmdAq-Ex$Dr6QbB$k%s=O&h9CMV|PR3<5;
zr{<*=C6=V7D5MqT=PHzBq=HozD`e(@IhlFIB}JvlCHY0VDVas7AVwuuG9$<rPz+)-
zF)%PVGcYg|^Dr<llrW?)HZwLcrZ5FFXfjWnx5VX_xF^V2xgaN{mF6XvWaj57B;}`6
z`mJQVCG3}90+&uraV;v!FVbX;V#}?}$t*5e$xy_?z`*b;W^y{CqZ!m+#rny`h6aZE
z8Tq-X<@rU~hI$2+w|I(D%TkMqQ{zF}KzfQnu3}&-Vw?PcQC0}kWs|v?QjI~b_bURa
z)MUEFn0SjZ32YyPU}a!nxW$~9k^<5UF^;8(bMjm!H#Kes28Nf+AOfW5B|8HHgP$hr
zE%x~Ml>FrQ_#!q228JTeNz9@KAg4f#1G7NNZ*kb<=BJeAq}qY3F9xLv7G@3(4mJ)Z
G5k>%*M2nID

delta 395
zcmaFFewd9fk(ZZ?fq{V`+D$Xjj%gyFOuZ8W149Z!3S$dH6hjJA3UdoX6k{q=3QIOq
zkzFct3TrB33R?<WGt+E_xlGNBj0~yFDeS2X=?p0xix`_3o0%9HQaFPdG`W%)LFPg+
zh|SEvz~Iclz);M?z`#(#P{LTl(9GD(7|fu_Sha{tK|w*mJ+mxzvKZq`RZXT_EIFCQ
zCAT<=Qp-|{ic?cG8Kc;8D?wr_8HzwA{fe9XnbDCeGq1R$s5H4Gzi6@zlOiw3o?<4D
zrL0A)le3s)`8XID7-|@57{wVt7EPYXl&TBTuE}(ZG4U2-64)#V!NS16aEm!HB?Y7f
zVhD2)$7Dlhw**iSy##4{$;!aM@RFT@fx%Ccr3l0+0;#^m5g(tIn420Oe~Y!CC^N4F
hVj@TfgatO6!zMRBr8Fni4(v*hJ`Q#cRt_c+MgXu=Plx~j

diff --git a/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-39.pyc b/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5a8613dee80bdb366573f9b28d6ec044938c16aa
GIT binary patch
literal 61284
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
Lk%f_k38WGLKnG#+

literal 0
HcmV?d00001

diff --git a/examples/example_simplest/students/cs101/deploy.py b/examples/example_simplest/students/cs101/deploy.py
deleted file mode 100644
index 3e9682d..0000000
--- a/examples/example_simplest/students/cs101/deploy.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from report1 import Report1
-from unitgrade_private2.hidden_create_files import setup_grade_file_report
-from snipper import snip_dir
-import shutil
-
-if __name__ == "__main__":
-    setup_grade_file_report(Report1, minify=False, obfuscate=False, execute=False)
-
-    # Deploy the files using snipper: https://gitlab.compute.dtu.dk/tuhe/snipper
-    snip_dir.snip_dir(source_dir="../cs101", dest_dir="../../students/cs101", clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py'])
-
-    # For my own sake, copy the homework to the other examples.
-    for f in ['../../../example_framework/instructor/cs102/homework1.py', '../../../example_docker/instructor/cs103/homework1.py']:
-        shutil.copy('homework1.py', f)
-
-
diff --git a/examples/example_simplest/students/cs101/homework1.py b/examples/example_simplest/students/cs101/homework1.py
index 286b79f..3543f1b 100644
--- a/examples/example_simplest/students/cs101/homework1.py
+++ b/examples/example_simplest/students/cs101/homework1.py
@@ -1,14 +1,19 @@
-def reverse_list(mylist): #!f
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
+def reverse_list(mylist): 
     """
     Given a list 'mylist' returns a list consisting of the same elements in reverse order. E.g.
     reverse_list([1,2,3]) should return [3,2,1] (as a list).
     """
-    return list(reversed(mylist))
+    # TODO: 1 lines missing.
+    raise NotImplementedError("Implement function body")
 
-def add(a,b): #!f
+def add(a,b): 
     """ Given two numbers `a` and `b` this function should simply return their sum:
     > add(a,b) = a+b """
-    return a+b
+    # TODO: 1 lines missing.
+    raise NotImplementedError("Implement function body")
 
 if __name__ == "__main__":
     # Problem 1: Write a function which add two numbers
diff --git a/examples/example_simplest/students/cs101/report1.py b/examples/example_simplest/students/cs101/report1.py
index ea4f3b2..a50ddcc 100644
--- a/examples/example_simplest/students/cs101/report1.py
+++ b/examples/example_simplest/students/cs101/report1.py
@@ -1,3 +1,6 @@
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
 from unitgrade2.unitgrade2 import Report
 from unitgrade2.unitgrade_helpers2 import evaluate_report_student
 from cs101.homework1 import reverse_list, add
diff --git a/examples/example_simplest/students/cs101/report1_grade.py b/examples/example_simplest/students/cs101/report1_grade.py
index d844649..841e8a3 100644
--- a/examples/example_simplest/students/cs101/report1_grade.py
+++ b/examples/example_simplest/students/cs101/report1_grade.py
@@ -1,4 +1,6 @@
-
+"""
+Example student code. This file is automatically generated from the files in the instructor-directory
+"""
 import numpy as np
 from tabulate import tabulate
 from datetime import datetime
@@ -40,8 +42,6 @@ parser.add_argument('--showcomputed',  action="store_true",  help='Show the answ
 parser.add_argument('--unmute',  action="store_true",  help='Show result of print(...) commands in code')
 parser.add_argument('--passall',  action="store_true",  help='Automatically pass all tests. Useful when debugging.')
 
-
-
 def evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):
     args = parser.parse_args()
     if question is None and args.q is not None:
@@ -138,13 +138,24 @@ class UnitgradeTextRunner(unittest.TextTestRunner):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+class SequentialTestLoader(unittest.TestLoader):
+    def getTestCaseNames(self, testCaseClass):
+        test_names = super().getTestCaseNames(testCaseClass)
+        testcase_methods = list(testCaseClass.__dict__.keys())
+        test_names.sort(key=testcase_methods.index)
+        return test_names
 
 def evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,
                     show_progress_bar=True,
-                    show_tol_err=False):
+                    show_tol_err=False,
+                    big_header=True):
+
     now = datetime.now()
-    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
-    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    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)
@@ -157,17 +168,7 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
     nL = 80
     t_start = time.time()
     score = {}
-
-    # Use the sequential test loader instead. See here:
-    class SequentialTestLoader(unittest.TestLoader):
-        def getTestCaseNames(self, testCaseClass):
-            test_names = super().getTestCaseNames(testCaseClass)
-            testcase_methods = list(testCaseClass.__dict__.keys())
-            test_names.sort(key=testcase_methods.index)
-            return test_names
     loader = SequentialTestLoader()
-    # loader = unittest.TestLoader()
-    # loader.suiteClass = MySuite
 
     for n, (q, w) in enumerate(report.questions):
         # q = q()
@@ -188,6 +189,8 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # unittest.Te
         # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]
         UTextResult.q_title_print = q_title_print # Hacky
+        UTextResult.show_progress_bar = show_progress_bar # Hacky.
+
         res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)
         # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)
         z = 234
@@ -262,14 +265,15 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa
         # ws, possible, obtained = upack(q_)
 
         possible = res.testsRun
-        obtained = possible - len(res.errors)
+        obtained = len(res.successes)
 
+        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 = w * int(obtained * 1.0 / possible )
+        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
@@ -368,38 +372,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -414,10 +435,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
@@ -428,10 +452,10 @@ def source_instantiate(name, report1_source, payload):
 
 
 
-report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nimport sys\nfrom io import StringIO\nimport collections\nimport inspect\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            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)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         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."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar"):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        self.thread = threading.Thread(target=self.run)\n        self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        self._running = False\n        self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        show_progress_bar = True\n        nL = 80\n        if show_progress_bar:\n            tsecs = np.round( self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n            #\n            #     if not hidden:\n            current = 1\n            possible = 1\n            # tsecs = 2\n            ss = "PASS" if current == possible else "*** FAILED"\n            if tsecs >= 0.1:\n                ss += " ("+ str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if show_progress_bar:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)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\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n\n    # Use the sequential test loader instead. See here:\n    class SequentialTestLoader(unittest.TestLoader):\n        def getTestCaseNames(self, testCaseClass):\n            test_names = super().getTestCaseNames(testCaseClass)\n            testcase_methods = list(testCaseClass.__dict__.keys())\n            test_names.sort(key=testcase_methods.index)\n            return test_names\n    loader = SequentialTestLoader()\n    # loader = unittest.TestLoader()\n    # loader.suiteClass = MySuite\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = possible - len(res.errors)\n\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = w * int(obtained * 1.0 / possible )\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    if len(report.individual_imports) > 0:\n        print("By uploading the .token file, you verify the files:")\n        for m in report.individual_imports:\n            print(">", m.__file__)\n        print("Are created/modified individually by you in agreement with DTUs exam rules")\n        report.pack_imports += report.individual_imports\n\n    sources = {}\n    if len(report.pack_imports) > 0:\n        print("Including files in upload...")\n        for k, m in enumerate(report.pack_imports):\n            nimp, top_package = gather_imports(m)\n            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n            nimp[\'report_relative_location\'] = report_relative_location\n            nimp[\'name\'] = m.__name__\n            sources[k] = nimp\n            # if len([k for k in nimp if k not in sources]) > 0:\n            print(f"*** {m.__name__}")\n            # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    # json_str = json.dumps(results, indent=4)\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
+report1_source = 'import os\n\n# DONT\'t import stuff here since install script requires __version__\n\ndef cache_write(object, file_name, verbose=True):\n    import compress_pickle\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    import compress_pickle # Import here because if you import in top the __version__ tag will fail.\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        try:\n            with open(file_name, \'rb\') as f:\n                return compress_pickle.load(f, compression="lzma")\n        except Exception as e:\n            print("Tried to load a bad pickle file at", file_name)\n            print("If the file appears to be automatically generated, you can try to delete it, otherwise download a new version")\n            print(e)\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\n# from . import cache_read\nimport unittest\nimport numpy as np\nimport sys\nfrom io import StringIO\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport itertools\nimport os\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Logger(object):\n    def __init__(self, buffer):\n        self.terminal = sys.stdout\n        self.log = buffer\n\n    def write(self, message):\n        self.terminal.write(message)\n        self.log.write(message)\n\n    def flush(self):\n        # this flush method is needed for python 3 compatibility.\n        pass\n\nclass Capturing(list):\n    def __init__(self, *args, unmute=False, **kwargs):\n        self.unmute = unmute\n        super().__init__(*args, **kwargs)\n\n    def __enter__(self, capture_errors=True): # don\'t put arguments here.\n        self._stdout = sys.stdout\n        self._stringio = StringIO()\n        if self.unmute:\n            sys.stdout = Logger(self._stringio)\n        else:\n            sys.stdout = self._stringio\n\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = None\n    tol = 0\n    estimated_time = 0.42\n    _precomputed_payload = None\n    _computed_answer = None # Internal helper to later get results.\n    weight = 1 # the weight of the question.\n\n    def __init__(self, question=None, *args, **kwargs):\n        if self.tol > 0 and self.testfun is None:\n            self.testfun = self.assertL2Relative\n        elif self.testfun is None:\n            self.testfun = self.assertEqual\n\n        self.name = self.__class__.__name__\n        # self._correct_answer_payload = correct_answer_payload\n        self.question = question\n\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def _safe_get_title(self):\n        if self._precomputed_title is not None:\n            return self._precomputed_title\n        return self.title\n\n    def assertNorm(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed).flat- np.asarray(expected)).flat )\n        nrm = np.sqrt(np.sum( diff ** 2))\n\n        self.error_computed = nrm\n\n        if nrm > tol:\n            print(f"Not equal within tolerance {tol}; norm of difference was {nrm}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        self.error_computed = np.max(diff)\n\n        if np.max(diff) > tol:\n            print(f"Not equal within tolerance {tol=}; deviation was {np.max(diff)=}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol=}, {np.max(diff)=}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        self.error_computed = np.max(np.abs(diff))\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def precomputed_payload(self):\n        return self._precomputed_payload\n\n    def precompute_payload(self):\n        # Pre-compute resources to include in tests (useful for getting around rng).\n        pass\n\n    def compute_answer(self, unmute=False):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False, show_expected=False, show_computed=False,unmute=False, passall=False, silent=False, **kwargs):\n        possible = 1\n        computed = None\n        def show_computed_(computed):\n            print(">>> Your output:")\n            print(computed)\n\n        def show_expected_(expected):\n            print(">>> Expected output (note: may have been processed; read text script):")\n            print(expected)\n\n        correct = self._correct_answer_payload\n        try:\n            if unmute: # Required to not mix together print stuff.\n                print("")\n            computed = self.compute_answer(unmute=unmute)\n        except Exception as e:\n            if not passall:\n                if not silent:\n                    print("\\n=================================================================================")\n                    print(f"When trying to run test class \'{self.name}\' your code threw an error:", e)\n                    show_expected_(correct)\n                    import traceback\n                    print(traceback.format_exc())\n                    print("=================================================================================")\n                return (0, possible)\n\n        if self._computed_answer is None:\n            self._computed_answer = computed\n\n        if show_expected or show_computed:\n            print("\\n")\n        if show_expected:\n            show_expected_(correct)\n        if show_computed:\n            show_computed_(computed)\n        try:\n            if not passall:\n                self.test(computed=computed, expected=correct)\n        except Exception as e:\n            if not silent:\n                print("\\n=================================================================================")\n                print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n                print(e)\n                show_computed_(computed)\n                show_expected_(correct)\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        """\n        Generate output which is to be tested. By default, both text written to the terminal using print(...) as well as return values\n        are send to process_output (see compute_answer below). In other words, the text generated is:\n\n        res = compute_Answer_print()\n        txt = (any terminal output generated above)\n        numbers = (any numbers found in terminal-output txt)\n\n        self.test(process_output(res, txt, numbers), <expected result>)\n\n        :return: Optional values for comparison\n        """\n        raise Exception("Generate output here. The output is passed to self.process_output")\n\n    def process_output(self, res, txt, numbers):\n        return res\n\n    def compute_answer(self, unmute=False):\n        with Capturing(unmute=unmute) as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        s = rm_progress_bar(s) # Remove progress bar.\n        numbers = extract_numbers(s)\n        self._computed_answer = (res, s, numbers)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        ks = list(classdict.keys())\n        for b in bases:\n            ks += b.__ordered__\n        classdict[\'__ordered__\'] = [key for key in ks if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Untitled question"\n    partially_scored = False\n    t_init = 0  # Time spend on initialization (placeholder; set this externally).\n    estimated_time = 0.42\n    has_called_init_ = False\n    _name = None\n    _items = None\n\n    @property\n    def items(self):\n        if self._items == None:\n            self._items = []\n            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)]\n            for I in members:\n                self._items.append( I(question=self))\n        return self._items\n\n    @items.setter\n    def items(self, value):\n        self._items = value\n\n    @property\n    def name(self):\n        if self._name == None:\n            self._name = self.__class__.__name__\n        return self._name #\n\n    @name.setter\n    def name(self, val):\n        self._name = val\n\n    def init(self):\n        # Can be used to set resources relevant for this question instance.\n        pass\n\n    def init_all_item_questions(self):\n        for item in self.items:\n            if not item.question.has_called_init_:\n                item.question.init()\n                item.question.has_called_init_ = True\n\n\nclass Report():\n    title = "report title"\n    version = None\n    questions = []\n    pack_imports = []\n    individual_imports = []\n    nL = 80 # Maximum line width\n\n    @classmethod\n    def reset(cls):\n        for (q,_) in cls.questions:\n            if hasattr(q, \'reset\'):\n                q.reset()\n\n    def _file(self):\n        return inspect.getfile(type(self))\n\n    def _import_base_relative(self):\n        a ="234"\n        b = "234"\n        root_dir = self.pack_imports[0].__path__._path[0]\n        root_dir = os.path.dirname(root_dir)\n        relative_path = os.path.relpath(self._file(), root_dir)\n        return root_dir, relative_path\n\n\n    def __init__(self, strict=False, payload=None):\n        working_directory = os.path.abspath(os.path.dirname(self._file()))\n\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        # self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n\n        if payload is not None:\n            self.set_payload(payload, strict=strict)\n        # else:\n        #     if os.path.isfile(self.computed_answers_file):\n        #         self.set_payload(cache_read(self.computed_answers_file), strict=strict)\n        #     else:\n        #         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."\n        #         if strict:\n        #             raise Exception(s)\n        #         else:\n        #             print(s)\n\n    def main(self, verbosity=1):\n        # Run all tests using standard unittest (nothing fancy).\n        import unittest\n        loader = unittest.TestLoader()\n        for q,_ in self.questions:\n            import time\n            start = time.time() # A good proxy for setup time is to\n            suite = loader.loadTestsFromTestCase(q)\n            unittest.TextTestRunner(verbosity=verbosity).run(suite)\n            total = time.time()              - start\n            q.time = total\n\n    def _setup_answers(self):\n        self.main()  # Run all tests in class just to get that out of the way...\n        report_cache = {}\n        for q, _ in self.questions:\n            if hasattr(q, \'_save_cache\'):\n                q()._save_cache()\n                q._cache[\'time\'] = q.time\n                report_cache[q.__qualname__] = q._cache\n            else:\n                report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\':True}\n        return report_cache\n\n    def set_payload(self, payloads, strict=False):\n        for q, _ in self.questions:\n            q._cache = payloads[q.__qualname__]\n\n            # for item in q.items:\n            #     if q.name not in payloads or item.name not in payloads[q.name]:\n            #         s = f"> Broken resource dictionary submitted to unitgrade for question {q.name} and subquestion {item.name}. Framework will not work."\n            #         if strict:\n            #             raise Exception(s)\n            #         else:\n            #             print(s)\n            #     else:\n            #         item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n            #         item.estimated_time = payloads[q.name][item.name].get("time", 1)\n            #         q.estimated_time = payloads[q.name].get("time", 1)\n            #         if "precomputed" in payloads[q.name][item.name]: # Consider removing later.\n            #             item._precomputed_payload = payloads[q.name][item.name][\'precomputed\']\n            #         try:\n            #             if "title" in payloads[q.name][item.name]: # can perhaps be removed later.\n            #                 item.title = payloads[q.name][item.name][\'title\']\n            #         except Exception as e: # Cannot set attribute error. The title is a function (and probably should not be).\n            #             pass\n            #             # print("bad", e)\n        # self.payloads = payloads\n\n\ndef rm_progress_bar(txt):\n    # More robust version. Apparently length of bar can depend on various factors, so check for order of symbols.\n    nlines = []\n    for l in txt.splitlines():\n        pct = l.find("%")\n        ql = False\n        if pct > 0:\n            i = l.find("|", pct+1)\n            if i > 0 and l.find("|", i+1) > 0:\n                ql = True\n        if not ql:\n            nlines.append(l)\n    return "\\n".join(nlines)\n\ndef extract_numbers(txt):\n    # txt = rm_progress_bar(txt)\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if (\'.\' in a or "e" in a) else int(a) for a in all]\n    if len(all) > 500:\n        print(txt)\n        raise Exception("unitgrade.unitgrade.py: Warning, many numbers!", len(all))\n    return all\n\n\nclass ActiveProgress():\n    def __init__(self, t, start=True, title="my progress bar",show_progress_bar=True):\n        self.t = t\n        self._running = False\n        self.title = title\n        self.dt = 0.1\n        self.n = int(np.round(self.t / self.dt))\n        self.show_progress_bar = show_progress_bar\n\n        # self.pbar = tqdm.tqdm(total=self.n)\n        if start:\n            self.start()\n\n    def start(self):\n        self._running = True\n        if self.show_progress_bar:\n            self.thread = threading.Thread(target=self.run)\n            self.thread.start()\n        self.time_started = time.time()\n\n    def terminate(self):\n        if not self._running:\n            raise Exception("Stopping a stopped progress bar. ")\n        self._running = False\n        if self.show_progress_bar:\n            self.thread.join()\n        if hasattr(self, \'pbar\') and self.pbar is not None:\n            self.pbar.update(1)\n            self.pbar.close()\n            self.pbar=None\n\n        sys.stdout.flush()\n        return time.time() - self.time_started\n\n    def run(self):\n        self.pbar = tqdm.tqdm(total=self.n, file=sys.stdout, position=0, leave=False, desc=self.title, ncols=100,\n                              bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')  # , unit_scale=dt, unit=\'seconds\'):\n\n        for _ in range(self.n-1): # Don\'t terminate completely; leave bar at 99% done until terminate.\n            if not self._running:\n                self.pbar.close()\n                self.pbar = None\n                break\n\n            time.sleep(self.dt)\n            self.pbar.update(1)\n\n\n\nfrom unittest.suite import _isnotsuite\n\nclass MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n    pass\n\ndef instance_call_stack(instance):\n    s = "-".join(map(lambda x: x.__name__, instance.__class__.mro()))\n    return s\n\ndef get_class_that_defined_method(meth):\n    for cls in inspect.getmro(meth.im_class):\n        if meth.__name__ in cls.__dict__:\n            return cls\n    return None\n\ndef caller_name(skip=2):\n    """Get a name of a caller in the format module.class.method\n\n       `skip` specifies how many levels of stack to skip while getting caller\n       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.\n\n       An empty string is returned if skipped levels exceed stack height\n    """\n    stack = inspect.stack()\n    start = 0 + skip\n    if len(stack) < start + 1:\n      return \'\'\n    parentframe = stack[start][0]\n\n    name = []\n    module = inspect.getmodule(parentframe)\n    # `modname` can be None when frame is executed directly in console\n    # TODO(techtonik): consider using __main__\n    if module:\n        name.append(module.__name__)\n    # detect classname\n    if \'self\' in parentframe.f_locals:\n        # I don\'t know any way to detect call from the object method\n        # XXX: there seems to be no way to detect static method call - it will\n        #      be just a function call\n        name.append(parentframe.f_locals[\'self\'].__class__.__name__)\n    codename = parentframe.f_code.co_name\n    if codename != \'<module>\':  # top level usually\n        name.append( codename ) # function or a method\n\n    ## Avoid circular refs and frame leaks\n    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack\n    del parentframe, stack\n\n    return ".".join(name)\n\ndef get_class_from_frame(fr):\n      import inspect\n      args, _, _, value_dict = inspect.getargvalues(fr)\n      # we check the first parameter for the frame function is\n      # named \'self\'\n      if len(args) and args[0] == \'self\':\n            # in that case, \'self\' will be referenced in value_dict\n            instance = value_dict.get(\'self\', None)\n            if instance:\n                  # return its class\n                  # isinstance(instance, Testing) # is the actual class instance.\n\n                  return getattr(instance, \'__class__\', None)\n      # return None otherwise\n      return None\n\nfrom typing import Any\nimport inspect, gc\n\ndef giveupthefunc():\n    frame = inspect.currentframe()\n    code  = frame.f_code\n    globs = frame.f_globals\n    functype = type(lambda: 0)\n    funcs = []\n    for func in gc.get_referrers(code):\n        if type(func) is functype:\n            if getattr(func, "__code__", None) is code:\n                if getattr(func, "__globals__", None) is globs:\n                    funcs.append(func)\n                    if len(funcs) > 1:\n                        return None\n    return funcs[0] if funcs else None\n\n\nfrom collections import defaultdict\n\nclass UTextResult(unittest.TextTestResult):\n    nL = 80\n    show_progress_bar = True\n    def __init__(self, stream, descriptions, verbosity):\n        super().__init__(stream, descriptions, verbosity)\n        self.successes = []\n\n    def printErrors(self) -> None:\n        # if self.dots or self.showAll:\n        #     self.stream.writeln()\n        # if hasattr(self, \'cc\'):\n        #     self.cc.terminate()\n        # self.cc_terminate(success=False)\n        self.printErrorList(\'ERROR\', self.errors)\n        self.printErrorList(\'FAIL\', self.failures)\n\n    def addError(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n\n    def addFailure(self, test, err):\n        super(unittest.TextTestResult, self).addFailure(test, err)\n        self.cc_terminate(success=False)\n        # if self.showAll:\n        #     self.stream.writeln("FAIL")\n        # elif self.dots:\n        #     self.stream.write(\'F\')\n        #     self.stream.flush()\n\n    def addSuccess(self, test: unittest.case.TestCase) -> None:\n        # super().addSuccess(test)\n        self.successes.append(test)\n        # super().addSuccess(test)\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        self.cc_terminate()\n\n\n\n    def cc_terminate(self, success=True):\n        if self.show_progress_bar or True:\n            tsecs = np.round(self.cc.terminate(), 2)\n            sys.stdout.flush()\n            ss = self.item_title_print\n            print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(ss))), end="")\n            # current = 1\n            # possible = 1\n            # current == possible\n            ss = "PASS" if success else "FAILED"\n            if tsecs >= 0.1:\n                ss += " (" + str(tsecs) + " seconds)"\n            print(ss)\n\n\n    def startTest(self, test):\n        # super().startTest(test)\n        self.testsRun += 1\n        # print("Starting the test...")\n        # show_progress_bar = True\n        n = 1\n        j = 1\n        item_title = self.getDescription(test)\n        item_title = item_title.split("\\n")[0]\n\n        # test.countTestCases()\n\n        self.item_title_print = "*** q%i.%i) %s" % (n + 1, j + 1, item_title)\n        estimated_time = 10\n        nL = 80\n        #\n        if self.show_progress_bar or True:\n            self.cc = ActiveProgress(t=estimated_time, title=self.item_title_print, show_progress_bar=self.show_progress_bar)\n        else:\n            print(self.item_title_print + (\'.\' * max(0, nL - 4 - len(self.item_title_print))), end="")\n\n        self._test = test\n\n    def _setupStdout(self):\n        if self._previousTestClass == None:\n            total_estimated_time = 2\n            if hasattr(self.__class__, \'q_title_print\'):\n                q_title_print = self.__class__.q_title_print\n            else:\n                q_title_print = "<unnamed test. See unitgrade.py>"\n\n            # q_title_print = "some printed title..."\n            cc = ActiveProgress(t=total_estimated_time, title=q_title_print, show_progress_bar=self.show_progress_bar)\n            self.cc = cc\n\n    def _restoreStdout(self): # Used when setting up the test.\n        if self._previousTestClass == None:\n            q_time = self.cc.terminate()\n            q_time = np.round(q_time, 2)\n            sys.stdout.flush()\n            print(self.cc.title, end="")\n            # start = 10\n            # q_time = np.round(time.time() - start, 2)\n            nL = 80\n            print(" " * max(0, nL - len(self.cc.title)) + (\n                " (" + str(q_time) + " seconds)" if q_time >= 0.1 else ""))  # if q.name in report.payloads else "")\n            print("=" * nL)\n\nfrom unittest.runner import _WritelnDecorator\nfrom io import StringIO\n\nclass UTextTestRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        from io import StringIO\n        stream = StringIO()\n        super().__init__(*args, stream=stream, **kwargs)\n\n    def _makeResult(self):\n        # stream = self.stream # not you!\n        stream = sys.stdout\n        stream = _WritelnDecorator(stream)\n        return self.resultclass(stream, self.descriptions, self.verbosity)\n\ndef wrapper(foo):\n    def magic(self):\n        s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\n        # print(s)\n        foo(self)\n    magic.__doc__ = foo.__doc__\n    return magic\n\nfrom functools import update_wrapper, _make_key, RLock\nfrom collections import namedtuple\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ndef cache(foo, typed=False):\n    """ Magic cache wrapper\n    https://github.com/python/cpython/blob/main/Lib/functools.py\n    """\n    maxsize = None\n    def wrapper(self, *args, **kwargs):\n        key = self.cache_id() + ("cache", _make_key(args, kwargs, typed))\n        if not self._cache_contains(key):\n            value = foo(self, *args, **kwargs)\n            self._cache_put(key, value)\n        else:\n            value = self._cache_get(key)\n        return value\n    return wrapper\n\n\nclass UTestCase(unittest.TestCase):\n    _outcome = None # A dictionary which stores the user-computed outcomes of all the tests. This differs from the cache.\n    _cache = None  # Read-only cache.\n    _cache2 = None # User-written cache\n\n    @classmethod\n    def reset(cls):\n        cls._outcome = None\n        cls._cache = None\n        cls._cache2 = None\n\n    def _get_outcome(self):\n        if not (self.__class__, \'_outcome\') or self.__class__._outcome == None:\n            self.__class__._outcome = {}\n        return self.__class__._outcome\n\n    def _callTestMethod(self, testMethod):\n        t = time.time()\n        res = testMethod()\n        elapsed = time.time() - t\n        # if res == None:\n        #     res = {}\n        # res[\'time\'] = elapsed\n        sd = self.shortDescription()\n        self._cache_put( (self.cache_id(), \'title\'), self._testMethodName if sd == None else sd)\n        # self._test_fun_output = res\n        self._get_outcome()[self.cache_id()] = res\n        self._cache_put( (self.cache_id(), "time"), elapsed)\n\n\n    # This is my base test class. So what is new about it?\n    def cache_id(self):\n        c = self.__class__.__qualname__\n        m = self._testMethodName\n        return (c,m)\n\n    def unique_cache_id(self):\n        k0 = self.cache_id()\n        key = ()\n        for i in itertools.count():\n            key = k0 + (i,)\n            if not self._cache2_contains(key):\n                break\n        return key\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._load_cache()\n        self.cache_indexes = defaultdict(lambda: 0)\n\n    def _ensure_cache_exists(self):\n        if not hasattr(self.__class__, \'_cache\') or self.__class__._cache == None:\n            self.__class__._cache = dict()\n        if not hasattr(self.__class__, \'_cache2\') or self.__class__._cache2 == None:\n            self.__class__._cache2 = dict()\n\n    def _cache_get(self, key, default=None):\n        self._ensure_cache_exists()\n        return self.__class__._cache.get(key, default)\n\n    def _cache_put(self, key, value):\n        self._ensure_cache_exists()\n        self.__class__._cache2[key] = value\n\n    def _cache_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache\n\n    def _cache2_contains(self, key):\n        self._ensure_cache_exists()\n        return key in self.__class__._cache2\n\n    def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n        id = self.unique_cache_id()\n        if not self._cache_contains(id):\n            print("Warning, framework missing key", id)\n\n        self.assertEqual(first, self._cache_get(id, first), msg)\n        self._cache_put(id, first)\n\n    def _cache_file(self):\n        return os.path.dirname(inspect.getfile(self.__class__) ) + "/unitgrade/" + self.__class__.__name__ + ".pkl"\n\n    def _save_cache(self):\n        # get the class name (i.e. what to save to).\n        cfile = self._cache_file()\n        if not os.path.isdir(os.path.dirname(cfile)):\n            os.makedirs(os.path.dirname(cfile))\n\n        if hasattr(self.__class__, \'_cache2\'):\n            with open(cfile, \'wb\') as f:\n                pickle.dump(self.__class__._cache2, f)\n\n    # But you can also set cache explicitly.\n    def _load_cache(self):\n        if self._cache != None: # Cache already loaded. We will not load it twice.\n            return\n            # raise Exception("Loaded cache which was already set. What is going on?!")\n        cfile = self._cache_file()\n        print("Loading cache from", cfile)\n        if os.path.exists(cfile):\n            with open(cfile, \'rb\') as f:\n                data = pickle.load(f)\n                self.__class__._cache = data\n        else:\n            print("Warning! data file not found", cfile)\n\ndef hide(func):\n    return func\n\ndef makeRegisteringDecorator(foreignDecorator):\n    """\n        Returns a copy of foreignDecorator, which is identical in every\n        way(*), except also appends a .decorator property to the callable it\n        spits out.\n    """\n    def newDecorator(func):\n        # Call to newDecorator(method)\n        # Exactly like old decorator, but output keeps track of what decorated it\n        R = foreignDecorator(func)  # apply foreignDecorator, like call to foreignDecorator(method) would have done\n        R.decorator = newDecorator  # keep track of decorator\n        # R.original = func         # might as well keep track of everything!\n        return R\n\n    newDecorator.__name__ = foreignDecorator.__name__\n    newDecorator.__doc__ = foreignDecorator.__doc__\n    # (*)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\n    return newDecorator\n\nhide = makeRegisteringDecorator(hide)\n\ndef methodsWithDecorator(cls, decorator):\n    """\n        Returns all methods in CLS with DECORATOR as the\n        outermost decorator.\n\n        DECORATOR must be a "registering decorator"; one\n        can make any decorator "registering" via the\n        makeRegisteringDecorator function.\n\n        import inspect\n        ls = list(methodsWithDecorator(GeneratorQuestion, deco))\n        for f in ls:\n            print(inspect.getsourcelines(f) ) # How to get all hidden questions.\n    """\n    for maybeDecorated in cls.__dict__.values():\n        if hasattr(maybeDecorated, \'decorator\'):\n            if maybeDecorated.decorator == decorator:\n                print(maybeDecorated)\n                yield maybeDecorated\n\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\n\nimport inspect\nimport os\nimport argparse\nimport sys\nimport time\nimport threading # don\'t import Thread bc. of minify issue.\nimport tqdm # don\'t do from tqdm import tqdm because of minify-issue\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python assignment1_dp.py\n\nTo run only question 2 or question 2.1\n\n> python assignment1_dp.py -q 2\n> python assignment1_dp.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (e.g.: -q 2)\')\nparser.add_argument(\'--showexpected\',  action="store_true",  help=\'Show the expected/desired result\')\nparser.add_argument(\'--showcomputed\',  action="store_true",  help=\'Show the answer your code computes\')\nparser.add_argument(\'--unmute\',  action="store_true",  help=\'Show result of print(...) commands in code\')\nparser.add_argument(\'--passall\',  action="store_true",  help=\'Automatically pass all tests. Useful when debugging.\')\n\ndef evaluate_report_student(report, question=None, qitem=None, unmute=None, passall=None, ignore_missing_file=False, show_tol_err=False):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n\n    if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file:\n        raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation")\n\n    if unmute is None:\n        unmute = args.unmute\n    if passall is None:\n        passall = args.passall\n\n    results, table_data = evaluate_report(report, question=question, show_progress_bar=not unmute, qitem=qitem, verbose=False, passall=passall, show_expected=args.showexpected, show_computed=args.showcomputed,unmute=unmute,\n                                          show_tol_err=show_tol_err)\n\n\n    # try:  # For registering stats.\n    #     import unitgrade_private\n    #     import irlc.lectures\n    #     import xlwings\n    #     from openpyxl import Workbook\n    #     import pandas as pd\n    #     from collections import defaultdict\n    #     dd = defaultdict(lambda: [])\n    #     error_computed = []\n    #     for k1, (q, _) in enumerate(report.questions):\n    #         for k2, item in enumerate(q.items):\n    #             dd[\'question_index\'].append(k1)\n    #             dd[\'item_index\'].append(k2)\n    #             dd[\'question\'].append(q.name)\n    #             dd[\'item\'].append(item.name)\n    #             dd[\'tol\'].append(0 if not hasattr(item, \'tol\') else item.tol)\n    #             error_computed.append(0 if not hasattr(item, \'error_computed\') else item.error_computed)\n    #\n    #     qstats = report.wdir + "/" + report.name + ".xlsx"\n    #\n    #     if os.path.isfile(qstats):\n    #         d_read = pd.read_excel(qstats).to_dict()\n    #     else:\n    #         d_read = dict()\n    #\n    #     for k in range(1000):\n    #         key = \'run_\'+str(k)\n    #         if key in d_read:\n    #             dd[key] = list(d_read[\'run_0\'].values())\n    #         else:\n    #             dd[key] = error_computed\n    #             break\n    #\n    #     workbook = Workbook()\n    #     worksheet = workbook.active\n    #     for col, key in enumerate(dd.keys()):\n    #         worksheet.cell(row=1, column=col+1).value = key\n    #         for row, item in enumerate(dd[key]):\n    #             worksheet.cell(row=row+2, column=col+1).value = item\n    #\n    #     workbook.save(qstats)\n    #     workbook.close()\n    #\n    # except ModuleNotFoundError as e:\n    #     s = 234\n    #     pass\n\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    gfile = os.path.basename(fr)[:-3] + "_grade.py"\n    if os.path.exists(gfile):\n        print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n        print(">>>", gfile)\n        print("In the same manner as you ran this file.")\n\n\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n\nclass UnitgradeTextRunner(unittest.TextTestRunner):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass SequentialTestLoader(unittest.TestLoader):\n    def getTestCaseNames(self, testCaseClass):\n        test_names = super().getTestCaseNames(testCaseClass)\n        testcase_methods = list(testCaseClass.__dict__.keys())\n        test_names.sort(key=testcase_methods.index)\n        return test_names\n\ndef evaluate_report(report, question=None, qitem=None, passall=False, verbose=False,  show_expected=False, show_computed=False,unmute=False, show_help_flag=True, silent=False,\n                    show_progress_bar=True,\n                    show_tol_err=False,\n                    big_header=True):\n\n    now = datetime.now()\n    if big_header:\n        ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n        b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    else:\n        b = "Unitgrade"\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    s = report.title\n    if hasattr(report, "version") and report.version is not None:\n        s += " version " + report.version\n    print("Evaluating " + s, "(use --help for options)" if show_help_flag else "")\n    # print(f"Loaded answers from: ", report.computed_answers_file, "\\n")\n    table_data = []\n    nL = 80\n    t_start = time.time()\n    score = {}\n    loader = SequentialTestLoader()\n\n    for n, (q, w) in enumerate(report.questions):\n        # q = q()\n        q_hidden = False\n        # q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n        suite = loader.loadTestsFromTestCase(q)\n        # print(suite)\n        qtitle = q.__name__\n        # qtitle = q.title if hasattr(q, "title") else q.id()\n        # q.title = qtitle\n        q_title_print = "Question %i: %s"%(n+1, qtitle)\n        print(q_title_print, end="")\n        q.possible = 0\n        q.obtained = 0\n        q_ = {} # Gather score in this class.\n        # unittest.Te\n        # q_with_outstanding_init = [item.question for item in q.items if not item.question.has_called_init_]\n        UTextResult.q_title_print = q_title_print # Hacky\n        UTextResult.show_progress_bar = show_progress_bar # Hacky.\n\n        res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\n        # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite)\n        z = 234\n        # for j, item in enumerate(q.items):\n        #     if qitem is not None and question is not None and j+1 != qitem:\n        #         continue\n        #\n        #     if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles.\n        #         # if not item.question.has_called_init_:\n        #         start = time.time()\n        #\n        #         cc = None\n        #         if show_progress_bar:\n        #             total_estimated_time = q.estimated_time # Use this. The time is estimated for the q itself.  # sum( [q2.estimated_time for q2 in q_with_outstanding_init] )\n        #             cc = ActiveProgress(t=total_estimated_time, title=q_title_print)\n        #         from unitgrade import Capturing # DON\'T REMOVE THIS LINE\n        #         with eval(\'Capturing\')(unmute=unmute):  # Clunky import syntax is required bc. of minify issue.\n        #             try:\n        #                 for q2 in q_with_outstanding_init:\n        #                     q2.init()\n        #                     q2.has_called_init_ = True\n        #\n        #                 # item.question.init()  # Initialize the question. Useful for sharing resources.\n        #             except Exception as e:\n        #                 if not passall:\n        #                     if not silent:\n        #                         print(" ")\n        #                         print("="*30)\n        #                         print(f"When initializing question {q.title} the initialization code threw an error")\n        #                         print(e)\n        #                         print("The remaining parts of this question will likely fail.")\n        #                         print("="*30)\n        #\n        #         if show_progress_bar:\n        #             cc.terminate()\n        #             sys.stdout.flush()\n        #             print(q_title_print, end="")\n        #\n        #         q_time =np.round(  time.time()-start, 2)\n        #\n        #         print(" "* max(0,nL - len(q_title_print) ) + (" (" + str(q_time) + " seconds)" if q_time >= 0.1 else "") ) # if q.name in report.payloads else "")\n        #         print("=" * nL)\n        #         q_with_outstanding_init = None\n        #\n        #     # item.question = q # Set the parent question instance for later reference.\n        #     item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title)\n        #\n        #     if show_progress_bar:\n        #         cc = ActiveProgress(t=item.estimated_time, title=item_title_print)\n        #     else:\n        #         print(item_title_print + ( \'.\'*max(0, nL-4-len(ss)) ), end="")\n        #     hidden = issubclass(item.__class__, Hidden)\n        #     # if not hidden:\n        #     #     print(ss, end="")\n        #     # sys.stdout.flush()\n        #     start = time.time()\n        #\n        #     (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent)\n        #     q_[j] = {\'w\': item.weight, \'possible\': possible, \'obtained\': current, \'hidden\': hidden, \'computed\': str(item._computed_answer), \'title\': item.title}\n        #     tsecs = np.round(time.time()-start, 2)\n        #     if show_progress_bar:\n        #         cc.terminate()\n        #         sys.stdout.flush()\n        #         print(item_title_print + (\'.\' * max(0, nL - 4 - len(ss))), end="")\n        #\n        #     if not hidden:\n        #         ss = "PASS" if current == possible else "*** FAILED"\n        #         if tsecs >= 0.1:\n        #             ss += " ("+ str(tsecs) + " seconds)"\n        #         print(ss)\n\n        # ws, possible, obtained = upack(q_)\n\n        possible = res.testsRun\n        obtained = len(res.successes)\n\n        assert len(res.successes) +  len(res.errors) + len(res.failures) == res.testsRun\n\n        # possible = int(ws @ possible)\n        # obtained = int(ws @ obtained)\n        # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0\n\n        obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'items\': q_, \'title\': qtitle}\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n\n    dt = int(time.time()-t_start)\n    minutes = dt//60\n    seconds = dt - minutes*60\n    plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "")\n\n    print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")")\n\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\nimport os\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_imports(imp):\n    resources = {}\n    m = imp\n    # for m in pack_imports:\n    # print(f"*** {m.__name__}")\n    f = m.__file__\n    # dn = os.path.dirname(f)\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = str(__import__(m.__name__.split(\'.\')[0]).__path__)\n    if m.__class__.__name__ == \'module\' and False:\n        top_package = os.path.dirname(m.__file__)\n        module_import = True\n    else:\n        top_package = __import__(m.__name__.split(\'.\')[0]).__path__._path[0]\n        module_import = False\n\n    # top_package = os.path.dirname(__import__(m.__name__.split(\'.\')[0]).__file__)\n    # top_package = os.path.dirname(top_package)\n    import zipfile\n    # import strea\n    # zipfile.ZipFile\n    import io\n    # file_like_object = io.BytesIO(my_zip_data)\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, \'w\') as zip:\n        # zip.write()\n        for root, dirs, files in os.walk(top_package):\n            for file in files:\n                if file.endswith(".py"):\n                    fpath = os.path.join(root, file)\n                    v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package))\n                    zip.write(fpath, v)\n\n    resources[\'zipfile\'] = zip_buffer.getvalue()\n    resources[\'top_package\'] = top_package\n    resources[\'module_import\'] = module_import\n    return resources, top_package\n\n    if f.endswith("__init__.py"):\n        for root, dirs, files in os.walk(os.path.dirname(f)):\n            for file in files:\n                if file.endswith(".py"):\n                    # print(file)\n                    # print()\n                    v = os.path.relpath(os.path.join(root, file), top_package)\n                    with open(os.path.join(root, file), \'r\') as ff:\n                        resources[v] = ff.read()\n    else:\n        v = os.path.relpath(f, top_package)\n        with open(f, \'r\') as ff:\n            resources[v] = ff.read()\n    return resources\n\nimport argparse\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Use this script to get the score of your report. Example:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nFor instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to \'Documents/` and run:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""", formatter_class=argparse.RawTextHelpFormatter)\nparser.add_argument(\'--noprogress\',  action="store_true",  help=\'Disable progress bars\')\nparser.add_argument(\'--autolab\',  action="store_true",  help=\'Show Autolab results\')\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = report.nL\n    args = parser.parse_args()\n    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,\n                                          show_progress_bar=not args.noprogress,\n                                          big_header=not args.autolab)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n\n    sources = {}\n\n    if not args.autolab:\n        if len(report.individual_imports) > 0:\n            print("By uploading the .token file, you verify the files:")\n            for m in report.individual_imports:\n                print(">", m.__file__)\n            print("Are created/modified individually by you in agreement with DTUs exam rules")\n            report.pack_imports += report.individual_imports\n\n        if len(report.pack_imports) > 0:\n            print("Including files in upload...")\n            for k, m in enumerate(report.pack_imports):\n                nimp, top_package = gather_imports(m)\n                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n                nimp[\'report_relative_location\'] = report_relative_location\n                nimp[\'name\'] = m.__name__\n                sources[k] = nimp\n                # if len([k for k in nimp if k not in sources]) > 0:\n                print(f"*** {m.__name__}")\n                # sources = {**sources, **nimp}\n    results[\'sources\'] = sources\n\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    vstring = "_v"+report.version if report.version is not None else ""\n\n    token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring)\n    token = os.path.join(output_dir, token)\n    with open(token, \'wb\') as f:\n        pickle.dump(results, f)\n\n    if not args.autolab:\n        print(" ")\n        print("To get credit for your results, please upload the single file: ")\n        print(">", token)\n        print("To campusnet without any modifications.")\n\n        # print("Now time for some autolab fun")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    pl = pickle.loads(bytes.fromhex(payload))\n    report = eval(name)(payload=pl, strict=True)\n    # report.set_payload(pl)\n    return report\n\n\n__version__ = "0.9.0"\n\nfrom cs101.homework1 import reverse_list, add\nimport unittest\n\nclass Week1(unittest.TestCase):\n    def test_add(self):\n        self.assertEqual(add(2,2), 4)\n        self.assertEqual(add(-100, 5), -95)\n\n    def test_reverse(self):\n        self.assertEqual(reverse_list([1,2,3]), [3,2,1])\n\nimport cs101\nclass Report1(Report):\n    title = "CS 101 Report 1"\n    questions = [(Week1, 10)]  # Include a single question for 10 credits.\n    pack_imports = [cs101]'
 report1_payload = '8004953f000000000000007d948c055765656b31947d948c2c6e6f20636163686520736565205f73657475705f616e737765727320696e20756e69746772616465322e7079948873732e'
 name="Report1"
 
 report = source_instantiate(name, report1_source, report1_payload)
 output_dir = os.path.dirname(__file__)
-gather_upload_to_campusnet(report, output_dir)
\ No newline at end of file
+gather_upload_to_campusnet(report, output_dir)
diff --git a/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-39.pyc b/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-39.pyc
index 7a413eb7b3090c5d5d4bbccbf251cfec013c941c..5c2e1ae6b68171c81e7ddf38fb9d45d3f1968a79 100644
GIT binary patch
delta 1815
zcmX>l`AI=Lk(ZZ?fq{Wx(`>CoA29}o#~=<eW@TVtaA06yDBd$syC9Gug(Zh4iYJvP
zi#LTcg)5aOi!X({mpO_*l|M@$g(rnKg)fD_mzj|P%oj-EO%a6fQ^Zn)QiNL=qXbh#
zQbb!AqJ&adf*CZ$H*OMVt*;VwElbQPO)N=OsLU@dQYcC-$S*3<OZ>&izz|xTs!)=V
zS*%c;T$EW*qEM2rke*tiP?C|VP@J4!l&X-QhN4NqwIVUMAScy|i_1=-pt2+*KM!i2
zVSIW~VoIuBK_wTLTV`HjPEMtcLSBAJYP~{9Mq-IVCW=7{nZ*hPiA5zK^Ai<v^HWN5
zQWZ1`5|gtN(^EBd6cY1Nz>a{p73R94)WXutqEv)du*pzeI$#aDxeECOC7Jno3TgR8
zU>DZs=PBeSRw^W=DwGzdrj_RCak=FeDP-mqmn7yTr|N)Q2J$b|s<h0URFLa3^Aue2
zlS^|`^Gb^Klk-c9ic{mEHt9o@8pkK+gTkT&<Y|bL6HpZ<fDEcn1Un-?PXVeO?9e=g
z<c!3;^i+kE%%arflKi4dP}r*@tV{rhK~ZTQa!~8$Dxlh+2XTra7gup=szOFdNkOrd
zzJ5x6a<LvHcJ%V=i_-Ot^(^&Ei!<}m^^<c`ax(K$^)gCwbGS5_Z*ixjLZUu1KkpV>
zYC&dBe)=u`wEUvn#FCQKqWI*T#Ny&A9$nqM{DPwV^rF<_;#*wBCHY0E@g+s2sa2vb
znZ=1oIjIUTS%sv;qGC;^TWpERplG<oQlF8UQ&7dJtD9I_lAn{9R3#FekzcOh2;nId
zr52awloTg3A!Q381_lOB1_lOaP|o<o$iPs;ki}5TRKmD`X(2-`a|v?|Lkc4pGSx6=
zvG6d|FlVvyFr+Z|vd1vhvedGcu%)mxGcq!iu%)op*D#4NKsjs>x`e%kwV4sb&Sa=%
z18JVXSgcgSQNxhM+02;2p3PFUs)Q|t1Ec~Z=E4vwP|IGzmcm)Xp2gM7#K=(CP{NkN
z1(9cA$l?a6GG}07i01~I!VNKH0%MU<Nj+N%H%MU#PYpvBcQd04Lu^P4Q!Pg=dkJq1
zdoyDUQ!QsLR|#Jh{{n#$!39D!3|Ybp8Ed!}GSza|aMy6A@YpcauxE+XaHjCuFw}6?
zu+?zbfPBUW5}nO3mkDHg;h7q?8kQ7(8-^Oj6oyiU35-QHC437+7cyklGJ=fE5=jvd
zXQ<_=VM!5`WQbv^<*ntbVaO6oVJKysz*v-(0<u_4oFR`XMYxtfMWlwOhPQ^VhChu(
zjG<Nl>^$)r#u|ZzOtk_f5;X!TqRmYGGPQywk_)72SZV|pGBHkWWEOGSWx&7)hLH@6
z;CPw9So{ttKEyz-1qY=7C?HGNQp7<bDGb33ni76)A)0KH_p_VH-x4g&$S;qFrK<R(
z#G+eVNtx;K8L5dWsYRQ=vUe~VOW$J3^SQ-VkXTflT6BvGOvfh{r56{0Qd1NcBF#pz
zLsQ@6X3n{cLX#D_BurR9DsQnv6W}eb5>PshPf09EECMCGA}t06hFgr;Q7n0xxdlbS
z3=9lKj+4{5QuRYX$r3~`FtRanF!C_6F|z&RVg|7|7<rgkm^c{u7&#cZz@z}v<d0m!
zvP@iz985e+Jd9FI0*ri&Jd8R_9E>2y#aI+SS)RMa7G&Tp)|~vrl;T^gNtGq3#kbhg
zit=+aQY$oBi=04?;e+P(_~QK1qU6+~M35j;K~7QF<Okep(mfz|ae(p>qW}mp6=g9n
zFlaLRX$ntP<k4pgm>kF>k^;7et28gO1YBSn74d<5W(YEhBe5tQ9Ei909E;MyWnTa!
zY(;_+%R^EtN<30?3fy3YLy;#)6Hj7F3MkOQ%8TMbhGk7|<56Q&o4l4sir<ujk%Non
N8ymL(qW}|^1pqbM2oL}O

delta 895
zcmeyQa7t1;k(ZZ?fq{V`+D$X@q#y&sV-N=!GczzSI503U6gN%OE)eHU;Y{IbVT|HQ
z;ZEUcVTj^QVF_l?<lFd0oV7lg2}z$I0|NsG0|SFINb4C!28I;ITBZ_)8io`mFl0_)
z>1B;!s%5TaDPc@uZDwR-C}B)tt6>shfU?;kbO}=pOEV*w&0NEf#nQ}}!ja8V)KS8i
z!U+-uiMcSu3e?uKmN2Gp)v#u<HZw6Y6vmY>rf@?PurOq?fs~muFfqilWir&Vm4J+5
zgBUe|u?XY@9+27+_8NvPwq`~bhS-o8rdsw|))I~y)@H^Srdp0#&JxZnt_9pBJPUYh
z7_#^lGS+Y|WUA$=;i}<C;k99?uVKyNui;4Hvtg*=s9~#Nw*mQxA0#@PVJ;KM_`-EH
zY&Fa&0yYdaj42GI3=<fO<VrXf2rOi%Wds?S#h)T5&QQx;!<-@{$q>U-%To)oN-%|?
zlyL%Mkxz<n4MUcoI71#&ibySAif9dY4Nnbk4PP3I7(=ZxKiGXjHH<a<3z=&9ON49q
zQ^cB?`ekYbN<<cj)-cxyOittyx1VFczzBws4B$YA1->}QiQq8g2Zdk>V~PYwB!wZE
zK~vK2WdH*MgIkCu%j8{LW}ErBI~du-ia;?w`4rDwcfnh1MX3e(MI}*O`K2WVr6utx
znMJo4^KP*hr52awloa3MDoISrNsUiQEJ-ZVVqjn>vSDCgxW$+q#gdnqTTmnn60(}S
zg*P?W4U}X&Kxv7Qg;9)=gOLY>nfVww7`YfhqyQ5OqW~iZqYxtxSe}cKgGq>qg9SwM
zF!3--F$pm8G4e3#FmZtO6opK#;A>&@n*5hfR^A$9Eg!_OhVjMurA5i9MPVR8rh=Rz
zpUIZ|YSI-T7l2YP1ET;4G8M%$FfeE``f2h_uHx4h1G(@POIC4yUXdio-5{k!UX%Cp
jOR^V*fCS<vD+#E{Nii@m@GycP$Q^LZ#KFR0#$^Nm2dcIk

diff --git a/unitgrade_private2/hidden_gather_upload.py b/unitgrade_private2/hidden_gather_upload.py
index 3ff0f7d..0fb11d8 100644
--- a/unitgrade_private2/hidden_gather_upload.py
+++ b/unitgrade_private2/hidden_gather_upload.py
@@ -66,38 +66,55 @@ def gather_imports(imp):
             resources[v] = ff.read()
     return resources
 
+import argparse
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Use this script to get the score of your report. Example:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+For instance, if the report file is in Documents/course_package/report3_complete.py, and `course_package` is a python package, then change directory to 'Documents/` and run:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""", formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('--noprogress',  action="store_true",  help='Disable progress bars')
+parser.add_argument('--autolab',  action="store_true",  help='Show Autolab results')
 
 def gather_upload_to_campusnet(report, output_dir=None):
-    n = 80
-    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True)
+    n = report.nL
+    args = parser.parse_args()
+    results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
+                                          show_progress_bar=not args.noprogress,
+                                          big_header=not args.autolab)
     print(" ")
     print("="*n)
     print("Final evaluation")
     print(tabulate(table_data))
     # also load the source code of missing files...
 
-    if len(report.individual_imports) > 0:
-        print("By uploading the .token file, you verify the files:")
-        for m in report.individual_imports:
-            print(">", m.__file__)
-        print("Are created/modified individually by you in agreement with DTUs exam rules")
-        report.pack_imports += report.individual_imports
-
     sources = {}
-    if len(report.pack_imports) > 0:
-        print("Including files in upload...")
-        for k, m in enumerate(report.pack_imports):
-            nimp, top_package = gather_imports(m)
-            report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
-            nimp['report_relative_location'] = report_relative_location
-            nimp['name'] = m.__name__
-            sources[k] = nimp
-            # if len([k for k in nimp if k not in sources]) > 0:
-            print(f"*** {m.__name__}")
-            # sources = {**sources, **nimp}
-    results['sources'] = sources
 
-    # json_str = json.dumps(results, indent=4)
+    if not args.autolab:
+        if len(report.individual_imports) > 0:
+            print("By uploading the .token file, you verify the files:")
+            for m in report.individual_imports:
+                print(">", m.__file__)
+            print("Are created/modified individually by you in agreement with DTUs exam rules")
+            report.pack_imports += report.individual_imports
+
+        if len(report.pack_imports) > 0:
+            print("Including files in upload...")
+            for k, m in enumerate(report.pack_imports):
+                nimp, top_package = gather_imports(m)
+                report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)
+                nimp['report_relative_location'] = report_relative_location
+                nimp['name'] = m.__name__
+                sources[k] = nimp
+                # if len([k for k in nimp if k not in sources]) > 0:
+                print(f"*** {m.__name__}")
+                # sources = {**sources, **nimp}
+    results['sources'] = sources
 
     if output_dir is None:
         output_dir = os.getcwd()
@@ -112,10 +129,13 @@ def gather_upload_to_campusnet(report, output_dir=None):
     with open(token, 'wb') as f:
         pickle.dump(results, f)
 
-    print(" ")
-    print("To get credit for your results, please upload the single file: ")
-    print(">", token)
-    print("To campusnet without any modifications.")
+    if not args.autolab:
+        print(" ")
+        print("To get credit for your results, please upload the single file: ")
+        print(">", token)
+        print("To campusnet without any modifications.")
+
+        # print("Now time for some autolab fun")
 
 def source_instantiate(name, report1_source, payload):
     eval("exec")(report1_source, globals())
-- 
GitLab