From 28e2d32f9839b0e3cb3e99ce0113832627610bd7 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Wed, 4 Feb 2015 20:03:13 +0530 Subject: Add test case model for testing redesign Conflicts: testapp/exam/models.py testapp/exam/views.py --- testapp/exam/code_server.py | 12 ++++++------ testapp/exam/models.py | 28 +++++++++++++++++++++++++++ testapp/exam/templates/exam/add_question.html | 3 +++ testapp/exam/views.py | 11 ++++++++++- testapp/exam/xmlrpc_clients.py | 4 ++-- 5 files changed, 49 insertions(+), 9 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index 792197d..64d6a47 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -68,7 +68,7 @@ class CodeServer(object): 'have an infinite loop in your code.' % SERVER_TIMEOUT self.timeout_msg = msg - def run_python_code(self, answer, test_code, in_dir=None): + def run_python_code(self, answer, test_code, test_obj, in_dir=None): #### """Tests given Python function (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change @@ -123,7 +123,7 @@ class CodeServer(object): return success, err - def run_bash_code(self, answer, test_code, in_dir=None): + def run_bash_code(self, answer, test_code, test_obj, in_dir=None): #### """Tests given Bash code (`answer`) with the `test_code` supplied. The testcode should typically contain two lines, the first is a path to @@ -290,7 +290,7 @@ class CodeServer(object): stdnt_stdout+stdnt_stderr) return False, err - def run_c_code(self, answer, test_code, in_dir=None): + def run_c_code(self, answer, test_code, test_obj, in_dir=None): #### """Tests given C code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -442,7 +442,7 @@ class CodeServer(object): err = err + "\n" + stdnt_stderr return success, err - def run_cplus_code(self, answer, test_code, in_dir=None): + def run_cplus_code(self, answer, test_code, test_obj, in_dir=None): #### """Tests given C++ code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -504,7 +504,7 @@ class CodeServer(object): return success, err - def run_java_code(self, answer, test_code, in_dir=None): + def run_java_code(self, answer, test_code, test_obj, in_dir=None): #### """Tests given java code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -659,7 +659,7 @@ class CodeServer(object): stripped = stripped + c return ''.join(stripped) - def run_scilab_code(self, answer, test_code, in_dir=None): + def run_scilab_code(self, answer, test_code, test_obj, in_dir=None): #### """Tests given Scilab function (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 72fb51b..df3b485 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -62,6 +62,15 @@ class Question(models.Model): # Test cases for the question in the form of code that is run. test = models.TextField(blank=True) + #Test case Keyword arguments in dict form + test_keyword_args = models.TextField(blank=True) #### + + #Test case Positional arguments in list form + test_pos_args = models.TextField(blank=True) #### + + #Test case Expected answer in list form + test_expected_answer = models.TextField(blank=True) #### + # Any multiple choice options. Place one option per line. options = models.TextField(blank=True) @@ -396,3 +405,22 @@ class AssignmentUpload(models.Model): user = models.ForeignKey(Profile) assignmentQuestion = models.ForeignKey(Question) assignmentFile = models.FileField(upload_to=get_assignment_dir) + + +################################################################################ +class TestCase(models.Model): + question = models.ForeignKey(Question) + + # Test case Keyword arguments in dict form + test_keyword_args = models.TextField(blank=True) + + # Test case Positional arguments in list form + test_pos_args = models.TextField(blank=True) + + # Test case Expected answer in list form + test_expected_answer = models.TextField(blank=True) + + # Is this Test Case public or not. If it is not + # public it will not be visible to the user + # public = models.BooleanField(default=False) + diff --git a/testapp/exam/templates/exam/add_question.html b/testapp/exam/templates/exam/add_question.html index b0b22b1..b6ce908 100644 --- a/testapp/exam/templates/exam/add_question.html +++ b/testapp/exam/templates/exam/add_question.html @@ -28,6 +28,9 @@ Rendered:

Description: {{ form.description}} {{form.description.errors}} Test: {{ form.test }}{{form.test.errors}} + Test Keyword Arguments: {{ form.test_keyword_args }}{{form.test.errors}} + Test Positional Arguments: {{ form.test_pos_args }}{{form.test.errors}} + Test Expected Answer: {{ form.test_expected_answer }}{{form.test.errors}} Snippet: {{ form.snippet }}{{ form.snippet.errors }} Tags: {{ form.tags }} Options: {{ form.options }} {{form.options.errors}} diff --git a/testapp/exam/views.py b/testapp/exam/views.py index 11aca06..9b6ce69 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -959,9 +959,18 @@ def validate_answer(user, user_answer, question): elif question.type == 'code': user_dir = get_user_dir(user) success, message = code_server.run_code(user_answer, question.test, - user_dir, question.language) + question.test_keyword_args, question.test_pos_args, + question.test_expected_answer, user_dir, question.language) #### if success: correct = True + + #### + print "MESS>>>", question.test + print "POS>>>", question.test_pos_args + print "KEY>>>", question.test_keyword_args + print "EXP>>>", question.test_expected_answer + #### + return correct, success, message diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index 14ebf27..1aab8de 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -29,7 +29,7 @@ class CodeServerProxy(object): "scilab": "run_scilab_code", } - def run_code(self, answer, test_code, user_dir, language): + def run_code(self, answer, test_code, test_obj, keyword_args, pos_args, expected_answer, user_dir, language): #### """Tests given code (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change it back to the original when @@ -55,7 +55,7 @@ class CodeServerProxy(object): try: server = self._get_server() method = getattr(server, method_name) - result = method(answer, test_code, user_dir) + result = method(answer, test_code, test_obj, user_dir) #### except ConnectionError: result = [False, 'Unable to connect to any code servers!'] return result -- cgit From ecb202633d3b90c55128030adb7817387bf28c95 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Thu, 5 Feb 2015 14:28:48 +0530 Subject: Modify method of passing test case attributes to code server Conflicts: testapp/exam/admin.py testapp/exam/models.py testapp/exam/views.py --- testapp/exam/admin.py | 3 +- testapp/exam/code_server.py | 32 ++++++++++++++---- testapp/exam/models.py | 75 ++++++++++++++++++++++++++++++++++------- testapp/exam/views.py | 76 ++++++++++++++++++++++++++++++++++-------- testapp/exam/xmlrpc_clients.py | 4 +-- 5 files changed, 154 insertions(+), 36 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/admin.py b/testapp/exam/admin.py index 060859a..1bdf436 100644 --- a/testapp/exam/admin.py +++ b/testapp/exam/admin.py @@ -1,5 +1,6 @@ -from testapp.exam.models import Question, Quiz +from exam.models import Question, Quiz, TestCase from django.contrib import admin admin.site.register(Question) +admin.site.register(TestCase) admin.site.register(Quiz) diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index 64d6a47..c070986 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -68,7 +68,8 @@ class CodeServer(object): 'have an infinite loop in your code.' % SERVER_TIMEOUT self.timeout_msg = msg - def run_python_code(self, answer, test_code, test_obj, in_dir=None): #### + def run_python_code(self, answer, test_code, test_parameter, in_dir=None): #### + # def run_python_code(self, answer, test_code, in_dir=None): #### """Tests given Python function (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change @@ -90,11 +91,23 @@ class CodeServer(object): success = False tb = None + + test_code_eval = "" + for test_case in test_parameter: + # for key, val in test_case.iteritems(): + pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ + else "" + kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ + if test_case.get('kw_args') else "" + args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args + tcode = "assert {0}({1}) == {2}" \ + .format(test_case.get('func_name'), args, test_case.get('expected_answer')) + test_code_eval += tcode + "\n" try: submitted = compile(answer, '', mode='exec') g = {} exec submitted in g - _tests = compile(test_code, '', mode='exec') + _tests = compile(test_code_eval, '', mode='exec') exec _tests in g except TimeoutException: err = self.timeout_msg @@ -123,7 +136,8 @@ class CodeServer(object): return success, err - def run_bash_code(self, answer, test_code, test_obj, in_dir=None): #### + # def run_bash_code(self, answer, test_code, test_parameter, in_dir=None): #### + def run_bash_code(self, answer, test_code, in_dir=None): #### """Tests given Bash code (`answer`) with the `test_code` supplied. The testcode should typically contain two lines, the first is a path to @@ -290,7 +304,8 @@ class CodeServer(object): stdnt_stdout+stdnt_stderr) return False, err - def run_c_code(self, answer, test_code, test_obj, in_dir=None): #### + # def run_c_code(self, answer, test_code, test_parameter, in_dir=None): #### + def run_c_code(self, answer, test_code, in_dir=None): #### """Tests given C code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -442,7 +457,8 @@ class CodeServer(object): err = err + "\n" + stdnt_stderr return success, err - def run_cplus_code(self, answer, test_code, test_obj, in_dir=None): #### + # def run_cplus_code(self, answer, test_code, test_parameter, in_dir=None): #### + def run_cplus_code(self, answer, test_code, in_dir=None): #### """Tests given C++ code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -504,7 +520,8 @@ class CodeServer(object): return success, err - def run_java_code(self, answer, test_code, test_obj, in_dir=None): #### + # def run_java_code(self, answer, test_code, test_parameter, in_dir=None): #### + def run_java_code(self, answer, test_code, in_dir=None): #### """Tests given java code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -659,7 +676,8 @@ class CodeServer(object): stripped = stripped + c return ''.join(stripped) - def run_scilab_code(self, answer, test_code, test_obj, in_dir=None): #### + # def run_scilab_code(self, answer, test_code, test_parameter, in_dir=None): #### + def run_scilab_code(self, answer, test_code, in_dir=None): #### """Tests given Scilab function (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change diff --git a/testapp/exam/models.py b/testapp/exam/models.py index df3b485..4eeceac 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -62,15 +62,6 @@ class Question(models.Model): # Test cases for the question in the form of code that is run. test = models.TextField(blank=True) - #Test case Keyword arguments in dict form - test_keyword_args = models.TextField(blank=True) #### - - #Test case Positional arguments in list form - test_pos_args = models.TextField(blank=True) #### - - #Test case Expected answer in list form - test_expected_answer = models.TextField(blank=True) #### - # Any multiple choice options. Place one option per line. options = models.TextField(blank=True) @@ -91,6 +82,34 @@ class Question(models.Model): # Tags for the Question. tags = TaggableManager() + def consolidate_test_cases(self, test): + test_case_parameter= [] + + for i in test: + kw_args_dict = {} + pos_args_list = [] + + parameter_dict = {} + parameter_dict['test_id'] = i.id + parameter_dict['func_name'] = i.func_name + parameter_dict['expected_answer'] = i.expected_answer + parameter_dict['ref_code_path'] = i.ref_code_path + + if i.kw_args: + for args in i.kw_args.split(","): + key, val = args.split("=") + kw_args_dict[key.strip()] = val.strip() + + if i.pos_args: + for args in i.pos_args.split(","): + pos_args_list.append(args.strip()) + + parameter_dict['kw_args'] = kw_args_dict + parameter_dict['pos_args'] = pos_args_list + test_case_parameter.append(parameter_dict) + + return test_case_parameter + def __unicode__(self): return self.summary @@ -411,16 +430,48 @@ class AssignmentUpload(models.Model): class TestCase(models.Model): question = models.ForeignKey(Question) + # Test case function name + func_name = models.CharField(max_length=200) + # Test case Keyword arguments in dict form - test_keyword_args = models.TextField(blank=True) + kw_args = models.TextField(blank=True) # Test case Positional arguments in list form - test_pos_args = models.TextField(blank=True) + pos_args = models.TextField(blank=True) # Test case Expected answer in list form - test_expected_answer = models.TextField(blank=True) + expected_answer = models.TextField(blank=True) + + # Test case path to system test code applicable for CPP, C, Java and Scilab + ref_code_path = models.TextField(blank=True) # Is this Test Case public or not. If it is not # public it will not be visible to the user # public = models.BooleanField(default=False) + # def consolidate_test_cases(self): + # test_case_parameter= {} + # kw_args_dict = {} + # pos_args_list = [] + + # for i in self: + # test_case_parameter.setdefault(i.id, {}) + # test_case_parameter[i.id]['func_name'] = i.func_name + # test_case_parameter[i.id]['expected_answer'] = i.expected_answer + + # for args in i.kw_args.split(","): + # key, val = args.split("=") + # kw_args_dict[key.strip()] = val.strip() + + + # for args in i.pos_args.split(","): + # pos_args_list.append(args.strip()) + + # test_case_parameter[i.id]['kw_args'] = kw_args_dict + # test_case_parameter[i.id]['pos_args'] = pos_args_list + + # # test_case_parameter[i.id]['kw_args'] = i.kw_args + # # test_case_parameter[i.id]['pos_args'] = i.pos_args + + # return test_case_parameter + diff --git a/testapp/exam/views.py b/testapp/exam/views.py index 9b6ce69..ac2668c 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -16,7 +16,7 @@ from taggit.models import Tag from itertools import chain # Local imports. from testapp.exam.models import Quiz, Question, QuestionPaper, QuestionSet -from testapp.exam.models import Profile, Answer, AnswerPaper, User +from testapp.exam.models import Profile, Answer, AnswerPaper, User, TestCase from testapp.exam.forms import UserRegisterForm, UserLoginForm, QuizForm,\ QuestionForm, RandomQuestionForm from testapp.exam.xmlrpc_clients import code_server @@ -848,6 +848,49 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): if not user.is_authenticated() or paper.end_time < datetime.datetime.now(): return my_redirect('/exam/login/') question = get_object_or_404(Question, pk=q_id) + q_paper = QuestionPaper.objects.get(id=questionpaper_id) + paper = AnswerPaper.objects.get(user=request.user, question_paper=q_paper) + test = TestCase.objects.filter(question=question) + test_parameter = question.consolidate_test_cases(test) + + # #### + # for i in TestCase.objects.all(): + # print "=====$$$$$=====KEY", i.kw_args + # print "=====$$$$$=====POS", i.pos_args + # print "=====$$$$$=====FUNC", i.func_name + + # test_case_parameters = {} + # kw_args_dict = {} + # pos_args_list = [] + + # for i in TestCase.objects.all(): + # test_case_parameters.setdefault(i.id, {}) + # test_case_parameters[i.id]['func_name'] = i.func_name + # test_case_parameters[i.id]['expected_answer'] = i.expected_answer + + # for args in i.kw_args.split(","): + # key, val = args.split("=") + # kw_args_dict[key.strip()] = val.strip() + + + # for args in i.pos_args.split(","): + # pos_args_list.append(args.strip()) + + # test_case_parameters[i.id]['kw_args'] = kw_args_dict + # test_case_parameters[i.id]['pos_args'] = pos_args_list + + # print "######-----####-----", test_case_parameters + + # test_obj = TestCase.objects.filter(question=question) + # test_parameter = [] + # for i in test_obj: + # d = {} + # d['test_func_name'] = i.test_func_name + # d['test_keyword_args'] = i.test_keyword_args + # d['test_pos_args'] = i.test_pos_args + # d['test_expected_answer'] = i.test_expected_answer + # test_parameter.append(d) + # #### snippet_code = request.POST.get('snippet') user_code = request.POST.get('answer') skip = request.POST.get('skip', None) @@ -860,6 +903,19 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): next_q = paper.skip() return show_question(request, next_q, attempt_num, questionpaper_id) + # ### of the form addnum(1, 2, one=2, two=2) + # test_code_eval = "" + # for param in test_parameter: + # # print "TESTCASE----OUTLOOP", literal_eval(test_case['test_keyword_args']) + + # tcode = "assert {0}({1}, {2})\n""" \ + # .format(param.get('test_func_name'), param.get('test_pos_args'), param.get('test_keyword_args')) + # # .format(", ".join(str(i) for i in test_case['test_pos_args']), + # # ", ".join(str(key+"="+arg) for key, arg in eval(test_case['test_keyword_args']).iteritems())) + # test_code_eval = "\n".join(tcode) + + # print test_code_eval + # Add the answer submitted, regardless of it being correct or not. if question.type == 'mcq': user_answer = request.POST.get('answer') @@ -876,7 +932,8 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): assign.save() user_answer = 'ASSIGNMENT UPLOADED' else: - user_answer = snippet_code + "\n" + user_code + user_code = request.POST.get('answer') + user_answer = user_code #snippet_code + "\n" + user_code new_answer = Answer(question=question, answer=user_answer, correct=False) @@ -887,7 +944,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # questions, we obtain the results via XML-RPC with the code executed # safely in a separate process (the code_server.py) running as nobody. if not question.type == 'upload': - correct, success, err_msg = validate_answer(user, user_answer, question) + correct, success, err_msg = validate_answer(user, user_answer, question, test_parameter) if correct: new_answer.correct = correct new_answer.marks = question.points @@ -933,7 +990,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): questionpaper_id, success_msg) -def validate_answer(user, user_answer, question): +def validate_answer(user, user_answer, question, test_parameter):#### """ Checks whether the answer submitted by the user is right or wrong. If right then returns correct = True, success and @@ -958,18 +1015,9 @@ def validate_answer(user, user_answer, question): message = 'Correct answer' elif question.type == 'code': user_dir = get_user_dir(user) - success, message = code_server.run_code(user_answer, question.test, - question.test_keyword_args, question.test_pos_args, - question.test_expected_answer, user_dir, question.language) #### + success, message = code_server.run_code(user_answer, question.test, test_parameter, user_dir, question.language) #### if success: correct = True - - #### - print "MESS>>>", question.test - print "POS>>>", question.test_pos_args - print "KEY>>>", question.test_keyword_args - print "EXP>>>", question.test_expected_answer - #### return correct, success, message diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index 1aab8de..320d627 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -29,7 +29,7 @@ class CodeServerProxy(object): "scilab": "run_scilab_code", } - def run_code(self, answer, test_code, test_obj, keyword_args, pos_args, expected_answer, user_dir, language): #### + def run_code(self, answer, test_code, test_parameter, user_dir, language): #### """Tests given code (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change it back to the original when @@ -55,7 +55,7 @@ class CodeServerProxy(object): try: server = self._get_server() method = getattr(server, method_name) - result = method(answer, test_code, test_obj, user_dir) #### + result = method(answer, test_code, test_parameter, user_dir) #### except ConnectionError: result = [False, 'Unable to connect to any code servers!'] return result -- cgit From 591c261ebbbaa0a052ed7b82428a3627ddaa7b1a Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Mon, 16 Feb 2015 16:53:18 +0530 Subject: Modify method of returning answers from code server --- testapp/exam/code_server.py | 62 ++++++++++++++++++++----------------- testapp/exam/models.py | 32 +------------------ testapp/exam/views.py | 70 ++++++------------------------------------ testapp/exam/xmlrpc_clients.py | 6 ++-- 4 files changed, 47 insertions(+), 123 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index c070986..c34971f 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -68,8 +68,7 @@ class CodeServer(object): 'have an infinite loop in your code.' % SERVER_TIMEOUT self.timeout_msg = msg - def run_python_code(self, answer, test_code, test_parameter, in_dir=None): #### - # def run_python_code(self, answer, test_code, in_dir=None): #### + def run_python_code(self, answer, test_parameter, in_dir=None): """Tests given Python function (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change @@ -92,9 +91,8 @@ class CodeServer(object): success = False tb = None - test_code_eval = "" + test_code = "" for test_case in test_parameter: - # for key, val in test_case.iteritems(): pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ else "" kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ @@ -102,12 +100,12 @@ class CodeServer(object): args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args tcode = "assert {0}({1}) == {2}" \ .format(test_case.get('func_name'), args, test_case.get('expected_answer')) - test_code_eval += tcode + "\n" + test_code += tcode + "\n" try: submitted = compile(answer, '', mode='exec') g = {} exec submitted in g - _tests = compile(test_code_eval, '', mode='exec') + _tests = compile(test_code, '', mode='exec') exec _tests in g except TimeoutException: err = self.timeout_msg @@ -134,10 +132,10 @@ class CodeServer(object): # Put us back into the server pool queue since we are free now. self.queue.put(self.port) - return success, err + result = {'success': success, 'error': err} + return result - # def run_bash_code(self, answer, test_code, test_parameter, in_dir=None): #### - def run_bash_code(self, answer, test_code, in_dir=None): #### + def run_bash_code(self, answer, test_parameter, in_dir=None): """Tests given Bash code (`answer`) with the `test_code` supplied. The testcode should typically contain two lines, the first is a path to @@ -172,7 +170,12 @@ class CodeServer(object): submit_path = abspath(submit_f.name) _set_exec(submit_path) - ref_path, test_case_path = test_code.strip().splitlines() + # ref path and path to arguments is a comma seperated string obtained + #from ref_code_path field in TestCase Mode + path_list = test_parameter.get('ref_code_path').split(',') + ref_path = path_list[0].strip() if path_list[0] else "" + test_case_path = path_list[1].strip() if path_list[1] else "" + if not ref_path.startswith('/'): ref_path = join(MY_DIR, ref_path) if not test_case_path.startswith('/'): @@ -205,7 +208,8 @@ class CodeServer(object): # Put us back into the server pool queue since we are free now. self.queue.put(self.port) - return success, err + result = {'success': success, 'error': err} + return result def _run_command(self, cmd_args, *args, **kw): """Run a command in a subprocess while blocking, the process is killed @@ -246,6 +250,8 @@ class CodeServer(object): the required permissions are not given to the file(s). """ + if not ref_script_path: + return False, "No Test Script path found" if not isfile(ref_script_path): return False, "No file at %s" % ref_script_path if not isfile(submit_script_path): @@ -255,7 +261,7 @@ class CodeServer(object): if not os.access(submit_script_path, os.X_OK): return False, 'Script %s is not executable' % submit_script_path - if test_case_path is None: + if test_case_path is None or "": ret = self._run_command(ref_script_path, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -304,8 +310,7 @@ class CodeServer(object): stdnt_stdout+stdnt_stderr) return False, err - # def run_c_code(self, answer, test_code, test_parameter, in_dir=None): #### - def run_c_code(self, answer, test_code, in_dir=None): #### + def run_c_code(self, answer, test_parameter, in_dir=None): """Tests given C code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -335,7 +340,7 @@ class CodeServer(object): submit_f.close() submit_path = abspath(submit_f.name) - ref_path = test_code.strip() + ref_path = test_parameter.get('ref_code_path').strip() if not ref_path.startswith('/'): ref_path = join(MY_DIR, ref_path) @@ -365,7 +370,8 @@ class CodeServer(object): # Put us back into the server pool queue since we are free now. self.queue.put(self.port) - return success, err + result = {'success': success, 'error': err} + return result def _compile_command(self, cmd, *args, **kw): """Compiles C/C++/java code and returns errors if any. @@ -457,8 +463,7 @@ class CodeServer(object): err = err + "\n" + stdnt_stderr return success, err - # def run_cplus_code(self, answer, test_code, test_parameter, in_dir=None): #### - def run_cplus_code(self, answer, test_code, in_dir=None): #### + def run_cplus_code(self, answer, test_parameter, in_dir=None): """Tests given C++ code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -488,7 +493,7 @@ class CodeServer(object): submit_f.close() submit_path = abspath(submit_f.name) - ref_path = test_code.strip() + ref_path = test_parameter.get('ref_code_path').strip() if not ref_path.startswith('/'): ref_path = join(MY_DIR, ref_path) @@ -518,10 +523,10 @@ class CodeServer(object): # Put us back into the server pool queue since we are free now. self.queue.put(self.port) - return success, err + result = {'success': success, 'error': err} + return result - # def run_java_code(self, answer, test_code, test_parameter, in_dir=None): #### - def run_java_code(self, answer, test_code, in_dir=None): #### + def run_java_code(self, answer, test_parameter, in_dir=None): """Tests given java code (`answer`) with the `test_code` supplied. The testcode is a path to the reference code. @@ -552,7 +557,7 @@ class CodeServer(object): submit_f.close() submit_path = abspath(submit_f.name) - ref_path = test_code.strip() + ref_path = test_parameter.get('ref_code_path').strip() if not ref_path.startswith('/'): ref_path = join(MY_DIR, ref_path) @@ -666,7 +671,8 @@ class CodeServer(object): err = err + "\n" + e except: err = err + "\n" + stdnt_stderr - return success, err + result = {'success': success, 'error': err} + return result def _remove_null_substitute_char(self, string): """Returns a string without any null and substitute characters""" @@ -676,8 +682,7 @@ class CodeServer(object): stripped = stripped + c return ''.join(stripped) - # def run_scilab_code(self, answer, test_code, test_parameter, in_dir=None): #### - def run_scilab_code(self, answer, test_code, in_dir=None): #### + def run_scilab_code(self, answer, test_parameter, in_dir=None): """Tests given Scilab function (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change @@ -718,7 +723,7 @@ class CodeServer(object): submit_f.close() submit_path = abspath(submit_f.name) - ref_path = test_code.strip() + ref_path = test_parameter.get('ref_code_path').strip() if not ref_path.startswith('/'): ref_path = join(MY_DIR, ref_path) @@ -766,7 +771,8 @@ class CodeServer(object): # Put us back into the server pool queue since we are free now. self.queue.put(self.port) - return success, err + result = {'success': success, 'error': err} + return result def _remove_scilab_exit(self, string): """ diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 4eeceac..4b8f737 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -94,6 +94,7 @@ class Question(models.Model): parameter_dict['func_name'] = i.func_name parameter_dict['expected_answer'] = i.expected_answer parameter_dict['ref_code_path'] = i.ref_code_path + parameter_dict['language'] = self.language if i.kw_args: for args in i.kw_args.split(","): @@ -444,34 +445,3 @@ class TestCase(models.Model): # Test case path to system test code applicable for CPP, C, Java and Scilab ref_code_path = models.TextField(blank=True) - - # Is this Test Case public or not. If it is not - # public it will not be visible to the user - # public = models.BooleanField(default=False) - - # def consolidate_test_cases(self): - # test_case_parameter= {} - # kw_args_dict = {} - # pos_args_list = [] - - # for i in self: - # test_case_parameter.setdefault(i.id, {}) - # test_case_parameter[i.id]['func_name'] = i.func_name - # test_case_parameter[i.id]['expected_answer'] = i.expected_answer - - # for args in i.kw_args.split(","): - # key, val = args.split("=") - # kw_args_dict[key.strip()] = val.strip() - - - # for args in i.pos_args.split(","): - # pos_args_list.append(args.strip()) - - # test_case_parameter[i.id]['kw_args'] = kw_args_dict - # test_case_parameter[i.id]['pos_args'] = pos_args_list - - # # test_case_parameter[i.id]['kw_args'] = i.kw_args - # # test_case_parameter[i.id]['pos_args'] = i.pos_args - - # return test_case_parameter - diff --git a/testapp/exam/views.py b/testapp/exam/views.py index ac2668c..be0d292 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -853,44 +853,6 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): test = TestCase.objects.filter(question=question) test_parameter = question.consolidate_test_cases(test) - # #### - # for i in TestCase.objects.all(): - # print "=====$$$$$=====KEY", i.kw_args - # print "=====$$$$$=====POS", i.pos_args - # print "=====$$$$$=====FUNC", i.func_name - - # test_case_parameters = {} - # kw_args_dict = {} - # pos_args_list = [] - - # for i in TestCase.objects.all(): - # test_case_parameters.setdefault(i.id, {}) - # test_case_parameters[i.id]['func_name'] = i.func_name - # test_case_parameters[i.id]['expected_answer'] = i.expected_answer - - # for args in i.kw_args.split(","): - # key, val = args.split("=") - # kw_args_dict[key.strip()] = val.strip() - - - # for args in i.pos_args.split(","): - # pos_args_list.append(args.strip()) - - # test_case_parameters[i.id]['kw_args'] = kw_args_dict - # test_case_parameters[i.id]['pos_args'] = pos_args_list - - # print "######-----####-----", test_case_parameters - - # test_obj = TestCase.objects.filter(question=question) - # test_parameter = [] - # for i in test_obj: - # d = {} - # d['test_func_name'] = i.test_func_name - # d['test_keyword_args'] = i.test_keyword_args - # d['test_pos_args'] = i.test_pos_args - # d['test_expected_answer'] = i.test_expected_answer - # test_parameter.append(d) - # #### snippet_code = request.POST.get('snippet') user_code = request.POST.get('answer') skip = request.POST.get('skip', None) @@ -903,19 +865,6 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): next_q = paper.skip() return show_question(request, next_q, attempt_num, questionpaper_id) - # ### of the form addnum(1, 2, one=2, two=2) - # test_code_eval = "" - # for param in test_parameter: - # # print "TESTCASE----OUTLOOP", literal_eval(test_case['test_keyword_args']) - - # tcode = "assert {0}({1}, {2})\n""" \ - # .format(param.get('test_func_name'), param.get('test_pos_args'), param.get('test_keyword_args')) - # # .format(", ".join(str(i) for i in test_case['test_pos_args']), - # # ", ".join(str(key+"="+arg) for key, arg in eval(test_case['test_keyword_args']).iteritems())) - # test_code_eval = "\n".join(tcode) - - # print test_code_eval - # Add the answer submitted, regardless of it being correct or not. if question.type == 'mcq': user_answer = request.POST.get('answer') @@ -944,7 +893,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # questions, we obtain the results via XML-RPC with the code executed # safely in a separate process (the code_server.py) running as nobody. if not question.type == 'upload': - correct, success, err_msg = validate_answer(user, user_answer, question, test_parameter) + correct, result = validate_answer(user, user_answer, question, test_parameter) if correct: new_answer.correct = correct new_answer.marks = question.points @@ -955,7 +904,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): new_answer.save() time_left = paper.time_left() - if not success: # Should only happen for non-mcq questions. + if not result.get('success'): # Should only happen for non-mcq questions. if time_left == 0: reason = 'Your time is up!' return complete(request, reason, attempt_num, questionpaper_id) @@ -970,8 +919,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): if old_answer: old_answer[0].answer = user_code old_answer[0].save() - context = {'question': question, 'questions': questions, - 'error_message': err_msg, + context = {'question': question, 'error_message': result.get('error'), 'paper': paper, 'last_attempt': user_code, 'quiz_name': paper.question_paper.quiz.description, 'time_left': time_left, 'to_attempt': to_attempt, @@ -990,7 +938,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): questionpaper_id, success_msg) -def validate_answer(user, user_answer, question, test_parameter):#### +def validate_answer(user, user_answer, question, test_parameter): """ Checks whether the answer submitted by the user is right or wrong. If right then returns correct = True, success and @@ -999,9 +947,9 @@ def validate_answer(user, user_answer, question, test_parameter):#### only one attempt are allowed for them. For code questions success is True only if the answer is correct. """ - success = True + + result = {'success': True, 'error': 'Incorrect answer'} correct = False - message = 'Incorrect answer' if user_answer is not None: if question.type == 'mcq': @@ -1015,11 +963,11 @@ def validate_answer(user, user_answer, question, test_parameter):#### message = 'Correct answer' elif question.type == 'code': user_dir = get_user_dir(user) - success, message = code_server.run_code(user_answer, question.test, test_parameter, user_dir, question.language) #### - if success: + result = code_server.run_code(user_answer, test_parameter, user_dir, question.language) + if result.get('success'): correct = True - return correct, success, message + return correct, result def quit(request, attempt_num=None, questionpaper_id=None): diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index 320d627..692fbb5 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -29,7 +29,7 @@ class CodeServerProxy(object): "scilab": "run_scilab_code", } - def run_code(self, answer, test_code, test_parameter, user_dir, language): #### + def run_code(self, answer, test_parameter, user_dir, language): """Tests given code (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change it back to the original when @@ -55,9 +55,9 @@ class CodeServerProxy(object): try: server = self._get_server() method = getattr(server, method_name) - result = method(answer, test_code, test_parameter, user_dir) #### + result = method(answer, test_parameter, user_dir) #### except ConnectionError: - result = [False, 'Unable to connect to any code servers!'] + result = {'success': False, 'error': 'Unable to connect to any code servers!'} return result def _get_server(self): -- cgit From 8e2469e937fd4f80ebf2053d6e21c9b670d38ea2 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Fri, 20 Feb 2015 18:04:36 +0530 Subject: Modify form, views and templates to reflect changes made to TestCase model --- testapp/exam/forms.py | 27 +++-- testapp/exam/models.py | 16 +-- testapp/exam/templates/exam/add_question.html | 37 +++++-- testapp/exam/templates/exam/edit_question.html | 34 ++++++- testapp/exam/views.py | 135 ++++++++++++++++++------- 5 files changed, 188 insertions(+), 61 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/forms.py b/testapp/exam/forms.py index 1f12a3b..e3cef40 100644 --- a/testapp/exam/forms.py +++ b/testapp/exam/forms.py @@ -1,5 +1,5 @@ from django import forms -from testapp.exam.models import Profile, Quiz, Question +from exam.models import Profile, Quiz, Question, TestCase from django.contrib.auth import authenticate from django.contrib.auth.models import User @@ -8,6 +8,7 @@ from taggit.forms import TagField from taggit_autocomplete_modified.managers import TaggableManagerAutocomplete from taggit_autocomplete_modified.widgets import TagAutocomplete from taggit_autocomplete_modified import settings +from django.forms.models import inlineformset_factory from string import letters, punctuation, digits import datetime @@ -177,7 +178,7 @@ class QuizForm(forms.Form): new_quiz.save() -class QuestionForm(forms.Form): +class QuestionForm(forms.ModelForm): """Creates a form to add or edit a Question. It has the related fields and functions required.""" @@ -186,8 +187,8 @@ class QuestionForm(forms.Form): description = forms.CharField(widget=forms.Textarea\ (attrs={'cols': 40, 'rows': 1})) points = forms.FloatField() - test = forms.CharField(widget=forms.Textarea\ - (attrs={'cols': 40, 'rows': 1})) + # test = forms.CharField(widget=forms.Textarea\ + # (attrs={'cols': 40, 'rows': 1}), required=False) options = forms.CharField(widget=forms.Textarea\ (attrs={'cols': 40, 'rows': 1}), required=False) language = forms.CharField(max_length=20, widget=forms.Select\ @@ -199,11 +200,11 @@ class QuestionForm(forms.Form): snippet = forms.CharField(widget=forms.Textarea\ (attrs={'cols': 40, 'rows': 1}), required=False) - def save(self): + def save(self, commit=True): summary = self.cleaned_data["summary"] description = self.cleaned_data["description"] points = self.cleaned_data['points'] - test = self.cleaned_data["test"] + # test = self.cleaned_data["test"] options = self.cleaned_data['options'] language = self.cleaned_data['language'] type = self.cleaned_data["type"] @@ -214,13 +215,20 @@ class QuestionForm(forms.Form): new_question.summary = summary new_question.description = description new_question.points = points - new_question.test = test + # new_question.test = test new_question.options = options new_question.language = language new_question.type = type new_question.active = active new_question.snippet = snippet - new_question.save() + new_question = super(QuestionForm, self).save(commit=False) + if commit: + new_question.save() + + return new_question + + class Meta: + model = Question class RandomQuestionForm(forms.Form): @@ -229,3 +237,6 @@ class RandomQuestionForm(forms.Form): marks = forms.CharField(max_length=8, widget=forms.Select\ (choices=(('select', 'Select Marks'),))) shuffle_questions = forms.BooleanField(required=False) + +TestCaseFormSet = inlineformset_factory(Question, TestCase,\ + can_order=False, can_delete=False, extra=1) diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 4b8f737..ea79ad2 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -59,8 +59,8 @@ class Question(models.Model): # Number of points for the question. points = models.FloatField(default=1.0) - # Test cases for the question in the form of code that is run. - test = models.TextField(blank=True) + # # Test cases for the question in the form of code that is run. + # test = models.TextField(blank=True) # Any multiple choice options. Place one option per line. options = models.TextField(blank=True) @@ -429,19 +429,19 @@ class AssignmentUpload(models.Model): ################################################################################ class TestCase(models.Model): - question = models.ForeignKey(Question) + question = models.ForeignKey(Question, blank=True, null = True) # Test case function name - func_name = models.CharField(max_length=200) + func_name = models.CharField(blank=True, null = True, max_length=200) # Test case Keyword arguments in dict form - kw_args = models.TextField(blank=True) + kw_args = models.TextField(blank=True, null = True) # Test case Positional arguments in list form - pos_args = models.TextField(blank=True) + pos_args = models.TextField(blank=True, null = True) # Test case Expected answer in list form - expected_answer = models.TextField(blank=True) + expected_answer = models.TextField(blank=True, null = True) # Test case path to system test code applicable for CPP, C, Java and Scilab - ref_code_path = models.TextField(blank=True) + ref_code_path = models.TextField(blank=True, null = True) diff --git a/testapp/exam/templates/exam/add_question.html b/testapp/exam/templates/exam/add_question.html index b6ce908..d989b81 100644 --- a/testapp/exam/templates/exam/add_question.html +++ b/testapp/exam/templates/exam/add_question.html @@ -27,17 +27,38 @@ Points:{{ form.points }}{{ form.points.errors }} Rendered:

Description: {{ form.description}} {{form.description.errors}} - Test: {{ form.test }}{{form.test.errors}} - Test Keyword Arguments: {{ form.test_keyword_args }}{{form.test.errors}} - Test Positional Arguments: {{ form.test_pos_args }}{{form.test.errors}} - Test Expected Answer: {{ form.test_expected_answer }}{{form.test.errors}} + Snippet: {{ form.snippet }}{{ form.snippet.errors }} Tags: {{ form.tags }} - Options: {{ form.options }} {{form.options.errors}} - - + Options: {{ form.options }} {{form.options.errors}} + +
+ {% if formset%} + {{ formset.management_form }} + {% for form in formset %} + {{ form }} + {% endfor %} + {% endif %} +
+ + -
+
+ +

+
{% endblock %} diff --git a/testapp/exam/templates/exam/edit_question.html b/testapp/exam/templates/exam/edit_question.html index b28cc3e..1c34dee 100644 --- a/testapp/exam/templates/exam/edit_question.html +++ b/testapp/exam/templates/exam/edit_question.html @@ -21,7 +21,7 @@ - {% for form in forms %} + + + {% for question, test in data_list %} + +
{{question.summary.value}} + + + {% endfor %}
+ + {% for i in data %} {% endfor %} diff --git a/testapp/exam/views.py b/testapp/exam/views.py index be0d292..3d22d55 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -18,8 +18,8 @@ from itertools import chain from testapp.exam.models import Quiz, Question, QuestionPaper, QuestionSet from testapp.exam.models import Profile, Answer, AnswerPaper, User, TestCase from testapp.exam.forms import UserRegisterForm, UserLoginForm, QuizForm,\ - QuestionForm, RandomQuestionForm -from testapp.exam.xmlrpc_clients import code_server + QuestionForm, RandomQuestionForm, TestCaseFormSet +from exam.xmlrpc_clients import code_server from settings import URL_ROOT from testapp.exam.models import AssignmentUpload @@ -281,7 +281,7 @@ def edit_quiz(request): def edit_question(request): - """Edit the list of questions seleted by the user for editing.""" + """Edit the list of questions selected by the user for editing.""" user = request.user if not user.is_authenticated() or not is_moderator(user): raise Http404('You are not allowed to view this page!') @@ -290,7 +290,6 @@ def edit_question(request): summary = request.POST.getlist('summary') description = request.POST.getlist('description') points = request.POST.getlist('points') - test = request.POST.getlist('test') options = request.POST.getlist('options') type = request.POST.getlist('type') active = request.POST.getlist('active') @@ -298,10 +297,15 @@ def edit_question(request): snippet = request.POST.getlist('snippet') for j, question_id in enumerate(question_list): question = Question.objects.get(id=question_id) + test_case_formset = TestCaseFormSet(request.POST, prefix='test', instance=question) + if test_case_formset.is_valid(): + test_case_instance = test_case_formset.save(commit=False) + for i in test_case_instance: + i.save() + question.summary = summary[j] question.description = description[j] question.points = points[j] - question.test = test[j] question.options = options[j] question.active = active[j] question.language = language[j] @@ -314,6 +318,16 @@ def edit_question(request): def add_question(request, question_id=None): """To add a new question in the database. Create a new question and store it.""" + + def add_or_delete_test_form(post_request, instance): + request_copy = post_request.copy() + if 'add_test' in post_request: + request_copy['test-TOTAL_FORMS'] = int(request_copy['test-TOTAL_FORMS']) + 1 + elif 'delete_test' in post_request: + request_copy['test-TOTAL_FORMS'] = int(request_copy['test-TOTAL_FORMS']) - 1 + test_case_formset = TestCaseFormSet(request_copy, prefix='test', instance=instance) + return test_case_formset + user = request.user ci = RequestContext(request) if not user.is_authenticated() or not is_moderator(user): @@ -321,44 +335,84 @@ def add_question(request, question_id=None): if request.method == "POST": form = QuestionForm(request.POST) if form.is_valid(): - data = form.cleaned_data if question_id is None: - form.save() - question = Question.objects.order_by("-id")[0] - tags = form['tags'].data.split(',') - for i in range(0, len(tags)-1): - tag = tags[i].strip() - question.tags.add(tag) - return my_redirect("/exam/manage/questions") + test_case_formset = add_or_delete_test_form(request.POST, form.save(commit=False)) + if 'save_question' in request.POST: + qtn = form.save(commit=False) + test_case_formset = TestCaseFormSet(request.POST, prefix='test', instance=qtn) + form.save() + question = Question.objects.order_by("-id")[0] + tags = form['tags'].data.split(',') + for i in range(0, len(tags)-1): + tag = tags[i].strip() + question.tags.add(tag) + if test_case_formset.is_valid(): + test_case_formset.save() + else: + return my_render_to_response('exam/add_question.html', + {'form': form, + 'formset': test_case_formset}, + context_instance=ci) + + return my_redirect("/exam/manage/questions") + + return my_render_to_response('exam/add_question.html', + {'form': form, + 'formset': test_case_formset}, + context_instance=ci) + else: d = Question.objects.get(id=question_id) - d.summary = form['summary'].data - d.description = form['description'].data - d.points = form['points'].data - d.test = form['test'].data - d.options = form['options'].data - d.type = form['type'].data - d.active = form['active'].data - d.language = form['language'].data - d.snippet = form['snippet'].data - d.save() - question = Question.objects.get(id=question_id) - for tag in question.tags.all(): - question.tags.remove(tag) - tags = form['tags'].data.split(',') - for i in range(0, len(tags)-1): - tag = tags[i].strip() - question.tags.add(tag) - return my_redirect("/exam/manage/questions") + test_case_formset = add_or_delete_test_form(request.POST, d) + if 'save_question' in request.POST: + d.summary = form['summary'].data + d.description = form['description'].data + d.points = form['points'].data + d.options = form['options'].data + d.type = form['type'].data + d.active = form['active'].data + d.language = form['language'].data + d.snippet = form['snippet'].data + d.save() + question = Question.objects.get(id=question_id) + for tag in question.tags.all(): + question.tags.remove(tag) + tags = form['tags'].data.split(',') + for i in range(0, len(tags)-1): + tag = tags[i].strip() + question.tags.add(tag) + + test_case_formset = TestCaseFormSet(request.POST, prefix='test', instance=question) + if test_case_formset.is_valid(): + test_case_instance = test_case_formset.save(commit=False) + for i in test_case_instance: + i.save() + else: + return my_render_to_response('exam/add_question.html', + {'form': form, + 'formset': test_case_formset}, + context_instance=ci) + + + return my_redirect("/exam/manage/questions") + return my_render_to_response('exam/add_question.html', + {'form': form, + 'formset': test_case_formset}, + context_instance=ci) + else: return my_render_to_response('exam/add_question.html', {'form': form}, context_instance=ci) else: + form = QuestionForm() + test_case_formset = TestCaseFormSet(prefix='test', instance=Question()) if question_id is None: form = QuestionForm() + test_case_formset = TestCaseFormSet(prefix='test', instance=Question()) return my_render_to_response('exam/add_question.html', - {'form': form}, + {'form': form, + 'formset': test_case_formset}, context_instance=ci) else: d = Question.objects.get(id=question_id) @@ -366,7 +420,6 @@ def add_question(request, question_id=None): form.initial['summary'] = d.summary form.initial['description'] = d.description form.initial['points'] = d.points - form.initial['test'] = d.test form.initial['options'] = d.options form.initial['type'] = d.type form.initial['active'] = d.active @@ -380,8 +433,13 @@ def add_question(request, question_id=None): if (initial_tags == ","): initial_tags = "" form.initial['tags'] = initial_tags + + test_case_formset = TestCaseFormSet(prefix='test', + instance=d) + return my_render_to_response('exam/add_question.html', - {'form': form}, + {'form': form, + 'formset': test_case_formset}, context_instance=ci) @@ -1172,13 +1230,13 @@ def show_all_questions(request): data = request.POST.getlist('question') forms = [] + formsets = [] for j in data: d = Question.objects.get(id=j) form = QuestionForm() form.initial['summary'] = d.summary form.initial['description'] = d.description form.initial['points'] = d.points - form.initial['test'] = d.test form.initial['options'] = d.options form.initial['type'] = d.type form.initial['active'] = d.active @@ -1193,8 +1251,13 @@ def show_all_questions(request): initial_tags = "" form.initial['tags'] = initial_tags forms.append(form) + test_case_formset = TestCaseFormSet(prefix='test', instance=d) + formsets.append(test_case_formset) + data_list = zip(forms, formsets) + return my_render_to_response('exam/edit_question.html', - {'forms': forms, 'data': data}, + {'data': data, + 'data_list': data_list}, context_instance=ci) else: questions = Question.objects.all() -- cgit From 9440bff5ae69c1d27f5c9622ca15cb8c603c6174 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Thu, 5 Mar 2015 12:27:02 +0530 Subject: Code Server code cleanup and code commonification - Pass question and test case info as json string (info_parameter) - Return success status and error message as a json string - Embed user answer and question lang in info_parameter - Commonify Python code evaluations and assertion test - Deprecate individual function call based on language --- testapp/exam/code_server.py | 1521 ++++++++++++++----------- testapp/exam/models.py | 14 +- testapp/exam/templates/exam/add_question.html | 14 - testapp/exam/views.py | 21 +- testapp/exam/xmlrpc_clients.py | 22 +- 5 files changed, 904 insertions(+), 688 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index c34971f..fa5d916 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -29,6 +29,7 @@ import signal from multiprocessing import Process, Queue import subprocess import re +import json # Local imports. from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT @@ -53,6 +54,131 @@ def timeout_handler(signum, frame): """A handler for the ALARM signal.""" raise TimeoutException('Code took too long to run.') +def create_delete_signal_handler(action=None, old_handler=None): + if action == "new": # Add a new signal handler for the execution of this code. + prev_handler = signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(SERVER_TIMEOUT) + return prev_handler + elif action == "original": # Set back any original signal handler. + if old_handler is not None: + signal.signal(signal.SIGALRM, old_handler) + return + else: + raise Exception("Signal Handler: object cannot be NoneType") + elif action == "delete": # Cancel the signal if any, see signal.alarm documentation. + signal.alarm(0) + return + else: + raise Exception("Signal Handler: action not specified") + + +############################################################################### +# `TestCode` class. +############################################################################### +class TestCode(object): + """Evaluates and tests the code obtained from Code Server""" + def __init__(self, info_parameter, in_dir=None): + info_parameter = json.loads(info_parameter) + self.test_parameter = info_parameter.get("test_parameter") + self.language = info_parameter.get("language") + self.user_answer = info_parameter.get("user_answer") + self.in_dir = in_dir + + def run_code(self): + self._change_dir(self.in_dir) + + # Create test cases + test_code = self.create_test_case() + # Evaluate, run code and obtain result + result = self.evaluate_code(test_code) + + return result + + def evaluate_code(self, test_code): + success = False + prev_handler = None + + if self.language == "python": + tb = None + prev_handler = create_delete_signal_handler("new") + + try: + submitted = compile(self.user_answer, '', mode='exec') + g = {} + exec submitted in g + _tests = compile(test_code, '', mode='exec') + exec _tests in g + except TimeoutException: + err = self.timeout_msg + except AssertionError: + type, value, tb = sys.exc_info() + info = traceback.extract_tb(tb) + fname, lineno, func, text = info[-1] + text = str(test_code).splitlines()[lineno-1] + err = "{0} {1} in: {2}".format(type.__name__, str(value), text) + except: + type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + else: + success = True + err = 'Correct answer' + finally: + del tb + # Set back any original signal handler. + create_delete_signal_handler("original", prev_handler) + + # Cancel the signal + create_delete_signal_handler("delete") + + if self.language == "c++": + pass + + if self.language == "c": + pass + + if self.language == "java": + pass + + if self.language == "scilab": + pass + + result = {'success': success, 'error': err} + return result + + def compile_code(self): + pass + + def create_test_case(self): + # Create assert based test cases in python + if self.language == "python": + test_code = "" + for test_case in self.test_parameter: + pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ + else "" + kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ + if test_case.get('kw_args') else "" + args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args + tcode = "assert {0}({1}) == {2}" \ + .format(test_case.get('func_name'), args, test_case.get('expected_answer')) + test_code += tcode + "\n" + return test_code + + if self.language == "c++": + pass + + if self.language == "c": + pass + + if self.language == "java": + pass + + if self.language == "scilab": + pass + + def _change_dir(self, in_dir): + if in_dir is not None and isdir(in_dir): + os.chdir(in_dir) + ############################################################################### # `CodeServer` class. @@ -68,6 +194,16 @@ class CodeServer(object): 'have an infinite loop in your code.' % SERVER_TIMEOUT self.timeout_msg = msg + def checker(self, info_parameter, in_dir=None): + """Calls the TestCode Class to test the current code""" + tc = TestCode(info_parameter, in_dir) + result = tc.run_code() + + # Put us back into the server pool queue since we are free now. + self.queue.put(self.port) + + return json.dumps(result) + def run_python_code(self, answer, test_parameter, in_dir=None): """Tests given Python function (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied @@ -88,6 +224,7 @@ class CodeServer(object): old_handler = signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(SERVER_TIMEOUT) + test_parameter = json.loads(test_parameter) success = False tb = None @@ -135,659 +272,741 @@ class CodeServer(object): result = {'success': success, 'error': err} return result - def run_bash_code(self, answer, test_parameter, in_dir=None): - """Tests given Bash code (`answer`) with the `test_code` supplied. - - The testcode should typically contain two lines, the first is a path to - the reference script we are to compare against. The second is a path - to the arguments to be supplied to the reference and submitted script. - The output of these will be compared for correctness. - - If the path's start with a "/" then we assume they are absolute paths. - If not, we assume they are relative paths w.r.t. the location of this - code_server script. - - If the optional `in_dir` keyword argument is supplied it changes the - directory to that directory (it does not change it back to the original - when done). - - Returns - ------- - - A tuple: (success, error message). - - """ - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) - - def _set_exec(fname): - os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR - | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP - | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) - submit_f = open('submit.sh', 'w') - submit_f.write(answer.lstrip()) - submit_f.close() - submit_path = abspath(submit_f.name) - _set_exec(submit_path) - - # ref path and path to arguments is a comma seperated string obtained - #from ref_code_path field in TestCase Mode - path_list = test_parameter.get('ref_code_path').split(',') - ref_path = path_list[0].strip() if path_list[0] else "" - test_case_path = path_list[1].strip() if path_list[1] else "" - - if not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - if not test_case_path.startswith('/'): - test_case_path = join(MY_DIR, test_case_path) - - # Add a new signal handler for the execution of this code. - old_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - - # Do whatever testing needed. - success = False - try: - success, err = self.check_bash_script(ref_path, submit_path, - test_case_path) - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - signal.signal(signal.SIGALRM, old_handler) - - # Delete the created file. - os.remove(submit_path) - - # Cancel the signal if any, see signal.alarm documentation. - signal.alarm(0) - - # Put us back into the server pool queue since we are free now. - self.queue.put(self.port) - - result = {'success': success, 'error': err} - return result - - def _run_command(self, cmd_args, *args, **kw): - """Run a command in a subprocess while blocking, the process is killed - if it takes more than 2 seconds to run. Return the Popen object, the - stdout and stderr. - """ - try: - proc = subprocess.Popen(cmd_args, *args, **kw) - stdout, stderr = proc.communicate() - except TimeoutException: - # Runaway code, so kill it. - proc.kill() - # Re-raise exception. - raise - return proc, stdout, stderr - - def check_bash_script(self, ref_script_path, submit_script_path, - test_case_path=None): - """ Function validates student script using instructor script as - reference. Test cases can optionally be provided. The first argument - ref_script_path, is the path to instructor script, it is assumed to - have executable permission. The second argument submit_script_path, is - the path to the student script, it is assumed to have executable - permission. The Third optional argument is the path to test the - scripts. Each line in this file is a test case and each test case is - passed to the script as standard arguments. - - Returns - -------- - - returns (True, "Correct answer") : If the student script passes all - test cases/have same output, when compared to the instructor script - - returns (False, error_msg): If the student script fails a single - test/have dissimilar output, when compared to the instructor script. - - Returns (False, error_msg): If mandatory arguments are not files or if - the required permissions are not given to the file(s). - - """ - if not ref_script_path: - return False, "No Test Script path found" - if not isfile(ref_script_path): - return False, "No file at %s" % ref_script_path - if not isfile(submit_script_path): - return False, 'No file at %s' % submit_script_path - if not os.access(ref_script_path, os.X_OK): - return False, 'Script %s is not executable' % ref_script_path - if not os.access(submit_script_path, os.X_OK): - return False, 'Script %s is not executable' % submit_script_path - - if test_case_path is None or "": - ret = self._run_command(ref_script_path, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, inst_stdout, inst_stderr = ret - ret = self._run_command(submit_script_path, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdnt_stdout, stdnt_stderr = ret - if inst_stdout == stdnt_stdout: - return True, 'Correct answer' - else: - err = "Error: expected %s, got %s" % (inst_stderr, - stdnt_stderr) - return False, err - else: - if not isfile(test_case_path): - return False, "No test case at %s" % test_case_path - if not os.access(ref_script_path, os.R_OK): - return False, "Test script %s, not readable" % test_case_path - valid_answer = True # We initially make it one, so that we can - # stop once a test case fails - loop_count = 0 # Loop count has to be greater than or - # equal to one. - # Useful for caching things like empty - # test files,etc. - test_cases = open(test_case_path).readlines() - num_lines = len(test_cases) - for test_case in test_cases: - loop_count += 1 - if valid_answer: - args = [ref_script_path] + [x for x in test_case.split()] - ret = self._run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, inst_stdout, inst_stderr = ret - args = [submit_script_path]+[x for x in test_case.split()] - ret = self._run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdnt_stdout, stdnt_stderr = ret - valid_answer = inst_stdout == stdnt_stdout - if valid_answer and (num_lines == loop_count): - return True, "Correct answer" - else: - err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, - stdnt_stdout+stdnt_stderr) - return False, err - - def run_c_code(self, answer, test_parameter, in_dir=None): - """Tests given C code (`answer`) with the `test_code` supplied. - - The testcode is a path to the reference code. - The reference code will call the function submitted by the student. - The reference code will check for the expected output. - - If the path's start with a "/" then we assume they are absolute paths. - If not, we assume they are relative paths w.r.t. the location of this - code_server script. - - If the optional `in_dir` keyword argument is supplied it changes the - directory to that directory (it does not change it back to the original - when done). - - Returns - ------- - - A tuple: (success, error message). - - """ - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) - - # File extension must be .c - submit_f = open('submit.c', 'w') - submit_f.write(answer.lstrip()) - submit_f.close() - submit_path = abspath(submit_f.name) - - ref_path = test_parameter.get('ref_code_path').strip() - if not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - - # Add a new signal handler for the execution of this code. - old_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - - # Do whatever testing needed. - success = False - try: - success, err = self._check_c_cpp_code(ref_path, submit_path) - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - signal.signal(signal.SIGALRM, old_handler) - - # Delete the created file. - os.remove(submit_path) - - # Cancel the signal if any, see signal.alarm documentation. - signal.alarm(0) - - # Put us back into the server pool queue since we are free now. - self.queue.put(self.port) - - result = {'success': success, 'error': err} - return result - - def _compile_command(self, cmd, *args, **kw): - """Compiles C/C++/java code and returns errors if any. - Run a command in a subprocess while blocking, the process is killed - if it takes more than 2 seconds to run. Return the Popen object, the - stderr. - """ - try: - proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = proc_compile.communicate() - except TimeoutException: - # Runaway code, so kill it. - proc_compile.kill() - # Re-raise exception. - raise - return proc_compile, err - - def _check_c_cpp_code(self, ref_code_path, submit_code_path): - """ Function validates student code using instructor code as - reference.The first argument ref_code_path, is the path to - instructor code, it is assumed to have executable permission. - The second argument submit_code_path, is the path to the student - code, it is assumed to have executable permission. - - Returns - -------- - - returns (True, "Correct answer") : If the student function returns - expected output when called by reference code. - - returns (False, error_msg): If the student function fails to return - expected output when called by reference code. - - Returns (False, error_msg): If mandatory arguments are not files or - if the required permissions are not given to the file(s). - - """ - if not isfile(ref_code_path): - return False, "No file at %s" % ref_code_path - if not isfile(submit_code_path): - return False, 'No file at %s' % submit_code_path - - success = False - output_path = os.getcwd() + '/output' - compile_command = "g++ %s -c -o %s" % (submit_code_path, output_path) - ret = self._compile_command(compile_command) - proc, stdnt_stderr = ret - - # Only if compilation is successful, the program is executed - # And tested with testcases - if stdnt_stderr == '': - executable = os.getcwd() + '/executable' - compile_main = "g++ %s %s -o %s" % (ref_code_path, output_path, - executable) - ret = self._compile_command(compile_main) - proc, main_err = ret - if main_err == '': - args = [executable] - ret = self._run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - if proc.returncode == 0: - success, err = True, "Correct answer" - else: - err = stdout + "\n" + stderr - os.remove(executable) - else: - err = "Error:" - try: - error_lines = main_err.splitlines() - for e in error_lines: - err = err + "\n" + e.split(":", 1)[1] - except: - err = err + "\n" + main_err - os.remove(output_path) - else: - err = "Compilation Error:" - try: - error_lines = stdnt_stderr.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + stdnt_stderr - return success, err - - def run_cplus_code(self, answer, test_parameter, in_dir=None): - """Tests given C++ code (`answer`) with the `test_code` supplied. - - The testcode is a path to the reference code. - The reference code will call the function submitted by the student. - The reference code will check for the expected output. - - If the path's start with a "/" then we assume they are absolute paths. - If not, we assume they are relative paths w.r.t. the location of this - code_server script. - - If the optional `in_dir` keyword argument is supplied it changes the - directory to that directory (it does not change it back to the original - when done). - - Returns - ------- - - A tuple: (success, error message). - - """ - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) - - # The file extension must be .cpp - submit_f = open('submitstd.cpp', 'w') - submit_f.write(answer.lstrip()) - submit_f.close() - submit_path = abspath(submit_f.name) - - ref_path = test_parameter.get('ref_code_path').strip() - if not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - - # Add a new signal handler for the execution of this code. - old_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - - # Do whatever testing needed. - success = False - try: - success, err = self._check_c_cpp_code(ref_path, submit_path) - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - signal.signal(signal.SIGALRM, old_handler) - - # Delete the created file. - os.remove(submit_path) - - # Cancel the signal if any, see signal.alarm documentation. - signal.alarm(0) - - # Put us back into the server pool queue since we are free now. - self.queue.put(self.port) - - result = {'success': success, 'error': err} - return result - - def run_java_code(self, answer, test_parameter, in_dir=None): - """Tests given java code (`answer`) with the `test_code` supplied. - - The testcode is a path to the reference code. - The reference code will call the function submitted by the student. - The reference code will check for the expected output. - - If the path's start with a "/" then we assume they are absolute paths. - If not, we assume they are relative paths w.r.t. the location of this - code_server script. - - If the optional `in_dir` keyword argument is supplied it changes the - directory to that directory (it does not change it back to the original - when done). - - Returns - ------- - - A tuple: (success, error message). - - """ - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) - - # The file extension must be .java - # The class name and file name must be same in java - submit_f = open('Test.java', 'w') - submit_f.write(answer.lstrip()) - submit_f.close() - submit_path = abspath(submit_f.name) - - ref_path = test_parameter.get('ref_code_path').strip() - if not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - - # Add a new signal handler for the execution of this code. - old_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - - # Do whatever testing needed. - success = False - try: - success, err = self._check_java_code(ref_path, submit_path) - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - signal.signal(signal.SIGALRM, old_handler) - - # Delete the created file. - os.remove(submit_path) - - # Cancel the signal if any, see signal.alarm documentation. - signal.alarm(0) - - # Put us back into the server pool queue since we are free now. - self.queue.put(self.port) - - return success, err - - def _check_java_code(self, ref_code_path, submit_code_path): - """ Function validates student code using instructor code as - reference.The first argument ref_code_path, is the path to - instructor code, it is assumed to have executable permission. - The second argument submit_code_path, is the path to the student - code, it is assumed to have executable permission. - - Returns - -------- - - returns (True, "Correct answer") : If the student function returns - expected output when called by reference code. - - returns (False, error_msg): If the student function fails to return - expected output when called by reference code. - - Returns (False, error_msg): If mandatory arguments are not files or - if the required permissions are not given to the file(s). - - """ - if not isfile(ref_code_path): - return False, "No file at %s" % ref_code_path - if not isfile(submit_code_path): - return False, 'No file at %s' % submit_code_path - - success = False - compile_command = "javac %s" % (submit_code_path) - ret = self._compile_command(compile_command) - proc, stdnt_stderr = ret - stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) - - # Only if compilation is successful, the program is executed - # And tested with testcases - if stdnt_stderr == '': - student_directory = os.getcwd() + '/' - student_file_name = "Test" - compile_main = "javac %s -classpath %s -d %s" % (ref_code_path, - student_directory, - student_directory) - ret = self._compile_command(compile_main) - proc, main_err = ret - main_err = self._remove_null_substitute_char(main_err) - - if main_err == '': - main_file_name = (ref_code_path.split('/')[-1]).split('.')[0] - run_command = "java -cp %s %s" % (student_directory, - main_file_name) - ret = self._run_command(run_command, - stdin=None, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - if proc.returncode == 0: - success, err = True, "Correct answer" - else: - err = stdout + "\n" + stderr - success = False - os.remove("%s%s.class" % (student_directory, main_file_name)) - else: - err = "Error:\n" - try: - error_lines = main_err.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + main_err - os.remove("%s%s.class" % (student_directory, student_file_name)) - else: - err = "Compilation Error:\n" - try: - error_lines = stdnt_stderr.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + stdnt_stderr - result = {'success': success, 'error': err} - return result - - def _remove_null_substitute_char(self, string): - """Returns a string without any null and substitute characters""" - stripped = "" - for c in string: - if ord(c) is not 26 and ord(c) is not 0: - stripped = stripped + c - return ''.join(stripped) +# ############################################################################### +# # `CodeServer` class. +# ############################################################################### +# class CodeServer(object): +# """A code server that executes user submitted test code, tests it and +# reports if the code was correct or not. +# """ +# def __init__(self, port, queue): +# self.port = port +# self.queue = queue +# msg = 'Code took more than %s seconds to run. You probably '\ +# 'have an infinite loop in your code.' % SERVER_TIMEOUT +# self.timeout_msg = msg + +# def run_python_code(self, answer, test_parameter, in_dir=None): +# """Tests given Python function (`answer`) with the `test_code` +# supplied. If the optional `in_dir` keyword argument is supplied +# it changes the directory to that directory (it does not change +# it back to the original when done). This function also timesout +# when the function takes more than SERVER_TIMEOUT seconds to run +# to prevent runaway code. +# Returns +# ------- + +# A tuple: (success, error message). + +# """ +# if in_dir is not None and isdir(in_dir): +# os.chdir(in_dir) + +# # Add a new signal handler for the execution of this code. +# old_handler = signal.signal(signal.SIGALRM, timeout_handler) +# signal.alarm(SERVER_TIMEOUT) + +# test_parameter = json.loads(test_parameter) +# success = False +# tb = None + +# test_code = "" +# for test_case in test_parameter: +# pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ +# else "" +# kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ +# if test_case.get('kw_args') else "" +# args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args +# tcode = "assert {0}({1}) == {2}" \ +# .format(test_case.get('func_name'), args, test_case.get('expected_answer')) +# test_code += tcode + "\n" +# try: +# submitted = compile(answer, '', mode='exec') +# g = {} +# exec submitted in g +# _tests = compile(test_code, '', mode='exec') +# exec _tests in g +# except TimeoutException: +# err = self.timeout_msg +# except AssertionError: +# type, value, tb = sys.exc_info() +# info = traceback.extract_tb(tb) +# fname, lineno, func, text = info[-1] +# text = str(test_code).splitlines()[lineno-1] +# err = "{0} {1} in: {2}".format(type.__name__, str(value), text) +# except: +# type, value = sys.exc_info()[:2] +# err = "Error: {0}".format(repr(value)) +# else: +# success = True +# err = 'Correct answer' +# finally: +# del tb +# # Set back any original signal handler. +# signal.signal(signal.SIGALRM, old_handler) + +# # Cancel the signal if any, see signal.alarm documentation. +# signal.alarm(0) + +# # Put us back into the server pool queue since we are free now. +# self.queue.put(self.port) + +# result = {'success': success, 'error': err} +# return result + +# def run_bash_code(self, answer, test_parameter, in_dir=None): +# """Tests given Bash code (`answer`) with the `test_code` supplied. + +# The testcode should typically contain two lines, the first is a path to +# the reference script we are to compare against. The second is a path +# to the arguments to be supplied to the reference and submitted script. +# The output of these will be compared for correctness. + +# If the path's start with a "/" then we assume they are absolute paths. +# If not, we assume they are relative paths w.r.t. the location of this +# code_server script. + +# If the optional `in_dir` keyword argument is supplied it changes the +# directory to that directory (it does not change it back to the original +# when done). + +# Returns +# ------- + +# A tuple: (success, error message). + +# """ +# if in_dir is not None and isdir(in_dir): +# os.chdir(in_dir) + +# def _set_exec(fname): +# os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR +# | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP +# | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) +# submit_f = open('submit.sh', 'w') +# submit_f.write(answer.lstrip()) +# submit_f.close() +# submit_path = abspath(submit_f.name) +# _set_exec(submit_path) + +# # ref path and path to arguments is a comma seperated string obtained +# #from ref_code_path field in TestCase Mode +# path_list = test_parameter.get('ref_code_path').split(',') +# ref_path = path_list[0].strip() if path_list[0] else "" +# test_case_path = path_list[1].strip() if path_list[1] else "" + +# if not ref_path.startswith('/'): +# ref_path = join(MY_DIR, ref_path) +# if not test_case_path.startswith('/'): +# test_case_path = join(MY_DIR, test_case_path) + +# # Add a new signal handler for the execution of this code. +# old_handler = signal.signal(signal.SIGALRM, timeout_handler) +# signal.alarm(SERVER_TIMEOUT) + +# # Do whatever testing needed. +# success = False +# try: +# success, err = self.check_bash_script(ref_path, submit_path, +# test_case_path) +# except TimeoutException: +# err = self.timeout_msg +# except: +# type, value = sys.exc_info()[:2] +# err = "Error: {0}".format(repr(value)) +# finally: +# # Set back any original signal handler. +# signal.signal(signal.SIGALRM, old_handler) + +# # Delete the created file. +# os.remove(submit_path) + +# # Cancel the signal if any, see signal.alarm documentation. +# signal.alarm(0) + +# # Put us back into the server pool queue since we are free now. +# self.queue.put(self.port) + +# result = {'success': success, 'error': err} +# return result + +# def _run_command(self, cmd_args, *args, **kw): +# """Run a command in a subprocess while blocking, the process is killed +# if it takes more than 2 seconds to run. Return the Popen object, the +# stdout and stderr. +# """ +# try: +# proc = subprocess.Popen(cmd_args, *args, **kw) +# stdout, stderr = proc.communicate() +# except TimeoutException: +# # Runaway code, so kill it. +# proc.kill() +# # Re-raise exception. +# raise +# return proc, stdout, stderr + +# def check_bash_script(self, ref_script_path, submit_script_path, +# test_case_path=None): +# """ Function validates student script using instructor script as +# reference. Test cases can optionally be provided. The first argument +# ref_script_path, is the path to instructor script, it is assumed to +# have executable permission. The second argument submit_script_path, is +# the path to the student script, it is assumed to have executable +# permission. The Third optional argument is the path to test the +# scripts. Each line in this file is a test case and each test case is +# passed to the script as standard arguments. + +# Returns +# -------- + +# returns (True, "Correct answer") : If the student script passes all +# test cases/have same output, when compared to the instructor script + +# returns (False, error_msg): If the student script fails a single +# test/have dissimilar output, when compared to the instructor script. + +# Returns (False, error_msg): If mandatory arguments are not files or if +# the required permissions are not given to the file(s). + +# """ +# if not ref_script_path: +# return False, "No Test Script path found" +# if not isfile(ref_script_path): +# return False, "No file at %s" % ref_script_path +# if not isfile(submit_script_path): +# return False, 'No file at %s' % submit_script_path +# if not os.access(ref_script_path, os.X_OK): +# return False, 'Script %s is not executable' % ref_script_path +# if not os.access(submit_script_path, os.X_OK): +# return False, 'Script %s is not executable' % submit_script_path + +# if test_case_path is None or "": +# ret = self._run_command(ref_script_path, stdin=None, +# stdout=subprocess.PIPE, +# stderr=subprocess.PIPE) +# proc, inst_stdout, inst_stderr = ret +# ret = self._run_command(submit_script_path, stdin=None, +# stdout=subprocess.PIPE, +# stderr=subprocess.PIPE) +# proc, stdnt_stdout, stdnt_stderr = ret +# if inst_stdout == stdnt_stdout: +# return True, 'Correct answer' +# else: +# err = "Error: expected %s, got %s" % (inst_stderr, +# stdnt_stderr) +# return False, err +# else: +# if not isfile(test_case_path): +# return False, "No test case at %s" % test_case_path +# if not os.access(ref_script_path, os.R_OK): +# return False, "Test script %s, not readable" % test_case_path +# valid_answer = True # We initially make it one, so that we can +# # stop once a test case fails +# loop_count = 0 # Loop count has to be greater than or +# # equal to one. +# # Useful for caching things like empty +# # test files,etc. +# test_cases = open(test_case_path).readlines() +# num_lines = len(test_cases) +# for test_case in test_cases: +# loop_count += 1 +# if valid_answer: +# args = [ref_script_path] + [x for x in test_case.split()] +# ret = self._run_command(args, stdin=None, +# stdout=subprocess.PIPE, +# stderr=subprocess.PIPE) +# proc, inst_stdout, inst_stderr = ret +# args = [submit_script_path]+[x for x in test_case.split()] +# ret = self._run_command(args, stdin=None, +# stdout=subprocess.PIPE, +# stderr=subprocess.PIPE) +# proc, stdnt_stdout, stdnt_stderr = ret +# valid_answer = inst_stdout == stdnt_stdout +# if valid_answer and (num_lines == loop_count): +# return True, "Correct answer" +# else: +# err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, +# stdnt_stdout+stdnt_stderr) +# return False, err + +# def run_c_code(self, answer, test_parameter, in_dir=None): +# """Tests given C code (`answer`) with the `test_code` supplied. + +# The testcode is a path to the reference code. +# The reference code will call the function submitted by the student. +# The reference code will check for the expected output. + +# If the path's start with a "/" then we assume they are absolute paths. +# If not, we assume they are relative paths w.r.t. the location of this +# code_server script. + +# If the optional `in_dir` keyword argument is supplied it changes the +# directory to that directory (it does not change it back to the original +# when done). + +# Returns +# ------- + +# A tuple: (success, error message). + +# """ +# if in_dir is not None and isdir(in_dir): +# os.chdir(in_dir) + +# # File extension must be .c +# submit_f = open('submit.c', 'w') +# submit_f.write(answer.lstrip()) +# submit_f.close() +# submit_path = abspath(submit_f.name) + +# ref_path = test_parameter.get('ref_code_path').strip() +# if not ref_path.startswith('/'): +# ref_path = join(MY_DIR, ref_path) + +# # Add a new signal handler for the execution of this code. +# old_handler = signal.signal(signal.SIGALRM, timeout_handler) +# signal.alarm(SERVER_TIMEOUT) + +# # Do whatever testing needed. +# success = False +# try: +# success, err = self._check_c_cpp_code(ref_path, submit_path) +# except TimeoutException: +# err = self.timeout_msg +# except: +# type, value = sys.exc_info()[:2] +# err = "Error: {0}".format(repr(value)) +# finally: +# # Set back any original signal handler. +# signal.signal(signal.SIGALRM, old_handler) + +# # Delete the created file. +# os.remove(submit_path) + +# # Cancel the signal if any, see signal.alarm documentation. +# signal.alarm(0) + +# # Put us back into the server pool queue since we are free now. +# self.queue.put(self.port) + +# result = {'success': success, 'error': err} +# return result + +# def _compile_command(self, cmd, *args, **kw): +# """Compiles C/C++/java code and returns errors if any. +# Run a command in a subprocess while blocking, the process is killed +# if it takes more than 2 seconds to run. Return the Popen object, the +# stderr. +# """ +# try: +# proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, +# stdout=subprocess.PIPE, +# stderr=subprocess.PIPE) +# out, err = proc_compile.communicate() +# except TimeoutException: +# # Runaway code, so kill it. +# proc_compile.kill() +# # Re-raise exception. +# raise +# return proc_compile, err + +# def _check_c_cpp_code(self, ref_code_path, submit_code_path): +# """ Function validates student code using instructor code as +# reference.The first argument ref_code_path, is the path to +# instructor code, it is assumed to have executable permission. +# The second argument submit_code_path, is the path to the student +# code, it is assumed to have executable permission. + +# Returns +# -------- + +# returns (True, "Correct answer") : If the student function returns +# expected output when called by reference code. + +# returns (False, error_msg): If the student function fails to return +# expected output when called by reference code. + +# Returns (False, error_msg): If mandatory arguments are not files or +# if the required permissions are not given to the file(s). + +# """ +# if not isfile(ref_code_path): +# return False, "No file at %s" % ref_code_path +# if not isfile(submit_code_path): +# return False, 'No file at %s' % submit_code_path + +# success = False +# output_path = os.getcwd() + '/output' +# compile_command = "g++ %s -c -o %s" % (submit_code_path, output_path) +# ret = self._compile_command(compile_command) +# proc, stdnt_stderr = ret + +# # Only if compilation is successful, the program is executed +# # And tested with testcases +# if stdnt_stderr == '': +# executable = os.getcwd() + '/executable' +# compile_main = "g++ %s %s -o %s" % (ref_code_path, output_path, +# executable) +# ret = self._compile_command(compile_main) +# proc, main_err = ret +# if main_err == '': +# args = [executable] +# ret = self._run_command(args, stdin=None, +# stdout=subprocess.PIPE, +# stderr=subprocess.PIPE) +# proc, stdout, stderr = ret +# if proc.returncode == 0: +# success, err = True, "Correct answer" +# else: +# err = stdout + "\n" + stderr +# os.remove(executable) +# else: +# err = "Error:" +# try: +# error_lines = main_err.splitlines() +# for e in error_lines: +# err = err + "\n" + e.split(":", 1)[1] +# except: +# err = err + "\n" + main_err +# os.remove(output_path) +# else: +# err = "Compilation Error:" +# try: +# error_lines = stdnt_stderr.splitlines() +# for e in error_lines: +# if ':' in e: +# err = err + "\n" + e.split(":", 1)[1] +# else: +# err = err + "\n" + e +# except: +# err = err + "\n" + stdnt_stderr +# return success, err + +# def run_cplus_code(self, answer, test_parameter, in_dir=None): +# """Tests given C++ code (`answer`) with the `test_code` supplied. + +# The testcode is a path to the reference code. +# The reference code will call the function submitted by the student. +# The reference code will check for the expected output. + +# If the path's start with a "/" then we assume they are absolute paths. +# If not, we assume they are relative paths w.r.t. the location of this +# code_server script. + +# If the optional `in_dir` keyword argument is supplied it changes the +# directory to that directory (it does not change it back to the original +# when done). + +# Returns +# ------- + +# A tuple: (success, error message). + +# """ +# if in_dir is not None and isdir(in_dir): +# os.chdir(in_dir) + +# # The file extension must be .cpp +# submit_f = open('submitstd.cpp', 'w') +# submit_f.write(answer.lstrip()) +# submit_f.close() +# submit_path = abspath(submit_f.name) + +# ref_path = test_parameter.get('ref_code_path').strip() +# if not ref_path.startswith('/'): +# ref_path = join(MY_DIR, ref_path) + +# # Add a new signal handler for the execution of this code. +# old_handler = signal.signal(signal.SIGALRM, timeout_handler) +# signal.alarm(SERVER_TIMEOUT) + +# # Do whatever testing needed. +# success = False +# try: +# success, err = self._check_c_cpp_code(ref_path, submit_path) +# except TimeoutException: +# err = self.timeout_msg +# except: +# type, value = sys.exc_info()[:2] +# err = "Error: {0}".format(repr(value)) +# finally: +# # Set back any original signal handler. +# signal.signal(signal.SIGALRM, old_handler) + +# # Delete the created file. +# os.remove(submit_path) + +# # Cancel the signal if any, see signal.alarm documentation. +# signal.alarm(0) + +# # Put us back into the server pool queue since we are free now. +# self.queue.put(self.port) + +# result = {'success': success, 'error': err} +# return result + +# def run_java_code(self, answer, test_parameter, in_dir=None): +# """Tests given java code (`answer`) with the `test_code` supplied. + +# The testcode is a path to the reference code. +# The reference code will call the function submitted by the student. +# The reference code will check for the expected output. + +# If the path's start with a "/" then we assume they are absolute paths. +# If not, we assume they are relative paths w.r.t. the location of this +# code_server script. + +# If the optional `in_dir` keyword argument is supplied it changes the +# directory to that directory (it does not change it back to the original +# when done). + +# Returns +# ------- + +# A tuple: (success, error message). + +# """ +# if in_dir is not None and isdir(in_dir): +# os.chdir(in_dir) + +# # The file extension must be .java +# # The class name and file name must be same in java +# submit_f = open('Test.java', 'w') +# submit_f.write(answer.lstrip()) +# submit_f.close() +# submit_path = abspath(submit_f.name) + +# ref_path = test_parameter.get('ref_code_path').strip() +# if not ref_path.startswith('/'): +# ref_path = join(MY_DIR, ref_path) + +# # Add a new signal handler for the execution of this code. +# old_handler = signal.signal(signal.SIGALRM, timeout_handler) +# signal.alarm(SERVER_TIMEOUT) + +# # Do whatever testing needed. +# success = False +# try: +# success, err = self._check_java_code(ref_path, submit_path) +# except TimeoutException: +# err = self.timeout_msg +# except: +# type, value = sys.exc_info()[:2] +# err = "Error: {0}".format(repr(value)) +# finally: +# # Set back any original signal handler. +# signal.signal(signal.SIGALRM, old_handler) + +# # Delete the created file. +# os.remove(submit_path) + +# # Cancel the signal if any, see signal.alarm documentation. +# signal.alarm(0) + +# # Put us back into the server pool queue since we are free now. +# self.queue.put(self.port) + +# return success, err + +# def _check_java_code(self, ref_code_path, submit_code_path): +# """ Function validates student code using instructor code as +# reference.The first argument ref_code_path, is the path to +# instructor code, it is assumed to have executable permission. +# The second argument submit_code_path, is the path to the student +# code, it is assumed to have executable permission. + +# Returns +# -------- + +# returns (True, "Correct answer") : If the student function returns +# expected output when called by reference code. + +# returns (False, error_msg): If the student function fails to return +# expected output when called by reference code. + +# Returns (False, error_msg): If mandatory arguments are not files or +# if the required permissions are not given to the file(s). + +# """ +# if not isfile(ref_code_path): +# return False, "No file at %s" % ref_code_path +# if not isfile(submit_code_path): +# return False, 'No file at %s' % submit_code_path + +# success = False +# compile_command = "javac %s" % (submit_code_path) +# ret = self._compile_command(compile_command) +# proc, stdnt_stderr = ret +# stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) + +# # Only if compilation is successful, the program is executed +# # And tested with testcases +# if stdnt_stderr == '': +# student_directory = os.getcwd() + '/' +# student_file_name = "Test" +# compile_main = "javac %s -classpath %s -d %s" % (ref_code_path, +# student_directory, +# student_directory) +# ret = self._compile_command(compile_main) +# proc, main_err = ret +# main_err = self._remove_null_substitute_char(main_err) + +# if main_err == '': +# main_file_name = (ref_code_path.split('/')[-1]).split('.')[0] +# run_command = "java -cp %s %s" % (student_directory, +# main_file_name) +# ret = self._run_command(run_command, +# stdin=None, +# shell=True, +# stdout=subprocess.PIPE, +# stderr=subprocess.PIPE) +# proc, stdout, stderr = ret +# if proc.returncode == 0: +# success, err = True, "Correct answer" +# else: +# err = stdout + "\n" + stderr +# success = False +# os.remove("%s%s.class" % (student_directory, main_file_name)) +# else: +# err = "Error:\n" +# try: +# error_lines = main_err.splitlines() +# for e in error_lines: +# if ':' in e: +# err = err + "\n" + e.split(":", 1)[1] +# else: +# err = err + "\n" + e +# except: +# err = err + "\n" + main_err +# os.remove("%s%s.class" % (student_directory, student_file_name)) +# else: +# err = "Compilation Error:\n" +# try: +# error_lines = stdnt_stderr.splitlines() +# for e in error_lines: +# if ':' in e: +# err = err + "\n" + e.split(":", 1)[1] +# else: +# err = err + "\n" + e +# except: +# err = err + "\n" + stdnt_stderr +# result = {'success': success, 'error': err} +# return result + +# def _remove_null_substitute_char(self, string): +# """Returns a string without any null and substitute characters""" +# stripped = "" +# for c in string: +# if ord(c) is not 26 and ord(c) is not 0: +# stripped = stripped + c +# return ''.join(stripped) - def run_scilab_code(self, answer, test_parameter, in_dir=None): - """Tests given Scilab function (`answer`) with the `test_code` - supplied. If the optional `in_dir` keyword argument is supplied - it changes the directory to that directory (it does not change - it back to the original when done). This function also timesout - when the function takes more than SERVER_TIMEOUT seconds to run - to prevent runaway code. - - The testcode is a path to the reference code. - The reference code will call the function submitted by the student. - The reference code will check for the expected output. - - If the path's start with a "/" then we assume they are absolute paths. - If not, we assume they are relative paths w.r.t. the location of this - code_server script. - - Returns - ------- - - A tuple: (success, error message). - - """ - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) +# def run_scilab_code(self, answer, test_parameter, in_dir=None): +# """Tests given Scilab function (`answer`) with the `test_code` +# supplied. If the optional `in_dir` keyword argument is supplied +# it changes the directory to that directory (it does not change +# it back to the original when done). This function also timesout +# when the function takes more than SERVER_TIMEOUT seconds to run +# to prevent runaway code. + +# The testcode is a path to the reference code. +# The reference code will call the function submitted by the student. +# The reference code will check for the expected output. + +# If the path's start with a "/" then we assume they are absolute paths. +# If not, we assume they are relative paths w.r.t. the location of this +# code_server script. + +# Returns +# ------- + +# A tuple: (success, error message). + +# """ +# if in_dir is not None and isdir(in_dir): +# os.chdir(in_dir) - # Removes all the commands that terminates scilab - answer,i = self._remove_scilab_exit(answer.lstrip()) - - # Throw message if there are commmands that terminates scilab - add_err="" - if i > 0: - add_err = "Please do not use exit, quit and abort commands in your\ - code.\n Otherwise your code will not be evaluated\ - correctly.\n" - - # The file extension should be .sci - submit_f = open('function.sci','w') - submit_f.write(answer) - submit_f.close() - submit_path = abspath(submit_f.name) - - ref_path = test_parameter.get('ref_code_path').strip() - if not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - - # Add a new signal handler for the execution of this code. - old_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - - # Do whatever testing needed. - success = False - try: - cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) - cmd += ' | timeout 8 scilab-cli -nb' - ret = self._run_command(cmd, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - - # Get only the error. - stderr = self._get_error(stdout) - if stderr is None: - # Clean output - stdout = self._strip_output(stdout) - if proc.returncode == 5: - success, err = True, "Correct answer" - else: - err = add_err + stdout - else: - err = add_err + stderr - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - signal.signal(signal.SIGALRM, old_handler) - - # Delete the created file. - os.remove(submit_path) - - # Cancel the signal if any, see signal.alarm documentation. - signal.alarm(0) - - # Put us back into the server pool queue since we are free now. - self.queue.put(self.port) - - result = {'success': success, 'error': err} - return result - - def _remove_scilab_exit(self, string): - """ - Removes exit, quit and abort from the scilab code - """ - new_string = "" - i=0 - for line in string.splitlines(): - new_line = re.sub(r"exit.*$","",line) - new_line = re.sub(r"quit.*$","",new_line) - new_line = re.sub(r"abort.*$","",new_line) - if line != new_line: - i=i+1 - new_string = new_string +'\n'+ new_line - return new_string, i +# # Removes all the commands that terminates scilab +# answer,i = self._remove_scilab_exit(answer.lstrip()) + +# # Throw message if there are commmands that terminates scilab +# add_err="" +# if i > 0: +# add_err = "Please do not use exit, quit and abort commands in your\ +# code.\n Otherwise your code will not be evaluated\ +# correctly.\n" + +# # The file extension should be .sci +# submit_f = open('function.sci','w') +# submit_f.write(answer) +# submit_f.close() +# submit_path = abspath(submit_f.name) + +# ref_path = test_parameter.get('ref_code_path').strip() +# if not ref_path.startswith('/'): +# ref_path = join(MY_DIR, ref_path) + +# # Add a new signal handler for the execution of this code. +# old_handler = signal.signal(signal.SIGALRM, timeout_handler) +# signal.alarm(SERVER_TIMEOUT) + +# # Do whatever testing needed. +# success = False +# try: +# cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) +# cmd += ' | timeout 8 scilab-cli -nb' +# ret = self._run_command(cmd, +# shell=True, +# stdout=subprocess.PIPE, +# stderr=subprocess.PIPE) +# proc, stdout, stderr = ret + +# # Get only the error. +# stderr = self._get_error(stdout) +# if stderr is None: +# # Clean output +# stdout = self._strip_output(stdout) +# if proc.returncode == 5: +# success, err = True, "Correct answer" +# else: +# err = add_err + stdout +# else: +# err = add_err + stderr +# except TimeoutException: +# err = self.timeout_msg +# except: +# type, value = sys.exc_info()[:2] +# err = "Error: {0}".format(repr(value)) +# finally: +# # Set back any original signal handler. +# signal.signal(signal.SIGALRM, old_handler) + +# # Delete the created file. +# os.remove(submit_path) + +# # Cancel the signal if any, see signal.alarm documentation. +# signal.alarm(0) + +# # Put us back into the server pool queue since we are free now. +# self.queue.put(self.port) + +# result = {'success': success, 'error': err} +# return result + +# def _remove_scilab_exit(self, string): +# """ +# Removes exit, quit and abort from the scilab code +# """ +# new_string = "" +# i=0 +# for line in string.splitlines(): +# new_line = re.sub(r"exit.*$","",line) +# new_line = re.sub(r"quit.*$","",new_line) +# new_line = re.sub(r"abort.*$","",new_line) +# if line != new_line: +# i=i+1 +# new_string = new_string +'\n'+ new_line +# return new_string, i def _get_error(self, string): """ diff --git a/testapp/exam/models.py b/testapp/exam/models.py index ea79ad2..dd0c806 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -1,4 +1,5 @@ import datetime +import json from random import sample, shuffle from django.db import models from django.contrib.auth.models import User @@ -82,8 +83,9 @@ class Question(models.Model): # Tags for the Question. tags = TaggableManager() - def consolidate_test_cases(self, test): - test_case_parameter= [] + def consolidate_answer_data(self, test, user_answer): + test_case_parameter = [] + info_parameter = {} for i in test: kw_args_dict = {} @@ -94,7 +96,6 @@ class Question(models.Model): parameter_dict['func_name'] = i.func_name parameter_dict['expected_answer'] = i.expected_answer parameter_dict['ref_code_path'] = i.ref_code_path - parameter_dict['language'] = self.language if i.kw_args: for args in i.kw_args.split(","): @@ -109,7 +110,12 @@ class Question(models.Model): parameter_dict['pos_args'] = pos_args_list test_case_parameter.append(parameter_dict) - return test_case_parameter + info_parameter['language'] = self.language + info_parameter['id'] = self.id + info_parameter['user_answer'] = user_answer + info_parameter['test_parameter'] = test_case_parameter + + return json.dumps(info_parameter) def __unicode__(self): return self.summary diff --git a/testapp/exam/templates/exam/add_question.html b/testapp/exam/templates/exam/add_question.html index d989b81..e5e2574 100644 --- a/testapp/exam/templates/exam/add_question.html +++ b/testapp/exam/templates/exam/add_question.html @@ -40,20 +40,6 @@ {% endfor %} {% endif %} - -
diff --git a/testapp/exam/views.py b/testapp/exam/views.py index 3d22d55..8aef5ab 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -14,6 +14,7 @@ from django.db.models import Sum from django.views.decorators.csrf import csrf_exempt from taggit.models import Tag from itertools import chain +import json # Local imports. from testapp.exam.models import Quiz, Question, QuestionPaper, QuestionSet from testapp.exam.models import Profile, Answer, AnswerPaper, User, TestCase @@ -909,7 +910,6 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): q_paper = QuestionPaper.objects.get(id=questionpaper_id) paper = AnswerPaper.objects.get(user=request.user, question_paper=q_paper) test = TestCase.objects.filter(question=question) - test_parameter = question.consolidate_test_cases(test) snippet_code = request.POST.get('snippet') user_code = request.POST.get('answer') @@ -940,7 +940,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): user_answer = 'ASSIGNMENT UPLOADED' else: user_code = request.POST.get('answer') - user_answer = user_code #snippet_code + "\n" + user_code + user_answer = snippet_code + "\n" + user_code if snippet_code else user_code new_answer = Answer(question=question, answer=user_answer, correct=False) @@ -951,14 +951,16 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # questions, we obtain the results via XML-RPC with the code executed # safely in a separate process (the code_server.py) running as nobody. if not question.type == 'upload': - correct, result = validate_answer(user, user_answer, question, test_parameter) + if question.type == 'code': + info_parameter = question.consolidate_answer_data(test, user_answer) + correct, result = validate_answer(user, user_answer, question, info_parameter) if correct: new_answer.correct = correct new_answer.marks = question.points - new_answer.error = err_msg + new_answer.error = result.get('error') success_msg = True else: - new_answer.error = err_msg + new_answer.error = result.get('error') new_answer.save() time_left = paper.time_left() @@ -996,7 +998,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): questionpaper_id, success_msg) -def validate_answer(user, user_answer, question, test_parameter): +def validate_answer(user, user_answer, question, info_parameter=None): """ Checks whether the answer submitted by the user is right or wrong. If right then returns correct = True, success and @@ -1011,17 +1013,18 @@ def validate_answer(user, user_answer, question, test_parameter): if user_answer is not None: if question.type == 'mcq': - if user_answer.strip() == question.test.strip(): + if user_answer.strip() == question.test.strip(): ####add question.answer/question.solution instead of test correct = True message = 'Correct answer' elif question.type == 'mcc': - answers = set(question.test.splitlines()) + answers = set(question.test.splitlines()) ####add question.answer/question.solution instead of test if set(user_answer) == answers: correct = True message = 'Correct answer' elif question.type == 'code': user_dir = get_user_dir(user) - result = code_server.run_code(user_answer, test_parameter, user_dir, question.language) + json_result = code_server.run_code(info_parameter, user_dir) + result = json.loads(json_result) if result.get('success'): correct = True diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index 692fbb5..bbd6110 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -29,7 +29,7 @@ class CodeServerProxy(object): "scilab": "run_scilab_code", } - def run_code(self, answer, test_parameter, user_dir, language): + def run_code(self, info_parameter, user_dir): """Tests given code (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change it back to the original when @@ -38,26 +38,28 @@ class CodeServerProxy(object): Parameters ---------- - answer : str - The user's answer for the question. + info_parameter contains; + user_answer : str + The user's answer for the question. test_code : str The test code to check the user code with. - user_dir : str (directory) - The directory to run the tests inside. language : str The programming language to use. + user_dir : str (directory) + The directory to run the tests inside. + + Returns ------- - A tuple: (success, error message). + A json string of a dict: {success: success, err: error message}. """ - method_name = self.methods[language] + # method_name = self.methods[language] try: server = self._get_server() - method = getattr(server, method_name) - result = method(answer, test_parameter, user_dir) #### + result = server.checker(info_parameter, user_dir) except ConnectionError: - result = {'success': False, 'error': 'Unable to connect to any code servers!'} + result = json.dumps({'success': False, 'error': 'Unable to connect to any code servers!'}) return result def _get_server(self): -- cgit From a88e59fa419d7ab31aa430e7424ff0a25169713f Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Wed, 18 Mar 2015 11:26:38 +0530 Subject: Code server code cleanup and code commonification - Commonify C, C++, Java and Scilab code evaluation --- testapp/exam/code_server.py | 1246 ++++++++++++++----------------------------- 1 file changed, 389 insertions(+), 857 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index fa5d916..2259ce8 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -85,21 +85,34 @@ class TestCode(object): self.in_dir = in_dir def run_code(self): - self._change_dir(self.in_dir) + """Tests given code (`answer`) with the `test_code` supplied. - # Create test cases - test_code = self.create_test_case() - # Evaluate, run code and obtain result - result = self.evaluate_code(test_code) + The ref_code_path is a path to the reference code. + The reference code will call the function submitted by the student. + The reference code will check for the expected output. - return result + If the path's start with a "/" then we assume they are absolute paths. + If not, we assume they are relative paths w.r.t. the location of this + code_server script. + + If the optional `in_dir` keyword argument is supplied it changes the + directory to that directory (it does not change it back to the original + when done). + + Returns + ------- - def evaluate_code(self, test_code): + A tuple: (success, error message). + """ success = False prev_handler = None + self._change_dir(self.in_dir) + if self.language == "python": tb = None + test_code = self.create_test_case() + # Add a new signal handler for the execution of this code. prev_handler = create_delete_signal_handler("new") try: @@ -130,26 +143,383 @@ class TestCode(object): # Cancel the signal create_delete_signal_handler("delete") - if self.language == "c++": - pass + else: + user_answer_file = {'C': 'submit.c', 'java': 'Test.java', 'scilab': 'function.sci', + 'C++': 'submitstd.cpp', 'bash': 'submit.sh'} + + # File extension depending on the question language + submit_f = open(user_answer_file.get(self.language), 'w') + submit_f.write(self.user_answer.lstrip()) + submit_f.close() + submit_path = abspath(submit_f.name) + if self.language == "bash": + self._set_exec(submit_path) + + path_list = self.ref_code_path.split(',') + ref_path = path_list[0].strip() if path_list[0] else "" + test_case_path = path_list[1].strip() if path_list[1] else "" + if ref_path and not ref_path.startswith('/'): + ref_path = join(MY_DIR, ref_path) + if test_case_path and not test_case_path.startswith('/'): + test_case_path = join(MY_DIR, test_case_path) + + if self.language in ["C++", "C", "java"]: + # Add a new signal handler for the execution of this code. + prev_handler = create_delete_signal_handler("new") + + # Do whatever testing needed. + try: + success, err = self._check_code(ref_path, submit_path) + except TimeoutException: + err = self.timeout_msg + except: + type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + finally: + # # Set back any original signal handler. + create_delete_signal_handler("original", prev_handler) + + elif self.language == "scilab": + # Add a new signal handler for the execution of this code. + prev_handler = create_delete_signal_handler("new") + + # Do whatever testing needed. + try: + cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) + cmd += ' | timeout 8 scilab-cli -nb' + ret = self._run_command(cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + + # Get only the error. + stderr = self._get_error(stdout) + if stderr is None: + # Clean output + stdout = self._strip_output(stdout) + if proc.returncode == 5: + success, err = True, "Correct answer" + else: + err = add_err + stdout + else: + err = add_err + stderr + except TimeoutException: + err = self.timeout_msg + except: + type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + finally: + # Set back any original signal handler. + create_delete_signal_handler("original", prev_handler) + + elif self.language == "bash": + # Add a new signal handler for the execution of this code. + prev_handler = create_delete_signal_handler("new") + + try: + success, err = self.check_bash_script(ref_path, submit_path, + test_case_path) + except TimeoutException: + err = self.timeout_msg + except: + type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + finally: + # Set back any original signal handler. + create_delete_signal_handler("original", prev_handler) + + # Delete the created file. + os.remove(submit_path) - if self.language == "c": - pass + # Cancel the signal + create_delete_signal_handler("delete") - if self.language == "java": - pass + result = {'success': success, 'error': err} + return result - if self.language == "scilab": - pass + def _check_code(self, ref_code_path, submit_code_path): + """ Function validates student code using instructor code as + reference.The first argument ref_code_path, is the path to + instructor code, it is assumed to have executable permission. + The second argument submit_code_path, is the path to the student + code, it is assumed to have executable permission. + + Returns + -------- + + returns (True, "Correct answer") : If the student function returns + expected output when called by reference code. + + returns (False, error_msg): If the student function fails to return + expected output when called by reference code. + + Returns (False, error_msg): If mandatory arguments are not files or + if the required permissions are not given to the file(s). + + """ + + language_dependent_path = { + 'c_user_output_path': os.getcwd() + '/output', + 'c_ref_output_path': os.getcwd() + '/executable', + 'java_student_directory': os.getcwd() + '/', + # 'java_student_file_name': 'Test', + 'java_ref_file_name': (ref_code_path.split('/')[-1]).split('.')[0], + } + + language_dependent_var = { + 'C': {'compile_command': 'g++ {0} -c -o {1}'.format(submit_code_path, + language_dependent_path.get('c_user_output_path')), + 'compile_main': 'g++ {0} {1} -o {2}'.format(ref_code_path, + language_dependent_path.get('c_user_output_path'), + language_dependent_path.get('c_ref_output_path')), + 'run_command_args': [language_dependent_path.get('c_ref_output_path')], + 'remove_user_output': language_dependent_path.get('c_user_output_path'), + 'remove_ref_output': language_dependent_path.get('c_ref_output_path') + }, + 'java':{'compile_command': 'javac {0}'.format(submit_code_path), + 'compile_main': 'javac {0} -classpath {1} -d {2}'.format(ref_code_path, + language_dependent_path.get('java_student_directory'), + language_dependent_path.get('java_student_directory')), + 'run_command_args': "java -cp {0} {1}".format( + language_dependent_path.get('java_student_directory'), + language_dependent_path.get('java_ref_file_name')), + 'remove_user_output': "%s%s.class".format( + language_dependent_path.get('java_student_directory'), + 'Test'), + 'remove_ref_output': "%s%s.class".format( + language_dependent_path.get('java_student_directory'), + language_dependent_path.get('java_ref_file_name')), + } + } + + if not isfile(ref_code_path): + return False, "No file at %s or Incorrect path" % ref_code_path + if not isfile(submit_code_path): + return False, 'No file at %s or Incorrect path' % submit_code_path + + success = False + # output_path = os.getcwd() + '/output' + compile_command = language_dependent_var.get(self.language).get('compile_command') # "g++ %s -c -o %s" % (submit_code_path, output_path) + ret = self._compile_command(compile_command) + proc, stdnt_stderr = ret + if self.language == "java": + stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) + + # Only if compilation is successful, the program is executed + # And tested with testcases + if stdnt_stderr == '': + compile_main = language_dependent_var.get(self.language).get('compile_main') # "g++ %s %s -o %s" % (ref_code_path, output_path, executable) + ret = self._compile_command(compile_main) + proc, main_err = ret + if self.language == "java": + main_err = self._remove_null_substitute_char(main_err) + + if main_err == '': + run_command_args = language_dependent_var.get(self.language).get('run_command_args') + ret = self._run_command(run_command_args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + if proc.returncode == 0: + success, err = True, "Correct answer" + else: + err = stdout + "\n" + stderr + os.remove(language_dependent_var.get(self.language).get('remove_ref_output')) # os.remove(executable) + else: + err = "Error:" + try: + error_lines = main_err.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + main_err + os.remove(language_dependent_var.get(self.language).get('remove_user_output')) # os.remove(output_path) + else: + err = "Compilation Error:" + try: + error_lines = stdnt_stderr.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + stdnt_stderr result = {'success': success, 'error': err} return result - def compile_code(self): - pass + def check_bash_script(self, ref_path, submit_path, + test_case_path=None): + """ Function validates student script using instructor script as + reference. Test cases can optionally be provided. The first argument + ref_path, is the path to instructor script, it is assumed to + have executable permission. The second argument submit_path, is + the path to the student script, it is assumed to have executable + permission. The Third optional argument is the path to test the + scripts. Each line in this file is a test case and each test case is + passed to the script as standard arguments. + + Returns + -------- + + returns (True, "Correct answer") : If the student script passes all + test cases/have same output, when compared to the instructor script + + returns (False, error_msg): If the student script fails a single + test/have dissimilar output, when compared to the instructor script. + + Returns (False, error_msg): If mandatory arguments are not files or if + the required permissions are not given to the file(s). + + """ + if not isfile(ref_path): + return False, "No file at %s or Incorrect path" % ref_path + if not isfile(submit_path): + return False, "No file at %s or Incorrect path" % submit_path + if not os.access(ref_path, os.X_OK): + return False, "Script %s is not executable" % ref_path + if not os.access(submit_path, os.X_OK): + return False, "Script %s is not executable" % submit_path + + if test_case_path is None or "": + ret = self._run_command(ref_path, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, inst_stdout, inst_stderr = ret + ret = self._run_command(submit_path, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdnt_stdout, stdnt_stderr = ret + if inst_stdout == stdnt_stdout: + return True, "Correct answer" + else: + err = "Error: expected %s, got %s" % (inst_stderr, + stdnt_stderr) + return False, err + else: + if not isfile(test_case_path): + return False, "No test case at %s" % test_case_path + if not os.access(ref_path, os.R_OK): + return False, "Test script %s, not readable" % test_case_path + valid_answer = True # We initially make it one, so that we can + # stop once a test case fails + loop_count = 0 # Loop count has to be greater than or + # equal to one. + # Useful for caching things like empty + # test files,etc. + test_cases = open(test_case_path).readlines() + num_lines = len(test_cases) + for test_case in test_cases: + loop_count += 1 + if valid_answer: + args = [ref_path] + [x for x in test_case.split()] + ret = self._run_command(args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, inst_stdout, inst_stderr = ret + args = [submit_path]+[x for x in test_case.split()] + ret = self._run_command(args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdnt_stdout, stdnt_stderr = ret + valid_answer = inst_stdout == stdnt_stdout + if valid_answer and (num_lines == loop_count): + return True, "Correct answer" + else: + err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, + stdnt_stdout+stdnt_stderr) + return False, err + + def _run_command(self, cmd_args, *args, **kw): + """Run a command in a subprocess while blocking, the process is killed + if it takes more than 2 seconds to run. Return the Popen object, the + stdout and stderr. + """ + try: + proc = subprocess.Popen(cmd_args, *args, **kw) + stdout, stderr = proc.communicate() + except TimeoutException: + # Runaway code, so kill it. + proc.kill() + # Re-raise exception. + raise + return proc, stdout, stderr + + def _compile_command(self, cmd, *args, **kw): + """Compiles C/C++/java code and returns errors if any. + Run a command in a subprocess while blocking, the process is killed + if it takes more than 2 seconds to run. Return the Popen object, the + stderr. + """ + try: + proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc_compile.communicate() + except TimeoutException: + # Runaway code, so kill it. + proc_compile.kill() + # Re-raise exception. + raise + return proc_compile, err + + def _remove_null_substitute_char(self, string): + """Returns a string without any null and substitute characters""" + stripped = "" + for c in string: + if ord(c) is not 26 and ord(c) is not 0: + stripped = stripped + c + return ''.join(stripped) + + def _remove_scilab_exit(self, string): + """ + Removes exit, quit and abort from the scilab code + """ + new_string = "" + i=0 + for line in string.splitlines(): + new_line = re.sub(r"exit.*$","",line) + new_line = re.sub(r"quit.*$","",new_line) + new_line = re.sub(r"abort.*$","",new_line) + if line != new_line: + i=i+1 + new_string = new_string +'\n'+ new_line + return new_string, i + + def _get_error(self, string): + """ + Fetches only the error from the string. + Returns None if no error. + """ + obj = re.search("!.+\n.+",string); + if obj: + return obj.group() + return None + + def _strip_output(self, out): + """ + Cleans whitespace from the output + """ + strip_out = "Message" + for l in out.split('\n'): + if l.strip(): + strip_out = strip_out+"\n"+l.strip() + return strip_out + + def _set_exec(self, fname): + os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR + | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP + | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) def create_test_case(self): - # Create assert based test cases in python + """ + Create assert based test cases in python + """ if self.language == "python": test_code = "" for test_case in self.test_parameter: @@ -163,18 +533,6 @@ class TestCode(object): test_code += tcode + "\n" return test_code - if self.language == "c++": - pass - - if self.language == "c": - pass - - if self.language == "java": - pass - - if self.language == "scilab": - pass - def _change_dir(self, in_dir): if in_dir is not None and isdir(in_dir): os.chdir(in_dir) @@ -198,839 +556,13 @@ class CodeServer(object): """Calls the TestCode Class to test the current code""" tc = TestCode(info_parameter, in_dir) result = tc.run_code() - # Put us back into the server pool queue since we are free now. self.queue.put(self.port) return json.dumps(result) - def run_python_code(self, answer, test_parameter, in_dir=None): - """Tests given Python function (`answer`) with the `test_code` - supplied. If the optional `in_dir` keyword argument is supplied - it changes the directory to that directory (it does not change - it back to the original when done). This function also timesout - when the function takes more than SERVER_TIMEOUT seconds to run - to prevent runaway code. - Returns - ------- - - A tuple: (success, error message). - - """ - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) - - # Add a new signal handler for the execution of this code. - old_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - - test_parameter = json.loads(test_parameter) - success = False - tb = None - - test_code = "" - for test_case in test_parameter: - pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ - else "" - kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ - if test_case.get('kw_args') else "" - args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args - tcode = "assert {0}({1}) == {2}" \ - .format(test_case.get('func_name'), args, test_case.get('expected_answer')) - test_code += tcode + "\n" - try: - submitted = compile(answer, '', mode='exec') - g = {} - exec submitted in g - _tests = compile(test_code, '', mode='exec') - exec _tests in g - except TimeoutException: - err = self.timeout_msg - except AssertionError: - type, value, tb = sys.exc_info() - info = traceback.extract_tb(tb) - fname, lineno, func, text = info[-1] - text = str(test_code).splitlines()[lineno-1] - err = "{0} {1} in: {2}".format(type.__name__, str(value), text) - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - else: - success = True - err = 'Correct answer' - finally: - del tb - # Set back any original signal handler. - signal.signal(signal.SIGALRM, old_handler) - - # Cancel the signal if any, see signal.alarm documentation. - signal.alarm(0) - - # Put us back into the server pool queue since we are free now. - self.queue.put(self.port) - - result = {'success': success, 'error': err} - return result - -# ############################################################################### -# # `CodeServer` class. -# ############################################################################### -# class CodeServer(object): -# """A code server that executes user submitted test code, tests it and -# reports if the code was correct or not. -# """ -# def __init__(self, port, queue): -# self.port = port -# self.queue = queue -# msg = 'Code took more than %s seconds to run. You probably '\ -# 'have an infinite loop in your code.' % SERVER_TIMEOUT -# self.timeout_msg = msg - -# def run_python_code(self, answer, test_parameter, in_dir=None): -# """Tests given Python function (`answer`) with the `test_code` -# supplied. If the optional `in_dir` keyword argument is supplied -# it changes the directory to that directory (it does not change -# it back to the original when done). This function also timesout -# when the function takes more than SERVER_TIMEOUT seconds to run -# to prevent runaway code. -# Returns -# ------- - -# A tuple: (success, error message). - -# """ -# if in_dir is not None and isdir(in_dir): -# os.chdir(in_dir) - -# # Add a new signal handler for the execution of this code. -# old_handler = signal.signal(signal.SIGALRM, timeout_handler) -# signal.alarm(SERVER_TIMEOUT) - -# test_parameter = json.loads(test_parameter) -# success = False -# tb = None - -# test_code = "" -# for test_case in test_parameter: -# pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ -# else "" -# kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ -# if test_case.get('kw_args') else "" -# args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args -# tcode = "assert {0}({1}) == {2}" \ -# .format(test_case.get('func_name'), args, test_case.get('expected_answer')) -# test_code += tcode + "\n" -# try: -# submitted = compile(answer, '', mode='exec') -# g = {} -# exec submitted in g -# _tests = compile(test_code, '', mode='exec') -# exec _tests in g -# except TimeoutException: -# err = self.timeout_msg -# except AssertionError: -# type, value, tb = sys.exc_info() -# info = traceback.extract_tb(tb) -# fname, lineno, func, text = info[-1] -# text = str(test_code).splitlines()[lineno-1] -# err = "{0} {1} in: {2}".format(type.__name__, str(value), text) -# except: -# type, value = sys.exc_info()[:2] -# err = "Error: {0}".format(repr(value)) -# else: -# success = True -# err = 'Correct answer' -# finally: -# del tb -# # Set back any original signal handler. -# signal.signal(signal.SIGALRM, old_handler) - -# # Cancel the signal if any, see signal.alarm documentation. -# signal.alarm(0) - -# # Put us back into the server pool queue since we are free now. -# self.queue.put(self.port) - -# result = {'success': success, 'error': err} -# return result - -# def run_bash_code(self, answer, test_parameter, in_dir=None): -# """Tests given Bash code (`answer`) with the `test_code` supplied. - -# The testcode should typically contain two lines, the first is a path to -# the reference script we are to compare against. The second is a path -# to the arguments to be supplied to the reference and submitted script. -# The output of these will be compared for correctness. - -# If the path's start with a "/" then we assume they are absolute paths. -# If not, we assume they are relative paths w.r.t. the location of this -# code_server script. - -# If the optional `in_dir` keyword argument is supplied it changes the -# directory to that directory (it does not change it back to the original -# when done). - -# Returns -# ------- - -# A tuple: (success, error message). - -# """ -# if in_dir is not None and isdir(in_dir): -# os.chdir(in_dir) - -# def _set_exec(fname): -# os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR -# | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP -# | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) -# submit_f = open('submit.sh', 'w') -# submit_f.write(answer.lstrip()) -# submit_f.close() -# submit_path = abspath(submit_f.name) -# _set_exec(submit_path) - -# # ref path and path to arguments is a comma seperated string obtained -# #from ref_code_path field in TestCase Mode -# path_list = test_parameter.get('ref_code_path').split(',') -# ref_path = path_list[0].strip() if path_list[0] else "" -# test_case_path = path_list[1].strip() if path_list[1] else "" - -# if not ref_path.startswith('/'): -# ref_path = join(MY_DIR, ref_path) -# if not test_case_path.startswith('/'): -# test_case_path = join(MY_DIR, test_case_path) - -# # Add a new signal handler for the execution of this code. -# old_handler = signal.signal(signal.SIGALRM, timeout_handler) -# signal.alarm(SERVER_TIMEOUT) - -# # Do whatever testing needed. -# success = False -# try: -# success, err = self.check_bash_script(ref_path, submit_path, -# test_case_path) -# except TimeoutException: -# err = self.timeout_msg -# except: -# type, value = sys.exc_info()[:2] -# err = "Error: {0}".format(repr(value)) -# finally: -# # Set back any original signal handler. -# signal.signal(signal.SIGALRM, old_handler) - -# # Delete the created file. -# os.remove(submit_path) - -# # Cancel the signal if any, see signal.alarm documentation. -# signal.alarm(0) - -# # Put us back into the server pool queue since we are free now. -# self.queue.put(self.port) - -# result = {'success': success, 'error': err} -# return result - -# def _run_command(self, cmd_args, *args, **kw): -# """Run a command in a subprocess while blocking, the process is killed -# if it takes more than 2 seconds to run. Return the Popen object, the -# stdout and stderr. -# """ -# try: -# proc = subprocess.Popen(cmd_args, *args, **kw) -# stdout, stderr = proc.communicate() -# except TimeoutException: -# # Runaway code, so kill it. -# proc.kill() -# # Re-raise exception. -# raise -# return proc, stdout, stderr - -# def check_bash_script(self, ref_script_path, submit_script_path, -# test_case_path=None): -# """ Function validates student script using instructor script as -# reference. Test cases can optionally be provided. The first argument -# ref_script_path, is the path to instructor script, it is assumed to -# have executable permission. The second argument submit_script_path, is -# the path to the student script, it is assumed to have executable -# permission. The Third optional argument is the path to test the -# scripts. Each line in this file is a test case and each test case is -# passed to the script as standard arguments. - -# Returns -# -------- - -# returns (True, "Correct answer") : If the student script passes all -# test cases/have same output, when compared to the instructor script - -# returns (False, error_msg): If the student script fails a single -# test/have dissimilar output, when compared to the instructor script. - -# Returns (False, error_msg): If mandatory arguments are not files or if -# the required permissions are not given to the file(s). - -# """ -# if not ref_script_path: -# return False, "No Test Script path found" -# if not isfile(ref_script_path): -# return False, "No file at %s" % ref_script_path -# if not isfile(submit_script_path): -# return False, 'No file at %s' % submit_script_path -# if not os.access(ref_script_path, os.X_OK): -# return False, 'Script %s is not executable' % ref_script_path -# if not os.access(submit_script_path, os.X_OK): -# return False, 'Script %s is not executable' % submit_script_path - -# if test_case_path is None or "": -# ret = self._run_command(ref_script_path, stdin=None, -# stdout=subprocess.PIPE, -# stderr=subprocess.PIPE) -# proc, inst_stdout, inst_stderr = ret -# ret = self._run_command(submit_script_path, stdin=None, -# stdout=subprocess.PIPE, -# stderr=subprocess.PIPE) -# proc, stdnt_stdout, stdnt_stderr = ret -# if inst_stdout == stdnt_stdout: -# return True, 'Correct answer' -# else: -# err = "Error: expected %s, got %s" % (inst_stderr, -# stdnt_stderr) -# return False, err -# else: -# if not isfile(test_case_path): -# return False, "No test case at %s" % test_case_path -# if not os.access(ref_script_path, os.R_OK): -# return False, "Test script %s, not readable" % test_case_path -# valid_answer = True # We initially make it one, so that we can -# # stop once a test case fails -# loop_count = 0 # Loop count has to be greater than or -# # equal to one. -# # Useful for caching things like empty -# # test files,etc. -# test_cases = open(test_case_path).readlines() -# num_lines = len(test_cases) -# for test_case in test_cases: -# loop_count += 1 -# if valid_answer: -# args = [ref_script_path] + [x for x in test_case.split()] -# ret = self._run_command(args, stdin=None, -# stdout=subprocess.PIPE, -# stderr=subprocess.PIPE) -# proc, inst_stdout, inst_stderr = ret -# args = [submit_script_path]+[x for x in test_case.split()] -# ret = self._run_command(args, stdin=None, -# stdout=subprocess.PIPE, -# stderr=subprocess.PIPE) -# proc, stdnt_stdout, stdnt_stderr = ret -# valid_answer = inst_stdout == stdnt_stdout -# if valid_answer and (num_lines == loop_count): -# return True, "Correct answer" -# else: -# err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, -# stdnt_stdout+stdnt_stderr) -# return False, err - -# def run_c_code(self, answer, test_parameter, in_dir=None): -# """Tests given C code (`answer`) with the `test_code` supplied. - -# The testcode is a path to the reference code. -# The reference code will call the function submitted by the student. -# The reference code will check for the expected output. - -# If the path's start with a "/" then we assume they are absolute paths. -# If not, we assume they are relative paths w.r.t. the location of this -# code_server script. - -# If the optional `in_dir` keyword argument is supplied it changes the -# directory to that directory (it does not change it back to the original -# when done). - -# Returns -# ------- - -# A tuple: (success, error message). - -# """ -# if in_dir is not None and isdir(in_dir): -# os.chdir(in_dir) - -# # File extension must be .c -# submit_f = open('submit.c', 'w') -# submit_f.write(answer.lstrip()) -# submit_f.close() -# submit_path = abspath(submit_f.name) - -# ref_path = test_parameter.get('ref_code_path').strip() -# if not ref_path.startswith('/'): -# ref_path = join(MY_DIR, ref_path) - -# # Add a new signal handler for the execution of this code. -# old_handler = signal.signal(signal.SIGALRM, timeout_handler) -# signal.alarm(SERVER_TIMEOUT) - -# # Do whatever testing needed. -# success = False -# try: -# success, err = self._check_c_cpp_code(ref_path, submit_path) -# except TimeoutException: -# err = self.timeout_msg -# except: -# type, value = sys.exc_info()[:2] -# err = "Error: {0}".format(repr(value)) -# finally: -# # Set back any original signal handler. -# signal.signal(signal.SIGALRM, old_handler) - -# # Delete the created file. -# os.remove(submit_path) - -# # Cancel the signal if any, see signal.alarm documentation. -# signal.alarm(0) - -# # Put us back into the server pool queue since we are free now. -# self.queue.put(self.port) - -# result = {'success': success, 'error': err} -# return result - -# def _compile_command(self, cmd, *args, **kw): -# """Compiles C/C++/java code and returns errors if any. -# Run a command in a subprocess while blocking, the process is killed -# if it takes more than 2 seconds to run. Return the Popen object, the -# stderr. -# """ -# try: -# proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, -# stdout=subprocess.PIPE, -# stderr=subprocess.PIPE) -# out, err = proc_compile.communicate() -# except TimeoutException: -# # Runaway code, so kill it. -# proc_compile.kill() -# # Re-raise exception. -# raise -# return proc_compile, err - -# def _check_c_cpp_code(self, ref_code_path, submit_code_path): -# """ Function validates student code using instructor code as -# reference.The first argument ref_code_path, is the path to -# instructor code, it is assumed to have executable permission. -# The second argument submit_code_path, is the path to the student -# code, it is assumed to have executable permission. - -# Returns -# -------- - -# returns (True, "Correct answer") : If the student function returns -# expected output when called by reference code. - -# returns (False, error_msg): If the student function fails to return -# expected output when called by reference code. - -# Returns (False, error_msg): If mandatory arguments are not files or -# if the required permissions are not given to the file(s). - -# """ -# if not isfile(ref_code_path): -# return False, "No file at %s" % ref_code_path -# if not isfile(submit_code_path): -# return False, 'No file at %s' % submit_code_path - -# success = False -# output_path = os.getcwd() + '/output' -# compile_command = "g++ %s -c -o %s" % (submit_code_path, output_path) -# ret = self._compile_command(compile_command) -# proc, stdnt_stderr = ret - -# # Only if compilation is successful, the program is executed -# # And tested with testcases -# if stdnt_stderr == '': -# executable = os.getcwd() + '/executable' -# compile_main = "g++ %s %s -o %s" % (ref_code_path, output_path, -# executable) -# ret = self._compile_command(compile_main) -# proc, main_err = ret -# if main_err == '': -# args = [executable] -# ret = self._run_command(args, stdin=None, -# stdout=subprocess.PIPE, -# stderr=subprocess.PIPE) -# proc, stdout, stderr = ret -# if proc.returncode == 0: -# success, err = True, "Correct answer" -# else: -# err = stdout + "\n" + stderr -# os.remove(executable) -# else: -# err = "Error:" -# try: -# error_lines = main_err.splitlines() -# for e in error_lines: -# err = err + "\n" + e.split(":", 1)[1] -# except: -# err = err + "\n" + main_err -# os.remove(output_path) -# else: -# err = "Compilation Error:" -# try: -# error_lines = stdnt_stderr.splitlines() -# for e in error_lines: -# if ':' in e: -# err = err + "\n" + e.split(":", 1)[1] -# else: -# err = err + "\n" + e -# except: -# err = err + "\n" + stdnt_stderr -# return success, err - -# def run_cplus_code(self, answer, test_parameter, in_dir=None): -# """Tests given C++ code (`answer`) with the `test_code` supplied. - -# The testcode is a path to the reference code. -# The reference code will call the function submitted by the student. -# The reference code will check for the expected output. - -# If the path's start with a "/" then we assume they are absolute paths. -# If not, we assume they are relative paths w.r.t. the location of this -# code_server script. - -# If the optional `in_dir` keyword argument is supplied it changes the -# directory to that directory (it does not change it back to the original -# when done). - -# Returns -# ------- - -# A tuple: (success, error message). - -# """ -# if in_dir is not None and isdir(in_dir): -# os.chdir(in_dir) - -# # The file extension must be .cpp -# submit_f = open('submitstd.cpp', 'w') -# submit_f.write(answer.lstrip()) -# submit_f.close() -# submit_path = abspath(submit_f.name) - -# ref_path = test_parameter.get('ref_code_path').strip() -# if not ref_path.startswith('/'): -# ref_path = join(MY_DIR, ref_path) - -# # Add a new signal handler for the execution of this code. -# old_handler = signal.signal(signal.SIGALRM, timeout_handler) -# signal.alarm(SERVER_TIMEOUT) - -# # Do whatever testing needed. -# success = False -# try: -# success, err = self._check_c_cpp_code(ref_path, submit_path) -# except TimeoutException: -# err = self.timeout_msg -# except: -# type, value = sys.exc_info()[:2] -# err = "Error: {0}".format(repr(value)) -# finally: -# # Set back any original signal handler. -# signal.signal(signal.SIGALRM, old_handler) - -# # Delete the created file. -# os.remove(submit_path) - -# # Cancel the signal if any, see signal.alarm documentation. -# signal.alarm(0) - -# # Put us back into the server pool queue since we are free now. -# self.queue.put(self.port) - -# result = {'success': success, 'error': err} -# return result - -# def run_java_code(self, answer, test_parameter, in_dir=None): -# """Tests given java code (`answer`) with the `test_code` supplied. - -# The testcode is a path to the reference code. -# The reference code will call the function submitted by the student. -# The reference code will check for the expected output. - -# If the path's start with a "/" then we assume they are absolute paths. -# If not, we assume they are relative paths w.r.t. the location of this -# code_server script. - -# If the optional `in_dir` keyword argument is supplied it changes the -# directory to that directory (it does not change it back to the original -# when done). - -# Returns -# ------- - -# A tuple: (success, error message). - -# """ -# if in_dir is not None and isdir(in_dir): -# os.chdir(in_dir) - -# # The file extension must be .java -# # The class name and file name must be same in java -# submit_f = open('Test.java', 'w') -# submit_f.write(answer.lstrip()) -# submit_f.close() -# submit_path = abspath(submit_f.name) - -# ref_path = test_parameter.get('ref_code_path').strip() -# if not ref_path.startswith('/'): -# ref_path = join(MY_DIR, ref_path) - -# # Add a new signal handler for the execution of this code. -# old_handler = signal.signal(signal.SIGALRM, timeout_handler) -# signal.alarm(SERVER_TIMEOUT) - -# # Do whatever testing needed. -# success = False -# try: -# success, err = self._check_java_code(ref_path, submit_path) -# except TimeoutException: -# err = self.timeout_msg -# except: -# type, value = sys.exc_info()[:2] -# err = "Error: {0}".format(repr(value)) -# finally: -# # Set back any original signal handler. -# signal.signal(signal.SIGALRM, old_handler) - -# # Delete the created file. -# os.remove(submit_path) - -# # Cancel the signal if any, see signal.alarm documentation. -# signal.alarm(0) - -# # Put us back into the server pool queue since we are free now. -# self.queue.put(self.port) - -# return success, err - -# def _check_java_code(self, ref_code_path, submit_code_path): -# """ Function validates student code using instructor code as -# reference.The first argument ref_code_path, is the path to -# instructor code, it is assumed to have executable permission. -# The second argument submit_code_path, is the path to the student -# code, it is assumed to have executable permission. - -# Returns -# -------- - -# returns (True, "Correct answer") : If the student function returns -# expected output when called by reference code. - -# returns (False, error_msg): If the student function fails to return -# expected output when called by reference code. - -# Returns (False, error_msg): If mandatory arguments are not files or -# if the required permissions are not given to the file(s). - -# """ -# if not isfile(ref_code_path): -# return False, "No file at %s" % ref_code_path -# if not isfile(submit_code_path): -# return False, 'No file at %s' % submit_code_path - -# success = False -# compile_command = "javac %s" % (submit_code_path) -# ret = self._compile_command(compile_command) -# proc, stdnt_stderr = ret -# stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) - -# # Only if compilation is successful, the program is executed -# # And tested with testcases -# if stdnt_stderr == '': -# student_directory = os.getcwd() + '/' -# student_file_name = "Test" -# compile_main = "javac %s -classpath %s -d %s" % (ref_code_path, -# student_directory, -# student_directory) -# ret = self._compile_command(compile_main) -# proc, main_err = ret -# main_err = self._remove_null_substitute_char(main_err) - -# if main_err == '': -# main_file_name = (ref_code_path.split('/')[-1]).split('.')[0] -# run_command = "java -cp %s %s" % (student_directory, -# main_file_name) -# ret = self._run_command(run_command, -# stdin=None, -# shell=True, -# stdout=subprocess.PIPE, -# stderr=subprocess.PIPE) -# proc, stdout, stderr = ret -# if proc.returncode == 0: -# success, err = True, "Correct answer" -# else: -# err = stdout + "\n" + stderr -# success = False -# os.remove("%s%s.class" % (student_directory, main_file_name)) -# else: -# err = "Error:\n" -# try: -# error_lines = main_err.splitlines() -# for e in error_lines: -# if ':' in e: -# err = err + "\n" + e.split(":", 1)[1] -# else: -# err = err + "\n" + e -# except: -# err = err + "\n" + main_err -# os.remove("%s%s.class" % (student_directory, student_file_name)) -# else: -# err = "Compilation Error:\n" -# try: -# error_lines = stdnt_stderr.splitlines() -# for e in error_lines: -# if ':' in e: -# err = err + "\n" + e.split(":", 1)[1] -# else: -# err = err + "\n" + e -# except: -# err = err + "\n" + stdnt_stderr -# result = {'success': success, 'error': err} -# return result - -# def _remove_null_substitute_char(self, string): -# """Returns a string without any null and substitute characters""" -# stripped = "" -# for c in string: -# if ord(c) is not 26 and ord(c) is not 0: -# stripped = stripped + c -# return ''.join(stripped) - -# def run_scilab_code(self, answer, test_parameter, in_dir=None): -# """Tests given Scilab function (`answer`) with the `test_code` -# supplied. If the optional `in_dir` keyword argument is supplied -# it changes the directory to that directory (it does not change -# it back to the original when done). This function also timesout -# when the function takes more than SERVER_TIMEOUT seconds to run -# to prevent runaway code. - -# The testcode is a path to the reference code. -# The reference code will call the function submitted by the student. -# The reference code will check for the expected output. - -# If the path's start with a "/" then we assume they are absolute paths. -# If not, we assume they are relative paths w.r.t. the location of this -# code_server script. - -# Returns -# ------- - -# A tuple: (success, error message). - -# """ -# if in_dir is not None and isdir(in_dir): -# os.chdir(in_dir) - -# # Removes all the commands that terminates scilab -# answer,i = self._remove_scilab_exit(answer.lstrip()) - -# # Throw message if there are commmands that terminates scilab -# add_err="" -# if i > 0: -# add_err = "Please do not use exit, quit and abort commands in your\ -# code.\n Otherwise your code will not be evaluated\ -# correctly.\n" - -# # The file extension should be .sci -# submit_f = open('function.sci','w') -# submit_f.write(answer) -# submit_f.close() -# submit_path = abspath(submit_f.name) - -# ref_path = test_parameter.get('ref_code_path').strip() -# if not ref_path.startswith('/'): -# ref_path = join(MY_DIR, ref_path) - -# # Add a new signal handler for the execution of this code. -# old_handler = signal.signal(signal.SIGALRM, timeout_handler) -# signal.alarm(SERVER_TIMEOUT) - -# # Do whatever testing needed. -# success = False -# try: -# cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) -# cmd += ' | timeout 8 scilab-cli -nb' -# ret = self._run_command(cmd, -# shell=True, -# stdout=subprocess.PIPE, -# stderr=subprocess.PIPE) -# proc, stdout, stderr = ret - -# # Get only the error. -# stderr = self._get_error(stdout) -# if stderr is None: -# # Clean output -# stdout = self._strip_output(stdout) -# if proc.returncode == 5: -# success, err = True, "Correct answer" -# else: -# err = add_err + stdout -# else: -# err = add_err + stderr -# except TimeoutException: -# err = self.timeout_msg -# except: -# type, value = sys.exc_info()[:2] -# err = "Error: {0}".format(repr(value)) -# finally: -# # Set back any original signal handler. -# signal.signal(signal.SIGALRM, old_handler) - -# # Delete the created file. -# os.remove(submit_path) - -# # Cancel the signal if any, see signal.alarm documentation. -# signal.alarm(0) - -# # Put us back into the server pool queue since we are free now. -# self.queue.put(self.port) - -# result = {'success': success, 'error': err} -# return result - -# def _remove_scilab_exit(self, string): -# """ -# Removes exit, quit and abort from the scilab code -# """ -# new_string = "" -# i=0 -# for line in string.splitlines(): -# new_line = re.sub(r"exit.*$","",line) -# new_line = re.sub(r"quit.*$","",new_line) -# new_line = re.sub(r"abort.*$","",new_line) -# if line != new_line: -# i=i+1 -# new_string = new_string +'\n'+ new_line -# return new_string, i - - def _get_error(self, string): - """ - Fetches only the error from the string. - Returns None if no error. - """ - obj = re.search("!.+\n.+",string); - if obj: - return obj.group() - return None - - def _strip_output(self, out): - """ - Cleans whitespace from the output - """ - strip_out = "Message" - for l in out.split('\n'): - if l.strip(): - strip_out = strip_out+"\n"+l.strip() - return strip_out - def run(self): - """Run XMLRPC server, serving our methods. - """ + """Run XMLRPC server, serving our methods.""" server = SimpleXMLRPCServer(("localhost", self.port)) self.server = server server.register_instance(self) -- cgit From 28ba37e907553aeac3841e221853683b9171f0db Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Wed, 18 Mar 2015 12:16:20 +0530 Subject: Changes to Question model, Views, Add-Question UI - ref_code_path is now part of Question model - MCQ/MCC answers checked using solution field in question model - Formset should reload even after errors - add_question page chould display solution field only in MCQ/MCC --- testapp/exam/forms.py | 23 ++++++++++++----------- testapp/exam/models.py | 12 ++++++++---- testapp/exam/static/exam/js/add_question.js | 15 ++++++++++++--- testapp/exam/templates/exam/add_question.html | 9 +++++---- testapp/exam/templates/exam/edit_question.html | 24 ------------------------ testapp/exam/views.py | 14 ++++++++------ 6 files changed, 45 insertions(+), 52 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/forms.py b/testapp/exam/forms.py index e3cef40..93584a6 100644 --- a/testapp/exam/forms.py +++ b/testapp/exam/forms.py @@ -187,8 +187,8 @@ class QuestionForm(forms.ModelForm): description = forms.CharField(widget=forms.Textarea\ (attrs={'cols': 40, 'rows': 1})) points = forms.FloatField() - # test = forms.CharField(widget=forms.Textarea\ - # (attrs={'cols': 40, 'rows': 1}), required=False) + solution = forms.CharField(widget=forms.Textarea\ + (attrs={'cols': 40, 'rows': 1}), required=False) options = forms.CharField(widget=forms.Textarea\ (attrs={'cols': 40, 'rows': 1}), required=False) language = forms.CharField(max_length=20, widget=forms.Select\ @@ -199,17 +199,18 @@ class QuestionForm(forms.ModelForm): tags = TagField(widget=TagAutocomplete(), required=False) snippet = forms.CharField(widget=forms.Textarea\ (attrs={'cols': 40, 'rows': 1}), required=False) + ref_code_path = forms.CharField(widget=forms.Textarea\ + (attrs={'cols': 40, 'rows': 1}), required=False) def save(self, commit=True): - summary = self.cleaned_data["summary"] - description = self.cleaned_data["description"] - points = self.cleaned_data['points'] - # test = self.cleaned_data["test"] - options = self.cleaned_data['options'] - language = self.cleaned_data['language'] - type = self.cleaned_data["type"] - active = self.cleaned_data["active"] - snippet = self.cleaned_data["snippet"] + summary = self.cleaned_data.get("summary") + description = self.cleaned_data.get("description") + points = self.cleaned_data.get("points") + options = self.cleaned_data.get("options") + language = self.cleaned_data.get("language") + type = self.cleaned_data.get("type") + active = self.cleaned_data.get("active") + snippet = self.cleaned_data.get("snippet") new_question = Question() new_question.summary = summary diff --git a/testapp/exam/models.py b/testapp/exam/models.py index dd0c806..5989be4 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -60,8 +60,11 @@ class Question(models.Model): # Number of points for the question. points = models.FloatField(default=1.0) - # # Test cases for the question in the form of code that is run. - # test = models.TextField(blank=True) + # Answer for MCQs. + solution = models.TextField(blank=True) + + # Test cases file paths + ref_code_path = models.TextField(blank=True) # Any multiple choice options. Place one option per line. options = models.TextField(blank=True) @@ -114,6 +117,7 @@ class Question(models.Model): info_parameter['id'] = self.id info_parameter['user_answer'] = user_answer info_parameter['test_parameter'] = test_case_parameter + info_parameter['ref_code_path'] = self.ref_code_path return json.dumps(info_parameter) @@ -449,5 +453,5 @@ class TestCase(models.Model): # Test case Expected answer in list form expected_answer = models.TextField(blank=True, null = True) - # Test case path to system test code applicable for CPP, C, Java and Scilab - ref_code_path = models.TextField(blank=True, null = True) + # # Test case path to system test code applicable for CPP, C, Java and Scilab + # ref_code_path = models.TextField(blank=True, null = True) diff --git a/testapp/exam/static/exam/js/add_question.js b/testapp/exam/static/exam/js/add_question.js index 267cdb2..5a94f4c 100644 --- a/testapp/exam/static/exam/js/add_question.js +++ b/testapp/exam/static/exam/js/add_question.js @@ -153,13 +153,17 @@ function textareaformat() if(value == 'mcq' || value == 'mcc') { document.getElementById('id_options').style.visibility='visible'; - document.getElementById('label_option').innerHTML="Options :" + document.getElementById('label_option').innerHTML="Options :"; + document.getElementById('id_solution').style.visibility='visible'; + document.getElementById('label_solution').innerHTML="Solutions :"; } else { document.getElementById('id_options').style.visibility='hidden'; document.getElementById('label_option').innerHTML = ""; + document.getElementById('id_solution').style.visibility='hidden'; + document.getElementById('label_solution').innerHTML="" } }); document.getElementById('my').innerHTML = document.getElementById('id_description').value ; @@ -168,12 +172,16 @@ function textareaformat() { document.getElementById('id_options').style.visibility='visible'; document.getElementById('label_option').innerHTML="Options :" + document.getElementById('id_solution').style.visibility='visible'; + document.getElementById('label_solution').innerHTML="Solutions :"; } else { document.getElementById('id_options').style.visibility='hidden'; document.getElementById('label_option').innerHTML = ""; + document.getElementById('id_solution').style.visibility='hidden'; + document.getElementById('label_solution').innerHTML="" } } @@ -189,8 +197,9 @@ function autosubmit() if(type.value == 'select') { type.style.border = 'solid red'; - return false; - } + return false; + } + if (type.value == 'mcq' || type.value == 'mcc') { diff --git a/testapp/exam/templates/exam/add_question.html b/testapp/exam/templates/exam/add_question.html index e5e2574..e117ef3 100644 --- a/testapp/exam/templates/exam/add_question.html +++ b/testapp/exam/templates/exam/add_question.html @@ -27,11 +27,12 @@ Points:{{ form.points }}{{ form.points.errors }} Rendered:

Description: {{ form.description}} {{form.description.errors}} - Snippet: {{ form.snippet }}{{ form.snippet.errors }} Tags: {{ form.tags }} Options: {{ form.options }} {{form.options.errors}} - + Solution: {{ form.solution }} {{form.solution.errors}} + Reference Code Path: {{ form.ref_code_path }} {{form.ref_code_path.errors}} +
{% if formset%} {{ formset.management_form }} @@ -41,8 +42,8 @@ {% endif %}
-
- +
+

diff --git a/testapp/exam/templates/exam/edit_question.html b/testapp/exam/templates/exam/edit_question.html index 1c34dee..6deca4a 100644 --- a/testapp/exam/templates/exam/edit_question.html +++ b/testapp/exam/templates/exam/edit_question.html @@ -21,30 +21,6 @@ - - {% for question, test in data_list %}
{{question.summary.value}} diff --git a/testapp/exam/views.py b/testapp/exam/views.py index 8aef5ab..840f30a 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -323,7 +323,7 @@ def add_question(request, question_id=None): def add_or_delete_test_form(post_request, instance): request_copy = post_request.copy() if 'add_test' in post_request: - request_copy['test-TOTAL_FORMS'] = int(request_copy['test-TOTAL_FORMS']) + 1 + request_copy['test-TOTAL_FORMS'] = int(request_copy['test-TOTAL_FORMS']) + 1 elif 'delete_test' in post_request: request_copy['test-TOTAL_FORMS'] = int(request_copy['test-TOTAL_FORMS']) - 1 test_case_formset = TestCaseFormSet(request_copy, prefix='test', instance=instance) @@ -402,8 +402,10 @@ def add_question(request, question_id=None): context_instance=ci) else: + test_case_formset = add_or_delete_test_form(request.POST, form.save(commit=False)) return my_render_to_response('exam/add_question.html', - {'form': form}, + {'form': form, + 'formset': test_case_formset}, context_instance=ci) else: form = QuestionForm() @@ -951,8 +953,8 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # questions, we obtain the results via XML-RPC with the code executed # safely in a separate process (the code_server.py) running as nobody. if not question.type == 'upload': - if question.type == 'code': - info_parameter = question.consolidate_answer_data(test, user_answer) + info_parameter = question.consolidate_answer_data(test, user_answer) \ + if question.type == 'code' else None correct, result = validate_answer(user, user_answer, question, info_parameter) if correct: new_answer.correct = correct @@ -1013,11 +1015,11 @@ def validate_answer(user, user_answer, question, info_parameter=None): if user_answer is not None: if question.type == 'mcq': - if user_answer.strip() == question.test.strip(): ####add question.answer/question.solution instead of test + if user_answer.strip() == question.solution.strip(): correct = True message = 'Correct answer' elif question.type == 'mcc': - answers = set(question.test.splitlines()) ####add question.answer/question.solution instead of test + answers = set(question.solution.splitlines()) if set(user_answer) == answers: correct = True message = 'Correct answer' -- cgit From 11489a740a020f0401ce04c0b1a52f6bef31257e Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Tue, 7 Apr 2015 15:34:42 +0530 Subject: Code review - changes as per code review discussion - make loop in consolidate_test_cases more readable - split signal handler func definition into three seperate func - pass seperate kwargs to TestCode class - unpack json in CodeServer class and then pass to TestCode --- testapp/exam/code_server.py | 73 ++++++++++++++++++++++++------------------ testapp/exam/models.py | 29 ++++++++--------- testapp/exam/xmlrpc_clients.py | 2 +- 3 files changed, 56 insertions(+), 48 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index 2259ce8..2f1607c 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -54,22 +54,24 @@ def timeout_handler(signum, frame): """A handler for the ALARM signal.""" raise TimeoutException('Code took too long to run.') -def create_delete_signal_handler(action=None, old_handler=None): - if action == "new": # Add a new signal handler for the execution of this code. - prev_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - return prev_handler - elif action == "original": # Set back any original signal handler. - if old_handler is not None: - signal.signal(signal.SIGALRM, old_handler) - return - else: - raise Exception("Signal Handler: object cannot be NoneType") - elif action == "delete": # Cancel the signal if any, see signal.alarm documentation. - signal.alarm(0) +def create_signal_handler(): + """Add a new signal handler for the execution of this code.""" + prev_handler = signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(SERVER_TIMEOUT) + return prev_handler + +def set_original_signal_handler(old_handler=None): + """Set back any original signal handler.""" + if old_handler is not None: + signal.signal(signal.SIGALRM, old_handler) return else: - raise Exception("Signal Handler: action not specified") + raise Exception("Signal Handler: object cannot be NoneType") + +def delete_signal_handler(): + signal.alarm(0) + return + ############################################################################### @@ -77,15 +79,16 @@ def create_delete_signal_handler(action=None, old_handler=None): ############################################################################### class TestCode(object): """Evaluates and tests the code obtained from Code Server""" - def __init__(self, info_parameter, in_dir=None): - info_parameter = json.loads(info_parameter) - self.test_parameter = info_parameter.get("test_parameter") - self.language = info_parameter.get("language") - self.user_answer = info_parameter.get("user_answer") + def __init__(self, test_parameter, language, user_answer, ref_code_path=None, in_dir=None): + self.test_parameter = test_parameter + self.language = language + self.user_answer = user_answer + self.ref_code_path = ref_code_path self.in_dir = in_dir def run_code(self): - """Tests given code (`answer`) with the `test_code` supplied. + """Tests given code (`answer`) with the test cases based on + given arguments. The ref_code_path is a path to the reference code. The reference code will call the function submitted by the student. @@ -113,7 +116,7 @@ class TestCode(object): tb = None test_code = self.create_test_case() # Add a new signal handler for the execution of this code. - prev_handler = create_delete_signal_handler("new") + prev_handler = create_signal_handler() try: submitted = compile(self.user_answer, '', mode='exec') @@ -138,10 +141,10 @@ class TestCode(object): finally: del tb # Set back any original signal handler. - create_delete_signal_handler("original", prev_handler) + set_original_signal_handler(prev_handler) # Cancel the signal - create_delete_signal_handler("delete") + delete_signal_handler() else: user_answer_file = {'C': 'submit.c', 'java': 'Test.java', 'scilab': 'function.sci', @@ -165,7 +168,7 @@ class TestCode(object): if self.language in ["C++", "C", "java"]: # Add a new signal handler for the execution of this code. - prev_handler = create_delete_signal_handler("new") + prev_handler = create_signal_handler() # Do whatever testing needed. try: @@ -176,12 +179,12 @@ class TestCode(object): type, value = sys.exc_info()[:2] err = "Error: {0}".format(repr(value)) finally: - # # Set back any original signal handler. - create_delete_signal_handler("original", prev_handler) + # Set back any original signal handler. + set_original_signal_handler(prev_handler) elif self.language == "scilab": # Add a new signal handler for the execution of this code. - prev_handler = create_delete_signal_handler("new") + prev_handler = create_signal_handler() # Do whatever testing needed. try: @@ -211,11 +214,11 @@ class TestCode(object): err = "Error: {0}".format(repr(value)) finally: # Set back any original signal handler. - create_delete_signal_handler("original", prev_handler) + set_original_signal_handler(prev_handler) elif self.language == "bash": # Add a new signal handler for the execution of this code. - prev_handler = create_delete_signal_handler("new") + prev_handler = create_signal_handler() try: success, err = self.check_bash_script(ref_path, submit_path, @@ -227,13 +230,13 @@ class TestCode(object): err = "Error: {0}".format(repr(value)) finally: # Set back any original signal handler. - create_delete_signal_handler("original", prev_handler) + set_original_signal_handler(prev_handler) # Delete the created file. os.remove(submit_path) # Cancel the signal - create_delete_signal_handler("delete") + delete_signal_handler() result = {'success': success, 'error': err} return result @@ -554,7 +557,13 @@ class CodeServer(object): def checker(self, info_parameter, in_dir=None): """Calls the TestCode Class to test the current code""" - tc = TestCode(info_parameter, in_dir) + info_parameter = json.loads(info_parameter) + test_parameter = info_parameter.get("test_parameter") + language = info_parameter.get("language") + user_answer = info_parameter.get("user_answer") + ref_code_path = info_parameter.get("ref_code_path") + + tc = TestCode(test_parameter, language, user_answer, ref_code_path, in_dir) result = tc.run_code() # Put us back into the server pool queue since we are free now. self.queue.put(self.port) diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 5989be4..706d864 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -63,7 +63,7 @@ class Question(models.Model): # Answer for MCQs. solution = models.TextField(blank=True) - # Test cases file paths + # Test cases file paths (comma seperated for reference code path and test case code path) ref_code_path = models.TextField(blank=True) # Any multiple choice options. Place one option per line. @@ -86,27 +86,26 @@ class Question(models.Model): # Tags for the Question. tags = TaggableManager() - def consolidate_answer_data(self, test, user_answer): + def consolidate_answer_data(self, test_cases, user_answer): #test test_case_parameter = [] info_parameter = {} - for i in test: + for test_case in test_cases: kw_args_dict = {} pos_args_list = [] parameter_dict = {} - parameter_dict['test_id'] = i.id - parameter_dict['func_name'] = i.func_name - parameter_dict['expected_answer'] = i.expected_answer - parameter_dict['ref_code_path'] = i.ref_code_path - - if i.kw_args: - for args in i.kw_args.split(","): - key, val = args.split("=") - kw_args_dict[key.strip()] = val.strip() - - if i.pos_args: - for args in i.pos_args.split(","): + parameter_dict['test_id'] = test_case.id + parameter_dict['func_name'] = test_case.func_name + parameter_dict['expected_answer'] = test_case.expected_answer + + if test_case.kw_args: + for args in test_case.kw_args.split(","): + arg_name, arg_value = args.split("=") + kw_args_dict[arg_name.strip()] = arg_value.strip() + + if test_case.pos_args: + for args in test_case.pos_args.split(","): pos_args_list.append(args.strip()) parameter_dict['kw_args'] = kw_args_dict diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index bbd6110..2b0f0fa 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -54,7 +54,7 @@ class CodeServerProxy(object): ------- A json string of a dict: {success: success, err: error message}. """ - # method_name = self.methods[language] + try: server = self._get_server() result = server.checker(info_parameter, user_dir) -- cgit From 3d998f9d467ccfe795448dd12b33eac52f269ed4 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Sun, 19 Apr 2015 17:05:39 +0530 Subject: Add test cases for models --- testapp/exam/tests.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 4 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/tests.py b/testapp/exam/tests.py index d76e4f8..30afb7e 100644 --- a/testapp/exam/tests.py +++ b/testapp/exam/tests.py @@ -1,7 +1,7 @@ from django.utils import unittest from exam.models import User, Profile, Question, Quiz, QuestionPaper,\ - QuestionSet, AnswerPaper, Answer -import datetime + QuestionSet, AnswerPaper, Answer, TestCase +import datetime, json def setUpModule(): @@ -51,12 +51,39 @@ class ProfileTestCases(unittest.TestCase): class QuestionTestCases(unittest.TestCase): def setUp(self): # Single question details + # self.question = Question(summary='Demo question', language='Python', + # type='Code', active=True, + # description='Write a function', points=1.0, + # test='Test Cases', snippet='def myfunc()') self.question = Question(summary='Demo question', language='Python', type='Code', active=True, description='Write a function', points=1.0, - test='Test Cases', snippet='def myfunc()') + snippet='def myfunc()') self.question.save() self.question.tags.add('python', 'function') + self.testcase = TestCase(question=self.question, + func_name='def myfunc', kw_args='a=10,b=11', + pos_args='12,13', expected_answer='15') + answer_data = {"user_answer": "demo_answer", + "test_parameter": [{"func_name": "def myfunc", + "expected_answer": "15", + "test_id": self.testcase.id, + "pos_args": ["12", "13"], + "kw_args": {"a": "10", + "b": "11"} + }], + "ref_code_path": "", + "id": self.question.id, + "language": "Python"} + self.answer_data_json = json.dumps(answer_data) + self.user_answer = "demo_answer" + + +# {"user_answer": "demo_answer", +# "test_parameter": [{"func_name": "def myfunc", +# "expected_answer": "15", "test_id": null, "pos_args": ["12", "13"], +# "kw_args": {"a": "10", "b": "11"}}], +# "ref_code_path": "", "id": 21, "language": "Python"} def test_question(self): """ Test question """ @@ -67,13 +94,41 @@ class QuestionTestCases(unittest.TestCase): self.assertEqual(self.question.description, 'Write a function') self.assertEqual(self.question.points, 1.0) self.assertTrue(self.question.active) - self.assertEqual(self.question.test, 'Test Cases') + # self.assertEqual(self.question.test, 'Test Cases') self.assertEqual(self.question.snippet, 'def myfunc()') tag_list = [] for tag in self.question.tags.all(): tag_list.append(tag.name) self.assertEqual(tag_list, ['python', 'function']) + def test_consolidate_answer_data(self): + """ Test consolidate_answer_data function """ + result = self.question.consolidate_answer_data([self.testcase], + user_answer) + self.assertEqual(result, self.answer_data_json) + + + +############################################################################### +class TestCaseTestCases(unittest.TestCase): + def setUp(self): + self.question = Question(summary='Demo question', language='Python', + type='Code', active=True, + description='Write a function', points=1.0, + snippet='def myfunc()') + self.question.save() + self.testcase = TestCase(question=self.question, + func_name='def myfunc', kw_args='a=10,b=11', + pos_args='12,13', expected_answer='15') + + def test_testcase(self): + """ Test question """ + self.assertEqual(self.testcase.question, self.question) + self.assertEqual(self.testcase.func_name, 'def myfunc') + self.assertEqual(self.testcase.kw_args, 'a=10,b=11') + self.assertEqual(self.testcase.pos_args, '12,13') + self.assertEqual(self.testcase.expected_answer, '15') + ############################################################################### class QuizTestCases(unittest.TestCase): -- cgit From cbeffec80d30fe2a9048644f5b0345f797479c92 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Mon, 20 Apr 2015 21:38:11 +0530 Subject: Code review - changes as per code review discussion - Further commonify and simplify code_server, fix bugs --- testapp/exam/code_server.py | 282 +++++++++++++++++++++-------------------- testapp/exam/models.py | 14 +- testapp/exam/xmlrpc_clients.py | 7 - 3 files changed, 150 insertions(+), 153 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index 2f1607c..5cff7dc 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -80,6 +80,9 @@ def delete_signal_handler(): class TestCode(object): """Evaluates and tests the code obtained from Code Server""" def __init__(self, test_parameter, language, user_answer, ref_code_path=None, in_dir=None): + msg = 'Code took more than %s seconds to run. You probably '\ + 'have an infinite loop in your code.' % SERVER_TIMEOUT + self.timeout_msg = msg self.test_parameter = test_parameter self.language = language self.user_answer = user_answer @@ -110,136 +113,144 @@ class TestCode(object): success = False prev_handler = None + methods = {"python": 'evaluate_python_code', + "bash": 'evaluate_bash_code', + "C": "evaluate_c_cpp_java_code", + "C++": "evaluate_c_cpp_java_code", + "java": "evaluate_c_cpp_java_code", + "scilab": "evaluate_scilab_code", + } + get_method_based_on_lang = methods[self.language] self._change_dir(self.in_dir) - if self.language == "python": + # Add a new signal handler for the execution of this code. + prev_handler = create_signal_handler() + success = False + + # Do whatever testing needed. + try: + evaluate_code = getattr(self, get_method_based_on_lang) + success, err = evaluate_code() + + except TimeoutException: + err = self.timeout_msg + except: + type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + finally: + # Set back any original signal handler. + set_original_signal_handler(prev_handler) + + # Cancel the signal + delete_signal_handler() + + result = {'success': success, 'error': err} + return result + + def evaluate_python_code(self): + success = False + + try: tb = None - test_code = self.create_test_case() - # Add a new signal handler for the execution of this code. - prev_handler = create_signal_handler() + test_code = self._create_test_case() + submitted = compile(self.user_answer, '', mode='exec') + g = {} + exec submitted in g + _tests = compile(test_code, '', mode='exec') + exec _tests in g + except AssertionError: + type, value, tb = sys.exc_info() + info = traceback.extract_tb(tb) + fname, lineno, func, text = info[-1] + text = str(test_code).splitlines()[lineno-1] + err = "{0} {1} in: {2}".format(type.__name__, str(value), text) + else: + success = True + err = 'Correct answer' - try: - submitted = compile(self.user_answer, '', mode='exec') - g = {} - exec submitted in g - _tests = compile(test_code, '', mode='exec') - exec _tests in g - except TimeoutException: - err = self.timeout_msg - except AssertionError: - type, value, tb = sys.exc_info() - info = traceback.extract_tb(tb) - fname, lineno, func, text = info[-1] - text = str(test_code).splitlines()[lineno-1] - err = "{0} {1} in: {2}".format(type.__name__, str(value), text) - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - else: - success = True - err = 'Correct answer' - finally: - del tb - # Set back any original signal handler. - set_original_signal_handler(prev_handler) + del tb + return success, err + + def evaluate_c_cpp_java_code(self): + submit_path = self._create_submit_code_file() + ref_path, test_case_path = self._set_test_code_file_path() + success = False + + success, err = self._check_code(ref_path, submit_path) + + # Delete the created file. + os.remove(submit_path) + + return success, err - # Cancel the signal - delete_signal_handler() + def evaluate_scilab_code(self): + submit_path = self._create_submit_code_file() + ref_path, test_case_path = self._set_test_code_file_path() + success = False + cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) + cmd += ' | timeout 8 scilab-cli -nb' + ret = self._run_command(cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + + # Get only the error. + stderr = self._get_error(stdout) + if stderr is None: + # Clean output + stdout = self._strip_output(stdout) + if proc.returncode == 5: + success, err = True, "Correct answer" + else: + err = add_err + stdout else: - user_answer_file = {'C': 'submit.c', 'java': 'Test.java', 'scilab': 'function.sci', - 'C++': 'submitstd.cpp', 'bash': 'submit.sh'} - - # File extension depending on the question language - submit_f = open(user_answer_file.get(self.language), 'w') - submit_f.write(self.user_answer.lstrip()) - submit_f.close() - submit_path = abspath(submit_f.name) - if self.language == "bash": - self._set_exec(submit_path) - - path_list = self.ref_code_path.split(',') - ref_path = path_list[0].strip() if path_list[0] else "" - test_case_path = path_list[1].strip() if path_list[1] else "" - if ref_path and not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - if test_case_path and not test_case_path.startswith('/'): - test_case_path = join(MY_DIR, test_case_path) - - if self.language in ["C++", "C", "java"]: - # Add a new signal handler for the execution of this code. - prev_handler = create_signal_handler() - - # Do whatever testing needed. - try: - success, err = self._check_code(ref_path, submit_path) - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - set_original_signal_handler(prev_handler) + err = add_err + stderr - elif self.language == "scilab": - # Add a new signal handler for the execution of this code. - prev_handler = create_signal_handler() + # Delete the created file. + os.remove(submit_path) - # Do whatever testing needed. - try: - cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) - cmd += ' | timeout 8 scilab-cli -nb' - ret = self._run_command(cmd, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - - # Get only the error. - stderr = self._get_error(stdout) - if stderr is None: - # Clean output - stdout = self._strip_output(stdout) - if proc.returncode == 5: - success, err = True, "Correct answer" - else: - err = add_err + stdout - else: - err = add_err + stderr - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - set_original_signal_handler(prev_handler) + return success, err - elif self.language == "bash": - # Add a new signal handler for the execution of this code. - prev_handler = create_signal_handler() + def evaluate_bash_code(self): + submit_path = self._create_submit_code_file() + ref_path, test_case_path = self._set_test_code_file_path() + success = False - try: - success, err = self.check_bash_script(ref_path, submit_path, - test_case_path) - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - set_original_signal_handler(prev_handler) + success, err = self.check_bash_script(ref_path, submit_path, + test_case_path) - # Delete the created file. - os.remove(submit_path) + # Delete the created file. + os.remove(submit_path) - # Cancel the signal - delete_signal_handler() + return success, err - result = {'success': success, 'error': err} - return result + def _create_submit_code_file(self): + """ Write the code (`answer`) to a file and set the file path""" + user_answer_file = {'C': 'submit.c', 'java': 'Test.java', + 'scilab': 'function.sci', 'C++': 'submitstd.cpp', + 'bash': 'submit.sh'} + + # File extension depending on the question language + submit_f = open(user_answer_file.get(self.language), 'w') + submit_f.write(self.user_answer.lstrip()) + submit_f.close() + submit_path = abspath(submit_f.name) + if self.language == "bash": + self._set_exec(submit_path) + + return submit_path + + def _set_test_code_file_path(self): + ref_path, test_case_path = self.ref_code_path.split(',') + + if ref_path and not ref_path.startswith('/'): + ref_path = join(MY_DIR, ref_path) + if test_case_path and not test_case_path.startswith('/'): + test_case_path = join(MY_DIR, test_case_path) + + return ref_path, test_case_path def _check_code(self, ref_code_path, submit_code_path): """ Function validates student code using instructor code as @@ -303,7 +314,7 @@ class TestCode(object): success = False # output_path = os.getcwd() + '/output' - compile_command = language_dependent_var.get(self.language).get('compile_command') # "g++ %s -c -o %s" % (submit_code_path, output_path) + compile_command = language_dependent_var.get(self.language).get('compile_command') ret = self._compile_command(compile_command) proc, stdnt_stderr = ret if self.language == "java": @@ -312,7 +323,7 @@ class TestCode(object): # Only if compilation is successful, the program is executed # And tested with testcases if stdnt_stderr == '': - compile_main = language_dependent_var.get(self.language).get('compile_main') # "g++ %s %s -o %s" % (ref_code_path, output_path, executable) + compile_main = language_dependent_var.get(self.language).get('compile_main') ret = self._compile_command(compile_main) proc, main_err = ret if self.language == "java": @@ -328,7 +339,7 @@ class TestCode(object): success, err = True, "Correct answer" else: err = stdout + "\n" + stderr - os.remove(language_dependent_var.get(self.language).get('remove_ref_output')) # os.remove(executable) + os.remove(language_dependent_var.get(self.language).get('remove_ref_output')) else: err = "Error:" try: @@ -340,7 +351,7 @@ class TestCode(object): err = err + "\n" + e except: err = err + "\n" + main_err - os.remove(language_dependent_var.get(self.language).get('remove_user_output')) # os.remove(output_path) + os.remove(language_dependent_var.get(self.language).get('remove_user_output')) else: err = "Compilation Error:" try: @@ -353,8 +364,7 @@ class TestCode(object): except: err = err + "\n" + stdnt_stderr - result = {'success': success, 'error': err} - return result + return success, err def check_bash_script(self, ref_path, submit_path, test_case_path=None): @@ -519,22 +529,21 @@ class TestCode(object): | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) - def create_test_case(self): + def _create_test_case(self): """ Create assert based test cases in python """ - if self.language == "python": - test_code = "" - for test_case in self.test_parameter: - pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ - else "" - kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ - if test_case.get('kw_args') else "" - args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args - tcode = "assert {0}({1}) == {2}" \ - .format(test_case.get('func_name'), args, test_case.get('expected_answer')) - test_code += tcode + "\n" - return test_code + test_code = "" + for test_case in self.test_parameter: + pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ + else "" + kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ + if test_case.get('kw_args') else "" + args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args + tcode = "assert {0}({1}) == {2}" \ + .format(test_case.get('func_name'), args, test_case.get('expected_answer')) + test_code += tcode + "\n" + return test_code def _change_dir(self, in_dir): if in_dir is not None and isdir(in_dir): @@ -551,9 +560,6 @@ class CodeServer(object): def __init__(self, port, queue): self.port = port self.queue = queue - msg = 'Code took more than %s seconds to run. You probably '\ - 'have an infinite loop in your code.' % SERVER_TIMEOUT - self.timeout_msg = msg def checker(self, info_parameter, in_dir=None): """Calls the TestCode Class to test the current code""" diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 706d864..1e7db2b 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -64,6 +64,7 @@ class Question(models.Model): solution = models.TextField(blank=True) # Test cases file paths (comma seperated for reference code path and test case code path) + # Applicable for CPP, C, Java and Scilab ref_code_path = models.TextField(blank=True) # Any multiple choice options. Place one option per line. @@ -112,11 +113,11 @@ class Question(models.Model): parameter_dict['pos_args'] = pos_args_list test_case_parameter.append(parameter_dict) - info_parameter['language'] = self.language - info_parameter['id'] = self.id - info_parameter['user_answer'] = user_answer - info_parameter['test_parameter'] = test_case_parameter - info_parameter['ref_code_path'] = self.ref_code_path + info_parameter['language'] = self.language + info_parameter['id'] = self.id + info_parameter['user_answer'] = user_answer + info_parameter['test_parameter'] = test_case_parameter + info_parameter['ref_code_path'] = self.ref_code_path return json.dumps(info_parameter) @@ -451,6 +452,3 @@ class TestCase(models.Model): # Test case Expected answer in list form expected_answer = models.TextField(blank=True, null = True) - - # # Test case path to system test code applicable for CPP, C, Java and Scilab - # ref_code_path = models.TextField(blank=True, null = True) diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index 2b0f0fa..5791dc6 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -21,13 +21,6 @@ class CodeServerProxy(object): def __init__(self): pool_url = 'http://localhost:%d' % (SERVER_POOL_PORT) self.pool_server = ServerProxy(pool_url) - self.methods = {"python": 'run_python_code', - "bash": 'run_bash_code', - "C": "run_c_code", - "C++": "run_cplus_code", - "java": "run_java_code", - "scilab": "run_scilab_code", - } def run_code(self, info_parameter, user_dir): """Tests given code (`answer`) with the `test_code` supplied. If the -- cgit From 3ffdba6e587422a0f2955879d12e0b2aeac342e1 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Mon, 20 Apr 2015 21:42:02 +0530 Subject: Code review - code refactoring as per suggestion - Add subclasses for different languages - Create seperate modules for different languages - Dynamic selection of subclasses based on language used - Add testcases --- testapp/exam/admin.py | 2 +- testapp/exam/code_server.py | 399 ++++------------------------------------ testapp/exam/evaluate_bash.py | 108 +++++++++++ testapp/exam/evaluate_c.py | 128 +++++++++++++ testapp/exam/evaluate_cpp.py | 43 +++++ testapp/exam/evaluate_java.py | 45 +++++ testapp/exam/evaluate_python.py | 52 ++++++ testapp/exam/evaluate_scilab.py | 79 ++++++++ testapp/exam/models.py | 2 +- testapp/exam/tests.py | 11 +- 10 files changed, 493 insertions(+), 376 deletions(-) create mode 100644 testapp/exam/evaluate_bash.py create mode 100644 testapp/exam/evaluate_c.py create mode 100644 testapp/exam/evaluate_cpp.py create mode 100644 testapp/exam/evaluate_java.py create mode 100644 testapp/exam/evaluate_python.py create mode 100644 testapp/exam/evaluate_scilab.py (limited to 'testapp/exam') diff --git a/testapp/exam/admin.py b/testapp/exam/admin.py index 1bdf436..86a10af 100644 --- a/testapp/exam/admin.py +++ b/testapp/exam/admin.py @@ -1,4 +1,4 @@ -from exam.models import Question, Quiz, TestCase +from testapp.exam.models import Question, Quiz, TestCase from django.contrib import admin admin.site.register(Question) diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index 5cff7dc..ae68398 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -30,11 +30,12 @@ from multiprocessing import Process, Queue import subprocess import re import json +import importlib # Local imports. from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT -MY_DIR = abspath(dirname(__file__)) +MY_DIR = abspath(dirname(__file__)) def run_as_nobody(): """Runs the current process as nobody.""" @@ -78,13 +79,13 @@ def delete_signal_handler(): # `TestCode` class. ############################################################################### class TestCode(object): - """Evaluates and tests the code obtained from Code Server""" + """Tests the code obtained from Code Server""" def __init__(self, test_parameter, language, user_answer, ref_code_path=None, in_dir=None): msg = 'Code took more than %s seconds to run. You probably '\ 'have an infinite loop in your code.' % SERVER_TIMEOUT self.timeout_msg = msg self.test_parameter = test_parameter - self.language = language + self.language = language.lower() self.user_answer = user_answer self.ref_code_path = ref_code_path self.in_dir = in_dir @@ -110,17 +111,6 @@ class TestCode(object): A tuple: (success, error message). """ - success = False - prev_handler = None - - methods = {"python": 'evaluate_python_code', - "bash": 'evaluate_bash_code', - "C": "evaluate_c_cpp_java_code", - "C++": "evaluate_c_cpp_java_code", - "java": "evaluate_c_cpp_java_code", - "scilab": "evaluate_scilab_code", - } - get_method_based_on_lang = methods[self.language] self._change_dir(self.in_dir) # Add a new signal handler for the execution of this code. @@ -129,8 +119,7 @@ class TestCode(object): # Do whatever testing needed. try: - evaluate_code = getattr(self, get_method_based_on_lang) - success, err = evaluate_code() + success, err = self.evaluate_code() except TimeoutException: err = self.timeout_msg @@ -147,93 +136,17 @@ class TestCode(object): result = {'success': success, 'error': err} return result - def evaluate_python_code(self): - success = False - - try: - tb = None - test_code = self._create_test_case() - submitted = compile(self.user_answer, '', mode='exec') - g = {} - exec submitted in g - _tests = compile(test_code, '', mode='exec') - exec _tests in g - except AssertionError: - type, value, tb = sys.exc_info() - info = traceback.extract_tb(tb) - fname, lineno, func, text = info[-1] - text = str(test_code).splitlines()[lineno-1] - err = "{0} {1} in: {2}".format(type.__name__, str(value), text) - else: - success = True - err = 'Correct answer' - - del tb - return success, err - - def evaluate_c_cpp_java_code(self): - submit_path = self._create_submit_code_file() - ref_path, test_case_path = self._set_test_code_file_path() - success = False - - success, err = self._check_code(ref_path, submit_path) - - # Delete the created file. - os.remove(submit_path) - - return success, err - - def evaluate_scilab_code(self): - submit_path = self._create_submit_code_file() - ref_path, test_case_path = self._set_test_code_file_path() - success = False - - cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) - cmd += ' | timeout 8 scilab-cli -nb' - ret = self._run_command(cmd, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - - # Get only the error. - stderr = self._get_error(stdout) - if stderr is None: - # Clean output - stdout = self._strip_output(stdout) - if proc.returncode == 5: - success, err = True, "Correct answer" - else: - err = add_err + stdout - else: - err = add_err + stderr - - # Delete the created file. - os.remove(submit_path) - - return success, err - - def evaluate_bash_code(self): - submit_path = self._create_submit_code_file() - ref_path, test_case_path = self._set_test_code_file_path() - success = False - - success, err = self.check_bash_script(ref_path, submit_path, - test_case_path) - - # Delete the created file. - os.remove(submit_path) + def evaluate_code(self): + pass - return success, err - - def _create_submit_code_file(self): + def _create_submit_code_file(self, file_name): """ Write the code (`answer`) to a file and set the file path""" - user_answer_file = {'C': 'submit.c', 'java': 'Test.java', - 'scilab': 'function.sci', 'C++': 'submitstd.cpp', - 'bash': 'submit.sh'} + # user_answer_file = {'c': 'submit.c', 'java': 'Test.java', + # 'scilab': 'function.sci', 'cpp': 'submitstd.cpp', + # 'bash': 'submit.sh'} - # File extension depending on the question language - submit_f = open(user_answer_file.get(self.language), 'w') + # File name/extension depending on the question language + submit_f = open(file_name, 'w') submit_f.write(self.user_answer.lstrip()) submit_f.close() submit_path = abspath(submit_f.name) @@ -242,8 +155,13 @@ class TestCode(object): return submit_path - def _set_test_code_file_path(self): - ref_path, test_case_path = self.ref_code_path.split(',') + def _set_exec(self, fname): + os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR + | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP + | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) + + def _set_test_code_file_path(self, ref_path=None, test_case_path=None): + # ref_path, test_case_path = self.ref_code_path.split(',') if ref_path and not ref_path.startswith('/'): ref_path = join(MY_DIR, ref_path) @@ -252,202 +170,6 @@ class TestCode(object): return ref_path, test_case_path - def _check_code(self, ref_code_path, submit_code_path): - """ Function validates student code using instructor code as - reference.The first argument ref_code_path, is the path to - instructor code, it is assumed to have executable permission. - The second argument submit_code_path, is the path to the student - code, it is assumed to have executable permission. - - Returns - -------- - - returns (True, "Correct answer") : If the student function returns - expected output when called by reference code. - - returns (False, error_msg): If the student function fails to return - expected output when called by reference code. - - Returns (False, error_msg): If mandatory arguments are not files or - if the required permissions are not given to the file(s). - - """ - - language_dependent_path = { - 'c_user_output_path': os.getcwd() + '/output', - 'c_ref_output_path': os.getcwd() + '/executable', - 'java_student_directory': os.getcwd() + '/', - # 'java_student_file_name': 'Test', - 'java_ref_file_name': (ref_code_path.split('/')[-1]).split('.')[0], - } - - language_dependent_var = { - 'C': {'compile_command': 'g++ {0} -c -o {1}'.format(submit_code_path, - language_dependent_path.get('c_user_output_path')), - 'compile_main': 'g++ {0} {1} -o {2}'.format(ref_code_path, - language_dependent_path.get('c_user_output_path'), - language_dependent_path.get('c_ref_output_path')), - 'run_command_args': [language_dependent_path.get('c_ref_output_path')], - 'remove_user_output': language_dependent_path.get('c_user_output_path'), - 'remove_ref_output': language_dependent_path.get('c_ref_output_path') - }, - 'java':{'compile_command': 'javac {0}'.format(submit_code_path), - 'compile_main': 'javac {0} -classpath {1} -d {2}'.format(ref_code_path, - language_dependent_path.get('java_student_directory'), - language_dependent_path.get('java_student_directory')), - 'run_command_args': "java -cp {0} {1}".format( - language_dependent_path.get('java_student_directory'), - language_dependent_path.get('java_ref_file_name')), - 'remove_user_output': "%s%s.class".format( - language_dependent_path.get('java_student_directory'), - 'Test'), - 'remove_ref_output': "%s%s.class".format( - language_dependent_path.get('java_student_directory'), - language_dependent_path.get('java_ref_file_name')), - } - } - - if not isfile(ref_code_path): - return False, "No file at %s or Incorrect path" % ref_code_path - if not isfile(submit_code_path): - return False, 'No file at %s or Incorrect path' % submit_code_path - - success = False - # output_path = os.getcwd() + '/output' - compile_command = language_dependent_var.get(self.language).get('compile_command') - ret = self._compile_command(compile_command) - proc, stdnt_stderr = ret - if self.language == "java": - stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) - - # Only if compilation is successful, the program is executed - # And tested with testcases - if stdnt_stderr == '': - compile_main = language_dependent_var.get(self.language).get('compile_main') - ret = self._compile_command(compile_main) - proc, main_err = ret - if self.language == "java": - main_err = self._remove_null_substitute_char(main_err) - - if main_err == '': - run_command_args = language_dependent_var.get(self.language).get('run_command_args') - ret = self._run_command(run_command_args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - if proc.returncode == 0: - success, err = True, "Correct answer" - else: - err = stdout + "\n" + stderr - os.remove(language_dependent_var.get(self.language).get('remove_ref_output')) - else: - err = "Error:" - try: - error_lines = main_err.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + main_err - os.remove(language_dependent_var.get(self.language).get('remove_user_output')) - else: - err = "Compilation Error:" - try: - error_lines = stdnt_stderr.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + stdnt_stderr - - return success, err - - def check_bash_script(self, ref_path, submit_path, - test_case_path=None): - """ Function validates student script using instructor script as - reference. Test cases can optionally be provided. The first argument - ref_path, is the path to instructor script, it is assumed to - have executable permission. The second argument submit_path, is - the path to the student script, it is assumed to have executable - permission. The Third optional argument is the path to test the - scripts. Each line in this file is a test case and each test case is - passed to the script as standard arguments. - - Returns - -------- - - returns (True, "Correct answer") : If the student script passes all - test cases/have same output, when compared to the instructor script - - returns (False, error_msg): If the student script fails a single - test/have dissimilar output, when compared to the instructor script. - - Returns (False, error_msg): If mandatory arguments are not files or if - the required permissions are not given to the file(s). - - """ - if not isfile(ref_path): - return False, "No file at %s or Incorrect path" % ref_path - if not isfile(submit_path): - return False, "No file at %s or Incorrect path" % submit_path - if not os.access(ref_path, os.X_OK): - return False, "Script %s is not executable" % ref_path - if not os.access(submit_path, os.X_OK): - return False, "Script %s is not executable" % submit_path - - if test_case_path is None or "": - ret = self._run_command(ref_path, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, inst_stdout, inst_stderr = ret - ret = self._run_command(submit_path, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdnt_stdout, stdnt_stderr = ret - if inst_stdout == stdnt_stdout: - return True, "Correct answer" - else: - err = "Error: expected %s, got %s" % (inst_stderr, - stdnt_stderr) - return False, err - else: - if not isfile(test_case_path): - return False, "No test case at %s" % test_case_path - if not os.access(ref_path, os.R_OK): - return False, "Test script %s, not readable" % test_case_path - valid_answer = True # We initially make it one, so that we can - # stop once a test case fails - loop_count = 0 # Loop count has to be greater than or - # equal to one. - # Useful for caching things like empty - # test files,etc. - test_cases = open(test_case_path).readlines() - num_lines = len(test_cases) - for test_case in test_cases: - loop_count += 1 - if valid_answer: - args = [ref_path] + [x for x in test_case.split()] - ret = self._run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, inst_stdout, inst_stderr = ret - args = [submit_path]+[x for x in test_case.split()] - ret = self._run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdnt_stdout, stdnt_stderr = ret - valid_answer = inst_stdout == stdnt_stdout - if valid_answer and (num_lines == loop_count): - return True, "Correct answer" - else: - err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, - stdnt_stdout+stdnt_stderr) - return False, err - def _run_command(self, cmd_args, *args, **kw): """Run a command in a subprocess while blocking, the process is killed if it takes more than 2 seconds to run. Return the Popen object, the @@ -481,70 +203,6 @@ class TestCode(object): raise return proc_compile, err - def _remove_null_substitute_char(self, string): - """Returns a string without any null and substitute characters""" - stripped = "" - for c in string: - if ord(c) is not 26 and ord(c) is not 0: - stripped = stripped + c - return ''.join(stripped) - - def _remove_scilab_exit(self, string): - """ - Removes exit, quit and abort from the scilab code - """ - new_string = "" - i=0 - for line in string.splitlines(): - new_line = re.sub(r"exit.*$","",line) - new_line = re.sub(r"quit.*$","",new_line) - new_line = re.sub(r"abort.*$","",new_line) - if line != new_line: - i=i+1 - new_string = new_string +'\n'+ new_line - return new_string, i - - def _get_error(self, string): - """ - Fetches only the error from the string. - Returns None if no error. - """ - obj = re.search("!.+\n.+",string); - if obj: - return obj.group() - return None - - def _strip_output(self, out): - """ - Cleans whitespace from the output - """ - strip_out = "Message" - for l in out.split('\n'): - if l.strip(): - strip_out = strip_out+"\n"+l.strip() - return strip_out - - def _set_exec(self, fname): - os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR - | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP - | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) - - def _create_test_case(self): - """ - Create assert based test cases in python - """ - test_code = "" - for test_case in self.test_parameter: - pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ - else "" - kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ - if test_case.get('kw_args') else "" - args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args - tcode = "assert {0}({1}) == {2}" \ - .format(test_case.get('func_name'), args, test_case.get('expected_answer')) - test_code += tcode + "\n" - return test_code - def _change_dir(self, in_dir): if in_dir is not None and isdir(in_dir): os.chdir(in_dir) @@ -569,13 +227,26 @@ class CodeServer(object): user_answer = info_parameter.get("user_answer") ref_code_path = info_parameter.get("ref_code_path") - tc = TestCode(test_parameter, language, user_answer, ref_code_path, in_dir) - result = tc.run_code() + eval_module_name = "evaluate_{0}".format(language.lower()) + eval_class_name = "Evaluate{0}".format(language.capitalize()) + + get_class = self._sub_class_factory(eval_module_name, eval_class_name) + + test_code_class = get_class(test_parameter, language, user_answer, ref_code_path, in_dir) + result = test_code_class.run_code() # Put us back into the server pool queue since we are free now. self.queue.put(self.port) return json.dumps(result) + def _sub_class_factory(self, module_name, class_name): + # load the module, will raise ImportError if module cannot be loaded + get_module = importlib.import_module(module_name) + # get the class, will raise AttributeError if class cannot be found + get_class = getattr(get_module, class_name) + + return get_class + def run(self): """Run XMLRPC server, serving our methods.""" server = SimpleXMLRPCServer(("localhost", self.port)) diff --git a/testapp/exam/evaluate_bash.py b/testapp/exam/evaluate_bash.py new file mode 100644 index 0000000..4e79053 --- /dev/null +++ b/testapp/exam/evaluate_bash.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from code_server import TestCode + +class EvaluateBash(TestCode): + """Tests the Bash code obtained from Code Server""" + def evaluate_code(self): + submit_path = self._create_submit_code_file('submit.sh') + get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') + ref_path, test_case_path = self._set_test_code_file_path(get_ref_path, get_test_case_path) + success = False + + success, err = self.check_bash_script(ref_path, submit_path, + test_case_path) + + # Delete the created file. + os.remove(submit_path) + + return success, err + + def check_bash_script(self, ref_path, submit_path, + test_case_path=None): + """ Function validates student script using instructor script as + reference. Test cases can optionally be provided. The first argument + ref_path, is the path to instructor script, it is assumed to + have executable permission. The second argument submit_path, is + the path to the student script, it is assumed to have executable + permission. The Third optional argument is the path to test the + scripts. Each line in this file is a test case and each test case is + passed to the script as standard arguments. + + Returns + -------- + + returns (True, "Correct answer") : If the student script passes all + test cases/have same output, when compared to the instructor script + + returns (False, error_msg): If the student script fails a single + test/have dissimilar output, when compared to the instructor script. + + Returns (False, error_msg): If mandatory arguments are not files or if + the required permissions are not given to the file(s). + + """ + if not isfile(ref_path): + return False, "No file at %s or Incorrect path" % ref_path + if not isfile(submit_path): + return False, "No file at %s or Incorrect path" % submit_path + if not os.access(ref_path, os.X_OK): + return False, "Script %s is not executable" % ref_path + if not os.access(submit_path, os.X_OK): + return False, "Script %s is not executable" % submit_path + + if test_case_path is None or "": + ret = self._run_command(ref_path, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, inst_stdout, inst_stderr = ret + ret = self._run_command(submit_path, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdnt_stdout, stdnt_stderr = ret + if inst_stdout == stdnt_stdout: + return True, "Correct answer" + else: + err = "Error: expected %s, got %s" % (inst_stderr, + stdnt_stderr) + return False, err + else: + if not isfile(test_case_path): + return False, "No test case at %s" % test_case_path + if not os.access(ref_path, os.R_OK): + return False, "Test script %s, not readable" % test_case_path + valid_answer = True # We initially make it one, so that we can + # stop once a test case fails + loop_count = 0 # Loop count has to be greater than or + # equal to one. + # Useful for caching things like empty + # test files,etc. + test_cases = open(test_case_path).readlines() + num_lines = len(test_cases) + for test_case in test_cases: + loop_count += 1 + if valid_answer: + args = [ref_path] + [x for x in test_case.split()] + ret = self._run_command(args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, inst_stdout, inst_stderr = ret + args = [submit_path]+[x for x in test_case.split()] + ret = self._run_command(args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdnt_stdout, stdnt_stderr = ret + valid_answer = inst_stdout == stdnt_stdout + if valid_answer and (num_lines == loop_count): + return True, "Correct answer" + else: + err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, + stdnt_stdout+stdnt_stderr) + return False, err \ No newline at end of file diff --git a/testapp/exam/evaluate_c.py b/testapp/exam/evaluate_c.py new file mode 100644 index 0000000..93c3725 --- /dev/null +++ b/testapp/exam/evaluate_c.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from code_server import TestCode + +class EvaluateC(TestCode): + """Tests the C code obtained from Code Server""" + def evaluate_code(self): + submit_path = self._create_submit_code_file('submit.c') + get_ref_path = self.ref_code_path + ref_path, test_case_path = self._set_test_code_file_path(get_ref_path) + success = False + + # Set file paths + c_user_output_path = os.getcwd() + '/output', + c_ref_output_path = os.getcwd() + '/executable', + + # Set command variables + compile_command = 'g++ {0} -c -o {1}'.format(submit_path, + c_user_output_path), + compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, + c_user_output_path, + c_ref_output_path), + run_command_args = c_ref_output_path + remove_user_output = c_user_output_path + remove_ref_output = c_ref_output_path + + success, err = self.check_code(ref_path, submit_path, compile_command, + compile_main, run_command_args, + remove_user_output, remove_ref_output) + + # Delete the created file. + os.remove(submit_path) + + return success, err + + def check_code(self, ref_code_path, submit_code_path, compile_command, + compile_main, run_command_args, remove_user_output, + remove_ref_output): + """ Function validates student code using instructor code as + reference.The first argument ref_code_path, is the path to + instructor code, it is assumed to have executable permission. + The second argument submit_code_path, is the path to the student + code, it is assumed to have executable permission. + + Returns + -------- + + returns (True, "Correct answer") : If the student function returns + expected output when called by reference code. + + returns (False, error_msg): If the student function fails to return + expected output when called by reference code. + + Returns (False, error_msg): If mandatory arguments are not files or + if the required permissions are not given to the file(s). + + """ + + if not isfile(ref_code_path): + return False, "No file at %s or Incorrect path" % ref_code_path + if not isfile(submit_code_path): + return False, 'No file at %s or Incorrect path' % submit_code_path + + success = False + # output_path = os.getcwd() + '/output' + ret = self._compile_command(compile_command) + proc, stdnt_stderr = ret + # if self.language == "java": + stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) + + # Only if compilation is successful, the program is executed + # And tested with testcases + if stdnt_stderr == '': + ret = self._compile_command(compile_main) + proc, main_err = ret + # if self.language == "java": + # main_err = self._remove_null_substitute_char(main_err) + + if main_err == '': + ret = self._run_command(run_command_args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + if proc.returncode == 0: + success, err = True, "Correct answer" + else: + err = stdout + "\n" + stderr + os.remove(remove_ref_output) + else: + err = "Error:" + try: + error_lines = main_err.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + main_err + os.remove(remove_user_output) + else: + err = "Compilation Error:" + try: + error_lines = stdnt_stderr.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + stdnt_stderr + + return success, err + + def _remove_null_substitute_char(self, string): + """Returns a string without any null and substitute characters""" + stripped = "" + for c in string: + if ord(c) is not 26 and ord(c) is not 0: + stripped = stripped + c + return ''.join(stripped) diff --git a/testapp/exam/evaluate_cpp.py b/testapp/exam/evaluate_cpp.py new file mode 100644 index 0000000..1723d3b --- /dev/null +++ b/testapp/exam/evaluate_cpp.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from evaluate_c import EvaluateC +from code_server import TestCode + + +class EvaluateCpp(EvaluateC, TestCode): + """Tests the C code obtained from Code Server""" + def evaluate_code(self): + submit_path = self._create_submit_code_file('submitstd.cpp') + get_ref_path = self.ref_code_path + ref_path, test_case_path = self._set_test_code_file_path(get_ref_path) + success = False + + # Set file paths + c_user_output_path = os.getcwd() + '/output', + c_ref_output_path = os.getcwd() + '/executable', + + # Set command variables + compile_command = 'g++ {0} -c -o {1}'.format(submit_path, + c_user_output_path), + compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, + c_user_output_path, + c_ref_output_path), + run_command_args = c_ref_output_path + remove_user_output = c_user_output_path + remove_ref_output = c_ref_output_path + + success, err = self.check_code(ref_path, submit_path, compile_command, + compile_main, run_command_args, + remove_user_output, remove_ref_output) + + # Delete the created file. + os.remove(submit_path) + + return success, err diff --git a/testapp/exam/evaluate_java.py b/testapp/exam/evaluate_java.py new file mode 100644 index 0000000..92969a3 --- /dev/null +++ b/testapp/exam/evaluate_java.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from evaluate_c import EvaluateC +from code_server import TestCode + + +class EvaluateJava(EvaluateC, TestCode): + """Tests the C code obtained from Code Server""" + def evaluate_code(self): + submit_path = self._create_submit_code_file('Test.java') + get_ref_path = self.ref_code_path + ref_path, test_case_path = self._set_test_code_file_path(get_ref_path) + success = False + + # Set file paths + java_student_directory = os.getcwd() + '/' + java_ref_file_name = (ref_code_path.split('/')[-1]).split('.')[0], + + # Set command variables + compile_command = 'javac {0}'.format(submit_code_path), + compile_main = 'javac {0} -classpath {1} -d {2}'.format(ref_code_path, + java_student_directory, + java_student_directory) + run_command_args = "java -cp {0} {1}".format(java_student_directory, + java_ref_file_name) + remove_user_output = "{0}{1}.class".format(java_student_directory, + 'Test') + remove_ref_output = "{0}{1}.class".format(java_student_directory, + java_ref_file_name) + + success, err = self.check_code(ref_path, submit_path, compile_command, + compile_main, run_command_args, + remove_user_output, remove_ref_output) + + # Delete the created file. + os.remove(submit_path) + + return success, err diff --git a/testapp/exam/evaluate_python.py b/testapp/exam/evaluate_python.py new file mode 100644 index 0000000..78d6fdf --- /dev/null +++ b/testapp/exam/evaluate_python.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +import sys +import traceback +import os +from os.path import join +import importlib + +# local imports +from code_server import TestCode + +class EvaluatePython(TestCode): + """Tests the Python code obtained from Code Server""" + # def evaluate_python_code(self): + def evaluate_code(self): + success = False + + try: + tb = None + test_code = self._create_test_case() + submitted = compile(self.user_answer, '', mode='exec') + g = {} + exec submitted in g + _tests = compile(test_code, '', mode='exec') + exec _tests in g + except AssertionError: + type, value, tb = sys.exc_info() + info = traceback.extract_tb(tb) + fname, lineno, func, text = info[-1] + text = str(test_code).splitlines()[lineno-1] + err = "{0} {1} in: {2}".format(type.__name__, str(value), text) + else: + success = True + err = 'Correct answer' + + del tb + return success, err + + def _create_test_case(self): + """ + Create assert based test cases in python + """ + test_code = "" + for test_case in self.test_parameter: + pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ + else "" + kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ + if test_case.get('kw_args') else "" + args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args + tcode = "assert {0}({1}) == {2}" \ + .format(test_case.get('func_name'), args, test_case.get('expected_answer')) + test_code += tcode + "\n" + return test_code \ No newline at end of file diff --git a/testapp/exam/evaluate_scilab.py b/testapp/exam/evaluate_scilab.py new file mode 100644 index 0000000..e36d9d8 --- /dev/null +++ b/testapp/exam/evaluate_scilab.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +import traceback +import os +from os.path import join, isfile +import subprocess +import re +import importlib + +# local imports +from code_server import TestCode + + +class EvaluateScilab(TestCode): + """Tests the Scilab code obtained from Code Server""" + # def evaluate_scilab_code(self): + def evaluate_code(self): + submit_path = self._create_submit_code_file('function.sci') + ref_path, test_case_path = self._set_test_code_file_path() + success = False + + cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) + cmd += ' | timeout 8 scilab-cli -nb' + ret = self._run_command(cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + + # Get only the error. + stderr = self._get_error(stdout) + if stderr is None: + # Clean output + stdout = self._strip_output(stdout) + if proc.returncode == 5: + success, err = True, "Correct answer" + else: + err = add_err + stdout + else: + err = add_err + stderr + + # Delete the created file. + os.remove(submit_path) + + return success, err + + def _remove_scilab_exit(self, string): + """ + Removes exit, quit and abort from the scilab code + """ + new_string = "" + i=0 + for line in string.splitlines(): + new_line = re.sub(r"exit.*$","",line) + new_line = re.sub(r"quit.*$","",new_line) + new_line = re.sub(r"abort.*$","",new_line) + if line != new_line: + i=i+1 + new_string = new_string +'\n'+ new_line + return new_string, i + + def _get_error(self, string): + """ + Fetches only the error from the string. + Returns None if no error. + """ + obj = re.search("!.+\n.+",string); + if obj: + return obj.group() + return None + + def _strip_output(self, out): + """ + Cleans whitespace from the output + """ + strip_out = "Message" + for l in out.split('\n'): + if l.strip(): + strip_out = strip_out+"\n"+l.strip() + return strip_out \ No newline at end of file diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 1e7db2b..874c81c 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -21,7 +21,7 @@ languages = ( ("python", "Python"), ("bash", "Bash"), ("C", "C Language"), - ("C++", "C++ Language"), + ("CPP", "C++ Language"), ("java", "Java Language"), ("scilab", "Scilab"), ) diff --git a/testapp/exam/tests.py b/testapp/exam/tests.py index 30afb7e..f73d0e2 100644 --- a/testapp/exam/tests.py +++ b/testapp/exam/tests.py @@ -3,7 +3,6 @@ from exam.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, TestCase import datetime, json - def setUpModule(): # create user profile user = User.objects.create_user(username='demo_user', @@ -78,13 +77,6 @@ class QuestionTestCases(unittest.TestCase): self.answer_data_json = json.dumps(answer_data) self.user_answer = "demo_answer" - -# {"user_answer": "demo_answer", -# "test_parameter": [{"func_name": "def myfunc", -# "expected_answer": "15", "test_id": null, "pos_args": ["12", "13"], -# "kw_args": {"a": "10", "b": "11"}}], -# "ref_code_path": "", "id": 21, "language": "Python"} - def test_question(self): """ Test question """ self.assertEqual(self.question.summary, 'Demo question') @@ -94,7 +86,6 @@ class QuestionTestCases(unittest.TestCase): self.assertEqual(self.question.description, 'Write a function') self.assertEqual(self.question.points, 1.0) self.assertTrue(self.question.active) - # self.assertEqual(self.question.test, 'Test Cases') self.assertEqual(self.question.snippet, 'def myfunc()') tag_list = [] for tag in self.question.tags.all(): @@ -104,7 +95,7 @@ class QuestionTestCases(unittest.TestCase): def test_consolidate_answer_data(self): """ Test consolidate_answer_data function """ result = self.question.consolidate_answer_data([self.testcase], - user_answer) + self.user_answer) self.assertEqual(result, self.answer_data_json) -- cgit From 0580f99fecc0bb495beb5706e18246834174710b Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Tue, 21 Apr 2015 18:15:10 +0530 Subject: Add code checker registration class --- testapp/exam/code_server.py | 18 +++++++++--------- testapp/exam/evaluate_bash.py | 6 +++++- testapp/exam/evaluate_c.py | 4 ++++ testapp/exam/evaluate_cpp.py | 4 ++++ testapp/exam/evaluate_java.py | 4 ++++ testapp/exam/evaluate_python.py | 5 ++++- testapp/exam/evaluate_scilab.py | 5 ++++- testapp/exam/registry.py | 14 ++++++++++++++ testapp/exam/xmlrpc_clients.py | 2 +- 9 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 testapp/exam/registry.py (limited to 'testapp/exam') diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index ae68398..f5c5698 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -33,10 +33,14 @@ import json import importlib # Local imports. from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT +from registry import registry MY_DIR = abspath(dirname(__file__)) +registry.register('python', ) +registry.register('py', MyTestCode) + def run_as_nobody(): """Runs the current process as nobody.""" # Set the effective uid and to that of nobody. @@ -137,25 +141,21 @@ class TestCode(object): return result def evaluate_code(self): - pass + raise NotImplementedError("evaluate_code method not implemented") def _create_submit_code_file(self, file_name): """ Write the code (`answer`) to a file and set the file path""" - # user_answer_file = {'c': 'submit.c', 'java': 'Test.java', - # 'scilab': 'function.sci', 'cpp': 'submitstd.cpp', - # 'bash': 'submit.sh'} - # File name/extension depending on the question language submit_f = open(file_name, 'w') submit_f.write(self.user_answer.lstrip()) submit_f.close() submit_path = abspath(submit_f.name) - if self.language == "bash": - self._set_exec(submit_path) + if sfile_elf.language == "bash": + self._set_file_as_executable(submit_path) return submit_path - def _set_exec(self, fname): + def _set_file_as_executable(self, fname): os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) @@ -219,7 +219,7 @@ class CodeServer(object): self.port = port self.queue = queue - def checker(self, info_parameter, in_dir=None): + def check_code(self, info_parameter, in_dir=None): """Calls the TestCode Class to test the current code""" info_parameter = json.loads(info_parameter) test_parameter = info_parameter.get("test_parameter") diff --git a/testapp/exam/evaluate_bash.py b/testapp/exam/evaluate_bash.py index 4e79053..57c89ae 100644 --- a/testapp/exam/evaluate_bash.py +++ b/testapp/exam/evaluate_bash.py @@ -8,6 +8,8 @@ import importlib # local imports from code_server import TestCode +from registry import registry + class EvaluateBash(TestCode): """Tests the Bash code obtained from Code Server""" @@ -105,4 +107,6 @@ class EvaluateBash(TestCode): else: err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, stdnt_stdout+stdnt_stderr) - return False, err \ No newline at end of file + return False, err + +registry.register('bash', evaluate_bash, EvaluateBash) \ No newline at end of file diff --git a/testapp/exam/evaluate_c.py b/testapp/exam/evaluate_c.py index 93c3725..bbe0e87 100644 --- a/testapp/exam/evaluate_c.py +++ b/testapp/exam/evaluate_c.py @@ -8,6 +8,8 @@ import importlib # local imports from code_server import TestCode +from registry import registry + class EvaluateC(TestCode): """Tests the C code obtained from Code Server""" @@ -126,3 +128,5 @@ class EvaluateC(TestCode): if ord(c) is not 26 and ord(c) is not 0: stripped = stripped + c return ''.join(stripped) + +registry.register('c', evaluate_c, EvaluateC) \ No newline at end of file diff --git a/testapp/exam/evaluate_cpp.py b/testapp/exam/evaluate_cpp.py index 1723d3b..87bd7a3 100644 --- a/testapp/exam/evaluate_cpp.py +++ b/testapp/exam/evaluate_cpp.py @@ -9,6 +9,8 @@ import importlib # local imports from evaluate_c import EvaluateC from code_server import TestCode +from registry import registry + class EvaluateCpp(EvaluateC, TestCode): @@ -41,3 +43,5 @@ class EvaluateCpp(EvaluateC, TestCode): os.remove(submit_path) return success, err + +registry.register('cpp', evaluate_cpp, EvaluateCpp) \ No newline at end of file diff --git a/testapp/exam/evaluate_java.py b/testapp/exam/evaluate_java.py index 92969a3..f908102 100644 --- a/testapp/exam/evaluate_java.py +++ b/testapp/exam/evaluate_java.py @@ -9,6 +9,8 @@ import importlib # local imports from evaluate_c import EvaluateC from code_server import TestCode +from registry import registry + class EvaluateJava(EvaluateC, TestCode): @@ -43,3 +45,5 @@ class EvaluateJava(EvaluateC, TestCode): os.remove(submit_path) return success, err + +registry.register('java', evaluate_java, EvaluateJava) \ No newline at end of file diff --git a/testapp/exam/evaluate_python.py b/testapp/exam/evaluate_python.py index 78d6fdf..18fde43 100644 --- a/testapp/exam/evaluate_python.py +++ b/testapp/exam/evaluate_python.py @@ -7,6 +7,7 @@ import importlib # local imports from code_server import TestCode +from registry import registry class EvaluatePython(TestCode): """Tests the Python code obtained from Code Server""" @@ -49,4 +50,6 @@ class EvaluatePython(TestCode): tcode = "assert {0}({1}) == {2}" \ .format(test_case.get('func_name'), args, test_case.get('expected_answer')) test_code += tcode + "\n" - return test_code \ No newline at end of file + return test_code + +registry.register('python', evaluate_python, EvaluatePython) \ No newline at end of file diff --git a/testapp/exam/evaluate_scilab.py b/testapp/exam/evaluate_scilab.py index e36d9d8..1874cf6 100644 --- a/testapp/exam/evaluate_scilab.py +++ b/testapp/exam/evaluate_scilab.py @@ -8,6 +8,7 @@ import importlib # local imports from code_server import TestCode +from registry import registryz class EvaluateScilab(TestCode): @@ -76,4 +77,6 @@ class EvaluateScilab(TestCode): for l in out.split('\n'): if l.strip(): strip_out = strip_out+"\n"+l.strip() - return strip_out \ No newline at end of file + return strip_out + +registry.register('scilab', evaluate_scilab, EvaluateScilab) \ No newline at end of file diff --git a/testapp/exam/registry.py b/testapp/exam/registry.py new file mode 100644 index 0000000..ef995ac --- /dev/null +++ b/testapp/exam/registry.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +class Registry(object): + def __init__(self): + self._registry = {} + + def get_class(self, language): + return self._registry[language] + + def register(self, language, cls): + self._registry[language] = cls + + +registry = Registry() \ No newline at end of file diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index 5791dc6..33fbc57 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -50,7 +50,7 @@ class CodeServerProxy(object): try: server = self._get_server() - result = server.checker(info_parameter, user_dir) + result = server.check_code(info_parameter, user_dir) except ConnectionError: result = json.dumps({'success': False, 'error': 'Unable to connect to any code servers!'}) return result -- cgit From 17752a69114e7dbad266337e768013920aec8c0c Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Thu, 23 Apr 2015 19:32:37 +0530 Subject: Code Review: Code refactoring - Add from_json classmethod - Question language is passed directly to the code server - Fix errors in evaluation of code - Fix test cases --- testapp/exam/c_cpp_files/main.cpp | 32 ++++ testapp/exam/c_cpp_files/main2.c | 30 ++++ testapp/exam/c_cpp_files/main_array_check.cpp | 34 ++++ testapp/exam/c_cpp_files/main_array_check_all.cpp | 34 ++++ testapp/exam/c_cpp_files/main_array_sum.cpp | 34 ++++ testapp/exam/c_cpp_files/main_blackJack.cpp | 41 +++++ testapp/exam/c_cpp_files/main_check_digit.cpp | 32 ++++ testapp/exam/c_cpp_files/main_count667.cpp | 42 +++++ testapp/exam/c_cpp_files/main_count7.cpp | 42 +++++ testapp/exam/c_cpp_files/main_fact.cpp | 32 ++++ testapp/exam/c_cpp_files/main_greatest.cpp | 44 +++++ testapp/exam/c_cpp_files/main_hello_name.c | 29 ++++ testapp/exam/c_cpp_files/main_lessThan9.cpp | 38 +++++ testapp/exam/c_cpp_files/main_mean.cpp | 38 +++++ testapp/exam/c_cpp_files/main_palindrome.cpp | 32 ++++ testapp/exam/c_cpp_files/main_roundTo10.cpp | 41 +++++ testapp/exam/c_cpp_files/main_specialSum.cpp | 41 +++++ testapp/exam/c_cpp_files/main_within.cpp | 38 +++++ testapp/exam/code_server.py | 198 ++-------------------- testapp/exam/evaluate_bash.py | 17 +- testapp/exam/evaluate_c.py | 33 ++-- testapp/exam/evaluate_cpp.py | 16 +- testapp/exam/evaluate_java.py | 10 +- testapp/exam/evaluate_python.py | 5 +- testapp/exam/evaluate_scilab.py | 13 +- testapp/exam/java_files/main_array_sum.java | 36 ++++ testapp/exam/java_files/main_fact.java | 29 ++++ testapp/exam/java_files/main_great.java | 39 +++++ testapp/exam/java_files/main_hello_name.java | 29 ++++ testapp/exam/java_files/main_lastDigit.java | 36 ++++ testapp/exam/java_files/main_moreThan30.java | 36 ++++ testapp/exam/java_files/main_palindrome.java | 29 ++++ testapp/exam/java_files/main_square.java | 32 ++++ testapp/exam/models.py | 4 +- testapp/exam/scilab_files/test_add.sce | 29 ++++ testapp/exam/test_code.py | 182 ++++++++++++++++++++ testapp/exam/views.py | 2 +- testapp/exam/xmlrpc_clients.py | 4 +- 38 files changed, 1196 insertions(+), 237 deletions(-) create mode 100755 testapp/exam/c_cpp_files/main.cpp create mode 100755 testapp/exam/c_cpp_files/main2.c create mode 100755 testapp/exam/c_cpp_files/main_array_check.cpp create mode 100755 testapp/exam/c_cpp_files/main_array_check_all.cpp create mode 100755 testapp/exam/c_cpp_files/main_array_sum.cpp create mode 100755 testapp/exam/c_cpp_files/main_blackJack.cpp create mode 100755 testapp/exam/c_cpp_files/main_check_digit.cpp create mode 100755 testapp/exam/c_cpp_files/main_count667.cpp create mode 100755 testapp/exam/c_cpp_files/main_count7.cpp create mode 100755 testapp/exam/c_cpp_files/main_fact.cpp create mode 100755 testapp/exam/c_cpp_files/main_greatest.cpp create mode 100755 testapp/exam/c_cpp_files/main_hello_name.c create mode 100755 testapp/exam/c_cpp_files/main_lessThan9.cpp create mode 100755 testapp/exam/c_cpp_files/main_mean.cpp create mode 100755 testapp/exam/c_cpp_files/main_palindrome.cpp create mode 100755 testapp/exam/c_cpp_files/main_roundTo10.cpp create mode 100755 testapp/exam/c_cpp_files/main_specialSum.cpp create mode 100755 testapp/exam/c_cpp_files/main_within.cpp create mode 100644 testapp/exam/java_files/main_array_sum.java create mode 100644 testapp/exam/java_files/main_fact.java create mode 100644 testapp/exam/java_files/main_great.java create mode 100644 testapp/exam/java_files/main_hello_name.java create mode 100644 testapp/exam/java_files/main_lastDigit.java create mode 100644 testapp/exam/java_files/main_moreThan30.java create mode 100644 testapp/exam/java_files/main_palindrome.java create mode 100644 testapp/exam/java_files/main_square.java create mode 100644 testapp/exam/scilab_files/test_add.sce create mode 100644 testapp/exam/test_code.py (limited to 'testapp/exam') diff --git a/testapp/exam/c_cpp_files/main.cpp b/testapp/exam/c_cpp_files/main.cpp new file mode 100755 index 0000000..ebe1f08 --- /dev/null +++ b/testapp/exam/c_cpp_files/main.cpp @@ -0,0 +1,32 @@ +#include +#include + +extern int add(int, int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + result = add(0,0); + printf("Input submitted to the function: 0, 0"); + check(0, result); + result = add(2,3); + printf("Input submitted to the function: 2 3"); + check(5,result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main2.c b/testapp/exam/c_cpp_files/main2.c new file mode 100755 index 0000000..ccd1768 --- /dev/null +++ b/testapp/exam/c_cpp_files/main2.c @@ -0,0 +1,30 @@ +#include +#include + +extern int add(int, int, int); + +template +void check(T expect,T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (0); + } +} + +int main(void) +{ + int result; + result = add(0,0,0); + printf("Input submitted to the function: 0, 0, 0"); + check(0, result); + result = add(2,3,3); + printf("Input submitted to the function: 2, 3, 3"); + check(8,result); + printf("All Correct\n"); +} diff --git a/testapp/exam/c_cpp_files/main_array_check.cpp b/testapp/exam/c_cpp_files/main_array_check.cpp new file mode 100755 index 0000000..ea34fdd --- /dev/null +++ b/testapp/exam/c_cpp_files/main_array_check.cpp @@ -0,0 +1,34 @@ +#include +#include + +extern bool array_check(int [], int); + +template + +void check(T expect,T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + bool result; + int a[] = {1,2,3,0,0}; + result = array_check(a, 2); + printf("Input submitted to the function: {1, 2, 3, 0, 0} and index 2"); + check(false, result); + int b[] = {1,2,3,4,5}; + result = array_check(b, 3); + printf("Input submitted to the function: {1, 2, 3, 4, 5} and index 3"); + check(true, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_array_check_all.cpp b/testapp/exam/c_cpp_files/main_array_check_all.cpp new file mode 100755 index 0000000..140578e --- /dev/null +++ b/testapp/exam/c_cpp_files/main_array_check_all.cpp @@ -0,0 +1,34 @@ +#include +#include + +extern bool array_check_all(int []); + +template + +void check(T expect,T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + bool result; + int a[] = {1,2,3,2,8}; + result = array_check_all(a); + printf("Input submitted to the function: {1, 2, 3, 2, 8}"); + check(false, result); + int b[] = {4,2,32,4,56}; + result = array_check_all(b); + printf("Input submitted to the function: {4, 2, 32, 4, 56}"); + check(true, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_array_sum.cpp b/testapp/exam/c_cpp_files/main_array_sum.cpp new file mode 100755 index 0000000..55b2ebf --- /dev/null +++ b/testapp/exam/c_cpp_files/main_array_sum.cpp @@ -0,0 +1,34 @@ +#include +#include + +extern int array_sum(int []); + +template + +void check(T expect,T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + int a[] = {1,2,3,0,0}; + result = array_sum(a); + printf("Input submitted to the function: {1, 2, 3, 0, 0}"); + check(6, result); + int b[] = {1,2,3,4,5}; + result = array_sum(b); + printf("Input submitted to the function: {1, 2, 3, 4, 5}"); + check(15,result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_blackJack.cpp b/testapp/exam/c_cpp_files/main_blackJack.cpp new file mode 100755 index 0000000..cc54e78 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_blackJack.cpp @@ -0,0 +1,41 @@ +#include +#include + +extern int blackJack(int, int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + result = blackJack(11, 12); + printf("Input submitted to the function: 11, 12"); + check(12, result); + result = blackJack(15, 19); + printf("Input submitted to the function: 15, 19"); + check(19, result); + result = blackJack(10, 21); + printf("Input submitted to the function: 10, 21"); + check(21, result); + result = blackJack(31, 22); + printf("Input submitted to the function: 31, 22"); + check(0, result); + result = blackJack(91, 61); + printf("Input submitted to the function: 91, 61"); + check(0, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_check_digit.cpp b/testapp/exam/c_cpp_files/main_check_digit.cpp new file mode 100755 index 0000000..d3bf3d6 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_check_digit.cpp @@ -0,0 +1,32 @@ +#include +#include + +extern bool check_digit(int, int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + bool result; + result = check_digit(12, 23); + printf("Input submitted to the function: 12, 23"); + check(true, result); + result = check_digit(22, 11); + printf("Input submitted to the function: 121"); + check(false, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_count667.cpp b/testapp/exam/c_cpp_files/main_count667.cpp new file mode 100755 index 0000000..f146e8c --- /dev/null +++ b/testapp/exam/c_cpp_files/main_count667.cpp @@ -0,0 +1,42 @@ +#include +#include + +extern int count667(int[]); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + int arr[5] = {2,6,4,5,6}; + result = count667(arr); + printf("Input submitted to the function: [2, 6, 4, 5,6]"); + check(0, result); + int arr2[5] = {6,6,2,17,9}; + result = count667(arr2); + printf("Input submitted to the function: [6, 6, 2, 17, 9]"); + check(1, result); + int arr3[5] = {6,6,6,7,1}; + result = count667(arr3); + printf("Input submitted to the function: [6, 6, 7, 2, 1]"); + check(3, result); + int arr4[5] = {6,7,7,6,6}; + result = count667(arr4); + printf("Input submitted to the function: [6, 7, 7, 6, 6]"); + check(2, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_count7.cpp b/testapp/exam/c_cpp_files/main_count7.cpp new file mode 100755 index 0000000..982e930 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_count7.cpp @@ -0,0 +1,42 @@ +#include +#include + +extern int count7(int[]); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + int arr[4] = {2,3,4,5}; + result = count7(arr); + printf("Input submitted to the function: [2, 3, 4, 5]"); + check(0, result); + int arr2[4] = {1,2,17,9}; + result = count7(arr2); + printf("Input submitted to the function: [1, 2, 17, 9]"); + check(0, result); + int arr3[4] = {7,9,2,1}; + result = count7(arr3); + printf("Input submitted to the function: [7, 9, 2, 1]"); + check(1, result); + int arr4[4] = {1,7,7,7}; + result = count7(arr4); + printf("Input submitted to the function: [1, 7, 7, 7]"); + check(3, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_fact.cpp b/testapp/exam/c_cpp_files/main_fact.cpp new file mode 100755 index 0000000..a4ff230 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_fact.cpp @@ -0,0 +1,32 @@ +#include +#include + +extern int factorial(int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + result = factorial(0); + printf("Input submitted to the function: 0"); + check(1, result); + result = factorial(3); + printf("Input submitted to the function: 3"); + check(6, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_greatest.cpp b/testapp/exam/c_cpp_files/main_greatest.cpp new file mode 100755 index 0000000..6d0a7c2 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_greatest.cpp @@ -0,0 +1,44 @@ +#include +#include + +extern int greatest(int, int, int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + result = greatest(1, 2, 3); + printf("Input submitted to the function: 1, 2, 3"); + check(3, result); + result = greatest(5, 9, 2); + printf("Input submitted to the function: 5, 9, 2"); + check(9, result); + result = greatest(7, 2, 4); + printf("Input submitted to the function: 7, 2, 4"); + check(7, result); + result = greatest(11, 2, 45); + printf("Input submitted to the function: 11, 2, 45"); + check(45, result); + result = greatest(2, 7, 0); + printf("Input submitted to the function: 2, 7, 0"); + check(7, result); + result = greatest(9, 6, 5); + printf("Input submitted to the function: 9, 6, 5"); + check(9, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_hello_name.c b/testapp/exam/c_cpp_files/main_hello_name.c new file mode 100755 index 0000000..71b83a2 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_hello_name.c @@ -0,0 +1,29 @@ +#include +#include + + +void check(char expect[], char result[]) +{ + if (expect == result) + { + printf("Correct:expected %s got %s \n",expect,result); + } + else + { + printf("ERROR:expected %s got %s \n",expect,result); + exit (0); + } +} + +int main(void) +{ + char result[20]; + char A[20]=" pratham"; + char B[20]=" sir"; + result[20] = message(A); + printf("%s",result); + check("hello pratham", result); + result[20] = message(B); + check("hello sir",result); + printf("All Correct\n"); +} diff --git a/testapp/exam/c_cpp_files/main_lessThan9.cpp b/testapp/exam/c_cpp_files/main_lessThan9.cpp new file mode 100755 index 0000000..722b4bb --- /dev/null +++ b/testapp/exam/c_cpp_files/main_lessThan9.cpp @@ -0,0 +1,38 @@ +#include +#include + +extern bool lessThan9(int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + bool result; + result = lessThan9(10); + printf("Input submitted to the function: 10"); + check(false, result); + result = lessThan9(17); + printf("Input submitted to the function: 17"); + check(true, result); + result = lessThan9(16); + printf("Input submitted to the function: 16"); + check(true, result); + result = lessThan9(15); + printf("Input submitted to the function: 15"); + check(false, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_mean.cpp b/testapp/exam/c_cpp_files/main_mean.cpp new file mode 100755 index 0000000..21a4b1a --- /dev/null +++ b/testapp/exam/c_cpp_files/main_mean.cpp @@ -0,0 +1,38 @@ +#include +#include + +extern bool mean(int, int , int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + bool result; + result = mean(11, 11, 11); + printf("Input submitted to the function: 11, 121, 11"); + check(true, result); + result = mean(16, 12, 9); + printf("Input submitted to the function: 16, 144, 9"); + check(true, result); + result = mean(19, 221, 9); + printf("Input submitted to the function: 19, 221, 9"); + check(false, result); + result = mean(34, 12, 3); + printf("Input submitted to the function: 11, 121, 11"); + check(false, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_palindrome.cpp b/testapp/exam/c_cpp_files/main_palindrome.cpp new file mode 100755 index 0000000..0e66928 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_palindrome.cpp @@ -0,0 +1,32 @@ +#include +#include + +extern bool palindrome(int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + bool result; + result = palindrome(123); + printf("Input submitted to the function: 123"); + check(false, result); + result = palindrome(121); + printf("Input submitted to the function: 121"); + check(true, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_roundTo10.cpp b/testapp/exam/c_cpp_files/main_roundTo10.cpp new file mode 100755 index 0000000..12c961d --- /dev/null +++ b/testapp/exam/c_cpp_files/main_roundTo10.cpp @@ -0,0 +1,41 @@ +#include +#include + +extern int roundTo10(int,int,int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + result = roundTo10(10, 22, 39); + printf("Input submitted to the function: 10, 22, 39"); + check(70, result); + result = roundTo10(45, 42, 39); + printf("Input submitted to the function: 45, 42, 39"); + check(130, result); + result = roundTo10(7, 3, 9); + printf("Input submitted to the function: 7, 3, 9"); + check(20, result); + result = roundTo10(1, 2, 3); + printf("Input submitted to the function: 1, 2, 3"); + check(0, result); + result = roundTo10(30, 40, 50); + printf("Input submitted to the function: 30, 40, 50"); + check(120, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_specialSum.cpp b/testapp/exam/c_cpp_files/main_specialSum.cpp new file mode 100755 index 0000000..d614536 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_specialSum.cpp @@ -0,0 +1,41 @@ +#include +#include + +extern int specialSum(int,int,int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + int result; + result = specialSum(10, 2, 9); + printf("Input submitted to the function: 10, 2, 9"); + check(21, result); + result = specialSum(1, 21, 9); + printf("Input submitted to the function: 1, 21, 9"); + check(1, result); + result = specialSum(21, 2, 3); + printf("Input submitted to the function: 21, 2, 3"); + check(0, result); + result = specialSum(10, 2, 21); + printf("Input submitted to the function: 10, 2, 21"); + check(12, result); + result = specialSum(10, 2, 6); + printf("Input submitted to the function: 10, 2, 6"); + check(18, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/c_cpp_files/main_within.cpp b/testapp/exam/c_cpp_files/main_within.cpp new file mode 100755 index 0000000..50f9ad0 --- /dev/null +++ b/testapp/exam/c_cpp_files/main_within.cpp @@ -0,0 +1,38 @@ +#include +#include + +extern bool within(int, int, int); + +template + +void check(T expect, T result) +{ + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } +} + +int main(void) +{ + bool result; + result = within(12, 3, 20); + printf("Input submitted to the function: 12, 3, 20"); + check(true, result); + result = within(12, 13, 20); + printf("Input submitted to the function: 12, 13, 20"); + check(false, result); + result = within(29, 13, 120); + printf("Input submitted to the function: 29, 13, 120"); + check(true, result); + result = within(12, 12, 20); + printf("Input submitted to the function: 12, 3, 20"); + check(false, result); + printf("All Correct\n"); + return 0; +} diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index f5c5698..db30798 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -19,7 +19,6 @@ settings.py:SERVER_POOL_PORT. This port exposes a `get_server_port` function that returns an available server. """ import sys -import traceback from SimpleXMLRPCServer import SimpleXMLRPCServer import pwd import os @@ -34,13 +33,15 @@ import importlib # Local imports. from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT from registry import registry - +from evaluate_python import EvaluatePython +from evaluate_c import EvaluateC +from evaluate_cpp import EvaluateCpp +from evaluate_java import EvaluateJava +from evaluate_scilab import EvaluateScilab +from evaluate_bash import EvaluateBash MY_DIR = abspath(dirname(__file__)) -registry.register('python', ) -registry.register('py', MyTestCode) - def run_as_nobody(): """Runs the current process as nobody.""" # Set the effective uid and to that of nobody. @@ -48,166 +49,6 @@ def run_as_nobody(): os.setegid(nobody.pw_gid) os.seteuid(nobody.pw_uid) - -# Raised when the code times-out. -# c.f. http://pguides.net/python/timeout-a-function -class TimeoutException(Exception): - pass - - -def timeout_handler(signum, frame): - """A handler for the ALARM signal.""" - raise TimeoutException('Code took too long to run.') - -def create_signal_handler(): - """Add a new signal handler for the execution of this code.""" - prev_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - return prev_handler - -def set_original_signal_handler(old_handler=None): - """Set back any original signal handler.""" - if old_handler is not None: - signal.signal(signal.SIGALRM, old_handler) - return - else: - raise Exception("Signal Handler: object cannot be NoneType") - -def delete_signal_handler(): - signal.alarm(0) - return - - - -############################################################################### -# `TestCode` class. -############################################################################### -class TestCode(object): - """Tests the code obtained from Code Server""" - def __init__(self, test_parameter, language, user_answer, ref_code_path=None, in_dir=None): - msg = 'Code took more than %s seconds to run. You probably '\ - 'have an infinite loop in your code.' % SERVER_TIMEOUT - self.timeout_msg = msg - self.test_parameter = test_parameter - self.language = language.lower() - self.user_answer = user_answer - self.ref_code_path = ref_code_path - self.in_dir = in_dir - - def run_code(self): - """Tests given code (`answer`) with the test cases based on - given arguments. - - The ref_code_path is a path to the reference code. - The reference code will call the function submitted by the student. - The reference code will check for the expected output. - - If the path's start with a "/" then we assume they are absolute paths. - If not, we assume they are relative paths w.r.t. the location of this - code_server script. - - If the optional `in_dir` keyword argument is supplied it changes the - directory to that directory (it does not change it back to the original - when done). - - Returns - ------- - - A tuple: (success, error message). - """ - self._change_dir(self.in_dir) - - # Add a new signal handler for the execution of this code. - prev_handler = create_signal_handler() - success = False - - # Do whatever testing needed. - try: - success, err = self.evaluate_code() - - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - set_original_signal_handler(prev_handler) - - # Cancel the signal - delete_signal_handler() - - result = {'success': success, 'error': err} - return result - - def evaluate_code(self): - raise NotImplementedError("evaluate_code method not implemented") - - def _create_submit_code_file(self, file_name): - """ Write the code (`answer`) to a file and set the file path""" - # File name/extension depending on the question language - submit_f = open(file_name, 'w') - submit_f.write(self.user_answer.lstrip()) - submit_f.close() - submit_path = abspath(submit_f.name) - if sfile_elf.language == "bash": - self._set_file_as_executable(submit_path) - - return submit_path - - def _set_file_as_executable(self, fname): - os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR - | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP - | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) - - def _set_test_code_file_path(self, ref_path=None, test_case_path=None): - # ref_path, test_case_path = self.ref_code_path.split(',') - - if ref_path and not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - if test_case_path and not test_case_path.startswith('/'): - test_case_path = join(MY_DIR, test_case_path) - - return ref_path, test_case_path - - def _run_command(self, cmd_args, *args, **kw): - """Run a command in a subprocess while blocking, the process is killed - if it takes more than 2 seconds to run. Return the Popen object, the - stdout and stderr. - """ - try: - proc = subprocess.Popen(cmd_args, *args, **kw) - stdout, stderr = proc.communicate() - except TimeoutException: - # Runaway code, so kill it. - proc.kill() - # Re-raise exception. - raise - return proc, stdout, stderr - - def _compile_command(self, cmd, *args, **kw): - """Compiles C/C++/java code and returns errors if any. - Run a command in a subprocess while blocking, the process is killed - if it takes more than 2 seconds to run. Return the Popen object, the - stderr. - """ - try: - proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = proc_compile.communicate() - except TimeoutException: - # Runaway code, so kill it. - proc_compile.kill() - # Re-raise exception. - raise - return proc_compile, err - - def _change_dir(self, in_dir): - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) - - ############################################################################### # `CodeServer` class. ############################################################################### @@ -219,34 +60,17 @@ class CodeServer(object): self.port = port self.queue = queue - def check_code(self, info_parameter, in_dir=None): - """Calls the TestCode Class to test the current code""" - info_parameter = json.loads(info_parameter) - test_parameter = info_parameter.get("test_parameter") - language = info_parameter.get("language") - user_answer = info_parameter.get("user_answer") - ref_code_path = info_parameter.get("ref_code_path") - - eval_module_name = "evaluate_{0}".format(language.lower()) - eval_class_name = "Evaluate{0}".format(language.capitalize()) - - get_class = self._sub_class_factory(eval_module_name, eval_class_name) + def check_code(self, info_parameter, language, in_dir=None): + """Calls the TestCode SUb Class based on language to test the current code""" + evaluate_code_class = registry.get_class(language) + evaluate_code_instance = evaluate_code_class.from_json(info_parameter, language, in_dir) + result = evaluate_code_instance.run_code() - test_code_class = get_class(test_parameter, language, user_answer, ref_code_path, in_dir) - result = test_code_class.run_code() # Put us back into the server pool queue since we are free now. self.queue.put(self.port) return json.dumps(result) - def _sub_class_factory(self, module_name, class_name): - # load the module, will raise ImportError if module cannot be loaded - get_module = importlib.import_module(module_name) - # get the class, will raise AttributeError if class cannot be found - get_class = getattr(get_module, class_name) - - return get_class - def run(self): """Run XMLRPC server, serving our methods.""" server = SimpleXMLRPCServer(("localhost", self.port)) diff --git a/testapp/exam/evaluate_bash.py b/testapp/exam/evaluate_bash.py index 57c89ae..fd769cd 100644 --- a/testapp/exam/evaluate_bash.py +++ b/testapp/exam/evaluate_bash.py @@ -7,16 +7,17 @@ import subprocess import importlib # local imports -from code_server import TestCode +from test_code import TestCode from registry import registry class EvaluateBash(TestCode): """Tests the Bash code obtained from Code Server""" def evaluate_code(self): - submit_path = self._create_submit_code_file('submit.sh') + fpath = self.create_submit_code_file('submit.sh') + submit_path = self.set_file_as_executable(fpath) get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') - ref_path, test_case_path = self._set_test_code_file_path(get_ref_path, get_test_case_path) + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, get_test_case_path) success = False success, err = self.check_bash_script(ref_path, submit_path, @@ -61,11 +62,11 @@ class EvaluateBash(TestCode): return False, "Script %s is not executable" % submit_path if test_case_path is None or "": - ret = self._run_command(ref_path, stdin=None, + ret = self.run_command(ref_path, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc, inst_stdout, inst_stderr = ret - ret = self._run_command(submit_path, stdin=None, + ret = self.run_command(submit_path, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc, stdnt_stdout, stdnt_stderr = ret @@ -92,12 +93,12 @@ class EvaluateBash(TestCode): loop_count += 1 if valid_answer: args = [ref_path] + [x for x in test_case.split()] - ret = self._run_command(args, stdin=None, + ret = self.run_command(args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc, inst_stdout, inst_stderr = ret args = [submit_path]+[x for x in test_case.split()] - ret = self._run_command(args, stdin=None, + ret = self.run_command(args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc, stdnt_stdout, stdnt_stderr = ret @@ -109,4 +110,4 @@ class EvaluateBash(TestCode): stdnt_stdout+stdnt_stderr) return False, err -registry.register('bash', evaluate_bash, EvaluateBash) \ No newline at end of file +registry.register('bash', EvaluateBash) \ No newline at end of file diff --git a/testapp/exam/evaluate_c.py b/testapp/exam/evaluate_c.py index bbe0e87..0700daa 100644 --- a/testapp/exam/evaluate_c.py +++ b/testapp/exam/evaluate_c.py @@ -7,29 +7,29 @@ import subprocess import importlib # local imports -from code_server import TestCode +from test_code import TestCode from registry import registry class EvaluateC(TestCode): """Tests the C code obtained from Code Server""" def evaluate_code(self): - submit_path = self._create_submit_code_file('submit.c') + submit_path = self.create_submit_code_file('submit.c') get_ref_path = self.ref_code_path - ref_path, test_case_path = self._set_test_code_file_path(get_ref_path) + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) success = False # Set file paths - c_user_output_path = os.getcwd() + '/output', - c_ref_output_path = os.getcwd() + '/executable', + c_user_output_path = os.getcwd() + '/output' + c_ref_output_path = os.getcwd() + '/executable' # Set command variables - compile_command = 'g++ {0} -c -o {1}'.format(submit_path, - c_user_output_path), + compile_command = 'g++ {0} -c -o {1}'.format(submit_path, + c_user_output_path) compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, c_user_output_path, - c_ref_output_path), - run_command_args = c_ref_output_path + c_ref_output_path) + run_command_args = [c_ref_output_path] remove_user_output = c_user_output_path remove_ref_output = c_ref_output_path @@ -72,21 +72,21 @@ class EvaluateC(TestCode): success = False # output_path = os.getcwd() + '/output' - ret = self._compile_command(compile_command) + ret = self.compile_command(compile_command) proc, stdnt_stderr = ret # if self.language == "java": - stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) + stdnt_stderr = self.remove_null_substitute_char(stdnt_stderr) # Only if compilation is successful, the program is executed # And tested with testcases if stdnt_stderr == '': - ret = self._compile_command(compile_main) + ret = self.compile_command(compile_main) proc, main_err = ret # if self.language == "java": - # main_err = self._remove_null_substitute_char(main_err) + main_err = self.remove_null_substitute_char(main_err) if main_err == '': - ret = self._run_command(run_command_args, stdin=None, + ret = self.run_command(run_command_args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc, stdout, stderr = ret @@ -121,7 +121,8 @@ class EvaluateC(TestCode): return success, err - def _remove_null_substitute_char(self, string): + + def remove_null_substitute_char(self, string): """Returns a string without any null and substitute characters""" stripped = "" for c in string: @@ -129,4 +130,4 @@ class EvaluateC(TestCode): stripped = stripped + c return ''.join(stripped) -registry.register('c', evaluate_c, EvaluateC) \ No newline at end of file +registry.register('c', EvaluateC) \ No newline at end of file diff --git a/testapp/exam/evaluate_cpp.py b/testapp/exam/evaluate_cpp.py index 87bd7a3..cffe744 100644 --- a/testapp/exam/evaluate_cpp.py +++ b/testapp/exam/evaluate_cpp.py @@ -8,7 +8,7 @@ import importlib # local imports from evaluate_c import EvaluateC -from code_server import TestCode +from test_code import TestCode from registry import registry @@ -16,21 +16,21 @@ from registry import registry class EvaluateCpp(EvaluateC, TestCode): """Tests the C code obtained from Code Server""" def evaluate_code(self): - submit_path = self._create_submit_code_file('submitstd.cpp') + submit_path = self.create_submit_code_file('submitstd.cpp') get_ref_path = self.ref_code_path - ref_path, test_case_path = self._set_test_code_file_path(get_ref_path) + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) success = False # Set file paths - c_user_output_path = os.getcwd() + '/output', - c_ref_output_path = os.getcwd() + '/executable', + c_user_output_path = os.getcwd() + '/output' + c_ref_output_path = os.getcwd() + '/executable' # Set command variables compile_command = 'g++ {0} -c -o {1}'.format(submit_path, - c_user_output_path), + c_user_output_path) compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, c_user_output_path, - c_ref_output_path), + c_ref_output_path) run_command_args = c_ref_output_path remove_user_output = c_user_output_path remove_ref_output = c_ref_output_path @@ -44,4 +44,4 @@ class EvaluateCpp(EvaluateC, TestCode): return success, err -registry.register('cpp', evaluate_cpp, EvaluateCpp) \ No newline at end of file +registry.register('cpp', EvaluateCpp) \ No newline at end of file diff --git a/testapp/exam/evaluate_java.py b/testapp/exam/evaluate_java.py index f908102..d3d4e2a 100644 --- a/testapp/exam/evaluate_java.py +++ b/testapp/exam/evaluate_java.py @@ -8,7 +8,7 @@ import importlib # local imports from evaluate_c import EvaluateC -from code_server import TestCode +from test_code import TestCode from registry import registry @@ -16,14 +16,14 @@ from registry import registry class EvaluateJava(EvaluateC, TestCode): """Tests the C code obtained from Code Server""" def evaluate_code(self): - submit_path = self._create_submit_code_file('Test.java') + submit_path = self.create_submit_code_file('Test.java') get_ref_path = self.ref_code_path - ref_path, test_case_path = self._set_test_code_file_path(get_ref_path) + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) success = False # Set file paths java_student_directory = os.getcwd() + '/' - java_ref_file_name = (ref_code_path.split('/')[-1]).split('.')[0], + java_ref_file_name = (ref_code_path.split('/')[-1]).split('.')[0] # Set command variables compile_command = 'javac {0}'.format(submit_code_path), @@ -46,4 +46,4 @@ class EvaluateJava(EvaluateC, TestCode): return success, err -registry.register('java', evaluate_java, EvaluateJava) \ No newline at end of file +registry.register('java', EvaluateJava) \ No newline at end of file diff --git a/testapp/exam/evaluate_python.py b/testapp/exam/evaluate_python.py index 18fde43..3af82b6 100644 --- a/testapp/exam/evaluate_python.py +++ b/testapp/exam/evaluate_python.py @@ -6,7 +6,7 @@ from os.path import join import importlib # local imports -from code_server import TestCode +from test_code import TestCode from registry import registry class EvaluatePython(TestCode): @@ -36,6 +36,7 @@ class EvaluatePython(TestCode): del tb return success, err + # Private Protocol def _create_test_case(self): """ Create assert based test cases in python @@ -52,4 +53,4 @@ class EvaluatePython(TestCode): test_code += tcode + "\n" return test_code -registry.register('python', evaluate_python, EvaluatePython) \ No newline at end of file +registry.register('python', EvaluatePython) \ No newline at end of file diff --git a/testapp/exam/evaluate_scilab.py b/testapp/exam/evaluate_scilab.py index 1874cf6..f4253ff 100644 --- a/testapp/exam/evaluate_scilab.py +++ b/testapp/exam/evaluate_scilab.py @@ -7,21 +7,21 @@ import re import importlib # local imports -from code_server import TestCode -from registry import registryz +from test_code import TestCode +from registry import registry class EvaluateScilab(TestCode): """Tests the Scilab code obtained from Code Server""" # def evaluate_scilab_code(self): def evaluate_code(self): - submit_path = self._create_submit_code_file('function.sci') - ref_path, test_case_path = self._set_test_code_file_path() + submit_path = self.create_submit_code_file('function.sci') + ref_path, test_case_path = self.set_test_code_file_path() success = False cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) cmd += ' | timeout 8 scilab-cli -nb' - ret = self._run_command(cmd, + ret = self.run_command(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -44,6 +44,7 @@ class EvaluateScilab(TestCode): return success, err + # Private Protocol def _remove_scilab_exit(self, string): """ Removes exit, quit and abort from the scilab code @@ -79,4 +80,4 @@ class EvaluateScilab(TestCode): strip_out = strip_out+"\n"+l.strip() return strip_out -registry.register('scilab', evaluate_scilab, EvaluateScilab) \ No newline at end of file +registry.register('scilab', EvaluateScilab) \ No newline at end of file diff --git a/testapp/exam/java_files/main_array_sum.java b/testapp/exam/java_files/main_array_sum.java new file mode 100644 index 0000000..5eae299 --- /dev/null +++ b/testapp/exam/java_files/main_array_sum.java @@ -0,0 +1,36 @@ +class main_array_sum +{ + public static void check(E expect, E result) + { + if(result.equals(expect)) + { + System.out.println("Correct:\nOutput expected "+expect+" and got "+result); + } + else + { + System.out.println("Incorrect:\nOutput expected "+expect+" but got "+result); + System.exit(1); + } + } + public static void main(String arg[]) + { + int result; + Test t = new Test(); + int x[] = {0,0,0,0,0}; + result = t.array_sum(x); + System.out.println("Input submitted to the function: {0,0,0,0,0}"); + check(0, result); + int a[] = {1,2,3,4,5}; + result = t.array_sum(a); + System.out.println("Input submitted to the function: {1,2,3,4,5}"); + check(15, result); + int b[] = {1,2,3,0,0}; + result = t.array_sum(b); + System.out.println("Input submitted to the function: {1,2,3,0,0}"); + check(6, result); + int c[] = {1,1,1,1,1}; + result = t.array_sum(c); + System.out.println("Input submitted to the function: {1,1,1,1,1}"); + check(5, result); + } +} diff --git a/testapp/exam/java_files/main_fact.java b/testapp/exam/java_files/main_fact.java new file mode 100644 index 0000000..325dab6 --- /dev/null +++ b/testapp/exam/java_files/main_fact.java @@ -0,0 +1,29 @@ +class main_fact +{ + public static void check(E expect, E result) + { + if(result.equals(expect)) + { + System.out.println("Correct:\nOutput expected "+expect+" and got "+result); + } + else + { + System.out.println("Incorrect:\nOutput expected "+expect+" but got "+result); + System.exit(1); + } + } + public static void main(String arg[]) + { + Test t = new Test(); + int result; + result = t.factorial(0); + System.out.println("Input submitted to the function: 0"); + check(1, result); + result = t.factorial(3); + System.out.println("Input submitted to the function: 3"); + check(6, result); + result = t.factorial(4); + System.out.println("Input submitted to the function: 4"); + check(24, result); + } +} diff --git a/testapp/exam/java_files/main_great.java b/testapp/exam/java_files/main_great.java new file mode 100644 index 0000000..4bfcb1f --- /dev/null +++ b/testapp/exam/java_files/main_great.java @@ -0,0 +1,39 @@ +class main_great +{ + public static void check(E expect, E result) + { + if(result.equals(expect)) + { + System.out.println("Correct:\nOutput expected "+expect+" and got "+result); + } + else + { + System.out.println("Incorrect:\nOutput expected "+expect+" but got "+result); + System.exit(1); + } + } + public static void main(String arg[]) + { + Test t = new Test(); + int result; + result = t.greatest(1, 3, 4); + System.out.println("Input submitted to the function: 1, 3, 4"); + check(4, result); + result = t.greatest(5, 10, 3); + System.out.println("Input submitted to the function: 5, 10, 3"); + check(10, result); + result = t.greatest(6, 1, 4); + System.out.println("Input submitted to the function: 6, 1, 4"); + check(6, result); + result = t.greatest(6, 11, 14); + System.out.println("Input submitted to the function: 6, 11, 14"); + check(14, result); + result = t.greatest(3, 31, 4); + System.out.println("Input submitted to the function: 3, 31, 4"); + check(31, result); + result = t.greatest(26, 13, 3); + System.out.println("Input submitted to the function: 26, 13, 3"); + check(26, result); + + } +} diff --git a/testapp/exam/java_files/main_hello_name.java b/testapp/exam/java_files/main_hello_name.java new file mode 100644 index 0000000..84bb282 --- /dev/null +++ b/testapp/exam/java_files/main_hello_name.java @@ -0,0 +1,29 @@ +class main_hello_name +{ + public static void check(E expect, E result) + { + if(result.equals(expect)) + { + System.out.println("Correct:\nOutput expected "+expect+" and got "+result); + } + else + { + System.out.println("Incorrect:\nOutput expected "+expect+" but got "+result); + System.exit(1); + } + } + public static void main(String arg[]) + { + Test t = new Test(); + String result; + result = t.hello_name("Raj"); + System.out.println("Input submitted to the function: 'Raj'"); + check("hello Raj", result); + result = t.hello_name("Pratham"); + System.out.println("Input submitted to the function: 'Pratham'"); + check("hello Pratham", result); + result = t.hello_name("Ram"); + System.out.println("Input submitted to the function: 'Ram'"); + check("hello Ram", result); + } +} diff --git a/testapp/exam/java_files/main_lastDigit.java b/testapp/exam/java_files/main_lastDigit.java new file mode 100644 index 0000000..05439e2 --- /dev/null +++ b/testapp/exam/java_files/main_lastDigit.java @@ -0,0 +1,36 @@ +class main_lastDigit +{ + public static void check(E expect, E result) + { + if(result.equals(expect)) + { + System.out.println("Correct:\nOutput expected "+expect+" and got "+result+"\n"); + } + else + { + System.out.println("Incorrect:\nOutput expected "+expect+" but got "+result+"\n"); + System.exit(1); + } + } + public static void main(String arg[]) + { + Test t = new Test(); + boolean result; + result= t.lastDigit(12, 2, 13); + System.out.println("Input submitted to the function: 12, 2, 13"); + check(true, result); + result = t.lastDigit(11, 52, 32); + System.out.println("Input submitted to the function: 11, 52, 32"); + check(true, result); + result = t.lastDigit(6, 34, 22); + System.out.println("Input submitted to the function: 6, 34, 22"); + check(false, result); + result = t.lastDigit(6, 46, 26); + System.out.println("Input submitted to the function: 63"); + check(true, result); + result = t.lastDigit(91, 90, 92); + System.out.println("Input submitted to the function: 91"); + check(false, result); + + } +} diff --git a/testapp/exam/java_files/main_moreThan30.java b/testapp/exam/java_files/main_moreThan30.java new file mode 100644 index 0000000..7da31cb --- /dev/null +++ b/testapp/exam/java_files/main_moreThan30.java @@ -0,0 +1,36 @@ +class main_moreThan30 +{ + public static void check(E expect, E result) + { + if(result.equals(expect)) + { + System.out.println("Correct:\nOutput expected "+expect+" and got "+result+"\n"); + } + else + { + System.out.println("Incorrect:\nOutput expected "+expect+" but got "+result+"\n"); + System.exit(1); + } + } + public static void main(String arg[]) + { + Test t = new Test(); + boolean result; + result= t.moreThan30(30); + System.out.println("Input submitted to the function: 30"); + check(false, result); + result = t.moreThan30(151); + System.out.println("Input submitted to the function: 151"); + check(true, result); + result = t.moreThan30(66); + System.out.println("Input submitted to the function: 66"); + check(false, result); + result = t.moreThan30(63); + System.out.println("Input submitted to the function: 63"); + check(true, result); + result = t.moreThan30(91); + System.out.println("Input submitted to the function: 91"); + check(true, result); + + } +} diff --git a/testapp/exam/java_files/main_palindrome.java b/testapp/exam/java_files/main_palindrome.java new file mode 100644 index 0000000..c0745f9 --- /dev/null +++ b/testapp/exam/java_files/main_palindrome.java @@ -0,0 +1,29 @@ +class main_palindrome +{ + public static void check(E expect, E result) + { + if(result.equals(expect)) + { + System.out.println("Correct:\nOutput expected "+expect+" and got "+result+"\n"); + } + else + { + System.out.println("Incorrect:\nOutput expected "+expect+" but got "+result+"\n"); + System.exit(1); + } + } + public static void main(String arg[]) + { + Test t = new Test(); + boolean result; + result= t.palindrome(123); + System.out.println("Input submitted to the function: 123"); + check(false, result); + result = t.palindrome(151); + System.out.println("Input submitted to the function: 151"); + check(true, result); + result = t.palindrome(23432); + System.out.println("Input submitted to the function: 23432"); + check(true, result); + } +} diff --git a/testapp/exam/java_files/main_square.java b/testapp/exam/java_files/main_square.java new file mode 100644 index 0000000..5cb8c35 --- /dev/null +++ b/testapp/exam/java_files/main_square.java @@ -0,0 +1,32 @@ +class main_square +{ + public static void check(E expect, E result) + { + if(result.equals(expect)) + { + System.out.println("Correct:\nOutput expected "+expect+" and got "+result); + } + else + { + System.out.println("Incorrect:\nOutput expected "+expect+" but got "+result); + System.exit(1); + } + } + public static void main(String arg[]) + { + Test t = new Test(); + int result, input, output; + input = 0; output = 0; + result = t.square_num(input); + System.out.println("Input submitted to the function: "+input); + check(output, result); + input = 5; output = 25; + result = t.square_num(input); + System.out.println("Input submitted to the function: "+input); + check(output, result); + input = 6; output = 36; + result = t.square_num(input); + System.out.println("Input submitted to the function: "+input); + check(output, result); + } +} diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 874c81c..d0c9cc2 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -87,7 +87,7 @@ class Question(models.Model): # Tags for the Question. tags = TaggableManager() - def consolidate_answer_data(self, test_cases, user_answer): #test + def consolidate_answer_data(self, test_cases, user_answer): test_case_parameter = [] info_parameter = {} @@ -113,7 +113,7 @@ class Question(models.Model): parameter_dict['pos_args'] = pos_args_list test_case_parameter.append(parameter_dict) - info_parameter['language'] = self.language + # info_parameter['language'] = self.language info_parameter['id'] = self.id info_parameter['user_answer'] = user_answer info_parameter['test_parameter'] = test_case_parameter diff --git a/testapp/exam/scilab_files/test_add.sce b/testapp/exam/scilab_files/test_add.sce new file mode 100644 index 0000000..a317cdb --- /dev/null +++ b/testapp/exam/scilab_files/test_add.sce @@ -0,0 +1,29 @@ +mode(-1) +exec("function.sci",-1); +i = 0 +p = add(3,5); +correct = (p == 8); +if correct then + i=i+1 +end +disp("Input submitted 3 and 5") +disp("Expected output 8 got " + string(p)) +p = add(22,-20); +correct = (p==2); +if correct then + i=i+1 +end +disp("Input submitted 22 and -20") +disp("Expected output 2 got " + string(p)) +p =add(91,0); +correct = (p==91); +if correct then + i=i+1 +end +disp("Input submitted 91 and 0") +disp("Expected output 91 got " + string(p)) +if i==3 then + exit(5); +else + exit(3); +end diff --git a/testapp/exam/test_code.py b/testapp/exam/test_code.py new file mode 100644 index 0000000..8930f55 --- /dev/null +++ b/testapp/exam/test_code.py @@ -0,0 +1,182 @@ +import sys +from SimpleXMLRPCServer import SimpleXMLRPCServer +import pwd +import os +import stat +from os.path import isdir, dirname, abspath, join, isfile +import signal +from multiprocessing import Process, Queue +import subprocess +import re +import json +import importlib +# Local imports. +from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT + + +MY_DIR = abspath(dirname(__file__)) + +# Raised when the code times-out. +# c.f. http://pguides.net/python/timeout-a-function +class TimeoutException(Exception): + pass + + +def timeout_handler(signum, frame): + """A handler for the ALARM signal.""" + raise TimeoutException('Code took too long to run.') + +def create_signal_handler(): + """Add a new signal handler for the execution of this code.""" + prev_handler = signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(SERVER_TIMEOUT) + return prev_handler + +def set_original_signal_handler(old_handler=None): + """Set back any original signal handler.""" + if old_handler is not None: + signal.signal(signal.SIGALRM, old_handler) + return + else: + raise Exception("Signal Handler: object cannot be NoneType") + +def delete_signal_handler(): + signal.alarm(0) + return + + + +############################################################################### +# `TestCode` class. +############################################################################### +class TestCode(object): + """Tests the code obtained from Code Server""" + def __init__(self, test_parameter, language, user_answer, ref_code_path=None, in_dir=None): + msg = 'Code took more than %s seconds to run. You probably '\ + 'have an infinite loop in your code.' % SERVER_TIMEOUT + self.timeout_msg = msg + self.test_parameter = test_parameter + self.language = language.lower() + self.user_answer = user_answer + self.ref_code_path = ref_code_path + self.in_dir = in_dir + + @classmethod + def from_json(cls, blob, language, in_dir): + info_parameter = json.loads(blob) + test_parameter = info_parameter.get("test_parameter") + user_answer = info_parameter.get("user_answer") + ref_code_path = info_parameter.get("ref_code_path") + + instance = cls(test_parameter, language, user_answer, ref_code_path, in_dir) + return instance + + def run_code(self): + """Tests given code (`answer`) with the test cases based on + given arguments. + + The ref_code_path is a path to the reference code. + The reference code will call the function submitted by the student. + The reference code will check for the expected output. + + If the path's start with a "/" then we assume they are absolute paths. + If not, we assume they are relative paths w.r.t. the location of this + code_server script. + + If the optional `in_dir` keyword argument is supplied it changes the + directory to that directory (it does not change it back to the original + when done). + + Returns + ------- + + A tuple: (success, error message). + """ + self._change_dir(self.in_dir) + + # Add a new signal handler for the execution of this code. + prev_handler = create_signal_handler() + success = False + + # Do whatever testing needed. + try: + success, err = self.evaluate_code() + + except TimeoutException: + err = self.timeout_msg + except: + type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + finally: + # Set back any original signal handler. + set_original_signal_handler(prev_handler) + + # Cancel the signal + delete_signal_handler() + + result = {'success': success, 'error': err} + return result + + def evaluate_code(self): + raise NotImplementedError("evaluate_code method not implemented") + + def create_submit_code_file(self, file_name): + """ Write the code (`answer`) to a file and set the file path""" + # File name/extension depending on the question language + submit_f = open(file_name, 'w') + submit_f.write(self.user_answer.lstrip()) + submit_f.close() + submit_path = abspath(submit_f.name) + + return submit_path + + def set_file_as_executable(self, fname): + os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR + | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP + | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) + + def set_test_code_file_path(self, ref_path=None, test_case_path=None): + if ref_path and not ref_path.startswith('/'): + ref_path = join(MY_DIR, ref_path) + + if test_case_path and not test_case_path.startswith('/'): + test_case_path = join(MY_DIR, test_case_path) + + return ref_path, test_case_path + + def run_command(self, cmd_args, *args, **kw): + """Run a command in a subprocess while blocking, the process is killed + if it takes more than 2 seconds to run. Return the Popen object, the + stdout and stderr. + """ + try: + proc = subprocess.Popen(cmd_args, *args, **kw) + stdout, stderr = proc.communicate() + except TimeoutException: + # Runaway code, so kill it. + proc.kill() + # Re-raise exception. + raise + return proc, stdout, stderr + + def compile_command(self, cmd, *args, **kw): + """Compiles C/C++/java code and returns errors if any. + Run a command in a subprocess while blocking, the process is killed + if it takes more than 2 seconds to run. Return the Popen object, the + stderr. + """ + try: + proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc_compile.communicate() + except TimeoutException: + # Runaway code, so kill it. + proc_compile.kill() + # Re-raise exception. + raise + return proc_compile, err + + def _change_dir(self, in_dir): + if in_dir is not None and isdir(in_dir): + os.chdir(in_dir) \ No newline at end of file diff --git a/testapp/exam/views.py b/testapp/exam/views.py index 840f30a..508b623 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -1025,7 +1025,7 @@ def validate_answer(user, user_answer, question, info_parameter=None): message = 'Correct answer' elif question.type == 'code': user_dir = get_user_dir(user) - json_result = code_server.run_code(info_parameter, user_dir) + json_result = code_server.run_code(info_parameter, question.language, user_dir) result = json.loads(json_result) if result.get('success'): correct = True diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index 33fbc57..5d95cae 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -22,7 +22,7 @@ class CodeServerProxy(object): pool_url = 'http://localhost:%d' % (SERVER_POOL_PORT) self.pool_server = ServerProxy(pool_url) - def run_code(self, info_parameter, user_dir): + def run_code(self, info_parameter, language, user_dir): """Tests given code (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change it back to the original when @@ -50,7 +50,7 @@ class CodeServerProxy(object): try: server = self._get_server() - result = server.check_code(info_parameter, user_dir) + result = server.check_code(info_parameter, language, user_dir) except ConnectionError: result = json.dumps({'success': False, 'error': 'Unable to connect to any code servers!'}) return result -- cgit From 8664a766406d6acf0d6a1688948153c407ea27f2 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Fri, 24 Apr 2015 14:25:26 +0530 Subject: Code Review: Code refactoring - Rename files - Create function for @classmethod call - Fix current, add new testcases - Fix views to fetch solution/ref_code_path fields in question post save - Fix errors --- testapp/exam/bash_files/sample.args | 2 + testapp/exam/bash_files/sample.sh | 2 + testapp/exam/code_server.py | 34 ++++--- testapp/exam/evaluate_bash.py | 113 --------------------- testapp/exam/evaluate_bash_code.py | 119 ++++++++++++++++++++++ testapp/exam/evaluate_c.py | 133 ------------------------- testapp/exam/evaluate_c_code.py | 136 +++++++++++++++++++++++++ testapp/exam/evaluate_code.py | 186 +++++++++++++++++++++++++++++++++++ testapp/exam/evaluate_cpp.py | 47 --------- testapp/exam/evaluate_cpp_code.py | 48 +++++++++ testapp/exam/evaluate_java.py | 49 --------- testapp/exam/evaluate_java_code.py | 49 +++++++++ testapp/exam/evaluate_python.py | 56 ----------- testapp/exam/evaluate_python_code.py | 58 +++++++++++ testapp/exam/evaluate_scilab.py | 83 ---------------- testapp/exam/evaluate_scilab_code.py | 86 ++++++++++++++++ testapp/exam/language_registry.py | 16 +++ testapp/exam/models.py | 30 +++--- testapp/exam/registry.py | 14 --- testapp/exam/test_code.py | 182 ---------------------------------- testapp/exam/views.py | 19 ++-- testapp/exam/xmlrpc_clients.py | 6 +- 22 files changed, 756 insertions(+), 712 deletions(-) create mode 100644 testapp/exam/bash_files/sample.args create mode 100755 testapp/exam/bash_files/sample.sh delete mode 100644 testapp/exam/evaluate_bash.py create mode 100644 testapp/exam/evaluate_bash_code.py delete mode 100644 testapp/exam/evaluate_c.py create mode 100644 testapp/exam/evaluate_c_code.py create mode 100644 testapp/exam/evaluate_code.py delete mode 100644 testapp/exam/evaluate_cpp.py create mode 100644 testapp/exam/evaluate_cpp_code.py delete mode 100644 testapp/exam/evaluate_java.py create mode 100644 testapp/exam/evaluate_java_code.py delete mode 100644 testapp/exam/evaluate_python.py create mode 100644 testapp/exam/evaluate_python_code.py delete mode 100644 testapp/exam/evaluate_scilab.py create mode 100644 testapp/exam/evaluate_scilab_code.py create mode 100644 testapp/exam/language_registry.py delete mode 100644 testapp/exam/registry.py delete mode 100644 testapp/exam/test_code.py (limited to 'testapp/exam') diff --git a/testapp/exam/bash_files/sample.args b/testapp/exam/bash_files/sample.args new file mode 100644 index 0000000..4d9f00d --- /dev/null +++ b/testapp/exam/bash_files/sample.args @@ -0,0 +1,2 @@ +1 2 +2 1 diff --git a/testapp/exam/bash_files/sample.sh b/testapp/exam/bash_files/sample.sh new file mode 100755 index 0000000..e935cb3 --- /dev/null +++ b/testapp/exam/bash_files/sample.sh @@ -0,0 +1,2 @@ +#!/bin/bash +[[ $# -eq 2 ]] && echo $(( $1 + $2 )) && exit $(( $1 + $2 )) diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index db30798..111562a 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -32,16 +32,17 @@ import json import importlib # Local imports. from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT -from registry import registry -from evaluate_python import EvaluatePython -from evaluate_c import EvaluateC -from evaluate_cpp import EvaluateCpp -from evaluate_java import EvaluateJava -from evaluate_scilab import EvaluateScilab -from evaluate_bash import EvaluateBash +from language_registry import registry +from evaluate_python_code import EvaluatePythonCode +from evaluate_c_code import EvaluateCCode +from evaluate_cpp_code import EvaluateCppCode +from evaluate_java_code import EvaluateJavaCode +from evaluate_scilab_code import EvaluateScilabCode +from evaluate_bash_code import EvaluateBashCode MY_DIR = abspath(dirname(__file__)) +## Private Protocol ########## def run_as_nobody(): """Runs the current process as nobody.""" # Set the effective uid and to that of nobody. @@ -60,10 +61,11 @@ class CodeServer(object): self.port = port self.queue = queue - def check_code(self, info_parameter, language, in_dir=None): - """Calls the TestCode SUb Class based on language to test the current code""" - evaluate_code_class = registry.get_class(language) - evaluate_code_instance = evaluate_code_class.from_json(info_parameter, language, in_dir) + ## Public Protocol ########## + def check_code(self, language, json_data, in_dir=None): + """Calls relevant EvaluateCode class based on language to check the answer code""" + evaluate_code_instance = self.create_class_instance(language, json_data, in_dir) + result = evaluate_code_instance.run_code() # Put us back into the server pool queue since we are free now. @@ -71,6 +73,14 @@ class CodeServer(object): return json.dumps(result) + ## Public Protocol ########## + def create_class_instance(self, language, json_data, in_dir): + """Create instance of relevant EvaluateCode class based on language""" + cls = registry.get_class(language) + instance = cls.from_json(language, json_data, in_dir) + return instance + + ## Public Protocol ########## def run(self): """Run XMLRPC server, serving our methods.""" server = SimpleXMLRPCServer(("localhost", self.port)) @@ -110,6 +120,8 @@ class ServerPool(object): p.start() self.servers = servers + ## Public Protocol ########## + def get_server_port(self): """Get available server port from ones in the pool. This will block till it gets an available server. diff --git a/testapp/exam/evaluate_bash.py b/testapp/exam/evaluate_bash.py deleted file mode 100644 index fd769cd..0000000 --- a/testapp/exam/evaluate_bash.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from test_code import TestCode -from registry import registry - - -class EvaluateBash(TestCode): - """Tests the Bash code obtained from Code Server""" - def evaluate_code(self): - fpath = self.create_submit_code_file('submit.sh') - submit_path = self.set_file_as_executable(fpath) - get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, get_test_case_path) - success = False - - success, err = self.check_bash_script(ref_path, submit_path, - test_case_path) - - # Delete the created file. - os.remove(submit_path) - - return success, err - - def check_bash_script(self, ref_path, submit_path, - test_case_path=None): - """ Function validates student script using instructor script as - reference. Test cases can optionally be provided. The first argument - ref_path, is the path to instructor script, it is assumed to - have executable permission. The second argument submit_path, is - the path to the student script, it is assumed to have executable - permission. The Third optional argument is the path to test the - scripts. Each line in this file is a test case and each test case is - passed to the script as standard arguments. - - Returns - -------- - - returns (True, "Correct answer") : If the student script passes all - test cases/have same output, when compared to the instructor script - - returns (False, error_msg): If the student script fails a single - test/have dissimilar output, when compared to the instructor script. - - Returns (False, error_msg): If mandatory arguments are not files or if - the required permissions are not given to the file(s). - - """ - if not isfile(ref_path): - return False, "No file at %s or Incorrect path" % ref_path - if not isfile(submit_path): - return False, "No file at %s or Incorrect path" % submit_path - if not os.access(ref_path, os.X_OK): - return False, "Script %s is not executable" % ref_path - if not os.access(submit_path, os.X_OK): - return False, "Script %s is not executable" % submit_path - - if test_case_path is None or "": - ret = self.run_command(ref_path, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, inst_stdout, inst_stderr = ret - ret = self.run_command(submit_path, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdnt_stdout, stdnt_stderr = ret - if inst_stdout == stdnt_stdout: - return True, "Correct answer" - else: - err = "Error: expected %s, got %s" % (inst_stderr, - stdnt_stderr) - return False, err - else: - if not isfile(test_case_path): - return False, "No test case at %s" % test_case_path - if not os.access(ref_path, os.R_OK): - return False, "Test script %s, not readable" % test_case_path - valid_answer = True # We initially make it one, so that we can - # stop once a test case fails - loop_count = 0 # Loop count has to be greater than or - # equal to one. - # Useful for caching things like empty - # test files,etc. - test_cases = open(test_case_path).readlines() - num_lines = len(test_cases) - for test_case in test_cases: - loop_count += 1 - if valid_answer: - args = [ref_path] + [x for x in test_case.split()] - ret = self.run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, inst_stdout, inst_stderr = ret - args = [submit_path]+[x for x in test_case.split()] - ret = self.run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdnt_stdout, stdnt_stderr = ret - valid_answer = inst_stdout == stdnt_stdout - if valid_answer and (num_lines == loop_count): - return True, "Correct answer" - else: - err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, - stdnt_stdout+stdnt_stderr) - return False, err - -registry.register('bash', EvaluateBash) \ No newline at end of file diff --git a/testapp/exam/evaluate_bash_code.py b/testapp/exam/evaluate_bash_code.py new file mode 100644 index 0000000..49f20fa --- /dev/null +++ b/testapp/exam/evaluate_bash_code.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from evaluate_code import EvaluateCode +from language_registry import registry + + +class EvaluateBashCode(EvaluateCode): + """Tests the Bash code obtained from Code Server""" + ## Public Protocol ########## + def evaluate_code(self): + submit_path = self.create_submit_code_file('submit.sh') + self.set_file_as_executable(submit_path) + get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') + get_ref_path = get_ref_path.strip() + get_test_case_path = get_test_case_path.strip() + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, get_test_case_path) + + success, err = self._check_bash_script(ref_path, submit_path, + test_case_path) + + # Delete the created file. + os.remove(submit_path) + + return success, err + + ## Private Protocol ########## + def _check_bash_script(self, ref_path, submit_path, + test_case_path=None): + """ Function validates student script using instructor script as + reference. Test cases can optionally be provided. The first argument + ref_path, is the path to instructor script, it is assumed to + have executable permission. The second argument submit_path, is + the path to the student script, it is assumed to have executable + permission. The Third optional argument is the path to test the + scripts. Each line in this file is a test case and each test case is + passed to the script as standard arguments. + + Returns + -------- + + returns (True, "Correct answer") : If the student script passes all + test cases/have same output, when compared to the instructor script + + returns (False, error_msg): If the student script fails a single + test/have dissimilar output, when compared to the instructor script. + + Returns (False, error_msg): If mandatory arguments are not files or if + the required permissions are not given to the file(s). + + """ + if not isfile(ref_path): + return False, "No file at %s or Incorrect path" % ref_path + if not isfile(submit_path): + return False, "No file at %s or Incorrect path" % submit_path + if not os.access(ref_path, os.X_OK): + return False, "Script %s is not executable" % ref_path + if not os.access(submit_path, os.X_OK): + return False, "Script %s is not executable" % submit_path + + success = False + + if test_case_path is None or "": + ret = self.run_command(ref_path, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, inst_stdout, inst_stderr = ret + ret = self.run_command(submit_path, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdnt_stdout, stdnt_stderr = ret + if inst_stdout == stdnt_stdout: + return True, "Correct answer" + else: + err = "Error: expected %s, got %s" % (inst_stderr, + stdnt_stderr) + return False, err + else: + if not isfile(test_case_path): + return False, "No test case at %s" % test_case_path + if not os.access(ref_path, os.R_OK): + return False, "Test script %s, not readable" % test_case_path + valid_answer = True # We initially make it one, so that we can + # stop once a test case fails + loop_count = 0 # Loop count has to be greater than or + # equal to one. + # Useful for caching things like empty + # test files,etc. + test_cases = open(test_case_path).readlines() + num_lines = len(test_cases) + for test_case in test_cases: + loop_count += 1 + if valid_answer: + args = [ref_path] + [x for x in test_case.split()] + ret = self.run_command(args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, inst_stdout, inst_stderr = ret + args = [submit_path]+[x for x in test_case.split()] + ret = self.run_command(args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdnt_stdout, stdnt_stderr = ret + valid_answer = inst_stdout == stdnt_stdout + if valid_answer and (num_lines == loop_count): + return True, "Correct answer" + else: + err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, + stdnt_stdout+stdnt_stderr) + return False, err + + +registry.register('bash', EvaluateBashCode) \ No newline at end of file diff --git a/testapp/exam/evaluate_c.py b/testapp/exam/evaluate_c.py deleted file mode 100644 index 0700daa..0000000 --- a/testapp/exam/evaluate_c.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from test_code import TestCode -from registry import registry - - -class EvaluateC(TestCode): - """Tests the C code obtained from Code Server""" - def evaluate_code(self): - submit_path = self.create_submit_code_file('submit.c') - get_ref_path = self.ref_code_path - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) - success = False - - # Set file paths - c_user_output_path = os.getcwd() + '/output' - c_ref_output_path = os.getcwd() + '/executable' - - # Set command variables - compile_command = 'g++ {0} -c -o {1}'.format(submit_path, - c_user_output_path) - compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, - c_user_output_path, - c_ref_output_path) - run_command_args = [c_ref_output_path] - remove_user_output = c_user_output_path - remove_ref_output = c_ref_output_path - - success, err = self.check_code(ref_path, submit_path, compile_command, - compile_main, run_command_args, - remove_user_output, remove_ref_output) - - # Delete the created file. - os.remove(submit_path) - - return success, err - - def check_code(self, ref_code_path, submit_code_path, compile_command, - compile_main, run_command_args, remove_user_output, - remove_ref_output): - """ Function validates student code using instructor code as - reference.The first argument ref_code_path, is the path to - instructor code, it is assumed to have executable permission. - The second argument submit_code_path, is the path to the student - code, it is assumed to have executable permission. - - Returns - -------- - - returns (True, "Correct answer") : If the student function returns - expected output when called by reference code. - - returns (False, error_msg): If the student function fails to return - expected output when called by reference code. - - Returns (False, error_msg): If mandatory arguments are not files or - if the required permissions are not given to the file(s). - - """ - - if not isfile(ref_code_path): - return False, "No file at %s or Incorrect path" % ref_code_path - if not isfile(submit_code_path): - return False, 'No file at %s or Incorrect path' % submit_code_path - - success = False - # output_path = os.getcwd() + '/output' - ret = self.compile_command(compile_command) - proc, stdnt_stderr = ret - # if self.language == "java": - stdnt_stderr = self.remove_null_substitute_char(stdnt_stderr) - - # Only if compilation is successful, the program is executed - # And tested with testcases - if stdnt_stderr == '': - ret = self.compile_command(compile_main) - proc, main_err = ret - # if self.language == "java": - main_err = self.remove_null_substitute_char(main_err) - - if main_err == '': - ret = self.run_command(run_command_args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - if proc.returncode == 0: - success, err = True, "Correct answer" - else: - err = stdout + "\n" + stderr - os.remove(remove_ref_output) - else: - err = "Error:" - try: - error_lines = main_err.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + main_err - os.remove(remove_user_output) - else: - err = "Compilation Error:" - try: - error_lines = stdnt_stderr.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + stdnt_stderr - - return success, err - - - def remove_null_substitute_char(self, string): - """Returns a string without any null and substitute characters""" - stripped = "" - for c in string: - if ord(c) is not 26 and ord(c) is not 0: - stripped = stripped + c - return ''.join(stripped) - -registry.register('c', EvaluateC) \ No newline at end of file diff --git a/testapp/exam/evaluate_c_code.py b/testapp/exam/evaluate_c_code.py new file mode 100644 index 0000000..d52beae --- /dev/null +++ b/testapp/exam/evaluate_c_code.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from evaluate_code import EvaluateCode +from language_registry import registry + + +class EvaluateCCode(EvaluateCode): + """Tests the C code obtained from Code Server""" + ## Public Protocol ########## + def evaluate_code(self): + submit_path = self.create_submit_code_file('submit.c') + get_ref_path = self.ref_code_path + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) + success = False + + # Set file paths + c_user_output_path = os.getcwd() + '/output' + c_ref_output_path = os.getcwd() + '/executable' + + # Set command variables + compile_command = 'g++ {0} -c -o {1}'.format(submit_path, + c_user_output_path) + compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, + c_user_output_path, + c_ref_output_path) + run_command_args = [c_ref_output_path] + remove_user_output = c_user_output_path + remove_ref_output = c_ref_output_path + + success, err = self.check_code(ref_path, submit_path, compile_command, + compile_main, run_command_args, + remove_user_output, remove_ref_output) + + # Delete the created file. + os.remove(submit_path) + + return success, err + + ## Public Protocol ########## + def check_code(self, ref_code_path, submit_code_path, compile_command, + compile_main, run_command_args, remove_user_output, + remove_ref_output): + """ Function validates student code using instructor code as + reference.The first argument ref_code_path, is the path to + instructor code, it is assumed to have executable permission. + The second argument submit_code_path, is the path to the student + code, it is assumed to have executable permission. + + Returns + -------- + + returns (True, "Correct answer") : If the student function returns + expected output when called by reference code. + + returns (False, error_msg): If the student function fails to return + expected output when called by reference code. + + Returns (False, error_msg): If mandatory arguments are not files or + if the required permissions are not given to the file(s). + + """ + if not isfile(ref_code_path): + return False, "No file at %s or Incorrect path" % ref_code_path + if not isfile(submit_code_path): + return False, 'No file at %s or Incorrect path' % submit_code_path + + success = False + # output_path = os.getcwd() + '/output' + ret = self.compile_command(compile_command) + proc, stdnt_stderr = ret + # if self.language == "java": + stdnt_stderr = self.remove_null_substitute_char(stdnt_stderr) + + # Only if compilation is successful, the program is executed + # And tested with testcases + if stdnt_stderr == '': + ret = self.compile_command(compile_main) + proc, main_err = ret + # if self.language == "java": + main_err = self.remove_null_substitute_char(main_err) + + if main_err == '': + ret = self.run_command(run_command_args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + if proc.returncode == 0: + success, err = True, "Correct answer" + else: + err = stdout + "\n" + stderr + os.remove(remove_ref_output) + else: + err = "Error:" + try: + error_lines = main_err.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + main_err + os.remove(remove_user_output) + else: + err = "Compilation Error:" + try: + error_lines = stdnt_stderr.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + stdnt_stderr + + return success, err + + + ## Public Protocol ########## + def remove_null_substitute_char(self, string): + """Returns a string without any null and substitute characters""" + stripped = "" + for c in string: + if ord(c) is not 26 and ord(c) is not 0: + stripped = stripped + c + return ''.join(stripped) + + +registry.register('c', EvaluateCCode) \ No newline at end of file diff --git a/testapp/exam/evaluate_code.py b/testapp/exam/evaluate_code.py new file mode 100644 index 0000000..161c1a2 --- /dev/null +++ b/testapp/exam/evaluate_code.py @@ -0,0 +1,186 @@ +import sys +from SimpleXMLRPCServer import SimpleXMLRPCServer +import pwd +import os +import stat +from os.path import isdir, dirname, abspath, join, isfile +import signal +from multiprocessing import Process, Queue +import subprocess +import re +import json +import importlib +# Local imports. +from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT + + +MY_DIR = abspath(dirname(__file__)) + +# Raised when the code times-out. +# c.f. http://pguides.net/python/timeout-a-function +class TimeoutException(Exception): + pass + +## Private Protocol ########## + +def timeout_handler(signum, frame): + """A handler for the ALARM signal.""" + raise TimeoutException('Code took too long to run.') + +def create_signal_handler(): + """Add a new signal handler for the execution of this code.""" + prev_handler = signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(SERVER_TIMEOUT) + return prev_handler + +def set_original_signal_handler(old_handler=None): + """Set back any original signal handler.""" + if old_handler is not None: + signal.signal(signal.SIGALRM, old_handler) + return + else: + raise Exception("Signal Handler: object cannot be NoneType") + +def delete_signal_handler(): + signal.alarm(0) + return + + + +############################################################################### +# `TestCode` class. +############################################################################### +class EvaluateCode(object): + """Tests the code obtained from Code Server""" + def __init__(self, test_case_data, language, user_answer, ref_code_path=None, in_dir=None): + msg = 'Code took more than %s seconds to run. You probably '\ + 'have an infinite loop in your code.' % SERVER_TIMEOUT + self.timeout_msg = msg + self.test_case_data = test_case_data + self.language = language.lower() + self.user_answer = user_answer + self.ref_code_path = ref_code_path + self.in_dir = in_dir + + ## Public Protocol ########## + + @classmethod + def from_json(cls, language, json_data, in_dir): + json_data = json.loads(json_data) + test_case_data = json_data.get("test_case_data") + user_answer = json_data.get("user_answer") + ref_code_path = json_data.get("ref_code_path") + + instance = cls(Test_case_data, language, user_answer, ref_code_path, in_dir) + return instance + + def run_code(self): + """Tests given code (`answer`) with the test cases based on + given arguments. + + The ref_code_path is a path to the reference code. + The reference code will call the function submitted by the student. + The reference code will check for the expected output. + + If the path's start with a "/" then we assume they are absolute paths. + If not, we assume they are relative paths w.r.t. the location of this + code_server script. + + If the optional `in_dir` keyword argument is supplied it changes the + directory to that directory (it does not change it back to the original + when done). + + Returns + ------- + + A tuple: (success, error message). + """ + self._change_dir(self.in_dir) + + # Add a new signal handler for the execution of this code. + prev_handler = create_signal_handler() + success = False + + # Do whatever testing needed. + try: + success, err = self.evaluate_code() + + except TimeoutException: + err = self.timeout_msg + except: + type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + finally: + # Set back any original signal handler. + set_original_signal_handler(prev_handler) + + # Cancel the signal + delete_signal_handler() + + result = {'success': success, 'error': err} + return result + + def evaluate_code(self): + raise NotImplementedError("evaluate_code method not implemented") + + def create_submit_code_file(self, file_name): + """ Write the code (`answer`) to a file and set the file path""" + submit_f = open(file_name, 'w') + submit_f.write(self.user_answer.lstrip()) + submit_f.close() + submit_path = abspath(submit_f.name) + + return submit_path + + def set_file_as_executable(self, fname): + os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR + | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP + | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) + + def set_test_code_file_path(self, ref_path=None, test_case_path=None): + if ref_path and not ref_path.startswith('/'): + ref_path = join(MY_DIR, ref_path) + + if test_case_path and not test_case_path.startswith('/'): + test_case_path = join(MY_DIR, test_case_path) + + return ref_path, test_case_path + + def run_command(self, cmd_args, *args, **kw): + """Run a command in a subprocess while blocking, the process is killed + if it takes more than 2 seconds to run. Return the Popen object, the + stdout and stderr. + """ + try: + proc = subprocess.Popen(cmd_args, *args, **kw) + stdout, stderr = proc.communicate() + except TimeoutException: + # Runaway code, so kill it. + proc.kill() + # Re-raise exception. + raise + return proc, stdout, stderr + + def compile_command(self, cmd, *args, **kw): + """Compiles C/C++/java code and returns errors if any. + Run a command in a subprocess while blocking, the process is killed + if it takes more than 2 seconds to run. Return the Popen object, the + stderr. + """ + try: + proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc_compile.communicate() + except TimeoutException: + # Runaway code, so kill it. + proc_compile.kill() + # Re-raise exception. + raise + return proc_compile, err + + ## Private Protocol ########## + + def _change_dir(self, in_dir): + if in_dir is not None and isdir(in_dir): + os.chdir(in_dir) \ No newline at end of file diff --git a/testapp/exam/evaluate_cpp.py b/testapp/exam/evaluate_cpp.py deleted file mode 100644 index cffe744..0000000 --- a/testapp/exam/evaluate_cpp.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from evaluate_c import EvaluateC -from test_code import TestCode -from registry import registry - - - -class EvaluateCpp(EvaluateC, TestCode): - """Tests the C code obtained from Code Server""" - def evaluate_code(self): - submit_path = self.create_submit_code_file('submitstd.cpp') - get_ref_path = self.ref_code_path - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) - success = False - - # Set file paths - c_user_output_path = os.getcwd() + '/output' - c_ref_output_path = os.getcwd() + '/executable' - - # Set command variables - compile_command = 'g++ {0} -c -o {1}'.format(submit_path, - c_user_output_path) - compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, - c_user_output_path, - c_ref_output_path) - run_command_args = c_ref_output_path - remove_user_output = c_user_output_path - remove_ref_output = c_ref_output_path - - success, err = self.check_code(ref_path, submit_path, compile_command, - compile_main, run_command_args, - remove_user_output, remove_ref_output) - - # Delete the created file. - os.remove(submit_path) - - return success, err - -registry.register('cpp', EvaluateCpp) \ No newline at end of file diff --git a/testapp/exam/evaluate_cpp_code.py b/testapp/exam/evaluate_cpp_code.py new file mode 100644 index 0000000..ed744d1 --- /dev/null +++ b/testapp/exam/evaluate_cpp_code.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from evaluate_c_code import EvaluateCCode +from evaluate_code import EvaluateCode +from language_registry import registry + + +class EvaluateCppCode(EvaluateCCode, EvaluateCode): + """Tests the C code obtained from Code Server""" + ## Public Protocol ########## + def evaluate_code(self): + submit_path = self.create_submit_code_file('submitstd.cpp') + get_ref_path = self.ref_code_path + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) + success = False + + # Set file paths + c_user_output_path = os.getcwd() + '/output' + c_ref_output_path = os.getcwd() + '/executable' + + # Set command variables + compile_command = 'g++ {0} -c -o {1}'.format(submit_path, + c_user_output_path) + compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, + c_user_output_path, + c_ref_output_path) + run_command_args = c_ref_output_path + remove_user_output = c_user_output_path + remove_ref_output = c_ref_output_path + + success, err = self.check_code(ref_path, submit_path, compile_command, + compile_main, run_command_args, + remove_user_output, remove_ref_output) + + # Delete the created file. + os.remove(submit_path) + + return success, err + + +registry.register('cpp', EvaluateCppCode) \ No newline at end of file diff --git a/testapp/exam/evaluate_java.py b/testapp/exam/evaluate_java.py deleted file mode 100644 index d3d4e2a..0000000 --- a/testapp/exam/evaluate_java.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from evaluate_c import EvaluateC -from test_code import TestCode -from registry import registry - - - -class EvaluateJava(EvaluateC, TestCode): - """Tests the C code obtained from Code Server""" - def evaluate_code(self): - submit_path = self.create_submit_code_file('Test.java') - get_ref_path = self.ref_code_path - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) - success = False - - # Set file paths - java_student_directory = os.getcwd() + '/' - java_ref_file_name = (ref_code_path.split('/')[-1]).split('.')[0] - - # Set command variables - compile_command = 'javac {0}'.format(submit_code_path), - compile_main = 'javac {0} -classpath {1} -d {2}'.format(ref_code_path, - java_student_directory, - java_student_directory) - run_command_args = "java -cp {0} {1}".format(java_student_directory, - java_ref_file_name) - remove_user_output = "{0}{1}.class".format(java_student_directory, - 'Test') - remove_ref_output = "{0}{1}.class".format(java_student_directory, - java_ref_file_name) - - success, err = self.check_code(ref_path, submit_path, compile_command, - compile_main, run_command_args, - remove_user_output, remove_ref_output) - - # Delete the created file. - os.remove(submit_path) - - return success, err - -registry.register('java', EvaluateJava) \ No newline at end of file diff --git a/testapp/exam/evaluate_java_code.py b/testapp/exam/evaluate_java_code.py new file mode 100644 index 0000000..9ddbbed --- /dev/null +++ b/testapp/exam/evaluate_java_code.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from evaluate_c_code import EvaluateCCode +from evaluate_code import EvaluateCode +from language_registry import registry + + +class EvaluateJavaCode(EvaluateCCode, EvaluateCode): + """Tests the C code obtained from Code Server""" + ## Public Protocol ########## + def evaluate_code(self): + submit_path = self.create_submit_code_file('Test.java') + ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) + success = False + + # Set file paths + java_student_directory = os.getcwd() + '/' + java_ref_file_name = (ref_path.split('/')[-1]) #.split('.')[0] + + # Set command variables + compile_command = 'javac {0}'.format(submit_path), + compile_main = 'javac {0} -classpath {1} -d {2}'.format(ref_path, + java_student_directory, + java_student_directory) + run_command_args = "java -cp {0} {1}".format(java_student_directory, + java_ref_file_name) + remove_user_output = "{0}{1}.class".format(java_student_directory, + 'Test') + remove_ref_output = "{0}{1}.class".format(java_student_directory, + java_ref_file_name) + + success, err = self.check_code(ref_path, submit_path, compile_command, + compile_main, run_command_args, + remove_user_output, remove_ref_output) + + # Delete the created file. + os.remove(submit_path) + + return success, err + + +registry.register('java', EvaluateJavaCode) \ No newline at end of file diff --git a/testapp/exam/evaluate_python.py b/testapp/exam/evaluate_python.py deleted file mode 100644 index 3af82b6..0000000 --- a/testapp/exam/evaluate_python.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -import sys -import traceback -import os -from os.path import join -import importlib - -# local imports -from test_code import TestCode -from registry import registry - -class EvaluatePython(TestCode): - """Tests the Python code obtained from Code Server""" - # def evaluate_python_code(self): - def evaluate_code(self): - success = False - - try: - tb = None - test_code = self._create_test_case() - submitted = compile(self.user_answer, '', mode='exec') - g = {} - exec submitted in g - _tests = compile(test_code, '', mode='exec') - exec _tests in g - except AssertionError: - type, value, tb = sys.exc_info() - info = traceback.extract_tb(tb) - fname, lineno, func, text = info[-1] - text = str(test_code).splitlines()[lineno-1] - err = "{0} {1} in: {2}".format(type.__name__, str(value), text) - else: - success = True - err = 'Correct answer' - - del tb - return success, err - - # Private Protocol - def _create_test_case(self): - """ - Create assert based test cases in python - """ - test_code = "" - for test_case in self.test_parameter: - pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ - else "" - kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ - if test_case.get('kw_args') else "" - args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args - tcode = "assert {0}({1}) == {2}" \ - .format(test_case.get('func_name'), args, test_case.get('expected_answer')) - test_code += tcode + "\n" - return test_code - -registry.register('python', EvaluatePython) \ No newline at end of file diff --git a/testapp/exam/evaluate_python_code.py b/testapp/exam/evaluate_python_code.py new file mode 100644 index 0000000..2682897 --- /dev/null +++ b/testapp/exam/evaluate_python_code.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +import sys +import traceback +import os +from os.path import join +import importlib + +# local imports +from evaluate_code import EvaluateCode +from language_registry import registry + + +class EvaluatePythonCode(EvaluateCode): + """Tests the Python code obtained from Code Server""" + ## Public Protocol ########## + def evaluate_code(self): + success = False + + try: + tb = None + test_code = self._create_test_case() + submitted = compile(self.user_answer, '', mode='exec') + g = {} + exec submitted in g + _tests = compile(test_code, '', mode='exec') + exec _tests in g + except AssertionError: + type, value, tb = sys.exc_info() + info = traceback.extract_tb(tb) + fname, lineno, func, text = info[-1] + text = str(test_code).splitlines()[lineno-1] + err = "{0} {1} in: {2}".format(type.__name__, str(value), text) + else: + success = True + err = 'Correct answer' + + del tb + return success, err + + ## Private Protocol ########## + def _create_test_case(self): + """ + Create assert based test cases in python + """ + test_code = "" + for test_case in self.test_case_data: + pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ + else "" + kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ + if test_case.get('kw_args') else "" + args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args + tcode = "assert {0}({1}) == {2}" \ + .format(test_case.get('func_name'), args, test_case.get('expected_answer')) + test_code += tcode + "\n" + return test_code + + +registry.register('python', EvaluatePythonCode) \ No newline at end of file diff --git a/testapp/exam/evaluate_scilab.py b/testapp/exam/evaluate_scilab.py deleted file mode 100644 index f4253ff..0000000 --- a/testapp/exam/evaluate_scilab.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -import traceback -import os -from os.path import join, isfile -import subprocess -import re -import importlib - -# local imports -from test_code import TestCode -from registry import registry - - -class EvaluateScilab(TestCode): - """Tests the Scilab code obtained from Code Server""" - # def evaluate_scilab_code(self): - def evaluate_code(self): - submit_path = self.create_submit_code_file('function.sci') - ref_path, test_case_path = self.set_test_code_file_path() - success = False - - cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) - cmd += ' | timeout 8 scilab-cli -nb' - ret = self.run_command(cmd, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - - # Get only the error. - stderr = self._get_error(stdout) - if stderr is None: - # Clean output - stdout = self._strip_output(stdout) - if proc.returncode == 5: - success, err = True, "Correct answer" - else: - err = add_err + stdout - else: - err = add_err + stderr - - # Delete the created file. - os.remove(submit_path) - - return success, err - - # Private Protocol - def _remove_scilab_exit(self, string): - """ - Removes exit, quit and abort from the scilab code - """ - new_string = "" - i=0 - for line in string.splitlines(): - new_line = re.sub(r"exit.*$","",line) - new_line = re.sub(r"quit.*$","",new_line) - new_line = re.sub(r"abort.*$","",new_line) - if line != new_line: - i=i+1 - new_string = new_string +'\n'+ new_line - return new_string, i - - def _get_error(self, string): - """ - Fetches only the error from the string. - Returns None if no error. - """ - obj = re.search("!.+\n.+",string); - if obj: - return obj.group() - return None - - def _strip_output(self, out): - """ - Cleans whitespace from the output - """ - strip_out = "Message" - for l in out.split('\n'): - if l.strip(): - strip_out = strip_out+"\n"+l.strip() - return strip_out - -registry.register('scilab', EvaluateScilab) \ No newline at end of file diff --git a/testapp/exam/evaluate_scilab_code.py b/testapp/exam/evaluate_scilab_code.py new file mode 100644 index 0000000..cc46605 --- /dev/null +++ b/testapp/exam/evaluate_scilab_code.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +import traceback +import os +from os.path import join, isfile +import subprocess +import re +import importlib + +# local imports +from evaluate_code import EvaluateCode +from language_registry import registry + + +class EvaluateScilabCode(EvaluateCode): + """Tests the Scilab code obtained from Code Server""" + ## Public Protocol ########## + def evaluate_code(self): + submit_path = self.create_submit_code_file('function.sci') + ref_path, test_case_path = self.set_test_code_file_path() + success = False + + cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) + cmd += ' | timeout 8 scilab-cli -nb' + ret = self.run_command(cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + + # Get only the error. + stderr = self._get_error(stdout) + if stderr is None: + # Clean output + stdout = self._strip_output(stdout) + if proc.returncode == 5: + success, err = True, "Correct answer" + else: + err = add_err + stdout + else: + err = add_err + stderr + + # Delete the created file. + os.remove(submit_path) + + return success, err + + ## Private Protocol ########## + def _remove_scilab_exit(self, string): + """ + Removes exit, quit and abort from the scilab code + """ + new_string = "" + i=0 + for line in string.splitlines(): + new_line = re.sub(r"exit.*$","",line) + new_line = re.sub(r"quit.*$","",new_line) + new_line = re.sub(r"abort.*$","",new_line) + if line != new_line: + i=i+1 + new_string = new_string +'\n'+ new_line + return new_string, i + + ## Private Protocol ########## + def _get_error(self, string): + """ + Fetches only the error from the string. + Returns None if no error. + """ + obj = re.search("!.+\n.+",string); + if obj: + return obj.group() + return None + + ## Private Protocol ########## + def _strip_output(self, out): + """ + Cleans whitespace from the output + """ + strip_out = "Message" + for l in out.split('\n'): + if l.strip(): + strip_out = strip_out+"\n"+l.strip() + return strip_out + + +registry.register('scilab', EvaluateScilabCode) \ No newline at end of file diff --git a/testapp/exam/language_registry.py b/testapp/exam/language_registry.py new file mode 100644 index 0000000..207cd21 --- /dev/null +++ b/testapp/exam/language_registry.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +class LanguageRegistry(object): + def __init__(self): + self._registry = {} + + ## Public Protocol ########## + def get_class(self, language): + return self._registry[language] + + ## Public Protocol ########## + def register(self, language, cls): + self._registry[language] = cls + + +registry = LanguageRegistry() \ No newline at end of file diff --git a/testapp/exam/models.py b/testapp/exam/models.py index d0c9cc2..51e773a 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -88,17 +88,17 @@ class Question(models.Model): tags = TaggableManager() def consolidate_answer_data(self, test_cases, user_answer): - test_case_parameter = [] - info_parameter = {} + test_case_data_dict = [] + question_info_dict = {} for test_case in test_cases: kw_args_dict = {} pos_args_list = [] - parameter_dict = {} - parameter_dict['test_id'] = test_case.id - parameter_dict['func_name'] = test_case.func_name - parameter_dict['expected_answer'] = test_case.expected_answer + test_case_data = {} + test_case_data['test_id'] = test_case.id + test_case_data['func_name'] = test_case.func_name + test_case_data['expected_answer'] = test_case.expected_answer if test_case.kw_args: for args in test_case.kw_args.split(","): @@ -109,17 +109,17 @@ class Question(models.Model): for args in test_case.pos_args.split(","): pos_args_list.append(args.strip()) - parameter_dict['kw_args'] = kw_args_dict - parameter_dict['pos_args'] = pos_args_list - test_case_parameter.append(parameter_dict) + test_case_data['kw_args'] = kw_args_dict + test_case_data['pos_args'] = pos_args_list + test_case_data_dict.append(test_case_data) - # info_parameter['language'] = self.language - info_parameter['id'] = self.id - info_parameter['user_answer'] = user_answer - info_parameter['test_parameter'] = test_case_parameter - info_parameter['ref_code_path'] = self.ref_code_path + # question_info_dict['language'] = self.language + question_info_dict['id'] = self.id + question_info_dict['user_answer'] = user_answer + question_info_dict['test_parameter'] = test_case_data_dict + question_info_dict['ref_code_path'] = self.ref_code_path - return json.dumps(info_parameter) + return json.dumps(question_info_dict) def __unicode__(self): return self.summary diff --git a/testapp/exam/registry.py b/testapp/exam/registry.py deleted file mode 100644 index ef995ac..0000000 --- a/testapp/exam/registry.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python - -class Registry(object): - def __init__(self): - self._registry = {} - - def get_class(self, language): - return self._registry[language] - - def register(self, language, cls): - self._registry[language] = cls - - -registry = Registry() \ No newline at end of file diff --git a/testapp/exam/test_code.py b/testapp/exam/test_code.py deleted file mode 100644 index 8930f55..0000000 --- a/testapp/exam/test_code.py +++ /dev/null @@ -1,182 +0,0 @@ -import sys -from SimpleXMLRPCServer import SimpleXMLRPCServer -import pwd -import os -import stat -from os.path import isdir, dirname, abspath, join, isfile -import signal -from multiprocessing import Process, Queue -import subprocess -import re -import json -import importlib -# Local imports. -from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT - - -MY_DIR = abspath(dirname(__file__)) - -# Raised when the code times-out. -# c.f. http://pguides.net/python/timeout-a-function -class TimeoutException(Exception): - pass - - -def timeout_handler(signum, frame): - """A handler for the ALARM signal.""" - raise TimeoutException('Code took too long to run.') - -def create_signal_handler(): - """Add a new signal handler for the execution of this code.""" - prev_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - return prev_handler - -def set_original_signal_handler(old_handler=None): - """Set back any original signal handler.""" - if old_handler is not None: - signal.signal(signal.SIGALRM, old_handler) - return - else: - raise Exception("Signal Handler: object cannot be NoneType") - -def delete_signal_handler(): - signal.alarm(0) - return - - - -############################################################################### -# `TestCode` class. -############################################################################### -class TestCode(object): - """Tests the code obtained from Code Server""" - def __init__(self, test_parameter, language, user_answer, ref_code_path=None, in_dir=None): - msg = 'Code took more than %s seconds to run. You probably '\ - 'have an infinite loop in your code.' % SERVER_TIMEOUT - self.timeout_msg = msg - self.test_parameter = test_parameter - self.language = language.lower() - self.user_answer = user_answer - self.ref_code_path = ref_code_path - self.in_dir = in_dir - - @classmethod - def from_json(cls, blob, language, in_dir): - info_parameter = json.loads(blob) - test_parameter = info_parameter.get("test_parameter") - user_answer = info_parameter.get("user_answer") - ref_code_path = info_parameter.get("ref_code_path") - - instance = cls(test_parameter, language, user_answer, ref_code_path, in_dir) - return instance - - def run_code(self): - """Tests given code (`answer`) with the test cases based on - given arguments. - - The ref_code_path is a path to the reference code. - The reference code will call the function submitted by the student. - The reference code will check for the expected output. - - If the path's start with a "/" then we assume they are absolute paths. - If not, we assume they are relative paths w.r.t. the location of this - code_server script. - - If the optional `in_dir` keyword argument is supplied it changes the - directory to that directory (it does not change it back to the original - when done). - - Returns - ------- - - A tuple: (success, error message). - """ - self._change_dir(self.in_dir) - - # Add a new signal handler for the execution of this code. - prev_handler = create_signal_handler() - success = False - - # Do whatever testing needed. - try: - success, err = self.evaluate_code() - - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - set_original_signal_handler(prev_handler) - - # Cancel the signal - delete_signal_handler() - - result = {'success': success, 'error': err} - return result - - def evaluate_code(self): - raise NotImplementedError("evaluate_code method not implemented") - - def create_submit_code_file(self, file_name): - """ Write the code (`answer`) to a file and set the file path""" - # File name/extension depending on the question language - submit_f = open(file_name, 'w') - submit_f.write(self.user_answer.lstrip()) - submit_f.close() - submit_path = abspath(submit_f.name) - - return submit_path - - def set_file_as_executable(self, fname): - os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR - | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP - | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) - - def set_test_code_file_path(self, ref_path=None, test_case_path=None): - if ref_path and not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - - if test_case_path and not test_case_path.startswith('/'): - test_case_path = join(MY_DIR, test_case_path) - - return ref_path, test_case_path - - def run_command(self, cmd_args, *args, **kw): - """Run a command in a subprocess while blocking, the process is killed - if it takes more than 2 seconds to run. Return the Popen object, the - stdout and stderr. - """ - try: - proc = subprocess.Popen(cmd_args, *args, **kw) - stdout, stderr = proc.communicate() - except TimeoutException: - # Runaway code, so kill it. - proc.kill() - # Re-raise exception. - raise - return proc, stdout, stderr - - def compile_command(self, cmd, *args, **kw): - """Compiles C/C++/java code and returns errors if any. - Run a command in a subprocess while blocking, the process is killed - if it takes more than 2 seconds to run. Return the Popen object, the - stderr. - """ - try: - proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = proc_compile.communicate() - except TimeoutException: - # Runaway code, so kill it. - proc_compile.kill() - # Re-raise exception. - raise - return proc_compile, err - - def _change_dir(self, in_dir): - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) \ No newline at end of file diff --git a/testapp/exam/views.py b/testapp/exam/views.py index 508b623..2542a28 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -286,7 +286,6 @@ def edit_question(request): user = request.user if not user.is_authenticated() or not is_moderator(user): raise Http404('You are not allowed to view this page!') - question_list = request.POST.getlist('questions') summary = request.POST.getlist('summary') description = request.POST.getlist('description') @@ -311,6 +310,8 @@ def edit_question(request): question.active = active[j] question.language = language[j] question.snippet = snippet[j] + question.ref_code_path = ref_code_path[j] + question.solution = solution[j] question.type = type[j] question.save() return my_redirect("/exam/manage/questions") @@ -374,6 +375,8 @@ def add_question(request, question_id=None): d.active = form['active'].data d.language = form['language'].data d.snippet = form['snippet'].data + d.ref_code_path = form['ref_code_path'].data + d.solution = form['solution'].data d.save() question = Question.objects.get(id=question_id) for tag in question.tags.all(): @@ -428,6 +431,8 @@ def add_question(request, question_id=None): form.initial['active'] = d.active form.initial['language'] = d.language form.initial['snippet'] = d.snippet + form.initial['ref_code_path'] = d.ref_code_path + form.initial['solution'] = d.solution form_tags = d.tags.all() form_tags_split = form_tags.values('name') initial_tags = "" @@ -953,9 +958,9 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # questions, we obtain the results via XML-RPC with the code executed # safely in a separate process (the code_server.py) running as nobody. if not question.type == 'upload': - info_parameter = question.consolidate_answer_data(test, user_answer) \ - if question.type == 'code' else None - correct, result = validate_answer(user, user_answer, question, info_parameter) + json_data = question.consolidate_answer_data(test, user_answer) \ + if question.type == 'code' else None + correct, result = validate_answer(user, user_answer, question, json_data) if correct: new_answer.correct = correct new_answer.marks = question.points @@ -1000,7 +1005,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): questionpaper_id, success_msg) -def validate_answer(user, user_answer, question, info_parameter=None): +def validate_answer(user, user_answer, question, json_data=None): """ Checks whether the answer submitted by the user is right or wrong. If right then returns correct = True, success and @@ -1025,7 +1030,7 @@ def validate_answer(user, user_answer, question, info_parameter=None): message = 'Correct answer' elif question.type == 'code': user_dir = get_user_dir(user) - json_result = code_server.run_code(info_parameter, question.language, user_dir) + json_result = code_server.run_code(question.language, json_data, user_dir) result = json.loads(json_result) if result.get('success'): correct = True @@ -1247,6 +1252,8 @@ def show_all_questions(request): form.initial['active'] = d.active form.initial['language'] = d.language form.initial['snippet'] = d.snippet + form.initial['ref_code_path'] = d.ref_code_path + form.initial['solution'] = d.solution form_tags = d.tags.all() form_tags_split = form_tags.values('name') initial_tags = "" diff --git a/testapp/exam/xmlrpc_clients.py b/testapp/exam/xmlrpc_clients.py index 5d95cae..8f5642e 100644 --- a/testapp/exam/xmlrpc_clients.py +++ b/testapp/exam/xmlrpc_clients.py @@ -22,7 +22,7 @@ class CodeServerProxy(object): pool_url = 'http://localhost:%d' % (SERVER_POOL_PORT) self.pool_server = ServerProxy(pool_url) - def run_code(self, info_parameter, language, user_dir): + def run_code(self, language, json_data, user_dir): """Tests given code (`answer`) with the `test_code` supplied. If the optional `in_dir` keyword argument is supplied it changes the directory to that directory (it does not change it back to the original when @@ -31,7 +31,7 @@ class CodeServerProxy(object): Parameters ---------- - info_parameter contains; + json_data contains; user_answer : str The user's answer for the question. test_code : str @@ -50,7 +50,7 @@ class CodeServerProxy(object): try: server = self._get_server() - result = server.check_code(info_parameter, language, user_dir) + result = server.check_code(language, json_data, user_dir) except ConnectionError: result = json.dumps({'success': False, 'error': 'Unable to connect to any code servers!'}) return result -- cgit From 18df6d88a2e1a9dfe7d05ca97b2d69ff0569e088 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Sun, 26 Apr 2015 21:13:53 +0530 Subject: Make PEP8 consistent --- testapp/exam/code_server.py | 20 +++++++++++++------- testapp/exam/evaluate_bash_code.py | 20 ++++++++++---------- testapp/exam/evaluate_c_code.py | 13 ++++++------- testapp/exam/evaluate_code.py | 21 +++++++++++++-------- testapp/exam/evaluate_cpp_code.py | 4 ++-- testapp/exam/evaluate_java_code.py | 13 +++++++------ testapp/exam/evaluate_python_code.py | 24 +++++++++++++----------- testapp/exam/evaluate_scilab_code.py | 24 ++++++++++++------------ testapp/exam/language_registry.py | 7 ++++--- 9 files changed, 80 insertions(+), 66 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index 111562a..697b131 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -40,9 +40,11 @@ from evaluate_java_code import EvaluateJavaCode from evaluate_scilab_code import EvaluateScilabCode from evaluate_bash_code import EvaluateBashCode + MY_DIR = abspath(dirname(__file__)) -## Private Protocol ########## + +# Private Protocol ########## def run_as_nobody(): """Runs the current process as nobody.""" # Set the effective uid and to that of nobody. @@ -50,6 +52,7 @@ def run_as_nobody(): os.setegid(nobody.pw_gid) os.seteuid(nobody.pw_uid) + ############################################################################### # `CodeServer` class. ############################################################################### @@ -61,10 +64,13 @@ class CodeServer(object): self.port = port self.queue = queue - ## Public Protocol ########## + # Public Protocol ########## def check_code(self, language, json_data, in_dir=None): - """Calls relevant EvaluateCode class based on language to check the answer code""" - evaluate_code_instance = self.create_class_instance(language, json_data, in_dir) + """Calls relevant EvaluateCode class based on language to check the + answer code + """ + evaluate_code_instance = self.create_class_instance(language, + json_data, in_dir) result = evaluate_code_instance.run_code() @@ -73,14 +79,14 @@ class CodeServer(object): return json.dumps(result) - ## Public Protocol ########## + # Public Protocol ########## def create_class_instance(self, language, json_data, in_dir): """Create instance of relevant EvaluateCode class based on language""" cls = registry.get_class(language) instance = cls.from_json(language, json_data, in_dir) return instance - ## Public Protocol ########## + # Public Protocol ########## def run(self): """Run XMLRPC server, serving our methods.""" server = SimpleXMLRPCServer(("localhost", self.port)) @@ -120,7 +126,7 @@ class ServerPool(object): p.start() self.servers = servers - ## Public Protocol ########## + # Public Protocol ########## def get_server_port(self): """Get available server port from ones in the pool. This will block diff --git a/testapp/exam/evaluate_bash_code.py b/testapp/exam/evaluate_bash_code.py index 49f20fa..2905d65 100644 --- a/testapp/exam/evaluate_bash_code.py +++ b/testapp/exam/evaluate_bash_code.py @@ -13,14 +13,15 @@ from language_registry import registry class EvaluateBashCode(EvaluateCode): """Tests the Bash code obtained from Code Server""" - ## Public Protocol ########## + # Public Protocol ########## def evaluate_code(self): submit_path = self.create_submit_code_file('submit.sh') self.set_file_as_executable(submit_path) get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') get_ref_path = get_ref_path.strip() get_test_case_path = get_test_case_path.strip() - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, get_test_case_path) + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, + get_test_case_path) success, err = self._check_bash_script(ref_path, submit_path, test_case_path) @@ -30,7 +31,7 @@ class EvaluateBashCode(EvaluateCode): return success, err - ## Private Protocol ########## + # Private Protocol ########## def _check_bash_script(self, ref_path, submit_path, test_case_path=None): """ Function validates student script using instructor script as @@ -86,12 +87,11 @@ class EvaluateBashCode(EvaluateCode): return False, "No test case at %s" % test_case_path if not os.access(ref_path, os.R_OK): return False, "Test script %s, not readable" % test_case_path - valid_answer = True # We initially make it one, so that we can - # stop once a test case fails - loop_count = 0 # Loop count has to be greater than or - # equal to one. - # Useful for caching things like empty - # test files,etc. + # valid_answer is True, so that we can stop once a test case fails + valid_answer = True + # loop_count has to be greater than or equal to one. + # Useful for caching things like empty test files,etc. + loop_count = 0 test_cases = open(test_case_path).readlines() num_lines = len(test_cases) for test_case in test_cases: @@ -116,4 +116,4 @@ class EvaluateBashCode(EvaluateCode): return False, err -registry.register('bash', EvaluateBashCode) \ No newline at end of file +registry.register('bash', EvaluateBashCode) diff --git a/testapp/exam/evaluate_c_code.py b/testapp/exam/evaluate_c_code.py index d52beae..0b9e352 100644 --- a/testapp/exam/evaluate_c_code.py +++ b/testapp/exam/evaluate_c_code.py @@ -13,7 +13,7 @@ from language_registry import registry class EvaluateCCode(EvaluateCode): """Tests the C code obtained from Code Server""" - ## Public Protocol ########## + # Public Protocol ########## def evaluate_code(self): submit_path = self.create_submit_code_file('submit.c') get_ref_path = self.ref_code_path @@ -25,8 +25,8 @@ class EvaluateCCode(EvaluateCode): c_ref_output_path = os.getcwd() + '/executable' # Set command variables - compile_command = 'g++ {0} -c -o {1}'.format(submit_path, - c_user_output_path) + compile_command = 'g++ {0} -c -o {1}'.format(submit_path, + c_user_output_path) compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, c_user_output_path, c_ref_output_path) @@ -43,7 +43,7 @@ class EvaluateCCode(EvaluateCode): return success, err - ## Public Protocol ########## + # Public Protocol ########## def check_code(self, ref_code_path, submit_code_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output): @@ -122,8 +122,7 @@ class EvaluateCCode(EvaluateCode): return success, err - - ## Public Protocol ########## + # Public Protocol ########## def remove_null_substitute_char(self, string): """Returns a string without any null and substitute characters""" stripped = "" @@ -133,4 +132,4 @@ class EvaluateCCode(EvaluateCode): return ''.join(stripped) -registry.register('c', EvaluateCCode) \ No newline at end of file +registry.register('c', EvaluateCCode) diff --git a/testapp/exam/evaluate_code.py b/testapp/exam/evaluate_code.py index 161c1a2..b9892ed 100644 --- a/testapp/exam/evaluate_code.py +++ b/testapp/exam/evaluate_code.py @@ -16,23 +16,26 @@ from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT MY_DIR = abspath(dirname(__file__)) + # Raised when the code times-out. # c.f. http://pguides.net/python/timeout-a-function class TimeoutException(Exception): pass -## Private Protocol ########## +# Private Protocol ########## def timeout_handler(signum, frame): """A handler for the ALARM signal.""" raise TimeoutException('Code took too long to run.') + def create_signal_handler(): """Add a new signal handler for the execution of this code.""" prev_handler = signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(SERVER_TIMEOUT) return prev_handler + def set_original_signal_handler(old_handler=None): """Set back any original signal handler.""" if old_handler is not None: @@ -41,18 +44,19 @@ def set_original_signal_handler(old_handler=None): else: raise Exception("Signal Handler: object cannot be NoneType") + def delete_signal_handler(): signal.alarm(0) return - ############################################################################### # `TestCode` class. ############################################################################### class EvaluateCode(object): """Tests the code obtained from Code Server""" - def __init__(self, test_case_data, language, user_answer, ref_code_path=None, in_dir=None): + def __init__(self, test_case_data, language, user_answer, + ref_code_path=None, in_dir=None): msg = 'Code took more than %s seconds to run. You probably '\ 'have an infinite loop in your code.' % SERVER_TIMEOUT self.timeout_msg = msg @@ -62,7 +66,7 @@ class EvaluateCode(object): self.ref_code_path = ref_code_path self.in_dir = in_dir - ## Public Protocol ########## + # Public Protocol ########## @classmethod def from_json(cls, language, json_data, in_dir): @@ -71,7 +75,8 @@ class EvaluateCode(object): user_answer = json_data.get("user_answer") ref_code_path = json_data.get("ref_code_path") - instance = cls(Test_case_data, language, user_answer, ref_code_path, in_dir) + instance = cls(Test_case_data, language, user_answer, ref_code_path, + in_dir) return instance def run_code(self): @@ -128,7 +133,7 @@ class EvaluateCode(object): submit_f = open(file_name, 'w') submit_f.write(self.user_answer.lstrip()) submit_f.close() - submit_path = abspath(submit_f.name) + submit_path = abspath(submit_f.name) return submit_path @@ -179,8 +184,8 @@ class EvaluateCode(object): raise return proc_compile, err - ## Private Protocol ########## + # Private Protocol ########## def _change_dir(self, in_dir): if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) \ No newline at end of file + os.chdir(in_dir) diff --git a/testapp/exam/evaluate_cpp_code.py b/testapp/exam/evaluate_cpp_code.py index ed744d1..987d041 100644 --- a/testapp/exam/evaluate_cpp_code.py +++ b/testapp/exam/evaluate_cpp_code.py @@ -14,7 +14,7 @@ from language_registry import registry class EvaluateCppCode(EvaluateCCode, EvaluateCode): """Tests the C code obtained from Code Server""" - ## Public Protocol ########## + # Public Protocol ########## def evaluate_code(self): submit_path = self.create_submit_code_file('submitstd.cpp') get_ref_path = self.ref_code_path @@ -45,4 +45,4 @@ class EvaluateCppCode(EvaluateCCode, EvaluateCode): return success, err -registry.register('cpp', EvaluateCppCode) \ No newline at end of file +registry.register('cpp', EvaluateCppCode) diff --git a/testapp/exam/evaluate_java_code.py b/testapp/exam/evaluate_java_code.py index 9ddbbed..d04be4e 100644 --- a/testapp/exam/evaluate_java_code.py +++ b/testapp/exam/evaluate_java_code.py @@ -14,7 +14,7 @@ from language_registry import registry class EvaluateJavaCode(EvaluateCCode, EvaluateCode): """Tests the C code obtained from Code Server""" - ## Public Protocol ########## + # Public Protocol ########## def evaluate_code(self): submit_path = self.create_submit_code_file('Test.java') ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) @@ -22,13 +22,14 @@ class EvaluateJavaCode(EvaluateCCode, EvaluateCode): # Set file paths java_student_directory = os.getcwd() + '/' - java_ref_file_name = (ref_path.split('/')[-1]) #.split('.')[0] + java_ref_file_name = (ref_path.split('/')[-1]).split('.')[0] # Set command variables compile_command = 'javac {0}'.format(submit_path), - compile_main = 'javac {0} -classpath {1} -d {2}'.format(ref_path, - java_student_directory, - java_student_directory) + compile_main = ('javac {0} -classpath ' + '{1} -d {2}').format(ref_path, + java_student_directory, + java_student_directory) run_command_args = "java -cp {0} {1}".format(java_student_directory, java_ref_file_name) remove_user_output = "{0}{1}.class".format(java_student_directory, @@ -46,4 +47,4 @@ class EvaluateJavaCode(EvaluateCCode, EvaluateCode): return success, err -registry.register('java', EvaluateJavaCode) \ No newline at end of file +registry.register('java', EvaluateJavaCode) diff --git a/testapp/exam/evaluate_python_code.py b/testapp/exam/evaluate_python_code.py index 2682897..8892ae6 100644 --- a/testapp/exam/evaluate_python_code.py +++ b/testapp/exam/evaluate_python_code.py @@ -12,7 +12,7 @@ from language_registry import registry class EvaluatePythonCode(EvaluateCode): """Tests the Python code obtained from Code Server""" - ## Public Protocol ########## + # Public Protocol ########## def evaluate_code(self): success = False @@ -37,22 +37,24 @@ class EvaluatePythonCode(EvaluateCode): del tb return success, err - ## Private Protocol ########## + # Private Protocol ########## def _create_test_case(self): - """ - Create assert based test cases in python + """ + Create assert based test cases in python """ test_code = "" for test_case in self.test_case_data: - pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) if test_case.get('pos_args') \ - else "" - kw_args = ", ".join(str(k+"="+a) for k, a in test_case.get('kw_args').iteritems()) \ + pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) \ + if test_case.get('pos_args') else "" + kw_args = ", ".join(str(k+"="+a) for k, a + in test_case.get('kw_args').iteritems()) \ if test_case.get('kw_args') else "" - args = pos_args + ", " + kw_args if pos_args and kw_args else pos_args or kw_args - tcode = "assert {0}({1}) == {2}" \ - .format(test_case.get('func_name'), args, test_case.get('expected_answer')) + args = pos_args + ", " + kw_args if pos_args and kw_args \ + else pos_args or kw_args + tcode = "assert {0}({1}) == {2}".format(test_case.get('func_name'), + args, test_case.get('expected_answer')) test_code += tcode + "\n" return test_code -registry.register('python', EvaluatePythonCode) \ No newline at end of file +registry.register('python', EvaluatePythonCode) diff --git a/testapp/exam/evaluate_scilab_code.py b/testapp/exam/evaluate_scilab_code.py index cc46605..899abc9 100644 --- a/testapp/exam/evaluate_scilab_code.py +++ b/testapp/exam/evaluate_scilab_code.py @@ -13,7 +13,7 @@ from language_registry import registry class EvaluateScilabCode(EvaluateCode): """Tests the Scilab code obtained from Code Server""" - ## Public Protocol ########## + # Public Protocol ########## def evaluate_code(self): submit_path = self.create_submit_code_file('function.sci') ref_path, test_case_path = self.set_test_code_file_path() @@ -44,34 +44,34 @@ class EvaluateScilabCode(EvaluateCode): return success, err - ## Private Protocol ########## + # Private Protocol ########## def _remove_scilab_exit(self, string): """ Removes exit, quit and abort from the scilab code """ new_string = "" - i=0 + i = 0 for line in string.splitlines(): - new_line = re.sub(r"exit.*$","",line) - new_line = re.sub(r"quit.*$","",new_line) - new_line = re.sub(r"abort.*$","",new_line) + new_line = re.sub(r"exit.*$", "", line) + new_line = re.sub(r"quit.*$", "", new_line) + new_line = re.sub(r"abort.*$", "", new_line) if line != new_line: - i=i+1 - new_string = new_string +'\n'+ new_line + i = i + 1 + new_string = new_string + '\n' + new_line return new_string, i - ## Private Protocol ########## + # Private Protocol ########## def _get_error(self, string): """ Fetches only the error from the string. Returns None if no error. """ - obj = re.search("!.+\n.+",string); + obj = re.search("!.+\n.+", string) if obj: return obj.group() return None - ## Private Protocol ########## + # Private Protocol ########## def _strip_output(self, out): """ Cleans whitespace from the output @@ -83,4 +83,4 @@ class EvaluateScilabCode(EvaluateCode): return strip_out -registry.register('scilab', EvaluateScilabCode) \ No newline at end of file +registry.register('scilab', EvaluateScilabCode) diff --git a/testapp/exam/language_registry.py b/testapp/exam/language_registry.py index 207cd21..4d56de2 100644 --- a/testapp/exam/language_registry.py +++ b/testapp/exam/language_registry.py @@ -1,16 +1,17 @@ #!/usr/bin/env python + class LanguageRegistry(object): def __init__(self): self._registry = {} - ## Public Protocol ########## + # Public Protocol ########## def get_class(self, language): return self._registry[language] - ## Public Protocol ########## + # Public Protocol ########## def register(self, language, cls): self._registry[language] = cls -registry = LanguageRegistry() \ No newline at end of file +registry = LanguageRegistry() -- cgit From d8847656ba79e51c96c6e3650374aaf616c375dc Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Thu, 30 Apr 2015 11:21:49 +0530 Subject: Code Review: Code Refactoring --- testapp/exam/bash_code_evaluator.py | 140 +++++++++++++++++++ testapp/exam/c_cpp_code_evaluator.py | 168 ++++++++++++++++++++++ testapp/exam/code_evaluator.py | 255 ++++++++++++++++++++++++++++++++++ testapp/exam/code_server.py | 38 ++--- testapp/exam/evaluate_bash_code.py | 119 ---------------- testapp/exam/evaluate_c_code.py | 135 ------------------ testapp/exam/evaluate_code.py | 191 ------------------------- testapp/exam/evaluate_cpp_code.py | 48 ------- testapp/exam/evaluate_java_code.py | 50 ------- testapp/exam/evaluate_python_code.py | 60 -------- testapp/exam/evaluate_scilab_code.py | 86 ------------ testapp/exam/java_code_evaluator.py | 166 ++++++++++++++++++++++ testapp/exam/language_registry.py | 30 ++-- testapp/exam/python_code_evaluator.py | 85 ++++++++++++ testapp/exam/scilab_code_evaluator.py | 131 +++++++++++++++++ testapp/exam/settings.py | 8 ++ 16 files changed, 994 insertions(+), 716 deletions(-) create mode 100644 testapp/exam/bash_code_evaluator.py create mode 100644 testapp/exam/c_cpp_code_evaluator.py create mode 100644 testapp/exam/code_evaluator.py delete mode 100644 testapp/exam/evaluate_bash_code.py delete mode 100644 testapp/exam/evaluate_c_code.py delete mode 100644 testapp/exam/evaluate_code.py delete mode 100644 testapp/exam/evaluate_cpp_code.py delete mode 100644 testapp/exam/evaluate_java_code.py delete mode 100644 testapp/exam/evaluate_python_code.py delete mode 100644 testapp/exam/evaluate_scilab_code.py create mode 100644 testapp/exam/java_code_evaluator.py create mode 100644 testapp/exam/python_code_evaluator.py create mode 100644 testapp/exam/scilab_code_evaluator.py (limited to 'testapp/exam') diff --git a/testapp/exam/bash_code_evaluator.py b/testapp/exam/bash_code_evaluator.py new file mode 100644 index 0000000..60f0bb3 --- /dev/null +++ b/testapp/exam/bash_code_evaluator.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from code_evaluator import CodeEvaluator +# from language_registry import registry + + +class BashCodeEvaluator(CodeEvaluator): + """Tests the Bash code obtained from Code Server""" + def __init__(self, test_case_data, language, user_answer, + ref_code_path=None, in_dir=None): + super(BashCodeEvaluator, self).__init__(test_case_data, language, user_answer, + ref_code_path, in_dir) + self.submit_path = self.create_submit_code_file('submit.sh') + self.test_case_args = self.setup_code_evaluator() + + def setup_code_evaluator(self): + super(BashCodeEvaluator, self).setup_code_evaluator() + + self.set_file_as_executable(self.submit_path) + get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') + get_ref_path = get_ref_path.strip() + get_test_case_path = get_test_case_path.strip() + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, + get_test_case_path) + + return ref_path, self.submit_path, test_case_path + + + # # Public Protocol ########## + # def evaluate_code(self): + # submit_path = self.create_submit_code_file('submit.sh') + # self.set_file_as_executable(submit_path) + # get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') + # get_ref_path = get_ref_path.strip() + # get_test_case_path = get_test_case_path.strip() + # ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, + # get_test_case_path) + + # success, err = self._check_bash_script(ref_path, submit_path, + # test_case_path) + + # # Delete the created file. + # os.remove(submit_path) + + # return success, err + + + # Private Protocol ########## + def check_code(self, ref_path, submit_path, + test_case_path=None): + """ Function validates student script using instructor script as + reference. Test cases can optionally be provided. The first argument + ref_path, is the path to instructor script, it is assumed to + have executable permission. The second argument submit_path, is + the path to the student script, it is assumed to have executable + permission. The Third optional argument is the path to test the + scripts. Each line in this file is a test case and each test case is + passed to the script as standard arguments. + + Returns + -------- + + returns (True, "Correct answer") : If the student script passes all + test cases/have same output, when compared to the instructor script + + returns (False, error_msg): If the student script fails a single + test/have dissimilar output, when compared to the instructor script. + + Returns (False, error_msg): If mandatory arguments are not files or if + the required permissions are not given to the file(s). + + """ + if not isfile(ref_path): + return False, "No file at %s or Incorrect path" % ref_path + if not isfile(submit_path): + return False, "No file at %s or Incorrect path" % submit_path + if not os.access(ref_path, os.X_OK): + return False, "Script %s is not executable" % ref_path + if not os.access(submit_path, os.X_OK): + return False, "Script %s is not executable" % submit_path + + success = False + + if test_case_path is None or "": + ret = self.run_command(ref_path, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, inst_stdout, inst_stderr = ret + ret = self.run_command(submit_path, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdnt_stdout, stdnt_stderr = ret + if inst_stdout == stdnt_stdout: + return True, "Correct answer" + else: + err = "Error: expected %s, got %s" % (inst_stderr, + stdnt_stderr) + return False, err + else: + if not isfile(test_case_path): + return False, "No test case at %s" % test_case_path + if not os.access(ref_path, os.R_OK): + return False, "Test script %s, not readable" % test_case_path + # valid_answer is True, so that we can stop once a test case fails + valid_answer = True + # loop_count has to be greater than or equal to one. + # Useful for caching things like empty test files,etc. + loop_count = 0 + test_cases = open(test_case_path).readlines() + num_lines = len(test_cases) + for test_case in test_cases: + loop_count += 1 + if valid_answer: + args = [ref_path] + [x for x in test_case.split()] + ret = self.run_command(args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, inst_stdout, inst_stderr = ret + args = [submit_path]+[x for x in test_case.split()] + ret = self.run_command(args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdnt_stdout, stdnt_stderr = ret + valid_answer = inst_stdout == stdnt_stdout + if valid_answer and (num_lines == loop_count): + return True, "Correct answer" + else: + err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, + stdnt_stdout+stdnt_stderr) + return False, err + + +# registry.register('bash', EvaluateBashCode) diff --git a/testapp/exam/c_cpp_code_evaluator.py b/testapp/exam/c_cpp_code_evaluator.py new file mode 100644 index 0000000..d611f96 --- /dev/null +++ b/testapp/exam/c_cpp_code_evaluator.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from code_evaluator import CodeEvaluator +# from language_registry import registry + + +class CCppCodeEvaluator(CodeEvaluator): + """Tests the C code obtained from Code Server""" + def __init__(self, test_case_data, language, user_answer, + ref_code_path=None, in_dir=None): + super(CCppCodeEvaluator, self).__init__(test_case_data, language, user_answer, + ref_code_path, in_dir) + self.submit_path = self.create_submit_code_file('submit.c') + self.test_case_args = self.setup_code_evaluator() + + # Private Protocol ########## + def setup_code_evaluator(self): + super(CCppCodeEvaluator, self).setup_code_evaluator() + + get_ref_path = self.ref_code_path + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) + + # Set file paths + c_user_output_path = os.getcwd() + '/output' + c_ref_output_path = os.getcwd() + '/executable' + + # Set command variables + compile_command = 'g++ {0} -c -o {1}'.format(self.submit_path, + c_user_output_path) + compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, + c_user_output_path, + c_ref_output_path) + run_command_args = [c_ref_output_path] + remove_user_output = c_user_output_path + remove_ref_output = c_ref_output_path + + return ref_path, self.submit_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output + + def teardown_code_evaluator(self): + # Delete the created file. + super(CCppCodeEvaluator, self).teardown_code_evaluator() + os.remove(self.submit_path) + + # # Public Protocol ########## + # def evaluate_code(self): + # submit_path = self.create_submit_code_file('submit.c') + # get_ref_path = self.ref_code_path + # ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) + # success = False + + # # Set file paths + # c_user_output_path = os.getcwd() + '/output' + # c_ref_output_path = os.getcwd() + '/executable' + + # # Set command variables + # compile_command = 'g++ {0} -c -o {1}'.format(submit_path, + # c_user_output_path) + # compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, + # c_user_output_path, + # c_ref_output_path) + # run_command_args = [c_ref_output_path] + # remove_user_output = c_user_output_path + # remove_ref_output = c_ref_output_path + + # success, err = self.check_code(ref_path, submit_path, compile_command, + # compile_main, run_command_args, + # remove_user_output, remove_ref_output) + + # # Delete the created file. + # os.remove(submit_path) + + # return success, err + + def check_code(self, ref_code_path, submit_code_path, compile_command, + compile_main, run_command_args, remove_user_output, + remove_ref_output): + """ Function validates student code using instructor code as + reference.The first argument ref_code_path, is the path to + instructor code, it is assumed to have executable permission. + The second argument submit_code_path, is the path to the student + code, it is assumed to have executable permission. + + Returns + -------- + + returns (True, "Correct answer") : If the student function returns + expected output when called by reference code. + + returns (False, error_msg): If the student function fails to return + expected output when called by reference code. + + Returns (False, error_msg): If mandatory arguments are not files or + if the required permissions are not given to the file(s). + + """ + if not isfile(ref_code_path): + return False, "No file at %s or Incorrect path" % ref_code_path + if not isfile(submit_code_path): + return False, 'No file at %s or Incorrect path' % submit_code_path + + success = False + # output_path = os.getcwd() + '/output' + ret = self.compile_command(compile_command) + proc, stdnt_stderr = ret + # if self.language == "java": + stdnt_stderr = self.remove_null_substitute_char(stdnt_stderr) + + # Only if compilation is successful, the program is executed + # And tested with testcases + if stdnt_stderr == '': + ret = self.compile_command(compile_main) + proc, main_err = ret + # if self.language == "java": + main_err = self.remove_null_substitute_char(main_err) + + if main_err == '': + ret = self.run_command(run_command_args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + if proc.returncode == 0: + success, err = True, "Correct answer" + else: + err = stdout + "\n" + stderr + os.remove(remove_ref_output) + else: + err = "Error:" + try: + error_lines = main_err.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + main_err + os.remove(remove_user_output) + else: + err = "Compilation Error:" + try: + error_lines = stdnt_stderr.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + stdnt_stderr + + return success, err + + def remove_null_substitute_char(self, string): + """Returns a string without any null and substitute characters""" + stripped = "" + for c in string: + if ord(c) is not 26 and ord(c) is not 0: + stripped = stripped + c + return ''.join(stripped) + + +# registry.register('c', EvaluateCCode) diff --git a/testapp/exam/code_evaluator.py b/testapp/exam/code_evaluator.py new file mode 100644 index 0000000..3cc7374 --- /dev/null +++ b/testapp/exam/code_evaluator.py @@ -0,0 +1,255 @@ +import sys +from SimpleXMLRPCServer import SimpleXMLRPCServer +import pwd +import os +import stat +from os.path import isdir, dirname, abspath, join, isfile +import signal +from multiprocessing import Process, Queue +import subprocess +import re +import json +# Local imports. +from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT + + +MY_DIR = abspath(dirname(__file__)) + + +# Raised when the code times-out. +# c.f. http://pguides.net/python/timeout-a-function +class TimeoutException(Exception): + pass + + +# Private Protocol ########## +def timeout_handler(signum, frame): + """A handler for the ALARM signal.""" + raise TimeoutException('Code took too long to run.') + + +def create_signal_handler(): + """Add a new signal handler for the execution of this code.""" + prev_handler = signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(SERVER_TIMEOUT) + return prev_handler + + +def set_original_signal_handler(old_handler=None): + """Set back any original signal handler.""" + if old_handler is not None: + signal.signal(signal.SIGALRM, old_handler) + return + else: + raise Exception("Signal Handler: object cannot be NoneType") + + +def delete_signal_handler(): + signal.alarm(0) + return + + +############################################################################### +# `TestCode` class. +############################################################################### +class CodeEvaluator(object): + """Tests the code obtained from Code Server""" + def __init__(self, test_case_data, language, user_answer, + ref_code_path=None, in_dir=None): + msg = 'Code took more than %s seconds to run. You probably '\ + 'have an infinite loop in your code.' % SERVER_TIMEOUT + self.timeout_msg = msg + self.test_case_data = test_case_data + self.language = language.lower() + self.user_answer = user_answer + self.ref_code_path = ref_code_path + self.in_dir = in_dir + self.test_case_args = None + + # Public Protocol ########## + @classmethod + def from_json(cls, language, json_data, in_dir): + json_data = json.loads(json_data) + test_case_data = json_data.get("test_case_data") + user_answer = json_data.get("user_answer") + ref_code_path = json_data.get("ref_code_path") + + instance = cls(Test_case_data, language, user_answer, ref_code_path, + in_dir) + return instance + + # def run_code(self): + # """Tests given code (`answer`) with the test cases based on + # given arguments. + + # The ref_code_path is a path to the reference code. + # The reference code will call the function submitted by the student. + # The reference code will check for the expected output. + + # If the path's start with a "/" then we assume they are absolute paths. + # If not, we assume they are relative paths w.r.t. the location of this + # code_server script. + + # If the optional `in_dir` keyword argument is supplied it changes the + # directory to that directory (it does not change it back to the original + # when done). + + # Returns + # ------- + + # A tuple: (success, error message). + # """ + # self._change_dir(self.in_dir) + + # # Add a new signal handler for the execution of this code. + # prev_handler = self.create_signal_handler() + # success = False + + # # Do whatever testing needed. + # try: + # success, err = self.evaluate_code() #pass *list where list is a list of args obtained from setup + + # except TimeoutException: + # err = self.timeout_msg + # except: + # type, value = sys.exc_info()[:2] + # err = "Error: {0}".format(repr(value)) + # finally: + # # Set back any original signal handler. + # self.set_original_signal_handler(prev_handler) + + # # Cancel the signal + # self.delete_signal_handler() + + # result = {'success': success, 'error': err} + # return result + + def code_evaluator(self): + """Tests given code (`answer`) with the test cases based on + given arguments. + + The ref_code_path is a path to the reference code. + The reference code will call the function submitted by the student. + The reference code will check for the expected output. + + If the path's start with a "/" then we assume they are absolute paths. + If not, we assume they are relative paths w.r.t. the location of this + code_server script. + + If the optional `in_dir` keyword argument is supplied it changes the + directory to that directory (it does not change it back to the original + when done). + + Returns + ------- + + A tuple: (success, error message). + """ + + self.setup_code_evaluator() + success, err = self.evaluate_code(self.test_case_args) + self.teardown_code_evaluator() + + result = {'success': success, 'error': err} + return result + + # Public Protocol ########## + def setup_code_evaluator(self): + self._change_dir(self.in_dir) + + def evaluate_code(self, args): + # Add a new signal handler for the execution of this code. + prev_handler = create_signal_handler() + success = False + args = args or [] + + # Do whatever testing needed. + try: + success, err = self.check_code(*args) + + except TimeoutException: + err = self.timeout_msg + except: + _type, value = sys.exc_info()[:2] + err = "Error: {0}".format(repr(value)) + finally: + # Set back any original signal handler. + set_original_signal_handler(prev_handler) + + return success, err + + def teardown_code_evaluator(self): + # Cancel the signal + delete_signal_handler() + + def check_code(self): + raise NotImplementedError("check_code method not implemented") + + # Private Protocol ########## + def create_submit_code_file(self, file_name): + """ Write the code (`answer`) to a file and set the file path""" + submit_f = open(file_name, 'w') + submit_f.write(self.user_answer.lstrip()) + submit_f.close() + submit_path = abspath(submit_f.name) + + return submit_path + + def set_file_as_executable(self, fname): + os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR + | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP + | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) + + def set_test_code_file_path(self, ref_path=None, test_case_path=None): + if ref_path and not ref_path.startswith('/'): + ref_path = join(MY_DIR, ref_path) + + if test_case_path and not test_case_path.startswith('/'): + test_case_path = join(MY_DIR, test_case_path) + + return ref_path, test_case_path + + def run_command(self, cmd_args, *args, **kw): + """Run a command in a subprocess while blocking, the process is killed + if it takes more than 2 seconds to run. Return the Popen object, the + stdout and stderr. + """ + try: + proc = subprocess.Popen(cmd_args, *args, **kw) + stdout, stderr = proc.communicate() + except TimeoutException: + # Runaway code, so kill it. + proc.kill() + # Re-raise exception. + raise + return proc, stdout, stderr + + def compile_command(self, cmd, *args, **kw): + """Compiles C/C++/java code and returns errors if any. + Run a command in a subprocess while blocking, the process is killed + if it takes more than 2 seconds to run. Return the Popen object, the + stderr. + """ + try: + proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc_compile.communicate() + except TimeoutException: + # Runaway code, so kill it. + proc_compile.kill() + # Re-raise exception. + raise + return proc_compile, err + + def _change_dir(self, in_dir): + if in_dir is not None and isdir(in_dir): + os.chdir(in_dir) + + def remove_null_substitute_char(self, string): + """Returns a string without any null and substitute characters""" + stripped = "" + for c in string: + if ord(c) is not 26 and ord(c) is not 0: + stripped = stripped + c + return ''.join(stripped) diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index 697b131..c621dcd 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -32,13 +32,13 @@ import json import importlib # Local imports. from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT -from language_registry import registry -from evaluate_python_code import EvaluatePythonCode -from evaluate_c_code import EvaluateCCode -from evaluate_cpp_code import EvaluateCppCode -from evaluate_java_code import EvaluateJavaCode -from evaluate_scilab_code import EvaluateScilabCode -from evaluate_bash_code import EvaluateBashCode +from language_registry import set_registry +# from evaluate_python_code import EvaluatePythonCode +# from evaluate_c_code import EvaluateCCode +# from evaluate_cpp_code import EvaluateCppCode +# from evaluate_java_code import EvaluateJavaCode +# from evaluate_scilab_code import EvaluateScilabCode +# from evaluate_bash_code import EvaluateBashCode MY_DIR = abspath(dirname(__file__)) @@ -69,24 +69,15 @@ class CodeServer(object): """Calls relevant EvaluateCode class based on language to check the answer code """ - evaluate_code_instance = self.create_class_instance(language, - json_data, in_dir) - - result = evaluate_code_instance.run_code() + code_evaluator = self._create_evaluator_instance(language, json_data, + in_dir) + result = code_evaluator.code_evaluator() # Put us back into the server pool queue since we are free now. self.queue.put(self.port) return json.dumps(result) - # Public Protocol ########## - def create_class_instance(self, language, json_data, in_dir): - """Create instance of relevant EvaluateCode class based on language""" - cls = registry.get_class(language) - instance = cls.from_json(language, json_data, in_dir) - return instance - - # Public Protocol ########## def run(self): """Run XMLRPC server, serving our methods.""" server = SimpleXMLRPCServer(("localhost", self.port)) @@ -95,6 +86,15 @@ class CodeServer(object): self.queue.put(self.port) server.serve_forever() + # Private Protocol ########## + def _create_evaluator_instance(self, language, json_data, in_dir): + """Create instance of relevant EvaluateCode class based on language""" + set_registry() + registry = get_registry() + cls = registry.get_class(language) + instance = cls.from_json(language, json_data, in_dir) + return instance + ############################################################################### # `ServerPool` class. diff --git a/testapp/exam/evaluate_bash_code.py b/testapp/exam/evaluate_bash_code.py deleted file mode 100644 index 2905d65..0000000 --- a/testapp/exam/evaluate_bash_code.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from evaluate_code import EvaluateCode -from language_registry import registry - - -class EvaluateBashCode(EvaluateCode): - """Tests the Bash code obtained from Code Server""" - # Public Protocol ########## - def evaluate_code(self): - submit_path = self.create_submit_code_file('submit.sh') - self.set_file_as_executable(submit_path) - get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') - get_ref_path = get_ref_path.strip() - get_test_case_path = get_test_case_path.strip() - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, - get_test_case_path) - - success, err = self._check_bash_script(ref_path, submit_path, - test_case_path) - - # Delete the created file. - os.remove(submit_path) - - return success, err - - # Private Protocol ########## - def _check_bash_script(self, ref_path, submit_path, - test_case_path=None): - """ Function validates student script using instructor script as - reference. Test cases can optionally be provided. The first argument - ref_path, is the path to instructor script, it is assumed to - have executable permission. The second argument submit_path, is - the path to the student script, it is assumed to have executable - permission. The Third optional argument is the path to test the - scripts. Each line in this file is a test case and each test case is - passed to the script as standard arguments. - - Returns - -------- - - returns (True, "Correct answer") : If the student script passes all - test cases/have same output, when compared to the instructor script - - returns (False, error_msg): If the student script fails a single - test/have dissimilar output, when compared to the instructor script. - - Returns (False, error_msg): If mandatory arguments are not files or if - the required permissions are not given to the file(s). - - """ - if not isfile(ref_path): - return False, "No file at %s or Incorrect path" % ref_path - if not isfile(submit_path): - return False, "No file at %s or Incorrect path" % submit_path - if not os.access(ref_path, os.X_OK): - return False, "Script %s is not executable" % ref_path - if not os.access(submit_path, os.X_OK): - return False, "Script %s is not executable" % submit_path - - success = False - - if test_case_path is None or "": - ret = self.run_command(ref_path, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, inst_stdout, inst_stderr = ret - ret = self.run_command(submit_path, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdnt_stdout, stdnt_stderr = ret - if inst_stdout == stdnt_stdout: - return True, "Correct answer" - else: - err = "Error: expected %s, got %s" % (inst_stderr, - stdnt_stderr) - return False, err - else: - if not isfile(test_case_path): - return False, "No test case at %s" % test_case_path - if not os.access(ref_path, os.R_OK): - return False, "Test script %s, not readable" % test_case_path - # valid_answer is True, so that we can stop once a test case fails - valid_answer = True - # loop_count has to be greater than or equal to one. - # Useful for caching things like empty test files,etc. - loop_count = 0 - test_cases = open(test_case_path).readlines() - num_lines = len(test_cases) - for test_case in test_cases: - loop_count += 1 - if valid_answer: - args = [ref_path] + [x for x in test_case.split()] - ret = self.run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, inst_stdout, inst_stderr = ret - args = [submit_path]+[x for x in test_case.split()] - ret = self.run_command(args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdnt_stdout, stdnt_stderr = ret - valid_answer = inst_stdout == stdnt_stdout - if valid_answer and (num_lines == loop_count): - return True, "Correct answer" - else: - err = "Error:expected %s, got %s" % (inst_stdout+inst_stderr, - stdnt_stdout+stdnt_stderr) - return False, err - - -registry.register('bash', EvaluateBashCode) diff --git a/testapp/exam/evaluate_c_code.py b/testapp/exam/evaluate_c_code.py deleted file mode 100644 index 0b9e352..0000000 --- a/testapp/exam/evaluate_c_code.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from evaluate_code import EvaluateCode -from language_registry import registry - - -class EvaluateCCode(EvaluateCode): - """Tests the C code obtained from Code Server""" - # Public Protocol ########## - def evaluate_code(self): - submit_path = self.create_submit_code_file('submit.c') - get_ref_path = self.ref_code_path - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) - success = False - - # Set file paths - c_user_output_path = os.getcwd() + '/output' - c_ref_output_path = os.getcwd() + '/executable' - - # Set command variables - compile_command = 'g++ {0} -c -o {1}'.format(submit_path, - c_user_output_path) - compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, - c_user_output_path, - c_ref_output_path) - run_command_args = [c_ref_output_path] - remove_user_output = c_user_output_path - remove_ref_output = c_ref_output_path - - success, err = self.check_code(ref_path, submit_path, compile_command, - compile_main, run_command_args, - remove_user_output, remove_ref_output) - - # Delete the created file. - os.remove(submit_path) - - return success, err - - # Public Protocol ########## - def check_code(self, ref_code_path, submit_code_path, compile_command, - compile_main, run_command_args, remove_user_output, - remove_ref_output): - """ Function validates student code using instructor code as - reference.The first argument ref_code_path, is the path to - instructor code, it is assumed to have executable permission. - The second argument submit_code_path, is the path to the student - code, it is assumed to have executable permission. - - Returns - -------- - - returns (True, "Correct answer") : If the student function returns - expected output when called by reference code. - - returns (False, error_msg): If the student function fails to return - expected output when called by reference code. - - Returns (False, error_msg): If mandatory arguments are not files or - if the required permissions are not given to the file(s). - - """ - if not isfile(ref_code_path): - return False, "No file at %s or Incorrect path" % ref_code_path - if not isfile(submit_code_path): - return False, 'No file at %s or Incorrect path' % submit_code_path - - success = False - # output_path = os.getcwd() + '/output' - ret = self.compile_command(compile_command) - proc, stdnt_stderr = ret - # if self.language == "java": - stdnt_stderr = self.remove_null_substitute_char(stdnt_stderr) - - # Only if compilation is successful, the program is executed - # And tested with testcases - if stdnt_stderr == '': - ret = self.compile_command(compile_main) - proc, main_err = ret - # if self.language == "java": - main_err = self.remove_null_substitute_char(main_err) - - if main_err == '': - ret = self.run_command(run_command_args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - if proc.returncode == 0: - success, err = True, "Correct answer" - else: - err = stdout + "\n" + stderr - os.remove(remove_ref_output) - else: - err = "Error:" - try: - error_lines = main_err.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + main_err - os.remove(remove_user_output) - else: - err = "Compilation Error:" - try: - error_lines = stdnt_stderr.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + stdnt_stderr - - return success, err - - # Public Protocol ########## - def remove_null_substitute_char(self, string): - """Returns a string without any null and substitute characters""" - stripped = "" - for c in string: - if ord(c) is not 26 and ord(c) is not 0: - stripped = stripped + c - return ''.join(stripped) - - -registry.register('c', EvaluateCCode) diff --git a/testapp/exam/evaluate_code.py b/testapp/exam/evaluate_code.py deleted file mode 100644 index b9892ed..0000000 --- a/testapp/exam/evaluate_code.py +++ /dev/null @@ -1,191 +0,0 @@ -import sys -from SimpleXMLRPCServer import SimpleXMLRPCServer -import pwd -import os -import stat -from os.path import isdir, dirname, abspath, join, isfile -import signal -from multiprocessing import Process, Queue -import subprocess -import re -import json -import importlib -# Local imports. -from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT - - -MY_DIR = abspath(dirname(__file__)) - - -# Raised when the code times-out. -# c.f. http://pguides.net/python/timeout-a-function -class TimeoutException(Exception): - pass - - -# Private Protocol ########## -def timeout_handler(signum, frame): - """A handler for the ALARM signal.""" - raise TimeoutException('Code took too long to run.') - - -def create_signal_handler(): - """Add a new signal handler for the execution of this code.""" - prev_handler = signal.signal(signal.SIGALRM, timeout_handler) - signal.alarm(SERVER_TIMEOUT) - return prev_handler - - -def set_original_signal_handler(old_handler=None): - """Set back any original signal handler.""" - if old_handler is not None: - signal.signal(signal.SIGALRM, old_handler) - return - else: - raise Exception("Signal Handler: object cannot be NoneType") - - -def delete_signal_handler(): - signal.alarm(0) - return - - -############################################################################### -# `TestCode` class. -############################################################################### -class EvaluateCode(object): - """Tests the code obtained from Code Server""" - def __init__(self, test_case_data, language, user_answer, - ref_code_path=None, in_dir=None): - msg = 'Code took more than %s seconds to run. You probably '\ - 'have an infinite loop in your code.' % SERVER_TIMEOUT - self.timeout_msg = msg - self.test_case_data = test_case_data - self.language = language.lower() - self.user_answer = user_answer - self.ref_code_path = ref_code_path - self.in_dir = in_dir - - # Public Protocol ########## - - @classmethod - def from_json(cls, language, json_data, in_dir): - json_data = json.loads(json_data) - test_case_data = json_data.get("test_case_data") - user_answer = json_data.get("user_answer") - ref_code_path = json_data.get("ref_code_path") - - instance = cls(Test_case_data, language, user_answer, ref_code_path, - in_dir) - return instance - - def run_code(self): - """Tests given code (`answer`) with the test cases based on - given arguments. - - The ref_code_path is a path to the reference code. - The reference code will call the function submitted by the student. - The reference code will check for the expected output. - - If the path's start with a "/" then we assume they are absolute paths. - If not, we assume they are relative paths w.r.t. the location of this - code_server script. - - If the optional `in_dir` keyword argument is supplied it changes the - directory to that directory (it does not change it back to the original - when done). - - Returns - ------- - - A tuple: (success, error message). - """ - self._change_dir(self.in_dir) - - # Add a new signal handler for the execution of this code. - prev_handler = create_signal_handler() - success = False - - # Do whatever testing needed. - try: - success, err = self.evaluate_code() - - except TimeoutException: - err = self.timeout_msg - except: - type, value = sys.exc_info()[:2] - err = "Error: {0}".format(repr(value)) - finally: - # Set back any original signal handler. - set_original_signal_handler(prev_handler) - - # Cancel the signal - delete_signal_handler() - - result = {'success': success, 'error': err} - return result - - def evaluate_code(self): - raise NotImplementedError("evaluate_code method not implemented") - - def create_submit_code_file(self, file_name): - """ Write the code (`answer`) to a file and set the file path""" - submit_f = open(file_name, 'w') - submit_f.write(self.user_answer.lstrip()) - submit_f.close() - submit_path = abspath(submit_f.name) - - return submit_path - - def set_file_as_executable(self, fname): - os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR - | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP - | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) - - def set_test_code_file_path(self, ref_path=None, test_case_path=None): - if ref_path and not ref_path.startswith('/'): - ref_path = join(MY_DIR, ref_path) - - if test_case_path and not test_case_path.startswith('/'): - test_case_path = join(MY_DIR, test_case_path) - - return ref_path, test_case_path - - def run_command(self, cmd_args, *args, **kw): - """Run a command in a subprocess while blocking, the process is killed - if it takes more than 2 seconds to run. Return the Popen object, the - stdout and stderr. - """ - try: - proc = subprocess.Popen(cmd_args, *args, **kw) - stdout, stderr = proc.communicate() - except TimeoutException: - # Runaway code, so kill it. - proc.kill() - # Re-raise exception. - raise - return proc, stdout, stderr - - def compile_command(self, cmd, *args, **kw): - """Compiles C/C++/java code and returns errors if any. - Run a command in a subprocess while blocking, the process is killed - if it takes more than 2 seconds to run. Return the Popen object, the - stderr. - """ - try: - proc_compile = subprocess.Popen(cmd, shell=True, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = proc_compile.communicate() - except TimeoutException: - # Runaway code, so kill it. - proc_compile.kill() - # Re-raise exception. - raise - return proc_compile, err - - # Private Protocol ########## - - def _change_dir(self, in_dir): - if in_dir is not None and isdir(in_dir): - os.chdir(in_dir) diff --git a/testapp/exam/evaluate_cpp_code.py b/testapp/exam/evaluate_cpp_code.py deleted file mode 100644 index 987d041..0000000 --- a/testapp/exam/evaluate_cpp_code.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from evaluate_c_code import EvaluateCCode -from evaluate_code import EvaluateCode -from language_registry import registry - - -class EvaluateCppCode(EvaluateCCode, EvaluateCode): - """Tests the C code obtained from Code Server""" - # Public Protocol ########## - def evaluate_code(self): - submit_path = self.create_submit_code_file('submitstd.cpp') - get_ref_path = self.ref_code_path - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) - success = False - - # Set file paths - c_user_output_path = os.getcwd() + '/output' - c_ref_output_path = os.getcwd() + '/executable' - - # Set command variables - compile_command = 'g++ {0} -c -o {1}'.format(submit_path, - c_user_output_path) - compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, - c_user_output_path, - c_ref_output_path) - run_command_args = c_ref_output_path - remove_user_output = c_user_output_path - remove_ref_output = c_ref_output_path - - success, err = self.check_code(ref_path, submit_path, compile_command, - compile_main, run_command_args, - remove_user_output, remove_ref_output) - - # Delete the created file. - os.remove(submit_path) - - return success, err - - -registry.register('cpp', EvaluateCppCode) diff --git a/testapp/exam/evaluate_java_code.py b/testapp/exam/evaluate_java_code.py deleted file mode 100644 index d04be4e..0000000 --- a/testapp/exam/evaluate_java_code.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from evaluate_c_code import EvaluateCCode -from evaluate_code import EvaluateCode -from language_registry import registry - - -class EvaluateJavaCode(EvaluateCCode, EvaluateCode): - """Tests the C code obtained from Code Server""" - # Public Protocol ########## - def evaluate_code(self): - submit_path = self.create_submit_code_file('Test.java') - ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) - success = False - - # Set file paths - java_student_directory = os.getcwd() + '/' - java_ref_file_name = (ref_path.split('/')[-1]).split('.')[0] - - # Set command variables - compile_command = 'javac {0}'.format(submit_path), - compile_main = ('javac {0} -classpath ' - '{1} -d {2}').format(ref_path, - java_student_directory, - java_student_directory) - run_command_args = "java -cp {0} {1}".format(java_student_directory, - java_ref_file_name) - remove_user_output = "{0}{1}.class".format(java_student_directory, - 'Test') - remove_ref_output = "{0}{1}.class".format(java_student_directory, - java_ref_file_name) - - success, err = self.check_code(ref_path, submit_path, compile_command, - compile_main, run_command_args, - remove_user_output, remove_ref_output) - - # Delete the created file. - os.remove(submit_path) - - return success, err - - -registry.register('java', EvaluateJavaCode) diff --git a/testapp/exam/evaluate_python_code.py b/testapp/exam/evaluate_python_code.py deleted file mode 100644 index 8892ae6..0000000 --- a/testapp/exam/evaluate_python_code.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python -import sys -import traceback -import os -from os.path import join -import importlib - -# local imports -from evaluate_code import EvaluateCode -from language_registry import registry - - -class EvaluatePythonCode(EvaluateCode): - """Tests the Python code obtained from Code Server""" - # Public Protocol ########## - def evaluate_code(self): - success = False - - try: - tb = None - test_code = self._create_test_case() - submitted = compile(self.user_answer, '', mode='exec') - g = {} - exec submitted in g - _tests = compile(test_code, '', mode='exec') - exec _tests in g - except AssertionError: - type, value, tb = sys.exc_info() - info = traceback.extract_tb(tb) - fname, lineno, func, text = info[-1] - text = str(test_code).splitlines()[lineno-1] - err = "{0} {1} in: {2}".format(type.__name__, str(value), text) - else: - success = True - err = 'Correct answer' - - del tb - return success, err - - # Private Protocol ########## - def _create_test_case(self): - """ - Create assert based test cases in python - """ - test_code = "" - for test_case in self.test_case_data: - pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) \ - if test_case.get('pos_args') else "" - kw_args = ", ".join(str(k+"="+a) for k, a - in test_case.get('kw_args').iteritems()) \ - if test_case.get('kw_args') else "" - args = pos_args + ", " + kw_args if pos_args and kw_args \ - else pos_args or kw_args - tcode = "assert {0}({1}) == {2}".format(test_case.get('func_name'), - args, test_case.get('expected_answer')) - test_code += tcode + "\n" - return test_code - - -registry.register('python', EvaluatePythonCode) diff --git a/testapp/exam/evaluate_scilab_code.py b/testapp/exam/evaluate_scilab_code.py deleted file mode 100644 index 899abc9..0000000 --- a/testapp/exam/evaluate_scilab_code.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python -import traceback -import os -from os.path import join, isfile -import subprocess -import re -import importlib - -# local imports -from evaluate_code import EvaluateCode -from language_registry import registry - - -class EvaluateScilabCode(EvaluateCode): - """Tests the Scilab code obtained from Code Server""" - # Public Protocol ########## - def evaluate_code(self): - submit_path = self.create_submit_code_file('function.sci') - ref_path, test_case_path = self.set_test_code_file_path() - success = False - - cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) - cmd += ' | timeout 8 scilab-cli -nb' - ret = self.run_command(cmd, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - - # Get only the error. - stderr = self._get_error(stdout) - if stderr is None: - # Clean output - stdout = self._strip_output(stdout) - if proc.returncode == 5: - success, err = True, "Correct answer" - else: - err = add_err + stdout - else: - err = add_err + stderr - - # Delete the created file. - os.remove(submit_path) - - return success, err - - # Private Protocol ########## - def _remove_scilab_exit(self, string): - """ - Removes exit, quit and abort from the scilab code - """ - new_string = "" - i = 0 - for line in string.splitlines(): - new_line = re.sub(r"exit.*$", "", line) - new_line = re.sub(r"quit.*$", "", new_line) - new_line = re.sub(r"abort.*$", "", new_line) - if line != new_line: - i = i + 1 - new_string = new_string + '\n' + new_line - return new_string, i - - # Private Protocol ########## - def _get_error(self, string): - """ - Fetches only the error from the string. - Returns None if no error. - """ - obj = re.search("!.+\n.+", string) - if obj: - return obj.group() - return None - - # Private Protocol ########## - def _strip_output(self, out): - """ - Cleans whitespace from the output - """ - strip_out = "Message" - for l in out.split('\n'): - if l.strip(): - strip_out = strip_out+"\n"+l.strip() - return strip_out - - -registry.register('scilab', EvaluateScilabCode) diff --git a/testapp/exam/java_code_evaluator.py b/testapp/exam/java_code_evaluator.py new file mode 100644 index 0000000..4a80acd --- /dev/null +++ b/testapp/exam/java_code_evaluator.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +# from c_code_evaluator import CCodeEvaluator +from code_evaluator import CodeEvaluator +# from language_registry import registry + + +class JavaCodeEvaluator(CodeEvaluator): + """Tests the Java code obtained from Code Server""" + def __init__(self, test_case_data, language, user_answer, + ref_code_path=None, in_dir=None): + super(JavaCodeEvaluator, self).__init__(test_case_data, language, user_answer, + ref_code_path, in_dir) + self.submit_path = self.create_submit_code_file('Test.java') + self.test_case_args = self.setup_code_evaluator() + + # Private Protocol ########## + def setup_code_evaluator(self): + super(JavaCodeEvaluator, self).setup_code_evaluator() + + ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) + + # Set file paths + java_student_directory = os.getcwd() + '/' + java_ref_file_name = (ref_path.split('/')[-1]).split('.')[0] + + # Set command variables + compile_command = 'javac {0}'.format(submit_path), + compile_main = ('javac {0} -classpath ' + '{1} -d {2}').format(ref_path, + java_student_directory, + java_student_directory) + run_command_args = "java -cp {0} {1}".format(java_student_directory, + java_ref_file_name) + remove_user_output = "{0}{1}.class".format(java_student_directory, + 'Test') + remove_ref_output = "{0}{1}.class".format(java_student_directory, + java_ref_file_name) + + return ref_path, submit_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output + + def teardown_code_evaluator(self): + # Delete the created file. + super(JavaCodeEvaluator, self).teardown_code_evaluator() + os.remove(self.submit_path) + + + # Public Protocol ########## + # def evaluate_code(self): + # submit_path = self.create_submit_code_file('Test.java') + # ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) + # success = False + + # # Set file paths + # java_student_directory = os.getcwd() + '/' + # java_ref_file_name = (ref_path.split('/')[-1]).split('.')[0] + + # # Set command variables + # compile_command = 'javac {0}'.format(submit_path), + # compile_main = ('javac {0} -classpath ' + # '{1} -d {2}').format(ref_path, + # java_student_directory, + # java_student_directory) + # run_command_args = "java -cp {0} {1}".format(java_student_directory, + # java_ref_file_name) + # remove_user_output = "{0}{1}.class".format(java_student_directory, + # 'Test') + # remove_ref_output = "{0}{1}.class".format(java_student_directory, + # java_ref_file_name) + + # success, err = self.check_code(ref_path, submit_path, compile_command, + # compile_main, run_command_args, + # remove_user_output, remove_ref_output) + + # # Delete the created file. + # os.remove(submit_path) + + # return success, err + + def check_code(self, ref_code_path, submit_code_path, compile_command, + compile_main, run_command_args, remove_user_output, + remove_ref_output): + """ Function validates student code using instructor code as + reference.The first argument ref_code_path, is the path to + instructor code, it is assumed to have executable permission. + The second argument submit_code_path, is the path to the student + code, it is assumed to have executable permission. + + Returns + -------- + + returns (True, "Correct answer") : If the student function returns + expected output when called by reference code. + + returns (False, error_msg): If the student function fails to return + expected output when called by reference code. + + Returns (False, error_msg): If mandatory arguments are not files or + if the required permissions are not given to the file(s). + + """ + if not isfile(ref_code_path): + return False, "No file at %s or Incorrect path" % ref_code_path + if not isfile(submit_code_path): + return False, 'No file at %s or Incorrect path' % submit_code_path + + success = False + # output_path = os.getcwd() + '/output' + ret = self.compile_command(compile_command) + proc, stdnt_stderr = ret + # if self.language == "java": + stdnt_stderr = self.remove_null_substitute_char(stdnt_stderr) + + # Only if compilation is successful, the program is executed + # And tested with testcases + if stdnt_stderr == '': + ret = self.compile_command(compile_main) + proc, main_err = ret + # if self.language == "java": + main_err = self.remove_null_substitute_char(main_err) + + if main_err == '': + ret = self.run_command(run_command_args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + if proc.returncode == 0: + success, err = True, "Correct answer" + else: + err = stdout + "\n" + stderr + os.remove(remove_ref_output) + else: + err = "Error:" + try: + error_lines = main_err.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + main_err + os.remove(remove_user_output) + else: + err = "Compilation Error:" + try: + error_lines = stdnt_stderr.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + stdnt_stderr + + return success, err + + +# registry.register('java', EvaluateJavaCode) diff --git a/testapp/exam/language_registry.py b/testapp/exam/language_registry.py index 4d56de2..8700d32 100644 --- a/testapp/exam/language_registry.py +++ b/testapp/exam/language_registry.py @@ -1,17 +1,31 @@ -#!/usr/bin/env python +from settings import language_register +registry = None -class LanguageRegistry(object): +def set_registry(): + globals registry = _LanguageRegistry() + +def get_registry(): + return registry + +class _LanguageRegistry(object): def __init__(self): - self._registry = {} + for language, module in language_register.iteritems(): + self._register[language] = None # Public Protocol ########## def get_class(self, language): - return self._registry[language] + if not self._register[language]: + self._register[language] = language_register[language] - # Public Protocol ########## - def register(self, language, cls): - self._registry[language] = cls + cls = self._register[language] + module_name, class_name = cls.split(".") + # load the module, will raise ImportError if module cannot be loaded + get_module = importlib.import_module(module_name) + # get the class, will raise AttributeError if class cannot be found + get_class = getattr(get_module, class_name) + return get_class + # def register(self, language, cls): + # self._register[language] = cls -registry = LanguageRegistry() diff --git a/testapp/exam/python_code_evaluator.py b/testapp/exam/python_code_evaluator.py new file mode 100644 index 0000000..05a5063 --- /dev/null +++ b/testapp/exam/python_code_evaluator.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +import sys +import traceback +import os +from os.path import join +import importlib + +# local imports +from code_evaluator import CodeEvaluator +# from language_registry import registry + + +class PythonCodeEvaluator(CodeEvaluator): + """Tests the Python code obtained from Code Server""" + # Private Protocol ########## + def check_code(self): + success = False + + try: + tb = None + test_code = self._create_test_case() + submitted = compile(self.user_answer, '', mode='exec') + g = {} + exec submitted in g + _tests = compile(test_code, '', mode='exec') + exec _tests in g + except AssertionError: + type, value, tb = sys.exc_info() + info = traceback.extract_tb(tb) + fname, lineno, func, text = info[-1] + text = str(test_code).splitlines()[lineno-1] + err = "{0} {1} in: {2}".format(type.__name__, str(value), text) + else: + success = True + err = 'Correct answer' + + del tb + return success, err + + # # Public Protocol ########## + # def evaluate_code(self): + # success = False + + # try: + # tb = None + # test_code = self._create_test_case() + # submitted = compile(self.user_answer, '', mode='exec') + # g = {} + # exec submitted in g + # _tests = compile(test_code, '', mode='exec') + # exec _tests in g + # except AssertionError: + # type, value, tb = sys.exc_info() + # info = traceback.extract_tb(tb) + # fname, lineno, func, text = info[-1] + # text = str(test_code).splitlines()[lineno-1] + # err = "{0} {1} in: {2}".format(type.__name__, str(value), text) + # else: + # success = True + # err = 'Correct answer' + + # del tb + # return success, err + + # Private Protocol ########## + def _create_test_case(self): + """ + Create assert based test cases in python + """ + test_code = "" + for test_case in self.test_case_data: + pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) \ + if test_case.get('pos_args') else "" + kw_args = ", ".join(str(k+"="+a) for k, a + in test_case.get('kw_args').iteritems()) \ + if test_case.get('kw_args') else "" + args = pos_args + ", " + kw_args if pos_args and kw_args \ + else pos_args or kw_args + tcode = "assert {0}({1}) == {2}".format(test_case.get('func_name'), + args, test_case.get('expected_answer')) + test_code += tcode + "\n" + return test_code + + +# registry.register('python', EvaluatePythonCode) diff --git a/testapp/exam/scilab_code_evaluator.py b/testapp/exam/scilab_code_evaluator.py new file mode 100644 index 0000000..073fbcb --- /dev/null +++ b/testapp/exam/scilab_code_evaluator.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +import traceback +import os +from os.path import join, isfile +import subprocess +import re +import importlib + +# local imports +from code_evaluator import CodeEvaluator +# from language_registry import registry + + +class ScilabCodeEvaluator(CodeEvaluator): + """Tests the Scilab code obtained from Code Server""" + def __init__(self, test_case_data, language, user_answer, + ref_code_path=None, in_dir=None): + super(ScilabCodeEvaluator, self).__init__(test_case_data, language, user_answer, + ref_code_path, in_dir) + self.submit_path = self.create_submit_code_file('function.sci') + self.test_case_args = self.setup_code_evaluator() + + # Private Protocol ########## + def setup_code_evaluator(self): + super(ScilabCodeEvaluator, self).setup_code_evaluator() + + ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) + + return ref_path, # Return as a tuple + + def teardown_code_evaluator(self): + # Delete the created file. + super(ScilabCodeEvaluator, self).teardown_code_evaluator() + os.remove(self.submit_path) + + def check_code(self, ref_path): + success = False + + cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) + cmd += ' | timeout 8 scilab-cli -nb' + ret = self.run_command(cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + + # Get only the error. + stderr = self._get_error(stdout) + if stderr is None: + # Clean output + stdout = self._strip_output(stdout) + if proc.returncode == 5: + success, err = True, "Correct answer" + else: + err = add_err + stdout + else: + err = add_err + stderr + + return success, err + + # # Public Protocol ########## + # def evaluate_code(self): + # submit_path = self.create_submit_code_file('function.sci') + # ref_path, test_case_path = self.set_test_code_file_path() + # success = False + + # cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) + # cmd += ' | timeout 8 scilab-cli -nb' + # ret = self.run_command(cmd, + # shell=True, + # stdout=subprocess.PIPE, + # stderr=subprocess.PIPE) + # proc, stdout, stderr = ret + + # # Get only the error. + # stderr = self._get_error(stdout) + # if stderr is None: + # # Clean output + # stdout = self._strip_output(stdout) + # if proc.returncode == 5: + # success, err = True, "Correct answer" + # else: + # err = add_err + stdout + # else: + # err = add_err + stderr + + # # Delete the created file. + # os.remove(submit_path) + + # return success, err + + # Private Protocol ########## + def _remove_scilab_exit(self, string): + """ + Removes exit, quit and abort from the scilab code + """ + new_string = "" + i = 0 + for line in string.splitlines(): + new_line = re.sub(r"exit.*$", "", line) + new_line = re.sub(r"quit.*$", "", new_line) + new_line = re.sub(r"abort.*$", "", new_line) + if line != new_line: + i = i + 1 + new_string = new_string + '\n' + new_line + return new_string, i + + # Private Protocol ########## + def _get_error(self, string): + """ + Fetches only the error from the string. + Returns None if no error. + """ + obj = re.search("!.+\n.+", string) + if obj: + return obj.group() + return None + + # Private Protocol ########## + def _strip_output(self, out): + """ + Cleans whitespace from the output + """ + strip_out = "Message" + for l in out.split('\n'): + if l.strip(): + strip_out = strip_out+"\n"+l.strip() + return strip_out + + +# registry.register('scilab', EvaluateScilabCode) diff --git a/testapp/exam/settings.py b/testapp/exam/settings.py index 682516f..497a620 100644 --- a/testapp/exam/settings.py +++ b/testapp/exam/settings.py @@ -18,3 +18,11 @@ SERVER_TIMEOUT = 2 # reason set this to the root you have to serve at. In the above example # host.org/foo/exam set URL_ROOT='/foo' URL_ROOT = '' + +language_register = {"python": "python_code_evaluator", + "c": "c_cpp_code_evaluator", + "cpp": "c_cpp_code_evaluator", + "java": "java_evaluator", + "bash": "bash_evaluator", + "scilab": "scilab_evaluator", + } -- cgit From 3e29dc7f6df7019562b179872b43cb13c7483738 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Thu, 30 Apr 2015 12:41:17 +0530 Subject: - Seperate testcases, Modify views, models, templates for compatibility - Change functions names in code_evaluator --- testapp/exam/bash_code_evaluator.py | 40 ++---- testapp/exam/c_cpp_code_evaluator.py | 168 -------------------------- testapp/exam/code_evaluator.py | 83 +++---------- testapp/exam/code_server.py | 3 +- testapp/exam/cpp_code_evaluator.py | 126 +++++++++++++++++++ testapp/exam/java_code_evaluator.py | 68 +++-------- testapp/exam/language_registry.py | 21 ++-- testapp/exam/models.py | 3 +- testapp/exam/python_code_evaluator.py | 59 +++------ testapp/exam/scilab_code_evaluator.py | 55 ++------- testapp/exam/settings.py | 12 +- testapp/exam/static/exam/js/add_question.js | 10 -- testapp/exam/templates/exam/add_question.html | 2 +- testapp/exam/tests.py | 1 - testapp/exam/views.py | 16 +-- 15 files changed, 227 insertions(+), 440 deletions(-) delete mode 100644 testapp/exam/c_cpp_code_evaluator.py create mode 100644 testapp/exam/cpp_code_evaluator.py (limited to 'testapp/exam') diff --git a/testapp/exam/bash_code_evaluator.py b/testapp/exam/bash_code_evaluator.py index 60f0bb3..7fcfb0f 100644 --- a/testapp/exam/bash_code_evaluator.py +++ b/testapp/exam/bash_code_evaluator.py @@ -8,20 +8,20 @@ import importlib # local imports from code_evaluator import CodeEvaluator -# from language_registry import registry class BashCodeEvaluator(CodeEvaluator): """Tests the Bash code obtained from Code Server""" - def __init__(self, test_case_data, language, user_answer, + def __init__(self, test_case_data, test, language, user_answer, ref_code_path=None, in_dir=None): - super(BashCodeEvaluator, self).__init__(test_case_data, language, user_answer, + super(BashCodeEvaluator, self).__init__(test_case_data, test, language, user_answer, ref_code_path, in_dir) self.submit_path = self.create_submit_code_file('submit.sh') - self.test_case_args = self.setup_code_evaluator() + self.test_case_args = self._setup() - def setup_code_evaluator(self): - super(BashCodeEvaluator, self).setup_code_evaluator() + # Private Protocol ########## + def _setup(self): + super(BashCodeEvaluator, self)._setup() self.set_file_as_executable(self.submit_path) get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') @@ -32,28 +32,12 @@ class BashCodeEvaluator(CodeEvaluator): return ref_path, self.submit_path, test_case_path + def _teardown(self): + # Delete the created file. + super(BashCodeEvaluator, self)._teardown() + os.remove(self.submit_path) - # # Public Protocol ########## - # def evaluate_code(self): - # submit_path = self.create_submit_code_file('submit.sh') - # self.set_file_as_executable(submit_path) - # get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') - # get_ref_path = get_ref_path.strip() - # get_test_case_path = get_test_case_path.strip() - # ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, - # get_test_case_path) - - # success, err = self._check_bash_script(ref_path, submit_path, - # test_case_path) - - # # Delete the created file. - # os.remove(submit_path) - - # return success, err - - - # Private Protocol ########## - def check_code(self, ref_path, submit_path, + def _check_code(self, ref_path, submit_path, test_case_path=None): """ Function validates student script using instructor script as reference. Test cases can optionally be provided. The first argument @@ -136,5 +120,3 @@ class BashCodeEvaluator(CodeEvaluator): stdnt_stdout+stdnt_stderr) return False, err - -# registry.register('bash', EvaluateBashCode) diff --git a/testapp/exam/c_cpp_code_evaluator.py b/testapp/exam/c_cpp_code_evaluator.py deleted file mode 100644 index d611f96..0000000 --- a/testapp/exam/c_cpp_code_evaluator.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env python -import traceback -import pwd -import os -from os.path import join, isfile -import subprocess -import importlib - -# local imports -from code_evaluator import CodeEvaluator -# from language_registry import registry - - -class CCppCodeEvaluator(CodeEvaluator): - """Tests the C code obtained from Code Server""" - def __init__(self, test_case_data, language, user_answer, - ref_code_path=None, in_dir=None): - super(CCppCodeEvaluator, self).__init__(test_case_data, language, user_answer, - ref_code_path, in_dir) - self.submit_path = self.create_submit_code_file('submit.c') - self.test_case_args = self.setup_code_evaluator() - - # Private Protocol ########## - def setup_code_evaluator(self): - super(CCppCodeEvaluator, self).setup_code_evaluator() - - get_ref_path = self.ref_code_path - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) - - # Set file paths - c_user_output_path = os.getcwd() + '/output' - c_ref_output_path = os.getcwd() + '/executable' - - # Set command variables - compile_command = 'g++ {0} -c -o {1}'.format(self.submit_path, - c_user_output_path) - compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, - c_user_output_path, - c_ref_output_path) - run_command_args = [c_ref_output_path] - remove_user_output = c_user_output_path - remove_ref_output = c_ref_output_path - - return ref_path, self.submit_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output - - def teardown_code_evaluator(self): - # Delete the created file. - super(CCppCodeEvaluator, self).teardown_code_evaluator() - os.remove(self.submit_path) - - # # Public Protocol ########## - # def evaluate_code(self): - # submit_path = self.create_submit_code_file('submit.c') - # get_ref_path = self.ref_code_path - # ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) - # success = False - - # # Set file paths - # c_user_output_path = os.getcwd() + '/output' - # c_ref_output_path = os.getcwd() + '/executable' - - # # Set command variables - # compile_command = 'g++ {0} -c -o {1}'.format(submit_path, - # c_user_output_path) - # compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, - # c_user_output_path, - # c_ref_output_path) - # run_command_args = [c_ref_output_path] - # remove_user_output = c_user_output_path - # remove_ref_output = c_ref_output_path - - # success, err = self.check_code(ref_path, submit_path, compile_command, - # compile_main, run_command_args, - # remove_user_output, remove_ref_output) - - # # Delete the created file. - # os.remove(submit_path) - - # return success, err - - def check_code(self, ref_code_path, submit_code_path, compile_command, - compile_main, run_command_args, remove_user_output, - remove_ref_output): - """ Function validates student code using instructor code as - reference.The first argument ref_code_path, is the path to - instructor code, it is assumed to have executable permission. - The second argument submit_code_path, is the path to the student - code, it is assumed to have executable permission. - - Returns - -------- - - returns (True, "Correct answer") : If the student function returns - expected output when called by reference code. - - returns (False, error_msg): If the student function fails to return - expected output when called by reference code. - - Returns (False, error_msg): If mandatory arguments are not files or - if the required permissions are not given to the file(s). - - """ - if not isfile(ref_code_path): - return False, "No file at %s or Incorrect path" % ref_code_path - if not isfile(submit_code_path): - return False, 'No file at %s or Incorrect path' % submit_code_path - - success = False - # output_path = os.getcwd() + '/output' - ret = self.compile_command(compile_command) - proc, stdnt_stderr = ret - # if self.language == "java": - stdnt_stderr = self.remove_null_substitute_char(stdnt_stderr) - - # Only if compilation is successful, the program is executed - # And tested with testcases - if stdnt_stderr == '': - ret = self.compile_command(compile_main) - proc, main_err = ret - # if self.language == "java": - main_err = self.remove_null_substitute_char(main_err) - - if main_err == '': - ret = self.run_command(run_command_args, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc, stdout, stderr = ret - if proc.returncode == 0: - success, err = True, "Correct answer" - else: - err = stdout + "\n" + stderr - os.remove(remove_ref_output) - else: - err = "Error:" - try: - error_lines = main_err.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + main_err - os.remove(remove_user_output) - else: - err = "Compilation Error:" - try: - error_lines = stdnt_stderr.splitlines() - for e in error_lines: - if ':' in e: - err = err + "\n" + e.split(":", 1)[1] - else: - err = err + "\n" + e - except: - err = err + "\n" + stdnt_stderr - - return success, err - - def remove_null_substitute_char(self, string): - """Returns a string without any null and substitute characters""" - stripped = "" - for c in string: - if ord(c) is not 26 and ord(c) is not 0: - stripped = stripped + c - return ''.join(stripped) - - -# registry.register('c', EvaluateCCode) diff --git a/testapp/exam/code_evaluator.py b/testapp/exam/code_evaluator.py index 3cc7374..1efd519 100644 --- a/testapp/exam/code_evaluator.py +++ b/testapp/exam/code_evaluator.py @@ -54,7 +54,7 @@ def delete_signal_handler(): ############################################################################### class CodeEvaluator(object): """Tests the code obtained from Code Server""" - def __init__(self, test_case_data, language, user_answer, + def __init__(self, test_case_data, test, language, user_answer, ref_code_path=None, in_dir=None): msg = 'Code took more than %s seconds to run. You probably '\ 'have an infinite loop in your code.' % SERVER_TIMEOUT @@ -63,6 +63,7 @@ class CodeEvaluator(object): self.language = language.lower() self.user_answer = user_answer self.ref_code_path = ref_code_path + self.test = test self.in_dir = in_dir self.test_case_args = None @@ -73,60 +74,15 @@ class CodeEvaluator(object): test_case_data = json_data.get("test_case_data") user_answer = json_data.get("user_answer") ref_code_path = json_data.get("ref_code_path") + test = json_data.get("test") - instance = cls(Test_case_data, language, user_answer, ref_code_path, + instance = cls(test_case_data, test, language, user_answer, ref_code_path, in_dir) return instance - # def run_code(self): - # """Tests given code (`answer`) with the test cases based on - # given arguments. - - # The ref_code_path is a path to the reference code. - # The reference code will call the function submitted by the student. - # The reference code will check for the expected output. - - # If the path's start with a "/" then we assume they are absolute paths. - # If not, we assume they are relative paths w.r.t. the location of this - # code_server script. - - # If the optional `in_dir` keyword argument is supplied it changes the - # directory to that directory (it does not change it back to the original - # when done). - - # Returns - # ------- - - # A tuple: (success, error message). - # """ - # self._change_dir(self.in_dir) - - # # Add a new signal handler for the execution of this code. - # prev_handler = self.create_signal_handler() - # success = False - - # # Do whatever testing needed. - # try: - # success, err = self.evaluate_code() #pass *list where list is a list of args obtained from setup - - # except TimeoutException: - # err = self.timeout_msg - # except: - # type, value = sys.exc_info()[:2] - # err = "Error: {0}".format(repr(value)) - # finally: - # # Set back any original signal handler. - # self.set_original_signal_handler(prev_handler) - - # # Cancel the signal - # self.delete_signal_handler() - - # result = {'success': success, 'error': err} - # return result - - def code_evaluator(self): - """Tests given code (`answer`) with the test cases based on - given arguments. + def evaluate(self): + """Evaluates given code with the test cases based on + given arguments in test_case_data. The ref_code_path is a path to the reference code. The reference code will call the function submitted by the student. @@ -146,18 +102,18 @@ class CodeEvaluator(object): A tuple: (success, error message). """ - self.setup_code_evaluator() - success, err = self.evaluate_code(self.test_case_args) - self.teardown_code_evaluator() + self._setup() + success, err = self._evaluate(self.test_case_args) + self._teardown() result = {'success': success, 'error': err} return result - # Public Protocol ########## - def setup_code_evaluator(self): + # Private Protocol ########## + def _setup(self): self._change_dir(self.in_dir) - def evaluate_code(self, args): + def _evaluate(self, args): # Add a new signal handler for the execution of this code. prev_handler = create_signal_handler() success = False @@ -165,7 +121,7 @@ class CodeEvaluator(object): # Do whatever testing needed. try: - success, err = self.check_code(*args) + success, err = self._check_code(*args) except TimeoutException: err = self.timeout_msg @@ -178,14 +134,13 @@ class CodeEvaluator(object): return success, err - def teardown_code_evaluator(self): + def _teardown(self): # Cancel the signal delete_signal_handler() - def check_code(self): + def _check_code(self): raise NotImplementedError("check_code method not implemented") - # Private Protocol ########## def create_submit_code_file(self, file_name): """ Write the code (`answer`) to a file and set the file path""" submit_f = open(file_name, 'w') @@ -209,7 +164,7 @@ class CodeEvaluator(object): return ref_path, test_case_path - def run_command(self, cmd_args, *args, **kw): + def _run_command(self, cmd_args, *args, **kw): """Run a command in a subprocess while blocking, the process is killed if it takes more than 2 seconds to run. Return the Popen object, the stdout and stderr. @@ -224,7 +179,7 @@ class CodeEvaluator(object): raise return proc, stdout, stderr - def compile_command(self, cmd, *args, **kw): + def _compile_command(self, cmd, *args, **kw): """Compiles C/C++/java code and returns errors if any. Run a command in a subprocess while blocking, the process is killed if it takes more than 2 seconds to run. Return the Popen object, the @@ -246,7 +201,7 @@ class CodeEvaluator(object): if in_dir is not None and isdir(in_dir): os.chdir(in_dir) - def remove_null_substitute_char(self, string): + def _remove_null_substitute_char(self, string): """Returns a string without any null and substitute characters""" stripped = "" for c in string: diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index c621dcd..3dd5072 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -29,7 +29,6 @@ from multiprocessing import Process, Queue import subprocess import re import json -import importlib # Local imports. from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT from language_registry import set_registry @@ -71,7 +70,7 @@ class CodeServer(object): """ code_evaluator = self._create_evaluator_instance(language, json_data, in_dir) - result = code_evaluator.code_evaluator() + result = code_evaluator.evaluate() # Put us back into the server pool queue since we are free now. self.queue.put(self.port) diff --git a/testapp/exam/cpp_code_evaluator.py b/testapp/exam/cpp_code_evaluator.py new file mode 100644 index 0000000..04efba8 --- /dev/null +++ b/testapp/exam/cpp_code_evaluator.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +import traceback +import pwd +import os +from os.path import join, isfile +import subprocess +import importlib + +# local imports +from code_evaluator import CodeEvaluator + + +class CppCodeEvaluator(CodeEvaluator): + """Tests the C code obtained from Code Server""" + def __init__(self, test_case_data, test, language, user_answer, + ref_code_path=None, in_dir=None): + super(CppCodeEvaluator, self).__init__(test_case_data, test, language, user_answer, + ref_code_path, in_dir) + self.submit_path = self.create_submit_code_file('submit.c') + self.test_case_args = self._setup() + + # Private Protocol ########## + def _setup(self): + super(CppCodeEvaluator, self)._setup() + + get_ref_path = self.ref_code_path + ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) + + # Set file paths + c_user_output_path = os.getcwd() + '/output' + c_ref_output_path = os.getcwd() + '/executable' + + # Set command variables + compile_command = 'g++ {0} -c -o {1}'.format(self.submit_path, + c_user_output_path) + compile_main = 'g++ {0} {1} -o {2}'.format(ref_path, + c_user_output_path, + c_ref_output_path) + run_command_args = [c_ref_output_path] + remove_user_output = c_user_output_path + remove_ref_output = c_ref_output_path + + return ref_path, self.submit_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output + + def _teardown(self): + # Delete the created file. + super(CppCodeEvaluator, self)._teardown() + os.remove(self.submit_path) + + def _check_code(self, ref_code_path, submit_code_path, compile_command, + compile_main, run_command_args, remove_user_output, + remove_ref_output): + """ Function validates student code using instructor code as + reference.The first argument ref_code_path, is the path to + instructor code, it is assumed to have executable permission. + The second argument submit_code_path, is the path to the student + code, it is assumed to have executable permission. + + Returns + -------- + + returns (True, "Correct answer") : If the student function returns + expected output when called by reference code. + + returns (False, error_msg): If the student function fails to return + expected output when called by reference code. + + Returns (False, error_msg): If mandatory arguments are not files or + if the required permissions are not given to the file(s). + + """ + if not isfile(ref_code_path): + return False, "No file at %s or Incorrect path" % ref_code_path + if not isfile(submit_code_path): + return False, 'No file at %s or Incorrect path' % submit_code_path + + success = False + # output_path = os.getcwd() + '/output' + ret = self._compile_command(compile_command) + proc, stdnt_stderr = ret + # if self.language == "java": + stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) + + # Only if compilation is successful, the program is executed + # And tested with testcases + if stdnt_stderr == '': + ret = self._compile_command(compile_main) + proc, main_err = ret + # if self.language == "java": + main_err = self._remove_null_substitute_char(main_err) + + if main_err == '': + ret = self._run_command(run_command_args, stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc, stdout, stderr = ret + if proc.returncode == 0: + success, err = True, "Correct answer" + else: + err = stdout + "\n" + stderr + os.remove(remove_ref_output) + else: + err = "Error:" + try: + error_lines = main_err.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + main_err + os.remove(remove_user_output) + else: + err = "Compilation Error:" + try: + error_lines = stdnt_stderr.splitlines() + for e in error_lines: + if ':' in e: + err = err + "\n" + e.split(":", 1)[1] + else: + err = err + "\n" + e + except: + err = err + "\n" + stdnt_stderr + + return success, err diff --git a/testapp/exam/java_code_evaluator.py b/testapp/exam/java_code_evaluator.py index 4a80acd..709a0a1 100644 --- a/testapp/exam/java_code_evaluator.py +++ b/testapp/exam/java_code_evaluator.py @@ -7,23 +7,21 @@ import subprocess import importlib # local imports -# from c_code_evaluator import CCodeEvaluator from code_evaluator import CodeEvaluator -# from language_registry import registry class JavaCodeEvaluator(CodeEvaluator): """Tests the Java code obtained from Code Server""" - def __init__(self, test_case_data, language, user_answer, + def __init__(self, test_case_data, test, language, user_answer, ref_code_path=None, in_dir=None): - super(JavaCodeEvaluator, self).__init__(test_case_data, language, user_answer, + super(JavaCodeEvaluator, self).__init__(test_case_data, test, language, user_answer, ref_code_path, in_dir) self.submit_path = self.create_submit_code_file('Test.java') - self.test_case_args = self.setup_code_evaluator() + self.test_case_args = self._setup() # Private Protocol ########## - def setup_code_evaluator(self): - super(JavaCodeEvaluator, self).setup_code_evaluator() + def _setup(self): + super(JavaCodeEvaluator, self)._setup() ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) @@ -32,7 +30,7 @@ class JavaCodeEvaluator(CodeEvaluator): java_ref_file_name = (ref_path.split('/')[-1]).split('.')[0] # Set command variables - compile_command = 'javac {0}'.format(submit_path), + compile_command = 'javac {0}'.format(self.submit_path), compile_main = ('javac {0} -classpath ' '{1} -d {2}').format(ref_path, java_student_directory, @@ -44,47 +42,14 @@ class JavaCodeEvaluator(CodeEvaluator): remove_ref_output = "{0}{1}.class".format(java_student_directory, java_ref_file_name) - return ref_path, submit_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output + return ref_path, self.submit_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output - def teardown_code_evaluator(self): + def _teardown(self): # Delete the created file. - super(JavaCodeEvaluator, self).teardown_code_evaluator() + super(JavaCodeEvaluator, self)._teardown() os.remove(self.submit_path) - - # Public Protocol ########## - # def evaluate_code(self): - # submit_path = self.create_submit_code_file('Test.java') - # ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) - # success = False - - # # Set file paths - # java_student_directory = os.getcwd() + '/' - # java_ref_file_name = (ref_path.split('/')[-1]).split('.')[0] - - # # Set command variables - # compile_command = 'javac {0}'.format(submit_path), - # compile_main = ('javac {0} -classpath ' - # '{1} -d {2}').format(ref_path, - # java_student_directory, - # java_student_directory) - # run_command_args = "java -cp {0} {1}".format(java_student_directory, - # java_ref_file_name) - # remove_user_output = "{0}{1}.class".format(java_student_directory, - # 'Test') - # remove_ref_output = "{0}{1}.class".format(java_student_directory, - # java_ref_file_name) - - # success, err = self.check_code(ref_path, submit_path, compile_command, - # compile_main, run_command_args, - # remove_user_output, remove_ref_output) - - # # Delete the created file. - # os.remove(submit_path) - - # return success, err - - def check_code(self, ref_code_path, submit_code_path, compile_command, + def _check_code(self, ref_code_path, submit_code_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output): """ Function validates student code using instructor code as @@ -113,21 +78,21 @@ class JavaCodeEvaluator(CodeEvaluator): success = False # output_path = os.getcwd() + '/output' - ret = self.compile_command(compile_command) + ret = self._compile_command(compile_command) proc, stdnt_stderr = ret # if self.language == "java": - stdnt_stderr = self.remove_null_substitute_char(stdnt_stderr) + stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) # Only if compilation is successful, the program is executed # And tested with testcases if stdnt_stderr == '': - ret = self.compile_command(compile_main) + ret = self._compile_command(compile_main) proc, main_err = ret # if self.language == "java": - main_err = self.remove_null_substitute_char(main_err) + main_err = self._remove_null_substitute_char(main_err) if main_err == '': - ret = self.run_command(run_command_args, stdin=None, + ret = self._run_command(run_command_args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc, stdout, stderr = ret @@ -161,6 +126,3 @@ class JavaCodeEvaluator(CodeEvaluator): err = err + "\n" + stdnt_stderr return success, err - - -# registry.register('java', EvaluateJavaCode) diff --git a/testapp/exam/language_registry.py b/testapp/exam/language_registry.py index 8700d32..76a23d7 100644 --- a/testapp/exam/language_registry.py +++ b/testapp/exam/language_registry.py @@ -1,31 +1,36 @@ -from settings import language_register +from settings import code_evaluators +import importlib registry = None def set_registry(): - globals registry = _LanguageRegistry() + global registry + registry = _LanguageRegistry() def get_registry(): return registry class _LanguageRegistry(object): def __init__(self): - for language, module in language_register.iteritems(): + self._register = {} + for language, module in code_evaluators.iteritems(): self._register[language] = None # Public Protocol ########## def get_class(self, language): - if not self._register[language]: - self._register[language] = language_register[language] + """ Get the code evaluator class for the given language """ + if not self._register.get(language): + self._register[language] = code_evaluators.get(language) cls = self._register[language] - module_name, class_name = cls.split(".") + module_name, class_name = cls.rsplit(".", 1) # load the module, will raise ImportError if module cannot be loaded get_module = importlib.import_module(module_name) # get the class, will raise AttributeError if class cannot be found get_class = getattr(get_module, class_name) return get_class - # def register(self, language, cls): - # self._register[language] = cls + def register(self, language, class_name): + """ Register a new code evaluator class for language""" + self._register[language] = class_name diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 51e773a..a60550c 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -61,7 +61,7 @@ class Question(models.Model): points = models.FloatField(default=1.0) # Answer for MCQs. - solution = models.TextField(blank=True) + test = models.TextField(blank=True) # Test cases file paths (comma seperated for reference code path and test case code path) # Applicable for CPP, C, Java and Scilab @@ -118,6 +118,7 @@ class Question(models.Model): question_info_dict['user_answer'] = user_answer question_info_dict['test_parameter'] = test_case_data_dict question_info_dict['ref_code_path'] = self.ref_code_path + question_info_dict['test'] = self.test return json.dumps(question_info_dict) diff --git a/testapp/exam/python_code_evaluator.py b/testapp/exam/python_code_evaluator.py index 05a5063..61eee66 100644 --- a/testapp/exam/python_code_evaluator.py +++ b/testapp/exam/python_code_evaluator.py @@ -7,13 +7,12 @@ import importlib # local imports from code_evaluator import CodeEvaluator -# from language_registry import registry class PythonCodeEvaluator(CodeEvaluator): """Tests the Python code obtained from Code Server""" # Private Protocol ########## - def check_code(self): + def _check_code(self): success = False try: @@ -37,49 +36,23 @@ class PythonCodeEvaluator(CodeEvaluator): del tb return success, err - # # Public Protocol ########## - # def evaluate_code(self): - # success = False - - # try: - # tb = None - # test_code = self._create_test_case() - # submitted = compile(self.user_answer, '', mode='exec') - # g = {} - # exec submitted in g - # _tests = compile(test_code, '', mode='exec') - # exec _tests in g - # except AssertionError: - # type, value, tb = sys.exc_info() - # info = traceback.extract_tb(tb) - # fname, lineno, func, text = info[-1] - # text = str(test_code).splitlines()[lineno-1] - # err = "{0} {1} in: {2}".format(type.__name__, str(value), text) - # else: - # success = True - # err = 'Correct answer' - - # del tb - # return success, err - - # Private Protocol ########## def _create_test_case(self): """ Create assert based test cases in python """ test_code = "" - for test_case in self.test_case_data: - pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) \ - if test_case.get('pos_args') else "" - kw_args = ", ".join(str(k+"="+a) for k, a - in test_case.get('kw_args').iteritems()) \ - if test_case.get('kw_args') else "" - args = pos_args + ", " + kw_args if pos_args and kw_args \ - else pos_args or kw_args - tcode = "assert {0}({1}) == {2}".format(test_case.get('func_name'), - args, test_case.get('expected_answer')) - test_code += tcode + "\n" - return test_code - - -# registry.register('python', EvaluatePythonCode) + if self.test: + return self.test + elif self.test_case_data: + for test_case in self.test_case_data: + pos_args = ", ".join(str(i) for i in test_case.get('pos_args')) \ + if test_case.get('pos_args') else "" + kw_args = ", ".join(str(k+"="+a) for k, a + in test_case.get('kw_args').iteritems()) \ + if test_case.get('kw_args') else "" + args = pos_args + ", " + kw_args if pos_args and kw_args \ + else pos_args or kw_args + tcode = "assert {0}({1}) == {2}".format(test_case.get('func_name'), + args, test_case.get('expected_answer')) + test_code += tcode + "\n" + return test_code diff --git a/testapp/exam/scilab_code_evaluator.py b/testapp/exam/scilab_code_evaluator.py index 073fbcb..a4628a2 100644 --- a/testapp/exam/scilab_code_evaluator.py +++ b/testapp/exam/scilab_code_evaluator.py @@ -8,37 +8,36 @@ import importlib # local imports from code_evaluator import CodeEvaluator -# from language_registry import registry class ScilabCodeEvaluator(CodeEvaluator): """Tests the Scilab code obtained from Code Server""" - def __init__(self, test_case_data, language, user_answer, + def __init__(self, test_case_data, test, language, user_answer, ref_code_path=None, in_dir=None): - super(ScilabCodeEvaluator, self).__init__(test_case_data, language, user_answer, + super(ScilabCodeEvaluator, self).__init__(test_case_data, test, language, user_answer, ref_code_path, in_dir) self.submit_path = self.create_submit_code_file('function.sci') - self.test_case_args = self.setup_code_evaluator() + self.test_case_args = self._setup() # Private Protocol ########## - def setup_code_evaluator(self): - super(ScilabCodeEvaluator, self).setup_code_evaluator() + def _setup(self): + super(ScilabCodeEvaluator, self)._setup() ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) return ref_path, # Return as a tuple - def teardown_code_evaluator(self): + def _teardown(self): # Delete the created file. - super(ScilabCodeEvaluator, self).teardown_code_evaluator() + super(ScilabCodeEvaluator, self)._teardown() os.remove(self.submit_path) - def check_code(self, ref_path): + def _check_code(self, ref_path): success = False cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) cmd += ' | timeout 8 scilab-cli -nb' - ret = self.run_command(cmd, + ret = self._run_command(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -58,38 +57,6 @@ class ScilabCodeEvaluator(CodeEvaluator): return success, err - # # Public Protocol ########## - # def evaluate_code(self): - # submit_path = self.create_submit_code_file('function.sci') - # ref_path, test_case_path = self.set_test_code_file_path() - # success = False - - # cmd = 'printf "lines(0)\nexec(\'{0}\',2);\nquit();"'.format(ref_path) - # cmd += ' | timeout 8 scilab-cli -nb' - # ret = self.run_command(cmd, - # shell=True, - # stdout=subprocess.PIPE, - # stderr=subprocess.PIPE) - # proc, stdout, stderr = ret - - # # Get only the error. - # stderr = self._get_error(stdout) - # if stderr is None: - # # Clean output - # stdout = self._strip_output(stdout) - # if proc.returncode == 5: - # success, err = True, "Correct answer" - # else: - # err = add_err + stdout - # else: - # err = add_err + stderr - - # # Delete the created file. - # os.remove(submit_path) - - # return success, err - - # Private Protocol ########## def _remove_scilab_exit(self, string): """ Removes exit, quit and abort from the scilab code @@ -105,7 +72,6 @@ class ScilabCodeEvaluator(CodeEvaluator): new_string = new_string + '\n' + new_line return new_string, i - # Private Protocol ########## def _get_error(self, string): """ Fetches only the error from the string. @@ -116,7 +82,6 @@ class ScilabCodeEvaluator(CodeEvaluator): return obj.group() return None - # Private Protocol ########## def _strip_output(self, out): """ Cleans whitespace from the output @@ -127,5 +92,3 @@ class ScilabCodeEvaluator(CodeEvaluator): strip_out = strip_out+"\n"+l.strip() return strip_out - -# registry.register('scilab', EvaluateScilabCode) diff --git a/testapp/exam/settings.py b/testapp/exam/settings.py index 497a620..93f90a9 100644 --- a/testapp/exam/settings.py +++ b/testapp/exam/settings.py @@ -19,10 +19,10 @@ SERVER_TIMEOUT = 2 # host.org/foo/exam set URL_ROOT='/foo' URL_ROOT = '' -language_register = {"python": "python_code_evaluator", - "c": "c_cpp_code_evaluator", - "cpp": "c_cpp_code_evaluator", - "java": "java_evaluator", - "bash": "bash_evaluator", - "scilab": "scilab_evaluator", +code_evaluators = {"python": "python_code_evaluator.PythonCodeEvaluator", + "c": "c_cpp_code_evaluator.CCPPCodeEvaluator", + "cpp": "c_cpp_code_evaluator.CCPPCodeEvaluator", + "java": "java_evaluator.JavaCodeEvaluator", + "bash": "bash_evaluator.BashCodeEvaluator", + "scilab": "scilab_evaluator.ScilabCodeEvaluator", } diff --git a/testapp/exam/static/exam/js/add_question.js b/testapp/exam/static/exam/js/add_question.js index 5a94f4c..946c139 100644 --- a/testapp/exam/static/exam/js/add_question.js +++ b/testapp/exam/static/exam/js/add_question.js @@ -154,16 +154,11 @@ function textareaformat() { document.getElementById('id_options').style.visibility='visible'; document.getElementById('label_option').innerHTML="Options :"; - document.getElementById('id_solution').style.visibility='visible'; - document.getElementById('label_solution').innerHTML="Solutions :"; - } else { document.getElementById('id_options').style.visibility='hidden'; document.getElementById('label_option').innerHTML = ""; - document.getElementById('id_solution').style.visibility='hidden'; - document.getElementById('label_solution').innerHTML="" } }); document.getElementById('my').innerHTML = document.getElementById('id_description').value ; @@ -172,16 +167,11 @@ function textareaformat() { document.getElementById('id_options').style.visibility='visible'; document.getElementById('label_option').innerHTML="Options :" - document.getElementById('id_solution').style.visibility='visible'; - document.getElementById('label_solution').innerHTML="Solutions :"; - } else { document.getElementById('id_options').style.visibility='hidden'; document.getElementById('label_option').innerHTML = ""; - document.getElementById('id_solution').style.visibility='hidden'; - document.getElementById('label_solution').innerHTML="" } } diff --git a/testapp/exam/templates/exam/add_question.html b/testapp/exam/templates/exam/add_question.html index e117ef3..43f09e1 100644 --- a/testapp/exam/templates/exam/add_question.html +++ b/testapp/exam/templates/exam/add_question.html @@ -30,7 +30,7 @@
Snippet: {{ form.snippet }}{{ form.snippet.errors }}
Tags: {{ form.tags }}
Options: {{ form.options }} {{form.options.errors}} -
Solution: {{ form.solution }} {{form.solution.errors}} +
Test: {{ form.solution }} {{form.solution.errors}}
Reference Code Path: {{ form.ref_code_path }} {{form.ref_code_path.errors}}
diff --git a/testapp/exam/tests.py b/testapp/exam/tests.py index f73d0e2..ff48c25 100644 --- a/testapp/exam/tests.py +++ b/testapp/exam/tests.py @@ -71,7 +71,6 @@ class QuestionTestCases(unittest.TestCase): "kw_args": {"a": "10", "b": "11"} }], - "ref_code_path": "", "id": self.question.id, "language": "Python"} self.answer_data_json = json.dumps(answer_data) diff --git a/testapp/exam/views.py b/testapp/exam/views.py index 2542a28..5b7baac 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -311,7 +311,7 @@ def edit_question(request): question.language = language[j] question.snippet = snippet[j] question.ref_code_path = ref_code_path[j] - question.solution = solution[j] + question.test = test[j] question.type = type[j] question.save() return my_redirect("/exam/manage/questions") @@ -376,7 +376,7 @@ def add_question(request, question_id=None): d.language = form['language'].data d.snippet = form['snippet'].data d.ref_code_path = form['ref_code_path'].data - d.solution = form['solution'].data + d.test = form['test'].data d.save() question = Question.objects.get(id=question_id) for tag in question.tags.all(): @@ -432,7 +432,7 @@ def add_question(request, question_id=None): form.initial['language'] = d.language form.initial['snippet'] = d.snippet form.initial['ref_code_path'] = d.ref_code_path - form.initial['solution'] = d.solution + form.initial['test'] = d.test form_tags = d.tags.all() form_tags_split = form_tags.values('name') initial_tags = "" @@ -916,7 +916,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): question = get_object_or_404(Question, pk=q_id) q_paper = QuestionPaper.objects.get(id=questionpaper_id) paper = AnswerPaper.objects.get(user=request.user, question_paper=q_paper) - test = TestCase.objects.filter(question=question) + test_cases = TestCase.objects.filter(question=question) snippet_code = request.POST.get('snippet') user_code = request.POST.get('answer') @@ -958,7 +958,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # questions, we obtain the results via XML-RPC with the code executed # safely in a separate process (the code_server.py) running as nobody. if not question.type == 'upload': - json_data = question.consolidate_answer_data(test, user_answer) \ + json_data = question.consolidate_answer_data(test_cases, user_answer) \ if question.type == 'code' else None correct, result = validate_answer(user, user_answer, question, json_data) if correct: @@ -1020,11 +1020,11 @@ def validate_answer(user, user_answer, question, json_data=None): if user_answer is not None: if question.type == 'mcq': - if user_answer.strip() == question.solution.strip(): + if user_answer.strip() == question.test.strip(): correct = True message = 'Correct answer' elif question.type == 'mcc': - answers = set(question.solution.splitlines()) + answers = set(question.test.splitlines()) if set(user_answer) == answers: correct = True message = 'Correct answer' @@ -1253,7 +1253,7 @@ def show_all_questions(request): form.initial['language'] = d.language form.initial['snippet'] = d.snippet form.initial['ref_code_path'] = d.ref_code_path - form.initial['solution'] = d.solution + form.initial['test'] = d.test form_tags = d.tags.all() form_tags_split = form_tags.values('name') initial_tags = "" -- cgit From 5b23647de575fd90552807260a4b8e0a96ab6afe Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Tue, 12 May 2015 17:35:16 +0530 Subject: Seperated tests into seperate folder, formatting changes --- testapp/exam/bash_code_evaluator.py | 4 ++-- testapp/exam/code_evaluator.py | 8 ++------ testapp/exam/code_server.py | 6 ------ testapp/exam/cpp_code_evaluator.py | 19 +++++++++---------- testapp/exam/java_code_evaluator.py | 11 +++++------ testapp/exam/models.py | 4 ++-- testapp/exam/python_code_evaluator.py | 7 +++++-- testapp/exam/scilab_code_evaluator.py | 5 +++-- 8 files changed, 28 insertions(+), 36 deletions(-) (limited to 'testapp/exam') diff --git a/testapp/exam/bash_code_evaluator.py b/testapp/exam/bash_code_evaluator.py index 7fcfb0f..23c0ae5 100644 --- a/testapp/exam/bash_code_evaluator.py +++ b/testapp/exam/bash_code_evaluator.py @@ -23,11 +23,11 @@ class BashCodeEvaluator(CodeEvaluator): def _setup(self): super(BashCodeEvaluator, self)._setup() - self.set_file_as_executable(self.submit_path) + self._set_file_as_executable(self.submit_path) get_ref_path, get_test_case_path = self.ref_code_path.strip().split(',') get_ref_path = get_ref_path.strip() get_test_case_path = get_test_case_path.strip() - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path, + ref_path, test_case_path = self._set_test_code_file_path(get_ref_path, get_test_case_path) return ref_path, self.submit_path, test_case_path diff --git a/testapp/exam/code_evaluator.py b/testapp/exam/code_evaluator.py index 1efd519..2a57257 100644 --- a/testapp/exam/code_evaluator.py +++ b/testapp/exam/code_evaluator.py @@ -22,7 +22,6 @@ class TimeoutException(Exception): pass -# Private Protocol ########## def timeout_handler(signum, frame): """A handler for the ALARM signal.""" raise TimeoutException('Code took too long to run.') @@ -49,9 +48,6 @@ def delete_signal_handler(): return -############################################################################### -# `TestCode` class. -############################################################################### class CodeEvaluator(object): """Tests the code obtained from Code Server""" def __init__(self, test_case_data, test, language, user_answer, @@ -150,12 +146,12 @@ class CodeEvaluator(object): return submit_path - def set_file_as_executable(self, fname): + def _set_file_as_executable(self, fname): os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) - def set_test_code_file_path(self, ref_path=None, test_case_path=None): + def _set_test_code_file_path(self, ref_path=None, test_case_path=None): if ref_path and not ref_path.startswith('/'): ref_path = join(MY_DIR, ref_path) diff --git a/testapp/exam/code_server.py b/testapp/exam/code_server.py index 3dd5072..580379f 100755 --- a/testapp/exam/code_server.py +++ b/testapp/exam/code_server.py @@ -32,12 +32,6 @@ import json # Local imports. from settings import SERVER_PORTS, SERVER_TIMEOUT, SERVER_POOL_PORT from language_registry import set_registry -# from evaluate_python_code import EvaluatePythonCode -# from evaluate_c_code import EvaluateCCode -# from evaluate_cpp_code import EvaluateCppCode -# from evaluate_java_code import EvaluateJavaCode -# from evaluate_scilab_code import EvaluateScilabCode -# from evaluate_bash_code import EvaluateBashCode MY_DIR = abspath(dirname(__file__)) diff --git a/testapp/exam/cpp_code_evaluator.py b/testapp/exam/cpp_code_evaluator.py index 04efba8..15e2b13 100644 --- a/testapp/exam/cpp_code_evaluator.py +++ b/testapp/exam/cpp_code_evaluator.py @@ -12,19 +12,20 @@ from code_evaluator import CodeEvaluator class CppCodeEvaluator(CodeEvaluator): """Tests the C code obtained from Code Server""" - def __init__(self, test_case_data, test, language, user_answer, - ref_code_path=None, in_dir=None): - super(CppCodeEvaluator, self).__init__(test_case_data, test, language, user_answer, - ref_code_path, in_dir) + def __init__(self, test_case_data, test, language, user_answer, + ref_code_path=None, in_dir=None): + super(CppCodeEvaluator, self).__init__(test_case_data, test, language, + user_answer, ref_code_path, + in_dir) self.submit_path = self.create_submit_code_file('submit.c') - self.test_case_args = self._setup() + self.test_case_args = self._setup() # Private Protocol ########## def _setup(self): super(CppCodeEvaluator, self)._setup() get_ref_path = self.ref_code_path - ref_path, test_case_path = self.set_test_code_file_path(get_ref_path) + ref_path, test_case_path = self._set_test_code_file_path(get_ref_path) # Set file paths c_user_output_path = os.getcwd() + '/output' @@ -40,7 +41,8 @@ class CppCodeEvaluator(CodeEvaluator): remove_user_output = c_user_output_path remove_ref_output = c_ref_output_path - return ref_path, self.submit_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output + return (ref_path, self.submit_path, compile_command, compile_main, + run_command_args, remove_user_output, remove_ref_output) def _teardown(self): # Delete the created file. @@ -75,10 +77,8 @@ class CppCodeEvaluator(CodeEvaluator): return False, 'No file at %s or Incorrect path' % submit_code_path success = False - # output_path = os.getcwd() + '/output' ret = self._compile_command(compile_command) proc, stdnt_stderr = ret - # if self.language == "java": stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) # Only if compilation is successful, the program is executed @@ -86,7 +86,6 @@ class CppCodeEvaluator(CodeEvaluator): if stdnt_stderr == '': ret = self._compile_command(compile_main) proc, main_err = ret - # if self.language == "java": main_err = self._remove_null_substitute_char(main_err) if main_err == '': diff --git a/testapp/exam/java_code_evaluator.py b/testapp/exam/java_code_evaluator.py index 709a0a1..08ae208 100644 --- a/testapp/exam/java_code_evaluator.py +++ b/testapp/exam/java_code_evaluator.py @@ -14,7 +14,8 @@ class JavaCodeEvaluator(CodeEvaluator): """Tests the Java code obtained from Code Server""" def __init__(self, test_case_data, test, language, user_answer, ref_code_path=None, in_dir=None): - super(JavaCodeEvaluator, self).__init__(test_case_data, test, language, user_answer, + super(JavaCodeEvaluator, self).__init__(test_case_data, test, + language, user_answer, ref_code_path, in_dir) self.submit_path = self.create_submit_code_file('Test.java') self.test_case_args = self._setup() @@ -23,7 +24,7 @@ class JavaCodeEvaluator(CodeEvaluator): def _setup(self): super(JavaCodeEvaluator, self)._setup() - ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) + ref_path, test_case_path = self._set_test_code_file_path(self.ref_code_path) # Set file paths java_student_directory = os.getcwd() + '/' @@ -42,7 +43,8 @@ class JavaCodeEvaluator(CodeEvaluator): remove_ref_output = "{0}{1}.class".format(java_student_directory, java_ref_file_name) - return ref_path, self.submit_path, compile_command, compile_main, run_command_args, remove_user_output, remove_ref_output + return (ref_path, self.submit_path, compile_command, compile_main, + run_command_args, remove_user_output, remove_ref_output) def _teardown(self): # Delete the created file. @@ -77,10 +79,8 @@ class JavaCodeEvaluator(CodeEvaluator): return False, 'No file at %s or Incorrect path' % submit_code_path success = False - # output_path = os.getcwd() + '/output' ret = self._compile_command(compile_command) proc, stdnt_stderr = ret - # if self.language == "java": stdnt_stderr = self._remove_null_substitute_char(stdnt_stderr) # Only if compilation is successful, the program is executed @@ -88,7 +88,6 @@ class JavaCodeEvaluator(CodeEvaluator): if stdnt_stderr == '': ret = self._compile_command(compile_main) proc, main_err = ret - # if self.language == "java": main_err = self._remove_null_substitute_char(main_err) if main_err == '': diff --git a/testapp/exam/models.py b/testapp/exam/models.py index a60550c..c5043dc 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -20,8 +20,8 @@ class Profile(models.Model): languages = ( ("python", "Python"), ("bash", "Bash"), - ("C", "C Language"), - ("CPP", "C++ Language"), + ("c", "C Language"), + ("cpp", "C++ Language"), ("java", "Java Language"), ("scilab", "Scilab"), ) diff --git a/testapp/exam/python_code_evaluator.py b/testapp/exam/python_code_evaluator.py index 61eee66..0c473cf 100644 --- a/testapp/exam/python_code_evaluator.py +++ b/testapp/exam/python_code_evaluator.py @@ -52,7 +52,10 @@ class PythonCodeEvaluator(CodeEvaluator): if test_case.get('kw_args') else "" args = pos_args + ", " + kw_args if pos_args and kw_args \ else pos_args or kw_args - tcode = "assert {0}({1}) == {2}".format(test_case.get('func_name'), - args, test_case.get('expected_answer')) + function_name = test_case.get('func_name') + expected_answer = test_case.get('expected_answer') + + tcode = "assert {0}({1}) == {2}".format(function_name, args, + expected_answer) test_code += tcode + "\n" return test_code diff --git a/testapp/exam/scilab_code_evaluator.py b/testapp/exam/scilab_code_evaluator.py index a4628a2..53640cc 100644 --- a/testapp/exam/scilab_code_evaluator.py +++ b/testapp/exam/scilab_code_evaluator.py @@ -14,7 +14,8 @@ class ScilabCodeEvaluator(CodeEvaluator): """Tests the Scilab code obtained from Code Server""" def __init__(self, test_case_data, test, language, user_answer, ref_code_path=None, in_dir=None): - super(ScilabCodeEvaluator, self).__init__(test_case_data, test, language, user_answer, + super(ScilabCodeEvaluator, self).__init__(test_case_data, test, + language, user_answer, ref_code_path, in_dir) self.submit_path = self.create_submit_code_file('function.sci') self.test_case_args = self._setup() @@ -23,7 +24,7 @@ class ScilabCodeEvaluator(CodeEvaluator): def _setup(self): super(ScilabCodeEvaluator, self)._setup() - ref_path, test_case_path = self.set_test_code_file_path(self.ref_code_path) + ref_path, test_case_path = self._set_test_code_file_path(self.ref_code_path) return ref_path, # Return as a tuple -- cgit