From acc45dbea61cd2587c5fd4c9db77d036edc61672 Mon Sep 17 00:00:00 2001 From: Tue Herlau <tuhe@dtu.dk> Date: Thu, 2 Sep 2021 14:40:53 +0200 Subject: [PATCH] Updates --- LICENSE | 19 + README.md | 40 +- autolab/__pycache__/autolab.cpython-38.pyc | Bin 5899 -> 0 bytes autolab/report_autolab.py | 0 build.md | 18 + cs202courseware/ascimenu.py | 43 ++ cs202courseware/ug2report1.py | 14 +- cs202courseware/ug2report1_nohidden.py | 13 +- dist/unitgrade-devel-0.0.1.tar.gz | Bin 0 -> 17133 bytes dist/unitgrade_devel-0.0.1-py3-none-any.whl | Bin 0 -> 18848 bytes .../docker_tango_python/Dockerfile | 0 .../docker_tango_python/requirements.txt | 1 + .../unitgrade-docker/Dockerfile | 4 +- .../home/cs103/Report3_handin_5_of_30.token | Bin 0 -> 113733 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 922 -> 923 bytes .../report3_complete_grade.cpython-38.pyc | Bin 0 -> 50864 bytes .../unitgrade-docker/home}/cs103/homework1.py | 0 .../unitgrade-docker/home}/cs103/report3.py | 6 +- .../home/cs103/report3_complete_grade.py | 338 ++++++++++++ .../home/cs103/report3_grade.py | 340 ++++++++++++ .../unitgrade-docker/requirements.txt | 1 + .../unitgrade-docker/tmp/cs103/homework1.py | 21 + .../unitgrade-docker/tmp/cs103/report3.py | 29 + .../tmp/cs103/report3_complete_grade.py | 338 ++++++++++++ .../tmp/cs103/report3_grade.py | 340 ++++++++++++ examples/02471/instructor/02471/report1.py | 2 +- examples/02631/instructor/programs/.coverage | Bin 0 -> 53248 bytes .../__pycache__/looping.cpython-38.pyc | Bin 0 -> 2019 bytes .../__pycache__/report1intro.cpython-38.pyc | Bin 0 -> 7198 bytes examples/02631/instructor/programs/deploy.py | 62 +++ examples/02631/instructor/programs/looping.py | 64 +++ .../02631/instructor/programs/report1intro.py | 139 +++++ .../instructor/programs/report1intro_grade.py | 337 ++++++++++++ .../programs/unitgrade/Bacteria.pkl | Bin 0 -> 2050 bytes .../programs/unitgrade/ClusterAnalysis.pkl | Bin 0 -> 763 bytes .../programs/unitgrade/FermentationRate.pkl | Bin 0 -> 618 bytes .../programs/unitgrade/RemoveIncomplete.pkl | Bin 0 -> 1444 bytes examples/02631/students/programs/.coverage | Bin 0 -> 53248 bytes .../__pycache__/looping.cpython-38.pyc | Bin 0 -> 2019 bytes .../__pycache__/report1intro.cpython-38.pyc | Bin 0 -> 7198 bytes examples/02631/students/programs/looping.py | 62 +++ .../02631/students/programs/report1intro.py | 142 +++++ .../students/programs/report1intro_grade.py | 339 ++++++++++++ .../students/programs/unitgrade/Bacteria.pkl | Bin 0 -> 2050 bytes .../programs/unitgrade/ClusterAnalysis.pkl | Bin 0 -> 763 bytes .../programs/unitgrade/FermentationRate.pkl | Bin 0 -> 618 bytes .../programs/unitgrade/RemoveIncomplete.pkl | Bin 0 -> 1444 bytes examples/autolab_example/autolab_example.py | 4 +- examples/autolab_example/tmp/cs101/cs101.yml | 6 +- .../tmp/cs101/src/driver_python.py | 6 +- .../tmp/cs101/src/report1_grade.py | 2 +- .../tmp/cs102/src/driver_python.py | 4 +- .../tmp/cs103/src/driver_python.py | 4 +- .../example_docker/instructor/cs103/.coverage | Bin 0 -> 53248 bytes .../cs103/Report3_handin_25_of_30.token | Bin 0 -> 117485 bytes .../cs103/Report3_handin_30_of_30.token | Bin 0 -> 117467 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 833 -> 833 bytes .../cs103/__pycache__/report3.cpython-38.pyc | Bin 1384 -> 1384 bytes .../report3_complete.cpython-38.pyc | Bin 1748 -> 1737 bytes .../example_docker/instructor/cs103/deploy.py | 18 +- .../instructor/cs103/report3.py | 6 +- .../instructor/cs103/report3_complete.py | 4 +- .../cs103/report3_complete_grade.py | 69 ++- .../instructor/cs103/report3_grade.py | 69 ++- .../cs103/unitgrade/AutomaticPass.pkl | Bin 93 -> 4 bytes .../instructor/cs103/unitgrade/Week1.pkl | Bin 87 -> 62 bytes .../tmp/cs103/Report3_handin_5_of_30.token | Bin 128198 -> 0 bytes .../tmp/cs103/report3_complete_grade.py | 345 ------------ .../tmp/cs103/report3_grade.py | 347 ------------ examples/example_docker/run_all_docker.py | 57 ++ .../example_docker/students/cs103/.coverage | Bin 0 -> 53248 bytes .../cs103/Report3_handin_10_of_30.token | Bin 65286 -> 57954 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 992 -> 992 bytes .../__pycache__/homework1.cpython-39.pyc | Bin 833 -> 833 bytes .../cs103/__pycache__/report3.cpython-38.pyc | Bin 1384 -> 1384 bytes .../report3_complete.cpython-38.pyc | Bin 1748 -> 1748 bytes .../report3_complete.cpython-39.pyc | Bin 1246 -> 1764 bytes .../report3_complete_grade.cpython-39.pyc} | Bin 57824 -> 57973 bytes .../__pycache__/report3_grade.cpython-38.pyc | Bin 57968 -> 50620 bytes .../example_docker/students/cs103/report3.py | 6 +- .../students/cs103/report3_grade.py | 69 ++- .../cs103/unitgrade/AutomaticPass.pkl | Bin 93 -> 4 bytes .../students/cs103/unitgrade/Week1.pkl | Bin 87 -> 62 bytes .../Report1Flat_handin_10_of_10.token | Bin 0 -> 65761 bytes .../__pycache__/deploy.cpython-38.pyc | Bin 0 -> 581 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 0 -> 835 bytes .../__pycache__/report1flat.cpython-38.pyc | Bin 0 -> 1204 bytes .../instructor/cs101flat/deploy.py | 15 + .../instructor/cs101flat/homework1.py | 16 + .../instructor/cs101flat/report1flat.py | 24 + .../instructor/cs101flat/report1flat_grade.py | 349 ++++++++++++ .../Report1Flat_handin_0_of_10.token | Bin 0 -> 65468 bytes .../__pycache__/deploy.cpython-38.pyc | Bin 0 -> 581 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 0 -> 994 bytes .../__pycache__/report1flat.cpython-38.pyc | Bin 0 -> 1204 bytes .../report1flat_grade.cpython-38.pyc | Bin 0 -> 57893 bytes .../students/cs101flat/homework1.py | 21 + .../students/cs101flat/report1flat.py | 27 + .../students/cs101flat/report1flat_grade.py | 351 ++++++++++++ .../instructor/cs102/.coverage | Bin 0 -> 53248 bytes .../cs102/Report2_handin_10_of_18.token | Bin 80985 -> 0 bytes .../cs102/Report2_handin_13_of_18.token | Bin 0 -> 60507 bytes .../cs102/Report2_handin_18_of_18.token | Bin 80175 -> 61089 bytes .../cs102/Report2_handin_28_of_28.token | Bin 81409 -> 0 bytes .../cs102/Report2_handin_5_of_18.token | Bin 78385 -> 0 bytes .../cs102/Report2_handin_5_of_28.token | Bin 80005 -> 0 bytes .../cs102/__pycache__/deploy.cpython-38.pyc | Bin 0 -> 760 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 836 -> 836 bytes .../__pycache__/report2_grade.cpython-38.pyc | Bin 65513 -> 59749 bytes .../instructor/cs102/deploy.py | 6 +- .../instructor/cs102/report2.py | 16 +- .../instructor/cs102/report2_grade.py | 182 +------ .../instructor/cs102/unitgrade/Question2.pkl | Bin 384 -> 360 bytes .../instructor/cs102/unitgrade/Week1.pkl | Bin 215 -> 101 bytes .../students/cs102/.coverage | Bin 0 -> 53248 bytes .../cs102/Report2_handin_0_of_18.token | Bin 79868 -> 0 bytes .../cs102/__pycache__/deploy.cpython-38.pyc | Bin 0 -> 760 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 995 -> 836 bytes .../cs102/__pycache__/report2.cpython-38.pyc | Bin 2048 -> 2165 bytes .../__pycache__/report2_grade.cpython-38.pyc | Bin 65631 -> 59749 bytes .../students/cs102/report2.py | 16 +- .../students/cs102/report2_grade.py | 182 +------ .../students/cs102/unitgrade/Question2.pkl | Bin 384 -> 360 bytes .../students/cs102/unitgrade/Week1.pkl | Bin 215 -> 101 bytes .../instructor/cs105/.coverage | Bin 0 -> 53248 bytes .../Report1Jupyter_handin_18_of_18.token | Bin 0 -> 58736 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 0 -> 187 bytes .../cs105/__pycache__/report5.cpython-38.pyc | Bin 0 -> 2312 bytes .../cs105/__pycache__/week2.cpython-38.pyc | Bin 0 -> 466 bytes .../instructor/cs105/deploy.py | 15 + .../instructor/cs105/homework1.py | 1 + .../instructor/cs105/report5.py | 49 ++ .../instructor/cs105/report5_grade.py | 336 ++++++++++++ .../instructor/cs105/unitgrade/Question2.pkl | Bin 0 -> 58 bytes .../instructor/cs105/unitgrade/Week1.pkl | 1 + .../instructor/cs105}/week2.ipynb | 0 .../example_jupyter/students/cs105/.coverage | Bin 0 -> 53248 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 0 -> 187 bytes .../cs105/__pycache__/report5.cpython-38.pyc | Bin 0 -> 2312 bytes .../cs105/__pycache__/week2.cpython-38.pyc | Bin 0 -> 466 bytes .../students/cs105/homework1.py | 4 + .../example_jupyter/students/cs105/report5.py | 52 ++ .../students/cs105/report5_grade.py | 338 ++++++++++++ .../students/cs105/unitgrade/Question2.pkl | Bin 0 -> 58 bytes .../students/cs105/unitgrade/Week1.pkl | 1 + .../students/cs105/week2.ipynb | 69 +++ .../cs101/Report1_handin_10_of_10.token | Bin 77365 -> 58016 bytes .../cs101/__pycache__/deploy.cpython-38.pyc | Bin 0 -> 911 bytes .../__pycache__/report1_grade.cpython-38.pyc | Bin 63801 -> 57383 bytes .../instructor/cs101/deploy.py | 17 +- .../instructor/cs101/report1.py | 4 +- .../instructor/cs101/report1_grade.py | 180 +------ .../cs101/Report1_handin_0_of_10.token | Bin 77002 -> 0 bytes .../cs101/__pycache__/deploy.cpython-38.pyc | Bin 0 -> 911 bytes .../__pycache__/homework1.cpython-38.pyc | Bin 994 -> 835 bytes .../cs101/__pycache__/report1.cpython-38.pyc | Bin 1209 -> 1228 bytes .../__pycache__/report1_grade.cpython-38.pyc | Bin 63919 -> 57383 bytes .../students/cs101/report1.py | 5 +- .../students/cs101/report1_grade.py | 180 +------ pyproject.toml | 6 + pytransform/__init__.py | 454 ---------------- .../__pycache__/__init__.cpython-36.pyc | Bin 11679 -> 0 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 11349 -> 0 bytes pytransform/_pytransform.dll | Bin 1368590 -> 0 bytes setup.py | 49 ++ src/unitgrade_devel.egg-info/PKG-INFO | 317 +++++++++++ src/unitgrade_devel.egg-info/SOURCES.txt | 18 + .../dependency_links.txt | 1 + src/unitgrade_devel.egg-info/requires.txt | 9 + src/unitgrade_devel.egg-info/top_level.txt | 1 + .../unitgrade_private2}/__init__.py | 1 + .../unitgrade_private2/autolab}/__init__.py | 0 .../unitgrade_private2/autolab}/autolab.py | 41 +- .../autolab}/lab_template/Makefile | 0 .../autolab}/lab_template/autograde-Makefile | 0 .../autolab}/lab_template/autograde.tar | Bin .../autolab}/lab_template/hello.rb | 0 .../autolab}/lab_template/hello.yml | 0 .../autolab}/lab_template/src/Makefile | 0 .../lab_template/src/Makefile-handout | 0 .../autolab}/lab_template/src/README | 0 .../autolab}/lab_template/src/README-handout | 0 .../autolab}/lab_template/src/driver.sh | 0 .../lab_template/src/driver_python.py | 4 +- .../autolab}/lab_template/src/hello.c | 0 .../autolab}/lab_template/src/hello.c-handout | 0 .../unitgrade_private2}/deployment.py | 3 +- .../unitgrade_private2}/docker_helpers.py | 83 +-- .../unitgrade_private2}/example/report0.py | 0 .../hidden_create_files.py | 18 +- .../hidden_gather_upload.py | 39 +- .../unitgrade_private2}/token_loader.py | 1 - src/unitgrade_private2/version.py | 1 + tutorial/ncode.py | 499 ++++++++++++++++++ tutorial/ncode2.py | 363 +++++++++++++ tutorial/submission_autograder.py | 5 + tutorial/tutorial.token | 2 +- .../__pycache__/__init__.cpython-36.pyc | Bin 888 -> 0 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 918 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 944 -> 0 bytes .../__pycache__/deployment.cpython-38.pyc | Bin 1392 -> 0 bytes .../__pycache__/deployment.cpython-39.pyc | Bin 1425 -> 0 bytes .../__pycache__/docker_helpers.cpython-38.pyc | Bin 3165 -> 0 bytes .../__pycache__/docker_helpers.cpython-39.pyc | Bin 3217 -> 0 bytes .../hidden_create_files.cpython-36.pyc | Bin 3954 -> 0 bytes .../hidden_create_files.cpython-38.pyc | Bin 4588 -> 0 bytes .../hidden_create_files.cpython-39.pyc | Bin 4665 -> 0 bytes .../hidden_gather_upload.cpython-36.pyc | Bin 2775 -> 0 bytes .../hidden_gather_upload.cpython-38.pyc | Bin 4107 -> 0 bytes .../hidden_gather_upload.cpython-39.pyc | Bin 4210 -> 0 bytes .../__pycache__/token_loader.cpython-38.pyc | Bin 978 -> 0 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 181 -> 0 bytes .../codejudge_example/codejudge_sum.py | 35 -- .../codejudge_example/sumfac.py | 6 - 214 files changed, 6013 insertions(+), 2128 deletions(-) create mode 100644 LICENSE delete mode 100644 autolab/__pycache__/autolab.cpython-38.pyc delete mode 100644 autolab/report_autolab.py create mode 100644 build.md create mode 100644 cs202courseware/ascimenu.py create mode 100644 dist/unitgrade-devel-0.0.1.tar.gz create mode 100644 dist/unitgrade_devel-0.0.1-py3-none-any.whl rename {autolab => docker_images}/docker_tango_python/Dockerfile (100%) rename {autolab => docker_images}/docker_tango_python/requirements.txt (86%) rename {examples/example_docker/instructor => docker_images}/unitgrade-docker/Dockerfile (93%) create mode 100644 docker_images/unitgrade-docker/home/cs103/Report3_handin_5_of_30.token rename {examples/example_docker/instructor/unitgrade-docker/tmp => docker_images/unitgrade-docker/home}/cs103/__pycache__/homework1.cpython-38.pyc (73%) create mode 100644 docker_images/unitgrade-docker/home/cs103/__pycache__/report3_complete_grade.cpython-38.pyc rename {examples/example_docker/instructor/unitgrade-docker/tmp => docker_images/unitgrade-docker/home}/cs103/homework1.py (100%) rename {examples/example_docker/instructor/unitgrade-docker/tmp => docker_images/unitgrade-docker/home}/cs103/report3.py (74%) create mode 100644 docker_images/unitgrade-docker/home/cs103/report3_complete_grade.py create mode 100644 docker_images/unitgrade-docker/home/cs103/report3_grade.py rename {examples/example_docker/instructor => docker_images}/unitgrade-docker/requirements.txt (86%) create mode 100644 docker_images/unitgrade-docker/tmp/cs103/homework1.py create mode 100644 docker_images/unitgrade-docker/tmp/cs103/report3.py create mode 100644 docker_images/unitgrade-docker/tmp/cs103/report3_complete_grade.py create mode 100644 docker_images/unitgrade-docker/tmp/cs103/report3_grade.py create mode 100644 examples/02631/instructor/programs/.coverage create mode 100644 examples/02631/instructor/programs/__pycache__/looping.cpython-38.pyc create mode 100644 examples/02631/instructor/programs/__pycache__/report1intro.cpython-38.pyc create mode 100644 examples/02631/instructor/programs/deploy.py create mode 100644 examples/02631/instructor/programs/looping.py create mode 100644 examples/02631/instructor/programs/report1intro.py create mode 100644 examples/02631/instructor/programs/report1intro_grade.py create mode 100644 examples/02631/instructor/programs/unitgrade/Bacteria.pkl create mode 100644 examples/02631/instructor/programs/unitgrade/ClusterAnalysis.pkl create mode 100644 examples/02631/instructor/programs/unitgrade/FermentationRate.pkl create mode 100644 examples/02631/instructor/programs/unitgrade/RemoveIncomplete.pkl create mode 100644 examples/02631/students/programs/.coverage create mode 100644 examples/02631/students/programs/__pycache__/looping.cpython-38.pyc create mode 100644 examples/02631/students/programs/__pycache__/report1intro.cpython-38.pyc create mode 100644 examples/02631/students/programs/looping.py create mode 100644 examples/02631/students/programs/report1intro.py create mode 100644 examples/02631/students/programs/report1intro_grade.py create mode 100644 examples/02631/students/programs/unitgrade/Bacteria.pkl create mode 100644 examples/02631/students/programs/unitgrade/ClusterAnalysis.pkl create mode 100644 examples/02631/students/programs/unitgrade/FermentationRate.pkl create mode 100644 examples/02631/students/programs/unitgrade/RemoveIncomplete.pkl create mode 100644 examples/example_docker/instructor/cs103/.coverage create mode 100644 examples/example_docker/instructor/cs103/Report3_handin_25_of_30.token create mode 100644 examples/example_docker/instructor/cs103/Report3_handin_30_of_30.token delete mode 100644 examples/example_docker/instructor/unitgrade-docker/tmp/cs103/Report3_handin_5_of_30.token delete mode 100644 examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete_grade.py delete mode 100644 examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_grade.py create mode 100644 examples/example_docker/run_all_docker.py create mode 100644 examples/example_docker/students/cs103/.coverage rename examples/example_docker/{instructor/unitgrade-docker/tmp/cs103/__pycache__/report3_complete_grade.cpython-38.pyc => students/cs103/__pycache__/report3_complete_grade.cpython-39.pyc} (90%) create mode 100644 examples/example_flat/instructor/cs101flat/Report1Flat_handin_10_of_10.token create mode 100644 examples/example_flat/instructor/cs101flat/__pycache__/deploy.cpython-38.pyc create mode 100644 examples/example_flat/instructor/cs101flat/__pycache__/homework1.cpython-38.pyc create mode 100644 examples/example_flat/instructor/cs101flat/__pycache__/report1flat.cpython-38.pyc create mode 100644 examples/example_flat/instructor/cs101flat/deploy.py create mode 100644 examples/example_flat/instructor/cs101flat/homework1.py create mode 100644 examples/example_flat/instructor/cs101flat/report1flat.py create mode 100644 examples/example_flat/instructor/cs101flat/report1flat_grade.py create mode 100644 examples/example_flat/students/cs101flat/Report1Flat_handin_0_of_10.token create mode 100644 examples/example_flat/students/cs101flat/__pycache__/deploy.cpython-38.pyc create mode 100644 examples/example_flat/students/cs101flat/__pycache__/homework1.cpython-38.pyc create mode 100644 examples/example_flat/students/cs101flat/__pycache__/report1flat.cpython-38.pyc create mode 100644 examples/example_flat/students/cs101flat/__pycache__/report1flat_grade.cpython-38.pyc create mode 100644 examples/example_flat/students/cs101flat/homework1.py create mode 100644 examples/example_flat/students/cs101flat/report1flat.py create mode 100644 examples/example_flat/students/cs101flat/report1flat_grade.py create mode 100644 examples/example_framework/instructor/cs102/.coverage delete mode 100644 examples/example_framework/instructor/cs102/Report2_handin_10_of_18.token create mode 100644 examples/example_framework/instructor/cs102/Report2_handin_13_of_18.token delete mode 100644 examples/example_framework/instructor/cs102/Report2_handin_28_of_28.token delete mode 100644 examples/example_framework/instructor/cs102/Report2_handin_5_of_18.token delete mode 100644 examples/example_framework/instructor/cs102/Report2_handin_5_of_28.token create mode 100644 examples/example_framework/instructor/cs102/__pycache__/deploy.cpython-38.pyc create mode 100644 examples/example_framework/students/cs102/.coverage delete mode 100644 examples/example_framework/students/cs102/Report2_handin_0_of_18.token create mode 100644 examples/example_framework/students/cs102/__pycache__/deploy.cpython-38.pyc create mode 100644 examples/example_jupyter/instructor/cs105/.coverage create mode 100644 examples/example_jupyter/instructor/cs105/Report1Jupyter_handin_18_of_18.token create mode 100644 examples/example_jupyter/instructor/cs105/__pycache__/homework1.cpython-38.pyc create mode 100644 examples/example_jupyter/instructor/cs105/__pycache__/report5.cpython-38.pyc create mode 100644 examples/example_jupyter/instructor/cs105/__pycache__/week2.cpython-38.pyc create mode 100644 examples/example_jupyter/instructor/cs105/deploy.py create mode 100644 examples/example_jupyter/instructor/cs105/homework1.py create mode 100644 examples/example_jupyter/instructor/cs105/report5.py create mode 100644 examples/example_jupyter/instructor/cs105/report5_grade.py create mode 100644 examples/example_jupyter/instructor/cs105/unitgrade/Question2.pkl create mode 100644 examples/example_jupyter/instructor/cs105/unitgrade/Week1.pkl rename examples/{02471/instructor/02471/week02 => example_jupyter/instructor/cs105}/week2.ipynb (100%) create mode 100644 examples/example_jupyter/students/cs105/.coverage create mode 100644 examples/example_jupyter/students/cs105/__pycache__/homework1.cpython-38.pyc create mode 100644 examples/example_jupyter/students/cs105/__pycache__/report5.cpython-38.pyc create mode 100644 examples/example_jupyter/students/cs105/__pycache__/week2.cpython-38.pyc create mode 100644 examples/example_jupyter/students/cs105/homework1.py create mode 100644 examples/example_jupyter/students/cs105/report5.py create mode 100644 examples/example_jupyter/students/cs105/report5_grade.py create mode 100644 examples/example_jupyter/students/cs105/unitgrade/Question2.pkl create mode 100644 examples/example_jupyter/students/cs105/unitgrade/Week1.pkl create mode 100644 examples/example_jupyter/students/cs105/week2.ipynb create mode 100644 examples/example_simplest/instructor/cs101/__pycache__/deploy.cpython-38.pyc delete mode 100644 examples/example_simplest/students/cs101/Report1_handin_0_of_10.token create mode 100644 examples/example_simplest/students/cs101/__pycache__/deploy.cpython-38.pyc create mode 100644 pyproject.toml delete mode 100644 pytransform/__init__.py delete mode 100644 pytransform/__pycache__/__init__.cpython-36.pyc delete mode 100644 pytransform/__pycache__/__init__.cpython-38.pyc delete mode 100644 pytransform/_pytransform.dll create mode 100644 setup.py create mode 100644 src/unitgrade_devel.egg-info/PKG-INFO create mode 100644 src/unitgrade_devel.egg-info/SOURCES.txt create mode 100644 src/unitgrade_devel.egg-info/dependency_links.txt create mode 100644 src/unitgrade_devel.egg-info/requires.txt create mode 100644 src/unitgrade_devel.egg-info/top_level.txt rename {unitgrade_private2 => src/unitgrade_private2}/__init__.py (97%) rename {unitgrade_private2/codejudge_example => src/unitgrade_private2/autolab}/__init__.py (100%) rename {autolab => src/unitgrade_private2/autolab}/autolab.py (89%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/Makefile (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/autograde-Makefile (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/autograde.tar (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/hello.rb (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/hello.yml (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/src/Makefile (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/src/Makefile-handout (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/src/README (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/src/README-handout (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/src/driver.sh (100%) mode change 100755 => 100644 rename {autolab => src/unitgrade_private2/autolab}/lab_template/src/driver_python.py (96%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/src/hello.c (100%) rename {autolab => src/unitgrade_private2/autolab}/lab_template/src/hello.c-handout (100%) rename {unitgrade_private2 => src/unitgrade_private2}/deployment.py (94%) rename {unitgrade_private2 => src/unitgrade_private2}/docker_helpers.py (58%) rename {unitgrade_private2 => src/unitgrade_private2}/example/report0.py (100%) rename {unitgrade_private2 => src/unitgrade_private2}/hidden_create_files.py (91%) rename {unitgrade_private2 => src/unitgrade_private2}/hidden_gather_upload.py (85%) rename {unitgrade_private2 => src/unitgrade_private2}/token_loader.py (99%) create mode 100644 src/unitgrade_private2/version.py create mode 100644 tutorial/ncode.py create mode 100644 tutorial/ncode2.py delete mode 100644 unitgrade_private2/__pycache__/__init__.cpython-36.pyc delete mode 100644 unitgrade_private2/__pycache__/__init__.cpython-38.pyc delete mode 100644 unitgrade_private2/__pycache__/__init__.cpython-39.pyc delete mode 100644 unitgrade_private2/__pycache__/deployment.cpython-38.pyc delete mode 100644 unitgrade_private2/__pycache__/deployment.cpython-39.pyc delete mode 100644 unitgrade_private2/__pycache__/docker_helpers.cpython-38.pyc delete mode 100644 unitgrade_private2/__pycache__/docker_helpers.cpython-39.pyc delete mode 100644 unitgrade_private2/__pycache__/hidden_create_files.cpython-36.pyc delete mode 100644 unitgrade_private2/__pycache__/hidden_create_files.cpython-38.pyc delete mode 100644 unitgrade_private2/__pycache__/hidden_create_files.cpython-39.pyc delete mode 100644 unitgrade_private2/__pycache__/hidden_gather_upload.cpython-36.pyc delete mode 100644 unitgrade_private2/__pycache__/hidden_gather_upload.cpython-38.pyc delete mode 100644 unitgrade_private2/__pycache__/hidden_gather_upload.cpython-39.pyc delete mode 100644 unitgrade_private2/__pycache__/token_loader.cpython-38.pyc delete mode 100644 unitgrade_private2/codejudge_example/__pycache__/__init__.cpython-38.pyc delete mode 100644 unitgrade_private2/codejudge_example/codejudge_sum.py delete mode 100644 unitgrade_private2/codejudge_example/sumfac.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..335ea9d --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 The Python Packaging Authority + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 410439e..577ac9a 100644 --- a/README.md +++ b/README.md @@ -55,41 +55,48 @@ if __name__ == "__main__": ``` ### The test: The test consists of individual problems and a report-class. The tests themselves are just regular Unittest (we will see a slightly smarter idea in a moment). For instance: + ```python -from homework1 import reverse_list, add +from looping import reverse_list, add import unittest + class Week1(unittest.TestCase): def test_add(self): - self.assertEqual(add(2,2), 4) + self.assertEqual(add(2, 2), 4) self.assertEqual(add(-100, 5), -95) def test_reverse(self): - self.assertEqual(reverse_list([1,2,3]), [3,2,1]) + self.assertEqual(reverse_list([1, 2, 3]), [3, 2, 1]) ``` A number of tests can be collected into a `Report`, which will allow us to assign points to the tests and use the more advanced features of the framework later. A complete, minimal example: ```python -from unitgrade2.unitgrade2 import Report -from unitgrade2.unitgrade_helpers2 import evaluate_report_student -from homework1 import reverse_list, add +from src.unitgrade2.unitgrade2 import Report +from src.unitgrade2 import evaluate_report_student +from looping import reverse_list, add import unittest + class Week1(unittest.TestCase): def test_add(self): - self.assertEqual(add(2,2), 4) + self.assertEqual(add(2, 2), 4) self.assertEqual(add(-100, 5), -95) def test_reverse(self): - self.assertEqual(reverse_list([1,2,3]), [3,2,1]) + self.assertEqual(reverse_list([1, 2, 3]), [3, 2, 1]) + import cs101 + + class Report1(Report): title = "CS 101 Report 1" questions = [(Week1, 10)] # Include a single question for 10 credits. pack_imports = [cs101] + if __name__ == "__main__": # Uncomment to simply run everything as a unittest: # unittest.main(verbosity=2) @@ -109,7 +116,7 @@ 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']) + snip_dir.snip_dir(source_dir="../programs", dest_dir="../../students/programs", clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py']) ``` - The first line creates the `report1_grade.py` script and any additional data files needed by the tests (none in this case) @@ -193,15 +200,18 @@ also that the students implementations didn't just detect what input was being u return the correct answer. To do that you need hidden tests and external validation. Our new testclass looks like this: + ```python -from unitgrade2.unitgrade2 import UTestCase, Report, hide -from unitgrade2.unitgrade_helpers2 import evaluate_report_student +from src.unitgrade2.unitgrade2 import UTestCase, Report, hide +from src.unitgrade2 import evaluate_report_student + class Week1(UTestCase): """ The first question for week 1. """ + def test_add(self): from cs103.homework1 import add - self.assertEqualC(add(2,2)) + self.assertEqualC(add(2, 2)) self.assertEqualC(add(-100, 5)) @hide @@ -209,14 +219,18 @@ class Week1(UTestCase): # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test. # See the output in the student directory for more information. from cs103.homework1 import add - self.assertEqualC(add(2,2)) + self.assertEqualC(add(2, 2)) + import cs103 + + class Report3(Report): title = "CS 101 Report 3" questions = [(Week1, 20)] # Include a single question for 10 credits. pack_imports = [cs103] + if __name__ == "__main__": evaluate_report_student(Report3()) ``` diff --git a/autolab/__pycache__/autolab.cpython-38.pyc b/autolab/__pycache__/autolab.cpython-38.pyc deleted file mode 100644 index 86ad4a1d06d4f3267ad22b37b7839b186984db04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5899 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 J-Gq$O_iw`C6yE>< diff --git a/autolab/report_autolab.py b/autolab/report_autolab.py deleted file mode 100644 index e69de29..0000000 diff --git a/build.md b/build.md new file mode 100644 index 0000000..4ba0737 --- /dev/null +++ b/build.md @@ -0,0 +1,18 @@ +# Unitgrade build info + +See https://packaging.python.org/tutorials/packaging-projects/ + +- Build the distribution package using: +``` +py -m pip install --upgrade build && py -m build +``` +- Upload to test repo +``` +py -m pip install --upgrade twine && py -m twine upload --repository testpypi dist/* +``` + +### build and upload (to actual pypi; remember the .pypi token. you can find it in personal dtu repo) +``` +rm -f dists/* && py -m build && twine upload dist/* +``` + diff --git a/cs202courseware/ascimenu.py b/cs202courseware/ascimenu.py new file mode 100644 index 0000000..c5f1561 --- /dev/null +++ b/cs202courseware/ascimenu.py @@ -0,0 +1,43 @@ +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.layout import Dimension, HSplit, Layout, ScrollablePane +from prompt_toolkit.widgets import Frame, Label, TextArea + +print("hello") + +z = 234 +def main(): + # Create a big layout of many text areas, then wrap them in a `ScrollablePane`. + root_container = Frame( + ScrollablePane( + HSplit( + [ + Frame(TextArea(text=f"label-{i}"), width=Dimension()) + for i in range(20) + ] + ) + ) + # ScrollablePane(HSplit([TextArea(text=f"label-{i}") for i in range(20)])) + ) + + layout = Layout(container=root_container) + + # Key bindings. + kb = KeyBindings() + + @kb.add("c-c") + def exit(event) -> None: + get_app().exit() + + kb.add("down")(focus_next) + kb.add("up")(focus_previous) + + # Create and run application. + application = Application(layout=layout, key_bindings=kb, full_screen=True) + application.run() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/cs202courseware/ug2report1.py b/cs202courseware/ug2report1.py index 586a004..e0e7e05 100644 --- a/cs202courseware/ug2report1.py +++ b/cs202courseware/ug2report1.py @@ -1,16 +1,13 @@ # from unitgrade.unitgrade import QuestionGroup, Report, QPrintItem # from unitgrade.unitgrade_helpers import evaluate_report_student -from cs202courseware import homework1 -import unittest - -from unitgrade2.unitgrade2 import wrapper, UTestCase, cache +from src.unitgrade2.unitgrade2 import UTestCase, cache from unittest import TestCase # from unitgrade2.unitgrade2 import cache -from unitgrade2.unitgrade2 import methodsWithDecorator, hide +from src.unitgrade2.unitgrade2 import hide import random -from cs202courseware.homework1 import reverse_list, my_sum +from cs202courseware.homework1 import my_sum class TestPartial(TestCase): def test_a(self): @@ -83,7 +80,7 @@ class ListQuestion(UTestCase): """ ccc test_integers-short """ self.assertEqual(2,2) -from unitgrade2.unitgrade2 import Report +from src.unitgrade2.unitgrade2 import Report class Report1(Report): title = "CS 202 Report 1" @@ -97,7 +94,6 @@ class Report1(Report): pack_imports = [cs202courseware] # Include this file in .token file a = 234 -import coverage if __name__ == "__main__": """ @@ -113,7 +109,7 @@ if __name__ == "__main__": # print(inspect.getsourcelines(f) ) # How to get all hidden questions. - from unitgrade2.unitgrade_helpers2 import evaluate_report_student + from src.unitgrade2 import evaluate_report_student # cov = coverage.Coverage() # cov.start() diff --git a/cs202courseware/ug2report1_nohidden.py b/cs202courseware/ug2report1_nohidden.py index 180417c..3e94a57 100644 --- a/cs202courseware/ug2report1_nohidden.py +++ b/cs202courseware/ug2report1_nohidden.py @@ -1,17 +1,14 @@ # from unitgrade.unitgrade import QuestionGroup, Report, QPrintItem # from unitgrade.unitgrade_helpers import evaluate_report_student -from cs202courseware import homework1 -import unittest - -from unitgrade2.unitgrade2 import wrapper, UTestCase, cache +from src.unitgrade2.unitgrade2 import UTestCase, cache # from unitgrade2.unitgrade2 import cache -from unitgrade2.unitgrade2 import methodsWithDecorator, hide +from src.unitgrade2.unitgrade2 import methodsWithDecorator, hide import random -from cs101courseware_example.homework1 import reverse_list, my_sum +from cs101courseware_example.homework1 import my_sum class GeneratorQuestion(UTestCase): def genTest(self, n): @@ -57,7 +54,7 @@ class ListQuestion(UTestCase): """ ccc test_integers-short """ self.assertEqual(2,2) -from unitgrade2.unitgrade2 import Report +from src.unitgrade2.unitgrade2 import Report class Report1(Report): title = "CS 202 Report 1" @@ -103,7 +100,7 @@ if __name__ == "__main__": for f in ls: print(inspect.getsourcelines(f) ) # How to get all hidden questions. - from unitgrade2.unitgrade_helpers2 import evaluate_report_student + from src.unitgrade2 import evaluate_report_student evaluate_report_student( Report1() ) diff --git a/dist/unitgrade-devel-0.0.1.tar.gz b/dist/unitgrade-devel-0.0.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..9a8b82a9d4e5c3c61bd6ced6cace4b2a6bfb40b9 GIT binary patch literal 17133 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 literal 0 HcmV?d00001 diff --git a/dist/unitgrade_devel-0.0.1-py3-none-any.whl b/dist/unitgrade_devel-0.0.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..6e566dfa026e00890940fe2102371f73794be2be GIT binary patch literal 18848 zcma%>W2|V+)}^;?+qP}nwr$%w+qP}nwr$&I+o#Xl-RXP3^zARbl3K~CA2XF&BbBG- z7z)zBASeI;01yD@dPK^P4q>WXfB*oZzyJVve`j6oEM3f<3{6b+9h@xP3|&l_==Jq2 z|4!=b(>ZvyD0bNmF~DrYzTs<lM*w38b%Nceb|?qN2I0HRjHZG}S_b*~lD?I+%eFZo z4R;;=mIzMV3}SH<IciD`UbKKwSIP5^E<)k>BVsE7HCqsH+z)rn`KyS-sy?){n)_H? z7L8fc*TB}<qC%W*642RUS_+Fs?-}%Ui~~qL#(`;(L3J~hwV0Plo8uEvgciaQ9b<P~ zBZ<`r?e|IM&<5n&T_~$8PtYQXO=>r%HOa1cQFH^zJ$pl~33oh{C>qC}Zj~;aHwA3- zz_cu1bq9=<E|2~PT`kc?Ul^08%?$}C-}{em`ciGxl%-E6mOJ2T5I6!of8)+ms$D?O z04hI}MKd~WYetymHui!B+aH4=Lp&{r#Gh}f&du+3$@;Aqo}MS_FUQo5Ma_d)j=(Rp zfo{n6!`U<7PcVi5v+JgcM9QPY@r9B=e|H=d0D#~>@4AVpgN?nXt*M>M-+i}Lk&Rno zK<H7w^v4)9#I?1uJW#7ZLL%TGq{ZP#o_runTCFw)-`b%2liQGEvP4_4?9=mlIy0@T zvZjqLfVpRkyIQrn0oPsiZlJ=!wXxqdLRt=U$B=Dv<X`FV5z^wyq@g|_$=a?LgK%{= z<+FHc*b~_YrtPr4L5?zPF(08A>pKSVRbKLnx=z?Eb#$A>s$sWAoqt5@qbP47FbLYb zN+C-Uu#ClLq6%>y9KNi;ad18uY<r;wMueDVzM}!*CatLEgqYXJL_E7pwT=fHB~RV% z$WAJeh@Ueuv^B7zSat$uDvZ>-7Kz1vUL+@0s*PBkkBQ*spRU)uI^aucfi6w_H7DRp zVf|H@_%gga3I5V2TmRV|)#zg=@|#WSszlC7HxNE?wK+?vKMuo*e#1*!k7=HzfoEng z%^_<eEg}lfUc5>!lejTtcUx7_)LDJjI3ql*-a7|}UoF4Ll0`Ut8G8Y_8f0aV7z0~L zI?5{iAw}o+6v|hds#&2NsH9;u#<d%Qax2F47n!JW-%dBbLRlySTX@u~e^g2QSq}_( z?3~GNQ-UmDfu!tR_ca446}ZJ9O(a(Z&*0e^)@Qqe+LuDk;^K*i7$SVL8nQ}Ny{EE{ zsPl5MOK#oL#zT`bJ#zN8dzg=|SswJlhS@yDFwl^;DVExO#kGjc**BZ<HjLEcA?UX} zutz)|u?3zy*3~t!YBXVXFB*@2jGhJ+!`4scYCVIjOk@qrK!Z%VUJJ^FQ1mHo*Z^ia zxEpC2zv>@=f636r5MNm@zAoJSuLJ{-002n-A2PHzwl;Orw=lJFFm-bNE5#EHZTliI z6u-FSKMq30tS+|Q6TK>_-AZ*z`dXG<zLu+v3@{#82J_SL%MD+9-m}vIfJY=Xtx6?Y zh=Cc-S<atuI>J<<fDN6#?$}XUR4*8kXsa7FCMoeBU><^h4vKucyW6`Rs8QqYoLBjv zR(8!g)|=t2w5@a7;xf_2bIJ8ex`~6Mj%+Ka(5{Noy_6*pnM=k)LP`*#3WZKxi4MKV z*198@PW|oiB+gM>l4<?XNTJcv&;$A;D9vDCt0e4&Ya^<M5H%WQi!o@l8$+g;sKm}a zq_7M+K$H$$=I5$gzer{w^0CJvy{*bvsK1t@kk^-10e8Crz0uZ}dE^b)h$X$daR{Dp zs!TdlNiPWaHRLID$*Y&GX>T<<$dK0>>7>cNYZTgbRIGVMTte)m{8k%dTjH-|#2~e+ zG#;en2lzA%Y2sM?1=N{KJb%3w+GJmP=Jsf+Bt%1TbXw*_t;f@Of#@cEaF?AvzuH3i zr5WRVVw=y-BWR|<-r$-5e<o4ad?{lGosG{~xbaAdk2kV#;xCTCQ<yYAk3XY{j7%cN zD5sClkO-v2yDdsFsh(*S@MwD)(iINNW9w>kYv8tDo!^q#SvGchw_RSG5Pi9oWqe$q z?=5W0>QKt5iQO%g7Omg6f8V?7eM^`=HaCQHUn(rZtIu<5i8aJNbf*Q*E?dhaCI%88 znu2*vMZil8p{6bsIbvD>hI$y7ZOw&90M+Y4&U?TzngW60Pec?-w$*c5n*|5baUzpR zt4X2+hPK=L8m8bZ^(OFW(!$H)<~k`Ru%qZ=#mx>$t8ZP=PEnIF#<&41CiO6Tc&5X+ z&*JP$TxfKmMxNbr;qL~qzutcc8OCibm(*+NZf4x#jTUacYi@DLd1{4OBkR5NAIsTO zz9_0TNKoaY!c-`b-C~P*M_QIsM@)hM<CAi4CJ@cl2~pErJZ&hA2ibk4VX2o_W?ScZ zF;oQ=<saKIc4-y_-^yvdb})&@zE-6?uEPOBj(8NH{}6gykl$o%<sNNvvze#!3UsIc z36m9_aqTg6w#jx4j1ibYjI=*R?G#UDo?;xspgTRMJg4m{du8y38>pE8me#W7CZgcH z{%HBOk7bu|Z(5Lb+G@*0;!JZ9X7QzjCS<7A4;AZPqY$2yYAD`QHZ<e5O){vx_H$@1 zO>sDp#lGcB%mkJNALB5ps?vhdEEE1DreF{ChiUDhEP-7pqg6MsG69bh+C@I^z&#)X zvGO*%%}|n~^^#o8A^gxpb~{#}N<^8Yr9Rx7ScWIC(p|w4q~@lKvVH@bp*0p6UT$_n z-CR1-Cxa1C5bw`6Oz8E3ixWS5*c&mzApk#p?Qblab=B0gm{><p`Xk@}Bc)+4WLLDY z!@ZWL7x+zQal|FAhcUXmReRdQE0|u*&k<X2`L6jGRXD9?5YUS<9S^{@xBfZLY_BnP z7)m^AFdUm72xQ3M`Q4+D3wVQkDn!DHB|=}sa{~M4U6JrA7HkO>1X$GAV~dwa^2&pH zlY)8qsghP?>w&_o_w}855G%J6)3`cu`KqkH7tzPzV29n2GXV4{I7)q6Dt~Qp4W#@) z279z8!4beKXNMej11^=nU)+bi<a9q;BoT<bdw?anYR21b;`uS4u1zaK$I#u#v1Vga zl0|5{8J#6%#ZL_4h&JkO4K3BHIAVvR_-snP(M&MCUW4TPQmd?~4x9Ls*TiS|g1oxw zYj#@XO+a5ts54t_sKe=QMpV|TL<q>2*)fqjXUp?jJ!#cVqn}mgGF^<A7$iJzf8}UJ zdT0ejxqnZ2Iu$&7&sdR+3d66PK@5xJ3;JIN^*_8Y)kRbN0uBJ6j}8Dp`JWGJ3riCd zQ#*ZQC)2;srf+6xWBSi&U4k)Xcf|34NAO300$kD}gF9zqXfOaB;~Ls|J$K+5!1*2v zh*pu28>u8h1u5>pe~*ffWU|p4Ym1MBs7m7wUGv#iSVezc5bArmDUly(!Ns;tav&0T zZqKX0Q%uakZ{Kyy``N?)?4A$$JwtX%4rPuNKTW7I7Trsy?eeEqs1$jm7QIeJJ{rD| zreiuUo$sCxzq8Zy#6u4^u>V_kf`u3xNp#_R=fY*bnkUb(?=0e(QT=8f9fv`V{PULq z{PjAbB@hc=&i0{C?A;xS^&1&ij$cE>nKR1*h>bKQcnV9BR=_l5?)Z52RkK_3(r1_3 z{7(L((vv`$YBTT&MfJT;;#o?$XD;xr%!N*y=Rl^NLgLjh(#*IB)O`$KKTbDM<W<2` zhKl_@UU*LJ-5tai43^(l^O}rA;S&Ja>abzbcf#Lu5Jqd990&<v2K8gshx<E&#t|}H z(Co`9!VXr5a+$fu;8;cdJg8~M32)hkf7yV$%hIU8;tDHBq*=+6W<CP8g${HSj_5^# zYF*Z9^;)`>Y)ki2qJ`IULL$~lJL6&sz>7fB9Q=#dhxSl9aZ#s1JvU25LaF%)qWGK( z;Ue_ZQ<1sk!6(S;rVY&&U1K1?o6r^MJLN0f+W4V>STBfznBJ0t79{HzA1#*LZ+Rx* zJ_W#x<fhJ9Lgg%<WfDoi58{51s*$^F05A{f@&x2{EVz&>5d}HPp<GTyVf*ZB2Pm*o zEE6f=)if<TV-)(`nNVF!P`aBqlZ77wKBn|{at1^{Ffkz=2817Jdik%w92t;7PN?TF zbXt>?Q&R;f7#kqZ@x+K3O#i{6tsRqi*lM=E7hrgJy~Xntwx8j7wPaV^G}A(tJd75! zFN9ZcVc)?~CW*Xuwf*&0DVv#+lX(}ahQwmZ4ylVX$@i?|@#tOIq7rFMbVNhAI7$R= zk8U@$X$c&Ju$!!DfF+PMt|xj;%{uJ-+75C9H1oV#wq&1;f*;}wRk3<!0l;mMxvz4f zI)(WUq`JhqOPAznRC(oY=7-5o->SFMylS=WWGMAiSNhKGz{;%Yt&qBz6Yg{~xWW{G zI*|^})UyO+>xJ!8>@C;#-cPA%XulrQH+{?!Vo=_vheMpprvOn599^C>v~*#vv)Y34 z;TAay<xeXG(De;15@Oipipz8L&plD|MH1r1lzn)@oQu`RFf((IB?4Ohu|s<eXXdrm zc4Ah>7VEJkI=jYcAM?A9)_sMEDnUEGmPB)9AN|iBNZ;`&G@iL!o!Ppf_^6=Clb}$g z%bs@uAW@%;jLQWW2w#iUowIE7s)UcP;b<K$`|)^bx1r+HO3u5)yYn%Z(y6(9$K{bY zxbuV#E(~NpjR0a=Llb(Et2h(=Ht%cRhYJg@$T+k;H3@4nSd7?Bc1hkI$J<V$GASd# z)+SOlx@5BDg@OW8320Wj3KEoGd~%S=#Za9yM9Nf~j}_PS{cTGdX<6V8uV~eev7=Vh z=vpZ3pwX;_Yh(PX`xFT4zX&U-e~YtW2N7aKc)|W!r;I3PRn!Y8nT`r>GtC2<M-w!T zrdQ>|CoMJ2cNMsq=?<1Lc3^lMC>a9ZcX_RM_|aPyFvgYz=<E&<LEpf{Y+Od{frEwk zK?Kqypec@V(<7!)_ccaTF`86TEGxLXZv;9?@v|dzYHi5~w89BMQPqY0mXe4i#s&$t zaa`vELStP3yRh%f9<-42u$pz^iPCYqpo1OSNVY|VQp@5jtqfgc2Z<_WU96q9fPR|E zc;1FFl**WbH$jb05!DX5VqeIwTG1~Q&6WM)li?KO!zgO^cP>ahZDuV9<T?Wep9Z?w zRY5x9#l{+rK6=VjtSucIzIebhUyn5&Mhpj);WVDRVIr#N8#h(It0(qgSAuIgm_)^) z>I@qA3foR|3i&#FI#<p9qaQde-Kt>uaPH*9AjUFty!m7$EzV`l#VWGFN--Ml>z4l~ zzOK-o6b$efa+9g89g+{R7{LMwtos<HYXC%SIA1p43u8EcA&vVR-LF9v;XG*c+tlzI ztaGUvCo=%=%{$TTc)%eG^9<YL;srB%2X3=UapK17`5SI01AqUN-hk-~>?57CJVLUd z>9NpvygwD2ff-P;%NS)8a68#Q*pLT@Uj;A)(JYPafu0ES7m{<IYkt_kK48;m;B&W3 z(TK>t{D{52avzmd9k5k3xI)E^N5|+Oi(KwNhFe@9R-GRaqfs11$k04zRVVz9s`>DM zknwA@EuRRW1rz+wTi5MxTAL`7!j;Ln*0k>{KoZRZm@f?Q_?;(6A|el4%8xF?V>nU4 zG)gR#y2!4ghJb)fZy?jTV-&=-LztK+88zyNvP{%cnL2`kyeGrQH(bpYhv9Q~JpRKe zbfkS`2ipQwJXDmgsP7SssJ;z~PYeTn$N*|%!XFfDGvtG6MR1TE;bVLLuZN2<`9E4o z{=y`x;t-`UoAk3Xg=|d(GHn_%dmtS8O<+`XcG#Xl?IUY38i<emkmA?C*;rP{-^(PG z89fIEy2b&}?~!-x@?;hQuwn*w#$Y*?^=f67D5II`*4QJC(J5Fc0n<aErTKuu)4toZ zaWcr!8}E&(d$roGG9==LGGy*a+I#m0V^2(6h5Cy23dr<$`JX2-!w#RF-a(2?9vM5# zBQr}jq)SnoEK@vsBz`m^(q2v&h;e&=-hXcW|0_J0R`ur`{e_3@zwkiy-@=2rp^L@e z%bx4s&V!-JUwY`#(2m<;L-T`=|BDY0B(f;7ZN8g;I0T|tHUXmAE=y^+AW*c5Y{tKq z6O(!E68`j_-4iJ{@-~5Px(0a2<W7w_lWUWD7pm&U24%$*!hWXS`^8Tpw3h8iz*eCQ z?ak%AKR?^=$@#-~@4DK2P(DAJzttJi#^a)}(|i_|ls7AaGCGZk_=)TF>zfS06iCGu z5<2a~c9w=o3%0Kk15<+Vf!no8h{q^mTF^>Rfs-xrG$leM;>aW-S4w)HaojJZI`?=2 zG#8jnshs3r!t^@Nm8Pc_Rc4S;KYXgUtq$!89Qt(=)Tq=CgJN3rKr`JA-sZP6Xl8A1 zZ^!R_Hdcb%XsY_kI*Bg?G3eNNf=>ljA=cBlast#h9zbO@vcYeO!J%3eVY>y^J`hw= zaZ}Pf&pRfSm}c(zKT_tg=p{g|zU!al+fk269@1_o+2Sq*YX{|`bOXYnF4DB(dPB{E zBj8sH(XoXf!ESPk#edIcmfx1I1`r4kR-sCIZ$#rt*WAB0FVQ@_9+_RHvDNps9DWT1 zUKC+vY?;PQ{0Ns)-LTW>s$0t=(R}>5e^oMTzT)I*-FpiN3PS0FIvBLjcqBRLd%yQ1 zdJfT%vkZ3N>NQT$@i%16&76ApEHeBUlGpUF7LZv{5bp8&>cHDdt>84pQw<3{y&eWG zaFehV3}_jTL93yMR>Uxnqo$M<>?i1~H()*GSicbH+L`7{Xz!1DlCXP6`{a|PQ6R&L z8E-1aiJeedBtg_g=DdhHCUU-4kn+p0#&@JxJ#crE75Z%fK%gVD?U`&O6#vMf=_mKn zGDb1(M}?~F*dyO_wkQwqaomC=#nYNO!Hy_iyx}C;H)mnqPy1{`I-jNZ3t5{Hol~`q z?E$M8k41dEHS{$%I`47wXDxhTIl6XChTvUU=<;H{UNS1?6OppQ$#bh!J(l(L8Lwxn z6(El703&MA_X6dHwVgcThFAk{tuLAehAdA+#TKlxeszn!+Sahbr;5#ZF64dV)&zE` z<q&vQsOkxvKQMtfg2WDndMT(r(A}v?D)Cr-dX{!=*fDef3Jg~xArf(8Hhlsf9tHar zx6oTStY|~(fc=l!#yflvH2a>jVArjNfvf=TvH^iuqk&4WAw+mD+DE%IYa-|tTB|xS zocs+S)k|JNa15Cm^@cNQ>VYeKZT%2|S$O#R60s&TlhtahF66yI6tr!5d0<Ri167&= z8}i=7rk48NYJg7D{-?J1hpwtLOZb2?_juk}NS-1BEQL{a(6TmFew~n;uzAvD#HzeJ z7QeQq6^gU*(C<?!DSN--#pr|@#^Bzr9$ORG-_{vY6>M#LDgJ%9tkJS<u6r&z^-k6w zx9`jl!9cPrL*1Z{HMsd|%0Ayh@c0|S@8HG#p8`qY0yLIOD$C*qsl9%*$N9%#Uqyt| z5H+?qWLR=zRKUte7DU<zEmC`qvTFus0`jW_j6HN2v0`p6-^uI#wkLtzuFdJv#^%+_ z8xZ_(ImNQ3CaS_Laas&XoWw#=*fR{7P}z+iijfueN98xp0$)qCfLu6lyn3IZ5w6Q& z3fvsn@K$9g1p3>NWIb2`>K<X0H|J)i=G=Xgcwi6+tJ7$V_oE(&L2Bu!v4tbgNFF~8 zgF(nt4ueox9Jxh2oR9F5I*+h8AXI`Rliv23)Vjcu5{){Vr0hl=X3~-jI39izATPMw z43=(8E_?|At|Wr)U4>@W)FcYV(%SHAqTS-_tD32%7W-#wobaUOFHLejt>x=ObR0un zt;dB2-n-Gw(67DO-GqBInZfG!-wEyKA{r6x&F-pE_5BZ$FEYPhEM9fS<R2b0`t?}r zF8k(*dp_9XCDH465NEV4pjln$AbPbawz^NM3CT%uQ8tT2p%5rS;D=D$XdiaQ^jQt( zh4QNbpGckHIeLkb)T^$}Aa1$ZJZTOQt^E;P+1Zpq2#m~qBSNLkc2H*<WdwAUFr3n! zbQ$UbdW;;iw7SWk&`0ws@595b01;<j9WWDbONuzU;Jhdfa@$HXWo<O^wy1pcbW;2b z-vaG^nL?D=vZ!&qyBzsBr3zzjm#wjE27YdyA`f<xBH)V{liUpg_|t)iTZ{AVM9oPK z9k&V#G4ftd*IXFi`<(@W5`#)Q`kG&X(s&5w(TS4!4Jspad?B%@W2{tUWnG9lIR|U? z&34ZFUbfvx79k>A0_;`X9P|g}&<~>7Zng&?l*7+ZmV$XT!=j$stcwnK{NO!ClAyxy z#k)%Gr&$YgiaZe%;2bH`a}BU9&eJ{D5qR68m|a1cTqmjPL8^$@?)_*r>MX2Yd*HYu z%aY~PRtM#$b$U%uZw&sSZ>egXn6mLKK7=tj7&vzJB(w#Akv>z;OClX|HoMj1Q5?QN zJJ=&%8sg&h&U2A3HD!JgW{bhF{c3!XFxPt7vA8M1{w`=_-(ClJ8R}FAG^XoZT@hcC zIe6NyR+Q20=N-~Ti1i)GVfRzzw9Gwaum-6|n(h@pJnr;aZ6(m(|3L)f8}c?Iw1)J5 zGfr|tnPB<oh_Bn8w&z&2gBdsK?B)m}0&y-s<Zv^A^fqu!G?(6#C0}DOFfrW5;KG5( z308=hU3~*kZIyb_enBXt$|Doe@2o)zNdiw?nMZvy7`0|!l6{PhbC{7ik!&i^)+5yI z_@w_ADnR^g2n$r$Ti*S3gGNCB0EqrGD!ABN|7|n;0|=&0e-Xh~Wy@)c0KxY)`j24| z9|Tg$rtD)PX5S0B3A`{(vI3z3KpFG*M<c<MQ*|n)uEAXH4sU(AUp-xaRV)2f$|Ukt z*`6RSH*ML)RNmdyir30GS~Dsvd}~rzF}r<A>XT<r5QksYeVBn+IJGxuC^RVOK>=<7 z2hVlBsa#~3!tq!O;~~AhteyjsA%{7}7BasfV;<qAq|bSDTMOJSwZ|U>tYpEkP8$;h zsn3UgmG~ZVXP#L41j!76`Cocj@wHX^z#a2CX}=EhR5Iv1b9B)VjN7Rc^(XBQ%I5WE zRU66OWr&J}MlC(t37*)nS{?#DFd>$4AA{8bQ9gYIuj;mjNHZ2b0h0BuR!AzIUX)7- zne1!RnwcoGW(uYJlzgO~_?cXFNw~_iZ(m7!Mnoh#=~NxtqAcm^_c^qXlQ}1}tpmc# z<#^)!0(FKgrZ|f?A#{OCD-$d&n;y+ZADpw_zC{uMV`6g<Y!EujnU0!&%Y`LLPaa5B zXE+mkO>kbjDLCvxn{v?S)^&<-`gUs?40=CXd07VKeLt?|s7TOKG|E$B$lpwGj^cPv ziilR52SeCbE5aoaFq6n&7P)x%Gx#Ko@}TV9Do1;#a`j=5w(9_wyY^th+o_qjh093o zA87TO%_1No#_{Lo{s8?;F17|l%4VtQT*!ZAg7|mf{%5(k{dEm2?d|?5g<6`HYIb@~ zxn5O)X`XqWSq13d+=yB|eC59d0RDPn|MMaH`~DABq@k;ey^W#Kzgv>P|5oLnR#}cL z&8`X*0022L008NKU*&&S`v-I}t?jqQe|rafh4X@OB*w^c%0V^Lsq~{?)e0hWx8%Ur z7$TswG_I0$bnlqrjNE^}%w7lUC?(4aE;%D;Yt34p@eUC{<nm9UAGb|77I`GhOvLdg zdMeXGNu8r-A}2HmAxXYiKcoc*12^{Zo38|^vM&XT{PF&|es7YN1EGC4mzrcvpe|j` ztY_*f5TDDV_3#r1MI<Grs<QLYs#}Rvudq6QGzV<dhtf-|tJY&On4HXn7X2Bz%sZ&^ z9XC}nw>rU_WtOa}4k>#Hn0hJFVU0vZyGvDV+b9suQq9ZQSCyo05s&{E(W27oJ=+M1 zOzoE)IJ-&9JaMl<Q971XW^^S6IGF!D$H-RxGA`H}v!q==lW{c=Bff}4V?{-Jeto%K zdLOwa`o4HG5-TN1ulA56uhdjDPnDd-CahTA<A3O+3^*j3AJ3bW-^~HeTv2K?0BS)l zp|oO=HD;K<9vXWcOJg1zIP3;d6W{@*U6xQ-0!~^W?dXXish_^U(^P<j(l()r8ps9l zTW>TK>tm=e31MKIs1*Ga2!a^z6CXwF$G@kMoVb>jvmWt49XUmm=o3%fbv4mtFs;zQ z@U0Wtf&~aKjSdI_EnYDPxQD^Vtga9r2zn4(i2<_cm$WOlN<pGM5}B7m7Q@B3_XhhJ zx7HFB<2~?&cuh08*m20%h6V$Hv5cZ9w-PbkaQ$eNW3<<3kZ`>gk<-kFsHj7y1%_Aw zH|`$$CHlJvs%Q-?yA<T0vN0>BpIFkMq=K1}JE@Gio<QOy<f3DBq1_z=0y$7}_NrwC z_QwzNTKjMyjEIZYht-gNPnBF|>o-cb#T;%Hy#W`f8uNn%^STM*bAPtr{=JRgmgWhl zC-}L6I?%gRd^JUcmg0?4Vm;J%+-YQ#YgEHA&=2}PKv<*A>1oyS;JHe%;()@drsLW1 zOU_P0!#>1L;md%HPlmP_9K|jOQ`!XRNY^n=b_$g<d=7O-so+@M@gIU^@!@l!+teni zhq~>^2GPi)kwFJ#98^9iQy4?xLB<{HBu{UYu~}>dG*1c_wHO4t8rDe8g^ihul4EF@ zY)JzXcXY&f;{*F+Lmk|kzFsyP){b>DjA$mUv6nFV@dt<aU-BNi22cq}G0S`f`b^7x zF8E3lPwuRSDJq46H}X~kfK|zX#)Q_HeL!@jbSO5t=i>uMDAp2IO)9w;AehY{{RH7P z`L`}f2oxoNK$$2=F)TyvV|<K~3<E}aSRvzz%VG0$L7+9ndS>sDsw&mGqFwVH0LF8u z%18G&CRlt?0g`(mS|DRxDYK>E1c~XLACqa`?lrdc*09whTcW1CF)@@d5*?Euf_fM6 z2anp(y!HG9l+_fZv}f@wEY0Y%2-k2C6WswJIDmFOAYU%Lq3M#eMJ%LClu&%*U>b2_ z{re!HlFH)Ik`j8falBg%(WL0fR^|p@j8tGTgR;yeAbQSmHu6}r{ex_>y9<<c<x_x# z6-p6C^(W>@L^9`Gl-tDgo*n700pB<W{Zw>PS=cbYG|a>N*f8k(B_0u_YiK(^Nj)Kw z=8N(6k@&$HDlFOfs(oiNYF?Y^-nc+d8nKr$&ZA9y&UP+FPTtGf(#+2~4X<#&ocw&4 zr2YL~@nY%e_;aS^=xO6WPX(i`cUigTxgsd3ys5MI(yb(xy8yu^D(re<xH6V!cFXgB zD`R9S*#n0&p~12|qLkNVvn6S^WJPLJH=w?F-{F_mJZ=!2x1F9gpaTZa;*tQo&fTFa zR%cnOt_spGQrCvnG|3xb=m2Q#9+-Lw$aTMt@t2S{MNJu=yad{oG&!ClWh~9`$I8pk z^68cy0S`x5jz|6JA(2|4VObOt#CgvBqmG%IKK*!Btk(wQGk#ld>{LHLyREoXK?-+_ zUg<J)E(l4LCG>94Q{6)9F{X=2j*laV{Dlfj%Y>)5x$={2VHo^&x$nhMNK=4|_BaCw z3IgtCHooEtYnC%BDfiCXQD%}N=!Ltzz69+luS&sqWldncEjtsqc?P;-JGl~cLNy32 zJ0xA(NVLo>M3<fmQby=Q-$Y+A-I1@1@Ia-4b^URP+H9vIY&ycCV-{L$=(Dr3+r|4p zovItt93V9eg{enA@07~)P&D-`$%NIy0@0P|8)`e9%EIG2Sp`7VSz^~2czptd3MgUQ zgA~KWZLDggX87lX%8Guk>u|u;2Nta-(noNgId908suQB_2)EYCl}K@Ko_@18U59pP zUyv6}{8*4LEE5jK!pke+oz;;TM-P#4D7@cCECit#$ctX`?G5AOm6;&-?5j#JO)I$b zmMczzE3Kcgpkm=57H6`4)NV>tZ!{8|bsf}gk*KX{#AEwB=nuy5mI=neMbmal%ow1! zfm+E8#`?*2YM8N?=gZtiMO8SlrfKnJlHi1JwycvpS$ab<f3B`u`y_;Oovd8TquB~< zjNWHLhopytlPe2{#Bib@1h^%FJ>o6tY}hH7T*jzta36BEgQFH3$0k3a0T{}mZ4|wF z@Hh54Yy?G0esi!zmZb6`_*pRCXyP61Z8yYHDAZ$(Zlw#epLRK2e%H|IQn6`*H^K2< zHz1{_GL201NAfh8$wY|C1X@CiG{8iD%J}2g<HlIiqx6|o5g}mUtNOH~pt?760I$#Y zDCD0p6rXJ^z`bz4CDc}2WshZ7`Cz<vP)O=j`=Emw0iZW57=}U6;0+>e?&Mv!ql(x) zWsB_dNQPn_2Q>md4oo7Gm_xB7uN4T00|#*oILH3o%v>2%_b4HOkWJvx{K3K?r-J-Y zwu>?|X^Nz}YB=^FD|=?sC-)=CuEAZd0vJCxnF$3&{eGS*eN75mw(Y0LK147?Vpopz zk7StS`3_a9r|^eBG`C~!WlQlQ*=tHWY0Z)}bUZ`$mD_b0ILr<-kndSPoB$rTiIJ`S zOT?N#F962)*UI-Mx@hf@P<T3e9@#w2f%oP3TgRt0sqDW3jb&%e$H?)`1Z>ldI>~n< z-`yu*b)BN`A*S2v`(bRXN5C#_H`<_c##h`dpM|BD6mbh$W!dh!$=4x%u?b(>6SIK@ zs+9GKdzqgxD&0!RPv;Sp<$Ww?*Y$yt-@(#-NZF=Y=~<Iu+q1RObAt)x-}H7U;xKJq z7mc>VG+xDHp?$C*ze9D2&m|U&;*r=)ACQ?t&!XN+mwuZjt<vIsUWGDlw3$5REH~F+ zHLGJ^$n@hk<K==V#v8wCgQE$;<T`-q5#0XHDYpCNL~)2ZJueD)6rca9fY9ky&Q8|c z*1sWeG^l?ks=G$oaN+>Cf<5XzB@ex4ds)u_+bX|1G_#`&(AU)};;s#_{?ccxx3CZl zcLR$ec@7nF2TYt*!iay~b0p&AhTTbGM;KGpOdu<rZn4C|aoKWAt|eOC83+(kj$7S} z;BakvAwm8TeiN^kVU}-A1Ag+x-DGcj9)9e8aG>TanD^tZc03B?AQN+Qbvir5?tU6L z`)wa|*T$3=dt3D9-yB>}Ln37@{xX4}zjQ17_hv=@U$E4~)Xmg}mVu6ej*-s9(%FU9 z($37DUPe+_R8Cn`S4lc<lL4Vue9|AoaWkdx=1Aq)EJ(9DjKgVADVB_J*fv7(8s+yL zH}d+99ci(}=wpwW#Lhd{x-s^CfnP{nUY}2JC98st@Rb*Z%eOYZtv?8xk#YS2mbjxz z`hiJL8;2opvl+uDH4gX=_!5Z&Ml_&%K<CQP0to`n`45yivj<4K?|dt{6fA4x7-DP~ znrAn6NE9B79D9C`d&`OhL`>CK(nT;FiAR0T?|X##<LYi=v$!NFV;6MsR&XyL-{2_G zGvk1PH-`0038Z@8ofy~-58vDCiCt0%zMJ7O#Kl-V;rJC0x!MNsk4(x9&u#bBHb|D# z_E+Qn(lRq!FCoK3c{>1mE|xG9v>JKGy>D6J;~yDm*jHB1OC2rukMxnkq9Kka3M(>& zBL~~0Q+j3a%lWSX*PJee&dukT5``ab+*rA)*W0*^hwa<lt^hZCx^tMb*Q4D$pQ<jn zm(yNWl~LR)e`46<e(*0~jh%rF*rek>ig{$v)Nmy_YTBh75lB#^`olr=2BT8d=8!g` zEs0rB4nhsxn}Iq&Q||Y|$59|}b3(rxK`IU<s-t8aY$ADP*q{^mhj0QrstJ&-6$!0L z15xgDg3^XtPpNbbCr*iwSLl8fm6SUf=}LjZK$TJ@*fEvnua^s@;=t0xv!lN&6~O2} zc6$kFUIY3M08OG!F)c<Cw+;YA(g;yS<si*fL8&omJJ`%}dKo`P?=>^1XEHX(84Rpm zXUJ3hV!?<zwF5KkI$S0e4eM)0Vk#r4n5GUj4H8s+4%>-LB=1kAuq2lzT}w}Ji$vgT zAYeFS$J%7~;ed`+=%IYaCZ^$-imxWQPkwOyxcIjQ!9Oyl_4ghQLjwQ^<pThq`2Ucx ztf-2hh@gt#jkdM(mRRa{to{P=TC7B92n}V$&PlS_kz@8RH)M0R)UlQmrv%x2Nut4E zLEyp;?Z>^dZSSr?FiPr3YJB#Sb&~nGtyjm}s`p?^mr88j@2(14wG?JoOns3$S3|Q( z+9osIt7REwXZwe8><1pdSV8;E!ue;vQauW5EY%tnWz~+6y~r{3Ek97zEL&A}wF@TI zQ@yhqyJ#!6(um$FDCV6@R<>=Hnbq0ZIp?0}q{{Wym|sh!F-u*Kr90`5b{+4nv$qXG zPldK@s=Uf=HHv!~Tx&JnEUclk4;h7?`cqg7ubM_uQNDid9Wg&@>ik*78NJ^ZpBW$0 zU21JDa<?+P?>8#h=BurvV*XREO_i5MrMta9b!+^+-nc!}EiD^V%mj$oMrw*CCtALB zQ?60ORhYSVg7%VmwO~iMEl-DI?Ch5#ZrbXvWF>d`dwqB^?~c}1JM&=nFRhHB)2i&I zR;^(EDY8#V?<F%<#$}B+GT-kXR6lL!*v3g*TWkn-Po}DD7+gI&_5)R;J~p$2XI;&k zDY>Cb_Dwi<y?vhVm(N-1--UZ7tr-=Kw5^{*;kavX`Xs7WHW{{pp9fW9^jq1(K5t)X z%^W_hJ9Uk&c9};fk5D#iV{>-{)z84kDB3<vOSY-oCuEVi6>T}V9xYe7<<Tly7p)ag z4t$PYbXGr#=z41d>McN+X6h^M^WHlY%9o~g)ss?N%eIRutXXm`S{a@C3TN-V%;_q& zz@$VSbyGlvjSZ2@0i~u{Cl!OG+GtIx(1-rUHcb%c^(pZ{+$vAAl_24oSf*`cggH`z zIg3^$Q8aTdHP^j1PvU$#bW_gVwlFUtly{A0!Drs80@<z=cD<gcRqo9mtK%60$s6kF znDIbTmi9fv4>P&3@Qx=8(&BJ#ul<Zcrl3Bz?_6e10&NR!b9wZ+{D06*pC`))Nw9Uz zS`}EZf#%e_js9kFvr=gWjlj1~;W869BLFX0W-MHDfDsx|h(n7pEZS<^O7&Ve+f(L+ zjI85TTDXzU6ym$P@4YIeyNqistTj}$5)>?)wNGem;TpuOsLmW25E<fFP^pD~S<GKS zL9GQmW4Wl3Mi<oN><*~?{io*E*ezAe*LA_B+uN-)uECfclTAd{9%N)apa2OfZT(_{ z)D8{Gc^W&_%e_{bmO=$=$VgnUfL+OjxIbD~NTo2aOp@A3)spf7bBJL3-`MogtUME~ zx~B+^ZN<9vZyLR`P4TNqW52T-;cJQj2|x`@p&*A^SxXV<HD*K*T6NI??6m}L*ffDq zYh$e1JVCjob~4!mMUv*%HH!;Xgkv65DpnimD(tD8XfN;h>l0UOIp-;j=4r3EZ{S#@ zZX?97UMQu3x2zi*D7d)2by@olw_u|uHk8MUy&X4)tKovUCl>rRmv|MoaCk;JqYcR+ z@Y`oI^<9I1Z(tUY<h6vqYa<9Dw3Eg=w>MU9CNhSW>tuhfok6Dn1^pWN<b(2<+|7Z0 zkq%5WM~OFinz_T@cP+S!X0TLE7~moZ9YcyC*ZhKKOxs5K&HRWVWe+jmzm=KMMjTpb zP{tJ|Ya1?x)-&E9*0o#SXcfS5?WEM26pa88PBSiC*3Be@zE9I&sU8YM()g<ub88tI z6lU^OIS$KjpCXFTkfIEDfp(TX6>_%mYaB&Q(R?bE2O4hi;%=OX3>j{@G{4-KC$<n9 z3A@}?CZ`-*aR!a5$pOzNutR<kkKJ$j^LENaS>5!dPfSPA--^4rmE&WVrA+Na)wRrJ zsGNRzn1odoi)*433^Lkl!Kfj^ON3AUdgL+cQT7DDrb~Tp*dZW&*nzv*nE&1b^0yRv z^I))agf70UFixtvcW#M<Y|e{YJ;_}#YJ{H!i>;xT(;$5@KR<FavW&XJM_~;Sui8@O z$daqu9CyfB)Jj``Dv?s!uZ#sVxfSK6pkqlevs__Gv60*#%(>+z{%Q;~c{#bay*?vd zC*3EloXL9S=}>yYg9+#QFo5*rWy^+8cttXHoSulRvz>hd1is-*)$Ul&Z$Q*Lda|^n zDtKd4b{~ojv7WxcqbPW*;_uia4}oIrG3$0vWq%S1wj8t{zhd&K1IV2V34FvQx(RLi zryxPk2Av4j>P;rYYIhdKhwJZD4r{QoH8f_u$GIK8ORWp~)NqtTMHEwN*1R->d7s+a zx|InZIS+=sym72Kill%g`sERs9_OF#qCw9F7TAhY&-s={%o;h29g<T*OPxyk`#&uo z{Ds#}PMkku6CMmd<Ywt_jM@m*2zzp5;Dvz(Wcso{&%hW`Qdh32%NPzP{9dZ{&8}*t z8@{Qn5L~TJB#?v6&>y9cCv1*wa9L`GvVQi%s*SsAW-|#R+Tg8L&CEbDAtL#lbE{ck zM1a3pfk;wH#H4k!Rf_$Ogt;ML3{}-Z3uaPh;U|=A@N>O|s$;jI;a?R|7*8&97~Ro{ z|8S7u|3QLtqX{BuDLQjL<dJJ}n!Ditz%qtZX0P)D3>G88b>+-0ryq-h&mz*xF0W&7 z`nb~<>;d=YJoI<%6Yf%8$4O-;G%lJ$#~+<8I}cT6YQ1I%cSztlLqLJ38j+`285yRe zoQfziYe$Gp;VcmJ$U&sws83{-q#d7|Pbw@dC+#6OEU_}Buwz{%md@KzIMq<@miqzE zAml-ND21WA&KYeex6K*Wb#TP=c^XV(=3!ZnyPw$9N%pv%fBB<&p{V)*ZqC1vqt))q znCQPl23rUPJj~_!4py-cid{?xBI$1_h%FAccKIE#5k{LE$B*E&n=A=ui9jU))o~%_ z9OU>r`vK_KS8W@dl-Ic+O5J#qPefkeysZSp1qa_NF2Es|1JE`pA6do2^TRX8$Fc_z zjGj8m<l;l=_z7|a%ae`s+VuWAOc=5-a6p|Nan!K|NUZI(K#9yUplBYvd3WAR*~mz; zUYVqOVyAkpX?*>{gpb8k`UC;NtcMvT#13Y$l}3~)3L^2F+qRMAYMNYPXoh3$YKlz2 z8S<*LTDFSx&@2q_SDnEhMw$>J$69lmSGvT9sIJOr3RF1ahQ1u!%s%L=uu(t4yI7~# zDUHPTj(FCyh$Z<Jhx4tD=rVLU6F3f%x0}(!D;&u9Hoz{ym-IiCnZ8EQb+AEhRo~BT zTy6cmRvlKhKtkWZkBA-Ze=ei9u3{oW;7N#_NcbtaA6IzD7ISD{@Z+_cU5(C<o+WYe zx*}6W1rm@d-_G8Sj+Px{f>2trm&U72zV{*9T&KW|u6q6ax$^8^b+R+&<jF6eXzVJv zl^4h8fcT%`q7&`;Y+}|4=cJC7Wq{s8l_3w9Pid1?q;yv-@l_&GP#1C6BuopMrwG#3 ziy8+nGo*`<g6#XcM8P7SJ-n$(+qMkJ7E8vU9JDZ;+L0ThW5}w|RHRKK>mt4cp^Ovz zd3EysDl7rENtatY!6|)eu}iWZ6KV!jcbCtg4G3v=>)loQ;Z{c;(XI2fY~WXHW8#*> z7JvbQBs8&Xm^~{ay1i7P$-p`ew;?UgZ_G7`atl$X^`tY?q&S^e7TRTlTxoRNbktBc zqF<y#!C0lXLJj09s2zH8u3fExKcD--NAj7X`q`^U=T4w?b!#0~lxmu-SFE6vcWkEt zJ1Bd0PiPfH@Tp7fI~Yp5#$rbKggq`sI6hzJhN4BDAbD`sj<noG-;_JIom|6@zqE{w z0)U|#Dnh4p;YCkP^&Cm%wymnWJBRvdBnGwB3?68=J3Xd%nSLSORl&HWkqD8Mn}Ck> z<u0z<KlCfjD%mLD{$3=q2py}-0w<Eh3cle6ksTSTW`QYTCxn2OL(vqC+DD762vTvX z99{_8*D)p$9nwL}5KOGv_jiHSZ{;Y>!sIb<7Uy38H<c)tLq5kF9>!<pv12)v1u1pV zDgzDbt?eYGI<x9(AbOn+3l%Q2qugB;eWFFJoZuE-ps*zFJ;XuwYn^%Ks;XFq+j1Z; z>AA@97K#`E)$~Eqy{``DGI{?A><312H;1E{A@5Q{^9;5ywxo#3@1eNur?C)8?Zi8B ztk4&@=7u?815A@;{={FHilP3KG{gDwGz<Yk^NdtXH?gQZ=p3Q^CK!X|1PX`I6DX4r z8YJlJEl6wa#a+(`&aCOF?-&Vfx4SS^a=?n{YSJB5pEMa>MPNTtFTxD2S+`Li5sOPr zRGrl<G@UL?4#|e_U^sFvum_H6w+PRe9<vmv@QY)&iX!p4KMx~?L#`2RW%{sDC}hGR zu6;t`B1xOebe>7jOvBM}j%qE{3F#=3)i(}E#Z-%l$~A@G{TW_twN`dmt3s33w#CX# zU`0|qE56mp%p;fH5HLPw!Cf5Aj_Dd9r<THw>n5i{+M1SCzS<mbyq}q|%J>iW?|Wq- z`!)!dD$|tP3<%=yqFwr^pQK4-2?Gh?caj3$D*889VJ)5Ag@OUpTF{rVvU&Y{Ng#8R z%?&6KA)R7#A;^nI1yq1-n70-|t{bZer_36b`Hs0FA}|pr0bOre5Ui~ciseNkMX9!+ z%7fD5K@L))!e(xpgUR2#LJ?DET2129f>I7{VjU|Rm_Sjev!W|CCcud4PPQTo5k{5s zkBn@!8~Q7{&{L*@am3(3IM5N4ppFQzihTzu$;itmGD4DbJ?CSM!R9aYIxQ9et=A@K zx>5}a%Q0dCZP`-X@hs_k0?>`4u9jVBh_llc!mthdt)P<gZEyjks9gbm@&zPKI2@fr zO5QHC;wi@we9x3FYC#t!<;g7uWh2`99vbBdX@sLngk|>bE-Sx6L-M(Q6D)ff0)EJi zl3C=9UWUR>z8ji~!AcR>!%N|-l-?Y@3i9<0fw&^Ni>{7UlK2Qk#>KC6OnRk(De0an zuf(Jg>$88mP@bIRr#=ibO&WuQqNdmZF&>hui@LkIdp*hYNK_P4sV3!@qwx#f1*^JK z(kZkxO!0{LF51mls)v)Aa%!cefPsA_a{nPAl3Ns!UhEWFrLfM3)276Y+~~*xDd=(d z%}_)13u*{k6e%Q6^{@v%%UBTo#->o`g=^e9$e}VSm!fn%UFZ@mLJ;wD=>&F8j)D~x zYeUp5b;wQG4Rg70inB{2HMGlQ4H%-s6kamyW{|$;Oj+~DNE!bnyvw)JmALg`Zx)UY zzd4OP(YCYD+Z<oNiG}Ni=0%R(b#{_du>%%G<?^$P3Gy9hRRl!7C2P@1FctUL8hH3~ zay`dHwx<p6qfvQqOT~qq&f>YR1Ow(Bl3RkG75Z4$tQeWEe23-%bGd=<-O%)MO}rZv zLk`t#KHL^}wS$PD?P{l7!ckMDjR<S!{MgiP3EM#Ic0#Eaj{VA(`qw+zU8fkZb!apm zSUM-r__3XT1T$v}n>aZH^uUN}FMmckJ^7^(4ov<F!j@AxVx?fDxdk`t3$23sg88>= zMd5e|7sGC9aA+N#X8YB1OowVF@#rx&sXtPR!CD|HB>DK_IG;<8-3iw+@%+n0u#Qn( za1lw&@G}hM5Dq#QCO#BVn=jXllym{miYEy8unJmF#S?q+;$%Jj3|8F&TeDAIf|u!V zUXP9vMDw1mz@Y}AlNka)>QGhhggiAK0~{a`Txaodz6EwrdxNwTl*sKW08J%J3W-oH zox*$x8`<8XFU91jj%3D8@Vmt&Nz*7IX&b^Xk48O4!NB!fE>Uq^bu0;_Miy{iaKFH} zK~+jsi~#*hVTal{gb@%<B_il(`027m<=(WX1>I1{s6E{v&g2fATqA>1sGL&jj4iH5 z^-daKSP`)ux7i1dJ;+EW#V1?sr3Ol{Jco8%wb*iv)&r(YWeO*2vw3=AOSs0S+PAYF zSq+AW6#~AV%!<3?-2{~j<o)I@3kPa)cwHLgpy6qd1&XFfXA;D;zL^swbjdqeEFC4M z68X1K;>&A|V*Cr9?qBP_yO1-t#fr#;JU1oQ)SV0*!mh(pJj!aOk~!&*LqF-d(>fEa z-Er_f-2~U*An8pzsIzaya-^Zo<@8X#BaBnb#*T(}q$V@X-NSlD&7V-Y*?Apx!F$~- z_{tVAzVUuPECHWZ%|WNH*vF>#nT6pjvZ5E@Ccaj8A(RO>NI7PxXTBgxum8HWi(G*1 z?semfPS0~(paeKKY~Z1=bMBC#f^lKpu9&}@92UHLy3BX?dM@6*SRw9Nn%b-R`w_Bx zaYQ>?RXZ}VXgYrk(_DG7m{XObH{&~(S9q)8xJbm8E`r~_kD!gcR?E{}LE9$fLzsf& z?G6m-aNR7eWP+b79;{bZXC3x7sM(G|XdHK|l$<g&xqHV^Cqx<HJXT9|1s$Iq?ixCF z<U?%ZiKp|JIFLKHIE)jMw`kb?QG+X{wI~~N&@apEl-e_R!E@V_G{nX4dSf%M`M&-B zo(lv+I$0~9z3*t{?tkHU2)^YrD@bFL4RevNI^pHVWZ{eF#$zfme_LGQH#bYxi<3E! zapeIvKy~wx+y!Hg)l^btCTQG#2kBI&ycwNS2FvA*5`II`Q8HgaxKbCwD!27fu$HGV z%$NUC!JGw?6ZHz`)%<Pw89TQ;gT=|rzLT=zsP7d6l<iJ-c_dl;7UuoTs-xMy_30pO zavo1i#6sBys;=Y9R%vtm9r~o^n=fu?;GWei60dY6LKTLh083G9k`km-NxS$xr>fxn zeCX(~$X-TCMZ&H@)IAVLpl4-g`#pzC(Z1K=eePmQNBt(wAr6@70t39zu9c_LGgpCh zGx#F7jE+W4g)RMV?~+(-Wg&GL=dUufdZNl>z;?Y&4NX5s1uUv{w!&1}CpKpF63WHt z&gRVI_vcHvPVe@Tr!G+VKIdK#cD;o~*Z(7MyuRzL^olEK_1?H2`qp()j?YvMZLSX) zV`2_8BKa2=U!nWSHh<90{XQq{S{!6*`kCtBlP>s_Tf)e1|17BbWfU0QHH3LJ_AHD% zNa7&Vv1#dOZcm#A<IB1@eBM@pJ!~h1zuByV?}i!fH*@N$c!yXI;=$pQi*!&X=m$5- zcOB~Y7IhgUyc#f$i%}j_$^_#clzxK~$G)#|D60Or$`fC;7BsaHN^(I(RVDGdL@Q-g zvQv2<?=)xfwTu>|s0Vwn;@$<HC-rRaz<q@9#muOqtf*=O?E!i&&s|S`U7ytiLA+Ul zDwn)tcC#hRqP0s44WGpw3ZqsZ&ZE3`7)BKsMVr|E;CNz+5xMtg6(mg~suT&*v?G0r zO#rE##6Y!QD((JeiydvLb8TGyIPaiA;$tKP)c3F`zlANbbCF1Z3dIkxJL>bVeB<s> zR94B@mrEg+K+-m+PT+>Ke+TOE8%Xr&PMoB^{_2`@zqAx*;yt3?<$3<LRA%LR1q{i0 z1V+X0)K3jlf9b}?dH}Z0l?elaL~~Q@DcHjo{CoTE`mQU>hP?@Q2OHOL`!VOt8F)+f zyY_c5{R<m1m)A11I!6x$4g=my`h~aF%jUshWZ#Pccj`}u{&kbaclrro6ERFw2<L*H zn`5Xsmki<M4>;R!&Y@^@JkV&o?rBBjJ2y?qe!XfY9j8pu(!xE%pCqKzP$cHZBPX!{ zuX+%+R)2_v$Bt=NL^aKHqs7T(sFj32BwbSxf*i{{XZoKc2ph{pF5~-cYM~x+DT-;^ z=sD1S{XqUJz4T8nJ4%76Qs-|E`=9Y&lS2R5l2Mls6_tUWlaQTRfc;-trY0qZW#)Y+ z`59?iDVhoTMkNWU2^zXz^xzeW^9=LMY-`N($54}V^b5~4tFRQbQZo}WjY<@hRI(>f zk}_>d6y+?dGgEWYE3(s7$A|wOhx%uEtPc>UsDGFCPr!=y@Ay|7>OU{f#oj^R<{vMK z&c(xpdS;47l3q$iGKQXFl17?FlJ)<pKaqM>Hu%?(I{E8J{nxnFf3B}2DlD%g5}GJo zS1ExacJ|s9fF2pna1KZh`akC4=wM-`$iXV|Nr;=JzP5O>o$O?@TDPgIysn-4_s!gd z<9Y3zLn~filt|{Na~F(~TbyzA?2*MCjK}|nPCKT(vj3mu2ZtcZxC^ztTA{mF+_DI= zI{5nFp=;NEoSH1GXz)7zaQ#ZnH>=9lB{tvR!KlULXce_pR`ivhY0}A^s++3sx64`| zz3w%kWJ8(d&Lf<T#wK3{@2HvIaA#d(b;JJN1GyVUt5hdlWDfCVUr@WSeam&-U1rAb zwa)bKw%ib-!hLRX!gZeioN)_GT79Q{DcI>Ix$430hl=NfIb5=`Bl$n-%v*g{vFql% z=_k*!Zm3SYoVS#_*y7C@^@E*EpY7XhjvX~Rx}im6d48AHw7WCxK2BcBS?FfE<z>Nz z1{v2^;Vt<kiM-G6>=s#*EMRkV(|nN~dv)9F6K?EppI^B&p5f_K1O4QaH+&<$|2UO% zV4vUj&9^jrbEOS#vA^qFz{oKF!eb%Jtl8hM7%#r2R$|A#L-((Tq_AfA^j$CV;$%J* zY`Vfzelg}$r^5yL*+Tp~);@Z@Ty9f#sCC)W1*hYD-1Y^wtE6icSU7zTD{ik%Z=0?g zTyglBOO4KH^WXD7^Xg7bJg9lp%ChgPre=t~HKUrwrn|Syx~C;7b^9Or$9*Hok7JR> zzKg*Yhq{)8UFnKj`l8YEO3K^((*awWm)Cy&d0RAh&zilzrhgS}%eemHuH%x1d8dUl z_kCK@HpTqiyQA-XE==+Gf8u`jz9nA!zUVsSRy!L>Dqkuv^L5>n_o91RL;J?Wqmt9z zg(hsXdDW94VB#Nmr@t-Wy2)#4{`vm74J($4eqb%F|MId_dCBz2GjBg$)55jv(1Qfc zsmj}0IO<X!Z|h!VZ?A3hu)aK@r(*G0ze7Bm&#XQl*t`A335DeszINs<``tPv@?gfZ zI?0F6d3Vf8+O6gI=a+PVHzSh>1MU-0fLRR;wl#t%M6Sbj9tyHq*v|EUn8d)at+5Qa zsuqh`D5rcNn}+Rp283yUfv2@#GY$Q)24oYl?IT8*xQr94iP(1)qZ^C9!w6yQW8l(I zEDnVli@x6o*<5VPkrC$dNnte?ZE-TPdDzygBFwXu!D=32^(wMa*cPiIjOxa26qco{ z$Yx?&x{5IKB7QRw3t5qk#J21JVPpny6#^n=uowBzMF{AIqE7-N4AlqjFC%0qY)Tm2 zRP^c?Vd`%O!lr^NXLKXct5AfIvm6N<iBh4W8;xFj!i?U+_=^amA$4hhH!B-RA0H5Q L0c&9fR}c>XfkJ0l literal 0 HcmV?d00001 diff --git a/autolab/docker_tango_python/Dockerfile b/docker_images/docker_tango_python/Dockerfile similarity index 100% rename from autolab/docker_tango_python/Dockerfile rename to docker_images/docker_tango_python/Dockerfile diff --git a/autolab/docker_tango_python/requirements.txt b/docker_images/docker_tango_python/requirements.txt similarity index 86% rename from autolab/docker_tango_python/requirements.txt rename to docker_images/docker_tango_python/requirements.txt index 9db6120..0a73d68 100644 --- a/autolab/docker_tango_python/requirements.txt +++ b/docker_images/docker_tango_python/requirements.txt @@ -4,3 +4,4 @@ jinja2 tabulate compress_pickle pyfiglet +colorama \ No newline at end of file diff --git a/examples/example_docker/instructor/unitgrade-docker/Dockerfile b/docker_images/unitgrade-docker/Dockerfile similarity index 93% rename from examples/example_docker/instructor/unitgrade-docker/Dockerfile rename to docker_images/unitgrade-docker/Dockerfile index 08764b5..98a4007 100644 --- a/examples/example_docker/instructor/unitgrade-docker/Dockerfile +++ b/docker_images/unitgrade-docker/Dockerfile @@ -5,7 +5,7 @@ FROM python:3.8-slim-buster RUN apt-get -y update RUN apt-get -y install git -WORKDIR /app +WORKDIR /home # Remember to include requirements. COPY requirements.txt requirements.txt @@ -16,6 +16,6 @@ RUN pip3 install -r requirements.txt COPY . . -ADD . /app +ADD . /home # CMD [ "python3", "app.py"] diff --git a/docker_images/unitgrade-docker/home/cs103/Report3_handin_5_of_30.token b/docker_images/unitgrade-docker/home/cs103/Report3_handin_5_of_30.token new file mode 100644 index 0000000000000000000000000000000000000000..a861bfbfbcc029d09e49a0a278598a0e2355ee66 GIT binary patch literal 113733 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 literal 0 HcmV?d00001 diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/homework1.cpython-38.pyc b/docker_images/unitgrade-docker/home/cs103/__pycache__/homework1.cpython-38.pyc similarity index 73% rename from examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/homework1.cpython-38.pyc rename to docker_images/unitgrade-docker/home/cs103/__pycache__/homework1.cpython-38.pyc index 6403436716a672ff6c51a7026fc0edf847aa3213..d57142a7eeb21f6b172586b47613efbfeadc1082 100644 GIT binary patch delta 32 mcmbQmKAW98l$V!_0SNr&8f@f#&B!L9pOK%Ny7@Qbd`19y%m~l` delta 31 lcmbQuK8u|@l$V!_0SHY0>1^bF&B!XQpIA_^`48iKMgVn_2&(`9 diff --git a/docker_images/unitgrade-docker/home/cs103/__pycache__/report3_complete_grade.cpython-38.pyc b/docker_images/unitgrade-docker/home/cs103/__pycache__/report3_complete_grade.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa3a3187faae8d51a53e55a82bd75e0521299a19 GIT binary patch literal 50864 zcmeIbTWnidx+X?acUiu>Y?te{i!PNYTcRvq>Y`I}waaB!SEt%l$9A30q1wZyxHlzA z6iMy9Wmz(nxwz*{5};;!f&u1orZF}!2xcB~o`T>YKpyg*r@V!jw>&iv1OWmBK?8F- z-}nD(?F&iSZk#^B1Q}JC<lcK-{`Iea{r9!@_eYOTX7G3K*H<df{+CSVf8|B;ZwePb z!oT2u%4RZd#?3Y|r7XU4joenQl*{toP-AFos5B(MvyI`>u#_7qjmY<CX;i+)N@Map zUK*G0iPD5OzA@<zyCe8M;*S0>RGRXpHjcVu?znf%&AJor<PRg+jNCioPPs?jV?Ur5 zxjv5T6N&4SxIUG*K8@=$dhIT{XWetZ%$AP3=iN7dnJJz4D&t;o-}+_7eajo!IQeX- zbP9LgcHhCBcW~!4?woP2y6?FTYP|ty4<D@^-PzJv8I>L7xzahgK3_T?-GATBqxU!6 zi|z;bz99F34TFX09xQx_5zHh;RKWGD8PPTOl6x7jz2(li^JwdBw^(||T`0ZlUMaoj zz3si{z2m)$Q8?b%#{19n-o+Q$(g)sKp5uM+jB}QKkeR*y8_x5+*=%<_tgP-dDxt^U zF22M1mbd@LoyV2N4z8TN){gJ^Uc2Ro#max5%Vh36scf|y-jXx<u;utWO{damIH4DW zfm6qYDT~LGH=XugxZc9eAgHf3x4dS!Sa#dR_THqe+iEuUob4UduA|&lr{yQEii<C6 z!<pZ9uD+xXdCn&8wJ?luy&gD0)vvb!r`z%Zr`ZafHNWDfhv;BXeRt=aoxod40HgR= zEK5ll&)s?xc-Wg`v+S)BI`vu-c@Fw-SNzav)qtd}mb=sNoI<-&-K?y6vvW?R>7trv z&}Tr!)%H%^2eh`Tv}|h4Nrm|>r_~np)qp*N@vX|9v+5D-+D@Z5c^6OW%^<8atKJ*| zOY~H$0}ud*$@{WZ-Qg?+3suZ|;FV2B3p(Cw<!XylA9@(FLi~$#!C$bUiUlGGX49#z zSDI^{<6=0~u;uSLVau6`d;Eo90Q8i^Gsc$gplC2ZIT?7KvmS=+U}<5&ZB+w+L|QAh z{I!K^#ZMP@f_igpp}OTZ>P@e>9&R-zXNO8hTu+Gr<W?H>+Vw_jt#llS+p2`2=a;Ju z(CU6}e*3rC&-V-8k~%%R5`=^zS}l0Rwc?Tx+3f6aX{1^4*MjbFxYzc&W3E@L>@>pe z@VeJ%?;o3=57t||-jg;)?zyGO0OR+{p}*tpI}h+gro%p70Eobmi@6Wb#%!)TTB&k= z{V^<A+ZbnJ2aWCLqXsI?VAu1N=Bh2wwJ8(qkI&EVG`CQE|B{r}mPv_izupWB#bR-m zrMEzYLI|wBKQTYw1~x#}``2&nge{<`UL}!(7z0eRUG_!idm%Gtcin3`uD80gwg%i6 z?^X%0{X2>ulqa|GFSrYN5@w!fUt|u(I*=%v<Ni-NV;edAn$TYdSvT9sZjSn2xH)&| zmzhrX`H-7^k^M6BEc^4^LG~cm91n*(*=N~<Y&g=%xWmtKekUB=7<)e6$!tu#$OfIx zNYDu<-4VHaMDMzz&xZb9wlgGs{+H-;>R_k?nFR>`8Lo~V42Q=$=&ds}mFeW%vCc5= z9>>2EouSUCJO0BYdLKWSFi`*R;YoMmSvH#qPj%$GGjT8(p2XGUGr)%XCtdu)_vz<n z@b8TmnHO32$eWpsvj;~6u5+CuxRY_G{(kPwjC=H4=3q+hpYKe)$o?wpe}NW`nU;=s zrpUo@=LGL`#=*%?-s@(IRp2xF1CP!O<KF}P3(nwUEz`+7ngX6Mu4mbQoZB9LkomV6 z@cSW5KIkh~ywc4+_NfcV$8V<>)>~WNLN!>ta;<OeM@+9f`B|eLgru^YbL@+qj%IS1 z{mXXEly$k5gK)<MUnzdpXn}%)oA_aCmd;pGE-L|-$&mJ^Z#s8;zvVAE55W}MzBg}+ zflmmXOED~c!^*uUXtC(rUiYe-x&{pUQO4JRh{GDXJ3G-Gw`;h2lFPE(_FHRM5kYyi z;&(^3>!G*R9eeEgtE~V_*(`zXQ7LN|skyW;bw_o5b*H3M*lLtL-`_v`o!@#~572L= zVfhFyew%fE!wLJ%zyICuez!jnA#}eiqRL2IF8;t-uRQidRPA{oI64-X@2z2^9yml% zMX<TH!ba-NIpEWk0OAA!n#`WqTG}7FdGqFe;cLAWRJJ?^OTG!FQ3+Vx@hf};9?s&$ z**CkRkUN3rcPHh$EIcl|-5m+qje6)G!63S0>y-e^)9>b5;D7B(xZWMD2W+79hQ*JW z+Cb*EJ8|bp)f17`9jmMctm2<TQ$C5cd%TBuN`h01c)haic1MJP`6T)77$h7yZ0XpV z7q-Cd{F;v;2BoR$j_-r~<gz<X(3*JA9bW~8<kzTBPcVD5JE|1xpCCM^_z^)?It?X; zjC-pNeWtl4gYnO^>tR96nDW)2I|-et;gz9HRZ1iByOgW>rI9su9j`P080((v8LwR| zGk6$$f{#pgJbNlTiq8wV@!Z?lv$=7(8qHnE=6*LhJdP*h+2QO|c06|`dp>&%SCiTC zA^!^M_Hl+0+!P-C6a0Xloda*pJkPwykh6kEWS{3c;IjwdzTirkzYp$P180h^hErD~ zys8a>4~^dI>uMA|2ouWVhcKZF+N?B}l~1XzENOPAo89e>w_Ct2(AOPrt%el{Z&xrj zoAW6?y4iXU0{!E-{T=?r{O8RbuKhvW#^<{8FW}=hq#1NMO7ZRm-CVO>8UrQ!eq|4R zy^E*a?0QdM-4SW|9v%i9T_$%5sP;d_uPQH-e?#UXcpE=FF`P=de~_s`umS1grJ;WS zE?ojE1`wyeZ`MOmCLelF!uva1lPPqvG5+IN1CnK!d6wD09CdR45R$+Jo@cZ7z{^L1 zoi^6%c)46hljSlR9}c`m&7Vi5?&#(&DfQoGyAx&Ab;@NOB1zHz3?JX)UqIYsviolj z0IL|aJi=bMe9GnSWVx&=Eq+gx%iBAZhCY!tyGKoLRj<*oJ&L&b9R0{L@R#uGA^u6< z<5{`>YvJ_F1L)3J_4P{QA?%B9T2S@<s*DWX@Q*V{nMK3~_whlbJ_k}k{<*ab6ay>< zE{sQcs0C0H!r_e(s2S(O(ataxjTcxlogs+qq3!dXp@Wf5ZeyY|0<$*z1I2R=laiy2 zHMHsN%^vap2wikffH@JK+u%L-$ey}GTYjrM37)dED(Yu<d<RoUi>fqkOLm7TFo|%t zUd6O_htVY1Z2+^ZJ5q1D-jmr;e;FN=j<P0OzfE1XG|5Yurl6E-1l{cI(h1(GqC|Pi zgU;**zD$Emh|s}TXy;GyFCZ7pWT$duE>qbvU^2(D`_qFETkIXy?1+C0mHjXHaho4( z)&G(ok*1*?OP_b};6KGbJO8|XSf$oz{|fj)jgpN8UtrRV26?nI<zEkT8_*A_EB=r> zIOYyNhiAbZ1#dg{d_;L0CiQr8*d04K-Z}alnjEfAgkzoK?!>uFIDRhUPM*tjPT+g^ z9Htf12QBj8WH<@k@(3pV&v1Q2^wU4<z~8{DbD57bC^6PK*%|l$EB9zK2Tcw78A#_; zIQ4uA+U@Cj=HN_t^jYTM4eFH}$Dcui{Z;nSKZF)}7XQv2oEJ@U!vA-jv+i*~klh^e z|K0XqcHVe?qH_)~z6l7<LEp>y|99uy^OG+!&Ed|ujZ+5~+>@rpg<9@x=HM;B@s@kc zJ@o@^{L`It<<s(c1`xd6c`JOQe6}-*&vTu#_&g6--o*Et;RW|h_=fw&54q=Wb>2jO z?{wbTc>Cbpkl*h@BD`mR$@hEDvcMLfIsC=4o4H?QgI|U3L#uw*J-s;$o%JumeCK@U zy)zm2?3qmG-M4^G;O6wfd$^1Howqv|+;jhOq;sZo_OlGG&y~+~-W7<?>zMvL<G%Up zp##UgfU%yz)mxqOf1dgE@WJ~SrQ^OW(7)3G4Ce{W`}{tW0X*;P+=XNA1)0NB&o8>~ zL~XwN>!DP8Lu<JgnCb1m3_s|cfn70lkUw}ABhH7@9gH2Le^-8E)Wif}%#=?9-c0#S zM0=#A_k@=6I_I6!=P}-ci=B(Eb0Kr^LFZ!U1J^l|IiES0cEt;H@L~9&dr@fZ1Hk+t zo=-o^f$~0-t7&&et_rwwES$ku0ULh51<dp6v|t|d=g$5x1UlsyFCAV|#|Wt3t>rpr z+{^zmcQDhL>71&OJ<Z)KO_0fdMMnsHcdgdeY_>F9Yc<1eb}}V`$T_Jzh(verLnQin zH7jV!J%hrqIqbtIGx9tu(iTE#2nrV@QwZ-%B*q&FE+7nmqOE~{B3NlgoXG57?9q(| ziF6%+2+#eK)9%9b*246kI@3R1n*Qn1^n?AJ^LYQroCDv74|93R5!Ie5`Tm3hC7-{X z{qqHQN}Tz5n(GeTDC$uOX1l5^bdOn^p&Zsjm?y{o*!rxd>r2jbKql`sT~#k`U=$ej zgxV1GaIbVkHA7+VYj}#k|KoFT$VL3{ukzy>KETLfdj#DP?PJ)v<ZSOx%sLL7Qemax zzlk!x%`W&8tUv{2e{5DC?T_2>{)RF<CFMA8lyK3!!3^YFaryEe9GqY|7u+E%QLsJ` z#mGJBO4OuoHt_!hwK+gcAg1p+uKd5nkC=^f0=wg%H7Z-HZsq2`!PWnXf0_OFUowAM zcK?Ko&1tt_i^8c8I+(FSX>_aJq`x9J=afbRui9$5zs=6VfiqI8`w)!7kZmD!wr@%! zQlFYnNF>3}>_Ka61hWO7qwWqhTU09dYV|dEu1ZJsyR0ss?(hcs?M{k0Lq}229mgnZ z^nv;_s9ri!E*mO=-m-!_zQYe{NpSYC7zI^#!fV2F=EGrHny{o2l-_7S>7WP??)t4Q z{-9j;f5sLl#p0<i9r<1~KXLhVk8UfO!88R{{F5kJ8bt@IFe<COI4-5xpz4a<A=r7P zamw`ocYG={rHNptT7_MUL5-qf%MVK9HK1k(#MnJ{3lDsn#A0TvDK}baw@Jm+XG_Cd z!Oj-k_X<N9w}4XG`3c*I2AhAHA8+vEEI(*3b&rW1%;i+BH7aY}(SSym4n<9-$<_K= zdEKK@=6k48nyLiVdL2eMmEUf5wKU;|u!VquH6^zNmKx(dEalQjP=zhl9VLocz9}n0 zRurD?U?r7Cw`C#u<SM1ycDXc!k;gdi=7L51yIRWaLN#*3(wGumP#RN;3rfT7hTrgu zsNX~E{)f2d;$QFtADI!_U&mmd4Z|FGJ2whd=~(s@u6TbmcOKU{JUO2|0X1qA|KYCu zH=a9%(v!JMxjf3?UhV{Jy$?~ZkUJ(y7t6t(!~JnwkLL>63z^~HjgEYX9t4WiVkgOy zJ&?MBOZW&gA(r|x*mQ30S?(X5ILLK!u<+o8%4U9+xsmzf%zda`Vm}Ps>khXt|G-BG zvtbMxo!Iq5tNT~GBfCCOiUl~n5wx0|7cRI>@bzr12RGfZ)qN@bKM)8i*cpt;|J(RZ z2^5lb1nYkX!wi~Awv!=*8&Ii3*fxaR|44oT`rspKdC!OW91?ffg~|<o1=a4+=VR_L z{AeR@g0*doZ%ptPN{k+iKo86`CJ#pC`w0AvW1^XjcSfHd?Tou)FLM9LKbQz}&yRJ` z0+jt<Ko6KaIC3!6nZng~2S=eV9;d$O{}bu|L}#*d^aZp`xjLD=2an_bY+yuN%^X_H zLEoHseyVe<bNmG~v+aMf{bw=WJLAt!zo2bB0X+zU)1Ack2)>WtJKKS+{UGybJq6F7 z#PC4V`d|PI(r0)k372~mbARgKMCSxOkZAe%XNlSWRd)LW{5#vcqOHNxY1)9F^rbaB zj3phrN4tcs4cI04MSo7Y{7}Ud?d`j=D))!rqVJCF*V`=GJrcIsHonk3YK+JF7UN9& zV7h9(vPRaQJ?sA+G^3pNeg4Yh177yJFWws%pIQ+tT5OK@Smy7t%!oka|2_WB)mz=M zFZRGBzy7u~R;st}5{}{BN@KG-4z?HU*5Qbvc<#cJ(-5~*hgaiR3F0O2_joz%gKN#6 zg!go-U77$(L97IPwwvAZ|1&)IpYh|D{9w2Ke}RwEB!*pH-Ko{Us)q0zjkH>b4`2w8 z_e2PcIetOFNyjvN;56!6TgTRbT?8t0z`_5BV!;>q$bh8`4~>qH@r=SrG(0>$G!946 zF}Q^24my?v<H&)5@V{IJcfd}D&S%eN9bq(Qv;IFtE0y17;R1jzl!%uxPy-Bz8&Q}p z0|&{lpkqUV|1m=L|M3Xf{?z<@vt=FP`)_<%59rB+|6N_=4qW5`149+WgBq39{WA|3 zzG8F;S0-W=q$Cnmgow-kC8!G)40t^KjIfV~;8vj*lK<s8LmR`0x(scMOd+q}hcVd4 z!v`af3ZvmD;ybj-@htc&*npXhaj38(FgN~lJR3Fk`Z)OYiGwlR0hiyHpe?@rw{V5B zs0&}u2CVsw@sD9#lqaPX#Ac+Gzd|XA--L`4;VxQwi2ldX+EiyuVor$cz*BnkMF#vB z(gA#Us)H;AaO$J@CdVGzIEG(S_%+cP?TmkxL90i37q)ATFl_%D+#hz2k-IbI1P*>a zJpTLyFagQ8adP7reE<iLMDB?<GwA;~cy?yv6#Wh;dlxo8uFhqssvn$mMXGd8bWYZW z;Q=@m9^YV;iJxzP$Deuu>lSy<c22pch5w%p&pkg6IdhtlC?SP_RoL+VEue*^iTKi} z`-b@*>zpDMML#%u@9sl5nYJ;Z33bosB}KU(@PoF3SV8;OzSwhi+EmY=!3pyyiozhV zg*gc~K(nsb5C}H$iD2os*_-=6x#c5t2_6e`Z2@wjUaKP*3Kv)XaovSq5%I^>J*m!g z14M2-hO8YjZ|BR0-v^F|1O>!Bz*6_${<>Lh><9qDV;SUDm=N;po;T4cFna`t9(!d* zr>GK_-ZD>&SYlWKVCci{Fon+k5UjKAnBo1Uu`;6D=*Oo{Sjv?j@1LFy%G337tA@|% zK!@JV?XHSxwf_^Q8Gxr^oNhf7lQS9iiNZGutg>F95laz)CW{jeX~~&=Q)~jCZf5@- zeo!|pO~&gOer;W#V$!H_a4JTql_er%OH*{6N4GZF9tA_`4J%_AG(lWXQKs_teH7@9 zVm#Gd*Z&c#&^}TrJ&uscmWVV;R+XTm?Rs^Sve(_&YR{e#tD-xJkPSlGaB%wmV-}^y zT6J$~{{Ely;~(Rro82r8Hz5E;(sr|D|DW>_C2r|NyRt`;E2A-(5&?_=jUtHJUmAOC z42lER5NX|G#9lD+RS{nfTjeU!U3P+|7mk8?1@}-fBPzKl@_#isK_!0_@>Qkmc=iP3 zDFZfB*<(YLwve<?^2c-ho`M9v09F3%(3FI6_}u^3Xt7Gg-Tu+b!hfQ~)7%chBH?L9 zaOPQt%Mr@zP&oV?ith{hs)y8~hq9*b&*2WE3d2z6y(eDvA#7trF$FEK!(dQV!`V?h zgGvJ>ztLJnMArXT=%zbD)IlFvrIOSgtHEephhurf{}+6EOc%vsS&82NuW_@KgM}nU zVGkZpX(8xw%sPP+KJ;!Af8(f>ff<DVa{gc7cjd>whUPDNpVJB^Coei*etYlX4AN)x z9zw^pngf-`a}ac^B2|SgBKJV^IN}VMgrPn;i3AL%TB)vkWwAsGt<?=g2IqwLh%<bS z&L2j2mmeYxV|HoM!Jp}p(M70rL0QRE9=T0t8SoWJ$wi}h7PtVVwOOrRlSr*Cr7^{V z+cd4S2Rz%_M0MV^bqpX&kg`n1$o~wM7Fnji4?dMofUT8;*U?mqA((S!c2{TSoLPo) zYdRcu*fW+QS6qyZx#&5#-g3UNzg5W}3fGsdruRM`C$VeB7$ZV^z8E7KXsb5MF`%<d zKhmiIl56!Rj@N*`8MPo-@x5@zZ$|j;>m>yP)$v68FPlo|qVu(ql@QdbSFO<Tr<q!I z`XM7=n+VTM(3BHa)*xg+c=VwZ1<b$hFwt)hu|#M{&`jv>#q-HI9X$R1>P+wCvoQY* zI(un>tG!S=oCa9clTbZ0d=s%Jc_9^!kL;fz+lM~oG;9Sn>r|XoeCh&{$%SjLpOAZ+ z_*xUwhy;hOC@7n35Q%IQU~&G@ng<sV^#fHSs?1u@gfiD-dYV(mQg6{t+O1=H-PUf? z0M+z%t=HEOdKv^zso&nbXi=vFDJsUa49PY|ghBB=tPv3BWImstT!Ub*xUN%lKK#(( z1@OOxFC?|)zm@cdAa5T*!mRT*FxHMZ9O_&Sf^{Ug;n$Dw4?1>{lL*gf(G7Qs?&d<c zv+gZ0UZ|;v>;}nRZMhOC3{Qjiu)W90YqlpXXWa|zPv5h@U;$$}f&D}`w%slJ+wenE zAKaP}!l}3H%6TB(y05>r<?AgiVdI%acqy4}Vj3uu(|L{Nm+<dmI$OS$MhYF-pDA>! zxfeC3`5pE$M5uh%uSDaiwi;+=tD>#gE87lIAU%Cj+i4;JvDHAPoAQG4R%O#GBcM9x z-2bLk-HaM&KxTk3Mg^((0q;nyu`t+?*=u@)Cnw9dDSEzc)>`0xaiu~&x{sx>l3%Zf z0e<9}PvYsX%9EhJ@9`#cR^<01a9lf>BHbbdE(XXaFI3s(fUEHq%`Xo=bWtSni$Vk* z^%@dkkbLA}fvAS1{CiE8ZD1IUUe)qXiKpUM_wU@fcknbgAiLk%^IJQ}PF;4?zwbO* z0u>eIX5k6K6I>;drs@_a=`>E2#5D>{Zq*ts#8rCQkeht0FahYDHWk3~>Yk=26mHLf zkwD?ZO#><MiFR%y5kcZ9<uZQpX1Sb{9kv!Sa`6luNMYxj*4moq8wqGyq_SW=P6Z}Q zpigntB$zNl>2Q}jccCkJ5%*)+BG3!JF%rC%onS902B8Zyr;8xT6i>CyNqNLBRWs54 zkOvSj4>)EhskVz+)|TpPz4v+4N38+IB4bK4eG#&aIbM9`XbJ%HcAK8(f`c>sWwMB` ziFyE&Xtj=XuRRrd<c&;<i%@#I(uTAKu@#u3a5zpcG4+IzI3j47oDzA}l9swGXq|H| zUDEuN1gwGvxSLsr7#@IKZ37SWM~ob1!W3qsgq4B;AnGNaigFnyB)n>6GyPQ=lE)-I zgiVZ!NJ4j8O{k_|zzznZxzeJW#;EB9I3fvZ8R>`14w0bxPx47T0@9a>fq=^{JC?x~ zVlib~)xpQ~!)c1?5p@ma+m1M4^;X<YTAcOOPFGXRu@JgXBdZ4jGXfxzv!}?wMKr{? zBP|k-km%m>wwNit-eQV+&!S3qi`LUOq8^N7<^WO?{)FjKX1;|odZ`ZL1zuq(5l;g1 zT*Ma)!iuAYh1nR9P(h-3RwQ#IHTWsudT;_H)|z2O5T>TdB09xI>?Eg$*n?_8u0eE+ z_WoshwQ$?6*wkW$mVG!zBsj_`Op)Ya*ocM}KH;j(|Gb$mGSUhTff#FyD~)LeC<0{r zNOLcnI|WpXi%F+uI(l084-VTTX#NSzDsVA83@5^gJc&nceqo+2IzJL2r7RhjDzd)y z5#aRP+f3*BsmCC2P~bKLOx{K5O63=<DJ1Da3L2WG*i+$28LuT-JSEBE+mVgXuv$;i zbo?b824FZBQ@ufv9@G<z34+NawN$5@oMVcILiD`I3^p#D7zT=-Rr=^Cyn`gr1RQN# zh7<)qZz)C~@Z-8syDKw^0yB@$?+SVnvBqz-j)t(Hc)>kDxKThA<LWEJhQ?K#z&JDe zDA9|-646hs<JCxrmfwBZLZwSq!qNaNuO1aguJb}5(9<L(MP-BBit-6Klt*$a>7$G9 z_Pg}rXU%W$d-3WO@}i$sp47LXrg163m~_K+WsE86BgRQsqEuqmtfI*KoTtL}oKuD| zkBvQzpcuiJ=1D-a$qr}n8EI?AFda5z|F*HO2g)C)OolfQkS&^p0Hm6zZ>GZ}ev=zT zg%Z48?s3(<x+W)9)(_}XK#{ejc7pg5BhROniievRsx0v26s=r&BveJ`QMufU07e3- z-(rFW)dZbsy`p@hEdh+E>zCQDZi~pk!im~Q)Udt?z(VFxu#aL!Y~8*B3E;IgOt{p@ zO2Id_wDs55MMhp=Yg+GcA=UKC()_hYM!*(OH1OI<AjCOjTSq_Ee@{HknZ0$;Pl+Ps zjT%ihj8R>nR_8RHQPD>e^s(FWH=!YMUSI@L35j7gdlm>-${i@PCxLLVwHRKxy^DsR z`%-!mw=&m##tt9PIelHnO<mL$j1Ry~ZL3HCD57>?x0jtfDA+_T%5JL+4k)1uIBJX7 zuaIzxq!+ogcE~E<E(&+i!F@vx^8lfZtt6L0^3NI^r!m=Kxfslg=>a}WT{vD&oI7a{ zYys3J-fQfZlyBM)^kOgTS7N1ZVZVTxW%a7UYFS=POzTAl=~404HEfW?Br`d%=~jFf z7KA2VI|blnosVkRGPyS!tM~K(!JekEp+$?#!um$;CbV)Qj|`Ih@nxu!Tqd+ezd?~7 z1+k}ZIcu#J+-H94iApZ9jHMLO6}F<~D|lkI6B<$2#7RY<Qb}ip?U;&KCjtHw$wvoX z;F>AKXp4T$(rr_al&W+al5p1p{5+BA$I1@-+Ln>AAT#t5>rzH#iP+2=otucFqj%WD zx=0iA2eL^ONE6KKVqJkMr&<Ddj0B_*YswiZt?W4_VVPH<IGM9FVToLXCgg1F1W28* z85GEGq#=t03I;uQD|_(oM_p<^2spuc1aO`nL{DCKU>8jcAx(1WSxX2i%H(o@9cJ2R zuV9kl(}0ePBuJc3`Dfg}+aOH%t+tb<kQM*|uo2_)-w6vdBy{LmN*A->7t7{%G+<jZ ziISCVh{9Mgqh^OY((Kh##eT8K=3dd%)0t*V;}Te~o>NZICYLc+jJQ>=A}eVI`w~Gd z@eKWq5@y=#WR#xdc;YpcWS67@M3R+hiswbRrnHt=Q?E;07TLhNZL@bupzVszxRu&e zDaM?xR4AYDFG4d|<+8vcr?OjgZnbH;G(*JpRZ?(;AlC#F!X3p-N_CK@{kY=STRZSv z!BT`@266yXCL2CE2C)iQG1%K$g?m>QR?}LpMs;S%2ImtdS;~TsSxLJ}qqR}2L7^_> zrxP-9n_j;3Y*3J){7vUd+yKd~o@k<jg~39XoyESjSZ7t)`))gA>AIBdZ<Q?z5Gh^- z7--3CH_{;J(2JVXTWDz4hI(R?TuWIO>>xYbPMsR_{(#Aq<p)C+`5aX+Z$xORA;P1^ zsHs1*GJpBe=T71CCHePr_mcDTqVse2GNP3obL%p0eU9r{mRh;vJ#tnq&!du-`g}%u z@Sm_bPhuc61_t&>eX#f^ckX}j?SnfyWJZfo?}<z(E@J0`K3Rbqx&pVlgeD6!#Tk-E zg=bOZy}bOMRVN+8vnWLps>uCDLzQt%0O+=W=4LTxH?AaTLD^%<o#9s)m5M@Yg?z+X zit(@Z-jegPir<71f^!~PE$-BX!754M!TTT)+hJ;20YPMO-a_!=v4<_l$dL(PY3no& zP>2yM6QM1BPISf{4$Jv1?DJ2geW6Fm>9+S^4f&=5_5EJ`85rShXITx|G-2u!>kfoK zjM%pbF7udQ=?7evJ!Q+#O^72rNv}(3$r{@7oo>rzV^@k-7Sp#$u$<;7z@hCp3#!Js zVXtD}hZHo>cQ4&Xo1D!U_|X#}OeP_XDd}DATiclPr#JX4!N#R%0v){^LYwN{@IIhB z4FLkcC>B_JC{e)zG&`8s%hD3?p5|q8SF0ch0v#}+E9f&weNEm+lp&?^#^TEaQ$k~e z$IuxTCk@#_#!tx>`&QKhMDZn(SOLQD6TMpErEDoW3C5mk7r>=yiE~5Ct#4k_5a=ic zRqE}G5oBACsb{R^aD#_MiaTu{#Z%}J7t(<|uQrgnl0YyK5IyaqEqTJ_RMdD-443t1 zCCm?K@Ej|3G&@`I{D>tmfbI-QhX@uI`B4y6Un!-j`73%sC?E`50dicBa&m=y3<{{V z#(6x87L+B6;2lDr!SdpjD|69G;aL0sfHkwJXL;sngWT!hDL*>S%2W6U+JWaDeD>7$ zsIY@y96Xvab|af2M;BWS2Oz8|?G>BOJjC@9%~@o=AnFs*rbNY<XVBRDBL!b{73lV# zemcz%5wrBdI!{HC&>*7qDd1mC%Y-SK&f=iSPL;3|&YQ`<RmAc)V}pf{#Lx^Hp4V1q z4H#Mqwz$`r1VyLQeL%>w!S*Xm8n$0ya=w{U$2|bZLqKqHO<>P$g;2wmAxxQHg24Rp z%r4^Hp0zQ61D5WW4|+E#8w5UhKIk?Q@X*Xwa)XpDfbA-@oATe?dhh^p8;e#qK&kM~ z-@WzqH+Q~NH!Hw{hT<byzU&kfR^)TSWl;b{!kaNK;+3PEHNR!x)~$0f3g-2(1gZ^W zy~-L)Se;0NMi=!8v^Cx32xT0zPQ%57PtO{2ht(2XRIAWxY;D3zAGs)T1}i{Ai0w>) zbCcrkhJvMTb8u-O4CV7qArJ82PAN#)SzP3SYi_#>W))*P5}kMUxUAjUxO!syTA|;! zpeGq=-{LF!<cff&>I~*!ev#|!$G57Rd$fvaFvF6G&jQnex{Uf!o-p03RkWsAI3)K0 z+73qPRkr5vC^`uWmPG9Bh<UmkrH~9DzpAQ?iQUT7MBDEG5sVLcNO%O3cO<ebYY-H6 zv&Z|TtB~n{QtlfDPG;`hzyIxhEGK!0I3Fyac<m0XL5jB(?9RW0w+zWi(!BA!Am<Zn z8Vx6v3W=c~GNB5}Y!U6;)qRD9xQ#R}s?~DD4B<+WeTl}o6C)<k4u=Cx^gjTI9w@L0 z94*DpGajNK4nXefJZv5#I<S@)ht)H4Df}d+D0N?vVzEailb9*8yMa1Nnjw)o`KLSq z*-18$M=FDx#M1;1{?!;vR<dQd^!ro^DR4NCj_5(ky(X(pRf_l^c{Z)Ih+zv{5!V*B zX4!TSSf0&(`S*JwidB^NNiV&!=OtV{iLEpq2Ro>=muQqdG9ukt3NZs&JmI!~zZavo zmCT?yQ%MCb5;y`vWQ{xoO()h!f-o;S8;()wP=?!+Bj9CBTn)TSK@=Nfkg$=I$9zjk z*dAZTOj~$OjMr3gwn=T>PgM{%?H5Rlg9D!$pNx>UV*xEALj$>t;!RAr`+?eI!-I_3 zi_TrNWmzPeL3oED93F=US63Qns2F?jPe=fZQmRKW{QZV3NQuGI-Qqx2o(Bg^*NfBj zSsYD}ccz^}LOd6Z(1kO~$PzmPa63f+#E1nd>qx!zT&P?UxLQ=%O*;8K*OH-OfY{@k z2$$2nSSQ555e{-hNWnhEz|w7$gOwOnDtnh>dHEtgjNx22>fu00!Fuc}P8b_f6ygxN z8mwS^rPo6u*)4)QE)m^A-FmC|z+~K}y#uZGLo_>hIlemC$f`Xwnx!4Wn2<^88h;>} zc+y8=v0H)wEqagXt7QX{xzexc6X{|mmQkjM#G(PS5E`{v1d2PG_=)gE>ZAl~T(FY; zOBDdp(k&EIk5j!4X40whXfFR5{7+c%$b82riimvD__OKQ?Y*1%wEZReX&?0uLW5lc zsUBaS_G3~fCS=soceNTTTnq&>l{ht*RK&3Z*wy1ap@qw46r`>(VkLu&(DahohL58^ zpv$3{*f%^3X=Nj7l;B1MBlSe@^lK#1dKjpWSOEJtL#*VpJN_k>KLMi;G+w(}L_f7) zUbw6=7zMkr{y{a&wlPav3f5I*VCg`5EG&|^8MB&c#$?Si4aH<G3k&CRf~;hu)=C`c zpUDWWS&Rb=pWgJ>7zb+C^l=nBYReF!9O)>NmzETx@M2I6%By8&{e7|idN~CQdZ}lZ zq`A5%t}&#rzaJ4qTrQgI{%3uA2?|;*UIs?$%3Oi&ak{3YjG1yq6sC<rDH~gcCb}g$ zLZQ}bVTB@H$V~;QvGD`>RcrOCi6wG_0@9ELV0&CKHfgNy$WD$0O|)L9n(tNY##^9= zYT=vu>O!<p0Rcy8fV)aKDIhy;4FKxI(&a7Ax9Q99{lll5nQu^`l_$t1xw>0`q$=cp zBx9o|r`2M98yH);j5G$jL8723+?u6rhM&~0LB{lKO3NnT3m*bcaSRIRH6bVnv*ghN z5_EP5%+Wlvc(PSwV6#Ek?L56Pp5%U}Z97TUhE68nM9mx8kh01i%^f>^X3qq2QIHi9 z1_hWEx_roCVD$7Vh6xo<I!W!}5>J-hy;4NZJtCiMF#<<jT>%ffZd9sXVP<9qQ;$#V z3&|rb3PQh~aQ+HNz$15*0JtU0Uz{Tb<(ozxCXUQ%B?yoof@7lL){yY#x`wmX8P1a0 z*lt2(^KTth<=)Ch+@67r4cP`Ri?a+ZL8V;kDH+EZR~n*pOY9YuC3(8Q#}alU*);6P zG=3oGaTM}P`Pl?auZ_E2B-!O3$najVt<CUwZebGyF!%*05eO~8jiK1U>QC}vy<;eY zTab~cWmhT1>MfjpE<1`U$R~q4w7Sm71elS?R_sLd<5P}J?SKJohAFM|4T{b~?%u$@ zcpN5zSUAlVK9r@a_Jl4y%pTbTJHC%SUGs9*zLs+CGy@(3Wp{s68gRPdka-`+(qoIQ zoD8fyGTGqOM2pZ6{CVz5L5_T?>)xcAIFbTwgQvSB8hHq?%}op*=NZe$FgWmBu5IpF zTE}*DY+OSE0EGBX13L>9R%{9s1Ijq(kpqbRO-)!Z@5lO$Ntf>xJy^3zeNvKOM4ROX zN_Ip6Zq@KZgEe%5U@XbP(aPy(Lnd?2wu*ZF!i;MU<?tXbme%oE!v5L*``a<39%cDo zg^ZIuH^ybl76zu++WbCsuJ<JUQnKAw%a!D2%WAk7d`U$U+cy0(FBs>QwyM~nWmaP< z;R2bg_CEEWMDb)wplU+;Fi!r_IUYC&&@I?IDkP+hS<g{C{c8tDM2MuqDsZ4#>B_ZP z$kM;=hC<LCTTrk86?B2Ul}HWRQu0*7EGA-z0%Lm7M+70yWbGO9AMnI3ub6(ZjvI{! zUiiHRYZ3#Z*bypCEw@;s<20ZQhnOVwqFI9=d8F70JcDc|ZCY0*k}b3#08eD3Ne8-p z(kAOgL*g@O0#8uOJ}=%jS$zr7l`N`UKk-o7z+__hN%NCPd5FEiUt(Kt@6whkp?w&O zve&_va8f4pHtN`fpkw<Em#J|{p`cl(QfCX9%Tzf#ehjLWl+_$C&jq)f1IS_8x||iB z7&?n_69jZGW|3MpW2R)_Q5-$B>EIF;0m04=i(5Dk9@Oqif^w7&!j9~p;V3q(IZDk| zwqZ-zZ{}YEFk+}cP-A4<64muO__ZlAxDm{Yv;!ceG!m+r2Zp^LwvpsFi5*9CBGX0+ zgNpq__q^MB^hnpRu(8#>A3_$g`C&#Sk;_Ahu2uY1G&l3rt@~fx`s&V%u2kET{gxKE zm<?SNt6mf5G$Sv{^>hQ&95$bLZQWKlaG++74pV%jwOMMI(G|S%18Z^0MZJ>wrs56D z5U(}ZE3=ViOAbj$FcAx|{l;V{>?@6!Goxb;d+>xLYAV4;)`YMg%&OcrTqj~BFK>*E z4y?A)(+4v^!IB2z!a@eU0HrKR<BAL1k`GC5Fj3g*;xc(XU|tFaweZ+&YFkf!bMiK* zg=-2zV-7J<{(H%M+3J|LJnTP7RWlQ~@kr9w#KKMAe>LvetY!|_4hDTE8DX)9A+AC1 zRXux9P2PiP3yeg-z7%(8OkPT)j8NJZyCJco@aoFOQsYq~HEU46<TaoM%0#l8>Z>tP zt*)SkA+teE+Iyzp3Wmkf1<YP-_*nQ~0iIHs$mBB3+kX3;GF#H(XJD)kB+7)C?2Udr zFnb*OBqlku_3-poO7KBoFMtX($gPL_RzT4|_)|Gl{vLb>(X?9Q%Z+uLzOFc+1hL1B z_LhuB5-(kw=a<AGjr;*Pl%j$_H?D8s(`GPI^CQmFQY|zv@c#&g66AlVUKzSxq(U!h z?Wn1!^$r4<73};-YGQ*E3@P;zrJJ)Jj+j`JV?2aTq)m>|ct@lswp*sEVN}#!XfbRq z4MkaWwgQbKQvsqDJW9K_9{8etcnD>Pj@n;1rAj8z6!jZ598-*F!&KFkzywhEprRWI z;*rQooYNN3p#l$a3`NNJr_9+x5*z#}yp>oBQ1?|;2erf0K{|2N09S#GDee}pFiYAi z@kEh1{63Q<Wqau)CiaBTPqcb<MJG)8hWhblRDHnCRjkW|&Q610BTfc`@d7Xza(o=4 z4WX^x*i@2!W(Ub*^bsK|0p=kbEzjQa43cSvUV?u`17a(QsbzkTHXM^xVpmAiIZq+c za`99R;z?C(7);QZIe69(*Wo@?%Q1U>;;|Fl$_533d$W|{5<FEO>1-Kt#Fhh(S?*U7 zFb$$tR!v~VEZ(%OXA#6TEa)7(f|b_$T$nNW+IGxl8$d@lR$?5l*Zb9%bPsj8*M)j$ z|5U9F05aRc*pytz^NATM*l-}>317q4o13m^AB5F-K^(%Aq#GJ+>U}mHB*@lGBN=l7 z5aq)$Yj_<9v`i1~uW~OtmvzEbofv=QY>)a*(8%j_YKDzYs3Q#oAtp>Z$ID8+X0V2o zp$0MZE}5c-FClexscQ-&($4fUTEzrpP^cneaZyP_gW8HxLxc^qlE4EAtJfhEXKk35 zd%@$&T2b?dQsYXh2#cYfGG2B0^2jlgYti|cylzd8FhxX!=Lx~3WDlX+#rrJzvNxnY z!Wjy|gM4B-+E|wY2Jdg=S-i0<GV9hLE?xjBxa+ZQ*3V}m&<*lXn3^$-xR_Qsq*tQL z3QTe@MIAI?_Kk~rLeVPQ_qbtJr5{;KbsXeY%l{{s>!)@1K-7?$964p7`J+Ty4IK%< zhQ5QX=zJHy=MEdi8m+y;EPQg&$Syi`GDlm(fKcF;9weLrVbAr#`IJTHYu%EmXK&a; zGMF?8zdxo1a+%I&Br3ocFHU=Py0`H50?dy(%OmXdOwvl-B}K@Jh#*T+Y81{AoWxY8 zW%?&BohucuI-8yyp&4f?X~-^?O9B9ShE}^nr|NC9eg(oM2WPH9Dh6xv3xLb}K?f(P z_VR#YqSc58ps9Wq$$6A+6HC*uA8W4Lg9v)hcTlug)+izNe0&Jrg5)xbmS&4Yu}mg5 z7u%Z+la(Q?GqJms4JW|47@d1AhP0d{<`n8huZZXkmnX86b;%}D#;P0`kd~ZZ8EZVP zX1Up6)^T16jwI_jO+eb2O+dqih(e6TqI_^zg_!oy%_|fFh~)G_Pk7h}Gc*UBDtg$l zlIBoEIZ`w|C3agbD8NkoMdu6bdq!Lzi;o-dv2znPk6{~N3XOUdDHrC5F`1Tx#?&({ z7*_(hDdn3i_qObx#`|*M^TU2pZp}w%3Cvec@1%{xdt|-gZoQgds6?<*V6;ei3&-2_ zZrJ-q8a2>sAa56EE80FWGIS{KfDW*epDBi|=I1;+mF@BG^@zlhgt1YSBGMC<3M^~e z$pb>A3jL&q&mt=skMw|3E^U+i9yrIKbk>eW#)7YiuCfUKj8ypolOcQ$vO<TLYAi09 za547>*wP~EeI1f4Mb5a0lem59K&Y&SFpng`2M=5K<3ts1B4vsQEE7$%kFm};7^0tG z|1=Ll#ykOEqQs0p(%6s_VqryMhgl*jW)?kO2irtGE>1GWktf=PWcFz1J<Cx}npnZ< zxG=o27#~x>C_s$vJa*n!rP*vQ5TxP*kLWA*d~QZfY(=%t71-!7A$yzO&>b+L!j_9N zCuL(&#;&Kn$w@CqZvQ<j;luWH`*sh1GzxXRK5R*TbMUH}+`b)8L$U;a0}ARJhVRC( zA6|pCs_@j&Qmngu!{TH6XAaA(<!?eZ>I^uoDvs!sG&%D7pw)Pcb-v_0tu_vdPqz*j z%+_O#g9TM8^cA~2Wszhf=e{G@f%FEln7bGxwLPI9(Sk75Pf5QA!ZnTl!CI*CQecTM zA$+uO`!;otm*aqggc1WRXz3MJj?*|S4tl>6IB}40tgyZUG`2zTBuarkUkw;*AOvQ8 zs69X;Q1Mb!ur~il@u*7gg%E$iwwKTpwdCMW5SKJz*4yiWZ95L^z7IC>@~N}Er*Mm# z^Ul+ToW*Jj*l%GFo}WAv%05Ei9Ygl~qNEzD#|OTuK73(pEDB~uM%kaVY1`|=VPDgO z$7XT&$`06(%nra_;g3hq*5t+@n91Sf;UP?ptr;|8)&5u$qXt80ibWY&^uh2T_UP0? z?FSakO*ASwnQWbZksD}$9I@=(NRZfW2K)EznLE82u-){RuLN!_a4R)uaS?KhOLEzy zjG(=E92S67oAKkm=B#Zb!cQaVJq}f6RtZ5gs0|%`V*EW#nMO>;<a!;EPi8ZkR*jm^ zJEnx@iRz2Z!A7GKp^;|kdwA`3bi=fe6s-3pDFxaWxCG9)M!r;UC0KBhY(<NYQ;Row z2nUi=kt5<l3RK||eUW<Px#*~Rb_Ux<o}M|S8hVR*|H;P5vpIHoO-}NN(l!ov=2<^# z3KCxFh<JxbAR12+Ekr8SS7v*)(xoguE#5Ekol8|YFQpEjbql6x1RqZ?mScusRYIV9 zZKx?YTXU0`5wpdVgd9o$d>^Z>7jFt2fdU?oU)B2@`+(Uy$4`iR9~TVNIMIx@vv@bS zJ37Mn{RC2xrI<hq`y}Ww<k9e=+DL0Fw*2~<IeeMaWYL4#TlKYdoYEV>cn5Upwq)Mr ze6Kjm*wEyCou(u#vGLfCq3D@F)l&;>wana&UcA_>D5+!mO;(+p<bpxPLstc#Lv)dt z6vx5OkQxya71E99{WJvovNZ$Tm=>s8-u~u+zFpwUJGZ~Rf9v75_lZH!c!YFNG&Z%j zpyWo)Cr)*Xt8MXoA0FJ#LoTCr9aqZ#5wzvFSsH~LE%L1P;zFsqdFOGxl7i(h^f0<C zX_g7dOg@kCBt*l38;D_OY?c{_$FKB711<lL&FfwjtBFW1=GKAc*>oB`IV)<9Bs>>- z=$?eU3pE^c0~-A?EC;L`s3nr(iCG1AiZbiW0Y;Y9u_x>vTlLH|bNRwqr%8735hBY@ zoWiNp9tvN))IP+{?sQ$)gwX`{Iss5AT{yW+VeVPF>Q{qw(CNytFtV$2ukL{zq)Xw9 z)lU{bhBPn}g-vSF`wBG0wX(W{cMf?r=?czJxz@w_VS;+Hx5QMp*S0zP{#Sc#H&57W zOBbd(UTt80!Fz_3B%JwfuGz`tq}=jBI`(n#mN^dmI~K<4UE+O;%QJU4F9hAZ;)z+X zJQu-h*KsmDjseKq7w9>Y4{?lt>V!hfaRur*>nu5m7wo~$2D!+@h{dwo<_-Z{SMq$L z^vtUa+QwJK#h10=%x^naCvV#G-~0NI=WOC$3&Rl$SU1rlAGrmuEHy|uBP}&lyra1P zF3x`E`!nLsbc~B-eUSynbC;aG0aqlNH4br*W&(*+S}_%Jo?gYA`|046$m%A~(w~#0 zO$umjy6=rawyL<=O)UhJQ30>$&^JJncUx?LV9C({c5MeQfVqn&)|7{nTiwT|CpEvI zZ8VXdT-9^p7pizqB;I6TI$F@8*I+Mi;e`@7BczRyGR^cC>4LxDC@U6-B$&*Z<9Q_r z5!gdY!A)l39)BV2fu53h#@NywaGL?B3$x0D@9ZWO2roD!_ri6Y&(`wS7OoXPU69i< z7phw>xma;M+-mR@BhXIqiZ40tOZ~0O(W<y#+2!t}A0zDw=+ZK?X7%vcfHF!7b_z4| z+gK4zTnEcDpW`RD)^KZ4I*K=k)Ak$f<(Y4rP)F>`rIipP8ZUUowc?VH>MRa<(Y4y! z+dSPiHW-2K9B5PW4kYpImqTo>$G}W)59s0{UDyf>Sl__8YrI4+?}ZZ#sp-CUFO_D1 z9U4mLOd6#1%NBye`_a??U#kZ-OQ;Mgw-)C<?P1WKP%OJVyeGc;0ju&=UENB(u!ogI z#L>Nh9SrcFVGThWg}8IQ)txmcacf0%Dk_bgSCck~WcVF1$dpcTZln>$=BSx%?pij# z6%XdZfNHM5En!QPP2fnfVC|_xqy@a{t-<#_q;_+W9Kq!c3CR~p79v(O2rPOF3m2*E zw%`h8ks=So9AHvM6-0QTSfq=fwR};M^=z56fVFpl0FD@Nt-z`+Jk}jN;v2L@?Cu(* zr^gPGexIo=j;7d>y);e{z+-g>i}}2z<+9SKU=a>14w}5*I;`sfIUpRIBI<f71yU=n zG+<)lf%m!IZ#sAAk;VC&SdZ9c9PgMh%Z5E;=~__iVa+dv1*Ux@zr@*IbZ*PJk|Jyj z&sA;@Q;&?o+R;Fg(wrrL7<6;r%(GruvN5FDU&%f&wald&CAxB<*-Ic;QpPcJJ*&ka zJ+Vscv?6DabZ*Z;lLP!XX$qO4s%4=q*?S{z$CK?lJVaDh{j&KsO<8tGhL7~=1V=Rm z?99gn?43F7b=h<tEdr|}9{!zLtjP<ItwpaL@&NquP@O{lJHPc9p7<8u+hY)@lWP05 z>Lt#+L(pAt9V@bO52%lyMer+6P1jDC3;GzFhs_$RQHe|#31VJJGD1+WxA`V0sCjZ1 zr|YgPK9VD!xH(h0T@!61PI6{4J|7lp{;V9N&Y)+cR_Qh!kgZ)HCb%T6B)mQb(tA|B zjLOAmMWALp>1GdR%c=(sZVfz!*?!)n25T(7R7DU`9IB@<CD=({w(0Ta&6`-h3QiAN z^Is#tf|{~F4f2Zb5`%pT@R&8NY5-g1t{l5$^sIP^wD6ThG?{m}pm89!F4c>&&k^tB zF02&lD>J(@IIADuIHE47W9rSNzC{#5{E@QTI(574$l@|P6tQFSuhWPOm%A>xVKb=s zK0H$EN_NIgu)eZ1$CnB4d(nJfeKZ&CO0*{rQ0w{|*eCw#H!5kM`~ltvMn78x8+8JE zI=Z#FuwmWaGgH7xff3C6+)~LjJ#4X+JT58>ymuQXk0p0>@xJU&rhK+e(7ra(wIgc6 z3$ReVie}5!`C+^OsS5Pds9}w;7-W9T0K{!z4iqNGpp|f&6p^mK<+tpytCp`|;URNn zXI1dTx0|J~!jUG{psf^pDfrg2G)!3BjX33U(74i16=_9mNRb@2<=z^2#T;^Y;-MPC zV==8dP)F;RI#h<NOwr#~h8U5e`|6{UA5}O-62%k}{%&Q4QP+&*NS2^cP_8v9Yvx4S z08F46n)1=KTXgbU8cvBqo~JeVkjbQwd0CsQ^|kUkk`6pQ7#9;MT{mGoNRV7S6h&Od z>SJ6b)*C88wO)sG7WM|^bFWs%i=RU<eSVokE(`hZvDUxx(VG}x7%fzo))qLQ9VWLb z#W4(Lr6FW0j_+g_sXduCb{uWRmgveOM_Wy6kkUS(5Q|;qiW@>(=UcDH#9F)D-&v!} z1jlH*3)5Q*(|_tr|9ENor%Tfh45Z5SS1F^M$IRSGzH=2fp-C`Y;<-yGfFl%b2YAH2 z<1*SPLh)c=Q7uG9VU$40kC&Q*bX*W7H3Os8nkwcd6$;9}beK$DOa$=FyL$*{x|;N* zBZc~wFTjU@*L0F<Wlq%BfRh%9exbIApreC8QUk5!O$LhUyt)e<2x8j8;VL(Jj!+h> zE<lUnWJE2!DXAoI={JBUnGR`_OfES!)LaJgd8^uhCpK}Y1YR<*i!J^Lo5QCkuWhw< zfz{HId>sg|x0AS53*+)4Gal^oc*T(7nRZZbCSKqmFRTEJ3hb-jim@$JfePEv+l<UU zXgQ$3nnHYMf`vezBR`4@gD)c(l!(rAU2vF8d^*=38}rqft}pR?uIWPaGPZhY|MBuw za!J*O%TSIDjSFoTt)2qF;|*rpMXRr*Z!;S4^Z=gcudoXa%9GA3<rb4!L?*?li;1+9 z^f5`qQEE`|WOJ9kkP7D_S+kGPqIUm1)574+&4Fd$C<A+t14RwpH@{JyODJG2p)Y~u zA*7d4hg9tFy)aj8l8=mgIRSL)jVDo`2~4AD9F*8|nq30r3{JC5pod5Dm3L7y&|{eS z+g}7+Bf4v1#<*q6Mdx=i$0gN&)N%p<8acbj%7|`Vau$nMaEf^}2qEg|itiv0B93WD zXk$h8vpt1YyyWbH@@)lkX`9ET{fR>KP6W8Mjl>PS>jT8k3;A&n0h=OGTRHL6_Jk?4 zKVuvQi@1#qk2vS<DHhKGQfT00#>-{mLkNPaTI6Zm(ZRyg-GjJ<;m3<WasiH)OOEAB z&2Q$3^<Z&Uzpo-%!^2I`WW4gUwpel`HvYvt)n<IG)bbWU1rNT`d>=%7!!PUaLN?g3 z!hx~MG%ENQ@vb2;SY$#J09y*rIMc}0#anjmO+YKnB^rne*_#@Gg1#wPtmb{MC?++{ zVzLdQ7yC2$*fxd7_NP2PRNskYH)`}K2eD2dO~~Q=1e&@74&&l`k>-tf(!t@CyYz^n zB{y7H_(;jtoLDS3kuzh;TryAF4WD~~%}b2yP%M_9TlfW>l1Sx)QX7ICw16WMsisrb zEXMLz0n=%hJZZF*mstgN04p4vp_5N&DAKB&`^BCM1{w%q{RRC()EiWh=LUE@*I#^| zpUtOKQXapIc+q8oZ6?X0e)e*HA@#sc8R8&m4r!>(DowTgZ4-fj<MMA{1W1IFm1(*i zut*e(P+orDkvYcf20Ikj1ccj6$j-oT@QP<$Kk=FZCJ^+*-3GanEGOkh#>O$tB>ijf z82kaEk3G@FjiA-E@x(aNxVnGU-t2qNy0|S?_w_B%tWGT(aJDwRCXT3wJ!_)K`ljyI zYHcT|il0)fO|vIYlDlCeOE?(a0UbO{VP+Pm^`jx+e-|%+qerC{>sB@MZfV;qrcumw zQ6S7g6!2ATwT;~hBqGjLm8EIaMM<#SYRdw`n{c5Q;-&FT_!O&~wgKykG0Q7|=@K#u zx1dnbkyS1;_l+E~hBe2#bn=+jedC?X+)8e}S$4|+^3GX-m}%RP+kj%&z9$EL;9aJ1 zZ^nC$BZg;@SjQJgYkz~A<XU{qW_b2Mxoj3f!t~g}OxRACZEw8kR^`;kS{bo=j_PZH znsydmZh;{n16AB<f*sLk60b$4#xSF=UYwD}#dMCR_~j^y@vY-0TFuK+6?Z@@ZD+xE z+Coe>{Cgp(|7DlGeaawIaQ?qA%f0lK7@DHR-~xU`PJ|&oa}y-uRm?3@pjfK6@7jW< zSY2Q1hb2PtW@=TrwYtIWMG*6?;um`mcwc{8*xD<jk%UW{SNd}KbU_77R((&eg}4ex zZ&!DC;9g7<7ad%hr612b*DepuKu{JtP&6%3VunD^Zl$paMiQ;I9zPbVa$H>b{O(DY zAyr{T@JSIyjyd}c0qA^BJ1oe*8rMT&Maw%c6*e{!!>q;ilgAO+nY3qt*z4W~;t-PM zDapYRSULjIR4FQSvY1SI5(55?F)M>Wu*I;1h<C2cpbUp0?=Qx0Gr!y|CrLiWgOsR{ z=v68ut_6rh(G<*5ICaMqw~lk=<`g#3)at6^bS37>?u71{vsaRg#lOXJt#{AUADVI^ z#ph5mu_gWxM3g|=;O62jR~8fflo*|Jln~6lR25I~W7%w&I`k;bmj^8azHT9&;TY5y zJd`*Lj+gcwYgbB2QH~d)h7wZ$gQrs=y+A!0nw(^{BMBXeCkx3Bi8P`jlW8eKlW8)l zUjN5*r2oIBBh5EkHazgET$eBFfuw)i@_ZZAOLBE8NR2_h_^Vodz@#Il(V#3HKD2Ob zhRm93-#ggvjAJFj7ET>NhEUMNxdckL5MDbWa)$W+YRP{wQdHi@s4il-%E&?(x`$Le zEXBoioV1Bh`mmgXwC~!)N?t#*163A|mr#Q^14U;q)6?rdf8sJlvm_<NvzRiZ2e%X5 zjl(i=OJ>@XGg=7|`G2At%lS*@#dy&z$z!xQi_;A3^K`O+>UFjk*@VDJ*0{GYP`J90 z69yJ$Qkhhp;){d@WqszC@l+WT%IGbE95bX?>%&9!H%brzk-+-nx{Lkc)=V3)*T%H| z#U8v=1e7)j8EsK)N`Y-oBJ^;))@yjPlZpJ}nBrK-5b+fAViy|6Mt|=yoOYs2=S&zF zWTvo~DRPU4`alauAZY>GPQ6x_{AoKtWD~CL37Ak%D|lh1$IM!j3;*!@0GWR{G1T9I z-r57Ufi`K(a(DirgV0GLWNBn48#o-zUpK3b9l^2a&{U=s?U?=^z=+RH?rubxzR*jm zw|=}fVUeWt0jzn$bJSJ|3R~&f<AC7q)nOxXk6BW9uXLlOZV14EqlIDB7*i)gkhQ;= zv=UW=cH6i5u?t)HR#Yfzdw@=FQ)i$q6HnGx7M(5yPtZVRhoj&h$G<XjScjrAANH^h z@Kjp{3qjkff&>*ehiKpmiGFGu^o3~Q$T8B_n_2xUo74eWt*->$tfQ-ZWmDGNCJBTk zd4E%k8r(4LAlqf&7Ghmh;(;IFrb&X1Q(j_!(4|Y}0_Rd(VxQLkCem$X#ypiln%|MF z5eqa12Gar9#4|DQFz?k}9{4GvH*c%vQDq3LLpO}6Z{PtDWn@)jjhP)=>PL*dh*I0Y ztIkGEv3v|GUjyIy^5gtvrH`VG2?Mvicj8F0buufs$~fU%o({^>^>V9*&uPS)=%Yih zuy-^zQV>HpXF$;vYMZmxMRcsIy&>jCj6p69x)p&u!Cq_t$bI??`VSLG9t9Dp4EF2? zj6IUaeIm#3XJ*7%vN5-~Yk)^YAkP|SHISVp^UyF{O(#?Sr}7c_hlc?PB9_B*^&>9c z*ES`F4m0Z2ieq@Av7^nXQz8L?8@SE($ikvU8%U0sg*d8vkBQ<4R|vQPPk%Q9=AI;g zA-+?kY@9TPa66Nv@S;~PZOc6u%US><)K|9~-Hl3DA@4o$s`**N(c+?Ocyk1l1)HSQ zW~3u=oI_AprT3^vXZE`H#1fH7X;R8W4e5fg%-RymZS0UIWmi=#Q%qWB%ySFjUEQPL zFSOHd7}rKYM9G8>Q1zAKr^PE+TjsSUKl8lJMZTX(^SsPhv8}*)D4GJ_oN43jg4JDA zKrk~pTY=ta3@t2UKSA$qTJhaigT*V?itCsRzLsmzZXiPOXzt}?3k}Sg^KjVM)w!#Q zoUg%U=J{$`=LS~`iQz}wnJ2%JU}4iacXstc$FqsR%J6>$`<!L(7YBlX2}Uk0%}0B! z^zafS-h;rHGN!w1;P%HGZE-57kiS%p#&i+c8G2vWHrYItFOyB5*fb)V7ZW_{P5i_z z4d|$GB>oS9Lo?U6?52S4fa|_Fk1%E+1Ysdm-Z!Vc^B~i7DE1YvWk%0?(LbP}YnVnz zCFI6ALNx~N`=WVVE9kc&QJXH3zx}{jyt0V1@ul814KQ1hrERrR5F*FRi>^TND5S73 zhq^eYkuH5v?YLtSCxnoV*(QYQ7q1vbXm$lA6*zVw;6A!l$YwI1T)A@n(;J_BdL{Mu zvHR)utDk&Qy>jEm#~<Cmf2-FPKfSJZKl<d#BA$Hm@%4{xe0=TW>o;m2Eq-+UqwAku z|Kub5t6ur&qea}mhNo9QHg~n=_0=nj*DIC9n*8DF3jSUH_=ah**4rXlMw4tC5O~@? z%HCi<Y%yx#8hX8s+Mj;peslx(KfYG;uC0Djxo!vb@eP!4uP=Ul1Ep#xwJ4>p-MFH# zYI|tA%DUC-H!3%-Ms=@!eB-KD8Z+}cJ9-a)ckj+l@e_X!`QL2z-t2T~L^dM&A7?Td zKgW+De$@E!6Mhu<@jgCE<JO(!Z}9Tit<;Xd(wRYfq5QX5bSk+M$}h9UEByFW+F>d$ zxufQ<_)EMw$s6B*=^{_$4vT)syTgpp`A>QMulVt|`SDNr@vr&u7yS5d@KHMM3#~3% z*6sfbKAPi4o*#e4kH5o@7yOt^e;mVQcl<L|Wp5HG!74t+vs3tQJpL~?erEW88-3$U y_JsbIo17URJvDr4DwCD}{{7^4eKm&1KSB$W!~fUl5!A{}E{{(PXYpT-CI27E9~HI$ literal 0 HcmV?d00001 diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/homework1.py b/docker_images/unitgrade-docker/home/cs103/homework1.py similarity index 100% rename from examples/example_docker/instructor/unitgrade-docker/tmp/cs103/homework1.py rename to docker_images/unitgrade-docker/home/cs103/homework1.py diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3.py b/docker_images/unitgrade-docker/home/cs103/report3.py similarity index 74% rename from examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3.py rename to docker_images/unitgrade-docker/home/cs103/report3.py index c97b5a4..f83bb53 100644 --- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3.py +++ b/docker_images/unitgrade-docker/home/cs103/report3.py @@ -1,8 +1,8 @@ """ Example student code. This file is automatically generated from the files in the instructor-directory """ -from unitgrade2.unitgrade2 import UTestCase, Report, hide -from unitgrade2.unitgrade_helpers2 import evaluate_report_student +from src.unitgrade2.unitgrade2 import UTestCase, Report +from src.unitgrade2 import evaluate_report_student class Week1(UTestCase): """ The first question for week 1. """ @@ -24,4 +24,6 @@ class Report3(Report): pack_imports = [cs103] if __name__ == "__main__": + # from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet + # gather_upload_to_campusnet(Report3()) evaluate_report_student(Report3()) diff --git a/docker_images/unitgrade-docker/home/cs103/report3_complete_grade.py b/docker_images/unitgrade-docker/home/cs103/report3_complete_grade.py new file mode 100644 index 0000000..8ea5f2e --- /dev/null +++ b/docker_images/unitgrade-docker/home/cs103/report3_complete_grade.py @@ -0,0 +1,338 @@ + +import numpy as np +from tabulate import tabulate +from datetime import datetime +import pyfiglet +import unittest +import inspect +import os +import argparse +import time + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print(s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + UTextResult.q_title_print = q_title_print # Hacky + UTextResult.show_progress_bar = show_progress_bar # Hacky. + UTextResult.number = n + UTextResult.nL = report.nL + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + obtained = len(res.successes) + + assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun + + obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f" * q{n+1}) Total" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + print("") + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.normpath(os.path.join(output_dir, token)) + + + with open(token, 'wb') as f: + pickle.dump(results, f) + + if not args.autolab: + print(" ") + print("To get credit for your results, please upload the single unmodified 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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.normpath(os.path.join(output_dir, token))\n\n\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n @hide\n def test_add_hidden(self):\n # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n # See the output in the student directory for more information.\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n @hide\n def test_hidden_fail(self):\n self.assertEqual(2,3)\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' +report1_payload = '80049589000000000000007d94288c055765656b31947d942868018c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b047568018c0f746573745f6164645f68696464656e948694680586947d944b004b04738c0474696d6594473fe3b8a400000000758c0d4175746f6d6174696350617373947d94680c473fc45a520000000073752e' +name="Report3" + +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 diff --git a/docker_images/unitgrade-docker/home/cs103/report3_grade.py b/docker_images/unitgrade-docker/home/cs103/report3_grade.py new file mode 100644 index 0000000..3c64c04 --- /dev/null +++ b/docker_images/unitgrade-docker/home/cs103/report3_grade.py @@ -0,0 +1,340 @@ +""" +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 +import pyfiglet +import unittest +import inspect +import os +import argparse +import time + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print(s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + UTextResult.q_title_print = q_title_print # Hacky + UTextResult.show_progress_bar = show_progress_bar # Hacky. + UTextResult.number = n + UTextResult.nL = report.nL + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + obtained = len(res.successes) + + assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun + + obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f" * q{n+1}) Total" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + print("") + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.normpath(os.path.join(output_dir, token)) + + + with open(token, 'wb') as f: + pickle.dump(results, f) + + if not args.autolab: + print(" ") + print("To get credit for your results, please upload the single unmodified 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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.normpath(os.path.join(output_dir, token))\n\n\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' +report1_payload = '80049568000000000000007d94288c055765656b31947d942868018c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04758c0474696d6594473fb71ac800000000758c0d4175746f6d6174696350617373947d946808473fb127100000000073752e' +name="Report3" + +report = source_instantiate(name, report1_source, report1_payload) +output_dir = os.path.dirname(__file__) +gather_upload_to_campusnet(report, output_dir) diff --git a/examples/example_docker/instructor/unitgrade-docker/requirements.txt b/docker_images/unitgrade-docker/requirements.txt similarity index 86% rename from examples/example_docker/instructor/unitgrade-docker/requirements.txt rename to docker_images/unitgrade-docker/requirements.txt index 9db6120..0a73d68 100644 --- a/examples/example_docker/instructor/unitgrade-docker/requirements.txt +++ b/docker_images/unitgrade-docker/requirements.txt @@ -4,3 +4,4 @@ jinja2 tabulate compress_pickle pyfiglet +colorama \ No newline at end of file diff --git a/docker_images/unitgrade-docker/tmp/cs103/homework1.py b/docker_images/unitgrade-docker/tmp/cs103/homework1.py new file mode 100644 index 0000000..3543f1b --- /dev/null +++ b/docker_images/unitgrade-docker/tmp/cs103/homework1.py @@ -0,0 +1,21 @@ +""" +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). + """ + # TODO: 1 lines missing. + raise NotImplementedError("Implement function body") + +def add(a,b): + """ Given two numbers `a` and `b` this function should simply return their sum: + > add(a,b) = a+b """ + # TODO: 1 lines missing. + raise NotImplementedError("Implement function body") + +if __name__ == "__main__": + # Problem 1: Write a function which add two numbers + print(f"Your result of 2 + 2 = {add(2,2)}") + print(f"Reversing a small list", reverse_list([2,3,5,7])) diff --git a/docker_images/unitgrade-docker/tmp/cs103/report3.py b/docker_images/unitgrade-docker/tmp/cs103/report3.py new file mode 100644 index 0000000..f83bb53 --- /dev/null +++ b/docker_images/unitgrade-docker/tmp/cs103/report3.py @@ -0,0 +1,29 @@ +""" +Example student code. This file is automatically generated from the files in the instructor-directory +""" +from src.unitgrade2.unitgrade2 import UTestCase, Report +from src.unitgrade2 import evaluate_report_student + +class Week1(UTestCase): + """ The first question for week 1. """ + def test_add(self): + from cs103.homework1 import add + self.assertEqualC(add(2,2)) + self.assertEqualC(add(-100, 5)) + + +class AutomaticPass(UTestCase): + def test_student_passed(self): + self.assertEqual(2,2) + + +import cs103 +class Report3(Report): + title = "CS 101 Report 3" + questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits. + pack_imports = [cs103] + +if __name__ == "__main__": + # from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet + # gather_upload_to_campusnet(Report3()) + evaluate_report_student(Report3()) diff --git a/docker_images/unitgrade-docker/tmp/cs103/report3_complete_grade.py b/docker_images/unitgrade-docker/tmp/cs103/report3_complete_grade.py new file mode 100644 index 0000000..1101b26 --- /dev/null +++ b/docker_images/unitgrade-docker/tmp/cs103/report3_complete_grade.py @@ -0,0 +1,338 @@ + +import numpy as np +from tabulate import tabulate +from datetime import datetime +import pyfiglet +import unittest +import inspect +import os +import argparse +import time + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print(s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + UTextResult.q_title_print = q_title_print # Hacky + UTextResult.show_progress_bar = show_progress_bar # Hacky. + UTextResult.number = n + UTextResult.nL = report.nL + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + obtained = len(res.successes) + + assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun + + obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f" * q{n+1}) Total" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + print("") + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.normpath(os.path.join(output_dir, token)) + + + with open(token, 'wb') as f: + pickle.dump(results, f) + + if not args.autolab: + print(" ") + print("To get credit for your results, please upload the single unmodified 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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.normpath(os.path.join(output_dir, token))\n\n\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n @hide\n def test_add_hidden(self):\n # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n # See the output in the student directory for more information.\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n @hide\n def test_hidden_fail(self):\n self.assertEqual(2,3)\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' +report1_payload = '80049589000000000000007d94288c055765656b31947d942868018c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b047568018c0f746573745f6164645f68696464656e948694680586947d944b004b04738c0474696d6594473fda6e8700000000758c0d4175746f6d6174696350617373947d94680c473fb8d5140000000073752e' +name="Report3" + +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 diff --git a/docker_images/unitgrade-docker/tmp/cs103/report3_grade.py b/docker_images/unitgrade-docker/tmp/cs103/report3_grade.py new file mode 100644 index 0000000..85573c9 --- /dev/null +++ b/docker_images/unitgrade-docker/tmp/cs103/report3_grade.py @@ -0,0 +1,340 @@ +""" +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 +import pyfiglet +import unittest +import inspect +import os +import argparse +import time + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print(s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + UTextResult.q_title_print = q_title_print # Hacky + UTextResult.show_progress_bar = show_progress_bar # Hacky. + UTextResult.number = n + UTextResult.nL = report.nL + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + obtained = len(res.successes) + + assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun + + obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f" * q{n+1}) Total" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + print("") + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.normpath(os.path.join(output_dir, token)) + + + with open(token, 'wb') as f: + pickle.dump(results, f) + + if not args.autolab: + print(" ") + print("To get credit for your results, please upload the single unmodified 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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.normpath(os.path.join(output_dir, token))\n\n\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' +report1_payload = '80049568000000000000007d94288c055765656b31947d942868018c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04758c0474696d6594473fb1eb1c00000000758c0d4175746f6d6174696350617373947d946808473fa78d300000000073752e' +name="Report3" + +report = source_instantiate(name, report1_source, report1_payload) +output_dir = os.path.dirname(__file__) +gather_upload_to_campusnet(report, output_dir) diff --git a/examples/02471/instructor/02471/report1.py b/examples/02471/instructor/02471/report1.py index 1ed131f..96bb952 100644 --- a/examples/02471/instructor/02471/report1.py +++ b/examples/02471/instructor/02471/report1.py @@ -114,7 +114,7 @@ if __name__ == "__main__": # from week02 import Week_2_sol import importnb - file = "week02/week2.ipynb" + file = "../../../example_jupyter/instructor/cs105/week2.ipynb" file2 = 'week02/Week_2_sol.ipynb' m = importnb.Notebook.load(file) # importnb.Notebook.l diff --git a/examples/02631/instructor/programs/.coverage b/examples/02631/instructor/programs/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..11b2ec620c9b142ae8ee79c7a6f5afa297121530 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/examples/02631/instructor/programs/__pycache__/looping.cpython-38.pyc b/examples/02631/instructor/programs/__pycache__/looping.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa302e5a102c505ba4b62aa5eab3205ca2683298 GIT binary patch literal 2019 zcmZux&2Jk;6rY)0+Z)GiLL1Vg6c~vj)fEa!TOm|cA&{z2weo>dAz5y%$<EkWykD7F zw@I}=xhMe%32u~AbL^Ra1O9<IAR$gUaNx>`_ja8&u6V0`Gw;oN^YPyAz5Az5)d|qw ze|#wBYlQrT!bxg?umQi>0w#&1A!*ZMWNjvybe@v7BVAd6(UnzMgRvq#IRj%=)nt9Z z^h4#TilWbF<g9GSQ}VQ&lk@Tod8Y3jQh8P`jGZy1u=hREeDes-AuZp<VL}xctMG5a zZ*Bq0N$w0>-9V3wJg0}`CV6ma<Vu!P_({&*B1>d}oCC`RGCH<jpk!x#XCVi>a|F(E zp8Q$oK${};hoQ(6&w@ztVVnga-x7YNbRc+NCy%pju6BK;ByVw(37rLTpZ9bUalz9h zMdu)idD7$Y>Pyd+V)N}F&dl{<if3KseiEBNDh;M5v?5Hm1z+c-?s_V;h`^$BoQQ?Z zl;+Nq)S5_IWkVg8f~F9!BV<ixX99RqmL#FE6+eM@HY=9JtF}_?Z0Q|KTb5=oh(=KS z`}OAa&V8e_>14xg)w!AY!$`qvI>R`~`dUcUO?B`H0@qQyB1%JLI;&Scy1Ld0Vw35i zpC!7J>I6(9(+QI#g|Mxpd-n8ka6d0%i@7$ZfsuwsUD}{a)T33Z5qmyf{g}|ugTDa6 z&oIH{=j4!%aK<txCu0hbqKibb0UJ?Vhu;NA&^fXUmd6s#`wTo;M!u<lwwfcn$+sxi zdTbd$gtJnI@)CF;;C}$v!`@Ej<xWlqY)tj{J3r>mi0*KNUjZ$*b;RI)8d;NCSL_C+ zIRn8?ZA}=Vwb-*&$Z_0P)(KT?-AIWTRsAT4ts}OKt;Omw`hgUNO}9O(RWDS2CSqT; z+3ht;qqcK<ty?6kWV3Lbo%6$?fkb~Ai*U~bW(kyL9vGo@y2vilk0Hbi1H246-@%m$ z;T)?CM{^z+25_J3(;k!Xw5MJUm4+e4>S;~o249M<XVLW%us)RC13sb$5PKJP91wls zfC6`87YfnM=}&COy;owbxIQ41t*lzAUocw_lgCPb1|XHTK1|ce{KPZsH^RVVeiEhY z@8g*W*(zc1)k%Xqx!6-0(OOjImw?fg8)4#$(5%Dc*s;m<9Jpz`bkk`!APHuul|*~} zHm<yb47b)7kQD)*e|_usV98(r2-6(+RA8oJ+JaS#X$jgs0f<7s8dK<BA9UD=eMd59 zpY#C2ucAGs(t$3znA7L(0e}#Kw^-~^=D~fk&cAroWG?fs#6WSuq22~uWIWqe(7cNB z<Uu?UmG%Rp$__Pk+o?3-TZ%L;VJYQw4Ez9Y3%8k0A3)rJD?m6AG?wnZpq4&*l`Q=( zsMp{(*vBzT^N=B}qU4df9b*nd_5T?<!tgaKWttGgc1Fd}hBRjA49Zmj=!j+0u`G~n zCy-Yx4a%c2FO!tLe%57JGT2~<TZf)APtQY9Xw+J-+l@pJcf0*xo&hai+vxxP8_?aW z8>QyiKcDok7IbB!ocsz)J)~O?kBz|#cPHf9#t~xd2#c**w-va3x>vpj-&S<Fq7n<} zyu3mzBGv8M3Ut^wgAR=?qkL6?#)1`S^ln6n9ENHgJ7v*EJ?b%U!JGAJUc+<$1EnzH AG5`Po literal 0 HcmV?d00001 diff --git a/examples/02631/instructor/programs/__pycache__/report1intro.cpython-38.pyc b/examples/02631/instructor/programs/__pycache__/report1intro.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddadca6243eeebd021c63d8d875e9092033e1190 GIT binary patch literal 7198 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 VDuv%8Mk<jzm3sp9WX?{R{{s&mC>a0% literal 0 HcmV?d00001 diff --git a/examples/02631/instructor/programs/deploy.py b/examples/02631/instructor/programs/deploy.py new file mode 100644 index 0000000..6099792 --- /dev/null +++ b/examples/02631/instructor/programs/deploy.py @@ -0,0 +1,62 @@ +from report1intro import Report1Flat +from unitgrade_private2.hidden_create_files import setup_grade_file_report +from snipper import snip_dir + +if __name__ == "__main__": + setup_grade_file_report(Report1Flat, minify=False, obfuscate=False, execute=False, with_coverage=True) + + # from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet + # gather_upload_to_campusnet((Report1Flat())) + + # Deploy the files using snipper: https://gitlab.compute.dtu.dk/tuhe/snipper + snip_dir.snip_dir(source_dir="", dest_dir="../../students/programs", clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py']) + + import os + os.system("python report1intro_grade.py") + + +""" +from coverage import CoverageData +import coverage +cov2 = coverage.Coverage() + + def setUp(self): + import trace + self.cov = cov2 + self.cov.start() + + + + # self.tracer.start() + + # using obj_to_trace + + def tearDown(self) -> None: + + self.cov.stop() + print() + + data = CoverageData() + + # data.measured_files() + # data.lines() + data = self.cov.get_data() + # data. + for file in data.measured_files(): + print(file) + print(data.lines(file)) + print(data.arcs(file)) + print( data.contexts_by_lineno(file)) + + # print(data[file]) + + + +- Idea: Measure coverage in setup/teardown. This gives a handful of covered lines. +- During setup, supply a dicionary to UTestCase of files, along with the lines that are removed. +- When running setup: Take the coverage report, and compare against files. Write functions/lines encountered to the cache dictionary. Rquires you to +- inspect the functions that are edited to figure out what is removed. This can probably be done by going upwars towards the first sensible class or function definition (which has not been removed). +- Supply a dictionary to UTestCase of files, along with the lines edited. Allow UTestCase to write this information to the + cache dictionary (i.e. lines removed). Then use this information when displaying helpful hints later. + +""" \ No newline at end of file diff --git a/examples/02631/instructor/programs/looping.py b/examples/02631/instructor/programs/looping.py new file mode 100644 index 0000000..59a1485 --- /dev/null +++ b/examples/02631/instructor/programs/looping.py @@ -0,0 +1,64 @@ +import numpy as np +import itertools + +def bacteriaGrowth(n0, alpha, K, N): #!f + """ + Calculate time until bacteria growth exceed N starting from a population of n0 bacteria. + hints: + * consider n0 + * alpha > 0 + :param n0: + :param alpha: + :param K: + :param N: + :return: + """ + if n0 > N: + return 0 + for t in itertools.count(): + n0 = (1 + alpha * (1-n0 / K) ) * n0 + if n0 > N: + break + return t+1 + +def clusterAnalysis(reflectance): + reflectance = np.asarray(reflectance) + I1 = np.arange(len(reflectance)) % 2 == 1 + while True: + m = np.asarray( [np.mean( reflectance[~I1] ), np.mean( reflectance[I1] ) ] ) + I1_ = np.argmin( np.abs( reflectance[:, np.newaxis] - m[np.newaxis, :] ), axis=1) == 1 + if all(I1_ == I1): + break + I1 = I1_ + return I1 + 1 + +def fermentationRate(measuredRate, lowerBound, upperBound): + # Insert your code here + return np.mean( [r for r in measuredRate if lowerBound < r < upperBound] ) + + + + +def removeIncomplete(id): + """ Hints: + * Take a look at the example in the exercise. + """ + id = np.asarray(id) + id2 = [] + for i, v in enumerate(id): + if len( [x for x in id if int(x) == int(v) ] ) == 3: + id2.append(v) + return np.asarray(id2) + + +if __name__ == "__main__": + # I = clusterAnalysis([1.7, 1.6, 1.3, 1.3, 2.8, 1.4, 2.8, 2.6, 1.6, 2.7]) + # print(I) + + print(fermentationRate(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 15, 25)) + + + # print(removeIncomplete(np.array([1.3, 2.2, 2.3, 4.2, 5.1, 3.2, 5.3, 3.3, 2.1, 1.1, 5.2, 3.1]))) + + # Problem 1: Write a function which add two numbers + # clusterAnalysis([2, 1, 2, 4, 5]) \ No newline at end of file diff --git a/examples/02631/instructor/programs/report1intro.py b/examples/02631/instructor/programs/report1intro.py new file mode 100644 index 0000000..10b1898 --- /dev/null +++ b/examples/02631/instructor/programs/report1intro.py @@ -0,0 +1,139 @@ +from src.unitgrade2.unitgrade2 import Report, UTestCase, cache +from src.unitgrade2 import evaluate_report_student +import numpy as np +import looping +from looping import bacteriaGrowth, clusterAnalysis, removeIncomplete, fermentationRate + +def trlist(x): + s = str(list(x)) + if len(s) > 30: + s = s[:30] + "...]" + return s + +class Bacteria(UTestCase): + """ Bacteria growth rates """ + + def stest(self, n0, alpha, K, N): + g = bacteriaGrowth(n0=n0, alpha=alpha, K=K, N=N) + self.title = f"bacteriaGrowth({n0}, {alpha}, {K}, {N}) = {g} ?" + self.assertEqualC(g) + + def test_growth1(self): + """ Hints: + * Make sure to frobulate the frobulator. + """ + self.stest(100, 0.4, 1000, 500) + + def test_growth2(self): + self.stest(10, 0.4, 1000, 500) + + def test_growth3(self): + self.stest(100, 1.4, 1000, 500) + + def test_growth4(self): + self.stest(100, 0.0004, 1000, 500) + + def test_growth5(self): + """ + hints: + * What happens when n0 > N? (in this case return t=0) """ + self.stest(100, 0.4, 1000, 99) + +class ClusterAnalysis(UTestCase): + """ Test the cluster analysis method """ + + def stest(self, n, seed): + np.random.seed(seed) + x = np.round(np.random.rand(n), 1) + I = clusterAnalysis(x) + self.title = f"clusterAnalysis({list(x)}) = {list(I)} ?" + self.assertEqualC(list(I)) + + def test_cluster1(self): + """ Hints: + * Make sure to frobulate the frobulator. + * Just try harder + """ + self.stest(3, 10) + + def test_cluster2(self): + self.stest(4, 146) + + def test_cluster3(self): + self.stest(5, 12) + + def test_cluster4(self): + """ + Cluster analysis for tied lists + Hints: + * It may be that an observations has the same distance to the two clusters. Where do you assign it in this case? + """ + x = np.array([10.0, 12.0, 10.0, 12.0, 9.0, 11.0, 11.0, 13.0]) + self.assertEqualC(list(clusterAnalysis(x) ) ) + + +class RemoveIncomplete(UTestCase): + """ Remove incomplete IDs """ + + def stest(self, x): + I = list( removeIncomplete(x) ) + self.title = f"removeId({trlist(x)}) = {trlist(I)} ?" + self.assertEqualC(I) + + @cache + def rseq(self, max, n): + np.random.seed(42) + return np.random.randint(max, size=(n,) ) + (np.random.randint(2, size=(n,) )+1)/10 + + def test_incomplete1(self): + self.stest( np.array([1.3, 2.2, 2.3, 4.2, 5.1, 3.2, 5.3, 3.3, 2.1, 1.1, 5.2, 3.1]) ) + + def test_incomplete2(self): + self.stest( np.array([1.1, 1.2, 1.3, 2.1, 2.2, 2.3]) ) + + def test_incomplete3(self): + self.stest(np.array([5.1, 5.2, 4.1, 4.3, 4.2, 8.1, 8.2, 8.3]) ) + + def test_incomplete4(self): + self.stest(np.array([1.1, 1.3, 2.1, 2.2, 3.1, 3.3, 4.1, 4.2, 4.3]) ) + + def test_incomplete5(self): + self.stest(self.rseq(10, 40)) + + +class FermentationRate(UTestCase): + """ Test the fermentation rate question """ + + def stest(self, x, lower, upper): + I = fermentationRate(x, lower, upper) + s = trlist(x) + self.title = f"fermentationRate({s}, {lower}, {upper}) = {I:.3f} ?" + self.assertEqualC(I) + + @cache + def rseq(self, max, n): + np.random.seed(42) + return np.random.randint(max, size=(n,) ) + (np.random.randint(3, size=(n,) )+1)/n + + def test_rate1(self): + self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 15, 25) + + def test_rate2(self): + self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 1, 200) + + def test_rate3(self): + self.stest(np.array([1.75]), 1, 2) + + def test_rate4(self): + self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 18.2, 20) + + +class Report1Flat(Report): + title = "Week 4: Looping" + questions = [(ClusterAnalysis, 10), (RemoveIncomplete, 10), (Bacteria, 10), (FermentationRate, 10),] + pack_imports = [looping] + +if __name__ == "__main__": + # Uncomment to simply run everything as a unittest: + # unittest.main(verbosity=2) + evaluate_report_student(Report1Flat()) diff --git a/examples/02631/instructor/programs/report1intro_grade.py b/examples/02631/instructor/programs/report1intro_grade.py new file mode 100644 index 0000000..4381d55 --- /dev/null +++ b/examples/02631/instructor/programs/report1intro_grade.py @@ -0,0 +1,337 @@ + +import numpy as np +from tabulate import tabulate +from datetime import datetime +import pyfiglet +import unittest +import inspect +import os +import argparse +import time + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print(s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + # nL = + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + UTextResult.q_title_print = q_title_print # Hacky + UTextResult.show_progress_bar = show_progress_bar # Hacky. + UTextResult.number = n + UTextResult.nL = report.nL + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + obtained = len(res.successes) + + assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun + + obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f"Question {n+1} total" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + print("") + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.join(output_dir, token) + with open(token, 'wb') as f: + pickle.dump(results, f) + + if not args.autolab: + print(" ") + print("To get credit for your results, please upload the single unmodified 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 =\n t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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 {n+1} total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 unmodified 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\nimport numpy as np\nimport looping\nfrom looping import bacteriaGrowth, clusterAnalysis, removeIncomplete, fermentationRate\n\ndef trlist(x):\n s = str(list(x))\n if len(s) > 30:\n s = s[:30] + "...]"\n return s\n\nclass Bacteria(UTestCase):\n """ Bacteria growth rates """\n\n def stest(self, n0, alpha, K, N):\n g = bacteriaGrowth(n0=n0, alpha=alpha, K=K, N=N)\n self.title = f"bacteriaGrowth({n0}, {alpha}, {K}, {N}) = {g} ?"\n self.assertEqualC(g)\n\n def test_growth1(self):\n """ Hints:\n * Make sure to frobulate the frobulator.\n """\n self.stest(100, 0.4, 1000, 500)\n\n def test_growth2(self):\n self.stest(10, 0.4, 1000, 500)\n\n def test_growth3(self):\n self.stest(100, 1.4, 1000, 500)\n\n def test_growth4(self):\n self.stest(100, 0.0004, 1000, 500)\n\n def test_growth5(self):\n """\n hints:\n * What happens when n0 > N? (in this case return t=0) """\n self.stest(100, 0.4, 1000, 99)\n\nclass ClusterAnalysis(UTestCase):\n """ Test the cluster analysis method """\n\n def stest(self, n, seed):\n np.random.seed(seed)\n x = np.round(np.random.rand(n), 1)\n I = clusterAnalysis(x)\n self.title = f"clusterAnalysis({list(x)}) = {list(I)} ?"\n self.assertEqualC(list(I))\n\n def test_cluster1(self):\n """ Hints:\n * Make sure to frobulate the frobulator.\n * Just try harder\n """\n self.stest(3, 10)\n\n def test_cluster2(self):\n self.stest(4, 146)\n\n def test_cluster3(self):\n self.stest(5, 12)\n\n def test_cluster4(self):\n """\n Cluster analysis for tied lists\n Hints:\n * It may be that an observations has the same distance to the two clusters. Where do you assign it in this case?\n """\n x = np.array([10.0, 12.0, 10.0, 12.0, 9.0, 11.0, 11.0, 13.0])\n self.assertEqualC(list(clusterAnalysis(x) ) )\n\n\nclass RemoveIncomplete(UTestCase):\n """ Remove incomplete IDs """\n\n def stest(self, x):\n I = list( removeIncomplete(x) )\n self.title = f"removeId({trlist(x)}) = {trlist(I)} ?"\n self.assertEqualC(I)\n\n @cache\n def rseq(self, max, n):\n np.random.seed(42)\n return np.random.randint(max, size=(n,) ) + (np.random.randint(2, size=(n,) )+1)/10\n\n def test_incomplete1(self):\n self.stest( np.array([1.3, 2.2, 2.3, 4.2, 5.1, 3.2, 5.3, 3.3, 2.1, 1.1, 5.2, 3.1]) )\n\n def test_incomplete2(self):\n self.stest( np.array([1.1, 1.2, 1.3, 2.1, 2.2, 2.3]) )\n\n def test_incomplete3(self):\n self.stest(np.array([5.1, 5.2, 4.1, 4.3, 4.2, 8.1, 8.2, 8.3]) )\n\n def test_incomplete4(self):\n self.stest(np.array([1.1, 1.3, 2.1, 2.2, 3.1, 3.3, 4.1, 4.2, 4.3]) )\n\n def test_incomplete5(self):\n self.stest(self.rseq(10, 40))\n\n\nclass FermentationRate(UTestCase):\n """ Test the fermentation rate question """\n\n def stest(self, x, lower, upper):\n I = fermentationRate(x, lower, upper)\n s = trlist(x)\n self.title = f"fermentationRate({s}, {lower}, {upper}) = {I:.3f} ?"\n self.assertEqualC(I)\n\n @cache\n def rseq(self, max, n):\n np.random.seed(42)\n return np.random.randint(max, size=(n,) ) + (np.random.randint(3, size=(n,) )+1)/n\n\n def test_rate1(self):\n self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 15, 25)\n\n def test_rate2(self):\n self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 1, 200)\n\n def test_rate3(self):\n self.stest(np.array([1.75]), 1, 2)\n\n def test_rate4(self):\n self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 18.2, 20)\n\n\nclass Report1Flat(Report):\n title = "Week 4: Looping"\n questions = [(ClusterAnalysis, 10), (RemoveIncomplete, 10), (Bacteria, 10), (FermentationRate, 10),]\n pack_imports = [looping]' +report1_payload = '80049592150000000000007d94288c0f436c7573746572416e616c79736973947d94288c0f436c7573746572416e616c79736973948c0d746573745f636c7573746572319486948c057469746c659486948c2e636c7573746572416e616c79736973285b302e382c20302e302c20302e365d29203d205b312c20322c20315d203f946803680486948c066173736572749486947d944b005d94288c156e756d70792e636f72652e6d756c74696172726179948c067363616c61729493948c056e756d7079948c0564747970659493948c02693494898887945294284b038c013c944e4e4e4affffffff4affffffff4b007494624304010000009486945294681068164304020000009486945294681068164304010000009486945294657368038c0d746573745f636c757374657232948694680686948c36636c7573746572416e616c79736973285b302e352c20302e362c20302e332c20302e335d29203d205b322c20322c20312c20315d203f94680368228694680a86947d944b005d9428681068164304020000009486945294681068164304020000009486945294681068164304010000009486945294681068164304010000009486945294657368038c0d746573745f636c757374657233948694680686948c3e636c7573746572416e616c79736973285b302e322c20302e372c20302e332c20302e352c20302e305d29203d205b312c20322c20312c20322c20315d203f94680368368694680a86947d944b005d9428681068164304010000009486945294681068164304020000009486945294681068164304010000009486945294681068164304020000009486945294681068164304010000009486945294657368038c0d746573745f636c757374657234948694680a86947d944b005d942868106816430401000000948694529468106816430402000000948694529468106816430401000000948694529468106816430402000000948694529468106816430401000000948694529468106816430401000000948694529468106816430401000000948694529468106816430402000000948694529465738c0474696d6594473fdf6c8500000000758c1052656d6f7665496e636f6d706c657465947d94288c1052656d6f7665496e636f6d706c657465948c10746573745f696e636f6d706c657465319486948c057469746c659486948c5372656d6f76654964285b312e332c20322e322c20322e332c20342e322c20352e312c20332e322c2e2e2e5d29203d205b322e322c20322e332c20352e312c20332e322c20352e332c20332e332c2e2e2e5d203f94686d686e86948c066173736572749486947d944b005d9428681068138c02663894898887945294284b0368174e4e4e4affffffff4affffffff4b0074946243089a9999999999014094869452946810687a4308666666666666024094869452946810687a4308666666666666144094869452946810687a43089a9999999999094094869452946810687a4308333333333333154094869452946810687a43086666666666660a4094869452946810687a4308cdcccccccccc004094869452946810687a4308cdcccccccccc144094869452946810687a4308cdcccccccccc084094869452946573686d8c10746573745f696e636f6d706c65746532948694687086948c4b72656d6f76654964285b312e312c20312e322c20312e332c20322e312c20322e322c20322e335d29203d205b312e312c20312e322c20312e332c20322e312c20322e322c20322e335d203f94686d68978694687486947d944b005d94286810687a43089a9999999999f13f94869452946810687a4308333333333333f33f94869452946810687a4308cdccccccccccf43f94869452946810687a4308cdcccccccccc004094869452946810687a43089a9999999999014094869452946810687a4308666666666666024094869452946573686d8c10746573745f696e636f6d706c65746533948694687086948c4f72656d6f76654964285b352e312c20352e322c20342e312c20342e332c20342e322c20382e312c2e2e2e5d29203d205b342e312c20342e332c20342e322c20382e312c20382e322c20382e335d203f94686d68b18694687486947d944b005d94286810687a4308666666666666104094869452946810687a4308333333333333114094869452946810687a4308cdcccccccccc104094869452946810687a4308333333333333204094869452946810687a4308666666666666204094869452946810687a43089a9999999999204094869452946573686d8c10746573745f696e636f6d706c65746534948694687086948c4072656d6f76654964285b312e312c20312e332c20322e312c20322e322c20332e312c20332e332c2e2e2e5d29203d205b342e312c20342e322c20342e335d203f94686d68cb8694687486947d944b005d94286810687a4308666666666666104094869452946810687a4308cdcccccccccc104094869452946810687a4308333333333333114094869452946573686d8c10746573745f696e636f6d706c657465359486948c06406361636865948c0472736571948c0966756e63746f6f6c73948c0a5f486173686564536571949394298194284b0a4b28654e7d948c096861736876616c7565948a0884d8ef03874d7f467386946287948694680e8c0c5f7265636f6e73747275637494939468118c076e6461727261799493944b0085944301629487945294284b014b28859468138c02663894898887945294284b0368174e4e4e4affffffff4affffffff4b0074946289424001000066666666666618409a99999999990940cdcccccccccc1c40cdcccccccccc1040cdcccccccccc184033333333333322409a999999999901406666666666661840cdcccccccccc1c40cdcccccccccc10409a999999999909406666666666661c40cdcccccccccc1c40cdcccccccccc0040cdcccccccccc14406666666666661040333333333333f33f6666666666661c406666666666661440333333333333f33f66666666666610409a9999999999c93f6666666666662240cdcccccccccc144066666666666620409a9999999999c93f66666666666622409a99999999990140cdcccccccccc18409a9999999999094066666666666620409a999999999901406666666666661040cdcccccccccc0040cdcccccccccc1840cdcccccccccc10406666666666662040cdcccccccccc1840333333333333f33f9a9999999999094094749462686d68dc8694687086948c5372656d6f76654964285b362e312c20332e322c20372e322c20342e322c20362e322c20392e312c2e2e2e5d29203d205b392e312c20352e322c20312e322c20352e312c20312e322c20392e322c2e2e2e5d203f94686d68dc8694687486947d944b005d94286810687a4308333333333333224094869452946810687a4308cdcccccccccc144094869452946810687a4308333333333333f33f94869452946810687a4308666666666666144094869452946810687a4308333333333333f33f94869452946810687a4308666666666666224094869452946810687a4308cdcccccccccc144094869452946810687a4308666666666666204094869452946810687a4308666666666666224094869452946810687a4308666666666666204094869452946810687a4308666666666666204094869452946810687a4308333333333333f33f94869452946573686a473fcf9dc400000000758c084261637465726961947d94288c084261637465726961948c0c746573745f67726f777468319486948c057469746c659486948c29626163746572696147726f777468283130302c20302e342c20313030302c2035303029203d2037203f946a250100006a2601000086948c066173736572749486947d944b004b07736a250100006a2601000086948c08636f7665726167659486947d94286a250100006a2601000086947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b158ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a2020202022222294869473736a250100008c0c746573745f67726f777468329486947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b158ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a2020202022222294869473736a250100008c0c746573745f67726f777468339486947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b158ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a2020202022222294869473736a250100008c0c746573745f67726f777468349486947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b158ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a2020202022222294869473736a250100008c0c746573745f67726f777468359486947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b118ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a202020202222229486947373756a250100006a3a01000086946a2801000086948c29626163746572696147726f7774682831302c20302e342c20313030302c2035303029203d203134203f946a250100006a3a01000086946a2c01000086947d944b004b0e736a250100006a3a01000086946a3001000086946a320100006a250100006a4201000086946a2801000086948c29626163746572696147726f777468283130302c20312e342c20313030302c2035303029203d2033203f946a250100006a4201000086946a2c01000086947d944b004b03736a250100006a4201000086946a3001000086946a320100006a250100006a4a01000086946a2801000086948c2f626163746572696147726f777468283130302c20302e303030342c20313030302c2035303029203d2035343934203f946a250100006a4a01000086946a2c01000086947d944b004d7615736a250100006a4a01000086946a3001000086946a320100006a250100006a5201000086946a2801000086948c28626163746572696147726f777468283130302c20302e342c20313030302c20393929203d2030203f946a250100006a5201000086946a2c01000086947d944b004b00736a250100006a5201000086946a3001000086946a32010000686a473fcf9d9a00000000758c104665726d656e746174696f6e52617465947d94288c104665726d656e746174696f6e52617465948c0a746573745f72617465319486948c057469746c659486948c476665726d656e746174696f6e52617465285b32302e312c2031392e332c20312e312c2031382e322c2031392e372c202e2e2e5d2c2031352c20323529203d2031392e363030203f946a7c0100006a7d01000086948c066173736572749486947d944b006810687a43089a999999999933409486945294736a7c0100008c0a746573745f72617465329486946a7f01000086948c476665726d656e746174696f6e52617465285b32302e312c2031392e332c20312e312c2031382e322c2031392e372c202e2e2e5d2c20312c2032303029203d2032392e393735203f946a7c0100006a8901000086946a8301000086947d944b006810687a43089899999999f93d409486945294736a7c0100008c0a746573745f72617465339486946a7f01000086948c286665726d656e746174696f6e52617465285b312e37355d2c20312c203229203d20312e373530203f946a7c0100006a9301000086946a8301000086947d944b006810687a4308000000000000fc3f9486945294736a7c0100008c0a746573745f72617465349486946a7f01000086948c496665726d656e746174696f6e52617465285b32302e312c2031392e332c20312e312c2031382e322c2031392e372c202e2e2e5d2c2031382e322c20323029203d2031392e353030203f946a7c0100006a9d01000086946a8301000086947d944b006810687a43080000000000803340948694529473686a473fc74c0a0000000075752e' +name="Report1Flat" + +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 diff --git a/examples/02631/instructor/programs/unitgrade/Bacteria.pkl b/examples/02631/instructor/programs/unitgrade/Bacteria.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b246df45c63387d95bc69cc26deba7770d8249c5 GIT binary patch literal 2050 zcmeHIO-sW-5Ut;(h~PnwG8B|nN=&syT2Mg`9)kS~vZmW6kY>YXQ}s~r<SEDXH~JI2 zI+JXRt@U6%+CX6DW#60Kmyq|e{`Rp|$gYvDq{VCIDlUDd({06Lb>ElbQF(R!s?!bS ztAJBgeK4zTv!|81ZP&oI8a2QQ%cgBt;R4R#N_#8bDl#@%9CN9VJkn0V*@_Kbbj6TM z)+bq<pQ9t4mI5InKkQr4GveuCm-k?1Xo!PY5P7TyP7ONMHfU!reJ_{GCBW;31#S{B z#X<Q44oRr|U?Ktev4B6hoOhuEv0_r;Jm^U=00xnWi0q3HL=QrH!n87R&kt4H&Ptkf z46X=czsn^q{eouOgG<P<cEltb;EJ4R+HB1f;!JfN(@Cxp8RiY15&eqM#HqJ*nwhxt z4#0T(3-8XB-YxvyHJ08j>|OIu-tGRc|6PLlu>Y#P^|=`|FM{fgJcEc;G~u&D;X}YS z-aA+y{3wYLQ~wrgXSpP?#*%9!Nx)e0$Vi^dgqBH4Ov%}>Y&KeEuf#RNcf&nWCS<H^ c8Rg1+<%w*oMG<W0dnM9%uLV*hAX7}NPbL6<qW}N^ literal 0 HcmV?d00001 diff --git a/examples/02631/instructor/programs/unitgrade/ClusterAnalysis.pkl b/examples/02631/instructor/programs/unitgrade/ClusterAnalysis.pkl new file mode 100644 index 0000000000000000000000000000000000000000..36635371f6e86d5f841a3827696a5909e766532b GIT binary patch literal 763 zcma)(Jx_!{5QYKK*r<*6TVg^saEGUbA2D>2P+WT<25}Qexbwm;1;r&+ek61^{!jmb z&Vq-6XxL(4XWyN9-q}a}?Oju<wF09&aMPR%mR&ExwBQMca!)WWZlgMF!xNO-A`vN* zfAlN8Q5!Rho|(j=mJE~Nm|T#VZIY2m?0{TBti~E%+X*>mnZODw@RR`C9xd|uvY^pJ z#^^jxMG|INSfG+eVH#$z#;%nFzxSe8GMUj52VP&EVG4$~RjTLU`~J-bt}1wnfIC-L z(Hb_ILcAXzy1KObW80iE9;<DuBb3ED(oT9zo>cy17+0ZoiZ-RwZ?gnT>^3fY3H}1A z#sBb)H++{pe28Ju(}u6YwOY^7dK9OJvuWLnJ@EARSRIMooyovzLr6NMI@@O2jOX+d D%KPtw literal 0 HcmV?d00001 diff --git a/examples/02631/instructor/programs/unitgrade/FermentationRate.pkl b/examples/02631/instructor/programs/unitgrade/FermentationRate.pkl new file mode 100644 index 0000000000000000000000000000000000000000..9f5cea13c96d67ff2704de398cd66b78a2db463b GIT binary patch literal 618 zcmZo*nHtZ;00y;FG<pQwQj2m^^GXs+GV}9-5=&C2^l+7=7MH{q0ojIA+NSidmSmRX zq=H!PX=rLSqKypn40RL?E%l6nB$#QTX9N~7*HO^Z(~AYNO?4EEOf?m3LCVYw3>56A zWH4qh0nKAeEG|whDghc@JH?x!M>MZAx1drlIlm}XFSj(OBr~z7D6tZ#tT;I_C$VVC zWT0EXN`Q1qNo7GQNQ5cPVoGO6`;?$58s5x3j5bsJ{QSKB0|A)uW+<7G<jgT^CI}ci z0Bs7IQk=nv<V7Q(SOyFEo&);LzyRbqBTGF?b5n@tB!OmfKzx<Kn<4DXF#}}(Pg`sb zForom12c3D^~_B{Aqcbt9DYD9H29R!ZGgn+AA2lbG=bURiD?5d@du7kBd{ldF=z^l OL0zm)Xn=)ssU83q4$F!F literal 0 HcmV?d00001 diff --git a/examples/02631/instructor/programs/unitgrade/RemoveIncomplete.pkl b/examples/02631/instructor/programs/unitgrade/RemoveIncomplete.pkl new file mode 100644 index 0000000000000000000000000000000000000000..30edf2db7bc0e41f0ada9c1c7443ac1d487a6d9a GIT binary patch literal 1444 zcma)6O-sW-5N)fK>Om^hd+=1DETm~`3yOj#jZ~<|A`;szDfEkEtMpLt<OlTTdeGmb zKSuPfvpY$;p<8qh$?VJ9&AfTDd$;=Xnoh_u<@s5*LHolSdNODY`=cIZl+Ut^`s}J3 zAtt})vo!0n9u?7rhb!9orb(=VQ6xq2fT$#*GBFE=1rZ_Sa@RO8nsY+*4Oqap;2~JX zA$OB*3ie)ee4ly@elz8E;u7-P9*p~=326;IO8Vm->pGt2On~XPoSx(HXGl#_;N51E z5fw<PQ{_*O54^$i_R1`|&+GO2=?@I~uoH}5A7|cP#Yi5(&l=p_baTg<jvT2)D5pZW za9xEgIkr^@_u5dQk9RQ=i%@KPWMz;=eK)yaND)$YSD{?CY6X<t6b1yQz%(p8+F%M} zmNC$RBJ7)qYL7KRwV#JdnNaPQ3SrXUi%?AMUnSS7wL-P$bG5RO%0g{Ps47L>SA}WR zc$#lURV=C!D`>hijbd~ztJ;K$w^RrfYgmTL!o6Y;79oJNic6v5qq*Y1J$c?jYb`u# zc!=at3lz(`gc3Xdlekn*pjw-0Im}eK6e^e%<gkYZK~xb@P4z}h;1&Q_wxl#vMm50Z qm%^<kc{F23Ei)z`J4Tu&MKk`#`fGcQu?WQ$E@j0!B9<!k$K)5^Qt5yI literal 0 HcmV?d00001 diff --git a/examples/02631/students/programs/.coverage b/examples/02631/students/programs/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..11b2ec620c9b142ae8ee79c7a6f5afa297121530 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/examples/02631/students/programs/__pycache__/looping.cpython-38.pyc b/examples/02631/students/programs/__pycache__/looping.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa302e5a102c505ba4b62aa5eab3205ca2683298 GIT binary patch literal 2019 zcmZux&2Jk;6rY)0+Z)GiLL1Vg6c~vj)fEa!TOm|cA&{z2weo>dAz5y%$<EkWykD7F zw@I}=xhMe%32u~AbL^Ra1O9<IAR$gUaNx>`_ja8&u6V0`Gw;oN^YPyAz5Az5)d|qw ze|#wBYlQrT!bxg?umQi>0w#&1A!*ZMWNjvybe@v7BVAd6(UnzMgRvq#IRj%=)nt9Z z^h4#TilWbF<g9GSQ}VQ&lk@Tod8Y3jQh8P`jGZy1u=hREeDes-AuZp<VL}xctMG5a zZ*Bq0N$w0>-9V3wJg0}`CV6ma<Vu!P_({&*B1>d}oCC`RGCH<jpk!x#XCVi>a|F(E zp8Q$oK${};hoQ(6&w@ztVVnga-x7YNbRc+NCy%pju6BK;ByVw(37rLTpZ9bUalz9h zMdu)idD7$Y>Pyd+V)N}F&dl{<if3KseiEBNDh;M5v?5Hm1z+c-?s_V;h`^$BoQQ?Z zl;+Nq)S5_IWkVg8f~F9!BV<ixX99RqmL#FE6+eM@HY=9JtF}_?Z0Q|KTb5=oh(=KS z`}OAa&V8e_>14xg)w!AY!$`qvI>R`~`dUcUO?B`H0@qQyB1%JLI;&Scy1Ld0Vw35i zpC!7J>I6(9(+QI#g|Mxpd-n8ka6d0%i@7$ZfsuwsUD}{a)T33Z5qmyf{g}|ugTDa6 z&oIH{=j4!%aK<txCu0hbqKibb0UJ?Vhu;NA&^fXUmd6s#`wTo;M!u<lwwfcn$+sxi zdTbd$gtJnI@)CF;;C}$v!`@Ej<xWlqY)tj{J3r>mi0*KNUjZ$*b;RI)8d;NCSL_C+ zIRn8?ZA}=Vwb-*&$Z_0P)(KT?-AIWTRsAT4ts}OKt;Omw`hgUNO}9O(RWDS2CSqT; z+3ht;qqcK<ty?6kWV3Lbo%6$?fkb~Ai*U~bW(kyL9vGo@y2vilk0Hbi1H246-@%m$ z;T)?CM{^z+25_J3(;k!Xw5MJUm4+e4>S;~o249M<XVLW%us)RC13sb$5PKJP91wls zfC6`87YfnM=}&COy;owbxIQ41t*lzAUocw_lgCPb1|XHTK1|ce{KPZsH^RVVeiEhY z@8g*W*(zc1)k%Xqx!6-0(OOjImw?fg8)4#$(5%Dc*s;m<9Jpz`bkk`!APHuul|*~} zHm<yb47b)7kQD)*e|_usV98(r2-6(+RA8oJ+JaS#X$jgs0f<7s8dK<BA9UD=eMd59 zpY#C2ucAGs(t$3znA7L(0e}#Kw^-~^=D~fk&cAroWG?fs#6WSuq22~uWIWqe(7cNB z<Uu?UmG%Rp$__Pk+o?3-TZ%L;VJYQw4Ez9Y3%8k0A3)rJD?m6AG?wnZpq4&*l`Q=( zsMp{(*vBzT^N=B}qU4df9b*nd_5T?<!tgaKWttGgc1Fd}hBRjA49Zmj=!j+0u`G~n zCy-Yx4a%c2FO!tLe%57JGT2~<TZf)APtQY9Xw+J-+l@pJcf0*xo&hai+vxxP8_?aW z8>QyiKcDok7IbB!ocsz)J)~O?kBz|#cPHf9#t~xd2#c**w-va3x>vpj-&S<Fq7n<} zyu3mzBGv8M3Ut^wgAR=?qkL6?#)1`S^ln6n9ENHgJ7v*EJ?b%U!JGAJUc+<$1EnzH AG5`Po literal 0 HcmV?d00001 diff --git a/examples/02631/students/programs/__pycache__/report1intro.cpython-38.pyc b/examples/02631/students/programs/__pycache__/report1intro.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddadca6243eeebd021c63d8d875e9092033e1190 GIT binary patch literal 7198 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 VDuv%8Mk<jzm3sp9WX?{R{{s&mC>a0% literal 0 HcmV?d00001 diff --git a/examples/02631/students/programs/looping.py b/examples/02631/students/programs/looping.py new file mode 100644 index 0000000..34517b1 --- /dev/null +++ b/examples/02631/students/programs/looping.py @@ -0,0 +1,62 @@ +""" +Example student code. This file is automatically generated from the files in the instructor-directory +""" +import numpy as np +import itertools + +def bacteriaGrowth(n0, alpha, K, N): + """ + Calculate time until bacteria growth exceed N starting from a population of n0 bacteria. + hints: + * consider n0 + * alpha > 0 + :param n0: + :param alpha: + :param K: + :param N: + :return: + """ + # TODO: 7 lines missing. + raise NotImplementedError("Implement function body") + +def clusterAnalysis(reflectance): + reflectance = np.asarray(reflectance) + I1 = np.arange(len(reflectance)) % 2 == 1 + while True: + m = np.asarray( [np.mean( reflectance[~I1] ), np.mean( reflectance[I1] ) ] ) + I1_ = np.argmin( np.abs( reflectance[:, np.newaxis] - m[np.newaxis, :] ), axis=1) == 1 + if all(I1_ == I1): + break + I1 = I1_ + return I1 + 1 + +def fermentationRate(measuredRate, lowerBound, upperBound): + # Insert your code here + return np.mean( [r for r in measuredRate if lowerBound < r < upperBound] ) + + + + +def removeIncomplete(id): + """ Hints: + * Take a look at the example in the exercise. + """ + id = np.asarray(id) + id2 = [] + for i, v in enumerate(id): + if len( [x for x in id if int(x) == int(v) ] ) == 3: + id2.append(v) + return np.asarray(id2) + + +if __name__ == "__main__": + # I = clusterAnalysis([1.7, 1.6, 1.3, 1.3, 2.8, 1.4, 2.8, 2.6, 1.6, 2.7]) + # print(I) + + print(fermentationRate(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 15, 25)) + + + # print(removeIncomplete(np.array([1.3, 2.2, 2.3, 4.2, 5.1, 3.2, 5.3, 3.3, 2.1, 1.1, 5.2, 3.1]))) + + # Problem 1: Write a function which add two numbers + # clusterAnalysis([2, 1, 2, 4, 5]) diff --git a/examples/02631/students/programs/report1intro.py b/examples/02631/students/programs/report1intro.py new file mode 100644 index 0000000..587129b --- /dev/null +++ b/examples/02631/students/programs/report1intro.py @@ -0,0 +1,142 @@ +""" +Example student code. This file is automatically generated from the files in the instructor-directory +""" +from src.unitgrade2.unitgrade2 import Report, UTestCase, cache +from src.unitgrade2 import evaluate_report_student +import numpy as np +import looping +from looping import bacteriaGrowth, clusterAnalysis, removeIncomplete, fermentationRate + +def trlist(x): + s = str(list(x)) + if len(s) > 30: + s = s[:30] + "...]" + return s + +class Bacteria(UTestCase): + """ Bacteria growth rates """ + + def stest(self, n0, alpha, K, N): + g = bacteriaGrowth(n0=n0, alpha=alpha, K=K, N=N) + self.title = f"bacteriaGrowth({n0}, {alpha}, {K}, {N}) = {g} ?" + self.assertEqualC(g) + + def test_growth1(self): + """ Hints: + * Make sure to frobulate the frobulator. + """ + self.stest(100, 0.4, 1000, 500) + + def test_growth2(self): + self.stest(10, 0.4, 1000, 500) + + def test_growth3(self): + self.stest(100, 1.4, 1000, 500) + + def test_growth4(self): + self.stest(100, 0.0004, 1000, 500) + + def test_growth5(self): + """ + hints: + * What happens when n0 > N? (in this case return t=0) """ + self.stest(100, 0.4, 1000, 99) + +class ClusterAnalysis(UTestCase): + """ Test the cluster analysis method """ + + def stest(self, n, seed): + np.random.seed(seed) + x = np.round(np.random.rand(n), 1) + I = clusterAnalysis(x) + self.title = f"clusterAnalysis({list(x)}) = {list(I)} ?" + self.assertEqualC(list(I)) + + def test_cluster1(self): + """ Hints: + * Make sure to frobulate the frobulator. + * Just try harder + """ + self.stest(3, 10) + + def test_cluster2(self): + self.stest(4, 146) + + def test_cluster3(self): + self.stest(5, 12) + + def test_cluster4(self): + """ + Cluster analysis for tied lists + Hints: + * It may be that an observations has the same distance to the two clusters. Where do you assign it in this case? + """ + x = np.array([10.0, 12.0, 10.0, 12.0, 9.0, 11.0, 11.0, 13.0]) + self.assertEqualC(list(clusterAnalysis(x) ) ) + + +class RemoveIncomplete(UTestCase): + """ Remove incomplete IDs """ + + def stest(self, x): + I = list( removeIncomplete(x) ) + self.title = f"removeId({trlist(x)}) = {trlist(I)} ?" + self.assertEqualC(I) + + @cache + def rseq(self, max, n): + np.random.seed(42) + return np.random.randint(max, size=(n,) ) + (np.random.randint(2, size=(n,) )+1)/10 + + def test_incomplete1(self): + self.stest( np.array([1.3, 2.2, 2.3, 4.2, 5.1, 3.2, 5.3, 3.3, 2.1, 1.1, 5.2, 3.1]) ) + + def test_incomplete2(self): + self.stest( np.array([1.1, 1.2, 1.3, 2.1, 2.2, 2.3]) ) + + def test_incomplete3(self): + self.stest(np.array([5.1, 5.2, 4.1, 4.3, 4.2, 8.1, 8.2, 8.3]) ) + + def test_incomplete4(self): + self.stest(np.array([1.1, 1.3, 2.1, 2.2, 3.1, 3.3, 4.1, 4.2, 4.3]) ) + + def test_incomplete5(self): + self.stest(self.rseq(10, 40)) + + +class FermentationRate(UTestCase): + """ Test the fermentation rate question """ + + def stest(self, x, lower, upper): + I = fermentationRate(x, lower, upper) + s = trlist(x) + self.title = f"fermentationRate({s}, {lower}, {upper}) = {I:.3f} ?" + self.assertEqualC(I) + + @cache + def rseq(self, max, n): + np.random.seed(42) + return np.random.randint(max, size=(n,) ) + (np.random.randint(3, size=(n,) )+1)/n + + def test_rate1(self): + self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 15, 25) + + def test_rate2(self): + self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 1, 200) + + def test_rate3(self): + self.stest(np.array([1.75]), 1, 2) + + def test_rate4(self): + self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 18.2, 20) + + +class Report1Flat(Report): + title = "Week 4: Looping" + questions = [(ClusterAnalysis, 10), (RemoveIncomplete, 10), (Bacteria, 10), (FermentationRate, 10),] + pack_imports = [looping] + +if __name__ == "__main__": + # Uncomment to simply run everything as a unittest: + # unittest.main(verbosity=2) + evaluate_report_student(Report1Flat()) diff --git a/examples/02631/students/programs/report1intro_grade.py b/examples/02631/students/programs/report1intro_grade.py new file mode 100644 index 0000000..e7ffde8 --- /dev/null +++ b/examples/02631/students/programs/report1intro_grade.py @@ -0,0 +1,339 @@ +""" +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 +import pyfiglet +import unittest +import inspect +import os +import argparse +import time + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print(s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + # nL = + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + UTextResult.q_title_print = q_title_print # Hacky + UTextResult.show_progress_bar = show_progress_bar # Hacky. + UTextResult.number = n + UTextResult.nL = report.nL + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + obtained = len(res.successes) + + assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun + + obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f"Question {n+1} total" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + print("") + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.join(output_dir, token) + with open(token, 'wb') as f: + pickle.dump(results, f) + + if not args.autolab: + print(" ") + print("To get credit for your results, please upload the single unmodified 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 =\n t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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 {n+1} total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 unmodified 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\nimport numpy as np\nimport looping\nfrom looping import bacteriaGrowth, clusterAnalysis, removeIncomplete, fermentationRate\n\ndef trlist(x):\n s = str(list(x))\n if len(s) > 30:\n s = s[:30] + "...]"\n return s\n\nclass Bacteria(UTestCase):\n """ Bacteria growth rates """\n\n def stest(self, n0, alpha, K, N):\n g = bacteriaGrowth(n0=n0, alpha=alpha, K=K, N=N)\n self.title = f"bacteriaGrowth({n0}, {alpha}, {K}, {N}) = {g} ?"\n self.assertEqualC(g)\n\n def test_growth1(self):\n """ Hints:\n * Make sure to frobulate the frobulator.\n """\n self.stest(100, 0.4, 1000, 500)\n\n def test_growth2(self):\n self.stest(10, 0.4, 1000, 500)\n\n def test_growth3(self):\n self.stest(100, 1.4, 1000, 500)\n\n def test_growth4(self):\n self.stest(100, 0.0004, 1000, 500)\n\n def test_growth5(self):\n """\n hints:\n * What happens when n0 > N? (in this case return t=0) """\n self.stest(100, 0.4, 1000, 99)\n\nclass ClusterAnalysis(UTestCase):\n """ Test the cluster analysis method """\n\n def stest(self, n, seed):\n np.random.seed(seed)\n x = np.round(np.random.rand(n), 1)\n I = clusterAnalysis(x)\n self.title = f"clusterAnalysis({list(x)}) = {list(I)} ?"\n self.assertEqualC(list(I))\n\n def test_cluster1(self):\n """ Hints:\n * Make sure to frobulate the frobulator.\n * Just try harder\n """\n self.stest(3, 10)\n\n def test_cluster2(self):\n self.stest(4, 146)\n\n def test_cluster3(self):\n self.stest(5, 12)\n\n def test_cluster4(self):\n """\n Cluster analysis for tied lists\n Hints:\n * It may be that an observations has the same distance to the two clusters. Where do you assign it in this case?\n """\n x = np.array([10.0, 12.0, 10.0, 12.0, 9.0, 11.0, 11.0, 13.0])\n self.assertEqualC(list(clusterAnalysis(x) ) )\n\n\nclass RemoveIncomplete(UTestCase):\n """ Remove incomplete IDs """\n\n def stest(self, x):\n I = list( removeIncomplete(x) )\n self.title = f"removeId({trlist(x)}) = {trlist(I)} ?"\n self.assertEqualC(I)\n\n @cache\n def rseq(self, max, n):\n np.random.seed(42)\n return np.random.randint(max, size=(n,) ) + (np.random.randint(2, size=(n,) )+1)/10\n\n def test_incomplete1(self):\n self.stest( np.array([1.3, 2.2, 2.3, 4.2, 5.1, 3.2, 5.3, 3.3, 2.1, 1.1, 5.2, 3.1]) )\n\n def test_incomplete2(self):\n self.stest( np.array([1.1, 1.2, 1.3, 2.1, 2.2, 2.3]) )\n\n def test_incomplete3(self):\n self.stest(np.array([5.1, 5.2, 4.1, 4.3, 4.2, 8.1, 8.2, 8.3]) )\n\n def test_incomplete4(self):\n self.stest(np.array([1.1, 1.3, 2.1, 2.2, 3.1, 3.3, 4.1, 4.2, 4.3]) )\n\n def test_incomplete5(self):\n self.stest(self.rseq(10, 40))\n\n\nclass FermentationRate(UTestCase):\n """ Test the fermentation rate question """\n\n def stest(self, x, lower, upper):\n I = fermentationRate(x, lower, upper)\n s = trlist(x)\n self.title = f"fermentationRate({s}, {lower}, {upper}) = {I:.3f} ?"\n self.assertEqualC(I)\n\n @cache\n def rseq(self, max, n):\n np.random.seed(42)\n return np.random.randint(max, size=(n,) ) + (np.random.randint(3, size=(n,) )+1)/n\n\n def test_rate1(self):\n self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 15, 25)\n\n def test_rate2(self):\n self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 1, 200)\n\n def test_rate3(self):\n self.stest(np.array([1.75]), 1, 2)\n\n def test_rate4(self):\n self.stest(np.array([20.1, 19.3, 1.1, 18.2, 19.7, 121.1, 20.3, 20.0]), 18.2, 20)\n\n\nclass Report1Flat(Report):\n title = "Week 4: Looping"\n questions = [(ClusterAnalysis, 10), (RemoveIncomplete, 10), (Bacteria, 10), (FermentationRate, 10),]\n pack_imports = [looping]' +report1_payload = '80049592150000000000007d94288c0f436c7573746572416e616c79736973947d94288c0f436c7573746572416e616c79736973948c0d746573745f636c7573746572319486948c057469746c659486948c2e636c7573746572416e616c79736973285b302e382c20302e302c20302e365d29203d205b312c20322c20315d203f946803680486948c066173736572749486947d944b005d94288c156e756d70792e636f72652e6d756c74696172726179948c067363616c61729493948c056e756d7079948c0564747970659493948c02693494898887945294284b038c013c944e4e4e4affffffff4affffffff4b007494624304010000009486945294681068164304020000009486945294681068164304010000009486945294657368038c0d746573745f636c757374657232948694680686948c36636c7573746572416e616c79736973285b302e352c20302e362c20302e332c20302e335d29203d205b322c20322c20312c20315d203f94680368228694680a86947d944b005d9428681068164304020000009486945294681068164304020000009486945294681068164304010000009486945294681068164304010000009486945294657368038c0d746573745f636c757374657233948694680686948c3e636c7573746572416e616c79736973285b302e322c20302e372c20302e332c20302e352c20302e305d29203d205b312c20322c20312c20322c20315d203f94680368368694680a86947d944b005d9428681068164304010000009486945294681068164304020000009486945294681068164304010000009486945294681068164304020000009486945294681068164304010000009486945294657368038c0d746573745f636c757374657234948694680a86947d944b005d942868106816430401000000948694529468106816430402000000948694529468106816430401000000948694529468106816430402000000948694529468106816430401000000948694529468106816430401000000948694529468106816430401000000948694529468106816430402000000948694529465738c0474696d6594473fdf6c8500000000758c1052656d6f7665496e636f6d706c657465947d94288c1052656d6f7665496e636f6d706c657465948c10746573745f696e636f6d706c657465319486948c057469746c659486948c5372656d6f76654964285b312e332c20322e322c20322e332c20342e322c20352e312c20332e322c2e2e2e5d29203d205b322e322c20322e332c20352e312c20332e322c20352e332c20332e332c2e2e2e5d203f94686d686e86948c066173736572749486947d944b005d9428681068138c02663894898887945294284b0368174e4e4e4affffffff4affffffff4b0074946243089a9999999999014094869452946810687a4308666666666666024094869452946810687a4308666666666666144094869452946810687a43089a9999999999094094869452946810687a4308333333333333154094869452946810687a43086666666666660a4094869452946810687a4308cdcccccccccc004094869452946810687a4308cdcccccccccc144094869452946810687a4308cdcccccccccc084094869452946573686d8c10746573745f696e636f6d706c65746532948694687086948c4b72656d6f76654964285b312e312c20312e322c20312e332c20322e312c20322e322c20322e335d29203d205b312e312c20312e322c20312e332c20322e312c20322e322c20322e335d203f94686d68978694687486947d944b005d94286810687a43089a9999999999f13f94869452946810687a4308333333333333f33f94869452946810687a4308cdccccccccccf43f94869452946810687a4308cdcccccccccc004094869452946810687a43089a9999999999014094869452946810687a4308666666666666024094869452946573686d8c10746573745f696e636f6d706c65746533948694687086948c4f72656d6f76654964285b352e312c20352e322c20342e312c20342e332c20342e322c20382e312c2e2e2e5d29203d205b342e312c20342e332c20342e322c20382e312c20382e322c20382e335d203f94686d68b18694687486947d944b005d94286810687a4308666666666666104094869452946810687a4308333333333333114094869452946810687a4308cdcccccccccc104094869452946810687a4308333333333333204094869452946810687a4308666666666666204094869452946810687a43089a9999999999204094869452946573686d8c10746573745f696e636f6d706c65746534948694687086948c4072656d6f76654964285b312e312c20312e332c20322e312c20322e322c20332e312c20332e332c2e2e2e5d29203d205b342e312c20342e322c20342e335d203f94686d68cb8694687486947d944b005d94286810687a4308666666666666104094869452946810687a4308cdcccccccccc104094869452946810687a4308333333333333114094869452946573686d8c10746573745f696e636f6d706c657465359486948c06406361636865948c0472736571948c0966756e63746f6f6c73948c0a5f486173686564536571949394298194284b0a4b28654e7d948c096861736876616c7565948a0884d8ef03874d7f467386946287948694680e8c0c5f7265636f6e73747275637494939468118c076e6461727261799493944b0085944301629487945294284b014b28859468138c02663894898887945294284b0368174e4e4e4affffffff4affffffff4b0074946289424001000066666666666618409a99999999990940cdcccccccccc1c40cdcccccccccc1040cdcccccccccc184033333333333322409a999999999901406666666666661840cdcccccccccc1c40cdcccccccccc10409a999999999909406666666666661c40cdcccccccccc1c40cdcccccccccc0040cdcccccccccc14406666666666661040333333333333f33f6666666666661c406666666666661440333333333333f33f66666666666610409a9999999999c93f6666666666662240cdcccccccccc144066666666666620409a9999999999c93f66666666666622409a99999999990140cdcccccccccc18409a9999999999094066666666666620409a999999999901406666666666661040cdcccccccccc0040cdcccccccccc1840cdcccccccccc10406666666666662040cdcccccccccc1840333333333333f33f9a9999999999094094749462686d68dc8694687086948c5372656d6f76654964285b362e312c20332e322c20372e322c20342e322c20362e322c20392e312c2e2e2e5d29203d205b392e312c20352e322c20312e322c20352e312c20312e322c20392e322c2e2e2e5d203f94686d68dc8694687486947d944b005d94286810687a4308333333333333224094869452946810687a4308cdcccccccccc144094869452946810687a4308333333333333f33f94869452946810687a4308666666666666144094869452946810687a4308333333333333f33f94869452946810687a4308666666666666224094869452946810687a4308cdcccccccccc144094869452946810687a4308666666666666204094869452946810687a4308666666666666224094869452946810687a4308666666666666204094869452946810687a4308666666666666204094869452946810687a4308333333333333f33f94869452946573686a473fcf9dc400000000758c084261637465726961947d94288c084261637465726961948c0c746573745f67726f777468319486948c057469746c659486948c29626163746572696147726f777468283130302c20302e342c20313030302c2035303029203d2037203f946a250100006a2601000086948c066173736572749486947d944b004b07736a250100006a2601000086948c08636f7665726167659486947d94286a250100006a2601000086947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b158ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a2020202022222294869473736a250100008c0c746573745f67726f777468329486947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b158ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a2020202022222294869473736a250100008c0c746573745f67726f777468339486947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b158ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a2020202022222294869473736a250100008c0c746573745f67726f777468349486947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b158ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a2020202022222294869473736a250100008c0c746573745f67726f777468359486947d948c0a6c6f6f70696e672e7079947d948c2564656620626163746572696147726f777468286e302c20616c7068612c204b2c204e293a20944b118ce72222220a2020202043616c63756c6174652074696d6520756e74696c2062616374657269612067726f77746820657863656564204e207374617274696e672066726f6d206120706f70756c6174696f6e206f66206e302062616374657269612e0a2020202068696e74733a0a20202020202020202a20636f6e7369646572206e300a20202020202020202a20616c706861203e20300a202020203a706172616d206e303a0a202020203a706172616d20616c7068613a0a202020203a706172616d204b3a0a202020203a706172616d204e3a0a202020203a72657475726e3a0a202020202222229486947373756a250100006a3a01000086946a2801000086948c29626163746572696147726f7774682831302c20302e342c20313030302c2035303029203d203134203f946a250100006a3a01000086946a2c01000086947d944b004b0e736a250100006a3a01000086946a3001000086946a320100006a250100006a4201000086946a2801000086948c29626163746572696147726f777468283130302c20312e342c20313030302c2035303029203d2033203f946a250100006a4201000086946a2c01000086947d944b004b03736a250100006a4201000086946a3001000086946a320100006a250100006a4a01000086946a2801000086948c2f626163746572696147726f777468283130302c20302e303030342c20313030302c2035303029203d2035343934203f946a250100006a4a01000086946a2c01000086947d944b004d7615736a250100006a4a01000086946a3001000086946a320100006a250100006a5201000086946a2801000086948c28626163746572696147726f777468283130302c20302e342c20313030302c20393929203d2030203f946a250100006a5201000086946a2c01000086947d944b004b00736a250100006a5201000086946a3001000086946a32010000686a473fcf9d9a00000000758c104665726d656e746174696f6e52617465947d94288c104665726d656e746174696f6e52617465948c0a746573745f72617465319486948c057469746c659486948c476665726d656e746174696f6e52617465285b32302e312c2031392e332c20312e312c2031382e322c2031392e372c202e2e2e5d2c2031352c20323529203d2031392e363030203f946a7c0100006a7d01000086948c066173736572749486947d944b006810687a43089a999999999933409486945294736a7c0100008c0a746573745f72617465329486946a7f01000086948c476665726d656e746174696f6e52617465285b32302e312c2031392e332c20312e312c2031382e322c2031392e372c202e2e2e5d2c20312c2032303029203d2032392e393735203f946a7c0100006a8901000086946a8301000086947d944b006810687a43089899999999f93d409486945294736a7c0100008c0a746573745f72617465339486946a7f01000086948c286665726d656e746174696f6e52617465285b312e37355d2c20312c203229203d20312e373530203f946a7c0100006a9301000086946a8301000086947d944b006810687a4308000000000000fc3f9486945294736a7c0100008c0a746573745f72617465349486946a7f01000086948c496665726d656e746174696f6e52617465285b32302e312c2031392e332c20312e312c2031382e322c2031392e372c202e2e2e5d2c2031382e322c20323029203d2031392e353030203f946a7c0100006a9d01000086946a8301000086947d944b006810687a43080000000000803340948694529473686a473fc74c0a0000000075752e' +name="Report1Flat" + +report = source_instantiate(name, report1_source, report1_payload) +output_dir = os.path.dirname(__file__) +gather_upload_to_campusnet(report, output_dir) diff --git a/examples/02631/students/programs/unitgrade/Bacteria.pkl b/examples/02631/students/programs/unitgrade/Bacteria.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b246df45c63387d95bc69cc26deba7770d8249c5 GIT binary patch literal 2050 zcmeHIO-sW-5Ut;(h~PnwG8B|nN=&syT2Mg`9)kS~vZmW6kY>YXQ}s~r<SEDXH~JI2 zI+JXRt@U6%+CX6DW#60Kmyq|e{`Rp|$gYvDq{VCIDlUDd({06Lb>ElbQF(R!s?!bS ztAJBgeK4zTv!|81ZP&oI8a2QQ%cgBt;R4R#N_#8bDl#@%9CN9VJkn0V*@_Kbbj6TM z)+bq<pQ9t4mI5InKkQr4GveuCm-k?1Xo!PY5P7TyP7ONMHfU!reJ_{GCBW;31#S{B z#X<Q44oRr|U?Ktev4B6hoOhuEv0_r;Jm^U=00xnWi0q3HL=QrH!n87R&kt4H&Ptkf z46X=czsn^q{eouOgG<P<cEltb;EJ4R+HB1f;!JfN(@Cxp8RiY15&eqM#HqJ*nwhxt z4#0T(3-8XB-YxvyHJ08j>|OIu-tGRc|6PLlu>Y#P^|=`|FM{fgJcEc;G~u&D;X}YS z-aA+y{3wYLQ~wrgXSpP?#*%9!Nx)e0$Vi^dgqBH4Ov%}>Y&KeEuf#RNcf&nWCS<H^ c8Rg1+<%w*oMG<W0dnM9%uLV*hAX7}NPbL6<qW}N^ literal 0 HcmV?d00001 diff --git a/examples/02631/students/programs/unitgrade/ClusterAnalysis.pkl b/examples/02631/students/programs/unitgrade/ClusterAnalysis.pkl new file mode 100644 index 0000000000000000000000000000000000000000..36635371f6e86d5f841a3827696a5909e766532b GIT binary patch literal 763 zcma)(Jx_!{5QYKK*r<*6TVg^saEGUbA2D>2P+WT<25}Qexbwm;1;r&+ek61^{!jmb z&Vq-6XxL(4XWyN9-q}a}?Oju<wF09&aMPR%mR&ExwBQMca!)WWZlgMF!xNO-A`vN* zfAlN8Q5!Rho|(j=mJE~Nm|T#VZIY2m?0{TBti~E%+X*>mnZODw@RR`C9xd|uvY^pJ z#^^jxMG|INSfG+eVH#$z#;%nFzxSe8GMUj52VP&EVG4$~RjTLU`~J-bt}1wnfIC-L z(Hb_ILcAXzy1KObW80iE9;<DuBb3ED(oT9zo>cy17+0ZoiZ-RwZ?gnT>^3fY3H}1A z#sBb)H++{pe28Ju(}u6YwOY^7dK9OJvuWLnJ@EARSRIMooyovzLr6NMI@@O2jOX+d D%KPtw literal 0 HcmV?d00001 diff --git a/examples/02631/students/programs/unitgrade/FermentationRate.pkl b/examples/02631/students/programs/unitgrade/FermentationRate.pkl new file mode 100644 index 0000000000000000000000000000000000000000..9f5cea13c96d67ff2704de398cd66b78a2db463b GIT binary patch literal 618 zcmZo*nHtZ;00y;FG<pQwQj2m^^GXs+GV}9-5=&C2^l+7=7MH{q0ojIA+NSidmSmRX zq=H!PX=rLSqKypn40RL?E%l6nB$#QTX9N~7*HO^Z(~AYNO?4EEOf?m3LCVYw3>56A zWH4qh0nKAeEG|whDghc@JH?x!M>MZAx1drlIlm}XFSj(OBr~z7D6tZ#tT;I_C$VVC zWT0EXN`Q1qNo7GQNQ5cPVoGO6`;?$58s5x3j5bsJ{QSKB0|A)uW+<7G<jgT^CI}ci z0Bs7IQk=nv<V7Q(SOyFEo&);LzyRbqBTGF?b5n@tB!OmfKzx<Kn<4DXF#}}(Pg`sb zForom12c3D^~_B{Aqcbt9DYD9H29R!ZGgn+AA2lbG=bURiD?5d@du7kBd{ldF=z^l OL0zm)Xn=)ssU83q4$F!F literal 0 HcmV?d00001 diff --git a/examples/02631/students/programs/unitgrade/RemoveIncomplete.pkl b/examples/02631/students/programs/unitgrade/RemoveIncomplete.pkl new file mode 100644 index 0000000000000000000000000000000000000000..30edf2db7bc0e41f0ada9c1c7443ac1d487a6d9a GIT binary patch literal 1444 zcma)6O-sW-5N)fK>Om^hd+=1DETm~`3yOj#jZ~<|A`;szDfEkEtMpLt<OlTTdeGmb zKSuPfvpY$;p<8qh$?VJ9&AfTDd$;=Xnoh_u<@s5*LHolSdNODY`=cIZl+Ut^`s}J3 zAtt})vo!0n9u?7rhb!9orb(=VQ6xq2fT$#*GBFE=1rZ_Sa@RO8nsY+*4Oqap;2~JX zA$OB*3ie)ee4ly@elz8E;u7-P9*p~=326;IO8Vm->pGt2On~XPoSx(HXGl#_;N51E z5fw<PQ{_*O54^$i_R1`|&+GO2=?@I~uoH}5A7|cP#Yi5(&l=p_baTg<jvT2)D5pZW za9xEgIkr^@_u5dQk9RQ=i%@KPWMz;=eK)yaND)$YSD{?CY6X<t6b1yQz%(p8+F%M} zmNC$RBJ7)qYL7KRwV#JdnNaPQ3SrXUi%?AMUnSS7wL-P$bG5RO%0g{Ps47L>SA}WR zc$#lURV=C!D`>hijbd~ztJ;K$w^RrfYgmTL!o6Y;79oJNic6v5qq*Y1J$c?jYb`u# zc!=at3lz(`gc3Xdlekn*pjw-0Im}eK6e^e%<gkYZK~xb@P4z}h;1&Q_wxl#vMm50Z qm%^<kc{F23Ei)z`J4Tu&MKk`#`fGcQu?WQ$E@j0!B9<!k$K)5^Qt5yI literal 0 HcmV?d00001 diff --git a/examples/autolab_example/autolab_example.py b/examples/autolab_example/autolab_example.py index 6cdf58a..a4bdf62 100644 --- a/examples/autolab_example/autolab_example.py +++ b/examples/autolab_example/autolab_example.py @@ -1,9 +1,9 @@ import os -from autolab.autolab import deploy_assignment +from unitgrade_private2.autolab.autolab import deploy_assignment if __name__ == "__main__": wdir = os.getcwd() - args = [('example_simplest', 'cs101', 'report1_grade.py', 'report1_grade.py'), + args = [('example_simplest', 'programs', 'report1_grade.py', 'report1_grade.py'), ('example_framework', 'cs102', 'report2_grade.py', 'report2_grade.py'), ('example_docker', 'cs103', 'report3_complete_grade.py', 'report3_grade.py'), ] diff --git a/examples/autolab_example/tmp/cs101/cs101.yml b/examples/autolab_example/tmp/cs101/cs101.yml index 7631a7f..6dc13d8 100644 --- a/examples/autolab_example/tmp/cs101/cs101.yml +++ b/examples/autolab_example/tmp/cs101/cs101.yml @@ -1,14 +1,14 @@ --- general: - name: cs101 + name: programs description: '' display_name: CS 101 Report 1 handin_filename: Report1_handin.token handin_directory: handin max_grace_days: 0 - handout: cs101-handout.tar - writeup: writeup/cs101.html + handout: programs-handout.tar + writeup: writeup/programs.html max_submissions: -1 disable_handins: false max_size: 2 diff --git a/examples/autolab_example/tmp/cs101/src/driver_python.py b/examples/autolab_example/tmp/cs101/src/driver_python.py index 9b3e081..074cc75 100644 --- a/examples/autolab_example/tmp/cs101/src/driver_python.py +++ b/examples/autolab_example/tmp/cs101/src/driver_python.py @@ -25,7 +25,7 @@ def pfiles(): student_token_file = 'Report1_handin.token' instructor_grade_script = 'report1_grade.py' -grade_file_relative_destination = "cs101\report1_grade.py" +grade_file_relative_destination = "programs\report1_grade.py" with open(student_token_file, 'rb') as f: results = pickle.load(f) sources = results['sources'][0] @@ -55,8 +55,8 @@ def rcom(cm): start = time.time() rcom(command) # pfiles() -# for f in glob.glob(host_tmp_dir + "/cs101/*"): -# print("cs101/", f) +# for f in glob.glob(host_tmp_dir + "/programs/*"): +# print("programs/", f) # print("---") ls = glob.glob(token) # print(ls) diff --git a/examples/autolab_example/tmp/cs101/src/report1_grade.py b/examples/autolab_example/tmp/cs101/src/report1_grade.py index 8972ab5..fbbeabf 100644 --- a/examples/autolab_example/tmp/cs101/src/report1_grade.py +++ b/examples/autolab_example/tmp/cs101/src/report1_grade.py @@ -453,7 +453,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"""\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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, too 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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n item_title = item_title.split("\\n")[0]\n\n item_title = test.shortDescription() # Better for printing (get from cache).\n # test.countTestCases()\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n @classmethod\n def question_title(cls):\n return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n # def _callSetUp(self):\n # # Always run before method is called.\n # print("asdf")\n # pass\n # @classmethod\n # def setUpClass(cls):\n # # self._cache_put((self.cache_id(), \'title\'), value)\n # cls.reset()\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 # i = 0\n # for i in itertools.count():\n # # key = k0 + (i,)\n # if i not in self._cache_get( (k0, \'assert\') ):\n # break\n # return i\n # return key\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 # print("Is this needed?")\n # self._ensure_cache_exists()\n # return key in self.__class__._cache2\n\n def wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\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 # print("Bad output\\n\\n")\n\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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, too 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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n item_title = item_title.split("\\n")[0]\n\n item_title = test.shortDescription() # Better for printing (get from cache).\n # test.countTestCases()\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n @classmethod\n def question_title(cls):\n return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n # def _callSetUp(self):\n # # Always run before method is called.\n # print("asdf")\n # pass\n # @classmethod\n # def setUpClass(cls):\n # # self._cache_put((self.cache_id(), \'title\'), value)\n # cls.reset()\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 # i = 0\n # for i in itertools.count():\n # # key = k0 + (i,)\n # if i not in self._cache_get( (k0, \'assert\') ):\n # break\n # return i\n # return key\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 # print("Is this needed?")\n # self._ensure_cache_exists()\n # return key in self.__class__._cache2\n\n def wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\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 programs.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 # print("Bad output\\n\\n")\n\n\nimport programs\nclass Report1(Report):\n title = "CS 101 Report 1"\n questions = [(Week1, 10)] # Include a single question for 10 credits.\n pack_imports = [programs]' report1_payload = '8004953f000000000000007d948c055765656b31947d948c2c6e6f20636163686520736565205f73657475705f616e737765727320696e20756e69746772616465322e7079948873732e' name="Report1" diff --git a/examples/autolab_example/tmp/cs102/src/driver_python.py b/examples/autolab_example/tmp/cs102/src/driver_python.py index 092842a..80da44e 100644 --- a/examples/autolab_example/tmp/cs102/src/driver_python.py +++ b/examples/autolab_example/tmp/cs102/src/driver_python.py @@ -55,8 +55,8 @@ def rcom(cm): start = time.time() rcom(command) # pfiles() -# for f in glob.glob(host_tmp_dir + "/cs101/*"): -# print("cs101/", f) +# for f in glob.glob(host_tmp_dir + "/programs/*"): +# print("programs/", f) # print("---") ls = glob.glob(token) # print(ls) diff --git a/examples/autolab_example/tmp/cs103/src/driver_python.py b/examples/autolab_example/tmp/cs103/src/driver_python.py index ed9bd8b..34e6b0b 100644 --- a/examples/autolab_example/tmp/cs103/src/driver_python.py +++ b/examples/autolab_example/tmp/cs103/src/driver_python.py @@ -55,8 +55,8 @@ def rcom(cm): start = time.time() rcom(command) # pfiles() -# for f in glob.glob(host_tmp_dir + "/cs101/*"): -# print("cs101/", f) +# for f in glob.glob(host_tmp_dir + "/programs/*"): +# print("programs/", f) # print("---") ls = glob.glob(token) # print(ls) diff --git a/examples/example_docker/instructor/cs103/.coverage b/examples/example_docker/instructor/cs103/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..f386b2198168450113494de5b3b3bd99d653d108 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/examples/example_docker/instructor/cs103/Report3_handin_25_of_30.token b/examples/example_docker/instructor/cs103/Report3_handin_25_of_30.token new file mode 100644 index 0000000000000000000000000000000000000000..af7c703d2692f8bf15644581b9cdf6688b4b9c3b GIT binary patch literal 117485 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 IHae^R4>-ihm;e9( literal 0 HcmV?d00001 diff --git a/examples/example_docker/instructor/cs103/Report3_handin_30_of_30.token b/examples/example_docker/instructor/cs103/Report3_handin_30_of_30.token new file mode 100644 index 0000000000000000000000000000000000000000..0ddcac53870efc12081817272c4b694c4c34fe9e GIT binary patch literal 117467 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 CdBY6= literal 0 HcmV?d00001 diff --git a/examples/example_docker/instructor/cs103/__pycache__/homework1.cpython-38.pyc b/examples/example_docker/instructor/cs103/__pycache__/homework1.cpython-38.pyc index 4beaff64aef655b294b4513aa4842f7eada9c024..c2ad43883cce209ac9cd30ec4db9f197297be5f1 100644 GIT binary patch delta 20 acmX@ec94xbl$V!_0SKh07;NM=V+H^%jRWTZ delta 20 acmX@ec94xbl$V!_0SG=G*4fBy#tZ;7Z3O55 diff --git a/examples/example_docker/instructor/cs103/__pycache__/report3.cpython-38.pyc b/examples/example_docker/instructor/cs103/__pycache__/report3.cpython-38.pyc index da19a02cc1c508d3eaafcf35b0b34e9d58501d7e..78157d3ccd77777a3e6bdc5d8ef57fa286b7b497 100644 GIT binary patch delta 25 fcmaFC^@58pl$V!_0SNTx8YFr#Z{&+)Wn=~bPCo@V delta 25 fcmaFC^@58pl$V!_0SJ`-=_DR!+Q=8l%E$-+RfPrt diff --git a/examples/example_docker/instructor/cs103/__pycache__/report3_complete.cpython-38.pyc b/examples/example_docker/instructor/cs103/__pycache__/report3_complete.cpython-38.pyc index 921af5c7208e7aa4d953fa1e4551029aeb05c182..ca3fa7b43ad2a579965093501504022367ac79d6 100644 GIT binary patch delta 53 zcmcb@dy<zol$V!_0SN3C8zipX$ZNpHDp_2VtT)+?O_h%i#463pEJ-g)Oi7*Gz!uEN F3;=XP4yFJA delta 64 zcmX@fdxe)bl$V!_0SI{i=_HzN<TYSp6rJqErYb92nwMFUUX+-UYNQ8e#%HAF6r>gv LPcCH(W@H2aEmRWf diff --git a/examples/example_docker/instructor/cs103/deploy.py b/examples/example_docker/instructor/cs103/deploy.py index 9379f59..e1350f9 100644 --- a/examples/example_docker/instructor/cs103/deploy.py +++ b/examples/example_docker/instructor/cs103/deploy.py @@ -4,25 +4,24 @@ from unitgrade_private2.hidden_create_files import setup_grade_file_report from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet from unitgrade_private2.deployment import remove_hidden_methods from unitgrade_private2.docker_helpers import docker_run_token_file -import shutil import os import glob import pickle from snipper.snip_dir import snip_dir +wd = os.path.dirname(__file__) def deploy_student_files(): setup_grade_file_report(Report3, minify=False, obfuscate=False, execute=False) - # Report3.reset() fout, ReportWithoutHidden = remove_hidden_methods(Report3, outfile="report3.py") setup_grade_file_report(ReportWithoutHidden, minify=False, obfuscate=False, execute=False) - sdir = "../../students/cs103" - snip_dir(source_dir="../cs103", dest_dir=sdir, clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py', 'report3_complete*.py']) + sdir = wd+"/../../students/cs103" + snip_dir(source_dir=wd+"/../cs103", dest_dir=sdir, clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py', 'report3_complete*.py']) return sdir def run_student_code_on_docker(Dockerfile, student_token_file): token = docker_run_token_file(Dockerfile_location=Dockerfile, - host_tmp_dir=os.path.dirname(Dockerfile) + "/tmp", + host_tmp_dir=os.path.dirname(Dockerfile) + "/home", student_token_file=student_token_file, instructor_grade_script="report3_complete_grade.py") with open(token, 'rb') as f: @@ -32,17 +31,14 @@ def run_student_code_on_docker(Dockerfile, student_token_file): if __name__ == "__main__": # Step 1: Deploy the students files and return the directory they were written to student_directory = deploy_student_files() - # import sys - # sys.exit() - # student_directory = "../../students/cs103" + # Step 2: Simulate that the student run their report script and generate a .token file. os.system("cd ../../students && python -m cs103.report3_grade") student_token_file = glob.glob(student_directory + "/*.token")[0] - # Step 3: Compile the Docker image (obviously you will only do this once; add your packages to requirements.txt). - Dockerfile = os.path.dirname(__file__) + "/../unitgrade-docker/Dockerfile" - os.system("cd ../unitgrade-docker && docker build --tag unitgrade-docker .") + Dockerfile = os.path.dirname(__file__) + "/../../../../docker_images/unitgrade-docker/Dockerfile" + os.system(f"cd {os.path.dirname(Dockerfile)} && docker build --tag unitgrade-docker .") # Step 4: Test the students .token file and get the results-token-file. Compare the contents with the students_token_file: checked_token = run_student_code_on_docker(Dockerfile, student_token_file) diff --git a/examples/example_docker/instructor/cs103/report3.py b/examples/example_docker/instructor/cs103/report3.py index 6dfbe04..3bdc6e6 100644 --- a/examples/example_docker/instructor/cs103/report3.py +++ b/examples/example_docker/instructor/cs103/report3.py @@ -1,5 +1,5 @@ -from unitgrade2.unitgrade2 import UTestCase, Report, hide -from unitgrade2.unitgrade_helpers2 import evaluate_report_student +from src.unitgrade2.unitgrade2 import UTestCase, Report +from src.unitgrade2 import evaluate_report_student class Week1(UTestCase): """ The first question for week 1. """ @@ -21,4 +21,6 @@ class Report3(Report): pack_imports = [cs103] if __name__ == "__main__": + # from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet + # gather_upload_to_campusnet(Report3()) evaluate_report_student(Report3()) \ No newline at end of file diff --git a/examples/example_docker/instructor/cs103/report3_complete.py b/examples/example_docker/instructor/cs103/report3_complete.py index 4e72f82..dd85bd8 100644 --- a/examples/example_docker/instructor/cs103/report3_complete.py +++ b/examples/example_docker/instructor/cs103/report3_complete.py @@ -1,5 +1,5 @@ from unitgrade2.unitgrade2 import UTestCase, Report, hide -from unitgrade2.unitgrade_helpers2 import evaluate_report_student +from unitgrade2 import evaluate_report_student class Week1(UTestCase): """ The first question for week 1. """ @@ -30,4 +30,6 @@ class Report3(Report): pack_imports = [cs103] if __name__ == "__main__": + # from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet + # gather_upload_to_campusnet(Report3()) evaluate_report_student(Report3()) diff --git a/examples/example_docker/instructor/cs103/report3_complete_grade.py b/examples/example_docker/instructor/cs103/report3_complete_grade.py index b053e48..8ea5f2e 100644 --- a/examples/example_docker/instructor/cs103/report3_complete_grade.py +++ b/examples/example_docker/instructor/cs103/report3_complete_grade.py @@ -4,15 +4,10 @@ from tabulate import tabulate from datetime import datetime import pyfiglet import unittest -# from unitgrade2.unitgrade2 import MySuite - import inspect import os import argparse -import sys import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: @@ -113,24 +108,20 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) else: b = "Unitgrade" - print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) s = report.title if hasattr(report, "version") and report.version is not None: s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + print(s, "(use --help for options)" if show_help_flag else "") # print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] - nL = 80 t_start = time.time() score = {} loader = SequentialTestLoader() for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) if question is not None and n+1 != question: continue suite = loader.loadTestsFromTestCase(q) @@ -140,11 +131,10 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa q.possible = 0 q.obtained = 0 q_ = {} # Gather score in this class. - # 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. UTextResult.number = n + UTextResult.nL = report.nL res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) @@ -153,20 +143,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun - # possible = int(ws @ possible) - # obtained = int(ws @ obtained) - # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0 - obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} q.obtained = obtained q.possible = possible - s1 = f"*** Question q{n+1}" + s1 = f" * q{n+1}) Total" s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) ws, possible, obtained = upack(score) possible = int( msum(possible) ) @@ -181,15 +167,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa seconds = dt - minutes*60 plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) results = {'total': (obtained, possible), 'details': score} return results, table_data - - from tabulate import tabulate from datetime import datetime import inspect @@ -212,7 +199,8 @@ def gather_imports(imp): # dn = os.path.dirname(f) # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: top_package = os.path.dirname(m.__file__) module_import = True else: @@ -233,7 +221,7 @@ def gather_imports(imp): for file in files: if file.endswith(".py"): fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) zip.write(fpath, v) resources['zipfile'] = zip_buffer.getvalue() @@ -277,14 +265,14 @@ def gather_upload_to_campusnet(report, output_dir=None): 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)) + # print(" ") + # print("="*n) + # print("Final evaluation") + # print(tabulate(table_data)) # also load the source code of missing files... sources = {} - + print("") if not args.autolab: if len(report.individual_imports) > 0: print("By uploading the .token file, you verify the files:") @@ -297,12 +285,15 @@ def gather_upload_to_campusnet(report, output_dir=None): 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) + _, report_relative_location, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import nimp['name'] = m.__name__ sources[k] = nimp # if len([k for k in nimp if k not in sources]) > 0: - print(f"*** {m.__name__}") + print(f" * {m.__name__}") # sources = {**sources, **nimp} results['sources'] = sources @@ -315,15 +306,17 @@ def gather_upload_to_campusnet(report, output_dir=None): vstring = "_v"+report.version if report.version is not None else "" token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) - token = os.path.join(output_dir, token) + token = os.path.normpath(os.path.join(output_dir, token)) + + with open(token, 'wb') as f: pickle.dump(results, f) if not args.autolab: print(" ") - print("To get credit for your results, please upload the single file: ") + print("To get credit for your results, please upload the single unmodified file: ") print(">", token) - print("To campusnet without any modifications.") + # print("To campusnet without any modifications.") # print("Now time for some autolab fun") @@ -336,8 +329,8 @@ 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"""\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, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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\nclass Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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\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, too many numbers!", len(all))\n return all\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\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n# raise Exception("no suite")\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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n # item_title = item_title.split("\\n")[0]\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n # test.countTestCases()\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 = 1\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n self._stdout = sys.stdout\n import io\n sys.stdout = io.StringIO()\n super().setUp()\n # print("Setting up...")\n\n def _callTearDown(self):\n sys.stdout = self._stdout\n super().tearDown()\n # print("asdfsfd")\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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# from unitgrade2.unitgrade2 import MySuite\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n @hide\n def test_add_hidden(self):\n # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n # See the output in the student directory for more information.\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n @hide\n def test_hidden_fail(self):\n self.assertEqual(2,3)\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' -report1_payload = '80049586000000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d659486944700000000000000008c0474696d6594473f60628000000000758c0d4175746f6d6174696350617373947d94680c473f689d000000000073752e' +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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.normpath(os.path.join(output_dir, token))\n\n\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n @hide\n def test_add_hidden(self):\n # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n # See the output in the student directory for more information.\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n @hide\n def test_hidden_fail(self):\n self.assertEqual(2,3)\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' +report1_payload = '80049589000000000000007d94288c055765656b31947d942868018c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b047568018c0f746573745f6164645f68696464656e948694680586947d944b004b04738c0474696d6594473fe3b8a400000000758c0d4175746f6d6174696350617373947d94680c473fc45a520000000073752e' name="Report3" report = source_instantiate(name, report1_source, report1_payload) diff --git a/examples/example_docker/instructor/cs103/report3_grade.py b/examples/example_docker/instructor/cs103/report3_grade.py index 06bc99f..3b6b512 100644 --- a/examples/example_docker/instructor/cs103/report3_grade.py +++ b/examples/example_docker/instructor/cs103/report3_grade.py @@ -4,15 +4,10 @@ from tabulate import tabulate from datetime import datetime import pyfiglet import unittest -# from unitgrade2.unitgrade2 import MySuite - import inspect import os import argparse -import sys import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: @@ -113,24 +108,20 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) else: b = "Unitgrade" - print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) s = report.title if hasattr(report, "version") and report.version is not None: s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + print(s, "(use --help for options)" if show_help_flag else "") # print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] - nL = 80 t_start = time.time() score = {} loader = SequentialTestLoader() for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) if question is not None and n+1 != question: continue suite = loader.loadTestsFromTestCase(q) @@ -140,11 +131,10 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa q.possible = 0 q.obtained = 0 q_ = {} # Gather score in this class. - # 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. UTextResult.number = n + UTextResult.nL = report.nL res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) @@ -153,20 +143,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun - # possible = int(ws @ possible) - # obtained = int(ws @ obtained) - # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0 - obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} q.obtained = obtained q.possible = possible - s1 = f"*** Question q{n+1}" + s1 = f" * q{n+1}) Total" s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) ws, possible, obtained = upack(score) possible = int( msum(possible) ) @@ -181,15 +167,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa seconds = dt - minutes*60 plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) results = {'total': (obtained, possible), 'details': score} return results, table_data - - from tabulate import tabulate from datetime import datetime import inspect @@ -212,7 +199,8 @@ def gather_imports(imp): # dn = os.path.dirname(f) # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: top_package = os.path.dirname(m.__file__) module_import = True else: @@ -233,7 +221,7 @@ def gather_imports(imp): for file in files: if file.endswith(".py"): fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) zip.write(fpath, v) resources['zipfile'] = zip_buffer.getvalue() @@ -277,14 +265,14 @@ def gather_upload_to_campusnet(report, output_dir=None): 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)) + # print(" ") + # print("="*n) + # print("Final evaluation") + # print(tabulate(table_data)) # also load the source code of missing files... sources = {} - + print("") if not args.autolab: if len(report.individual_imports) > 0: print("By uploading the .token file, you verify the files:") @@ -297,12 +285,15 @@ def gather_upload_to_campusnet(report, output_dir=None): 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) + _, report_relative_location, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import nimp['name'] = m.__name__ sources[k] = nimp # if len([k for k in nimp if k not in sources]) > 0: - print(f"*** {m.__name__}") + print(f" * {m.__name__}") # sources = {**sources, **nimp} results['sources'] = sources @@ -315,15 +306,17 @@ def gather_upload_to_campusnet(report, output_dir=None): vstring = "_v"+report.version if report.version is not None else "" token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) - token = os.path.join(output_dir, token) + token = os.path.normpath(os.path.join(output_dir, token)) + + with open(token, 'wb') as f: pickle.dump(results, f) if not args.autolab: print(" ") - print("To get credit for your results, please upload the single file: ") + print("To get credit for your results, please upload the single unmodified file: ") print(">", token) - print("To campusnet without any modifications.") + # print("To campusnet without any modifications.") # print("Now time for some autolab fun") @@ -336,8 +329,8 @@ 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"""\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, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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\nclass Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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\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, too many numbers!", len(all))\n return all\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\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n# raise Exception("no suite")\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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n # item_title = item_title.split("\\n")[0]\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n # test.countTestCases()\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 = 1\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n self._stdout = sys.stdout\n import io\n sys.stdout = io.StringIO()\n super().setUp()\n # print("Setting up...")\n\n def _callTearDown(self):\n sys.stdout = self._stdout\n super().tearDown()\n # print("asdfsfd")\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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# from unitgrade2.unitgrade2 import MySuite\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' -report1_payload = '80049525010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d65948694473f506a000000000068038c0f746573745f6164645f68696464656e948694680686947d944b004b04736803680c8694680a86944700000000000000008c0474696d6594473f926de000000000758c0d4175746f6d6174696350617373947d94288c0d4175746f6d6174696350617373948c10746573745f68696464656e5f6661696c9486948c066173736572749486947d9468158c13746573745f73747564656e745f706173736564948694681886947d946815681b86948c0474696d659486944700000000000000006812473f9894100000000075752e' +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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.normpath(os.path.join(output_dir, token))\n\n\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' +report1_payload = '80049568000000000000007d94288c055765656b31947d942868018c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04758c0474696d6594473fb71ac800000000758c0d4175746f6d6174696350617373947d946808473fb127100000000073752e' name="Report3" report = source_instantiate(name, report1_source, report1_payload) diff --git a/examples/example_docker/instructor/cs103/unitgrade/AutomaticPass.pkl b/examples/example_docker/instructor/cs103/unitgrade/AutomaticPass.pkl index 2a722e2b9c8264b76eca73fec2c7dd84eb0e02d3..9b6ff7ac689837f86e1b0e393993ec7acbb784e8 100644 GIT binary patch literal 4 LcmZo*@zVnU0@?uq literal 93 zcmZo*nHt0Z0ku;!dUzd6OY(CQOEQxK5{rwc^az)v7MH{qmz1WY=9R=30L4;MrnF7z gVFR&>N`TDTDH)6zOh6%)lFZyxpnyBnIEGR^0Ou<p2><{9 diff --git a/examples/example_docker/instructor/cs103/unitgrade/Week1.pkl b/examples/example_docker/instructor/cs103/unitgrade/Week1.pkl index fe27b785553c86fe6975853b9990eed439d2d5bc..20eb565b4b7903e4aef2d3d44e08726a2b0e14ed 100644 GIT binary patch delta 22 ZcmWHy<85G>YRmuuwNobY=`$7U0RS=m1aSZW delta 47 tcmcBu=WAe@>cap5wNo@E^6E=vFlI2dP3d7N$;?fi(l*5%D$7u+2LK2n3he*@ diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/Report3_handin_5_of_30.token b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/Report3_handin_5_of_30.token deleted file mode 100644 index 675c59014e1063147604cc9ab25520eb3d1bbb5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128198 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 diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete_grade.py b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete_grade.py deleted file mode 100644 index b053e48..0000000 --- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_complete_grade.py +++ /dev/null @@ -1,345 +0,0 @@ - -import numpy as np -from tabulate import tabulate -from datetime import datetime -import pyfiglet -import unittest -# from unitgrade2.unitgrade2 import MySuite - -import inspect -import os -import argparse -import sys -import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue - -parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: -To run all tests in a report: - -> python assignment1_dp.py - -To run only question 2 or question 2.1 - -> python assignment1_dp.py -q 2 -> python assignment1_dp.py -q 2.1 - -Note this scripts does not grade your report. To grade your report, use: - -> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') -parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') -parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') -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: - question = args.q - if "." in question: - question, qitem = [int(v) for v in question.split(".")] - else: - question = int(question) - - if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: - raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") - - if unmute is None: - unmute = args.unmute - if passall is None: - passall = args.passall - - 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, - show_tol_err=show_tol_err) - - - if question is None: - print("Provisional evaluation") - tabulate(table_data) - table = table_data - print(tabulate(table)) - print(" ") - - fr = inspect.getouterframes(inspect.currentframe())[1].filename - gfile = os.path.basename(fr)[:-3] + "_grade.py" - if os.path.exists(gfile): - print("Note your results have not yet been registered. \nTo register your results, please run the file:") - print(">>>", gfile) - print("In the same manner as you ran this file.") - - - return results - - -def upack(q): - # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) - h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] - h = np.asarray(h) - return h[:,0], h[:,1], h[:,2], - -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()) - ls = [] - for C in testCaseClass.mro(): - if issubclass(C, unittest.TestCase): - ls = list(C.__dict__.keys()) + ls - testcase_methods = ls - 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, - big_header=True): - - now = datetime.now() - if big_header: - ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") - b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) - else: - b = "Unitgrade" - print(b + " v" + __version__) - dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) - s = report.title - if hasattr(report, "version") and report.version is not None: - s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") - # print(f"Loaded answers from: ", report.computed_answers_file, "\n") - table_data = [] - nL = 80 - t_start = time.time() - score = {} - loader = SequentialTestLoader() - - for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) - if question is not None and n+1 != question: - continue - suite = loader.loadTestsFromTestCase(q) - qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ - q_title_print = "Question %i: %s"%(n+1, qtitle) - print(q_title_print, end="") - q.possible = 0 - q.obtained = 0 - q_ = {} # Gather score in this class. - # 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. - UTextResult.number = n - - res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) - - possible = res.testsRun - 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 = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 - score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} - q.obtained = obtained - q.possible = possible - - s1 = f"*** Question q{n+1}" - s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) - print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) - - ws, possible, obtained = upack(score) - possible = int( msum(possible) ) - obtained = int( msum(obtained) ) # Cast to python int - report.possible = possible - report.obtained = obtained - now = datetime.now() - dt_string = now.strftime("%H:%M:%S") - - dt = int(time.time()-t_start) - minutes = dt//60 - seconds = dt - minutes*60 - plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") - - table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) - results = {'total': (obtained, possible), 'details': score} - return results, table_data - - - - -from tabulate import tabulate -from datetime import datetime -import inspect -import json -import os -import bz2 -import pickle -import os - -def bzwrite(json_str, token): # to get around obfuscation issues - with getattr(bz2, 'open')(token, "wt") as f: - f.write(json_str) - -def gather_imports(imp): - resources = {} - m = imp - # for m in pack_imports: - # print(f"*** {m.__name__}") - f = m.__file__ - # dn = os.path.dirname(f) - # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) - # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: - top_package = os.path.dirname(m.__file__) - module_import = True - else: - top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] - module_import = False - - # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) - # top_package = os.path.dirname(top_package) - import zipfile - # import strea - # zipfile.ZipFile - import io - # file_like_object = io.BytesIO(my_zip_data) - zip_buffer = io.BytesIO() - with zipfile.ZipFile(zip_buffer, 'w') as zip: - # zip.write() - for root, dirs, files in os.walk(top_package): - for file in files: - if file.endswith(".py"): - fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) - zip.write(fpath, v) - - resources['zipfile'] = zip_buffer.getvalue() - resources['top_package'] = top_package - resources['module_import'] = module_import - return resources, top_package - - if f.endswith("__init__.py"): - for root, dirs, files in os.walk(os.path.dirname(f)): - for file in files: - if file.endswith(".py"): - # print(file) - # print() - v = os.path.relpath(os.path.join(root, file), top_package) - with open(os.path.join(root, file), 'r') as ff: - resources[v] = ff.read() - else: - v = os.path.relpath(f, top_package) - with open(f, 'r') as ff: - 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 = 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... - - sources = {} - - 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() - - payload_out_base = report.__class__.__name__ + "_handin" - - obtain, possible = results['total'] - vstring = "_v"+report.version if report.version is not None else "" - - token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) - token = os.path.join(output_dir, token) - with open(token, 'wb') as f: - pickle.dump(results, f) - - 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()) - pl = pickle.loads(bytes.fromhex(payload)) - report = eval(name)(payload=pl, strict=True) - # report.set_payload(pl) - return report - - - -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, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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\nclass Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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\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, too many numbers!", len(all))\n return all\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\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n# raise Exception("no suite")\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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n # item_title = item_title.split("\\n")[0]\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n # test.countTestCases()\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 = 1\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n self._stdout = sys.stdout\n import io\n sys.stdout = io.StringIO()\n super().setUp()\n # print("Setting up...")\n\n def _callTearDown(self):\n sys.stdout = self._stdout\n super().tearDown()\n # print("asdfsfd")\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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# from unitgrade2.unitgrade2 import MySuite\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n @hide\n def test_add_hidden(self):\n # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test.\n # See the output in the student directory for more information.\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n @hide\n def test_hidden_fail(self):\n self.assertEqual(2,3)\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' -report1_payload = '80049586000000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d659486944700000000000000008c0474696d6594473f60628000000000758c0d4175746f6d6174696350617373947d94680c473f689d000000000073752e' -name="Report3" - -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 diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_grade.py b/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_grade.py deleted file mode 100644 index ecff9f7..0000000 --- a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/report3_grade.py +++ /dev/null @@ -1,347 +0,0 @@ -""" -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 -import pyfiglet -import unittest -# from unitgrade2.unitgrade2 import MySuite - -import inspect -import os -import argparse -import sys -import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue - -parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: -To run all tests in a report: - -> python assignment1_dp.py - -To run only question 2 or question 2.1 - -> python assignment1_dp.py -q 2 -> python assignment1_dp.py -q 2.1 - -Note this scripts does not grade your report. To grade your report, use: - -> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') -parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') -parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') -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: - question = args.q - if "." in question: - question, qitem = [int(v) for v in question.split(".")] - else: - question = int(question) - - if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: - raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") - - if unmute is None: - unmute = args.unmute - if passall is None: - passall = args.passall - - 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, - show_tol_err=show_tol_err) - - - if question is None: - print("Provisional evaluation") - tabulate(table_data) - table = table_data - print(tabulate(table)) - print(" ") - - fr = inspect.getouterframes(inspect.currentframe())[1].filename - gfile = os.path.basename(fr)[:-3] + "_grade.py" - if os.path.exists(gfile): - print("Note your results have not yet been registered. \nTo register your results, please run the file:") - print(">>>", gfile) - print("In the same manner as you ran this file.") - - - return results - - -def upack(q): - # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) - h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] - h = np.asarray(h) - return h[:,0], h[:,1], h[:,2], - -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()) - ls = [] - for C in testCaseClass.mro(): - if issubclass(C, unittest.TestCase): - ls = list(C.__dict__.keys()) + ls - testcase_methods = ls - 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, - big_header=True): - - now = datetime.now() - if big_header: - ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") - b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) - else: - b = "Unitgrade" - print(b + " v" + __version__) - dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) - s = report.title - if hasattr(report, "version") and report.version is not None: - s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") - # print(f"Loaded answers from: ", report.computed_answers_file, "\n") - table_data = [] - nL = 80 - t_start = time.time() - score = {} - loader = SequentialTestLoader() - - for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) - if question is not None and n+1 != question: - continue - suite = loader.loadTestsFromTestCase(q) - qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ - q_title_print = "Question %i: %s"%(n+1, qtitle) - print(q_title_print, end="") - q.possible = 0 - q.obtained = 0 - q_ = {} # Gather score in this class. - # 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. - UTextResult.number = n - - res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) - - possible = res.testsRun - 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 = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 - score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} - q.obtained = obtained - q.possible = possible - - s1 = f"*** Question q{n+1}" - s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) - print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) - - ws, possible, obtained = upack(score) - possible = int( msum(possible) ) - obtained = int( msum(obtained) ) # Cast to python int - report.possible = possible - report.obtained = obtained - now = datetime.now() - dt_string = now.strftime("%H:%M:%S") - - dt = int(time.time()-t_start) - minutes = dt//60 - seconds = dt - minutes*60 - plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") - - table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) - results = {'total': (obtained, possible), 'details': score} - return results, table_data - - - - -from tabulate import tabulate -from datetime import datetime -import inspect -import json -import os -import bz2 -import pickle -import os - -def bzwrite(json_str, token): # to get around obfuscation issues - with getattr(bz2, 'open')(token, "wt") as f: - f.write(json_str) - -def gather_imports(imp): - resources = {} - m = imp - # for m in pack_imports: - # print(f"*** {m.__name__}") - f = m.__file__ - # dn = os.path.dirname(f) - # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) - # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: - top_package = os.path.dirname(m.__file__) - module_import = True - else: - top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] - module_import = False - - # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) - # top_package = os.path.dirname(top_package) - import zipfile - # import strea - # zipfile.ZipFile - import io - # file_like_object = io.BytesIO(my_zip_data) - zip_buffer = io.BytesIO() - with zipfile.ZipFile(zip_buffer, 'w') as zip: - # zip.write() - for root, dirs, files in os.walk(top_package): - for file in files: - if file.endswith(".py"): - fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) - zip.write(fpath, v) - - resources['zipfile'] = zip_buffer.getvalue() - resources['top_package'] = top_package - resources['module_import'] = module_import - return resources, top_package - - if f.endswith("__init__.py"): - for root, dirs, files in os.walk(os.path.dirname(f)): - for file in files: - if file.endswith(".py"): - # print(file) - # print() - v = os.path.relpath(os.path.join(root, file), top_package) - with open(os.path.join(root, file), 'r') as ff: - resources[v] = ff.read() - else: - v = os.path.relpath(f, top_package) - with open(f, 'r') as ff: - 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 = 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... - - sources = {} - - 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() - - payload_out_base = report.__class__.__name__ + "_handin" - - obtain, possible = results['total'] - vstring = "_v"+report.version if report.version is not None else "" - - token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) - token = os.path.join(output_dir, token) - with open(token, 'wb') as f: - pickle.dump(results, f) - - 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()) - pl = pickle.loads(bytes.fromhex(payload)) - report = eval(name)(payload=pl, strict=True) - # report.set_payload(pl) - return report - - - -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, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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\nclass Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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\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, too many numbers!", len(all))\n return all\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\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n# raise Exception("no suite")\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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n # item_title = item_title.split("\\n")[0]\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n # test.countTestCases()\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 = 1\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n self._stdout = sys.stdout\n import io\n sys.stdout = io.StringIO()\n super().setUp()\n # print("Setting up...")\n\n def _callTearDown(self):\n sys.stdout = self._stdout\n super().tearDown()\n # print("asdfsfd")\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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# from unitgrade2.unitgrade2 import MySuite\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' -report1_payload = '80049525010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d65948694473f506a000000000068038c0f746573745f6164645f68696464656e948694680686947d944b004b04736803680c8694680a86944700000000000000008c0474696d6594473f926de000000000758c0d4175746f6d6174696350617373947d94288c0d4175746f6d6174696350617373948c10746573745f68696464656e5f6661696c9486948c066173736572749486947d9468158c13746573745f73747564656e745f706173736564948694681886947d946815681b86948c0474696d659486944700000000000000006812473f9894100000000075752e' -name="Report3" - -report = source_instantiate(name, report1_source, report1_payload) -output_dir = os.path.dirname(__file__) -gather_upload_to_campusnet(report, output_dir) diff --git a/examples/example_docker/run_all_docker.py b/examples/example_docker/run_all_docker.py new file mode 100644 index 0000000..15e9ea7 --- /dev/null +++ b/examples/example_docker/run_all_docker.py @@ -0,0 +1,57 @@ +from unitgrade_private2.docker_helpers import docker_run_token_file +import os +import glob +import pickle +import time + +""" Run all examples on docker. """ + +if __name__ == "__main__": + # Step 0: Compile our two docker images. + from unitgrade_private2.docker_helpers import compile_docker_image + + docker_files = [f"{os.getcwd()}/../../docker_images/unitgrade-docker/Dockerfile"] + docker_tags = [] + for f in docker_files: + tag = compile_docker_image(f) + docker_tags.append( (f, tag) ) + + EX_BASE = f"{os.getcwd()}/../" # Base of examples. + + runs = [ + ("example_flat", "programs", "programs/report1flat_grade.py", "programs", "programs/report1flat_grade.py",), + ("example_simplest", "", "cs101/report1_grade.py", "", "cs101/report1_grade.py", ), + ("example_framework", "", "cs102/report2_grade.py", "", "cs102/report2_grade.py", ), + ("example_docker", "", "cs103/report3_complete_grade.py", "", "cs103/report3_grade.py",), + ] + rs = [] + + def p2mod(file, base): + return ".".join(os.path.normpath(os.path.relpath(file, base)).split(os.sep))[:-3] + start = time.time() + for ex, ibase, ig, sbase, sg in runs: + ibase = f"{EX_BASE}/{ex}/instructor/{ibase}" + ig = f"{EX_BASE}/{ex}/instructor/{ig}" + sbase = f"{EX_BASE}/{ex}/students/{sbase}" + sg = f"{EX_BASE}/{ex}/students/{sg}" + + # Uncomment to run example deployment scripts: + # os.system(f"cd {ibase} && python -m {p2mod(os.path.dirname(ig) + '/deploy.py', ibase)}") + + os.system(f"cd {sbase} && python -m { p2mod(sg, sbase) }") + stoken = glob.glob(f"{os.path.dirname(sg)}/*.token")[0] + + Dockerfile, tag = docker_tags[0] # Get first docker file. + token = docker_run_token_file(Dockerfile_location=Dockerfile, + host_tmp_dir=os.path.dirname(Dockerfile) + "/tmp", + student_token_file=stoken, + instructor_grade_script=ig) + with open(token, 'rb') as f: + iresults = pickle.load(f) + with open(stoken, 'rb') as f: + sresults = pickle.load(f) + rs.append( (ex, sresults, iresults) ) + + for ex, sresults, iresults in rs: + print( f"[{ex}]", "Student's score was:", sresults['total'], "score using my eval script", iresults['total']) + print("Total elapsed time", time.time()-start, "seconds") \ No newline at end of file diff --git a/examples/example_docker/students/cs103/.coverage b/examples/example_docker/students/cs103/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..f386b2198168450113494de5b3b3bd99d653d108 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/examples/example_docker/students/cs103/Report3_handin_10_of_30.token b/examples/example_docker/students/cs103/Report3_handin_10_of_30.token index 7231343c1882f4366cfe40348680c8d947889798..3880b47087c30a84d5471426ba613e6421ea54fe 100644 GIT binary patch delta 8228 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 F@PBGw8_56w delta 15750 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 Wdq&=#ID4isZFhEDc^2v9`2PXKe~fnk diff --git a/examples/example_docker/students/cs103/__pycache__/homework1.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/homework1.cpython-38.pyc index 5c552a81fc42feaf0654da24d93270b73dc806af..d82e790ad8cee1871f1919af140451af558d6b85 100644 GIT binary patch delta 20 acmaFB{(zl3l$V!_0SFA{8f@gg$_xNBQUv4x delta 20 acmaFB{(zl3l$V!_0SJ`;>1^b_$_xNDDFrkD diff --git a/examples/example_docker/students/cs103/__pycache__/homework1.cpython-39.pyc b/examples/example_docker/students/cs103/__pycache__/homework1.cpython-39.pyc index 6dfacc25f48700797f5286eae416edaeaf0e2b5b..0e7a0c627f56abdd78d2ba3ec05f9d13ea233030 100644 GIT binary patch delta 20 acmX@ec94xbk(ZZ?0SF!^Yj5N>V+H^+dIW_4 delta 20 acmX@ec94xbk(ZZ?0SKbqG&gdaF#`ZD3IpB% diff --git a/examples/example_docker/students/cs103/__pycache__/report3.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/report3.cpython-38.pyc index da19a02cc1c508d3eaafcf35b0b34e9d58501d7e..78157d3ccd77777a3e6bdc5d8ef57fa286b7b497 100644 GIT binary patch delta 25 fcmaFC^@58pl$V!_0SNTx8YFr#Z{&+)Wn=~bPCo@V delta 25 fcmaFC^@58pl$V!_0SJ`-=_DR!+Q=8l%E$-+RfPrt diff --git a/examples/example_docker/students/cs103/__pycache__/report3_complete.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/report3_complete.cpython-38.pyc index 921af5c7208e7aa4d953fa1e4551029aeb05c182..41e62d06e5af5bb771d3c92d6afde5e8a0d36380 100644 GIT binary patch delta 24 ecmcb@dxe)bl$V!_0SFES=_c;j$a{>9kr@C^>jp6Z delta 24 ecmcb@dxe)bl$V!_0SI{i=_HzN<UPj5$Or&RJO!cv diff --git a/examples/example_docker/students/cs103/__pycache__/report3_complete.cpython-39.pyc b/examples/example_docker/students/cs103/__pycache__/report3_complete.cpython-39.pyc index 2f18f8be0976727ab41dd1114924b8483c6d3aac..3e87b71662ab4e431da5c9b952594f5411205f72 100644 GIT binary patch literal 1764 zcma)6&u=3&6t+D-GHKH`%eE{6?P?GRDbgy_Y_A9{Du`Qz*hNTlv4*kJ44KJf?U^n_ z%Z2vO;J_a9FZs$T{{jcfd!A$`MJ-~adGfQL-~0T%&rS{w_6U5x{rxmKb_w|to!t-S z$aDDh9E>1>=A@u8Em+J7Cw3^#vD_`Z*rVhdA{^mf6X8mF<-|Vtp76o<t>1yzP9DSo z%m<<i^Igdxt}FM%-i)cAq$j&q<cdPp&I>ZwUqekYa7_36mr_^fiI&FyPA*DS8F!iq zIiTi|yiD>ssU%l+f$ORka$cplb@y|?c@Dq+6pSP>6(nYY#*SdZxhAnIJ>d%P8p_AM z@S%Lict1!v8!f}|<y3}~OzA3oRRfZ&oQIQAg|A^b91X*iLeA~SVBo=j4!=GHBPcKg zto;h-dPDwTZz!<jT(P&U4}}|F5Gd@A6((F!<0L|?F=6)m&CSgk)2^KODAl9Wv*ENX z<m*z+Mk;`L(@V6Ls`~m>o#f{$W?U`viCtO|bp#QAT}HSis_LnXzADqY0G@PI&$H@6 zB|`Fr$}WM)NdBA@i(Kj`sjD(ie&m~pXgJ)iaZ#o-siJJID^;gesUiz1YPxX7(-Kou za=7@#1Ql?`;V3?!&}azl(LP-sT7HMy<@;$2VatEfXlZjB+EfRERTs@ZngcX={>JoS zWRA8aImig)f`J;PPTU?{p4^7Di5Y_+0?y}2AvrgDoEN32a~$_LN76Sd9ggI1t{y-- zg-O+WXj%w%gg6v(fe{)z7*Ll69;0^le*K0J+Ui#@(5@rh=Hh{NJ>j8*%)yt?ydtTx z^ji?dEoxC*Sk)>Naeyk3Uiy~deTMC9YzYJf?(e8-Mb#lh@0`Sv`OuQNxp#bl8^lA1 z(<od*mml9k(scWF^#4M~-l20EbZflQ>H{<%qG<`Xu=*BO(_cPGviyArYA~_olRGf& zgpLQk!Xv0-G$&|2LSwu67)NcfwwsS&0b6Y)9?-ynUgF&N{$_p#%WX4lBiV$(Q{+t8 z1&e*zxu9_%T<D2sI?da6wmdq25spqrVdIBqYlOMRhkQKf{tLMAs;tVT6_rI}y4yEQ z8_!-^(_19zjAsSDtoqKQJ%QZFz~*Q!++Kxqcq57=n{z(cHz)6&l4rwqs89yV_6q4U zkK3g-ice*}kV-#O_{Ozh)hFm;rRG?7&~Adyo16Fr>RO{hk7<wf5Bott`(8l*0eOCd AF#rGn delta 416 zcmY*VJx{|h5X~iZ;#6%KphZF|Eo%frtF8#a0$Ue48Hp&0tt#p#PC9i!sQiJ-JHLPh z{s#Xb0~0d~8x!YNlyZ`v{O+Egp6{dl?s`$VTr{w5E`z9NExkJ2E{{7cZg4Vmm~&-t zn>&vNcZ9jH7!@w315+|V1YH;l0MuO@f&21w0>IQ89NHebeaGq(TB>cn`X!`N_tsER zUuM$uH7{t`*-W-IbE{GB#yI*{CbDxp>5+OMHNOvx2piGi7G@}@XVSdy22}2%M($~- zBIG1XvZ0VR-kEzh6KR%A#%WICwB72k(lm+(VKSOdWR`9PUB#(rlYIp3mTCtI1t`L* zm(wsDMaeh}gCeWV#!1$fs%JNF52+no9Q?jr=lT2I#yO{_qY0l4#TjUOy{RgcOy8s6 G6AC}%7f#9m diff --git a/examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/report3_complete_grade.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/report3_complete_grade.cpython-39.pyc similarity index 90% rename from examples/example_docker/instructor/unitgrade-docker/tmp/cs103/__pycache__/report3_complete_grade.cpython-38.pyc rename to examples/example_docker/students/cs103/__pycache__/report3_complete_grade.cpython-39.pyc index 8bcaebe92988bf2a57647836e6aabc65f30fdadc..5efad03805d904ffdc7a11abb12cf5d179ea47c5 100644 GIT binary patch delta 2103 zcmah~Uu;uV7{BMX>zc0Zx^7*!c3r!5Y<8A)8!#Z7h>AlNFh-XE6}Vn{@7lYyx8>Y> z%Z#_qtzZB(0!Mivku7Kl@ddJ&ATh>7qt8B>R7qIki#`yel3+}X-*?B@<i*~c-~G<_ z=k%QK`<?IJ{n~c_8=F`3cw8L(7XC?!{y%SeKX8N$j!u~M)4aqPWL2sViI}9!u`e8H zADGX9*<G=jc5vZXZ~ZV2uXOzn6G|#qEXXO{$jPZa#f(vqRb5LNs-kD9D9J*JDrHfZ zQ}V1>DCK2sO%SAFW=5tdMb&g_Wb`6sBhEqlAF#1y)wzv#w1Gqe?7Hh*e-nz$2&+nv z(#=X%EmB!1D4M3ISz$`a%e0*xZRjL5W;VP*hS`ILdv@yMICjt7O~S0n(@mmmt0zfD zndp(jc37qnhSmz8b5(8{PONYy|1n{AJbh$<)p_G&7fX0w0jzi<;b*{+dJ)C|4#UuR z04Jeiu;OlZ-+Pff#m+RoMxJKBH16<O{+R34pnYt}H@@pRobN$+9$_ECID+M9!TSH~ z1;bGoYyY#CedfEw8-yKbI)blHn{N9ixCLTHrNE5+JfS!A7&H8*Nf-OuACGjKfhsv- ztHdkON=%RGaWh&aRZ@wf%to90NQ`Bg4+pxWgw&|_nA~(rl>|AdBgpAJ?EB_htNCLs zmmq)rfg!S;9SI~Fw%~TRB5Y&l1KlLVmIJx25Guj|HKNf&@J^7L4T_uK=?I!cHr5*B zsgE6N&5=#)yVjB6C(ye=5Xv&ul%gsKH75j2=}JMS$1tV~;UIe1E#+R83J&?wsI{{9 za1*6qjQH7wV3JI-FN3i;+JGSru~d>(iH^b~Z7*m>fgT4jk)WG#fpt~|aN2@^RG>iw zWLnLu<%%bToSZKSQ+Y94b7;UaRi}RRXh#p%aU}~5z;sHdK#_KfT80(c+8iSK46=9H z;+vx=V$#xenxW`&+Hpcxbl|IsI{t)Evr$=F%jl17$v_Gt)(K&8u8(!J&%fFSmJG6P z?PmkQ2?Keh?E=s_9WHN`H;Keo`S(K=-sBf~lUpK$JIG0#9iFO<aH?;J(*_4QVm*gB z!hUX-$pV`QMaZu#7n&tHTM6aDk45~pG2*3gZ{$rNn5)uIahvWv+|hi+V|o@FrTQfT zbWAc9esX6=#b^2!152Dq%*L_*Zo)srohRD68y1E6i6o3pb)2n)g9HB`{nLhM=Ie-% zEo`7;V)wI<3~F7sZj|BxwR%aRs#uU~E<sQVr6Sb@fyU6{6oitf=YWHCmOxLjTOIwK zIV*|+1;9w>EYGa0m&LpxQ}bdZ8kh$iJ&o`-!YPFJ*w)C|*Z}V)PJDQdxZv%!wUJgb z!h4CEBniE|d?#|8C%-T6jtvp)r&?D(Ug|4dJGXaKn)LR?kZCuYmiWi&wwkir8xGBx zw&^-Z<309Q*Fcow4%V5h;nZTRj#7w?cRz5+Af|btOb@Xa;%9wjv_ud(5&GClynPOD zCq=5!Nr1FpQ6;6UNQRhym^zJ~L4&&_W@a9#3K(!1;ZfG<1)L_(w;pn5wdzO3sX0KE zIVoA!oMO7E2~cw!C@Gnlyj-(OMxm7OTbH)xDjIsp&;=+XokiaP1nasCqO^$cAwZ4H zr0pt{c(pViv2S}iTd-ZE>&wt%;FCe$Lf?7zpl9Bl<gGeEmFP8gdebR#mAQH&(JQ!+ zm&(YKAweq|G$YgNxCmd$)6HzWcL%w-JlFfx=UaB-Vpg*7Leeb4egr(Wm5Q`eq}h^4 zH5uCC>l)27dUtdVlMYQok_50?Ur(z^(P0d;I+u4+p4BI1O@G-?RcJH#?Mb^(vA<~b z9sEd8><el8v{qE<n>fFUuz+CI;u=af5pDpa8`sXnk}J*-9&@!P;vJ!95wNoK6ZYcD G#eV@W+XAov delta 1936 zcmZ{lUu;uV7{Kqj?b_0HZMU^+*R6lYIyVMv;DGT*K^Zf|m^cC9W~969Ubg;O+nswm z1a2Lys2N6B{0xRfaSIX>jZU+Ki1A;dFGgeHlcs1QFFqK<g9M{5#_zjh%olHSe&;*q zJLfz1et%ByeP#LOON;wpU7b_Fr|+L`Idktr_iMIL*1`81_Kc_q*)_{jnJLU*SsQ#} zlf0-tk7`%hVpwsB1CsVlB6gDfhnlq;<zf*|J9-?N6^+^iacR-fC)xry;6C`xdEW0O zv4OBWiIUU^@?xXNgK(ub!X)^#_B88(e%GA`R$yISf`#BIWCSMbx>-M5sZ#?M-0Z=o zkv(0g2pN1$LxhOCgRO^s?l!g)GVW)PpSi;=kK!WkCTvF>!KR4_2jkn(+HTn5d6n&e zuRSAd7j${IdCm3YoaIe}z`gq(*+b*U2#*s63400VLKCa!44~pDwv}@R;7{)*F}nsX z)DKhld;P~=JvPIPsN&CB=NSJ$kHd|I7gz#zHnxS^41a|UTgq+aXgQ|G^|eN{!YZsB zB^ivy4i<;E8;|%CN~hw{+YKS(tFVBev<3vd9U6QeIW-%ej!_W2@SX1xdQkQEvOaj- z-&xyBFTIJd8NTr+STo%5kH?#7B7~?ijUU3klT|I6&O1f^FsV{-qA6y-i=uEU4&VD) z;AYbkED2&@OHBt(O0X*sXd0%aF~UJa)vBi_R6YQiK(F^O&6=*efA+h97^{bG1Kn&0 z{tm<rRNZ?tP33yJkRRZ@z<Dj1uwh=Qd4>1mK(bC~r3pTQVrM7s#*VKhm5+vv1V5pP z5P+k>)<8XpK}6MgEIpPQS7k-zT!pz{%$6eEMz|4dTOT2joFmm}rL?X}wn;s$=Tx4f zIdL*2S-7gLc+S?`?cYE<R>N;*SP}-C&kiP0GfFwO@=(IA!A1_yx)8dcV{KJLgDGM~ zyyz{9hBz-8!UAK$vqGQnv@j$bhPIH(&cJLa%-(_Pq32izVlCtL746lQWOy9Y#96K_ zyA0P};pm}qol!UMQS1v0lPU<mwyYa!D0>a>ynjK!3i0e*H7?At$sjgozDKuYtn>8- z*7br|_UJytqPXy-{%<E-Yz?zsc&~MT*KYh2Zk9%zqy(aBSJFH$PpDOADwUon7Py{D z@hHT@el`PJ!(EXva}`%}MOh!`Wg0ogRDDX$l~jHTro&PHG+C<<&Jkt^FT+xJHWm_H z%t1d<U{35^mLO|lG11Lj*z?Ov!;xbm`)TR(STCd7>5q69Ra_*|A8#nv>&^2a!)nwo zi2rf3U|2FWb4>dYE+jTYC+V!!9O5)IQ%EDJ8Ez&1aw;fFVrq&Xf}h)Fr%R-1C4>nS zeaV?G6nS9`Q$~~Qa!D`b<YSyFg;QSeAzIUr&MWDuv{I6D_dTWY7fInN%A?r_Q(4+@ znef2L{2~n>qP6z1X;nX-#zfssa}<oK4c$3<LNTk5le_sTbGn!w&F0jqRVhsrI~&Xr zsyYiLy;#yy7&U%^maZq5xxJC3D+EAPSyr;<F`~<Xc>|{;Z&5y(1Et;XqpFtdQ+Q>h z^JDyZ+B6NXw4Ze~iDtMkSo|7<lIPe0yp;?`-z1A}u2O#VrnEwdkE;AS84-&)-VL{s z+t}iguj7lGo41lN(`!^l{5WAhfxh4LiR6&^SW)JhikH?&jc34qdq;#KBDrKmNujnh zq2_fyLOXg0{V;OdH%)&ClJkJ7=}#8SYk_X=<p5eUS|QI38ZQve5X>lCBWaQFK0@-W hd<6Ldt#JCZ<?D&+fxkqcu=1;f)eFNZz}?$3{{lKB%*Fr! diff --git a/examples/example_docker/students/cs103/__pycache__/report3_grade.cpython-38.pyc b/examples/example_docker/students/cs103/__pycache__/report3_grade.cpython-38.pyc index 7433fca776754f5b6910141e9adc509cbf13a6f5..fe08941631eb3353b6394ba8fca90bb65c4db357 100644 GIT binary patch delta 10563 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 M&*C4xcCC>9A3>*p$^ZZW delta 17866 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 Vo9Hu8&uncSed}L|uTJZw{|o*POrZb( diff --git a/examples/example_docker/students/cs103/report3.py b/examples/example_docker/students/cs103/report3.py index c97b5a4..f83bb53 100644 --- a/examples/example_docker/students/cs103/report3.py +++ b/examples/example_docker/students/cs103/report3.py @@ -1,8 +1,8 @@ """ Example student code. This file is automatically generated from the files in the instructor-directory """ -from unitgrade2.unitgrade2 import UTestCase, Report, hide -from unitgrade2.unitgrade_helpers2 import evaluate_report_student +from src.unitgrade2.unitgrade2 import UTestCase, Report +from src.unitgrade2 import evaluate_report_student class Week1(UTestCase): """ The first question for week 1. """ @@ -24,4 +24,6 @@ class Report3(Report): pack_imports = [cs103] if __name__ == "__main__": + # from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet + # gather_upload_to_campusnet(Report3()) evaluate_report_student(Report3()) diff --git a/examples/example_docker/students/cs103/report3_grade.py b/examples/example_docker/students/cs103/report3_grade.py index ecff9f7..3c64c04 100644 --- a/examples/example_docker/students/cs103/report3_grade.py +++ b/examples/example_docker/students/cs103/report3_grade.py @@ -6,15 +6,10 @@ from tabulate import tabulate from datetime import datetime import pyfiglet import unittest -# from unitgrade2.unitgrade2 import MySuite - import inspect import os import argparse -import sys import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: @@ -115,24 +110,20 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) else: b = "Unitgrade" - print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) s = report.title if hasattr(report, "version") and report.version is not None: s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + print(s, "(use --help for options)" if show_help_flag else "") # print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] - nL = 80 t_start = time.time() score = {} loader = SequentialTestLoader() for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) if question is not None and n+1 != question: continue suite = loader.loadTestsFromTestCase(q) @@ -142,11 +133,10 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa q.possible = 0 q.obtained = 0 q_ = {} # Gather score in this class. - # 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. UTextResult.number = n + UTextResult.nL = report.nL res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) @@ -155,20 +145,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun - # possible = int(ws @ possible) - # obtained = int(ws @ obtained) - # obtained = int(myround(int((w * obtained) / possible ))) if possible > 0 else 0 - obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} q.obtained = obtained q.possible = possible - s1 = f"*** Question q{n+1}" + s1 = f" * q{n+1}) Total" s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) ws, possible, obtained = upack(score) possible = int( msum(possible) ) @@ -183,15 +169,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa seconds = dt - minutes*60 plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) results = {'total': (obtained, possible), 'details': score} return results, table_data - - from tabulate import tabulate from datetime import datetime import inspect @@ -214,7 +201,8 @@ def gather_imports(imp): # dn = os.path.dirname(f) # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: top_package = os.path.dirname(m.__file__) module_import = True else: @@ -235,7 +223,7 @@ def gather_imports(imp): for file in files: if file.endswith(".py"): fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) zip.write(fpath, v) resources['zipfile'] = zip_buffer.getvalue() @@ -279,14 +267,14 @@ def gather_upload_to_campusnet(report, output_dir=None): 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)) + # print(" ") + # print("="*n) + # print("Final evaluation") + # print(tabulate(table_data)) # also load the source code of missing files... sources = {} - + print("") if not args.autolab: if len(report.individual_imports) > 0: print("By uploading the .token file, you verify the files:") @@ -299,12 +287,15 @@ def gather_upload_to_campusnet(report, output_dir=None): 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) + _, report_relative_location, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import nimp['name'] = m.__name__ sources[k] = nimp # if len([k for k in nimp if k not in sources]) > 0: - print(f"*** {m.__name__}") + print(f" * {m.__name__}") # sources = {**sources, **nimp} results['sources'] = sources @@ -317,15 +308,17 @@ def gather_upload_to_campusnet(report, output_dir=None): vstring = "_v"+report.version if report.version is not None else "" token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) - token = os.path.join(output_dir, token) + token = os.path.normpath(os.path.join(output_dir, token)) + + with open(token, 'wb') as f: pickle.dump(results, f) if not args.autolab: print(" ") - print("To get credit for your results, please upload the single file: ") + print("To get credit for your results, please upload the single unmodified file: ") print(">", token) - print("To campusnet without any modifications.") + # print("To campusnet without any modifications.") # print("Now time for some autolab fun") @@ -338,8 +331,8 @@ 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"""\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, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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\nclass Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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\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, too many numbers!", len(all))\n return all\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\n# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n# raise Exception("no suite")\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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n # item_title = item_title.split("\\n")[0]\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n # test.countTestCases()\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 = 1\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n self._stdout = sys.stdout\n import io\n sys.stdout = io.StringIO()\n super().setUp()\n # print("Setting up...")\n\n def _callTearDown(self):\n sys.stdout = self._stdout\n super().tearDown()\n # print("asdfsfd")\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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# from unitgrade2.unitgrade2 import MySuite\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' -report1_payload = '80049525010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04756803680486948c0474696d65948694473f506a000000000068038c0f746573745f6164645f68696464656e948694680686947d944b004b04736803680c8694680a86944700000000000000008c0474696d6594473f926de000000000758c0d4175746f6d6174696350617373947d94288c0d4175746f6d6174696350617373948c10746573745f68696464656e5f6661696c9486948c066173736572749486947d9468158c13746573745f73747564656e745f706173736564948694681886947d946815681b86948c0474696d659486944700000000000000006812473f9894100000000075752e' +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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.normpath(os.path.join(output_dir, token))\n\n\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n from cs103.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n\nclass AutomaticPass(UTestCase):\n def test_student_passed(self):\n self.assertEqual(2,2)\n\n\nimport cs103\nclass Report3(Report):\n title = "CS 101 Report 3"\n questions = [(Week1, 20), (AutomaticPass, 10)] # Include a single question for 10 credits.\n pack_imports = [cs103]' +report1_payload = '80049568000000000000007d94288c055765656b31947d942868018c08746573745f6164649486948c066173736572749486947d94284b014aa1ffffff4b004b04758c0474696d6594473fb71ac800000000758c0d4175746f6d6174696350617373947d946808473fb127100000000073752e' name="Report3" report = source_instantiate(name, report1_source, report1_payload) diff --git a/examples/example_docker/students/cs103/unitgrade/AutomaticPass.pkl b/examples/example_docker/students/cs103/unitgrade/AutomaticPass.pkl index 2a722e2b9c8264b76eca73fec2c7dd84eb0e02d3..9b6ff7ac689837f86e1b0e393993ec7acbb784e8 100644 GIT binary patch literal 4 LcmZo*@zVnU0@?uq literal 93 zcmZo*nHt0Z0ku;!dUzd6OY(CQOEQxK5{rwc^az)v7MH{qmz1WY=9R=30L4;MrnF7z gVFR&>N`TDTDH)6zOh6%)lFZyxpnyBnIEGR^0Ou<p2><{9 diff --git a/examples/example_docker/students/cs103/unitgrade/Week1.pkl b/examples/example_docker/students/cs103/unitgrade/Week1.pkl index fe27b785553c86fe6975853b9990eed439d2d5bc..20eb565b4b7903e4aef2d3d44e08726a2b0e14ed 100644 GIT binary patch delta 22 ZcmWHy<85G>YRmuuwNobY=`$7U0RS=m1aSZW delta 47 tcmcBu=WAe@>cap5wNo@E^6E=vFlI2dP3d7N$;?fi(l*5%D$7u+2LK2n3he*@ diff --git a/examples/example_flat/instructor/cs101flat/Report1Flat_handin_10_of_10.token b/examples/example_flat/instructor/cs101flat/Report1Flat_handin_10_of_10.token new file mode 100644 index 0000000000000000000000000000000000000000..6207232bba6714f3ffac3686c93f17dac4be1cb1 GIT binary patch literal 65761 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 P{Nmq*F?xy1Y4iUBSTFT6 literal 0 HcmV?d00001 diff --git a/examples/example_flat/instructor/cs101flat/__pycache__/deploy.cpython-38.pyc b/examples/example_flat/instructor/cs101flat/__pycache__/deploy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c91ca3192aaec26f60abe00c1109cb32983e52a GIT binary patch literal 581 zcmZWn%Wl*#6tySGBs0?iHVD1|DM+LYON1&UsxG^LRzg;UW$Zg0Q^$$?K%H53&xi0I zd_}gb_yty6r=mi_FP(F*<#W%KFW2iO!SUn#g}9v&@-vZtM~UPuZvPPvMHDrpqm0so zXJ*!AESqp<@~&V7B`{a{^~_yB22>T(oPWzPUVJH3sVcQlOSMu*SD9L;xO(t`)DM5* z>!haR>OBm-3!5_|0@5cQLLB&&6AJiN8{lrrW2*924_s+CR-AW2Th7nwjLkc3^>)t| z{pW4;QUqWn+<=S$Msj%CG{?Anhze}*$I@?JZnmg(F|Ift_EN|laDFJCH=(}<dsrwK zOuui2eVwzV@1v8DL|LW4hY3BF2888_Yb%285#t3^l?G+b>yoYRs-Bb_cA2ne(duw# zYjFB2miemLX@ymB=>X~E*1IfSE|b*(oXe3VyH<2q<#*y17?_UVpWeN0FFiQlhPZ?F zw3o3<7Tf=XTDTG2V8H(cJT;)z)(015=v{l?mG<8KSiR|b6%CwFl=SJ(98sLJiawhE E0rXq1T>t<8 literal 0 HcmV?d00001 diff --git a/examples/example_flat/instructor/cs101flat/__pycache__/homework1.cpython-38.pyc b/examples/example_flat/instructor/cs101flat/__pycache__/homework1.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ad952b7db7aa4adbd8a113e6d74f318bd77ce47 GIT binary patch literal 835 zcmYjPy>8nu5GLh6)pfR_S#OJl7($(FK@$W`f3^%QGBi;j(;{sPktl<toZ9f_zD3cc zTVF|QC%;0c9%-xa0Y}~)cy}MaySt;KE<p+XkllKO{ISlVbkMm*)gSTTgwyZjo^X#h z@TS7!%_mR26Aj)Hl(%_%<NY8%y$z)VV;w;-=JOZ4J8f?0#-oJ4{6d0WLa|sa)^4EQ zpz0nTh8Vgg_@3fhy86`xluzkGNMMkq+Q3P^wD$=pVM--+9Zw3WF`mjP6caEr0h;9k zL?&_}jfPZW#zH79pio?>5I%&{(AD3^V_UcXFdUo>&L0ENv!cxSz70H_V|e%oeO7l2 zcFBQPHEm+$@5}Ij-nVxke-mYY_m|yVM0Xl{N2Z*K=zWot4pdai)JzrQBA%;s!HkH+ zH<r&cp=%?aWXwdV)JBzwDO8l`;hUig&Wc<-7wTyk&X-m1@Dr9+jT0rLrCN9<&bB^^ zwXHhNeKgi&O^=D=d%Wk`JQpC#e2kD`#74j*htYV1w5A#+rA$m($eJOYBI0FD6f&PG z&}Dw%xV-|#d7ll&0bBw*9fM;8Nu#WyW9RISCL*aC_}D_)fo~{lSAHDlER}J*I{sRe z3LEJ%Gj_^naEf{fTl=5Y>(6e!b^{pboMoBYn5`Yb*3z)G3w;|j9Vh>5OI(vUu39(& dX$}C{H04nFZ}WnenYePiT1<Y&Z~5LU`VUWM*y8{I literal 0 HcmV?d00001 diff --git a/examples/example_flat/instructor/cs101flat/__pycache__/report1flat.cpython-38.pyc b/examples/example_flat/instructor/cs101flat/__pycache__/report1flat.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b843499aeb6a5314ec477e4a8ff9439e6927c59 GIT binary patch literal 1204 zcmZ`&OK%e~5VpOKIFCLQC<qCp9La&I=@B7>6ri^pC>4?w$y&v3y2P8LwzojVE&UlB z=&^rkubgt_$feBewoN38rJ4OaW6yZLnLXOrXb@P<gBQtfM#wMRtgjF@Z$R7}lp=~6 zl2Jh^VrEzth@ixQ3A0E<SuEmmADWt|9S{|%_=2cd*FN(_AnI@{G@%H=Q&Su~TvM<& zbW_zQ%o-i&#)2#`et1Z_jU{9yU0TE+^n7MLpvU?wF?r&(w53TppDR7}T~@TLKGW7| zX;SBlAW>>C!u#tBYOLyc3P2M<6%kBP#V&{lRG>omhAM*GaS?sedeR%wwfl%8kUofe z4(dDkNj!xBbiow6Wc1bop}-QMMG*`)SYqxa5}N+FzP?^!ob(b9JiVDXr>%c?nkUAh z<BArc)8_b!*bUhJ8TDTdkKl+K`us!>-_AyP235OZK281DCQ8e>P0xUrq5hg=bEDlY zkjEzR!*uGr%}0J_ha=b9>y^fe!V>S!zZKkT=Oyq~gOm<mN@$y&Z<ajl-a7vwB!+V( zAsqmBgfJD1g9Qanf!}d(M?^SSB8wJtECWzoW^o2B=Ce&ySaGE`0Trz>^G$Q{fI}t; zJ#Vi)?8ZIlMg^C0nq*qaq9J8AQ@KIhk`mdiJa!8L*fy$FvJzoNixr`i9B|6R((?ad z>cH96Fo{5iiXF2t6_JiX*Hi!#5Ejj9ihJ*&A?I8D!w&SJQ`tJbC6X}ca{B<j_93br zP(|cZZ*=(pQ9sS0L+Nbliu%bc(_dzG(i5$DGMdOV!#~8`ThAj1v<>30C{9D#pcZXN zRhFbvDZ4GPb2m!+yDMmsYw>JlSo!3MHgg!seT!|hPf(Rj!~qr@uVCi{{|LID!1QAJ hGI3SpJ=7QcRn@WAI3x}q-J&hJO*w1VIo*zU@E4}j6Al0X literal 0 HcmV?d00001 diff --git a/examples/example_flat/instructor/cs101flat/deploy.py b/examples/example_flat/instructor/cs101flat/deploy.py new file mode 100644 index 0000000..be04b5a --- /dev/null +++ b/examples/example_flat/instructor/cs101flat/deploy.py @@ -0,0 +1,15 @@ +from report1flat import Report1Flat +from unitgrade_private2.hidden_create_files import setup_grade_file_report +from snipper import snip_dir + +if __name__ == "__main__": + setup_grade_file_report(Report1Flat, minify=False, obfuscate=False, execute=False) + + # from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet + # gather_upload_to_campusnet((Report1Flat())) + + # Deploy the files using snipper: https://gitlab.compute.dtu.dk/tuhe/snipper + snip_dir.snip_dir(source_dir="", dest_dir="../../students/cs101flat", clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py']) + + + diff --git a/examples/example_flat/instructor/cs101flat/homework1.py b/examples/example_flat/instructor/cs101flat/homework1.py new file mode 100644 index 0000000..286b79f --- /dev/null +++ b/examples/example_flat/instructor/cs101flat/homework1.py @@ -0,0 +1,16 @@ +def reverse_list(mylist): #!f + """ + 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)) + +def add(a,b): #!f + """ Given two numbers `a` and `b` this function should simply return their sum: + > add(a,b) = a+b """ + return a+b + +if __name__ == "__main__": + # Problem 1: Write a function which add two numbers + print(f"Your result of 2 + 2 = {add(2,2)}") + print(f"Reversing a small list", reverse_list([2,3,5,7])) diff --git a/examples/example_flat/instructor/cs101flat/report1flat.py b/examples/example_flat/instructor/cs101flat/report1flat.py new file mode 100644 index 0000000..9ede035 --- /dev/null +++ b/examples/example_flat/instructor/cs101flat/report1flat.py @@ -0,0 +1,24 @@ +from src.unitgrade2.unitgrade2 import Report +from src.unitgrade2 import evaluate_report_student +from homework1 import reverse_list, add +import unittest + +class Week1(unittest.TestCase): + def test_add(self): + self.assertEqual(add(2,2), 4) + self.assertEqual(add(-100, 5), -95) + + def test_reverse(self): + self.assertEqual(reverse_list([1,2,3]), [3,2,1]) + + +import homework1 +class Report1Flat(Report): + title = "CS 101 Report 1" + questions = [(Week1, 10)] # Include a single question for 10 credits. + pack_imports = [homework1] + +if __name__ == "__main__": + # Uncomment to simply run everything as a unittest: + # unittest.main(verbosity=2) + evaluate_report_student(Report1Flat()) diff --git a/examples/example_flat/instructor/cs101flat/report1flat_grade.py b/examples/example_flat/instructor/cs101flat/report1flat_grade.py new file mode 100644 index 0000000..48b0980 --- /dev/null +++ b/examples/example_flat/instructor/cs101flat/report1flat_grade.py @@ -0,0 +1,349 @@ + +import numpy as np +from tabulate import tabulate +from datetime import datetime +import pyfiglet +import unittest +# from unitgrade2.unitgrade2 import MySuite + +import inspect +import os +import argparse +import sys +import time +import threading # don't import Thread bc. of minify issue. +import tqdm # don't do from tqdm import tqdm because of minify-issue + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + print(b + " v" + __version__) + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + nL = 80 + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + # q = q() + # q_hidden = False + # q_hidden = issubclass(q.__class__, Hidden) + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + # 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. + UTextResult.number = n + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + 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 = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f"*** Question q{n+1}" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.join(output_dir, token) + with open(token, 'wb') as f: + pickle.dump(results, f) + + 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport os\nfrom io import StringIO\nfrom unittest.runner import _WritelnDecorator\nimport inspect\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, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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\nclass Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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\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, too many numbers!", len(all))\n return all\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# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n# raise Exception("no suite")\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\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 number = -1 # HAcky way to set question number.\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\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="")\n else:\n print( dot_parts, end="")\n\n if tsecs >= 0.1:\n state += " (" + str(tsecs) + " seconds)"\n print(state)\n\n\n def startTest(self, test):\n # super().startTest(test)\n j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n # item_title = item_title.split("\\n")[0]\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n # test.countTestCases()\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 = 1\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 if self.show_progress_bar:\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\n\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\n# def wrapper(foo):\n# def magic(self):\n# # s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\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", foo.__name__, _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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n self._stdout = sys.stdout\n import io\n sys.stdout = io.StringIO()\n super().setUp()\n # print("Setting up...")\n\n def _callTearDown(self):\n sys.stdout = self._stdout\n super().tearDown()\n # print("asdfsfd")\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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# from unitgrade2.unitgrade2 import MySuite\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 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\n\nimport homework1\nclass Report1Flat(Report):\n title = "CS 101 Report 1"\n questions = [(Week1, 10)] # Include a single question for 10 credits.\n pack_imports = [homework1]' +report1_payload = '8004953f000000000000007d948c055765656b31947d948c2c6e6f20636163686520736565205f73657475705f616e737765727320696e20756e69746772616465322e7079948873732e' +name="Report1Flat" + +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 diff --git a/examples/example_flat/students/cs101flat/Report1Flat_handin_0_of_10.token b/examples/example_flat/students/cs101flat/Report1Flat_handin_0_of_10.token new file mode 100644 index 0000000000000000000000000000000000000000..c9122d00c8fff99b66be2f551819687826d1b9c0 GIT binary patch literal 65468 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�N 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 K@ETXk=KmLBw3|}^ literal 0 HcmV?d00001 diff --git a/examples/example_flat/students/cs101flat/__pycache__/deploy.cpython-38.pyc b/examples/example_flat/students/cs101flat/__pycache__/deploy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c91ca3192aaec26f60abe00c1109cb32983e52a GIT binary patch literal 581 zcmZWn%Wl*#6tySGBs0?iHVD1|DM+LYON1&UsxG^LRzg;UW$Zg0Q^$$?K%H53&xi0I zd_}gb_yty6r=mi_FP(F*<#W%KFW2iO!SUn#g}9v&@-vZtM~UPuZvPPvMHDrpqm0so zXJ*!AESqp<@~&V7B`{a{^~_yB22>T(oPWzPUVJH3sVcQlOSMu*SD9L;xO(t`)DM5* z>!haR>OBm-3!5_|0@5cQLLB&&6AJiN8{lrrW2*924_s+CR-AW2Th7nwjLkc3^>)t| z{pW4;QUqWn+<=S$Msj%CG{?Anhze}*$I@?JZnmg(F|Ift_EN|laDFJCH=(}<dsrwK zOuui2eVwzV@1v8DL|LW4hY3BF2888_Yb%285#t3^l?G+b>yoYRs-Bb_cA2ne(duw# zYjFB2miemLX@ymB=>X~E*1IfSE|b*(oXe3VyH<2q<#*y17?_UVpWeN0FFiQlhPZ?F zw3o3<7Tf=XTDTG2V8H(cJT;)z)(015=v{l?mG<8KSiR|b6%CwFl=SJ(98sLJiawhE E0rXq1T>t<8 literal 0 HcmV?d00001 diff --git a/examples/example_flat/students/cs101flat/__pycache__/homework1.cpython-38.pyc b/examples/example_flat/students/cs101flat/__pycache__/homework1.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f019923d47a404593f992813d90c89b3e2da2546 GIT binary patch literal 994 zcmaJ=J#X7E5Y>l26gL41v}o4b5+SKkCqRav2@0fX(GDKkW@w^7rbOB%B9Q?}CAG)8 z|Dx#9Eq_UCCy!k-^+?-6rXE1@_$2Y(JwBanY;-(~zfZg5AoRRHcJnyexOsy^f5eG; z=N|QG@WVUzX-FHm1}vn_t3bVD4ccM>ZPWHF`04!$X1?zs7BL^OHQJ$1Mva+23w)2R zfA->y>8Nx1jg;2~18pkGgn?XA)`c%)u3^X#$CXq@mc;O!6vYHaOfW?ZqcBvm1T$vt zMGaiI4Hw#|DmPNSq+BtZOgg!b%vWLa0LKXqy^fQ?hGzbEYzj$*)88Gyc+YQ`00ITq z2DZwHUAI6nQz?O*7I-d&Mx2Wg$e}B*Ny&f}ti)y<C&g}<(hQ`cOm*S3JL<agmw0N= z-M;AU?(Obh#-PWtD(JEdT<jy<yM%4Bsu(XmzTf;{RkKB4Sc%+lDIk+{(!cZTXMJfN z9y5BXlvJ^Q-dgl<=cxwve4m~iCTAL{C8ip)<gLsr$2qA4HzP$TORp8bL8TM6xFfwv z(qTbNvb-^!<hu8&=b*7H*{xJpz3%m-UVq%7Evc>OUQ4xb&RuTz7&iakg=EXiFt-v! zRc1&U24nz4P#9zb)ZMyeCBsre%~7GrN=dW{SD>r%(20Kxgwk!Yo5gShWG4e<P0?4D zMI6*Vsr}5!w3wO**CBpl-Lhz-q1G%IrD;jHNYm-&S6L}6r>nwPGwi_*jw6`cGp8>; zIU87`5YQz$*%@da;={Q;er|uVe%!3vX-Z|DrcS1ZA_8rdrgamqM3_g+tggz@i(Z$K VRs}n@ooM?*H^L~2T2b)a{|E8E60iUO literal 0 HcmV?d00001 diff --git a/examples/example_flat/students/cs101flat/__pycache__/report1flat.cpython-38.pyc b/examples/example_flat/students/cs101flat/__pycache__/report1flat.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b843499aeb6a5314ec477e4a8ff9439e6927c59 GIT binary patch literal 1204 zcmZ`&OK%e~5VpOKIFCLQC<qCp9La&I=@B7>6ri^pC>4?w$y&v3y2P8LwzojVE&UlB z=&^rkubgt_$feBewoN38rJ4OaW6yZLnLXOrXb@P<gBQtfM#wMRtgjF@Z$R7}lp=~6 zl2Jh^VrEzth@ixQ3A0E<SuEmmADWt|9S{|%_=2cd*FN(_AnI@{G@%H=Q&Su~TvM<& zbW_zQ%o-i&#)2#`et1Z_jU{9yU0TE+^n7MLpvU?wF?r&(w53TppDR7}T~@TLKGW7| zX;SBlAW>>C!u#tBYOLyc3P2M<6%kBP#V&{lRG>omhAM*GaS?sedeR%wwfl%8kUofe z4(dDkNj!xBbiow6Wc1bop}-QMMG*`)SYqxa5}N+FzP?^!ob(b9JiVDXr>%c?nkUAh z<BArc)8_b!*bUhJ8TDTdkKl+K`us!>-_AyP235OZK281DCQ8e>P0xUrq5hg=bEDlY zkjEzR!*uGr%}0J_ha=b9>y^fe!V>S!zZKkT=Oyq~gOm<mN@$y&Z<ajl-a7vwB!+V( zAsqmBgfJD1g9Qanf!}d(M?^SSB8wJtECWzoW^o2B=Ce&ySaGE`0Trz>^G$Q{fI}t; zJ#Vi)?8ZIlMg^C0nq*qaq9J8AQ@KIhk`mdiJa!8L*fy$FvJzoNixr`i9B|6R((?ad z>cH96Fo{5iiXF2t6_JiX*Hi!#5Ejj9ihJ*&A?I8D!w&SJQ`tJbC6X}ca{B<j_93br zP(|cZZ*=(pQ9sS0L+Nbliu%bc(_dzG(i5$DGMdOV!#~8`ThAj1v<>30C{9D#pcZXN zRhFbvDZ4GPb2m!+yDMmsYw>JlSo!3MHgg!seT!|hPf(Rj!~qr@uVCi{{|LID!1QAJ hGI3SpJ=7QcRn@WAI3x}q-J&hJO*w1VIo*zU@E4}j6Al0X literal 0 HcmV?d00001 diff --git a/examples/example_flat/students/cs101flat/__pycache__/report1flat_grade.cpython-38.pyc b/examples/example_flat/students/cs101flat/__pycache__/report1flat_grade.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01435181f7f83079961ed2105a4d343c2511a9d0 GIT binary patch literal 57893 zcmeIb-H#ksmM4~(^~vO?C{m<8S_&pbRVG!H#VSf-t6D{gqR4Iy)RdZ}rhBqPq_Q$1 ztCE$KSrw7RVwJM&*<sJ@!bW??_Q3FBXBH$DSa|)i^RzJhw12=p7=AJme)B%K0mCqC zU>_P-kAJ^&?u{FfnMJW@8si;cN@8Y4#Qiw;+;h+Oz47(&<M|B!{_j)e+Ry%dCiB1X zBKda=7oXr?^uK2_89!6W_*p;K%2smt9&3$lj#b98yf@w&-yE-u%kNxkqB0@nCM%Qj zJyn^K@9E04d>^SCk?(vZFW*NiN9Fri<(PaQuN)7KJUrn~_>=fP=}-M?ta2(i_3$Ns z+CLJU_H%yTKl-bQY)0-K^N;%{{FA=|ByxQU*DocmPviRK#Pu0mpVe!B(Ld*(|7EuF zvj3|8+AlMeGe6GwulsNOGULAyOg=pOWUTTE?)<=i6L;RkopZQz-oNU5{sq)}70@1z z1v~23DzC|??C4*wye`*oRNjd17yOIp{RjTr{v~|BDffXTgN1hnurPxW6cZyV;rg-} z(R=>8{w!d7%b)YhXv_2GD;NALm4d%exfpoC#o$6vz$o4hrXOB<`c5$OEL$lCZv}4$ z#V0w;U+!L}^#1-@{?_B#=2k24qVA3#w7Xut;|FE${(3X=8cp2CcWtNJ*{pS&^;)a7 z=dA_pAgpx*-)n@OO|QEiNWsW!w&kMPj=JGaz1s=r{AL*N$6o%|B(^)HY=632Tit1) zI)D54?lw1r{j;|o)ml5a^7cAAp%(^Qov>T3{g1g!#=y7a<?naAaHs772(KGNUFoo9 z%HnbUy0^90UGJbo6gAh{n;6YP)!!;_?d5IVP8$Q*-a+jq$}M`GFmY90cwQUc+_ty) zoId0^o4C`#FuKHFR1ce5fYa{;k=O2Yy|u9Br-$faP(ycTy`3mnN&utySg1-#8PDxz zo0BoiX4zY<>oprm<Z<4%YGK#wG`yO(+3|NUm&L7GeWSJ(lxDqJ+n3qWrVXe-=JpQe z%vF_^O|4m}Ft_P-wgi0*V9#KDv$p501_ZmY(<<k0;|a;2)~*M$1dK#tda^_XYWb{F z-{CAp^L5O66jV(|^IG(3wca6Vb^{DqA^tX9@LQoy3q%skwpU-TwbueKCPA;;@n&3) z-wFmmPf0vuZ0Qcl2J`uR6a?OSx4RWB&CmOtdIXS2YvoS3Hh;DJ;rve2Y_HANH~m(# z9hBF*o2`6l4D2pYA^^EnCWEbJtFu-)3B+MJb%U^4Z-G|#b939j$$q?F{F2le*p(n8 z6w_)kD6f^5gvd&z@ybNI7OqA8@$TMM(4X>yMs26n?T@bqt*!kNb92#pXE%7fg~b>6 zm3)LH7*w$+gMIHFp2&3A$MXOYQ|@DaBD7J;^(Sj}&Tlw{C2JewOzfbs{X*P8tsU(K zq0$_g22dwuqW$T)xt;bVitk^R(%Ld9aVu=LyTx+3Tw>`>5TUGMR^LA|H@5|BfUNi5 zyOH8JyzQ2K+51|^%-dZL+MXY*?yRi=_vPDl0&M?|;|GP>P5g^)L#TB#PqWW52UESQ zpV^oWf3G+7Fo$19^w&Yw&-St#li{ZjI%B`g^s-OK{Oq&rXPGD2Z*m9OgIs&MJKoDa z$sT086TOT-{v;Rnx|0v5o=*2N505;{M!nud)a&N`3AuYz@A{KZ#{MYV8<Rf&GxT}v zV5~PTSN|=pjvtJ7PxR1RZ|qp6m-DB3<G6bg|4#MBdXxV2ukz@9`rwFx`v2^{<R5vG z&1Sl%d-B~oa**%7gsc1$z=r!T`S^wJm!F=&zq8LW&$9l}S2GV^IXEhCo$DRNos56% zk8`hP{Nv{`2gl_8`QEW-*+0#OpQ42mrlpg;V-yv*bBcF*(-0Xi-RWn`b>K7p1CP#( z<KI2}i_YL<Ez`?9I0ignTu-w9Wo~=?UglqB!0*Q}`Jk^{`AR?gD5OFnAHP|<x%3U@ zG>pFK?yLvj#EbNsop!S;Jg*AEc?9wBO`y^$vOlT~$dYf2?25jrM+;XL<d#{u{2jB+ ze*U9YGwPB~uU{p!1bZ@*%j~~vS6x->RZWZKk6Il_z34iA*qW6yb}>|S2~^3__Ag!c zZiQhdT!QQjK<L4oEe1{_D@BTdc^F|3JVuLU@8)_?-_TWL7?`ro79hteyj?odpSE0} z{}LBAkkMJgnu@BcwXi?2-RuUN{i#PmxY~)p49p_xAD6OrnVU-sQ-4xdX#bd$>ULVy zAPo0k`6}!@YDVa{*0P)k7r)7Rzvh(x`ak^DU;Wkokr<)-RS|YZW|MtH-g@m(Ai{1h z=t3r7iH5-%MjC*}lvRkEYb&m#-mC{sUyC3nAmXh~v9v#S{rdI&;^%rRs%-`ym<IF@ z$aq%w!WthLU8P)lwLb}=2U6<i<-00eGP~WMh_+hIZg>=f=ufTJBCy}EpX-2wZq>T$ z{mEv;*{Phh_%Tx($=vpj+<IIOMCkRWYO4{egy+yyNOJF=93Y;G;M5{suWY;h31NXD z7fpW(GLk&Ha$+s$cEB&gMu;Ism1FgtFoa-~%l<S$YvVzGdKDOwUz0*TQR#SpQYkh( zMR-p0BZjQ<GSncl{>>&dqV}2$COprs#|1G{%9*2n9$Hx|s6rpBRVL(jCD#Zm6Km|+ zt<>-Y>z*4JZ@gIWF!~`rGTG_u>FgvvU&~GB-pIa^o0hA|+-up~U**TA@nkwXo;{YG z&Yj7g&z`_lK07@YUP0X<-ZFuk!o`0NKd3N+J7=C|o@L0J!DX^fb3JhC1MqL~wag!b ze>cF};;Zr0)da5^W8h?ycZRx}L=VEo3iu&x>@{sxn#;<kR9B`}8tZ3w`_o$;U>E4? zPj^<kHHd#-FjmTi6hQrKa{z(iN!<Q9{yF~h>JHcbC~gyS-G#5=<JY7abU8@{A&C09 z_Eu#IlpKb&J@oY^p7yir1AX-;q~)*iFyiPkxzj*(_#uAPd71nhGZ)bt_z`3(KxZZQ z4>JvjMj(B<GWHL_=PO|50OIAZEo;6XJnr7z;hIdLla28o#~P6=yO}4MhnS;Y?!SO6 z@qy=3_70@MM6|Po^*UXxHqm6YipIyIpw$THP^mw;u}ez*m)ZW2s%lHsst%E)7=DD0 zZ}2Z7ZZg^ZH%5R}j$58!FI+y=YCm7Cs#1*K$EwxsomxwuNSpoRrnh>~YS|t|V10~! zWEq4@__cz6()V;$uKz|jJ#!D*H&%VK*18W1<%<rCg|IFoLpS{63{qwhvC&<85UEdr zRFHpeEd$j8i-8N{K>>;d6pilq!wD!V=ev`=ajGiMuw;5;5c*@==X+xZ6TRHSBfW`- zu+D!)0Z*GSM=Nb?BiJh)4S$3#`lrC02+vLMo;zeu{jtrk)6ava?5v6c+n=VOrcG9v zwk7-H6qrP~->hR=`{QU5>^6d7*Pm#%{orwFGF(OnmE)|*)^Ac9uH<<s(-c*5t*D>9 zSvkd9b(E-X2GF|wD3ocC2@yK@G1~bS{~~h1O!iog%;i}244BM`?Ea-uh%FBet27bb zK;`gLe%#~-TMa+sN35%8$I|C5JgDNIoqt~6uT$@|e<l2&PRhojH!*2O2R+_97QWZb zJ%olyZSz;TgA@Mv({Z{%z}rqdolxF}Nj=#f_oog{V){<{)8{hXsdJb>Ow{qGaFF2n zsqUno@0~Jtp@$y4)XhUXJ&H;H6KJ4EMZ^7*-b?;5UY*Na%b>)hf4uk7Ip}lYf8(EM z=lqk@@<2YP{Zj`ocaJ?i20i&qGjnjZd;Ce};1z194^KXUF8rt22Y&>8^c?=3KX_Gi z(j(!&>z(u2T6SYB{CC^`ruWLzQ@!(m?KMDh-aq{$2dy&KJOA{hXPNeR@BG8l2e11t zn>w#Ia<60#-oW_Y@K5?@enrQ`+1~l;%koJWe$acPd$#&YFOSc2y>s|H-+k48rF+&t z_p98~*Lts^r#E|VK79S)t?nE6{sClyXMf4}n@_U99G|_#Uo5);n4<rx>p?Sr%Rjp@ z4ha9Ud!hGg&pVUx&!5Tk-g+H)24>D4c({xEy&v>m_h0?<iQd`XxsNiqK3_f4drKgF zO~>+2Gk=jgxZuBz5uW*{a4Yp*#hvkk0>*a1|3U9%Kycwn_B<ge@cT>#uoQIWFe9(a zyq<nq@ZWUprP>->%RR%~Z~vR_#ok$1C1VE{58lF9FLdATVPqKhTk_kJ-yWdLR9^<9 znd+IC?npOp3Ef=KdG4J#kC7g{-Fw^jUdtR@>b>2&<O2)mGY9YZ7yQC6GY2z(?jmW- ze;W|b;Q6H|Ib6LXSMT^Ua#b`m$PtwMlif?8nKQ(<{|2zntCxADV*pfdHFCYP{^dW< z9Ta=T-suKe&%1XjN65f`OgBko-0yTYOWDeJqtov8v-y;GAy=egAkx~y50TdA)##w1 z_XN^wV?2b(W@L3%Bq;>W7?deUoi2Pcv50OZR=6x~5nE_hgeFadQ<?pX1A5OWajgRo zn`J-eJ=%Zil0ScGbN<pFc$a>%bm^x{m+tK!xz~kXCh(U$QQE0&?;r7?zVny2fAp3* z0HCjV`{#>rsd#g9wCg?kTh#RumHL@qe+BSFG3cML_CdAV?7|c|`7`U`y3}0qE=6SM zLEBeV;(b&D7LKUB(CqG2j;a<Ytb7em@%Mjy3~so9AK@ZDXe@zg!_J8M6WYg#%a<>E zaX;JPt0?lD?0k3x-}`y@V1L>U{MQuLDRIV01XpyE2#b*u$5qOIa8iP&yylN#C61q? z#xQY5I?`chqwx1po1?>QVlLmqRY;aRFq{49k6N|ORlj!qf55~42mdnr7oIbjT6X{F zO&E%FKVhmcnZ?TFX0uJNMsC)tOh!Sy)AoOpEmbD&ce=F}Rhce-zy}^c4QxfFcS4S? zKi2M0OWbQT*U(z!xPDjF3)CNf*lD)=d9hFEJBq>?RH_`URt-r&c1~k_4LV9gDm$Ua z52|$V|L`UyPya~JhBJ+h*~$@14^ic83t9waa&#NcTK=G*4S&HFD3;v(R*rrxs+@R% z`p37GmS}jlyWvYHTA4%#t3g;fDFxY<>V5q&*ld+)%IFApLMkSeBhgO14jUEf!Xzxq zP8e0D8^FpAh^>F(1|EboZ^dL*vuv`qwM8nWZdw`NjCMA|Z}GEK3TN?^uy1H|g)j5t zEI(f12W_7I39)6lR_GR3>rX~BoD_OBd-AKzwd#66#VQO?rE;tm)tgNi(o|sk*;NEA zy0B+3=ryIJc{ZGDe^Hs@eJtF{L{x`O)t@A0S*I<kx2zUC+rcWSOm52}30v$Uw_OE> zFt2VY<)VdtE?TVQcA?_<-O7{_R#cf%s){P(TdlAamQjCzmcw^&Zv+3LE<Q37w4+YI z&Kl32f?|IRcGekOzk=&=v9Ts|=f}@vb9nX|O7Qt)c6^MVleyEm$!tD%d9r{yygmV| z?QPV02fwI3u@o$`+$nt0R-4VegSG{N)H)`WbKR4mqRaT`X1Z9#Phgk%xhJ`QeCi<A z%fU*6FDeVe=l#sjGIuk-P#a+EPJg_Ec?T}KQ2nQ%m5FUWwz|LApV$q7PWVx$A4Z)v z=Y?x*1N=MN7{E(^YIR>q|62k<B{_r9g#Q5FDZxQXj$!@RFs`6uWP2Gx_z-Gw7xoDu z4?mD!fIj+wn%dKGK8NHS_n|h!=RnnY^68X64sY7TtKeD>rym~SFO--(n1C*qY2^<l z<@+eSj#HvlP4_0B9`8;2Q_ph$I6OGg%{@KQLkm#YzlA){9~?b6);or)uMUnw$AixI zEF1or^na?C?;U>zO;N61O5TIt@qacjqOEogE#?kR_KrL~-8<1c`3ySM_MdJ4iNkwu z`svHhXdfSeCIbQK=kYy(@1yw6_FymH%RE?5!SnkL9_UJMkAOk??4C)&<sZk~pFTL% zJ4HVvT0Z$vV)p+uyZtu)z0$s-t-;@E+JLw8xix!PNP-i0Xj{-dLH<}p=%Bk^t=<>; zBZBU>tjPT_IO_XT`^_yD?H}!SwrrH4f7}>=%}qv|_Q7%00%eVCSb8P=BhZR6+Y9_v zzz2No%}_irFd(%e%tNso-eQ?2EHfd{gumqPT(i@k`g9M>@$)Y$Q<dh{ZNf3WTWf9f zr@`)`-6ot#6uW(RV_M=``cHT@jg=sle)t_P$3t+f(o686Zf;eMfSn*>0{+_1Ziat| z=iwjo<4^d(Zo~f^AC)|YU0vO2G{C0D@EeVEI*1Zr2$17M2n;fQRVEtJF^v~Eeumc8 zi8Wvs;R+pa^gp3k^eH|vU@~AP6J$A)aP*9iPmfK**>eJJ9lCT*WQ$-;U>*D~m%$w{ zlCksISF)b4m{+plKS3+C-(VfW0j!=p4VW-o0}O~OP#7-52g%@|X9I(OA1C`e9w*yB zHaFMqSf}>>+0U90y^-*ttDD<{n>%87h;isvZFT?5JqEEDBf^!5TSX~}L=_?C^8YKS z3-$+iJbi_*ZO7mmp(m05<$7Ze#}RuOdpL0{16zCww(a-<VmOHKKw9A&oc;{>yZoNi z@9tD@LgOuv9+RvKd$Q1#ND%bbDHtCAJ!(#~G<rS)j(+N33U`hmvXiH^z5R!{l1NPt z9-W60ExHCXqk2?YIjXII*TQ~;HP7C~f~OV!;67lSMtjG5Qxcs*3<w_46VEc>*^nUM z)yH~A{A1we$MJm}-;608@lW8_k=|r)`Xi2uQKpl$TG{&czs3D={}d$vqf_YlqwXo% z?39iVPa`=4HvR#mm4Eux4EjF_z7Cs?{sokM2#X$9=Q7aL&m6o2>m8D(cdGYNV;uJV z>F%kAXP%yg1UL=Jbh`UW@3jAl$cC4@=boO240@STDj}g1qyGz_w6kzdF%JyFqabr} zo_EBL`0Ab8_u)+1#-t}y!jP8~_hG~jHBgrKPf6%J5!?`Iv48c`J#S}=Ivou!1|Z6! zX-ICvti&3ik2f28@=SQ=(r>cY_kZt3h{z|HGt7i}2$W`{iC8N9Tg^vJA5KL?bXWJJ zIs)D`glz&wyghP#@3Z?~M;;?u2zr1Q@4xYRyWZLn0HgzER47b{nD)<`&=jNuB14aY zDnnD$p)0SOCq|wzrT{SXVSk*`YJZ&OT7Sw&jmlIN(Q$MX`an%3SA8Uw)c(ttqUxn) zwbQ`orAQIb&+V@6f6O!maAOSAZ+68JO$L49;Eh77OdEK_BIZC~KFy$kIucEGqBff0 z=n?NgU~G@62{Et{A~@j=XX_1q{D2?ScPn|vAK>%Wg)erJ1X9%EIPH*b1(itf$}#%Z z<6G37Me0<}S^>_e3IczMRu$$H`29%?xxVX%pRfw;El~kPSgTN;+-lZ0D9-(z&8^ZI zF;x0_j0~}I_&vjLmzC(qR#l$HMEKA6@n7SkpWUd8w;@wSc=xl_@Xz^(LcDTntF}jC ztfDbkClPE44K9ckQJH#VY?S|$HU5wv&P7QFM*lS<vUNtP-A*+||5@<E==V`5Bl^AQ z`TuWzOmuvR=Wz;fBfh7zr$qZd4gLQFN=TdrcEfZIqC9sDqWv{Ir?tQsk3J9o1zN7t zCa`~W)bO9!2Q<>V;J$D~BX;v7Bda<0bgVo66rA=MUD;#m2Y?=^-qG<MgAU`c6oSV= z{XQ&h1UUsgF!5lk)w`uhJcG6b`=QlYMZh+EhHm;3L?Kk2Rq9{;DdsD!!|yy1{so_& z&}Fkw)m0Jxceq)}!SE7$a{!;GwGdQ9rnSIF-wnPZ9;Z<$1Ir5k<-)(j@7hf`8B|g7 zI#E7<(fjPnJNIXhpCf7}^Q;;T584z`uA0b?vC-b46r6+uAfHEu2i8x0J*bM2Q|zoh zMBs2%mY4X-XX!y=0C@R6@;pjQc@KZ4O9mgIF-BD-S9#>Oy=A~xrruXJI&G1QRaz@) z^@ap;Z7B^o7X7wqoju^$<_4<suB~GLS%Ls(wnyRTFyhEm1%7a{LIP~9B;1fzpo3u6 zo7r8Rne|GH^fq)j>~LT#Wv)DjjoJ7)_~LS*wZB;_917QGopx{mkCWInV|0kno-aB? z18wzo)d5;!ev?iOklbiK_JS5v*|-J4N*Hu^!gh@Bp<YrjP}5Je|GcU6E_$CUSqVX{ z2K5>phMEdyryo)TW{)uC1WkF}+8P)!lIiG{DGQi?(_y0D0b+^Kkf51vxaa1Rb2@tZ z!_}F=$!B5y8FUWP0#|#naX1aIY9Nt(7!M|(Px3;p9Us|0L$>!rN^+PXY}TuJtN7Ff zB$Mmay2FG#(8TAO`9>so^lm|qWrN6+qYN97QmKkYok<NLG>s0@o9KY_npo-`nqa$4 zOt0VBZ5yE4!LD^38$wTm5GeKAn-?wWbRcELn3f^gIz$*0-@zIIapnt!LVgXBzvla1 z*?Z?5j~BrICccn0SNKx0Bcg(R1VO*f-@w>b%;8Yy-6&c|1|5EVf`3ro%bY|wRm*;N zr|fUcGXrRz;Y7`KWH-nr+mb5<14PQa)3PORU3*+KdBg8)G}2zfZ@IO{iEeM%ThU%* zf7Uy#7I-NbMtmy_>_gb*Xc*ipzOn6Z#=qfwwZ9E(H4uRD3y{r@h4P;G6hHq`?^_7V z@SHN6#Z;@3RR*p0XYeh<liLYx>n2eopWoaIJ3C0-T=vwX?LA(?sLFD)__*YOA($La zznrHJHB}PVD3sr9v^t0-474FP`B;Qa1dg>u9j3avr-Aq4%~?^DaL+7eYL;l_CL-(- zJg8Rji#MxP!%l1kl(LM0?(U+!kNi~>SNgk1w#6)2Te2BZu-Kxe&x=CJ6g#yI=%Qid z3M4tLt)(60^8%mulctXxXC-n<)b*wx6q#(0;7*3{DMf9yFFI>$L1?6mDaz{BVI)n= zdg^IOG{u(7$m)T27wJ^X9*C|Sb$zIGF~5-_ND9SMt)7=hY+VIC3(R_mx<}00b?Y<L zc3I2XQbVl|K9Boow6IPGF>n#0f*DnOhMpO4XP#|42z)R^MxjjB?^Thuut8RvNUqvb z8N&Ht3YtaU&Ds`ZA68J22?2*=?=tf|W}zMtKg;AhNQ0Iv(&gJw=x}xUvL=HJVgY_( z)**sNAXnSKL;c}!!?crPDb5Zl8UW&6+)PxfFxlY#s+x(e%a8(Q%ptB}6fUehzte`E z2X5wJESlLYik?G^%vu88m>@{|>4(c6aiGdX@`)P&sf#{w;1+yl*|RLB=y<+uRmbkA zhm+)}5p@lT+m5tN*G^hC4Ao9oQ^c_lx=-V300J`t;E|K3$iPKBMAwlPaU%q_H-k;= zEb!Jl5U$OE6_xH4ji+zK9hk*V_*2vUn3+gswuLSRX%0zGaNbG@HwR295tl8<%Ej}F zC5K0d^LU!gykU%#Xh<*L+47<eGaO}LF^s8svY1XW5j)H2K@Q+ruxl{w@IJIk7mGLT zf=#VesH}&hMB<j5!W2m!hK_h--N#(Cg>Tx0vPcDK#vx0?jS-3f+aZ$DtL9D-6<smu z)Xc{~3;z+Kn<ULYff`(k++x|ofMTr36E}AA3p$v}u%CU7FnlpqAQ*;aNY_l3#2wL~ z=xywWQP~DkYLmA*6h>F1Lp*Il4-I^TdqtREDZjTUPZ0Tq7NS}Ih-Zkoooq#%&QRQZ zlPYVTftWuhv!ujhBo@jIRcLe(iU$dHDAR<MCGG~zwRMhv0oCYpGdJMs=amg(my>H# zV>D)M+7d=y46<;y1nd%l%h<c_U6IWnf@q5$2sq-*)rcp_mN4WE(T33V7mIg|zbKx! zpcQvwaQ<7id5cMij(I8_sEQ@O57Dv+=tZ;ipf+;Hm6?*nw2gic!!GSxWMV$-&jd8z zD=rBK|6(jK7ln$$VR)722CGy96SExE8bKA@RFe>7vJ)KG_K+e`rF`lsP$7rAOmO=0 zaJ?82$zsx?14<|U4)*RC;<Kw`7W_%K+F*xNrzNzQhL%*ftSc~0VDdoc4nJck;1O+L z7NFQ8+q~!%`6H4<-=gibROZ%(IVocvStv^S%yTTV9nua!E75k?WotA9ur&(3z%9H8 z0Tp*GlmIc&!R92h`dKyHbVY!EHh7(bO%F#c=SBg$0zBlhf(6jD7ykp&$hjNL`wo>3 zeuPOv3s~la0j@AU*@c1j9U49uX~;GTw_4&lpHmY!u7^r&1kP&em&MXxbs3P(oh+~z zgUM;zjMkRFA8h)HNa57ZEgSfcf)>0(=paD?hs^61?5M{@A}~P%oZ!TRzrjo|W2*h& z5xk`^ugwf6T3#NUEK5NDzt8pZ!7PYz5Y(aBw?g3W3KhtbU)GgB?-dt<xgUD(KA+E+ z=R-UOo>|9Wo=aUoBiWb`nX95iMn)t+2{$QGHvjcl%HIj5F+!yplx;}?l#)|M1#=w8 zHD=}fU|ScxuiQ2?#>q8jgbEoV4~R5EJj2rnZ6)%UXo|hNV%|gWio?F9zNWCfR&pk+ z+V++W8W~{}4zmoBW_v;qhBZ1bAsgVbR$d<Xw<u|f1;PMl0sx{PJIQDH<j~cXXj~}J zsA?lve1daRt=NJSsvDVhZQ#VX8B*KktnWc?T7hV8CWna#C?o>Hy>SKjeA``n6+0Ci zaob%(kgf}{^tC<UW~{+mRHs0w#$!z2fF+Id8r$Z)Kj2n+H8GY(E)UyVg7s6hr4Udp z#%hIweW0UTM*T6JL0`e9^d;=ngLTf`fU*k^*&LD&>i-Co2v>R+CYy<Ql}3XXckEOT zqs9U~GT%^9M(MjTI)@?+U^>PX0+snLR213yjP*dJaTBqdZU^K68dN-Lu+Gsw$IU;3 z<N>5wbXw@lwOt#AT=dKF!50WfkW~UcLt*J@AUNi^X1fkQPC)!kd--?$H-H3o#y=~` zf6h(Xm<I*`foJ%;h2eN=%+7o#^GNOgU`ErsgbUE^z6&GG6HRmpftG<lhIRron)_ib zp>R<MJvi*za{M+<cDfRRRip&OWRfhTaK~lJnx%~{>PbJ&aE-$68n4SE5Nm`(@e0_D z*>sp#yC4I*Dva4~HXa{tgT*?&AySa8rDC_}*lrTdJT)W5jM5}$76-gKZW;!gNY_ZU zK4KDWZ426n+V)X`FUHe5iVHk<J;kylX0pq+eP=Y_)&Q-&_)0!(rN*fH+aM;pLhn=d z8c&o2tyCCuFP@iPpYhgpc#P`nvDHh>0zRFxCoP#3>*n6B742`tI!@8js0p+!d>@^# z+{h(j8?X--hSrbBJ<RA(IB1A?9C5fIzQD|zuGQ<702Bwt7{A_Ea0qwBxhF0@Bi9L- zAwIK4p%pT%f^Z~cKLfOOm_Ifr^w?S5U2kH#zy-j75qswRaxXqI-q1P^4ngb+(Pb6M z3?;$0&9d~t#o;SC;KZ<XIlu$v`Yv|IGOlU{C%*3}2BXFl7kJ6pl#%{SXQIaw(8uS6 zSk|)|v^u+`GTet6>h^X!p^u1=LIGvL9X!;GaB7PkEvV12t?CUqYr){i&5*j9i%d9+ zM`U1e&DK^sj}r9he6Ui_UN$5ED~hv5_U24O;+2lUa&(d&dlB$rP>+Fh(;`me`N)be zvzD$W*!q%UcgfQjJDe(tXHnHj8nv*A-4SueNlH+a`<6|1C>91ugX%UGE+*Ox0#rgh zLo**_3~BJe)TJlVW%`NHriX{;*tMY(6mgBDiQ6=iRIL+4E{P&>d;(5z9x}6RG%6CJ z4#IV)3yt(4LrM2FL?kTdq7mAbBOyKwH&MwQzEO-yqDO9mZio#zl>><)p;H+x)eHwQ zqD0dYH!;v99O_CdAQww1@gy5ej+mg~I)=e~8DhIq;NK);|7pOTTv3t72O0c+B5X** zz3~iVUj>rnu#n?u7*#c}3q%~E#KahI5~&nr)7@vt&NBf?k__?#g56P#A!~<#akF@$ z-3=mL{Ce@zGWU@ubuk8{S82!!Q0cix{dW;rN01l1grhf^38ag-#g&=F<CzEOaRq(I z^5QpIMl;YsctO1ZH;SlYvTdtKQp1%yKwzfyU~rm<W~N4b|IlM;V0Li+7$*2u5wlVg zaF<6+qCuC9{1m=Mb|O6Qe(c0o!GKxh$pWpts%)oV3W*rJ0&7>czTnms#uDx`6of2d zux@zm7;r(w=Ds@did!w{&xpDLpFhH~;uZ}s4F=#C^l9xGUjc;7A(15<zk*~`B;EMB z;U)!%<d5J^Z2ne}5wl4$kUIkHCs=9-EOqx{I+ZemB9UsNEG*N4HjyTfQ~__r%^WEN zfmp5*^u4mCNzeGg?61H^<R&%4tk$|Eb(_dTZPC#iDF@XI;j9!=2TQ=P!3_;*6&^fb zS(EBhZbmZWMYhhv+T3vqxHapkj*y0zE8%kjXaG+Sl_N?Fx2)SOi{9r&8|_)<<3zMN z$z*gIlx=?^2rXmoG3O)Z0J*3<q9n176a504^3*gnG?ip9ht4D)j*g^g5Bl5;b97P? z(U2OL)VXND;l<#<c+X5VARii)=@XBfP~(ga4&tJB69%oGk|#P5*A7IXampe892v~m z8o*hUC1t0DBq2xXIjNF%!U_FgLCG9}6LIMwn{5yPHq4nLg$TN28q-7q0u)L`v9fKb zJh))fRftfw<jTwFopfUX{-p~IHVV4XoGT-)?#daE+EvWkG-KP7Yh`P2-pRiC1jn#& z%fi(alH38n<*avyHw?IE?Jw~AhgZN2e_DIo+(aTBa|;m9^1JJXR%Cq+2u#dp&@I-n z`P(6hcrR|xR*@-;V+mVPj3{WaiW&U`?#;<mDDBNiQ!|NnY}sv`O2}S}CN@%VZ!+f; zaMTm^4b@u)111(1a+E7uDhDa&keU>0S~Q^)j@(o+*kEF)LW#-wS-J8+B&zP{97F(j zFfuI>vooqDq^#CMkQbTlXe&nXa1PV=w<YPzRmBd`pQvH86akAc2>Zy<z1>kFkN{p= z1NRA=;H0ehh6ylSUlj~ev8~CdsZ>ohy|OfS^?}JaDxzo<Y$bt^%yZj1`mx)G*bp)c z<z&!=K5oU4fX941?ys<ZY!ieS3~|F!+kk_Q^MdRHW@R~;&3S1Aj6KGC4U}OE1(t>a z(7R{}eN%cH_6mX@%tz(pSz3wdzTMbOR>Xi_t!KI=Ae7uA>1(jtcOji)4qf#-Rq#gH zIR%kj#$jm*4_9iDOJgw`O0V+mval8%+ZW(3_aFukgmpXyfrB+5%26Fwi%r0RrbYfd zoZler0ZDNz$kd2uP$x2pob-aw#9DM^CrXdRQz{*a!USv4%3L8saJcmB=QZ^lFxl!T zP^C9aW2120`*|&Fg8-j@_97y>c5Id?1=`VrJD|hF9Gs^XvYf_8a?@=ScfPzY#7Dam z4jE|6bgJCNA%>8>68vDEzqcClzyfR{fXb_q+??!Kb`BedKHy&`$`Orw$Po`(^a(4{ z6+8@GY`}Pr5*&LIXV#h3uAwmG@-8o=W*5hLxC2;m%rCw9)JgnW=p&+{hkr@j2iYEL z*iq81TRVV#8qd2e7_($<KR~R7xw+aznJ&aH<ZdF1m1_&~GuB%USy|S<&#Zj*YX1Nz z&Ot_!9UheVQKZ#=!&~bhu^Ia3W3}~!!$=`@m8IL@xgKakA(nj;1T}T4N^#q*F7}-m z)Z;Eo1B*!ZFFGX0ze?N)P?W<%(rw6zG6S&Namn`_=OJ|2gS@#dqhu|fN0X54%0x9P zFr*&JS`b>0Z3O8l7tMtp;fFi$7Q>+j?EzXUj-7^^go!-#((Tsv;2DhZrpg*jJ#1S8 zFy9@-Pjo`P@1N1MMpTUuP1N=V)dCqdv$<W71OW-A1Tk8BWrk!RroQ4ThRl{s!oIQ% zt-{&&bP(W<3@cSHz3N`DrSE3i+!;$0(I;9>O%W-cR3y7!7I7}c%o25Ehe)HCC82fG zos<TgT*jy+jZ@O>omfw-^cS5NJAR<TwmFC;-9|F#Fr$xDSsN5<xZ9!jtdgRP8ejHq zY|-7+?qc7x%0UtrJZiuZ4|}#wiAQjHc6P8y0cpX=!H4(&7Ud*e4ndqftQhTWu6Cet z%RIGhnzB*<S&}T7&e&AGw&T59bux}txdBt5Sh$ps_uI^&bq*BHHpMi`52dg)6HWAB z@uSeY3+ZK!s!diDOW$_KvQ3m6ZjrqS0AexmihpQ?y2189Ql1Xmn)@y7*bt8Mc4{d| z1$ya5<y+fX1F|rlQS+)u<{?zgIs%I@TB-TcErbd)D|7EY_}D9cyd?j=@h^Mdl)Z2K zcacZbGq>Kwt&f>k#8NA_f(PEpyK|_dr9PgK4#LOmK9GH5v~l1X9qy27Zu$3b-Tm~- zd$)AJFppX9u}mc{Tn@QDS;6+FTDMq}?VQD#@(c;1#!zP=D9G=U?!{p21*J$lHM!qv z8EdjZiY=l!&e;1`5{XPIb4mL#!x)s~g?OEp-LI{^C9B#nwY0;-kKu!qW4NLVAP?JN zYEl7#btB%uzP3lfSH?44G)qq%vFdd)f*&3wkt4F11%1(jxLnxWOL?^mvl4-W$P`C6 z_N9l(-NvLr5h5aN<B`g(D&!kAG|WMI(P#ye3M}2GEBb8g3f`;0*7+wDNKu>!q?YN{ zH`DLDKK8o_FP1zS0vrtt-E$hZu^hI@cpyfx=da9<q6k!Rla<YWXM!Y38-%dq8!@1Q zRBNdd?|OqUCD^Wjm_X6svK~Ez5#8x_MS_e33fZ?<zAwLuP!b@r6?DvnBg7a~t%40| z7)e5QO5YTh*lL6)@GGY@DJO1BT&+9Gdq=*M8e|&wp~Y|y8<w|7E;S$wKhdkDItj0M z2~M197r>?Ii*rLWa%i&C5a=icRk|0&AqXv4w+u_cH$k%C-eh=XrJj(<^DQ$LDer9Y zg(t-U-XAwnuD7s%KY{vqYFJxrZbzYRR}d1xxrxsKY<PQ8-G!D&BqXQNO`NEnBM4H4 zbr|*Q9{yzqhJ@4+DZsvaq^IC4_eX*C8t^3;N_n!t5za!OC|X{)a%DDN)*S2bAI5FY zNnD=!u0@7(@Et#T-pY6IV{9Q#dGOJ9VL-JN<Z|!;(*oU|I|s2{DT2urEc@VXyj-3! zhwD*yjQb@Y7d;f|>1#^5<@Ow`A2fpl0<fPirfZ4Ab31lx??*a7QB0r%fB4}gZvMj- zF2r71u7=ZvIW!}mWhU%mPg5aFJ)ay>B|z+Pn1m1P-zwJrh8rSp3o%5kAlOnX92_!6 z0iU~G9g*_v3h{AzKi#`W`^W7%5=%vlHF+S|y2CY&;~kL|AbUmCiA}6PXkn{fP)2+F z8(QtqfTck!YAvDeZL&-2`i8m<|28;KeWPga83sxRSz*p>A=|8N72VG0#|*l`dta?W zld?{9w~YqJSR;_Ah%k!=jxcr$R%xOKv~jAq#be#g>C+6yt((qCK5*$$5lHw|*)R*0 zo15B9g4crz<dKDpRp}JA2W6)z!hnSWGF|3ULtz9|;%K1^vKVbNx0V-8SI7eRF-)|Y zY_O&(Tr*|aAB{CYjx-+Q{{!oRaaT6ZY)cv6vc+#13FLV~NVDLvTcUy>Q~_&8G;B@@ z4MLX1D_MBd0kCHd{6LT|-p$F0C`+;B1)u;{s8`rsr}5K5Q=0+Q8683D_MQQ|;NdJD z=M%eu)WFTH?jFPg_fgVfSKL5SkmUmy3Jn7%^K)<|<bw=jZaR}O+EfXBK$Sw?$3Phf zI#a^HZ}A>nK_Wbxm5S(ss+W{2c^|1%M}Y-9duF0$RA*I_M*K%0R;jfj?LMG`T3R%2 zPGg#=R~BV0L_n$D!z-NPH$y0Z7rk%si1oh(t|Y=l`q0<oXrI6iPtM_PJ@Y~&m{+7b zDCyWfD*zzu`{id}7P~=xz1wLwH?XGp7(3)c<M2Vq8*J7@&`HMPI<_PBgwm{;q0i#w zXCcMOxNs0FS0Tg}nPAPWhN#?$;guV5#8oR1Cc)-Hv&(t|hw75C(CI+(vn_*|+<VFN zlpI-a9bft)|GDV>;upVI;v$d)B}SLg0GxH;L+REqM|kU*sV{_xoi;d;g2$#Lnq#A^ zFTRnQhwYYfoP<<}iwXIgnCqxPt|$&30B5oo6Nf1%_LvxJs3cvV4`2=n6R-%43txcg z^^wlT^_e9Oo@c?xy1=x`0c9gW#N?aq=q!teBvwz7O<aU0?NO)cd-Y~m$BW(g^0o%v z)&$*1X%CvfhB7bk_QT$wwOGD3-)gRgwQz6Vl04s`brF*^N9SsYJQRG+$=VTsxwkig z2eDs}(<{%+>MN9uR3O@nY3n#Ua~-P!W3j;#Eesm;7y<|k{4arOL^Z?<VBtawC?k&| z)-?4lQtqh>Lf9*greMl8U+4nBO;i_bmp2?fn>JnUqAVZZQz|fR8a6|aRNdq+undO4 zwV+#lq(K~^LFNv&UPposG*w+za>@_r6Eg#n$6h-gP?s2->5VNwOEGd1%*PF%Te*wc z+N|gtcGRYh6Vz#d^l^OZOcNVTgt${Z+Cq5mkm)-X@w1Xs3I<Oi1Q#P_;ql?lLe)%c z4JR42HTg7TzzJivhAk`6MYG0`yhCPbhZ93895g<0$AD|vQ|YUzvU_5s7|-*K_TIpO zEwkQQ-DniB!*{j-KbMj<#FZEvmruU-$A}*;Ll1KyMZpnyE#UB=GpauJfjRh0p2>1W za2AgxMyndpl6OVN$;XM{ICU7_B2lc>In@Yyfl@+dh_S!4_988^JX|Gkz~Kk)q~{%k zYq8D(lI6mds#QpNK{)I3PP!haZB$(|xO8>Fz;J!G!A>J9iv-pY6enF#j7w=cZDR4w zHqw8|x#tseEmgT90k!;ACy|nfp7oPRinD<bXl_iqC1bIk8aY$_t|U@BHJb;rOW;86 zn`tna2{Isc1!0=!xP$&DH|iU)K!o=<&HytmBQ-~cEMZiS9S+nfha<p2ZF3e9Hhz^M z{H-o@;buVI<dZy71<KiTtHzTe=0|*DJOF|oBo7IXc+X^`n2A|<bJvB4T+ecLkTl|V zz+*tr+9DLz8?A&tRLu*0Ca~$rIIVWVe3rHbEdF{u8E{CnQm>a|`!Lmp^ii)@-BU9> zHHt06v6dH8x@Cx8a1-;)t-E)>ybInWtw=_FBF-l_9a6N$?HiweVJp~k_7gEaoZBVM zTX!F#QP|25+LN3}(K>syl)HEe<1R^i3QDPrc5dq_{?HU{8|i6$-k{?-O|-+|z~dOV z=4SP810s%`e~yB<K3<S$3dDNC6?GA-AB;dIh=k~dk{}sf6y`{#ncFjt6ov~~*G>{| z_w>aIiJ4py|Ax#O<-XYEawf>Ag<=;eX27<@nhpwqK3<EKiKffIKWff{IJ6lZ3{KdB z8i@s+NMDpZuvp8THK%ep0JNq|2>piI#2_lQkO^jkA0hm{Tu4oY&Z=`z4f)9ox{ZC8 zaF@mOe9`?9A}JaHExs;!=Ua(GWAYoNrrlMV+$^;$?AE6DV;S+G%{b79_G&hj&yGG| zlEa)?Hs7eLdrI|d5MSHXm4^&XemAoVJzrqb(^TlNG@Hfn!2ZNt`?fmVjmI^k*v2jW zfnpXs+ym(rb@uGdss@vhzl4w~Vg@KA1?0lTDHYXSIN+#v#9sRZ&Z*gl;7N;aTtbK` zZUTnp#6s$cG7-ySDbCVLixM?)Z~e%0CQ?watRpT?^AMN@kR+#10m@3xp)+~RaZ5Wy zPx9R=*6v^_Gy_$YYFpu}8~5%Z?gVC{%U$&fVK}!wE2Jxd)gaOqLhvmkGaaF;%S<TZ zE<x!L3d5XrSKw_L_9C*bFYQ^hq2C5gtY+39eanO6f;S|FYj}CG^5}TEHaV(pyck&- zn9+H<F5#i{46=Y?650O+uUMe(2DB?RO1LNhGkkqdRB}l;tYpT+Ng%E97Bvu|&HLbz z38tH%FUJjC!8E^o=o$Yv+Tc5K;-Lt_S(bOXSHT6kE*fYG_qa0X<f8S%vOGiAEfe%U zMAr$8j$<GLPAHgVRT~`<xw*4c3U>CH)AvNyCwoj)a2FayFEXlJN~T`A5@=sW0Fc8} z4>a&Tm4tFJm1QD0<{}`0je`R}B}pQ25CJG4+3u+9{)^sitZ=K+sHvn0T;lsl4^mRL z5^DZ{^FCg-FNh);Cf+9A77d=_7`s-|^X|actw?COz;oKSFEz`TnkBrstKeNiRuvtK zv))7fa+O?bpo#Qeiv_pOFljE37Mx78n#wTwjAV;Ks`Wa69Ed7+m(%^*@Uc{I;OZES z1Wa;aT^d7BB!-tdnU;fiKHo|6Kw*FHS*|yVRe=#Y4warF=<jgjDA788pjkTi<g`g) z2?=IkvC5rOkC>x!A1&OJ-L!`s0fii`Y8r#+(D4C|7mX$@66Yg?+)cfyD`}>%BL7ka z(6r1;tbdhLkK@s^;%Ek)Dv#z0A0f04rxuTN;0vwq@hC%B5qf~{*5362X?1Z3pn@EG zD09kIs7H{+rC=FNcoL5vK%OPP6r6t@vLgx{(bwU~;T^G%kra%yqaZ~AU|@KMjjw)p zSl`03ai(L;wd3*pwj{!l$l-hjnxrBUH%i#zg0Y6BcYa%K#R53ACWpmrVkOcR=G?ip zM40R!#FWvr6U>PZl^$e#G1v*k5DQl-4vk@uv9T?MrU3aKt3Xcs;nKG-mZ$|3Q^;fe zgsz~N8&DRw5Dk)5?=Wgv0qv;?iS%wdc;QOZq&y_OYtCI<GSz7;<d+F@`wP)<)wL-C zo6G~60n<jU7;~1n+&8R8$kOa2Gg`{wry+_w9dR}_QNTYnQL(n~nNzEilIxIz0JYAt zekBg;O{^Lo*=$@~q~rwCByrGHWVw^Mf>Rs@Al@``Ax4@j7oVLw{A}nPZA9wWyb~!# z&dY+rVumwLkZ#Hu#X0TqlCsY2#ppPZz8f;A6BZkt4hE!#4)1T))|z!I9P~C(i;<FN zet*1Lx^rCG#;RRQ=un$Col1w041T9xt)dS+w^ycw8MGA4t8<b*OC**tg01fn(C|Or zG9jZv6oivj#n#tZ@9q~J1XxrTb7!X8iXsBAM+4!+>P_aee%@~IRAP6(Scvb#3%pWT zM>qgK3QV#O^jGb1)ZAwzxIl|kem}sHAU;L?BbLG6;cxbhA5A$D0=d|y1%zucJ1IgL zHs*w*<kxrPy!m-Ozk0rIzE|N&pJ!}y{)^`7e2fz0+e!mAN(`Hd5MfN>-sNm234jv@ zy4U#8hy$OveHiwc%&QEh+VqQ1Jc@-+<PxLSDI><1u@IxasBP-$*Jk)&IF6dZm0X6E zwhils@1l>2=r*y?2(*NClt2#Q6!D5;aV11;6=yfFLA${-E;p<x*iMqQp>GQ~aWzAb zQdVx78;1JgGjG8Pp+iB1PBKBgW5&jVCE$~=lY?b;1CeeC!y=~0yab$pAu?NoNI2~* zoU=|0=CHG}Bs4I9Y(;j&jUNb9@Ps;U1lE`jtT4fb8VH&QZis^G`z{ji<~nUe*tL{* ztIf0+rZ~t^>a`Yv%s6EsMED%XUPcrb%7T&&E+$%pS`p6Kb2&`cf-YSbJYRh%>CRv> zJ}hAotW8c<*9p-JEG^FPC&K$oRF^l{uH9r|j$lOwN`j2I-tOu<O^7R7R&_VeE*Lgt z;4m33WKGDfz^j)Bj>v|!Xzx?TCW?EAU~|2x{L}|78UHy008WRtVjBIxY!{kEHUVUg zEFC5;#bjnTSTaauj=eWIOcK~e1^{yPkv0Hnz$4BObx9iJ0%MvUV~Uq&GKFS-M?A4p zpOBo)k+~Oizt-%n#6%=#6D>L~RNSuN>&;Gl#}z`2W~Y4ba3K#2qEE+YVs!<;82whA z5z;uxMiRB|<4q@@;Uy(j{E-m&m+CnQx`WMV7hT)Jh`wu)--sGMTp)3;gYE>lOk{7; z5CF_xwXW;IN{Mk&0Y=eLJ_X)4JdyU!Pi0Zx!6J^wU`Rgl4~K6ME@%bRPQ}{P7C-CM z?Se?{O-L-1xI+ih;E-ZGI5dii9uGlCktZz|y%D0x_;ewp(1mNl3{YDFs`N){o!Y^F z0ydXEszC}5j^Cn;NEjRgrbR~jiPRk2obQ^mLdn@Eic=jTC71MC)@zl$Zh#_#5)>t; zPn$(N2(1yf+FY!n!4$Vr2wA8$1Q|ar+lPmcmCV0O{c-#wv2QAgrs0MixtT?1sZ#)j z-Vl>(f`N#01J<SF1}9d4;w});r6LYz;2|~%Cl+MfPoWfI9f4;mF9V;19AJv4E_CNi zPLs4{=5CHy{-P0)B6s5kkz&|prUwM6Dy1;3`PM`VoKVh<ho(ze(q41jvbo=8+NJCu z#kp0JAbR)`c|Ihv4$YXwg$RYzrpFPkPZWCJzQ4z<nFLUaE2EYgf;~9W1e7B|oCBRq z4u`6PI-ry@BqqtEK=ZMiLKw_$JD_DOO7vU=qxzObG{a>+dI16r@x<ytz%eX67fUO0 zPrKl{;s^lsK$Yi$P~ZRxbi-*@x9Jc`iPXRFKenRqd><iV&HaI=3Hf?Vb`mL#Y`|44 z8u)}imX1|7f*nFNn*b(=o=!|1hg)!o9ey$3u|1lg;!&)=Sf7P$8FxF7KykQ9U?ovp zVX$Zni9*ZWr@9yuM4*q9e9TJ4Znc2hq?{Mn(1DpFJ{GD1l58T1Nt>1=Avcmy3}676 zh8rI0pH!GevqXXC#TcR~Dq4MF^e@)?;&Zx(x;0YH4=`lupQ;(n3IO16q)4R<C1z%M zP873c1ha!DCFo?0!m48xhj4l6MiTLo9Sj4)MV<ge1)F2IF9Ly<ace`z&Qr0twAEZ2 z9Di(Z#(gIi+>3N-hK)|>JsSyvLpm+Qs?xj}tXTlC-nlge$(Mf}TYScH;dtR0`ciOo zrsI(U)WjIcs|W21NzRjqlQ~HMMm}rB%}a|hH`4ZvnT+EK4qQQG*jk=7ywv07474Z= z6iRDu;$H|pE6>Rx6ju0R*f%bkz5^#da~DuaKSG#I-{9UAbkl-YTFR`%Rfi`i9)~o! z()3AgQZ0UxoQT*PA;n#vT4oqc8ef&BC>^wbycFW5AtLE($wMM1PDY8=<QG7Ijf1fH zVA7d%Gf0SdQ3vI?v>Fp4ni^iGa`Xw?NYId31NN8-d$tVxIDEkfaU9ojF(7z?7}9oc zO`Ml__W=Jg8(~<oytUCXK^fv9xo3U=o{MImKlD6_V9_<@6`N&j<PgcreIGpGyW2?w zX6uA8rdF97bIMITi-u0^IQ30`_R5Z0+9@TlLkdQr_|1QZRB*Zqy7?Zl1i_GAA?{tI z(mj-VyoMIZ*Kq!%3(9hm*FWn!XqD&&c_v7}%sj%|&`#jX#(Lpl8rZN7>)Nb0<Oj8y zbp%h$OY~$~64HKPS}-o{PLLZb)lj5w%1HvX7QEd)cF^-A4IE1Ha~@m;zZN|myay%8 z>+Uw|34NWzQR<5)iMNmtEvNP;ju8Dq8a05cV~wwFv3+7t=+LNTYdB|r4TlJR%=hfF zJ^sA_eZ+nScE%=jp!C0i5h-EB?ExE>5{!wW1=901Jh9Jt!7TP^)<D4{-jb?A(9e-m zj%utOLpSUiO$&(&Qe0mt%lVPxW!)gf_l;bvK(dq#u^Ki(PqjM7HL|h>`wV;G+iUUr zLusC1yHh*9VH!w!=<bRi4-<%dY`i8FTg3(|;gBVJf-nHV7Du?FC}3crh3X<FKH$|X zUZw`GZ`c(I*ZnSRyBym@^n8j-fV*yJG|{<LTOD|d-xl`@K%ZCB&ce%efm1Z&;Utkl z7`OoFzr}(6$x`atbQB1K^D(v&@QjHj3|X9I34INxKl7EVe2-^*9`*({Ov1FqiSZjm zjPN{DlA9=;opl$}K8Q92jz9qo=xgvfcFD4L4xbPYK9(JX9lwM_hIRm;yh(wZOk>Kr zo4_gJEQtZLPlE0skB1l6hNHUNfgNq$O-foK2IUVgR}=#q%UX_jPPZk~juQ&oYc4Ej zDDtlI-XtuLQyS11fuZ=BKs8Xy#j(;P90#=s>O~JU2lhsuiXARYB#=oetnaNggLboy zmBjf>wv9LdnPD^H?%oKiqech%9P%|FxgTLzP<1Hb$g5+viN=LSF;S4hAZYBGmZ&^W zrzLH~TYV5TxDBTOx8dmPoasRpFL4v^Q`KEg*h~qXf`DNe$zhL7+=Yt<%Rxd1i1^40 zL}}Y{YG9h<r7hhVJ^l-)N;SFXF~U1)i3NlG<RJ5TG7aGTP}acbkWE}tx;~<x!@c5` z7F^XdW6ZL69^(KqO$!{(%`fh$qvo?)H^01l<NlX-DLpZ=nDnr$@lxCl@D?|p*uUed zZSrIoBHk;6LCoA-rNWPZEY~b)fTNXdfpN>Jx&;sKqDsMX7<zPL3wE<7AhWJ?)yCrC zAb4sHDISSObL4g>{FynJQ?Z&jp#m%4;|?k>xz&;Ls*)n1{pn$$a*j>0LA?)~KClnc z5+x==({|&Cj5+$o@=x}J*RrezW}3;w(%X;AV%@>Z=cH#si%QppaulO^P>({Tbm1hk zf{3(q<+|a3`KN<US9Yvh%Xr>iGXPUemtuCXnImL4#;^R=s9kLpyA$~;b&Yd(&9%m} zc`k#jX7kiu+vc|R8qGCq{kPXP7XyKHX+6$&T6CwkEZVNj?)extGoQ9Cyer)FibW4D z62<QnN4Ug3B5?6<d$tE=9|8eP%+%MTKD^a>nG6YUoo+T@OOs2K<GMJ-B(CN|=rFUA zN1NC-<o9Y&uOUL}TA5Q`lNazpkh=x7e8VuRuLyWpOr%UK&)gy{5$E8f+!wlqoX;{g zsRUb1yz(09`UQw#z6)x}%iq^`!-y<|pIUa*gURY4uwFiY-M(}h_7zz6Ce)CHs=vh; zy{#*ZGY&;A(gp9XaDvQp+VJMKy~Xc)&L-}3FdSjnx)}<w;SM}$RNN%rEj3gRgBrSv z!zdyooV(6+j0;tpFPG2Xrc7?Z1Vyv#jV{hcvSmsIOohmun%Rf#VP|-KgD(l6l^`WW zi8gKCvV<#n-;t{-Et^^@d*<*`#w~eQA3242%{LYqxiG-S3&P5I3OA6joJb5wsYkz= zp2YG4a9}D3V2K2a=8*&o+CYZ7fH$v2ugWF}o~D5*REWP#7yOo^tXUwEV1i?<^Ry1Q z;_P{&;D9r($8QA#kkCmyV{GXT5alIBVpe(bnoSd7PyrwzRNsbvv^IaW{NcPDcsO6* z^vQY4+?bpP@xr!~GZY}q)a+Z1SH<1hF85FU1Sh=$T{vd}WlCoCa1T`#B?UXhnYnGO zh&HaH<(ZH1lgTa2qDs3EXXRzO%QIgxuK?#n>$(y`jA^_Wl-J5jLdqrVD$=z&*xMX} z9lLfr3cBNVTAtZbkmQJJ7rP-aFw@&Tl%=$UPOmRGoyYo?S7t$1LeA9ld$H*iFWign zB~e1>-cZb!yvQgzydT{%^Flo+$fBxow_Q=3I_LohTKGb-?DFuQ%r2bo>*~f%w}bhn ziG-PAC3kRUnF*qN$=)tsisc8ZJ8MwC*UIQr)G#}**nUgG#BnEhMIRv5%h{NliBlPu z%&j)R&DJ3%O1O<fdIfF?Bd}^5km8B42lJ2?s(5iK-(;W4ry@Cm%Nr7sFOsayV8^15 zsSb-&(lfZ@pGC^sT(@M1yzRt$TtS2fipBa9TEk9#@(f$X)uOn!cg=_a*9zzFksiYK zC|S{NKtMW%9?Hi0!xp%05lyirducp1fX8}`EanTAma9smg2jUF$>&h5|G}J@tJUO) zI-a><D(S()uF{B!aRVQ6yIl8V1LyOPxW&@7px7gBp9>3|yRrNd-$~iKDW@ok#5Oz^ zI0Cmfr`Fo63m{2pj<cPfX>;GqvtG$$qmEfc!<Fm<Q_EbcIjJib)(DigR!}w0nSs?Z zyh`k}!geB^r?wn$(iAegj$}32ea-RFRR)g49y~T>*&!J|&fCT<d&l%;$;B10cLp~3 z#fyN1`uxAtVohFvY%O~2kVoKGcwXVFu=5D#EOek?STTZ&gmcd7#W*m|;@T<Sau18f zJ&WO2tvy{kVd5B{muJbRK_xO_*oa9e(UOSgG>Er5eHub(W#Iu1Z^V9mn`^UX{LGf( z86W0D9?BtSqSMzMt5v$403>LO8(*QclCX9Rq<5%#8NHRF4u>Dn(M`J9gZ(eBw6sPk zU98`<slgg6HB}M(7-yhBn}$9j!jxbq-*{<y#49_od=;Dlv=%<snvyFBd4+Uv+SGJV z;Mg+NN7*W~(Cm`YW8Xzg<4`M+FFRb&IHg#*%tdb<bFhz8yp>{e1-nLZ>^{Ej22pbv zZxVf=?6xWFHntLxolB}kOMjC_Y#Z_>V)Lfqb#2+WHr7{`X8B$KelM8s#Rs$T=?Qkf z3AL`@eZKtn$LY;WEABzXrh~NBV#ca?%2SE^_MVvn`d;8ove~V7K)m*)v`p4v#_LVS zT@vKU`{Gcee70~6wUMqJQxgvVL3O&C%u!~s=OR^s9ySEIk*bMUC1k#10OIN}XH;?C zX(ikwMWpLXvbvLHEQy6j8*)|C>~HFke5{4-45>_B7Pf!RvvP9?CM<4qC>l$%rNMEz z1Qb41q?IEmR!l-(9;{K8qzN}vLwK(0NF8lh>ZsC{)Xl66k%K9RB#a6tD?d`2nUxvl zrpl{SiFFtl)kdqfX3lLjJH)l8c-k#G`8_NOxdD3gIgCjmW4ayLCY+Jgbp)J(_&8W8 z#uK_E7$UBfGA>Qt257-mab!}9>dhv$Wea<Q@@af7VES~um~$YDc)wM%`{NM3i2;Up zEJKAsSjf%XDtdJX8+$7)AyYVf5q3*-p}L~_(&W@%9tG2~Ntbf!@&l9qmDV7oeL^7? zyUG>i;j5H5-Xn%zt5pO?C}^uyh0Lcu&)^K811i{0gI*mREA7u;+MK`i2i~QhEM5BP z(xrQ5EJiLM8;!@VE}>;?skW_Xqg=m6!IK;=!8_19q`w+qjLK@Nn444<DA&?joV+;= zgE8;dVdv>;(wBkJczQTG7KEx+;0wjQxj9m(;9va;*l<l&Ae0poZ+dH#jr}zWCeo-X zw`Si13q0FdJV|zWq||Hl9KU(isVP8{;abUEW5gxBD5>;08J4^v4fjp%Ws4zCYAhr9 zyD=Nc3)|Svfp_=pf}Ri_gA+19Oeru-!oBw=!OyY#4ef3xGMcy_&UkSL53@}@O0ZKN zP%n&?+eUsOM{c;~0CXqSLOavqB5A+7!054k?$#n%1@l~*Z4+V;-tk+$1YuiflEaIK zZgvyMdb32zmmOIM^f?0VTyi{Se^go?ci>^4q1!b5GvitJE;X09Yx+_V1A}5O#T+J- zSW~s(j2t*PWV>uNAOP+X=(fvN6H4FaPOI+@;8OiDUTy;#QOco2Wa6Ssipx&D81F@g z)DuD@S~;tXPBWpBZZ!tHWhWn+UZIU2V^0<m@2C>SU+`SK$Sb>?H7wf-?}}cS-ujE) zQm6r3KY_mJ0=&tZLt+HAhwqsd90HO}#AjEvRb?eAVCkVDDRmz*+h}s&L93mJZzWme z;-@+;h+R%JmwJOnjM~Ihq8|3m4jjLbm{W3pmWlMd4ZPmcfu%V;a)w5_2zXt57d{lc ziGZm%wyF3WCT19>`i}twvomM2VBGXY>_*-v@jXYn7rg|iaXEYMIpsF@^ni5MDehvA zX<WyfkNfek1D<pg!@^e)$O^pTkYFoXKof1@=R)~Ps=>pW<1F(}*2zj+lAYzmnq6=K z+doc`;ic^nm-Z*_3f1G*Hh5ip=!`CPci>EFx!TGx)>CWTcIrkNEntidIf^0XEZg6K zr5w0D>CvK;@xF6C9n62Xn<$~&25n)ZG5lMXagf6ob0m#up`_o7h~SZ4(3YD)BT;E7 zE2f~_#-_MEjcKJ(7=#So=)w>%+&sxvb|I1NIN>5%<<>yN{_x@*F$rZdv_D(Q%rdgz zb~cOlrrmRxXn=I3h3riYOTu=M_r#bO0*VPBMs(W-(Tn|=d~BP-WBXGcAF5HtY7uG- zC|6FWk|yMR0tA}6S6LLW(wtEQ6a$Ex!D&Z6<ag)iKTr{1j*F3-NU%3$E}N%Yt&ne* zFb7XJ;RG^gCF%BYQHg{MaG(d4S(7q%!D&4Ln9h5na1%anhDE6GCG<U1H=EPhS<wIh zZAX7Wu|ok|z6);yo*Q^^Lx1skp+xrp$zcRR74Gw0L2RM$E>u&ivRL$&-Yv|h9@uHa zeg#Dv_?x&%cmkCi%YY?XwACH;Av%SiG3c)qp@0ycu3BSnnP!r{K6o$8Ze{CUii4HE zRS?(sm24>VAzzwpBBw3}ySl$<Z)*0Jy+RRjqO9(7CuUG&b!zm0lrQ*_BhV3rlH?F! zxgg!f4vwIgaGJzV)gYBbG!#QnC@i8oGCI80W~PL<OQ0dlz;2g%dMe|vVa|)Qy<#H8 zc_2!MdF3KJXPa9_wFDZ?dqra<mYW$+KnMU~YiO&YMvwv62CQSq(jpI|`EC<Rm}c-C z7@pKXOT0^$lQ|9=k!T(=WPNKkg%in-q&GQq*aiSc6vO@piOS=}(5^S*%g1?EJWB|# zb~62qZjv<ng3a(XgtW>8G%QD}hi+mfY$r_lL_mYea{94WMz#Y-^|?Szs|W9?zz~qb zCeA&<j#0>C>`@7r(UX{Gq;av*oyv`KnV*9qhi@G}(W;FZ+ySk$<YiE4do|tgZ-u1c zw`dObsc+l(+nVJ;`brE<(V}!juO=tLfMsriM1q>RWeSul&8^$Epebf{%3_I-yqTg` zHRp(9{dCHo?m;+w{$+7<uZl(_7@=MBYITPj#W59$g8Ece{XnlpR|TZEt7*o#=V;<0 zM`D(~n|H2VzR(jPVx-hV@B>hZU;;h6wblk0NlZ!!Q<Egasw_?eLaMpG_?UlK5qwgH zVH6Q+C@))kg@JaoDB9zCNUUgim!`tTMjXtx0>2+6D%h8y+L;|}zy-7{Pe~4rz;cbI zsZuo8WHFiaBn15JFe`(AW6rRIn0KzspbUp0Z;HcjGr!EkmozD3wI?bhdX-9vYXKrr zGzGI1PTeuZts7stIfYF;wJ`&0B<9L)B^#Kt?<E<Fe~V?j2Ruz0ziY~g-8_eqi7oNF zAfg1?MmHzxlX5tIQSC4#&d6JTAeTpM1+diRCiszTUTGl9q=r3_GM#C@JZc&6BwRPc z4pavoN*o5qbNhDsm6B4Nj)oYf1o(gF$z4cxP%nt4O4%$$=98$G1W9Vi%rGWpOE%cw zCrAB#augH%kQ_DF?%25f_hsIE){JN;b9;fjo{lePdQRq04T(C~cJO_z-a~NTV^R;w z8s8HeO={mb^9cT7<k4WKEAo}1Hg<xloeSZ$)5R_|z8E{X6O^j5v4q=S2xnj-0d5|a zaD$ai<0@$tq4Z%H5NYpvDq!>?aVC4t@Zo_W%k0wML;k;Iie^cXaI-i%(v)t<Ub!HV zYspNTa>fB)iMd=ZTsH5wGdCok(qhY{JLpquQzjE0v~}#;;4UpruEg!tqJmo-nQ4+= zi%e9g{-Q=9nM}oT%IuP<H7bhW!9edMD;}!1P$C)#K{g*XeH?mVEwT|W|1^X8l*b|v zHo`!jS{CzBz|nmNaGNp#n9l^pWX=ImOo=WgpdrQK-W{BFN=)a>R2XHEu$U=wgNH*v z_Q8`e55<NrkYcjA9Uzk9R`(=>fnhl;*nlhEY#g}%HD6eSqj|y|XtoJY7~8ilq=end zjh%-MLg$4w)5uO5zK5gv^LD+pBRCcuQg2qYW5x#nBR)5P7nT`{{iJ#u#%mkKM@sZ# zd9S67`^4#W91Q>90o7q6V-U&hc*TgkF^L`rz#*?^a@L_ikTtcLEfZIRej>E0&FUxL zg(7K)Lu~^#t1epg-RA5-GbWy_`zQ`e!R<3rudy9+{3|nubtu~LVGoA@Pqk&RVBTS- z-k3v_Z3_#(wGFyGv~X<25A|ji{K^K^JyshkAt%{$m9K2bn%f|Oup|j`L+lsaFzq1C zX6OaN1V~qvI;BUrWLU)h;j9Pe`?-ACT<}Onc51zCB2e(sDJ?3aG{0lJ!U?b8e6qwd zp$1HReV2N!4Bs4j$%qyL`p^U8^%+w{j8Qr+tH|fZA~Tzk)obW{hEm(WtFA>2XFY;N zPwt0sfAvw}T|?kDVh!XD-f?km>wHykm2;w*OHuVwv)XCk^Af^E^v5BXI5-;XL3a?& z8c<Yl*yija5lg}$j7xzYL&20&?Byo?0K@x@%|28gn_3v7QdPiiEXFuvIXfg@3^Ptf z6U;5{P2m3HFz~S(n7k{Q?S`Rg(wZ_mm4Co>?BPoAKGPs&LSkT~M%PuW83~66F5m&% zBa4Du&_x+Z@|vSzQ}+%t(BY~V2<4n$Ge&OE0uZ8zDd8e!%J3<&ui&2L(zV=ip{fNi zC_RkTn6}iPiUqzIuTU}^%M~@R9J1L@TXgUz_5(-7RXT#o+(5G)Jhn6vzfF>y6E&m@ z!cwv&mbY4n4}fs%U4417kbz~kJl+akKshqDxRricw^B2F;fdT5(*Bk5hvh3+I<l8% zojV{q;l_gXk>eC<q;SxY#UnAW_rN-|<2_6!ckSmvu(5FH0gLoDGkjV_3vyUO!aX4S zpfyG+=N=SUW^r~AO#eNla!%{WQgeI>toJ@j&VBg4VIFZ;2K4XkE;+>F%EIj8?9~To zapfw0EieRTha4lv4Ee5Z0J#m>S=8TVR5o#;aPywGaAg5!9m;$zX#CG6D^O!!DGJ_Z zQ4HrE(I+el12-*FJH&dirG<hQu9y{PHs&SO0=5=I_@MG<KfH3~y$|2N+PISXyXJrR z-Vf_n-hcnv2k+y*)vF61zNhyV>mLLkG#0OXaP@-){QKbt?=N1tc9o?Tue{&juWRpJ zd;c1K<9TrH>NT`*ZSmSw)cEj&0A=4t{SU9b_rbMmizxBl2k&29Tnw&Vx%MG?{2|I; zT?{Hm6F4hP-ofAP+ofat#NU1XSIXWgmHJcWAlk}=Y{d(&WipvC$B!|7H2Cp*{3!C{ z0zN9!))Ubm!_K$xA@3cDUu0bw=hn2!i5sa6XO%Ofw%UYmu<o(sMw{?k+zPMo<3m0? z5x?I^PEih*cr$+=6#j)I`-Hbx^c~(EXV4`);Po?p`~!ac6Mp;)e*7gr{uMqdCqtzt zyMV*L;G<c76!`Iv`0+>l_~-n{r$0{MvOjI#)tJRqL==pVO=pkczv=8WekK0PO`jS6 zKPJzf$)3{xa`~C*$<yPfk7cs*-+##e!ql_1a?|6}AE1T&_`jb#idwn+kEV}|XYpT- GCI3IRfNa<R literal 0 HcmV?d00001 diff --git a/examples/example_flat/students/cs101flat/homework1.py b/examples/example_flat/students/cs101flat/homework1.py new file mode 100644 index 0000000..3543f1b --- /dev/null +++ b/examples/example_flat/students/cs101flat/homework1.py @@ -0,0 +1,21 @@ +""" +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). + """ + # TODO: 1 lines missing. + raise NotImplementedError("Implement function body") + +def add(a,b): + """ Given two numbers `a` and `b` this function should simply return their sum: + > add(a,b) = a+b """ + # TODO: 1 lines missing. + raise NotImplementedError("Implement function body") + +if __name__ == "__main__": + # Problem 1: Write a function which add two numbers + print(f"Your result of 2 + 2 = {add(2,2)}") + print(f"Reversing a small list", reverse_list([2,3,5,7])) diff --git a/examples/example_flat/students/cs101flat/report1flat.py b/examples/example_flat/students/cs101flat/report1flat.py new file mode 100644 index 0000000..4a268f7 --- /dev/null +++ b/examples/example_flat/students/cs101flat/report1flat.py @@ -0,0 +1,27 @@ +""" +Example student code. This file is automatically generated from the files in the instructor-directory +""" +from src.unitgrade2.unitgrade2 import Report +from src.unitgrade2 import evaluate_report_student +from homework1 import reverse_list, add +import unittest + +class Week1(unittest.TestCase): + def test_add(self): + self.assertEqual(add(2,2), 4) + self.assertEqual(add(-100, 5), -95) + + def test_reverse(self): + self.assertEqual(reverse_list([1,2,3]), [3,2,1]) + + +import homework1 +class Report1Flat(Report): + title = "CS 101 Report 1" + questions = [(Week1, 10)] # Include a single question for 10 credits. + pack_imports = [homework1] + +if __name__ == "__main__": + # Uncomment to simply run everything as a unittest: + # unittest.main(verbosity=2) + evaluate_report_student(Report1Flat()) diff --git a/examples/example_flat/students/cs101flat/report1flat_grade.py b/examples/example_flat/students/cs101flat/report1flat_grade.py new file mode 100644 index 0000000..65db51b --- /dev/null +++ b/examples/example_flat/students/cs101flat/report1flat_grade.py @@ -0,0 +1,351 @@ +""" +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 +import pyfiglet +import unittest +# from unitgrade2.unitgrade2 import MySuite + +import inspect +import os +import argparse +import sys +import time +import threading # don't import Thread bc. of minify issue. +import tqdm # don't do from tqdm import tqdm because of minify-issue + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + print(b + " v" + __version__) + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + nL = 80 + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + # q = q() + # q_hidden = False + # q_hidden = issubclass(q.__class__, Hidden) + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + # 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. + UTextResult.number = n + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + 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 = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f"*** Question q{n+1}" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.join(output_dir, token) + with open(token, 'wb') as f: + pickle.dump(results, f) + + 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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\nimport collections\nimport re\nimport threading\nimport tqdm\nimport time\nimport pickle\nimport os\nfrom io import StringIO\nfrom unittest.runner import _WritelnDecorator\nimport inspect\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, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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\nclass Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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\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, too many numbers!", len(all))\n return all\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# class MySuite(unittest.suite.TestSuite): # Not sure we need this one anymore.\n# raise Exception("no suite")\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\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 number = -1 # HAcky way to set question number.\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\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="")\n else:\n print( dot_parts, end="")\n\n if tsecs >= 0.1:\n state += " (" + str(tsecs) + " seconds)"\n print(state)\n\n\n def startTest(self, test):\n # super().startTest(test)\n j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n # item_title = item_title.split("\\n")[0]\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n # test.countTestCases()\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 = 1\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 if self.show_progress_bar:\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\n\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\n# def wrapper(foo):\n# def magic(self):\n# # s = "-".join(map(lambda x: x.__name__, self.__class__.mro()))\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", foo.__name__, _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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n def capture(self):\n return Capturing2(stdout=self._stdout)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n self._stdout = sys.stdout\n import io\n sys.stdout = io.StringIO()\n super().setUp()\n # print("Setting up...")\n\n def _callTearDown(self):\n sys.stdout = self._stdout\n super().tearDown()\n # print("asdfsfd")\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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# from unitgrade2.unitgrade2 import MySuite\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 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\n\nimport homework1\nclass Report1Flat(Report):\n title = "CS 101 Report 1"\n questions = [(Week1, 10)] # Include a single question for 10 credits.\n pack_imports = [homework1]' +report1_payload = '8004953f000000000000007d948c055765656b31947d948c2c6e6f20636163686520736565205f73657475705f616e737765727320696e20756e69746772616465322e7079948873732e' +name="Report1Flat" + +report = source_instantiate(name, report1_source, report1_payload) +output_dir = os.path.dirname(__file__) +gather_upload_to_campusnet(report, output_dir) diff --git a/examples/example_framework/instructor/cs102/.coverage b/examples/example_framework/instructor/cs102/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..a93b4d7e94a84f8ad080beeafaa98b3784e23e91 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/examples/example_framework/instructor/cs102/Report2_handin_10_of_18.token b/examples/example_framework/instructor/cs102/Report2_handin_10_of_18.token deleted file mode 100644 index 3dc602acc93517ba2a3211a3f926b2dfe0cf8c90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80985 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 diff --git a/examples/example_framework/instructor/cs102/Report2_handin_13_of_18.token b/examples/example_framework/instructor/cs102/Report2_handin_13_of_18.token new file mode 100644 index 0000000000000000000000000000000000000000..303223eab586229ad1fd65b48a7b96322ded0274 GIT binary patch literal 60507 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 literal 0 HcmV?d00001 diff --git a/examples/example_framework/instructor/cs102/Report2_handin_18_of_18.token b/examples/example_framework/instructor/cs102/Report2_handin_18_of_18.token index 01a473c0bc4e8c0b536bb6c2b01d901ca33c5689..fe0c87cc2b34d3770b07ece28291836a66a6d7ed 100644 GIT binary patch delta 9214 zcmbU{3v^WFncTcd2(P?JAb%!-%s?{EOp?hYHpo*%i$sElc9S^VxpOBOn9L3L-bsvE z2zqoaE}->8?)m`roUVJUY|%49kQR!<;#)vyE!v{AXU}?S+jZSt&#vfpzwf{IP9BK$ zbYOBH|NnpQ=l{>a&oZ9=O?u|`j7RqV>4%vo%Rm0Zlqv1Eq-PXPp-)$=vg7US=#JHC zQ>Lu`;qShh*4MA7I#aa#+`%xsyrRIUY!>q1H^MylN-*XPh8H=mbGxsTG_S|!sAFMS z(}Urt+QtXq$7xEsCsvuPNbk&@{kIz&9bY{BVe^}prc9x4@f72RfKUog=MTWzWhLO5 zUXiXx`;wQYzdIGOiwcrYS3Z#q;Vbp9t9BZ!x>B6Y7BR%3!IM{trsHi_+~La4W%#Lv zy;myXh1!SUHs^j2zAA#V?kp&A#go5x?GYfO&T!B)`rN)=zEfW(Yl)cdj(?Z6F;ylG z@g5$nTMq@_c^=fZ&kJu$ASY2J&W7WQyjz*B=wb|p7ca@SrW+s57AoMq#SQse_zV|Y zPg9Jx0--eNz2S&Z8SYo3aorh-s+x{y9DJPhEBI1lqHa_c2^|G>CjBO9EGBAtn_(0Q zp6lw^MB<Q9i&bX9bu3r<`eU*#hb?I9vfU;Tux9Cr<g8`Uv`%wLG{SmCP1JSO*;mJE zgTY8NCI^GHE+)pqEUxI2hWkammxfzWuxvFqfM~pjiA;+UMn=n9liOEpOP|9KHUa2U z!U=?l<76#zOp{^y&DF*&6+%JMdD92kwbd5c4wllH{ie;aFmCJ7P-=uZu(&--s5W-j zpoD|%x6B~vm`popvGLJDl<}$dj<mF3ogvl=Wh?6@5s=cVMNu#$YI0DOW1=1%kew-E zT5{)?Y68^><u}iUvmIHmqhn3Isq3J{oYpT((MVK67)rdh8+AH?YuM6J3cu{wE3jmI zJ*4F(;y8@+<?zEB+(LD-e#4clbce$Me{+Wep1s2h|8~a=L4lz=s?(whJaEhpFFacX zpI)ANi;fmLsEYkvaZ8?(5>r&MPwa|ERHcvEkIWjrQBh@UD3XXvx}wB1YYgr>(LB$Z zmWW4n9j!VA7M%^(!NC)^kJupHQJmxy6FP#_WW8;@nvh*&2J{MD1>b3GNJ*#+?{u^Z zH06jct4tn{)gh)N^!@}Q=!%Es2<tJGrny?X;$u3DIEIN}_o{U(SKhjHSljAwbwOS* zR$(qexQTuWA;Hj4P(qK;oXhJNJQVbns5qp?8P_FFKo=1?13%cfVj9kq(eiE-(e#q9 zY%CPuf*8ot^e~dtcB>MXAvffLH$r)Ed1E0ogkrEiw4{d2SCeBAchH>4+S(XR<`!&? zz(*U4l79>d0{l@r2PeW06fYP1kq~6w=S<zYT48y3VNNU>mo>Bu7z!__GPx$s^*eb8 z=)#EQ-h>gr$>+HMw$@bQm}_h%rv~*e$HV3%crsjxSnt`ujTHaz8K$5S5w4!JIG*SW z$tn?H-H|%F;?-;kmu8==_bOp}=8p`7?FF!S+tjpb2H)T2np17#VdF(g^WmOt3p1<v z-GOZ$n*Zx<^XVdtl+k6*NEKbKAK6ToXGYqlcg0N{>v)KTdH3C$;q`eK{OG>5xh&{n z(Kr&JLH|Q#@X_v>F#qp9hTMHK7P$LGX|v_ITBoPGj&)(sGNV!#h9#3>$h?0!<eV&k z{QDg_W<=|XCl}wpS%Bw$@LD>;hoA4Rg1tLtz>jteK;6zbBzL;u*zRhm*i{JE?K%Np z?phY8V}nr)cankzEcVE4+=6TzBnvc=2{UZ;2X}j6$<fSWcW}Zm%AK4BF27VT%Z(w2 z2gEk6H6(Az?&Cr&pRjdIZGQnn$<-J*k&yn-KPQE~H9|V(MOa`g<k#DbK{#!63Tm?I zAAT*srT;C><N+JJeT5k$CfI(q#Jo8ds>q`^NHQNNdQ28PaMxo|5cU^lkslz%vmd`A zz)UF<-1~YfaHjcsjCgL2eEAFe?#Urv;t${1|M&2x{Z-klo_R5mBnzI*7e**3j8BVo zJmXQSt7B;Ms)+C%nBfTmcUmkc6kQwYYwN0Ych%OJkq;jRd!xEMW(p`kT4H;zr6E$D z`I>1%np@KgC>W40&`34YxTBspQUvWUW@Y2EF6uHAA95~5q-bs`hPpQ9G$~*US)X{P z)5G&}J&Vcc!<emI$RFMFEbnqzj$91~ldh5R`F{~HV!}jL<Keaxn?#|#jE5bIdOKlW zYaN%nBynk5>xm$?sLd2*m(yYsg`;b-q_M?q%;RoKfhQrDEN?pGDm95E2$uLQx*+So zt<Z7crZh={|M-^zcy8ki_{o81lVv~Ig=TRuA2vR}7+!gPKHPh7w%~%H<o-Oe0gEtT zFZv3`Y-J2(@u8ZsiSJW<vNjlgvAlw$%E_}utZ6Cv!tp1n;Pqq)`d<cosAa*4Bc=0G z0451|f}o)C=zLmH_pN4IwgSraB|;N;j}~Ow0t=Lqu~?#-h~b$-Zusv*0eI|i8|03b zLe;1*A9a8R+ZWWMy6J!`su?ChW~XEp!SpT<D(Yr7*ek0t>yh;pvL>m~eqMz3i9?ut zdG;g!bDN@rhYtrpA1!M}<J>Yo>Yg8UvH6;V&1cRDRW45m7$dw`JCky#2+-Cg;&5QJ z5l%l*#zomalnLG=n`hfRagQN~al!G6)hJ;n?0m5RK0k5;91GR8riRf)qKdmir+m12 ztrJbrB#wMt)0O`9h|Ec7>loq1Jo$@oeX*A4msO_=ozj}Y$DrY*ErJ*R@ukaf?C3KP z`st03_Q`Dc==BUZ{!<-Eb&k_`Cba#m()e_~kPUDCY+4293~Of7A8nOzfxC<crwUE^ zO=ecd8WM?wcaQZHYq~0neUmwWGY87y&+lZzgU7!}roB8@fVJmaA^FM^u;%2P8eZ7Q zda#%3cqbaA<xr?%3Mf24!rk3vBV_ElIy5dA@ql9&ckZwpma#uT2xE@GXK0!m(?(2` zWM@|`{llWsys5+JNHRmSAVCub`fyT^i%ti>YsCoK-8GUL-h@N(ez&OhPz38l@G)hO zOFt6DDx=rMB4~=el#TiN?(v{;Eq~d$se25eMJ9o=K`i275MiO2wbE41KN831I>suW z&KO4*BXqc4>yKf<$;YuCFq?^(%_3eY3R8Y{I$9mB@s#}~VJ@&x6OPFOvs_8j&<@0* znlh`o2#H~nS!yyh3?nWOPu>7ep8QwN%vE`wXJJz+V~L)ZkC<K7_sUjl$r73z#@4Ji zidDJBs8Fusf3fY7HV^S8OzTw=F>K^wgW{0JLNZUYZni>+ch%}lt&n3_T*onpU{Pse zBs-LNAqszfuL$z@%`CTySj>wiO!9T>9SC2RkRKJ;332?`|GhHTE(l5%Sh7hKwmyu) zyoly5Wr1_SOZUx#r%uhoJmz5Q%*do8$6M^SnL8euD>mueM)8jY87n?_Fc?;(U=Th! z&EVrxGqX@E*hMD4IF&0v|7#_X|Ev6I<EtuA220??ndwDlJw*i}7HpCz^->i;kr~$B zlQm+Fy6qvf-QjU%7SWMvsLX0_j5r!JgN8DI>+EvSz1*q`$NSs(xu%Jlqj2T%JOrZO z&b@O?O+iVF#n#KBx<VO@L;K^!IlBEINQEO4?P^@SAUwR`qtZ7P4cXMU<Ksli`7A9S z?Z>EU&$1pTPPAD>vK1?a$b`oHghic7u+${XLP2^rnR)8K>R1r}+p{hEcVDC5Mn7A+ zy0nO3x%*^MOQ>>~??Px!uDw#rEENrFf4$coSJXbbaT;gJg_4=7f?Eu2jQDmEZm4>& zAYbZ@qQ_Wo`Mgq$AABnH6*P*#RE)72H<0b68AjhcAvc!~kqN1=@nSh_FU`rNLN9_* zR}90sdkgVZMxouwSs{u)rUrXNHH1sLYF8~?w`}REl^CAmu;jVodOppELzeX2k{s6* z72AujZ1!_?Y_lx)^TwLkAY2Zs)8a)XHIOdKBdI4JrK=^+l~iMt=3$eIQBvZRN;GPd z32R)zA~XhD{-=mIsz6bgv~<S0?EpwniE>LE)7+G^C=hT*3P!_D7vIdG$jsxzM1KO$ z(L&hxhgr~mPiF1JA-QuB9NHZr-^1=?9y)#uA8p;@6IZRJ22>cF(@TlyMUq64`x?|& z!zaL*l-WFVS4J6Z6QvYYjQ60V>sUQjJM~^OHk@5MvmSYMV9a#TBug@zx*0BW@E&#I zdy<fGqFN|~9q;5BSv5jw)p!?kPja;$N$Jywjv2WBf;6Sz)4N4D*hf}qx(lCcCcJT5 z0lv;nt+8&GWf9Y6k|m?mXlsp(S>h@~Hl|5ZCk~k6CFr%*i%!H$Z;ADB+1AEwL(V7V z!Iy8$tF|K?#!ss(w0+nPpDx&cx&jJM-((eGnV_Bi9^}4yBUHXsDTG0LwaN<Iu=mvq z5PLmjO?(sv&Xv!l7Hf^TS!U$zn5r;VQ~JX!ftx>|yio;dUlwCd%0+Tk%|RO<i&Ulw z@Fl)Z*!Sk#ER$h4`Q~Z}y|n@szcw3QdaDekzpZ3RxcP?;a_;jDaP~qDl$~i!K5*t> z8vO3vn}uq)6t00YFPFl>H}jzV{APf&Wh0$qnC!^6SspS`5Y<arTpncMsHD5u1{wRl zcmf0X5Nd+&F-3Ju(2|H*oej#E!v|#C9;PIZyoow7$k!6HeF<F*#q4^WkRelW{Cp|A zfBwjb;}&^{4M)RUxt=3R0(WCc+a#*%$F&3$91&Jj)3C;Nv-TK9P%JLc)WTRNDnm{e zZlR46pILk;Y8aD&g56&)ZrdT$80`w$8-rq8Z+ElRe3Y4cLiUDFM2yDJSdllYD#4BL zSd5B!)FW{xDXJ<<SeEcz^c2Z%c;`YC9=*^EN+=hW{^ITAj9<=5%Qtg7FKAjBIDhpH z)Lg7J@-v0$@SUSMJS2ni-c-2b;#>-bFnn<hH2CJgPHnnX?PpJ2hLsr?3(UG$U|cE} za;S+*!vgL2#WlP*qjD%1k>&78RaI1Ny|RuIXLTMXrC8DNLAzupeNg>!J69dqra~+a zS_L)MThx^>k_Vcwdtx<%9?HE_i^k$urpB>ax3P?CzQ&^>bf^)m({T$O0}rOq0TP}` z6cxV%tjgSA!2)`sjp88X0~u3RypY6Fm@4|I*tS0K>dR~zFOB$KR#z>>leB0AHlBBJ zS)zQ?mM;f(vI^=R%rTxX7c%pZk|?)39H2r912H91e23{`QdqY$N3MinhBr=O3PK~+ zPH)7qm@Ty!Vu~bDn#YEgwq`UXHh|XK%7&%bR`+n<Rw{_iqXg|b)BgCzGP)kFK}E=6 z^fOG~SoV`oQnyUD$2G!z1|>I4p0Sk;%~TS$t<thqhaE>@&FX1c%a?aX!`*fSosFi8 zIxt^$5`b=G!BISp1%~~!3EBiiahDXvlP@kOP&_-##S+Mlu#;e7+C{JnDo9N@M3ofR znA4t*9!nFlQPea555B>-GYD?!Og_~uVmU_*S`?!jW=JGs;{+;E>-B6nMjPmp%!TZM zo0_cR{^^*3SayP&Q6vLdbq7hFYxtJ%3Pr*&Dy}X_$5Fdqu&su7mJ|56YO!6-q&Tu# z4huw1DQ5~{bka>XHj4a(iE}DhH-EMboMOQ&i(NFUo2cDRDK^8lxKuZdoh)_J<5C}o zhPe$FqnlIDKPCrz`{C;%hV8{xtgx3+fZ^@vdX(6@sR`Vj!a@bNGpY_3EEctJL<@@% z=JjIlNJiCWZaHEIRuQu;cN^~*AcTXt`EWt54Z`69=Npb3(<MysO=DwMgR+_nTZ>L> zA&NEoeJ9kv|7o)PgQ~RT%1c%0nbj=dZHCC@yWqE%8~9o4v%mdhYVzLyJVxiNEtAh! zXV4id9kQBO>5%mx9<T;r*Oxtb$hsr>?_cc|9?27oN79AD=@ZW@A9+exZ`98ep8uzv zvjpL5k5i2&XA2X~Q`b&BOC80t)U)6A9Cb`EC~Mlryw*9YtxtP@HFE89RAWn#a0+HE zn+y9cJ4d$_3k%bNCLVIyu@hA~ogkeDgLdmpnPJids(JZ@Z#-3n?JLSgUo92-zUgSy zI9MxGl7Eci`D%L3gtJvkRrYx)KU=ll(b+09&sBT!$s(<74~NURv{0xSEt@Z_{_Bra zZMoW}PFH@Uda~T?6~6Wu)wo<Cl)<TvS%$+YJSwo!V|W>Vc52+dNN~fV!&9MYgKIRo zNcgT`Os^MK85`<_$6$GOIefOcaF(N2j>Qy)8>rY4Hu{jGN*|sh@5?Tvn+ot`XN+dM zh3}_>GjHkWMz8RcVAMAVfi$_r=xGo(7(Z?jN{sqOVV*JEC@eHSz`L@5<Y{bf^tbrK z{-&13#^#2I><NTCk$@3s5>(^&O~Sy4*B|iu0{)gpoa}G(_#6CQ{03+Nz6trm%`Hul zW}m;Q*^ggeGmT4pti|tdZt~0i21F6Te^PS;e=jyg8UubW&O|8w#-;{b=WS_h!BJmJ z0Ph+@9v^<`8W;&Qw>0{_jdF9?0%|QqbchHM5Fw%m|I00YUqhqR6!v*q8a&MnM6};W z>*=lu*ZTql(gKYnBWo%4@IHXw7W_&nGQ5o=CM^x)$?yk!gfAeAKFQzcZEW^NJd!`q za7~mAh>|$+ehZW-@=T03o1EJyLvlb8{mrN+R8+ttUK3?XfMY0)8L0D8B*TNWqO$z~ zk4!obHH7>PlJU4-cs+l@k7JB2&4Q;Af8Ek?{<A|rzZHKkL*Lo_S2X6I!*>rDCz^#D zg#X$b5GpIL{{73?+{1TYFct>{SK9ec3^gD;`aS-Gnl96GeD40CJv+b2jrMf!aqi6I ZYxeB4exl;rBk#<h6Y@RwNJ2}v{~s1;2W|iW literal 80175 zcmeIbTW?%hmL8bZQ|;<WPmT+XMq|(qg;OEO3`%71qFbh_nUyM~vQj8zW|gSw>NY7v z1|v?8kqkxz;{-)9)dFT<V1NPgG5ZJjFBtF`1_m(ZFWB$)lmCF<jhXLTm%aD71W9%E z*gZ2HRVhZCvoC9}z4qE`uiO5gfA}~5$9wvD`+fVnKU_=~z47<I`r}{y@&Ee$cRw2B zi{5Ble2-VZdXMjaaQ^+T-v928X49e=9gK7NF+D(`Nj~6@KO8Oc6P8CyqXi0l_mdY# zc{Uu)i$!*NmKTfBbdn9H^Xxp&kF%}L_h-NQ0UAE~N5A@`qaWeFKf-_i{oC)4{_%JJ z<8I!cP6pX*K1EL_-=Cd*_oHHZHt*+bU%~m&x1-r`gl>NEFCP5hy<h+8zx&~z$XNc< z{-5psmw)#ke*C9@@ZNj;@1Njlzu4N`-WcSw@${lIyBN-=Cz(9!%=6iFzSz!2C;Xc| z*PkD}FGbEKqs8I8H^{rQ`RG+|k#Bd7MuR~<>GtP2es<Y^;dP6AaW?B}dA{gs+gG-A z*jpUs^X?gd>kYiI(!=gz+U@sFW@p7DUs!v^WHg)Q^A7)Z2cx-H^xq%6KN@D;?xc5; zce~lnPS)ynPcT>AZtLL(?|<-qhR^a&+J=bs>Z%r9GB_`{mYs|yqv6HQXT5QeuVvGN z;aSlKiu^(TI`5zPPjAyeZ|l@@6g#a>XG3PDwU!M)GT|Yf7mG9Ceo=%1{c+x#bXjaP z>5;mm^o#i!Ajn_$$0)wDx6<v-F8aOx5%93GmaW|CET+f#gkJ+eth@wBk%YESezN=T z|MS24w_k#M_}?D~5;~fm<mc1*@fN6PkPowY{wkjrd3QW27VVP@{#kvP-T3KHvDRv} z^vf?tukuOO%UB>=(Sj?ey*Qgs3V#lgFYtaeIn1WR3`CI?n7u5=3gED_(Zu`9rt<;j zK6}zR>}UriK=!uQw%6{wT+NE3>DhQ-{bYN0@ObNG*6w+it5!R}sSpX6ZEJGi;{u94 zc%Osr4F>Jr+CgQYnT~OBKFua)CkH@F_I2;;tcPX%^}*K|&Zx+SXOq5g0RybSvKwDG zd>Aq4xHvljfs#Bw4n1W%S?~5iRs|E!2-9VR&US8Ozg9kyZ9U9>HwT*n#setlN2C4` zd&&~f6#%UI$)Y`M{S{abfGp0&3(m}Tb{qfgWN(Ow?X~UIw=JV+ukc*Rf)m-xij&@W zEEokzm!}Dnw03Xp!AmZ(NPR#2=Reu~kKc^f|K*R~dyoJ9I8dK4E9YQ=J#Ba5SLXw| zfUC?WDcL>B$6z2u;C=b4-uR4MO}pvZa=WC7Q1!Pjum&IZxbkJCt!3boIp?N7?iEG$ zJ23pMb||%K6O8%HF5e>Jl#}olWC%ohk&lPo2xpiLeLC$I3ryIdY%xM^C;8%NI*4ts zXu|u$Vs@ONX`Of$WFH*>pU)RhPS1Md$8Dc(n`{<3A1#iwWWP7VV$R#E8CKWyY+*xW zA%2uHTEO{unt_90Ro>lpvSER0cWt!Mv4ym^`Em`#lYXnE6}a`S&CRv!9?40^5s+qd z*N8YJR;~%*PQ)da%p)v_UM3rPwwMYxBKnPC5aP=)nbP@XC{4??ooxyDPCCCa!C||Z ze9D4Coa#@64kH#$E<o1g)xEJGVJ#aMDFxjSLR`^mE7*Ga(|K<JStXwonASoN5O@Yy z*<`xN=4X=(A`Ln9E#I7OW&J63>P0?}<GztSofL~4yE}{ar-M903rTIf!~O)0>2-x| zvaoR-6rYh62kcLX4lmOVruaPvmF4T!1i3yLR*|i*XQNKuLEk9{J|BTA5rlbu*qaX^ z$AD~y+0pb|fVAOcM?FXx*rGrJN4;02F3G!PX985|JpNuJVjED}UK=yDjG((%@%`hA z8eNYI@aMhlwYzH{zKm#+Rih-mlMP$X8@V**U_zmmoaD{8*m>K+YIs3`7Sq$4WCz%m z^99z*ajq!sEihXb*=&l<f_pTlEl|qAba0U%WDIt@qia2-$PAL>IOjmKRrbc2k_qaK z&wCe``}uTm)@Og9h|%E@NKYoW)bgE6TK1)d=DD`Vg)pJx;%CyNtQ9sn%ljf{fPS;y z{P0ZVj--=1+mR=YL)peyDud}Sz(=v)laWZrzG!4kpdbFvBykY}oe8)!s(*dm`b@SS z?`B(@TPD0`+a`1QkwZwKEnCnU%B`+t?Z8ylvJY1?qmWszf85pHrHVj+cUKD-OLk5d zjzx%$`}^M<{bcvv*`NLRU$6e)J^uHn)%{)15ZL7dGO!QKF1WEwX0{zKdIx7?Y}USY z`EPqYz^law!lPl+e}@)v2pS!Zd77{ilpr`S$rOwxLJ*<Iv<P289J3w--|(Qgh%ec3 zc(FLbQE9-%bR!!~Co5PYdenL$FS3JvhkQqm4`7SMSq?(5GK<r}NvJoN>Xsu9(*g&1 zzjs#Tv6*#gh9eOm=U5mHaOaVc^{)y3yp6*{e?FQ~DBW3kViHhxp#mR|gdH3d^4VxS zJ>0?0_~bQ?VdMN^_QCrvrouic+!1dgp7*ebSc&gHc>m*Ub^!qdaz9SYhZ8QQt?pnZ z6C3JJCs+_pst_x&<-}i|t;^fV)=#tT%Q}(*+RB$xP>bvVI^qJ;0hY#nOwO#OL1!3U z?de(uEX(0Bj7*2Sl~C*l?|+8F7*_cjn`V#TkC4z4lydXK#E2~2%TB=B#(CD}^6MSu zSe`sG2&WTVW(qDadwMnkj|x?#WvjI&71mF(sZ6DafQI~&-UWF!Kp&osL0X>y!=UU% zZ_<aj3doYKcx)$h;NlP`mHln>K@2$JSx*}}<U3vNvsgemrc*)wx?J#UHq>(xNa37h z{iELGFwbz{1;d@r!4ap~O6*ZjP1u~v$j0!>U6}Slh@{B#>}au=6%RKy1`ve-7HO|D zogZ%8>3p<tR$vWo^iKw3=xaJhi<9vO?}MP%h^mtwmj1jevieR~DbIW7FY?!m&v8ru z&a@0RVqH=l2*X_z6&$r!)=#laCiq?Kto$5b7Z<a92jVyn@xva(kex556QG-Cj-8dS zNb}A#fqx(>(0n_`nf9U3?dr<X?$&XDJkMWqzsd*bFYC#<a;H@QojIg}vm8US?si?M z@e0rc6+pd<NLO^@0@by<=}n4r2xCI=BJ5cU#U&k`O->*)UaJe;`pNC|P|@jhRzVmi zCs;Q^y6p3kt|&-ifnL?uqqD^nlRoOpmd#38;B}qsx3ZkF^CPG=2Km9+;UUPY!(LTK z;ImsfOhTzZ{<s$A+vwtyx<mV}sIVUfto;HP<@BC_zoR=*zqx{m1OYwSxSP-C`pm@y zQYCf(qg(ky$}QRemgg9-fIxkqL=vj7AeQNzaut(D@^Nye3pHeGLAfYpqP<q9MIPdX zl6vgW9|H><HelPMp5fK1u&q~V%}y~Jj}~pTx0=YYIdp7G+C*b)%e#>Iut6U63as;a zn1YrQc2@~j(AmQI73`NIo`Q!!HQ5;zWQ$g<Q>j!)tyrmHh7w5Uy%F~5C$Iatu<3T| z<Lt?NKAk_5(^u*AVvC4fV<kH*y@P@<T@K7z)(Tr&{u&y!PWJdH?;q=mG)@dOVGkHX zhQij*gLohYr9aMlWzOw|&A9#wwTw_3s`(qMXFt6|UCjkpW)G_FE(En6=CHDS!kqcM z?)aBA>iEu~pPn9~rJ{S#o9}S)6gs1@9ig+e4Cmj2X%Td?zP9YIHRVi_8$r=Y`K%Ri z3Wx>#$(r`MW1Yt(K?=P5Q(C_151AQl=|;AgUp(XihI=^9t(e+eG(MQgMPcWZ8`4oC z)s{5)-VzI0nbCaQ?~JjN@SNZ9{`L4A%%=24R3B3ujAs|G$KKBGpcg)vPLCU!m|;y( z=?dY+G)xMms!*n5_6{-x_g2~1HqF7P4|$xRYH_G!c6>wSyY{$uaxmyU%=W0ij^)4t zr`XO_55YQn#yqT7VDESf$Dq@-th-8fljDGqL*EfrxszAWk!VeJ0eK-i-iG8v)>W!| z+TpW?Dy%jb?5%jK-O*%_zg~IS5#_?5eY{lypT+rM6}DI_?wYU&lo$6NV%w)3nm{gX zKNe<hmzT#56N_(h@8D{4*2?;@E3nWi^!DI&(jtC00#KeO$HuZ&%cV;llwWyYDM_5l z5&<db2HZKP$>?_0+Gycldqza@S?Be*cx{L18)+jLtutcV6tr32Y2$nhu<t|Ng<W?5 zA+<N?!W@&2+g2PF7+7jRzYeKPce1NBG*l{|0wwfFLYz~kZbJpSS%>F1zW^{R5UIN> zw;`9dk4s&%e+-VL(2xRe+g1d{Xbgp3qAjubXvsZkcN28M^J5OdqSkoh*vbu-=P(^1 z%kgsnOgeW--_syP#sjFF2bD3)0>x1dt<?^7&r-NUiP;r;I!I!&KOL_*9wJv53_2J# z2Uf#MXuE?69=GSy^Bu4jG;?+`*}<RNTdRr|^o=%BxH%^Fi8@W}1k0beu6N0L4c(%d z+u%#hU2`2;-zf+I8wHe`PM(bg`eW>z0j*wyWIbDCU&tBu%jx2?>DgrPgzT5h-i0(p zFNM(UJ9oV*N5P%f1p&~XbFU^LLQGLD7_wfC3Tzd<vC9Z1zOXrdToKfc@`O>Ny~PQ4 zClNLj9KO@jM7h3Ga(v&)qOdfiX2rBbox^+qlSMus&cQ~Cwm<Hl&F4^j$WzeI-qy<w zWjgX2?fOs--8+G^9Kejkx9#D4b?@Q&otI=uLBU{bDd9vS;T3IZ#%U!17n72^@<H|C z)ME?RzQBSw$Z>#iQ6e97vi%7aJ0@F{Dz0TX?*lkd`-qYk<9L|baLmZ_@y8!SWL1Ew z=xse!1+iFmabla`pa@FAS&{rp&%#6q1bz~RRu4*X8Gt^gY3GcxZ2QzleIq-<e84TD zz1JS?t(>pGYJ|V;uwg&lWdk;ES$!k2ywkwCT#`UjzfUU)IN_PhU`6ZA=e>*ekx`<J z=V<TY8ZBG=yJdg3U#@X{YBv0>`(IEG@%)U)YP+7Y!)Gs`c+=;O1EQr1fnl`hcFoXp zi-PQ0cI(#hIlp;H(nxUz3ut?_6KYh;Mp$;CegYkjdSiC_46YhwVEmakPe8CFSeS;< zFJ)0sTMkLzo;{{*)XNhbz7UTMvI>W-)=0T}Bru4_sNY9tT{V2N71^drH85E2rQNQx z^w@%0O_GBqx$|SvN4bTQ`LvzFP1vy_sdYV&nQK3WuFw?9m?%X-wFYZRj{@W48eCZS zW0SgBv}tga*NVGN0S!YNTgOhdN=M|*gdG|ovYgiCd~@cmJb$S=D^*+gY05lcDrYS{ zriHVW6_SIF%ju@curpLzf?BigaNIlGQQww=40K`T<Ma_r{*jhTr$VWEN#T<21dGsH zO8*{=4&gRH(iccLqgrqRvmuTqW_9S`r*D;@i&!gB1@wykXoTaca6rh3W{<$;K(j;_ zln3QgyG0i8OS)i*rV@k3(SCZ8GTj4o+}fYCI^Vzm*Ut9F!qsppquxFCp_&vbKOx4D zT7km$xI>qL8TKVOn{2+!j5HEyjiZ+y0xz;1NO}DMH>d0sh3IaV=4I^0-L5q=Sipxu z&Zppl({uJUq-~_#x;famd9rcyud<t;KfL+H!<)N4AlpH97f_GqgNJDQb{3kkX1T0Q zViyaT&HzT_rx4e%wG1HB=~x|mNEw4imx_*~$Zi9j)D&<i(to+0_GWNCQG*GN$5Ikp zw$-L3W__I`D-)p#`C=6cv7s)4K+SqWYWdi2m;^Fx33rA2f#^};H3WA(pn~2rVv+F$ zV|pu9L7{69fuShMKERb9ekafH<HJoI0H=l~2#7+Mk&aA@;}@oX%1CeCYEio7VVB;? z8n*yc(qiP56FpR)U-M8+;<}NYqUlp#F7~Laeuj7>d_a4sy-OJGDcGm5-fnj-`&^t9 zZ8)q2)Tgqg&%3JnZf|8j1q9}2kt|Wxq%nLnIZF)x#TjI20H$c^@DDkB@fl``U%1;r zCW#=Qx}Em4<DGQnk*-6hS|}r;f<WH;5tn152M(peZZ~OIo1t?iEbk!O-?)?M&C$c` zX3@IYp4`S^L;*-3QtCZTtHa1Q8NkAqlsfIW<b<}3Y>1N6j*C#0=Ww$G(O^b@2^A$M zrVv8n?nw)W%#P`(*vyS|hm3;)J#^{d(S<M#5}nX=D)I~(-xF<uBT=O{oNH8o<2xvj zR3T9+*a3>+KMF0=KqaGOhlfuVL@RYBC?N#hm4DyJK8Lhv2RIHOty<e|P`&=lnn;k4 zwxP~Ws5tQ@#a^J3!y_q2>M|)1{0W-O(c)qUCq5K`g-P(clQg*6!EwJW<eJRtjdyJ^ zkz-8Yl<t~a{}!-@GakXeA+z*N)A_cdF4NNdm#dW%n6hYDs$u36-YqY_u{F$E$pVhL zi~2G)#ZI}`C6t$R2j^B;FR4r?zvJd17JQ-blQXz=z|^XwMw=!bWn?M4ayE9S->yZj zjU!shzNO)GJ@ElQ{XQm#5I4O9AT@ez?3XQxqkI?CID(9VMaA6%ZJ%~3k8aRRCab?A z1`a6)WQJmbL>?t!W?G0V4Jig5u#jU;x2w$&<EQYs#Br^8qJjWJAmilomkj=qsC=M9 z5L-cevC}jVoKNWNv{PFBr3J+H_z~?jmxp=^nC|+qI6_h+xRHJO)t4(Tvgc2}`06j7 zWG_B{x|=<F`sI@*{EJT;rSA4hXkcX(r#+fQ&7c5>W3>5rd^S0To=!F8#RZ(TUUQ|G zf52dgM~Q-5MGFlIP2hOxD^Y%FZMK4G<Y~0LzkBoPUlgqJY0wm<_5{G>Yg$*n-%w?< zGYRXKBYp}1aBv-cs~%*bgj;br%HpUeqAHlF23jOsx=c!@^9okHRMSO3%BA_lR=J&! zu0)v?*Hg}wrP+arqglFr*TG;(eysFP>(-skM#9!&@n5YlZ2gYbZqBA4lO*n_%-)<r zhQzXYi{%cjzn2l~B7`V-e9{r>9Nan+Men;n>edSabmgdC&_yt3qzB=nn~nH&mYpob zw~!u;@WU7)%15+KKHe+4s3ES2D&L8r_WK=Z4&ehv6V%cvfsYx^Py;$f565T4(ejel z6Yy$VATdC;M{W+a)tt)QHrRbUA*{=`vzEC9iZvRW2^4uNJL$b{Z?56=u`XxMGGB#5 z=IyNA;{R`FG)!p!eA>eHiG6!uXD0*fW5}YNt#EV_e^KCC&0O3Y?2t6;T~IW~W<xVn zNwAF*99BYhE1NuPMEsJiroNR{iJ-!cxIcn=cPSBoL_wHPVZ`<09$B&@(y@^S-eHbH zefk3u&-^Ip6mwGH9c{xq1qXmzw{B&pH%FbDqt!(IhjgU!moCwCn6;L#D+V<0ry_QT z-CZcjx6>-jDHJlaiqfGvN!9u!n{1`Ca!bhz)3<(iojMJu6ybT>aGuc?!@)8+JPO#C zogcL-X4K4`a=xr_W-X;ORX`U|J5hNvS1;sulkVDPWPpa=f)iIk4KgZdPrKroU9?5M z3mUl6uc~K*ymU1~#j8P#HaJY}M7If5$LW4mo7dCs-ZwNDzJWsip#pWD(}x@9T?ZF0 z{41XN@e)tNz*cnL@JPR)n8Sx>WE?t){?K$Ipo0}XOIz2vNma=nvS*(QC@{UNSe{Vq zwORbt?EPwrV2?^#O)CKl0W2&m1+dz?#%{SB8dpOV2ba?}yH_6b>qoo0qRg_nVQi&O zo-tdpq0b&YefH#2Qx>we5<kD8Cq=mO^4dCL8Gg{8DC62K-5R7)WoM|4g7tg_f)vx& z=U9>sC?hmAau>61W+bXDV|g!$_YeuOJwwHV!*?u@G(fh)Ld97h5xPYl<248lj2wue zlD)572rMi-6`=^>577BrW0Xtik%OgN1t9_fouRFGz)8oq^CJ5sJS(lm1l~~4pIuh) z<U-{DR%)Mv%0i`VBlLt&2T>{qXrNA3Vu0c(g6mZs*rYXl-Rf+Xnk1n#Hb|`UBMPRy zN#riDqo+KDKfOzE3IB?xTtfvOokCj}7=-G{OzX+^OPVgP9Y<4AkU5~~aEmPrbSS&> zZ(KEZ`UX0=x5>JYh?rrE60~>mcH_-?TEqx?i+dhiA#Y`E>~^x(ZLR8G4K~3t*>)X1 z;n#X<vF2}nqH5UvZT5yyacI!NE4o#Khn7(t{1q!nro~9g(PA^6o$zq)53x^cgFf(5 zDSxOjK~Sqga+<n7IjB*qy0{mYfwx|I?~S1c|ElkoTW*5g0d*?oi^OW7tqiDt<qAnR z4%4yQ7$M!`D9>GHu-MqRZwgftapfsEwiUYNuV>@=81K~2EgNB<w#`o2pa{)E6@{?m z5jELSIQIEcIvBPdo69~8D~VkN4VVDV?%D<e3<~-U<`Wda&)aYSz<UE35A++~w^n(e zQ(QDqSnCB-E3l20cpurM`An?dh96e?!lw|iNrpWzLJ8)h93fg8EZ(-)k9?i`y(Mm- zSU?4|t>>~2LF#Twxb=Uc&~XP4*Rc5S>qZut<41_$`KFj+*D!TW5<+zFEh1O!@eDzH zW9T`OQfh}i`1YLf+HE#MV;bOQ@IFJ&xfmebA+m?ZG*Go>9H7;TqOH4fied@n#Pu0{ zk|n}%6S^g72~%>upn(i#req@?cFO(A<cdjORQnz#)&{_C*2WB};so^SrX%`xqlnUl zs)9vZPrnUs!+MR@FG>$@PGIv;d&gT>6%5fBAJaX=%^--Wn&7c}<-w^>U1yAHh@_cE zt~8W`--D>p;42Y3G@o;bRI3O)rgMb$xTH<Wp4V)LuG_&nip`CEn$ao@m%IIn#ssa1 zZd(js$<$mvqKF^^mposav#|mVQK?orbd}qFr%`o)=EV^Zrjy;&t(o$i8p@qjr|_Dc zUph6_Hj^F=JGS5~m5M`VPcgH3&!1V5&OaN?J_7;jV=FkCdMN>iily%K;2StK1KFc# z=NA`X{7=6^B8e`V5g&K!pFa=IhB)Y#OWJE$yp^8Yu@2VqIgEr@yLgt6$PNCs6`YU( z)tvL`6bCP?69~fuRsv>#r}N(U7#txi7GKqzM}AnB<*8wjdP#R$LgaeB>fqd45OT;W zvx`=>B`-ZBy(DW=+!qu)wvxc@HTd^cQezGqJKAXbG*?@QVH3_5(g7~%AYx7cM9=^V z>huN<x3_{)1agI<C%)Sp(g#pnU|lnp)JVFPTFLKP>!tN7rAtq|Jd>yJaH4FNTSJ&W z2N_Ien9K(j6*RMOeX7jMm6YKqc0>+?C#+A--#vkZ9bq{&c#$P@`EJCLpl?ZgvK+a4 zUP5v!b6t_cbUSq<`QI>Ud#^}A!=X5dJJnQ}a?>d2MHgo~0XKq(B1>Vpy#FXFDP@tE zjMKq^Py)z23=+Wmqu(>K*e!(~?rPEvb*1V@fOx+oh8DShEEJj0{X;zNKg8pf<8jv~ zQ}_P6D$w)OQNhqNoR-xtoFRtnvQX6?0z=2WgR7d|Wz;Q$NKuwtn#Y$IA9_jZF+Ch% z^Ez7~4~3-kqSfB_QOdh^ewsYft$=R5cq%J%j-W+`Yx9{i6ze@b^q}}MA5T&K1}C*r zd}+`gr{0;aat$THui>b8iNB?@!SY|}*C!za@^KvSr}XDb95c%;`LF1BDKUZ4l+B*8 z%&LaOe#62yDVLC-en(jna-bFbjF<);Sp6G_8b-De+M>kpqr0`5L?!Qogz~CPg-B-K zxXWiLon~z2^ju4akw{^9tExls5rn7j=+$TduQ5MoiHm1ao=CFc{^9~UP(nzPDLJmD zh!ddJ#5W)QM#yd_?^OEpLz$zR+rJ#`j5O>0SGrERXS6`$xg{y{2rh}5ba1c%VQ4fQ zA$&b{0z2-(g+L1Vv<LSNNz9{}f<ArmTUY_HTOe=&0&f*^12{dBQtiBS96mz?u8i}v ziA%8kbi&MhM75xj618F<(_aOSXgvmYSSW@gHGu5gfl&Epf=#<5Ct%SV-_h;7XrCma zgICkm5x~aS;8)1)H$@^tsukin+a+%ga)I2-v3OW=IMqTO(=4WVUjz~1W8&9Z?@>hG zWiNG2ctiZFjZp!hGIKan*NfWn+B>HB#d`H3V(sIK+EZVzW6lAKa@LP!CE|fKgjf=2 zyqA)=>VTV+8Y%qne2N;BTes{7BRAM>&|}F?vbSQdWvKT313Rbyz_@wPH>l#|oV=7d z#q{;h2iz?b4Q4y13nut5%;#Y_gqaPvrEai`bRHmRD7Ce|>gA$VPU;)js=PBT!7CiG zxe#Ew?!Ic>HoA7_YYe0MB>HT-UEli@Z5A84S#)oXy3-;4y$QPw)n#ymtq#czKoQ6_ z1IAW_x92AxIb24>k+7+V(*Z}RTb}Y<eqhIdgeFwW*BKz05zR#u2yh4wSjTWLVei$Q ziiBg&UZ^4v>~Sz!$VuG<W$VJiwk*M_NYp?&Bp$9=sDf<}NsHvj>G+aY=q#jS75)mG zKu>i+B@?)^vWh|&SzJ?oPHTP1^lJ1kA!93$Of<9om>I(eh^jr6o^e>E2Qa*ipaJEw zb`SWit`@-Lsh5XyKXIUIG1qEq)tFH%dpMpRU@s2QsWWQUZBE2hq<ug=O@~_Gqx`j# zjE4X!DWZyWLU2~S$j%Hwqa@|Niamx<y4uQS3^!0{$HX6{J@ZiLaLQs44xInf3Jx<m zTfxf5duTZKC;JoeD15OWlSm<f*bspa@Ds_95d<011Z7CcR81O`L}#!es&X|atm=7H zTcs)?(YwZE36$ejAMQ^`0|pEwJ`ixxRqW}zW<@M0Oxjzu29(BqQCaNb019n=&rb02 z<QR&EHXess>hKI|HJWD{Q*sU&1UJqEcB>T^CF$HowU3p#57FH;-m%xpm~@o}v@onz z$Bd0|r#1feq}N&w+@}!Nf6Dh|RNH6+G(Ko3#vMS+cz@Duhu>mU)rkVehojfZ_QHFG z-$<!~)ljGg2qV&yJ}#YH7#f}`2?~{Yt}!qx#%vlvU?apnFXCZBXs|UNY8pTBu~S%o z@4<3&v&<~Z5K1bdXKS2YK3PH!bI?PL8d#nm!>muf5xfL_w4q?}=QW~W-DW@0GEC0F z=M-n5@{nUf4<}IM{uY~%kVe*9G|)Hu;K$l{s+qhv>lyP(;1tLf;g3!h8#5Ul<_IID z+qO#fOz8+*vFHGqmJ!-Af`VAmX`VA#&0u;yF{mc+-?Y8iiC-ab7;;+@{*(+*l#~gV zZ4a%GXt^RbtRy8BT{nnkY9XZQ5L+5@ZDpP8pZ?Q~KY;TS{6R9X)>o262c}PZg#_V8 z{0`hfO5^4MPKY|Ui{c1r!SL-9{D<?nXYuI_7H6Ho@y6opDBlR4+#PnazJ4|nzivV! zkbrWTH@)LTugj8aX@6qtz+)xCgCucbcm`je{fRU+ntEHiA_qPFDilH#zb8cr&*s34 zbriA+#wSP>7=K`k4L_Vo`&xx>;bJ-+0|<MfdyDybfCHJe`Sru-5A|B|X1a@cXMaLF zeyJ$!`mZsyC&Td+dwZo7c_{C7@4=3UT=8J?-Gd7?fgxm;q9&g?U*sN(h8P)JwURk6 z@hT|hOhE)Sf6k<A*r>g@iCA8ARB)T|vfiyaDxu6t?-&x*yoe>V9V1n*r+tu21WJoO zGv>pM79MN4WoWI@J9pPu_1X0B5MkB2?KnJa!WH)x(yb;VFTmv{!4u{~Ot~gHl*KNk z3=%owwKl+xS(!bk9H0T($X`$}6=A|aOYNI!$&1x{s1=C)sOK`9OPhzGR<iTbtvR!_ z)r`8y^8pwmg+<F|ct?0H&Z7q-BnG<xC2|1#&>b;iG6Yey8C~sNHK5WW-pf(PF;+gd zFBs5bK%jm}-d+_+pW?dPg@OzT2R%aHa%VGj+jL%7XfZZk<O`*&SIR~!!d^0L0Sm4H zbPK<1-YpatEC+#{#%<aOzsAx?MwrYzaT}=#wT(!<V;<l})1;*eW3SaOS9H9v7+@%) zq6!96l4BsiQyeOO#A%3~X_+`upz|a@VXn!eDb1LRSV;gXcZ}xC4`OLs{jB7$D_KDr zsU%%+vJ<EXJ7NuC9F;}PaVY8%t6?cfJ3%RSW}Tf-iA{1tLe|T(nVzI{coERjq&d<_ zCb5W5dU>o>tV`H|;XKX&2k$KJH&5YDm!_uhj0f74INbuJy15f{x>MrBSAI4n<J#0J zWhZhz@7&$iSBQ5-T!7>lANaAV)KJL$Wlq5Ml9DW!9dpM}y$zc?B`)!`hXw?)ikz2i zZK;nks&1hk&&>7!I$Qqoz!#KOs2W&{m$BrH)!Z5hntdK%ETA4q{61vH98a#p(Y}a5 zeo95a^Eo-{ZTf8cxvB4BjeH!sG3>u<t8x#34>$l|+q6>HlAW4`q23befm0UKzS`s@ zGuZqh4nV3qQZY0BwFJ@Ce3-EC6R^R!rJyB?-LZ^x>EtUvI9l&e=Dg?)^DgK|12Ss+ zy!TR~T5XXVoM#{?cbo7S_|+ujE)`?hw4A-tmxN3y;Y%cvFsZ>84I&dPb1bzyV<FF! zRRLrYA{>!KZy38HM&ym*Wjc=Up!}hO7PH+P6m58@#<Vxy?<&HUdW&@_V;ONAkX3z` zB|L93r+|rOic_RB;a`S@N;hy2+g?S{*t<*(SX4RfADT!DVO(tVV~!zNd>oaYp@z^; zEixp!0f*peQI54^oR+9rd4pPSe})-D6JP=aM8k8kb2#9<LCf$|ix8!)KGAyu*7ZOY zDxwx!fsRr#!0;NCa(x+<PNOWnwqr7#!IR=}=W4)J8$qBVX*}Eh`$8D-d`;*j5=BZ6 zOQv`RG5vL$cq|bF=Q?@y`%L-{W_^&qf)<iS5S{F#X<U#{nKmb;|Mt_q^L7oCSp!z7 zEF}crE&ks`5W))JO^vBX*g$(LU;hxs2-49~24wGR%m?bgWDYN-G;3c7E~%sRMiwRT z2#-;lP<cr^ZCu4@{{GUGkb9({ba!$cHxZa_DCss;+Rm(kGig#}Y|kZw+Lp@utinAn z6x+;d25AmDaNc@GQyoR8B4aup<h_?8BsOW$8JoxSd~&#I!k#c|J3l&)G+t|BiY<)v zm{=O2BzDH6K8=^cJmGfKZV?U~S^*RlsAWFPcT1OC+P;va34;y0Kwy2uAY~B``NKp+ zO@8XM;XF1tZ7_?nZ!h*R9V|^Pk~Te5fSxhr;7XFqMRIp1e#+4CsrP}W@i^c(FU1{p z!$4C^$W-CrgxpxjGnoAq=H85*NJSXCiN(CbfTL1pj)!jBzg^PV6BC=PLkKxYuJ{mP zcQ{lssiGuPgr|xGfen5J(xlS|4t-{hSzQ8R=-L+oK{RO?c#_G-<TQ!Ram00nH+Ygx zA)^JJL#&aU|5zme8<8fjAqRn0n5jp&5-eCSCM7F^Q1GKX3nA38Gd_02I)wJy7mQJl z{z<A{M=efn3K%_MLb+e|Z3QevLj66<7})#!lbyfoPZbsHjvwdlk|+TaLJJk}jXXTU zjx+~Sw!iX5rj$pCxBDyV*4U6E;USd%uDjIs<G->}oNl;Mgg6p3$D+Y(x0v_(`2j*N zF2h$S)PdFwRs&`(hRO7@Dy_e3^zJm_*ESKP5(q(ofGWZTlJJFMx&cd}f5|B~a6BVg zkg`*CwQ2H4$2TvjDY2@?(?koDmkxvgrma*emEJYYsMmK0o9g?!gm*>^HFAi{*IC$i z0xw2RV?}Q~gWyq>`rcuYFI~g`mDjJY-51=i)zL!@KDvR+J&x6t8?ITvLct}Pfj(4~ z)XAhRIhNKqKi8*r*|fw<)YUkdzP_&{8y0L{JKlu9MV1KPv%|`wKq3CuH7Tr4O_5j< zc5xj_FaS*TmjDd8A*dLC%}rnQjG}GyZJGcYN!lWYgh}y$(0Cy>DK<PG@-NK@>JzP( zpn_9?Tf<_?DQ7ZdI7Xs<-ruv99l$24n>kG-(BrZ!Re{0kB3BIAq{^i<&<mU+DL4ak zY>1j5JuXRP0Rh+IeNTvNRl$K?SJe1p0K6+v7MruDgM2(aU+qATsTNwM0UyALNWp-d z5sbUgXMsP&jzE!-xpf~&W=TU-GFb~Wv4i_@;m#9AW#Gh?dk523Da~r$IyNsA{pV1^ z5=9V~%r@h7YYCkM^5T3%vMFPLK2jLkYvA76ggfD%SJv3arKTP#njdCr8HO515ia6C zafuvHh@^~YDG^2Oc9trYP=Lpx#+=(wKrob1)OIi9C=Y?`Upph2mfxA&Ums+`*NtjW zCT*uo6xL2EtfyozUJs8wCs`$-7%5YSl61;BQF4(Hm3ynx3@2W;I!!>U%_TI&_wy6f zi#N2ik<@?^=?MslSdnyWbi41wnQ=0LztwBXBuG%Kw<@D@EF};&?BYs0ARnikSjBMK zFk20kocnym`=;~3W9|Z9<Z`=nn|FLUrauwWuNp4ngyZIIICNo+2TLDCKCluPx{>(0 z22*16?^utY$~6&blkmc5Y7WjQ1dge#)0U5keo-%m*Piz}c2E220nf|=SOOPG0WH8C z`T$0FxpW)p{<^VdXmoEM_l(B^;+4wl3uNW-FIq=SkYV_PLI)nSS;<1%y7bE8@0jTn zemO5=S2l0NI-O6i#zL}pSn`EmB2;?T#w_&&?wy8Nb=<km9|@S{>mXPg;FChMU|N|5 zZG6#{?U8qV$oX2|E<9!FChZ<C=<$fVBpIeX!N4tnnkp3G5eTsyMoCcE&%g|s9}0Yh zrb3#k#uu)nz@Ajjw-`-g4T<v-V8rze@Wu0XSMpzkM+);NScx!-EiUvYIhGWw?W94{ zwpO&|3fbt&^nUh|i6r)5tCSnkk<;~1T~_f26tRZ`sYI%?I_?lyXpVk;sZB;IBQ@cT z5z}7F+(dW}N4JZ!6CP-|W9#KhmUVCSQ6bacX6wA{E6D_Ph|NaDiweM8B2NjSL0%ax z$CQlNQ)^^T!%bv6e4U=7Qa+^f;XM8m!CD8T01{}L45)N?(TdY*rE^THybjIP=ap<| z`Ly!w66=qSQ483BFR}QQhA5S81fck{Hbg$O{R~5>kIy>BJ8ws*6bq_08YIeuXPaJp zDdH7Z7Pd9h6(WKGe!R%Q#)&N_2UM?Pq+L_g6T9wP!tdT=7i&}I4IGP06W3SOfMDaC zOcIWuPG+`3JD^M<VU!CAqHC!zD|HA6+lm!P>8U>lOYl=J)xap+1If3#=i;-B`wevX zff1@}vL;709_WtxDQgMH02%tKPXl9;$1!}zW0aDY$QQ_L<=~!Q^j?on;BCg-^)Sl~ z7Dqn$&ATopIbXE<yx%;|AZG&ied%>ycoz^yF<CLC|6(?PB;i5G$i0u)6tsq&s8WoD zPZ71tu7Z%)49^K&G67FNZNrv^vm~&+P|8O~LX*@JHL_Y#nOmcl4G>8i>gFer93jT0 z-X;<Q(D@En7fMa%3|Sqp<!bhdkmn(yjY0cX)jjLbkgR2Ym56LudD!?6S5%4r$8(A1 zR~94KfV|((fC>zhYrHqwWTkGYZMi&W)v|e@XpzrKU}*Tf!OqZv1TKVJ$(2SWx!boB z!Ci3pH?6vvGHS+j-mnBUAn^mI2l^jHxcxRqABSTtML~m|n|GI?d+aB2SDVaO&7G33 z*W{Y0Qtt@{%pb$#d)#KL*9{2CB|wTlrF2&9myth#qjm?=F4&la0%6<j;4X|*`Bhqf z(pq2?m6e4|o?QAmyk}VdyI|a~@kSm1!^0u&<@9yJrHEB)6F^4}veO3F1!)osHW-$y zN|;J|WI=q6lzl_o?GpgiHx#}3l-WIS(sUyx(?uUs;MG8h&cs4p?x0+r6|4sZkyi^> zAtf3ru6Id)z~~USjsC7TXI|XP?;C~9*oWs7ddc|S661Cwtsai$t}D{LI{$z9)eE6R z9yXzvffP*D%5z*L#Ty^(dTRK*&lyk!4|t?cr>GV#C+*m7!vV$-VGa}twb@}geyYd$ z^xPQRS&A|?VVWdRgwh1rDN_p@2hgAtX5rr|Pcsq)#>;|n^)~GQ3}qVh=7VtUftZir zV}dwPIG6-6pZ%I+c62A_Lw@LqFS%m;mE11$OrEB@KbISJyC8QgTWr=5L$V^(vm-!T zy&VL|f0P|grwC1$PhacF4yy_7hZ4~RDp*V_ja=`ljB=%9Wa$N0?TGB~Exm)F+=xUO zg60Vsvfe_fgado}S=SMkYcYqlxSUJ%bke&rP-l^~%~O_#5Qg~4m!}`HpPV<cZ+Jfd z6n)t2A#&ncc<4&#N<{75dG7)$!w8N_Te!6XmknSXZ{EgNI=9(1CbuHWba~02Hn~Kg z;Z<O<P)bmaJKRMgFyXvUT<yz2U-1?p^up;N_R6=IFz(jdDp#y5$ZNLsW`AYE$V8%x ziqrChaUGDZB%os(*>Cn&9#Za2uxc_@3UW#S>9)by4xxw8F&R%5K_KM=*PbXx1aM+o zQc*>h!{X?G?{9Lpu&Xo@R>*S7<iIE`Ikp{?y$f2g<a8jXs>)0B&&Mwtn`GmZzqq6P zLfN*<0dU8Wt9*M9bZ{FQB6XlhC(#G=3*gR=nDvtVyj7RivxE2|fcCbNea1T#<BmdD z`77NS;(+<BX?G}>;IGSeh;r!$qKsPzi^iRTzx<<RF0CxXTw>fW(}T>1UV)=u_g8Ex zu?ov;D8eSq*EB~?0zS)0tMo>P(+bu$G$>n%EW|Ozc58Qilt@?LyJkz6s#bLR86c14 zBwYp7QPY><F$4$1)G$RlRLI;xA-d<O_P7ize<_}mj$8`78p+ZB_7X(b&0!H)(m#eN zXX)z<kU@D+#U1r#1yvX_Oe~|TM;S)y$M#ERT39$-pD!6yv+<5IfP3q(2*DK*n+RF- z0I5y&Vh0rgwR?yJJqC6QN1$~`M27=p+W~Vpnx2gZBB~$cuHq#4{FG6r+82GJ|CZl} z=yHTjR$M0RjN@2H=9&yuW6`R{3EHQO?Lqm#yU^y-18&`>?&xHXW>o4=78r(}MYxN@ ze1ZK3w<qVW=zRri)AS69HDSF#Tn{!1>`>wEAW@dHVsUYDfN(t9{3min9{_db3HiL- z<_EY+@gWIoo=K*`F)j~8bV=)Gdhj}BP(`8)CIYeKSc4fW%6wylvfj#H1C=O+`->}O zphyit3TfdHiZ-;#4h0BRH1Q`tfPoZrTJ^-%@w)<bY@OTgoY<MPoJi)B%Bx1A(j?*s z2G%ssIy4xGkl&^cE{tOb^%7LQqsIQq-umsAKhN4fN3vP`x4%EQg@15K?*8C5d`AD; zpWf!vpVNz!#rB@$FSEVd>!@i3f4;w>J<VSekX(*;)Cx8BOkG6hFP=RA#aFveZ2Syl zMa9=L!}!tW__RrVwTB2x+!xuCfN|JWI`At3BkggMZ{@;s@QynX9!ojWRZq%{$F_Z7 zPMGinEpj67ZKmX*8(i#`b6DL!TVa#$#BZ~Uhkop2xWN<$D43VcF85OqnRi*9Y5<{2 zRAuMN)lI%YMZaZQemj&wgD-~{yDL;U(N62+f~S^XXl$*C--JJsY*VC9Z<nJJp`L`3 z1Wd`ZF8sOVcA473(kc>9!m|gNYfvuAJIND``%5a1No<@JI2UWOb?bXWffy`O!-2f3 zfsYny&r+e$IU(5;U_-QUhyNhwCl1<ToalJE1r8phAXuz02<@9^!>IM-97x&bV^M;w z8V6_|N>iU2@Jz^$=v9e5zw<)AA?4N_B8!dy6Ju1wG4P{^l#(3<GAW=hA?hHk4nx8Y z%UKI2Mn@k92#$bk*pM=6gEU4aZTVO$BdD#DU1Up`QEur0bNGs$ofO3ko~0bT)G{Ef zGjl0XoBaw9I#|Vo==2gqU<|JGW-;o$XCM?wjDSsBs`9~A$~BhhoXvO-Zo9f~gho1c zm3&+SVWL=HG^mbxc~iUgDAu%nA(=FUlITd2Y4z4Pksy)U6b8_it_;mCvJ*op#Z?M+ zTx0`*Ln7`)f0esu^dK+#N{+$-;DZ3WezCK)xw#er=fLV84Bu}4-GQswm~83o8~*dx z+1?xIr)O|AdHd&Y!u?inUvi2dF6njCFh%!pbKL+eko*^ISgv+G?#JHA`$d0%(56Er z>CR*wN-Q*m<RuI`4g@bgNK6bdzWA9AR74%9FFyL{CPS1FfeIV>IMzti7#!j?76^-l zNob~zQW4BkG-mUSe-6ML;NBWX$`?W%#W>GrGV@6KkdQtL#I6(CWh<7afyksw7(&o+ zjBxE-cT_-vkw+}4`_vZ~yDSr9VdcGO>Ewl=_)Ch;xZ4BXVG`o521sHOTO(qW-Sp^Y zN7AiZCFLSh@+;hY@6UYK;+ed)ZZ|=n^k(gt$>BA9CUA)6^|)RQj|6Ds1C|@jeUq_8 zCAJTC4fbjUskQ3j5iD^7yJVg26h{sK=?ca*N)_m227?C=foz<mI-?V9$BPs9pbmNG z;IS_4bAC&Eg^L+%D4}y~zm*z1uMtvb%w@Fg;&?RM+4lb6?e$BZSR^R~MeiOYb{1x* z)WQLNcygNU2XqLBnhv3xBE?6bsju1S*YxM;GYw&Zfbw${YG6`^ge5XP=Z9(!md)T@ zhzL_)!crEo%g`KWr6c8bwtxy)VUUvlh`OvX+LB|qj-vGV!XR&D$lcaSpnHTwfhV&C z4p<UiPL8cO!X5)$vng&j1}YDD_$B0o_!M5tq<{s-IDJL&MCVf}&lV7Vh?aO0fmLi- z>!KUVbW(CP`*RfzP+}|0rOg_oVv~#YJSBz$(6H3plg3}d_UNM{rtsDeMrxtM0|$+y zCRRJ-<>K*7nlngmWMAVQTliW|X*gArW-v9dIcUGY6f=k6Bs+i>i`F`v??{a}6(D7R zjt$N{Uw!&jdy)5#7SqY-80(quMH``det0y&Lf}qPByWK!j2(FjIpa*xgTtD>42v^B zi%Tp3fGUnzK46wS9hnD2x=)7L8A_hUaca&+yt}86IOa7cl)Is4CRuv(pfPiD2K8IY zdM9|UPJV7=fBBbx`H<^FoK<<tGFkuvp>QoXhZ$Rp`m&y3B_IJ~H%2ZUUbm<N(DtNR zG$0D`L_@$Dv0RF#rjbH_p{Ss7+8LR%5kn-SD%cLFq}&8w;1niYBH%WZ#w{xQD}SzY z^D%VAL;@3B$*O@p2LZxkbCcp&Qb<7#)+K!$gYrWTTaH=n@CNKDucjj)YBcYkjUinj z!xuCr5V({eAyph3n}fpRXt9_T$YL~@_L2VT0!(Srna&S4wmT0t#-oEdG_D)I5{?!p z;~SW;b=uqJP<r8CUft-SAIv9_;6qMKe5Zif>cmY$>e9*K0iViUvB(VBOkva&NZZ4Z zvj+@avxONbKAlWjyOv?|!T-fO2?lY!9wF`%C?l^5w&xXZaFbN6iEwD-<6&SB6~Y*l zx$l5;^Fc)S$neIABlrW%rxJ^|YfOr8sTUZ(f!WaU$;&2dRtZ<O32YF3IB-%(wxuZ7 ziHe@iLc*DvHW{Y(DRfVE1ydj*%FRzpFd@oPZ*Wa`Q$T@Li!wk3E`1-8ZQ&+m<s0R0 zz0?TSNChCs4{H*rfb$4RmUcWa1L&+62Cq_$*GM-SZBPC+f&poW-;9@5e@<JObnhg` z4WkP13Lg#$#~f&D(>Hd>mjwqG9-5589!;<W%hU#s!G0iA_?Vo{FchxA;LfCU688+d z#7@cBVB-qTFgx-PjFrC?ET)hY!w0DiZ!D#xzLd2N7aY_SPaGA#9%h@?EZ?)T?_U&_ z_>8o2*e3zNn*szh&yjM9eY)5}9D`h?QC5JC2TDbJ;gbq2pqx(wONbLB+Y*OJ!9OeW zNu{E7Qmbr3EY}x2G&I0Q*lpq4n}F+6%D3@j3s8zv-4vyRSjNAVh=C97fmG(AMPu?^ zHC#?KCcY<b)lNH*VHwpy9z=RkD*x>ZXc3=F7MLi%^BKc<^tn%od8g^fRF{RBU1zSC z&mZ-1J-2O)RFDUgmX^aB;s|QTb<~gg5sKY)ccZ*_g1cLAyqu3T^PR``!Ue%0leVi# zP#_ClS~t!RIa<8O(5O{k6DH9DN?@}&J7()2%d$?0K@ulRI|Ce#Bvwuug_!(koT__O zi?2NjfSNXJJV_1D(pptH(eIZH*hx$M(pAcFVD9bI?}n3Y+wN=BgL`01eja2Lkqq@2 zGKfK?`sDfZubz{Q3B(fBmAaJ3(}*9d^ck+f40Xblj=s|prATfly~x=V8dA_2LhI(q zjtYQeLc=k%Op^-GY6lH{rgug{JoQE*0tCMPWy6otoHWI1|4pGHLGu&Iic9CZl+@w` zq}nvPbb(t4Hb|c(!eu}5Mi-2wvD;ty4CEFunNXXRv+KAk)4HqIfhH6CQ1mjQN}vcK zY5`nqn6EZ2Bb{}bSZ|6|6G#+Rzjf4^Zlw??-}nKhbxA`yq<6R*xrz;$9U64BjT%<9 z1GADt62#eOr&XR8o#d!u^GnXFT|s!Mx+Wz>lCNwq`+Idaauv96H!{)^^Ozw<YQG&B zI#|2ZlCS(bg(|=Ikk7*n$@_LiG9UUK$@}4wWIhG4F1;n0?mS`(HX!6z;vS>(cfEU( z<0yqbOFRd}tN-KNko}e3vOL(C613$(jIH^yt#6%>q_}q`RLACP&>A?MxC6kWaywa1 ztaTXy%6rOi(h_GYy@owP%5p~Q-1@2op$jn+UAVJZ8iZ7_zU1QOa@#k2+5t*pOJcV$ zhR3T=YwsHFY%aH4x#QWULd7cKrMT0%Tm!RGy3M)#mLSD@oT(-P5jI`qT(0Jf!pMcK zkXU5tzW@(Rcxo#6L{=9|R=D9end#=y3NLhy{mK-uG#@kOEx}%JG1w*Z;UR=)Bo>xS zJ7Ob%fHD4V&7lUrB-n8zwDu*kW=G{h-#l0%5{%Ot<nm9W;3pe=Lbs*z@rSw#v^5by zqDZTCAp-&6^HL`~4gNxckmQ&xbHbsw0gP&m#NRG;-hi5fIO!wySEOccZpgSF@zc0) zFi5#1`Sj+fb91zs-9$>~n{bb%&TuXJM*qZNCC#C;a!lM-5=34S(m7^CAMp=A@5)OA z?tUMTRxHtroiYrNmB>hy$Rf-dHw)fYr4B7vdz_9DM*9k9dpF)vW-Cn$DZL^-r9&At z1ZJpQaHz!}f62wDE|$BJc~;hAx)L3~jDUSbFPImo{aBJHEXykhJ9M{<OLS^EKXuY% z+S0Q?RQz#UJS{Qf=8&DV2%6Uc2JK<Fg+MIqy}^l(k4rVsVC&D}8z2fNSqRxK6STVL zDb#i!gL_6QWD7UyZOFwKGRII{2|1C=l&dw<qFD1QSagakf%><iM1mU^0uEs|6rl!q zfWpfuFVO=AhkN}7s%6JnSU4vg#+`Y_F}*Xva5$paHHgw(JvK{_L&@M?p-<j@?@!hp z&zW544Wcaj#6DnRYRZxHO&y+!{hBrFz}46pTT_T|#d{V+30dx9fZ<YiI9GX#ce{)j zHefe@b6Zxp^k#809IwK22>!#0>W`U)H{LEThG)LjC?sgH)Oi{g(8*ONFsIPns3hgH z$FP^~5a`J>u)3<96-IhoQsBy(E-(AY$CFQy_7^u7Okq1)nm)gz%OTIfUXG?3^f~g} zS(Z?6*^lUwbiGS^=kWN^V<FhVt{CBP^>4z@{WKQzJJyp{VrY-(YC;b3(kf$4dQ(x6 z?j3%1N|Iw$XKVHg3UD0Da9PJs<K)MJ!MCE}Wex8z>9K=kET)H(B?txpm71v1te|Qp zI%SQOl#L-A938QLKBq-&-X2b;J}5pu=^c*xwcX{KhHmL9+J;I-ZpD}_=ww9bU%OL; zNdMSoV0GzsfmyuvUs}L0AmsufQx_?BEI0|PUqROf2`dQav5Qm)YuWQ>Q<`3crW!Pr zvY8;rh6c#)W5zc;oeY`8J-%qS!gILO?X`|z0l=3QgR*en9l!Nn7o%_K(cR(+K)%0} zbw?bF6i38kr+`LzVEQnVIbaD!2rgzp%lSm<=-nuV8#9j1<XVjlHBD{w?e76p<{Pw# zZaf<uYy`wG21EAOU}!cNF)~%(a)MIA;AG-1G&LlJ#gPa`Oe}#MG&1Qjr0xiJnyqE+ z)+h24qu1FZ=I93zCZVm39-r%9K?3+?LKQb8ZADY-`zzXdMMD&}Glc{;;stO}FeOH? zXLE!o$K|P%S}xm0Szih)%8%H`lniB{b%ZEZwY2Tl8nM$wQXw|!$pd1+2GPd*N#TOj zFCfK=M*C7&pV||nbFPPkAwvMfaUf;I`ossx%twf!qydE_0EYG`4JXkYO(JBdi3+Jb z=j$GH$zUp07WWI7XM>DU&?6l-59VcsWknAz6hH*q9fey_<Q=2u=WsGypHAQbpvAH$ z6MF2>A%pH9i0*@iAK8EAFz&;YSAe9j{gDBdTHH>Wgd{Xy_uNt5xpx1Fa7{N4bMi>} zZa7exwHncGs5W02vL`+6iT`+e6~{d|Ji<_b$TtvKQY?dYTktE}v%W}@u3*LpKr=`f zVm-FrWDtujU4z#FHX)&E`Z(dFtJCb!@-#r%#UAGFBsOpO2_SF_&IQ;Tm`@W{luoK3 z6hO-qqL30=c_}zGgg3qN$skd(5^-ET^x}LkQJVEFht8qCbysVp+dEf2$|{Z%_QW3f z!qz&E2VV#+eF@r$;M%kn!Q-e2${KXo`zh?nQB7O@blUg%Uq%<P(+F2}WC0%uGZeWu z3`DTb1o&+zTWicW=oJ~eJV;rp4OANg()3G-HX{-~!9uI#rjhbBL^%40jp|r9OFHIH zq;f6&NNm!XWHNs#D<mtue5}F~Lw~r+1+*rvd%OAKx9W%JY)@6IV0Ow<Ap63(2FBss zc?}J=;An`NR_Ie*daG9qhQ3Vf1-#b0V0{%UC|Je6oyl=LWh)NJ-)^WRaBYFBQ8`7x zfQ%nR;yk`KakddER)fqYE(EsdvBL&qVkpq@2A8ckSr*}dA4``)YE!DIH|<mt=8zms zSDAndu_-+8NhLBwX_C5=^C3TE5%uNjd^w>x7QSIyk8Ehl;y@7aG@Q^u0~?gemN;ZX z29rW_y79sDt55z9NBOwU+1|TNu9U7Wq{tM~1$`s?j5&qjeW-h;<h#xwSI#X0o}2C` zI)Z^8<r21%0Jgo?$Vx!i$WKK&VE8a&qT|rGH>-}yW+rLbJg50SP6t%&NnnRFP~~P^ z-;EFmhX4R}vZ*2ldu^cji0hQ>SNCL?+70DXjs&pBfh1@Uk*xs^4bKD+Hn7kqyb~$v z;QxqsVKO(NOze(yWX*Qrug9=+yyjJY$m59k^U>nx+Bc~?fJGouQl{cl0gFp1&D0iD zW#ROhY(<S5T)V%<gQWJYObL22h&(2(0yilT%2<v!9gYC0O|!EuI|P)TGOEqg!di?Z zIilMQ)CzhRo@%Uvmyj0Y47%Wto}oS&t(6*P0j%Yb2s>ylojTbN+s6nF*k6fs>o|=a zunqHC=U07U{AlFZ;TwB~0Xo)%A6;8%;j&9r?GTPqRT5;b37w2oywNf0h<vJWfM;A= zqI$RGWV6KgvOEE?yWN;?y1GY23dGH5l(Vg6yWle=1fs(BishPMt-fT#5TLHPU07K; zhNM`yCIs7;fdFx|SxBsGSpoxPS9&W&8OC-aC<H|KB97L0N~MR8V7ZoxNT;p|!A8{y znQ7U$*aZl2QcSx*y=3a7jPR@k7!`z)J?&ahG<Lr&r)MC-4xF{F{giFHG9dN!#*tmC z<G9Mm9~!~XZq1eyBs<K*HUmiIm|q7#*R`FLp7Q)ET$tAz{=6){T?uB1MPQ6e0fp@( zqKHTGt}a`H&ekwgGK_A5TWwvLkW3ppYSw=Q?xvj))wVozC`uLqHI-Rm@#+Cm94{r^ zgXI;1>W0-~2ht|sNqZp2MJZ%2dYhDlf3+8@Gp*R5L5$Lvc4<8s`?X*+p$8Gz`D=w2 z+BWu%AEQA6QA{JxO9nwVvaCk9k5^_PDH&%7w>8TIg}U{*ier&R_eKMX7E&+1O7Ru+ zK~g^HRtwd!Wj1kbjqFE@$kkMjji{u*hZ%fxkwUFFly3k-(qY$K1*NAdW!e>X(rlni zv;_!b*S0}OnmD$SE_7SNp2NzsszLqc5lNw1G$2B9!w%nyw*YUn@LN$4*`UQxv1Dg< zJPyV@Va7@3oGM5+Ea?xlPGSS43KHT(2a!J_k}*<<H;62zsWrV%I0O00G-*P>WU`EK zeXx3^t^e|&OGB$Ej!4od6|T5lQo~Ws;^^tB9hduEW!=PEw*?4_L%e}~>5bcVRrKKO z#G>_*^O#^@8#EC}0S*d~VR;idQ!n(!$gqH;tt~~Q3LlU9Fw*%ou`)}ESW}%Pj8G?B zItu482xX5Yu~2Ue^Tz;5ADC|oIm~{?e3Vc%QrE?2kSDXn`KXjSl$cY3jH{5(;QlUm z20cLpmN@gaThF9f175YAylu>T#lX;|3GjzgdSXl`KbL#v*&sjHA(&u(V|Q#s2TS_{ zIF(XL1QjT8(lB96OI$t6m)@(62;m+GyXjtV9f|&1U49BGBq3CfFgEC~%R9<XrGj3i z&bgA*^vPh=0wvcp(?H=QY<z@|xDH4?YJNL}LogCXOb&xZlBZlm&qWiZw-L-69ME&Q z4V_6*`qSA3;{>bKkg@S-gqtKF93Z3&VYN{Fa7V&%f1U&!LHFFg#q*yKkJ7ycl(F$n zI9*deEow7HY^lzBjO)*<_utMKGJ?ZkM{ycp=7@1pOh%bthXdlmpsF2L3Z=zPM6a1J zDd;d>BcO!&QbxxJG(+N-03*_&GYy(vdT@*&T?j|W_kK+D2ye8Ka3e@B?S0P8t%CHn z%pSo4GSFG|Jp#4aJ(X#Mx%ygyRHuVg<d$M;8oAPm@y(Iao+qe<1uhvgJ0<WD_AtO$ z9cBfDY>f_)NPy`)2raQFA6!o?Y8xzgxig~Nn9M(9#>rt~#WnhT?o2v`2OFE3xf;+E z-YI02YSGeJrs%TS^F{^&1pg!3MdTAbR%EhTM;C{qd@|}|d2wFT#$lf(w?x=91I!C7 zmmYGk=Id|~f?kJtoFFN2OC&!Jj1{d5#bUxB;eouNAQjIKX0<1ggkUhJI6lM4o<YiM z(gv*zVo4a`R&kAyL%c3z5kiJlB|<nIv7y0(Zc5BOFaaVP3=%_Q337s<>A|&fP&#Q^ zQRJx{Ap)r=cx^i;&L$xpC+CCA1gKxiFZf<|G2tq*UW(sAffv^rl&Cz)najiF`~+Cl zH7B9hkDu+TGVs$UkH32U=*3shnR*i=jFgDwhJ+SVATacR#UJ0tnkUSB#5u@X^E@!V zSgrLl;4NvH1i?{Dv}tI(RJoPmGR-nP%kU&qz$^<T=*mG~5o*Ls+hqrajWR~<I*;l6 zH<qwl@#|sloK3KVjB$2ZDUg<LbVo^KNX;a!*0v#~M}+<$$kxdeQ=(qj!Hnk_o&tMi zXGni~klUn_!<B>IJ95&617)(^CSq8*GDWL^@VX#|dgbydFT<AVl$LbGar2{NFV{}E zyB5l_i%|}?SuRGR;{R;Q;OdHv57an8thl&5EP4lseT`Wa{I|VkDiK-j)`0&GEzT~6 zqeG;p2oF3;Ch65?EfkqT-D>S&xr2WcAYgj{_wVqX9mgM@cm@-VU4bmjs%Rpb?4aKv zJ3^Y6(GZ7nvItPEm0_-(SaE>O2lAGr;qg|a#6jNg!K@wISvOX*KM^qJAaDn{^T=4$ zuQ`BuJ8`o?=8q>N9pV+tl##_VIQJqc6l8|kXgobc@I?!IGH<IyUaQFqy`)YyM%Xfh zjeyfiExzBMe4MF62M>MV++>kk-N8)!I6{5lpl-I#wrQ@3znJytvUaldQ@VRD???)0 zD_`Q`MP=U_3<vZ66dFbfcar0?H0lhat35@E6mhFfI@IBAb)~Eg>@)7wV;pADG<&4_ ze3-V*eF9S>J5kSK{WByW=pXkEbBJgR=jP_3joU?#_$9Y7#;Ve?)zaN)9oJ0D!9bXj zAs>s2%ow2KQd3YD_d8Ix#1mulgcUV*C5J}}k`v@~>C2{8Y``-c#DW}W0^f#0bq9(o zQo<s~s1@X|%LTvYFndk{DV$(fM@%9E?W89-2MgR^iCumzIKZwA&XQmo<16<8lOc&z zsDvq8-M5H_EWi~4-tTli+K^NW8~u|3`EQ5e?zn5O1(^dp6&XCRx#^jGCoGody>o`m zeU2>iK$?~T57Sk}*tsr>3X%|-v3|;xGQpDqO9|QAFeUJeqHkVVy}AY?*La39U(r<r zH%jQ56Tu=-ej6H|&O@QzRq*Adovy>2bnf?Cu}=nJGs93zoL;&Mgt%ac-sLVz%Y1v4 z4R8Rt;uG=|98s?9%3WvPp)1W1uk_C#Voaf2P*z;pnchux&2AKU5$qU&*%s9ix<T5w zfe<tMy|g=~&B3C*y1z$fizz69hb+t<tKlexqX9W!hn~y4Iao4;4Dy4sL&)tgrg8%H zFzC~bux%19lJel{VL+@u*8=MkMLd2o8o<8WZC!N3VJ2a{Syz97x#C#UHO;5!dpxrg z>7$F=;=2eBEj#IvQn57{HOn8;a|pcvX9(nol`QT*<V-BnIpyLe>*V8P`wul_&q28; zX`wx&hahY5LP<T!1OC{C4cPWLnXmLdpI2#3k5YlR4_xD1m}ofEdz)yCZTU}`4^{(E zuAuTMXgQ5{m2w50Elv2q5xZ`~()epN7z0Z;h!#>SR%)1`Wb`$)&c~U=_g<cJAM4s- z@tpgY0t4N0*zrU$)5#u7YEjwljX?uV*cx!+Cl{fWLD365DgANYD|2oyY{qTj%A}x{ z5o$v<e`EFRr+27J8pjz3cjLTk%Ad;esV^I!U>w0n_-Rbl4ku5c^MxhL%LC#=GB#gZ z_Sc$nj>+u-7lR~h*9tfV!~*_gO?%z3&LhD(;V%DMTE6KInHg;f0aSA&%!H`Otvx+) zVJ5+qyp+(pW?Bj1C5$n81(C0z%xFIDcgDylL?6b6_pit2U~Z*1BA`NhG?`tz#-UyN z`W>=vA55oqgRokn?A45pb6}v5Y2dp?`1G~3)8N>QgRk3y)8Yf#Ym#@tPP|8l<=8A( z;1rv@tyJ<CV;<Hkuy>5$G2Ho+>7_q8&K^0!2XZ5{cmt51uCyk*fE1A(L(xo*O;#1_ zTBh}S6;>Mzkdw%%7KV9Pf{<L<!?KS(BVz0)RANW<3Lcu;P{Dgi+SfRY5|Y?HKLQy0 zNQ(2PL~bmvfvqN~ut^5Xb>~c$E%4AO#XPwzTg8LMM$2>Ncv;$Rxi~gn4JZ-7%kR9m zlt4~p!GJJ~kMi~!vGCBYW6#74LtD=4aq-#$tb$>eh{K4TE!!5M&DQ6C04V?#0PMj7 z2(s{;pl){Dw&FmEr1^0l0-e!hkBd_r8gg?#;V1zm)Jd|`?2Sk)xmkxt?X|eS>TH6m z6>mdwZ67<D*rI$eJeEUK+N*725g3*N@t_cmiE@vmmIR@*<xSFuD=BKtH;%8|WO*Lb z`4a%~PwE_#zNdYdtO#&9S1Mzd1&X5_I>Q~gEr5kPl&D{oI3%$N_9PbO@>(AZ{Hn|v zUP9X)-Zt2tPtSM2Y|zZv$pqJ7kC6&n(Sp8#ycBMZi+!R_6N52q*C&+q8oEU@x525J zyOt%dOfv<6%?LJVb954IEKo|-8i>%$4qr%OyDz7U&v@1E6EbA7e-|N@iYS(D-?^K% z&b`=015lu|vL>-YP5`Z6&ynT7fD8k*xk(Ks&=4v7xYh{SfL+2cYP7dvM~yEP9KKWC zR=Iwv5!CkxutR%VYe*$bq6idSs<<o^%%~+c=5ku8)xE8k$lQ&Xyx{76Xr^7~E_Bx8 zTc$pfi_6HWf+oUPQ^JkR7$?m*t;FH9FeyRv!?PfgRMNam?tOs;agZbHp$iwZWa6rN zs(?(v>-r6;iSr2((LpDL^)AOvLSDMg+)(uK#~(wARfwwiZ9Ubx5+{L^9HaX{Dpd4? ziJ@6YqHI?@{vFY#S~uyDPxah&M)9_N>chT~9brPgMWn-Cdj!6OOd2n-vhd9_A%q7p zamGtMh=>>9Y2aWkVxYm_zb(Z9VME+0t`wZlVYNFlvb3=s?LA!E1ho9SWq-F{B2RD# zoAKCT$xZeDKrG`m+yWhzT_~++lkAP@04>()QedS&^NFEp111|5X5i14AaBhYg@b@S z6Bl$ZFH03K#BxQx;jq;jDOZogrs)T0(Xs5b!5~noK~old=@2mk-i$MH!Yags;G!e} zBCCGPeWBcf#1bZ_J{HCDq_*}zTs+#3H3>7T#XLWi8mtwK7LRLifeCn`TNY(o)xnv} z?ynVhodWs@Iz#PLt8_#vO$Ka)$Z}fOSJ{=vGgVilS`9yJ*_DVI<Qa})dWs82FDoSH zAQ$6J(P2ldv;@^`-Ql=*XleZma?^#Cj|4z8_(xhU9U7S<25ZX;q<_=n*rlz){MS8# ze3#Edej06&8CJzJLF^SB{B&^ybP;PM6woXBqY*NM2seb_XqF7@6f|g9BJehDpdKxL zIY*D8-o&7B@`tZRGKUA~IJp`l+Zzkn;?xG8n52_ZAP~uwQQ~6{6Ozk2Ub;trBD)b! z(gh(A<+$xf%J>hsRb{UzR(HFYb8gh#u7Nk8T8yp|WP{Ul_BEtg1-Fz8Hg2A5-2AKT z=I0M@e(~_;u8+vJlU+u^=Yxl6`*s$Zv1YlhO@>#Oe3?{?$j>JF5?T@i$aFqd#~xDR z;Feg?fyjmD7?BP|`Y+eh2{$;OsM421B&5)CwAwV`tgn-1Wg=9MUuZDBP+B18N{qZa z=M3E$NYz#vwFp%3Y7nvH_=+*Tma3@GH8yg`W0ZD)EN5w@Bm$fpnl4Il5jr@z)EqDT z;yJoEVb4ZHd($R>S<!0bmJ>a7pCj%tJvkfTF71_W=S2<-G=1s|h6Km77-m4NU3pM? zm#|!3F(=HI(Y>FK2GGVPs|3{Hou?Bo)``jOExLR8`(%+47W%G^CT{WP6UpihkQFT* z{&@ioT6_kp9=~wEgM^|;VTDs6Qw}od9W1O<!ZV$0p{+<X2p(;sL{h#$FKJpE(hM`` zq4gUxZ)Z1051C8nW*ftUnoOEYU{V5RatGwG(CRP`Rui+Gc3g}?+lh<+X~(6h%5#Rv zzj=!Z{ACZ;s5wkt&}YjIN-0m~rxT&S5mVy5uyYyl#VMxiAOp5Qq!W6oNvH!F*W1c` zu4IMrpu;XqJ>!}RQ5AT>+MU{owmD75R6+>4EB}U-AJS*yi<MTbZ8@l3e`ZZ2NJ!hz zY*46(B^A*GAsCOO9O>)1N`hcf2h)NVkm3laa<breCuy+xl1ygbeWw=PtPWYGln}Lp zV*wIr0@Dd)eG6E_p--oioZ*|M18+s6rlt8W*Q?HJ<kGTOeL`!i_#0crtd%U`sLrS_ zWINNAdtE|#>23i6h37WB)Hp8gA7cKObBue;(5eKN4p>9!k}8YL^|!G*{f41(AG(}e z8%MO1flI@Yei9romWvQKy#ydNdTs2N%{L_*Lff^g0VN5Lobmi}I5<B8nV@4$-X%z+ z<r<bK<c34&E*U^^cGAxFPPaRu95@wf662@avf7&`D#$R3T>k5y-!p4cLgEpjqTmin zc-NFa?#-ZVr&NF|RlfRif8|B?{K*$z{l$~)#ph3Vvu97ge9|-l;x0|m8&>Gh1XQ2M zns5T2Ei*gd)&Vs8cziZNs$tW=7Z-><c}={UbHQMVM~dSjj(|_f_ZODm@%@!pUrHvd z%@1Z0t}zn9qz^cTrVk2YKZJ)r*N?`xfvjS3WY(#z+q5AhBc&SJ`Gj@L?w$ewoL`X( zMN}D~gd2M~%Hjz3_F>+@AmJ?0hr?jgM6o~9gFSH<?o|85_x0h~Lbrp`l_;~~df2(L zG`lpRRMb`c%}*T+eI82hv~Jz;Ealg!Fl_yf*Jf}w1)0Q_qmnne3a@)B7!GB`1_?<D zJ_EeMi?>neg^@0<UZ>-9kGXPGFT@pwsb>fueRL!@NZB_-+)U{eOm|^qPZkA~qvy&l zYKUv1ly~B(OxG2z8f#=X1n#mNrDdS6C*w7)Cd>M@6SAg(NyPAJUMEq_Qr6f^ph!<p z7V?vo1S^fdY5(ohk%<Q;aQ!an$Re%`%9xU@P6NvY(#mdHC4ve&;ueWDxl5q{BnrZW zCL^vNcjpq0OV3ET7X){&;L3;^CnBFq<#)8L8C-AOx|JpAH0=lg0ZBzLU7{&{Re`=X z)i!Ub!hp*gH?)3|8M6v=3WZ?3qI#%KQnfw~-EEwj;F)s^Ii%W%xSl*)zq>9O@Cv4J zYB<zro8f$!oFN79%Z`y+^&j$GLmG*$OVPbT^>Vsn)n@kp>-nygHr`?BT*d6K$LN)L zHLVN12-Z0-7REIaOxL(L#>3Nzu9ikAq>?qdA<MMsxyY87k-wBhIxcYZv?P~wEF=4$ zsp#6R>m-zpbrn$suWd;A-8qzYuH=9p)&XO<adO*1Y8A0GQJERbdr7>9h=>`~jM1?` z(g2wd3l*NDztj@!pvXTVTG(<Su(a@0EabSO2&MpQEL<bO1OjCcA`s9d#ysG3mpd=A zPr|d(%1q!51^wA&1y3&YvRYPZpCgxNsFZDlo-*VX=21RCgIlMS7@$jv;CfXcRbWV3 z!`H3OW~s@g4f?JvI;Xvfq=~WI`mnaA7Qu&^Jn(**AK$;TAQuyyS|39DBQ7r7x#XwH zwh{1>8q8ZrUx$H1!|(b0lYtjo81hikU;oDScBgNkuzPDtcA@ueDMZ=YyLh|t<~%K8 zq`k%65U!cGkUsmF?txpY`ga>!A<3xNb{#*PHo>QKV2GMy_tDuKMy;Yj2Vd(}4IWJ@ z$#$<;Q!+tDT#go-d&G_7<nIpwv|`f-UMl4eDLpb65wS|px0tg$2uZ7&>e-t_3;wnA z-W$Vv|Elkon|6X-0(C0pjl`;<t;pTaB<@s-HWj>$5wRoKR;ia8EH*aoo3_<*@yb*1 zZ!2`mU(d#KMibgypCfoM+Eovl$ybaLLKVoWj)gd)qB~~s^+l+3Fl;@xNB|mM61xi; zFwvadwG9Rs9P}H^D=2`Ux8W^-_XaW^=r_J^t@5a+xM-lT)(hT5%{E%%tz?ttGqHNR zwNZNEQ;1|xhCQ%Cadm+DnNDeFjVre8Ekqs<iIAaEVDqr9qKFV9vPqF{%T5QW%PL{l z|B1%OT}E90;=ivOxMZ#(AwuYz0zpL{E|f$l9ej(R827Op^^chd)_$^-x<nj&%fym- zn~l)m2Dqokuzv7DUn~d-oD^ma${wEKnqYbaLsx_tB{Lw4;tAytJTY&hPojw4pVU1` zTbP*h1<hzMMkPYP&@RdGo@I)~@-N!|u7V2Bn3I{?`7uwbSOL|#eTjLaP(<ed_a;IQ zBMN<Pz}|-S8f|2hCf=OD=tIw$ZuhOL5{77ukLgk37jYtJi}x<Lzl~=`U@8GlJJB@r zu$YE&$eJV`u-$Guc4(gKB4jb)=4$A3Nvrh2yLL-6ATZcbv2fY1S+~NFDFN)S=tY&d z6O=CfVl_`D=<;zzG#SW5hPG!T1~Q`Rt#ULgxBZSI>j2%0Be|A>o$^!dSjIvc=ga>( zQ&c68WQc|#B?LMgk-i)@dx{y(Ej7UnFVgvEquFPns1+Pdy{1qpRZv~KfE6oY+WEx= zHUcCLKe_0lC-IrL{`vFZjLB><HAzW(EeqLz^PbzW4%an2q+}iAX+kIhudU{U7`PPY z(<wr#v1TAB6I_YI0jAD-<704#uyA~ZbGrCpU6!Y^Ug{;?X$dj&@Kpzf$AYj!dYN6c zsx5ixA!#OApK_DEg2z@8xV;AdzDjD$VU<T4ZJ+3B3o#JGc|<zEB^>~hhB*NcVF)Ow z6CAi59MWuB;Ex7?;+!x6kT}u00X(gf^se>N2exGyKSd9v6{O|Mk1Cs-GGcBQVG<p1 zu$*BsE1oy5Iy+a=ha=k2Ijkk3`MW2RutzMX2`{o_M&FHO67(%;PnIP&)Jq5tKGn7| zhv^oZQZm0s>Ubq7o`&LZ?zB^3(M{vvg?@3q6M!R-C~^rjmqQRmD5WkElyN=8+emPl z$3S8k_@m!DoEh<YP^iNGr6IvC_fW^GE(i$DPJ^MDlSJa5315ghR<ZLRf`BPXd|eLE z{hG$b{~rYbuTQ4#K6rI3>8GPY(v)}&q+gw19u?a|^eK|@T;2381AQ5*iqc&CJmi2+ zkZcn{8&^cY3kl(&Fq|TMv6vtoo)R;5G|8?5r`LHYDI^7AJ7!Iu>2Ae<I6Rd_I>${e z3>N1z=PuTLdM-loWxk@~OAvhB%&$^C46fq`iX1V}c`elCQ{k)f!sg#K9|nmR<T&7$ z=`*y%6STR-=)f79vI#W)SyiOiZ<q+(3C*5JN&!b%633vg{TP`>GFT5tYs3U+oMy2c z>8`IfRY^R7%nYz}ilBw4Y2U}ohb=`T_I9dt1tT_$L^Q)&)isK%Av}ghuSNqToA!g4 zc#0<Fi8349FPM*-5E2ryUPM0OASys<QXcp$F+T?1sYvLjHb*?A0Xh2_Y1aF%be(k1 z2!zHXO;Y9&ywUoQ(&1dX0oiCY97*C8A0!OH2Nx2VK=0YZIqoE&eNMK<Phb2NZY$U} z5DWn+xC*%eoSs>kGjZuSe1-^I8RuyemtgxTZmvEPR0}F8W-InF{Z-(I)?;9Yg<?Eo z1ISIHH}~(7(11mM2q_wQ7IbGX+Ud1G91uU+IwIp3g8VA)j;E%1hzD^O$31ceqA<te zL7L&n3!P1~%%VNvRz&~;0eD%LDhn@tD`K%X#lO~^MC^#T9*_NXPUQ0UG37DVtQQf3 zAy?I&`ivcO9$1t!f2{6bNI0t!6QWa~CSOYCsxEHQe550Q2US$F+`45ykT~=fyAA3t z*-n5sd#`x%AJ}5pBtVQ?2&I6E$8_>iW)_p!KSy$NCvdwFjLw|!cZZx77DbrfsM28I z6H<zRtf2wd{#At+6?jtL0XI#bd2_zP37otKez4tFt=mT5?#qpVRG%cyg%q8ke)L|I zbD^P|Mfc{YJ00TRn=ts0p~0!PIw-dsMKIS47~|jGo*#(h<QY*%0;kSSha5$5c`C>D zro5SHzRa*W#{`)e@Fk4in0A)(3VVidFw7zu<p5bN$vjMU7PMoG8Wg7NMz<rN9Y=kx zUg(Z(7^EI+k-Rw_Ve$%{1))VaEwBP*)&&(p;Lpl53Snt+W%&UP!EVTd;+}zWccN`+ z%pOKSR8CTwhCdnoiQ%{ebtzZ3yVP%WwE%{u_t!Ze6mPs1GMM*UtHzdM*+X1!g<Uzs z5Y4EHPtsZz?E@-uI#d}S<*%J+Jmg4;5mlrUg0t#Hc4ml4B{BC^05XKqaaXowIF&+y zCO7T1XC7}Ij$SOnf#cG!7?Z(8vDx{kvneDFCV)bWzZ*<XSW=XWNNSoNZ?)k-tX}Mn z1!ULBUdWz}M8wfsg@kdA8Zgme<%Fd}xfH`*bdgv`G6Q=voOCvY^gy;y*@F7b>di{h z3XAb0n)ZINwF#F%rWi!v>Xs|q(R3sR@fuZosw576+-A?)Yuk=#w$@x|9Rbv9**zB2 z_5$#@x)d=WdE)?{>pPr>Bo86S1P$dQq%S~fJ6a2Q{-lsYG;|`MmyO8!>$1gIqYiai zuD!Rlw!L=erGoF1=%&Q&=84=zvNxRXAPFN{_CzO+2>L`4tVS$A`KW!tAC+Jrfo3Ic zgCr5JK#N)AAY$l!SwsQzD4M}RxDg{sVSeDNTb4AZxCJ>@vaZbI$my?%OsgjDitixZ zTVrj!gBLa~_42x+0cX3)3b|F$H3b45`V*FlmuUx6{GJ05dAvx>xF`a6G#jN5XBdJw z3=#wc%l762NP*&2$Ris2wc#KR3AY(ekack?k+*4=Sa(1=WJ*Al&gAb!Bw^tT#R>(r zF;~k-8o8ukO1h{~wuV1$uiahyFo}*33RI$|t@^o;J~!E3R-7<nk}Lts@XqIN#>Gx* zoF2pGY+2Y-lFH;>L&7QM+ZR;c2!1QKtI32)c2u341XJVU&Lu7T+CcMMoIE{A2>s7w zt~8r4nq<f}Rocji&LfegN=rJauRwXyI25(A+eIuo7-U@ml5pe!CrF8eWILcwAOC0E zSa_YU;^)HYx7+&HS*TA$>+x>3wYi0?!otV5!F;)f-4U0^_`SAp^EH%%{M-&)VJ-WR zcjy2odV*kjPo5pYwY<7)`CFSN0JslydM;u60`AL|58l^JY0Gycyj%HjbMx*;_de=v zGC@W7JQ#d*cl*N+`<wUfJ-B}l|2w#|_0iqtrzrK|gS+?dJ-GAW?!Dptt^0TH-~H(B zhxhRx>f_Bv_^*Heo<BoFckd7H-(mR&+xI`ZpWlD5y?Ous{o#Y{?M;+L)dzR*WPmpL zYsmKQf4F%E|9QLj?{A_09hBI9V7)1HcMmY2gU!46Z|iQaw>6Z{gL{?wcL^7JP^j)c z2t&6oED!kmC}$%BtU!kJh<>#mN)cR+G{0AR5BRt6U!P+kgxdesCQIXckADd<;k_r= z#<x58Z<~;_&q|G)HR^Dp=VUDP_YdxFnGfB_i?Mm@Zr-`P%Pv+?bR{Vl+)b?#wi z@GXoTzjfyB+&_5mk<TlpuyIaGBjo5f<_~p*TZ8)_Zpp|1Wdp5jm$B-!0?YzG1JGLk zem`Ov-|ld9fO|{m%FFj3+_^KjcW`HW8>R2um481Z*3o+kL4wI<Y@>Yd?t?qSdl+n_ zm<RVB+}qBvz9<^*fb(*xZ?`^p-&Y)&pPcfpA=g&c=;{aW$HNEO@nebGY21E}a(2|e zo{Dkx*T4F~AN<LCfAStb|7rivcK^%2`wu_<(?59cJ^uGk<f;DizyB}%D=cJVkV9*9 z0ozhqdHduiyZ`<_|EqudB`Wg2KQ2{%j3-(-h=!H=Km6xE+5L~-jMx9=kKTKa|NXdB z|Ns1<*H^@BS8D&w(NA{oo&DL5|Mlt*-s69NTB`jo{)bBKkgVg^zxu;}#G*gM|Nbxh z{qFt`@bBOJ8$QG5`|tj7qR9CEyWc*3xDV3>l*NlP$R;6g=6(n%3VFS#d4lXv?Aw=a zxF}_xr<(a0E*+ll3r@cO?t_xu@B9DlyZ=dtS=YvV|J{!b17Qr`fA>RD-1p!8p|*Bb HoOS+hrn664 diff --git a/examples/example_framework/instructor/cs102/Report2_handin_28_of_28.token b/examples/example_framework/instructor/cs102/Report2_handin_28_of_28.token deleted file mode 100644 index 4fe9c89fea3b77998c9201c910a729cc1eb16d89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81409 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 diff --git a/examples/example_framework/instructor/cs102/Report2_handin_5_of_18.token b/examples/example_framework/instructor/cs102/Report2_handin_5_of_18.token deleted file mode 100644 index e22d430bac7ac0c21c0931fd87cd6871fa95b8c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78385 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≫b~u@Yv!sK`r^S7 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 Gn*YD>CmzTE diff --git a/examples/example_framework/instructor/cs102/Report2_handin_5_of_28.token b/examples/example_framework/instructor/cs102/Report2_handin_5_of_28.token deleted file mode 100644 index cb4ed5d6abfec70f738f205440eba8f73c909f7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80005 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	VYv4OJy9IYF%8fHRvBO8C#SY*jkv$&Mj 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 CGTn0k diff --git a/examples/example_framework/instructor/cs102/__pycache__/deploy.cpython-38.pyc b/examples/example_framework/instructor/cs102/__pycache__/deploy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d9b529d6cae3250756694b95ca75ce15b33bf86 GIT binary patch literal 760 zcmZ`$%Z}496piy}(np30G^)f0fXX5RT_S{#KuBy@0JE_oS;4+-O_MnCgPC?aEZFc5 zu;iDrLSn@)u;Mz^AP~Ziug>wk@jX61&+|!u<J+yGdJzV}cTfHtDUv6+%ts6&5Tp(o zLJ9FWtivXv(SV~mZW5XdIIc%cO49)+bw)GD-i<#`VETp78RTNL3-t#`fLsMvcoIZF zL^{muQ;a-?nHVnujFUwmreY>?ak7cT4WAXs>tH$mfog&!>C;!xDQ#B>7Y3~B*hW_Z z*jm=W^pL~U?M-F3pqcAxRS9Ml<5kl+(}KlpW?I=XA@$2XW2~uU%h>Z}L`RKm<$6!Y z>h0PYURj_iTmp9%4&$?;IOFEQ{ng>rL;Q?&0$OX1+i&_QW8I!td<%>n(tCwfJ7^DM z0VrZ$bo*sYCq_BV!S|+_0AmMq%4?`v=DBPuD-~iaz}~#Z7RwWw51otS{Z<;^EueQ? zEA8>}otE!W?W)*Hfsz>4fOPQ0=*Sn}{eSUaR_WMR&%Hq%=;GQao#1vKD$~auI@_nH zudN#J{teDE8eRy`{4Qf(1D)d^L-@&_J}l1-Xj59Zh4Pu=uJOK=|7a@V5}&CCa}C&9 u<9QcK?@HMks~xvWmxCGQZ(sZDQKN*b;W5FU<|c+2InIpC!i-GFocscNIOT}| literal 0 HcmV?d00001 diff --git a/examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-38.pyc b/examples/example_framework/instructor/cs102/__pycache__/homework1.cpython-38.pyc index aca3c8b22c11ae4d9d824d7ba252b4194f5db12f..d67337369ba5bf909f1eb07c3dda178779750fb2 100644 GIT binary patch delta 20 acmX@Yc7%;Pl$V!_0SJ18^)_-_G6MiE3<KT( delta 20 acmX@Yc7%;Pl$V!_0SG=G*4fBy$qWEA<pl8n diff --git a/examples/example_framework/instructor/cs102/__pycache__/report2_grade.cpython-38.pyc b/examples/example_framework/instructor/cs102/__pycache__/report2_grade.cpython-38.pyc index 5fb59bc9a735c474aff24e25cd7c318cc0869f8d..42fb3a4a526346eae8494f38ac29d254b5f30b83 100644 GIT binary patch delta 3461 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 delta 7469 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 Zc=q>4Z<ZtZd4yqb<)!5Z>B&1S{|}U&Ox^$h diff --git a/examples/example_framework/instructor/cs102/deploy.py b/examples/example_framework/instructor/cs102/deploy.py index c585908..4e47e5e 100644 --- a/examples/example_framework/instructor/cs102/deploy.py +++ b/examples/example_framework/instructor/cs102/deploy.py @@ -2,8 +2,10 @@ from cs102.report2 import Report2 from unitgrade_private2.hidden_create_files import setup_grade_file_report from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet from snipper.snip_dir import snip_dir -if __name__ == "__main__": +import os +wd = os.path.dirname(__file__) +if __name__ == "__main__": gather_upload_to_campusnet(Report2()) setup_grade_file_report(Report2, minify=False, obfuscate=False, execute=False) - snip_dir(source_dir="../cs102", dest_dir="../../students/cs102", clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py']) + snip_dir(source_dir=wd+"/../cs102", dest_dir=wd+"/../../students/cs102", clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py']) diff --git a/examples/example_framework/instructor/cs102/report2.py b/examples/example_framework/instructor/cs102/report2.py index 381cdb1..c7be1cd 100644 --- a/examples/example_framework/instructor/cs102/report2.py +++ b/examples/example_framework/instructor/cs102/report2.py @@ -1,6 +1,7 @@ -from unitgrade2.unitgrade2 import Report -from unitgrade2.unitgrade_helpers2 import evaluate_report_student -from unitgrade2.unitgrade2 import UTestCase, cache, hide +from src.unitgrade2.unitgrade2 import Report +from src.unitgrade2 import evaluate_report_student +from src.unitgrade2.unitgrade2 import UTestCase, cache + class Week1(UTestCase): """ The first question for week 1. """ @@ -8,9 +9,6 @@ class Week1(UTestCase): """ Docstring for this method """ from cs102.homework1 import add self.assertEqualC(add(2,2)) - with self.capture() as out: - print("hello world 42") - self.assertEqual(out.numbers[0], 42) self.assertEqualC(add(-100, 5)) def test_reverse(self): @@ -18,6 +16,12 @@ class Week1(UTestCase): from cs102.homework1 import reverse_list self.assertEqualC(reverse_list([1,2,3])) + def test_output_capture(self): + with self.capture() as out: + print("hello world 42") # Genereate some output (i.e. in a homework script) + self.assertEqual(out.numbers[0], 42) # out.numbers is a list of all numbers generated + self.assertEqual(out.output, "hello world 42") # you can also access the raw output. + class Question2(UTestCase): """ Second problem """ diff --git a/examples/example_framework/instructor/cs102/report2_grade.py b/examples/example_framework/instructor/cs102/report2_grade.py index 503237e..eeb50ec 100644 --- a/examples/example_framework/instructor/cs102/report2_grade.py +++ b/examples/example_framework/instructor/cs102/report2_grade.py @@ -4,14 +4,10 @@ from tabulate import tabulate from datetime import datetime import pyfiglet import unittest - import inspect import os import argparse -import sys import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: @@ -61,53 +57,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass show_tol_err=show_tol_err) - # try: # For registering stats. - # import unitgrade_private - # import irlc.lectures - # import xlwings - # from openpyxl import Workbook - # import pandas as pd - # from collections import defaultdict - # dd = defaultdict(lambda: []) - # error_computed = [] - # for k1, (q, _) in enumerate(report.questions): - # for k2, item in enumerate(q.items): - # dd['question_index'].append(k1) - # dd['item_index'].append(k2) - # dd['question'].append(q.name) - # dd['item'].append(item.name) - # dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol) - # error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed) - # - # qstats = report.wdir + "/" + report.name + ".xlsx" - # - # if os.path.isfile(qstats): - # d_read = pd.read_excel(qstats).to_dict() - # else: - # d_read = dict() - # - # for k in range(1000): - # key = 'run_'+str(k) - # if key in d_read: - # dd[key] = list(d_read['run_0'].values()) - # else: - # dd[key] = error_computed - # break - # - # workbook = Workbook() - # worksheet = workbook.active - # for col, key in enumerate(dd.keys()): - # worksheet.cell(row=1, column=col+1).value = key - # for row, item in enumerate(dd[key]): - # worksheet.cell(row=row+2, column=col+1).value = item - # - # workbook.save(qstats) - # workbook.close() - # - # except ModuleNotFoundError as e: - # s = 234 - # pass - if question is None: print("Provisional evaluation") tabulate(table_data) @@ -159,24 +108,20 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) else: b = "Unitgrade" - print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) s = report.title if hasattr(report, "version") and report.version is not None: s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + print(s, "(use --help for options)" if show_help_flag else "") # print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] - nL = 80 t_start = time.time() score = {} loader = SequentialTestLoader() for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) if question is not None and n+1 != question: continue suite = loader.loadTestsFromTestCase(q) @@ -186,104 +131,28 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa q.possible = 0 q.obtained = 0 q_ = {} # Gather score in this class. - # 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. UTextResult.number = n + UTextResult.nL = report.nL res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) - # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite) - z = 234 - # for j, item in enumerate(q.items): - # if qitem is not None and question is not None and j+1 != qitem: - # continue - # - # if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles. - # # if not item.question.has_called_init_: - # start = time.time() - # - # cc = None - # if show_progress_bar: - # 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] ) - # cc = ActiveProgress(t=total_estimated_time, title=q_title_print) - # from unitgrade import Capturing # DON'T REMOVE THIS LINE - # with eval('Capturing')(unmute=unmute): # Clunky import syntax is required bc. of minify issue. - # try: - # for q2 in q_with_outstanding_init: - # q2.init() - # q2.has_called_init_ = True - # - # # item.question.init() # Initialize the question. Useful for sharing resources. - # except Exception as e: - # if not passall: - # if not silent: - # print(" ") - # print("="*30) - # print(f"When initializing question {q.title} the initialization code threw an error") - # print(e) - # print("The remaining parts of this question will likely fail.") - # print("="*30) - # - # if show_progress_bar: - # cc.terminate() - # sys.stdout.flush() - # print(q_title_print, end="") - # - # q_time =np.round( time.time()-start, 2) - # - # 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 "") - # print("=" * nL) - # q_with_outstanding_init = None - # - # # item.question = q # Set the parent question instance for later reference. - # item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title) - # - # if show_progress_bar: - # cc = ActiveProgress(t=item.estimated_time, title=item_title_print) - # else: - # print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="") - # hidden = issubclass(item.__class__, Hidden) - # # if not hidden: - # # print(ss, end="") - # # sys.stdout.flush() - # start = time.time() - # - # (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent) - # q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title} - # tsecs = np.round(time.time()-start, 2) - # if show_progress_bar: - # cc.terminate() - # sys.stdout.flush() - # print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="") - # - # if not hidden: - # ss = "PASS" if current == possible else "*** FAILED" - # if tsecs >= 0.1: - # ss += " ("+ str(tsecs) + " seconds)" - # print(ss) - - # ws, possible, obtained = upack(q_) possible = res.testsRun 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 = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} q.obtained = obtained q.possible = possible - s1 = f"*** Question q{n+1}" + s1 = f"Question {n+1} total" s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) ws, possible, obtained = upack(score) possible = int( msum(possible) ) @@ -298,15 +167,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa seconds = dt - minutes*60 plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) results = {'total': (obtained, possible), 'details': score} return results, table_data - - from tabulate import tabulate from datetime import datetime import inspect @@ -329,7 +199,8 @@ def gather_imports(imp): # dn = os.path.dirname(f) # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: top_package = os.path.dirname(m.__file__) module_import = True else: @@ -350,7 +221,7 @@ def gather_imports(imp): for file in files: if file.endswith(".py"): fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) zip.write(fpath, v) resources['zipfile'] = zip_buffer.getvalue() @@ -394,14 +265,14 @@ def gather_upload_to_campusnet(report, output_dir=None): 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)) + # print(" ") + # print("="*n) + # print("Final evaluation") + # print(tabulate(table_data)) # also load the source code of missing files... sources = {} - + print("") if not args.autolab: if len(report.individual_imports) > 0: print("By uploading the .token file, you verify the files:") @@ -414,12 +285,15 @@ def gather_upload_to_campusnet(report, output_dir=None): 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) + _, report_relative_location, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import nimp['name'] = m.__name__ sources[k] = nimp # if len([k for k in nimp if k not in sources]) > 0: - print(f"*** {m.__name__}") + print(f" * {m.__name__}") # sources = {**sources, **nimp} results['sources'] = sources @@ -438,9 +312,9 @@ def gather_upload_to_campusnet(report, output_dir=None): if not args.autolab: print(" ") - print("To get credit for your results, please upload the single file: ") + print("To get credit for your results, please upload the single unmodified file: ") print(">", token) - print("To campusnet without any modifications.") + # print("To campusnet without any modifications.") # print("Now time for some autolab fun") @@ -453,8 +327,8 @@ 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"""\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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, too 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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n item_title = item_title.split("\\n")[0]\n\n item_title = test.shortDescription() # Better for printing (get from cache).\n # test.countTestCases()\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n @classmethod\n def question_title(cls):\n return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n # def _callSetUp(self):\n # # Always run before method is called.\n # print("asdf")\n # pass\n # @classmethod\n # def setUpClass(cls):\n # # self._cache_put((self.cache_id(), \'title\'), value)\n # cls.reset()\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 if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 # i = 0\n # for i in itertools.count():\n # # key = k0 + (i,)\n # if i not in self._cache_get( (k0, \'assert\') ):\n # break\n # return i\n # return key\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 # print("Is this needed?")\n # self._ensure_cache_exists()\n # return key in self.__class__._cache2\n\n def wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\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\nimport random\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n\n def test_add(self):\n """ Docstring for this method """\n from cs102.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n def test_reverse(self):\n """ Reverse a list """ # Add a title to the test.\n from cs102.homework1 import reverse_list\n self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n """ Second problem """\n @cache\n def my_reversal(self, ls):\n # The \'@cache\' decorator ensures the function is not run on the *students* computer\n # Instead the code is run on the teachers computer and the result is passed on with the\n # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n # implemented reverse_list.\n from cs102.homework1 import reverse_list\n return reverse_list(ls)\n\n def test_reverse_tricky(self):\n ls = [2,4,8]\n self.title = f"Reversing a small list containing {ls=}"\n ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n ls3 = self.my_reversal( tuple([1,2,3]) ) # Also works; the cache respects input arguments.\n self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n title = "CS 101 Report 2"\n questions = [(Week1, 10), (Question2, 8) ] # Include a single question for 10 credits.\n pack_imports = [cs102]' -report1_payload = '8004959a010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c057469746c659486948c19446f63737472696e6720666f722074686973206d6574686f64946803680486948c066173736572749486947d94284b004b044b014aa1ffffff756803680486948c0474696d6594869447000000000000000068038c0c746573745f72657665727365948694680686948c0e526576657273652061206c69737494680368108694680a86947d944b005d94284b034b024b016573680368108694680e86944700000000000000008c0474696d6594470000000000000000758c095175657374696f6e32947d94288c095175657374696f6e32948c13746573745f726576657273655f747269636b799486948c066173736572749486947d944b005d94284b024b044b086573681d681e86948c057469746c659486948c2e526576657273696e67206120736d616c6c206c69737420636f6e7461696e696e67206c733d5b322c20342c20385d94681d681e86948c0474696d65948694470000000000000000681a473f5066000000000075752e' +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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 {n+1} total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n """ Docstring for this method """\n from cs102.homework1 import add\n self.assertEqualC(add(2,2))\n with self.capture() as out:\n print("hello world 42")\n self.assertEqual(out.numbers[0], 42)\n self.assertEqualC(add(-100, 5))\n\n def test_reverse(self):\n """ Reverse a list """ # Add a title to the test.\n from cs102.homework1 import reverse_list\n self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n """ Second problem """\n @cache\n def my_reversal(self, ls):\n # The \'@cache\' decorator ensures the function is not run on the *students* computer\n # Instead the code is run on the teachers computer and the result is passed on with the\n # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n # implemented reverse_list.\n from cs102.homework1 import reverse_list\n return reverse_list(ls)\n\n def test_reverse_tricky(self):\n ls = [2,4,8]\n self.title = f"Reversing a small list containing {ls=}" # Titles can be set like this at any point in the function body.\n ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n ls3 = self.my_reversal( tuple([1,2,3]) ) # Also works; the cache respects input arguments.\n self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n title = "CS 101 Report 2"\n questions = [(Week1, 10), (Question2, 8) ]\n pack_imports = [cs102]' +report1_payload = '80049510010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b004b044b014aa1ffffff7568038c0c746573745f72657665727365948694680686947d944b005d94284b034b024b0165738c0474696d6594473fe6a7e700000000758c095175657374696f6e32947d94288c095175657374696f6e32948c13746573745f726576657273655f747269636b799486948c057469746c659486948c2e526576657273696e67206120736d616c6c206c69737420636f6e7461696e696e67206c733d5b322c20342c20385d946811681286948c066173736572749486947d944b005d94284b024b044b086573680e473facac280000000075752e' name="Report2" report = source_instantiate(name, report1_source, report1_payload) diff --git a/examples/example_framework/instructor/cs102/unitgrade/Question2.pkl b/examples/example_framework/instructor/cs102/unitgrade/Question2.pkl index 634a7fbbe4bad27f24b2d894ef3c0b37c4f5dd94..b950c49faa2b91b7675ee266d63a13fe3652e3cc 100644 GIT binary patch delta 119 zcmZo*e!;}jz%n&<B8&dS;0<CN8JrnBnvGMu8NHdjncJr%`qfU!5ST2<sO$rm;_zni zW`ap|aqR0^R{wxw2Ul_1l%)14ZBt^WXaM!Hcypv?FlI2dP3d7vEG|whDgjE>PVr{Q JP$@3e0|4%EC_MlG delta 140 zcmaFC)WFQrz%sRTB8$E(TVio>YEj9Qwkfq!ycuGrXm~Suvv_l)7H2SKFikYrEXJF` zmm#RxIK`V0D8t-7CDE^TN`~mfpUN(984jQhCYVeY$G)Cr^$$39a22;rNoofgoWTw< Ut8GdTOG##KDp0_k0SZd>0H5?NtpET3 diff --git a/examples/example_framework/instructor/cs102/unitgrade/Week1.pkl b/examples/example_framework/instructor/cs102/unitgrade/Week1.pkl index 7912698f036128bb2a8b616c2a66c58ac9e774e5..6b4ee502e9c67e2ff3aba1b11c4911bb88195e34 100644 GIT binary patch delta 35 rcmcc4n99<?GBs)<i^9YJ>50vvg0)k;8Dghscr$x5c{8RKm+Aok$2|&n delta 146 zcmYej&dAchGWE<v76nb#lFX8v)G2LKdL&))lZ#7=GV{_E((;QGN-{Ew6>?KcGV)WV zWH4qhO;nT<%U}YkV=2kZ1!-_+fP#s(;`Tj!L8)b_Ma8KKi3&NH#U&sud~H)QINGMv aPVr`louc8*?9JrOm|6^#;6XB=R1W|>sV&3+ diff --git a/examples/example_framework/students/cs102/.coverage b/examples/example_framework/students/cs102/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..a93b4d7e94a84f8ad080beeafaa98b3784e23e91 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/examples/example_framework/students/cs102/Report2_handin_0_of_18.token b/examples/example_framework/students/cs102/Report2_handin_0_of_18.token deleted file mode 100644 index 63734376c0eae1c4df3121a0c656e90452e80cba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79868 zcmeIbTW?%fmL}+&s_Lw9!Gl7f(CDYc$sp7WN@Vb&o6Srrt(4`oVA*m?%F3!H)!|^o z2{O#MF-}kvtr9TvGQa?SbOG}d8tB)?%*#9t`nezahxBil?^~C>_c?Kbq$;bsYPu^j zEk>NPFKe&8_S$Q&+x}nw`0xIo5A^fy$L0@zGM~&l!ymu=;LE@G-+%n!r@egM84Qab z@#@PD`2Hv7KYsb)4}UtH6vg0hn9GmJAqtK29%}vYCxdxDVtKSQn4`cCKYMwcXZ^vf zm}jSFc`+YM##w(d%g*!sB-?KNc=qK_(D3oU`0`JWe~SP9^ve&9|L1o<9{<@7|I3TK zI~n(~>1=|YMn9gN{qWObayIMcY+u3o>Gy+ae}Ha&J-zpn55D>GkN)^CWh{Sp{IeGy z{>PtwbMKEn_<;ZY1)g?`?X8{7<H;yLpUh6SThoh1qw&#)kKc4g(_x+!^Rr$)o@d=j zFK=Zpj|T;y#&i7boXsbr&V11A42KulQ9jORoq68N`m@O>o6GQqd65mq@?$VA=CiZz zd@|eU4Q4sNTzvGQ^wrDz*(`sZ&x*V~92E2B=z@RNcC(K@%<z#y{PLT@>wKJbGQQ1L zwbUxA&d+9Jz@yKBodWL%<D+cSm#&M>D9>^r3j+~&tg~z~>*cdn_PBM_(w<6C>~F8{ ztlxgMmKDd7vtiHr$@XvK@%F2%+3_ydtagBNC42eovuC^6HXs@U1)~AzVjMc@42nGa zdNO}P3=^ol_jop&%$kkxB<r7zyFwI)lio#R?V}Gr`jBYq^m@(C`eBM1!^-@8l8w(s zhrnX?ZRgvp0~-1E@LM2&Q|7JNP>KP%zVH!Yt_HKLI2-LM79NCdvc0Tx>o88hf1QC1 z`dPa@#w4}d+1_5(XtzgLIqi1C;91GOQDV=wceCHmu$C~_VMyo4gYGdQ&Jq;pK&G?7 zc;4(c{s!v<gDuX6bJE04b_@UQWp9bio%NlycSJA=%qu(>Y9tADvSI}K7Sd|0qmhJ6 z_dy=(ch~Q~T0`~nD*Mm>_Gkb8-~IH15BT4ohgCMqr<2)y=O1H@$)Y?P59UX+PA}hS z#jhD?0Z?Vn1-OqsTw1byoDZiUuN|+EzwQjru;kl1uD0=tOPUB(fA<nR?@_17*RyV? zdyGHFgI@m8hX%_G)H@lG=)1#CQDnadSKMxfN^3?KSa4uTm+?_0U7cL6pE<g1bJ~l1 z*!N~Q^K9y~Nf$hdvrbMd3ulzik0-sj09aHIBy6)4*ruZdO>1W?$Ua&CKcCGXpPqGw zkD5+!MsCXsZESCCt!H;hSQX%n0&54LWXshd9Fk+oR<eg!8l6n`ooqf4jz}~Z(;^I) zjVPt~%Z8SgYdY%`@O=`0Yx~?z{X~}cwTSXhg+L+}Mi-zruENexkg%Q&i*zQzqd*d? zT5UD!VY|e>gbB^Z#Tl4@ARurLFvsy^p3Tn28FpaqS~rc1i<{W=M$@x-K8xdC$(~>z z%R68;D9TNdJ7H2A??BGr!CqGwF$)|2LGdYRqR0Nu2lHdLly)$|?-@8zzF|#p=OODV zvW<;w(84xc=IQ4H@F#*W%a1y<9`@XVT<&;sE<pMYe%zT(fdZ1iap!fZOYR1?5tTUH zA4DR$EtlGBVy2c6bQ>$LdvdWr*TVvw8l3jd`p2&#nq+AyN$+L-h7x|2Ut<o&l)?D? z?XcK;2f2pi&d(5RY)@BcN>JJS45%QS*&3FP$UB1H&hWf*fvKBKdS~5S5IY+j9fNXY zZW2rli`$p9>?;G!b7>BXNk+M=_=Pknq|PR%5RfRUfc&z~?C4BHm!y+L)*?@8hq8mQ zR0h+XgB?KtARkDHgk%djk8>1wv2Z$H!@5cAH1)49JD-TgqZirs*0yaW*^c4c1zefQ zrttE0lv`WRnt>;*XCJR+uZWW;<L(eLCwE><97Ij8E?jwgE9=hk-e6uBHm05KNn5uK zX<xtvr2zgb#EK=`myK<wxwxzUr{RD2FU^}j`GEiZ%j&M)mYww<V{f-j6Vl)GB7;;q zp4t{X?;M^Du|fOp<iG8853l9}$i~)y{|-|>z32}x1M~2JQUhrwArHv)B-T)5QiQJ{ z+-V0weRxn@#Fy+iyqF))z*D(SSF+w@ygGM~UdoH?u-hU_8(}T?FTin&GZLwRJwNS@ zLcQL^&L4c37C6kioioVYp_vV7h9eOmXIQijaO<Hhxo-&mtl7(p?rbonJifR3*rcB9 zLZwOyPp#GUET0aBlcPOsx^{Nr@#W=2I5Q<k;!RE;9V~EG;`@(2e2`5qAY<TIgEPX> zm<xTo-J61r_}1!A##pRj9qi!XlKg6IU*1l(ahmO1){zv@R&XjIbHgU3BQ7u<*fP0A z3VD|XodLRqr|TK84EZeSP=~u676@N{J{Xe&t+Q$N*uj+d)7H7KVq!#K?_?veU9f^C zS9|9u2kYSZMz>XwUKLzm_Vf&=%UD%fwp!~_VPlj{WGaQ981hG*i|jBb=>4-HNb7T8 zm?zfGxC_}7kR@ID`4<OUNQ}ywH@hGP9AB-cO&#*>Hn(srpd8bwAb(pf_^lkIodi-i z$65EdGd{|*a9ZZhy&8MeBOdnsGO{teau+Q_Ke1lqd3HRXPmA5n%^sv(fJNGCO=d@% zw_BfVo{{Blc1Jy)TU*ET(NK=)>qHff`VfWMB9QNemGZoE{xW|v{{lw?;7rS4kJlyD zg1p;CQNdAjb>kGvWQ^a%-s-RLb$&6;_aJevLrCj)AmQwNJsI;TfX4P#pONOBM+5($ zhp+;gIgaeRLbq$HOS{{^Y4<#TL-v&S&|lV(BlTXRfH;^#;5f^1q_gf`xV+>QHnHkK zN-lj}(T&S$*Y2h>F3ut62@?@H(po4k>F8`cf;f7uE>tWbx6?CitJPWqVT?vtH^PP3 z=OtZHM#TcXs;`G79S|#Jf!DRN-^p^y&X1wB>E(xKM@Jy97JF4~hR<&00NGTET@UkZ zba6^8ntfMP*bf8Neu0Z}dQIul=5pQpyffrklLXP8PloM$Hq&P=!jLMl0~p=PA5w17 z1hAAB!2$yHff7lm!h%?)b;?yt9?8dcHf5zyL$(%_i}EMhYqT0vj(MS^URoDe;IIMP zK9w1-*Mx1oPHVP`>2NS_qP?|5M9wkdC2gWHw&gv`e6UXdb=)bi&Szl?8cx`4C0Idc zV@<}+5l_HFI`iY!pdg;DTB}m2kXo@)%?u@w&O|@<_)Rw#Hr;GI$V5H1D+jyM!N?X7 zyT(d(SUQIVVY(cc^{f%Lw)_nghOO+;ao#=A6=|GU_kbZ}C~W<zg)=-U{c+wab8ate z#`RYqAtTh9YW~LR*-tJMcBrel0L$z^>Dz{&*1;TBmQR>7pVvMAVuX*w%Mn^C+J~Ll z9w$$sGYZ=iI$O_hv_6~^VafQ~vcJ}pGf8d)MJwgAR=_DB7VszQ+UuTm9+w0Mdikfc zeA6F7!P?SFHlJPW@*u%I97keIZ7xb3?B?Orup%9qd>C|LZScz15Q@De7P2yf*|6Ih zVke;rq2~RY;W>C)>5XVrCOC6XFWwBjo!{fIcQ~1x)HE>#Yo<;a!iy=96iU_3Oor?o zWC-r93On00djm?}1XYVe+q35zD&IASozY>hvzzU|GG3<Tzyc@O&Q-<1I{U^vtXE*~ zWE)4c)Ag*qMs}0qfFUY=Usz6FLH(jN2@2$e>|_U$6IoZO?rDq9YO1hWueZPIt+of_ zUjAnFRZH|1z2?bw349jkhgI0(Lh+rY#l45v_Gydi=cN_cek{yimzT#56N_(=t#Y+F zYi0fQY?X!9V3xp{L|Vk}S^&!P<k(o&YPoc&gYqlyD<z#%St1|>-GE!?kUgO*Z)`U3 zuRSB8_^kD2SiG?V{z}>iM(d2&HU(|gciKoKJ?#6?q+!?XK}hZN+BtasuxZ6x^GRFi z*CCbZPIk4LhDzmApoAVth|nw_<xOZ;w-(_!$u9uRDn#n`>Mh8n&685s>>q<;DKw<O z+qM-!VIhzt3cW&GV$IT$`_k?f=z{0R9D>C{<F#WeH&~v-bc8I&&ml1B+$DWaEfX0J zpmHAMvW-G7YN+EppQD5qZc$=(U7;2uCcBg2y5k{og<h|PVRK*$SP5;nFu}v-Y;wK_ z)`DivM&mvFxwXBfXhGj-BZZq|VxOo}$4(5_i(Rr_O}A*~7Wh(q*Ib9zcM3wlMghI4 zlV@##?hrd?K&uxaSx@KLS8|5^dNTifayC|rADO)iX^LJ7p*y$lcvX&qJFg1@pg-qc zbwY&LyBe^Ty&e?UDmp`#5lnnxbNsj>s2Sx6qegp+v-VyhY$!N<r>BW>eW&F3zL7;? zsZY&{X^C1#`Fui+dVdBsQZ)T>_iQ$U;zOQ-e)hLtwJ6h(*J#&A{E2p)vmC;7#JA1< zY;Avc<Mu1Eq@Z9hwv=!pk?@M9G~=|AfQw1VUHPD@cB;sQYhPeN9OgK{xG0hLTG_#v ziW!qFN)^{Loc95osC}q3AmF=hUrf4u@ZbSNRt2bv-o_I(MTp@VCpK8tNhvrhl7H!0 znC(K~Ct+yypl_D}=yRI9&M3<^Pkq!Y*)i}42Z82(bFja9zWNHwYJb&T7wo4yFRa4+ z&DA#`%R3FM%Owdk^@p^gfD@ka6o#_SY}UDG9vdavc#ijX*S7#8|8CpgomcA|pV|h0 z=SEHHA)cQRSxwhdw)pHN6mR<6aX_@RAutT)?Y7yFZc>n4&u-p4Ip;So$q6gYVD)US zwL*<**$B%EsGmT`gU*nhKAphSS^~zOdGiDWOM-=I4E<Ub1vUAQ^zGRr8bG}~!Ql(B zmXlRDYzvK)t49KZc#Qg8bk<heCR>qhs#F7m<zCuuJ4=r(s6i$<Xp%l2kv_^TjAoN& z3O8ZLilo-{P-d?A2)aU3C}W}&1=Sj?B|Qp^j~3v<x*wX<&7yUKtGrg+wF+opo7iji zs#RJdcP8x62$AKqF6Wywcjfs@)mf?9!cSACkEoorR6h~UR;kj$<#f|z*cmD<L9JQ4 zKkOXsy@X;5$1pO`xs{L8M=beAj!>mjp;W!3a7lN9Md&T1e-8&oa1|iw3nZLTEjWfn z5=RrWI<)Z9w@T1Otd*z&Iz@Ldz;RVLAml`|M__ZHS)vWfgT}GhAPe{n{Tjeg`>^@# z;b=b@rA+q_9XAffjn;RQ!MK_24~47YR7Smf>_asv;<VGmnWvuth0S4$UIA0=OWA{L z>s4l?kw|MCz4Q=xk?labTOM+A%3f25ZntT<#%|nhTQj{m{3hgl3NAP~XJ38VMw*Qq zz0Dh=%^QD{-S}en##g&HUig5t)y9iC)Z=+?7j56lLNnGZm$gakVx7|&z=-@5;ySj5 z0c1KIt78u-WANxw(Qy>nEufQ{0uDv`FW1xF49>@DFrjsakb=uLnzY1hY>;GSB2*z? ztU)2x*F_MhSx-m}ANz_)ApM4LS9msv9u+Q5@Hzu3=%OPQ8DB7_w^9`px&{##ilXcT zT>0U5{1iVv-qHbZYG{IhD1;el$+S3r;Z;C^<n22xIuu@y2en%ODrqtD%CR1*&#!r? zCULD~r)c`rmy11W!)FH0d3YmyKzmqtmoVH@uuoyV?e=>1g;ciTuoh6C%9cLws_MJ7 zo&6jTm<va;L|Kz|_QCkfWHml1&LB$zFhxs?f5_pB&oN8<!rcxsNd)=SZOErB@1!k{ zbR9a?LKzVi1oGaGxEvduY$z4B+eyRP3{^XDV+7g$*4-^{40f{{MdL<ud<%yW1t5V) zsrNLk4kO>V2Mb?P>a^vO6WTViAp%ZYE<#nF!^I6mgBkq|RFshG3Lzx!p0set?3iVn z&8(z5WE>Rep-tbBHiT)A=$NKck!R5OzGxF1i7LI}T%!UU-$8+-3W-v|7Es)#D`(zQ z$tc<3;dlknN*x_a2tjw{-z(V{kT&fA#{r~O7q%NzUwmdwBuGfxP!~s39Q%@DFVM;1 zk(48KnG^{A1Wo2(ezAuWABw=jB>3G+8eD7PxZf0VO=flFeOpZA7!x?9yZY9@2dv?Y zNAOo<mcFYy-&WLRTAKfIwQ>Se7A;FP%zVPT<;8clhFL3Fz)^RxxQtD)Q|@&M<t5$i zVnpzQf-;@_j+=*A@P)!hXK*=zsZ~jhHq8PjELqC#(h<AUZ`UH%+7T^f-_mfpp7?;D zZWoh7i0fVgkOg|J?UyZy%Xk~qIDm|TMaA6%ZJ)L(k5*_VlhsfEYJ6ikATtybB=RT; zGt*q$d`K~H*M%H&x>IeA7(a!dC5~(L6BPs)0vRWtzhv+aMdbq>g4hb0^S!!(;Cw<q z_Px^TFD)Rp$B$^QxjfWUz;xG-I#X!UDH5z?pFR6}^=0<_@mJ6O>T&k+izhF#r%%3q zT!(*gm!s6(Tn!DZuHm#tv#1#q;2MoK9}UmOC(zTWro6a-AJ`ir)%*i`OFT*x<SJTd zP^bgPOJ9ldOKY<gOe0UD<^A28PyeD|l~04FD77a5CSTLK@_j{>&CVpOTaNe%0Kma@ z@I8q!dR+x8E=O4$cVyDQOf}>n;nHPNGM!hj;-#7{0#Yu`C$`G%gmfj!thk<Xt}M+C zOdQS9?RyanmgL7u?=^1T-l`>REf)XP3jN0KY3=503NlIJj_mC1DP%}2n|FfYP)4ka z5TfApMn|Y~aO+GIz3&65TQ3OEm7^k`ndS7dCwz3X5tq@jlZE&_4hJVW{4n~6cM)xq zkN3(h77$lQmG8w+yWJKvhwuRdJ6*m~;A4g}R1bntt3Ny|j+d9bo`AQo1rh^fd*tR& zTg|A<ZGzp$6T*gUJL{QSpje}}nLv>@vr*?wb88)^j}19<mia22MQ>%z2LF%q6ATmD zKc6<RePZA4+1beedjMIqwH=O5;>!wLtC@>?gB_BloePTQ*lcKqDhal6g2PJ4Zf4`B zwQFI?R<pR3R*9g(j<`R9dS6f?0EvPyp~8sk$33!SN2FsT4ZQsvg}U?yB%b+E&?)Am z!aLfAcM1*wH*em|PHzlaHwJ5o{153!<u6^L=`a^szOEQhzn_ZO9d>u2B;QV}FsD$+ z&?-uY>LgX`lWelp*6K|qFHGOYoek<Vpi+caaLsu}TMP%w<nSn9Uv_@fs+dtTcgp#4 zfir6<rKtkCfQ1v4H*@tuen081X+{QU=q*fd4b&i`g7&mMg<@!4G)2A(8o1K0s%L|| zbTvc8t3ix5I85zDw+U6p>3+2^ucz((?`SZ53x)iy0(G9#zZ~aX2Ny5=E1vrC5>Lax zR(0O+NWY+%!-r>N96E{q&~zi9gH=6CTi3ctRmmQ*XO{~oFuki-o>1(yS^U-P{c4I} zk4jo~D*+1uEG#Squ-ZGuZn+$4S3?yCm(w=8S03}t!xt|^nPqdsxHg|WW42^NpFe!^ z^zmn=EM#pZetttwig4xS3+sqw_(6Z7jB7S@YmiEnouN7k*7FqzQcPc;V@W!o@TjsF zR?NDYk*Kze<-H`{LnOrZ3>6O!-?2c_0ND-;6=z+<sup<+1tB;vav)4f_P%l<u(0q{ zgd&7yK<96bQ7)lJ4wiBiga`z5hPL7XCmr9;i|o_zth5#rctb&dc3HvEg~|b})I0~3 zg-Y3G=m}vMqErshK%K0_0L4)R*Q+|PNo)AJ-P$TONkXY@kXYqM6ioZ$$X#GZPk9P| zdY9l5{uNKTh6+47g|;v-2-TCB)|2g*G+kahj;5p_b3oJK7TXwTUv}l+x@zw9Ep&2e zxQ;~Y^c$3*y^D97Z_m>rM$p^b^WX}3GizeElf7<xP5){j43^1`>+lJ`)>Df$fAbSn z!|rdh-;atzgATTa#sWOFjOyU8SV=N1MpBLzoAGSK!@WPmKCKP<z)Pk4p~fpgtqRF$ z;{N2IMy=}NUR(y=dg;A4h93N@zF%&+33dn6shBSktA)1GqyCjEB;7bn$8KYUbdRGv zcbVRNbMu}lR87Q{r{LID=%&A(4rfEWQ$M$CfPLCFJ7t3+Gz(P}!jcEnWJlrH=S%6J z-*{v$`!uX1b`>;W0yw*C8VoQf=r@>8Pyjz~!2tm84P-pfZ+zca<AF|bQA1&kmrOFi zHX7o6WRvDIv33i7Sm_I&Lc~oO_P_`wPLOhhXl<}~n_fTib?*0;!hm7{70|Yx%RU6D zyD8z;|A|7!9Y9>e;=ivOS!9kMA%^F>VuD@6)HO*6(c$-qT(QSf#0L(c=SWJa9rp11 za|VSs*$9nkfO~S1kD&>IwHqNSI8AhfJ%g(IXGK@f+Y<g`wqa2$p`5rrqffF#IBr6> zBrRb|&gV3c!OWCw#Qj#eUzuDn>5FRLqtcoH*v;CQAyu4!Ufpy=-)<C9x=>ZHXzS^> z;cZy2(fUQ{;q3@E54Cr^b5%hfjqx$vL);94m?|2H+;;{}ed;=6R6`_9J#wX{9Q+<c zjRs$d*rEBHL!?^U-+D#o2<>r6o0L7T*$!Q|gLM>}8~Ze)RTwUJ`&EqzS{2>47{HRL zxqL(sK?W{)zBXrL1sbAKt#arpxBXtDY5~pjV<1c?yRKU^<vBH!JF8CNH9Nm_YN~A} zJsNgw!C6ufhs>U0X7i3evm&j3Gnjr30@TM=a4_*w0uB{R?aASHaB2p!2b0#XFTnVp zJVWx2HkuJ1ck7=&56}8I=$A{{YgxRNo|~}_*77-wgjl<HmXOE_f7=R9$bf3j*<^x) z7uE@cVFD`wGr-e%XLtgR5EhHCYR)4+tjqG$ut>e6J1rq{JzuqO?kxy8WEG@bskY>$ z`=pm-O^W-1g2z@8xV;AdzD{b)U}HxcO`qm!3o&fM`9eCtB^^Y}34jP1KtY|}z~S~+ zQHnsWQuM@kn?w2liVLi3=8_so*HSC_z0i7Ty-MlQ6EDx)Fg%<n+vU~}rq4kJlj$e( zfkg%FEnJ@}^KvC+IEo#S{oo1flk*QxAYn&XP7PjU$y~l4u_WkQ(w;0w?w*&B-0DnM zWIx?b9ZCK-OxpfyQc%AyPU22A6{cJ_3VPAS*-pTXAfm`pST65Bib_gZBqrl@a3GWb zG7p0U@c!rzj4XCbp+)DqbVFUK`Vk=BFAYo1JQSJGg-Bx+EdMzkmm$R8mV;%#rIE1X z=O>E-0~yh#^WR@Q?#6iH-hWpGdVV%27<z`&vf70+#E@MUs><YG!_MJV&AwpNErUo= zmRy?0mlz*<gpCU!$4JQmBL(6%;h%zpEX6pVhgdh*d5|Lm&Z;ws{`7s6@~)kq#!q!C zpj$7V%F3J}Xp!OCeC7<rddFm6C&o^czrjhZ6ki&&$EkOwt6W1#@M}0KUgB@*Y_R-S z`t?Z&fqWbX{3-qU635JPOa3c5UP?@$G-b1AEVHU1vEQ)pjmsq@sNdaUlaK?g;Ag}% z=)mf)AZi$yP&mzE_|e^3O`?*pLGq`mOod2h-?+<XDV=6)=JZ@khmlBOc&n;I@ezcl z@8I>I2d^<dXNikvQl3b%;r{vpI#5DLlPNha0+evd6rk3{Hy{2+Oi#ggD*gGP%u&tl zUygQ0nv4Bcx=y-hv_S2-B`Na|E{VvFLU)r*2t$MZ0O9Mg6WDPNF9cG^ryaO=NX{Qk zLiE|o-@yuqq<;upfWTXY+yG9Gq*OaE9f!{lfh*%YZQ>GaYZe%yT2M)eTCtDmuL4K3 zo&Y;66vL5fKz8mxsQfd=rd^T~z$1+B=yqN-M~Ud*)wFd4urW6HHS!itOsx>l*)Dl| zkPGBqj>V%V!>JbPn0hhA`yz-49}~aU#U4fUUG`Gfgg3;$+87l8Dl>;O^<q(5Ui&8$ zzgTavh*<l$qW0C->x6T_qMY>;S&4XH4I!2U8t<hft~%g4rA7)rJfETl<>pQM!N?7E z8}wMRlkBhBYZ<D2|Huw105EPI-1aL@&dE!eQ%qm?yvN-_(O|Z7x?qA2!+ai=Lzvls zTj~beNaq3G0b`HWSG`=+%1M0#Ta|aFC3uY^HWva+*X`GhTSnLJe2rmLpG2Q+x9fYK z%CXSUjiP;H(4O@1?+w^($jIObTOE=cfFh9V28^u;Z_iIYa=46$BVkh!rvr{sw>;&! z{Lqd82~DV$uhl~`BbtjS5a19Vu#Vwg!rrSp6$!_lAz%wLXvw4Gq;7(;bzxy!%4BpC z0@5MzaLqy$Y=cNzBu7rim%Kt}K~xd`3Y<VsbwMQ)xU#Z}LKs<GQ+`fse9g>l^e-V} zE0CF0Qms-#LfSe4qH0g2XB<}P0Ss>=Xh6BF-2;BRtpzZ7>gD0wPaNnP`J24kSTkl6 z%N`9UhuDilbn29vb(<4$6=@z)Pt&3n_&9&#B;z4~N{Xl=oe-QgFS0j9&?rf{t74BK zl&-e28N&?}+A;A*X-+*9I-IgtgahaQw1UHo&Q`JV@g5q^gYm&wJPKbP#3WM4fY(Rh z1N=moQViMK1Z7CcR81O`L}#!es&XYGTU+(Ks;yF$kmy}wvINR;Yr6+y(trU&i4O!^ zv=w{$u3ixf3X}HMtO2EQUsM*m*n>h_-?J0E9GyVwZQ^mLr4G-aR-<|5N+suzL2%<t zV7FRvQIgJWR0mj@2N2y&;~jggj7e8%Knufab<B|c_8P<QN1euU;68(s?a%qXjA|Qg zfW`+6#kd2A8SjqU&G1`{syb1?cz^Il*<N_B@Ea*ruo?<20K$m$q>oD{7lwu>N`gXV zo@)%uiZPo;5ZDN@&kJiw2o1-LBd`ciqxje<tbg!exw%<pmSqSf716Ub&Mu!Up@$jh zVSyT0o*%=kPrebn1bsB2VDaY*M8UfCexhZVoP*CO&cdK0$Ak_}pvZzPHX$L6tTV5n zZ}!2DwedtVd2!Y==9R!HkS)R=okt!xYFD#at7PA0)a?wS{REjcn9mC?I@la}Iz4AH z=icOeY*3BiziE536Td>>Fyyu*{3#irC@B*z+w5B*(Q-v>SV>AMx^587)Idnn(E!^E z5`VO^fBmmB{s7KL_=99%jc1ZY2c}PZg#+|4zXNxW(ztnm_E6_mQ5+*J7`}aq|FGM5 z7N1sce%9)pY|hV)^UdJN-C{Qz8)sAT>n1b;2`HE8zgtf9x-7Yt4#u_)JXRt+ND>!@ zXYlno7)w)wiMRDa<e(?dLLo%)ds39}YzE9&M<J_Ve1fci@dvip@WYw3uT}UK&L@*0 zfUqaJx0s&?IFMPJ-|R+zs5g?+(_PG42V>gtOGRnde}kzV^@kJe?Uh#Kp}g0<2Rq^! z3?YMA`|v_dU<jF|sL5x}7rDoxAx6eltz^ziyb6jrQxHMTpED^NHfk?!B9<2&6x?RK ztaq!9N+>hxoFE<NtcWGF9V1n*r+tu21WJoOGv>pM79MN4C4XI@ckZsS>eI>55yGl< z+i`f<ge&eXq+3l!UVzI@f+x&}m~u^YD2rW686<MVYi)oXvod>7IY0xnk-wl|D#C<; zmfAPdk{4U-VWB|mM?II>T-rPgwUW%8Zq1oxrpCEtoaa3-Mhc6T&G5GHTrji40TP2< zfD$<Xe&~)EF&Tm=nvAaYu4+(e5%1-w;}|O++ZPOIF(6RCByX>Zq)&0(ZbLx^?Vm^J zcO_&bXX>`etgz5xY`n-9N?EUzDOZHOWY_{0Tm$G9e%ZWRC@xqI0y&M_G!uS}rICy< znR((iQWI($k$T5GzzrrzOBKexP`_N!@xo$&p^S<u7|c43fdEf&sQ3}5A$F!^;z)tc zQ9i=W49Ff$Gv+*25`fAbqxtfKSlU)UD>>{+R**(2Nf(^#1S-OgSVI^`Wf5~6in_#V zSPIfkP>P*dXD3u*liZMy_3~`0Cn+6X1hh11j&zbqEaH=19%~ir5_VuXk2Ao*JInj+ z6Zq4msVO|;fp#TMw?L_G?gX9glsNH~pH0ZPwzNvwiJZ?{cXsp@;$0CJAnD6{eyl1r z6f%FA6R^FcB+F&T+%Z&d!{$zjOMLBlezqgItu6IYM%8W9qu$dVKxfNe9{7UN3RMkj z@iLaYv6@>WL9@>Tj0MyqiQk9JnB&QHINBF6$WN&Vcs?gby-lC)JU8`StdS2xH-`Q9 zZB<hJ`+x%gwoNOAE!nAA80syd9yn$3k`$YqWCrVB!~sZkM=EB<zm_1nnhz5eegZZa zw-mHwu{)NLE}eYk2S@82%A6OSe%=QCXh6ooKJUGhs8(C#2Im<F%H1YB27WaOxl6@Z zHZ5nb^d%uvO86RyBur}XMT5u$%N$EB&(N$(u~mRfLWCod=ndlwVnp5;UM9o%4$2=o zXffN(VbO$#YD|0M{jMQwsWab@GL{j?0a?>`S;F(iGYXh!rZ`196aHmbsB{Adv7I#( zjlIj%fJK$l{-KGq5XQwuA8-uG;^V0F4AqBzYMvp{4LAf(i*l?TA~7H}D{oQj-7hd> zXaY=tfM|G5b`A%ew`dujY7wHe)yH}T!G<2FLPgYKE6`C&1{mJeQm!wf(rJ{X*K|y# zGdL=a_O1q8wGjj=lE%}We=39l&)0-rB2lFDuw;s-5Yyi@iN_K_aITYAf5@coVb**3 zYiJ>v5kx0DX&QGgRHn^|=^uaE_uj38G8ceVDoY8$cZ>f&5rnV;cvEBQ5jN1=&Nn`W zF@kiolmXfM1?B^FU^0i7Qku0d1eerNdLxSxc!bBOO{l!2oi?swH2-vIO2|D@P`W$0 zj++QfW#4q0Ds5+0!I?BEGPdWE!NQixOS8fiG8Eg)Y6fWzI&isRgPfZYI~mjQAn&~# zA+bq|&e%MrXXB$a6ZV8z+xgLXr14r4Q*2?R$HdYImtTBFIB;l11T+<>Wj@SzOP5^Q zzL2B|gEhNAV12|OWf2eg!$d?)e(JR0JT^FOFnzdhFZM7UEKMzvHa%2;o-yR$N|MV( za(5?w%Fywt_kpMJIN&%h#T~Z8KvPV}RN>%++*rsnnEegr-i)0{MHst@#k|9Sqf%#% zhj!DyUDDZO6Pv6<2sucuxQnnm94eVqQIaXbQ$>QndcOc^(&+<-J~PLxEdeof?F)e* znlubN$>d{l>cr+a;zflwc#=*bqXnKrtdX4mSS6G?N3bQ#C!iHjrHVhi5E0TyY>`m# zV+!UfdL6M2q5bv+W7MO6l9{=VTAbVzFnYp-MUbq3rASzOpMZUEFy8xz{!~#RXbPCJ zL<yJ>TBv|;<lzx^q&bkXgVnb(r94W!J6Kh>#+n=nyHNVO?$Uekzp_%CZn#o}I1)5_ zLH>26@vPI$4-tBC8NTLTs0FPXtOm?n43p_)RT}@$=-sQsuWceoB@luFOG_Fg;S0rd z1C~Pnl2dNrct*4!WvA+NDY9wuN5?lWsVT9l+S5b>l$Q>q+DfHTsfW55_4*EBU44I- z@Xk1MwH)H|br$xWz>AU7SkW8LAb3=zzV}$<OV_Zmo&Q$r*Cn%;+^^NqLk&K<fy-r$ z)s-8rS-?WUO`U;0RF%}qq%Ap?E^vOXOYO35iI=FWb~0I?pDW3Rh3eN1-|*vaktM?S z?69&ZP>BEaf)ut$O_5j<c5xj_FaS*Tmw*JhA*dL?;ifNoM$tC<HcbGHByABx!lZaW zXuJ@c6dNAD@beqH($^<iZ(0SX0Jnz4lvB=R$Z(8A`@FwrJv)R=R5x>)N}$JOS*ikq z)kUrtvPqRoX`mN4M^bPG=-3c7L3&)0$N~bc#rvKR*{XsAy{@S7#{hT@qbxROPlx$% za=zAr9#bu}OatD76On=eIU`<GJSe~)Vn?9J$lTfwC9|X<Dw(V~n%Kkjzi{UXqcU(} z%bml?>y&0SZylSLivF|D>@oD2^OiQOC3F(Vi*pglri=mlNMUGifO~5b?u5Urtg#2B zrgjz0yO~;sp~g{!i}+7mBF7UVDI;1+M6qx?OO;9}z++Kk&TS|l7|JMWyO(j4hd}nP zosmq-?@aDr9Av`RjcQOPZKq5W)=ny{r(`c)505-2StX$uDN~1%bjmqVa*+|0dt0O# zPP}Y&nt)cDOK6JkXCu^$H?%a7)PNG{2!uqeNIEvU-M8Y*I2pm;>J4QQBq-J^piw!N z5(sN{aitxQk5f*pVmPgtt(r>CeV*|;?Y#GhyTDhuTt(gF9bb;=PsQ}BhRZnNZHzhx z%^_UXW{DynSP2Z>NPN8jQ)2XQS&yIbniCr^X$VoV`FUM)a7H0;Ol_UEd_we#dNI8A zyw|aN+E)*`R0XgEE|LOTfIIXdjPP=&IMV&KW6jX${=sTmK)h0URfMcO{zdC(2{H_S zQ0TyeCM#KJTbo{4{2eo$!Y}7l?8@eiSf}&p)mTXO4okl98-z+vo0z4Jz`a*9tByO@ z`6B_deC-8m1AJ157ECMCpbanDvOV&y4>@1!+qtJK-J;#&B|RQ-mn8ThG>DbtXz&Px zSPr8kDC`$thRhEIzCu$WO;y7SS5jb4D(72_Cb62tc?mG$`Ud#odAlw7FTx{*`BSV! z7{%rn`jgZq#e{SnFC01rQL*G$E822}Y)U1NgIF77b$(#0l<VA))AeYs${C@EeH=(7 zQk~UthrnEO^rH;OMVpLdNI>w$h-n{}tIM?V9uC~Fj>vZRnrOUw#j@_LJ}9JIldUsQ zD#-+Ogw009iweM8B2Nf}L0%ax$CQlNQ)^^T!cAm*e4U=7Qa+^f;XM9R!D|bo01~L1 z45)N?(MmceN#~eWc^#TtoL91;<<rWyORPWIjTZ3v5M&~z)C2b8OqYfzl~w{!{JAhh zJ~U2=AuNv1I>tM1N2rwfB?Z07tRPV)Jlpi*OA)WQvaqe0t`HFn@Z&`WHco6gIiPwS zBkfuoi2Igsn!U$1)~3uGI2M;CuCH1Ef{k-B1NF+xCQ!0LP^OSD%7p~c3#l+Gb);UY z0=bHB6nyI+_PD%R(29E?`BwK_e3o&)fet?~LUm2n<fz62-BCYfEdd#5+E;xV7?V7X z;X59ql)OZ~KxQik_x!5!W-x-c8FSaeEZ3VK`{dW}x|n1{(RF!sdYnPd1n&FN>%Q<V zpoC(w<BESV8%aj)1H`7FHS9!{Vk~-!s9kmyguHrqMs&#pJl(VnTN=)i!1h8ZADwLX z^0Z3ZODc0~R1ypjNgJyEkw}gZW3xc2jJ&7w9k4EoJe@OSb-?P@>=hx;LquCr)jjLb zkgR2Ym56Lud9b7*uBa0KkLME2uPjE!4SBz&0Tmc1*LZKV$x7W)+j4o%nq~7q(ITIg zz|in{8#Y4=61Wg@C081i<Zj<i1b4yV-?Zvx%2+U_^M)m;0f|~TJ<$Is!tJ*?`ZyeG zDGF-r+`PLC-E#{zh6pOGx>NG?x?B@g>OH}L`D2)TkK1hZx&g^{k^m|Gl+szXUq=1} zj@s@`+F)Z63IyS&g}X3P<yUF_No#>oR8|%;d2;FN@SbA*zX0QgjW_ZD7#@yrFQ=~) zTeUU;bmSmAt#MtDCV{G8Sh6Z%D(R60@i|iVin!Y+0IF{&c!W=x-2*30*J3hV^f3kA z0w~d$Sg6Y#l*_Y%^`Ic~YT+uRL_@{(F6j>#?c=u5-*;xri+lNfqp%tK@SH*~8Q(i% z+-{`R!?E0TMY>n#|F56D6guQ#6N(u~!Bnk0$5m3i@zJiQhR^#<Von)t$jeK_d0R~G zE!%B4z&Ikzfg)jHc36&|>Ty0jH^z3BqKr+LCJ7XwG(mRC)WXIAG$@5x__xc`j6{L) zvS3`jO*;TXnR=aBFI;;d<|Fu+APy7`CPB<+zoxK{?&N&P4?XcESB$@s+l8LW({%Ue za>H&H<c?*F%`F4sFRoqazks%SI|z{fFguz|5SlQXywQ`LFdmKQ0u{_Bap|y`YhIO6 zu9S={z2K@DksZFJcMy~tktjpZe3FhW9bE>86B%K-7IRpO%ehofC%vs4)>!1i<|)fV z2t)kj%hQiTgNVr4cf20}iau=i5IJ!zJai>=C8GB3ymJASVFX8|Eo242Wdj(;+jsGm z&TV#$$*qVoZC>)HO)e2=cokSIloFI<4|kCWOgQfoR|j&?SG+|Cy>L2+{qij)jJx%= z$`vaM@|tbEJy;zxGLh(_;<P+rTnD5p3Fz2C_M3y%UCO-)R!yc#K~4!E-8MMeA@mSB zCgaH>2&8=A+7so708VU6DyrymSR5Vj{Y}mmc9llL3RzB>92lh~$F_sAcR?$boDM{Y zsJuk~eEh1mNj6UTi#y7%m2JBm0Cya@%C`eS2e+XiQU{835`93w0Pg&VSufeoJ9T+I zJB%*^XzyCt=e$!f?kI$nztWu{4w&ESc878a{<>_3D3`7w%D9EFXxu6I%RgG?(#kT- zCC2?SJ;;3M6*&6!VAZA)tFXL=B5cz9g67Ccz-KvWmEP#h2G=$;DBFoF#4*Ko7w-Bf zk*>gZ&6Y4#jp+2#Lmta<x(ceJrZ2-I2o8v;VTyF9khz0Gbk9@maT!?tQamRexfFOc zlB56aC5T=$hec#b{}`s6rLQwU2IWB&cifp4RAI<4v5c-BWf-X++b@}EVc~FnzGP7K z#yidc?ybWj1Xo0CB4p7+q&C%y9aIF=?jaI%7}zZwfz}}r9S)I%2F&4jayIOVsD7Bc zij&~;Q%0R?U-XUsTYev+%MmtNahb3)j$<L2Ycf=gMXMSoXrD5+2jv6rLYqwvxpkYm zqm@0JQmH?lV;FiC;VusI1@<4@o}9a)_cg3dlQSgNg!KY(J=iF)LxsD8M4Qfv#l`3l z;dr+BkL8L!0P4yU@_D(<4{(*@LlV|Jl}v*}Tpoz%lE#hn;C0HNibNSq1Y*gt1~XQa z`NjZcy_LTODp3me7gx$aks5*&(!v82t!a}T3J|Jj;!l1611adV>WQu6cLnOuI=9_9 zu`_8ok<2NTSB*raNyHBftZtrlXfP5XzfBih7{?CkC8&Bwjf2(wja#pNl{J5bWV871 z;GlOC|KO6`gWfIpjQ*`Zy~U@$q8BNP?LW?6W&5`_P}2(j>R?rSn!O<)xg77P6{_u- zx`@_aJ%0Y{XD=Sx_!-EGif?3w@uSJ{X_NYD9}$+gFR~*6<FKo=;8z4j+TkYO$c5+N z9d{x;mU5)4j+7Y=ZTrBSFyRSW<V4=xO36bvxY#Y{u)2RX!Y1E}-=-J4e(Yqp!2|~= zn3v5i_j3@LcUhil0HI4%W#`J(O};=yzhy}c?N9~{zKHgASEz8Jy~gN*r<P!7Y^;ml zgg=sOQ>0ICm!lJ*o`jPGOv$r0{JG?InT3U=RV1E-XAd&hpj?!9k|!GXmsB2;*f=e4 zF4ko0*7v3Y(VM4+19`UqK3Z6KmI{r|3CX4a8={3<{0BKd5hX3giI%5Z;NU?Dg2f7h z(7t&#j9O35ft0O37A4qP-~jbQsq0e%o(cIey(+Qiw_eIOq}-Z8WYG~|VvLG727VNg zQnIB$CI$2*L>+|HVMy3vIcwoMOh+FF2#$bk*i1yiN-3EbBa^m#td$WgtdkeWmN2E< z(gEi16+JsCiWxjhIe4jMK)A@vrNqMQSAdX0iaoW|Cy2lpT<OhXG{jCpD3TZfo3>Qt zgR7KlEYmuh@*dn~b>9e$wCpPRxCX*RvA$?f9rf~N;o75E)AogA(s;7R5acB9)9S5p zB0(YxQy4&7x-vAo$X*Po6jv#vAVD?|I3(g;^w+t2M)&fftK=vg06qw?>lb_5TU+Z9 za1LzogW=oFzk6^s8<H)(d&__RHrsy-{qz*BChvavHr#LZ?iHs9;*wrB4O4UnH`n#R z0?B{ThUIG4<9_U&ykGPO2yHr2l5UMRpu|E`NM6FA<3RA@gT%xT!;4?&Kt<Gn`r?yM zZZJd{5vZ_{4`Yo)jlm&aV}Y<(n1p)zC>6mxMPoMK_~#JJ0q(7Fq<kUNQ4I5ZDl?Cy z4+-hBK<ql9UAAI*8i-7~gdqeC#|YQXwFd<x7<t5!x=(#|@q%SyEUdg2EuFj&6n{<8 z8FzcYJ4`~{)c{FMVrxW<vYQ^=>`1zGt0dGfusecZ;pTgP=In@P^2UbU1U>3Zn=zBa zn_!;BRZvu}@?NQ!S3zzx_f5tYmDoPmHQ1|#by929#v@qbdUnY=-6@V80MZqVYm_R` z$rJ_;9s=1oOSJ|gZO4lf_n;1W=hkD^rG3tCX|HfGgAFBgj_tQngXc9u>WrB<Jv57x z!E|rO`-8XFZ+K#nq!1LnJCN8}n4MA!2l(O1X|^BGAslL2gl>WqAAzR6WuxEHpQFn( zgarc1&sC^_Nf{EB$n=~asy$dXgLfe!Oo0hYS;Q_wb3EX-q}<*%PyrPqQt}^Dmo-FN zatzl|lpbFg<c$ou+gb^950NNvG@avsCE?}d*oq_UF~BvO;&x-8@_>h5LQaTJ;k8T( zSa6KfR}@clK9%xp0pW*eiMJ3~#g?@$x}i)bCD*cFs&If3+hHzk)*uy|T&(9QF&uz~ zrRKgg{tC87A007;w|+2E3mqOfXe2eU+959&k7v@HLAsKCi+60{TRElSR85+})G*H_ z)l0+_Gl$_M+k+O1);gT;NR2oZAZ37#4bD8zK6}=j=iTG^WIQ;*dggo4MyQ@29!;<i zxRVsgTVM)fN1j5?I8*fCu%<7=;tbH@5(@yJier`!m?cj~<^hrJlVP^{lBaQ)nzIq_ z?kOZrc+CmrZs?gwmfk#Q%$%G-{g$%c37)HypOx&d|N5_Yxjw{MmA5RT1t1U#*K%{1 zF=QE%^$aTk2^hOEa_KM!lny}KlV;I?D8v&D0c*r^DVpj=3jKwmg2riCtBDheA(BxQ zYzI_QZh|jx3KK37a2rbF7L|k5U+UaEfUcNGV1g@IHL&L(KzL|wQXESPDagUPq>p1z ze#l|VF{>S}z@GAYG613mv+mgt(iJj%L1O}eO9>KE#fh;wC_Ikm^J#%BM!iWF>8~!p zl*X;e>}YeRb$@d>IGjP_y6G$7cs?4gV8S+NZ<|5sg@1W<qlbPlpG1NWIWh5_0%ofd zHw~$v$l(E>%3ZO@4B1R!)D}pa{gAT<3|+H@87MxTOj^61;kG6GU%ZoG5ZCJw;y!^g z@~U8aUU7w+q-srsLn9v#1B19?F(`B20q2$_9MC;5ym8_Peh>4h#NzE5lOkN|1;%e+ zHgtUQvdNlN!j)|T8$=floD`C6DN2qr(bHK-xcEKCVS1lJ_heVH{emWA5mA<UgX_YZ z0t&2JlmRMm>HCmu3pXh%-zazMr3SD@DgZ%#SeHNroJUBqwBvypKxf4;c$I3rM!L~x zd-AUl3`j$K_q??FbK1(JdnY+=7*&8*_;5%#=0IDUzOhTbEI7FE&}0<$aEv8brZ#vC zb_1cp$K-5^p^zwoJCo8$+%xPFJ0)X-jVn0A?8rkfR{mD7m_kwvAEY+Cv6PbfQiu;O zIH)O}I4XSYW?R-Q-?OssUlf-3jC;&cmjnQB3J}mNN6IPo>0%3U404r5Sphm8C>8OA zPb##4ay|_#Ax@BNOB^Bv|E$a>m5SEMLS-9bxxV0`rU5p>ZVTVu1YDm|zKtJSfKr_5 zrYIf6GXAYZ418!0q%s#R8k6s;;c}ue@jY>?cG`gq%cu_WAkvFc`R`sri}+j;vqt%y z<v|&wWbFJ55KXx-87>26M%H@vw{Qxou;lf;^bCv>etDN3d{QNByWDq1e6XZj^UM zxVr_%%h^CP-+62=To4>GX}g*P1+w6!b>j??qs4m+jav0JVG=FiV@Xq`r|4|sfh_BU z7$kABw9~`!NMhxrQHaS8hpD<(wfNej0H|rhhU3)mEUi_Q6WwmvfSt6|EnTG?2j<>R z-F7(HHtoJfJ-7$P<mW*~5y?=WB7+!Is*j&PfA*YgOdyu1uGFPOo<{swrO$B<W~dXc zbo8B;C`EEZ=|%7f4Jl|1p>?xlM+HDKq2U->rbz{8t%Zg@*E=I2o_Zq@0Rmt5vf;;R zPMTu1e^jVQ(ELQQ;?lV;B{et!sWy!+UEmgi4bo?caM_Q%(FJ2^><(5x2f0N|Ce&u- z>^knsw7$^mK$D5x6}^n85-385S^yUt=4-XfNM~Ip)|+D01QLbSZyj}}TPXy}H-3O= zUDA*a={@d7u3|%GhXx&OV*#t$fmz8R3F7Rt*C@}6PI6SS`6cJot{}WrU6Ya`$yYX* z{gb*Ixe8pk8yRVddCU+awcm~m9jsky$yffJLY3cp$mijP<O91RnGgMr<b!ZYGM|E2 zm)??0cOJ0?8xZm<agWjYyWTy?ag;)zC7uJ~)&FsB$o@)iSsv_63EJ`?#@2k<E^eKW zq_}q`RLABQpfzw>aR-1$<xaAmSnDzZl=qb3q$SQ)dJTJol;w=px%E{GLKk8tx^QQ+ zGzh6;W68zM<+fLR+5t*pOJcV$hR3T=7v9y}*<5b9a>uhxg^E?eOL3=jxdvvXbenVe zEkTO+I8#joBCNZ}xm?W|g^>$eA+gBPe+eF#@YGcBiL5S`tZ>8cGSkhY6<+8Z`;{qR zX+CDmTY|mdV(@~@hldcJkyuzR?TC#40%i(uYYsK|CBcp(p|vlOHCrkd`sTqBkzkzG zAeVm@1wYx~6S^&xk3ZC1ps9%n5=GiV7cvk4J}Y&?)8MZq2uY6FGAA5*6Tqm}Nc`<m z=MAVyh!fmAH5RFvn;SCjNBq<-91Kz}Nj|+XXx$jBWjB!0`3Br$sWV*9zSBQ(SV?o} ztQ-@!odl7WgmjJ>(MSBl&-?NcfxFuUq!ml_e6I`xWF<0EC9(*!#?6AaRjES@E<8@h z2%~+4v%Md0DYKP2hLm0rpVFa>8Uiy^E;!WUkH6$%R2Rz^l6h7S0|}IlUq-;brWedh z)P5vM6qe-`gdMtF#w9wnoS!;rGHvNuAS(X2EuNN`aec^6S_IAO0E70h+(IB0_Fm(} z$H%1_Xt421_y&l=Nftu(f(csP^Au{k2jHHO3faQSVjFTXhRiV(S3*wYGUaOZv?$j6 z3KpFrOQ8O}D3RdCg@8kt4MnH{9-#1Y%1iWs!Qo!No@&`~78cHle6@jj#xcD&!EiXD z*fogKT|G8SkVDDfUZGFkfA3G$9nYCu=ry7&`@}wAVrt5f^vxnX75g=7E&{i}&e)nl zge%^&AWFz`7Xu8Jy2H7~TfCcP#IOOo`J3Ca!lgHho8fp3o<r~-R#ZP=7T$QfxEP-K zR-=%h#Zu>KTtFvRoxq$zccYS&&mO~Gx<{bLPr>S{c2*ea$F$0+D!I+eKJwxCGo<~U zbwFg7q|Yzua>#SAm!qi~eU3bL;Xx?4>_>D-y56P9`#C&*^jHXX@Is7mxcWEY=YARs z`W@>@D>1Z3bTuIdd1;j~C%vhtNcRpuJ0;1ns<Sow1qC>cWw@;4=W+65!Qfj_^Kt?2 zFzK;_WGtqKlO+fS0F|1k(yX9rCR$~Um6VMk92_07e?Fr{Y}V{gCO#-W9(9fe-G$xd znuc!aD%yrhMsCHJE$C!K=wG{2gh>C`W?*$`w}Dx__Fr1SFd*duAyXGAcPuywt6u>w z+mS)S3c`78BNf7W_WbFDrWc{98cn5aCW!8%0kZvw@eNPLeI{{_FPe?;9PV`cjbm5< z@TI|^EZld;Z=E;A;Cp&>H+TY&@2_Ot5yv9M5%Jh5pi%CbKFoG=Sb`CPi&@ZeK2bV) zH%j5gjN>!8R%265Q=47;dkB^JCM}|yPX~va0Wplhko`3nnhi#bOx3rXpp-B;nYas0 z4M|~fBq9eBOCSf0Ou7uIJ;0r2>shn$sr<y~b@qt4Hce4J2~BPE<Xry>62LDLs#uY< z6?Ls2tZM5O4N=(66cX5o=fFY1lo-LD%@Lv;m#0!{xoi_<eJQXgKVmRaGL(TXB1EyO zrERy?h@Cc)3b9E~9uN!Gh&J9&3Kyh)0V!5A+Lyxm)Seifb3G&s83G`V11T%kCq77K zK0*v74Jaf5FtmqhIEm(H5+OrPR7mYP+wh=E22-)JxL?3L8)S@v9_g@oFfS`CD|&FD z03z6KDcp)8?-)Hlhm+yPWDE}gEtWkV(_@DY8FU9hbRRVQ$o?~faUaQ{3y>7HKQh2l zi#th^kc4I%o;%7r*X}<RuIc7sP97=W4F@W-RwLRC)#fWh_N2!>@gHxl;kXBfM;Hp4 zdWV#{)Cd=YbzAT&+q2eBlCEIJ2tYkZ7~*1Vy~!XJTe=Rf18hP<)%0<~Nmr-YqvdIU zvWq>;-AQcT@Do7b7Mu&PH!z<jtSGHiK`4NhDMTS9wDMALY6!1;<WF_Lbdg#UZ zUZOM?w;Vc$`qo{om2U4``6#P6PS_KB<O^HtJRW={wDdJ-CxUC!S_F@yIw%*Q!`@F} zPmXHZ>SvR#&;K&Ih@D2bsv`^dNSL9>y<s4Nbtb@XLfN{&e1l$*vCF-brP@HXF(6I9 zlxQ;|;ZrQMN^Tk{zkmowAF)v#3uj5k{Hau4NIw#rbS9b1U&;!}N-rO)@Wjv`u5tmb ziR<2reEvK2Lv*&Ms#P#MWhsz-;amgbaPGW@23v46L`^I7sV=?IDSCZhCiVheYhG}1 z6)PxM#lM@%aXe)!4$0qds3dT0fvZtDMZkcJA4K9jeqrKlBUG#gnN3^>Y|&$f4aUS! zpydrNTXC{1!T~>)E``*lRI}K$Q%#sdax`6K0xra+@W3aP$PlGT>Q2sw{E$T~F4xRx zKcP7mUa_r5HZ)~%AP9IGj_II*4N7H89I_#UNue3t_~7}~CI5$`d|c;j@BJoMN>>+B zWD4nmUdcXZPGNW->Yge2t~1D$GmC)dru(Umpyx-qgsmii?d;dG5)d}>Q;`lBKFpZt zI5h6fs-v=*Nm@3~X}*us0abev*x?LRxf$1YEd;_L0Dzrrs))f}8z?^FIwkwHeHo^9 zL-~{=0qk)g2^vIXtARtqGXaDREc6NQM2b52KjK}O%uOg0yCWT0vlsB!W7s)f^C~~& zaYX$2VE!xZo75e^A`mGlQ}L;Q#if*HY745eaQaNPqS_6v*<I&BQu|h>1U(r<9+Osq zn-mCTEGJtIM}X9(*;$tz0!mLA)n;m8Ek=?Y(Cr3l1-%PTHP*pPNQ-d>UGPWGP@fFe zOAWIC*787v9W<9tootBB69fkwtVX(ZoW>5=hIy^?tG+OPG;r+jjXlEv9c#jmuC26i z*`=y>2uG<Z2{PA&PDU!;=oocGK2<otGp;RBz1woKS>k(Ho`BfhZp=4b-J>D};$}3; z+19dM@R<?<QDJ+<a!s&SU$S8cP}kfptgIYEQY>5(f}P7ifH+z&Bv!U8fq}9sy_KR2 zW4jR)0wR17N9#PL(nCnFypW1Wr>+jcM%4+KY1z2g1qg9cOuIn6Wa^}h@T>$F6@-#K z?OIUOcE2O1XCT53oC{t1Dcg2sK<ev_BfD0|ag~uj)PkYink^|vc9@4929U}ze-Qv( z*LG5R!t<+eVP0?etFriZC72}^fiW%x6t<IyA|A=xx@-+PTf<PvFuDnDwTsGxWZKwK zz5XL`H|>n5w&kHiQL+fAsmuzCR}YZlcq!=~EUyq$H>?&rkTwBN+5<T*N+EmEyQC!i ztG(DF(~1om#3+qvm)4W9UkgSPdJut~zfpLhZDa5FG3X@_#WeD~WDs;C%WBZGSx8F8 z8NzMNGC`s4;#|eCNTd6M9z_eO7hk3L3i==^pLDB*7O`bEaczz4M~leSRF90Pq`%z^ zzPU)DRvgJUfFbFy7hMIVrz&OI6?W2WpiHy{2xHf_L0py|&vRILRyC;KJR&Jniv~nU zZrI@)@fP6C27W6lA{&%=0Vsk|vNb&!24kKu<0Nwq>!rMB#ghI&>m)W%svsdwv=I3t zA{irvc!S7%l3LTdgfoz@Op|11tJ#TsuzIDf|MH<rLu)BOBx#fiSKKbC;V5TuERm~r zT<&+3brWyh79c1N@doy#H*VWi(Y>>gMe8N!F~PtNXd;jT926kK@+NYoUg!*wVF5>5 zTZ%{(J{)vmr1NWHWtI}LraDU)p-#AT6wYH1${tB#q0SKIj~<dfFy9t(nEjslD4}Yk zu8YqgPiFJ;K`C`8F{hhKT!nlF_jkE7=rJO&#F@9*cq+{r@T%?PZDZam28J$8fIphh z6Js*|mE1GW2Kl)T!36UgyJI6dSlS=JsgzP8s6dI6h6!U@;_6wx^j>vD2zNl(b@zhn zNc7+8@>5VD388v~u||Jg-cf!o74#}~&XuI5PX?<ND7mJY1_~!(lLh#Q>wwgw=C>m_ z1S4U@_$XK;dCEofTr^R78^OH60X>)7(3upaJDFZEPOw@H85@rWxJd%S0Yb_URtv=s zcO)G5XGy>jbkEJ3JpT#tDBWv785?hf(>3+eqBdj1mg>BRxc<C)|Lv3^BRCAU6sG}Z zju<D!WRwYZI3O+zs@id-P+Dvxdd*}<fezyh0!o-KWpIK(GbDZqFd`i~)1c|42PX*9 zg>Zy??<Yi$@J1^MH-hxi-sjxhDoAh2>=7&=1D#dhBT$>&Q<+AXtFI+Ub<$fyZYidw zkt>}T-yA9Jd4gJ4;F2-3Qvx4h4+D(VVOBuM*5C+<1enf)&=QOC!S%$Vw!wmzJ0r@C z$^1iR93LfCT%*tD&ZJX#u(7F`s|HQsokCWr7A>7+iY}WyS27SF_#fFWBA@87B9qlP zzBn4><3Sh8i}RW`4*N8@CBmi|U|wLkbdZBJ-++q{^g7Jr1WAcoBKdh>tY}>*783>u z59AF6sd#=ct38P%1cO1v@i|WR3{qZ~HfUuKOTqxRiffD<;&ma55HhSP5yI(+4GkW2 zQ)2Fc2@u&}kQf?EkP`$=53ZGi(n-^bB2VQ25lBVBYuh<-HVNrCIUi&uK>bpF!S}L@ z30INzQv4nYytvk&MCDP=TpljxC%~$%ISIXf^z?-)13!EG=-Km!FP}YU>P?I=QX-Zc z5?V}vz|aF0e|#Tnj+ps~bC5M=d0>38TH_bMThcNKf}@sb)6jUSawEfKnq_#F;Yp@| zSr$srm4m(_)QFe1%MJ`1WsKT&9+TN`En&Cf*Tdd9n_vkU<Lt6hAT8hMj*`fbnn_%( zZGB3Q2>n5jt&=IHM7^+s8P7931@_9$kpA>Aw@D|5D+j-~<fID+%4EAu#ISN@idF&P zi-H*HmCL8R3|p#GTGAEA&5w?~Tsz_JS}4md207SfxfqFx|FbEBt1C7>P~#Y};u_P} zIYjJh%&IWA-}aiRL}ayF1O7X-IKAi(j*y-rJn$%)q*t4@P-FsitF?#a4*pSqfb9X? zzr%NS9DjJ?8B8>G1+p-!qKRm-!)}Z02x(#leH_ZkB0#lPhPifP#U3^v$Xk+z$6JvS zhk3UHvvzD}!&uG1Siqctz#Zh)Lt|CnZ~(Jr;%0-)ACE~o#4DI7Ba5eS?nP24$PClL zaB_s;iw5@O5&TM!*J}JyFR7D_5w;9rBjB`Bi|-G{4>EP=;Gqwkn=Ep>-J6OZN2o6x z)Xmn}4$U?37qcE+)=su@N_WrY9Z3Og<!fBLsO(#V;b7jMK*LDkPI7#fMx9}F3r~?E zMcitW4t2QOZ7FL5`<#3A5QkYb%^s;f@29PEpTN|}PSmkj{|pHTx+k5Z93mRSxw*M$ z<8~1we#vc&v8uFewRAVyz%|oyFc79>$j9O$GY06m)D+ak{SMSE@x<6XVMUEy$>EWL z<QO?!y0WPioAAsAu^`8pz_+PT-G<_dlrYaRY6bb*a=~vo%#M>l3MUxWF_XwZJLw6| z!2%CfW0&6w4zO#3vn1HY_{x32WJn?vDq#v&_bs9!3vh*i_uH*cHYL@<W_Q#h|7|hc z9e3?DAaj7HB7+AuH$AiOg~jr`bI!22FOX#(NYgUlVY;dqJJ&{0K@vhUHcq)x#&}X- zDIt3srUaf*^vx@)SJz?W8ctE>8C^wiqlB(G5iA1bH=*Ha?F#j-fiExZbOYw3bHCq; zeKH7}8HO6-^wORq#05k2E?=Ou%(qwB1P72SJ|R!R5#`FR+;!$Xy3z#5O8*QZ#uUm0 zWyPhP>D^S<>_&kX!HyA_O;H`88>Efv2{E(ZOS@y*94y+a`+ImcpMVm0$inQg8jeyp z8ju6F=()_BgC#>qFF!mxg4_;cDko46gFf8|+a}>6DbE?tY)$>S9$23!;_;Kw0QTK( z>!KSDGYRX>y7~*u6~~&kX+A~Y<C&#MA8p(g-$r<7*-4L-imkz@S^kioL+AxKLm)@2 zWO4r?XJVPwDHk_cCm$!<f2bjQ4$4JI3+*921X+t0O6pM_@W(c6z_!oHe69ESyiRL+ zlnT6k;2P(`M9rbz+eBk*%YVvzuo{4J1(i=h!)d&&lq=|LXu=1M*mWD0#$T(!7+AVN zw2)e{Qq2q{qhC<#Jjf)z_wt<kSl14V=iI*(80eP6jwgzlR`y6zi^_Iy3>s*{)_@Z~ zxd^QcieBJJ>5ubXnR9z#Gj0o4CIz*OP;09B8>?qOy+d8nIL<)08|H0O{#2IF;<E7x z#u1E!pT<<}aq<*8Us$rdJRm+KWAn9Tf2}F!nA{$4F-XF8t$<TNEZ|SpwbwoCJQA!E z?()B-<(vMHnbDRIKs7_cOo)oy+S3ykW)fV<O9{QJdwd8lVT{phh<r6=2D4$eHAF@s z`Y_hKe=|G>b1S_O0TtS#@$}*i4(;04?~!%;a5AwQgw+yducmaI0|SLj1K%~mr>~`* z8pmcFeBBnD7Vptsle`Od;(a<S$7aC-C)ng|rINoG^RQlly%PkF;m)5-Fa61J_Q(-F zkQ<@J8-V<Dr8U_Fq=@VUie_?bvZ`3uGOaIGVYOZlIf<NVVVH*{2+5T_Ec?VWBF26~ zC3dt}!9!DPDtIqR`x=K)LK55OM*w3VNpb#^$c^PSu+=0Lw#Z<)?wrZ81s*!3m?xKI zt9Ve`XnD>YFH74k7stk{0VM)>`JMNc63D467!Zc>QQlr779QGl?3tKhXv=vsEZ$gv zRWR%laTu|)W!oaO*~R(qK?;Bc0DEu`f-F2IsGA)&tvFC336I=zqcfW9adC=6LvHRT z93`NHI!TtAy%C8ew-(`1doAv-Ive9^#aobEn<tJYwkRJAkLA#m_G;T$1cs$RJSaqC zqTC~?B|+$Hd5iSnN{WT%YsXh^vOJIJ{0V^gCv}cV-_t%!Rs^`5E0wX!0>yC-o#CF` z7Qn(SO4P4Q9Fo`sdlCzCd9C+)epTiIUP9X~-Zt2rP0shgY|zZvXpHNyhe(C3XhGjV zUJ5tI#XeD|j=>nV7bleUYPv-;x4^0DyOt%dOfv<6%?LJVb954|El^6;8i>%$4qr)P zyRRqn&w17FV=`p2e-|N@iYS)u+`f~x&b`=015lu|vO2LsP5_N>X2|kiK!$<Z+@uB* zXowVkTx*1Ez%F4JHQHOTqsEsC4&SM6t6V?T2<m$T*r7eG)u$3BQ3Q%ERa_PdX4DcJ zb2+Wl+Wz({WbQ^xUU2n3GSjYe7dq?lEmNP##bsnwK@(xDDd9$DjFV=ZR^o6Pn3SOT z;aLz#DrsIO_rAb_ILwjt(1i<HGI7Z|RY0cTb^V6a#Q6k?=%ACrdY9uSAunBLZYX;2 z-~psqg{X?(#uKe8aS}MmF}e?=LPbBA7@CD7%67%$-w|!9b(0?XRL@;!6mOfSKJ1n3 z7!&e6A|3Xd1Mnqe(s+fHg>Rk-Av}nQGhXRIM7#h`0|#>v0}cM+Z7B{28{$qoMQ1jH z)$Z8H(#CeYzq`H#X!&>B{_ea&p5PER<FUh%o9e$pEaNrY0v(nYP+HL@*%{ISTCCNj zz)FAS6GPJmOg1dcz@M){-g-qQ4g&T}T+qF|ELFS^%N6;C!?w^!xq2ivO+P@3j%B9} z27yuynzG<ahlm;QW}J}|Rv{(?7bOW0S@k3C3*{ChmM}T>u_%@&wY7)h;?aDhNtjtJ z=J~1AV6AAhc(ec)n1Cm`Wl^?O9h}MR{#tR@Dxi;`Gt^$SN=u~DWWYv<ET?sSm0fu} zQ*}kE)$qfXU5Thcp5Z8_r?_zRvO;nWaxvZ%9d^V@OHj?$?hiXhme#)@H=SGgNB~5G zf28Hop^-Uau(rHF`Zqm}UD_(lf9+$)clj*jr_mOfVO2a6#9q<DPZvi(7qM1C0iB{d z7$8H4a6<@=X34-#L4$@R0&n65>cRXsGxR9xO$-_*fB0%7b9ji3ldCbZ{h^R8PHpgs zNjfP70+C!9B|h{pA-TNcm3#CjvK#RvT@Vscj@y2ujQ@~ZRrZ=<b-Rr@=SJOb8+bjc z#po(QHaIzFUwxWYa7#&V^TufN#@}Q&zSzC-)$WZKJ|f#rUN8zi@9m=PTUltvn&rAS z8D3rTWl}LBKbz=FXh{qp)A?8(dq|0cTVh2AA{U-xL^>4dzg$lz+~9nyN?#6<kV4DR zXwrnUu|b-ZiBLU$p~3V*X@Q(8G4k%5(|2beRa<G)B2dArLBx{dE5`I%s-i;I*vK7^ zQQ85roTZhL2ykv_x+uj(=-}j1bG-12=jh&qJsT12b(;WYMXQlpj`h@ij=010<ZOVu zv{$;F7dbG{^r<fx5**WFm;rU+%7fCogyr&zIbptx?)_rWgElr<C7=%PJe_#4PK<AD z)7{J8CySJ@(06q(c8fosNLF`%tY~TR&r5L7;&WK__=Wo&BosvoE1U|Ma*#pqU}2pS zp6O%@ZAGF%@MseylJW(5Nz>YpW|%<_jo+GiJG(L1WiFi?O$-leGHEV>NeP(A9gxRD ztHV54P0V)Maxn^RCocY{EtjS$&lx8F_8lhhHyv1`W-xg{pDjBmr97FRR)l^fro?+` z=Q84pQ%u)E25f;y$MjT_PzN-wx0U%^$qM5^hh3O@#x)h9D)52}cWNiv<}@8s2_fjN z{2Nw&NS}!>R$6so%R%+UXVyf5gtQIK28D`PQV~rMg7HYok-na*BnTFDI4O7mDUN_D zCkuY}k_PK9$z=B3_iEA2>X2nh2~k@(79f!(Fr84=_kcAV`gA(U8NRDK@K!WxTAKfI zz3RM1E-j1ICv;&Ie`l+hwUQ+q)ftNm+0L}(UYAf_x?6xi;kgYjHI9q>hnWB69OE7{ zv?{@+1J+Qwq{<?5{cY?{zhS7{hb|}A+7T^f;L>oUp9BYt<s!s&F9FB`z1H^2=IfFT zq3wmM0VN5Lobmi}I5<B8nV@4$-X%z+<r<bK<c34&E*U^^Hfm=3r#r1s4x9=#iSg4N zS?%=`6=WDiF8}q<ADA^MA@PV%QE&$(ylcuIb*50ZQ!2ofD$l+?Sbdp2fBe<6zj~a# z{Nl-r?CF!QAJ<KQxJy&?h7~$A0o5n6CXC>-Wo8H5I)G*$4bR3%HEjC#;sUWJZ-`fO zF6b@sNO4@m5%5X*{=)J*zP}RdOUZ<_`N2%WHAW(s^a00E_d!AIhw$*{`qB6{kX1~M z%sLC}Hf;#WNU4T)K4IOmyC(nu=U3!H5miPg;l^H$vN*=QeV8{eNH|M$;V@V?QS8t3 zV2|B}JJo*i{o?R!q1!>}N|ae~J?varnq8VuD(Wi!=4TNMeI82hHE!PaEalg!&~N;n z*Jf}w1)0Q_qmnne3a@)77!GB`1_?<DJ_Edjinmedg^@0<UZ>-9kGXPGFU1vxsb>fu zeRL!@NZB_-+)U{eOm|^qPZkA~qvy&l77$lQDeuKonXW5bHCD@R2;5~kO3OfBPsXcV zO_ud*FJw&vlZfHdyiTH;rL4A@K#`uHEaWFE304|^)BO9VBNGoy;QD>ikwsh?lrbgQ zA`L7TNGrQ(l?W>Ah+8Dq<O>P~AW;w|G#PRIxI33{TzW>zy&$-Ig*P@;n<k%2<#)8L z8C-APyqP8GH0=lg0ZBzLU7{&{Ewp@HNuYj96$V`1xS{ov%$QY}Qz!)M71cv^lB)G_ z=x*ZF1kaqC$RX84#P#^;#+?nxfLAb$Q_Z19+YIN+<P0f*Uv`Yts{fqt8q!E~U5f4% zs+ZFpYhh;pd(U^Rv~g{Qe>Jne9-~+0RktqmB3S3VSQytxFulOVO$yP~QY(d2vPRcr znKnHa*%CAIm$FF51&*GU<dTkMWdAc2U9)kWgwnCDBC6oE4Jp4nhtkfK9Pq<BWDGY> zZhJ_rB9<m9Gh=x#iT4l@v0ZD%=vW|WfJ}&m3eVABY6*5w<ev~NY`G9vT6iiJa@<h_ zQ-C!Vu908@fiegY2xt;x9&ozLofp}s;aO>ACh&%W{_L`XqYJ&PmX(_4$mJO-Wt*X= z47r7Qln>D0)@daM=#nD1UKL0c7?RfTb-T4yYI13VzH5ulX@4wfVl1~ltnH~qa5s|& z-Y@gx`*#-PVuDlaF0?=5;=-LveyVI60WYb+yoL027&tWip3grSc(IKk_a*)HZ(VP9 z`W6biccx?)df$;kl&!sscbjj|(;`ON+uRM|nt2oHv!Ci7xV@%-cd!+bjEWuC@v~_Y zd`bs~s5y2Yo&A2)DjIa~wQelHqe&&%?iFiFCdi1((PDFtxN)5P{ULx>Z2G`UrTihK zM+PGzRtfqRbCw4oX;o7_dy{Cvzn0#6V|edh_5E_wPOwX$PQ|>DST(d2x%-*Kol4QB zg10dub_Clh^>V%W=H@-qwpuP;c?$k*g>L%m>2StqLc8m801rmH>OnL4icvzS0$J6u z5C>Fr$1J|S2$c@{jYk#<K*LL7cR>RtnzOs6!2pAUeuH@h1@QA0yan*yK*j_8#`ldi z9`zI#H5Ar($(yLzMnk-nY|?xt)^0U6OD}v1kqpYP2UaMq4p2YSDGjZ0#WuZ#$m1aq zGE@p|9@bS95n@C(Dbj7(=^%AkCG7e?(fGK_i0fbc_jLo8%vB^r2z^%|sK~>Gk_e^4 z?-3N^K9+;-2@}EEPnJ@bh{NxhSTb+25gOb8_v8fD4_@et1tEcx!mL5r{WDw>Opjpb ziV&k@24qn@p&WuIW=-@-6w&*Wx+iH16LUVN84bp$L<s2HB{|-+OtD!0Mf=}YP~jOf zn!24I^Q4LuP_5gSm^TVVbPjNDBJ?n#(B}s1ZCJ0-Mn-Ak?FdF6dd{@l?_8D8M`L_U zj}pI#6G2<NcftK_JTn4Q32@qprm2U;)RaTkB=LZ4x0|s;^IR7piwQT^LZ3@or5E0{ zTbcra!H$ZB%YMze6^2X+;9ylRs>Ge3bm<qXc``wlk1L|dKqfM@JsUBQ5mj%MqglD_ z_Z(RZ=$;?TwG`}>pK8Z47ScFh{<oQ;DuE<JGz=*r(BX*m<*?aP%y4e030AyF>)#Bf zpNFDWa4_+jLZwtewe12{tb|GH*B96bkT`sF(MC_=GjIL#=iwQX*<xyvlJ;5_vH|Bk zH)9>HYj{Y>I>yt4Py}9E%?UAZDb6Mngj8eAKu{*Q5{Cm!op**O;0|Ho_zLHA@x!_- zPi4K-OS;n%V&>ti77ULCVTbfGy=YWh^3r|MOtL<|LmmvcUlKgFlECdX`1f^EV+N}{ z+GzSjS6hgI7|tWo0WRqPm^91@fCxiCL7m{h?ck7R(*l1q02Jqh34p|j)(zlkouqfI zmp-s9%lIjJD6JqZUw%~C<dhL}y9kr$fP>}qlUeb+an;$ml0F>Kj?Vr<BAS1AG6{Rc za+>fWOJ?-_NG3twlJ;a-aznj@@ZeKzE3=<&u_-0<d!&xnlH#c^4(Cog6&77L4qoUN z=Q{y70*NA*Kyx_+QG`<JB0(9~L%fXyr+Ew{mVrO|gTt8-uLp%H>|YuZ>~arvtm=Y* z;OsOQnmI`%{+aNFsACm7|2YVlqQtl50NrnCT>Kv>2zX;WareQiV@W?76q2UIYaspV z?DDAC4x&$yjOXg6Uog;@v8pJ|#m_?y_yox|5wvkd1iX+C9ty)L!WWA%(%~sFV@H$h zI&gZOm6AeIAhu)H_^Iwz42Z*1S)?=E<icQaK6CD3-KXaw6kp~mD!v54*UkJY)x+RA zexS$^1D)4gT|O1QDle@6z2L(j@q!!&{4#xpmUx2Jw-_BbV^cPP#y_iy6#ESmp*x}3 z6G<uHC`;lP^tB%&(?|yE0cnkx;EdBOmLuKu)ut+mCy<!|mQE405H;=lc=@oUXvE%5 zhZDhw4I>fF@K$w=;%W$w;lb-c56PzeASRxoNqM5o2KQ^`qb7uegsc~lPdJDQP@0qn zK1<Ax!FMVW`l-zkPia8Teny&${a3n9x@QDJ?U5!a^AO%>T}bJ0F5QG|H0Tc`@rn-; zhTy{siA<pP?9mK&63{*;TjOUhe+Rb}Y#RuMfD~MX+yG9`tjw9XbR0fI1g?zpw24cw z{RB5xp9!i3l@zlT`<VVJa760~u){(zp0NhxI?<c^cS&f#qCbKZjXVpwvlq?uS|ARH zA8j3xaSTCzowtV*(>%n3xQpW+IRjCcWAPx(aO8!~re0>zo^Y!o0D%C!tV@-Jm%bIT z*qh>CYfd6|L|l)@{zXpY^7jelG1goxA_haQs(tktJK;RAC};je-M^4<RwX7xr$9}< zl+0CKT&MX+M*t71sAjo&(|#av=uLJT)LpWj0CDzS@#H_S#jr_$7`G5g0Tqww<fY6k zCb4^t<mOJ`b|V;_IpOaPIV~)TFuzfy!N4b^6aiU71Frq63NI?~q`m`gnm+U9e2o)0 zc@O+x+pin9jK1BM8w066Nt_EQIz#>Fy(;HILpO@{jX`_T$G<mV@F7EkQ*CunZaIoz zt{X7Mzr8&_5Xs3iqK*Vkot+LjisJHAj_q}MGt+#TVRMcNGBMyw7`-v=EaesU4B=py zMKa1BvRaaPnCvWQ#~3vzOxcZYM?gD{`dq!x9osNSJ=P+5b2`H06*>z-i*Q<C1<I@o zDulqFm1z{h(&EbU0~~_gkO##*1Lf{S+tQdljDV<|q%;kGGWrw4aS7^Du5Nd!-)?IG z3{CH^b3Q2EcnxGQ?>5$qEyc1&xZVo8a)=?CQWc-1wJe&4ROGa%GCt1VIMH~>krE@S zNGAkm&5P_!5tB+{?y3M}2&Ln$Y|C&eg#t}(+G|cd-Z~t;ScC(|rC~89gNtIT^+{_> zNE}Q6g&2R=n~Ydel#57enxAYp;Xtfj?2ZLw*U4VWo{dDr(OZRtagG`=(P8C;r9-(C z!=AU1SVuAgdo!GLHih&+wouuE`pxRiO4168@gth{Zn3=umq4Z%MBwVSE8Nj^BnI&s zReP!=4t~^R&pYcoj%l`5UuXjX)a%(@7S#3v@VL4ZF(7&E0G{hRoQEV2A;$y_<wK+| zKx#W$3wi#ekV7=IBA}Oz$ogxu#aLqz>a<*Qe|vpr{q`#b-zU*ci97WZ`2xw_aK3{i zjA+@BP8<>RsU%p9Sb*|T`+`3z!9W7dO56rXB3^+Ov&ccj(EGB80_IUPgM)A*Mv}t( zz*jddX-;tya;#)snZ=RQUlW;DP23gVLAtlb+IR;qY+UN)bwvZtc9j)!tD<WP1U&R7 zEEO-)4kq|L10wQxk(hB&1n_7!N+HfL1aTN72nd$#%zBUl#jB7<H1=!5K^zipGaMu9 z;zS~E(=M^@fON=|fGVBIKZr=e!WW7a3Tk4mmXS1aNx_tKu|U}x{<yP#XZ_<OIzlK= ziJG?R=R*42WII_gV#Xv{0+!*O&)*J<z0^28gw5Hqu%{%I$-RbzQ_Qz7sk{;VR&H05 z36<=qIyVWXhQ;kmTK2Vp=D9d|dXf<OU&vf(Heoc$kZr28krACoB2AT+bh5Yt<w@;O z)XHubvFKosbp=Smkq4Y0B@&Wtk3N0;pK)X1b-s$93#Z>{>R)G}J`s&aFS6~eZDbV| zKE4Cy%QftdxID)1H-($8qa5VtX5b3z*~h#?2RP9a1k-!+><F&q)n&`y-ZBBeeW25G z3ELNNU#))hp>9gsz9Zq?>c?AKcRsoMNoR`*D#GV}?~^+_AAj84x_kHjy}S6|;qC2D z?$kd;sgLj9xp(*e?fZA`_U~=qyL0c(CwD%+hyPF?Z$80)-FtWa85+8CuYd10%irI* z_sPBd-u<1ed-v}3@9*qvp)9K2zl|q7w8>w6ws-I2t=ss|+r4*h8})Ca#Lj)|O`*GU zhyfjL-NAp`cRHQzzI^WAt<=9mxY&b2b?1H<x_x1Jz~4nV8yR2)GNecJtMyQd;But- z-O_u&zm5O8919`T{<pVS8s9toONa^YUBNcK-Nt`Agq(d=YUHf32q$_@#<KYS{#}&# z<nA^wAmhNO`}gwOJIQR;zWunn4Y;+=UCa!=g|Xwe&fM*Lhxb46dBqgg&Pi#69398} zv5s)NckknE85y9gp_QF7R-IOWS>UG!TI=5HMl9poZH^9bZwp;{`R@JOw|jRFZ}04& z^zA$H@5jVCdQTxpFj<dnl<(ZRf4hGdgN+n(|L*;}J2}=DMZ-OCUM}^W#z!CeiX-!r zQ{FY?+R7ST`{=`X_&_^;EO9%H+s{$Xj{4VAF{1o8U;gBO`pXag@&kVU?)YafKKzeA z{pQ{ueeePQ`wMxh|NLM78~+Lu*$na>3{2Jf|M}nk?BD;ppMLNG|NHY&{XSaM`Z~Iu zn%e(q_#ggD^X5-J;D7(JRQs2IR;e8_ZhZ6QpZp6J{UiME|Ha?`^X^aZ@8A93e1^}D zKm1vujrj407ms!iV1IzdcYX!|BP72(2ysCnITzJO5CDn;`_c}#ogBC<bRZ-8@rRE} prn?{ik01V(4subU^5YMGZsQLl`0<B7Cei%(!=GqtXT@3T{{vSM@jn0n diff --git a/examples/example_framework/students/cs102/__pycache__/deploy.cpython-38.pyc b/examples/example_framework/students/cs102/__pycache__/deploy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d9b529d6cae3250756694b95ca75ce15b33bf86 GIT binary patch literal 760 zcmZ`$%Z}496piy}(np30G^)f0fXX5RT_S{#KuBy@0JE_oS;4+-O_MnCgPC?aEZFc5 zu;iDrLSn@)u;Mz^AP~Ziug>wk@jX61&+|!u<J+yGdJzV}cTfHtDUv6+%ts6&5Tp(o zLJ9FWtivXv(SV~mZW5XdIIc%cO49)+bw)GD-i<#`VETp78RTNL3-t#`fLsMvcoIZF zL^{muQ;a-?nHVnujFUwmreY>?ak7cT4WAXs>tH$mfog&!>C;!xDQ#B>7Y3~B*hW_Z z*jm=W^pL~U?M-F3pqcAxRS9Ml<5kl+(}KlpW?I=XA@$2XW2~uU%h>Z}L`RKm<$6!Y z>h0PYURj_iTmp9%4&$?;IOFEQ{ng>rL;Q?&0$OX1+i&_QW8I!td<%>n(tCwfJ7^DM z0VrZ$bo*sYCq_BV!S|+_0AmMq%4?`v=DBPuD-~iaz}~#Z7RwWw51otS{Z<;^EueQ? zEA8>}otE!W?W)*Hfsz>4fOPQ0=*Sn}{eSUaR_WMR&%Hq%=;GQao#1vKD$~auI@_nH zudN#J{teDE8eRy`{4Qf(1D)d^L-@&_J}l1-Xj59Zh4Pu=uJOK=|7a@V5}&CCa}C&9 u<9QcK?@HMks~xvWmxCGQZ(sZDQKN*b;W5FU<|c+2InIpC!i-GFocscNIOT}| literal 0 HcmV?d00001 diff --git a/examples/example_framework/students/cs102/__pycache__/homework1.cpython-38.pyc b/examples/example_framework/students/cs102/__pycache__/homework1.cpython-38.pyc index a099ef9f65bf987d85152d20d4dad941ae1d27bc..d67337369ba5bf909f1eb07c3dda178779750fb2 100644 GIT binary patch delta 398 zcmYjLJx{|x47D#`M`*i%U}1oPAp^`HAqKt{Bo<)l5~cA8RkSI(q)4Dh9m2?hGNKN! z@jv(#S@{u2OxP&{mh5|eXTRsK;M4D2ce^2QJg@8~7wV<AF+1aqfba?WK#ClG(I|3n zl|3>Jc?QWRzf!L-R}~^K7Z_kp(-MU@ZiSUXpyf-5Iw^|wUkG+Mk2&)WmjVhbf#;ED z@$o&}<fzwhz8$H?9S!rM(bn7>n;VL-?~fmZ`v5B{KZE)kPs3rB7j~K!6I-9Ctv&I` z6&LWV$2D!S^c;dE+XFO%5=vYLF|Xg0*%4WG#-zbP>#-;<(~Xl5$;v|9vAW)OuFl=o q#L7pb<2XykCXO5L&JMHUpMnV6Rz@F;Co~<I!}d_w(}DK1TE$<OSxc?} delta 535 zcmZWnKWh|06rVS{x3^hy0gYmlz*xzFiXd1?8a+jYYmzkAoRfWXmm#}5GBYc=Gy&Jn zDr_lf0v7oSru9>7YvX4~l{c4=LLa<&@BQAt--G#`{OPal4h9(``FQa}zKl-RHjY0~ zglxhPkbh$nzzAcqTt(PB;P$PGF;N`**l+j`cFG$7Mp&wVuocYk&OC0Q;lOZpn+*p? z^K5h|%g==p&ey0aFLHzGnb_IaPRumnWM%D5DZS2*Wl=1|Tvf_SuTadaDTUux%StD7 zwG4FSysdL@>{HZM1!s}v5H7iq;B(T~q;8cSPZu@tm98RI(a~8ZNZ#o$suEHZ+IjJ~ zTon4`;`^J+hwlU5GH{*MRqnN^#Gb*$_#C?RvGKQQg(GW?9l|6D&CZ`Q8`HAOPwfGW z(wl=_`wJco0`3AOme{2I3!C@;r`4`=^&(Io+fX^=9mo#$mNT!P9>HBC5kvF!xY(|= vKl#qxUe}*a(d5&qO(;nyL=T>awg2{~uS$b;p|-**7d}87rD>X^{61U(O4)_n diff --git a/examples/example_framework/students/cs102/__pycache__/report2.cpython-38.pyc b/examples/example_framework/students/cs102/__pycache__/report2.cpython-38.pyc index d06f59685aba1b0e7100778b3122129ce8573306..e88ea2f06cb46d8605dea67d9293e601ee149b83 100644 GIT binary patch delta 1025 zcmZuvT~8B16rI_R?ruNG%7+4i0)c47v|1$bK~0D;`ap<ajD`m`OJ~rv+isbeML~?l z`f5nCubRlCFUI&AeDfd7KS1K6G4TcOY)uhElRdd}_U<|7-svm%r(3vRC}a`*?C%%- zFYUr8-fB;m=Wioo5c3TpCN*06vIXZ1vEXc_XPY2mht9GCyhF0UXDNohEX|QzO=BBW zr`Z;2VGMokPj}kCHhOe0ap1WL4_`^1<B2(a7wz=C(1bw@)%pO<qD8cX9-?jZ!Q9rK zWC$LD2)i0qjJAzch?cgS%HBgoC;5y^6I(rxX&6RhPa_tRu`89m3*ZpEJ2*JlGoe~2 zTFE}1oRe17Z;F^vX*OBV5Hja;PMNs19{ZuR8gYG<GG5fB!D+b0Z1_~_Q7k?p)(`b> zo=j;oD}F4Z&|mfFGr!&pX}MG?|1G^W=GQ5NsFi~T7c8!dh?T4S@})|d(PqR%rPSP% zwxC>iJ|Px}@;m_2^4P^$+>3jY_u9@?#j!yJ1%RT)db@-QlBQt;P|19%#pnhEr*1;@ z#hH_;<8-&}=m$)Bs-5r*fQ=1oJIqR&nbE{FUSB^2B-nNRi+XA;wUBr6995IK`li=0 z_rt;DtMP4R2o%_`k};$y0ufR+0Vk+qSXO5qqchJq@XLD0D+*>hL#6uZWPSfm<_Q*5 zYn$WA4|8^DL|Kd~IHTaK0yR=<#LlVeFlJYfjlm1G8?NqEO|Idrow5tbzCN71&CI`? zgjWkihy7<U1$4T(v|T#uG2CDfHMlg>+m-pIU#)pTUEMK1*8Mz4sDEX5?=EJFleXv8 z{h;A_#k?FDh#P@;#C$?4rNfeGsxc8(<?dk$HVtGd(MfyE$kM)6k4PNS8(2BJ0C}9p O{hE8i?X$6K+V~Iu4&`V7 delta 899 zcmZuvJ8#oa6!yJ--6VEvNFPAUt15#9lr|ND0T@6CrbrdASP{m`y+u~+Bpf@1N(c~P zVnLBtmM%#A27UrFi$^vV1`rENIQK@R6xGr_KIgl~-*+DV<-PLU2d-Py@VWTuLUd*1 zo+PhF=P%vo8aEO<u<vNx<klOFTf!LX0mInl3}dF^ipr}7Rm3a2ig;BJ%&Q59*E$ql z2wl{M+K>?B8-2J-nxpSKGZf`aJh$*<>jjXNI(<&K!ObDvld0A{&9{p$WX+A=kH_P0 zGP&vN#W}jV;M3B`M6wMG!H*iu;OqCJ;w|00EuKYbKM~<JM5%b%gH9{%W;qPnc@J9c zY-7FA0@3e5-dOAJmMj-p9!8v3WEmD3jXK0Dmh^X5YZ$;$X&f{@1$PQaJtYdW=#`FQ z0kI<>6SKuveQU#$8S~S$gCBs67g9_qGGb6>72MdnegrWTnkxIc5-n+xO)5FaQ`XYm za3UKei^Waz^TWE7oRc(0Y36aB2w27l{Q*v8(upzYWC`&=PqKzIo0E1Fzv`*}Z%<(k zal5m7rs!Cgw-%(tqBO^(IWCP{juL{i(%Ua{N(d+LLJlO8JHws{B~_--;)k{U@)V*? zzXJcUpGH`XOJxR?Cj(qq-0Nngsg@Zy{ixju<5aFRJ5+cDDSSMChfO>J5-V94rcvAt zL*EUS=Lg+5e+&^9jkSFj<Zl`jKkY^kSP*r2FWs*OR`F6%O{zYm>ylkddwh_H8$?zh T|0ahxWR5WE&3bi4Jd=?>ny|T5 diff --git a/examples/example_framework/students/cs102/__pycache__/report2_grade.cpython-38.pyc b/examples/example_framework/students/cs102/__pycache__/report2_grade.cpython-38.pyc index cb391caf0c1bf2175df62b4c8789fee08d1961e3..42fb3a4a526346eae8494f38ac29d254b5f30b83 100644 GIT binary patch delta 4043 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 JkXhs_{|n40T%Z5| delta 8117 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 B=!XCR diff --git a/examples/example_framework/students/cs102/report2.py b/examples/example_framework/students/cs102/report2.py index d84e9c4..e4d6b02 100644 --- a/examples/example_framework/students/cs102/report2.py +++ b/examples/example_framework/students/cs102/report2.py @@ -1,18 +1,20 @@ """ 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 unitgrade2.unitgrade2 import UTestCase, cache, hide -import random +from src.unitgrade2.unitgrade2 import Report +from src.unitgrade2 import evaluate_report_student +from src.unitgrade2.unitgrade2 import UTestCase, cache + class Week1(UTestCase): """ The first question for week 1. """ - def test_add(self): """ Docstring for this method """ from cs102.homework1 import add self.assertEqualC(add(2,2)) + with self.capture() as out: + print("hello world 42") + self.assertEqual(out.numbers[0], 42) self.assertEqualC(add(-100, 5)) def test_reverse(self): @@ -34,7 +36,7 @@ class Question2(UTestCase): def test_reverse_tricky(self): ls = [2,4,8] - self.title = f"Reversing a small list containing {ls=}" + self.title = f"Reversing a small list containing {ls=}" # Titles can be set like this at any point in the function body. ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result. ls3 = self.my_reversal( tuple([1,2,3]) ) # Also works; the cache respects input arguments. self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code. @@ -43,7 +45,7 @@ class Question2(UTestCase): import cs102 class Report2(Report): title = "CS 101 Report 2" - questions = [(Week1, 10), (Question2, 8) ] # Include a single question for 10 credits. + questions = [(Week1, 10), (Question2, 8) ] pack_imports = [cs102] if __name__ == "__main__": diff --git a/examples/example_framework/students/cs102/report2_grade.py b/examples/example_framework/students/cs102/report2_grade.py index 1f9a885..e6318de 100644 --- a/examples/example_framework/students/cs102/report2_grade.py +++ b/examples/example_framework/students/cs102/report2_grade.py @@ -6,14 +6,10 @@ from tabulate import tabulate from datetime import datetime import pyfiglet import unittest - import inspect import os import argparse -import sys import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: @@ -63,53 +59,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass show_tol_err=show_tol_err) - # try: # For registering stats. - # import unitgrade_private - # import irlc.lectures - # import xlwings - # from openpyxl import Workbook - # import pandas as pd - # from collections import defaultdict - # dd = defaultdict(lambda: []) - # error_computed = [] - # for k1, (q, _) in enumerate(report.questions): - # for k2, item in enumerate(q.items): - # dd['question_index'].append(k1) - # dd['item_index'].append(k2) - # dd['question'].append(q.name) - # dd['item'].append(item.name) - # dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol) - # error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed) - # - # qstats = report.wdir + "/" + report.name + ".xlsx" - # - # if os.path.isfile(qstats): - # d_read = pd.read_excel(qstats).to_dict() - # else: - # d_read = dict() - # - # for k in range(1000): - # key = 'run_'+str(k) - # if key in d_read: - # dd[key] = list(d_read['run_0'].values()) - # else: - # dd[key] = error_computed - # break - # - # workbook = Workbook() - # worksheet = workbook.active - # for col, key in enumerate(dd.keys()): - # worksheet.cell(row=1, column=col+1).value = key - # for row, item in enumerate(dd[key]): - # worksheet.cell(row=row+2, column=col+1).value = item - # - # workbook.save(qstats) - # workbook.close() - # - # except ModuleNotFoundError as e: - # s = 234 - # pass - if question is None: print("Provisional evaluation") tabulate(table_data) @@ -161,24 +110,20 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) else: b = "Unitgrade" - print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) s = report.title if hasattr(report, "version") and report.version is not None: s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + print(s, "(use --help for options)" if show_help_flag else "") # print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] - nL = 80 t_start = time.time() score = {} loader = SequentialTestLoader() for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) if question is not None and n+1 != question: continue suite = loader.loadTestsFromTestCase(q) @@ -188,104 +133,28 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa q.possible = 0 q.obtained = 0 q_ = {} # Gather score in this class. - # 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. UTextResult.number = n + UTextResult.nL = report.nL res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) - # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite) - z = 234 - # for j, item in enumerate(q.items): - # if qitem is not None and question is not None and j+1 != qitem: - # continue - # - # if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles. - # # if not item.question.has_called_init_: - # start = time.time() - # - # cc = None - # if show_progress_bar: - # 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] ) - # cc = ActiveProgress(t=total_estimated_time, title=q_title_print) - # from unitgrade import Capturing # DON'T REMOVE THIS LINE - # with eval('Capturing')(unmute=unmute): # Clunky import syntax is required bc. of minify issue. - # try: - # for q2 in q_with_outstanding_init: - # q2.init() - # q2.has_called_init_ = True - # - # # item.question.init() # Initialize the question. Useful for sharing resources. - # except Exception as e: - # if not passall: - # if not silent: - # print(" ") - # print("="*30) - # print(f"When initializing question {q.title} the initialization code threw an error") - # print(e) - # print("The remaining parts of this question will likely fail.") - # print("="*30) - # - # if show_progress_bar: - # cc.terminate() - # sys.stdout.flush() - # print(q_title_print, end="") - # - # q_time =np.round( time.time()-start, 2) - # - # 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 "") - # print("=" * nL) - # q_with_outstanding_init = None - # - # # item.question = q # Set the parent question instance for later reference. - # item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title) - # - # if show_progress_bar: - # cc = ActiveProgress(t=item.estimated_time, title=item_title_print) - # else: - # print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="") - # hidden = issubclass(item.__class__, Hidden) - # # if not hidden: - # # print(ss, end="") - # # sys.stdout.flush() - # start = time.time() - # - # (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent) - # q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title} - # tsecs = np.round(time.time()-start, 2) - # if show_progress_bar: - # cc.terminate() - # sys.stdout.flush() - # print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="") - # - # if not hidden: - # ss = "PASS" if current == possible else "*** FAILED" - # if tsecs >= 0.1: - # ss += " ("+ str(tsecs) + " seconds)" - # print(ss) - - # ws, possible, obtained = upack(q_) possible = res.testsRun 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 = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} q.obtained = obtained q.possible = possible - s1 = f"*** Question q{n+1}" + s1 = f"Question {n+1} total" s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) ws, possible, obtained = upack(score) possible = int( msum(possible) ) @@ -300,15 +169,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa seconds = dt - minutes*60 plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) results = {'total': (obtained, possible), 'details': score} return results, table_data - - from tabulate import tabulate from datetime import datetime import inspect @@ -331,7 +201,8 @@ def gather_imports(imp): # dn = os.path.dirname(f) # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: top_package = os.path.dirname(m.__file__) module_import = True else: @@ -352,7 +223,7 @@ def gather_imports(imp): for file in files: if file.endswith(".py"): fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) zip.write(fpath, v) resources['zipfile'] = zip_buffer.getvalue() @@ -396,14 +267,14 @@ def gather_upload_to_campusnet(report, output_dir=None): 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)) + # print(" ") + # print("="*n) + # print("Final evaluation") + # print(tabulate(table_data)) # also load the source code of missing files... sources = {} - + print("") if not args.autolab: if len(report.individual_imports) > 0: print("By uploading the .token file, you verify the files:") @@ -416,12 +287,15 @@ def gather_upload_to_campusnet(report, output_dir=None): 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) + _, report_relative_location, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import nimp['name'] = m.__name__ sources[k] = nimp # if len([k for k in nimp if k not in sources]) > 0: - print(f"*** {m.__name__}") + print(f" * {m.__name__}") # sources = {**sources, **nimp} results['sources'] = sources @@ -440,9 +314,9 @@ def gather_upload_to_campusnet(report, output_dir=None): if not args.autolab: print(" ") - print("To get credit for your results, please upload the single file: ") + print("To get credit for your results, please upload the single unmodified file: ") print(">", token) - print("To campusnet without any modifications.") + # print("To campusnet without any modifications.") # print("Now time for some autolab fun") @@ -455,8 +329,8 @@ 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"""\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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, too 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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n item_title = item_title.split("\\n")[0]\n\n item_title = test.shortDescription() # Better for printing (get from cache).\n # test.countTestCases()\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n @classmethod\n def question_title(cls):\n return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n # def _callSetUp(self):\n # # Always run before method is called.\n # print("asdf")\n # pass\n # @classmethod\n # def setUpClass(cls):\n # # self._cache_put((self.cache_id(), \'title\'), value)\n # cls.reset()\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 if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 # i = 0\n # for i in itertools.count():\n # # key = k0 + (i,)\n # if i not in self._cache_get( (k0, \'assert\') ):\n # break\n # return i\n # return key\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 # print("Is this needed?")\n # self._ensure_cache_exists()\n # return key in self.__class__._cache2\n\n def wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\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\nimport random\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n\n def test_add(self):\n """ Docstring for this method """\n from cs102.homework1 import add\n self.assertEqualC(add(2,2))\n self.assertEqualC(add(-100, 5))\n\n def test_reverse(self):\n """ Reverse a list """ # Add a title to the test.\n from cs102.homework1 import reverse_list\n self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n """ Second problem """\n @cache\n def my_reversal(self, ls):\n # The \'@cache\' decorator ensures the function is not run on the *students* computer\n # Instead the code is run on the teachers computer and the result is passed on with the\n # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n # implemented reverse_list.\n from cs102.homework1 import reverse_list\n return reverse_list(ls)\n\n def test_reverse_tricky(self):\n ls = [2,4,8]\n self.title = f"Reversing a small list containing {ls=}"\n ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n ls3 = self.my_reversal( tuple([1,2,3]) ) # Also works; the cache respects input arguments.\n self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n title = "CS 101 Report 2"\n questions = [(Week1, 10), (Question2, 8) ] # Include a single question for 10 credits.\n pack_imports = [cs102]' -report1_payload = '8004959a010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c057469746c659486948c19446f63737472696e6720666f722074686973206d6574686f64946803680486948c066173736572749486947d94284b004b044b014aa1ffffff756803680486948c0474696d6594869447000000000000000068038c0c746573745f72657665727365948694680686948c0e526576657273652061206c69737494680368108694680a86947d944b005d94284b034b024b016573680368108694680e86944700000000000000008c0474696d6594470000000000000000758c095175657374696f6e32947d94288c095175657374696f6e32948c13746573745f726576657273655f747269636b799486948c066173736572749486947d944b005d94284b024b044b086573681d681e86948c057469746c659486948c2e526576657273696e67206120736d616c6c206c69737420636f6e7461696e696e67206c733d5b322c20342c20385d94681d681e86948c0474696d65948694470000000000000000681a473f5066000000000075752e' +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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 {n+1} total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 unmodified 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\n\nclass Week1(UTestCase):\n """ The first question for week 1. """\n def test_add(self):\n """ Docstring for this method """\n from cs102.homework1 import add\n self.assertEqualC(add(2,2))\n with self.capture() as out:\n print("hello world 42")\n self.assertEqual(out.numbers[0], 42)\n self.assertEqualC(add(-100, 5))\n\n def test_reverse(self):\n """ Reverse a list """ # Add a title to the test.\n from cs102.homework1 import reverse_list\n self.assertEqualC(reverse_list([1,2,3]))\n\n\nclass Question2(UTestCase):\n """ Second problem """\n @cache\n def my_reversal(self, ls):\n # The \'@cache\' decorator ensures the function is not run on the *students* computer\n # Instead the code is run on the teachers computer and the result is passed on with the\n # other pre-computed results -- i.e. this function will run regardless of how the student happens to have\n # implemented reverse_list.\n from cs102.homework1 import reverse_list\n return reverse_list(ls)\n\n def test_reverse_tricky(self):\n ls = [2,4,8]\n self.title = f"Reversing a small list containing {ls=}" # Titles can be set like this at any point in the function body.\n ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result.\n ls3 = self.my_reversal( tuple([1,2,3]) ) # Also works; the cache respects input arguments.\n self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code.\n\n\nimport cs102\nclass Report2(Report):\n title = "CS 101 Report 2"\n questions = [(Week1, 10), (Question2, 8) ]\n pack_imports = [cs102]' +report1_payload = '80049510010000000000007d94288c055765656b31947d94288c055765656b31948c08746573745f6164649486948c066173736572749486947d94284b004b044b014aa1ffffff7568038c0c746573745f72657665727365948694680686947d944b005d94284b034b024b0165738c0474696d6594473fe6a7e700000000758c095175657374696f6e32947d94288c095175657374696f6e32948c13746573745f726576657273655f747269636b799486948c057469746c659486948c2e526576657273696e67206120736d616c6c206c69737420636f6e7461696e696e67206c733d5b322c20342c20385d946811681286948c066173736572749486947d944b005d94284b024b044b086573680e473facac280000000075752e' name="Report2" report = source_instantiate(name, report1_source, report1_payload) diff --git a/examples/example_framework/students/cs102/unitgrade/Question2.pkl b/examples/example_framework/students/cs102/unitgrade/Question2.pkl index 634a7fbbe4bad27f24b2d894ef3c0b37c4f5dd94..b950c49faa2b91b7675ee266d63a13fe3652e3cc 100644 GIT binary patch delta 119 zcmZo*e!;}jz%n&<B8&dS;0<CN8JrnBnvGMu8NHdjncJr%`qfU!5ST2<sO$rm;_zni zW`ap|aqR0^R{wxw2Ul_1l%)14ZBt^WXaM!Hcypv?FlI2dP3d7vEG|whDgjE>PVr{Q JP$@3e0|4%EC_MlG delta 140 zcmaFC)WFQrz%sRTB8$E(TVio>YEj9Qwkfq!ycuGrXm~Suvv_l)7H2SKFikYrEXJF` zmm#RxIK`V0D8t-7CDE^TN`~mfpUN(984jQhCYVeY$G)Cr^$$39a22;rNoofgoWTw< Ut8GdTOG##KDp0_k0SZd>0H5?NtpET3 diff --git a/examples/example_framework/students/cs102/unitgrade/Week1.pkl b/examples/example_framework/students/cs102/unitgrade/Week1.pkl index 7912698f036128bb2a8b616c2a66c58ac9e774e5..6b4ee502e9c67e2ff3aba1b11c4911bb88195e34 100644 GIT binary patch delta 35 rcmcc4n99<?GBs)<i^9YJ>50vvg0)k;8Dghscr$x5c{8RKm+Aok$2|&n delta 146 zcmYej&dAchGWE<v76nb#lFX8v)G2LKdL&))lZ#7=GV{_E((;QGN-{Ew6>?KcGV)WV zWH4qhO;nT<%U}YkV=2kZ1!-_+fP#s(;`Tj!L8)b_Ma8KKi3&NH#U&sud~H)QINGMv aPVr`louc8*?9JrOm|6^#;6XB=R1W|>sV&3+ diff --git a/examples/example_jupyter/instructor/cs105/.coverage b/examples/example_jupyter/instructor/cs105/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..572452543e2092b38f99ec9afedd779fdc155f29 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/instructor/cs105/Report1Jupyter_handin_18_of_18.token b/examples/example_jupyter/instructor/cs105/Report1Jupyter_handin_18_of_18.token new file mode 100644 index 0000000000000000000000000000000000000000..cfb09b6ef919a3d4c721651cb236201490461fc4 GIT binary patch literal 58736 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 literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/instructor/cs105/__pycache__/homework1.cpython-38.pyc b/examples/example_jupyter/instructor/cs105/__pycache__/homework1.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..405a1c978c8bf2114f9b470bfd5312fb8f02188f GIT binary patch literal 187 zcmWIL<>g`kf`1XZiQ+)|F^Gc<7=auIATDMB5-AM944RC7D;bJF!U*D5g0odjXmM&$ zaZE{RMrw>pesXDUYF<fkOle+bNqSLYN@{#TQD#|UNoq`LMPhD2PHHiX5ua6BP+5{% z6qA`(TvAk;T#{cDlU!_QU>cK=pPO2qUzBaAS5SG2!zMRBr8Fni4rKFZAZ7pnaIQ1^ literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/instructor/cs105/__pycache__/report5.cpython-38.pyc b/examples/example_jupyter/instructor/cs105/__pycache__/report5.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c80ebb8aac0fa657aacc6cb4b52f65783b2e1ec8 GIT binary patch literal 2312 zcmaJ?OK&4Z5bo}Ic*d`oWfNXnFmDYKR$?g^giwS*NE|HfhG@~qVl;Ml5-0QUcF$yq zm0NO0;s6&8$uU>{1pi^Kobng;0#!ZnW3kz>rrcH4(_Q^lebonzMwP(x*YDfmpB5p1 zqq6*%pzOh`egnY?rxA&%Ph;jYiaLvo*z`?3HX_Tnz-C0YZ^OR{b1Qx&c6<l?mhXN? zxXmk9gjWQ+H2f;)4tGI!wO#|g%4?w4L=F6P(CfSbdP7jq8=}dZr%e7TOwqU`mlRet z_KCM~2eA>474{P`No7{J2R{gv^+F{)TD&37!YB_j5lHO~RF-p*WPSLc+6yOHF2~7H zw{Yj<7q#a?oOV0o$$2sy;o8g39FY!K1@S2-J`)C~oc%$$!Obi4lKLjMxP3(cj|z9- z-{uq|bqnh$EWOjerG+g8Y@Lkg(&9LF;qAezJ_3=EOY+M@N-}!Fu8qvRp%)~uo|0Yi zm^>jrLI(E9E$w@@gyfOJ8OI1fGAx{anu%eWo)%`5hP*Hj$B`&lGK8ItQKW8(d>y`c z@l)@M!GRJ|4YK@L48BQ6c?{sFL7t4WBN=iLOyu|s;2Vga!*~)2H5&vc`Q$tkaxhL* zCi792%E3tOZ0`<Apm#fy^P;Lmb};Ehp;CA-h3FCH&}mBp(OI}xfUsev24Wiq7v%aL z1OS@eFwSnE0AI3#g&Sx^P&~7L$Kbm=-0;o;;h9AZq7X9s_E{cA5^b^tqOjug!#n|C zOeLa2jY}yWqSwiwmV%J;CTL~+bc;@#W&AVeO7ys|jvg18#d!i{GLMk;r;TF~MQQta zDkI+RdZv5}CND*1oP|=pgEmaoQc!ihuM}sHIq?8|3J*)@^ubbC_inFJI6;tvu?T{q z8U%65^9aZFAV4JUJ+%=gDHhpr%H<|*_Xx#%C>Fcx5gtiLi5Myr4rR1uI;OV%KV`Zw zbw(LZeL%xEh4mlm*rHOB2WaGI+Ej{a|LYm0`w|Oj1@Ad+J^`<$&DM-I7vy9uabyjq z-cv``VJsUcFwcdP%=lvj$xyzAjs*%D0K^FMwW#UMMU<~9Y-G7E4MYxtBPY-s%3xUG z27yjQ5J)`9y6o$!eHVAuNoUi>(!R;ib7dR9d>_RJC_YA^SNjl07<l1KWI6#doa@z= z&bG)fHlQtd6=LI16MmRwMkRVyvW&s(!K>Z`@luw7m(T)DZUQf@!hM_rqsD2{l@*w| zlF2#maUEn1Kt!(@R>=*!!KRP=Hu@4{lQ_wPyJCsjz4AzJqk9c`bY0^K=A4-a)S1z_ z?^$a>)Rb7%!2&A})<djyF(6hFEh`uqVBEuLn2lwOgvr?<11=bc?1=fmfg`N`60v4^ zukCzaHr(k(Z@&$Fwq2UryLWhpYb5$B$uF#IoJFE+Dj1XRKD)<X74C5w3+O(lJAQo< zj!uK}{!nYn;WPO7Q+O5bhDWri{f}u#v}1>f!*LP>-iE(9zdO2}#ZY2>NCd!tw6HEF z1kekiAXQgl>y)3P(4@qhO=3~V4HUX>WBN40diOF_Ytm9UUzXkOE99KQf@#q@b>P=x OCT%ra^_JDDIL5y&>)6Bq literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/instructor/cs105/__pycache__/week2.cpython-38.pyc b/examples/example_jupyter/instructor/cs105/__pycache__/week2.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d81c0675c7056e7e11ab8b96d289c407997be566 GIT binary patch literal 466 zcmYjM%}T>S5Z>9%Ut3!c3cYv;cnV6voTUiTYcJv@m(XTbET&D_Cf1hT>I?W7zKjpB zS5NUNoQV`WFf-rm%zod@W4GHDWaE$P=@;d1E>2BRaYw!$Q9zLhgalBcdKZb*5*>68 z61{;)ly)>600d>l8U4h?09!QaVf#Tuo!$9mIDXS6o3C{k598rYd)Ix|d0vE@!sHmn znIxH`F=oFfUk4QSVh?>mOfdNrk+d+iFf%}%CfLqK>z8;qUu~B&>n^rW)$)S>Hl$!& z!ZqWXud4a*`iDZs3`b+k?MAdmpd?uYI#Lc;T1|{cuQks_f1D9m!2dOjLoJqHRQVOb zqx>1+N#t8UO|i(*)T-C1X_m5HQI)HzwC>8xm!*ZB;o%d?=EjCMxLp)j<$B~ZH81G& N9SBqp0ti|T`~q1`QnUa7 literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/instructor/cs105/deploy.py b/examples/example_jupyter/instructor/cs105/deploy.py new file mode 100644 index 0000000..8acafa9 --- /dev/null +++ b/examples/example_jupyter/instructor/cs105/deploy.py @@ -0,0 +1,15 @@ +from report5 import Report1Jupyter +from unitgrade_private2.hidden_create_files import setup_grade_file_report +from snipper import snip_dir + +if __name__ == "__main__": + setup_grade_file_report(Report1Jupyter, minify=False, obfuscate=False, execute=False) + + # from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet + # gather_upload_to_campusnet((Report1Flat())) + + # Deploy the files using snipper: https://gitlab.compute.dtu.dk/tuhe/snipper + snip_dir.snip_dir(source_dir="", dest_dir="../../students/cs105", clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py']) + + + diff --git a/examples/example_jupyter/instructor/cs105/homework1.py b/examples/example_jupyter/instructor/cs105/homework1.py new file mode 100644 index 0000000..b01dcab --- /dev/null +++ b/examples/example_jupyter/instructor/cs105/homework1.py @@ -0,0 +1 @@ +# This file is blank. diff --git a/examples/example_jupyter/instructor/cs105/report5.py b/examples/example_jupyter/instructor/cs105/report5.py new file mode 100644 index 0000000..38e9fbd --- /dev/null +++ b/examples/example_jupyter/instructor/cs105/report5.py @@ -0,0 +1,49 @@ +from src.unitgrade2.unitgrade2 import Report, UTestCase +from src.unitgrade2 import evaluate_report_student +import homework1 +import importnb +from src.unitgrade2.unitgrade2 import Capturing2 + +file = 'week2.ipynb' +class Week1(UTestCase): + @classmethod + def setUpClass(cls) -> None: + with Capturing2(): + cls.nb = importnb.Notebook.load(file) + + def test_add(self): + self.assertEqual(Week1.nb.myfun(2,2), 4) + self.assertEqual(Week1.nb.myfun(2,4), 8) + + def test_reverse(self): + self.assertEqual(Week1.nb.var, "hello world 2") + +# Nicer: Automatically load the notebook. +class NBTestCase(UTestCase): + notebook = None + _nb = None + @classmethod + def setUpClass(cls) -> None: + with Capturing2(): + cls._nb = importnb.Notebook.load(cls.notebook) + + @property + def nb(self): + return self.__class__._nb + +class Question2(NBTestCase): + notebook = "week2.ipynb" + def test_add(self): + self.assertEqualC(self.nb.myfun(2,8)) + +class Report1Jupyter(Report): + title = "CS 105 Report 5" + questions = [(Week1, 10), + (Question2, 8) + ] # Include a single question for 10 credits. + pack_imports = [homework1] + +if __name__ == "__main__": + # Uncomment to simply run everything as a unittest: + # unittest.main(verbosity=2) + evaluate_report_student(Report1Jupyter()) diff --git a/examples/example_jupyter/instructor/cs105/report5_grade.py b/examples/example_jupyter/instructor/cs105/report5_grade.py new file mode 100644 index 0000000..3e65178 --- /dev/null +++ b/examples/example_jupyter/instructor/cs105/report5_grade.py @@ -0,0 +1,336 @@ + +import numpy as np +from tabulate import tabulate +from datetime import datetime +import pyfiglet +import unittest +import inspect +import os +import argparse +import time + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print(s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + UTextResult.q_title_print = q_title_print # Hacky + UTextResult.show_progress_bar = show_progress_bar # Hacky. + UTextResult.number = n + UTextResult.nL = report.nL + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + obtained = len(res.successes) + + assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun + + obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f" * q{n+1}) Total" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + print("") + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.join(output_dir, token) + with open(token, 'wb') as f: + pickle.dump(results, f) + + if not args.autolab: + print(" ") + print("To get credit for your results, please upload the single unmodified 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 unmodified 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\nimport homework1\nimport importnb\n\nfile = \'week2.ipynb\'\nclass Week1(UTestCase):\n @classmethod\n def setUpClass(cls) -> None:\n with Capturing2():\n cls.nb = importnb.Notebook.load(file)\n\n def test_add(self):\n self.assertEqual(Week1.nb.myfun(2,2), 4)\n self.assertEqual(Week1.nb.myfun(2,4), 8)\n\n def test_reverse(self):\n self.assertEqual(Week1.nb.var, "hello world 2")\n\n# Nicer: Automatically load the notebook.\nclass NBTestCase(UTestCase):\n notebook = None\n _nb = None\n @classmethod\n def setUpClass(cls) -> None:\n with Capturing2():\n cls._nb = importnb.Notebook.load(cls.notebook)\n\n @property\n def nb(self):\n return self.__class__._nb\n\nclass Question2(NBTestCase):\n notebook = "week2.ipynb"\n def test_add(self):\n self.assertEqualC(self.nb.myfun(2,8))\n\nclass Report1Jupyter(Report):\n title = "CS 105 Report 5"\n questions = [(Week1, 10),\n (Question2, 8)\n ] # Include a single question for 10 credits.\n pack_imports = [homework1]' +report1_payload = '8004955c000000000000007d94288c055765656b31947d948c0474696d6594473fed915600000000738c095175657374696f6e32947d942868048c08746573745f6164649486948c066173736572749486947d944b004b10736803473fcc28f40000000075752e' +name="Report1Jupyter" + +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 diff --git a/examples/example_jupyter/instructor/cs105/unitgrade/Question2.pkl b/examples/example_jupyter/instructor/cs105/unitgrade/Question2.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b08cef102cf660c34ffd24ee8633b7254630acac GIT binary patch literal 58 zcmZo*nX1nK0ku<lI0H*li%T-|^NgnSaFhU<@rfxZQ`)BVuq753rxuj}nYB~A8N3CG Gi}e5+GZZuc literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/instructor/cs105/unitgrade/Week1.pkl b/examples/example_jupyter/instructor/cs105/unitgrade/Week1.pkl new file mode 100644 index 0000000..9b6ff7a --- /dev/null +++ b/examples/example_jupyter/instructor/cs105/unitgrade/Week1.pkl @@ -0,0 +1 @@ +€N. \ No newline at end of file diff --git a/examples/02471/instructor/02471/week02/week2.ipynb b/examples/example_jupyter/instructor/cs105/week2.ipynb similarity index 100% rename from examples/02471/instructor/02471/week02/week2.ipynb rename to examples/example_jupyter/instructor/cs105/week2.ipynb diff --git a/examples/example_jupyter/students/cs105/.coverage b/examples/example_jupyter/students/cs105/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..572452543e2092b38f99ec9afedd779fdc155f29 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/students/cs105/__pycache__/homework1.cpython-38.pyc b/examples/example_jupyter/students/cs105/__pycache__/homework1.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..405a1c978c8bf2114f9b470bfd5312fb8f02188f GIT binary patch literal 187 zcmWIL<>g`kf`1XZiQ+)|F^Gc<7=auIATDMB5-AM944RC7D;bJF!U*D5g0odjXmM&$ zaZE{RMrw>pesXDUYF<fkOle+bNqSLYN@{#TQD#|UNoq`LMPhD2PHHiX5ua6BP+5{% z6qA`(TvAk;T#{cDlU!_QU>cK=pPO2qUzBaAS5SG2!zMRBr8Fni4rKFZAZ7pnaIQ1^ literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/students/cs105/__pycache__/report5.cpython-38.pyc b/examples/example_jupyter/students/cs105/__pycache__/report5.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c80ebb8aac0fa657aacc6cb4b52f65783b2e1ec8 GIT binary patch literal 2312 zcmaJ?OK&4Z5bo}Ic*d`oWfNXnFmDYKR$?g^giwS*NE|HfhG@~qVl;Ml5-0QUcF$yq zm0NO0;s6&8$uU>{1pi^Kobng;0#!ZnW3kz>rrcH4(_Q^lebonzMwP(x*YDfmpB5p1 zqq6*%pzOh`egnY?rxA&%Ph;jYiaLvo*z`?3HX_Tnz-C0YZ^OR{b1Qx&c6<l?mhXN? zxXmk9gjWQ+H2f;)4tGI!wO#|g%4?w4L=F6P(CfSbdP7jq8=}dZr%e7TOwqU`mlRet z_KCM~2eA>474{P`No7{J2R{gv^+F{)TD&37!YB_j5lHO~RF-p*WPSLc+6yOHF2~7H zw{Yj<7q#a?oOV0o$$2sy;o8g39FY!K1@S2-J`)C~oc%$$!Obi4lKLjMxP3(cj|z9- z-{uq|bqnh$EWOjerG+g8Y@Lkg(&9LF;qAezJ_3=EOY+M@N-}!Fu8qvRp%)~uo|0Yi zm^>jrLI(E9E$w@@gyfOJ8OI1fGAx{anu%eWo)%`5hP*Hj$B`&lGK8ItQKW8(d>y`c z@l)@M!GRJ|4YK@L48BQ6c?{sFL7t4WBN=iLOyu|s;2Vga!*~)2H5&vc`Q$tkaxhL* zCi792%E3tOZ0`<Apm#fy^P;Lmb};Ehp;CA-h3FCH&}mBp(OI}xfUsev24Wiq7v%aL z1OS@eFwSnE0AI3#g&Sx^P&~7L$Kbm=-0;o;;h9AZq7X9s_E{cA5^b^tqOjug!#n|C zOeLa2jY}yWqSwiwmV%J;CTL~+bc;@#W&AVeO7ys|jvg18#d!i{GLMk;r;TF~MQQta zDkI+RdZv5}CND*1oP|=pgEmaoQc!ihuM}sHIq?8|3J*)@^ubbC_inFJI6;tvu?T{q z8U%65^9aZFAV4JUJ+%=gDHhpr%H<|*_Xx#%C>Fcx5gtiLi5Myr4rR1uI;OV%KV`Zw zbw(LZeL%xEh4mlm*rHOB2WaGI+Ej{a|LYm0`w|Oj1@Ad+J^`<$&DM-I7vy9uabyjq z-cv``VJsUcFwcdP%=lvj$xyzAjs*%D0K^FMwW#UMMU<~9Y-G7E4MYxtBPY-s%3xUG z27yjQ5J)`9y6o$!eHVAuNoUi>(!R;ib7dR9d>_RJC_YA^SNjl07<l1KWI6#doa@z= z&bG)fHlQtd6=LI16MmRwMkRVyvW&s(!K>Z`@luw7m(T)DZUQf@!hM_rqsD2{l@*w| zlF2#maUEn1Kt!(@R>=*!!KRP=Hu@4{lQ_wPyJCsjz4AzJqk9c`bY0^K=A4-a)S1z_ z?^$a>)Rb7%!2&A})<djyF(6hFEh`uqVBEuLn2lwOgvr?<11=bc?1=fmfg`N`60v4^ zukCzaHr(k(Z@&$Fwq2UryLWhpYb5$B$uF#IoJFE+Dj1XRKD)<X74C5w3+O(lJAQo< zj!uK}{!nYn;WPO7Q+O5bhDWri{f}u#v}1>f!*LP>-iE(9zdO2}#ZY2>NCd!tw6HEF z1kekiAXQgl>y)3P(4@qhO=3~V4HUX>WBN40diOF_Ytm9UUzXkOE99KQf@#q@b>P=x OCT%ra^_JDDIL5y&>)6Bq literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/students/cs105/__pycache__/week2.cpython-38.pyc b/examples/example_jupyter/students/cs105/__pycache__/week2.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d81c0675c7056e7e11ab8b96d289c407997be566 GIT binary patch literal 466 zcmYjM%}T>S5Z>9%Ut3!c3cYv;cnV6voTUiTYcJv@m(XTbET&D_Cf1hT>I?W7zKjpB zS5NUNoQV`WFf-rm%zod@W4GHDWaE$P=@;d1E>2BRaYw!$Q9zLhgalBcdKZb*5*>68 z61{;)ly)>600d>l8U4h?09!QaVf#Tuo!$9mIDXS6o3C{k598rYd)Ix|d0vE@!sHmn znIxH`F=oFfUk4QSVh?>mOfdNrk+d+iFf%}%CfLqK>z8;qUu~B&>n^rW)$)S>Hl$!& z!ZqWXud4a*`iDZs3`b+k?MAdmpd?uYI#Lc;T1|{cuQks_f1D9m!2dOjLoJqHRQVOb zqx>1+N#t8UO|i(*)T-C1X_m5HQI)HzwC>8xm!*ZB;o%d?=EjCMxLp)j<$B~ZH81G& N9SBqp0ti|T`~q1`QnUa7 literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/students/cs105/homework1.py b/examples/example_jupyter/students/cs105/homework1.py new file mode 100644 index 0000000..80562c2 --- /dev/null +++ b/examples/example_jupyter/students/cs105/homework1.py @@ -0,0 +1,4 @@ +""" +Example student code. This file is automatically generated from the files in the instructor-directory +""" +# This file is blank. diff --git a/examples/example_jupyter/students/cs105/report5.py b/examples/example_jupyter/students/cs105/report5.py new file mode 100644 index 0000000..e91cba1 --- /dev/null +++ b/examples/example_jupyter/students/cs105/report5.py @@ -0,0 +1,52 @@ +""" +Example student code. This file is automatically generated from the files in the instructor-directory +""" +from src.unitgrade2.unitgrade2 import Report, UTestCase +from src.unitgrade2 import evaluate_report_student +import homework1 +import importnb +from src.unitgrade2.unitgrade2 import Capturing2 + +file = 'week2.ipynb' +class Week1(UTestCase): + @classmethod + def setUpClass(cls) -> None: + with Capturing2(): + cls.nb = importnb.Notebook.load(file) + + def test_add(self): + self.assertEqual(Week1.nb.myfun(2,2), 4) + self.assertEqual(Week1.nb.myfun(2,4), 8) + + def test_reverse(self): + self.assertEqual(Week1.nb.var, "hello world 2") + +# Nicer: Automatically load the notebook. +class NBTestCase(UTestCase): + notebook = None + _nb = None + @classmethod + def setUpClass(cls) -> None: + with Capturing2(): + cls._nb = importnb.Notebook.load(cls.notebook) + + @property + def nb(self): + return self.__class__._nb + +class Question2(NBTestCase): + notebook = "week2.ipynb" + def test_add(self): + self.assertEqualC(self.nb.myfun(2,8)) + +class Report1Jupyter(Report): + title = "CS 105 Report 5" + questions = [(Week1, 10), + (Question2, 8) + ] # Include a single question for 10 credits. + pack_imports = [homework1] + +if __name__ == "__main__": + # Uncomment to simply run everything as a unittest: + # unittest.main(verbosity=2) + evaluate_report_student(Report1Jupyter()) diff --git a/examples/example_jupyter/students/cs105/report5_grade.py b/examples/example_jupyter/students/cs105/report5_grade.py new file mode 100644 index 0000000..b059908 --- /dev/null +++ b/examples/example_jupyter/students/cs105/report5_grade.py @@ -0,0 +1,338 @@ +""" +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 +import pyfiglet +import unittest +import inspect +import os +import argparse +import time + +parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: +To run all tests in a report: + +> python assignment1_dp.py + +To run only question 2 or question 2.1 + +> python assignment1_dp.py -q 2 +> python assignment1_dp.py -q 2.1 + +Note this scripts does not grade your report. To grade your report, use: + +> 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('-q', nargs='?', type=str, default=None, help='Only evaluate this question (e.g.: -q 2)') +parser.add_argument('--showexpected', action="store_true", help='Show the expected/desired result') +parser.add_argument('--showcomputed', action="store_true", help='Show the answer your code computes') +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: + question = args.q + if "." in question: + question, qitem = [int(v) for v in question.split(".")] + else: + question = int(question) + + if hasattr(report, "computed_answer_file") and not os.path.isfile(report.computed_answers_file) and not ignore_missing_file: + raise Exception("> Error: The pre-computed answer file", os.path.abspath(report.computed_answers_file), "does not exist. Check your package installation") + + if unmute is None: + unmute = args.unmute + if passall is None: + passall = args.passall + + 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, + show_tol_err=show_tol_err) + + + if question is None: + print("Provisional evaluation") + tabulate(table_data) + table = table_data + print(tabulate(table)) + print(" ") + + fr = inspect.getouterframes(inspect.currentframe())[1].filename + gfile = os.path.basename(fr)[:-3] + "_grade.py" + if os.path.exists(gfile): + print("Note your results have not yet been registered. \nTo register your results, please run the file:") + print(">>>", gfile) + print("In the same manner as you ran this file.") + + + return results + + +def upack(q): + # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]) + h =[(i['w'], i['possible'], i['obtained']) for i in q.values()] + h = np.asarray(h) + return h[:,0], h[:,1], h[:,2], + +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()) + ls = [] + for C in testCaseClass.mro(): + if issubclass(C, unittest.TestCase): + ls = list(C.__dict__.keys()) + ls + testcase_methods = ls + 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, + big_header=True): + + now = datetime.now() + if big_header: + ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom") + b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) + else: + b = "Unitgrade" + dt_string = now.strftime("%d/%m/%Y %H:%M:%S") + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) + s = report.title + if hasattr(report, "version") and report.version is not None: + s += " version " + report.version + print(s, "(use --help for options)" if show_help_flag else "") + # print(f"Loaded answers from: ", report.computed_answers_file, "\n") + table_data = [] + t_start = time.time() + score = {} + loader = SequentialTestLoader() + + for n, (q, w) in enumerate(report.questions): + if question is not None and n+1 != question: + continue + suite = loader.loadTestsFromTestCase(q) + qtitle = q.question_title() if hasattr(q, 'question_title') else q.__qualname__ + q_title_print = "Question %i: %s"%(n+1, qtitle) + print(q_title_print, end="") + q.possible = 0 + q.obtained = 0 + q_ = {} # Gather score in this class. + UTextResult.q_title_print = q_title_print # Hacky + UTextResult.show_progress_bar = show_progress_bar # Hacky. + UTextResult.number = n + UTextResult.nL = report.nL + + res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) + + possible = res.testsRun + obtained = len(res.successes) + + assert len(res.successes) + len(res.errors) + len(res.failures) == res.testsRun + + obtained = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 + score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} + q.obtained = obtained + q.possible = possible + + s1 = f" * q{n+1}) Total" + s2 = f" {q.obtained}/{w}" + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) + print(" ") + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) + + ws, possible, obtained = upack(score) + possible = int( msum(possible) ) + obtained = int( msum(obtained) ) # Cast to python int + report.possible = possible + report.obtained = obtained + now = datetime.now() + dt_string = now.strftime("%H:%M:%S") + + dt = int(time.time()-t_start) + minutes = dt//60 + seconds = dt - minutes*60 + plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") + + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") + + table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) + results = {'total': (obtained, possible), 'details': score} + return results, table_data + + +from tabulate import tabulate +from datetime import datetime +import inspect +import json +import os +import bz2 +import pickle +import os + +def bzwrite(json_str, token): # to get around obfuscation issues + with getattr(bz2, 'open')(token, "wt") as f: + f.write(json_str) + +def gather_imports(imp): + resources = {} + m = imp + # for m in pack_imports: + # print(f"*** {m.__name__}") + f = m.__file__ + # dn = os.path.dirname(f) + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = str(__import__(m.__name__.split('.')[0]).__path__) + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: + top_package = os.path.dirname(m.__file__) + module_import = True + else: + top_package = __import__(m.__name__.split('.')[0]).__path__._path[0] + module_import = False + + # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) + # top_package = os.path.dirname(top_package) + import zipfile + # import strea + # zipfile.ZipFile + import io + # file_like_object = io.BytesIO(my_zip_data) + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip: + # zip.write() + for root, dirs, files in os.walk(top_package): + for file in files: + if file.endswith(".py"): + fpath = os.path.join(root, file) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) + zip.write(fpath, v) + + resources['zipfile'] = zip_buffer.getvalue() + resources['top_package'] = top_package + resources['module_import'] = module_import + return resources, top_package + + if f.endswith("__init__.py"): + for root, dirs, files in os.walk(os.path.dirname(f)): + for file in files: + if file.endswith(".py"): + # print(file) + # print() + v = os.path.relpath(os.path.join(root, file), top_package) + with open(os.path.join(root, file), 'r') as ff: + resources[v] = ff.read() + else: + v = os.path.relpath(f, top_package) + with open(f, 'r') as ff: + 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 = 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... + + sources = {} + print("") + 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, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) + nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import + 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() + + payload_out_base = report.__class__.__name__ + "_handin" + + obtain, possible = results['total'] + vstring = "_v"+report.version if report.version is not None else "" + + token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) + token = os.path.join(output_dir, token) + with open(token, 'wb') as f: + pickle.dump(results, f) + + if not args.autolab: + print(" ") + print("To get credit for your results, please upload the single unmodified 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()) + pl = pickle.loads(bytes.fromhex(payload)) + report = eval(name)(payload=pl, strict=True) + # report.set_payload(pl) + return report + + + +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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 # print(self.questions)\n if hasattr(q, \'_save_cache\'):\n q()._save_cache()\n print("q is", q())\n q()._cache_put(\'time\', q.time) # = q.time\n report_cache[q.__qualname__] = q._cache2\n else:\n report_cache[q.__qualname__] = {\'no cache see _setup_answers in unitgrade2.py\': True}\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n # self._stdout = sys.stdout\n # sys._stdout = io.StringIO()\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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" * q{n+1}) Total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 unmodified 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\nimport homework1\nimport importnb\n\nfile = \'week2.ipynb\'\nclass Week1(UTestCase):\n @classmethod\n def setUpClass(cls) -> None:\n with Capturing2():\n cls.nb = importnb.Notebook.load(file)\n\n def test_add(self):\n self.assertEqual(Week1.nb.myfun(2,2), 4)\n self.assertEqual(Week1.nb.myfun(2,4), 8)\n\n def test_reverse(self):\n self.assertEqual(Week1.nb.var, "hello world 2")\n\n# Nicer: Automatically load the notebook.\nclass NBTestCase(UTestCase):\n notebook = None\n _nb = None\n @classmethod\n def setUpClass(cls) -> None:\n with Capturing2():\n cls._nb = importnb.Notebook.load(cls.notebook)\n\n @property\n def nb(self):\n return self.__class__._nb\n\nclass Question2(NBTestCase):\n notebook = "week2.ipynb"\n def test_add(self):\n self.assertEqualC(self.nb.myfun(2,8))\n\nclass Report1Jupyter(Report):\n title = "CS 105 Report 5"\n questions = [(Week1, 10),\n (Question2, 8)\n ] # Include a single question for 10 credits.\n pack_imports = [homework1]' +report1_payload = '8004955c000000000000007d94288c055765656b31947d948c0474696d6594473fed915600000000738c095175657374696f6e32947d942868048c08746573745f6164649486948c066173736572749486947d944b004b10736803473fcc28f40000000075752e' +name="Report1Jupyter" + +report = source_instantiate(name, report1_source, report1_payload) +output_dir = os.path.dirname(__file__) +gather_upload_to_campusnet(report, output_dir) diff --git a/examples/example_jupyter/students/cs105/unitgrade/Question2.pkl b/examples/example_jupyter/students/cs105/unitgrade/Question2.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b08cef102cf660c34ffd24ee8633b7254630acac GIT binary patch literal 58 zcmZo*nX1nK0ku<lI0H*li%T-|^NgnSaFhU<@rfxZQ`)BVuq753rxuj}nYB~A8N3CG Gi}e5+GZZuc literal 0 HcmV?d00001 diff --git a/examples/example_jupyter/students/cs105/unitgrade/Week1.pkl b/examples/example_jupyter/students/cs105/unitgrade/Week1.pkl new file mode 100644 index 0000000..9b6ff7a --- /dev/null +++ b/examples/example_jupyter/students/cs105/unitgrade/Week1.pkl @@ -0,0 +1 @@ +€N. \ No newline at end of file diff --git a/examples/example_jupyter/students/cs105/week2.ipynb b/examples/example_jupyter/students/cs105/week2.ipynb new file mode 100644 index 0000000..6bc411a --- /dev/null +++ b/examples/example_jupyter/students/cs105/week2.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exercise 2.2.1" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello world\n", + "6\n" + ] + } + ], + "source": [ + "var = \"hello world 2\"\n", + "def myfun(a,b):\n", + " return a*b\n", + "\n", + "output = myfun(2,3) + 10\n", + "print(var)\n", + "print(output)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "z = 234 \n", + "def mymul(d):\n", + " return myfun(d,2)+1" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} 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 5d34d0fd91b71eecb9ff050591857b09f382421b..1884f587f74133902500c7b2e2ea1095e067afd8 100644 GIT binary patch delta 8583 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 literal 77365 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 Dq5~IP diff --git a/examples/example_simplest/instructor/cs101/__pycache__/deploy.cpython-38.pyc b/examples/example_simplest/instructor/cs101/__pycache__/deploy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e14e2ac2aed2a082d17689ac69828e53aeba409 GIT binary patch literal 911 zcmZWnO>fjN5Ve!+Ci~TvqG~T3I3X>EEZYi!5JE^)9612JSOF`IXS?nuaj+fQjS&B! zC$1dYBY!DZPW*+Qm}JXGTRR!O@pwGr_k1uK^$CtIcf<IbOUTd0`G5H6yu{y}qY*^V zlw_1q+F&PjGMBk6c2h6wuuhA;w9C2}_fntv;2#I)J?LLhHh`h%EFJwFT%e+RHT^Tp zg3fUV1|k>}w7oGAeK8P2F*<U^#+iqwZtjy0_t*%!=Nq_VtKD}8?PyFc97;ZY72Z2C z+5UxHk_oN-w@@f;X9&jzY+3Ll9Sh)dnF7~s3`2p*Wx<8iZ|Z>aES5RvuO}YsW-^!a z73-<Pd1;c^0^Z{kk`jS;SjnR5O~Y`SnAxLQwbMZSj4cJ^)~uJ`*8|RrRT3u);Jor5 zhE^>huX+NCRIS2dg?0Kyl{$f@0~QD{wnaNh3USUGU*@ru3NdcL4w4j0Ox)_}x*}RQ zjk5x;G}mzkCrU4;GB;M2iB-C7WOuqy>&OgiR*!FG3Y9ED-%Q)Pp7!Kc+B}Wz&7@ho zc&*;(+BuhWI@x5SmbY-N!c04;7<*9W(yk>cwET>W?}iI0kWijzK(|%SXndlC`HgG^ zXzev#*DOja)7nv{_KMgpY9Hy!k&i~yR&dUo6VZ6hD(5ww*IuHE)eoXK@rc%3eth*T zIxwJ3WXlCa`zk53M$hP0Yta?!<`3XTHYyk!{bN6BZyEiSyAGabN|Y(QpjgsuqZ!g> PZqcC=&~5j=8@Rs#9|je( literal 0 HcmV?d00001 diff --git a/examples/example_simplest/instructor/cs101/__pycache__/report1_grade.cpython-38.pyc b/examples/example_simplest/instructor/cs101/__pycache__/report1_grade.cpython-38.pyc index 3b0930bdcddcda845eef8dfac07d6ab732a2473f..aed06f7cf667b51f32903bf52b3c17138bda2777 100644 GIT binary patch delta 2862 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 Mx6{1|g8yFkKk?5%NdN!< delta 7319 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 V_b+p#1V<c(=d1SZp>_Y%{{I@N9*O_} diff --git a/examples/example_simplest/instructor/cs101/deploy.py b/examples/example_simplest/instructor/cs101/deploy.py index 3e9682d..b51f79c 100644 --- a/examples/example_simplest/instructor/cs101/deploy.py +++ b/examples/example_simplest/instructor/cs101/deploy.py @@ -1,16 +1,19 @@ -from report1 import Report1 +from cs101.report1 import Report1 from unitgrade_private2.hidden_create_files import setup_grade_file_report from snipper import snip_dir -import shutil +import shutil, os +wd = os.path.dirname(__file__) if __name__ == "__main__": - setup_grade_file_report(Report1, minify=False, obfuscate=False, execute=False) + setup_grade_file_report(Report1, minify=False, obfuscate=False, execute=False, bzip=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']) + snip_dir.snip_dir(source_dir=wd+"/../cs101", dest_dir=wd+"/../../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) + # For my own sake, copy the homework file to the other examples. + for f in ['../../../example_framework/instructor/cs102/homework1.py', + '../../../example_docker/instructor/cs103/homework1.py', + '../../../example_flat/instructor/cs101flat/homework1.py']: + shutil.copy(wd+'/homework1.py', wd+"/"+f) diff --git a/examples/example_simplest/instructor/cs101/report1.py b/examples/example_simplest/instructor/cs101/report1.py index e00853f..447cedb 100644 --- a/examples/example_simplest/instructor/cs101/report1.py +++ b/examples/example_simplest/instructor/cs101/report1.py @@ -1,5 +1,5 @@ -from unitgrade2.unitgrade2 import Report -from unitgrade2.unitgrade_helpers2 import evaluate_report_student +from src.unitgrade2.unitgrade2 import Report +from src.unitgrade2 import evaluate_report_student from cs101.homework1 import reverse_list, add import unittest diff --git a/examples/example_simplest/instructor/cs101/report1_grade.py b/examples/example_simplest/instructor/cs101/report1_grade.py index 8972ab5..7f2feaa 100644 --- a/examples/example_simplest/instructor/cs101/report1_grade.py +++ b/examples/example_simplest/instructor/cs101/report1_grade.py @@ -4,14 +4,10 @@ from tabulate import tabulate from datetime import datetime import pyfiglet import unittest - import inspect import os import argparse -import sys import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: @@ -61,53 +57,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass show_tol_err=show_tol_err) - # try: # For registering stats. - # import unitgrade_private - # import irlc.lectures - # import xlwings - # from openpyxl import Workbook - # import pandas as pd - # from collections import defaultdict - # dd = defaultdict(lambda: []) - # error_computed = [] - # for k1, (q, _) in enumerate(report.questions): - # for k2, item in enumerate(q.items): - # dd['question_index'].append(k1) - # dd['item_index'].append(k2) - # dd['question'].append(q.name) - # dd['item'].append(item.name) - # dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol) - # error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed) - # - # qstats = report.wdir + "/" + report.name + ".xlsx" - # - # if os.path.isfile(qstats): - # d_read = pd.read_excel(qstats).to_dict() - # else: - # d_read = dict() - # - # for k in range(1000): - # key = 'run_'+str(k) - # if key in d_read: - # dd[key] = list(d_read['run_0'].values()) - # else: - # dd[key] = error_computed - # break - # - # workbook = Workbook() - # worksheet = workbook.active - # for col, key in enumerate(dd.keys()): - # worksheet.cell(row=1, column=col+1).value = key - # for row, item in enumerate(dd[key]): - # worksheet.cell(row=row+2, column=col+1).value = item - # - # workbook.save(qstats) - # workbook.close() - # - # except ModuleNotFoundError as e: - # s = 234 - # pass - if question is None: print("Provisional evaluation") tabulate(table_data) @@ -159,24 +108,20 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) else: b = "Unitgrade" - print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) s = report.title if hasattr(report, "version") and report.version is not None: s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + print(s, "(use --help for options)" if show_help_flag else "") # print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] - nL = 80 t_start = time.time() score = {} loader = SequentialTestLoader() for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) if question is not None and n+1 != question: continue suite = loader.loadTestsFromTestCase(q) @@ -186,104 +131,28 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa q.possible = 0 q.obtained = 0 q_ = {} # Gather score in this class. - # 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. UTextResult.number = n + UTextResult.nL = report.nL res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) - # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite) - z = 234 - # for j, item in enumerate(q.items): - # if qitem is not None and question is not None and j+1 != qitem: - # continue - # - # if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles. - # # if not item.question.has_called_init_: - # start = time.time() - # - # cc = None - # if show_progress_bar: - # 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] ) - # cc = ActiveProgress(t=total_estimated_time, title=q_title_print) - # from unitgrade import Capturing # DON'T REMOVE THIS LINE - # with eval('Capturing')(unmute=unmute): # Clunky import syntax is required bc. of minify issue. - # try: - # for q2 in q_with_outstanding_init: - # q2.init() - # q2.has_called_init_ = True - # - # # item.question.init() # Initialize the question. Useful for sharing resources. - # except Exception as e: - # if not passall: - # if not silent: - # print(" ") - # print("="*30) - # print(f"When initializing question {q.title} the initialization code threw an error") - # print(e) - # print("The remaining parts of this question will likely fail.") - # print("="*30) - # - # if show_progress_bar: - # cc.terminate() - # sys.stdout.flush() - # print(q_title_print, end="") - # - # q_time =np.round( time.time()-start, 2) - # - # 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 "") - # print("=" * nL) - # q_with_outstanding_init = None - # - # # item.question = q # Set the parent question instance for later reference. - # item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title) - # - # if show_progress_bar: - # cc = ActiveProgress(t=item.estimated_time, title=item_title_print) - # else: - # print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="") - # hidden = issubclass(item.__class__, Hidden) - # # if not hidden: - # # print(ss, end="") - # # sys.stdout.flush() - # start = time.time() - # - # (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent) - # q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title} - # tsecs = np.round(time.time()-start, 2) - # if show_progress_bar: - # cc.terminate() - # sys.stdout.flush() - # print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="") - # - # if not hidden: - # ss = "PASS" if current == possible else "*** FAILED" - # if tsecs >= 0.1: - # ss += " ("+ str(tsecs) + " seconds)" - # print(ss) - - # ws, possible, obtained = upack(q_) possible = res.testsRun 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 = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} q.obtained = obtained q.possible = possible - s1 = f"*** Question q{n+1}" + s1 = f"Question {n+1} total" s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) ws, possible, obtained = upack(score) possible = int( msum(possible) ) @@ -298,15 +167,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa seconds = dt - minutes*60 plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) results = {'total': (obtained, possible), 'details': score} return results, table_data - - from tabulate import tabulate from datetime import datetime import inspect @@ -329,7 +199,8 @@ def gather_imports(imp): # dn = os.path.dirname(f) # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: top_package = os.path.dirname(m.__file__) module_import = True else: @@ -350,7 +221,7 @@ def gather_imports(imp): for file in files: if file.endswith(".py"): fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) zip.write(fpath, v) resources['zipfile'] = zip_buffer.getvalue() @@ -394,14 +265,14 @@ def gather_upload_to_campusnet(report, output_dir=None): 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)) + # print(" ") + # print("="*n) + # print("Final evaluation") + # print(tabulate(table_data)) # also load the source code of missing files... sources = {} - + print("") if not args.autolab: if len(report.individual_imports) > 0: print("By uploading the .token file, you verify the files:") @@ -414,12 +285,15 @@ def gather_upload_to_campusnet(report, output_dir=None): 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) + _, report_relative_location, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import nimp['name'] = m.__name__ sources[k] = nimp # if len([k for k in nimp if k not in sources]) > 0: - print(f"*** {m.__name__}") + print(f" * {m.__name__}") # sources = {**sources, **nimp} results['sources'] = sources @@ -438,9 +312,9 @@ def gather_upload_to_campusnet(report, output_dir=None): if not args.autolab: print(" ") - print("To get credit for your results, please upload the single file: ") + print("To get credit for your results, please upload the single unmodified file: ") print(">", token) - print("To campusnet without any modifications.") + # print("To campusnet without any modifications.") # print("Now time for some autolab fun") @@ -453,7 +327,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"""\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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, too 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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n item_title = item_title.split("\\n")[0]\n\n item_title = test.shortDescription() # Better for printing (get from cache).\n # test.countTestCases()\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n @classmethod\n def question_title(cls):\n return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n # def _callSetUp(self):\n # # Always run before method is called.\n # print("asdf")\n # pass\n # @classmethod\n # def setUpClass(cls):\n # # self._cache_put((self.cache_id(), \'title\'), value)\n # cls.reset()\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 # i = 0\n # for i in itertools.count():\n # # key = k0 + (i,)\n # if i not in self._cache_get( (k0, \'assert\') ):\n # break\n # return i\n # return key\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 # print("Is this needed?")\n # self._ensure_cache_exists()\n # return key in self.__class__._cache2\n\n def wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\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 # print("Bad output\\n\\n")\n\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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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 {n+1} total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 unmodified 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\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 deleted file mode 100644 index 5ccd4e5495ad2bbfe4c3cc09832916b356f0d31b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77002 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 ATmS$7 diff --git a/examples/example_simplest/students/cs101/__pycache__/deploy.cpython-38.pyc b/examples/example_simplest/students/cs101/__pycache__/deploy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e14e2ac2aed2a082d17689ac69828e53aeba409 GIT binary patch literal 911 zcmZWnO>fjN5Ve!+Ci~TvqG~T3I3X>EEZYi!5JE^)9612JSOF`IXS?nuaj+fQjS&B! zC$1dYBY!DZPW*+Qm}JXGTRR!O@pwGr_k1uK^$CtIcf<IbOUTd0`G5H6yu{y}qY*^V zlw_1q+F&PjGMBk6c2h6wuuhA;w9C2}_fntv;2#I)J?LLhHh`h%EFJwFT%e+RHT^Tp zg3fUV1|k>}w7oGAeK8P2F*<U^#+iqwZtjy0_t*%!=Nq_VtKD}8?PyFc97;ZY72Z2C z+5UxHk_oN-w@@f;X9&jzY+3Ll9Sh)dnF7~s3`2p*Wx<8iZ|Z>aES5RvuO}YsW-^!a z73-<Pd1;c^0^Z{kk`jS;SjnR5O~Y`SnAxLQwbMZSj4cJ^)~uJ`*8|RrRT3u);Jor5 zhE^>huX+NCRIS2dg?0Kyl{$f@0~QD{wnaNh3USUGU*@ru3NdcL4w4j0Ox)_}x*}RQ zjk5x;G}mzkCrU4;GB;M2iB-C7WOuqy>&OgiR*!FG3Y9ED-%Q)Pp7!Kc+B}Wz&7@ho zc&*;(+BuhWI@x5SmbY-N!c04;7<*9W(yk>cwET>W?}iI0kWijzK(|%SXndlC`HgG^ zXzev#*DOja)7nv{_KMgpY9Hy!k&i~yR&dUo6VZ6hD(5ww*IuHE)eoXK@rc%3eth*T zIxwJ3WXlCa`zk53M$hP0Yta?!<`3XTHYyk!{bN6BZyEiSyAGabN|Y(QpjgsuqZ!g> PZqcC=&~5j=8@Rs#9|je( literal 0 HcmV?d00001 diff --git a/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-38.pyc b/examples/example_simplest/students/cs101/__pycache__/homework1.cpython-38.pyc index 1149b3ace2379ce11502c3e4633382e7f8d4950a..7dbbdac7ae33a6d3d1764a740a5f47ff3e00b252 100644 GIT binary patch delta 374 zcmYk0y-EW?5XWcczSc8ICE6%xA##Pig;*G|5iI;@(i~@Ik;8oG-bGLXDb5#=EhQj! zK8J5{rSBkum9rNM2WEEmzw`gienuZ*=d#_7LE`IdCp)F*&id?FZVZf1&;us+<U~#E z-;g=f9tRpZ<glU_m{WxaqD2}+Q@q6SwO?UH2)uj_Nvn^J{THtHB)gJDOG*hPPC@2T zX7TYY-juMbINwgF_6K=U*2?I6Z3@i^_QK(V)84^2%1>s$@hHwmMQJAea%^muw%Tq- zK~f+vfxV-Rj*@-~Q(ObL#J-Z=b#~c+S&df>Yf*fWYcFGVmIcH!%I<j=C%y?pGwz{Q mX*$Y=I!)`~*5sq|AHji5gHij#F;52ip!tyVsYnHiR`3_qMNBUM delta 534 zcmZWmJ#W-N5Zzh(d_I$lL<ph*X{pdjfRG|lAsUh(D6Szi*H|0R-encri+0zDOA`sx zrAk~P8bp(yfGG1XT+&d}regLIQNT#EGxO%_jrJn>Ib44`9%qc?_r*i`G&)_sd;FRr zWHW|<{1cl2Mi`UjD#F1bx4SCFL~$JAu;riG7v2Ie!cql<jWEL-i@1fB1H-j9Y%)Gt zWIG>Z^{!OH`36<(MPX1q7JCQUiMb}6tZclgq}Ro<EX$=>s9IU+6^gkvmGB2@Rq2GT zSAnjbw@u-VeT3So;4HHO!X?)dd`|j;)UDCu>7o`s(pAJNI{KOkl9&3us)ZD#c3wQF zmZd)Zak71R=T+ca1+Md^F1$9i*f+SGo<o0YYW&}{!X0akoxm&!&CZ`Q8`HAOygP*L z^!i}eeTVzwfV)76B{u1P!M%<DX>}W1Z3XI68!9Kf2ie2^YUb55Ah?etVrae^7u!+y ujqlwY^us(yQ{=f#C}~G$(d{Q;>%Z;kv&vvos^?*o3;!QSX__V}--JIUYKDLS diff --git a/examples/example_simplest/students/cs101/__pycache__/report1.cpython-38.pyc b/examples/example_simplest/students/cs101/__pycache__/report1.cpython-38.pyc index d7c0fcd96cc7cec28f9155fb61bec8c3bb6e00df..83e9e30d8000e629914b0160acf822078d8a3b78 100644 GIT binary patch delta 163 zcmdnVd4`iOl$V!_0SF3D>Lk8k+Q>JXk+Ep<Dn?_|#L|-doW!K~)QZI1f}B)+Jw1IW zGrl+zBvM?WpP5%&QdF8;l3%2sTx@7ysK5C?<8nsfTbzZZK<Ui<yy9D|V2N9kcQUIn z3QT^$EYEmr5=)fyEsoN>%#sqIs#_c(K+-v}IJJlk=#C<($)zl|?3^5o0<0X&0RDJ3 Ag8%>k delta 176 zcmX@Zxs#JGl$V!_0SN9M)=A`M-pDtbkuiSqDn?_a)QZI1f}GU&;!F@#ToRL+S6ot5 znp~1!6q8(RXkZw#S%_&lqi_^wVJT2LGe58R7AsgHYVsLoH6=lyp~Y-Kf`gHTk&CfN z49L925g(tMn3)$JugM!FI@y3lWU>Z}t8^4cX<lYY3DA@%ju0T}oLHP%1kze0Ik|(y NmYs`(QGk_$8302FF&+Q_ diff --git a/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-38.pyc b/examples/example_simplest/students/cs101/__pycache__/report1_grade.cpython-38.pyc index 0aeda2d66a25eaae02e31a9c629a9a2887fb7dbd..aed06f7cf667b51f32903bf52b3c17138bda2777 100644 GIT binary patch delta 3389 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 QmF?JWv%?1snMJ<xKk62%OaK4? delta 7949 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 diff --git a/examples/example_simplest/students/cs101/report1.py b/examples/example_simplest/students/cs101/report1.py index 8e5dfca..9e9fce7 100644 --- a/examples/example_simplest/students/cs101/report1.py +++ b/examples/example_simplest/students/cs101/report1.py @@ -1,8 +1,8 @@ """ 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 src.unitgrade2.unitgrade2 import Report +from src.unitgrade2 import evaluate_report_student from cs101.homework1 import reverse_list, add import unittest @@ -13,7 +13,6 @@ class Week1(unittest.TestCase): def test_reverse(self): self.assertEqual(reverse_list([1,2,3]), [3,2,1]) - # print("Bad output\n\n") import cs101 diff --git a/examples/example_simplest/students/cs101/report1_grade.py b/examples/example_simplest/students/cs101/report1_grade.py index efb1981..c244e79 100644 --- a/examples/example_simplest/students/cs101/report1_grade.py +++ b/examples/example_simplest/students/cs101/report1_grade.py @@ -6,14 +6,10 @@ from tabulate import tabulate from datetime import datetime import pyfiglet import unittest - import inspect import os import argparse -import sys import time -import threading # don't import Thread bc. of minify issue. -import tqdm # don't do from tqdm import tqdm because of minify-issue parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: To run all tests in a report: @@ -63,53 +59,6 @@ def evaluate_report_student(report, question=None, qitem=None, unmute=None, pass show_tol_err=show_tol_err) - # try: # For registering stats. - # import unitgrade_private - # import irlc.lectures - # import xlwings - # from openpyxl import Workbook - # import pandas as pd - # from collections import defaultdict - # dd = defaultdict(lambda: []) - # error_computed = [] - # for k1, (q, _) in enumerate(report.questions): - # for k2, item in enumerate(q.items): - # dd['question_index'].append(k1) - # dd['item_index'].append(k2) - # dd['question'].append(q.name) - # dd['item'].append(item.name) - # dd['tol'].append(0 if not hasattr(item, 'tol') else item.tol) - # error_computed.append(0 if not hasattr(item, 'error_computed') else item.error_computed) - # - # qstats = report.wdir + "/" + report.name + ".xlsx" - # - # if os.path.isfile(qstats): - # d_read = pd.read_excel(qstats).to_dict() - # else: - # d_read = dict() - # - # for k in range(1000): - # key = 'run_'+str(k) - # if key in d_read: - # dd[key] = list(d_read['run_0'].values()) - # else: - # dd[key] = error_computed - # break - # - # workbook = Workbook() - # worksheet = workbook.active - # for col, key in enumerate(dd.keys()): - # worksheet.cell(row=1, column=col+1).value = key - # for row, item in enumerate(dd[key]): - # worksheet.cell(row=row+2, column=col+1).value = item - # - # workbook.save(qstats) - # workbook.close() - # - # except ModuleNotFoundError as e: - # s = 234 - # pass - if question is None: print("Provisional evaluation") tabulate(table_data) @@ -161,24 +110,20 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] ) else: b = "Unitgrade" - print(b + " v" + __version__) dt_string = now.strftime("%d/%m/%Y %H:%M:%S") - print("Started: " + dt_string) + print(b + " v" + __version__ + ", started: " + dt_string+ "\n") + # print("Started: " + dt_string) s = report.title if hasattr(report, "version") and report.version is not None: s += " version " + report.version - print("Evaluating " + s, "(use --help for options)" if show_help_flag else "") + print(s, "(use --help for options)" if show_help_flag else "") # print(f"Loaded answers from: ", report.computed_answers_file, "\n") table_data = [] - nL = 80 t_start = time.time() score = {} loader = SequentialTestLoader() for n, (q, w) in enumerate(report.questions): - # q = q() - # q_hidden = False - # q_hidden = issubclass(q.__class__, Hidden) if question is not None and n+1 != question: continue suite = loader.loadTestsFromTestCase(q) @@ -188,104 +133,28 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa q.possible = 0 q.obtained = 0 q_ = {} # Gather score in this class. - # 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. UTextResult.number = n + UTextResult.nL = report.nL res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite) - # res = UTextTestRunner(verbosity=2, resultclass=unittest.TextTestResult).run(suite) - z = 234 - # for j, item in enumerate(q.items): - # if qitem is not None and question is not None and j+1 != qitem: - # continue - # - # if q_with_outstanding_init is not None: # check for None bc. this must be called to set titles. - # # if not item.question.has_called_init_: - # start = time.time() - # - # cc = None - # if show_progress_bar: - # 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] ) - # cc = ActiveProgress(t=total_estimated_time, title=q_title_print) - # from unitgrade import Capturing # DON'T REMOVE THIS LINE - # with eval('Capturing')(unmute=unmute): # Clunky import syntax is required bc. of minify issue. - # try: - # for q2 in q_with_outstanding_init: - # q2.init() - # q2.has_called_init_ = True - # - # # item.question.init() # Initialize the question. Useful for sharing resources. - # except Exception as e: - # if not passall: - # if not silent: - # print(" ") - # print("="*30) - # print(f"When initializing question {q.title} the initialization code threw an error") - # print(e) - # print("The remaining parts of this question will likely fail.") - # print("="*30) - # - # if show_progress_bar: - # cc.terminate() - # sys.stdout.flush() - # print(q_title_print, end="") - # - # q_time =np.round( time.time()-start, 2) - # - # 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 "") - # print("=" * nL) - # q_with_outstanding_init = None - # - # # item.question = q # Set the parent question instance for later reference. - # item_title_print = ss = "*** q%i.%i) %s"%(n+1, j+1, item.title) - # - # if show_progress_bar: - # cc = ActiveProgress(t=item.estimated_time, title=item_title_print) - # else: - # print(item_title_print + ( '.'*max(0, nL-4-len(ss)) ), end="") - # hidden = issubclass(item.__class__, Hidden) - # # if not hidden: - # # print(ss, end="") - # # sys.stdout.flush() - # start = time.time() - # - # (current, possible) = item.get_points(show_expected=show_expected, show_computed=show_computed,unmute=unmute, passall=passall, silent=silent) - # q_[j] = {'w': item.weight, 'possible': possible, 'obtained': current, 'hidden': hidden, 'computed': str(item._computed_answer), 'title': item.title} - # tsecs = np.round(time.time()-start, 2) - # if show_progress_bar: - # cc.terminate() - # sys.stdout.flush() - # print(item_title_print + ('.' * max(0, nL - 4 - len(ss))), end="") - # - # if not hidden: - # ss = "PASS" if current == possible else "*** FAILED" - # if tsecs >= 0.1: - # ss += " ("+ str(tsecs) + " seconds)" - # print(ss) - - # ws, possible, obtained = upack(q_) possible = res.testsRun 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 = int(w * obtained * 1.0 / possible ) if possible > 0 else 0 score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'items': q_, 'title': qtitle} q.obtained = obtained q.possible = possible - s1 = f"*** Question q{n+1}" + s1 = f"Question {n+1} total" s2 = f" {q.obtained}/{w}" - print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 ) + print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 ) print(" ") - table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"]) + table_data.append([f"q{n+1}) Total", f"{q.obtained}/{w}"]) ws, possible, obtained = upack(score) possible = int( msum(possible) ) @@ -300,15 +169,16 @@ def evaluate_report(report, question=None, qitem=None, passall=False, verbose=Fa seconds = dt - minutes*60 plrl = lambda i, s: str(i) + " " + s + ("s" if i != 1 else "") - print(f"Completed: "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")") + dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")", + last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL) + + # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total") table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ]) results = {'total': (obtained, possible), 'details': score} return results, table_data - - from tabulate import tabulate from datetime import datetime import inspect @@ -331,7 +201,8 @@ def gather_imports(imp): # dn = os.path.dirname(f) # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: top_package = os.path.dirname(m.__file__) module_import = True else: @@ -352,7 +223,7 @@ def gather_imports(imp): for file in files: if file.endswith(".py"): fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) zip.write(fpath, v) resources['zipfile'] = zip_buffer.getvalue() @@ -396,14 +267,14 @@ def gather_upload_to_campusnet(report, output_dir=None): 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)) + # print(" ") + # print("="*n) + # print("Final evaluation") + # print(tabulate(table_data)) # also load the source code of missing files... sources = {} - + print("") if not args.autolab: if len(report.individual_imports) > 0: print("By uploading the .token file, you verify the files:") @@ -416,12 +287,15 @@ def gather_upload_to_campusnet(report, output_dir=None): 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) + _, report_relative_location, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import nimp['name'] = m.__name__ sources[k] = nimp # if len([k for k in nimp if k not in sources]) > 0: - print(f"*** {m.__name__}") + print(f" * {m.__name__}") # sources = {**sources, **nimp} results['sources'] = sources @@ -440,9 +314,9 @@ def gather_upload_to_campusnet(report, output_dir=None): if not args.autolab: print(" ") - print("To get credit for your results, please upload the single file: ") + print("To get credit for your results, please upload the single unmodified file: ") print(">", token) - print("To campusnet without any modifications.") + # print("To campusnet without any modifications.") # print("Now time for some autolab fun") @@ -455,7 +329,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"""\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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\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 modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\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 for (q,_) in self.questions:\n q.nL = self.nL # Set maximum line length.\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, too 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 number = -1 # HAcky way to set question number.\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 j =self.testsRun\n self.testsRun += 1\n # print("Starting the test...")\n # show_progress_bar = True\n n = UTextResult.number\n\n item_title = self.getDescription(test)\n item_title = item_title.split("\\n")[0]\n\n item_title = test.shortDescription() # Better for printing (get from cache).\n # test.countTestCases()\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", foo.__name__, _make_key(args, kwargs, typed)) )\n # key = (self.cache_id(), \'@cache\')\n # if self._cache_contains[key]\n\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n\n @classmethod\n def question_title(cls):\n return cls.__doc__.splitlines()[0].strip() if cls.__doc__ != None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd == None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n # self._testMethodDoc.strip().splitlines()[0].strip()\n sd = self.shortDescriptionStandard()\n title = self._cache_get( (self.cache_id(), \'title\'), sd )\n return title if title != None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n # def _callSetUp(self):\n # # Always run before method is called.\n # print("asdf")\n # pass\n # @classmethod\n # def setUpClass(cls):\n # # self._cache_put((self.cache_id(), \'title\'), value)\n # cls.reset()\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 self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc != None:\n # Ensure the cache is eventually updated with the right docstring.\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard() )\n # Fix temp cache here (for using the @cache decorator)\n self._cache2[ (self.cache_id(), \'assert\') ] = {}\n\n res = testMethod()\n elapsed = time.time() - t\n # self._cache_put( (self.cache_id(), \'title\'), self.shortDescription() )\n\n self._get_outcome()[self.cache_id()] = res\n self._cache_put( (self.cache_id(), "time"), elapsed)\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 # i = 0\n # for i in itertools.count():\n # # key = k0 + (i,)\n # if i not in self._cache_get( (k0, \'assert\') ):\n # break\n # return i\n # return key\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 0\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 # print("Is this needed?")\n # self._ensure_cache_exists()\n # return key in self.__class__._cache2\n\n def wrap_assert(self, assert_fun, first, *args, **kwargs):\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n cache = self._cache_get(key, {})\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, first)\n assert_fun(first, _expected, *args, **kwargs)\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.number = n\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 # print("Bad output\\n\\n")\n\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"""\nimport numpy as np\nimport sys\nimport re\nimport threading\nimport tqdm\nimport pickle\nimport os\nfrom io import StringIO\nimport io\nfrom unittest.runner import _WritelnDecorator\nfrom typing import Any\nimport inspect\nimport textwrap\nimport colorama\nfrom colorama import Fore\nfrom functools import _make_key, RLock\nfrom collections import namedtuple\nimport unittest\nimport time\n\n_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])\n\ncolorama.init(autoreset=True) # auto resets your settings after every output\n\ndef gprint(s):\n print(f"{Fore.GREEN}{s}")\n\nmyround = lambda x: np.round(x) # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\n\ndef setup_dir_by_class(C, base_dir):\n name = C.__class__.__name__\n return base_dir, name\n\n\nclass Logger(object):\n def __init__(self, buffer):\n assert False\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\n\nclass Capturing(list):\n def __init__(self, *args, stdout=None, unmute=False, **kwargs):\n self._stdout = stdout\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 if self._stdout == None else self._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 Capturing2(Capturing):\n def __exit__(self, *args):\n lines = self._stringio.getvalue().splitlines()\n txt = "\\n".join(lines)\n numbers = extract_numbers(txt)\n self.extend(lines)\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 self.output = txt\n self.numbers = numbers\n\n\n# @classmethod\n# class OrderedClassMembers(type):\n# def __prepare__(self, name, bases):\n# assert False\n# return collections.OrderedDict()\n#\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\n\nclass Report:\n title = "report title"\n version = None\n questions = []\n pack_imports = []\n individual_imports = []\n nL = 120 # 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 @classmethod\n def mfile(clc):\n return inspect.getfile(clc)\n\n def _file(self):\n return inspect.getfile(type(self))\n\n def _import_base_relative(self):\n if hasattr(self.pack_imports[0], \'__path__\'):\n root_dir = self.pack_imports[0].__path__._path[0]\n else:\n root_dir = self.pack_imports[0].__file__\n\n root_dir = os.path.dirname(root_dir)\n relative_path = os.path.relpath(self._file(), root_dir)\n modules = os.path.normpath(relative_path[:-3]).split(os.sep)\n return root_dir, relative_path, modules\n\n def __init__(self, strict=False, payload=None):\n working_directory = os.path.abspath(os.path.dirname(self._file()))\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 for (q, _) in self.questions:\n q.nL = self.nL # Set maximum line length.\n\n if payload is not None:\n self.set_payload(payload, strict=strict)\n\n def main(self, verbosity=1):\n # Run all tests using standard unittest (nothing fancy).\n loader = unittest.TestLoader()\n for q, _ in self.questions:\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, with_coverage=False):\n if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = True\n q._report = self\n\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 if with_coverage:\n for q, _ in self.questions:\n q._with_coverage = False\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\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\n\ndef extract_numbers(txt):\n # txt = rm_progress_bar(txt)\n numeric_const_pattern = r\'[-+]? (?: (?: \\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, too 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, file=None):\n if file == None:\n file = sys.stdout\n self.file = file\n self.t = t\n self._running = False\n self.title = title\n self.dt = 0.01\n self.n = int(np.round(self.t / self.dt))\n self.show_progress_bar = show_progress_bar\n self.pbar = None\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 self.pbar is not None:\n self.pbar.update(1)\n self.pbar.close()\n self.pbar = None\n\n self.file.flush()\n return time.time() - self.time_started\n\n def run(self):\n self.pbar = tqdm.tqdm(total=self.n, file=self.file, position=0, leave=False, desc=self.title, ncols=100,\n bar_format=\'{l_bar}{bar}| [{elapsed}<{remaining}]\')\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\ndef dprint(first, last, nL, extra = "", file=None, dotsym=\'.\', color=\'white\'):\n if file == None:\n file = sys.stdout\n\n # ss = self.item_title_print\n # state = "PASS" if success else "FAILED"\n dot_parts = (dotsym * max(0, nL - len(last) - len(first)))\n # if self.show_progress_bar or True:\n print(first + dot_parts, end="", file=file)\n # else:\n # print(dot_parts, end="", file=self.cc.file)\n last += extra\n # if tsecs >= 0.5:\n # state += " (" + str(tsecs) + " seconds)"\n print(last, file=file)\n\n\nclass UTextResult(unittest.TextTestResult):\n nL = 80\n number = -1 # HAcky way to set question number.\n show_progress_bar = True\n cc = None\n\n def __init__(self, stream, descriptions, verbosity):\n super().__init__(stream, descriptions, verbosity)\n self.successes = []\n\n def printErrors(self) -> None:\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\n def addSuccess(self, test: unittest.case.TestCase) -> None:\n self.successes.append(test)\n self.cc_terminate()\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 self.cc.file.flush()\n ss = self.item_title_print\n\n state = "PASS" if success else "FAILED"\n\n dot_parts = (\'.\' * max(0, self.nL - len(state) - len(ss)))\n if self.show_progress_bar or True:\n print(self.item_title_print + dot_parts, end="", file=self.cc.file)\n else:\n print(dot_parts, end="", file=self.cc.file)\n\n if tsecs >= 0.5:\n state += " (" + str(tsecs) + " seconds)"\n print(state, file=self.cc.file)\n\n def startTest(self, test):\n # j =self.testsRun\n self.testsRun += 1\n # item_title = self.getDescription(test)\n item_title = test.shortDescription() # Better for printing (get from cache).\n if item_title == None:\n # For unittest framework where getDescription may return None.\n item_title = self.getDescription(test)\n self.item_title_print = " * q%i.%i) %s" % (UTextResult.number + 1, self.testsRun, item_title)\n estimated_time = 10\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, file=sys.stdout)\n else:\n print(self.item_title_print + (\'.\' * max(0, self.nL - 4 - len(self.item_title_print))), end="")\n\n self._test = test\n self._stdout = sys.stdout\n sys.stdout = io.StringIO()\n\n def stopTest(self, test):\n sys.stdout = self._stdout\n super().stopTest(test)\n\n def _setupStdout(self):\n if self._previousTestClass == None:\n total_estimated_time = 1\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 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 is None:\n q_time = self.cc.terminate()\n q_time = np.round(q_time, 2)\n sys.stdout.flush()\n if self.show_progress_bar:\n print(self.cc.title, end="")\n print(" " * max(0, self.nL - len(self.cc.title)) + (" (" + str(q_time) + " seconds)" if q_time >= 0.5 else ""))\n\n\nclass UTextTestRunner(unittest.TextTestRunner):\n def __init__(self, *args, **kwargs):\n stream = io.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\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", foo.__name__, _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\n return wrapper\n\n\ndef get_hints(ss):\n if ss == None:\n return None\n try:\n ss = textwrap.dedent(ss)\n ss = ss.replace(\'\'\'"""\'\'\', "").strip()\n hints = ["hints:", ]\n j = np.argmax([ss.lower().find(h) for h in hints])\n h = hints[j]\n ss = ss[ss.find(h) + len(h) + 1:]\n ss = "\\n".join([l for l in ss.split("\\n") if not l.strip().startswith(":")])\n ss = textwrap.dedent(ss)\n ss = ss.strip()\n return ss\n except Exception as e:\n print("bad hints", ss, e)\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. Ensures method always produce same result.\n _cache2 = None # User-written cache.\n _with_coverage = False\n _report = None # The report used. This is very, very hacky and should always be None. Don\'t rely on it!\n\n def capture(self):\n if hasattr(self, \'_stdout\') and self._stdout is not None:\n file = self._stdout\n else:\n file = sys.stdout\n return Capturing2(stdout=file)\n\n @classmethod\n def question_title(cls):\n """ Return the question title """\n return cls.__doc__.strip().splitlines()[0].strip() if cls.__doc__ is not None else cls.__qualname__\n\n @classmethod\n def reset(cls):\n print("Warning, I am not sure UTestCase.reset() is needed anymore and it seems very hacky.")\n cls._outcome = None\n cls._cache = None\n cls._cache2 = None\n\n def _callSetUp(self):\n if self._with_coverage:\n if not hasattr(self._report, \'covcache\'):\n self._report.covcache = {}\n import coverage\n self.cov = coverage.Coverage()\n self.cov.start()\n self.setUp()\n\n def _callTearDown(self):\n self.tearDown()\n if self._with_coverage:\n from pathlib import Path\n from snipper import snipper\n self.cov.stop()\n data = self.cov.get_data()\n base, _, _ = self._report._import_base_relative()\n for file in data.measured_files():\n file = os.path.normpath(file)\n root = Path(base)\n child = Path(file)\n if root in child.parents:\n with open(child, \'r\') as f:\n s = f.read()\n lines = s.splitlines()\n garb = \'GARBAGE\'\n\n lines2 = snipper.censor_code(lines, keep=True)\n assert len(lines) == len(lines2)\n\n for l in data.contexts_by_lineno(file):\n if lines2[l].strip() == garb:\n if self.cache_id() not in self._report.covcache:\n self._report.covcache[self.cache_id()] = {}\n\n rel = os.path.relpath(child, root)\n cc = self._report.covcache[self.cache_id()]\n j = 0\n for j in range(l, -1, -1):\n if "def" in lines2[j] or "class" in lines2[j]:\n break\n from snipper.snipper import gcoms\n fun = lines2[j]\n comments, _ = gcoms("\\n".join(lines2[j:l]))\n if rel not in cc:\n cc[rel] = {}\n cc[rel][fun] = (l, "\\n".join(comments))\n self._cache_put((self.cache_id(), \'coverage\'), self._report.covcache)\n\n def shortDescriptionStandard(self):\n sd = super().shortDescription()\n if sd is None:\n sd = self._testMethodName\n return sd\n\n def shortDescription(self):\n sd = self.shortDescriptionStandard()\n title = self._cache_get((self.cache_id(), \'title\'), sd)\n return title if title is not None else sd\n\n @property\n def title(self):\n return self.shortDescription()\n\n @title.setter\n def title(self, value):\n self._cache_put((self.cache_id(), \'title\'), value)\n\n def _get_outcome(self):\n if not (self.__class__, \'_outcome\') or self.__class__._outcome is None:\n self.__class__._outcome = {}\n return self.__class__._outcome\n\n def _callTestMethod(self, testMethod):\n t = time.time()\n self._ensure_cache_exists() # Make sure cache is there.\n if self._testMethodDoc is not None:\n self._cache_put((self.cache_id(), \'title\'), self.shortDescriptionStandard())\n\n self._cache2[(self.cache_id(), \'assert\')] = {}\n res = testMethod()\n elapsed = time.time() - t\n self._get_outcome()[self.cache_id()] = res\n self._cache_put((self.cache_id(), "time"), elapsed)\n\n def cache_id(self):\n c = self.__class__.__qualname__\n m = self._testMethodName\n return c, m\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n self._load_cache()\n self._assert_cache_index = 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 wrap_assert(self, assert_fun, first, *args, **kwargs):\n # sys.stdout = self._stdout\n key = (self.cache_id(), \'assert\')\n if not self._cache_contains(key):\n print("Warning, framework missing", key)\n self.__class__._cache[\n key] = {} # A new dict. We manually insert it because we have to use that the dict is mutable.\n cache = self._cache_get(key)\n id = self._assert_cache_index\n if not id in cache:\n print("Warning, framework missing cache index", key, "id =", id)\n _expected = cache.get(id, f"Key {id} not found in cache; framework files missing. Please run deploy()")\n\n # The order of these calls is important. If the method assert fails, we should still store the correct result in cache.\n cache[id] = first\n self._cache_put(key, cache)\n self._assert_cache_index += 1\n assert_fun(first, _expected, *args, **kwargs)\n\n def assertEqualC(self, first: Any, msg: Any = ...) -> None:\n self.wrap_assert(self.assertEqual, first, msg)\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 is not 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 if os.path.exists(cfile):\n try:\n with open(cfile, \'rb\') as f:\n data = pickle.load(f)\n self.__class__._cache = data\n except Exception as e:\n print("Bad cache", cfile)\n print(e)\n else:\n print("Warning! data file not found", cfile)\n\n def _feedErrorsToResult(self, result, errors):\n """ Use this to show hints on test failure. """\n if not isinstance(result, UTextResult):\n er = [e for e, v in errors if v != None]\n\n if len(er) > 0:\n hints = []\n key = (self.cache_id(), \'coverage\')\n if self._cache_contains(key):\n CC = self._cache_get(key)\n for id in CC:\n if id == self.cache_id():\n cl, m = id\n gprint(f"> An error occured while solving: {cl}.{m}. The files/methods you need to edit are:") # For the test {id} in {file} you should edit:")\n for file in CC[id]:\n rec = CC[id][file]\n gprint(f"> * {file}")\n for l in rec:\n _, comments = CC[id][file][l]\n hint = get_hints(comments)\n\n if hint != None:\n hints.append(hint)\n gprint(f"> - {l}")\n\n er = er[0]\n doc = er._testMethodDoc\n if doc is not None:\n hint = get_hints(er._testMethodDoc)\n if hint is not None:\n hints = [hint] + hints\n if len(hints) > 0:\n gprint("> Hints:")\n gprint(textwrap.indent("\\n".join(hints), "> "))\n\n super()._feedErrorsToResult(result, errors)\n\n def startTestRun(self):\n # print("asdfsdaf 11", file=sys.stderr)\n super().startTestRun()\n # print("asdfsdaf")\n\n def _callTestMethod(self, method):\n # print("asdfsdaf")\n super()._callTestMethod(method)\n\n\ndef hide(func):\n return func\n\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\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 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# 817\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pyfiglet\nimport unittest\nimport inspect\nimport os\nimport argparse\nimport time\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 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 ls = []\n for C in testCaseClass.mro():\n if issubclass(C, unittest.TestCase):\n ls = list(C.__dict__.keys()) + ls\n testcase_methods = ls\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 dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n print(b + " v" + __version__ + ", started: " + dt_string+ "\\n")\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(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 t_start = time.time()\n score = {}\n loader = SequentialTestLoader()\n\n for n, (q, w) in enumerate(report.questions):\n if question is not None and n+1 != question:\n continue\n suite = loader.loadTestsFromTestCase(q)\n qtitle = q.question_title() if hasattr(q, \'question_title\') else q.__qualname__\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 UTextResult.q_title_print = q_title_print # Hacky\n UTextResult.show_progress_bar = show_progress_bar # Hacky.\n UTextResult.number = n\n UTextResult.nL = report.nL\n\n res = UTextTestRunner(verbosity=2, resultclass=UTextResult).run(suite)\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 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 {n+1} total"\n s2 = f" {q.obtained}/{w}"\n print(s1 + ("."* (report.nL-len(s1)-len(s2) )) + s2 )\n print(" ")\n table_data.append([f"q{n+1}) Total", 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 dprint(first = "Total points at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +")",\n last=""+str(report.obtained)+"/"+str(report.possible), nL = report.nL)\n\n # print(f"Completed at "+ dt_string + " (" + plrl(minutes, "minute") + ", "+ plrl(seconds, "second") +"). Total")\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\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\n if hasattr(m, \'__file__\') and not hasattr(m, \'__path__\'): # Importing a simple file: 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) if not module_import else 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 print("")\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, module_import = report._import_base_relative()\n\n # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package)\n nimp[\'report_relative_location\'] = report_relative_location\n nimp[\'report_module_specification\'] = module_import\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 unmodified 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\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/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b5a3c46 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/pytransform/__init__.py b/pytransform/__init__.py deleted file mode 100644 index f656a22..0000000 --- a/pytransform/__init__.py +++ /dev/null @@ -1,454 +0,0 @@ -# These module alos are used by protection code, so that protection -# code needn't import anything -import os -import platform -import sys -import struct - -# Because ctypes is new from Python 2.5, so pytransform doesn't work -# before Python 2.5 -# -from ctypes import cdll, c_char, c_char_p, c_int, c_void_p, \ - pythonapi, py_object, PYFUNCTYPE, CFUNCTYPE -from fnmatch import fnmatch - -# -# Support Platforms -# -plat_path = 'platforms' - -plat_table = ( - ('windows', ('windows', 'cygwin-*')), - ('darwin', ('darwin', 'ios')), - ('linux', ('linux*',)), - ('freebsd', ('freebsd*', 'openbsd*')), - ('poky', ('poky',)), -) - -arch_table = ( - ('x86', ('i?86', )), - ('x86_64', ('x64', 'x86_64', 'amd64', 'intel')), - ('arm', ('armv5',)), - ('armv6', ('armv6l',)), - ('armv7', ('armv7l',)), - ('ppc64', ('ppc64le',)), - ('mips32', ('mips',)), - ('aarch32', ('aarch32',)), - ('aarch64', ('aarch64', 'arm64')) -) - -# -# Hardware type -# -HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_IPV6, HT_DOMAIN = range(5) - -# -# Global -# -_pytransform = None - - -class PytransformError(Exception): - pass - - -def dllmethod(func): - def wrap(*args, **kwargs): - return func(*args, **kwargs) - return wrap - - -@dllmethod -def version_info(): - prototype = PYFUNCTYPE(py_object) - dlfunc = prototype(('version_info', _pytransform)) - return dlfunc() - - -@dllmethod -def init_pytransform(): - major, minor = sys.version_info[0:2] - # Python2.5 no sys.maxsize but sys.maxint - # bitness = 64 if sys.maxsize > 2**32 else 32 - prototype = PYFUNCTYPE(c_int, c_int, c_int, c_void_p) - init_module = prototype(('init_module', _pytransform)) - ret = init_module(major, minor, pythonapi._handle) - if (ret & 0xF000) == 0x1000: - raise PytransformError('Initialize python wrapper failed (%d)' - % (ret & 0xFFF)) - return ret - - -@dllmethod -def init_runtime(): - prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) - _init_runtime = prototype(('init_runtime', _pytransform)) - return _init_runtime(0, 0, 0, 0) - - -@dllmethod -def encrypt_code_object(pubkey, co, flags, suffix=''): - _pytransform.set_option(6, suffix.encode()) - prototype = PYFUNCTYPE(py_object, py_object, py_object, c_int) - dlfunc = prototype(('encrypt_code_object', _pytransform)) - return dlfunc(pubkey, co, flags) - - -@dllmethod -def generate_license_file(filename, priname, rcode, start=-1, count=1): - prototype = PYFUNCTYPE(c_int, c_char_p, c_char_p, c_char_p, c_int, c_int) - dlfunc = prototype(('generate_project_license_files', _pytransform)) - return dlfunc(filename.encode(), priname.encode(), rcode.encode(), - start, count) if sys.version_info[0] == 3 \ - else dlfunc(filename, priname, rcode, start, count) - - -@dllmethod -def generate_license_key(prikey, keysize, rcode): - prototype = PYFUNCTYPE(py_object, c_char_p, c_int, c_char_p) - dlfunc = prototype(('generate_license_key', _pytransform)) - return dlfunc(prikey, keysize, rcode) if sys.version_info[0] == 2 \ - else dlfunc(prikey, keysize, rcode.encode()) - - -@dllmethod -def get_registration_code(): - prototype = PYFUNCTYPE(py_object) - dlfunc = prototype(('get_registration_code', _pytransform)) - return dlfunc() - - -@dllmethod -def get_expired_days(): - prototype = PYFUNCTYPE(py_object) - dlfunc = prototype(('get_expired_days', _pytransform)) - return dlfunc() - - -@dllmethod -def clean_obj(obj, kind): - prototype = PYFUNCTYPE(c_int, py_object, c_int) - dlfunc = prototype(('clean_obj', _pytransform)) - return dlfunc(obj, kind) - - -def clean_str(*args): - tdict = { - 'str': 0, - 'bytearray': 1, - 'unicode': 2 - } - for obj in args: - k = tdict.get(type(obj).__name__) - if k is None: - raise RuntimeError('Can not clean object: %s' % obj) - clean_obj(obj, k) - - -def get_hd_info(hdtype, name=None): - if hdtype not in range(HT_DOMAIN + 1): - raise RuntimeError('Invalid parameter hdtype: %s' % hdtype) - size = 256 - t_buf = c_char * size - buf = t_buf() - cname = c_char_p(0 if name is None - else name.encode('utf-8') if hasattr('name', 'encode') - else name) - if (_pytransform.get_hd_info(hdtype, buf, size, cname) == -1): - raise PytransformError('Get hardware information failed') - return buf.value.decode() - - -def show_hd_info(): - return _pytransform.show_hd_info() - - -def assert_armored(*names): - prototype = PYFUNCTYPE(py_object, py_object) - dlfunc = prototype(('assert_armored', _pytransform)) - - def wrapper(func): - def wrap_execute(*args, **kwargs): - dlfunc(names) - return func(*args, **kwargs) - return wrap_execute - return wrapper - - -def get_license_info(): - info = { - 'ISSUER': None, - 'EXPIRED': None, - 'HARDDISK': None, - 'IFMAC': None, - 'IFIPV4': None, - 'DOMAIN': None, - 'DATA': None, - 'CODE': None, - } - rcode = get_registration_code().decode() - if rcode.startswith('*VERSION:'): - index = rcode.find('\n') - info['ISSUER'] = rcode[9:index].split('.')[0].replace('-sn-1.txt', '') - rcode = rcode[index+1:] - - index = 0 - if rcode.startswith('*TIME:'): - from time import ctime - index = rcode.find('\n') - info['EXPIRED'] = ctime(float(rcode[6:index])) - index += 1 - - if rcode[index:].startswith('*FLAGS:'): - index += len('*FLAGS:') + 1 - info['FLAGS'] = ord(rcode[index - 1]) - - prev = None - start = index - for k in ['HARDDISK', 'IFMAC', 'IFIPV4', 'DOMAIN', 'FIXKEY', 'CODE']: - index = rcode.find('*%s:' % k) - if index > -1: - if prev is not None: - info[prev] = rcode[start:index] - prev = k - start = index + len(k) + 2 - info['CODE'] = rcode[start:] - i = info['CODE'].find(';') - if i > 0: - info['DATA'] = info['CODE'][i+1:] - info['CODE'] = info['CODE'][:i] - return info - - -def get_license_code(): - return get_license_info()['CODE'] - - -def get_user_data(): - return get_license_info()['DATA'] - - -def _match_features(patterns, s): - for pat in patterns: - if fnmatch(s, pat): - return True - - -def _gnu_get_libc_version(): - try: - prototype = CFUNCTYPE(c_char_p) - ver = prototype(('gnu_get_libc_version', cdll.LoadLibrary('')))() - return ver.decode().split('.') - except Exception: - pass - - -def format_platform(platid=None): - if platid: - return os.path.normpath(platid) - - plat = platform.system().lower() - mach = platform.machine().lower() - - for alias, platlist in plat_table: - if _match_features(platlist, plat): - plat = alias - break - - if plat == 'linux': - cname, cver = platform.libc_ver() - if cname == 'musl': - plat = 'musl' - elif cname == 'libc': - plat = 'android' - elif cname == 'glibc': - v = _gnu_get_libc_version() - if v and len(v) >= 2 and (int(v[0]) * 100 + int(v[1])) < 214: - plat = 'centos6' - - for alias, archlist in arch_table: - if _match_features(archlist, mach): - mach = alias - break - - if plat == 'windows' and mach == 'x86_64': - bitness = struct.calcsize('P'.encode()) * 8 - if bitness == 32: - mach = 'x86' - - return os.path.join(plat, mach) - - -# Load _pytransform library -def _load_library(path=None, is_runtime=0, platid=None, suffix='', advanced=0): - path = os.path.dirname(__file__) if path is None \ - else os.path.normpath(path) - - plat = platform.system().lower() - name = '_pytransform' + suffix - if plat == 'linux': - filename = os.path.abspath(os.path.join(path, name + '.so')) - elif plat == 'darwin': - filename = os.path.join(path, name + '.dylib') - elif plat == 'windows': - filename = os.path.join(path, name + '.dll') - elif plat == 'freebsd': - filename = os.path.join(path, name + '.so') - else: - raise PytransformError('Platform %s not supported' % plat) - - if platid is not None and os.path.isfile(platid): - filename = platid - elif platid is not None or not os.path.exists(filename) or not is_runtime: - libpath = platid if platid is not None and os.path.isabs(platid) else \ - os.path.join(path, plat_path, format_platform(platid)) - filename = os.path.join(libpath, os.path.basename(filename)) - - if not os.path.exists(filename): - raise PytransformError('Could not find "%s"' % filename) - - try: - m = cdll.LoadLibrary(filename) - except Exception as e: - if sys.flags.debug: - print('Load %s failed:\n%s' % (filename, e)) - raise - - # Removed from v4.6.1 - # if plat == 'linux': - # m.set_option(-1, find_library('c').encode()) - - if not os.path.abspath('.') == os.path.abspath(path): - m.set_option(1, path.encode() if sys.version_info[0] == 3 else path) - - # Required from Python3.6 - m.set_option(2, sys.byteorder.encode()) - - if sys.flags.debug: - m.set_option(3, c_char_p(1)) - m.set_option(4, c_char_p(not is_runtime)) - - # Disable advanced mode by default - m.set_option(5, c_char_p(not advanced)) - - # Set suffix for private package - if suffix: - m.set_option(6, suffix.encode()) - - return m - - -def pyarmor_init(path=None, is_runtime=0, platid=None, suffix='', advanced=0): - global _pytransform - _pytransform = _load_library(path, is_runtime, platid, suffix, advanced) - return init_pytransform() - - -def pyarmor_runtime(path=None, suffix='', advanced=0): - if _pytransform is not None: - return - - try: - pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced) - init_runtime() - except Exception as e: - if sys.flags.debug or hasattr(sys, '_catch_pyarmor'): - raise - sys.stderr.write("%s\n" % str(e)) - sys.exit(1) - - -# ---------------------------------------------------------- -# End of pytransform -# ---------------------------------------------------------- - -# -# Not available from v5.6 -# - - -def generate_capsule(licfile): - prikey, pubkey, prolic = _generate_project_capsule() - capkey, newkey = _generate_pytransform_key(licfile, pubkey) - return prikey, pubkey, capkey, newkey, prolic - - -@dllmethod -def _generate_project_capsule(): - prototype = PYFUNCTYPE(py_object) - dlfunc = prototype(('generate_project_capsule', _pytransform)) - return dlfunc() - - -@dllmethod -def _generate_pytransform_key(licfile, pubkey): - prototype = PYFUNCTYPE(py_object, c_char_p, py_object) - dlfunc = prototype(('generate_pytransform_key', _pytransform)) - return dlfunc(licfile.encode() if sys.version_info[0] == 3 else licfile, - pubkey) - - -# -# Deprecated functions from v5.1 -# -@dllmethod -def encrypt_project_files(proname, filelist, mode=0): - prototype = PYFUNCTYPE(c_int, c_char_p, py_object, c_int) - dlfunc = prototype(('encrypt_project_files', _pytransform)) - return dlfunc(proname.encode(), filelist, mode) - - -def generate_project_capsule(licfile): - prikey, pubkey, prolic = _generate_project_capsule() - capkey = _encode_capsule_key_file(licfile) - return prikey, pubkey, capkey, prolic - - -@dllmethod -def _encode_capsule_key_file(licfile): - prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) - dlfunc = prototype(('encode_capsule_key_file', _pytransform)) - return dlfunc(licfile.encode(), None) - - -@dllmethod -def encrypt_files(key, filelist, mode=0): - t_key = c_char * 32 - prototype = PYFUNCTYPE(c_int, t_key, py_object, c_int) - dlfunc = prototype(('encrypt_files', _pytransform)) - return dlfunc(t_key(*key), filelist, mode) - - -@dllmethod -def generate_module_key(pubname, key): - t_key = c_char * 32 - prototype = PYFUNCTYPE(py_object, c_char_p, t_key, c_char_p) - dlfunc = prototype(('generate_module_key', _pytransform)) - return dlfunc(pubname.encode(), t_key(*key), None) - -# -# Compatible for PyArmor v3.0 -# -@dllmethod -def old_init_runtime(systrace=0, sysprofile=1, threadtrace=0, threadprofile=1): - '''Only for old version, before PyArmor 3''' - pyarmor_init(is_runtime=1) - prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int) - _init_runtime = prototype(('init_runtime', _pytransform)) - return _init_runtime(systrace, sysprofile, threadtrace, threadprofile) - - -@dllmethod -def import_module(modname, filename): - '''Only for old version, before PyArmor 3''' - prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p) - _import_module = prototype(('import_module', _pytransform)) - return _import_module(modname.encode(), filename.encode()) - - -@dllmethod -def exec_file(filename): - '''Only for old version, before PyArmor 3''' - prototype = PYFUNCTYPE(c_int, c_char_p) - _exec_file = prototype(('exec_file', _pytransform)) - return _exec_file(filename.encode()) diff --git a/pytransform/__pycache__/__init__.cpython-36.pyc b/pytransform/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 75bb2539be65062814dda6c5080880056d0f8c70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11679 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 diff --git a/pytransform/__pycache__/__init__.cpython-38.pyc b/pytransform/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 9aaff7fb561fefe38907458054c4c1944bed1091..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11349 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 bq%+w)*<;xtyRG+r7Vib!8b`@y)<*gtFcsJ= diff --git a/pytransform/_pytransform.dll b/pytransform/_pytransform.dll deleted file mode 100644 index b1af3263115ebab5c213656cf1daf87b3510116b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1368590 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%0DKl6{QX*q$K@cXc&w&ttbf3q zNIY0j;&Gy_9gi>sjh~;6hKFjD1CI}&Md?ppfxN)ulN=i!L%=8S=#P}Z!-td;k2^)4 zg~w$v@aO?P3lIL>uf(GR2r3>`bdB{7m=lQy3rak8wXx$d8G^>9zedADHOhg<tI(q0 zF$?knkL8!z@aPLZfk$_w1RnPz<-}u%$g}XskAcUf;Ir@;Mc?_Ac(egQ#p54zjr9+h z6Nv{4N<6-8ZO7v;5HwazkA{b8lmm}vp+&(X0C|DOKQFQ2(HVRKkBgBKc-)SZ6OX<k z&%z@w1|H{v&%%R0CN1$u0zt*&8M?;$2h54Yg9RlXAE(>#xEX@R`%gu~Lp92Q$5d!( zoE<obuhC*(N?;328L#<osfHTgbet^L%{LQ&`KT*zZ@tAuC!3b`!5O#K(Q^5CdTPwY z77hTDO&>a3Oh-sA&BCAM9{gERgg>C=XX4jbQGu70b^R)wwgV0H!m{RH2Lhj*73xlh zXoYc}YUnZ|>3_ZuCvF`H{;I#rFScm@O0ZTi*BYcA9J6#ZOfmk22bTKO)6H7?5GiLZ z%|o8?L)48o7b_L!z~&y3YJu%b{FXEqrccF7K1L6e82lBr)Pfz+0(Umyn^VSo>_mZg zqhNNRdh~S7zZ{3};Gly^!J1&YhYu0c`Kg5#cpj9H-{XfH^@-&6NTd?eU<;#SG@eu* zdTb4wLE0kQ4AL6#>rbC1g0!PJzuAXN5vhxja$+z?<XMs0NpYNb&GW9yz-L7&e-&Lu z>TnR$NZm=-4SL|u(IS-vWu&$vMrEXX57$O3I3)rcg-12YQ5)a>$<}h$PcuRJ4MC~Q z3@3w6wA}lV5_r6hloOAz$g_&hje*A<;Ir`HkH||ria}8E_=c_<^fH(e*>YJ>;&D1L zDtOGtwQ(#I4G+~Q2Ob}%F&>TaaE!igMF%>STnU5v8LRNf`ocruzQ<N48Nohul4|PG zH}JMUI8(B~Nl?hs0rWPg3_e;w^Y6i%LdJL49<uZ%YF9Vm(d5!nN)142^z?zgogL@t zI1?VntagBkR4SBI;y_nc*`9vgp3OK=qiKV$uH&#;?ppC(SA4Y*=(_Z)xoOaOu`kf& zMvW?n&PAZZG%!Lyn{|ldz0zRyL$7)RB<77*4nM6&hFS?jfC?&MZ%0(Z*+sVp$1u0A zwx>D%JmV1P00I4KWY)t3986hlT!qFgWMi2V?$r|{(PD6jb}vXbKC8R&Bf_mKsRP67 z_~xo8H$^`OdBzz~9XuFbb^#SNXvkndS~#y1=b7Me+Y;YloS6AKaPl2a(EO)!B3^s) zeI({h0uMCOH)Tn4`X-#5?mL{QJ$Wjq$jHQx3#S(s;*-bpvFU<ZEj$}}1Py1>*RSb7 zz~{k-11QpQ_&1IpbekVY*8CMj;E(Qx^3x8j##NSge5myokPBs}p;yjEPu7vXQ%Q1o zPTSO)9%tiY$KI>?iVnR&Gc59S)ciEy&@mS^yIY6GdNO?5agbq(Z%5jNzXo=BTa+KU zPxC)X<#|1sfrH+m<wv|YV;C9yB{@QFD`JSTY-*i>!%A^beqAAd37L_|-Pnvlvi9Oi zAOWK?w5NVWw@GL~6+Q%r3urET0|yZM%3dxnO!Jogy?jy{E}q3t@{MKFaoE1G25j7a z;sDXpw7^5CqVkDJL$ts+q#D1)8OFY{4_WvFNa11uev)o1d%dv+MOihnUVJt{6g`1- zs#K~k%P?&Frtepxsa%##Uzw&hWt@f5z>zXlguR=?-tCF&fFtskq_%WTpN?43D^KO4 z^RwYDef7X9ogBt>s>{v4_=~!SHk9F_PoCTeIp5AqoadMgHupQk?)H?|BR;{U#{ny% zAu9CCnAHuyYr_jWkRlnczMaGI4d_ywqgjYw{^$Zx$!PiO*612V;vXWf7y;?^lmu4? z$KtH!O<=?X0mn=3Uy%%B(tU@=Y5r%yN*zEPMa;GNfwh|dAhN>ls|$2(>KVw@s(R(Z z$SnNk;5QS$m*O`Azn9=Q9lsaj7ZKKrd`o$QoEpntLqK+_-n+VmOCNIzcM~b(JX;4x zha(SxwKFvYQxS?KXoRH**BgCx!)xh0pFA+A-kE~!kT1jvbxrUHrF7;v6t@y|8k4*e z2`aKfRiwbAZ(q(}7>5uP`Wf_R8EPBo*=${aJiJ$eNx{xkqZq#wKXCR1I7>cN(qYk} zLiK!19vT<xAD_SgNg_t3;P>q;Ms@TIdr{BwqZ#CpY8Z~|B{g(rAS@_E{Y}DwH1&Y9 zAJ<<t*I%A68TIFVgzD~Ru!nl(Q}?o&lQ^hG^M6Xoo~fapZo))k8MleV8ncq{sfw4E zqk`&NMLN9{d1eI_qJny&f{IW<{d{%BwK!jW2I#8wD3Cwk;6uKspKN`c;IY-uJ%Y~l zGlZZpts(`4rUHZB=R;9HrvpU&q2u_I32|rrq#@6$pZ>&p9ashYR}lQ(4lt<@V%dN+ zpCRoM+=Rk&^p9E!H~Il~D&2IW4*76HOl>|!E^00l5`-uCGKpV*XBnB7LUnN`({Mug z3nGuI<~0)Y`SI8EQAPfu4}pAt6ytQ~O5~ZyX9M{hAfFH9yHKy7pA|(y6UL$`W$MRT zh~bYNk!|7-@=Rv}0|}suk4b<UpO@!#pM@NKIT)Shyo)>w-Mb*n{q>;5<tSmu;Na?k z6+4U30K$GxPjndY)B}4xxz#vMsV92M?EP;78g7B>WqWBNL;zJu3Y+TeN7JB<>H<f* z@^6Z~rn;#nxg2&U6Qdt}p<$Kaa%WQwx;;H{skZ?~y^Dr}U_C0RzGrG0M*aGp8FaK# zeb2PG#Ix|@9@uN()%877imo1b?dqN#b_Vy>=;wyhlYBqMUAPj1Zeit+j#SrBLw;o- zold2g9WXjnN^@V4vcXLUM(HoZC*RI2YJ>D(Q?EOMNe9~5OwM;hMaq{F20Ek4>^4qp z#~8q{F|N&+?Qme0nf%8vI}SOFL4+Ak5*EXD7~{^OUy0gQ=1B?G5`mGDgnAHG_iDMb zo@#<3KDY`KHXuPD5i%j@gb*Wz-*gg2zs188m0-qHWpy%l5|468K&fnq{?cI}1mq@g zo|`K8PLb57f<&s(=Ja96lVRwtsH2GEDG6cQMY7)rcB4v+?*wMJEKQ*K3h6(sS%0!f znyOvmGVJ%z!65L*4GIru_}muT`_IIdE>UQ1H5WU5(2(G#gAw{WiKEz)f@mL(`%FR( zIW7u(C_?|@#=H~>B2ZnzJ>;k8$Dt8<|4r<3o5@eI>Jgvn_zXosOA19dEGRLHM~%)E z2a?f(0I`(FU;d^9u5YAk{SsJ*IxI$pEy}sIU|5$*QmHALb?8dJn7=Z$1Rijtdwb1W zgu-&SpgQU6ANm5OxN#C&enlMtBtgTZv=A8S;}crC3hHqqGIa{@7X1&&O%!szrqi`4 z^c0IGv4ovYr(X+A3_kVeiDVkz-q!-pLO75=ij1UbG7j9<s!pfxr2GR}INFV8HZ=Lx zmah!DJ(Z;%kG>5?&;j2bYO1%PTPr_W5WaiEqiFr#hwolXpVO%M*{uk?s|w@_0qv>R zq1Cq|r^O~;ZG3QtRz>>tr@%^TTY)R67iMYxu}Hz4F8CToeq3d|Z)IGwm4Ts>h#H)< zI^GpJ-B;@lKi+im<jF%D&)d+x9RIvKf-7rwq`h8V4kwCMh6l!LMVnpKzAXuV9`5N_ z@Vz!=DdL_0!#6-Ez)-7`@arbLC&uIM2gms|!8PBG&c*<g$4$w2l^RPGTp+9qr6??O zlO_`FPNKM-YZ@H<Y+!IDCXT_?xz+kfjIT7pO@buV+~4CcZq(wtA>Q-C?sK%NwDO~u zcu#G_bTR!LWZ<eS4OHCNl<lP3iS%K&)RrI3@-_yxJh~@$ZTS^h-jv4e<%Oqam38pd z#1(u!z3f+5Q?O>zXTe{A?1k&JD)*_C?v_j}`?YaNU~SoVjSJa}#&<9u6ZdnOusa7s zW<HvK7j1cb;YHpNg_j0D_m<$?_5z=t<2&5qfpZ(rA5AwO+H%B*w?p`v4Zb5S9y~Ri zS{^RFW<%Tt-{GN;ZWUckT_y)yZuN9uU1speu;8z^jJVlw!F!g!Dz5QWUsIws#RnV^ zf;<%u9?0E-wEHBrc3`vF)zY<uI?l!(=!PJtk4H}2stfB^_jYCbR>eWX1DE@b-Sk+l z*~C~BUDk_W%-!fa)`D)cRwi8dRj?s$NAQUL7}^7R^0IddcHiG>b?+uLFrx&ON+!71 zh)L-cG}6gQ>5E(Or6tgtjEnF`jNjt^7tO0AG!`>&ujj_{E*EKmDKHdqm<>~Y?<NDK zW%HDD?tA7fg?C6U;-ai0=7;xFNXWqvI=LIY6L~zd!AypM)%!=`x)OPqt$>GZ7K|Qb zng|oRrpk1|jQY;jU4lo@P&75g<A6|1ed@h$lT&CEF>eRHdY2uc$fh14cq}v!U)JmE zP4pd2D(l9xY;S(JB);HRZR*WnsO;?u4a`RshI?hBZn?dcqH|AfYC&`IxK)QX#H~gx zyF$ZK0$a-dWnh$-?G1|$86n_|-YE-};6dPaM2C4!V6FFV-;ubo$6#v~_huma>N18I zcp;?;)SE!n{FC9o?+BJG{G3Rb?!Cx	h{cInxe^P`F8kLIrpb3MIJIXMT6G2}}an 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	^w94lTb<o|{&_nu1^XLk%MP0~e&lgJl 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%0DF_%+5HO@5V8{$XWw1So&cTi}{E{DO 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ž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	Ry>| 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≶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 UAD%#K{@ZJO>Vp%wf_=R3Z_os&EC2ui diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f19f26e --- /dev/null +++ b/setup.py @@ -0,0 +1,49 @@ +# Use this guide: +# https://packaging.python.org/tutorials/packaging-projects/ + +# from unitgrade2.version import __version__ +import setuptools +with open("src/unitgrade_private2/version.py", "r", encoding="utf-8") as fh: + __version__ = fh.read().strip().split(" = ")[1].strip()[1:-1] +# long_description = fh.read() + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setuptools.setup( + name="unitgrade-devel", + version=__version__, + author="Tue Herlau", + author_email="tuhe@dtu.dk", + description="A set of tools to develop unitgrade reports and evaluate them", + long_description=long_description, + long_description_content_type="text/markdown", + license="MIT", + url='https://lab.compute.dtu.dk/tuhe/unitgrade_private', + project_urls={ + "Bug Tracker": "https://lab.compute.dtu.dk/tuhe/unitgrade_private/issues", + }, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + package_dir={"": "src"}, + packages=setuptools.find_packages(where="src"), + python_requires=">=3.8", + install_requires=['numpy', "unitgrade", "codesnipper", 'tabulate', 'tqdm', "pyfiglet", "colorama", "coverage", "compress_pickle"], +) + +# setup( +# name='unitgrade', +# version=__version__, +# packages=['unitgrade2'], +# url=, +# license='MIT', +# author='Tue Herlau', +# author_email='tuhe@dtu.dk', +# description=""" +# A student homework/exam evaluation framework build on pythons unittest framework. This package contains all files required to run unitgrade tests as a student. To develop tests, please use unitgrade_private. +# """, +# include_package_data=False, +# ) diff --git a/src/unitgrade_devel.egg-info/PKG-INFO b/src/unitgrade_devel.egg-info/PKG-INFO new file mode 100644 index 0000000..ab8dded --- /dev/null +++ b/src/unitgrade_devel.egg-info/PKG-INFO @@ -0,0 +1,317 @@ +Metadata-Version: 2.1 +Name: unitgrade-devel +Version: 0.0.1 +Summary: A set of tools to develop unitgrade reports and evaluate them +Home-page: https://lab.compute.dtu.dk/tuhe/unitgrade_private +Author: Tue Herlau +Author-email: tuhe@dtu.dk +License: MIT +Project-URL: Bug Tracker, https://lab.compute.dtu.dk/tuhe/unitgrade_private/issues +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +License-File: LICENSE + +# Unitgrade-private +Unitgrade is an automatic report and exam evaluation framework that enables instructors to offer automatically evaluated programming assignments. + Unitgrade is build on pythons `unittest` framework so that the tests can be specified in a familiar syntax and will integrate with any modern IDE. What it offers beyond `unittest` is the ability to collect tests in reports (for automatic evaluation) and an easy and 100% safe mechanism for verifying the students results and creating additional, hidden tests. A powerful cache system allows instructors to automatically create test-answers based on a working solution. + + - 100% Python `unittest` compatible + - No external configuration files: Just write a `unittest` + - No unnatural limitations: Use any package or framework. If you can `unittest` it, it works. + - Granular security model: + - Students get public `unittests` for easy development of solutions + - Students get a tamper-resistant file to create submissions which are uploaded + - Instructors can automatically verify the students solution using a Docker VM and run hidden tests + - Tests are quick to run and will integrate with your IDE + +**Note: This is the development version of unitgrade. If you are a student, please see http://gitlab.compute.dtu.dk/tuhe/unitgrade.** + +# Using unitgrade +The examples can be found in the `/examples/` directory: https://gitlab.compute.dtu.dk/tuhe/unitgrade_private/examples + +## A simple example +Unitgrade makes the following assumptions: + - Your code is in python + - Whatever you want to do can be specified as a `unittest` + +Although not required, it is recommended you maintain two version of the code: + - A fully-working version (i.e. all tests pass) + - A public version distributed to students (some code removed)) + +In this example, I will use `snipper` (see http://gitlab.compute.dtu.dk/tuhe/snipper) to synchronize the two versions automatically. +Let's look at an example. You need three files +``` +instructor/cs101/homework.py # This contains the students homework +instructor/cs101/report1.py # This contains the tests +instructor/cs101/deploy.py # A private file to deploy the tests +``` + +### The homework +The homework is just any old python code you would give to the students. For instance: +```python +def reverse_list(mylist): #!f + """ + 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)) + +def add(a,b): #!f + """ Given two numbers `a` and `b` this function should simply return their sum: + > add(a,b) = a+b """ + return a+b + +if __name__ == "__main__": + # Problem 1: Write a function which add two numbers + print(f"Your result of 2 + 2 = {add(2,2)}") + print(f"Reversing a small list", reverse_list([2,3,5,7])) +``` +### The test: +The test consists of individual problems and a report-class. The tests themselves are just regular Unittest (we will see a slightly smarter idea in a moment). For instance: + +```python +from looping import reverse_list, add +import unittest + + +class Week1(unittest.TestCase): + def test_add(self): + self.assertEqual(add(2, 2), 4) + self.assertEqual(add(-100, 5), -95) + + def test_reverse(self): + self.assertEqual(reverse_list([1, 2, 3]), [3, 2, 1]) + +``` +A number of tests can be collected into a `Report`, which will allow us to assign points to the tests and use the more advanced features of the framework later. A complete, minimal example: + +```python +from src.unitgrade2.unitgrade2 import Report +from src.unitgrade2 import evaluate_report_student +from looping import reverse_list, add +import unittest + + +class Week1(unittest.TestCase): + def test_add(self): + self.assertEqual(add(2, 2), 4) + self.assertEqual(add(-100, 5), -95) + + def test_reverse(self): + self.assertEqual(reverse_list([1, 2, 3]), [3, 2, 1]) + + +import cs101 + + +class Report1(Report): + title = "CS 101 Report 1" + questions = [(Week1, 10)] # Include a single question for 10 credits. + pack_imports = [cs101] + + +if __name__ == "__main__": + # Uncomment to simply run everything as a unittest: + # unittest.main(verbosity=2) + evaluate_report_student(Report1()) +``` + +### Deployment +The above is all you need if you simply want to use the framework as a self-check: Students can run the code and see how well they did. +In order to begin using the framework for evaluation we need to create a bit more structure. We do that by deploying the report class as follows: +```python +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="../programs", dest_dir="../../students/programs", clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py']) + +``` + - The first line creates the `report1_grade.py` script and any additional data files needed by the tests (none in this case) + - The second line set up the students directory (remember, we have included the solutions!) and remove the students solutions. You can check the results in the students folder. + +### Using the framework as a student +You can now upload the `student' directory to the students. The students can run their tests either by running `cs101.report1` in their IDE or by typing: +``` +python -m cs101.report1 +``` +in the command line. This produces a detailed output of the test and the program is 100% compatible with a debugger. When the students are happy with their output they can run (using command line or IDE): +``` +python -m cs101.report1_grade +``` +This runs an identical set of tests, but produces a `.token` file the students can upload to get credit. + - The reason to have a seperate `report1_grade.py` script is to avoid accidential removal of tests. + - The `report1_grade.py` includes all tests and the main parts of the framework and is obfuscated by default. You can apply a much strong level of protection by using e.g. `pyarmor`. + - The `report1_token.token` file includes the outcome of the tests, the time taken, and all python source code in the package. In other words, the file can be used for manual grading, for plagirism detection and for detecting tampering. + - You can easily use the framework to include output of functions. + - See below for how to validate the students results + +### How safe is this? +Cheating within the framework is probably best done by manually editing the `.token`-file or by creating a broken set of tests. This involves risk of being trivially detected, for instance because tests have the wrong runtime, but more importantly +the framework automatically pack all the used source code and so if a student is cheating, there is no way to hide it for an instructor who looks at the results. If the +program is used in conjunction with automatic plagiarism software, cheating therefore involves both breaking the framework, and creating 'false' solutions which statistically match other students solutions, and then hope nobody bothers to check the output. + The bottom line is that I think plain old plagiarism is a much more significant risk, and one the framework reduces relative to other project work +by demanding the source code is included. + +If this is not enough you have two options: You can either use `pyarmor` to create a **very** difficult challenge for a prospective hacker, or you can simply validate the students results as shown below. + + +## Example 2: The framework +One of the main advantages of `unitgrade` over web-based autograders it that tests are really easy to develop and maintain. To take advantage of this, we simply change the class the questions inherit from to `UTestCase` (this is still a `unittest.TestCase`) and we can make use of the chache system. As an example: + +```python +class Week1(UTestCase): + """ The first question for week 1. """ + def test_add(self): + from cs102.homework1 import add + self.assertEqualC(add(2,2)) + self.assertEqualC(add(-100, 5)) + + def test_reverse(self): + from cs102.homework1 import reverse_list + """ Reverse a list """ # Add a title to the test. + self.assertEqualC(reverse_list([1,2,3])) +``` +Note we have changed the test-function to `self.assertEqualC` (the `C` is for cache) and dropped the expected result. What `unitgrade` will do +is to evaluate the test *on the working version of the code*, compute the results of the test, and allow them to be available to the user. All this happens in the `deploy.py` script from before. + +There are other ways to send the output to the user. For instance: +```python +class Question2(UTestCase): + """ Second problem """ + @cache + def my_reversal(self, ls): + # The '@cache' decorator ensures the function is not run on the *students* computer + # Instead the code is run on the teachers computer and the result is passed on with the + # other pre-computed results -- i.e. this function will run regardless of how the student happens to have + # implemented reverse_list. + from cs102.homework1 import reverse_list + return reverse_list(ls) + + def test_reverse_tricky(self): + ls = ("butterfly", 4, 1) + ls2 = self.my_reversal( tuple(ls) ) # This will always produce the right result. + ls3 = self.my_reversal( tuple([1,2,3]) ) # Also works; the cache respects input arguments. + self.assertEqualC(self.my_reversal( tuple(ls2) )) # This will actually test the students code. + return ls +``` +This code showcase the `@cache` decorator. What it does is it computes the output of the function on your computer and allows that +result to be availble to students (the input arguments must be immutable). This may seem odd, but it is very helpful + - if you have exercises that depend on each other, and you want students to have access to the expected result of older methods which they may not have implemented correctly. + - If you want to use functions the students write to set up appropriate tests without giving away the solution + +Furthermore, one of the test now has a return value, which will be automatically included in the `.token` file. + +## Example 3: Hidden and secure tests +To use `unitgrade` as a true autograder you both want security nobody tampered with your tests (or the `.token` files), and +also that the students implementations didn't just detect what input was being used and +return the correct answer. To do that you need hidden tests and external validation. + +Our new testclass looks like this: + +```python +from src.unitgrade2.unitgrade2 import UTestCase, Report, hide +from src.unitgrade2 import evaluate_report_student + + +class Week1(UTestCase): + """ The first question for week 1. """ + + def test_add(self): + from cs103.homework1 import add + self.assertEqualC(add(2, 2)) + self.assertEqualC(add(-100, 5)) + + @hide + def test_add_hidden(self): + # This is a hidden test. The @hide-decorator will allow unitgrade to remove the test. + # See the output in the student directory for more information. + from cs103.homework1 import add + self.assertEqualC(add(2, 2)) + + +import cs103 + + +class Report3(Report): + title = "CS 101 Report 3" + questions = [(Week1, 20)] # Include a single question for 10 credits. + pack_imports = [cs103] + + +if __name__ == "__main__": + evaluate_report_student(Report3()) +``` + +This test is stored as `report3_complete.py`. Note the `@hide` decorator which will tell the framework that test (and all code) should be hidden from the user. + +In order to use the hidden tests, we first need a version for the students without them. This can be done by changing the `deploy.py` script as follows: + +```python +def deploy_student_files(): + setup_grade_file_report(Report3, minify=False, obfuscate=False, execute=False) + Report3.reset() + + fout, ReportWithoutHidden = remove_hidden_methods(Report3, outfile="report3.py") + setup_grade_file_report(ReportWithoutHidden, minify=False, obfuscate=False, execute=False) + sdir = "../../students/cs103" + snip_dir(source_dir="../cs103", dest_dir=sdir, clean_destination_dir=True, exclude=['__pycache__', '*.token', 'deploy.py', 'report3_complete*.py']) + return sdir + + +if __name__ == "__main__": + # Step 1: Deploy the students files and return the directory they were written to + student_directory = deploy_student_files() +``` +This script first compiles the `report3_complete_grade.py`-script (which we will use) and then +remove the hidden methods and compiles the students script `report3_grade.py`-script. Finally, we synchronize with the s +student folder, which now contains no traces of our hidden method -- not in any of the sources files or the data files. + +The next step is optional, but we quickly simulate that the student runs his script and we get a link to the `.token` file: +```python +os.system("cd ../../students && python -m cs103.report3_grade") +student_token_file = glob.glob(student_directory + "/*.token")[0] +``` +This is the file we assume the student uploads. The external validation can be carried out as follows: + +```python +def run_student_code_on_docker(Dockerfile, student_token_file): + token = docker_run_token_file(Dockerfile_location=Dockerfile, + host_tmp_dir=os.path.dirname(Dockerfile) + "/tmp", + student_token_file=student_token_file, + instructor_grade_script="report3_complete_grade.py") + with open(token, 'rb') as f: + results = pickle.load(f) + return results + +if __name__ == "__main__": + # Step 3: Compile the Docker image (obviously you will only do this once; add your packages to requirements.txt). + Dockerfile = os.path.dirname(__file__) + "/../unitgrade-docker/Dockerfile" + os.system("cd ../unitgrade-docker && docker build --tag unitgrade-docker .") + + # Step 4: Test the students .token file and get the results-token-file. Compare the contents with the students_token_file: + checked_token = run_student_code_on_docker(Dockerfile, student_token_file) + + # Let's quickly compare the students score to what we got (the dictionary contains all relevant information including code). + with open(student_token_file, 'rb') as f: + results = pickle.load(f) + print("Student's score was:", results['total']) + print("My independent evaluation of the students score was", checked_token['total']) +``` + +These steps compile a Docker image (you can easily add whatever packages you need) and runs **our** `project3_complete_grade.py` script on the **students** source code (as taken from the token file). + +The last lines load the result and compare the score -- in this case both will return 0 points, and any dissimilarity in the results should be immediate cause for concern. + + - Docker prevents students from doing mailicious things to your computer and allows the results to be reproducible by TAs. + + diff --git a/src/unitgrade_devel.egg-info/SOURCES.txt b/src/unitgrade_devel.egg-info/SOURCES.txt new file mode 100644 index 0000000..b7b0b23 --- /dev/null +++ b/src/unitgrade_devel.egg-info/SOURCES.txt @@ -0,0 +1,18 @@ +LICENSE +README.md +pyproject.toml +setup.py +src/unitgrade_devel.egg-info/PKG-INFO +src/unitgrade_devel.egg-info/SOURCES.txt +src/unitgrade_devel.egg-info/dependency_links.txt +src/unitgrade_devel.egg-info/requires.txt +src/unitgrade_devel.egg-info/top_level.txt +src/unitgrade_private2/__init__.py +src/unitgrade_private2/deployment.py +src/unitgrade_private2/docker_helpers.py +src/unitgrade_private2/hidden_create_files.py +src/unitgrade_private2/hidden_gather_upload.py +src/unitgrade_private2/token_loader.py +src/unitgrade_private2/version.py +src/unitgrade_private2/autolab/__init__.py +src/unitgrade_private2/autolab/autolab.py \ No newline at end of file diff --git a/src/unitgrade_devel.egg-info/dependency_links.txt b/src/unitgrade_devel.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/unitgrade_devel.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/unitgrade_devel.egg-info/requires.txt b/src/unitgrade_devel.egg-info/requires.txt new file mode 100644 index 0000000..70825fe --- /dev/null +++ b/src/unitgrade_devel.egg-info/requires.txt @@ -0,0 +1,9 @@ +numpy +unitgrade +codesnipper +tabulate +tqdm +pyfiglet +colorama +coverage +compress_pickle diff --git a/src/unitgrade_devel.egg-info/top_level.txt b/src/unitgrade_devel.egg-info/top_level.txt new file mode 100644 index 0000000..9da4671 --- /dev/null +++ b/src/unitgrade_devel.egg-info/top_level.txt @@ -0,0 +1 @@ +unitgrade_private2 diff --git a/unitgrade_private2/__init__.py b/src/unitgrade_private2/__init__.py similarity index 97% rename from unitgrade_private2/__init__.py rename to src/unitgrade_private2/__init__.py index 3164fd6..b233adc 100644 --- a/unitgrade_private2/__init__.py +++ b/src/unitgrade_private2/__init__.py @@ -1,5 +1,6 @@ import os import compress_pickle +# __version__ = "0.0.1" def cache_write(object, file_name, verbose=True): dn = os.path.dirname(file_name) diff --git a/unitgrade_private2/codejudge_example/__init__.py b/src/unitgrade_private2/autolab/__init__.py similarity index 100% rename from unitgrade_private2/codejudge_example/__init__.py rename to src/unitgrade_private2/autolab/__init__.py diff --git a/autolab/autolab.py b/src/unitgrade_private2/autolab/autolab.py similarity index 89% rename from autolab/autolab.py rename to src/unitgrade_private2/autolab/autolab.py index 6f9fc05..1c863c5 100644 --- a/autolab/autolab.py +++ b/src/unitgrade_private2/autolab/autolab.py @@ -4,17 +4,14 @@ 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 import os import shutil from jinja2 import Environment, FileSystemLoader import glob import pickle -from unitgrade2.unitgrade2 import Report -import inspect +from src.unitgrade2.unitgrade2 import Report from unitgrade_private2 import docker_helpers COURSES_BASE = "/home/tuhe/Autolab/courses/AutoPopulated" @@ -58,10 +55,10 @@ def zipFilesInDir(dirName, zipFileName, filter): 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) + # f2 = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/programs" + # spec1 = importlib.util.spec_from_file_location("programs", f2) + # programs = importlib.util.module_from_spec(spec1) + # spec1.loader.exec_module(programs) from importlib.machinery import SourceFileLoader foo = SourceFileLoader(mod, report_file).load_module() @@ -85,22 +82,6 @@ def run_relative(file, base): 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, INSTRUCTOR_BASE, INSTRUCTOR_GRADE_FILE, STUDENT_BASE, STUDENT_GRADE_FILE, @@ -121,8 +102,8 @@ def deploy_assignment(base_name, INSTRUCTOR_BASE, INSTRUCTOR_GRADE_FILE, STUDENT LAB_DEST = os.path.join(COURSES_BASE, base_name) - # 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" + # STUDENT_HANDOUT_DIR = os.path.dirname(STUDENT_GRADE_FILE) #"/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/programs" + # INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/programs/report5.py" # Make instructor token file. # Get the instructor result file. run_relative(INSTRUCTOR_GRADE_FILE, INSTRUCTOR_BASE) @@ -142,7 +123,7 @@ def deploy_assignment(base_name, INSTRUCTOR_BASE, INSTRUCTOR_GRADE_FILE, STUDENT 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 {os.path.dirname(STUDENT_HANDOUT_DIR)} && python -m programs.{os.path.basename(INSTRUCTOR_GRADE_FILE)[:-3]}") # os.system(f"cd {STUDENT_HANDOUT_DIR} && python {os.path.basename(INSTRUCTOR_GRADE_FILE)}") # handin_filename = os.path.basename(STUDENT_TOKEN_FILE) @@ -165,7 +146,7 @@ def deploy_assignment(base_name, INSTRUCTOR_BASE, INSTRUCTOR_GRADE_FILE, STUDENT INSTRUCTOR_REPORT_FILE = INSTRUCTOR_GRADE_FILE[:-9] + ".py" a = 234 - # /home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1.py" + # /home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/programs/report5.py" data = { 'base_name': base_name, # 'nice_name': base_name + "please", @@ -213,10 +194,10 @@ if __name__ == "__main__": print("Deploying to", COURSES_BASE) docker_build_image() - INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/cs101/report1_grade.py" + INSTRUCTOR_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/instructor/programs/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" + STUDENT_GRADE_FILE = "/home/tuhe/Documents/unitgrade_private/examples/example_simplest/students/programs/report1_grade.py" output_tar = deploy_assignment("hello4", INSTRUCTOR_BASE, INSTRUCTOR_GRADE_FILE, STUDENT_BASE, STUDENT_GRADE_FILE=STUDENT_GRADE_FILE) diff --git a/autolab/lab_template/Makefile b/src/unitgrade_private2/autolab/lab_template/Makefile similarity index 100% rename from autolab/lab_template/Makefile rename to src/unitgrade_private2/autolab/lab_template/Makefile diff --git a/autolab/lab_template/autograde-Makefile b/src/unitgrade_private2/autolab/lab_template/autograde-Makefile similarity index 100% rename from autolab/lab_template/autograde-Makefile rename to src/unitgrade_private2/autolab/lab_template/autograde-Makefile diff --git a/autolab/lab_template/autograde.tar b/src/unitgrade_private2/autolab/lab_template/autograde.tar similarity index 100% rename from autolab/lab_template/autograde.tar rename to src/unitgrade_private2/autolab/lab_template/autograde.tar diff --git a/autolab/lab_template/hello.rb b/src/unitgrade_private2/autolab/lab_template/hello.rb similarity index 100% rename from autolab/lab_template/hello.rb rename to src/unitgrade_private2/autolab/lab_template/hello.rb diff --git a/autolab/lab_template/hello.yml b/src/unitgrade_private2/autolab/lab_template/hello.yml similarity index 100% rename from autolab/lab_template/hello.yml rename to src/unitgrade_private2/autolab/lab_template/hello.yml diff --git a/autolab/lab_template/src/Makefile b/src/unitgrade_private2/autolab/lab_template/src/Makefile similarity index 100% rename from autolab/lab_template/src/Makefile rename to src/unitgrade_private2/autolab/lab_template/src/Makefile diff --git a/autolab/lab_template/src/Makefile-handout b/src/unitgrade_private2/autolab/lab_template/src/Makefile-handout similarity index 100% rename from autolab/lab_template/src/Makefile-handout rename to src/unitgrade_private2/autolab/lab_template/src/Makefile-handout diff --git a/autolab/lab_template/src/README b/src/unitgrade_private2/autolab/lab_template/src/README similarity index 100% rename from autolab/lab_template/src/README rename to src/unitgrade_private2/autolab/lab_template/src/README diff --git a/autolab/lab_template/src/README-handout b/src/unitgrade_private2/autolab/lab_template/src/README-handout similarity index 100% rename from autolab/lab_template/src/README-handout rename to src/unitgrade_private2/autolab/lab_template/src/README-handout diff --git a/autolab/lab_template/src/driver.sh b/src/unitgrade_private2/autolab/lab_template/src/driver.sh old mode 100755 new mode 100644 similarity index 100% rename from autolab/lab_template/src/driver.sh rename to src/unitgrade_private2/autolab/lab_template/src/driver.sh diff --git a/autolab/lab_template/src/driver_python.py b/src/unitgrade_private2/autolab/lab_template/src/driver_python.py similarity index 96% rename from autolab/lab_template/src/driver_python.py rename to src/unitgrade_private2/autolab/lab_template/src/driver_python.py index 1046485..b5ad50f 100644 --- a/autolab/lab_template/src/driver_python.py +++ b/src/unitgrade_private2/autolab/lab_template/src/driver_python.py @@ -55,8 +55,8 @@ def rcom(cm): start = time.time() rcom(command) # pfiles() -# for f in glob.glob(host_tmp_dir + "/cs101/*"): -# print("cs101/", f) +# for f in glob.glob(host_tmp_dir + "/programs/*"): +# print("programs/", f) # print("---") ls = glob.glob(token) # print(ls) diff --git a/autolab/lab_template/src/hello.c b/src/unitgrade_private2/autolab/lab_template/src/hello.c similarity index 100% rename from autolab/lab_template/src/hello.c rename to src/unitgrade_private2/autolab/lab_template/src/hello.c diff --git a/autolab/lab_template/src/hello.c-handout b/src/unitgrade_private2/autolab/lab_template/src/hello.c-handout similarity index 100% rename from autolab/lab_template/src/hello.c-handout rename to src/unitgrade_private2/autolab/lab_template/src/hello.c-handout diff --git a/unitgrade_private2/deployment.py b/src/unitgrade_private2/deployment.py similarity index 94% rename from unitgrade_private2/deployment.py rename to src/unitgrade_private2/deployment.py index e25bd89..1acee08 100644 --- a/unitgrade_private2/deployment.py +++ b/src/unitgrade_private2/deployment.py @@ -1,6 +1,5 @@ import inspect -from unitgrade2.unitgrade2 import methodsWithDecorator, hide -import os +from src.unitgrade2.unitgrade2 import methodsWithDecorator, hide import os import importlib diff --git a/unitgrade_private2/docker_helpers.py b/src/unitgrade_private2/docker_helpers.py similarity index 58% rename from unitgrade_private2/docker_helpers.py rename to src/unitgrade_private2/docker_helpers.py index cde3e6e..81bbd12 100644 --- a/unitgrade_private2/docker_helpers.py +++ b/src/unitgrade_private2/docker_helpers.py @@ -1,15 +1,23 @@ # from cs202courseware.ug2report1 import Report1 -# import thtools + import pickle import os import glob -# from unitgrade_private2.deployment import remove_hidden_methods -# from unitgrade_private2.hidden_gather_upload import gather_upload_to_campusnet -# from unitgrade_private2.hidden_create_files import setup_grade_file_report import shutil import time import zipfile import io +import inspect +import subprocess + +def compile_docker_image(Dockerfile, tag=None): + assert os.path.isfile(Dockerfile) + base = os.path.dirname(Dockerfile) + if tag == None: + tag = os.path.basename(base) + os.system(f"cd {base} && docker build --tag {tag} .") + return tag + def student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade_script, grade_file_relative_destination): """ @@ -20,7 +28,8 @@ def student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade :param instructor_grade_script: :return: """ - # assert os.path.exists(Dockerfile_location) + assert os.path.exists(student_token_file) + assert os.path.exists(instructor_grade_script) start = time.time() with open(student_token_file, 'rb') as f: @@ -31,15 +40,11 @@ def student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade with zipfile.ZipFile(zb) as zip: zip.extractall(host_tmp_dir) # Done extracting the zip file! Now time to move the (good) report test class into the location. - import inspect - # if ReportClass is not None: - # gscript = inspect.getfile(ReportClass)[:-3] + "_grade.py" - # else: + gscript = instructor_grade_script print(f"{sources['report_relative_location']=}") print(f"{sources['name']=}") - # student_grade_script = host_tmp_dir + "/" + sources['name'] + "/" + sources['report_relative_location'] - # instructor_grade_script = os.path.dirname(student_grade_script) + "/" + os.path.basename(gscript) + print("Now in docker_helpers.py") print(f'{gscript=}') print(f'{instructor_grade_script=}') @@ -49,50 +54,24 @@ def student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade shutil.copy(gscript, gscript_destination) # Now everything appears very close to being set up and ready to roll!. - # import thtools - - # os.path.split() d = os.path.normpath(grade_file_relative_destination).split(os.sep) d = d[:-1] + [os.path.basename(instructor_grade_script)[:-3]] - # print(f'{d=}') pycom = ".".join(d) - """ docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker python3 -m cs202courseware.ug2report1_grade """ - # dockname = os.path.basename(os.path.dirname(Dockerfile_location)) - - # tmp_grade_file = sources['name'] + "/" + sources['report_relative_location'] - # print(f'{tmp_grade_file=}') - # pycom = ".".join((sources['name'],) + os.path.split(sources['report_relative_location'])[1:-1] + (os.path.basename(gscript),)) pycom = "python3 -m " + pycom # pycom[:-3] print(f"{pycom=}") - # tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/") - # dcom = f"docker run -v {tmp_path}:/app {dockname} {pycom}" - # cdcom = f"cd {os.path.dirname(Dockerfile_location)}" - # fcom = f"{cdcom} && {dcom}" - # print("> Running docker command") - # print(fcom) - - # thtools.execute_command(fcom.split()) - # get token file: token_location = host_tmp_dir + "/" + os.path.dirname( grade_file_relative_destination ) + "/*.token" - - # host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) + "/" - # tokens = glob.glob(host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) + "/*.token") - # token_location = host_tmp_dir + "/" + os.path.dirname(tmp_grade_file) - - # for t in tokens: - # print("Source image produced token", t) elapsed = time.time() - start # print("Elapsed time is", elapsed) return pycom, token_location - pass -def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file, ReportClass=None, instructor_grade_script=None): + +def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file, instructor_grade_script=None): """ This thingy works: @@ -119,41 +98,33 @@ def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file, with zipfile.ZipFile(zb) as zip: zip.extractall(host_tmp_dir) # Done extracting the zip file! Now time to move the (good) report test class into the location. - import inspect - if ReportClass is not None: - gscript = inspect.getfile(ReportClass)[:-3] + "_grade.py" - else: - gscript = instructor_grade_script + gscript = instructor_grade_script - student_grade_script = host_tmp_dir + "/" + sources['name'] + "/" + sources['report_relative_location'] + student_grade_script = host_tmp_dir + "/" + sources['report_relative_location'] instructor_grade_script = os.path.dirname(student_grade_script) + "/"+os.path.basename(gscript) shutil.copy(gscript, instructor_grade_script) - # Now everything appears very close to being set up and ready to roll!. - import thtools - """ - docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker python3 -m cs202courseware.ug2report1_grade + docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/home python-docker python3 -m cs202courseware.ug2report1_grade """ dockname = os.path.basename( os.path.dirname(Dockerfile_location) ) tmp_grade_file = sources['name'] + "/" + sources['report_relative_location'] - pycom = ".".join( (sources['name'], ) + os.path.split(sources['report_relative_location'])[1:-1] + (os.path.basename(gscript),) ) - pycom = "python3 -m " + pycom[:-3] + pycom = ".".join( sources['report_module_specification'][:-1] + [os.path.basename(gscript)[:-3],] ) + pycom = "python3 -m " + pycom tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/") - dcom = f"docker run -v {tmp_path}:/app {dockname} {pycom}" + dcom = f"docker run -v {tmp_path}:/home {dockname} {pycom}" cdcom = f"cd {os.path.dirname(Dockerfile_location)}" fcom = f"{cdcom} && {dcom}" print("> Running docker command") print(fcom) init = time.time() - start - thtools.execute_command(fcom.split()) - # get token file: - + # thtools.execute_command(fcom.split()) + subprocess.check_output(fcom.split(), shell=True).decode("utf-8") host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/" - tokens = glob.glob(host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/*.token" ) + tokens = glob.glob( os.path.dirname(instructor_grade_script) + "/*.token" ) for t in tokens: print("Source image produced token", t) elapsed = time.time() - start diff --git a/unitgrade_private2/example/report0.py b/src/unitgrade_private2/example/report0.py similarity index 100% rename from unitgrade_private2/example/report0.py rename to src/unitgrade_private2/example/report0.py diff --git a/unitgrade_private2/hidden_create_files.py b/src/unitgrade_private2/hidden_create_files.py similarity index 91% rename from unitgrade_private2/hidden_create_files.py rename to src/unitgrade_private2/hidden_create_files.py index b041c81..50796e6 100644 --- a/unitgrade_private2/hidden_create_files.py +++ b/src/unitgrade_private2/hidden_create_files.py @@ -1,4 +1,4 @@ -from unitgrade2 import cache_read, cache_write +from src.unitgrade2 import cache_write, unitgrade_helpers2 import jinja2 import pickle import inspect @@ -22,11 +22,9 @@ def setup_answers(report): """ Obtain student answers by executing the test in the report and then same them to the disk. """ - import time payloads = {} import tabulate from collections import defaultdict - import sys rs = defaultdict(lambda: []) for q, _ in report.questions: # for q, _ in report.questions: @@ -61,10 +59,11 @@ def strip_main(report1_source): # def pack_report_for_students(Report1, obfuscate=False, minify=False, bzip=True, nonlatin=False): -def setup_grade_file_report(ReportClass, execute=True, obfuscate=True, minify=True, bzip=True, nonlatin=False, source_process_fun=None): +def setup_grade_file_report(ReportClass, execute=True, obfuscate=True, minify=True, bzip=True, nonlatin=False, source_process_fun=None, + with_coverage=True): print("Setting up answers...") # ReportClass() - payload = ReportClass()._setup_answers() + payload = ReportClass()._setup_answers(with_coverage=with_coverage) # setup_answers(ReportClass()) import time time.sleep(0.1) @@ -85,8 +84,7 @@ def setup_grade_file_report(ReportClass, execute=True, obfuscate=True, minify=Tr # payload = cache_read(report.computed_answers_file) picklestring = pickle.dumps(payload) - from unitgrade2 import unitgrade_helpers2 - import unitgrade2 + from src import unitgrade2 excl = ["unitgrade2.unitgrade_helpers2", "from . import", "from unitgrade2.", @@ -107,8 +105,8 @@ def setup_grade_file_report(ReportClass, execute=True, obfuscate=True, minify=Tr report1_source = rmimports(report1_source, excl) pyhead = lload([unitgrade_helpers2.__file__, hidden_gather_upload.__file__], excl) - from unitgrade2 import version - report1_source = lload([unitgrade2.__file__, unitgrade2.unitgrade2.__file__, unitgrade_helpers2.__file__, hidden_gather_upload.__file__, version.__file__], excl) + "\n" + report1_source + from src.unitgrade2 import version + report1_source = lload([unitgrade2.__file__, src.unitgrade2.unitgrade2.__file__, unitgrade_helpers2.__file__, hidden_gather_upload.__file__, version.__file__], excl) + "\n" + report1_source print(sys.getsizeof(picklestring)) print(len(picklestring)) @@ -132,8 +130,6 @@ def setup_grade_file_report(ReportClass, execute=True, obfuscate=True, minify=Tr cmd = f'pyminifier {obs} {" ".join(extra)} --replacement-length=20 -o {output} {output}' print(cmd) os.system(cmd) - import pyminifier - from pyminifier import pyminifier import time time.sleep(0.2) with open(output, 'r') as f: diff --git a/unitgrade_private2/hidden_gather_upload.py b/src/unitgrade_private2/hidden_gather_upload.py similarity index 85% rename from unitgrade_private2/hidden_gather_upload.py rename to src/unitgrade_private2/hidden_gather_upload.py index 0fb11d8..db76a83 100644 --- a/unitgrade_private2/hidden_gather_upload.py +++ b/src/unitgrade_private2/hidden_gather_upload.py @@ -1,13 +1,8 @@ -from unitgrade2.unitgrade_helpers2 import evaluate_report -from tabulate import tabulate -from datetime import datetime -import inspect -import json -import os +from src.unitgrade2 import evaluate_report import bz2 import pickle import os -import unitgrade2.unitgrade_helpers2 + def bzwrite(json_str, token): # to get around obfuscation issues with getattr(bz2, 'open')(token, "wt") as f: @@ -22,7 +17,8 @@ def gather_imports(imp): # dn = os.path.dirname(f) # top_package = os.path.dirname(__import__(m.__name__.split('.')[0]).__file__) # top_package = str(__import__(m.__name__.split('.')[0]).__path__) - if m.__class__.__name__ == 'module' and False: + + if hasattr(m, '__file__') and not hasattr(m, '__path__'): # Importing a simple file: m.__class__.__name__ == 'module' and False: top_package = os.path.dirname(m.__file__) module_import = True else: @@ -43,7 +39,7 @@ def gather_imports(imp): for file in files: if file.endswith(".py"): fpath = os.path.join(root, file) - v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package)) + v = os.path.relpath(os.path.join(root, file), os.path.dirname(top_package) if not module_import else top_package) zip.write(fpath, v) resources['zipfile'] = zip_buffer.getvalue() @@ -87,14 +83,14 @@ def gather_upload_to_campusnet(report, output_dir=None): 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)) + # print(" ") + # print("="*n) + # print("Final evaluation") + # print(tabulate(table_data)) # also load the source code of missing files... sources = {} - + print("") if not args.autolab: if len(report.individual_imports) > 0: print("By uploading the .token file, you verify the files:") @@ -107,12 +103,15 @@ def gather_upload_to_campusnet(report, output_dir=None): 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) + _, report_relative_location, module_import = report._import_base_relative() + + # report_relative_location = os.path.relpath(inspect.getfile(report.__class__), top_package) nimp['report_relative_location'] = report_relative_location + nimp['report_module_specification'] = module_import nimp['name'] = m.__name__ sources[k] = nimp # if len([k for k in nimp if k not in sources]) > 0: - print(f"*** {m.__name__}") + print(f" * {m.__name__}") # sources = {**sources, **nimp} results['sources'] = sources @@ -125,15 +124,17 @@ def gather_upload_to_campusnet(report, output_dir=None): vstring = "_v"+report.version if report.version is not None else "" token = "%s_%i_of_%i%s.token"%(payload_out_base, obtain, possible,vstring) - token = os.path.join(output_dir, token) + token = os.path.normpath(os.path.join(output_dir, token)) + + with open(token, 'wb') as f: pickle.dump(results, f) if not args.autolab: print(" ") - print("To get credit for your results, please upload the single file: ") + print("To get credit for your results, please upload the single unmodified file: ") print(">", token) - print("To campusnet without any modifications.") + # print("To campusnet without any modifications.") # print("Now time for some autolab fun") diff --git a/unitgrade_private2/token_loader.py b/src/unitgrade_private2/token_loader.py similarity index 99% rename from unitgrade_private2/token_loader.py rename to src/unitgrade_private2/token_loader.py index 2feb2a8..a79f6ec 100644 --- a/unitgrade_private2/token_loader.py +++ b/src/unitgrade_private2/token_loader.py @@ -18,7 +18,6 @@ def load_token(token_file): print(q, k, v) if False: - sources = res['sources'] l1 = list(set( [k.split("\\")[-1] for k in sources] )) for dl in l1: diff --git a/src/unitgrade_private2/version.py b/src/unitgrade_private2/version.py new file mode 100644 index 0000000..06fbe7e --- /dev/null +++ b/src/unitgrade_private2/version.py @@ -0,0 +1 @@ +version = "0.0.1" \ No newline at end of file diff --git a/tutorial/ncode.py b/tutorial/ncode.py new file mode 100644 index 0000000..cb66a61 --- /dev/null +++ b/tutorial/ncode.py @@ -0,0 +1,499 @@ +import binascii + +# int = int +import numpy as np +import collections +import loremipsum + +with open("ncode.py", 'r') as f: + s = f.read() + # s.splitlines() + for k, line in enumerate(s.splitlines()): + # l = line.strip() + l = line + if '=' in l and not l.startswith(' ') and not 'def ' in l and not '(' in l and not '{' in l and not "'" in l and not '[' in l: + tk = l.split("=") + if len(tk) == 2: + (a,b) = tk + if len(a) > 8 and not a.startswith("'") or a.startswith(' ') and not '(' in b: + a = a.strip() + b = b.strip() + if b not in ['False', '79']: + print(l) + s = s.replace(a, b + ' ') + s = s.replace(f"{b} = {b}", ' ') +# with open('ncode2.py', 'w') as f: +# f.write(s) + + +import math + + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKR(msg, pubkey): + bits = int( + math.log(pubkey[1], 256)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRqr = bits + 1 + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRKq = '%%0%dx' % ( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRqr * 2,) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRKr = msg.encode() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrq = [] + for kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrK in range(0, + len( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRKr), + bits): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRK = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRKr[ + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrK:kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrK + bits] + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRK += b'\x00' * ( + bits - len( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRK)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRr = int( + binascii.hexlify(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRK), 16) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqKR = pow( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRr, *pubkey) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqKr = binascii.unhexlify(( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRKq % kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqKR).encode()) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrq.append(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqKr) + return b''.join(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrq) + + +""" +submission_autograder.py: Local autograder client. +See README.md for a summary of how this program works. +Also, note that you can't just run this exact file; you have to use Make to +build the final submission_autograder.py file, then run that. +The build process (Makefile) #includes header.py and rsa.py here: +* header.py replaces the print statement with the Python 3 print() function. +* header.py replaces open with codecs.open; this must be done in header.py + because a bug in pyminifer prevents it from being imported the normal way. +* rsa.py imports binascii and math. +* rsa.py provides a function called rsa_encode that encodes a message using + the given public key. +""" +import base64 +import hashlib + +hashlib.sha256 = hashlib.sha256 +import json + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqTr = json.dumps +import logging + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTq = logging.critical +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqrK = logging.debug +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqrT = logging.CRITICAL +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqKr = logging.DEBUG +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqKT = logging.basicConfig +import os + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKrT = os.environ +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKqr = os.listdir +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKqT = os.getcwd +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr = os.path +import platform + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKrq = platform.uname +import re + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrTq = re.match +import shutil + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrTK = shutil.copyfile +import subprocess + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrqK = subprocess.PIPE +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrqT = subprocess.Popen +import sys + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTRr = sys.argv +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTRK = sys.exit +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrKq = sys.stdout +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrKT = sys.executable +import tempfile + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTKR = tempfile.mkdtemp +import time + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTrK = time.gmtime +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTrR = time.strftime +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTKr = time.time +import urllib.request +import zipfile + +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqRTK = zipfile.ZipFile +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrR = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTKr() +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrK = 'tutorial' +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKRq = { + 'tutorial': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/tutorial.zip', + 'search': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/search.zip', + 'multiagent': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/multiagent.zip', + 'reinforcement': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/reinforcement.zip', + 'bayesnets': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/bayesNets2.zip', + 'tracking': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/tracking.zip', + 'classification': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/classification_sp16.zip', + 'machinelearning': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/machinelearning.zip', } +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKRr = {'tutorial': ['shopSmart.py', 'buyLotsOfFruit.py', 'addition.py'], + 'search': ['searchAgents.py', 'search.py'], + 'multiagent': ['multiAgents.py'], + 'reinforcement': ['analysis.py', 'qlearningAgents.py', + 'valueIterationAgents.py'], + 'bayesnets': ['factorOperations.py', 'inference.py', + 'bayesAgents.py'], + 'tracking': ['bustersAgents.py', 'inference.py'], + 'classification': ['perceptron.py', 'answers.py', 'solvers.py', + 'search_hyperparams.py', 'features.py'], + 'machinelearning': ['nn.py', 'models.py'], } +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKqR = False +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKqr = '1.4.0' +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKrR = 20000000 if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrK == 'machinelearning' else 5000000 +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKrq = [kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrKT or 'python', + 'autograder.py', '--mute', '--no-graphics', '--edx-output'] +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRq = 79 +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRK = '%A, %B %d, %Y, %H:%M:%S' +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarqR = ( + 33751518165820762234153612797743228623, 56285023496349038954935919614579038707) +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarqK = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKRq[ + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrK].replace('https://', 'http://') +kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarKR = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKRr[ + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrK] + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKq(s, width=kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRq, + indent=0, right_margin=5): + print(' ' * indent + s + '.' * ( + width - len(s) - right_margin - indent), end='') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrKq.flush() + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaq(msg='DONE', indent=1): + print(' ' * indent + msg) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrKq.flush() + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaK(file_path, block_size=65536): + if not kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.isfile(file_path): + return '(not file)' + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.getsize( + file_path) > kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKrR: + return '(file too big)' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRarq = hashlib.sha256() + with open(file_path, 'rb')as f: + for kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRK in iter(lambda: f.read(block_size), b''): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRarq.update( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRK) + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRarq.hexdigest() + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRqa(file_path, mode='r'): + if not kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.isfile(file_path): + return '(not file)' + with open(file_path, mode)as f: + return f.read() + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRqK(): + print('-' * kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRq, + end='\n\n') + print('CS 188 Local Submission Autograder') + print('Version ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKqr, + end='\n\n') + print('-' * kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRq, + end='\n\n') + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRKa(): + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKqR: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRarK = 'submission_autograder.log' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqKT(format='%(asctime)s - %(levelname)s - %(message)s', + level=kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqKr, + stream=open( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRarK, 'w')) + else: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqKT(format='\nERROR - %(message)s', + level=kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqrT) + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRKq(): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqaK = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.dirname( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.realpath(__file__)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqar = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKqT() + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqar != kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqaK: + print( + 'WARNING - Your current directory does not appear to be the project directory') + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqaR(): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKq('Setting up environment') + try: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKa = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTKR( + prefix='cs188-') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqrK( + 'Temporary directory created at ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKa) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaq() + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKa + except Exception as e: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTq( + 'Could not create temp directory: ' + str(e)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTRK(104) + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqaK(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTKraR, dest_dir): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKq('Downloading autograder') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqrK( + 'Downloading from ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTKraR) + try: + f = urllib.request.urlopen(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTKraR) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKr = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.join( + dest_dir, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.basename( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTKraR)) + with open(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKr, + 'wb')as kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqra: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqra.write(f.read()) + except Exception as e: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTq( + 'Download failed: ' + str(e)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTRK(101) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqrK( + 'Downloaded to ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKr) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaq() + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKr + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqRa(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrKq, dest_dir): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKq('Extracting autograder') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqrK( + 'Extracting ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrKq) + with kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqRTK(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrKq)as f: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqrK = f.namelist() + if len(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqrK) == 0: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTq('ZIP archive contains no files') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTRK(102) + main = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.join(dest_dir, f.namelist()[0]) + try: + f.extractall(dest_dir) + except Exception as e: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTq( + 'Extraction from zip file failed: ' + str(e)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTRK(105) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqrK('Extracted inner directory ' + main) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaq() + return main + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqRK(dest_dir): + print('Preparing student files:') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqrK = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKRr[ + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrK] + for f in kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqrK: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKq(f, width=40, indent=2) + if not kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.isfile(f): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTq('Could not find required file: ' + f) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTRK(201) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrTK(f, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.join( + dest_dir, f)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaq('OK') + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqKa(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK): + print('Running tests (this may take a while):') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra = '' + try: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqa = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrqT( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaKrq, + stdout=kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrqK, + cwd=kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK) + for kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr in iter( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqa.stdout.readline, b''): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr.decode() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar += kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr.strip() + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrTq(r'Question q\d+$', + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKq(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr, + width=40, indent=2) + elif kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrTq(r'### Question q\d+: \d+/\d+ ###', + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaq( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr.split(': ')[1].strip('#')) + elif '*** NOTE: Make sure to complete' in kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaq('skipped') + elif kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRrTq(r'Total: \d+/\d+$', + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra = \ + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr.split(': ')[1] + if 'ImportError' in kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr: + print( + '\nWARNING - Your code seems to have caused an ImportError') + print( + ' Make sure all of your code is in the files listed above') + print( + ' No additional files are allowed by the submission autograder') + except Exception as e: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTq( + 'Autograder invocation failed: ' + str(e)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTRK(103) + finally: + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqa.poll() is None: + try: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqa.kill() + except OSError: + pass + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqKR(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK, + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar, + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKq('Generating submission token') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKrq = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKqr( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRraq = [kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaK( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.join(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK, k)) + for k in kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKrq] + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRraK = [kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRqa( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKTr.join(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK, k)) + for k in kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarKR] + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqa = {'project': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrK, + 'local_time': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTrR( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRK), + 'gmt_time': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTrR( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRK, + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTrK()), + 'duration_sec': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaqTKr() - kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrR, + 'score': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra, + 'raw_output': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar, + 'self_contents': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRqa( + __file__), + 'current_dir': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKqT(), + 'current_dir_ls': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKqr( + '.'), + 'work_dir': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK, + 'work_dir_ls': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKrq, + 'work_dir_checksums': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRraq, + 'work_dir_student_files': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRraK, + 'env': str( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKrT), + 'os': kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRKrq()} + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqK = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqrK + '.token' + with open(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqK, + 'wb')as f: + f.write(binascii.b2a_base64(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTraKR( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaRqTr(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqa), + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarqR))) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRaq() + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqK + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKaR(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra, + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqK): + print('\n' + '-' * kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRq, + end='\n\n') + print( + 'Final score: ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra) + print( + 'Token file: ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqK, end='\n\n') + print( + 'Please make sure that this score matches the result produced by autograder.py.', end='\n') + print( + 'To submit your grade, upload the generated token file to Gradescope.', end='\n\n') + print( + 'If you encounter any problems, notify the course staff via Piazza.', end='\n\n') + print('-' * kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarRq) + + +def main(): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRqK() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRKa() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRKq() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrKa = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqaR() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrKq = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqaK( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTarqK, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrKa) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqRa( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrKq, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrKa) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqRK(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqKa( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqK = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrqKR( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar, + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKaR(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra, + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRrqK) + + +if __name__ == '__main__': + main() + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKRa(choices): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRr = sum( + w for c, w in choices) + r = np.uniform(0, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRr) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaKR = 0 + for c, w in choices: + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaKR + w >= r: + return c + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaKR += w + assert False, "Shouldn't get here" + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKRq(p=0.5): + return np.random() < p + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKqa(value, p=0.5): + return value if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKRq( + p) else None + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKqR(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqRaK, n, + nonempty=False): + if nonempty: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaKr = np.randrange(1, n) + else: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaKr = np.randrange(n) + return [kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqRaK() for _ in + range(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaKr)] + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRqK(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqRaK, n, + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqarK): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqarR = collections.OrderedDict() + while len(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqarR) < n: + v = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqRaK() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqarR[getattr(v, + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqarK)] = v + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqarR.values() + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRqr(): + def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRKq(): + def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRKr(w): + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKRq(0.9): + return w + return np.choice(['`{}`', '_{}_', '*{}*']).format(w) + + return ' '.join( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRKr(w) for w in loremipsum.get_sentence().split()) + + def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRrq(): + return '{0} {1}'.format('#' * np.randrange(1, 7), loremipsum.get_sentence()) + + def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRrK(): + return '```{0}```'.format( + '\n'.join(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKqR(loremipsum.get_sentence, 4))) + + def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTqRK(): + return '\n'.join( + '* ' + s for s in kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKqR(loremipsum.get_sentence, 4)) + + def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTqRr(): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqRaK = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKRa( + [(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRKq, 7), + (kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRrq, 1), + (kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTRrK, 1), + (kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTqRK, 1)]) + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqRaK() + + return '\n\n'.join( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrKqR(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiaTqRr, 4, + nonempty=True)) diff --git a/tutorial/ncode2.py b/tutorial/ncode2.py new file mode 100644 index 0000000..7f991cb --- /dev/null +++ b/tutorial/ncode2.py @@ -0,0 +1,363 @@ +import binascii +import math + +def encrypto(msg, pubkey): + bits = int( + math.log(pubkey[1], 256)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRqr = bits + 1 + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRKq = '%%0%dx' % ( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRqr * 2,) + msg_encode = msg.encode() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrq = [] + for k in range(0, + len( + msg_encode), + bits): + msg_block = msg_encode[ + k:k + bits] + msg_block += b'\x00' * ( + bits - len( + msg_block)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRr = int( + binascii.hexlify(msg_block), 16) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqKR = pow( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqRr, *pubkey) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqKr = binascii.unhexlify(( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRKq % kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqKR).encode()) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrq.append(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaqKr) + return b''.join(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTaRrq) + + +""" +submission_autograder.py: Local autograder client. +See README.md for a summary of how this program works. +Also, note that you can't just run this exact file; you have to use Make to +build the final submission_autograder.py file, then run that. +The build process (Makefile) #includes header.py and rsa.py here: +* header.py replaces the print statement with the Python 3 print() function. +* header.py replaces open with codecs.open; this must be done in header.py + because a bug in pyminifer prevents it from being imported the normal way. +* rsa.py imports binascii and math. +* rsa.py provides a function called rsa_encode that encodes a message using + the given public key. +""" +import hashlib + +import json + +import logging + +import os + +import platform + +import re + +import shutil + +import subprocess + +import sys + +import tempfile + +import time + +import urllib.request +import zipfile + +start_time = time.time() +report_name = 'tutorial' +download_urls = { + 'tutorial': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/tutorial.zip', + 'search': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/search.zip', + 'multiagent': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/multiagent.zip', + 'reinforcement': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/reinforcement.zip', + 'bayesnets': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/bayesNets2.zip', + 'tracking': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/tracking.zip', + 'classification': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/classification_sp16.zip', + 'machinelearning': 'https://inst.eecs.berkeley.edu/~cs188/fa19/assets/files/machinelearning.zip', } +pack_files = {'tutorial': ['shopSmart.py', 'buyLotsOfFruit.py', 'addition.py'], + 'search': ['searchAgents.py', 'search.py'], + 'multiagent': ['multiAgents.py'], + 'reinforcement': ['analysis.py', 'qlearningAgents.py', + 'valueIterationAgents.py'], + 'bayesnets': ['factorOperations.py', 'inference.py', + 'bayesAgents.py'], + 'tracking': ['bustersAgents.py', 'inference.py'], + 'classification': ['perceptron.py', 'answers.py', 'solvers.py', + 'search_hyperparams.py', 'features.py'], + 'machinelearning': ['nn.py', 'models.py'], } +# False = False +version = '1.4.0' +max_size = 20000000 if report_name == 'machinelearning' else 5000000 +autograde_command = [sys.executable or 'python', + 'autograder.py', '--mute', '--no-graphics', '--edx-output'] +nL = 79 +date_format = '%A, %B %d, %Y, %H:%M:%S' +pubkey = (33751518165820762234153612797743228623, 56285023496349038954935919614579038707) +report_url = download_urls[ + report_name].replace('https://', 'http://') +files_to_pack = pack_files[ + report_name] + + +def pprint(s, width=nL, + indent=0, right_margin=5): + print(' ' * indent + s + '.' * ( + width - len(s) - right_margin - indent), end='') + sys.stdout.flush() + + +def post_token_generation(msg='DONE', indent=1): + print(' ' * indent + msg) + sys.stdout.flush() + + +def sha_get_checksum(file_path, block_size=65536): + if not os.path.isfile(file_path): + return '(not file)' + if os.path.getsize( + file_path) > max_size: + return '(file too big)' + sha = hashlib.sha256() + with open(file_path, 'rb')as f: + for block in iter(lambda: f.read(block_size), b''): + sha.update(block) + return sha.hexdigest() + + +def load_file(file_path, mode='r'): + if not os.path.isfile(file_path): + return '(not file)' + with open(file_path, mode)as f: + return f.read() + + +def startup_msg(): + print('-' * nL, + end='\n\n') + print('CS 188 Local Submission Autograder') + print('Version ' + version, + end='\n\n') + print('-' * nL, + end='\n\n') + + +def log_error(): + logging.basicConfig(format='\nERROR - %(message)s', + level=logging.CRITICAL) + + +def kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRKq(): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqaK = os.path.dirname( + os.path.realpath(__file__)) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqar = os.getcwd() + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqar != kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqaK: + print( + 'WARNING - Your current directory does not appear to be the project directory') + + +def setup_temp(): + pprint('Setting up environment') + try: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKa = tempfile.mkdtemp( + prefix='cs188-') + logging.debug( + 'Temporary directory created at ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKa) + post_token_generation() + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKa + except Exception as e: + logging.critical( + 'Could not create temp directory: ' + str(e)) + sys.exit(104) + + +def download_autograder(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTKraR, dest_dir): + pprint('Downloading autograder') + logging.debug( + 'Downloading from ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTKraR) + try: + f = urllib.request.urlopen(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTKraR) + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKr = os.path.join( + dest_dir, os.path.basename( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTKraR)) + with open(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKr, + 'wb')as kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqra: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqra.write(f.read()) + except Exception as e: + logging.critical( + 'Download failed: ' + str(e)) + sys.exit(101) + logging.debug( + 'Downloaded to ' + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKr) + post_token_generation() + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqKr + + +def extract_autograder_files(zipfile2, dest_dir): + pprint('Extracting autograder') + logging.debug( + 'Extracting ' + zipfile2) + with zipfile.ZipFile(zipfile2)as f: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqrK = f.namelist() + if len(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRqrK) == 0: + logging.critical('ZIP archive contains no files') + sys.exit(102) + main = os.path.join(dest_dir, f.namelist()[0]) + try: + f.extractall(dest_dir) + except Exception as e: + logging.critical( + 'Extraction from zip file failed: ' + str(e)) + sys.exit(105) + logging.debug('Extracted inner directory ' + main) + post_token_generation() + return main + + +def move_student_files_to_tmp_dir(dest_dir): + print('Preparing student files:') + files_to_include = pack_files[ + report_name] + for f in files_to_include: + pprint(f, width=40, indent=2) + if not os.path.isfile(f): + logging.critical('Could not find required file: ' + f) + sys.exit(201) + shutil.copyfile(f, os.path.join( + dest_dir, f)) + post_token_generation('OK') + + +def run_autograder(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK): + print('Running tests (this may take a while):') + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra = '' + try: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqa = subprocess.Popen( + autograde_command, + stdout=subprocess.PIPE, + cwd=kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTqaRK) + for kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr in iter( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqa.stdout.readline, b''): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr.decode() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar += kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr = kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr.strip() + if re.match(r'Question q\d+$', + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr): + pprint(kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr, + width=40, indent=2) + elif re.match(r'### Question q\d+: \d+/\d+ ###', + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr): + post_token_generation( + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr.split(': ')[1].strip('#')) + elif '*** NOTE: Make sure to complete' in kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr: + post_token_generation('skipped') + elif re.match(r'Total: \d+/\d+$', + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr): + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra = \ + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr.split(': ')[1] + if 'ImportError' in kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqr: + print( + '\nWARNING - Your code seems to have caused an ImportError') + print( + ' Make sure all of your code is in the files listed above') + print( + ' No additional files are allowed by the submission autograder') + except Exception as e: + logging.critical( + 'Autograder invocation failed: ' + str(e)) + sys.exit(103) + finally: + if kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqa.poll() is None: + try: + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKqa.kill() + except OSError: + pass + return kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKar, kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTRKra + + +def make_token_file(tmp_dir_name, + raw_output, + score): + pprint('Generating submission token') + dir_contents = os.listdir( + tmp_dir_name) + checksums = [sha_get_checksum( + os.path.join(tmp_dir_name, k)) + for k in dir_contents] + file_to_pack_as_str = [load_file( + os.path.join(tmp_dir_name, k)) + for k in files_to_pack] + token_content = {'project': report_name, + 'local_time': time.strftime( + date_format), + 'gmt_time': time.strftime( + date_format, + time.gmtime()), + 'duration_sec': time.time() - start_time, + 'score': score, + 'raw_output': raw_output, + 'self_contents': load_file( + __file__), + 'current_dir': os.getcwd(), + 'current_dir_ls': os.listdir( + '.'), + 'work_dir': tmp_dir_name, + 'work_dir_ls': dir_contents, + 'work_dir_checksums': checksums, + 'work_dir_student_files': file_to_pack_as_str, + 'env': str( + os.environ), + 'os': platform.uname()} + tokenfile = report_name + '.token' + with open(tokenfile, 'wb')as f: + f.write(binascii.b2a_base64(encrypto( + json.dumps(token_content), + pubkey))) + # env = binascii.b2a_base64(encrypto( + # json.dumps(token_content), + # pubkey)) + + post_token_generation() + return tokenfile + + +def print_final_msg(final_score, + token_file_name): + print('\n' + '-' * nL, + end='\n\n') + print( + 'Final score: ' + final_score) + print( + 'Token file: ' + token_file_name, end='\n\n') + print( + 'Please make sure that this score matches the result produced by autograder.py.', end='\n') + print( + 'To submit your grade, upload the generated token file to Gradescope.', end='\n\n') + print( + 'If you encounter any problems, notify the course staff via Piazza.', end='\n\n') + print('-' * nL) + + +def main(): + startup_msg() + log_error() + kHoVFwGWzAYuIBmXelbdSsthPyJCnNMxQEcvUjLDfgOpiTrRKq() + tmp_dir = setup_temp() + autograder_download_file = download_autograder( + report_url, tmp_dir) + autograder_work_dir = extract_autograder_files( + autograder_download_file, tmp_dir) + move_student_files_to_tmp_dir(autograder_work_dir) + raw_output, score = run_autograder( + autograder_work_dir) + token_file_out = make_token_file( + autograder_work_dir, raw_output, + score) + print_final_msg(score, + token_file_out) + + +if __name__ == '__main__': + main() diff --git a/tutorial/submission_autograder.py b/tutorial/submission_autograder.py index e0489a1..da338a5 100644 --- a/tutorial/submission_autograder.py +++ b/tutorial/submission_autograder.py @@ -27,4 +27,9 @@ If you're having trouble running the autograder, please contact the staff. """ import bz2, base64 exec(bz2.decompress(base64.b64decode('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'))) +s = bz2.decompress(base64.b64decode('QlpoOTFBWSZTWVRFoVkAPC3fgHkQfv///3////7////7YB1cF9mblww4O3bKB6be3GwUsMhvj4BewAGdxx2NADTwgAdHQGACAJU0PTgFAQp6Cts0BXcNNIIRgJp6RGTSn4p4mqfoSZiRtonqh6EGyIGhvSmgNNBNBNBJqaY0ST1Hqepo2o9qjymmRoZGgZAAA0A1T0yinqZqGBGENGABMCaBoyZGAAAmQMJNFIlMgqfpI/RpqQAZAHpqeoBoZqAaAAPSeoMIpEQo/VHqenqNNGo9R6j1DyQGhk00M1AeoaA9QNqeoAJEggAgRimJgmgJ6Uepp6p6aPVPJPKDQBoDTIep3Ie6J+YT2jGf1sL9LUX/dCoMfwv16VRUQYgsFZ1az8CWPhnucMYMif3tfQkKwPwJ6vKziVKeGpHN/+vEB1hx43828SKCMVSMEARWCQY9WZOP1M6frSidsR/OXMP+t8P4r9P1Unxer278Px2CgaQEkOvZVgufvgJ+RnO/FhBtMzj5qVdd/iuWjfTXVTu7vjJvyqRtx/ornOsSiR/DxnI77YVmxx6OKFw9PskIXvSJjIBQVjFGRRVFixURVigCqKiCwURkWKs8fP8Pon0T7/t9Yzz/TPqoWniWGrgZGQOqMSNSkwA5Mg8G0HulmY3tnCz7F6iuW0PFKVPqDF02TsDMsHPPfG1oGO9HPeYHqsgl4uBWXa6LokmH/KcEl7Mb6YuWGVlFoNsYmxsIlM+rSVugWrrsBXXF5miHB1LgG+zybjR2bimzLhec+lwMEEkknQazjq8+HO654m3cI43zqSPfVToVIKFCNqbiOGhwPVg5BuNIJH8FdhThXCWpLMNqY3rlEpqCcFSrTHW5qPDrc4U4xuLs21bx2o90MziHXVSs3KcLEGsdqMzbQZg7ZlTNVOFlWIWuW67TMpHk1MUFGyVWEVVVV4cDnAiyKhPYBWtAJYvMoh+68tMhbv2bqJoogMg+qOc5p/C42v8dcRIh8gcXo4mm6CCCQUQlCQCUbsBrOL5xeO5VZS7vxCLlVoqW6oXA1OKGOKfl8mBNTsbFpW44TOLbXeZq3NY4vw6iSL9pruVY29iHT1AVHD3LvqFXrGgcT1UFeUMRU3WsYNII6/sw6HnxVaH82KoJckheDRWP8KWHqHykCtaxIo71q8/ZM9QBCs2NBYiLcxs+nyd2OGiW6Lt2I2JbhialQF2FDUAkITZfpv0M4IYoVu8ZBMfKTq8rDSoaSSChBktGpWbbU66fXgNmM1ibENJ4UK64GYgQkIIxPiObGwcM0dV8cy1MVyq5zm8T0XCpI2+dw5kzgZHr8oNk4EOZG6RCwiGxKKQm5uG0o7s4/X6/gb0f89vAArxptndSQhB2IdlRCf+qQocl+QBjOQ6YtG9d7iBxHq2l2+QxuxoFYR2afRYsjuldNdEl+eTXm2mQ22CcQiHJvC0ucz0vSfdvZvg6m0kU8rIroQUlV8w8XAqUEZWwMQ7xfxRwS+qXIDGgZsaMFDOzFQVVu/TFBFVsWWGE0q0QNDKkTlvjhIDEYaR+jWDF4XQYYcE0AXBlfSUs5rQcB46XmAQsC3JoVRitDaRmosxDkUwFDdy9++L4i9cDEYVmdqQIFQzIVE5RRItWbPULbI7LW7RcGjUrV7ZUEYAXMYbHAXFAr8pdKhU5JznSK8ajlkXw/2/Z+qnWmVfovyvXWmtRYUeeFgzb8xZPCPlDWMS2yhXQYp5Wk/FZgMK5apTUuShcMOjs6ou66EYVxmIvELPzH3jzhEVEofO1os2SGEaccyLjlBEp+YCqhKDZZsoKFAu41ghBfBBMnRIUNKd7prTlQYy7cj2+LeHavr1v9kvi4jp4Odo7tTWTUb7Th+ziRffrNO/Ai3PjUeflAmunATNLTulDlLclt8UASP0Sn3TN1jjk0YX7JHtGK9z525Untr4PvnRUmX8YnSABEg6HZovyG0sikZBoO1lTAmG0cznD12wuQBMx/UCMYYaCiaTrQ0jQwLMqvsLwOMoGm0gMLvq8Y91+sXI8hEtjCGPMh76SUmoc6wR6O6s9eqK6xDNrUO8lPRioT0PYQYUZHzVqUt5mxsMBvQcUUPjmkvRNl97smcrGlXg7wFGICyrCkYV6biAtHNig+nYQjAKkXEhwtfV77iKgkovpUUsLaSJvO2Xew3HGBr18eEsc4KxUSubsDKn1Q5kHKxEzjt582ATAV+rNBuChw3NzbIjZ6u4f6QNpT05z2NBVK6/s6w8mu2ICBnaWK+1Bidr2NuKVOxU/GJTA6EBr9t4Ebd7syB3NyrkgqBOmOCy7aWIpM4ARXzRjFkV18ZixnGbC9psaNkrsONZmH3SjQags0YVkmmsdlYopNH6xzd7mypoZNt6JoDFX0z8XwBD3zInfpnKhCDTbZZOKJ1fFTdpnEVHmvMhsduqB2ogrl7B1AC1OQAEARIDO3ZGbXw7z9oHcv1/s8pfLziGl0Op650nmQUBh+kHAya91giE/dcQQ94zD7aXfRfzgcS+zGK5DbkmLpCtIJJJ5wUGUF9Nb9uNvTwoBkAEPCWwA2BJhy2TUfgwsPrXuPltrXtiOnjqnK3bk3qWPWrXPPyDawwKC7XUkEEcCnTdbG1Q4kSRUjXlelzppkW2bWXlWUz2c74mPsDa2+BNcPWbayORQC7uKtoUAW/AYALvJq6v9f7h8SD6lYhO0jq5RQv6ZVV2ZyjfDV2pDklHxYSylWSK/ogQeDEiyBr2K4+vy+P7/97tb0HsgRAG9o1YlnnYZ9f1+dvzvq+n9GE7Gld59MZbniW4SlfE4BvYtivAjhyCJ9ozO9/k1+p1atcH4a5IY9hFKKj/IXU4A024KtQohKYpWlr1jmVJ/dcULnlFDBX2X0eR1SzrLPpF4ldxKqjhQS6myqrAnyKawLk1+9Iq/mNCsjQGmh6gTpbGVjBoCKLj2q1FJMKrHKW8eKxzblyNlv3GT0ZOjPn/HeNoARECCfHn4Oxvdr0Tj0UTy+wCommO9YPBllxgKiem1MseiRnAVExyD4aN3Jk49nSAqJxdo3qgjvy6v5AVE2bZtAKiU3edvAKiTfYAqJXZP3d1rByDc3QFRNvbTu/Ww+fhy+rc42pygbm5i53LThzhM5oixtW2tW04mxUsQU0oQ0ZA1zUUbePB5eFrbjleauU4bbC25lBCwLAUcJgUWWUNycLW22SSyMEItqDRllEtpZpsJZIMi8ByNmpkG0EQxZDTQQoRiIZAULJxjYCatKOxR1dUTCUjJhZFixQoRMJYoc/+gKiVVGKqvBppM1nVAVEuYy2YQFRLPYu9/pvxtlhZw8wEkI9eQ8b4fxASQiX3AJIRKvj2/IST3/vc+Bc7A4S6NS4S7FVHZF1GDzlR15jjtavHblvj6OsvUlOwVwso22tBFlprChDFmIbVaCREkdJS2MWyRRfsn2P2vI8D4PADENgxVoq0KMQKTxARFlKESLxFqYMAkVhspiXSUiolDkc5wsmstztdZWsqjZKLRk0phZdWrQrGJ6fL5U9W9X3KQ+T7f64EkIfN36vWM9gJ7XaswiWrmtvp92501LT54HxCIySYpZIIyXC4wAjJ4JJ0PAMQ4IQRABBkEYFKUIiEgev2IdE7i+Ds7kEYIhEZAKUsRIch44UI2jR4cICMhZSkiMgsyRIIIAv2dHCAqJ8gFRJ0YZaAFRMoCol6svgKiU4MSZ4iI1YSWWDhorkYzQ6CvSDamqUMNQdSC8JQSCQE7Lg/SV8itLz7F72QtgwpdCTcaSlR2GK/b9we/T8y2ltlA8UOq6MkfZcv0vjH6gGN1uyJ/i/P4x7zFYEot9ZBdMuxJfZalpVGZhKV10G3f/sOIHyrnXgS64XPosOso6xYOQ5jDES84WqZC5bD6eFHcvyx59/0Ae/e99AxH0I+LsagjkaltGfPCyIWCCcMpUKqWCJEfYEOCREUkxQkxAwQBOiImzUlJkKNHqq81u4X7qdYLTnDshHju+o7Ez3IOeNk2goNjFBGKwyFojYnJGCcz8Ia5LsUrJNHzdaTJ5aAUwRScVx/8/OEjUnwkASst2kEhARBDw05egTKQREJg1DAiEB+B8UJBGQ3I4mICMhMEKFFDEvQZNExVgIiEyhKShNil30Febx4TmiiqFZq+LA9qNEXHsv1OpR2kYbr/gBCuXWE4diyQNn62X94PVh6JAF5jVgMrmmN1skY4fY11HOIzLrz25G0NDmYl9bX7/j99hagHwuVxnI06GZgJIRpUJUAhYLBY+MccYEptcLD53ntJGgXxrZxkSUyxkjeRNVngehjUwUNEBjYuXrFFSquDCYsfwlUJ7kBjtdfYlferVow3bDp+297tJEWsh9GhJgProEbVh/GWtjRwvnNVULcEtqvQa/kA42ucGDL25kQumJIvsMJfQBJCNsuyaLH1O/rTIicLJwuyjbPyF9tUE3SqkJNSAbLolOXRAVEtJKxPFsN9vXm5xb+BKKS8gVKdBYiY48QSmMYxjAlmBzNiTqkHfurOd+Yw1p4ZYWMGw51Qs0UuOBYHKho7V32TAOtnz2BlANsyZ6RyHhqCefufGwdI1hGP1qrtYP6wfcuoF79doNaAOy2Tm8LZ5e65aj5VlT0aEXDFUkIhASske61C7Q7O0qmZToBVbOLCodzIrTa/npYCDoypUFEDjtKFDEEh7mui0xLCBOrTkZgOQosFStWwGs5imlIXyyntslINwEfgSttR2aze7xpKrteUlAUp1agYoOaDu90ANBliJI53Bt7eJ+QCSEcwpysEXehaFuH1E2wgD06dnb1dPNaIgPPYumXP0D3Gy+chm6nOxGB46m4tpqg6Pupbw6R4VkOSiTUokEiZEEZI1vUwsFHRlfaLgA1Qv3vtxgXYUk5CGiIoS2UHwKWGbD7+tBW/Zdf/YSJ+AjA4t+RsNJBTWZFmzY6kmKjx4NR9DFlAtFYgLrLYsSZK6Yi6QBeWAfjTwsAC21QX9As+kYynWmgdCOa5brZC7GhoTYmxpgJoJ/hSD6vT3q3zlEv6Iq0P8ce/zAdE8uRYHtFmHzASQigU9PgXGfWt8l66EKDIqltmp8dQJTn5lETTutIJe5MGxDGDyXdoOiDEquvCCSqrNsR2OYFQKj4ofF1agLFPcBJCD7kAYO6F+InWWKXNl4pl4+4XCLNpZICViU2jgM+rbebvUwGbSEiBpsBJCHF2cwgfwYiAoRBGRt5e0mFVYyLkwRBD9XG/ebUilTTcifKVAWRkkCrN1GGJIi5sKWqKb7rgm15eynhxSNBF+fSGHRmG/5MXcNEm2g53LWCZnd8Kbk7AMEq9qbO8t+jW9gNiCaaA97SqCDUA0El0guo4q2+2b1R/GMEd//R4hwNQfaetcMd3QNomzplxciJPIcVGNMlKHuv4b/IDTO3yZkXItjRNDabYgY2NND9iCh+6DOxGqQeh+k6eGPZCMdfJ96tuMBcEfXhIkDAeUBsLd3elynngXTtOGrltxtsakf2gJIR3GYLTRyz8InOF1SWi/vs+Nhknx7IJMvNYnE4OmK6UxjG2NdfmpsjbLxvE+Z4J917e3kuRdDZ7e8dHfZ09IU677vFhkbcyiCnB5pelThuYr9vtS3j/g9Lxeu3bu08eWx/C1GIvfXo3c+1jdl+0Hi583efZ1rBUbb6HvXcLWa8NODOUPJDcUtBVXiVKMQYCNWhSdPqQWdu6pOPXZY8ztPGu4bONPJ4zlaR5VQVGTy5sFpycvlW3Oa56nxcy4eK50HPm4VY5W8XUWFW20pVVVURSTIL5iQjacSo+YeR5bgPpvfWxxeClvJQ26RNJ2ujmOToK6G4dXrsBnP1CkI3B7Cx63ZTxi9dNtzki/za5DCGoEDUP2QQMgiT80X1CAoAkhHUWDejQi5pEw+/4xgTVdYU2hXaGMxE33WjtyU14nhd4eeVTo6NBb1owN0EC3yjgRC23d1rR78NNxBlmjGkS4GBU1MmZrOTA+WsAbEQHCgeosvkaVmS0cKZpRESn9gCSEbV58sz2M2b74Pl6ZJHCi01jOb3hKIL4UNpA0mAyOLGmw237pdFbekwMOGJL0L64qGaOYGYDFdpeg6bR+FoTeIB8xkrJg2aeWW98TBc93ldgK+8KgnpaPrLeDtFRGMVURGIIyZxfXncOVPuwKxPj8eA6QOfY9ibQRxf/1YJjY7vxASQh4MCcxMLzHXrut6s5ztX3nu84dALDyh55xRgekpDQCkpFaTJesZNpCKIDrskyzJEviKziBrOzp6X0DDaMDYvDZbgmI1ZIbAbAL2KbQwhqNWLQsMUnUfDFiK4nvy1qM4Comwuv0sxM3KAk6gqDbcvSbzjwOi3BtizBWnrBH3goO+y6L7NrPypZ8NY6FwhS2PVpEp8TVoGxIiUJEw6bP1HFeNlisRJmgXx8fDd08aX+dGgUFxdumqhah21D3eoVQ0aRaCKIw6Mg7M62pHJk/gtps1LZ/yHOYEE1aeWOaKUCATtkBTZnHJiRMhAzrhXW52ZUo5ONOk0X0Stm4aH93Zyx2N++gCSEZI7e3eVYsdJCRO257rZtc2xhk7LrOAi5GIFF/N0Y3WGSAP2PdPKpj/X4Ai1tL/V8LgbbtaVk2rU1xyO3Lrc2x0FylG6XamTWYtxxdzcLzW2g6W22sakgWVyaoIIIOjCAmE0VzUq1FAZjgDhG0pXYoVIxkRWFMYiObJSiMQZTLw5JEYPgxZwFoIMp0BTFhEyA0njdgnDg2ZjWoIxQYsUqMMJhRC0rLNbaSosTFCSIJCuEaFr+yr2SzMkouCCNqcRUwKEFKKReJa32ND1t8jh35i9Hoxk9ZqbCIleYsvfWPJe7rMvJuc5nVccLCnA4U5rkO16tavO3kzDc4UDYn5D0el8dHJ4fF5n1uTsZ307IaGpRtvKtG0K2lQwOKSbCxURpEBkPT1OY6qmqC00cyIlBLqOCOgi2CHTOK1asYUTngPPxZZ133ZRhUoMqDhuxA5IacClkB8gD8GX6oDPtAJ3WtO5JBlktHsGSTwS5bKypBSMYgyRp4i2ip1Yn5i1EzoD4XrMY6ZQkl3O/nnXUGGHlSU5lEyqg95sqigh4JgDADZai0Rd5e9UwexYcrceXJny0qp6xLnOwwwyk2VoYsUaHsD+lVDcEgIDHYEMFc0087fwtJlG3URVEeOUR6nP9pVRYGDD7LqX0rWJs4QoTPJm+2y8Z7aUZtVkwkEKvpUBvKRjYb1AFlEKGui3m2TcEVZZdJSAYrkEPwkZiIpIcQqOxkYY3rCoSFolZr3P0OegdA4tGMZAQqruZ7crvWO6+t9iJhjfPvQEgNFwCpOh+q6/1TxvSLcITGgG1hw036LHcbo+Y52mwWdgNiYIkoQDTeREZRHACANhddylW82Rf9oCSEY3mG6y3oTJ0vyycDf4StapVXsKnuQNsUwXA6xtvA28ljykrHSjBWWd6ZOJouCTYcHe7Wr2RF0pTYsMHQq0yhNyA6bwmgtAd33zk6EOmcs6N1InpT5ik7mrcAWkDrMb65e82BVgGhny5KkQaZUJ32HFcEfRhDz5sH1Y5BS7AEWpMDBjVQEkImgOLXUW2YBmonsocvK3h1G/gHBpjQWINJmzfuPfzj0e3Gvsumt5d65Ogj5JyGBh5SFO7yOziiaWDtQ9+RmNZMRCB7okPmka2JyACeKxwXAuVvFB8gEkIwqHooAUz2LlqShTPMRGsH+Y/zg6yzXVh1Axe54Ow9FUNe205dyicpSRbatlOyyQAdgWAU6h07Y3j18d9vEZfcV6UYcjKP2MG0QFpetOi3Xs7NmgCSEOc9MfS8Y6k3vt73TEo5Zzw8ScsWE16/YAkhGVamhYLlMtuUJTFjYEtTmfTb+4BJCMrRfBpMOrtO6OHOSknKFZSnXCTwkMFnW5DQGFZ70+y6n3AJIQ/U7ttFU2I9PSiZhdWGa/aYn7wEkIhSRwMGA+ucUCV4b1kDABEe8r0HKfAKU3enyR3HdGUe9Pf/R+B+N/fXEkS4YMJDbgD/4u5IpwoSCoi0KyA')) +with open("ncode.py", 'w') as f: + f.write(s.decode()) + + diff --git a/tutorial/tutorial.token b/tutorial/tutorial.token index d42145c..76e7958 100644 --- a/tutorial/tutorial.token +++ b/tutorial/tutorial.token @@ -1 +1 @@ -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 diff --git a/unitgrade_private2/__pycache__/__init__.cpython-36.pyc b/unitgrade_private2/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 55b1e08fb7a2e153288f98c9683097b615a31d04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 888 zcmZuv&1=*^6rVSlWYe^@#gBrX)LTG;Q1BokLJQs&w3W)T5VDzRo9*U<nb}sC>}h)~ z{vG~3-sbAbe?c$4msBmqS>DIKWZwI|-!HqHo6)cFxAd9E*mrj0;D7c8%_Qg~lQ3hW zKEep(n)IZ&gb|m%3@+KolLHxI6cP?t9R0vMY<QSc6FNTH2HGB)IYwvM6sEj|6)e3j z_gZfS<|*caGGHy&&#f;3-{HV)(1PSE{$*z=>|h$UtQ7>|$^&+Uu->uv>?7To;2odt zONKy#;bQwp7q+NQ9_Lwps*)se{9@w7Y*A)0xb?g)8?B5f>T2Qd*Oht_uQ^^DCz{Nj zx`8Zom6gf`>QiB?aejG*CFeJ~sH_upqbj%cr%Bor`Poc4A?IZi`!1*_Q<Yn{c3jL< z+VvTnD?O==a$Hspj=wT}59#>%cJJl*gHhUy?fg`Y_v?IKs>+)2yejO8&ZJ6l;(2D( ze=2EO;9F^$G@o%HS~I=CrIjGI@1kR{fxid@i!eUIs2^Z82YQBwB1h}wA)T(}VJlJ- zhOwtfK^>3_(Z!&*C@1W_yN{XM=!f@I-NA~Xu8iv)^na&OZ5^$f_5zcC({y6J7QZzV zdV2|0Ons7FdRP45TH;EOu${PK=umG>-x%VdW#ix$PEB((ZG?)~nT&aln@G70KI*}1 zl}TF5(4LGFaXX%Vh<OZ$vEL7%jva;FX5G^p?K)OELc&*NE$1`!ni%dnh7j^73jN<Q C#>7Ve diff --git a/unitgrade_private2/__pycache__/__init__.cpython-38.pyc b/unitgrade_private2/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 6e9fc2e8e234b8d82d001a785d86af68821340b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 918 zcmZuvO=}cE5UuX%nVlUI4Sqxj9yGTBS-f}&B4Pr9hXpkuvMh|V)172CJ0EoSCK@KE z<Qn`737+zo`s&HQ;K{0)1Y@w5eqCEVT~)7MZ7nWFfb0Ek@5^%!;3xgLxzGo1aGL>w z1j%MF>P|LdvM)U;PT7b{Uk0Zz@?=kjcnZlrK^*<UH!$4HD9H8l(dgq|$8B~HEKJ!s zZ`cJp_FCI(ybESlEu0Io7&KhJu)bssJK+p$&@jnQ`1j>wVSCfCfksdnXC8xXcnKfi zBYdVe6MW;J;Cl&JOfWoJ+Sa+v%iX70n(e8<VBq-S#EIEakzO))C##BDD`WDiJn}cH zQay|Nj#tKsI<<SQCv#n<g>r%Vnj34JU+kmH`L)hV>qJ$n(%riblGJ&&KT}S~c~Qr{ z3#!RfW!Cj~@|jB7IK2a<CzVl-%hIu(OVAIo9lzhLuZ_2i(q?SuduqH<W%ELn){N(6 zZg+JmRe~80Qmg*sNs=7DN|HhS4G9s=<Pb?KLEXNO0Biw&5fc#Mxq_!&MsLRG?rM=u zlC=^opm8M;T#%}47<)QEukI0$mNC;yWK;beJVMJYbOm(nx{Dse5rFH*i2r0HKh0sA z@huvEGwL<#H8^)D_>*I1#ndPHrFX`+fnUk}0|oA6F9nI+3ND5YXFwAXP4wg7R@IuQ z=|u#bfhv_T?^rw__bn<>M@6et(zOW@fKw}1J-vc<%r;}cOF$9ZrnWrW+Z*B9z-@cN VS4Ab~GxeJK-9`*q$fGFq{{R*Z#^L|~ diff --git a/unitgrade_private2/__pycache__/__init__.cpython-39.pyc b/unitgrade_private2/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 7bf2f7227e54c7b2301a6dc324a1119b37119103..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 944 zcmZWnO>fgc5Zzg?9VfIUh%bo)D%^Z=3P*$x5~asnTB#x&jN{!lPU8=)Hz`E!DZN(w zh4$FLv{z321y0P^ErhbGeIAdKH}mFgyt&z9xIXU%@dJmkAM~=mFvs5GsX_$FB+S^T zeZmN2S2|Lh!-z{)dgpB9$d2^!DJ1N(p!XC1!-fY5k=!m910T-^Jk=7xuqj;f8m{2X zX=0~#uAq8p*rlKdSk3j0aV6BS;=qhogXAmzefvz9&eX42EvStP2iPIDCUCDX&ik)e z^<{@avp*ps&jNYe6>>_34SK_6|4^qU&5yT}IGLz03@tw$TQOT^@io|cNs*OWRaIK# zOLw=()r+8Oc~MzW#%5wWGSzvUDeI}PX=N(wW+#}kZmHAUSW%QJxA$*JqB2cRX37dV z&&t5HUNN4k#MtgpI#W@@(^)7zE-GcY%q<*UGyMoh^y}kbQe<ji<`Xs8Es}Yra#IcF zd1{VzELDW%7O_$PXI>AYD8-*eQCNOMIV3VVMafFg{I?JoY~b#}7Genc31$+Y{LP}6 z3~UVBz~II(c10e;FmSYoS=}M%A_SoO6jSFdJjTdww8h&&QQ?XsFs`2>{!@|if3C<7 zU;b9qd&a5p7NY&n&%lVOOP4`97yOX%HS_lwn*V0q{9KUbjqPG+xgKj0rD+U-cSnIH znR*ieZ$c5vfVYYsQu>apUMowhSkmW;8fTnF8anzJ#sM4zZd-sDn*lePo3Cqh4RJFn V|7}*t`Aof|**A<n_`KKi-QO!K$2|Z5 diff --git a/unitgrade_private2/__pycache__/deployment.cpython-38.pyc b/unitgrade_private2/__pycache__/deployment.cpython-38.pyc deleted file mode 100644 index e5ef1a82ffe3b183639daf4d1197711d94e8aead..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1392 zcmZuxOK%%D5aw_nTCE;+5<5=PG;j}1p&W!DK+>Wxi~@0iq6edi8>Cn$Sgb`#T6-Td zN#$X1FI9`Cr=tHrI_97BH}Kk%`x6Qj?r>$-0a`9N94=?R`DQq~&pMru;J6#zO+N7m z`NMAB9}aH5#3^@BFv1`skzHv9IgM!UL=LRpyOCR;U#wo_F^@US{ShLc!9MBvr-(uJ zdep4vTun+QpQmcF%ZH^%R4I%*Nf{qfYji%kxHfTG7V-cH(PT~*WJxNjKvM>D`s>xo z(awS{$-=?+?sM{xJRwiX_hd|dLHk2@m%R9HKr65Em8X2|sm7enp=#&`o_t<8Dp)kN zvjnsWs-_BcFbCb-Bvts9phrtL8NEStXr=Z13~`UYJ$|ITpUDX73<K5H?i_Z>91z)M z-lC)3C2ATNIilis<Vu+T6TL}T1Cc>>4?kF}X;^~zv(~R@iaUzCs|@4+0GJ=JCg!(R z=XcHG0lCZ$Gh44(y0zF~p*_(phI4V<?&>(<CaIA(ZIp}X7`vrzEN<!+vTLhbx{Wv= z&S}-rpgSXqx76A*AWpH?WN5|3uvg&XyoOWWMlr_XzF5biL50-clB0%jHT{hqyZhuF z5k8vq97Fr}4ICNB-a*vP(t^uam9gM?ZSdDOkG~i^lU&Gwnoam%w;axLUMM-36{#AF zgz<PP(wB+i|9$gd!1y#P-&lwK^o<F(vs9{KnNN2%ElJD3$0r~sJq%dB<S{lF<3)Vl zef{mM9449ELFK~JKzRE1+1c4iJ1)wVIbxsO*-!H{OT^YF&G^<dQ4_g^&045*lydg5 zfma6lz1zl%t$}gNDKCsGc*0C5r&+4%)J^kv#-&QjLK@eG6t>xgEv;#!g`D!CGTkv( zvYd$_U->v0LU|%7#@sY{f%U<!1w$N`X<=xINS2QA`D+A}CXh+aYZA?`UJv=yMl;?k zk)lr+$9c(S8INP*j?#i5ZoLEtiR1-3nr50$OM!F`OlPH@yO;*@N|X{Nh+}IS$Gxs; zJ>}N?ah6DF8s$u_(yt|qjK506z!L*;L^EkX8c(W3DC6*gMP6P<^;fi_D__?a^d4y4 zCc^%sM9Y?G<9}~5z$1Ad1)(9d;W~9`7rGGOY{MOT3ql&e8m<BE*QpO7+@m3FgLs5K zJ=p6tgzXu#@o(Qf=wGUr*CakdEAgon)^o+qHrh(7DF)lCY<BGDBQ14|jgj$j{sCq` Bg3JH_ diff --git a/unitgrade_private2/__pycache__/deployment.cpython-39.pyc b/unitgrade_private2/__pycache__/deployment.cpython-39.pyc deleted file mode 100644 index a1183907f15d5618aa6e7584380d3fad693ad2d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1425 zcmZuxOK%%D5aw`qrPb<DC$VeCH4OJw<sdmZBsmyHfw~X$V4!t@plXA~Mx>;5_GOZC z5`%lGS~LX;wEYLtF@K5ZttX#*3(&g5mEE{Vx!`a}&V2I?Iozn-4hXhyKIulUT|)k_ zlk3gF$%okGJ_<${BxGPmnm|eiG<60JEZ5zETkT(s-oRrXbC~-*3~CIHNVj&5804@^ z&1%ZkBxmwbtR@G1n2Si|!nl)|@gcQFm#u@NkKMA6KLa6}%*dQ9NJ$lF%3wx+*(x3F z%;|#69b9)GAsZLJ8S=_j?h<)_mq7J>jdDzWLX2bgfIR-{TUvT$O?j%OJyoC48I*Nh z$DN0zqx^Y8I}1P?zig;L`!mpuby5Z|33@bjgV7D511qiP1H?UHC!Z_tM>2vcYhSgr zJA(r<14MS2H*afqftm(Jo>K8U@+YkJ1HDaH9g#tGj^CNDXjp*wyV7rHiZhC{n+)TB z2beRk2Ijn3<$TNH0r^kHH}8^PLEgEZGv>QmHg$8p#sX`vn+*H>T|29y2kWGYw{Bx! zaY9B-wK2b~o5;DPHgyYe-kZ^~twFa(6z{WnXh59ft&pJ=SIu4)mJ+#zU2dTm<B>mJ z#iN806M9KbLAaX!Mo-)$@`{KWnsgmQd%Fgn8c1Hjpq0cKm!Zl-!PCm%^{4$wp7Oq$ zPI&(yA5K%ADcPT9u^Nkr@vso_X{7kuEBE@07fJrYI`oPcCfG}2sfKx4?5|_wKe3au zF2>1Ed59IrcotrkXKydbhfyN;QMvLl5S;&YadC0h3bTA^j;LqbM{yb_k=Pl<3EwFq zHIX}5&6$cvF=y`^cxIs2-7;Qi4UC%?JTtD~5i@}-l2}z4o5snMOBLstG_DOP>}wGA zL`^-;WWk5ZbjDoCd@6=~>Ek2_<%uX8bJO4%o(w)G3~`*tnV~r%Svsc1pCh0&zKl{{ zk!XDUe8>wM&3Mm5j6Ojara7A?JPeIHiZh0|)!;vgB+t;%G~%?#1=4+D+DrA)#nh2k zq?9mz7+TXX>~>7^OK#0SN+KyuJ)f#&`jv!{sVx&R@P&bJKoe;|8c(W7DC6*q4ZO69 zT3gZ@-1w}zplhI2bqM<t6HR+eYyW$b_9r5DQ4ktH3+_;tcAx`3_7-f@O$ewDD>(Yq zZVdvo31|z%1N7;_VYe<eP%~@)R@}Yb8}-dKiEXqJA6Q{Mm+WkXFSUxIzqib0-@Y1Y Lsbg%6jGyxl)Q5)a diff --git a/unitgrade_private2/__pycache__/docker_helpers.cpython-38.pyc b/unitgrade_private2/__pycache__/docker_helpers.cpython-38.pyc deleted file mode 100644 index 64a1893f40852651e6f4f6145d3b8ddb4b5e6dcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3165 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 Jg2VQw{{ye@S>gZy diff --git a/unitgrade_private2/__pycache__/docker_helpers.cpython-39.pyc b/unitgrade_private2/__pycache__/docker_helpers.cpython-39.pyc deleted file mode 100644 index d8bd23d3e204c918fa1d3cc8d5f223591f47ac4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3217 zcmZ`*TW=$`6(%{O(dd4$Y)j4}nWpV-yojxh3$%byFN$Ov^a8kT(xR=}Kp2a(HM^sk zDTmsu2h>Y81^Sl0F9M8Tg7%LT{S5^ARP<s0L1Cb14>^)ouCgQOkUWPxhdd;|kA9<3 zMezOo=lA`8?IQF~-B|xDX#4>F=_v?`C=Su6DDeoJHW?99S|iJp_Q*D6X;d<$Gjb@Q z*5_zcrVh1f>2o}CDgGGkmtTTUbULwhvHUtvR^a~u{L`;6LIUOJ3N37bC8qc}`C@NT z5+t`|=?X8LUn5FvWTWR6Mp64clpZ}m*!+GAQaaQcAZicLqAcvZB+IlkKv(#SM+;Xt zd0Dzp-&<5<C3hw0G1~C=*}A_CYrbxUwUMy0s7kV^i7Is<T6ut8kT1z|@C+IkG?iPL z>e5n^wY7eANmeb1Xk!;iYZoo*;Grq&D~qV*by<`3D+0b7kDGZ*T30x4L%jo;X~+g- zz4b>jD6Cyct}tlYT62NHUxRk?E!lv+P1==B&@>^!mTayf6tRu(p7y<qXb*a|iXD-5 zA1&IlDO=|R_HsM>s_MaaocP?Id5_}B6UNVjkd4E5;)@`Tj@RweI2B_tOU5+dw+yM6 zQx<_Oe!`-$c5uu55ldn&J_!9Zy`>MLRPgyk#C&|geagn^ga?UO_Tfy-`Emk^@Uv4@ zPUAVBu$03IL#mtwi4K*0YpMR-J#GSxISXO(PhYL#-X|xj|2kvVz8N3+Gp766x{~!z z<4?UH@~BD6c*??r@$@jc%-oA&rYBkdHETGz#>1?)>^7z>6+vVYdh+tW@V)#G2!#)? zYssaU#?g1YgPE5($>k)Tom{)a`-di#``gME!Hg+8PFSRzB$zx2p%unHRV0X2`Ta}5 z(jWhf>)WWxqagVZGNP*Nnc)6J_+hBZFvEmRgeoOGh=g*|>0AV%vL|tJsYskEJMqO- zxlzn#rdCRmFc8X0S)$xCKV>=_%6=RNkt)O1>*jt()z^=M>TaF^wfmansJDvkHYe?7 zv(}QNN~!R<Q21PxIZNlENV&!|MY1zx!M^itv)*NXM_XT$XoV>(StoSVEY9WXOsg`S zk0fREuZSOlpX;9vr}2yp#eB+!a0liyILqm99tEP9CnWBvFW9E$Xt;4U)L=6aV?K`} z#+wi;JpzHME~(>paGkVq9e+q%+s0kowOrhUUmZ6|-Ra@CNwo%=TE9{!`~i%bl=Rr; zH-Pd6a{oj$<UYnZ(SVq?e}X>4!pbdx;F7Q<SwgTQO1UE)0FUwpD89cAiU4^l2zHl# z@fGHgC<Ej|4Zy3CSEYLc%oPJ{6$8vwS<M?ouZ<{wS&y<|{oA_L0CRIulNKd&%>ONG z1y}+cIvNgXS;K2&OM6iVl5qiWT@BO#ctyRc>x+h&e_KPQ)|*xb09b>m4eRO3y0kSM zn|NCQr#g?F<tWG|WUEzpzU8+~Yk9BGpJUT|8(I((;?!wF&u~53Im81`9Uz0wY6UQZ zBF>J^R=!JH)V(5$E^W*13q(7z1+ptUFVL6HVvBCQniFWqq9=P`@5wD_S&MDar`v{@ z`qGrLnGfW4zI_F8fmC0uzAf8^79bn2k9+yv*VuN<Y<J|&BlJmou?xO;<?eIH(x1s6 zep8$*Spq8QgQYL~+9G!}Wdo@UvVR!T_~kT6JuwZUi%aj*m_GsRFZ8i@HV;AyHs%F0 z|AM{i!38N$zqn7!4hILqzwio#JXl%|Z=Apw<HAppV!ZLb?52$ePrb?U@K-5-_W$4Q zqX$Rt4R1Lf0<RqpwXa)h@1YmZMKTwMVSG`9=5X22slg*dL<W1gzKdC<;4z>GUWPVz zw9vPf>-XAAO<Ua6q6Xp`|2}Kpo^Iw8Q@F-nW=*J<l|1k6T`%iD^gaRzfmANZ$OBlL z`4Q!HaFo6AvEjucnO?$UI-fAeuV&+{^}_<}JWV8CknX?1HFoel5XxcCf)v2Dp*0$x zcuR}67Vxly4typ!V_d)RcvlO}G<-{oo)$|WW<rBG_q6IwE&3o-SxiM7hbezctL_+; zf0i1S0box;f5P|<s7EdKj7{c(jh72k_C*+<?GHv<k2W7A{2SWc+j?AW?QOxosny@s z;;t6=w9vG{-_c@U3q4(aAH=A)!s+#=2mhWnxq4SL%K>x1Yv&bN@pp|`EA<-S&8+FA zG%<yJ4h`j)tfvYKexPG%f+@rhYHGXKaLov7uca}V(jS39jW%9!LmgLF{D58DCpNi* z@8G)SmU^T|zKh$Whj)NCnqYNF)9T|lp{Dtxiunon+9#){`;}rx&6Xd7(rjz7-)1Mx m#%gvkL)NBiCT`~Jet!lhDr66JE>nFrARu;aBO$c=i~j*@NKQBa diff --git a/unitgrade_private2/__pycache__/hidden_create_files.cpython-36.pyc b/unitgrade_private2/__pycache__/hidden_create_files.cpython-36.pyc deleted file mode 100644 index c10d3e26f4209a3cb1de8b9a8cbcd399dba53b33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3954 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 diff --git a/unitgrade_private2/__pycache__/hidden_create_files.cpython-38.pyc b/unitgrade_private2/__pycache__/hidden_create_files.cpython-38.pyc deleted file mode 100644 index 495660ae33400dda27e6cf64c7f939036a688a98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4588 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 a75&g(BN(LS&`d+9><85?d%}6qasC4{Dd9!{ diff --git a/unitgrade_private2/__pycache__/hidden_create_files.cpython-39.pyc b/unitgrade_private2/__pycache__/hidden_create_files.cpython-39.pyc deleted file mode 100644 index e9bdd98457f31c4fc351bf81ea01a6d12fb3d531..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4665 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 CujMNM diff --git a/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-36.pyc b/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-36.pyc deleted file mode 100644 index 7aa51f153e94d74ee8abd09983ea96ac0bc500f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2775 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 diff --git a/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-38.pyc b/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-38.pyc deleted file mode 100644 index 07472b0de4bf0b0f74be03f08a47140647e9cb27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4107 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 QdmFXy{=3?s`};rsFLbe-ssI20 diff --git a/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-39.pyc b/unitgrade_private2/__pycache__/hidden_gather_upload.cpython-39.pyc deleted file mode 100644 index 5c2e1ae6b68171c81e7ddf38fb9d45d3f1968a79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4210 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 F{{pLyr|JLz diff --git a/unitgrade_private2/__pycache__/token_loader.cpython-38.pyc b/unitgrade_private2/__pycache__/token_loader.cpython-38.pyc deleted file mode 100644 index 31836c22ea1ea42dda4ca41936fce6eb9ec9f4b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 978 zcmZuv!EVz)5S?AGQ#WngP>N_J4)mBFf`r5cRTWZuL#hN-1+s#So81&Q8#~!`DpA?u z&>lE|_=V)ipYRQPMI87APRzJf8zk16H@h?Ac{8(n+-fxe*VkX+`yT?}Cmj|S+TaC> z{emDtG7ZtSW)Z8;JmR(XB2RMZT|p$2FU63X&$Yb~HEO*y3OFzrEk{iTs(H48$j(-0 zE!xjn6e^g33Z?%L@U*7ZBy2&{R`*o<l3}bM9j(hobYHR~=q&xlir}!%NZqUk>SdID z6uXB|z>Jkx^_+hTD^{`@pF_ppz%zISui-6V#h|^)E4`U0`J7ckdQx0L<(C3E{6oi0 zVTj@%9v=<Fb%bJf5r|_8>z~5`yxZyVidDSilHb}k(B@U5z#2-g6qg=@$>YhY#1tLx z&@D+-B&oK}PYX4&9d2mw&L5jJD>}aOjIxeRA~88nv|~fZ&cB19tNZhE|9S7&Dr0-a z^n>ah<o)SLWrgicv$QxhiBvIWIZq1pA4`!BRTk?UqfB@F(TOn<wWup<57-9Z;1`?Z zkL%FxuFm_3wtHO)>?}L)<l?Cl`B-I67?nsT&TO7JpMJ4U=+qX@vkF10%+TCA4cEM; z9i&=0HgL?EHaZMZnpFa7>>5^Z=bCo~P>iJ*U?E%Q0c-ML(`-^y5XYk=&EohXe6lkz z)3n&>)<o$i?5MNh#Hr3t9M7$5jMM&5D?^>${+uNv6~|7bM)#v_!fs-1dYaYaT-e8T za<{VJjoNpI8#f;$R1!5wS;tB70k1hVMB}*39Mk3j@zYT7nc@8=3ys$!sJjn#N4cD8 awMWm;K0*js{qbO%^g3&JArHyM1O68u`}k-8 diff --git a/unitgrade_private2/codejudge_example/__pycache__/__init__.cpython-38.pyc b/unitgrade_private2/codejudge_example/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index ae23c7f18121d96a18d55cdeddffe576411baec2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181 zcmWIL<>g`kg6WlVi6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o11%*(xTqIJKxa zrld3@HO3`BximL5ucSDpG%vFxy(lpyHNK!Ivn;VB6;s40COJPPHLElwJvBbHA~Clh hCp9KMJ`<=hK3=b&@)n0pZhlH>PO2Tq&d)&1007uJF(Lo} diff --git a/unitgrade_private2/codejudge_example/codejudge_sum.py b/unitgrade_private2/codejudge_example/codejudge_sum.py deleted file mode 100644 index 8a43e38..0000000 --- a/unitgrade_private2/codejudge_example/codejudge_sum.py +++ /dev/null @@ -1,35 +0,0 @@ -# Implement https://www.codejudge.net/docs/quickstartfiles#the-problem -from unitgrade.unitgrade import QuestionGroup, Report, QPrintItem -from unitgrade.unitgrade_helpers import evaluate_report_student -from cs101courseware_example import homework1 -import random - -class SumItem(QPrintItem): - ls = [] - def __init__(self, question, *args, **kwargs): - super().__init__(question, *args, **kwargs) - - def compute_answer_print(self): - random.seed(42) - - from unitgrade_private.codejudge_example.sumfac import sumlist - return sumlist(self.ls) - -class SumQuestion(QuestionGroup): - title = "Sum of two integers" - def __init__(self): - pass - - class FactorialQuestion(QPrintItem): - n = 3 - def compute_answer_print(self): - from unitgrade.unitgrade_private.codejudge_sum import factorial - return factorial(self.n) - -class Report1(Report): - title = "CS 101 Report 1" - questions = [(ListReversalQuestion, 5), (LinearRegressionQuestion, 13)] - pack_imports = [homework1] # Include this file in .token file - -if __name__ == "__main__": - evaluate_report_student(Report1()) diff --git a/unitgrade_private2/codejudge_example/sumfac.py b/unitgrade_private2/codejudge_example/sumfac.py deleted file mode 100644 index f429a12..0000000 --- a/unitgrade_private2/codejudge_example/sumfac.py +++ /dev/null @@ -1,6 +0,0 @@ -def sumlist( ls ): - return sum(ls) - -if __name__ == "__main__": - sumlist([1, 4, 4]) - -- GitLab