001package daikon.test.split; 002 003import daikon.*; 004import daikon.split.*; 005import java.io.BufferedWriter; 006import java.io.ByteArrayOutputStream; 007import java.io.File; 008import java.io.IOException; 009import java.io.OutputStream; 010import java.io.PrintStream; 011import java.util.ArrayList; 012import java.util.HashSet; 013import java.util.List; 014import org.plumelib.util.FilesPlume; 015 016/** 017 * This class's main method can be used to update both the target files of SplitterFactoryTest and 018 * the code of the SplitterFactoryTest itself. 019 * 020 * <p>To use this program to update SplitterFactoryTest and the target files, run 021 * 022 * <pre> \rm -f targets/*.java.goal SplitterFactoryTest.java</pre> 023 * 024 * Then simply run the main method without any arguments in the daikon/java directory, and then 025 * re-compile the SplitterFactoryTest. 026 * 027 * <p>To add additional tests to this test program, place the .spinfo and decls files into the 028 * "targets" directory then add a call to generateSplitters with the new files. generateSplitters is 029 * overloaded, and takes either one {@code .spinfo} file and one decls file, or else a list of each. 030 */ 031public class SplitterFactoryTestUpdater { 032 public static java.lang.Runtime commander = java.lang.Runtime.getRuntime(); 033 private static String targetDir = "daikon/test/split/targets/"; 034 private static String splitDir = "daikon/test/split/"; 035 036 private static ArrayList<ArrayList<File>> spinfoFileLists = new ArrayList<>(); 037 private static ArrayList<ArrayList<File>> declsFileLists = new ArrayList<>(); 038 private static ArrayList<String> classNames = new ArrayList<>(); 039 040 private SplitterFactoryTestUpdater() {} // blocks public constructor 041 042 /** 043 * If one has changed the test cases used below, for best results run {@code rm *.java.goal} while 044 * in the targets directory before running this method. Creates new splitter java files, moves the 045 * new files into target directory, rewrites the code of SplitterFactoryTest to use the new files. 046 * One should recompile SplitterFactoryTest after running this method. 047 * 048 * @param args are ignored 049 */ 050 public static void main(String[] args) { 051 // For debugging 052 // SplitterFactory.dkconfig_delete_splitters_on_exit = false; 053 054 generateSplitters("StreetNumberSet.spinfo", "StreetNumberSet.decls"); 055 generateSplitters("Fib.spinfo", "Fib.decls"); 056 generateSplitters("QueueAr.spinfo", "QueueAr.decls"); 057 generateSplitters("BigFloat.spinfo", "BigFloat.decls"); 058 moveFiles(); 059 writeTestClass(); 060 } 061 062 /** 063 * This is a short-cut method if only one spinfo file and only one decls files is to be used. 064 * 065 * @see #generateSplitters(List, List) 066 */ 067 private static void generateSplitters(String spinfoFile, String declsFile) { 068 List<String> spinfo = new ArrayList<>(); 069 spinfo.add(spinfoFile); 070 List<String> decls = new ArrayList<>(); 071 decls.add(declsFile); 072 generateSplitters(spinfo, decls); 073 } 074 075 /** 076 * Generates the splitter {@code .java} files. 077 * 078 * @param spinfos the spinfo files that should be used in generating the splitter java files 079 * @param decls the decls files that should be used in generating the splitter java files 080 */ 081 private static void generateSplitters(List<String> spinfos, List<String> decls) { 082 HashSet<File> declsFileSet = new HashSet<>(); 083 HashSet<File> spinfoFiles = new HashSet<>(); 084 PptMap allPpts = new PptMap(); 085 for (String spinfoFile : spinfos) { 086 spinfoFile = targetDir + spinfoFile; 087 spinfoFiles.add(new File(spinfoFile)); 088 } 089 spinfoFileLists.add(new ArrayList<File>(spinfoFiles)); 090 for (String declsFile : decls) { 091 declsFile = targetDir + declsFile; 092 declsFileSet.add(new File(declsFile)); 093 } 094 declsFileLists.add(new ArrayList<File>(declsFileSet)); 095 try { 096 PptSplitter.dkconfig_suppressSplitterErrors = true; 097 Daikon.create_splitters(spinfoFiles); 098 // calling read_data_trace_file in a loop instead of calling 099 // read_data_trace_files allows us to mix version 1 and 100 // version 2 decls file formats. 101 for (String declsFile : decls) { 102 // This reset allows current format to differ from previous. 103 FileIO.resetNewDeclFormat(); 104 FileIO.read_data_trace_file(targetDir + declsFile, allPpts); 105 } 106 } catch (IOException e) { 107 throw new RuntimeException(e); 108 } 109 } 110 111 /** Moves the generated splitter files from the tempDir to the target Dir. */ 112 private static void moveFiles() { 113 File tempDir = new File(SplitterFactory.getTempDir()); 114 String[] fileNames = tempDir.list(); 115 if (fileNames == null) { 116 throw new Error("tmpdir = " + tempDir + " which is not a directory"); 117 } 118 for (int i = 0; i < fileNames.length; i++) { 119 if (fileNames[i].endsWith(".java")) { 120 String fileName = fileNames[i]; 121 String fromName = tempDir.getPath() + File.separator + fileName; 122 String toName = targetDir + fileName + ".goal"; 123 try { 124 moveFile(fromName, toName); 125 } catch (Error e) { 126 System.out.printf("Failed to move %s to %s%n", fromName, toName); 127 throw e; 128 } catch (RuntimeException e) { 129 System.out.printf("Failed to move %s to %s%n", fromName, toName); 130 throw e; 131 } 132 String javaFileName = new File(fileName).getName(); 133 String className = javaFileName.substring(0, javaFileName.length() - ".java".length()); 134 classNames.add(className); 135 } 136 } 137 } 138 139 private static void moveFile(String fromName, String toName) { 140 File from = new File(fromName); 141 File to = new File(toName); 142 if (!from.exists()) { 143 throw new Error("Does not exist: " + fromName); 144 } 145 if (!from.canRead()) { 146 throw new Error("Cannot read " + fromName); 147 } 148 // canWrite() requires that the file already exists. So comment this out. 149 // if (! to.canWrite()) { 150 // throw new Error("Cannot write " + toName + " = " + to.getAbsoluteFile() 151 // + " when copying from " + fromName); 152 // } 153 if (to.exists()) { 154 to.delete(); 155 } 156 // file.renameTo(to) fails if the two files are on different file systems 157 // (e.g., /tmp and /scratch may be different). 158 // So read and write the file directly rather than using renameTo(). 159 FilesPlume.writeFile(to, FilesPlume.readFile(from)); 160 } 161 162 /** Writes the new code for "SplitterFactoryTest.java". */ 163 private static void writeTestClass() { 164 String code = getTestClassText(); 165 try { 166 // Delete the file, in case it is unwriteable (in which case deleting 167 // works, but overwriting does not). 168 new File(splitDir + "SplitterFactoryTest.java").delete(); 169 try (BufferedWriter writer = 170 FilesPlume.newBufferedFileWriter(splitDir + "SplitterFactoryTest.java")) { 171 writer.write(code); 172 writer.flush(); 173 } 174 } catch (IOException e) { 175 throw new RuntimeException(e); 176 } 177 } 178 179 /** Returns a String of the new text for the SplitterFactoryTest class. */ 180 private static String getTestClassText() { 181 OutputStream code = new ByteArrayOutputStream(); 182 PrintStream ps = new PrintStream(code); 183 184 // Use string concatenation to avoid matching the text literally. 185 ps.println("// ***** This file is automatically generated by SplitterFactoryTestUpdater.java"); 186 ps.println(); 187 ps.println("package daikon.test.split;"); 188 ps.println(); 189 ps.println("import daikon.*;"); 190 ps.println("import daikon.split.*;"); 191 ps.println("import gnu.getopt.*;"); 192 ps.println("import java.io.*;"); 193 ps.println("import java.util.*;"); 194 ps.println("import junit.framework.*;"); 195 ps.println("import org.junit.Test;"); 196 ps.println("import org.plumelib.util.FilesPlume;"); 197 ps.println("import org.plumelib.util.StringsPlume;"); 198 ps.println("import org.plumelib.util.UtilPlume;"); 199 ps.println("import static org.junit.Assert.fail;"); 200 201 ps.println(); 202 ps.println("import org.checkerframework.checker.nullness.qual.*;"); 203 ps.println(); 204 ps.println("/**"); 205 ps.println( 206 " * THIS CLASS WAS GENERATED BY SplitterFactoryTestUpdater. Therefore, it is a bad idea"); 207 ps.println( 208 " * to directly edit this class's code for all but temporary reasons. Any permanent"); 209 ps.println(" * changes should be made through SplitterFactoryUpdater."); 210 ps.println(" *"); 211 ps.println( 212 " * <p>This class contains regression tests for the SplitterFactory class. The tests"); 213 ps.println( 214 " * directly test the Java files produced by the load_splitters method by comparing them"); 215 ps.println( 216 " * against goal files. Note that it is normal for some classes not to compile during"); 217 ps.println(" * this test."); 218 ps.println(" *"); 219 ps.println(" * <p>These tests assume that the goal files are contained in the directory:"); 220 ps.println(" * \"" + targetDir + "\""); 221 ps.println(" *"); 222 ps.println(" * <p>These tests ignore extra white spaces."); 223 ps.println(" */"); 224 ps.println("public class SplitterFactoryTest {"); 225 ps.println(" // Because the SplitterFactory sequentially numbers the"); 226 ps.println(" // java files it produces, changing the order that the setUpTests"); 227 ps.println(" // commands are run will cause the tests to fail."); 228 ps.println(); 229 ps.println(" private static String targetDir = \"" + targetDir + "\";"); 230 ps.println(); 231 ps.println(" private static @Nullable String tempDir = null;"); 232 ps.println(); 233 ps.println(" private static boolean saveFiles = false;"); 234 ps.println(); 235 ps.println(" private static String usage ="); 236 ps.println(" StringsPlume.joinLines("); 237 ps.println(" \"Usage: java daikon.tools.CreateSpinfo FILE.java ...\","); 238 ps.println( 239 " \" -s Save (do not delete) the splitter java files in the temp directory\","); 240 ps.println(" \" -h Display this usage message\");"); 241 ps.println(); 242 ps.println(" public static void main(String[] args) {"); 243 ps.println( 244 " Getopt g = new Getopt(\"daikon.test.split.SplitterFactoryTest\", args, \"hs\");"); 245 ps.println(" int c;"); 246 ps.println(" while ((c = g.getopt()) != -1) {"); 247 ps.println(" switch (c) {"); 248 ps.println(" case 's':"); 249 ps.println(" saveFiles = true;"); 250 ps.println(" break;"); 251 ps.println(" case 'h':"); 252 ps.println(" System.out.println(usage);"); 253 ps.println(" System.exit(1);"); 254 ps.println(" break;"); 255 ps.println(" case '?':"); 256 ps.println(" break;"); 257 ps.println(" default:"); 258 ps.println(" System.out.println(\"getopt() returned \" + c);"); 259 ps.println(" break;"); 260 ps.println(" }"); 261 ps.println(" }"); 262 ps.println(" }"); 263 ps.println(); 264 265 appendSetUpTest(ps); 266 267 ps.println(); 268 ps.println(" /** Sets up the test by generating the needed splitter java files. */"); 269 ps.println(" private static void createSplitterFiles(String spinfo, String decl) {"); 270 ps.println(" List<String> spinfoFiles = new ArrayList<>();"); 271 ps.println(" spinfoFiles.add(spinfo);"); 272 ps.println(" List<String> declsFiles = Collections.singletonList(decl);"); 273 ps.println(" createSplitterFiles(spinfoFiles, declsFiles);"); 274 ps.println(" }"); 275 ps.println(); 276 ps.println(" /** Sets up the test by generating the needed splitter java files. */"); 277 ps.println( 278 " private static void createSplitterFiles(List<String> spinfos, List<String> decls) {"); 279 ps.println(" Set<File> spFiles = new HashSet<>();"); 280 ps.println(" PptMap allPpts = new PptMap();"); 281 ps.println(" for (String spinfo : spinfos) {"); 282 ps.println(" spFiles.add(new File(spinfo));"); 283 ps.println(" }"); 284 ps.println(" try {"); 285 ps.println(" if (saveFiles) {"); 286 ps.println(" SplitterFactory.dkconfig_delete_splitters_on_exit = false;"); 287 ps.println(" }"); 288 ps.println(" PptSplitter.dkconfig_suppressSplitterErrors = true;"); 289 ps.println(" Daikon.create_splitters(spFiles);"); 290 ps.println(" for (String declsFile : decls) {"); 291 ps.println(" FileIO.resetNewDeclFormat();"); 292 ps.println( 293 " FileIO.read_data_trace_file(declsFile, allPpts);"); // invoked for side effects 294 ps.println(" }"); 295 ps.println(" tempDir = SplitterFactory.getTempDir();"); 296 ps.println(" } catch (IOException e) {"); 297 ps.println(" throw new RuntimeException(e);"); 298 ps.println(" }"); 299 ps.println(" }"); 300 ps.println(); 301 302 appendTests(ps); 303 304 ps.println("}"); 305 306 ps.close(); 307 return code.toString(); 308 } 309 310 /** 311 * Appends the code to write the static block of code to code. This code is used by the 312 * SplitterFactoryTest to set up the needed files to run the tests on. 313 */ 314 public static void appendSetUpTest(PrintStream ps) { 315 ps.println(" private static void setUpTests() {"); 316 ps.println(" List<String> spinfoFiles;"); 317 ps.println(" List<String> declsFiles;"); 318 for (int i = 0; i < spinfoFileLists.size(); i++) { 319 ps.println(" createSplitterFiles("); 320 ps.println( 321 " \"" 322 + FilesPlume.javaSource(spinfoFileLists.get(i).get(0)) 323 + "\", \"" 324 + FilesPlume.javaSource(declsFileLists.get(i).get(0)) 325 + "\");"); 326 } 327 ps.println(" }"); 328 } 329 330 /** Appends the code that executes the test in SplitterFactoryTest to code. */ 331 private static void appendTests(PrintStream ps) { 332 ps.println(" /** Returns true iff files are the same (ignoring extra white space). */"); 333 ps.println(" public static void assertEqualFiles(String f1, String f2) {"); 334 ps.println(" if (!FilesPlume.equalFiles(f1, f2)) {"); 335 ps.println(" fail(\"Files \" + f1 + \" and \" + f2 + \" differ.\");"); 336 ps.println(" }"); 337 ps.println(" }"); 338 ps.println(); 339 ps.println(" public static void assertEqualFiles(String f1) {"); 340 ps.println(" assertEqualFiles(tempDir + f1, targetDir + f1 + \".goal\");"); 341 ps.println(" }"); 342 ps.println(); 343 for (String className : classNames) { 344 ps.println(" @Test"); 345 ps.println(" public static void test" + className + "() {"); 346 ps.println(" assertEqualFiles(\"" + className + ".java\");"); 347 ps.println(" }"); 348 ps.println(); 349 } 350 } 351}