summaryrefslogtreecommitdiff
path: root/yaksh
diff options
context:
space:
mode:
authorPalaparthy Adityachandra2020-04-02 16:03:06 +0530
committerGitHub2020-04-02 16:03:06 +0530
commitd749a166c86db15f580b9e07eb65fe3344d9881a (patch)
tree68a26e2c014314086608671d97967614a5db1ff0 /yaksh
parent34b72832775c48788bd5154227a3e0faf6affadd (diff)
parent61abccdcb0b0a44f4db60e3bb4d09d1ec5dc50e3 (diff)
downloadonline_test-d749a166c86db15f580b9e07eb65fe3344d9881a.tar.gz
online_test-d749a166c86db15f580b9e07eb65fe3344d9881a.tar.bz2
online_test-d749a166c86db15f580b9e07eb65fe3344d9881a.zip
Merge pull request #670 from prathamesh920/R-tests-N-codemirror-mode
Add tests for R evaluator and codemirror R mode
Diffstat (limited to 'yaksh')
-rw-r--r--yaksh/evaluator_tests/test_r_evaluation.py196
-rw-r--r--yaksh/r_code_evaluator.py5
-rw-r--r--yaksh/static/yaksh/js/codemirror/mode/r/index.html85
-rw-r--r--yaksh/static/yaksh/js/codemirror/mode/r/r.js164
-rw-r--r--yaksh/static/yaksh/js/requesthandler.js3
-rw-r--r--yaksh/templates/yaksh/question.html1
6 files changed, 451 insertions, 3 deletions
diff --git a/yaksh/evaluator_tests/test_r_evaluation.py b/yaksh/evaluator_tests/test_r_evaluation.py
new file mode 100644
index 0000000..b161dc9
--- /dev/null
+++ b/yaksh/evaluator_tests/test_r_evaluation.py
@@ -0,0 +1,196 @@
+from __future__ import unicode_literals
+import unittest
+from textwrap import dedent
+import os
+import tempfile
+import shutil
+from psutil import Process
+
+from yaksh.grader import Grader
+from yaksh.settings import SERVER_TIMEOUT
+from yaksh.evaluator_tests.test_python_evaluation import EvaluatorBaseTest
+
+
+class RAssertionEvaluationTestCase(EvaluatorBaseTest):
+ def setUp(self):
+ self.tmp_file = os.path.join(tempfile.gettempdir(), 'test.txt')
+ with open(self.tmp_file, 'wb') as f:
+ f.write('2'.encode('ascii'))
+ tmp_in_dir_path = tempfile.mkdtemp()
+ self.in_dir = tmp_in_dir_path
+ self.test_case = dedent(
+ '''
+ source("function.r")
+ check_empty = function(obj){
+ stopifnot(is.null(obj) == FALSE)
+ }
+ check = function(input, output){
+ stopifnot(input == output)
+ }
+ is_correct = function(){
+ if (count == 3){
+ quit("no", 31)
+ }
+ }
+ check_empty(odd_or_even(3))
+ check(odd_or_even(6), "EVEN")
+ check(odd_or_even(1), "ODD")
+ check(odd_or_even(10), "EVEN")
+ check(odd_or_even(777), "ODD")
+ check(odd_or_even(778), "EVEN")
+ count = 3
+ is_correct()
+ '''
+ )
+ self.test_case_data = [{"test_case": self.test_case,
+ "test_case_type": "standardtestcase",
+ "weight": 0.0
+ }]
+ self.timeout_msg = ("Code took more than {0} seconds to run. "
+ "You probably have an infinite loop in"
+ " your code.").format(SERVER_TIMEOUT)
+ self.file_paths = None
+
+ def tearDown(self):
+ os.remove(self.tmp_file)
+ shutil.rmtree(self.in_dir)
+
+ def test_correct_answer(self):
+ # Given
+ user_answer = dedent(
+ '''
+ odd_or_even <- function(n){
+ if(n %% 2 == 0){
+ return("EVEN")
+ }
+ return("ODD")
+ }
+ '''
+ )
+ kwargs = {'metadata': {
+ 'user_answer': user_answer,
+ 'file_paths': self.file_paths,
+ 'partial_grading': False,
+ 'language': 'r'},
+ 'test_case_data': self.test_case_data,
+ }
+
+ # When
+ grader = Grader(self.in_dir)
+ result = grader.evaluate(kwargs)
+
+ # Then
+ self.assertTrue(result.get('success'))
+
+ def test_incorrect_answer(self):
+ # Given
+ user_answer = dedent(
+ '''
+ odd_or_even <- function(n){
+ if(n %% 2 == 0){
+ return("ODD")
+ }
+ return("EVEN")
+ }
+ '''
+ )
+ err = ['Error: input == output is not TRUE\nExecution halted\n']
+ kwargs = {'metadata': {
+ 'user_answer': user_answer,
+ 'file_paths': self.file_paths,
+ 'partial_grading': False,
+ 'language': 'r'},
+ 'test_case_data': self.test_case_data,
+ }
+
+ # When
+ grader = Grader(self.in_dir)
+ result = grader.evaluate(kwargs)
+ errors = result.get('error')
+ # Then
+ self.assertFalse(result.get('success'))
+ self.assertEqual(errors, err)
+
+ def test_error_code(self):
+ # Given
+ user_answer = dedent(
+ '''
+ odd_or_even <- function(n){
+ a
+ }
+ '''
+ )
+ kwargs = {'metadata': {
+ 'user_answer': user_answer,
+ 'file_paths': self.file_paths,
+ 'partial_grading': False,
+ 'language': 'r'},
+ 'test_case_data': self.test_case_data,
+ }
+
+ # When
+ grader = Grader(self.in_dir)
+ result = grader.evaluate(kwargs)
+ errors = result.get('error')
+
+ # Then
+ self.assertFalse(result.get("success"))
+ self.assertIn("object 'a' not found", errors[0])
+
+ def test_empty_function(self):
+ # Given
+ user_answer = dedent(
+ '''
+ odd_or_even <- function(n){
+ }
+ '''
+ )
+ kwargs = {'metadata': {
+ 'user_answer': user_answer,
+ 'file_paths': self.file_paths,
+ 'partial_grading': False,
+ 'language': 'r'},
+ 'test_case_data': self.test_case_data,
+ }
+
+ # When
+ grader = Grader(self.in_dir)
+ result = grader.evaluate(kwargs)
+ errors = result.get('error')
+
+ # Then
+ self.assertFalse(result.get("success"))
+ self.assertIn("Error: is.null(obj) == FALSE is not TRUE", errors[0])
+
+ def test_infinite_loop(self):
+ # Given
+ user_answer = dedent(
+ '''
+ odd_or_even <- function(n){
+ while(0 == 0){
+ a <- 1
+ }
+ }
+ '''
+ )
+ kwargs = {'metadata': {
+ 'user_answer': user_answer,
+ 'file_paths': self.file_paths,
+ 'partial_grading': False,
+ 'language': 'r'},
+ 'test_case_data': self.test_case_data,
+ }
+
+ # When
+ grader = Grader(self.in_dir)
+ result = grader.evaluate(kwargs)
+
+ # Then
+ self.assertFalse(result.get("success"))
+ self.assert_correct_output(self.timeout_msg,
+ result.get("error")[0]["message"]
+ )
+ parent_proc = Process(os.getpid()).children()
+ if parent_proc:
+ children_procs = Process(parent_proc[0].pid)
+ self.assertFalse(any(children_procs.children(recursive=True)))
diff --git a/yaksh/r_code_evaluator.py b/yaksh/r_code_evaluator.py
index ca4c94a..11bc970 100644
--- a/yaksh/r_code_evaluator.py
+++ b/yaksh/r_code_evaluator.py
@@ -49,7 +49,7 @@ class RCodeEvaluator(BaseEvaluator):
# Throw message if there are commmands that terminates scilab
add_err = ""
if terminate_commands:
- add_err = "Please do not use quit() in your\
+ add_err = "Please do not use quit() q() in your\
code.\n Otherwise your code will not be evaluated\
correctly.\n"
@@ -79,7 +79,8 @@ class RCodeEvaluator(BaseEvaluator):
new_string = ""
terminate_commands = False
for line in string.splitlines():
- new_line = re.sub(r"quit.*$", "", line)
+ new_line = re.sub(r'quit(.*$)', "", line)
+ new_line = re.sub(r'q(.*$)', "", new_line)
if line != new_line:
terminate_commands = True
new_string = new_string + '\n' + new_line
diff --git a/yaksh/static/yaksh/js/codemirror/mode/r/index.html b/yaksh/static/yaksh/js/codemirror/mode/r/index.html
new file mode 100644
index 0000000..6dd9634
--- /dev/null
+++ b/yaksh/static/yaksh/js/codemirror/mode/r/index.html
@@ -0,0 +1,85 @@
+<!doctype html>
+
+<title>CodeMirror: R mode</title>
+<meta charset="utf-8"/>
+<link rel=stylesheet href="../../doc/docs.css">
+
+<link rel="stylesheet" href="../../lib/codemirror.css">
+<script src="../../lib/codemirror.js"></script>
+<script src="r.js"></script>
+<style>
+ .CodeMirror { border-top: 1px solid silver; border-bottom: 1px solid silver; }
+ .cm-s-default span.cm-semi { color: blue; font-weight: bold; }
+ .cm-s-default span.cm-dollar { color: orange; font-weight: bold; }
+ .cm-s-default span.cm-arrow { color: brown; }
+ .cm-s-default span.cm-arg-is { color: brown; }
+ </style>
+<div id=nav>
+ <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
+
+ <ul>
+ <li><a href="../../index.html">Home</a>
+ <li><a href="../../doc/manual.html">Manual</a>
+ <li><a href="https://github.com/codemirror/codemirror">Code</a>
+ </ul>
+ <ul>
+ <li><a href="../index.html">Language modes</a>
+ <li><a class=active href="#">R</a>
+ </ul>
+</div>
+
+<article>
+<h2>R mode</h2>
+<form><textarea id="code" name="code">
+# Code from http://www.mayin.org/ajayshah/KB/R/
+
+# FIRST LEARN ABOUT LISTS --
+X = list(height=5.4, weight=54)
+print("Use default printing --")
+print(X)
+print("Accessing individual elements --")
+cat("Your height is ", X$height, " and your weight is ", X$weight, "\n")
+
+# FUNCTIONS --
+square <- function(x) {
+ return(x*x)
+}
+cat("The square of 3 is ", square(3), "\n")
+
+ # default value of the arg is set to 5.
+cube <- function(x=5) {
+ return(x*x*x);
+}
+cat("Calling cube with 2 : ", cube(2), "\n") # will give 2^3
+cat("Calling cube : ", cube(), "\n") # will default to 5^3.
+
+# LEARN ABOUT FUNCTIONS THAT RETURN MULTIPLE OBJECTS --
+powers <- function(x) {
+ parcel = list(x2=x*x, x3=x*x*x, x4=x*x*x*x);
+ return(parcel);
+}
+
+X = powers(3);
+print("Showing powers of 3 --"); print(X);
+
+# WRITING THIS COMPACTLY (4 lines instead of 7)
+
+powerful <- function(x) {
+ return(list(x2=x*x, x3=x*x*x, x4=x*x*x*x));
+}
+print("Showing powers of 3 --"); print(powerful(3));
+
+# In R, the last expression in a function is, by default, what is
+# returned. So you could equally just say:
+powerful <- function(x) {list(x2=x*x, x3=x*x*x, x4=x*x*x*x)}
+</textarea></form>
+ <script>
+ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
+ </script>
+
+ <p><strong>MIME types defined:</strong> <code>text/x-rsrc</code>.</p>
+
+ <p>Development of the CodeMirror R mode was kindly sponsored
+ by <a href="https://twitter.com/ubalo">Ubalo</a>.</p>
+
+ </article>
diff --git a/yaksh/static/yaksh/js/codemirror/mode/r/r.js b/yaksh/static/yaksh/js/codemirror/mode/r/r.js
new file mode 100644
index 0000000..d41d1c5
--- /dev/null
+++ b/yaksh/static/yaksh/js/codemirror/mode/r/r.js
@@ -0,0 +1,164 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.registerHelper("wordChars", "r", /[\w.]/);
+
+CodeMirror.defineMode("r", function(config) {
+ function wordObj(str) {
+ var words = str.split(" "), res = {};
+ for (var i = 0; i < words.length; ++i) res[words[i]] = true;
+ return res;
+ }
+ var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_");
+ var builtins = wordObj("list quote bquote eval return call parse deparse");
+ var keywords = wordObj("if else repeat while function for in next break");
+ var blockkeywords = wordObj("if else repeat while function for");
+ var opChars = /[+\-*\/^<>=!&|~$:]/;
+ var curPunc;
+
+ function tokenBase(stream, state) {
+ curPunc = null;
+ var ch = stream.next();
+ if (ch == "#") {
+ stream.skipToEnd();
+ return "comment";
+ } else if (ch == "0" && stream.eat("x")) {
+ stream.eatWhile(/[\da-f]/i);
+ return "number";
+ } else if (ch == "." && stream.eat(/\d/)) {
+ stream.match(/\d*(?:e[+\-]?\d+)?/);
+ return "number";
+ } else if (/\d/.test(ch)) {
+ stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/);
+ return "number";
+ } else if (ch == "'" || ch == '"') {
+ state.tokenize = tokenString(ch);
+ return "string";
+ } else if (ch == "." && stream.match(/.[.\d]+/)) {
+ return "keyword";
+ } else if (/[\w\.]/.test(ch) && ch != "_") {
+ stream.eatWhile(/[\w\.]/);
+ var word = stream.current();
+ if (atoms.propertyIsEnumerable(word)) return "atom";
+ if (keywords.propertyIsEnumerable(word)) {
+ // Block keywords start new blocks, except 'else if', which only starts
+ // one new block for the 'if', no block for the 'else'.
+ if (blockkeywords.propertyIsEnumerable(word) &&
+ !stream.match(/\s*if(\s+|$)/, false))
+ curPunc = "block";
+ return "keyword";
+ }
+ if (builtins.propertyIsEnumerable(word)) return "builtin";
+ return "variable";
+ } else if (ch == "%") {
+ if (stream.skipTo("%")) stream.next();
+ return "variable-2";
+ } else if (ch == "<" && stream.eat("-")) {
+ return "arrow";
+ } else if (ch == "=" && state.ctx.argList) {
+ return "arg-is";
+ } else if (opChars.test(ch)) {
+ if (ch == "$") return "dollar";
+ stream.eatWhile(opChars);
+ return "operator";
+ } else if (/[\(\){}\[\];]/.test(ch)) {
+ curPunc = ch;
+ if (ch == ";") return "semi";
+ return null;
+ } else {
+ return null;
+ }
+ }
+
+ function tokenString(quote) {
+ return function(stream, state) {
+ if (stream.eat("\\")) {
+ var ch = stream.next();
+ if (ch == "x") stream.match(/^[a-f0-9]{2}/i);
+ else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next();
+ else if (ch == "u") stream.match(/^[a-f0-9]{4}/i);
+ else if (ch == "U") stream.match(/^[a-f0-9]{8}/i);
+ else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/);
+ return "string-2";
+ } else {
+ var next;
+ while ((next = stream.next()) != null) {
+ if (next == quote) { state.tokenize = tokenBase; break; }
+ if (next == "\\") { stream.backUp(1); break; }
+ }
+ return "string";
+ }
+ };
+ }
+
+ function push(state, type, stream) {
+ state.ctx = {type: type,
+ indent: state.indent,
+ align: null,
+ column: stream.column(),
+ prev: state.ctx};
+ }
+ function pop(state) {
+ state.indent = state.ctx.indent;
+ state.ctx = state.ctx.prev;
+ }
+
+ return {
+ startState: function() {
+ return {tokenize: tokenBase,
+ ctx: {type: "top",
+ indent: -config.indentUnit,
+ align: false},
+ indent: 0,
+ afterIdent: false};
+ },
+
+ token: function(stream, state) {
+ if (stream.sol()) {
+ if (state.ctx.align == null) state.ctx.align = false;
+ state.indent = stream.indentation();
+ }
+ if (stream.eatSpace()) return null;
+ var style = state.tokenize(stream, state);
+ if (style != "comment" && state.ctx.align == null) state.ctx.align = true;
+
+ var ctype = state.ctx.type;
+ if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state);
+ if (curPunc == "{") push(state, "}", stream);
+ else if (curPunc == "(") {
+ push(state, ")", stream);
+ if (state.afterIdent) state.ctx.argList = true;
+ }
+ else if (curPunc == "[") push(state, "]", stream);
+ else if (curPunc == "block") push(state, "block", stream);
+ else if (curPunc == ctype) pop(state);
+ state.afterIdent = style == "variable" || style == "keyword";
+ return style;
+ },
+
+ indent: function(state, textAfter) {
+ if (state.tokenize != tokenBase) return 0;
+ var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx,
+ closing = firstChar == ctx.type;
+ if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit);
+ else if (ctx.align) return ctx.column + (closing ? 0 : 1);
+ else return ctx.indent + (closing ? 0 : config.indentUnit);
+ },
+
+ lineComment: "#"
+ };
+});
+
+CodeMirror.defineMIME("text/x-rsrc", "r");
+
+});
diff --git a/yaksh/static/yaksh/js/requesthandler.js b/yaksh/static/yaksh/js/requesthandler.js
index 7ccdef0..80b67fb 100644
--- a/yaksh/static/yaksh/js/requesthandler.js
+++ b/yaksh/static/yaksh/js/requesthandler.js
@@ -160,7 +160,8 @@ $(document).ready(function(){
'cpp': 'text/x-c++src',
'java': 'text/x-java',
'bash': 'text/x-sh',
- 'scilab': 'text/x-csrc'
+ 'scilab': 'text/x-csrc',
+ 'r':'text/x-rsrc',
}
// Code mirror Options
diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html
index 74343f8..92d591f 100644
--- a/yaksh/templates/yaksh/question.html
+++ b/yaksh/templates/yaksh/question.html
@@ -26,6 +26,7 @@
<script src="{% static 'yaksh/js/codemirror/mode/python/python.js' %}"></script>
<script src="{% static 'yaksh/js/codemirror/mode/clike/clike.js' %}"></script>
<script src="{% static 'yaksh/js/codemirror/mode/shell/shell.js' %}"></script>
+<script src="{% static 'yaksh/js/codemirror/mode/r/r.js' %}"></script>
<script type="text/javascript" src="{% static 'yaksh/js/mathjax/MathJax.js' %}?config=TeX-MML-AM_CHTML"></script>
<script src="{% static 'yaksh/js/jquery-sortable.js' %}"></script>
<script>