Skip to content

[GR-68627] Add TimeLimit utility and use it in DumpPathTest. #11952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
import java.util.ArrayList;
import java.util.List;

import jdk.graal.compiler.core.GraalCompilerOptions;
import org.graalvm.collections.EconomicMap;
import org.junit.Test;

import jdk.graal.compiler.core.GraalCompilerOptions;
import jdk.graal.compiler.debug.DebugOptions;
import jdk.graal.compiler.debug.DebugOptions.PrintGraphTarget;
import jdk.graal.compiler.debug.TTY;
Expand All @@ -45,46 +45,61 @@
* Check that setting the dump path results in files ending up in the right directory with matching
* names.
*/
public class DumpPathTest extends GraalCompilerTest {
public class DumpPathTest extends SubprocessTest {

/**
* If this test does not complete in 60 seconds, something is very wrong.
*/
private static final int TIME_LIMIT = 60000;

public static Object snippet() {
return new String("snippet");
}

@Test
public void testDump() throws Exception {
public void test() throws IOException, InterruptedException {
launchSubprocess(this::runInSubprocess, "-Xmx50M");

}

public void runInSubprocess() {
assumeManagementLibraryIsLoadable();
try (TemporaryDirectory temp = new TemporaryDirectory("DumpPathTest")) {
String[] extensions = {".cfg", ".bgv", ".graph-strings"};
EconomicMap<OptionKey<?>, Object> overrides = OptionValues.newOptionMap();
overrides.put(DebugOptions.DumpPath, temp.toString());
overrides.put(DebugOptions.ShowDumpFiles, false);
overrides.put(DebugOptions.PrintBackendCFG, true);
overrides.put(DebugOptions.PrintGraph, PrintGraphTarget.File);
overrides.put(DebugOptions.PrintCanonicalGraphStrings, true);
overrides.put(DebugOptions.Dump, "*");
overrides.put(GraalCompilerOptions.DumpHeapAfter, "<compilation>:Schedule");
overrides.put(DebugOptions.MethodFilter, null);
try (var _ = new TTY.Filter();
TimeLimit _ = TimeLimit.create(TIME_LIMIT, "DumpPathTest")) {
try (TemporaryDirectory temp = new TemporaryDirectory("DumpPathTest")) {
String[] extensions = {".cfg", ".bgv", ".graph-strings"};
EconomicMap<OptionKey<?>, Object> overrides = OptionValues.newOptionMap();
overrides.put(DebugOptions.DumpPath, temp.toString());
overrides.put(DebugOptions.ShowDumpFiles, false);
overrides.put(DebugOptions.PrintBackendCFG, true);
overrides.put(DebugOptions.PrintGraph, PrintGraphTarget.File);
overrides.put(DebugOptions.PrintCanonicalGraphStrings, true);
overrides.put(DebugOptions.Dump, "*");
overrides.put(GraalCompilerOptions.DumpHeapAfter, "<compilation>:Schedule");
overrides.put(DebugOptions.MethodFilter, null);

try (AutoCloseable _ = new TTY.Filter()) {
// Generate dump files.
test(new OptionValues(getInitialOptions(), overrides), "snippet");
}
// Check that IGV files got created, in the right place.
List<Path> paths = checkForFiles(temp.path, extensions);
List<Path> compilationHeapDumps = new ArrayList<>();
List<Path> phaseHeapDumps = new ArrayList<>();
for (Path path : paths) {
String name = path.toString();
if (name.endsWith(".compilation.hprof")) {
compilationHeapDumps.add(path);
} else if (name.endsWith(".hprof")) {
phaseHeapDumps.add(path);
try (var _ = new TTY.Filter()) {
// Generate dump files.
test(new OptionValues(getInitialOptions(), overrides), "snippet");
}
// Check that IGV files got created, in the right place.
List<Path> paths = checkForFiles(temp.path, extensions);
List<Path> compilationHeapDumps = new ArrayList<>();
List<Path> phaseHeapDumps = new ArrayList<>();
for (Path path : paths) {
String name = path.toString();
if (name.endsWith(".compilation.hprof")) {
compilationHeapDumps.add(path);
} else if (name.endsWith(".hprof")) {
phaseHeapDumps.add(path);
}
}
}

assertTrue(!compilationHeapDumps.isEmpty());
assertTrue(!phaseHeapDumps.isEmpty());
assertTrue(!compilationHeapDumps.isEmpty());
assertTrue(!phaseHeapDumps.isEmpty());
}
} catch (IOException e) {
throw new AssertionError(e);
}
}

Expand All @@ -99,6 +114,7 @@ private static List<Path> checkForFiles(Path directoryPath, String[] extensions)
for (Path filePath : stream) {
result.add(filePath);
String fileName = filePath.getFileName().toString();
System.out.printf("%s -> %,d bytes%n", filePath, Files.size(filePath));
for (int i = 0; i < extensions.length; i++) {
String extension = extensions[i];
if (fileName.endsWith(extensions[i])) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@
import java.util.List;
import java.util.function.Predicate;

import jdk.graal.compiler.test.GraalTest;
import jdk.graal.compiler.test.SubprocessUtil;
import jdk.graal.compiler.test.SubprocessUtil.Subprocess;

/**
* Utility class for executing Graal compiler tests in a subprocess. This can be useful for tests
* that need special VM arguments or that produce textual output or a special process termination
* status that need to be analyzed. The class to be executed may be the current class or any other
* unit test class.
* status that need to be analyzed. Another use case is a test that behaves very differently when
* run after many other tests (that fill up the heap and pollute profiles). The class to be executed
* may be the current class or any other unit test class.
* <p/>
* If the test class contains multiple {@code @Test} methods, they will all be executed in the
* subprocess, except when using one of the methods that take a {@code testSelector} argument. All
Expand All @@ -59,24 +61,24 @@
public abstract class SubprocessTest extends GraalCompilerTest {

/**
* Launches the {@code runnable} in a subprocess, with any extra {@code args} passed as
* arguments to the subprocess VM. Checks that the subprocess terminated successfully, i.e., an
* exit code different from 0 raises an error.
*
* @return Inside the subprocess, returns {@code null}. Outside the subprocess, returns a
* {@link Subprocess} instance describing the process after its successful termination.
* Calls {@link #launchSubprocess(Predicate, boolean, Class, String, Runnable, String...)} with
* the given args, {@code vmArgsFilter=null}, {@code check=true}, {@code testClass=getClass()}
* and {@code testSelector=currentUnitTestName()}.
*/
public SubprocessUtil.Subprocess launchSubprocess(Runnable runnable, String... args) throws InterruptedException, IOException {
return launchSubprocess(null, null, true, getClass(), currentUnitTestName(), runnable, args);
}

public SubprocessUtil.Subprocess launchSubprocess(Predicate<String> vmArgsFilter, Runnable runnable, String... args) throws InterruptedException, IOException {
return launchSubprocess(null, vmArgsFilter, true, getClass(), currentUnitTestName(), runnable, args);
public SubprocessUtil.Subprocess launchSubprocess(Runnable runnable, String... extraVmArgs) throws InterruptedException, IOException {
return launchSubprocess(null, true, getClass(), currentUnitTestName(), runnable, extraVmArgs);
}

public static SubprocessUtil.Subprocess launchSubprocess(Class<? extends GraalCompilerTest> testClass, String testSelector, Runnable runnable, String... args)
throws InterruptedException, IOException {
return launchSubprocess(null, null, true, testClass, testSelector, runnable, args);
/**
* Calls {@link #launchSubprocess(Predicate, boolean, Class, String, Runnable, String...)} with
* the given args, {@code vmArgsFilter=null} and {@code check=true}.
*/
public static SubprocessUtil.Subprocess launchSubprocess(
Class<? extends GraalCompilerTest> testClass,
String testSelector,
Runnable runnable,
String... extraVmArgs) throws InterruptedException, IOException {
return launchSubprocess(null, true, testClass, testSelector, runnable, extraVmArgs);
}

private static List<String> filter(List<String> args, Predicate<String> vmArgsFilter) {
Expand Down Expand Up @@ -106,17 +108,36 @@ private static String getRecursionPropName(Class<? extends GraalCompilerTest> te
return "test." + testClass.getName() + ".subprocess";
}

public static SubprocessUtil.Subprocess launchSubprocess(Predicate<List<String>> testPredicate, Predicate<String> vmArgsFilter, boolean expectNormalExit,
Class<? extends GraalCompilerTest> testClass, String testSelector, Runnable runnable, String... args)
throws InterruptedException, IOException {
/**
* Launches {@code runnable} in a subprocess.
*
* @param runnable task to be run in the subprocess
* @param vmArgsFilter filters the VM args to only those matching this predicate
* @param check if true, and the process exits with a non-zero exit code, an AssertionError
* exception will be thrown
* @param testClass the class defining the test
* @param testSelector name of the current test. This is typically provided by
* {@link GraalTest#currentUnitTestName()}. Use {@link #ALL_TESTS} to denote that all
* tests in {@code testClass} are to be run.
* @param extraVmArgs extra VM args to pass to the subprocess
* @return returns {@code null} when run in the subprocess. Outside the subprocess, returns a
* {@link Subprocess} instance describing the process after its successful termination.
*/
public static SubprocessUtil.Subprocess launchSubprocess(
Predicate<String> vmArgsFilter,
boolean check,
Class<? extends GraalCompilerTest> testClass,
String testSelector,
Runnable runnable,
String... extraVmArgs) throws InterruptedException, IOException {
if (isRecursiveLaunch(testClass)) {
runnable.run();
return null;
} else {
List<String> vmArgs = withoutDebuggerArguments(getVMCommandLine());
vmArgs.add(SubprocessUtil.PACKAGE_OPENING_OPTIONS);
vmArgs.add("-D" + getRecursionPropName(testClass) + "=true");
vmArgs.addAll(Arrays.asList(args));
vmArgs.addAll(Arrays.asList(extraVmArgs));
if (vmArgsFilter != null) {
vmArgs = filter(vmArgs, vmArgsFilter);
}
Expand All @@ -132,13 +153,9 @@ public static SubprocessUtil.Subprocess launchSubprocess(Predicate<List<String>>
}
SubprocessUtil.Subprocess proc = java(vmArgs, mainClassAndArgs);

if (testPredicate != null && !testPredicate.test(proc.output)) {
fail("Subprocess produced unexpected output:%n%s", proc.preserveArgfile());
}
int exitCode = proc.exitCode;
if ((exitCode == 0) != expectNormalExit) {
String expectExitCode = expectNormalExit ? "0" : "non-0";
fail("Subprocess produced exit code %d, but expected %s%n%s", exitCode, expectExitCode, proc.preserveArgfile());
if (check && exitCode != 0) {
fail("Subprocess produced non-0 exit code %d%n%s", exitCode, proc.preserveArgfile());
}

// Test passed
Expand All @@ -148,5 +165,4 @@ public static SubprocessUtil.Subprocess launchSubprocess(Predicate<List<String>>
return proc;
}
}

}
Loading
Loading