summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/evaluator_tests/test_python_evaluation.py154
-rw-r--r--yaksh/python_code_evaluator.py6
-rw-r--r--yaksh/static/yaksh/css/base.css36
-rw-r--r--yaksh/static/yaksh/css/question.css2
-rw-r--r--yaksh/templates/base.html2
-rw-r--r--yaksh/templates/yaksh/add_question.html1
-rw-r--r--yaksh/templates/yaksh/login.html2
-rw-r--r--yaksh/templates/yaksh/question.html113
-rw-r--r--yaksh/views.py33
9 files changed, 246 insertions, 103 deletions
diff --git a/yaksh/evaluator_tests/test_python_evaluation.py b/yaksh/evaluator_tests/test_python_evaluation.py
index 0478353..c55f04f 100644
--- a/yaksh/evaluator_tests/test_python_evaluation.py
+++ b/yaksh/evaluator_tests/test_python_evaluation.py
@@ -2,52 +2,146 @@ import unittest
import os
from yaksh.python_code_evaluator import PythonCodeEvaluator
from yaksh.settings import SERVER_TIMEOUT
+from textwrap import dedent
+
class PythonEvaluationTestCases(unittest.TestCase):
def setUp(self):
self.language = "Python"
self.test = None
- self.test_case_data = [{"func_name": "add",
- "expected_answer": "5",
- "test_id": u'null',
- "pos_args": ["3", "2"],
- "kw_args": {}
+ 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 = PythonCodeEvaluator(self.test_case_data, self.test, self.language, user_answer, ref_code_path=None, in_dir=None)
- result = get_class.evaluate()
+ user_answer = dedent("""
+ def add(a, b):
+ return a + b
+ """)
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
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_case_data = [{"func_name": "add",
- "expected_answer": "5",
- "test_id": u'null',
- "pos_args": ["3", "2"],
- "kw_args": {}
- }]
- get_class = PythonCodeEvaluator(self.test_case_data, self.test, self.language, user_answer, ref_code_path=None, in_dir=None)
- result = get_class.evaluate()
+ user_answer = dedent("""
+ def add(a, b):
+ return a - b
+ """)
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
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_case_data = [{"func_name": "add",
- "expected_answer": "5",
- "test_id": u'null',
- "pos_args": ["3", "2"],
- "kw_args": {}
- }]
- get_class = PythonCodeEvaluator(self.test_case_data, self.test, self.language, user_answer, ref_code_path=None, in_dir=None)
- result = get_class.evaluate()
+ user_answer = dedent("""
+ def add(a, b):
+ while True:
+ pass
+ """)
+ timeout_msg = ("Code took more than {0} seconds to run. "
+ "You probably have an infinite loop in your code.").format(SERVER_TIMEOUT)
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
+ self.assertFalse(result.get("success"))
+ self.assertEquals(result.get("error"), timeout_msg)
+
+ def test_syntax_error(self):
+ user_answer = dedent("""
+ def add(a, b);
+ return a + b
+ """)
+ syntax_error_msg = ["Traceback", "call", "File", "line", "<string>",
+ "SyntaxError", "invalid syntax"]
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
+ err = result.get("error").splitlines()
+ self.assertFalse(result.get("success"))
+ self.assertEqual(5, len(err))
+ for msg in syntax_error_msg:
+ self.assertIn(msg, result.get("error"))
+
+ def test_indent_error(self):
+ user_answer = dedent("""
+ def add(a, b):
+ return a + b
+ """)
+ indent_error_msg = ["Traceback", "call", "File", "line", "<string>",
+ "IndentationError", "indented block"]
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
+ err = result.get("error").splitlines()
+ self.assertFalse(result.get("success"))
+ self.assertEqual(5, len(err))
+ for msg in indent_error_msg:
+ self.assertIn(msg, result.get("error"))
+
+ def test_name_error(self):
+ user_answer = ""
+ name_error_msg = ["Traceback", "call", "NameError", "name", "defined"]
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
+ err = result.get("error").splitlines()
+ self.assertFalse(result.get("success"))
+ self.assertEqual(2, len(err))
+ for msg in name_error_msg:
+ self.assertIn(msg, result.get("error"))
+
+ def test_recursion_error(self):
+ user_answer = dedent("""
+ def add(a, b):
+ return add(3, 3)
+ """)
+ recursion_error_msg = ["Traceback", "call", "RuntimeError",
+ "maximum recursion depth exceeded"]
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
+ err = result.get("error").splitlines()
+ self.assertFalse(result.get("success"))
+ self.assertEqual(2, len(err))
+ for msg in recursion_error_msg:
+ self.assertIn(msg, result.get("error"))
+
+ def test_type_error(self):
+ user_answer = dedent("""
+ def add(a):
+ return a + b
+ """)
+ type_error_msg = ["Traceback", "call", "TypeError", "exactly", "argument"]
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
+ err = result.get("error").splitlines()
+ self.assertFalse(result.get("success"))
+ self.assertEqual(2, len(err))
+ for msg in type_error_msg:
+ self.assertIn(msg, result.get("error"))
+
+ def test_value_error(self):
+ user_answer = dedent("""
+ def add(a, b):
+ c = 'a'
+ return int(a) + int(b) + int(c)
+ """)
+ value_error_msg = ["Traceback", "call", "ValueError", "invalid literal", "base"]
+ get_evaluator = PythonCodeEvaluator(self.test_case_data, self.test,
+ self.language, user_answer)
+ result = get_evaluator.evaluate()
+ err = result.get("error").splitlines()
self.assertFalse(result.get("success"))
- self.assertEquals(result.get("error"), self.timeout_msg)
+ self.assertEqual(2, len(err))
+ for msg in value_error_msg:
+ self.assertIn(msg, result.get("error"))
if __name__ == '__main__':
- unittest.main()
+ unittest.main() \ No newline at end of file
diff --git a/yaksh/python_code_evaluator.py b/yaksh/python_code_evaluator.py
index 0c473cf..c87c420 100644
--- a/yaksh/python_code_evaluator.py
+++ b/yaksh/python_code_evaluator.py
@@ -6,7 +6,7 @@ from os.path import join
import importlib
# local imports
-from code_evaluator import CodeEvaluator
+from code_evaluator import CodeEvaluator, TimeoutException
class PythonCodeEvaluator(CodeEvaluator):
@@ -29,6 +29,10 @@ class PythonCodeEvaluator(CodeEvaluator):
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 TimeoutException:
+ raise
+ except Exception:
+ err = traceback.format_exc(limit=0)
else:
success = True
err = 'Correct answer'
diff --git a/yaksh/static/yaksh/css/base.css b/yaksh/static/yaksh/css/base.css
index 362f401..af3ba8b 100644
--- a/yaksh/static/yaksh/css/base.css
+++ b/yaksh/static/yaksh/css/base.css
@@ -221,7 +221,7 @@ body {
box-shadow: 0 1px 2px rgba(0,0,0,.15);
}
.content .span10,
-.content .span14 {
+.content {
min-height: 475px;
}
.content .span4 {
@@ -431,6 +431,7 @@ a:hover {
/* Typography.less
* Headings, body text, lists, code, and more for a versatile and durable typography system
* ---------------------------------------------------------------------------------------- */
+
p {
font-size: 13px;
font-weight: normal;
@@ -478,6 +479,7 @@ h4,
h5,
h6 {
line-height: 36px;
+
}
h3 {
font-size: 18px;
@@ -493,6 +495,8 @@ h4 small {
}
h5 {
font-size: 14px;
+ color:white;
+ text-align:left;
}
h6 {
font-size: 13px;
@@ -963,8 +967,7 @@ input[disabled],
select[disabled],
textarea[disabled],
input[readonly],
-select[readonly],
-textarea[readonly] {
+select[readonly]{
background-color: #f5f5f5;
border-color: #ddd;
cursor: not-allowed;
@@ -1793,6 +1796,7 @@ footer {
-ms-transition: 0.1s linear all;
-o-transition: 0.1s linear all;
transition: 0.1s linear all;
+ margin-right:50px
}
.btn:hover {
background-position: 0 -15px;
@@ -2340,3 +2344,29 @@ blink {
-webkit-animation-timing-function: cubic-bezier(1.0, 0, 0, 1.0);
-webkit-animation-duration: 1s;
}
+
+.error{
+padding:0;
+height:100px;
+width:730px;
+resize:None;
+overflow-y:scroll;
+background-color:white;
+border: 0 None white;
+}
+.error_msg{
+padding:0;
+height:100px;
+width:730px;
+resize:None;
+overflow:hidden;
+
+}
+.bash{
+padding:0;
+height:auto;
+width:750px;
+resize:none;
+overflow:hidden;
+background-color:white;
+}
diff --git a/yaksh/static/yaksh/css/question.css b/yaksh/static/yaksh/css/question.css
index b72f873..06109e5 100644
--- a/yaksh/static/yaksh/css/question.css
+++ b/yaksh/static/yaksh/css/question.css
@@ -13,7 +13,7 @@
}
.td1-class
{
- width:175px;
+ width:300px;
}
.td2-class
{
diff --git a/yaksh/templates/base.html b/yaksh/templates/base.html
index 5284a77..d3e4f91 100644
--- a/yaksh/templates/base.html
+++ b/yaksh/templates/base.html
@@ -37,7 +37,7 @@
</div>
</div>
<footer>
- <p>&copy; FOSSEE group, IIT Bombay</p>
+ <p align="center">&copy; FOSSEE group, IIT Bombay</p>
</footer>
</div>
diff --git a/yaksh/templates/yaksh/add_question.html b/yaksh/templates/yaksh/add_question.html
index b896081..61b146c 100644
--- a/yaksh/templates/yaksh/add_question.html
+++ b/yaksh/templates/yaksh/add_question.html
@@ -47,4 +47,3 @@
<button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/questions/");'>Cancel</button> </center>
</form>
{% endblock %}
-
diff --git a/yaksh/templates/yaksh/login.html b/yaksh/templates/yaksh/login.html
index dfeac1e..d679748 100644
--- a/yaksh/templates/yaksh/login.html
+++ b/yaksh/templates/yaksh/login.html
@@ -14,7 +14,7 @@
<center><table class=span1>
{{ form.as_table }}
</table></center>
- <center><button class="btn" type="submit">Login</button>&nbsp;&nbsp;&nbsp;&nbsp;<button class="btn" type="reset">Cancel</button></center>
+ <center><button class="btn" type="submit" style="margin-left: 50px">Login</button>&nbsp;&nbsp;&nbsp;&nbsp;<button class="btn" type="reset">Cancel</button></center>
<br><center><a href="{{URL_ROOT}}/exam/forgotpassword/">Forgot Password?</a></center><br>
<center><a href="{{URL_ROOT}}/exam/register/">New User? Sign-Up </a></center>
</form>
diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html
index a0b74fa..0d1daee 100644
--- a/yaksh/templates/yaksh/question.html
+++ b/yaksh/templates/yaksh/question.html
@@ -45,6 +45,7 @@ function updateClock(){
var ss = ('0' + t.seconds).slice(-2);
if(t.total<0){
+
document.forms["code"].submit();
clearInterval(timeinterval);
return null;
@@ -89,58 +90,58 @@ function call_skip(url)
form.action = url
form.submit();
}
-
+ {% if question.type == 'code' and success == 'True'%}
+ {% if to_attempt|length != 0 %}
+ window.setTimeout(function()
+ {
+ {% for qid, num in questions.items %}
+ location.href="{{ URL_ROOT }}/exam/{{ qid.id }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/"
+ {% endfor %}
+ }, 1000);
+ {% else %}
+ window.setTimeout(function()
+ {
+ location.href="{{ URL_ROOT }}/exam/{{ question.id }}/check/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/"
+ }, 1000);
+ {% endif %}
+ {% endif %}
</script>
-{% endblock script %}
-
+{% endblock script %}
{% block onload %} onload="updateTime();setSnippetHeight()" {% endblock %}
-{% block pagetitle %}
-
-<table><h6><div>
- <tr><td class=td1-class><h5>You have {{ paper.questions_left }} question(s) left in {{ quiz_name }}</h5>
- <td class=td2-class><div class=time-div id="time_left">
- </div>
-</div></h6></table>
-
-{% endblock %}
-
{% block content %}
<div class="topbar">
<div class="fill">
<div class="container">
<h3 class="brand"><strong>Online Test</h3></strong>
<ul>
- <li> <h5><a> Hi {{user.first_name.title}} {{user.last_name.title}} </a></h5>
- </ul>
- <form id="logout" action="{{URL_ROOT}}/exam/quit/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post" class="pull-right">
- {% csrf_token %}
- <button class="btn" type="submit" name="quit">Quit Exam</button> </li>
-
- </form>
+ <li><h5><a> Hi {{user.first_name.title}} {{user.last_name.title}} </a></h5></li>
+ </ul><br>
+ <div class=time-div id="time_left"></div>
+ <h5 class=td1-class>You have {{ paper.questions_left }} question(s) left in {{ quiz_name }}</h5>
</div>
</div>
</div>
-<div class = container>
+<div class = "container">
<div class="sidebar">
<p>Question Navigator </p>
<div class="pagination">
- <ul>
- {% for qid, num in questions.items %}
- {% if qid in to_attempt %}
- {% if qid == question.id|slugify %}
- <li class="active"><a href="#" onclick="call_skip('{{ URL_ROOT }}/exam/{{ qid }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')">{{ num }}</a></li>
- {% else %}
- <li><a href="#" onclick="call_skip('{{ URL_ROOT }}/exam/{{ qid }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')">{{ num }}</a></li>
- {% endif %}
- {% endif %}
- {% if qid in submitted %}
- <li class="disabled"><a href="#">{{ num }}</a></li>
- {% endif %}
- {% endfor %}
- </ul>
+ <ul>
+ {% for qid, num in questions.items %}
+ {% if qid.id|slugify in to_attempt %}
+ {% if qid.id|slugify == question.id|slugify %}
+ <li class="active"><a href="#" data-toggle="tooltip" title="{{ qid.description }}" onclick="call_skip('{{ URL_ROOT }}/exam/{{ qid.id }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')">{{ num }}</a></li>
+ {% else %}
+ <li><a href="#" data-toggle="tooltip" title="{{ qid.description }}" onclick="call_skip('{{ URL_ROOT }}/exam/{{ qid.id }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')">{{ num }}</a></li>
+ {% endif %}
+ {% endif %}
+ {% if qid.id|slugify in submitted %}
+ <li class="disabled"><a href="#">{{ num }}</a></li>
+ {% endif %}
+ {% endfor %}
+ </ul>
</div>
</div>
</div>
@@ -149,22 +150,22 @@ function call_skip(url)
<h4><u> {{ question.summary }} </u><font class=pull-right>(Marks : {{ question.points }}) </font></h4><br>
<font size=3 face=arial> {{ question.description|safe }} </font>
<br><font size=3 face=arial> Language: {{ question.language }} </font><br>
-</div>
-{% if error_message %}
- <div class="alert alert-error">
- {% for e in error_message.splitlines %}
- {{ e|join:"" }}
- <br/>
- {% endfor%}
- </div>
-{% endif %}
-
+ {% if question.type == "code" %}
+ <br><h3>Output:</h3></br>
+ {% if error_message %}
+ <div class="alert alert-error">
+ <textarea class="error" readonly="yes">{{ error_message }}</textarea>
+ {% else %}
+ <textarea class="error_msg" readonly="yes" placeholder="Please submit your answer below"></textarea>
+ {% endif %}
+ </div>
+ {% endif %}
+<br>
<p id="status"></p>
-
<form id="code" action="{{URL_ROOT}}/exam/{{ question.id }}/check/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post" enctype="multipart/form-data">
- {% csrf_token %}
- <input type=hidden name="question_id" id="question_id" value={{ question.id }}></input>
+ {% csrf_token %}
+ <input type=hidden name="question_id" id="question_id" value={{ question.id }}></input>
{% if question.type == "mcq" %}
{% for option in question.options.strip.splitlines %}
@@ -183,10 +184,9 @@ function call_skip(url)
{% endfor %}
{% endif %}
{% if question.type == "code" %}
-
- <textarea rows="1" style="padding:0;height:auto;width:750px;overflow:hidden;background-color:white;border: 0 none white;" readonly="yes" name="snippet" id="snippet" wrap="off">{% if last_attempt %}{{ question.snippet }}{% else %}{% if question.type == "bash" %} #!/bin/bash&#13;&#10;{{ question.snippet }}{% else %}{{ question.snippet }}{% endif %}{% endif %}</textarea>
-
- <textarea tabindex=1 rows="10" style="padding:0;height:auto; box-shadow: none;width:750px;margin-bottom:10px;overflow:hidden;border:none;" name="answer" id="answer" wrap="off" onkeydown="return catchTab(this,event)">{% if last_attempt %}{{last_attempt}}{% else %}{% if question.type == "bash" %}{% else %}{% endif %}{% endif %}</textarea>
+ <h3>Program:</h3>
+ <textarea rows="1" class="bash" readonly="yes" name="snippet" id="snippet" wrap="off" >{% if last_attempt %}{{ question.snippet }}{% else %}{% if question.type == "bash" %} #!/bin/bash&#13;&#10;{{ question.snippet }}{% else %}{{ question.snippet }}{% endif %}{% endif %}</textarea>
+ <textarea rows="10" class="bash" name="answer" id="answer" wrap="off" onkeydown="return catchTab(this,event)">{% if last_attempt %}{{last_attempt}}{% else %}{% if question.type == "bash" %}{% else %}{% endif %}{% endif %}</textarea>
<br>
<script type="text/javascript">
@@ -194,7 +194,7 @@ function call_skip(url)
</script>
<script>addLineNumbers('snippet');</script>
{% endif %}
-
+
{% if question.type == "mcq" or question.type == "mcc "%}
<br><button class="btn" type="submit" name="check" id="check">Submit Answer</button>&nbsp;&nbsp;
{% elif question.type == "upload" %}
@@ -206,7 +206,7 @@ function call_skip(url)
<button class="btn" type="submit" name="skip" id="skip">Attempt Later</button>
{% endif %}
</form>
-
+</div>
<!-- Modal -->
<div class="modal fade " id="upload_alert" >
@@ -224,5 +224,8 @@ function call_skip(url)
</div>
</div>
</div>
-
+ <form id="logout" action="{{URL_ROOT}}/exam/quit/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post" class="pull-right">
+ {% csrf_token %}
+ <button class="btn" type="submit" name="quit">Quit Exam</button>
+ </form>
{% endblock content %}
diff --git a/yaksh/views.py b/yaksh/views.py
index bdb86b9..03f4f61 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -868,7 +868,8 @@ def get_questions(paper):
q_unanswered = paper.get_unanswered_questions()
q_unanswered.sort()
to_attempt = q_unanswered
- for index, value in enumerate(all_questions, 1):
+ question = Question.objects.filter(id__in=all_questions)
+ for index, value in enumerate(question, 1):
questions[value] = index
questions = collections.OrderedDict(sorted(questions.items(), key=lambda x:x[1]))
return questions, to_attempt, submitted
@@ -928,6 +929,7 @@ def show_question(request, q_id, attempt_num, questionpaper_id, success_msg=None
return complete(request, msg, attempt_num, questionpaper_id)
else:
return question(request, q_id, attempt_num, questionpaper_id, success_msg)
+
def _save_skipped_answer(old_skipped, user_answer, paper, question):
@@ -1042,25 +1044,38 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
if time_left <= 0:
reason = 'Your time is up!'
return complete(request, reason, attempt_num, questionpaper_id)
-
# Display the same question if user_answer is None
elif not user_answer:
msg = "Please submit a valid option or code"
time_left = paper.time_left()
questions, to_attempt, submitted = get_questions(paper)
- context = {'question': question, 'error_message': msg,
- 'paper': paper, 'quiz_name': paper.question_paper.quiz.description,
- 'time_left': time_left, 'questions': questions,
- 'to_attempt': to_attempt, 'submitted': submitted}
+ context = {'question': question, 'paper': paper,
+ 'quiz_name': paper.question_paper.quiz.description,
+ 'time_left': time_left, 'questions': questions,
+ 'to_attempt': to_attempt, 'submitted': submitted,
+ 'error_message': msg}
+ ci = RequestContext(request)
+
+ elif question.type == 'code' and user_answer:
+ msg = "Correct Output"
+ success = "True"
+ paper.completed_question(question.id)
+ time_left = paper.time_left()
+ questions, to_attempt, submitted = get_questions(paper)
+ context = {'question': question, 'paper': paper,
+ 'quiz_name': paper.question_paper.quiz.description,
+ 'time_left': time_left, 'questions': questions,
+ 'to_attempt': to_attempt, 'submitted': submitted,
+ 'error_message': msg, 'success': success}
ci = RequestContext(request)
- return my_render_to_response('yaksh/question.html', context,
- context_instance=ci)
else:
next_q = paper.completed_question(question.id)
return show_question(request, next_q, attempt_num,
questionpaper_id, success_msg)
+ return my_render_to_response('yaksh/question.html', context,
+ context_instance=ci)
def validate_answer(user, user_answer, question, json_data=None):
"""
@@ -1091,7 +1106,6 @@ def validate_answer(user, user_answer, question, json_data=None):
result = json.loads(json_result)
if result.get('success'):
correct = True
-
return correct, result
def get_question_labels(request, attempt_num=None, questionpaper_id=None):
@@ -1536,7 +1550,6 @@ def show_all_questions(request):
return my_render_to_response('yaksh/showquestions.html', context,
context_instance=ci)
-
@login_required
def user_data(request, username, questionpaper_id=None):
"""Render user data."""