From 5e8d2ce4f7a12d286184101a4859d7da6be9d5e5 Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Mon, 20 May 2013 13:59:12 -0400 Subject: [PATCH 01/18] Start making comprehensive calc tests --- common/lib/calc/tests/test_calc.py | 266 +++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 common/lib/calc/tests/test_calc.py diff --git a/common/lib/calc/tests/test_calc.py b/common/lib/calc/tests/test_calc.py new file mode 100644 index 0000000000..87b68e36be --- /dev/null +++ b/common/lib/calc/tests/test_calc.py @@ -0,0 +1,266 @@ +""" +unittests for calc.py + +Run like this: + + rake test_common/lib/calc ?? + +""" + +import unittest +import numpy +import calc + + +class EvaluatorTest(unittest.TestCase): + ''' Run tests for calc.evaluator + Go through all functionalities as specifically as possible-- + work from number input to functions and complex expressions + Also test custom variable substitutions (i.e. + `evaluator({'x':3.0},{}, '3*x')` + gives 9.0) and more. + ''' + + def test_number_input(self): + ''' Test different kinds of float inputs ''' + easy_eval = lambda x: calc.evaluator({}, {}, x) + + self.assertEqual(easy_eval("13"), 13) + self.assertEqual(easy_eval("3.14"), 3.14) + self.assertEqual(easy_eval(".618033989"), 0.618033989) + + self.assertEqual(easy_eval("-13"), -13) + self.assertEqual(easy_eval("-3.14"), -3.14) + self.assertEqual(easy_eval("-.618033989"), -0.618033989) + # see also test_exponential_answer + # and test_si_suffix + + def test_exponential_answer(self): + ''' Test for correct interpretation of scientific notation''' + answer = 50 + correct_responses = ["50", "50.0", "5e1", "5e+1", "50e0", "50.0e0", "500e-1"] + incorrect_responses = ["", "3.9", "4.1", "0", "5.01e1"] + + for input_str in correct_responses: + result = calc.evaluator({}, {}, input_str) + fail_msg = "Failed when checking '{0}' against {1} (expected equality)".format( + input_str, answer) + self.assertEqual(answer, result, msg=fail_msg) + + for input_str in incorrect_responses: + result = calc.evaluator({}, {}, input_str) + fail_msg = "Failed when checking '{0}' against {1} (expected inequality)".format( + input_str, answer) + self.assertNotEqual(answer, result, msg=fail_msg) + + def test_si_suffix(self): + ''' Test calc.py's unique functionality of interpreting si 'suffixes'. + For instance 'k' stand for 'kilo-' so '1k' should be 1,000 ''' + test_mapping = [('4.2%', 0.042), ('2.25k', 2250), ('8.3M', 8300000), + ('9.9G', 9.9e9), ('1.2T', 1.2e12), ('7.4c', 0.074), + ('5.4m', 0.0054), ('8.7u', 0.0000087), + ('5.6n', 5.6e-9), ('4.2p', 4.2e-12)] + + for (expr, answer) in test_mapping: + tolerance = answer * 1e-6 # Testing exactly fails for the large values + fail_msg = "Failure in testing suffix '{0}': '{1}' was not {2}".format( + expr[-1], expr, answer) + self.assertAlmostEqual(calc.evaluator({}, {}, expr), answer, + delta=tolerance, msg=fail_msg) + + def test_operator_sanity(self): + ''' Test for simple things like "5+2" and "5/2"''' + var1 = 5.0 + var2 = 2.0 + operators = [('+', 7), ('-', 3), ('*', 10), ('/', 2.5), ('^', 25)] + + for (operator, answer) in operators: + input_str = "{0} {1} {2}".format(var1, operator, var2) + result = calc.evaluator({}, {}, input_str) + fail_msg = "Failed on operator '{0}': '{1}' was not {2}".format( + operator, input_str, answer) + self.assertEqual(answer, result, msg=fail_msg) + + self.assertRaises(ZeroDivisionError, calc.evaluator, + {}, {}, '1/0') + + def test_parallel_resistors(self): + ''' Test the special operator || + The formula is given by + a || b || c ... + = 1 / (1/a + 1/b + 1/c + ...) + It is the resistance of a parallel circuit of resistors with resistance + a, b, c, etc&. See if this evaulates correctly.''' + self.assertEqual(calc.evaluator({}, {}, '1||1'), 0.5) + self.assertEqual(calc.evaluator({}, {}, '1||1||2'), 0.4) + + # I don't know why you would want this, but it works. + self.assertEqual(calc.evaluator({}, {}, "j||1"), 0.5 + 0.5j) + + self.assertRaises(ZeroDivisionError, calc.evaluator, + {}, {}, '0||1') + + + def assert_function_values(self, fname, ins, outs, tolerance=1e-3): + ''' Helper function to test many values at once + Test the accuracy of evaluator's use of the function given by fname + Specifically, the equality of `fname(ins[i])` against outs[i]. + Used later to test a whole bunch of f(x) = y at a time ''' + + for (arg, val) in zip(ins, outs): + input_str = "{0}({1})".format(fname, arg) + result = calc.evaluator({}, {}, input_str) + fail_msg = "Failed on function {0}: '{1}' was not {2}".format( + fname, input_str, val) + self.assertAlmostEqual(val, result, delta=tolerance, msg=fail_msg) + + def test_trig_functions(self): + """Test the trig functions provided in common/lib/calc/calc.py""" + # which are: sin, cos, tan, arccos, arcsin, arctan + + angles = ['-pi/4', '0', 'pi/6', 'pi/5', '5*pi/4', '9*pi/4', 'j', '1 + j'] + sin_values = [-0.707, 0, 0.5, 0.588, -0.707, 0.707, 1.175j, 1.298 + 0.635j] + cos_values = [0.707, 1, 0.866, 0.809, -0.707, 0.707, 1.543, 0.834 - 0.989j] + tan_values = [-1, 0, 0.577, 0.727, 1, 1, 0.762j, 0.272 + 1.084j] + # Cannot test tan(pi/2) b/c pi/2 is a float and not precise... + + self.assert_function_values('sin', angles, sin_values) + self.assert_function_values('cos', angles, cos_values) + self.assert_function_values('tan', angles, tan_values) + + # include those where the real part is between -pi/2 and pi/2 + arcsin_inputs = ['-0.707', '0', '0.5', '0.588', '1.298 + 0.635*j'] + arcsin_angles = [-0.785, 0, 0.524, 0.629, 1 + 1j] + self.assert_function_values('arcsin', arcsin_inputs, arcsin_angles) + # rather than throwing an exception, numpy.arcsin gives nan + # self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arcsin(-1.1)'))) + # self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arcsin(1.1)'))) + # disabled for now because they are giving a runtime warning... :-/ + + # include those where the real part is between 0 and pi + arccos_inputs = ['1', '0.866', '0.809', '0.834-0.989*j'] + arccos_angles = [0, 0.524, 0.628, 1 + 1j] + self.assert_function_values('arccos', arccos_inputs, arccos_angles) + # self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arccos(-1.1)'))) + # self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arccos(1.1)'))) + + # has the same range as arcsin + arctan_inputs = ['-1', '0', '0.577', '0.727', '0.272 + 1.084*j'] + arctan_angles = arcsin_angles + self.assert_function_values('arctan', arctan_inputs, arctan_angles) + + def test_other_functions(self): + """Test the other functions provided in common/lib/calc/calc.py""" + # sqrt, log10, log2, ln, abs, + # fact, factorial + + # test sqrt + self.assert_function_values('sqrt', + [0, 1, 2, 1024], # -1 + [0, 1, 1.414, 32]) # 1j + # sqrt(-1) is NAN not j (!!). + + # test logs + self.assert_function_values('log10', + [0.1, 1, 3.162, 1000000, '1+j'], + [-1, 0, 0.5, 6, 0.151 + 0.341j]) + self.assert_function_values('log2', + [0.5, 1, 1.414, 1024, '1+j'], + [-1, 0, 0.5, 10, 0.5 + 1.133j]) + self.assert_function_values('ln', + [0.368, 1, 1.649, 2.718, 42, '1+j'], + [-1, 0, 0.5, 1, 3.738, 0.347 + 0.785j]) + + # test abs + self.assert_function_values('abs', [-1, 0, 1, 'j'], [1, 0, 1, 1]) + + # test factorial + fact_inputs = [0, 1, 3, 7] + fact_values = [1, 1, 6, 5040] + self.assert_function_values('fact', fact_inputs, fact_values) + self.assert_function_values('factorial', fact_inputs, fact_values) + + self.assertRaises(ValueError, calc.evaluator, {}, {}, "fact(-1)") + self.assertRaises(ValueError, calc.evaluator, {}, {}, "fact(0.5)") + self.assertRaises(ValueError, calc.evaluator, {}, {}, "factorial(-1)") + self.assertRaises(ValueError, calc.evaluator, {}, {}, "factorial(0.5)") + + def test_constants(self): + """Test the default constants provided in common/lib/calc/calc.py""" + # which are: j (complex number), e, pi, k, c, T, q + + # ('expr', python value, tolerance (or None for exact)) + default_variables = [('j', 1j, None), + ('e', 2.7183, 1e-3), + ('pi', 3.1416, 1e-3), + # c = speed of light + ('c', 2.998e8, 1e5), + # 0 deg C = T Kelvin + ('T', 298.15, 0.01), + # note k = scipy.constants.k = 1.3806488e-23 + ('k', 1.3806488e-23, 1e-26), + # note q = scipy.constants.e = 1.602176565e-19 + ('q', 1.602176565e-19, 1e-22)] + for (variable, value, tolerance) in default_variables: + fail_msg = "Failed on constant '{0}', not within bounds".format( + variable) + result = calc.evaluator({}, {}, variable) + if tolerance is None: + self.assertEqual(value, result, msg=fail_msg) + else: + self.assertAlmostEqual(value, result, + delta=tolerance, msg=fail_msg) + + def test_complex_expression(self): + """We've only tried simple things so far, make sure it can handle a + more complexity than this.""" + + self.assertAlmostEqual( + calc.evaluator({}, {}, "(2^2+1.0)/sqrt(5e0)*5-1"), + 10.180, + delta=1e-3) + + self.assertAlmostEqual( + calc.evaluator({}, {}, "1+1/(1+1/(1+1/(1+1)))"), + 1.6, + delta=1e-3) + self.assertAlmostEqual( + calc.evaluator({}, {}, "10||sin(7+5)"), + -0.567, delta=0.01) + + def test_calc(self): + variables = {'R1': 2.0, 'R3': 4.0} + functions = {'sin': numpy.sin, 'cos': numpy.cos} + + self.assertAlmostEqual( + calc.evaluator(variables, functions, "10||sin(7+5)"), + -0.567, delta=0.01) + self.assertEqual(calc.evaluator({'R1': 2.0, 'R3': 4.0}, {}, "13"), 13) + self.assertEqual(calc.evaluator(variables, functions, "13"), 13) + self.assertEqual( + calc.evaluator({ + 'a': 2.2997471478310274, 'k': 9, 'm': 8, + 'x': 0.66009498411213041}, + {}, "5"), + 5) + self.assertEqual(calc.evaluator(variables, functions, "R1*R3"), 8.0) + self.assertAlmostEqual(calc.evaluator(variables, functions, "sin(e)"), 0.41, delta=0.01) + self.assertAlmostEqual(calc.evaluator(variables, functions, "k*T/q"), 0.025, delta=1e-3) + self.assertAlmostEqual(calc.evaluator(variables, functions, "e^(j*pi)"), -1, delta=1e-5) + + variables['t'] = 1.0 + self.assertAlmostEqual(calc.evaluator(variables, functions, "t"), 1.0, delta=1e-5) + self.assertAlmostEqual(calc.evaluator(variables, functions, "T"), 1.0, delta=1e-5) + self.assertAlmostEqual(calc.evaluator(variables, functions, "t", cs=True), 1.0, delta=1e-5) + self.assertAlmostEqual(calc.evaluator(variables, functions, "T", cs=True), 298, delta=0.2) + + self.assertRaises(calc.UndefinedVariable, calc.evaluator, + {}, {}, "5+7 QWSEKO") + + self.assertRaises(calc.UndefinedVariable, calc.evaluator, + {'r1': 5}, {}, "r1+r2") + + self.assertEqual(calc.evaluator(variables, functions, "r1*r3"), 8.0) + + self.assertRaises(calc.UndefinedVariable, calc.evaluator, + variables, functions, "r1*r3", cs=True) From 82482e89ea2bab3eb426bc7ce0fabf2e6b9fe731 Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Tue, 21 May 2013 11:07:19 -0400 Subject: [PATCH 02/18] Finish calc.py tests; add docstrings, etc --- common/lib/calc/tests/test_calc.py | 126 ++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 40 deletions(-) diff --git a/common/lib/calc/tests/test_calc.py b/common/lib/calc/tests/test_calc.py index 87b68e36be..2e77aeec6c 100644 --- a/common/lib/calc/tests/test_calc.py +++ b/common/lib/calc/tests/test_calc.py @@ -38,18 +38,19 @@ class EvaluatorTest(unittest.TestCase): def test_exponential_answer(self): ''' Test for correct interpretation of scientific notation''' answer = 50 - correct_responses = ["50", "50.0", "5e1", "5e+1", "50e0", "50.0e0", "500e-1"] + correct_responses = ["50", "50.0", "5e1", "5e+1", + "50e0", "50.0e0", "500e-1"] incorrect_responses = ["", "3.9", "4.1", "0", "5.01e1"] for input_str in correct_responses: result = calc.evaluator({}, {}, input_str) - fail_msg = "Failed when checking '{0}' against {1} (expected equality)".format( + fail_msg = "Expected '{0}' to equal {1}".format( input_str, answer) self.assertEqual(answer, result, msg=fail_msg) for input_str in incorrect_responses: result = calc.evaluator({}, {}, input_str) - fail_msg = "Failed when checking '{0}' against {1} (expected inequality)".format( + fail_msg = "Expected '{0}' to not equal {1}".format( input_str, answer) self.assertNotEqual(answer, result, msg=fail_msg) @@ -62,9 +63,9 @@ class EvaluatorTest(unittest.TestCase): ('5.6n', 5.6e-9), ('4.2p', 4.2e-12)] for (expr, answer) in test_mapping: - tolerance = answer * 1e-6 # Testing exactly fails for the large values - fail_msg = "Failure in testing suffix '{0}': '{1}' was not {2}".format( - expr[-1], expr, answer) + tolerance = answer * 1e-6 # Make rel. tolerance, because of floats + fail_msg = "Failure in testing suffix '{0}': '{1}' was not {2}" + fail_msg = fail_msg.format(expr[-1], expr, answer) self.assertAlmostEqual(calc.evaluator({}, {}, expr), answer, delta=tolerance, msg=fail_msg) @@ -85,7 +86,7 @@ class EvaluatorTest(unittest.TestCase): {}, {}, '1/0') def test_parallel_resistors(self): - ''' Test the special operator || + ''' Test the parallel resistor operator || The formula is given by a || b || c ... = 1 / (1/a + 1/b + 1/c + ...) @@ -100,7 +101,6 @@ class EvaluatorTest(unittest.TestCase): self.assertRaises(ZeroDivisionError, calc.evaluator, {}, {}, '0||1') - def assert_function_values(self, fname, ins, outs, tolerance=1e-3): ''' Helper function to test many values at once Test the accuracy of evaluator's use of the function given by fname @@ -115,12 +115,12 @@ class EvaluatorTest(unittest.TestCase): self.assertAlmostEqual(val, result, delta=tolerance, msg=fail_msg) def test_trig_functions(self): - """Test the trig functions provided in common/lib/calc/calc.py""" - # which are: sin, cos, tan, arccos, arcsin, arctan + """Test the trig functions provided in common/lib/calc/calc.py + which are: sin, cos, tan, arccos, arcsin, arctan""" - angles = ['-pi/4', '0', 'pi/6', 'pi/5', '5*pi/4', '9*pi/4', 'j', '1 + j'] - sin_values = [-0.707, 0, 0.5, 0.588, -0.707, 0.707, 1.175j, 1.298 + 0.635j] - cos_values = [0.707, 1, 0.866, 0.809, -0.707, 0.707, 1.543, 0.834 - 0.989j] + angles = ['-pi/4', '0', 'pi/6', 'pi/5', '5*pi/4', '9*pi/4', '1 + j'] + sin_values = [-0.707, 0, 0.5, 0.588, -0.707, 0.707, 1.298 + 0.635j] + cos_values = [0.707, 1, 0.866, 0.809, -0.707, 0.707, 0.834 - 0.989j] tan_values = [-1, 0, 0.577, 0.727, 1, 1, 0.762j, 0.272 + 1.084j] # Cannot test tan(pi/2) b/c pi/2 is a float and not precise... @@ -150,9 +150,10 @@ class EvaluatorTest(unittest.TestCase): self.assert_function_values('arctan', arctan_inputs, arctan_angles) def test_other_functions(self): - """Test the other functions provided in common/lib/calc/calc.py""" - # sqrt, log10, log2, ln, abs, - # fact, factorial + """Test the non-trig functions provided in common/lib/calc/calc.py + Specifically: + sqrt, log10, log2, ln, abs, + fact, factorial""" # test sqrt self.assert_function_values('sqrt', @@ -212,8 +213,7 @@ class EvaluatorTest(unittest.TestCase): delta=tolerance, msg=fail_msg) def test_complex_expression(self): - """We've only tried simple things so far, make sure it can handle a - more complexity than this.""" + """ Calculate combinations of operators and default functions """ self.assertAlmostEqual( calc.evaluator({}, {}, "(2^2+1.0)/sqrt(5e0)*5-1"), @@ -227,40 +227,86 @@ class EvaluatorTest(unittest.TestCase): self.assertAlmostEqual( calc.evaluator({}, {}, "10||sin(7+5)"), -0.567, delta=0.01) + self.assertAlmostEqual(calc.evaluator({}, {}, "sin(e)"), + 0.41, delta=0.01) + self.assertAlmostEqual(calc.evaluator({}, {}, "k*T/q"), + 0.025, delta=1e-3) + self.assertAlmostEqual(calc.evaluator({}, {}, "e^(j*pi)"), + -1, delta=1e-5) - def test_calc(self): - variables = {'R1': 2.0, 'R3': 4.0} - functions = {'sin': numpy.sin, 'cos': numpy.cos} + def test_simple_vars(self): + """ Substitution of variables into simple equations """ + variables = {'x': 9.72, 'y': 7.91, 'loooooong': 6.4} - self.assertAlmostEqual( - calc.evaluator(variables, functions, "10||sin(7+5)"), - -0.567, delta=0.01) - self.assertEqual(calc.evaluator({'R1': 2.0, 'R3': 4.0}, {}, "13"), 13) - self.assertEqual(calc.evaluator(variables, functions, "13"), 13) + # Should not change value of constant + self.assertEqual(calc.evaluator(variables, {}, '13'), 13) + + # Easy evaluation + self.assertEqual(calc.evaluator(variables, {}, 'x'), 9.72) + self.assertEqual(calc.evaluator(variables, {}, 'y'), 7.91) + self.assertEqual(calc.evaluator(variables, {}, 'loooooong'), 6.4) + + # Test a simple equation + self.assertAlmostEqual(calc.evaluator(variables, {}, '3*x-y'), + 21.25, delta=0.01) # = 3 * 9.72 - 7.91 + self.assertAlmostEqual(calc.evaluator(variables, {}, 'x*y'), + 76.89, delta=0.01) + + # more, I guess + self.assertEqual(calc.evaluator({'x': 9.72, 'y': 7.91}, {}, "13"), 13) + self.assertEqual(calc.evaluator(variables, {}, "13"), 13) self.assertEqual( calc.evaluator({ 'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5"), 5) - self.assertEqual(calc.evaluator(variables, functions, "R1*R3"), 8.0) - self.assertAlmostEqual(calc.evaluator(variables, functions, "sin(e)"), 0.41, delta=0.01) - self.assertAlmostEqual(calc.evaluator(variables, functions, "k*T/q"), 0.025, delta=1e-3) - self.assertAlmostEqual(calc.evaluator(variables, functions, "e^(j*pi)"), -1, delta=1e-5) - variables['t'] = 1.0 - self.assertAlmostEqual(calc.evaluator(variables, functions, "t"), 1.0, delta=1e-5) - self.assertAlmostEqual(calc.evaluator(variables, functions, "T"), 1.0, delta=1e-5) - self.assertAlmostEqual(calc.evaluator(variables, functions, "t", cs=True), 1.0, delta=1e-5) - self.assertAlmostEqual(calc.evaluator(variables, functions, "T", cs=True), 298, delta=0.2) + def test_variable_case_sensitivity(self): + """ Test the case sensitivity flag and corresponding behavior """ + self.assertEqual( + calc.evaluator({'R1': 2.0, 'R3': 4.0}, {}, "r1*r3"), + 8.0) + + variables = {'t': 1.0} + self.assertEqual(calc.evaluator(variables, {}, "t"), 1.0) + self.assertEqual(calc.evaluator(variables, {}, "T"), 1.0) + self.assertEqual(calc.evaluator(variables, {}, "t", cs=True), 1.0) + # Recall 'T' is a default constant, with value 298.15 + self.assertAlmostEqual(calc.evaluator(variables, {}, "T", cs=True), + 298, delta=0.2) + + def test_simple_funcs(self): + """ Subsitution of custom functions """ + variables = {'x': 4.712} + functions = {'id': lambda x: x} + self.assertEqual(calc.evaluator({}, functions, 'id(2.81)'), 2.81) + self.assertEqual(calc.evaluator({}, functions, 'id(2.81)'), 2.81) + self.assertEqual(calc.evaluator(variables, functions, 'id(x)'), 4.712) + + functions.update({'f': numpy.sin}) + self.assertAlmostEqual(calc.evaluator(variables, functions, 'f(x)'), + -1, delta=1e-3) + + def test_function_case_sensitivity(self): + """ Test the case sensitivity of functions """ + functions = {'f': lambda x: x, + 'F': lambda x: x + 1} + # Which is it? will it call f or F? + # In any case, they should both be the same... + self.assertEqual(calc.evaluator({}, functions, 'f(6)'), + calc.evaluator({}, functions, 'F(6)')) + # except if we want case sensitivity... + self.assertNotEqual(calc.evaluator({}, functions, 'f(6)', cs=True), + calc.evaluator({}, functions, 'F(6)', cs=True)) + + def test_undefined_vars(self): + """ Check to see if the evaluator catches undefined variables """ + variables = {'R1': 2.0, 'R3': 4.0} self.assertRaises(calc.UndefinedVariable, calc.evaluator, {}, {}, "5+7 QWSEKO") - self.assertRaises(calc.UndefinedVariable, calc.evaluator, {'r1': 5}, {}, "r1+r2") - - self.assertEqual(calc.evaluator(variables, functions, "r1*r3"), 8.0) - self.assertRaises(calc.UndefinedVariable, calc.evaluator, - variables, functions, "r1*r3", cs=True) + variables, {}, "r1*r3", cs=True) From 43395b70b7356bf794ed224a86eb716c10c5f27a Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Tue, 21 May 2013 15:20:51 -0400 Subject: [PATCH 03/18] Enable coverage for calc module; split off tests --- common/lib/calc/.coveragerc | 15 +++++++++++++++ common/lib/calc/calc.py | 24 ------------------------ common/lib/calc/tests/test_calc.py | 11 +++++++++-- 3 files changed, 24 insertions(+), 26 deletions(-) create mode 100644 common/lib/calc/.coveragerc diff --git a/common/lib/calc/.coveragerc b/common/lib/calc/.coveragerc new file mode 100644 index 0000000000..352ddf399e --- /dev/null +++ b/common/lib/calc/.coveragerc @@ -0,0 +1,15 @@ +# .coveragerc for common/lib/calc +[run] +data_file = reports/common/lib/calc/.coverage +source = common/lib/calc +branch = true + +[report] +ignore_errors = True + +[html] +title = Calc Python Test Coverage Report +directory = reports/common/lib/calc/cover + +[xml] +output = reports/common/lib/calc/coverage.xml diff --git a/common/lib/calc/calc.py b/common/lib/calc/calc.py index bb1fb97153..2271676f34 100644 --- a/common/lib/calc/calc.py +++ b/common/lib/calc/calc.py @@ -230,27 +230,3 @@ def evaluator(variables, functions, string, cs=False): expr << Optional((plus | minus)) + term + ZeroOrMore((plus | minus) + term) # -5 + 4 - 3 expr = expr.setParseAction(sum_parse_action) return (expr + stringEnd).parseString(string)[0] - -if __name__ == '__main__': - variables = {'R1': 2.0, 'R3': 4.0} - functions = {'sin': numpy.sin, 'cos': numpy.cos} - print "X", evaluator(variables, functions, "10000||sin(7+5)-6k") - print "X", evaluator(variables, functions, "13") - print evaluator({'R1': 2.0, 'R3': 4.0}, {}, "13") - - print evaluator({'e1': 1, 'e2': 1.0, 'R3': 7, 'V0': 5, 'R5': 15, 'I1': 1, 'R4': 6}, {}, "e2") - - print evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5") - print evaluator({}, {}, "-1") - print evaluator({}, {}, "-(7+5)") - print evaluator({}, {}, "-0.33") - print evaluator({}, {}, "-.33") - print evaluator({}, {}, "5+1*j") - print evaluator({}, {}, "j||1") - print evaluator({}, {}, "e^(j*pi)") - print evaluator({}, {}, "fact(5)") - print evaluator({}, {}, "factorial(5)") - try: - print evaluator({}, {}, "5+7 QWSEKO") - except UndefinedVariable: - print "Successfully caught undefined variable" diff --git a/common/lib/calc/tests/test_calc.py b/common/lib/calc/tests/test_calc.py index 2e77aeec6c..41710030ca 100644 --- a/common/lib/calc/tests/test_calc.py +++ b/common/lib/calc/tests/test_calc.py @@ -3,7 +3,7 @@ unittests for calc.py Run like this: - rake test_common/lib/calc ?? + rake test_common/lib/calc """ @@ -82,6 +82,8 @@ class EvaluatorTest(unittest.TestCase): operator, input_str, answer) self.assertEqual(answer, result, msg=fail_msg) + def test_raises_zero_division_err(self): + ''' Ensure division by zero gives an error ''' self.assertRaises(ZeroDivisionError, calc.evaluator, {}, {}, '1/0') @@ -98,6 +100,8 @@ class EvaluatorTest(unittest.TestCase): # I don't know why you would want this, but it works. self.assertEqual(calc.evaluator({}, {}, "j||1"), 0.5 + 0.5j) + def test_parallel_resistors_zero_error(self): + ''' Check the behavior of the || operator with 0 ''' self.assertRaises(ZeroDivisionError, calc.evaluator, {}, {}, '0||1') @@ -121,7 +125,7 @@ class EvaluatorTest(unittest.TestCase): angles = ['-pi/4', '0', 'pi/6', 'pi/5', '5*pi/4', '9*pi/4', '1 + j'] sin_values = [-0.707, 0, 0.5, 0.588, -0.707, 0.707, 1.298 + 0.635j] cos_values = [0.707, 1, 0.866, 0.809, -0.707, 0.707, 0.834 - 0.989j] - tan_values = [-1, 0, 0.577, 0.727, 1, 1, 0.762j, 0.272 + 1.084j] + tan_values = [-1, 0, 0.577, 0.727, 1, 1, 0.272 + 1.084j] # Cannot test tan(pi/2) b/c pi/2 is a float and not precise... self.assert_function_values('sin', angles, sin_values) @@ -239,6 +243,9 @@ class EvaluatorTest(unittest.TestCase): variables = {'x': 9.72, 'y': 7.91, 'loooooong': 6.4} # Should not change value of constant + # even with different numbers of variables... + self.assertEqual(calc.evaluator({'x': 9.72}, {}, '13'), 13) + self.assertEqual(calc.evaluator({'x': 9.72, 'y': 7.91}, {}, '13'), 13) self.assertEqual(calc.evaluator(variables, {}, '13'), 13) # Easy evaluation From db261dc1c32d4c4e5408c81d89eef9eb37d02dea Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Tue, 21 May 2013 15:20:51 -0400 Subject: [PATCH 04/18] Add docstrings; test divsion by zero at Response level --- .../lib/capa/capa/tests/test_responsetypes.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 8bf6954139..25fef0cd3f 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -349,10 +349,13 @@ class OptionResponseTest(ResponseTest): class FormulaResponseTest(ResponseTest): + """ Test the FormulaResponse class """ from response_xml_factory import FormulaResponseXMLFactory xml_factory_class = FormulaResponseXMLFactory def test_grade(self): + """ Test basic functionality of FormulaResponse + Specifically, if it can understand equivalence of formulae""" # Sample variables x and y in the range [-10, 10] sample_dict = {'x': (-10, 10), 'y': (-10, 10)} @@ -373,6 +376,7 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, input_formula, "incorrect") def test_hint(self): + """ Test the hint-giving functionality of FormulaResponse""" # Sample variables x and y in the range [-10, 10] sample_dict = {'x': (-10, 10), 'y': (-10, 10)} @@ -401,6 +405,8 @@ class FormulaResponseTest(ResponseTest): 'Try including the variable x') def test_script(self): + """ Test if python script can be used to generate answers""" + # Calculate the answer using a script script = "calculated_ans = 'x+x'" @@ -496,8 +502,8 @@ class FormulaResponseTest(ResponseTest): msg="Failed on function {0}; the given, incorrect answer was {1} but graded 'correct'".format(func, incorrect)) def test_grade_infinity(self): - # This resolves a bug where a problem with relative tolerance would - # pass with any arbitrarily large student answer. + """This resolves a bug where a problem with relative tolerance would + pass with any arbitrarily large student answer""" sample_dict = {'x': (1, 2)} @@ -514,8 +520,8 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, input_formula, "incorrect") def test_grade_nan(self): - # Attempt to produce a value which causes the student's answer to be - # evaluated to nan. See if this is resolved correctly. + """Attempt to produce a value which causes the student's answer to be + evaluated to nan. See if this is resolved correctly.""" sample_dict = {'x': (1, 2)} @@ -532,6 +538,15 @@ class FormulaResponseTest(ResponseTest): input_formula = "x + 0*1e999" self.assert_grade(problem, input_formula, "incorrect") + def test_raises_zero_division_err(self): + """See if division by zero is handled correctly""" + sample_dict = {'x': (1, 2)} + problem = self.build_problem(sample_dict=sample_dict, + num_samples=10, + tolerance="1%", + answer="x") # Answer doesn't matter + input_dict = {'1_2_1': '1/0'} + self.assertRaises(StudentInputError, problem.grade_answers, input_dict) class StringResponseTest(ResponseTest): from response_xml_factory import StringResponseXMLFactory @@ -898,6 +913,13 @@ class NumericalResponseTest(ResponseTest): incorrect_responses = ["", "3.9", "4.1", "0", "5.01e1"] self.assert_multiple_grade(problem, correct_responses, incorrect_responses) + def test_raises_zero_division_err(self): + """See if division by zero is handled correctly""" + problem = self.build_problem(question_text="What 5 * 10?", + explanation="The answer is 50", + answer="5e+1") # Answer doesn't matter + input_dict = {'1_2_1': '1/0'} + self.assertRaises(StudentInputError, problem.grade_answers, input_dict) class CustomResponseTest(ResponseTest): from response_xml_factory import CustomResponseXMLFactory From 70898a06daecce393590cde93dae4e03092dcc59 Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Thu, 23 May 2013 11:09:23 -0400 Subject: [PATCH 05/18] Pep8 fix --- common/lib/capa/capa/tests/test_responsetypes.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 25fef0cd3f..18ca9b3cf5 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -223,7 +223,6 @@ 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): problem = self.build_problem(math_display=True, expect="[[cos(theta),i*sin(theta)],[i*sin(theta),cos(theta)]]", @@ -241,7 +240,7 @@ class SymbolicResponseTest(ResponseTest): # Correct answer with mock.patch.object(requests, 'post') as mock_post: - # Simulate what the LaTeX-to-MathML server would + # Simulate what the LaTeX-to-MathML server would # send for the correct response input mock_post.return_value.text = correct_snuggletex_response @@ -323,7 +322,7 @@ class SymbolicResponseTest(ResponseTest): dynamath_input, expected_correctness): input_dict = {'1_2_1': str(student_input), - '1_2_1_dynamath': str(dynamath_input) } + '1_2_1_dynamath': str(dynamath_input)} correct_map = problem.grade_answers(input_dict) @@ -548,6 +547,7 @@ class FormulaResponseTest(ResponseTest): input_dict = {'1_2_1': '1/0'} self.assertRaises(StudentInputError, problem.grade_answers, input_dict) + class StringResponseTest(ResponseTest): from response_xml_factory import StringResponseXMLFactory xml_factory_class = StringResponseXMLFactory @@ -607,7 +607,7 @@ class StringResponseTest(ResponseTest): problem = self.build_problem( answer="Michigan", hintfn="gimme_a_hint", - script = textwrap.dedent(""" + script=textwrap.dedent(""" def gimme_a_hint(answer_ids, student_answers, new_cmap, old_cmap): aid = answer_ids[0] answer = student_answers[aid] @@ -917,10 +917,11 @@ class NumericalResponseTest(ResponseTest): """See if division by zero is handled correctly""" problem = self.build_problem(question_text="What 5 * 10?", explanation="The answer is 50", - answer="5e+1") # Answer doesn't matter + answer="5e+1") # Answer doesn't matter input_dict = {'1_2_1': '1/0'} self.assertRaises(StudentInputError, problem.grade_answers, input_dict) + class CustomResponseTest(ResponseTest): from response_xml_factory import CustomResponseXMLFactory xml_factory_class = CustomResponseXMLFactory @@ -969,8 +970,8 @@ class CustomResponseTest(ResponseTest): # # 'answer_given' is the answer the student gave (if there is just one input) # or an ordered list of answers (if there are multiple inputs) - # - # The function should return a dict of the form + # + # The function should return a dict of the form # { 'ok': BOOL, 'msg': STRING } # script = textwrap.dedent(""" From 2486f7c2719e91745f121c6b509b5f8155d7ace4 Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Thu, 23 May 2013 17:29:14 -0400 Subject: [PATCH 06/18] Fixed docstrings and other comments from PR review --- common/lib/calc/tests/test_calc.py | 138 +++++++++++------- .../lib/capa/capa/tests/test_responsetypes.py | 58 +++++--- doc/testing.md | 5 + 3 files changed, 130 insertions(+), 71 deletions(-) diff --git a/common/lib/calc/tests/test_calc.py b/common/lib/calc/tests/test_calc.py index 41710030ca..cd28801d83 100644 --- a/common/lib/calc/tests/test_calc.py +++ b/common/lib/calc/tests/test_calc.py @@ -1,10 +1,5 @@ """ -unittests for calc.py - -Run like this: - - rake test_common/lib/calc - +Unit tests for calc.py """ import unittest @@ -13,16 +8,19 @@ import calc class EvaluatorTest(unittest.TestCase): - ''' Run tests for calc.evaluator + """ + Run tests for calc.evaluator Go through all functionalities as specifically as possible-- work from number input to functions and complex expressions Also test custom variable substitutions (i.e. `evaluator({'x':3.0},{}, '3*x')` gives 9.0) and more. - ''' + """ def test_number_input(self): - ''' Test different kinds of float inputs ''' + """ + Test different kinds of float inputs + """ easy_eval = lambda x: calc.evaluator({}, {}, x) self.assertEqual(easy_eval("13"), 13) @@ -32,11 +30,12 @@ class EvaluatorTest(unittest.TestCase): self.assertEqual(easy_eval("-13"), -13) self.assertEqual(easy_eval("-3.14"), -3.14) self.assertEqual(easy_eval("-.618033989"), -0.618033989) - # see also test_exponential_answer - # and test_si_suffix + # See also test_exponential_answer and test_si_suffix def test_exponential_answer(self): - ''' Test for correct interpretation of scientific notation''' + """ + Test for correct interpretation of scientific notation + """ answer = 50 correct_responses = ["50", "50.0", "5e1", "5e+1", "50e0", "50.0e0", "500e-1"] @@ -55,8 +54,11 @@ class EvaluatorTest(unittest.TestCase): self.assertNotEqual(answer, result, msg=fail_msg) def test_si_suffix(self): - ''' Test calc.py's unique functionality of interpreting si 'suffixes'. - For instance 'k' stand for 'kilo-' so '1k' should be 1,000 ''' + """ + Test calc.py's unique functionality of interpreting si 'suffixes'. + + For instance 'k' stand for 'kilo-' so '1k' should be 1,000 + """ test_mapping = [('4.2%', 0.042), ('2.25k', 2250), ('8.3M', 8300000), ('9.9G', 9.9e9), ('1.2T', 1.2e12), ('7.4c', 0.074), ('5.4m', 0.0054), ('8.7u', 0.0000087), @@ -70,7 +72,9 @@ class EvaluatorTest(unittest.TestCase): delta=tolerance, msg=fail_msg) def test_operator_sanity(self): - ''' Test for simple things like "5+2" and "5/2"''' + """ + Test for simple things like '5+2' and '5/2' + """ var1 = 5.0 var2 = 2.0 operators = [('+', 7), ('-', 3), ('*', 10), ('/', 2.5), ('^', 25)] @@ -83,33 +87,41 @@ class EvaluatorTest(unittest.TestCase): self.assertEqual(answer, result, msg=fail_msg) def test_raises_zero_division_err(self): - ''' Ensure division by zero gives an error ''' + """ + Ensure division by zero gives an error + """ self.assertRaises(ZeroDivisionError, calc.evaluator, {}, {}, '1/0') def test_parallel_resistors(self): - ''' Test the parallel resistor operator || + """ + Test the parallel resistor operator || + The formula is given by a || b || c ... = 1 / (1/a + 1/b + 1/c + ...) It is the resistance of a parallel circuit of resistors with resistance - a, b, c, etc&. See if this evaulates correctly.''' + a, b, c, etc&. See if this evaulates correctly. + """ self.assertEqual(calc.evaluator({}, {}, '1||1'), 0.5) self.assertEqual(calc.evaluator({}, {}, '1||1||2'), 0.4) - - # I don't know why you would want this, but it works. self.assertEqual(calc.evaluator({}, {}, "j||1"), 0.5 + 0.5j) def test_parallel_resistors_zero_error(self): - ''' Check the behavior of the || operator with 0 ''' + """ + Check the behavior of the || operator with 0 + """ self.assertRaises(ZeroDivisionError, calc.evaluator, {}, {}, '0||1') def assert_function_values(self, fname, ins, outs, tolerance=1e-3): - ''' Helper function to test many values at once + """ + Helper function to test many values at once + Test the accuracy of evaluator's use of the function given by fname Specifically, the equality of `fname(ins[i])` against outs[i]. - Used later to test a whole bunch of f(x) = y at a time ''' + This is used later to test a whole bunch of f(x) = y at a time + """ for (arg, val) in zip(ins, outs): input_str = "{0}({1})".format(fname, arg) @@ -119,8 +131,11 @@ class EvaluatorTest(unittest.TestCase): self.assertAlmostEqual(val, result, delta=tolerance, msg=fail_msg) def test_trig_functions(self): - """Test the trig functions provided in common/lib/calc/calc.py - which are: sin, cos, tan, arccos, arcsin, arctan""" + """ + Test the trig functions provided in calc.py + + which are: sin, cos, tan, arccos, arcsin, arctan + """ angles = ['-pi/4', '0', 'pi/6', 'pi/5', '5*pi/4', '9*pi/4', '1 + j'] sin_values = [-0.707, 0, 0.5, 0.588, -0.707, 0.707, 1.298 + 0.635j] @@ -132,40 +147,43 @@ class EvaluatorTest(unittest.TestCase): self.assert_function_values('cos', angles, cos_values) self.assert_function_values('tan', angles, tan_values) - # include those where the real part is between -pi/2 and pi/2 + # Include those where the real part is between -pi/2 and pi/2 arcsin_inputs = ['-0.707', '0', '0.5', '0.588', '1.298 + 0.635*j'] arcsin_angles = [-0.785, 0, 0.524, 0.629, 1 + 1j] self.assert_function_values('arcsin', arcsin_inputs, arcsin_angles) - # rather than throwing an exception, numpy.arcsin gives nan + # Rather than throwing an exception, numpy.arcsin gives nan # self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arcsin(-1.1)'))) # self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arcsin(1.1)'))) - # disabled for now because they are giving a runtime warning... :-/ + # Disabled for now because they are giving a runtime warning... :-/ - # include those where the real part is between 0 and pi + # Include those where the real part is between 0 and pi arccos_inputs = ['1', '0.866', '0.809', '0.834-0.989*j'] arccos_angles = [0, 0.524, 0.628, 1 + 1j] self.assert_function_values('arccos', arccos_inputs, arccos_angles) # self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arccos(-1.1)'))) # self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arccos(1.1)'))) - # has the same range as arcsin + # Has the same range as arcsin arctan_inputs = ['-1', '0', '0.577', '0.727', '0.272 + 1.084*j'] arctan_angles = arcsin_angles self.assert_function_values('arctan', arctan_inputs, arctan_angles) def test_other_functions(self): - """Test the non-trig functions provided in common/lib/calc/calc.py + """ + Test the non-trig functions provided in calc.py + Specifically: sqrt, log10, log2, ln, abs, - fact, factorial""" + fact, factorial + """ - # test sqrt + # Test sqrt self.assert_function_values('sqrt', [0, 1, 2, 1024], # -1 [0, 1, 1.414, 32]) # 1j # sqrt(-1) is NAN not j (!!). - # test logs + # Test logs self.assert_function_values('log10', [0.1, 1, 3.162, 1000000, '1+j'], [-1, 0, 0.5, 6, 0.151 + 0.341j]) @@ -176,10 +194,10 @@ class EvaluatorTest(unittest.TestCase): [0.368, 1, 1.649, 2.718, 42, '1+j'], [-1, 0, 0.5, 1, 3.738, 0.347 + 0.785j]) - # test abs + # Test abs self.assert_function_values('abs', [-1, 0, 1, 'j'], [1, 0, 1, 1]) - # test factorial + # Test factorial fact_inputs = [0, 1, 3, 7] fact_values = [1, 1, 6, 5040] self.assert_function_values('fact', fact_inputs, fact_values) @@ -191,10 +209,13 @@ class EvaluatorTest(unittest.TestCase): self.assertRaises(ValueError, calc.evaluator, {}, {}, "factorial(0.5)") def test_constants(self): - """Test the default constants provided in common/lib/calc/calc.py""" - # which are: j (complex number), e, pi, k, c, T, q + """ + Test the default constants provided in calc.py - # ('expr', python value, tolerance (or None for exact)) + which are: j (complex number), e, pi, k, c, T, q + """ + + # Of the form ('expr', python value, tolerance (or None for exact)) default_variables = [('j', 1j, None), ('e', 2.7183, 1e-3), ('pi', 3.1416, 1e-3), @@ -202,9 +223,9 @@ class EvaluatorTest(unittest.TestCase): ('c', 2.998e8, 1e5), # 0 deg C = T Kelvin ('T', 298.15, 0.01), - # note k = scipy.constants.k = 1.3806488e-23 + # Note k = scipy.constants.k = 1.3806488e-23 ('k', 1.3806488e-23, 1e-26), - # note q = scipy.constants.e = 1.602176565e-19 + # Note q = scipy.constants.e = 1.602176565e-19 ('q', 1.602176565e-19, 1e-22)] for (variable, value, tolerance) in default_variables: fail_msg = "Failed on constant '{0}', not within bounds".format( @@ -214,10 +235,12 @@ class EvaluatorTest(unittest.TestCase): self.assertEqual(value, result, msg=fail_msg) else: self.assertAlmostEqual(value, result, - delta=tolerance, msg=fail_msg) + delta=tolerance, msg=fail_msg) def test_complex_expression(self): - """ Calculate combinations of operators and default functions """ + """ + Calculate combinations of operators and default functions + """ self.assertAlmostEqual( calc.evaluator({}, {}, "(2^2+1.0)/sqrt(5e0)*5-1"), @@ -239,7 +262,9 @@ class EvaluatorTest(unittest.TestCase): -1, delta=1e-5) def test_simple_vars(self): - """ Substitution of variables into simple equations """ + """ + Substitution of variables into simple equations + """ variables = {'x': 9.72, 'y': 7.91, 'loooooong': 6.4} # Should not change value of constant @@ -259,18 +284,19 @@ class EvaluatorTest(unittest.TestCase): self.assertAlmostEqual(calc.evaluator(variables, {}, 'x*y'), 76.89, delta=0.01) - # more, I guess self.assertEqual(calc.evaluator({'x': 9.72, 'y': 7.91}, {}, "13"), 13) self.assertEqual(calc.evaluator(variables, {}, "13"), 13) self.assertEqual( calc.evaluator({ 'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, - {}, "5"), + {}, "5"), 5) def test_variable_case_sensitivity(self): - """ Test the case sensitivity flag and corresponding behavior """ + """ + Test the case sensitivity flag and corresponding behavior + """ self.assertEqual( calc.evaluator({'R1': 2.0, 'R3': 4.0}, {}, "r1*r3"), 8.0) @@ -284,7 +310,9 @@ class EvaluatorTest(unittest.TestCase): 298, delta=0.2) def test_simple_funcs(self): - """ Subsitution of custom functions """ + """ + Subsitution of custom functions + """ variables = {'x': 4.712} functions = {'id': lambda x: x} self.assertEqual(calc.evaluator({}, functions, 'id(2.81)'), 2.81) @@ -296,19 +324,23 @@ class EvaluatorTest(unittest.TestCase): -1, delta=1e-3) def test_function_case_sensitivity(self): - """ Test the case sensitivity of functions """ + """ + Test the case sensitivity of functions + """ functions = {'f': lambda x: x, 'F': lambda x: x + 1} - # Which is it? will it call f or F? - # In any case, they should both be the same... + # Test case insensitive evaluation + # Both evaulations should call the same function self.assertEqual(calc.evaluator({}, functions, 'f(6)'), calc.evaluator({}, functions, 'F(6)')) - # except if we want case sensitivity... + # Test case sensitive evaluation self.assertNotEqual(calc.evaluator({}, functions, 'f(6)', cs=True), calc.evaluator({}, functions, 'F(6)', cs=True)) def test_undefined_vars(self): - """ Check to see if the evaluator catches undefined variables """ + """ + Check to see if the evaluator catches undefined variables + """ variables = {'R1': 2.0, 'R3': 4.0} self.assertRaises(calc.UndefinedVariable, calc.evaluator, diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 18ca9b3cf5..77cd547e55 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -190,7 +190,7 @@ class SymbolicResponseTest(ResponseTest): def test_grade_single_input(self): problem = self.build_problem(math_display=True, - expect="2*x+3*y") + expect="2*x+3*y") # Correct answers correct_inputs = [ @@ -348,13 +348,18 @@ class OptionResponseTest(ResponseTest): class FormulaResponseTest(ResponseTest): - """ Test the FormulaResponse class """ + """ + Test the FormulaResponse class + """ from response_xml_factory import FormulaResponseXMLFactory xml_factory_class = FormulaResponseXMLFactory def test_grade(self): - """ Test basic functionality of FormulaResponse - Specifically, if it can understand equivalence of formulae""" + """ + Test basic functionality of FormulaResponse + + Specifically, if it can understand equivalence of formulae + """ # Sample variables x and y in the range [-10, 10] sample_dict = {'x': (-10, 10), 'y': (-10, 10)} @@ -375,7 +380,9 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, input_formula, "incorrect") def test_hint(self): - """ Test the hint-giving functionality of FormulaResponse""" + """ + Test the hint-giving functionality of FormulaResponse + """ # Sample variables x and y in the range [-10, 10] sample_dict = {'x': (-10, 10), 'y': (-10, 10)} @@ -404,7 +411,9 @@ class FormulaResponseTest(ResponseTest): 'Try including the variable x') def test_script(self): - """ Test if python script can be used to generate answers""" + """ + Test if python script can be used to generate answers + """ # Calculate the answer using a script script = "calculated_ans = 'x+x'" @@ -424,7 +433,9 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, '3*x', 'incorrect') def test_parallel_resistors(self): - """Test parallel resistors""" + """ + Test parallel resistors + """ sample_dict = {'R1': (10, 10), 'R2': (2, 2), 'R3': (5, 5), 'R4': (1, 1)} # Test problem @@ -445,8 +456,11 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, input_formula, "incorrect") def test_default_variables(self): - """Test the default variables provided in common/lib/capa/capa/calc.py""" - # which are: j (complex number), e, pi, k, c, T, q + """ + Test the default variables provided in calc.py + + which are: j (complex number), e, pi, k, c, T, q + """ # Sample x in the range [-10,10] sample_dict = {'x': (-10, 10)} @@ -469,11 +483,14 @@ class FormulaResponseTest(ResponseTest): msg="Failed on variable {0}; the given, incorrect answer was {1} but graded 'correct'".format(var, incorrect)) def test_default_functions(self): - """Test the default functions provided in common/lib/capa/capa/calc.py""" - # which are: sin, cos, tan, sqrt, log10, log2, ln, - # arccos, arcsin, arctan, abs, - # fact, factorial + """ + Test the default functions provided in common/lib/capa/capa/calc.py + which are: + sin, cos, tan, sqrt, log10, log2, ln, + arccos, arcsin, arctan, abs, + fact, factorial + """ w = random.randint(3, 10) sample_dict = {'x': (-10, 10), # Sample x in the range [-10,10] 'y': (1, 10), # Sample y in the range [1,10] - logs, arccos need positive inputs @@ -501,8 +518,10 @@ class FormulaResponseTest(ResponseTest): msg="Failed on function {0}; the given, incorrect answer was {1} but graded 'correct'".format(func, incorrect)) def test_grade_infinity(self): - """This resolves a bug where a problem with relative tolerance would - pass with any arbitrarily large student answer""" + """ + Test that a large input on a problem with relative tolerance isn't + erroneously marked as correct. + """ sample_dict = {'x': (1, 2)} @@ -519,8 +538,9 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, input_formula, "incorrect") def test_grade_nan(self): - """Attempt to produce a value which causes the student's answer to be - evaluated to nan. See if this is resolved correctly.""" + """ + Test that expressions that evaluate to NaN are not marked as correct. + """ sample_dict = {'x': (1, 2)} @@ -538,7 +558,9 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, input_formula, "incorrect") def test_raises_zero_division_err(self): - """See if division by zero is handled correctly""" + """ + See if division by zero raises an error. + """ sample_dict = {'x': (1, 2)} problem = self.build_problem(sample_dict=sample_dict, num_samples=10, diff --git a/doc/testing.md b/doc/testing.md index d6c7b7ee86..8b80177798 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -115,6 +115,11 @@ xmodule can be tested independently, with this: rake test_common/lib/xmodule +other module level tests include + +* `rake test_common/lib/capa` +* `rake test_common/lib/calc` + To run a single django test class: django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/courseware/tests/tests.py:TestViewAuth From 2793bb43373ff860556854694a17412c8f020e26 Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Thu, 23 May 2013 17:23:17 -0400 Subject: [PATCH 07/18] Add few more tests; fix a small bug with parallel resistors --- common/lib/calc/calc.py | 2 ++ common/lib/calc/tests/test_calc.py | 11 ++++++++--- common/lib/capa/capa/tests/test_responsetypes.py | 1 - 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/lib/calc/calc.py b/common/lib/calc/calc.py index 2271676f34..2f33b66bfd 100644 --- a/common/lib/calc/calc.py +++ b/common/lib/calc/calc.py @@ -144,6 +144,8 @@ def evaluator(variables, functions, string, cs=False): return x def parallel(x): # Parallel resistors [ 1 2 ] => 2/3 + # convert from pyparsing.ParseResults, which doesn't support '0 in x' + x = list(x) if len(x) == 1: return x[0] if 0 in x: diff --git a/common/lib/calc/tests/test_calc.py b/common/lib/calc/tests/test_calc.py index cd28801d83..58d0860af6 100644 --- a/common/lib/calc/tests/test_calc.py +++ b/common/lib/calc/tests/test_calc.py @@ -92,6 +92,10 @@ class EvaluatorTest(unittest.TestCase): """ self.assertRaises(ZeroDivisionError, calc.evaluator, {}, {}, '1/0') + self.assertRaises(ZeroDivisionError, calc.evaluator, + {}, {}, '1/0.0') + self.assertRaises(ZeroDivisionError, calc.evaluator, + {'x': 0.0}, {}, '1/x') def test_parallel_resistors(self): """ @@ -107,12 +111,13 @@ class EvaluatorTest(unittest.TestCase): self.assertEqual(calc.evaluator({}, {}, '1||1||2'), 0.4) self.assertEqual(calc.evaluator({}, {}, "j||1"), 0.5 + 0.5j) - def test_parallel_resistors_zero_error(self): + def test_parallel_resistors_with_zero(self): """ Check the behavior of the || operator with 0 """ - self.assertRaises(ZeroDivisionError, calc.evaluator, - {}, {}, '0||1') + self.assertTrue(numpy.isnan(calc.evaluator({}, {}, '0||1'))) + self.assertTrue(numpy.isnan(calc.evaluator({}, {}, '0.0||1'))) + self.assertTrue(numpy.isnan(calc.evaluator({'x': 0.0}, {}, 'x||1'))) def assert_function_values(self, fname, ins, outs, tolerance=1e-3): """ diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 77cd547e55..780c475b09 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -10,7 +10,6 @@ import random import unittest import textwrap import mock -import textwrap from . import new_loncapa_problem, test_system From 4c97d16edc6aca1cc34ac08f880273e0f0b4a382 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Tue, 28 May 2013 23:40:16 -0400 Subject: [PATCH 08/18] add hack to assets.rake to unblock the release train --- rakefiles/assets.rake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rakefiles/assets.rake b/rakefiles/assets.rake index c0757b712b..68127a317f 100644 --- a/rakefiles/assets.rake +++ b/rakefiles/assets.rake @@ -10,11 +10,14 @@ end # the ENV_TOKENS to the templating context. def preprocess_with_mako(filename) # simple command-line invocation of Mako engine + # cdodge: the .gsub() are used to translate true->True and false->False to make the generated + # python actually valid python. This is just a short term hack to unblock the release train + # until a real fix can be made by people who know this better mako = "from mako.template import Template;" + "print Template(filename=\"#{filename}\")" + # Total hack. It works because a Python dict literal has # the same format as a JSON object. - ".render(env=#{ENV_TOKENS.to_json});" + ".render(env=#{ENV_TOKENS.to_json.gsub("true","True").gsub("false","False")});" # strip off the .mako extension output_filename = filename.chomp(File.extname(filename)) From 879f7d1051c4583c52909b59622216deff296e1e Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Wed, 29 May 2013 12:11:25 -0400 Subject: [PATCH 09/18] fix Edge login to use the updated POST-back URL /login -> /login_ajax --- lms/templates/university_profile/edge.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/university_profile/edge.html b/lms/templates/university_profile/edge.html index a3e115ddd8..9e6adfe3d8 100644 --- a/lms/templates/university_profile/edge.html +++ b/lms/templates/university_profile/edge.html @@ -9,7 +9,7 @@