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}