001    package ps6.test;
002    
003    import java.util.Arrays;
004    import java.util.HashMap;
005    import java.util.HashSet;
006    import java.util.Iterator;
007    import java.util.Map;
008    import java.util.Set;
009    
010    import junit.framework.TestCase;
011    import ps6.Directions;
012    import ps6.DirectionsFinder;
013    import ps6.InvalidAddressException;
014    import ps6.InvalidDatabaseException;
015    import ps6.NoPathException;
016    
017    public class PS6ProgTestCase extends TestCase {
018        /** Threshold for comparing double values */
019        public static final double THRESHOLD = 0.001;
020    
021        // Store the DirectionsDFinder in a static field, so that we only
022        // make one DirectionsFinder per database no matter how many tests we
023        // instantiate.
024        private static final Map<TestRecord.TestDB,DirectionsFinder> dfs = new HashMap<TestRecord.TestDB,DirectionsFinder>();
025        private static final Set<TestRecord.TestDB> dfsLoadFailed = new HashSet<TestRecord.TestDB>();
026    
027        private final TestRecord test;
028        private ps2.WalkingRouteFormatter walkingFormatter;
029        private ps2.DrivingRouteFormatter drivingFormatter;
030    
031        /**
032         * @effects creates a new test which runs the given test in the given mode
033         **/
034        public PS6ProgTestCase(ps6.test.TestRecord test) {
035            super(test.getTestName());
036            this.test = test;
037            this.walkingFormatter = new ps2.WalkingRouteFormatter();
038            this.drivingFormatter = new ps2.DrivingRouteFormatter();
039        }
040    
041        private static void loadDatabase(TestRecord.TestDB db) {
042            // might be done already...
043            if (dfs.containsKey(db) && dfs.get(db) != null) {
044                return;
045            }
046    
047            // might have failed already ...
048            if (dfsLoadFailed.contains(db)) {
049                fail("A previous attempt at loading the database has already failed");
050            }
051    
052            // otherwise, give it a shot...
053            try {
054                dfs.put(db, DirectionsFinder.getDirectionsFinder(db.dbPath(), null));
055    
056            } catch (InvalidDatabaseException e) {
057            dfsLoadFailed.add(db);
058                fail("Load of tiny database failed with an exception: " + e);
059            }
060        }
061    
062        /**
063         * Runs the test against getDirections(Address x 2, RouteDirections)
064         **/
065        @Override
066        public void runTest() {
067            loadDatabase(test.getDb());
068    
069            String usefulName = "query from '" + test.getStart() + "' to '" + test.getEnd() + "'";
070            switch(test.getType()) {
071            case DRIVING:
072                runProgADTDirectionsDriving(usefulName);
073                break;
074            case WALKING:
075                runProgADTDirectionsWalking(usefulName);
076                break;
077            case INVALID_ADDRESS:
078                runProgADTBadAddress(usefulName);
079                break;
080            case NO_PATH:
081                runProgADTNoPath(usefulName);
082                break;
083            case INVALID_DIR_TYPE:
084                //Ignore for Programmatic Tests
085                return;
086            default:
087                throw new IllegalStateException("Unknown desired result");
088            }
089        }
090    
091        private void runProgADTBadAddress(String usefulName) {
092            try {
093                @SuppressWarnings("unused")
094                Directions result = dfs.get(test.getDb()).getDirections(test.getStart(), test.getEnd(), drivingFormatter);
095                fail("Expected InvalidAddressException on " + usefulName);
096            } catch (InvalidAddressException e) {
097                // this is what we want
098                return;
099            } catch (NoPathException e) {
100                fail("Unexpected exception on " + usefulName + ": " + e.toString());
101            }
102        }
103    
104        private void runProgADTNoPath(String usefulName) {
105            try {
106                @SuppressWarnings("unused")
107                Directions result = dfs.get(test.getDb()).getDirections(test.getStart(), test.getEnd(), drivingFormatter);
108                fail("Expected NoPathException on " + usefulName);
109            } catch (InvalidAddressException e) {
110                fail("Unexpected exception on " + usefulName + ": " + e.toString());
111            } catch (NoPathException e) {
112                // this is what we want
113                return;
114            }
115        }
116    
117        private void runProgADTDirectionsDriving(String usefulName) {
118            try {
119                Directions result = dfs.get(test.getDb()).getDirections(test.getStart(), test.getEnd(), drivingFormatter);
120    
121                assertEquals("The start given by getStart() should match the starting point given in the query (" + usefulName + ")",
122                             test.getStart(),
123                             result.getStart());
124    
125                assertEquals("The end given by getEnd() should match the ending point given in the query (" + usefulName + ")",
126                             test.getEnd(),
127                             result.getEnd());
128    
129                assertEquals("The length given by getLength() should match the expected length for " + usefulName,
130                             test.getLength(),
131                             result.getLength(),
132                             THRESHOLD);
133    
134                Iterator<String> queryDirs = Arrays.asList(test.getDirections()).iterator();
135                Iterator<String> resultDirs = result.iterator();
136                assertEquals("getDirections(Address x 2, RouteDirections) " + usefulName,
137                             queryDirs, resultDirs);
138    
139            } catch (InvalidAddressException e) {
140                fail("Unexpected exception on " + usefulName + ": " + e.toString());
141            } catch (NoPathException e) {
142                fail("Unexpected exception on " + usefulName + ": " + e.toString());
143            }
144        }
145    
146        private void runProgADTDirectionsWalking(String usefulName) {
147            try {
148                Directions result = dfs.get(test.getDb()).getDirections(test.getStart(), test.getEnd(), walkingFormatter);
149    
150                assertEquals("The start given by getStart() should match the starting point given in the query (" + usefulName + ")",
151                             test.getStart(),
152                             result.getStart());
153    
154                assertEquals("The end given by getEnd() should match the ending point given in the query (" + usefulName + ")",
155                             test.getEnd(),
156                             result.getEnd());
157    
158                assertEquals("The length given by getLength() should match the expected length for " + usefulName,
159                             test.getLength(),
160                             result.getLength(),
161                             THRESHOLD);
162    
163                Iterator<String> queryDirs = Arrays.asList(test.getDirections()).iterator();
164                Iterator<String> resultDirs = result.iterator();
165                assertEquals("getDirections(Address x 2, RouteDirections) " + usefulName,
166                             queryDirs, resultDirs);
167    
168            } catch (InvalidAddressException e) {
169                fail("Unexpected exception on " + usefulName + ": " + e.toString());
170            } catch (NoPathException e) {
171                fail("Unexpected exception on " + usefulName + ": " + e.toString());
172            }
173        }
174    
175        // (destructively) checks that two iterators return equal elements
176        protected static void assertEquals(String message, Iterator<?> expected, Iterator<?> actual) {
177            boolean expectedHasNext = expected.hasNext();
178            boolean actualHasNext = actual.hasNext();
179    
180            // if both iterators are exhausted, we're fine
181            if (!expectedHasNext && !actualHasNext) {
182                return;
183            }
184    
185            // if just one is exhausted, we fail
186            if (expectedHasNext && !actualHasNext) {
187                Object expectedNext = expected.next();
188                fail(message + ": expected had '" + expectedNext + "' but actual had no more elements");
189            }
190    
191            // if just one is exhausted, we fail
192            if (!expectedHasNext && actualHasNext) {
193                Object actualNext = actual.next();
194                fail(message + ": actual had '" + actualNext + "' but expected had no more elements");
195            }
196    
197            // assert that the current element pairing is equal
198            Object expectedNext = expected.next();
199            Object actualNext = actual.next();
200            assertEquals(message,
201                         expectedNext, actualNext);
202    
203            // check the rest recursively
204            assertEquals(message, expected, actual);
205        }
206    }