/** Test Exprs. * See http://www.radford.edu/itec380/2008fall/Hw06/hw06.html * * Meant to cover most syntax situations for L0-L3. * HOWEVER, the testing approach has some systemic flaws -- * we never test against the expected internal representation. * (For example, testParseToString just checks that parse and * 'toString' are inverses of each other; if they are both * the identity function, it would pass all tests.) * * Compiling this function will generate a warning * ('unchecked generic array creation', due to using Arrays.asList * and varargs). * * @author Ian Barland * @version 2008.Dec.06 */ public class ExprTest extends junit.framework.TestCase { static final double TOLERANCE = 0.000001; java.util.List> allTests; /* allTests should be a list of pairs: * an L0 Expr, and what that expression evaluates to. * Use a Double, for Num values. */ /** * Sets up the test fixture. * * Called before every test case method. */ protected void setUp() { /* allTests should be a list of pairs: * an L0 Expr, and what that expression evaluates to. * Use a Double, for Num values. */ allTests = java.util.Arrays.asList( /* M0 tests: */ new Pair( "7", 7 ) ,new Pair( "7.0", 7 ) ,new Pair( "(3 + 4)", 7 ) ,new Pair( "(3 * 4)", 12 ) ,new Pair( "((3 + 4) + (3 * 4))", 19) ,new Pair( "ifZero( 0, 1, 2)", 1 ) ,new Pair( "ifZero( 1, 1, 2)", 2 ) ,new Pair( "ifZero( (3+-3), 1, 2)", 1 ) ,new Pair( "ifZero( (ifZero(ifZero(0,1,2), 3, 4) + -3), 1, 2)", 2 ) /**** M1 tests: ****/ /**** YOU NEED TO UPDATE THESE TO THE M1 SYNTAX (infix, not prefix): ,new Pair( "{mod 3.0 4.0}", 3) ,new Pair( "{mod {plus 5.0 6.0} 3.0}", 2) ,new Pair( "{mod 8.1 3.0}", 2.1) ,new Pair( "{mod 8.0 3.1}", 1.8) ,new Pair( "{mod -8.1 3.0}", 0.9) ,new Pair( "{mod -8.0 3.1}", 1.3) ,new Pair( "{mod 8.1 -3}", -0.9) ,new Pair( "{mod 8.0 -3.1}", -1.3) ,new Pair( "{mod -8.1 -3.0}", -2.1) ,new Pair( "{mod -8.0 -3.1}", -1.8) ,new Pair( "{mod 8.0 2.0}", 0) ,new Pair( "{mod -8.0 2.0}", 0) ,new Pair( "{mod 8.0 -2.0}", 0) ,new Pair( "{mod -8.0 -2.0}", 0) ,new Pair( "{mod 8.0 3.0}", 2) ,new Pair( "{mod -8.0 3.0}", 1) ,new Pair( "{mod 8.0 -3.0}", -1) ,new Pair( "{mod -8.0 -3.0}", -2) ,new Pair( "{ifNeg 0.0 1.0 2.0}", 2) ,new Pair( "{ifNeg -2.0 -3.0 -4.0}", -3) ,new Pair( "{ifNeg {mod 8.0 -2.0} 3.0 {plus 3.0 {times 4.0 5.0}}}", 23) ,new Pair( "{ifNeg {mod 9.0 -2.0} 3.0 {plus 3.0 {times 4.0 5.0}}}", 3) // ****/ /**** M2 tests: ****/ /* ,new Pair( "let y be (3) in (7)", 7 ) ,new Pair( "let y be (3) in (y)", 3 ) ,new Pair( "let y be (3) in ((y + y))", 6) ,new Pair( "let x be ((2 + 3)) in (x)", 5 ) ,new Pair( "let x be (5) in ((4 + 3))", 7 ) ,new Pair( "let x be (5) in ((x + 3))", 8 ) // Two variables, not shadowing: ,new Pair( "let y be (3) in (let x be (5) in ((x + y)))", 8) ,new Pair( "let y be (3) in (let x be (y) in ((x+y)))", 6 ) // Two variables, shadowing in the body: ,new Pair( "let x be (5) in (let x be (3) in (x))", 3) ,new Pair( "let x be (5) in (let x be (x) in (x))", 5) ,new Pair( "let x be (5) in (let x be ((x + 1)) in (x))", 6) ,new Pair( "let x be (5) in (let x be ((x + 1)) in (x + 2))", 8) ,new Pair( "let x be (5) in (let y be (3) in ((let x be (y) in ((x + y)) + x)))", 11) ,new Pair( "let y be (let z be (4) in (let y be (99) in (z))) in (let z be (5) in ((let z be (10) in (y) + (y + z))))", 13 ) // Check that we don't substitute Expr0: ,new Pair( "let x be (let x be (5) in (x)) in ((x + 4))", 9 ) ,new Pair( "let x be (let y be (5) in (y)) in ((x + 4))", 9 ) ,new Pair( "let x be (let x be (5) in ((x + 1))) in ((x + -4))", 2 ) ,new Pair( "let x be (let y be (5) in ((y + 1))) in ((x + -4))", 2 ) ,new Pair( "let x be (let x be (5) in (x)) in (x)", 5 ) ,new Pair( "let x be (let x be (5) in ((x + 1))) in (x)", 6 ) ,new Pair( "let x be (let y be (5) in ((y + 1))) in (x)", 6 ) /* These cases are *not* necessarily comprehensive. * To really understand what your code needs to do, * you'll have to work out some of these by hand, * and/or make your own test cases (make them complicated, * but only complicated enough to do something interesting -- * shadow a variable in one place, or shadow something from 2 levels above, or ... */ /**** M3 tests: ****/ /**** YOU NEED TO UPDATE THESE TO THE M1 SYNTAX (infix, not prefix): ,new Pair( "{fun x 5.0}" , "How to test this?" ) ,new Pair( "{fun x x}" , "How to test this?" ) ,new Pair( "{fun x {plus x 1.0}}" , "How to test this?" ) ,new Pair( "{fun x {ifNeg x {times -1.0 x} x}}" , "How to test this?" ) ,new Pair( "{fun x y}" , "How to test this?" ) ,new Pair( "{fun x {fun y {plus x y}}}", "How to test this?" ) ,new Pair( "{{fun x 5.0} 3.0}", 5 ) ,new Pair( "{{fun x x} 3.0}", 3 ) ,new Pair( "{{fun x {plus x 1.0}} 3.0}", 4 ) ,new Pair( "{{fun x {fun y {plus x y}}} 3.0}", "How to test this?" ) ,new Pair( "{{fun x {ifNeg x {times -1.0 x} x}} -5.0}", 5 ) ,new Pair( "{{fun x {ifNeg x {times -1.0 x} x}} +5.0}", 5 ) ,new Pair( "{with abs {fun x {ifNeg x {times -1.0 x} x}}\n {abs -5.0}}", 5 ) ,new Pair( "{with abs {fun x {ifNeg x {times -1.0 x} x}}\n {abs +5.0}}", 5 ) // Check that, when eval'ing a function-application, // you evaluate the function being applied, since it might be // a fuction-appliation-which-will-return-a-function: ,new Pair( "{{{fun x {fun y {plus x y}}} 3} 4}", 7 ) ****/ ); } /** For every element in allTests, * parse the string, and then call toString on the result, * checking that we get back exactly the input string * (up to whitespace). */ public void testParseToString() { for ( Pair t : allTests ) { String expected = t.getFirst(); String actual = Expr.parse( t.getFirst() ).toString(); if (! UtilIan.equalsIgnoreWhitespace(expected, actual)) { // This assert will fail; just present it to the user: assertEquals( expected, actual ); } } } /** For every element in allTests, * parse the string and eval the result, * checking that we get back the second item in the pair. * If the second item is a number, we check within {@value TOLERANCE}. * If it's not a number (as in L2, once function-values are introduced), * this code doesn't actually test anything, and asserts an error saying so. */ public void testEval() { for ( Pair t : allTests ) { if (Number.class.isInstance( t.getSecond() )) { assertEquals( ((Number)t.getSecond()).doubleValue(), ((Num)(Expr.parse( t.getFirst() ).eval())).doubleValue(), TOLERANCE ); } else { // I guess Java expects us to implement .equals for every single Expr. // Should we do that, or hack some other way of doing our tests? assertEquals( t.getFirst() + " didn't evaluate to a double", "How to express the expected Fun value?" ); } } } /** * Tears down the test fixture. * * Called after every test case method. */ protected void tearDown() { } }