diff --git a/convert.py b/convert.py deleted file mode 100755 index 62b10f89a6858dfa7658e99c238a35f3b8c22f90..0000000000000000000000000000000000000000 --- a/convert.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/local/bin/python3 -""" a simple script that converts ilias exam output to readable json - -The json output will look like this: -{ - "max.mustermann": { <<--- OR all uppercase letter of the name + username/matrikel_no - "matrikel_no": "12345678", - "name": "Mustermann, Max", - "task_list": { - "[task_id_1]": "print Hello World!", - ...., - "[task_id_n]": "#include <stdio.h> etc." - } - }, - ... ans so on -} - -usage: convert.py [-h] [-n NUMBER_OF_TASKS] INFILE OUTFILE - -positional arguments: - INFILE Ilias exam data - OUTFILE Where to write the final file - -optional arguments: - -h, --help show this help message and exit - -n NUMBER_OF_TASKS, --NUMBER_OF_TASKS NUMBER_OF_TASKS - Where to write the final file - - -Author: Jan Maximilian Michal -Date: 30 March 2017 -""" - -import json -import os -import re -import argparse -import urllib.parse -from collections import namedtuple, defaultdict - -from xlrd import open_workbook - -parser = argparse.ArgumentParser() -parser.add_argument('INFILE', help='Ilias exam data') -parser.add_argument('OUTFILE', help='Where to write the final file') -parser.add_argument('-u', '--usernames', help='a json dict matno -> email') -parser.add_argument( - '-n', '--NUMBER_OF_TASKS', - default=0, # don't check - metavar='NUMBER_OF_TASKS', - type=int, - help='Where to write the final file') -args = parser.parse_args() - -# meta sheet contains ilias evaluation names usernames etc - data contains code -meta, *data = open_workbook(args.INFILE, open(os.devnull, 'w')).sheets() - -# one user has one submission (code) per task -# yes, I know it is possible to name match groups via (?P<name>) but -# I like this solution better since it gets the job done nicely -user_head = namedtuple('user_head', 'kohorte, name') -user_head_re = re.compile(r'^Ergebnisse von Testdurchlauf (?P<kohorte>\d+) für (?P<name>[\w\s\.,-]+)$') - -# one task has a title and id and hpfly code -task_head_re = re.compile(r'^Quellcode Frage(?P<title>.*) \d{8}$') - -# nor parsing the weird mat no -matno_re = re.compile(r'^(?P<matrikel_no>\d{8})-(\d{3})-(\d{3})$') - -# Modify these iterators in order to change extraction behaviour - - -def sheet_iter_meta(sheet): - """ yield first and second col entry as tuple of (name, matnr) """ - for row in (sheet.row(i) for i in range(1, sheet.nrows)): - m = re.search(matno_re, row[1].value) - yield row[0].value, m.group('matrikel_no') if m else row[1].value - - -def sheet_iter_data(sheet): - """ yields all rows that are not of empty type as one string """ - for row in (sheet.row(i) for i in range(sheet.nrows)): - if any(map(lambda c: c.ctype, row)): - yield ''.join(c.value for c in row) - -# nice! -name2mat = dict(sheet_iter_meta(meta)) - -# from xls to lists and namedtuples -# [ [user0, task0_h, code0, ..., taskn, coden ], ..., [...] ] -root = [] -for sheet in data: - for row in sheet_iter_data(sheet): - user = re.search(user_head_re, row) - task = re.search(task_head_re, row) - if user: - root.append([user_head(*user.groups())]) - elif task: - root[-1].append(task.group('title')) - else: # should be code - root[-1].append(urllib.parse.unquote(row).strip()) - -if args.NUMBER_OF_TASKS: - for (user, *task_list) in sorted(root, key=lambda u: u[0].name): - assert len(task_list) == args.NUMBER_OF_TASKS * 2 - -mat_to_email = defaultdict(str) -if args.usernames: - with open(args.usernames) as data: - mat_to_email.update(json.JSONDecoder().decode(data.read())) - -def get_username(user): - if name2mat[user.name] in mat_to_email: - return mat_to_email[name2mat[user.name]].split('@')[0] - return ''.join(filter(str.isupper, user.name)) + name2mat[user.name] - -usernames = {user.name : get_username(user) for (user, *_) in root} - -# form list to json_like via comprehension -# the format {userinitials + matrikel_no : {name:, matrikel_no:, tasklist: {id:, ..., id:}}} -json_dict = { - usernames[user.name] : { - 'name' : name2mat[user.name], - 'email' : mat_to_email[name2mat[user.name]], - 'matrikel_no' : name2mat[user.name], - 'submissions' : [ - { - "type" : task, - "code" : code, - "label" : [], - "annotations": {} - } for task, code in zip(task_list[::2], task_list[1::2]) - ] - } for (user, *task_list) in sorted(root, key=lambda u: u[0].name) -} - -# just encode python style -with open(args.OUTFILE, "w") as out: - out.write(json.JSONEncoder().encode(json_dict)) - -print(f"Wrote data to {args.OUTFILE}. Done.") diff --git a/scripts/convert.py b/scripts/convert.py index 62b10f89a6858dfa7658e99c238a35f3b8c22f90..638fc16f8504f48d6f55488d20e6973b56f06f51 100755 --- a/scripts/convert.py +++ b/scripts/convert.py @@ -15,7 +15,7 @@ The json output will look like this: ... ans so on } -usage: convert.py [-h] [-n NUMBER_OF_TASKS] INFILE OUTFILE +usage: convert.py [-h] [-u USERNAMES] [-n NUMBER_OF_TASKS] INFILE OUTFILE positional arguments: INFILE Ilias exam data @@ -23,6 +23,8 @@ positional arguments: optional arguments: -h, --help show this help message and exit + -u USERNAMES, --usernames USERNAMES + a json dict matno -> email -n NUMBER_OF_TASKS, --NUMBER_OF_TASKS NUMBER_OF_TASKS Where to write the final file @@ -120,15 +122,14 @@ usernames = {user.name : get_username(user) for (user, *_) in root} # the format {userinitials + matrikel_no : {name:, matrikel_no:, tasklist: {id:, ..., id:}}} json_dict = { usernames[user.name] : { - 'name' : name2mat[user.name], + 'name' : user.name, 'email' : mat_to_email[name2mat[user.name]], 'matrikel_no' : name2mat[user.name], 'submissions' : [ { "type" : task, "code" : code, - "label" : [], - "annotations": {} + "tests" : {}, } for task, code in zip(task_list[::2], task_list[1::2]) ] } for (user, *task_list) in sorted(root, key=lambda u: u[0].name) diff --git a/util/processing.py b/util/processing.py index 997cf1b86f70c1b55fe3b7303234512fee8d345b..bf0b60baa78d7bcdbe2f587c0a1086546e2d2f19 100644 --- a/util/processing.py +++ b/util/processing.py @@ -1,86 +1,46 @@ import abc import hashlib -import json import tempfile import os +import re +import json import shutil import subprocess -before = { - "username": { - "name": "87654321", - "email": "username@example.org", - "matrikel_no": "87654321", - "submissions": [ - { - "type": "[a01] Zeichen und Strings", - "code": "#include <ctype.h>\nint umschalt(char *s)\n\n{\n\t if (s == 0) return 0;int i = 0, count = 0;\n\twhile(s[i] != '\\0')\n\t{\n\t\tif(islower(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = toupper(s[i]);\n\t\t} else if(isupper(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = tolower(s[i]);\n\t\t} else\n\t\t{\n\t\t\tcount++;\n\t\t}\n\t\ti++;\n\t}\n\treturn count;\n}", - "tests" : {} - }, - { - "type": "[a02] Iteration", - "code": "#include <stdlib.h>\nlong iteP(unsigned int k)\n{\n\tif(k == 0)\n\t{\n\t\treturn 3;\n\t}\n\tif( k == 1)\n\t{\n\t\treturn 0;\n\t}\n\tif( k == 3)\n\t{\n\t\treturn 2;\n\t}\n\tint *p = malloc(k * sizeof(int));\n\tp[0] = 3;\n\tp[1] = 0;\n\tp[2] = 2;\n\tfor(int i = 3; i < k; i++)\n\t{\n\t\tp[i] = p[i-2] + p[i - 3];\n\t}\n\n\treturn p[k];\n}", - "tests" : {} - } - ] - } -} - -after = { - "username": { - "name": "87654321", - "email": "username@example.org", - "matrikel_no": "87654321", - "submissions": [ - { - "type": "[a01] Zeichen und Strings", - "code": "#include <ctype.h>\nint umschalt(char *s)\n\n{\n\tint i = 0, count = 0;\n\twhile(s[i] != '\\0')\n\t{\n\t\tif(islower(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = toupper(s[i]);\n\t\t} else if(isupper(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = tolower(s[i]);\n\t\t} else\n\t\t{\n\t\t\tcount++;\n\t\t}\n\t\ti++;\n\t}\n\treturn count;\n}", - "tests": { - "EmptyTest": { - "name": "EmptyTest", - "annotation": "", - "label": "NOT_EMPTY" - } - } - }, - { - "type": "[a02] Iteration", - "code": "#include <stdlib.h>\nlong iteP(unsigned int k)\n{\n\tif(k == 0)\n\t{\n\t\treturn 3;\n\t}\n\tif( k == 1)\n\t{\n\t\treturn 0;\n\t}\n\tif( k == 3)\n\t{\n\t\treturn 2;\n\t}\n\tint *p = malloc(k * sizeof(int));\n\tp[0] = 3;\n\tp[1] = 0;\n\tp[2] = 2;\n\tfor(int i = 3; i < k; i++)\n\t{\n\t\tp[i] = p[i-2] + p[i - 3];\n\t}\n\n\treturn p[k];\n}", - "tests": {} - } - ] - } -} - - -def sha(s): - return hashlib.sha1(s.encode()).hexdigest() - - -def run_cmd(cmd, stdin=None): +import testcases + +DESCFILE = '../data/descfile.txt' +BINARIES = '../data/klausur20170627/bin' +OBJECTS = '../data/klausur20170627/objects' +SUBMISSIONS = '../data/ok.json' +HEADER = '../data/klausur20170627/code-testing' + +def run_cmd(cmd, stdin=None, check=False): return subprocess.run( - cmd, + 'ulimit -v 1024;' + cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, input=stdin, shell=True, + check=check, encoding='utf-8', timeout=0.5 ) +'submission' def testcase(i, args, stdout): try: - ret = run_cmd("./code %s" % args) + ret = run_cmd("./code %s" % args, check=True) assert ret.stdout == stdout except AssertionError: - return f"Case #{i}: [ASSERT FAILED] ./umschalt {args} WAS '{ret.stdout}' SHOULD '{stdout}'" + return f"Case #{i}: [ASSERT FAILED] ./program {args} WAS '{ret.stdout}' SHOULD '{stdout}'" except subprocess.CalledProcessError: - return f"Case #{i}: [FAILED] ./umschalt {args}" + return f"Case #{i}: [FAILED] ./program {args}" except subprocess.TimeoutExpired: - return f"Case #{i}: [TIMEOUT] ./umschalt {args}" + return f"Case #{i}: [TIMEOUT] ./program {args}" else: - return f"Case #{i}: [SUCCESS] ./umschalt {args}" + return f"Case #{i}: [SUCCESS] ./program {args}" def all_subclasses(cls): @@ -108,16 +68,15 @@ class Test(metaclass=abc.ABCMeta): def __init__(self, submission_obj, **kwargs): if not self.dependencies_satisfied(submission_obj): - self.result = self.label_failure + self.result = False self.annotation = "TEST DEPENDENCY NOT MET" - return + self.serialize(submission_obj) elif str(self) in submission_obj['tests']: self.deserialize(submission_obj['tests'][str(self)]) else: - self.result, self.annotation = self.run_test( - submission_obj, **kwargs) + self.result, self.annotation = self.run_test(submission_obj, **kwargs) self.serialize(submission_obj) def __bool__(self): @@ -170,11 +129,7 @@ class CompileTest(Test): def run_test(self, submission_obj): - try: - ret = run_cmd("gcc-7 -c -xc -o code.o -", submission_obj['code']) - except subprocess.CalledProcessError as err: - print('[FATAL] The compiler failed.') - + ret = run_cmd("gcc-7 -c -xc -Icode-testing -o code.o -", submission_obj['code']) return not ret.returncode, ret.stderr @@ -186,7 +141,10 @@ class LinkTest(Test): def run_test(self, submission_obj): - ret = run_cmd("gcc-7 -o code a01-testing.o code.o") + t = submission_obj['type'] + m = re.search(r'(a0\d)', t) + + ret = run_cmd(f"gcc-7 -o code objects/{m.group(0)}-testing.o code.o") return not ret.returncode, ret.stderr @@ -199,26 +157,37 @@ class UnitTestTest(Test): def run_test(self, submission_obj): - test_cases = ( - ('', 'umschalt(NULL)\n== 0\n'), - ('...', 'input : "..."\numschalt("...")\n== 3\nresult: "..."\n'), - ('anV', 'input : "anV"\numschalt("anV")\n== 0\nresult: "ANv"\n'), - ) + task = testcases_dict[submission_obj['type']] + + return 1, '\n'.join(testcase(i, case, run_cmd(f"{task['cmd']} {case}").stdout) for i, case in enumerate(task['cases'])) + + +def main(): + + with open(SUBMISSIONS) as submission_file: + submissions = json.JSONDecoder().decode(submission_file.read()) - return 1, '\n'.join(testcase(i, *test) for i, test in enumerate(test_cases)) + # Get something disposable + path = tempfile.mkdtemp() + run_cmd(f'cp -r {OBJECTS} {path}') + run_cmd(f'cp -r {BINARIES} {path}') + run_cmd(f'cp -r {HEADER} {path}') + os.chdir(path) + for username, data in submissions.items(): + for submission_obj in data['submissions']: + UnitTestTest(submission_obj) + run_cmd('rm code*') -path = tempfile.mkdtemp() -run_cmd(f'cp ../data/testing_facility/klausur_tag_02/objects/* {path}') -os.chdir(path) + print(json.dumps(submissions, sort_keys=True, indent=4)) -d = UnitTestTest(before["username"]['submissions'][0]) +if __name__ == '__main__': + testcases_dict = testcases.testcases(DESCFILE) -# shutil.rmtree(path) + from pprint import pprint + main() -print(path) -print(d.annotation) +# x = "#include \"a06-testing.h\"\n#include <stdio.h>\nvoid main() {\nchar *a;\nint *b;\nint i=0;\nint j=0;\nint k=0;\nwhile (scanf(\"%s\", &a)) {\nb[i]=ahoi(a);\ni++;\n}\nwhile(i-j) {\nprintf(\"%i\", b[j++]);\n}\nint c=1;\nint *t=b+i;\nint recSort(b, t, c)\nwhile (i-k) {\nprintf(\"%i\", b[k++]);\n}" -with open("/dev/stdout", "w") as out: - out.write(json.JSONEncoder().encode(before)) +# print(x) diff --git a/util/testcases.py b/util/testcases.py index 87e24ed44e7cef2ea4f45315d21d2fcf230dfec2..e96efc26b21a1bfa131125bea0711468bae8ddd1 100644 --- a/util/testcases.py +++ b/util/testcases.py @@ -1,60 +1,53 @@ -r = '''-- [a01] Zeichen und Strings -USAGE: ./bin/a01 <string> <character> --- [a02] Iteration -USAGE: ./bin/a02 <unsigned integer> --- [a03] Rekursion -USAGE: ./bin/a03 <integer> <integer> <integer> <integer> --- [a04] Strukturen I -USAGE: ./bin/a04 <integer> <integer> ... <integer> <integer> --- [a05] Strukturen II -NO EXECUTABLE --- [a06] Speicher auf dem Heap -USAGE: ./bin/a06 <integer> <integer> ... <integer> <integer> -''' - import re +import json import random from string import ascii_letters, digits types = ('integer', 'unsigned_integer', 'character', 'string') list_sep = '...' +re_task = re.compile( + r'^-- (?P<title>.*)\n(USAGE: (?P<cmd>[\./\w]+) (?P<syntax>.*)|NO EXECUTABLE)', re.MULTILINE) +re_args = re.compile(rf"<({'|'.join(types)}|{'|'.join(t + '_list' for t in types)})>") + + def call_function(name: str, *args, **kwargs): return globals()[name](*args, **kwargs) + def integer(bounds=1000): return random.randint(-bounds, bounds) -def unsigned_integer(bounds=1000): - return random.randint(0, bounds) + +def unsigned_integer(upper=1000): + return random.randint(0, upper) + def character(): return random.choice(5*ascii_letters + 2*digits + '%*+,-./:?@[]^_{}~') + def string(lenght=31): - return ''.join(character() for i in range(unsigned_integer(lenght))) + return ''.join(character() for i in range(2, 2 + unsigned_integer(lenght))) + def type_list(_type): def generic_list(): return ' '.join(str(call_function(_type)) for i in range(unsigned_integer(30))) return generic_list -def rubbish(): - return ' '.join(string(4)) - -for t in types: - globals()[t + '_list'] = type_list(t) # I fucking love it - -task = re.compile(r'^-- (?P<title>.*)\n(USAGE: (?P<cmd>[\./\w]+) (?P<syntax>.*)|NO EXECUTABLE)', re.MULTILINE) -args = re.compile(rf"<({'|'.join(types)}|{'|'.join(t + '_list' for t in types)})>") +def rubbish(): + return str(call_function(random.choice(tuple(t + '_list' for t in types) + types))) def argument_generator(syntax): - syntax, _ = re.subn(r'<([\w\s]+)> <\1> \.\.\. <\1> <\1>', r'<\1_list>', syntax) + syntax, _ = re.subn( + r'<([\w\s]+)> <\1> \.\.\. <\1> <\1>', r'<\1_list>', syntax) syntax, _ = re.subn(r'<(\w+)\s(\w+)>', r'<\1_\2>', syntax) - return ' '.join(str(call_function(arg)) for arg in re.findall(args, syntax)) + return ' '.join(str(call_function(arg)) for arg in re.findall(re_args, syntax)) + def testcases_generator(task, n=10): syntax = task.group('syntax') @@ -63,16 +56,29 @@ def testcases_generator(task, n=10): return yield '' - yield 0 + yield '0' - for i in range(n//3): + for i in range(n//2): yield rubbish() for i in range(n): yield argument_generator(syntax) -for task in re.finditer(task, r): - for t in testcases_generator(task): - print(t) + +def testcases(description_path): + for t in types: + globals()[t + '_list'] = type_list(t) # I fucking love it + + with open(description_path) as description_file: + description = description_file.read() + + return { + task['title'] : { + 'cmd' : task['cmd'], + 'cases' : [t for t in testcases_generator(task)] + } for task in re.finditer(re_task, description) + } +if __name__ == '__main__': + print(json.JSONEncoder().encode(testcases()))