diff options
author | ankitjavalkar | 2015-04-24 14:25:26 +0530 |
---|---|---|
committer | ankitjavalkar | 2015-04-26 21:11:52 +0530 |
commit | 8664a766406d6acf0d6a1688948153c407ea27f2 (patch) | |
tree | 7505ef02b5e36395fc7e92ff5cde7700f2025979 | |
parent | 17752a69114e7dbad266337e768013920aec8c0c (diff) | |
download | online_test-8664a766406d6acf0d6a1688948153c407ea27f2.tar.gz online_test-8664a766406d6acf0d6a1688948153c407ea27f2.tar.bz2 online_test-8664a766406d6acf0d6a1688948153c407ea27f2.zip |
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
-rw-r--r-- | testapp/docs/sample_questions.py | 84 | ||||
-rw-r--r-- | testapp/docs/sample_questions.xml | 43 | ||||
-rw-r--r-- | testapp/exam/bash_files/sample.args (renamed from testapp/docs/sample.args) | 0 | ||||
-rwxr-xr-x | testapp/exam/bash_files/sample.sh (renamed from testapp/docs/sample.sh) | 0 | ||||
-rwxr-xr-x | testapp/exam/code_server.py | 34 | ||||
-rw-r--r-- | testapp/exam/evaluate_bash_code.py (renamed from testapp/exam/evaluate_bash.py) | 24 | ||||
-rw-r--r-- | testapp/exam/evaluate_c_code.py (renamed from testapp/exam/evaluate_c.py) | 13 | ||||
-rw-r--r-- | testapp/exam/evaluate_code.py (renamed from testapp/exam/test_code.py) | 24 | ||||
-rw-r--r-- | testapp/exam/evaluate_cpp_code.py (renamed from testapp/exam/evaluate_cpp.py) | 13 | ||||
-rw-r--r-- | testapp/exam/evaluate_java_code.py (renamed from testapp/exam/evaluate_java.py) | 22 | ||||
-rw-r--r-- | testapp/exam/evaluate_python_code.py (renamed from testapp/exam/evaluate_python.py) | 16 | ||||
-rw-r--r-- | testapp/exam/evaluate_scilab_code.py (renamed from testapp/exam/evaluate_scilab.py) | 15 | ||||
-rw-r--r-- | testapp/exam/language_registry.py (renamed from testapp/exam/registry.py) | 6 | ||||
-rw-r--r-- | testapp/exam/models.py | 30 | ||||
-rw-r--r-- | testapp/exam/views.py | 19 | ||||
-rw-r--r-- | testapp/exam/xmlrpc_clients.py | 6 | ||||
-rw-r--r-- | testapp/test_server.py | 187 |
17 files changed, 298 insertions, 238 deletions
diff --git a/testapp/docs/sample_questions.py b/testapp/docs/sample_questions.py deleted file mode 100644 index 60f32cb..0000000 --- a/testapp/docs/sample_questions.py +++ /dev/null @@ -1,84 +0,0 @@ -from datetime import date - -questions = [ -[Question( - summary='Factorial', - points=2, - language='python', - type='code', - description=''' -Write a function called <code>fact</code> which takes a single integer argument -(say <code>n</code>) and returns the factorial of the number. -For example:<br/> -<code>fact(3) -> 6</code> -''', - test=''' -assert fact(0) == 1 -assert fact(5) == 120 -''', - snippet="def fact(num):" - ), -#Add tags here as a list of string. -['Python','function','factorial'], -], - -[Question( - summary='Simple function', - points=1, - language='python', - type='code', - description='''Create a simple function called <code>sqr</code> which takes a single -argument and returns the square of the argument. For example: <br/> -<code>sqr(3) -> 9</code>.''', - test=''' -import math -assert sqr(3) == 9 -assert abs(sqr(math.sqrt(2)) - 2.0) < 1e-14 - ''', - snippet="def sqr(num):" - ), -#Add tags here as a list of string. -['Python','function'], -], - -[Question( - summary='Bash addition', - points=2, - language='bash', - type='code', - description='''Write a shell script which takes two arguments on the - command line and prints the sum of the two on the output.''', - test='''\ -docs/sample.sh -docs/sample.args -''', - snippet="#!/bin/bash" - ), -#Add tags here as a list of string. -[''], -], - -[Question( - summary='Size of integer in Python', - points=0.5, - language='python', - type='mcq', - description='''What is the largest integer value that can be represented -in Python?''', - options='''No Limit -2**32 -2**32 - 1 -None of the above -''', - test = "No Limit" - ), -#Add tags here as a list of string. -['mcq'], -], - -] #list of questions ends here - -quiz = Quiz(start_date=date.today(), - duration=10, - description='Basic Python Quiz 1' - ) diff --git a/testapp/docs/sample_questions.xml b/testapp/docs/sample_questions.xml deleted file mode 100644 index 53c76f8..0000000 --- a/testapp/docs/sample_questions.xml +++ /dev/null @@ -1,43 +0,0 @@ -<question_bank> - -<question> -<summary> -Factorial -</summary> -<description> -Write a function called "fact" which takes a single integer argument (say "n") -and returns the factorial of the number. -For example fact(3) -> 6 -</description> -<points>2</points> -<type>python</type> -<test> -assert fact(0) == 1 -assert fact(5) == 120 -</test> -<options> -</options> -</question> - -<question> -<summary> -Simple function -</summary> -<description> -Create a simple function called "sqr" which takes a single argument and -returns the square of the argument -For example sqr(3) -> 9. -</description> -<points>1</points> -<type>python</type> -<test> -import math -assert sqr(3) == 9 -assert abs(sqr(math.sqrt(2)) - 2.0) < 1e-14 -</test> -<options> -</options> -</question> - - -</question_bank> diff --git a/testapp/docs/sample.args b/testapp/exam/bash_files/sample.args index 4d9f00d..4d9f00d 100644 --- a/testapp/docs/sample.args +++ b/testapp/exam/bash_files/sample.args diff --git a/testapp/docs/sample.sh b/testapp/exam/bash_files/sample.sh index e935cb3..e935cb3 100755 --- a/testapp/docs/sample.sh +++ b/testapp/exam/bash_files/sample.sh 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_code.py index fd769cd..49f20fa 100644 --- a/testapp/exam/evaluate_bash.py +++ b/testapp/exam/evaluate_bash_code.py @@ -7,20 +7,22 @@ import subprocess import importlib # local imports -from test_code import TestCode -from registry import registry +from evaluate_code import EvaluateCode +from language_registry import registry -class EvaluateBash(TestCode): +class EvaluateBashCode(EvaluateCode): """Tests the Bash code obtained from Code Server""" + ## Public Protocol ########## def evaluate_code(self): - fpath = self.create_submit_code_file('submit.sh') - submit_path = self.set_file_as_executable(fpath) + 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 = False - success, err = self.check_bash_script(ref_path, submit_path, + success, err = self._check_bash_script(ref_path, submit_path, test_case_path) # Delete the created file. @@ -28,7 +30,8 @@ class EvaluateBash(TestCode): return success, err - def check_bash_script(self, ref_path, submit_path, + ## 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 @@ -61,6 +64,8 @@ class EvaluateBash(TestCode): 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, @@ -110,4 +115,5 @@ class EvaluateBash(TestCode): stdnt_stdout+stdnt_stderr) return False, err -registry.register('bash', EvaluateBash)
\ No newline at end of file + +registry.register('bash', EvaluateBashCode)
\ No newline at end of file diff --git a/testapp/exam/evaluate_c.py b/testapp/exam/evaluate_c_code.py index 0700daa..d52beae 100644 --- a/testapp/exam/evaluate_c.py +++ b/testapp/exam/evaluate_c_code.py @@ -7,12 +7,13 @@ import subprocess import importlib # local imports -from test_code import TestCode -from registry import registry +from evaluate_code import EvaluateCode +from language_registry import registry -class EvaluateC(TestCode): +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 @@ -42,6 +43,7 @@ class EvaluateC(TestCode): 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): @@ -64,7 +66,6 @@ class EvaluateC(TestCode): 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): @@ -122,6 +123,7 @@ class EvaluateC(TestCode): return success, err + ## Public Protocol ########## def remove_null_substitute_char(self, string): """Returns a string without any null and substitute characters""" stripped = "" @@ -130,4 +132,5 @@ class EvaluateC(TestCode): stripped = stripped + c return ''.join(stripped) -registry.register('c', EvaluateC)
\ No newline at end of file + +registry.register('c', EvaluateCCode)
\ No newline at end of file diff --git a/testapp/exam/test_code.py b/testapp/exam/evaluate_code.py index 8930f55..161c1a2 100644 --- a/testapp/exam/test_code.py +++ b/testapp/exam/evaluate_code.py @@ -21,6 +21,7 @@ MY_DIR = abspath(dirname(__file__)) class TimeoutException(Exception): pass +## Private Protocol ########## def timeout_handler(signum, frame): """A handler for the ALARM signal.""" @@ -49,26 +50,28 @@ def delete_signal_handler(): ############################################################################### # `TestCode` class. ############################################################################### -class TestCode(object): +class EvaluateCode(object): """Tests the code obtained from Code Server""" - def __init__(self, test_parameter, 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 - self.test_parameter = test_parameter + 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, 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") + 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_parameter, 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): @@ -122,7 +125,6 @@ class TestCode(object): 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() @@ -177,6 +179,8 @@ class TestCode(object): 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_code.py index cffe744..ed744d1 100644 --- a/testapp/exam/evaluate_cpp.py +++ b/testapp/exam/evaluate_cpp_code.py @@ -7,14 +7,14 @@ import subprocess import importlib # local imports -from evaluate_c import EvaluateC -from test_code import TestCode -from registry import registry +from evaluate_c_code import EvaluateCCode +from evaluate_code import EvaluateCode +from language_registry import registry - -class EvaluateCpp(EvaluateC, TestCode): +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 @@ -44,4 +44,5 @@ class EvaluateCpp(EvaluateC, TestCode): return success, err -registry.register('cpp', EvaluateCpp)
\ No newline at end of file + +registry.register('cpp', EvaluateCppCode)
\ No newline at end of file diff --git a/testapp/exam/evaluate_java.py b/testapp/exam/evaluate_java_code.py index d3d4e2a..9ddbbed 100644 --- a/testapp/exam/evaluate_java.py +++ b/testapp/exam/evaluate_java_code.py @@ -7,27 +7,26 @@ import subprocess import importlib # local imports -from evaluate_c import EvaluateC -from test_code import TestCode -from registry import registry +from evaluate_c_code import EvaluateCCode +from evaluate_code import EvaluateCode +from language_registry import registry - -class EvaluateJava(EvaluateC, TestCode): +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') - 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(self.ref_code_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_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, + 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, @@ -46,4 +45,5 @@ class EvaluateJava(EvaluateC, TestCode): return success, err -registry.register('java', EvaluateJava)
\ No newline at end of file + +registry.register('java', EvaluateJavaCode)
\ No newline at end of file diff --git a/testapp/exam/evaluate_python.py b/testapp/exam/evaluate_python_code.py index 3af82b6..2682897 100644 --- a/testapp/exam/evaluate_python.py +++ b/testapp/exam/evaluate_python_code.py @@ -6,12 +6,13 @@ from os.path import join import importlib # local imports -from test_code import TestCode -from registry import registry +from evaluate_code import EvaluateCode +from language_registry import registry -class EvaluatePython(TestCode): + +class EvaluatePythonCode(EvaluateCode): """Tests the Python code obtained from Code Server""" - # def evaluate_python_code(self): + ## Public Protocol ########## def evaluate_code(self): success = False @@ -36,13 +37,13 @@ class EvaluatePython(TestCode): del tb return success, err - # Private Protocol + ## Private Protocol ########## def _create_test_case(self): """ Create assert based test cases in python """ test_code = "" - for test_case in self.test_parameter: + 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()) \ @@ -53,4 +54,5 @@ class EvaluatePython(TestCode): test_code += tcode + "\n" return test_code -registry.register('python', EvaluatePython)
\ No newline at end of file + +registry.register('python', EvaluatePythonCode)
\ No newline at end of file diff --git a/testapp/exam/evaluate_scilab.py b/testapp/exam/evaluate_scilab_code.py index f4253ff..cc46605 100644 --- a/testapp/exam/evaluate_scilab.py +++ b/testapp/exam/evaluate_scilab_code.py @@ -7,13 +7,13 @@ import re import importlib # local imports -from test_code import TestCode -from registry import registry +from evaluate_code import EvaluateCode +from language_registry import registry -class EvaluateScilab(TestCode): +class EvaluateScilabCode(EvaluateCode): """Tests the Scilab code obtained from Code Server""" - # def evaluate_scilab_code(self): + ## 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,7 +44,7 @@ class EvaluateScilab(TestCode): return success, err - # Private Protocol + ## Private Protocol ########## def _remove_scilab_exit(self, string): """ Removes exit, quit and abort from the scilab code @@ -60,6 +60,7 @@ class EvaluateScilab(TestCode): 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. @@ -70,6 +71,7 @@ class EvaluateScilab(TestCode): return obj.group() return None + ## Private Protocol ########## def _strip_output(self, out): """ Cleans whitespace from the output @@ -80,4 +82,5 @@ class EvaluateScilab(TestCode): strip_out = strip_out+"\n"+l.strip() return strip_out -registry.register('scilab', EvaluateScilab)
\ No newline at end of file + +registry.register('scilab', EvaluateScilabCode)
\ No newline at end of file diff --git a/testapp/exam/registry.py b/testapp/exam/language_registry.py index ef995ac..207cd21 100644 --- a/testapp/exam/registry.py +++ b/testapp/exam/language_registry.py @@ -1,14 +1,16 @@ #!/usr/bin/env python -class Registry(object): +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 = Registry()
\ No newline at end of file +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/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 diff --git a/testapp/test_server.py b/testapp/test_server.py index c35c411..39995e1 100644 --- a/testapp/test_server.py +++ b/testapp/test_server.py @@ -1,97 +1,244 @@ import unittest import os -from exam import evaluate_c, evaluate_cpp, evaluate_bash, evaluate_python +from exam import evaluate_c_code, evaluate_cpp_code, evaluate_java_code, evaluate_python_code, evaluate_scilab_code, evaluate_bash_code +from exam.language_registry import registry +from exam.settings import SERVER_TIMEOUT -class TestPythonEvaluation(unittest.TestCase): +class RegistryTestCase(unittest.TestCase): + def setUp(self): + self.registry_object = registry + + def test_set_register(self): + self.registry_object.register("demo_language", "demo_object") + self.assertEquals(self.registry_object._registry["demo_language"], "demo_object") + + def test_get_class(self): + self.test_set_register() + cls = self.registry_object.get_class("demo_language") + self.assertEquals(cls, "demo_object") + + +############################################################################### +class PythonEvaluationTestCases(unittest.TestCase): def setUp(self): self.language = "Python" - self.test_parameter = [{"func_name": "add", + self.test_case_data = [{"func_name": "add", "expected_answer": "5", "test_id": u'null', "pos_args": ["3", "2"], "kw_args": {} }] + self.timeout_msg = ("Code took more than {0} seconds to run. " + "You probably have an infinite loop in your code.").format(SERVER_TIMEOUT) + def test_correct_answer(self): user_answer = "def add(a, b):\n\treturn a + b""" - get_class = evaluate_python.EvaluatePython(self.test_parameter, self.language, user_answer, ref_code_path=None, in_dir=None) + get_class = evaluate_python_code.EvaluatePythonCode(self.test_case_data, self.language, user_answer, ref_code_path=None, in_dir=None) result = get_class.run_code() self.assertTrue(result.get("success")) self.assertEqual(result.get("error"), "Correct answer") def test_incorrect_answer(self): user_answer = "def add(a, b):\n\treturn a - b" - test_parameter = [{"func_name": "add", + test_case_data = [{"func_name": "add", "expected_answer": "5", "test_id": u'null', "pos_args": ["3", "2"], "kw_args": {} }] - get_class = evaluate_python.EvaluatePython(self.test_parameter, self.language, user_answer, ref_code_path=None, in_dir=None) + get_class = evaluate_python_code.EvaluatePythonCode(self.test_case_data, self.language, user_answer, ref_code_path=None, in_dir=None) result = get_class.run_code() self.assertFalse(result.get("success")) self.assertEqual(result.get("error"), "AssertionError in: assert add(3, 2) == 5") def test_infinite_loop(self): user_answer = "def add(a, b):\n\twhile True:\n\t\tpass""" - test_parameter = [{"func_name": "add", + test_case_data = [{"func_name": "add", "expected_answer": "5", "test_id": u'null', "pos_args": ["3", "2"], "kw_args": {} }] - get_class = evaluate_python.EvaluatePython(self.test_parameter, self.language, user_answer, ref_code_path=None, in_dir=None) + get_class = evaluate_python_code.EvaluatePythonCode(self.test_case_data, self.language, user_answer, ref_code_path=None, in_dir=None) result = get_class.run_code() self.assertFalse(result.get("success")) - self.assertTrue("Code took more than" in result.get("error")) + self.assertEquals(result.get("error"), self.timeout_msg) + ############################################################################### -class TestCEvaluation(unittest.TestCase): +class CEvaluationTestCases(unittest.TestCase): def setUp(self): self.language = "C" self.ref_code_path = "c_cpp_files/main.cpp" self.in_dir = "/tmp" - self.test_parameter = [] + self.test_case_data = [] + self.timeout_msg = ("Code took more than {0} seconds to run. " + "You probably have an infinite loop in your code.").format(SERVER_TIMEOUT) + def test_correct_answer(self): user_answer = "int add(int a, int b)\n{return a+b;}" - get_class = evaluate_c.EvaluateC(self.test_parameter, self.language, user_answer, self.ref_code_path, self.in_dir) + get_class = evaluate_c_code.EvaluateCCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) result = get_class.run_code() self.assertTrue(result.get("success")) self.assertEqual(result.get("error"), "Correct answer") - def test_incorrect_answer(self): + def test_compilation_error(self): user_answer = "int add(int a, int b)\n{return a+b}" - get_class = evaluate_c.EvaluateC(self.test_parameter, self.language, user_answer, self.ref_code_path, self.in_dir) + get_class = evaluate_c_code.EvaluateCCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) result = get_class.run_code() self.assertFalse(result.get("success")) self.assertTrue("Compilation Error" in result.get("error")) + def test_infinite_loop(self): + user_answer = "int add(int a, int b)\n{while(1>0){}}" + get_class = evaluate_c_code.EvaluateCCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertFalse(result.get("success")) + self.assertEquals(result.get("error"), self.timeout_msg) + + ############################################################################### -class TestCPPEvaluation(unittest.TestCase): +class CppEvaluationTestCases(unittest.TestCase): def setUp(self): self.language = "CPP" self.ref_code_path = "c_cpp_files/main.cpp" self.in_dir = "/tmp" - self.test_parameter = [] + self.test_case_data = [] + self.timeout_msg = ("Code took more than {0} seconds to run. " + "You probably have an infinite loop in your code.").format(SERVER_TIMEOUT) + def test_correct_answer(self): user_answer = "int add(int a, int b)\n{return a+b;}" - get_class = evaluate_cpp.EvaluateCpp(self.test_parameter, self.language, user_answer, self.ref_code_path, self.in_dir) + get_class = evaluate_cpp_code.EvaluateCppCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) result = get_class.run_code() self.assertTrue(result.get("success")) self.assertEqual(result.get("error"), "Correct answer") - def test_incorrect_answer(self): + def test_compilation_error(self): user_answer = "int add(int a, int b)\n{return a+b}" - get_class = evaluate_cpp.EvaluateCpp(self.test_parameter, self.language, user_answer, self.ref_code_path, self.in_dir) + get_class = evaluate_cpp_code.EvaluateCppCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) result = get_class.run_code() - error_msg = "" self.assertFalse(result.get("success")) self.assertTrue("Compilation Error" in result.get("error")) + def test_infinite_loop(self): + user_answer = "int add(int a, int b)\n{while(1>0){}}" + get_class = evaluate_cpp_code.EvaluateCppCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertFalse(result.get("success")) + self.assertEquals(result.get("error"), self.timeout_msg) + + +############################################################################### +class BashEvaluationTestCases(unittest.TestCase): + def setUp(self): + self.language = "bash" + self.ref_code_path = "bash_files/sample.sh,bash_files/sample.args" + self.in_dir = "/tmp" + self.test_case_data = [] + self.timeout_msg = ("Code took more than {0} seconds to run. " + "You probably have an infinite loop in your code.").format(SERVER_TIMEOUT) + + def test_correct_answer(self): + user_answer = "#!/bin/bash\n[[ $# -eq 2 ]] && echo $(( $1 + $2 )) && exit $(( $1 + $2 ))" + get_class = evaluate_bash_code.EvaluateBashCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertTrue(result.get("success")) + self.assertEqual(result.get("error"), "Correct answer") + + def test_error(self): + user_answer = "#!/bin/bash\n[[ $# -eq 2 ]] && echo $(( $1 - $2 )) && exit $(( $1 - $2 ))" + get_class = evaluate_bash_code.EvaluateBashCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertFalse(result.get("success")) + self.assertTrue("Error" in result.get("error")) + + def test_infinite_loop(self): + user_answer = "#!/bin/bash\nwhile [ 1 ] ; do echo "" > /dev/null ; done" + get_class = evaluate_bash_code.EvaluateBashCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertFalse(result.get("success")) + self.assertEquals(result.get("error"), self.timeout_msg) + + +############################################################################### +class JavaEvaluationTestCases(unittest.TestCase): + def setUp(self): + self.language = "java" + self.ref_code_path = "java_files/main_square.java" + self.in_dir = "/tmp" + self.test_case_data = [] + self.timeout_msg = ("Code took more than {0} seconds to run. " + "You probably have an infinite loop in your code.").format(SERVER_TIMEOUT) + + def test_correct_answer(self): + user_answer = "class Test {\n\tint square_num(int a) {\n\treturn a*a;\n\t}\n}" + get_class = evaluate_java_code.EvaluateJavaCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertTrue(result.get("success")) + self.assertEqual(result.get("error"), "Correct answer") + + def test_error(self): + user_answer = "class Test {\n\tint square_num(int a) {\n\treturn a*a" + get_class = evaluate_java_code.EvaluateJavaCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertFalse(result.get("success")) + self.assertTrue("Error" in result.get("error")) + + def test_infinite_loop(self): + user_answer = "class Test {\n\tint square_num(int a) {\n\t\twhile(0==0){\n\t\t}\n\t}\n}" + get_class = evaluate_java_code.EvaluateJavaCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertFalse(result.get("success")) + self.assertEquals(result.get("error"), self.timeout_msg) + + +############################################################################### +class ScilabEvaluationTestCases(unittest.TestCase): + def setUp(self): + self.language = "scilab" + self.ref_code_path = "scilab_files/test_add.sce" + self.in_dir = "/tmp" + self.test_case_data = [] + + def test_correct_answer(self): + user_answer = "funcprot(0)\nfunction[c]=add(a,b)\n\tc=a+b;\nendfunction" + get_class = evaluate_scilab_code.EvaluateScilabCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertTrue(result.get("success")) + self.assertEqual(result.get("error"), "Correct answer") + + def test_correct_answer_2(self): + user_answer = "funcprot(0)\nfunction[c]=add(a,b)\n\tc=a-b;\nendfunction" + get_class = evaluate_scilab_code.EvaluateScilabCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertTrue(result.get("success")) + self.assertEqual(result.get("error"), "Correct answer") + + def test_error(self): + user_answer = "funcprot(0)\nfunction[c]=add(a,b)\n\t c=a+b;\ndis(\tendfunction" + get_class = evaluate_java_code.EvaluateJavaCode(self.test_case_data, self.language, user_answer, self.ref_code_path, self.in_dir) + result = get_class.run_code() + + self.assertFalse(result.get("success")) + self.assertTrue("Error" in result.get("error")) + + if __name__ == '__main__': unittest.main()
\ No newline at end of file |