001    package ps3.test;
002    
003    import static org.junit.Assert.assertEquals;
004    
005    import java.io.*;
006    import java.net.URISyntaxException;
007    import java.util.ArrayList;
008    import java.util.LinkedList;
009    import java.util.List;
010    
011    import org.junit.BeforeClass;
012    import org.junit.Test;
013    import org.junit.runner.RunWith;
014    import utils.test.LabelledParameterized;
015    import utils.test.LabelledParameterized.*;
016    import org.junit.runners.Parameterized.Parameters;
017    
018    import ps3.test.PS3TestDriver;
019    
020    /**
021     * This class, along with a complete PS3TestDriver implementation,
022     * can be used to test the your implementations of Graph and the
023     * path finding algorithm using the script file format described
024     * in the problem set.  It is assumed that the files are
025     * located in the same directory as this class.
026     * 
027     * It works by parameterizing test methods over some data values, and then
028     * creating an instance for the cross-product of test methods and data values.
029     * In this case, it will create one ScriptFileTests instance per .expected file,
030     * and for each of those it will run the checkAgainstExpectedOutput() test.
031     * See the JUnit4 Javadocs for more information, or Google for more examples.
032    */
033    @RunWith(LabelledParameterized.class)
034    public class ScriptFileTests {
035    
036            //static fields and methods used during setup of the parameterized runner
037            private static FileFilter testFileFilter = new FileFilter() {
038            public boolean accept(File file) {
039                return file.getName().endsWith(".test");
040            }
041        };
042        private static List<String> testScriptNames = null; // not yet calculated
043        private static List<File> testScriptFiles = null; // not yet calculated
044        
045        //used by the actual test instance
046            private final File testScriptFile; 
047            
048            /**
049             * This method searches for and creates file handles for each script test.
050             * It only searches the immediate directory where the ScriptFileTests.class
051             * classfile is located. 
052             */
053            public static void calculateTestFiles() {
054                    if (ScriptFileTests.testScriptFiles != null
055                            || ScriptFileTests.testScriptNames != null) {
056                            //already initialized
057                            return;
058                    }
059                    
060                    ScriptFileTests.testScriptNames = new LinkedList<String>();
061                    ScriptFileTests.testScriptFiles = new LinkedList<File>();
062                    try {
063                // getResource() cannot be null: this file itself is ScriptFileTests
064                // getParentFile() cannot be null: ScriptFileTests has a package
065                @SuppressWarnings("nullness")
066                File myDirectory = new File(ScriptFileTests.class.getResource("ScriptFileTests.class").toURI()).getParentFile();
067                for (File f : myDirectory.listFiles(ScriptFileTests.testFileFilter)) {
068                    testScriptNames.add(f.getName());
069                    testScriptFiles.add(f);
070                }
071         
072            } catch (URISyntaxException e) {
073                throw new RuntimeException(e);
074            }       
075            }
076            
077            /**
078             * This method is called in the constructor of Parameterized.
079             * 
080             * @return List of argument arrays that should be invoked on the ScriptFileTests constructor by the
081             * Parameterized test runner. Since that runner's constructor has one parameter, the
082             * array only has one element.
083             */
084            @Parameters
085            public static List<Object[]> getTestFiles() {
086                    ScriptFileTests.calculateTestFiles();
087                    
088                    if (ScriptFileTests.testScriptFiles == null)
089                            throw new IllegalStateException("Did not initialise any files to test!");
090    
091                    //we have to wrap testScriptFiles here so Parameterized.class receives a list of arg array.
092                    List<Object[]> filesToTest = new ArrayList<Object[]>(testScriptFiles.size());
093                    for (File f : ScriptFileTests.testScriptFiles) {
094                            filesToTest.add(new Object[]{ f });
095                    }
096                    
097                    return filesToTest;
098            }
099            
100            /**
101             * This method is called in the constructor of LabelledParameterized. Since
102             * getTestFiles (and thus calculateTestFiles()) should have already been 
103             * called by the Parameterized constructor, the test script names should already have been computed.
104             * 
105             * @return List of labels to be used as names for each of the parameterized tests. These names
106             * are the same as the script file used to run the test.
107             */
108            @Labels
109            public static List<String> getTestLabels() {
110                    if (ScriptFileTests.testScriptNames == null)
111                            throw new IllegalStateException("Must initialize list of test names before creating tests.");
112                    
113                    return ScriptFileTests.testScriptNames;
114            }
115            
116        /**
117         * This constructor is reflectively called by the Parameterized runner. It creates 
118         * a script file test instance, representing one script file to be tested.
119         */
120        public ScriptFileTests(File testScriptFile) {
121            this.testScriptFile = testScriptFile;
122        }
123    
124        /**
125         * Reads in the contents of a file
126         * @throws FileNotFoundException, IOException
127         * @requires that the specified File exists && File ends with a newline
128         * @returns the contents of that file
129         */
130        private String fileContents(File f) throws IOException {
131            if (f == null)
132                throw new IllegalArgumentException("No file specified");
133    
134            BufferedReader br = new BufferedReader(new FileReader(f));
135    
136            StringBuilder result = new StringBuilder();
137            String line = null;
138    
139            //read line reads up to *any* newline character
140            while ( (line = br.readLine()) != null) {
141                result.append(line);
142                result.append('\n');
143            }
144    
145            br.close();
146            return result.toString();
147        }
148    
149        /**
150         * @throws IOException
151         * @requires there exists a test file indicated by testScriptFile
152         *
153         * @effects runs the test in filename, and output its results to a file in
154         * the same directory with name filename+".actual"; if that file already
155         * exists, it will be overwritten.
156         * @returns the contents of the output file
157         */
158        private String runScriptFile() throws IOException {
159            if (testScriptFile == null)
160                throw new RuntimeException("No file specified");
161    
162            File actual = fileWithSuffix("actual");
163    
164            Reader r = new FileReader(testScriptFile);
165            Writer w = new FileWriter(actual);
166    
167            PS3TestDriver td = new PS3TestDriver(r, w);
168            td.runTests();
169    
170            return fileContents(actual);
171        }
172    
173        /**
174         * @param newSuffix
175         * @return a File with the same name as testScriptFile, except that the test
176         *         suffix is replaced by the given suffix
177         */
178        private File fileWithSuffix(String newSuffix) {
179            File parent = testScriptFile.getParentFile();
180            String driverName = testScriptFile.getName();
181            String baseName = driverName.substring(0, driverName.length() - "test".length());
182    
183            return new File(parent, baseName + newSuffix);
184        }
185    
186        /**
187         * The only test that is run: run a script file and test its output.
188         * @throws IOException
189         */
190        @Test
191        public void checkAgainstExpectedOutput() throws IOException {
192            File expected = fileWithSuffix("expected");
193            assertEquals(testScriptFile.getName(), fileContents(expected), runScriptFile());
194        }
195    }