summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPrabhu Ramachandran2017-11-15 17:38:20 +0530
committerGitHub2017-11-15 17:38:20 +0530
commitcad223af77566b3d074637e4802510148268ef32 (patch)
treee156c628a3fa80ada750d3148b6459420dd668e1
parent79edbabe2431ff9bf841394f79479ca64f0312b8 (diff)
parent79735097d2633f27c8bce16def07748ff1184811 (diff)
downloadonline_test-cad223af77566b3d074637e4802510148268ef32.tar.gz
online_test-cad223af77566b3d074637e4802510148268ef32.tar.bz2
online_test-cad223af77566b3d074637e4802510148268ef32.zip
Merge pull request #396 from maheshgudi/regrade_fix
Test for regrade fix
-rw-r--r--yaksh/evaluator_tests/test_simple_question_types.py164
-rw-r--r--yaksh/models.py16
-rw-r--r--yaksh/test_models.py79
-rw-r--r--yaksh/xmlrpc_clients.py84
4 files changed, 245 insertions, 98 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/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()