Commit 76707cb8 by Peter Baratta

Add tests for preview.py

Test the `render_X` methods as well as `latex_preview` on the whole.
Add a few 'pragma: no cover's to hide unreachable code from coverage.
parent 995caf70
......@@ -11,11 +11,13 @@ string of latex, store it in a custom class `LatexRendered`
from pyparsing import ParseResults
from calc import parse_algebra, add_defaults, SUFFIXES
PREVIEW_DEBUG = False
def PRINT(x):
PREVIEW_DEBUG = False
if PREVIEW_DEBUG:
print x
class LatexRendered(object):
"""
Data structure to hold a typeset representation of some math.
......@@ -41,7 +43,7 @@ class LatexRendered(object):
self.tall = tall
# generate parens and overwrite self.latex
if parens != None:
if parens is not None:
left_parens = parens
if left_parens == '{':
left_parens = r'\{'
......@@ -65,6 +67,7 @@ class LatexRendered(object):
right=right_parens
)
def render_number(children):
PRINT("rendering number ['{}']".format("', '".join(k.latex for k in children)))
# TODO exponential notation
......@@ -72,6 +75,7 @@ def render_number(children):
children[-1].latex = ur"\text{{{suffix}}}".format(suffix=children[-1].latex)
return LatexRendered("".join(k.latex for k in children))
def variable_closure(variables, casify):
def render_variable(children):
# TODO epsilon_0
......@@ -88,6 +92,7 @@ def variable_closure(variables, casify):
return LatexRendered(varname) #.replace("_", r"\_"))
return render_variable
def function_closure(functions, casify):
def render_function(children):
fname = children[0].latex
......@@ -123,17 +128,19 @@ def render_power(children):
children_latex[-1] = children[-1].sans_parens
PRINT("rendering power ['{}']".format("', '".join(children_latex)))
raise_power = lambda x,y: u"{}^{{{}}}".format(y, x)
raise_power = lambda x, y: u"{}^{{{}}}".format(y, x)
latex = reduce(raise_power, reversed(children_latex))
return LatexRendered(latex, tall=True)
def render_parallel(children):
children_latex = [k.latex for k in children if k.latex != "||"]
PRINT("rendering parallel ['{}']".format("', '".join(children_latex)))
latex = r"\|".join(children)
latex = r"\|".join(children_latex)
tall = any(k.tall for k in children)
return LatexRendered(latex, tall=tall)
def render_frac(numerator, denominator):
# subtlety: avoid parens if there is only thing in that part
if len(numerator) == 1:
......@@ -172,7 +179,7 @@ def render_product(children):
# render the current fraction and add it to the latex
latex += render_frac(numerator, denominator) + r"\cdot "
# reset back to
# reset back to beginning state
position = "numerator"
numerator = []
denominator = []
......@@ -190,6 +197,7 @@ def render_product(children):
tall = fraction_mode_ever or any(k.tall for k in children)
return LatexRendered(latex, tall=tall)
def render_sum(children):
children_latex = [k.latex for k in children]
PRINT("rendering sum ['{}']".format("', '".join(children_latex)))
......@@ -197,6 +205,7 @@ def render_sum(children):
tall = any(k.tall for k in children)
return LatexRendered(latex, tall=tall)
def render_atom(children):
PRINT("rendering atom ['{}']".format("', '".join(k.latex for k in children)))
parens = None
......@@ -213,6 +222,7 @@ def render_atom(children):
tall
)
def latex_preview(string, variables=None, functions=None, case_sensitive=False):
"""
"""
......@@ -231,6 +241,7 @@ def latex_preview(string, variables=None, functions=None, case_sensitive=False):
casify = lambda x: x
else:
casify = lambda x: x.lower() # Lowercase for case insens.
render_action = {
'number': render_number,
'variable': variable_closure(set(variables), casify),
......@@ -250,7 +261,7 @@ def latex_preview(string, variables=None, functions=None, case_sensitive=False):
name = branch.getName()
if len(branch) == 1 and name not in ("number", "variable"):
return render_branch(branch[0])
elif name not in render_action:
elif name not in render_action: # pragma: no cover
raise Exception(u"Unknown branch name '{}'".format(name))
else:
action = render_action[name]
......
......@@ -50,7 +50,7 @@ class EvaluatorTest(unittest.TestCase):
"""
try:
self.assertEqual(4.0, calc.evaluator({}, {}, '4.'))
except ParseException:
except ParseException: # pragma: no cover
self.fail("'4.' is a valid input, but threw an exception")
def test_exponential_answer(self):
......
......@@ -5,15 +5,213 @@ Unit tests for preview.py
import unittest
import preview
import pyparsing
from mock import MagicMock
class PreviewTestUtility(unittest.TestCase):
def assert_latex_rendered(self, to_be_tested, tall=False, latex=None, sans_parens=None):
if sans_parens is None:
sans_parens = latex
self.assertEquals(to_be_tested.sans_parens, sans_parens)
self.assertEquals(to_be_tested.latex, latex)
self.assertEquals(to_be_tested.tall, tall)
class PreviewTest(unittest.TestCase):
class LatexRenderedTest(PreviewTestUtility):
def test_simple(self):
math = 'x^2'
obj = preview.LatexRendered(math, tall=True)
self.assert_latex_rendered(obj, tall=True, latex=math)
def _each_parens(self, with_parens, math, parens, tall=False):
obj = preview.LatexRendered(math, parens=parens, tall=tall)
self.assert_latex_rendered(obj, tall=tall, latex=with_parens, sans_parens=math)
def test_parens(self):
self._each_parens('(x+y)', 'x+y', '(')
def test_brackets(self):
self._each_parens('[x+y]', 'x+y', '[')
def test_squiggles(self):
self._each_parens(r'\{x+y\}', 'x+y', '{')
def test_parens_tall(self):
self._each_parens(r'\left(x^y\right)', 'x^y', '(', tall=True)
def test_brackets_tall(self):
self._each_parens(r'\left[x^y\right]', 'x^y', '[', tall=True)
def test_squiggles_tall(self):
self._each_parens(r'\left\{x^y\right\}', 'x^y', '{', tall=True)
def test_bad_parens(self):
with self.assertRaisesRegexp(Exception, 'Unknown parenthesis'):
preview.LatexRendered('x^2', parens='not parens')
def _call_render_method(render_method, string_children):
children = [preview.LatexRendered(k) for k in string_children]
return render_method(children)
class RenderMethodsTest(PreviewTestUtility):
def test_number_simple(self):
out = _call_render_method(preview.render_number, ['3.1415'])
self.assert_latex_rendered(out, latex='3.1415')
def test_number_suffix(self):
out = _call_render_method(preview.render_number, ['1.618', 'k'])
self.assert_latex_rendered(out, latex=r'1.618\text{k}')
def test_variable_simple(self):
render_variable = preview.variable_closure(['x', 'y'], lambda x: x.lower())
out = _call_render_method(render_variable, ['x'])
self.assert_latex_rendered(out, latex='x')
def test_greek(self):
render_variable = preview.variable_closure(['pi'], lambda x: x.lower())
out = _call_render_method(render_variable, ['pi'])
self.assert_latex_rendered(out, latex=r'\pi ')
def test_function_simple(self):
render_function = preview.function_closure(['f'], lambda x: x.lower())
out = _call_render_method(render_function, ['f', 'x'])
self.assert_latex_rendered(out, latex=r'\text{f}(x)')
def test_function_tall(self):
render_function = preview.function_closure(['f'], lambda x: x.lower())
out = render_function([
preview.LatexRendered('f'),
preview.LatexRendered('x^2', tall=True)
])
self.assert_latex_rendered(out, latex=r'\text{f}\left(x^2\right)', tall=True)
def test_function_sqrt(self):
render_function = preview.function_closure(['sqrt'], lambda x: x.lower())
out = _call_render_method(render_function, ['sqrt', 'x'])
self.assert_latex_rendered(out, latex=r'\sqrt{x}')
def test_function_log10(self):
render_function = preview.function_closure(['log10'], lambda x: x.lower())
out = _call_render_method(render_function, ['log10', 'x'])
self.assert_latex_rendered(out, latex=r'\log_{10}(x)')
def test_function_log2(self):
render_function = preview.function_closure(['log2'], lambda x: x.lower())
out = _call_render_method(render_function, ['log2', 'x'])
self.assert_latex_rendered(out, latex=r'\log_2(x)')
def test_power_simple(self):
out = _call_render_method(
preview.render_power,
['x', '^', 'y', '^', '2']
)
self.assert_latex_rendered(out, latex='x^{y^{2}}', tall=True)
def test_power_parens(self):
children = ['x', '^', 'y', '^'] # (x+y)
children = [preview.LatexRendered(k) for k in children]
children.append(preview.LatexRendered('x+y', parens='('))
out = preview.render_power(children)
self.assert_latex_rendered(out, latex='x^{y^{x+y}}', tall=True)
def test_parallel(self):
out = _call_render_method(preview.render_parallel, ['x', '||', 'y'])
self.assert_latex_rendered(out, latex=r'x\|y')
def test_product_mult_only(self):
out = _call_render_method(preview.render_product, ['x', '*', 'y'])
self.assert_latex_rendered(out, latex=r'x\cdot y')
def test_product_big_frac(self):
out = _call_render_method(
preview.render_product, list('w*x/y/z')
)
self.assert_latex_rendered(out, latex=r'\frac{w\cdot x}{y\cdot z}', tall=True)
def test_product_single_frac(self):
out = preview.render_product([
preview.LatexRendered('x+1', parens='('),
preview.LatexRendered('/'),
preview.LatexRendered('y+1', parens='(')
])
self.assert_latex_rendered(out, latex=r'\frac{x+1}{y+1}', tall=True)
def test_product_keep_going(self):
out = _call_render_method(
preview.render_product, list('p/q*r/s*t')
)
self.assert_latex_rendered(out, latex=r'\frac{p}{q}\cdot \frac{r}{s}\cdot t', tall=True)
def test_sum(self):
out = _call_render_method(preview.render_sum, list('-a+b-c+d'))
self.assert_latex_rendered(out, latex=r'-a+b-c+d')
def test_sum_tall(self):
out = preview.render_sum([
preview.LatexRendered('x'),
preview.LatexRendered('+'),
preview.LatexRendered('y^2', tall=True)
])
self.assert_latex_rendered(out, latex='x+y^2', tall=True)
def test_atom_simple(self):
out = _call_render_method(preview.render_atom, list('x'))
self.assert_latex_rendered(out, latex='x')
def test_atom_parens(self):
out = _call_render_method(preview.render_atom, list('(x)'))
self.assert_latex_rendered(out, latex='(x)', sans_parens='x')
class LatexPreviewTest(unittest.TestCase):
"""
Run tests for preview.latex_preview
"""
def test_method_works(self):
def test_no_input(self):
self.assertEquals('<nada/>', preview.latex_preview(''))
self.assertEquals('<nada/>', preview.latex_preview(' '))
self.assertEquals('<nada/>', preview.latex_preview(' \t '))
def test_complicated(self):
self.assertEquals(
preview.latex_preview('11*f(x)+x^2*(3||4)/sqrt(pi)'),
r'11\cdot \text{f}(x)+\frac{x^{2}\cdot (3\|4)}{\sqrt{\pi }}'
)
self.assertEquals(
preview.latex_preview('log10(1+3/4/Cos(x^2)*(x+1))', case_sensitive=True),
r'\log_{10}\left(1+\frac{3}{4\cdot \text{Cos}\left(x^{2}\right)}\cdot (x+1)\right)'
)
def test_syntax_errors(self):
"""
Test that no exceptions are thrown and something returns
Test a lot of math strings that give syntax errors
Rather than have a lot of self.assertRaises, make a loop and keep track
of those that do not throw a `ParseException`, and assert at the end.
"""
result = preview.latex_preview(u"✖^2+1/2")
self.assertTrue(len(result) > 0)
bad_math_list = [
'11+',
'11*',
'f((x)',
'sqrt(x^)',
'3f(x)', # not 3*f(x)
'3|4',
'3|||4'
]
bad_exceptions = {}
for math in bad_math_list:
try:
preview.latex_preview(math)
except pyparsing.ParseException:
pass # This is what we were expecting.
except Exception as error: # pragma: no cover
bad_exceptions[math] = error
else: # pragma: no cover
# If there is no exception thrown, this is a problem
bad_exceptions[math] = None
self.assertEquals({}, bad_exceptions)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment