From ec4696a6084369a10db650f9df17c074bd12e669 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 18:59:36 -0700 Subject: [PATCH 1/7] Make SplitterFactoryTest's main run its tests; fix test drivers' main methods and usage messages --- java/daikon/test/SampleTester.java | 31 ++++++++++-- .../split/SplitterFactoryTestUpdater.java | 47 ++++++++++++++----- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/java/daikon/test/SampleTester.java b/java/daikon/test/SampleTester.java index 40919edb6b..aa0888a8a5 100644 --- a/java/daikon/test/SampleTester.java +++ b/java/daikon/test/SampleTester.java @@ -67,7 +67,7 @@ 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, @@ -78,8 +78,29 @@ public class SampleTester { " Specify a class, varinfos, and ppt to debug track.", " Format is class@ppt"); - @SuppressWarnings("JUnitMethodInvoked") // main() runs the tests without JUnit + /** + * Runs the tests without JUnit. + * + * @param args command-line arguments, like those of {@link #mainHelper} + * @throws IOException if there is a problem reading a test file + */ public static void main(String[] args) throws IOException { + try { + mainHelper(args); + } catch (Daikon.DaikonTerminationException e) { + Daikon.handleDaikonTerminationException(e); + } + } + + /** + * 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, like those of {@link #main} + * @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 { LongOpt[] longopts = new LongOpt[] { @@ -89,7 +110,7 @@ public static void main(String[] args) throws IOException { new LongOpt(Daikon.track_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0), }; - Getopt g = new Getopt("daikon.test.SampleTester", args, "h:", longopts); + Getopt g = new Getopt("daikon.test.SampleTester", args, "h", longopts); int c; while ((c = g.getopt()) != -1) { switch (c) { @@ -135,6 +156,10 @@ public static void main(String[] args) throws IOException { break; } } + 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 0c806f6071..bf348fdf42 100644 --- a/java/daikon/test/split/SplitterFactoryTestUpdater.java +++ b/java/daikon/test/split/SplitterFactoryTestUpdater.java @@ -195,7 +195,11 @@ private static String getTestClassText() { 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.runner.JUnitCore;"); + ps.println("import org.junit.runner.Result;"); + ps.println("import org.junit.runner.notification.Failure;"); ps.println("import org.plumelib.util.FilesPlume;"); ps.println("import org.plumelib.util.StringsPlume;"); ps.println("import org.plumelib.util.UtilPlume;"); @@ -229,11 +233,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,12 +241,21 @@ 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(" public static void main(String[] args) {"); + ps.println(" try {"); + ps.println(" mainHelper(args);"); + ps.println(" } catch (Daikon.DaikonTerminationException e) {"); + ps.println(" Daikon.handleDaikonTerminationException(e);"); + ps.println(" }"); + ps.println(" }"); + ps.println(); + ps.println(" public static void mainHelper(String[] args) {"); ps.println( " Getopt g = new Getopt(\"daikon.test.split.SplitterFactoryTest\", args, \"hs\");"); ps.println(" int c;"); @@ -258,8 +266,7 @@ private static String getTestClassText() { ps.println(" break;"); ps.println(" case 'h':"); ps.println(" System.out.println(usage);"); - ps.println(" System.exit(1);"); - ps.println(" break;"); + ps.println(" throw new Daikon.NormalTermination();"); ps.println(" case '?':"); ps.println(" break;"); ps.println(" default:"); @@ -267,6 +274,21 @@ private static String getTestClassText() { ps.println(" break;"); 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(" Result result = JUnitCore.runClasses(SplitterFactoryTest.class);"); + ps.println(" for (Failure failure : result.getFailures()) {"); + ps.println(" System.err.println(failure.getTrace());"); + ps.println(" }"); + ps.println(" if (!result.wasSuccessful()) {"); + ps.println( + " throw new Daikon.UserError(StringsPlume.nPlural(result.getFailureCount(), \"test\")" + + " + \" failed\");"); + ps.println(" }"); ps.println(" }"); ps.println(); @@ -320,7 +342,8 @@ private static String getTestClassText() { * SplitterFactoryTest to set up the needed files to run the tests on. */ public static void appendSetUpTest(PrintStream ps) { - ps.println(" private static void setUpTests() {"); + ps.println(" @BeforeClass"); + ps.println(" public static void setUpTests() {"); ps.println(" List spinfoFiles;"); ps.println(" List declsFiles;"); for (int i = 0; i < spinfoFileLists.size(); i++) { @@ -350,7 +373,7 @@ private static void appendTests(PrintStream ps) { 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(); From 8aaa95b2d66a4b2738585d9f80764a64472f0998 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 19:09:16 -0700 Subject: [PATCH 2/7] Accept --help in SampleTester; name failing tests in SplitterFactoryTest; remove unused locals --- java/daikon/test/SampleTester.java | 1 + java/daikon/test/split/SplitterFactoryTestUpdater.java | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java/daikon/test/SampleTester.java b/java/daikon/test/SampleTester.java index aa0888a8a5..e2c2adbaba 100644 --- a/java/daikon/test/SampleTester.java +++ b/java/daikon/test/SampleTester.java @@ -104,6 +104,7 @@ public static void mainHelper(String[] args) throws IOException { LongOpt[] longopts = new LongOpt[] { + new LongOpt(Daikon.help_SWITCH, LongOpt.NO_ARGUMENT, null, 0), new LongOpt(Daikon.config_option_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0), new LongOpt(Daikon.debugAll_SWITCH, LongOpt.NO_ARGUMENT, null, 0), new LongOpt(Daikon.debug_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0), diff --git a/java/daikon/test/split/SplitterFactoryTestUpdater.java b/java/daikon/test/split/SplitterFactoryTestUpdater.java index bf348fdf42..df255d9b95 100644 --- a/java/daikon/test/split/SplitterFactoryTestUpdater.java +++ b/java/daikon/test/split/SplitterFactoryTestUpdater.java @@ -282,12 +282,13 @@ private static String getTestClassText() { ps.println(" }"); ps.println(" Result result = JUnitCore.runClasses(SplitterFactoryTest.class);"); ps.println(" for (Failure failure : result.getFailures()) {"); + ps.println(" System.err.println(\"Failure in \" + failure.getTestHeader() + \":\");"); ps.println(" System.err.println(failure.getTrace());"); ps.println(" }"); ps.println(" if (!result.wasSuccessful()) {"); ps.println( - " throw new Daikon.UserError(StringsPlume.nPlural(result.getFailureCount(), \"test\")" - + " + \" failed\");"); + " throw new Daikon.UserError(StringsPlume.nPlural(result.getFailureCount()," + + " \"failure\"));"); ps.println(" }"); ps.println(" }"); ps.println(); @@ -344,8 +345,6 @@ private static String getTestClassText() { public static void appendSetUpTest(PrintStream ps) { ps.println(" @BeforeClass"); ps.println(" public static void setUpTests() {"); - ps.println(" List spinfoFiles;"); - ps.println(" List declsFiles;"); for (int i = 0; i < spinfoFileLists.size(); i++) { ps.println(" createSplitterFiles("); ps.println( From 9d284dd30533dc661f5848115108644ab051d62e Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 11 Oct 2026 11:59:05 -0700 Subject: [PATCH 3/7] Reject unknown options in test drivers; report SplitterFactoryTest failures by exit status; run SplitterFactoryTest from make junit Also make the generated mainHelper private, fix Javadoc, and document --debugAll in SampleTester's usage message. --- java/Makefile | 11 +++----- java/daikon/test/AllTestsSuite.java | 7 +++-- java/daikon/test/SampleTester.java | 9 ++++--- .../split/SplitterFactoryTestUpdater.java | 26 ++++++++++++++----- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/java/Makefile b/java/Makefile index ce436e5d69..69bc75de71 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1227,14 +1227,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 it must run in its + @# own JVM: it depends on the sequential numbering of splitter files. + LC_ALL=en_US.UTF-8 ${JAVA_COMMAND} daikon.test.split.SplitterFactoryTest ### ### Clover (code coverage) diff --git a/java/daikon/test/AllTestsSuite.java b/java/daikon/test/AllTestsSuite.java index 7d74e7e7ac..2bbf9d93ea 100644 --- a/java/daikon/test/AllTestsSuite.java +++ b/java/daikon/test/AllTestsSuite.java @@ -34,9 +34,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 must run in its own + // JVM: it depends on the sequential numbering of splitter files. 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 e2c2adbaba..fec8775b72 100644 --- a/java/daikon/test/SampleTester.java +++ b/java/daikon/test/SampleTester.java @@ -72,6 +72,8 @@ public class SampleTester { " Display this usage message", " --" + Daikon.config_option_SWITCH, " Specify a configuration option ", + " --" + Daikon.debugAll_SWITCH, + " Turn on all debug flags (voluminous output)", " --" + Daikon.debug_SWITCH, " Specify a logger to enable", " --" + Daikon.track_SWITCH, @@ -81,7 +83,7 @@ public class SampleTester { /** * Runs the tests without JUnit. * - * @param args command-line arguments, like those of {@link #mainHelper} + * @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 { @@ -96,7 +98,7 @@ public static void main(String[] args) throws IOException { * 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, like those of {@link #main} + * @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 @@ -150,7 +152,8 @@ public static void mainHelper(String[] args) throws IOException { throw new Daikon.NormalTermination(); case '?': - break; // getopt() already printed an error + // getopt() already printed an error + throw new Daikon.UserError("Run with -h for usage"); default: System.out.println("getopt() returned " + c); diff --git a/java/daikon/test/split/SplitterFactoryTestUpdater.java b/java/daikon/test/split/SplitterFactoryTestUpdater.java index df255d9b95..e44087c468 100644 --- a/java/daikon/test/split/SplitterFactoryTestUpdater.java +++ b/java/daikon/test/split/SplitterFactoryTestUpdater.java @@ -249,13 +249,23 @@ private static String getTestClassText() { 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(" }"); ps.println(" }"); ps.println(); - ps.println(" public static void mainHelper(String[] args) {"); + ps.println(" /**"); + ps.println(" * This does the work of {@link #main(String[])}, but it never calls System.exit."); + ps.println(" * It may be called only once per JVM, because SplitterFactory numbers the files it"); + ps.println(" * produces sequentially and setUpTests sets static state."); + ps.println(" *"); + ps.println(" * @param args the command-line arguments; see {@link #usage}"); + ps.println(" * @return true if all the tests passed"); + ps.println(" */"); + ps.println(" private static boolean mainHelper(String[] args) {"); ps.println( " Getopt g = new Getopt(\"daikon.test.split.SplitterFactoryTest\", args, \"hs\");"); ps.println(" int c;"); @@ -268,7 +278,8 @@ private static String getTestClassText() { ps.println(" System.out.println(usage);"); ps.println(" throw new Daikon.NormalTermination();"); ps.println(" case '?':"); - ps.println(" break;"); + ps.println(" // getopt() already printed an error"); + ps.println(" throw new Daikon.UserError(\"Run with -h for usage\");"); ps.println(" default:"); ps.println(" System.out.println(\"getopt() returned \" + c);"); ps.println(" break;"); @@ -287,9 +298,10 @@ private static String getTestClassText() { ps.println(" }"); ps.println(" if (!result.wasSuccessful()) {"); ps.println( - " throw new Daikon.UserError(StringsPlume.nPlural(result.getFailureCount()," - + " \"failure\"));"); + " System.err.println(\"SplitterFactoryTest: \" +" + + " StringsPlume.nPlural(result.getFailureCount(), \"failure\"));"); ps.println(" }"); + ps.println(" return result.wasSuccessful();"); ps.println(" }"); ps.println(); @@ -339,8 +351,8 @@ 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(" @BeforeClass"); From 0d4569fc047aea4044f6b0e679207913cf747ec6 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 11 Oct 2026 12:13:27 -0700 Subject: [PATCH 4/7] Reset splitter numbering in SplitterFactoryTest; run it via JUnitCore; rebuild it when its inputs change; tidy usage messages --- java/Makefile | 11 ++++---- java/daikon/split/SplitterFactory.java | 9 +++++++ java/daikon/test/AllTestsSuite.java | 6 ++--- java/daikon/test/SampleTester.java | 13 +++++----- .../split/SplitterFactoryTestUpdater.java | 26 ++++++------------- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/java/Makefile b/java/Makefile index 69bc75de71..878ffe8e3a 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1219,7 +1219,7 @@ check-serialize: .PHONY: junit -junit: daikon/Daikon.class +junit: daikon/Daikon.class ${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,9 +1227,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 - @# SplitterFactoryTest is not in AllTestsSuite, because it must run in its - @# own JVM: it depends on the sequential numbering of splitter files. - LC_ALL=en_US.UTF-8 ${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) @@ -1463,7 +1463,8 @@ 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 +daikon/test/split/SplitterFactoryTest.java: 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. 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 2bbf9d93ea..f3801a1607 100644 --- a/java/daikon/test/AllTestsSuite.java +++ b/java/daikon/test/AllTestsSuite.java @@ -34,8 +34,8 @@ daikon.test.TestQuant.class, daikon.test.TestAnnotate.class, daikon.test.DtraceDiffTester.class, - // daikon.test.split.SplitterFactoryTest is not in this suite, because it must run in its own - // JVM: it depends on the sequential numbering of splitter files. The Makefile's "junit" - // target runs it separately. + // 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 fec8775b72..516e37d0c5 100644 --- a/java/daikon/test/SampleTester.java +++ b/java/daikon/test/SampleTester.java @@ -70,15 +70,14 @@ public class SampleTester { "Usage: java daikon.test.SampleTester [OPTION]...", " -h, --" + Daikon.help_SWITCH, " Display this usage message", - " --" + Daikon.config_option_SWITCH, - " Specify a configuration option ", + " --" + 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, - " Specify a logger to enable", - " --" + Daikon.track_SWITCH, - " Specify a class, varinfos, and ppt to debug track.", - " Format is class@ppt"); + " --" + 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 tests without JUnit. diff --git a/java/daikon/test/split/SplitterFactoryTestUpdater.java b/java/daikon/test/split/SplitterFactoryTestUpdater.java index e44087c468..112058501d 100644 --- a/java/daikon/test/split/SplitterFactoryTestUpdater.java +++ b/java/daikon/test/split/SplitterFactoryTestUpdater.java @@ -194,12 +194,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.junit.runner.Result;"); - ps.println("import org.junit.runner.notification.Failure;"); ps.println("import org.plumelib.util.FilesPlume;"); ps.println("import org.plumelib.util.StringsPlume;"); ps.println("import org.plumelib.util.UtilPlume;"); @@ -258,14 +256,13 @@ private static String getTestClassText() { ps.println(" }"); ps.println(); ps.println(" /**"); - ps.println(" * This does the work of {@link #main(String[])}, but it never calls System.exit."); - ps.println(" * It may be called only once per JVM, because SplitterFactory numbers the files it"); - ps.println(" * produces sequentially and setUpTests sets static state."); + ps.println(" * 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 the command-line arguments; see {@link #usage}"); ps.println(" * @return true if all the tests passed"); ps.println(" */"); - ps.println(" private static boolean mainHelper(String[] args) {"); + ps.println(" public static boolean mainHelper(String[] args) {"); ps.println( " Getopt g = new Getopt(\"daikon.test.split.SplitterFactoryTest\", args, \"hs\");"); ps.println(" int c;"); @@ -291,17 +288,9 @@ private static String getTestClassText() { " \"Unexpected argument \" + args[g.getOptind()] + \"; run with -h for" + " usage\");"); ps.println(" }"); - ps.println(" Result result = JUnitCore.runClasses(SplitterFactoryTest.class);"); - ps.println(" for (Failure failure : result.getFailures()) {"); - ps.println(" System.err.println(\"Failure in \" + failure.getTestHeader() + \":\");"); - ps.println(" System.err.println(failure.getTrace());"); - ps.println(" }"); - ps.println(" if (!result.wasSuccessful()) {"); - ps.println( - " System.err.println(\"SplitterFactoryTest: \" +" - + " StringsPlume.nPlural(result.getFailureCount(), \"failure\"));"); - ps.println(" }"); - ps.println(" return result.wasSuccessful();"); + 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(); @@ -357,6 +346,7 @@ private static String getTestClassText() { public static void appendSetUpTest(PrintStream ps) { 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( From 8df52e116981458bfcc64090745b7964d3aef695 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 11 Oct 2026 12:28:29 -0700 Subject: [PATCH 5/7] Write SplitterFactoryTest goal files only via make update-splitter-goals; avoid make -j race in junit; fix line length and Javadoc --- java/Makefile | 15 +++- .../split/SplitterFactoryTestUpdater.java | 79 ++++++++++++------- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/java/Makefile b/java/Makefile index 878ffe8e3a..9779897f00 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1219,7 +1219,10 @@ check-serialize: .PHONY: junit -junit: daikon/Daikon.class ${AUTO_GENERATED_CLASSES_2} +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) @@ -1474,6 +1477,16 @@ daikon/test/split/SplitterFactoryTest.java: daikon/test/split/SplitterFactoryTes ${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 + rm -f daikon/test/split/SplitterFactoryTest.java + (${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater --update-goals > /dev/null 2>&1) \ + || ${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater --update-goals + chmod a-w daikon/test/split/SplitterFactoryTest.java + ${MAKE} ${AUTO_GENERATED_CLASSES_2} + # Create the auto-generated files with jpp .PHONY: jpp jpp: ${PLUME_SCRIPTS} ${AUTO_GENERATED_FILES} diff --git a/java/daikon/test/split/SplitterFactoryTestUpdater.java b/java/daikon/test/split/SplitterFactoryTestUpdater.java index 112058501d..0a9d11ba6d 100644 --- a/java/daikon/test/split/SplitterFactoryTestUpdater.java +++ b/java/daikon/test/split/SplitterFactoryTestUpdater.java @@ -17,15 +17,13 @@ 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 +41,29 @@ 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. + * + *

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 with {@code --update-goals}. * - * @param args are ignored + * @param args either empty, or {@code --update-goals} to also move the new splitter java files + * into the target directory as goal 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 +71,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,8 +124,13 @@ 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 move the generated splitter files to the target directory as + * goal files + */ + private static void processFiles(boolean updateGoals) { File tempDir = new File(SplitterFactory.getTempDir()); String[] fileNames = tempDir.list(); if (fileNames == null) { @@ -121,16 +139,18 @@ private static void moveFiles() { 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()); @@ -256,7 +276,9 @@ private static String getTestClassText() { ps.println(" }"); ps.println(); ps.println(" /**"); - ps.println(" * This does the work of {@link #main(String[])}, but it never calls System.exit, so"); + ps.println( + " * 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 the command-line arguments; see {@link #usage}"); @@ -361,7 +383,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.\");"); From 965a5f63d2e8eae7150498ad0d9b2e8309b7a3db Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 11 Oct 2026 16:32:53 -0700 Subject: [PATCH 6/7] update-splitter-goals removes stale goal files and rebuilds the updater; clear failure for a missing goal file --- java/Makefile | 2 +- .../split/SplitterFactoryTestUpdater.java | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/java/Makefile b/java/Makefile index 9779897f00..3a4f2fed50 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1480,7 +1480,7 @@ ${AUTO_GENERATED_CLASSES_2}: ${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 +update-splitter-goals: daikon/Daikon.class daikon/test/split/SplitterFactoryTestUpdater.class rm -f daikon/test/split/SplitterFactoryTest.java (${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater --update-goals > /dev/null 2>&1) \ || ${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater --update-goals diff --git a/java/daikon/test/split/SplitterFactoryTestUpdater.java b/java/daikon/test/split/SplitterFactoryTestUpdater.java index 0a9d11ba6d..e3758e9f5d 100644 --- a/java/daikon/test/split/SplitterFactoryTestUpdater.java +++ b/java/daikon/test/split/SplitterFactoryTestUpdater.java @@ -45,11 +45,8 @@ private SplitterFactoryTestUpdater() {} // blocks public constructor * splitter files against the goal files. One should recompile SplitterFactoryTest after running * this method. * - *

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 with {@code --update-goals}. - * - * @param args either empty, or {@code --update-goals} to also move the new splitter java files - * into the target directory as goal files + * @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; @@ -127,8 +124,8 @@ private static void generateSplitters(List spinfos, List decls) /** * Records the class names of the generated splitter files, which are in the tempDir. * - * @param updateGoals if true, also move the generated splitter files to the target directory as - * goal files + * @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()); @@ -136,6 +133,18 @@ private static void processFiles(boolean updateGoals) { if (fileNames == null) { throw new Error("tmpdir = " + tempDir + " which is not a directory"); } + if (updateGoals) { + // Remove goal files for splitters that SplitterFactory no longer produces. + 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]; @@ -392,7 +401,13 @@ 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) { From 22f695c80af1ade2417558f3c94351abd8e9d300 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 12 Oct 2026 05:16:21 -0700 Subject: [PATCH 7/7] Regenerate SplitterFactoryTest when Daikon is recompiled; deterministic test order; share the updater recipe --- java/Makefile | 16 ++++++++++------ .../test/split/SplitterFactoryTestUpdater.java | 6 +++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/java/Makefile b/java/Makefile index 3b29cbd76b..79fb0f2f8a 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1466,12 +1466,19 @@ 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} @@ -1482,10 +1489,7 @@ ${AUTO_GENERATED_CLASSES_2}: ${AUTO_GENERATED_FILES_2} .PHONY: update-splitter-goals update-splitter-goals: daikon/Daikon.class daikon/test/split/SplitterFactoryTestUpdater.class rm -f daikon/test/split/SplitterFactoryTest.java - (${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater --update-goals > /dev/null 2>&1) \ - || ${JAVA_COMMAND} daikon.test.split.SplitterFactoryTestUpdater --update-goals - chmod a-w daikon/test/split/SplitterFactoryTest.java - ${MAKE} ${AUTO_GENERATED_CLASSES_2} + ${MAKE} SPLITTER_UPDATER_ARGS=--update-goals ${AUTO_GENERATED_CLASSES_2} # Create the auto-generated files with jpp .PHONY: jpp diff --git a/java/daikon/test/split/SplitterFactoryTestUpdater.java b/java/daikon/test/split/SplitterFactoryTestUpdater.java index ed0d7aac0f..03b2d648d0 100644 --- a/java/daikon/test/split/SplitterFactoryTestUpdater.java +++ b/java/daikon/test/split/SplitterFactoryTestUpdater.java @@ -12,6 +12,7 @@ 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; @@ -133,8 +134,11 @@ private static void processFiles(boolean updateGoals) { 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 goal files for splitters that SplitterFactory no longer produces. + // 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");