001    package hw7.test;
002    
003    import static org.junit.Assert.assertEquals;
004    
005    import java.io.BufferedReader;
006    import java.io.File;
007    import java.io.FileFilter;
008    import java.io.FileNotFoundException;
009    import java.io.FileReader;
010    import java.io.IOException;
011    import java.net.URISyntaxException;
012    import java.util.ArrayList;
013    import java.util.LinkedList;
014    import java.util.List;
015    
016    import org.junit.Test;
017    import org.junit.runner.RunWith;
018    import org.junit.runners.Parameterized.Parameters;
019    
020    import utils.tests.LabelledParameterized;
021    import utils.tests.LabelledParameterized.Labels;
022    
023    /**
024     * This class, along with HW7TestDriver, can be used to test your Campus Paths
025     * application. It is assumed that the files are located in the same directory.
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 HW7TestDriver 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.trim());
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            HW7TestDriver td = new HW7TestDriver(testScriptFile, actual);
165            td.runTests();
166    
167            return fileContents(actual);
168        }
169    
170        /**
171         * @param newSuffix
172         * @return a File with the same name as testScriptFile, except that the test
173         *         suffix is replaced by the given suffix
174         */
175        private File fileWithSuffix(String newSuffix) {
176            File parent = testScriptFile.getParentFile();
177            String driverName = testScriptFile.getName();
178            String baseName = driverName.substring(0, driverName.length() - "test".length());
179    
180            return new File(parent, baseName + newSuffix);
181        }
182    
183        /**
184         * The only test that is run: run a script file and test its output.
185         * @throws IOException
186         */
187        @Test(timeout=30000)
188        public void checkAgainstExpectedOutput() throws IOException {
189            File expected = fileWithSuffix("expected");
190            assertEquals(testScriptFile.getName(), fileContents(expected), runScriptFile());
191        }
192    }