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 }