diff options
author | jwansek <eddie.atten.ea29@gmail.com> | 2022-05-01 16:03:24 +0100 |
---|---|---|
committer | jwansek <eddie.atten.ea29@gmail.com> | 2022-05-01 16:03:24 +0100 |
commit | abc7f067ff20bc2bd07d9236c30055549481547c (patch) | |
tree | d486cb4efda107633bcf243e5f60fdebd7094e5f /docs | |
parent | ee1b57ec6197c554f3c011f9a648e2222d845994 (diff) | |
download | Smarker-abc7f067ff20bc2bd07d9236c30055549481547c.tar.gz Smarker-abc7f067ff20bc2bd07d9236c30055549481547c.zip |
Finished plagarism detector, added docs
Diffstat (limited to 'docs')
-rw-r--r-- | docs/source/_static/QuickStart/simple_assessment.yml | 12 | ||||
-rw-r--r-- | docs/source/_static/QuickStart/simple_submission_1/euclid.py | 22 | ||||
-rw-r--r-- | docs/source/_static/QuickStart/simple_submission_2/euclid.py | 10 | ||||
-rw-r--r-- | docs/source/_static/QuickStart/simple_submission_3/euclid.py | 11 | ||||
-rw-r--r-- | docs/source/_static/QuickStart/simple_submission_4/euclid.py | 16 | ||||
-rw-r--r-- | docs/source/_static/readme_matrix.png | bin | 0 -> 17528 bytes | |||
-rw-r--r-- | docs/source/_static/report.txt | 9 | ||||
-rw-r--r-- | docs/source/_static/simple.json | 60 | ||||
-rw-r--r-- | docs/source/_static/simple.txt | 89 | ||||
-rw-r--r-- | docs/source/assessments.rst | 24 | ||||
-rw-r--r-- | docs/source/docker.rst | 8 | ||||
-rw-r--r-- | docs/source/index.rst | 6 | ||||
-rw-r--r-- | docs/source/quickstart.rst | 97 | ||||
-rw-r--r-- | docs/source/readme.md | 4 | ||||
-rw-r--r-- | docs/source/reflect.rst | 23 |
15 files changed, 388 insertions, 3 deletions
diff --git a/docs/source/_static/QuickStart/simple_assessment.yml b/docs/source/_static/QuickStart/simple_assessment.yml new file mode 100644 index 0000000..414f00b --- /dev/null +++ b/docs/source/_static/QuickStart/simple_assessment.yml @@ -0,0 +1,12 @@ +name: simple_assessment +files: + - euclid.py: + functions: + - gcd(2) + tests: + - | + assert euclid.gcd(8,12) == 4 + run: + - python euclid.py: + regexes: + - ^4 diff --git a/docs/source/_static/QuickStart/simple_submission_1/euclid.py b/docs/source/_static/QuickStart/simple_submission_1/euclid.py new file mode 100644 index 0000000..f72707a --- /dev/null +++ b/docs/source/_static/QuickStart/simple_submission_1/euclid.py @@ -0,0 +1,22 @@ +# the newest! +# assessment 1 + +def gcd(m,n) -> int: + """Calculates the greatest common denominator between two numbers. + + Args: + x (int): Number One + y (int): Number Two + + Returns: + int: The GCD of the two numbers + """ + if m< n: + (m,n) = (n,m) + if(m%n) == 0: + return n + else: + return (gcd(n, m % n)) # recursion taking place + +# gcd +print(gcd(8,12)) diff --git a/docs/source/_static/QuickStart/simple_submission_2/euclid.py b/docs/source/_static/QuickStart/simple_submission_2/euclid.py new file mode 100644 index 0000000..0819bc5 --- /dev/null +++ b/docs/source/_static/QuickStart/simple_submission_2/euclid.py @@ -0,0 +1,10 @@ +def gcd(m,n): + if m< n: + (m,n) = (n,m) + if(m%n) == 0: + return n + else: + return (gcd(n, m % n)) # recursion taking place + +# calling function with parameters and printing it out +print(gcd(8,12)) diff --git a/docs/source/_static/QuickStart/simple_submission_3/euclid.py b/docs/source/_static/QuickStart/simple_submission_3/euclid.py new file mode 100644 index 0000000..73e7d9c --- /dev/null +++ b/docs/source/_static/QuickStart/simple_submission_3/euclid.py @@ -0,0 +1,11 @@ +def gcd(p,q): + """Docstring gcd""" + if p < q: + (p,q) = (q,p) + if(p%q) == 0: + return q + else: + return (gcd(q, p % q)) # recursion taking place + +# calling function with parameters and printing it out +print(gcd(8,12)) diff --git a/docs/source/_static/QuickStart/simple_submission_4/euclid.py b/docs/source/_static/QuickStart/simple_submission_4/euclid.py new file mode 100644 index 0000000..064d1e5 --- /dev/null +++ b/docs/source/_static/QuickStart/simple_submission_4/euclid.py @@ -0,0 +1,16 @@ +# assessment A +# student id: 4 + +def gcd(x,y): + if x > y: + small = y + else: + small = x + for i in range(1, small+1): + if((x % i == 0) and (y % i == 0)): + g = i + + return g + +# calling function with parameters and printing it out +print(gcd(8,12)) diff --git a/docs/source/_static/readme_matrix.png b/docs/source/_static/readme_matrix.png Binary files differnew file mode 100644 index 0000000..e91358b --- /dev/null +++ b/docs/source/_static/readme_matrix.png diff --git a/docs/source/_static/report.txt b/docs/source/_static/report.txt new file mode 100644 index 0000000..b78e20b --- /dev/null +++ b/docs/source/_static/report.txt @@ -0,0 +1,9 @@ +euclid.py
+ 2 ... 1
+2 100.00 ... 94.74
+3 100.00 ... 94.74
+4 63.16 ... 57.89
+1 94.74 ... 100.00
+
+[4 rows x 4 columns]
+Written report to /Smarker/plagarism_report_details.pickle
diff --git a/docs/source/_static/simple.json b/docs/source/_static/simple.json new file mode 100644 index 0000000..40accc7 --- /dev/null +++ b/docs/source/_static/simple.json @@ -0,0 +1,60 @@ +{ + "files": [ + { + "euclid.py": { + "functions": [ + { + "gcd(2)": { + "present": true, + "documentation": { + "comments": "None", + "doc": "Docstring gcd" + }, + "arguments": "(p, q)", + "minimum_arguments": 2, + "source_code": "def gcd(p,q):\n \"\"\"Docstring gcd\"\"\"\n if p < q:\n (p,q) = (q,p)\n if(p%q) == 0:\n return q\n else:\n return (gcd(q, p % q)) # recursion taking place" + } + } + ], + "run": [ + { + "python euclid.py": { + "regexes": { + "^4": [ + "4" + ] + }, + "full_output": "4\n" + } + } + ], + "tests": [ + "assert euclid.gcd(8,12) == 4\n" + ], + "present": true, + "has_exception": false, + "documentation": { + "comments": "None", + "doc": "None" + } + } + } + ], + "name": "simple_assessment", + "student_no": "123456790", + "test_results": { + "pytest_report": "============================= test session starts ==============================\nplatform linux -- Python 3.10.4, pytest-7.1.1, pluggy-1.0.0 -- /usr/bin/python3\ncachedir: .pytest_cache\nrootdir: /tmp/tmpjzy020i4/simple_submission_3\ncollecting ... collected 1 item\n\n../../../../../../tmp/tmpjzy020i4/simple_submission_3/test_euclid.py::test_1 PASSED [100%]\n\n--------------- generated xml file: /tmp/tmpyu0qypji/report.xml ----------------\n============================== 1 passed in 0.01s ===============================\n", + "junitxml": "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites><testsuite name=\"pytest\" errors=\"0\" failures=\"0\" skipped=\"0\" tests=\"1\" time=\"0.019\" timestamp=\"2022-05-01T15:03:57.143881\" hostname=\"thonkpad2\"><testcase classname=\"test_euclid\" name=\"test_1\" time=\"0.001\" /></testsuite></testsuites>", + "meta": { + "name": "pytest", + "errors": "0", + "failures": "0", + "skipped": "0", + "tests": "1", + "time": "0.019", + "timestamp": "2022-05-01T15:03:57.143881", + "hostname": "thonkpad2" + } + }, + "class_tree": {} +} diff --git a/docs/source/_static/simple.txt b/docs/source/_static/simple.txt new file mode 100644 index 0000000..b6dcc16 --- /dev/null +++ b/docs/source/_static/simple.txt @@ -0,0 +1,89 @@ +============================= test session starts ============================== +platform linux -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0 -- /usr/bin/python3 +cachedir: .pytest_cache +rootdir: /tmp/tmp398_c3x6/simple_submission_1 +collecting ... collected 1 item + +../tmp/tmp398_c3x6/simple_submission_1/test_euclid.py::test_1 PASSED [100%] + +--------------- generated xml file: /tmp/tmpceag5_nn/report.xml ---------------- +============================== 1 passed in 0.01s =============================== +4 +=== simple_assessment - Student ID: 1 Automatic marking report === +Report generated at 2022-05-01 15:49:15.701124 + +== Class Tree: == + +{} + + +== File Analysis == + + = euclid.py = + Documentation: + 28 characters long + Comments: + ``` + # the newest! + # assessment 1 + ``` + Docstring: + *** No docstring present *** + Functions: + gcd(2): + Arguments: + (m, n) -> int + Enough? YES + Documentation: + 164 characters long + Comments: + *** No comments present *** + Docstring: + ``` + Calculates the greatest common denominator between two numbers. + + Args: + x (int): Number One + y (int): Number Two + + Returns: + int: The GCD of the two numbers + ``` + Source: + 15 lines (356 characters) + Code: + ``` + def gcd(m,n) -> int: + """Calculates the greatest common denominator between two numbers. + + Args: + x (int): Number One + y (int): Number Two + + Returns: + int: The GCD of the two numbers + """ + if m< n: + (m,n) = (n,m) + if(m%n) == 0: + return n + else: + return (gcd(n, m % n)) # recursion taking place + ``` + Runtime Analysis: + Command `python euclid.py`: + Monitor: + stdout + Regexes: + `^4`: + Found occurrences: 1 + Occurrences list: + 4 + Full runtime output: + ``` + 4 + + ``` + + + diff --git a/docs/source/assessments.rst b/docs/source/assessments.rst new file mode 100644 index 0000000..a8d7311 --- /dev/null +++ b/docs/source/assessments.rst @@ -0,0 +1,24 @@ +.. _assessments: + +``assessments.py`` +================== + +``assessments.py`` contains many useful arguments for interacting with the database: + +.. argparse:: + :module: assessments + :func: getparser + :prog: python Smarker/assessments.py + +Classes +******* + +.. autoclass:: assessments.SimilarityMetric + :members: + +Functions +********* + +.. autofunction:: assessments.visualise_matrix + +.. autofunction:: assessments.generate_plagarism_report
\ No newline at end of file diff --git a/docs/source/docker.rst b/docs/source/docker.rst index 7c3237a..232c7f4 100644 --- a/docs/source/docker.rst +++ b/docs/source/docker.rst @@ -41,4 +41,10 @@ To list assessments in the database using docker: .. code-block:: bash - sudo docker run -it --entrypoint python --rm smarker assessments.py --list yes
\ No newline at end of file + sudo docker run -it --entrypoint python --rm smarker assessments.py --list yes + +.. code-block:: bash + + touch out/report.pickle && sudo docker run -v "$(pwd)/out/report.pickle":/Smarker/plagarism_report_details.pickle -it --entrypoint python --rm smarker assessments.py --plagarism_report example + +If a file doesn't exist before it's passed through as a volume in docker, it will be created automatically as a *directory*- this causes issues if the docker image produces a file so we make a blank file first.
\ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index e36cc86..f2d7426 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,5 +1,7 @@ .. mdinclude:: readme.md +Read the :ref:`quickstart`. + Setting up ---------- @@ -26,6 +28,8 @@ Please note that the ``-o`` flag is required for rendering to PDFs. ``assessments.py`` contains many useful arguments for interacting with the database: +Also see :ref:`assessments` + .. argparse:: :module: assessments :func: getparser @@ -37,11 +41,13 @@ Please note that the ``-o`` flag is required for rendering to PDFs. reflect.rst database.rst + assessments.rst .. toctree:: :maxdepth: 2 :caption: Other Pages: + quickstart.rst configfile.rst docker.rst assessmentyaml.rst diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst new file mode 100644 index 0000000..08f3cec --- /dev/null +++ b/docs/source/quickstart.rst @@ -0,0 +1,97 @@ +.. _quickstart: + +Quick start guide +================= + +This guide implements a simple assessment to make a *greatest common denominator* function. + +First make an assessment yaml file: + +.. literalinclude:: _static/QuickStart/simple_assessment.yml + :linenos: + :language: yaml + +This expects a single function called ``gcd()`` in a file called ``euclid.py`` with no fewer +than two arguments. It expects it to print ``4`` to stdout when executed. It also runs pytest +on the function. + +Then add it to the database: + +.. code-block:: bash + + docker run -v "$(pwd)/docs/source/_static/QuickStart/simple_assessment.yml":/tmp/assessment.yml -it --entrypoint python --rm smarker assessments.py -c /tmp/assessment.yml + +If using windows, I recommend using the mingw shell since powershell is bad at dealing with relative file paths in docker. + +Then add some students: + +.. code-block:: bash + + docker run -v "$(pwd)/docs/source/_static/QuickStart/simple_assessment.yml":/tmp/assessment.yml -it --entrypoint python --rm smarker assessments.py -s "1,Alice,a.bar@uea.ac.uk" + docker run -v "$(pwd)/docs/source/_static/QuickStart/simple_assessment.yml":/tmp/assessment.yml -it --entrypoint python --rm smarker assessments.py -s "2,Bob,b.bar@uea.ac.uk" + docker run -v "$(pwd)/docs/source/_static/QuickStart/simple_assessment.yml":/tmp/assessment.yml -it --entrypoint python --rm smarker assessments.py -s "3,Christina,c.bar@uea.ac.uk" + docker run -v "$(pwd)/docs/source/_static/QuickStart/simple_assessment.yml":/tmp/assessment.yml -it --entrypoint python --rm smarker assessments.py -s "4,Dan,d.bar@uea.ac.uk" + +Now we are ready to make some reports! The submissions are zip files with the student's id as the name. First lets just use the default parameters: + +.. code-block:: bash + + docker run -v "$(pwd)/docs/source/_static/QuickStart/1.zip":/tmp/1.zip -e submission=/tmp/1.zip -e assessment=simple_assessment --rm smarker + +This prints out the result as text to stdout: + +.. literalinclude:: _static/simple.txt + +Smarker can render to text, markdown, json, yaml and PDF, and produce less information, but for now we'll only use the defaults. +Do the same for the other three submissions. + +We can now generate a plagarism report. But first, lets look at the actual submitted files. Here's the submission from student 1: + +.. literalinclude:: _static/QuickStart/simple_submission_1/euclid.py + :linenos: + :language: python + +Student 2: + +.. literalinclude:: _static/QuickStart/simple_submission_2/euclid.py + :linenos: + :language: python + +Student 3: + +.. literalinclude:: _static/QuickStart/simple_submission_3/euclid.py + :linenos: + :language: python + +Student 4: + +.. literalinclude:: _static/QuickStart/simple_submission_4/euclid.py + :linenos: + :language: python + +From this we can tell that student 2 has copied from student 1 (or the other way around), changing only the header comments. +Student 3 has also copied from student 1, but has changed the variable names in an attempt to hide it. Submission 4 is completely different. + +Now we can generate a plagarism report: + +.. code-block:: bash + + touch out/report.pickle && sudo docker run -v "$(pwd)/out/report.pickle":/Smarker/plagarism_report_details.pickle -it --entrypoint python --rm smarker assessments.py --plagarism_report simple_assessment + +Which produces a pickled report matrix, and prints out to stdout: + +.. code-block:: text + + 2 3 4 1 + 2 100.00 100.00 42.86 94.74 + 3 100.00 100.00 42.86 94.74 + 4 63.16 63.16 100.00 57.89 + 1 94.74 94.74 39.29 100.00 + Written report to /Smarker/plagarism_report_details.pickle + +If we run it outside of docker, we can also get it rendered nicely in matplotlib: + +.. image:: _static/readme_matrix.png + +The matrix isn't symmetrical, which is intentional, since it considers the difference in complexity between submissions. This can be useful for +finding the culprit in copying.
\ No newline at end of file diff --git a/docs/source/readme.md b/docs/source/readme.md index 2914835..3b61499 100644 --- a/docs/source/readme.md +++ b/docs/source/readme.md @@ -32,3 +32,7 @@ File with an exception  Using pytest + + + +Plagarism and collusion detection matrix diff --git a/docs/source/reflect.rst b/docs/source/reflect.rst index c059206..6c0767a 100644 --- a/docs/source/reflect.rst +++ b/docs/source/reflect.rst @@ -1,5 +1,24 @@ ``reflect.py``: Getting information about code ============================================== -.. automodule:: reflect - :members:
\ No newline at end of file +Classes +******* + +.. autoclass:: reflect.Reflect + :members: + +.. autoexception:: reflect.MonitoredFileNotInProducedFilesException + +Thrown if the user has tried to monitor a file that isn't in the list of produced files in the :ref:`assessmentyaml`. + +Functions +********* + +.. autofunction:: reflect.gen_reflection_report + +Generates a json file report. It is quite a complex structure, but it is made so users can add other rendering templates +later on. For example, the :ref:`quickstart` looks like this: + +.. literalinclude:: _static/simple.json + :linenos: + :language: yaml
\ No newline at end of file |