From 128988aa401b0f6cb62b317df6c712fe15725b52 Mon Sep 17 00:00:00 2001 From: janmax <j.michal@stud.uni-goettingen.de> Date: Thu, 30 Nov 2017 12:37:02 +0100 Subject: [PATCH] Ran autopep8 on util and removed old script folder --- .gitlab-ci.yml | 17 ++++---- .pylintrc | 20 +++++++++ delbert.py | 14 +++--- scripts/README.rst | 11 ----- scripts/compile.py | 104 --------------------------------------------- util/convert.py | 30 +++++++------ util/factories.py | 2 +- util/importer.py | 18 ++++---- util/messages.py | 5 +++ util/processing.py | 48 ++++++++++----------- util/testcases.py | 29 +++++++------ 11 files changed, 107 insertions(+), 191 deletions(-) create mode 100644 .pylintrc delete mode 100644 scripts/README.rst delete mode 100755 scripts/compile.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2d62fe51..7519a7a0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,8 +21,6 @@ build_backend: .test_template_backend: &test_definition_backend stage: test image: $IMAGE_TAG - before_script: - - cd backend/ test_pytest: <<: *test_definition_backend @@ -32,7 +30,7 @@ test_pytest: - DJANGO_SETTINGS_MODULE=grady.settings pytest --cov artifacts: paths: - - backend/.coverage + - .coverage test_prospector: <<: *test_definition_backend @@ -54,16 +52,18 @@ test_frontend: # =========================== Gitlab pages section =========================== # test_coverage: - <<: *test_definition_backend + image: $IMAGE_TAG stage: pages script: - - coverage html -d ../public + - coverage html -d public dependencies: - test_pytest artifacts: paths: - public + only: + - master # ============================== Staging section ============================= # .staging_template: &staging_definition @@ -71,6 +71,8 @@ test_coverage: image: docker:latest only: - master + before_script: + - apk add --update py-pip && pip install docker-compose staging: <<: *staging_definition @@ -79,15 +81,12 @@ staging: url: https://staging.grady.janmax.org on_stop: staging_stop script: - - apk add --update py-pip && pip install docker-compose - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY - - docker-compose pull - - docker-compose up -d --build + - docker-compose up -d --force-recreate staging_stop: <<: *staging_definition script: - - apk add --update py-pip && pip install docker-compose - docker-compose rm --force --stop when: manual environment: diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..2bfbcef1 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,20 @@ +[MASTER] + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS, migrations, static, env, docs, manage.py + +# Use multiple processes to speed up Pylint. +jobs=4 + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages +suggestion-mode=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins=pylint_django + diff --git a/delbert.py b/delbert.py index 4e05dabd..d3526487 100755 --- a/delbert.py +++ b/delbert.py @@ -14,13 +14,12 @@ import util.importer from core.models import Student, Submission +unused_variable = [] -unused_variable = [] - def parseme(): - parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers(dest="command") + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest="command") parser.add_argument( '-o', '--output', @@ -109,7 +108,7 @@ def handle_enableusers(switch, exclude, include, **kwargs): for user in User.objects.filter(username__in=include): user.is_active = switch == 'on' user.save() - else: # this includes nothing set + else: # this includes nothing set for user in User.objects.exclude(username__in=exclude): user.is_active = switch == 'on' user.save() @@ -126,16 +125,19 @@ def handle_replaceusernames(matno2username_dict, **kwargs): def handle_extractsubmissions(output, **kwargs): for submission in Submission.objects.filter(feedback__isnull=False).order_by('type'): - print(submission.feedback.score, repr(submission.text), file=open(str(submission.type).replace(' ', '_'), 'a')) + print(submission.feedback.score, repr(submission.text), + file=open(str(submission.type).replace(' ', '_'), 'a')) def handle_importer(**kwargs): util.importer.start() + def main(): args = parseme() if args.command: globals()['handle_' + args.command](**vars(args)) + if __name__ == '__main__': main() diff --git a/scripts/README.rst b/scripts/README.rst deleted file mode 100644 index 46af791e..00000000 --- a/scripts/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -What is this directory about? -============================= - -Well, it just serves as a collection of files that currently live in folders -not part of the git repository, since they contain volatile or test data. I -include them here for the sake of completeness, but they will be removed in -later versions, since their work has to be encapsulated in the overall process. -When documentations becomes more accurate a detailed explanation on how to use -them will be added. - -.. note:: Please keep in mind: These file are of poor quality and are likely to fail if not used in a correct manner. diff --git a/scripts/compile.py b/scripts/compile.py deleted file mode 100755 index 091669a7..00000000 --- a/scripts/compile.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/local/bin/python3 -"""This script adds compiler output to the json output of the convert script - -[description] -""" -import subprocess -import json -import os - -TEMP_DIR = 'temp_code' -OUTFILE = 'submissions_compiled.json' -INFILE = 'submissions.json' -OBJECT_DIR = 'klausur_tag_01/objects' - -ALL_OK = 0 -COMPILE_ERROR = 1 -LINKER_ERROR = 2 -WARNINGS = 3 - - -def get_compiler_output(task_id, text): - dst = open(os.path.join(TEMP_DIR, task_id + '.o'), 'w') - try: - compile_cmd = subprocess.run( - [ - "gcc-6", "-Wall", "-std=c11", "-c", "-xc", - "-o", dst.name, - f"-I{TEMP_DIR}", - "-" - ], - stderr=subprocess.PIPE, - input=text, - encoding='utf-8', - ) - - if compile_cmd.returncode: - return compile_cmd, None, COMPILE_ERROR # it fucking failed - - object_code = os.path.join(OBJECT_DIR, f"{task_id}-testing.o") - if os.path.exists(object_code): # this is ok - link_cmd = subprocess.run( - [ - "gcc-6", - "-o", "/dev/null", - dst.name, - object_code - - ], - stderr=subprocess.PIPE, - encoding='utf-8', - ) - else: # this case is weird - if task_id == 'a05': # for day 2 task a05 its ok to just compile - return compile_cmd, None, compile_cmd.returncode - elif task_id == 'a06': - link_cmd = subprocess.run( - [ - "gcc-6", - "-o", "/dev/null", - dst.name, - os.path.join(TEMP_DIR, "a05.o"), - ], - stderr=subprocess.PIPE, - encoding='utf-8', - ) - - if link_cmd.returncode: - return compile_cmd, link_cmd, LINKER_ERROR - - return compile_cmd, link_cmd, ALL_OK - finally: - dst.close() - - -def main(): - with open(INFILE, 'r', encoding='utf-8') as submissions: - data = json.JSONDecoder().decode(submissions.read()) - - total = len(data) - for i, (username, userinfo) in enumerate(data.items()): - print(f"\r- {i+1}/{total} done. processing submissions of {username}\t\t\t\t", end='') - - # create new entry - co = userinfo['compiler_output'] = {} - - for task_id, submission in userinfo['submissions'].items(): - if submission: - _compile, _link, err = get_compiler_output(task_id, submission) - msg = { - COMPILE_ERROR: "\n\n[FAIL] DID NOT COMPILE", - LINKER_ERROR: "\n\n[FAIL] COULD NOT LINK", - ALL_OK: "", - }[err] - co[task_id] = _compile.stderr + (_link.stderr if _link else "") + msg - else: - co[task_id] = "" - - print() - - with open(OUTFILE, 'w', encoding='utf-8') as submissions: - submissions.write(json.JSONEncoder().encode(data)) - -if __name__ == '__main__': - main() diff --git a/util/convert.py b/util/convert.py index e038b7f3..44995cbb 100755 --- a/util/convert.py +++ b/util/convert.py @@ -48,18 +48,18 @@ 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 + default=0, # don't check metavar='NUMBER_OF_TASKS', type=int, help='Where to write the final file') - # 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\.,-]+)$') +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}$') @@ -67,6 +67,7 @@ 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})$') + def converter(infile, usernames=None, number_of_tasks=0,): # Modify these iterators in order to change extraction behaviour @@ -77,7 +78,6 @@ def converter(infile, usernames=None, number_of_tasks=0,): 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)): @@ -102,7 +102,7 @@ def converter(infile, usernames=None, number_of_tasks=0,): root.append([user_head(*user.groups())]) elif task: root[-1].append(task.group('title')) - else: # should be code + else: # should be code root[-1].append(urllib.parse.unquote(row).strip()) if number_of_tasks: @@ -119,25 +119,26 @@ def converter(infile, usernames=None, number_of_tasks=0,): 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} + 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:}}} return { - usernames[user.name] : { - 'name' : user.name, - 'email' : mat_to_email[name2mat[user.name]], - 'matrikel_no' : name2mat[user.name], - 'submissions' : [ + usernames[user.name]: { + 'name': user.name, + 'email': mat_to_email[name2mat[user.name]], + 'matrikel_no': name2mat[user.name], + 'submissions': [ { - "type" : task, - "code" : code, - "tests" : {}, + "type": task, + "code": code, + "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) } + def write_to_file(json_dict, outfile): # just encode python style with open(outfile, "w") as out: @@ -151,6 +152,7 @@ def main(): json_dict = converter(args.INFILE, args.usernames, args.NUMBER_OF_TASKS) write_to_file(json_dict, args.OUTFILE) + if __name__ == '__main__': SCRIPT = True main() diff --git a/util/factories.py b/util/factories.py index 32c2d67f..3d2edef2 100644 --- a/util/factories.py +++ b/util/factories.py @@ -22,7 +22,7 @@ def store_password(username, groupname, password): storage = configparser.ConfigParser() storage.read(PASSWORDS) - if not groupname in storage: + if groupname not in storage: storage[groupname] = {} storage[groupname][username] = password diff --git a/util/importer.py b/util/importer.py index f9fed844..1fbbbb77 100644 --- a/util/importer.py +++ b/util/importer.py @@ -7,8 +7,8 @@ from typing import Callable import util.convert import util.processing from core.models import UserAccount as User -from core.models import (ExamType, Feedback, Reviewer, Student, Submission, - SubmissionType, Test, Tutor) +from core.models import (ExamType, Feedback, Student, Submission, + SubmissionType, Test) from util.messages import info, warn from util.processing import EmptyTest @@ -60,7 +60,8 @@ class chdir_context(object): def i(prompt: str, default: str='', is_path: bool=False, is_file: bool=False): if default is YES or default is NO: - answer = valid[input(f'[Q] {prompt} ({default}): ').lower() or ('y' if YES == default else 'n')] + answer = valid[input(f'[Q] {prompt} ({default}): ').lower() or ( + 'y' if YES == default else 'n')] elif default: answer = input(f'[Q] {prompt} ({default}): ') or default else: @@ -72,7 +73,6 @@ def i(prompt: str, default: str='', is_path: bool=False, is_file: bool=False): return answer -# TODO more factories def add_user(username, group, **kwargs): user = GradyUserFactory()._make_base_user( @@ -277,8 +277,8 @@ def do_preprocess_submissions(): print(''' Preprocessing might take some time depending on the amount of data - and the complexity of the programs and the corresponding unit tests. You can - specify what test you want to run. + and the complexity of the programs and the corresponding unit tests. You + can specify what test you want to run. Tests do depend on each other. Therefore specifying a test will also result in running all its dependencies\n''') @@ -360,9 +360,9 @@ def start(): print('''Welcome to the Grady importer! - This script aims at making the setup of the database as easy as possible. It - at the same time serves as a documentation on how data is imported in Grady. - Let\'s dive right in.\n''') + This script aims at making the setup of the database as easy as possible. + It at the same time serves as a documentation on how data is imported in + Grady. Let\'s dive right in.\n''') try: print('The following importers are available:\n') diff --git a/util/messages.py b/util/messages.py index 8f236313..7f1aeac4 100644 --- a/util/messages.py +++ b/util/messages.py @@ -4,18 +4,23 @@ import sys def warn(*message): print('[W]', *message) + def debug(*message): print('[DEBUG]', *message) + def info(*message): print('[I]', *message) + def error(*message): print('[E]', *message) + def abort(*message): print('[FATAL]', *message) sys.exit('exiting...') + def exit(message='exiting...'): sys.exit(*message) diff --git a/util/processing.py b/util/processing.py index e83a44eb..26671595 100644 --- a/util/processing.py +++ b/util/processing.py @@ -12,11 +12,11 @@ try: except ModuleNotFoundError: from util import testcases -DESCFILE = '../data/descfile.txt' -BINARIES = '../data/klausur_zweittermin/bin' -OBJECTS = '../data/klausur_zweittermin/objects' +DESCFILE = '../data/descfile.txt' +BINARIES = '../data/klausur_zweittermin/bin' +OBJECTS = '../data/klausur_zweittermin/objects' SUBMISSIONS = '../data/binf1801_pre.json' -HEADER = '../data/klausur_zweittermin/code-testing' +HEADER = '../data/klausur_zweittermin/code-testing' def run_cmd(cmd, stdin=None, check=False, timeout=1): @@ -46,18 +46,18 @@ class Test(metaclass=abc.ABCMeta): @classmethod def available_tests(cls): - return {sub.__name__ : sub for sub in all_subclasses(cls)} + return {sub.__name__: sub for sub in all_subclasses(cls)} def __new__(cls, *args, **kwargs): - assert hasattr(cls, 'depends'), "depends not defined" - assert hasattr(cls, 'label_success'), "label_success not defined" - assert hasattr(cls, 'label_failure'), "label_failure not defined" + assert hasattr(cls, 'depends'), "depends not defined" + assert hasattr(cls, 'label_success'), "label_success not defined" + assert hasattr(cls, 'label_failure'), "label_failure not defined" return super().__new__(cls) def __init__(self, submission_obj, **kwargs): if not self.dependencies_satisfied(submission_obj): - self.result = False + self.result = False self.annotation = "TEST DEPENDENCY NOT MET" self.serialize(submission_obj) @@ -79,13 +79,13 @@ class Test(metaclass=abc.ABCMeta): return all(dep(submission_obj).result for dep in self.depends) def deserialize(self, test): - self.result = test['label'] == self.label_success + self.result = test['label'] == self.label_success self.annotation = test['annotation'] def serialize(self, submission_obj): as_dict = { - 'name' : str(self), - 'annotation' : self.annotation + 'name': str(self), + 'annotation': self.annotation } if self.result: @@ -103,9 +103,9 @@ class Test(metaclass=abc.ABCMeta): class EmptyTest(Test): """docstring for EmptyTest""" - depends = () - label_success = 'NOT_EMPTY' - label_failure = 'EMPTY' + depends = () + label_success = 'NOT_EMPTY' + label_failure = 'EMPTY' def run_test(self, submission_obj): return bool(submission_obj['code'].strip()), "" @@ -113,9 +113,9 @@ class EmptyTest(Test): class CompileTest(Test): - depends = (EmptyTest, ) - label_success = 'COMPILATION_SUCCESSFUL' - label_failure = 'COMPILATION_FAILED' + depends = (EmptyTest, ) + label_success = 'COMPILATION_SUCCESSFUL' + label_failure = 'COMPILATION_FAILED' def run_test(self, submission_obj): @@ -126,9 +126,9 @@ class CompileTest(Test): class LinkTest(Test): - depends = (CompileTest, ) - label_success = 'LINKING_SUCCESSFUL' - label_failure = 'LINKING_FAILED' + depends = (CompileTest, ) + label_success = 'LINKING_SUCCESSFUL' + label_failure = 'LINKING_FAILED' def run_test(self, submission_obj): @@ -142,9 +142,9 @@ class LinkTest(Test): class UnitTestTest(Test): """docstring for UnitTestTest""" - depends = (LinkTest, ) - label_success = 'UNITTEST_SUCCSESSFUL' - label_failure = 'UNITTEST_FAILED' + depends = (LinkTest, ) + label_success = 'UNITTEST_SUCCSESSFUL' + label_failure = 'UNITTEST_FAILED' @staticmethod def testcase(i, args, stdout): diff --git a/util/testcases.py b/util/testcases.py index b94fab23..a95e657d 100644 --- a/util/testcases.py +++ b/util/testcases.py @@ -12,8 +12,7 @@ except ModuleNotFoundError: 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_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)})>") @@ -30,7 +29,7 @@ def unsigned_integer(upper=50): def character(): - return random.choice(10*ascii_letters + 2*digits + '%*+,-./:?@[]^_{}~') + return random.choice(10 * ascii_letters + 2 * digits + '%*+,-./:?@[]^_{}~') def string(lenght=31): @@ -48,8 +47,7 @@ def rubbish(): 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(re_args, syntax)) @@ -64,7 +62,7 @@ def testcases_generator(task, n=10): yield '' yield '0' - for i in range(n//2): + for i in range(n // 2): yield rubbish() for i in range(n): @@ -73,26 +71,31 @@ def testcases_generator(task, n=10): def testcases(description_path): for t in types: - globals()[t + '_list'] = type_list(t) # I fucking love it + 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)] + task['title']: { + 'cmd': task['cmd'], + 'cases': [t for t in testcases_generator(task)] } for task in re.finditer(re_task, description) } + def evaluated_testcases(description_path): task_testcases = testcases(description_path) for task in filter(lambda t: t['cmd'], task_testcases.values()): - path_to_binary = os.path.join(os.path.join(processing.BINARIES, os.path.basename(task['cmd']))) - task['results'] = [processing.run_cmd(f"{path_to_binary} {case}").stdout for case in task['cases']] + path_to_binary = os.path.join(os.path.join( + processing.BINARIES, os.path.basename(task['cmd']))) + task['results'] = [processing.run_cmd( + f"{path_to_binary} {case}").stdout for case in task['cases']] return task_testcases + if __name__ == '__main__': - print(json.dumps(evaluated_testcases(processing.DESCFILE), sort_keys=True, indent=4)) + print(json.dumps(evaluated_testcases( + processing.DESCFILE), sort_keys=True, indent=4)) -- GitLab