001    package ps6.test;
002    
003    import java.util.*;
004    
005    import ps6.Directions;
006    import ps6.DirectionsFinder;
007    import ps6.InvalidAddressException;
008    import ps6.InvalidDatabaseException;
009    import ps6.NoPathException;
010    
011    import org.junit.Test;
012    import org.junit.runner.RunWith;
013    import org.junit.runners.Parameterized;
014    import org.junit.runners.Parameterized.Parameters;
015    
016    import static org.junit.Assert.assertEquals;
017    import static org.junit.Assert.fail;
018    
019    @RunWith(Parameterized.class)
020    public class PublicProgADTTest {
021    
022        /* TEST INSTANCE FIELDS */
023        private final TestRecord test;
024        private final ps2.WalkingRouteFormatter walkingFormatter;
025        private final ps2.DrivingRouteFormatter drivingFormatter;
026    
027        /* STATIC FIELDS */
028        public static final String INPUT_PROMPTS =
029            "starting number? starting street? starting zipcode? " +
030            "destination number? destination street? destination zipcode? walking or driving [w/d]? ";
031    
032        public static final String END_PROMPT = "starting number? ";
033        public static String EOL = System.getProperty("line.separator");
034    
035        //this could instead be a different set, e.g. ValidateQueries.badQueries
036        private static final TestRecord[] QUERIES_TO_TEST = ValidateQueries.allQueries;
037    
038        /** Threshold for comparing double values */
039        public static final double THRESHOLD = 0.001;
040    
041        // Store the DirectionsDFinder in a static field, so that we only
042        // make one DirectionsFinder per database no matter how many tests we
043        // instantiate.
044        private static final Map<TestRecord.TestDB,DirectionsFinder> dfs = new HashMap<TestRecord.TestDB,DirectionsFinder>();
045        private static final Set<TestRecord.TestDB> dfsLoadFailed = new HashSet<TestRecord.TestDB>();
046    
047        /* TEST INSTANCE METHODS */
048        public PublicProgADTTest(TestRecord test) {
049            this.test = test;
050            this.walkingFormatter = new ps2.WalkingRouteFormatter();
051            this.drivingFormatter = new ps2.DrivingRouteFormatter();
052        }
053    
054        /**
055         * Runs the test against getDirections(Address x 2, RouteDirections)
056         **/
057        @Test
058        public void ProgTestCase() {
059            loadDatabase(test.getDb());
060    
061            String usefulName = "query from '" + test.getStart() + "' to '" + test.getEnd() + "'";
062            switch(test.getType()) {
063            case DRIVING:
064                runProgADTDirectionsDriving(usefulName);
065                break;
066            case WALKING:
067                runProgADTDirectionsWalking(usefulName);
068                break;
069            case INVALID_ADDRESS:
070                runProgADTBadAddress(usefulName);
071                break;
072            case NO_PATH:
073                runProgADTNoPath(usefulName);
074                break;
075            case INVALID_DIR_TYPE:
076                //Ignore for Programmatic Tests
077                return;
078            default:
079                throw new IllegalStateException("Unknown desired result");
080            }
081        }   
082    
083        private void runProgADTBadAddress(String usefulName) {
084            try {
085                @SuppressWarnings("unused")
086                Directions result = dfs.get(test.getDb()).getDirections(test.getStart(), test.getEnd(), drivingFormatter);
087                fail("Expected InvalidAddressException on " + usefulName);
088            } catch (InvalidAddressException e) {
089                // this is what we want
090                return;
091            } catch (NoPathException e) {
092                fail("Unexpected exception on " + usefulName + ": " + e.toString());
093            }
094        }
095    
096        private void runProgADTNoPath(String usefulName) {
097            try {
098                @SuppressWarnings("unused")
099                Directions result = dfs.get(test.getDb()).getDirections(test.getStart(), test.getEnd(), drivingFormatter);
100                fail("Expected NoPathException on " + usefulName);
101            } catch (InvalidAddressException e) {
102                fail("Unexpected exception on " + usefulName + ": " + e.toString());
103            } catch (NoPathException e) {
104                // this is what we want
105                return;
106            }
107        }
108    
109        private void runProgADTDirectionsDriving(String usefulName) {
110            try {
111                Directions result = dfs.get(test.getDb()).getDirections(test.getStart(), test.getEnd(), drivingFormatter);
112    
113                assertEquals("The start given by getStart() should match the starting point given in the query (" + usefulName + ")",
114                             test.getStart(),
115                             result.getStart());
116    
117                assertEquals("The end given by getEnd() should match the ending point given in the query (" + usefulName + ")",
118                             test.getEnd(),
119                             result.getEnd());
120    
121                assertEquals("The length given by getLength() should match the expected length for " + usefulName,
122                             test.getLength(),
123                             result.getLength(),
124                             THRESHOLD);
125    
126                Iterator<String> queryDirs = Arrays.asList(test.getDirections()).iterator();
127                Iterator<String> resultDirs = result.iterator();
128                assertIteratedEquals("getDirections(Address x 2, RouteDirections) " + usefulName,
129                             queryDirs, resultDirs);
130    
131            } catch (InvalidAddressException e) {
132                fail("Unexpected exception on " + usefulName + ": " + e.toString());
133            } catch (NoPathException e) {
134                fail("Unexpected exception on " + usefulName + ": " + e.toString());
135            }
136        }
137    
138        private void runProgADTDirectionsWalking(String usefulName) {
139            try {
140                Directions result = dfs.get(test.getDb()).getDirections(test.getStart(), test.getEnd(), walkingFormatter);
141    
142                assertEquals("The start given by getStart() should match the starting point given in the query (" + usefulName + ")",
143                             test.getStart(),
144                             result.getStart());
145    
146                assertEquals("The end given by getEnd() should match the ending point given in the query (" + usefulName + ")",
147                             test.getEnd(),
148                             result.getEnd());
149    
150                assertEquals("The length given by getLength() should match the expected length for " + usefulName,
151                             test.getLength(),
152                             result.getLength(),
153                             THRESHOLD);
154    
155                Iterator<String> queryDirs = Arrays.asList(test.getDirections()).iterator();
156                Iterator<String> resultDirs = result.iterator();
157                assertIteratedEquals("getDirections(Address x 2, RouteDirections) " + usefulName,
158                             queryDirs, resultDirs);
159    
160            } catch (InvalidAddressException e) {
161                fail("Unexpected exception on " + usefulName + ": " + e.toString());
162            } catch (NoPathException e) {
163                fail("Unexpected exception on " + usefulName + ": " + e.toString());
164            }
165        }
166    
167    
168        /* CLASS METHODS */
169    
170        /**
171         * Constructs a list of parameters for each instance of PublicProgADTTest.
172         * The test runner Parameterized will instantiate a PublicProgADTTest for every Object[] in the
173         * returned list. Since this call to the constructor is done through reflection, we can (and must)
174         * pass around opaque arrays of objects here.
175         */
176        @Parameters
177        public static List<Object[]> getTestParameters() {
178            List<Object[]> queryTestList = new ArrayList<Object[]>(PublicProgADTTest.QUERIES_TO_TEST.length);
179            for (TestRecord record : PublicProgADTTest.QUERIES_TO_TEST) {
180                queryTestList.add(new Object[]{ record });
181            }
182            return queryTestList;
183        }
184    
185        private static void loadDatabase(TestRecord.TestDB db) {
186            // might be done already...
187            if (dfs.containsKey(db) && dfs.get(db) != null) {
188                return;
189            }
190    
191            // might have failed already ...
192            if (dfsLoadFailed.contains(db)) {
193                fail("A previous attempt at loading the database has already failed");
194            }
195    
196            // otherwise, give it a shot...
197            try {
198                dfs.put(db, DirectionsFinder.getDirectionsFinder(db.dbPath(), null));
199    
200            } catch (InvalidDatabaseException e) {
201            dfsLoadFailed.add(db);
202                fail("Load of tiny database failed with an exception: " + e);
203            }
204        }
205    
206        // (destructively) checks that two iterators return equal elements
207        protected static void assertIteratedEquals(String message, Iterator<?> expected, Iterator<?> actual) {
208            boolean expectedHasNext = expected.hasNext();
209            boolean actualHasNext = actual.hasNext();
210    
211            // if both iterators are exhausted, we're fine
212            if (!expectedHasNext && !actualHasNext) {
213                return;
214            }
215    
216            // if just one is exhausted, we fail
217            if (expectedHasNext && !actualHasNext) {
218                Object expectedNext = expected.next();
219                fail(message + ": expected had '" + expectedNext + "' but actual had no more elements");
220            }
221    
222            // if just one is exhausted, we fail
223            if (!expectedHasNext && actualHasNext) {
224                Object actualNext = actual.next();
225                fail(message + ": actual had '" + actualNext + "' but expected had no more elements");
226            }
227    
228            // assert that the current element pairing is equal
229            Object expectedNext = expected.next();
230            Object actualNext = actual.next();
231            assertEquals(message, expectedNext, actualNext);
232    
233            // check the rest recursively
234            assertIteratedEquals(message, expected, actual);
235        }
236    }