diff options
-rw-r--r-- | yaksh/evaluator_tests/test_simple_question_types.py | 164 | ||||
-rw-r--r-- | yaksh/models.py | 16 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_question.html | 2 | ||||
-rw-r--r-- | yaksh/templates/yaksh/grade_user.html | 2 | ||||
-rw-r--r-- | yaksh/templates/yaksh/question.html | 2 | ||||
-rw-r--r-- | yaksh/templates/yaksh/user_data.html | 2 | ||||
-rw-r--r-- | yaksh/templates/yaksh/view_answerpaper.html | 2 | ||||
-rw-r--r-- | yaksh/test_models.py | 79 | ||||
-rw-r--r-- | yaksh/xmlrpc_clients.py | 84 |
9 files changed, 251 insertions, 102 deletions
diff --git a/yaksh/evaluator_tests/test_simple_question_types.py b/yaksh/evaluator_tests/test_simple_question_types.py index fb1c220..c0b81d6 100644 --- a/yaksh/evaluator_tests/test_simple_question_types.py +++ b/yaksh/evaluator_tests/test_simple_question_types.py @@ -68,6 +68,7 @@ class IntegerQuestionTestCases(unittest.TestCase): self.answerpaper = AnswerPaper.objects.get(question_paper\ =self.question_paper) self.answerpaper.attempt_number = 1 + self.answerpaper.questions.add(self.question1) self.answerpaper.save() # For question self.integer_based_testcase = IntegerTestCase(question=self.question1, @@ -80,7 +81,7 @@ class IntegerQuestionTestCases(unittest.TestCase): def tearDownClass(self): self.question1.delete() - def test_integer_correct_answer(self): + def test_validate_regrade_integer_correct_answer(self): # Given integer_answer = 25 self.answer = Answer(question=self.question1, @@ -88,7 +89,7 @@ class IntegerQuestionTestCases(unittest.TestCase): ) self.answer.save() self.answerpaper.answers.add(self.answer) - + self.answerpaper.save() # When json_data = None result = self.answerpaper.validate_answer(integer_answer, @@ -98,7 +99,26 @@ class IntegerQuestionTestCases(unittest.TestCase): # Then self.assertTrue(result['success']) - def test_integer_incorrect_answer(self): + # Regrade + # Given + self.answer.correct = True + self.answer.marks = 1 + + self.answer.answer = 200 + self.answer.save() + + # When + details = self.answerpaper.regrade(self.question1.id) + + # Then + self.answer = self.answerpaper.answers.filter(question=self.question1 + ).last() + self.assertTrue(details[0]) + self.assertEqual(self.answer.marks, 0) + self.assertFalse(self.answer.correct) + + + def test_validate_regrade_integer_incorrect_answer(self): # Given integer_answer = 26 self.answer = Answer(question=self.question1, @@ -116,6 +136,24 @@ class IntegerQuestionTestCases(unittest.TestCase): # Then self.assertFalse(result['success']) + # Regrade + # Given + self.answer.correct = True + self.answer.marks = 1 + + self.answer.answer = 25 + self.answer.save() + + # When + details = self.answerpaper.regrade(self.question1.id) + + # Then + self.answer = self.answerpaper.answers.filter(question=self.question1 + ).last() + self.assertTrue(details[0]) + self.assertEqual(self.answer.marks, 1) + self.assertTrue(self.answer.correct) + class StringQuestionTestCases(unittest.TestCase): @classmethod @@ -147,6 +185,7 @@ class StringQuestionTestCases(unittest.TestCase): self.answerpaper = AnswerPaper.objects.get(question_paper\ =self.question_paper) self.answerpaper.attempt_number = 1 + self.answerpaper.questions.add(*[self.question1, self.question2]) self.answerpaper.save() # For question @@ -169,7 +208,7 @@ class StringQuestionTestCases(unittest.TestCase): self.question1.delete() self.question2.delete() - def test_case_insensitive_string_correct_answer(self): + def test_validate_regrade_case_insensitive_string_correct_answer(self): # Given string_answer = "hello, earth!" answer = Answer(question=self.question1,answer=string_answer) @@ -184,7 +223,25 @@ class StringQuestionTestCases(unittest.TestCase): # Then self.assertTrue(result['success']) - def test_case_insensitive_string_incorrect_answer(self): + # Regrade + # Given + answer.correct = True + answer.marks = 1 + + answer.answer = "hello, mars!" + answer.save() + + # When + details = self.answerpaper.regrade(self.question1.id) + + # Then + answer = self.answerpaper.answers.filter(question=self.question1)\ + .last() + self.assertTrue(details[0]) + self.assertEqual(answer.marks, 0) + self.assertFalse(answer.correct) + + def test_validate_regrade_case_insensitive_string_incorrect_answer(self): # Given string_answer = "hello, mars!" answer = Answer(question=self.question1,answer=string_answer) @@ -200,7 +257,25 @@ class StringQuestionTestCases(unittest.TestCase): # Then self.assertFalse(result['success']) - def test_case_sensitive_string_correct_answer(self): + # Regrade + # Given + answer.correct = True + answer.marks = 1 + + answer.answer = "hello, earth!" + answer.save() + + # When + details = self.answerpaper.regrade(self.question1.id) + + # Then + answer = self.answerpaper.answers.filter(question=self.question1)\ + .last() + self.assertTrue(details[0]) + self.assertEqual(answer.marks, 1) + self.assertTrue(answer.correct) + + def test_validate_regrade_case_sensitive_string_correct_answer(self): # Given string_answer = "Hello, EARTH!" answer = Answer(question=self.question2,answer=string_answer) @@ -215,6 +290,24 @@ class StringQuestionTestCases(unittest.TestCase): # Then self.assertTrue(result['success']) + # Regrade + # Given + answer.correct = True + answer.marks = 1 + + answer.answer = "hello, earth!" + answer.save() + + # When + details = self.answerpaper.regrade(self.question2.id) + + # Then + answer = self.answerpaper.answers.filter(question=self.question2)\ + .last() + self.assertTrue(details[0]) + self.assertEqual(answer.marks, 0) + self.assertFalse(answer.correct) + def test_case_sensitive_string_incorrect_answer(self): # Given string_answer = "hello, earth!" @@ -231,6 +324,24 @@ class StringQuestionTestCases(unittest.TestCase): # Then self.assertFalse(result['success']) + # Regrade + # Given + answer.correct = True + answer.marks = 1 + + answer.answer = "Hello, EARTH!" + answer.save() + + # When + details = self.answerpaper.regrade(self.question2.id) + + # Then + answer = self.answerpaper.answers.filter(question=self.question2)\ + .last() + self.assertTrue(details[0]) + self.assertEqual(answer.marks, 1) + self.assertTrue(answer.correct) + class FloatQuestionTestCases(unittest.TestCase): @classmethod @@ -254,6 +365,7 @@ class FloatQuestionTestCases(unittest.TestCase): self.answerpaper = AnswerPaper.objects.get(question_paper\ =self.question_paper) self.answerpaper.attempt_number = 1 + self.answerpaper.questions.add(self.question1) self.answerpaper.save() # For question self.float_based_testcase = FloatTestCase(question=self.question1, @@ -267,7 +379,7 @@ class FloatQuestionTestCases(unittest.TestCase): def tearDownClass(self): self.question1.delete() - def test_float_correct_answer(self): + def test_validate_regrade_float_correct_answer(self): # Given float_answer = 99.9 self.answer = Answer(question=self.question1, @@ -285,7 +397,25 @@ class FloatQuestionTestCases(unittest.TestCase): # Then self.assertTrue(result['success']) - def test_integer_incorrect_answer(self): + # Regrade + # Given + self.answer.correct = True + self.answer.marks = 1 + + self.answer.answer = 0.0 + self.answer.save() + + # When + details = self.answerpaper.regrade(self.question1.id) + + # Then + self.answer = self.answerpaper.answers.filter(question=self.question1 + ).last() + self.assertTrue(details[0]) + self.assertEqual(self.answer.marks, 0) + self.assertFalse(self.answer.correct) + + def test_float_incorrect_answer(self): # Given float_answer = 99.8 self.answer = Answer(question=self.question1, @@ -302,3 +432,21 @@ class FloatQuestionTestCases(unittest.TestCase): # Then self.assertFalse(result['success']) + + # Regrade + # Given + self.answer.correct = True + self.answer.marks = 1 + + self.answer.answer = 99.9 + self.answer.save() + + # When + details = self.answerpaper.regrade(self.question1.id) + + # Then + self.answer = self.answerpaper.answers.filter(question=self.question1 + ).last() + self.assertTrue(details[0]) + self.assertEqual(self.answer.marks, 1) + self.assertTrue(self.answer.correct) diff --git a/yaksh/models.py b/yaksh/models.py index 8e7089f..7e97e6c 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -29,9 +29,10 @@ import tempfile from textwrap import dedent from ast import literal_eval from .file_utils import extract_files, delete_files -from yaksh.code_server import(submit, SERVER_POOL_PORT, +from yaksh.code_server import(submit, get_result as get_result_from_code_server ) +from yaksh.settings import SERVER_POOL_PORT from django.conf import settings from django.forms.models import model_to_dict @@ -1385,7 +1386,8 @@ class AnswerPaper(models.Model): def get_previous_answers(self, question): return self.answers.filter(question=question).order_by('-id') - def validate_answer(self, user_answer, question, json_data=None, uid=None): + def validate_answer(self, user_answer, question, json_data=None, uid=None, + server_port=SERVER_POOL_PORT): """ Checks whether the answer submitted by the user is right or wrong. If right then returns correct = True, success and @@ -1447,12 +1449,12 @@ class AnswerPaper(models.Model): elif question.type == 'code' or question.type == "upload": user_dir = self.user.profile.get_user_dir() - url = 'http://localhost:%s' % SERVER_POOL_PORT + url = 'http://localhost:%s' % server_port submit(url, uid, json_data, user_dir) result = {'uid': uid, 'status': 'running'} return result - def regrade(self, question_id): + def regrade(self, question_id, server_port=SERVER_POOL_PORT): try: question = self.questions.get(id=question_id) msg = 'User: {0}; Quiz: {1}; Question: {2}.\n'.format( @@ -1478,9 +1480,11 @@ class AnswerPaper(models.Model): json_data = question.consolidate_answer_data(answer) \ if question.type == 'code' else None result = self.validate_answer(answer, question, - json_data, user_answer.id) + json_data, user_answer.id, + server_port=server_port + ) if question.type == "code": - url = 'http://localhost:%s' % SERVER_POOL_PORT + url = 'http://localhost:%s' % server_port check_result = get_result_from_code_server(url, result['uid'], block=True ) diff --git a/yaksh/templates/yaksh/add_question.html b/yaksh/templates/yaksh/add_question.html index a33950a..6ead019 100644 --- a/yaksh/templates/yaksh/add_question.html +++ b/yaksh/templates/yaksh/add_question.html @@ -8,7 +8,7 @@ {% block script %} <script src="{{ URL_ROOT }}/static/yaksh/js/add_question.js"></script> -<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML"></script> +<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script> {% endblock %} {% block onload %} onload='javascript:textareaformat();' {% endblock %} diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html index 1fef026..d946647 100644 --- a/yaksh/templates/yaksh/grade_user.html +++ b/yaksh/templates/yaksh/grade_user.html @@ -9,6 +9,8 @@ {% block script %} <script src="{{ URL_ROOT }}/static/yaksh/js/jquery.tablesorter.min.js"></script> +<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"> +</script> <script type="text/javascript"> $(document).ready(function() { diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html index c9c4e28..af24778 100644 --- a/yaksh/templates/yaksh/question.html +++ b/yaksh/templates/yaksh/question.html @@ -20,7 +20,7 @@ <script src="{{ URL_ROOT }}/static/yaksh/js/codemirror/mode/python/python.js"></script> <script src="{{ URL_ROOT }}/static/yaksh/js/codemirror/mode/clike/clike.js"></script> <script src="{{ URL_ROOT }}/static/yaksh/js/codemirror/mode/shell/shell.js"></script> -<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML"></script> +<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script> <script> var time_left = {{ paper.time_left }} diff --git a/yaksh/templates/yaksh/user_data.html b/yaksh/templates/yaksh/user_data.html index 73157ff..e12a0a0 100644 --- a/yaksh/templates/yaksh/user_data.html +++ b/yaksh/templates/yaksh/user_data.html @@ -7,7 +7,7 @@ {% block script %} <script src= "{{ URL_ROOT }}/static/yaksh/js/edit_question.js"></script> -<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML"></script> +<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script> {% endblock %} <form action="" method="post"> diff --git a/yaksh/templates/yaksh/view_answerpaper.html b/yaksh/templates/yaksh/view_answerpaper.html index b433ad5..49276bab 100644 --- a/yaksh/templates/yaksh/view_answerpaper.html +++ b/yaksh/templates/yaksh/view_answerpaper.html @@ -4,7 +4,7 @@ {% block pagetitle %} Answer Paper for {{ quiz.description }}{% endblock pagetitle %} {% block script %} -<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML"></script> +<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script> {% endblock script %} {% block main %} diff --git a/yaksh/test_models.py b/yaksh/test_models.py index ddacb2a..2eea631 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -2,6 +2,9 @@ import unittest from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload +from yaksh.code_server import(ServerPool, + get_result as get_result_from_code_server + ) import json import ruamel.yaml as yaml from datetime import datetime, timedelta @@ -10,10 +13,13 @@ import pytz from django.contrib.auth.models import Group from django.core.files import File from django.forms.models import model_to_dict +from textwrap import dedent import zipfile import os import shutil import tempfile +from threading import Thread +from yaksh import settings def setUpModule(): # create user profile @@ -735,6 +741,20 @@ class AnswerPaperTestCases(unittest.TestCase): self.user2_answerpaper2 = self.question_paper.make_answerpaper( self.user2, self.ip, 1 ) + settings.code_evaluators['python']['standardtestcase'] = \ + "yaksh.python_assertion_evaluator.PythonAssertionEvaluator" + self.SERVER_POOL_PORT = 4000 + server_pool = ServerPool(n=1, pool_port=self.SERVER_POOL_PORT) + self.server_pool = server_pool + self.server_thread = t = Thread(target=server_pool.run) + t.start() + + @classmethod + def tearDownClass(self): + self.server_pool.stop() + self.server_thread.join() + settings.code_evaluators['python']['standardtestcase'] = \ + "python_assertion_evaluator.PythonAssertionEvaluator" def test_get_per_question_score(self): # Given @@ -839,6 +859,65 @@ class AnswerPaperTestCases(unittest.TestCase): self.assertEqual(self.answer.marks, 0) self.assertFalse(self.answer.correct) + def test_validate_and_regrade_code_correct_answer(self): + # Given + # Start code server + + user_answer = dedent("""\ + def add(a,b): + return a+b + """) + self.answer = Answer(question=self.question1, + answer=user_answer, + ) + self.answer.save() + self.answerpaper.answers.add(self.answer) + user = self.answerpaper.user + + # When + json_data = self.question1.consolidate_answer_data(user_answer, + user + ) + get_result = self.answerpaper.validate_answer(user_answer, + self.question1, + json_data, + self.answer.id, + self.SERVER_POOL_PORT + ) + url = 'http://localhost:%s' % self.SERVER_POOL_PORT + check_result = get_result_from_code_server(url,get_result['uid'], + block=True + ) + result = json.loads(check_result.get('result')) + + # Then + self.assertTrue(result['success']) + self.answer.correct = True + self.answer.marks = 1 + + # Regrade + # Given + self.answer.correct = True + self.answer.marks = 1 + + self.answer.answer = dedent(""" + def add(a,b): + return a-b + """) + self.answer.save() + + # When + details = self.answerpaper.regrade(self.question1.id, + self.SERVER_POOL_PORT + ) + + # Then + self.answer = self.answerpaper.answers.filter(question=self.question1 + ).last() + self.assertTrue(details[0]) + self.assertEqual(self.answer.marks, 0) + self.assertFalse(self.answer.correct) + def test_validate_and_regrade_mcq_correct_answer(self): # Given mcq_answer = str(self.mcq_based_testcase.id) diff --git a/yaksh/xmlrpc_clients.py b/yaksh/xmlrpc_clients.py deleted file mode 100644 index 54b2dd3..0000000 --- a/yaksh/xmlrpc_clients.py +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import unicode_literals -import time -import random -import socket -import json -import urllib -from six.moves import urllib - -try: - from xmlrpclib import ServerProxy -except ImportError: - # The above import will not work on Python-3.x. - from xmlrpc.client import ServerProxy - -# Local imports -from .settings import SERVER_PORTS, SERVER_POOL_PORT - - -class ConnectionError(Exception): - pass - -############################################################################### -# `CodeServerProxy` class. -############################################################################### - - -class CodeServerProxy(object): - """A class that manages accesing the farm of Python servers and making - calls to them such that no one XMLRPC server is overloaded. - """ - def __init__(self): - pool_url = 'http://localhost:%d' % (SERVER_POOL_PORT) - self.pool_url = pool_url - - 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 - done). The parameter language specifies which language to use for the - tests. - - Parameters - ---------- - json_data contains; - user_answer : str - The user's answer for the question. - test_code : str - The test code to check the user code with. - language : str - The programming language to use. - - user_dir : str (directory) - The directory to run the tests inside. - - - Returns - ------- - A json string of a dict containing: - {"success": success, "weight": weight, "error": error message} - - success - Boolean, indicating if code was executed successfully, correctly - weight - Float, indicating total weight of all successful test cases - error - String, error message if success is false - """ - - try: - server = self._get_server() - result = server.check_code(language, json_data, user_dir) - except ConnectionError: - result = json.dumps({'success': False, - 'weight': 0.0, - 'error': ['Unable to connect to any code servers!'] - }) - return result - - def _get_server(self): - response = urllib.request.urlopen(self.pool_url) - port = json.loads(response.read().decode('utf-8')) - proxy = ServerProxy('http://localhost:%d' % port) - return proxy - -# views.py calls this Python server which forwards the request to one -# of the running servers. -code_server = CodeServerProxy() |