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.jinja2Binary files differ index eca6ebd..eca6ebd 120000..100644 --- a/templates/text.jinja2 +++ b/templates/text.jinja2 | 
