diff --git a/common/lib/capa/capa/tests/test_files/dynamath_input.txt b/common/lib/capa/capa/tests/test_files/dynamath_input.txt new file mode 100644 index 0000000000..1822f90220 --- /dev/null +++ b/common/lib/capa/capa/tests/test_files/dynamath_input.txt @@ -0,0 +1,43 @@ + + + + cos + (θ) + + + + [ + + + 10 + + + 01 + + + ] + + + + i + + + sin + + (θ) + + + + + [ + + + 01 + + + 10 + + + ] + + + diff --git a/common/lib/capa/capa/tests/test_files/snuggletex_2x+3y.xml b/common/lib/capa/capa/tests/test_files/snuggletex_2x+3y.xml new file mode 100644 index 0000000000..c3c3976669 --- /dev/null +++ b/common/lib/capa/capa/tests/test_files/snuggletex_2x+3y.xml @@ -0,0 +1,235 @@ + + + + + + + + + + + + SnuggleTeX - ASCIIMathML Enrichment Demo + + + + + + + +

SnuggleTeX (1.2.2)

+
+
Skip Navigation
+ +
+ +
+

ASCIIMathML Enrichment Demo

+

Input

+

+ This demo is similar to the + MathML Semantic Enrichnment Demo + but uses + ASCIIMathML as + an alternative input format, which provides real-time feedback as you + type but can often generate MathML with odd semantics in it. + SnuggleTeX includes some functionality that can to convert this raw MathML into + something equivalent to its own MathML output, thereby allowing you to + semantically enrich it in + certain simple cases, making ASCIIMathML a possibly viable input format + for simple semantic maths. + +

+

+ To try the demo, simply enter some some ASCIIMathML into the box below. + You should see a real time preview of this while you type. + Then hit Go! to use SnuggleTeX to semantically enrich your + input. + +

+
+
+ ASCIIMath Input: +
+
+

Live Preview

+

+ This is a MathML rendering of your input, generated by ASCIIMathML as you type. + +

+
+
+
+

+ This is the underlying MathML source generated by ASCIIMathML, again updated in real time. + +

+
 
+

Enhanced Presentation MathML

+

+ This shows the result of attempting to enrich the raw Presentation MathML + generated by ASCIIMathML: + +

<math xmlns="http://www.w3.org/1998/Math/MathML">
+   <mrow>
+      <mrow>
+         <mn>2</mn>
+         <mo>*</mo>
+         <mi>x</mi>
+      </mrow>
+      <mo>+</mo>
+      <mrow>
+         <mn>3</mn>
+         <mo>*</mo>
+         <mi>y</mi>
+      </mrow>
+   </mrow>
+</math>

Content MathML

+

+ This shows the result of an attempted + conversion to Content MathML: + +

<math xmlns="http://www.w3.org/1998/Math/MathML">
+   <apply>
+      <plus/>
+      <apply>
+         <times/>
+         <cn>2</cn>
+         <ci>x</ci>
+      </apply>
+      <apply>
+         <times/>
+         <cn>3</cn>
+         <ci>y</ci>
+      </apply>
+   </apply>
+</math>

Maxima Input Form

+

+ This shows the result of an attempted + conversion to Maxima Input syntax: + +

(2 * x) + (3 * y)

MathML Parallel Markup

+

+ This shows the enhanced Presentation MathML with other forms encapsulated + as annotations: + +

<math xmlns="http://www.w3.org/1998/Math/MathML">
+   <semantics>
+      <mrow>
+         <mrow>
+            <mn>2</mn>
+            <mo>*</mo>
+            <mi>x</mi>
+         </mrow>
+         <mo>+</mo>
+         <mrow>
+            <mn>3</mn>
+            <mo>*</mo>
+            <mi>y</mi>
+         </mrow>
+      </mrow>
+      <annotation-xml encoding="MathML-Content">
+         <apply>
+            <plus/>
+            <apply>
+               <times/>
+               <cn>2</cn>
+               <ci>x</ci>
+            </apply>
+            <apply>
+               <times/>
+               <cn>3</cn>
+               <ci>y</ci>
+            </apply>
+         </apply>
+      </annotation-xml>
+      <annotation encoding="ASCIIMathInput"/>
+      <annotation encoding="Maxima">(2 * x) + (3 * y)</annotation>
+   </semantics>
+</math>
+
+
+
+ + + \ No newline at end of file diff --git a/common/lib/capa/capa/tests/test_files/snuggletex_x+x+3y.xml b/common/lib/capa/capa/tests/test_files/snuggletex_x+x+3y.xml new file mode 100644 index 0000000000..09b5ae8312 --- /dev/null +++ b/common/lib/capa/capa/tests/test_files/snuggletex_x+x+3y.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + SnuggleTeX - ASCIIMathML Enrichment Demo + + + + + + + +

SnuggleTeX (1.2.2)

+
+
Skip Navigation
+ +
+ +
+

ASCIIMathML Enrichment Demo

+

Input

+

+ This demo is similar to the + MathML Semantic Enrichnment Demo + but uses + ASCIIMathML as + an alternative input format, which provides real-time feedback as you + type but can often generate MathML with odd semantics in it. + SnuggleTeX includes some functionality that can to convert this raw MathML into + something equivalent to its own MathML output, thereby allowing you to + semantically enrich it in + certain simple cases, making ASCIIMathML a possibly viable input format + for simple semantic maths. + +

+

+ To try the demo, simply enter some some ASCIIMathML into the box below. + You should see a real time preview of this while you type. + Then hit Go! to use SnuggleTeX to semantically enrich your + input. + +

+
+
+ ASCIIMath Input: +
+
+

Live Preview

+

+ This is a MathML rendering of your input, generated by ASCIIMathML as you type. + +

+
+
+
+

+ This is the underlying MathML source generated by ASCIIMathML, again updated in real time. + +

+
 
+

Enhanced Presentation MathML

+

+ This shows the result of attempting to enrich the raw Presentation MathML + generated by ASCIIMathML: + +

<math xmlns="http://www.w3.org/1998/Math/MathML">
+   <mrow>
+      <mi>x</mi>
+      <mo>+</mo>
+      <mi>x</mi>
+      <mo>+</mo>
+      <mrow>
+         <mn>3</mn>
+         <mo>*</mo>
+         <mi>y</mi>
+      </mrow>
+   </mrow>
+</math>

Content MathML

+

+ This shows the result of an attempted + conversion to Content MathML: + +

<math xmlns="http://www.w3.org/1998/Math/MathML">
+   <apply>
+      <plus/>
+      <ci>x</ci>
+      <ci>x</ci>
+      <apply>
+         <times/>
+         <cn>3</cn>
+         <ci>y</ci>
+      </apply>
+   </apply>
+</math>

Maxima Input Form

+

+ This shows the result of an attempted + conversion to Maxima Input syntax: + +

x + x + (3 * y)

MathML Parallel Markup

+

+ This shows the enhanced Presentation MathML with other forms encapsulated + as annotations: + +

<math xmlns="http://www.w3.org/1998/Math/MathML">
+   <semantics>
+      <mrow>
+         <mi>x</mi>
+         <mo>+</mo>
+         <mi>x</mi>
+         <mo>+</mo>
+         <mrow>
+            <mn>3</mn>
+            <mo>*</mo>
+            <mi>y</mi>
+         </mrow>
+      </mrow>
+      <annotation-xml encoding="MathML-Content">
+         <apply>
+            <plus/>
+            <ci>x</ci>
+            <ci>x</ci>
+            <apply>
+               <times/>
+               <cn>3</cn>
+               <ci>y</ci>
+            </apply>
+         </apply>
+      </annotation-xml>
+      <annotation encoding="ASCIIMathInput"/>
+      <annotation encoding="Maxima">x + x + (3 * y)</annotation>
+   </semantics>
+</math>
+
+
+
+ + + \ No newline at end of file diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index fd056f884e..405c385ea9 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -9,6 +9,7 @@ import pyparsing import random import unittest import textwrap +import requests import mock from . import new_loncapa_problem, test_system @@ -199,9 +200,8 @@ class SymbolicResponseTest(ResponseTest): from capa.tests.response_xml_factory import SymbolicResponseXMLFactory xml_factory_class = SymbolicResponseXMLFactory - def test_grade_single_input(self): - problem = self.build_problem(math_display=True, - expect="2*x+3*y") + def test_grade_single_input_correct(self): + problem = self.build_problem(math_display=True, expect="2*x+3*y") # Correct answers correct_inputs = [ @@ -209,17 +209,27 @@ class SymbolicResponseTest(ResponseTest): 2*x+3*y - """)), + """), + 'snuggletex_2x+3y.xml'), ('x+x+3y', textwrap.dedent(""" x+x+3*y - """)), + """), + 'snuggletex_x+x+3y.xml'), ] - for (input_str, input_mathml) in correct_inputs: - self._assert_symbolic_grade(problem, input_str, input_mathml, 'correct') + for (input_str, input_mathml, server_fixture) in correct_inputs: + print "Testing input: {0}".format(input_str) + server_resp = self._load_fixture(server_fixture) + self._assert_symbolic_grade( + problem, input_str, input_mathml, + 'correct', snuggletex_resp=server_resp + ) + + def test_grade_single_input_incorrect(self): + problem = self.build_problem(math_display=True, expect="2*x+3*y") # Incorrect answers incorrect_inputs = [ @@ -234,112 +244,86 @@ class SymbolicResponseTest(ResponseTest): for (input_str, input_mathml) in incorrect_inputs: self._assert_symbolic_grade(problem, input_str, input_mathml, 'incorrect') - def test_complex_number_grade(self): + def test_complex_number_grade_correct(self): + problem = self.build_problem( + math_display=True, + expect="[[cos(theta),i*sin(theta)],[i*sin(theta),cos(theta)]]", + options=["matrix", "imaginary"] + ) + + correct_snuggletex = self._load_fixture('snuggletex_correct.html') + dynamath_input = self._load_fixture('dynamath_input.txt') + student_response = "cos(theta)*[[1,0],[0,1]] + i*sin(theta)*[[0,1],[1,0]]" + + self._assert_symbolic_grade( + problem, student_response, dynamath_input, + 'correct', + snuggletex_resp=correct_snuggletex + ) + + def test_complex_number_grade_incorrect(self): + problem = self.build_problem(math_display=True, expect="[[cos(theta),i*sin(theta)],[i*sin(theta),cos(theta)]]", options=["matrix", "imaginary"]) - # For LaTeX-style inputs, symmath_check() will try to contact - # a server to convert the input to MathML. - # We mock out the server, simulating the response that it would give - # for this input. - import requests - dirpath = os.path.dirname(__file__) - correct_snuggletex_response = open(os.path.join(dirpath, "test_files/snuggletex_correct.html")).read().decode('utf8') - wrong_snuggletex_response = open(os.path.join(dirpath, "test_files/snuggletex_wrong.html")).read().decode('utf8') + wrong_snuggletex = self._load_fixture('snuggletex_wrong.html') + dynamath_input = textwrap.dedent(""" + + 2 + + """) - # Correct answer - with mock.patch.object(requests, 'post') as mock_post: - - # Simulate what the LaTeX-to-MathML server would - # send for the correct response input - mock_post.return_value.text = correct_snuggletex_response - - self._assert_symbolic_grade(problem, - "cos(theta)*[[1,0],[0,1]] + i*sin(theta)*[[0,1],[1,0]]", - textwrap.dedent(""" - - - - cos - (θ) - - - - [ - - - 10 - - - 01 - - - ] - - + - i - - - sin - - (θ) - - - - - [ - - - 01 - - - 10 - - - ] - - - - """), - 'correct') - - # Incorrect answer - with mock.patch.object(requests, 'post') as mock_post: - - # Simulate what the LaTeX-to-MathML server would - # send for the incorrect response input - mock_post.return_value.text = wrong_snuggletex_response - - self._assert_symbolic_grade(problem, "2", - textwrap.dedent(""" - - 2 - - """), - 'incorrect') + self._assert_symbolic_grade( + problem, "2", dynamath_input, + 'incorrect', + snuggletex_resp=wrong_snuggletex, + ) def test_multiple_inputs_exception(self): # Should not allow multiple inputs, since we specify # only one "expect" value with self.assertRaises(Exception): - self.build_problem(math_display=True, - expect="2*x+3*y", - num_inputs=3) + self.build_problem(math_display=True, expect="2*x+3*y", num_inputs=3) - def _assert_symbolic_grade(self, problem, - student_input, - dynamath_input, - expected_correctness): + def _assert_symbolic_grade( + self, problem, student_input, dynamath_input, expected_correctness, + snuggletex_resp="" + ): + """ + Assert that the symbolic response has a certain grade. + + `problem` is the capa problem containing the symbolic response. + `student_input` is the text the student entered. + `dynamath_input` is the JavaScript rendered MathML from the page. + `expected_correctness` is either "correct" or "incorrect" + `snuggletex_resp` is the simulated response from the Snuggletex server + """ input_dict = {'1_2_1': str(student_input), '1_2_1_dynamath': str(dynamath_input)} - correct_map = problem.grade_answers(input_dict) + # Simulate what the Snuggletex server would respond + with mock.patch.object(requests, 'post') as mock_post: + mock_post.return_value.text = snuggletex_resp - self.assertEqual( - correct_map.get_correctness('1_2_1'), expected_correctness - ) + correct_map = problem.grade_answers(input_dict) + + self.assertEqual( + correct_map.get_correctness('1_2_1'), expected_correctness + ) + + @staticmethod + def _load_fixture(relpath): + """ + Return a `unicode` object representing the contents + of the fixture file at `relpath` (relative to the test files dir) + """ + abspath = os.path.join(os.path.dirname(__file__), 'test_files', relpath) + with open(abspath) as fixture_file: + contents = fixture_file.read() + + return contents.decode('utf8') class OptionResponseTest(ResponseTest): diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py index 5c5d8307af..b0c1617580 100644 --- a/common/lib/xmodule/xmodule/tests/test_export.py +++ b/common/lib/xmodule/xmodule/tests/test_export.py @@ -6,6 +6,8 @@ from datetime import datetime, timedelta, tzinfo from tempfile import mkdtemp import unittest import shutil +from textwrap import dedent +import mock import pytz from fs.osfs import OSFS @@ -35,12 +37,23 @@ def strip_filenames(descriptor): class RoundTripTestCase(unittest.TestCase): - ''' Check that our test courses roundtrip properly. - Same course imported , than exported, then imported again. - And we compare original import with second import (after export). - Thus we make sure that export and import work properly. - ''' - def check_export_roundtrip(self, data_dir, course_dir): + """ + Check that our test courses roundtrip properly. + Same course imported , than exported, then imported again. + And we compare original import with second import (after export). + Thus we make sure that export and import work properly. + """ + + @mock.patch('xmodule.course_module.requests.get') + def check_export_roundtrip(self, data_dir, course_dir, mock_get): + + # Patch network calls to retrieve the textbook TOC + mock_get.return_value.text = dedent(""" + + + + """).strip() + root_dir = path(self.temp_dir) print("Copying test course to temp dir {0}".format(root_dir))