diff options
-rw-r--r-- | ExampleAssessments/example.yml | 95 | ||||
-rw-r--r-- | ExampleSubmission/animals.py | 50 | ||||
-rw-r--r-- | ExampleSubmission/example.py | 148 | ||||
-rw-r--r-- | ExampleSubmission/test_dont_test_me.py | 12 | ||||
-rw-r--r-- | examplerun.bat | 4 | ||||
-rw-r--r-- | mark.py | 51 | ||||
-rw-r--r-- | misc_classes.py | 84 | ||||
-rw-r--r-- | reflect.py | 46 | ||||
-rw-r--r-- | smarker.conf | 24 | ||||
-rw-r--r--[l---------] | templates/text.jinja2 | bin | 28 -> 28 bytes |
10 files changed, 303 insertions, 211 deletions
diff --git a/ExampleAssessments/example.yml b/ExampleAssessments/example.yml index be1609f..b6e7b12 100644 --- a/ExampleAssessments/example.yml +++ b/ExampleAssessments/example.yml @@ -1,43 +1,54 @@ -name: CMP-5021B-19-20
-files:
- - example.py:
- classes:
- - Application:
- methods:
- - __init__(3)
- - a_method_with_defaults(3)
- - add(3)
- - aMethodThatIsntThere(1)
- functions:
- - hello_world(2)
- - an_undocumented_function(0)
- - aFunctionThatIsntThere(2)
- - greet(2)
- tests:
- - |
- dateOne = example.MyDate(day = 12, month = 8, year = 2001)
- dateTwo = example.MyDate(day = 12, month = 8, year = 2001)
- assert dateOne == dateTwo
- - |
- dateOne = example.MyDate(day = 12, month = 8, year = 2001)
- dateTwo = example.MyDate(day = 5, month = 4, year = 1999)
- assert dateOne == dateTwo
- - aFileThatIsntThere.py:
- functions:
- - hello_world(2)
- - animals.py:
- classes:
- - Dog:
- - Cat:
- - Kitten:
- tests:
- - |
- nibbles = animals.Kitten()
- assert nibbles.speak() == "nyaa~~"
- - |
- milton = animals.Dog()
- assert milton.move() == "*moves*"
-dependencies:
- files:
- - ../wsData.txt
+name: CMP-5021B-19-20 +files: + - example.py: + classes: + - Application: + methods: + - __init__(3) + - a_method_with_defaults(3) + - add(3) + - aMethodThatIsntThere(1) + functions: + - hello_world(2) + - an_undocumented_function(0) + - aFunctionThatIsntThere(2) + - greet(2) + tests: + - | + dateOne = example.MyDate(day = 12, month = 8, year = 2001) + dateTwo = example.MyDate(day = 12, month = 8, year = 2001) + assert dateOne == dateTwo + - | + dateOne = example.MyDate(day = 12, month = 8, year = 2001) + dateTwo = example.MyDate(day = 5, month = 4, year = 1999) + assert dateOne == dateTwo + run: + - python example.py 1: + regexes: + - hello world\! + - aFileThatIsntThere.py: + functions: + - hello_world(2) + - animals.py: + classes: + - Dog: + - Cat: + - Kitten: + tests: + - | + nibbles = animals.Kitten() + assert nibbles.speak() == "nyaa~~" + - | + milton = animals.Dog() + assert milton.move() == "*moves*" + run: + - python animals.py: + monitor: animals.txt + regexes: + - TURRÓN +produced_files: + - animals.txt +dependencies: + files: + - ../wsData.txt - ../IncludeDirectory
\ No newline at end of file diff --git a/ExampleSubmission/animals.py b/ExampleSubmission/animals.py index d32d6ff..924c6fb 100644 --- a/ExampleSubmission/animals.py +++ b/ExampleSubmission/animals.py @@ -1,24 +1,26 @@ -import datetime
-
-class Animal:
- def __init__(self):
- self.birthday = datetime.datetime.now()
-
- def move(self):
- return "*moves*"
-
-class Dog(Animal):
- def speak(self):
- return "woof"
-
-class Cat(Animal):
- def speak(self):
- return "meow"
-
-class Kitten(Cat):
- """nyaa~~~
- """
- def speak(self):
- return "meow (but cuter)"
-
-print()
\ No newline at end of file +import datetime + +class Animal: + def __init__(self): + self.birthday = datetime.datetime.now() + + def move(self): + return "*moves*" + +class Dog(Animal): + def speak(self): + return "woof" + +class Cat(Animal): + def speak(self): + return "meow" + +class Kitten(Cat): + """nyaa~~~ + """ + def speak(self): + return "meow (but cuter)" + +kitten = Kitten() +with open("animals.txt", "w") as f: + f.write(kitten.speak())
\ No newline at end of file diff --git a/ExampleSubmission/example.py b/ExampleSubmission/example.py index af0331c..9c1d06a 100644 --- a/ExampleSubmission/example.py +++ b/ExampleSubmission/example.py @@ -1,74 +1,74 @@ -# Eden Attenborough
-# 12-01-21
-
-import tkinter as tk
-from dataclasses import dataclass
-
-class Application(tk.Tk):
- """An example class, which implements a GUI by inheriting from tkinter.Tk
- """
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- self.title("Hello World!")
-
- def a_method_with_defaults(self, n:str, arg_1 = "hello", arg_2 = "world", count:int = 3):
- """Adds two strings together with a space between them.
-
- Args:
- arg_1 (str, optional): The first string to add. Defaults to "hello".
- arg_2 (str, optional): The second string to add. Defaults to "world".
-
- Returns:
- str: A concatinated string.
- """
- return "%s %s" % (arg_1, arg_2)
-
- def add(self, num1:int, num2:int) -> int:
- """Adds two numbers together and returns the output
-
- Args:
- num1 (int): The first number to add
- num2 (int): The second number to add
-
- Returns:
- int: The two numbers added together
- """
- return num1 + num2
-
-@dataclass
-class MyDate:
- year:int
- month:int
- day:int
-
- def __eq__(self, otherDate):
- return self.year == otherDate.year and self.month == otherDate.month and self.day == otherDate.day
-
- def __str__(self):
- "%d-%d-%4d" % (self.day, self.month, self.year)
-
-
-# hello world!
-def hello_world(times):
- """Prints 'hello world!' to stdout. Prints it out `times` times.
-
- Args:
- times (int): The number of times to print out hello world.
-
- Returns:
- str: Hello world, repeated as many times as nessicary
- """
- return "hello world! " * 3
-
-def an_undocumented_function():
- return 3.14156
-
-# kwonlyargs demo
-def greet(*names, greeting="Hello"):
- for name in names:
- print(greeting, name)
-
-if __name__ == "__main__":
- app = Application()
- app.mainloop()
\ No newline at end of file +# Eden Attenborough +# 12-01-21 + +import tkinter as tk +from dataclasses import dataclass +import sys + +class Application(tk.Tk): + """An example class, which implements a GUI by inheriting from tkinter.Tk + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.title("Hello World!") + + def a_method_with_defaults(self, n:str, arg_1 = "hello", arg_2 = "world", count:int = 3): + """Adds two strings together with a space between them. + + Args: + arg_1 (str, optional): The first string to add. Defaults to "hello". + arg_2 (str, optional): The second string to add. Defaults to "world". + + Returns: + str: A concatinated string. + """ + return "%s %s" % (arg_1, arg_2) + + def add(self, num1:int, num2:int) -> int: + """Adds two numbers together and returns the output + + Args: + num1 (int): The first number to add + num2 (int): The second number to add + + Returns: + int: The two numbers added together + """ + return num1 + num2 + +@dataclass +class MyDate: + year:int + month:int + day:int + + def __eq__(self, otherDate): + return self.year == otherDate.year and self.month == otherDate.month and self.day == otherDate.day + + def __str__(self): + "%d-%d-%4d" % (self.day, self.month, self.year) + + +# hello world! +def hello_world(times): + """Prints 'hello world!' to stdout. Prints it out `times` times. + + Args: + times (int): The number of times to print out hello world. + + Returns: + str: Hello world, repeated as many times as nessicary + """ + return "hello world! " * times + +def an_undocumented_function(): + return 3.14156 + +# kwonlyargs demo +def greet(*names, greeting="Hello"): + for name in names: + print(greeting, name) + +if __name__ == "__main__": + print(hello_world(int(sys.argv[1])))
\ No newline at end of file diff --git a/ExampleSubmission/test_dont_test_me.py b/ExampleSubmission/test_dont_test_me.py index 808879c..511c713 100644 --- a/ExampleSubmission/test_dont_test_me.py +++ b/ExampleSubmission/test_dont_test_me.py @@ -1,7 +1,7 @@ -"""My default pytest will assume that all files prefixed with
-'test' are test files. This file is here to make sure that
-pytest only runs on the files it should run on.
-"""
-
-def test_1():
+"""My default pytest will assume that all files prefixed with +'test' are test files. This file is here to make sure that +pytest only runs on the files it should run on. +""" + +def test_1(): assert 1 == 2
\ No newline at end of file diff --git a/examplerun.bat b/examplerun.bat index 3364a63..8ddbc7d 100644 --- a/examplerun.bat +++ b/examplerun.bat @@ -1,3 +1,3 @@ -zip -r 100301654.zip .\ExampleSubmission\
-python .\mark.py -s 100301654.zip -a .\ExampleAssessments\example.yml -f md -o auto
+zip -r 100301654.zip .\ExampleSubmission\ +python .\mark.py -s 100301654.zip -a .\ExampleAssessments\example.yml -f yaml rm 100301654.zip
\ No newline at end of file @@ -1,6 +1,7 @@ from dataclasses import dataclass import jinja_helpers import configparser +import misc_classes import argparse import tempfile import zipfile @@ -11,59 +12,15 @@ import yaml import json import os -@dataclass -class FileDependencies: - assessment_struct:dict - - def __enter__(self): - try: - for file_dep in self.assessment_struct["dependencies"]["files"]: - if os.path.isfile(file_dep): - shutil.copy(file_dep, os.path.split(file_dep)[-1]) - else: - shutil.copytree(file_dep, os.path.split(file_dep)[-1]) - # print("%s --> %s" % (file_dep, os.path.join(os.getcwd(), os.path.split(file_dep)[-1]))) - except KeyError: - pass - - def __exit__(self, type, value, traceback): - stuff_to_remove = [] - try: - stuff_to_remove += [os.path.split(f)[-1] for f in self.assessment_struct["dependencies"]["files"]] - except KeyError: - pass - try: - stuff_to_remove += self.assessment_struct["produced_files"] - except KeyError: - pass - - for file_dep in stuff_to_remove: - if os.path.exists(file_dep): - if os.path.isfile(file_dep): - os.remove(file_dep) - else: - shutil.rmtree(file_dep) - def main(**kwargs): student_no = os.path.splitext(os.path.split(args["submission"])[-1])[0] - with tempfile.TemporaryDirectory() as tempdir: - with zipfile.ZipFile(args["submission"]) as z: - z.extractall(tempdir) - - # some zipping applications make a folder inside the zip with the files in that folder. - # try to deal with this here. - submission_files = tempdir - if os.path.isdir( - os.path.join(submission_files, os.listdir(submission_files)[0]) - ) and len(os.listdir(submission_files)) == 1: - submission_files = os.path.join(submission_files, os.listdir(submission_files)[0]) - + with misc_classes.ExtractZipToTempDir(args["submission"]) as submission_files: with open(kwargs["assessment"], "r") as f: assessment_struct = yaml.safe_load(f) - with FileDependencies(assessment_struct): - output = reflect.gen_reflection_report(submission_files, assessment_struct, student_no, kwargs) + with misc_classes.FileDependencies(assessment_struct): + output = reflect.gen_reflection_report(submission_files, assessment_struct, student_no, kwargs, args["submission"]) output_file = kwargs["out"] if kwargs["format"] == "yaml": diff --git a/misc_classes.py b/misc_classes.py new file mode 100644 index 0000000..6ac5897 --- /dev/null +++ b/misc_classes.py @@ -0,0 +1,84 @@ +from dataclasses import dataclass +import tempfile +import zipfile +import shutil +import os + +@dataclass +class ExtractZipToTempDir(tempfile.TemporaryDirectory): + zip_file:str + + def __post_init__(self): + super().__init__() + + def __enter__(self): + return self.extract() + + def __exit__(self, exc, value, tb): + self.cleanup() + + def extract(self): + with zipfile.ZipFile(self.zip_file) as z: + z.extractall(self.name) + + # some zipping applications make a folder inside the zip with the files in that folder. + # try to deal with this here. + submission_files = self.name + if os.path.isdir( + os.path.join(submission_files, os.listdir(submission_files)[0]) + ) and len(os.listdir(submission_files)) == 1: + submission_files = os.path.join(submission_files, os.listdir(submission_files)[0]) + + return submission_files + +@dataclass +class FileDependencies: + assessment_struct:dict + to_:str=str(os.getcwd()) + + def __enter__(self): + self.get_deps() + + def __exit__(self, type, value, traceback): + self.rm_deps() + + def get_deps(self): + try: + for file_dep in self.assessment_struct["dependencies"]["files"]: + if os.path.isfile(file_dep): + shutil.copy(file_dep, os.path.join(self.to_, os.path.split(file_dep)[-1])) + else: + shutil.copytree(file_dep, os.path.join(self.to_, os.path.split(file_dep)[-1])) + except KeyError: + pass + + def rm_deps(self): + stuff_to_remove = [] + try: + stuff_to_remove += [os.path.split(f)[-1] for f in self.assessment_struct["dependencies"]["files"]] + except KeyError: + pass + try: + stuff_to_remove += self.assessment_struct["produced_files"] + except KeyError: + pass + + for file_dep in stuff_to_remove: + file_dep = os.path.join(self.to_, file_dep) + # print("rm: ", file_dep) + if os.path.exists(file_dep): + if os.path.isfile(file_dep): + os.remove(file_dep) + else: + shutil.rmtree(file_dep) + +@dataclass +class ChangeDirectory: + target:str + cwd:str=str(os.getcwd()) + + def __enter__(self): + os.chdir(self.target) + + def __exit__(self, type, value, traceback): + os.chdir(self.cwd) @@ -2,6 +2,8 @@ import xml.etree.ElementTree as etree from dataclasses import dataclass from functools import reduce from operator import getitem +import jinja_helpers +import misc_classes import subprocess import importlib import traceback @@ -17,6 +19,8 @@ import re @dataclass class Reflect: client_code_path:str + assessment_struct:dict + zip_file:str imported_modules = {} def __post_init__(self): @@ -227,14 +231,18 @@ class Reflect: def __format_doc(*doc): return str(doc[1]).rstrip() -def gen_reflection_report(client_code_path, assessment_struct, student_no, configuration): - # print(configuration) - reflection = Reflect(client_code_path) +def gen_reflection_report(client_code_path, assessment_struct, student_no, configuration, zip_file): + reflection = Reflect(client_code_path, assessment_struct, zip_file) present_module_names = [i.name for i in reflection.client_modules] out = assessment_struct out["student_no"] = student_no tests_to_run = {} + try: + produced_files = assessment_struct["produced_files"] + except KeyError: + produced_files = [] + for i, required_file in enumerate(assessment_struct["files"], 0): required_file = list(required_file.keys())[0] module_name = os.path.splitext(required_file)[0] @@ -327,9 +335,39 @@ def gen_reflection_report(client_code_path, assessment_struct, student_no, confi except KeyError: tests_to_run[filename] = [test] + if "run" in required_files_features.keys(): + with misc_classes.ExtractZipToTempDir(zip_file) as tempdir: + with misc_classes.FileDependencies(assessment_struct, tempdir): + with misc_classes.ChangeDirectory(tempdir): + for cmd, contents in jinja_helpers.flatten_struct(required_files_features["run"]).items(): + + lines = "" + if "monitor" in contents.keys(): + + if contents["monitor"] not in produced_files: + raise MonitoredFileNotInProducedFilesException("The monitored file %s is not in the list of produced files. It needs to be added." % contents["monitor"]) + + subprocess.run(cmd.split()) + with open(contents["monitor"], "r") as f: + lines = f.read() + + else: + proc = subprocess.Popen(cmd.split(), stdout = subprocess.PIPE) + while True: + line = proc.stdout.readline() + if not line: + break + lines += line.decode() + + print("===Lines===") + print(lines) + out["test_results"] = reflection.run_tests(tests_to_run, configuration["out"] == "stdout" and configuration["format"] in ["text", "txt"]) out["class_tree"] = reflection.get_class_tree() - return out + # return out + +class MonitoredFileNotInProducedFilesException(Exception): + pass if __name__ == "__main__": # user_code_path = "D:\\Edencloud\\UniStuff\\3.0 - CMP 3rd Year Project\\Smarker\\../ExampleSubmissions/Submission_A" diff --git a/smarker.conf b/smarker.conf index aa5a415..44aea24 100644 --- a/smarker.conf +++ b/smarker.conf @@ -1,13 +1,13 @@ -[mysql]
-host = 192.168.1.92
-port = 3306
-user = smarker
-passwd = smarkerPassword
-
-[md]
-show_full_docs = True
-show_source = True
-
-[txt]
-show_full_docs = True
+[mysql] +host = 192.168.1.92 +port = 3306 +user = smarker +passwd = smarkerPassword + +[md] +show_full_docs = True +show_source = True + +[txt] +show_full_docs = True show_source = True
\ No newline at end of file diff --git a/templates/text.jinja2 b/templates/text.jinja2 Binary files differindex eca6ebd..eca6ebd 120000..100644 --- a/templates/text.jinja2 +++ b/templates/text.jinja2 |