diff --git a/java/Makefile b/java/Makefile index 886b00683b..79fb0f2f8a 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1220,6 +1220,9 @@ check-serialize: .PHONY: junit junit: daikon/Daikon.class + @# This is not a prerequisite, so that "make -j" does not run it concurrently + @# with compiling Daikon, whose classes the SplitterFactoryTest rules use. + ${MAKE} ${AUTO_GENERATED_CLASSES_2} @echo "Running unit (not system) tests..." LC_ALL=en_US.UTF-8 ${JAVA_COMMAND} org.junit.runner.JUnitCore daikon.test.AllTestsSuite ifeq (${JAVA24}, 1) @@ -1227,14 +1230,9 @@ ifeq (${JAVA24}, 1) @# under versions of Java that do not have java.lang.classfile. LC_ALL=en_US.UTF-8 ${JAVA_COMMAND} org.junit.runner.JUnitCore daikon.test.AllTestsSuite24 endif - -## TODO: This target is never used!!! -junit-all: junit - @# Alternately, MasterUnitTester could run SplitterFactoryTest, - @# but I'm having trouble with that, apparently due to hard-coded - @# directory paths. And these tests are slow, so we want them to - @# be separate anyway. - ${JAVA_COMMAND} daikon.test.split.SplitterFactoryTest + @# SplitterFactoryTest is not in AllTestsSuite, because SplitterFactoryTest + @# is generated after the rest of Daikon (including AllTestsSuite) is compiled. + LC_ALL=en_US.UTF-8 ${JAVA_COMMAND} org.junit.runner.JUnitCore daikon.test.split.SplitterFactoryTest ### ### Clover (code coverage) @@ -1468,16 +1466,31 @@ daikon/test/split/SplitterFactoryTestUpdater.class: daikon/test/split/SplitterFa ${JAVAC_COMMAND} $< ## First, a file not generated via java-cpp. -daikon/test/split/SplitterFactoryTest.java: daikon/test/split/SplitterFactoryTestUpdater.class +# Depending on daikon/Daikon.class regenerates the test whenever Daikon is +# recompiled, because SplitterFactory determines which splitters the test covers. +# The rule for daikon/Daikon.class makes this file only after touching +# daikon/Daikon.class, so this prerequisite does not cause a regress. +# SPLITTER_UPDATER_ARGS is set by the update-splitter-goals target. +daikon/test/split/SplitterFactoryTest.java: daikon/Daikon.class \ + daikon/test/split/SplitterFactoryTestUpdater.class \ + $(wildcard daikon/test/split/targets/*.spinfo daikon/test/split/targets/*.decls) rm -f $@ # Has expected errors while compiling splitter files. # Suppress output unless whole command fails. - (${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater > /dev/null 2>&1) || ${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater + (${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater ${SPLITTER_UPDATER_ARGS} > /dev/null 2>&1) \ + || ${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater ${SPLITTER_UPDATER_ARGS} chmod a-w $@ ${AUTO_GENERATED_CLASSES_2}: ${AUTO_GENERATED_FILES_2} ${JAVAC_COMMAND} ${AUTO_GENERATED_FILES_2} +# Overwrites SplitterFactoryTest's goal files with the current SplitterFactory output. +# Examine the differences (e.g., with "git diff") before committing them. +.PHONY: update-splitter-goals +update-splitter-goals: daikon/Daikon.class daikon/test/split/SplitterFactoryTestUpdater.class + rm -f daikon/test/split/SplitterFactoryTest.java + ${MAKE} SPLITTER_UPDATER_ARGS=--update-goals ${AUTO_GENERATED_CLASSES_2} + # Create the auto-generated files with jpp .PHONY: jpp jpp: ${PLUME_SCRIPTS} ${AUTO_GENERATED_FILES} diff --git a/java/daikon/split/SplitterFactory.java b/java/daikon/split/SplitterFactory.java index f7c590b287..af9d52a1d3 100644 --- a/java/daikon/split/SplitterFactory.java +++ b/java/daikon/split/SplitterFactory.java @@ -144,6 +144,15 @@ public static void load_splitters(PptTopLevel ppt, List spfiles) { Global.debugSplit.fine("<> load_splitters"); } + /** + * Resets the counter that numbers splitter files, so that the names of subsequently-written + * splitter files do not depend on what splitters were created earlier in this JVM. Accessible for + * the purpose of testing. + */ + public static void resetGuid() { + guid = 0; + } + // Accessible for the purpose of testing. public static String getTempDir() { if (tempdir == null) { diff --git a/java/daikon/test/AllTestsSuite.java b/java/daikon/test/AllTestsSuite.java index 9a817b266f..3d2dba4f18 100644 --- a/java/daikon/test/AllTestsSuite.java +++ b/java/daikon/test/AllTestsSuite.java @@ -35,9 +35,8 @@ daikon.test.TestQuant.class, daikon.test.TestAnnotate.class, daikon.test.DtraceDiffTester.class, - // , - // // I'm having trouble with this; need to fix, reinstate, and not call - // // specially from Makefile. -MDE 7/8/2005 - // daikon.test.split.SplitterFactoryTest.class + // daikon.test.split.SplitterFactoryTest is not in this suite, because it is generated after the + // rest of Daikon (including this class) is compiled. The Makefile's "junit" target runs it + // separately. }) public final class AllTestsSuite {} diff --git a/java/daikon/test/SampleTester.java b/java/daikon/test/SampleTester.java index ee6c5eda2e..d97f2970da 100644 --- a/java/daikon/test/SampleTester.java +++ b/java/daikon/test/SampleTester.java @@ -67,22 +67,23 @@ public class SampleTester { /** The usage message for this program. */ private static String usage = StringsPlume.joinLines( - "Usage: java daikon.PrintInvariants [OPTION]... FILE", + "Usage: java daikon.test.SampleTester [OPTION]...", " -h, --" + Daikon.help_SWITCH, " Display this usage message", - " --" + Daikon.config_option_SWITCH, - " Specify a configuration option ", - " --" + Daikon.debug_SWITCH, - " Specify a logger to enable", - " --" + Daikon.track_SWITCH, - " Specify a class, varinfos, and ppt to debug track.", - " Format is class@ppt"); + " --" + Daikon.config_option_SWITCH + " config_var=val", + " Set the specified configuration variable", + " --" + Daikon.debugAll_SWITCH, + " Turn on all debug flags (voluminous output)", + " --" + Daikon.debug_SWITCH + " logger", + " Turn on the specified debug logger", + " --" + Daikon.track_SWITCH + " class@ppt", + " Print debug info on the specified invariant class, vars, and ppt"); /** - * Runs the sample tests. + * Runs the tests without JUnit. * - * @param args command-line arguments - * @throws IOException if there is trouble reading a file + * @param args command-line arguments; see {@link #usage} + * @throws IOException if there is a problem reading a test file */ public static void main(String[] args) throws IOException { try { @@ -93,11 +94,11 @@ public static void main(String[] args) throws IOException { } /** - * Does the work of {@link #main}, but throws an exception rather than calling {@code - * System.exit}. + * This does the work of {@link #main(String[])}, but it never calls System.exit, so it is + * appropriate to be called programmatically. * - * @param args command-line arguments - * @throws IOException if there is trouble reading a file + * @param args command-line arguments; see {@link #usage} + * @throws IOException if there is a problem reading a test file */ @SuppressWarnings("JUnitMethodInvoked") // mainHelper() runs the tests without JUnit public static void mainHelper(String[] args) throws IOException { @@ -144,6 +145,10 @@ public static void mainHelper(String[] args) throws IOException { throw new Daikon.BugInDaikon("getopt() returned " + c); } } + if (g.getOptind() < args.length) { + throw new Daikon.UserError( + "Unexpected argument " + args[g.getOptind()] + "; run with -h for usage"); + } daikon.LogHelper.setupLogs(Global.debugAll ? FINE : INFO); diff --git a/java/daikon/test/split/SplitterFactoryTestUpdater.java b/java/daikon/test/split/SplitterFactoryTestUpdater.java index 5391293804..03b2d648d0 100644 --- a/java/daikon/test/split/SplitterFactoryTestUpdater.java +++ b/java/daikon/test/split/SplitterFactoryTestUpdater.java @@ -12,20 +12,19 @@ import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import org.plumelib.util.FilesPlume; /** - * This class's main method can be used to update both the target files of SplitterFactoryTest and - * the code of the SplitterFactoryTest itself. + * This class's main method writes the code of SplitterFactoryTest. When passed {@code + * --update-goals}, it also overwrites the goal files of SplitterFactoryTest with the splitter files + * that the current SplitterFactory produces. * - *

To use this program to update SplitterFactoryTest and the target files, run - * - *

    \rm -f targets/*.java.goal SplitterFactoryTest.java
- * - * Then simply run the main method without any arguments in the daikon/java directory, and then - * re-compile the SplitterFactoryTest. + *

To update the goal files, run {@code make update-splitter-goals} in the daikon/java directory, + * then examine the differences in the goal files (for example, with {@code git diff}) before + * committing them. * *

To add additional tests to this test program, place the .spinfo and decls files into the * "targets" directory then add a call to generateSplitters with the new files. generateSplitters is @@ -43,14 +42,26 @@ public class SplitterFactoryTestUpdater { private SplitterFactoryTestUpdater() {} // blocks public constructor /** - * If one has changed the test cases used below, for best results run {@code rm *.java.goal} while - * in the targets directory before running this method. Creates new splitter java files, moves the - * new files into target directory, rewrites the code of SplitterFactoryTest to use the new files. - * One should recompile SplitterFactoryTest after running this method. + * Creates new splitter java files and writes the code of SplitterFactoryTest, which compares + * splitter files against the goal files. One should recompile SplitterFactoryTest after running + * this method. * - * @param args are ignored + * @param args either empty, or {@code --update-goals} to also replace the goal files in the + * target directory by the new splitter java files */ public static void main(String[] args) { + boolean updateGoals; + if (args.length == 0) { + updateGoals = false; + } else if (args.length == 1 && args[0].equals("--update-goals")) { + updateGoals = true; + } else { + System.err.println( + "Usage: java daikon.test.split.SplitterFactoryTestUpdater [--update-goals]"); + System.exit(1); + return; + } + // For debugging // SplitterFactory.dkconfig_delete_splitters_on_exit = false; @@ -58,7 +69,7 @@ public static void main(String[] args) { generateSplitters("Fib.spinfo", "Fib.decls"); generateSplitters("QueueAr.spinfo", "QueueAr.decls"); generateSplitters("BigFloat.spinfo", "BigFloat.decls"); - moveFiles(); + processFiles(updateGoals); writeTestClass(); } @@ -111,26 +122,48 @@ private static void generateSplitters(List spinfos, List decls) } } - /** Moves the generated splitter files from the tempDir to the target Dir. */ - private static void moveFiles() { + /** + * Records the class names of the generated splitter files, which are in the tempDir. + * + * @param updateGoals if true, also replace the goal files in the target directory by the + * generated splitter files + */ + private static void processFiles(boolean updateGoals) { File tempDir = new File(SplitterFactory.getTempDir()); String[] fileNames = tempDir.list(); if (fileNames == null) { throw new Error("tmpdir = " + tempDir + " which is not a directory"); } + // Sort so that the generated test does not depend on the file system's directory order. + Arrays.sort(fileNames); + if (updateGoals) { + // Remove all goal files, so that none remain for splitters that SplitterFactory does not + // produce. + File[] oldGoals = new File(targetDir).listFiles((dir, name) -> name.endsWith(".java.goal")); + if (oldGoals == null) { + throw new Error("targetDir = " + targetDir + " which is not a directory"); + } + for (File oldGoal : oldGoals) { + if (!oldGoal.delete()) { + throw new Error("Failed to delete " + oldGoal); + } + } + } for (int i = 0; i < fileNames.length; i++) { if (fileNames[i].endsWith(".java")) { String fileName = fileNames[i]; - String fromName = tempDir.getPath() + File.separator + fileName; - String toName = targetDir + fileName + ".goal"; - try { - moveFile(fromName, toName); - } catch (Error e) { - System.out.printf("Failed to move %s to %s%n", fromName, toName); - throw e; - } catch (RuntimeException e) { - System.out.printf("Failed to move %s to %s%n", fromName, toName); - throw e; + if (updateGoals) { + String fromName = tempDir.getPath() + File.separator + fileName; + String toName = targetDir + fileName + ".goal"; + try { + moveFile(fromName, toName); + } catch (Error e) { + System.out.printf("Failed to move %s to %s%n", fromName, toName); + throw e; + } catch (RuntimeException e) { + System.out.printf("Failed to move %s to %s%n", fromName, toName); + throw e; + } } String javaFileName = new File(fileName).getName(); String className = javaFileName.substring(0, javaFileName.length() - ".java".length()); @@ -194,8 +227,10 @@ private static String getTestClassText() { ps.println("import gnu.getopt.*;"); ps.println("import java.io.*;"); ps.println("import java.util.*;"); - ps.println("import junit.framework.*;"); + ps.println("import org.junit.BeforeClass;"); ps.println("import org.junit.Test;"); + ps.println("import org.junit.internal.TextListener;"); + ps.println("import org.junit.runner.JUnitCore;"); ps.println("import org.plumelib.util.FilesPlume;"); ps.println("import org.plumelib.util.StringsPlume;"); ps.println("import org.plumelib.util.UtilPlume;"); @@ -229,11 +264,6 @@ private static String getTestClassText() { ps.println(" // java files it produces, changing the order that the setUpTests"); ps.println(" // commands are run will cause the tests to fail."); ps.println(); - ps.println(" /** Do not instantiate. */"); - ps.println(" private SplitterFactoryTest() {"); - ps.println(" throw new Error(\"Do not instantiate\");"); - ps.println(" }"); - ps.println(); ps.println(" private static String targetDir = \"" + targetDir + "\";"); ps.println(); ps.println(" private static @Nullable String tempDir = null;"); @@ -242,19 +272,22 @@ private static String getTestClassText() { ps.println(); ps.println(" private static String usage ="); ps.println(" StringsPlume.joinLines("); - ps.println(" \"Usage: java daikon.tools.CreateSpinfo FILE.java ...\","); + ps.println(" \"Usage: java daikon.test.split.SplitterFactoryTest [OPTION]...\","); ps.println( - " \" -s Save (do not delete) the splitter java files in the temp directory\","); - ps.println(" \" -h Display this usage message\");"); + " \" -s Save (do not delete) the splitter java files in the temp" + + " directory\","); + ps.println(" \" -h Display this usage message\");"); ps.println(); ps.println(" /**"); - ps.println(" * Processes command-line options."); + ps.println(" * Runs the tests."); ps.println(" *"); ps.println(" * @param args command-line arguments"); ps.println(" */"); ps.println(" public static void main(String[] args) {"); ps.println(" try {"); - ps.println(" mainHelper(args);"); + ps.println(" if (!mainHelper(args)) {"); + ps.println(" System.exit(1);"); + ps.println(" }"); ps.println(" } catch (Daikon.DaikonTerminationException e) {"); ps.println(" Daikon.handleDaikonTerminationException(e);"); ps.println(" }"); @@ -262,12 +295,14 @@ private static String getTestClassText() { ps.println(); ps.println(" /**"); ps.println( - " * Does the work of {@link #main}, but throws an exception rather than calling {@code"); - ps.println(" * System.exit}."); + " * This does the work of {@link #main(String[])}, but it never calls System.exit," + + " so"); + ps.println(" * it is appropriate to be called programmatically."); ps.println(" *"); - ps.println(" * @param args command-line arguments"); + ps.println(" * @param args the command-line arguments; see {@link #usage}"); + ps.println(" * @return true if all the tests passed"); ps.println(" */"); - ps.println(" public static void mainHelper(String[] args) {"); + ps.println(" public static boolean mainHelper(String[] args) {"); ps.println(" DaikonGetopt g = new DaikonGetopt(args, \"s\", new LongOpt[0], usage);"); ps.println(" int c;"); ps.println(" while ((c = g.getopt()) != -1) {"); @@ -279,6 +314,15 @@ private static String getTestClassText() { ps.println(" throw new Daikon.BugInDaikon(\"getopt() returned \" + c);"); ps.println(" }"); ps.println(" }"); + ps.println(" if (g.getOptind() < args.length) {"); + ps.println(" throw new Daikon.UserError("); + ps.println( + " \"Unexpected argument \" + args[g.getOptind()] + \"; run with -h for" + + " usage\");"); + ps.println(" }"); + ps.println(" JUnitCore junit = new JUnitCore();"); + ps.println(" junit.addListener(new TextListener(System.out));"); + ps.println(" return junit.run(SplitterFactoryTest.class).wasSuccessful();"); ps.println(" }"); ps.println(); @@ -328,13 +372,13 @@ private static String getTestClassText() { } /** - * Appends the code to write the static block of code to code. This code is used by the - * SplitterFactoryTest to set up the needed files to run the tests on. + * Writes the {@code @BeforeClass} method setUpTests to {@code ps}. SplitterFactoryTest uses + * setUpTests to generate the splitter files that its tests check. */ public static void appendSetUpTest(PrintStream ps) { - ps.println(" private static void setUpTests() {"); - ps.println(" List spinfoFiles;"); - ps.println(" List declsFiles;"); + ps.println(" @BeforeClass"); + ps.println(" public static void setUpTests() {"); + ps.println(" SplitterFactory.resetGuid();"); for (int i = 0; i < spinfoFileLists.size(); i++) { ps.println(" createSplitterFiles("); ps.println( @@ -349,7 +393,8 @@ public static void appendSetUpTest(PrintStream ps) { /** Appends the code that executes the test in SplitterFactoryTest to code. */ private static void appendTests(PrintStream ps) { - ps.println(" /** Returns true iff files are the same (ignoring extra white space). */"); + ps.println( + " /** Fails the test unless the files are the same (ignoring extra white space). */"); ps.println(" public static void assertEqualFiles(String f1, String f2) {"); ps.println(" if (!FilesPlume.equalFiles(f1, f2)) {"); ps.println(" fail(\"Files \" + f1 + \" and \" + f2 + \" differ.\");"); @@ -357,12 +402,18 @@ private static void appendTests(PrintStream ps) { ps.println(" }"); ps.println(); ps.println(" public static void assertEqualFiles(String f1) {"); - ps.println(" assertEqualFiles(tempDir + f1, targetDir + f1 + \".goal\");"); + ps.println(" String goalFile = targetDir + f1 + \".goal\";"); + ps.println(" if (!new File(goalFile).exists()) {"); + ps.println( + " fail(\"Goal file \" + goalFile" + + " + \" does not exist; run: make update-splitter-goals\");"); + ps.println(" }"); + ps.println(" assertEqualFiles(tempDir + f1, goalFile);"); ps.println(" }"); ps.println(); for (String className : classNames) { ps.println(" @Test"); - ps.println(" public static void test" + className + "() {"); + ps.println(" public void test" + className + "() {"); ps.println(" assertEqualFiles(\"" + className + ".java\");"); ps.println(" }"); ps.println();