From e56db37734aa7cbc0f20ba3fc469f51224f288fa Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Thu, 16 Oct 2025 16:02:26 +0000 Subject: [PATCH] 8369232: testlibrary_tests/ir_framework/tests/TestScenariosCrossProduct.java timed out Reviewed-by: dfenacci, epeter --- .../lib/ir_framework/TestFramework.java | 27 +- .../tests/TestScenariosCrossProduct.java | 384 +++++++++++++----- 2 files changed, 305 insertions(+), 106 deletions(-) diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java index 85c52ef33da..09e291ce5a4 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java @@ -327,8 +327,10 @@ public class TestFramework { for (Scenario scenario : scenarios) { int scenarioIndex = scenario.getIndex(); - TestFormat.checkNoThrow(scenarioIndices.add(scenarioIndex), - "Cannot define two scenarios with the same index " + scenarioIndex); + if (!scenarioIndices.add(scenarioIndex)) { + TestFormat.failNoThrow("Cannot define two scenarios with the same index " + scenarioIndex); + continue; + } this.scenarios.add(scenario); } TestFormat.throwIfAnyFailures(); @@ -336,9 +338,12 @@ public class TestFramework { } /** - * Add the cross-product (cartesian product) of sets of flags as Scenarios. Unlike when when constructing + * Add the cross-product (cartesian product) of sets of flags as Scenarios. Unlike when constructing * scenarios directly a string can contain multiple flags separated with a space. This allows grouping - * flags that have to be specified togeher. Further, an empty string in a set stands in for "no flag". + * flags that have to be specified together. Further, an empty string in a set stands in for "no flag". + *

+ * Passing a single set will create a scenario for each of the provided flags in the set (i.e. the same as + * passing an additional set with an empty string only). *

* Example: *

@@ -355,7 +360,7 @@ public class TestFramework {
      *     Scenario(5, "-Xbatch -XX:-TieredCompilation", "-XX:+UseNewCode2")
      * 
* - * @param sets sets of flags to generate the cross product for. + * @param flagSets sets of flags to generate the cross product for. * @return the same framework instance. */ @SafeVarargs @@ -376,7 +381,7 @@ public class TestFramework { Stream> crossProduct = Arrays.stream(flagSets) .reduce( - Stream.of(Collections.emptyList()), // Initialize Stream> acc with a Stream containing an empty list of Strings. + Stream.of(Collections.emptyList()), // Initialize Stream> acc with a Stream containing an empty list of Strings. (Stream> acc, Set set) -> acc.flatMap(lAcc -> // For each List> lAcc in acc... set.stream().map(flag -> { // ...and each flag in the current set... @@ -384,19 +389,19 @@ public class TestFramework { newList.add(flag); // ...and append the flag. return newList; }) // This results in one List> for each lAcc... - ), // ...that get flattend into one big List>. - (a, b) -> Stream.concat(a, b)); // combiner; if any reduction steps are executed in parallel, just concat two streams. + ), // ...that get flattened into one big List>. + Stream::concat); // combiner; if any reduction steps are executed in parallel, just concat two streams. Scenario[] newScenarios = crossProduct .map(flags -> new Scenario( // For each List flags in crossProduct create a new Scenario. idx.getAndIncrement(), flags.stream() // Process flags - .map(s -> Set.of(s.split("[ ]"))) // Split muliple flags in the same string into separate strings. + .map(s -> Set.of(s.split("[ ]"))) // Split multiple flags in the same string into separate strings. .flatMap(Collection::stream) // Flatten the Stream> into Stream>. .filter(s -> !s.isEmpty()) // Remove empty string flags. - .collect(Collectors.toList()) + .toList() .toArray(new String[0]))) - .collect(Collectors.toList()).toArray(new Scenario[0]); + .toList().toArray(new Scenario[0]); return addScenarios(newScenarios); } diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestScenariosCrossProduct.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestScenariosCrossProduct.java index 496fcbddb0f..46813bbff78 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestScenariosCrossProduct.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestScenariosCrossProduct.java @@ -23,15 +23,22 @@ package ir_framework.tests; -import java.util.Set; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import compiler.lib.ir_framework.*; -import compiler.lib.ir_framework.shared.TestRunException; import compiler.lib.ir_framework.shared.TestFormatException; +import compiler.lib.ir_framework.shared.TestRunException; import jdk.test.lib.Asserts; /* * @test + * @bug 8365262 8369232 * @requires vm.debug == true & vm.compMode != "Xint" & vm.compiler2.enabled & vm.flagless * @summary Test cross product scenarios with the framework. * @library /test/lib /testlibrary_tests / @@ -39,29 +46,20 @@ import jdk.test.lib.Asserts; */ public class TestScenariosCrossProduct { - static void hasNFailures(String s, int count) { - if (!s.matches("The following scenarios have failed: (#[0-9](, )?){" + count + "}. Please check stderr for more information.")) { - throw new RuntimeException("Expected " + count + " failures in \"" + s + "\""); - } - } public static void main(String[] args) { - // Test argument handling - try { - TestFramework t = new TestFramework(); - t.addCrossProductScenarios((Set[]) null); - Asserts.fail("Should have thrown exception"); - } catch (TestFormatException e) {} - try { - TestFramework t = new TestFramework(); - t.addCrossProductScenarios(Set.of("foo", "bar"), null); - Asserts.fail("Should have thrown exception"); - } catch (TestFormatException e) {} + expectFormatFailure((Set[]) null); + expectFormatFailure(Set.of("foo", "bar"), null); + try { TestFramework t = new TestFramework(); t.addCrossProductScenarios(Set.of("blub"), Set.of("foo", null)); - Asserts.fail("Should have thrown exception"); - } catch (NullPointerException e) {} // Set.of prevents null elements + shouldHaveThrown(); + } catch (NullPointerException _) { + // Expected: Set.of prevents null elements + } + + try { TestFramework t = new TestFramework(); t.addCrossProductScenarios(); @@ -70,95 +68,291 @@ public class TestScenariosCrossProduct { } // Single set should test all flags in the set by themselves. - try { - TestFramework t1 = new TestFramework(); - t1.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=51", - "-XX:TLABRefillWasteFraction=53", - "-XX:TLABRefillWasteFraction=64")); - t1.start(); - Asserts.fail("Should have thrown exception"); - } catch (TestRunException e) { - hasNFailures(e.getMessage(), 3); - } + new TestCase() + .inputFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=51", + "-XX:TLABRefillWasteFraction=53", + "-XX:TLABRefillWasteFraction=64") + ) + ) + .expectedScenariosWithFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=51"), + Set.of("-XX:TLABRefillWasteFraction=53"), + Set.of("-XX:TLABRefillWasteFraction=64") + )) + .run(); // The cross product of a set with one element and a set with three elements is three sets. - try { - TestFramework t2 = new TestFramework(); - t2.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=53"), - Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2", "-XX:+UseNewCode3")); - t2.start(); - Asserts.fail("Should have thrown exception"); - } catch (TestRunException e) { - hasNFailures(e.getMessage(), 3); - } + new TestCase() + .inputFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=53"), + Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2", "-XX:+UseNewCode3") + ) + ) + .expectedScenariosWithFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode3") + )) + .run(); + // The cross product of two sets with two elements is four sets. - try { - TestFramework t3 = new TestFramework(); - t3.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=53", "-XX:TLABRefillWasteFraction=64"), - Set.of("-XX:+UseNewCode", "-XX:-UseNewCode")); - t3.start(); - Asserts.fail("Should have thrown exception"); - } catch (TestRunException e) { - hasNFailures(e.getMessage(), 4); - } + new TestCase() + .inputFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:TLABRefillWasteFraction=64"), + Set.of("-XX:+UseNewCode", "-XX:-UseNewCode") + ) + ) + .expectedScenariosWithFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:-UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=64", "-XX:+UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=64", "-XX:-UseNewCode") + )) + .run(); + // Test with a pair of flags. - try { - TestFramework t4 = new TestFramework(); - t4.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=50 -XX:+UseNewCode", "-XX:TLABRefillWasteFraction=40"), - Set.of("-XX:+UseNewCode2")); - t4.start(); - Asserts.fail("Should have thrown exception"); - } catch (TestRunException e) { - hasNFailures(e.getMessage(), 1); - } + new TestCase() + .inputFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=50 -XX:+UseNewCode", "-XX:TLABRefillWasteFraction=40"), + Set.of("-XX:+UseNewCode2") + ) + ) + .expectedScenariosWithFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=50", "-XX:+UseNewCode", "-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=40", "-XX:+UseNewCode2") + )) + .run(); - // Test with an empty string. All 6 scenarios fail because 64 is the default value for TLABRefillWasteFraction. - try { - TestFramework t5 = new TestFramework(); - t5.addCrossProductScenarios(Set.of("", "-XX:TLABRefillWasteFraction=51", "-XX:TLABRefillWasteFraction=53"), - Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2")); - t5.start(); - Asserts.fail("Should have thrown exception"); - } catch (TestRunException e) { - hasNFailures(e.getMessage(), 6); - } + // Test with an empty string, resulting in 6 scenarios. + new TestCase() + .inputFlags(Set.of( + Set.of("", "-XX:TLABRefillWasteFraction=51", "-XX:TLABRefillWasteFraction=53"), + Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2") + ) + ) + .expectedScenariosWithFlags(Set.of( + Set.of("-XX:+UseNewCode"), + Set.of("-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=51", "-XX:+UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=51", "-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode2") + )) + .run(); + // Test with 3 input sets which equals to 2x2x2 = 8 scenarios. + new TestCase() + .inputFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=51", + "-XX:TLABRefillWasteFraction=53"), + Set.of("-XX:+UseNewCode", + "-XX:-UseNewCode"), + Set.of("-XX:+UseNewCode2", + "-XX:-UseNewCode2") + ) + ) + .expectedScenariosWithFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=51", "-XX:+UseNewCode", "-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode", "-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=51", "-XX:-UseNewCode", "-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:-UseNewCode", "-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=51", "-XX:+UseNewCode", "-XX:-UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode", "-XX:-UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=51", "-XX:-UseNewCode", "-XX:-UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:-UseNewCode", "-XX:-UseNewCode2") + )) + .run(); + + TestFramework testFramework = new TestFramework(); + testFramework.addScenarios(new Scenario(0, "-XX:TLABRefillWasteFraction=50", "-XX:+UseNewCode")); + testFramework.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=51", "-XX:TLABRefillWasteFraction=53"), + Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2")); try { - TestFramework t6 = new TestFramework(); - t6.addScenarios(new Scenario(0, "-XX:TLABRefillWasteFraction=50", "-XX:+UseNewCode")); // failPair - t6.addCrossProductScenarios(Set.of("-XX:TLABRefillWasteFraction=51", "-XX:TLABRefillWasteFraction=53"), - Set.of("-XX:+UseNewCode", "-XX:+UseNewCode2")); - try { - t6.addScenarios(new Scenario(4, "-XX:+UseNewCode3")); // fails because index 4 is already used - Asserts.fail("Should have thrown exception"); - } catch (TestFormatException e) {} - t6.addScenarios(new Scenario(5, "-XX:+UseNewCode3")); // fail default - t6.start(); - Asserts.fail("Should have thrown exception"); - } catch (TestRunException e) { - hasNFailures(e.getMessage(), 6); + testFramework.addScenarios(new Scenario(4, "-XX:+UseNewCode3")); // fails because index 4 is already used + shouldHaveThrown(); + } catch (TestFormatException _) { + // Expected. + } + testFramework.addScenarios(new Scenario(5, "-XX:+UseNewCode3")); + + new TestCase() + .expectedScenariosWithFlags(Set.of( + Set.of("-XX:TLABRefillWasteFraction=50", "-XX:+UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=51", "-XX:+UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=51", "-XX:+UseNewCode2"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode"), + Set.of("-XX:TLABRefillWasteFraction=53", "-XX:+UseNewCode2"), + Set.of("-XX:+UseNewCode3") + )) + .runWithPreAddedScenarios(testFramework); + + runEndToEndTest(); + } + + private static void expectFormatFailure(Set... flagSets) { + TestFramework testFramework = new TestFramework(); + try { + testFramework.addCrossProductScenarios(flagSets); + shouldHaveThrown(); + } catch (TestFormatException _) { + // Expected. } } - @Test - @IR(applyIf = {"TLABRefillWasteFraction", "64"}, counts = {IRNode.CALL, "1"}) - public void failDefault() { + private static void shouldHaveThrown() { + Asserts.fail("Should have thrown exception"); + } + + static class TestCase { + private Set> inputFlags; + private Set> expectedScenariosWithFlags; + + public TestCase inputFlags(Set> inputFlags) { + this.inputFlags = inputFlags; + return this; + } + + public TestCase expectedScenariosWithFlags(Set> expectedScenariosWithFlags) { + this.expectedScenariosWithFlags = expectedScenariosWithFlags; + return this; + } + + public void run() { + TestFramework testFramework = new TestFramework(); + testFramework.addCrossProductScenarios(inputFlags.toArray(new Set[0])); + runWithPreAddedScenarios(testFramework); + } + + public void runWithPreAddedScenarios(TestFramework testFramework) { + List scenariosFromCrossProduct = getScenarios(testFramework); + assertScenarioCount(expectedScenariosWithFlags.size(), scenariosFromCrossProduct); + assertScenariosWithFlags(scenariosFromCrossProduct, expectedScenariosWithFlags); + assertSameResultWhenManuallyAdding(scenariosFromCrossProduct, expectedScenariosWithFlags); + } + + private static void assertScenarioCount(int expectedCount, List scenarios) { + Asserts.assertEQ(expectedCount, scenarios.size(), "Scenario count is off"); + } + + /** + * Check that the added scenarios to the IR framework with TestFramework.addCrossProductScenarios() + * (i.e. 'scenariosFromCrossProduct') match the expected flag combos (i.e. 'expectedScenariosWithFlags'). + */ + private static void assertScenariosWithFlags(List scenariosFromCrossProduct, + Set> expectedScenariosWithFlags) { + for (Set expectedScenarioFlags : expectedScenariosWithFlags) { + if (scenariosFromCrossProduct.stream() + .map(Scenario::getFlags) + .map(Set::copyOf) + .anyMatch(flags -> flags.equals(expectedScenarioFlags))) { + continue; + } + System.err.println("Scenarios from cross product:"); + for (Scenario s : scenariosFromCrossProduct) { + System.err.println(Arrays.toString(s.getFlags().toArray())); + } + throw new RuntimeException("Could not find a scenario with the provided flags: " + Arrays.toString(expectedScenarioFlags.toArray())); + } + } + + /** + * Add scenarios for the provided flag sets in 'expectedScenariosWithFlags' by using TestFramework.addScenarios(). + * We should end up with the same scenarios as if we added them with TestFramework.addCrossProductScenarios(). + * This is verified by this method by comparing the flags of the scenarios, ignoring scenario indices. + */ + private static void assertSameResultWhenManuallyAdding(List scenariosFromCrossProduct, + Set> expectedScenariosWithFlags) { + List expectedScenarios = getScenariosWithFlags(expectedScenariosWithFlags); + List fetchedScenarios = addScenariosAndFetchFromFramework(expectedScenarios); + assertSameScenarios(scenariosFromCrossProduct, fetchedScenarios); + } + + private static List getScenariosWithFlags(Set> expectedScenariosWithFlags) { + List expecedScenarioList = new ArrayList<>(); + int index = -1; // Use some different indices - should not matter what we choose. + for (Set expectedScenarioFlags : expectedScenariosWithFlags) { + expecedScenarioList.add(new Scenario(index--, expectedScenarioFlags.toArray(new String[0]))); + } + return expecedScenarioList; + } + + private static List addScenariosAndFetchFromFramework(List expecedScenarioList) { + TestFramework testFramework = new TestFramework(); + testFramework.addScenarios(expecedScenarioList.toArray(new Scenario[0])); + return getScenarios(testFramework); + } + + private static void assertSameScenarios(List scenariosFromCrossProduct, + List expectedScenarios) { + assertScenariosWithFlags(scenariosFromCrossProduct, fetchFlags(expectedScenarios)); + } + + private static Set> fetchFlags(List scenarios) { + return scenarios.stream() + .map(scenario -> new HashSet<>(scenario.getFlags())) + .collect(Collectors.toSet()); + } + } + + private static List getScenarios(TestFramework testFramework) { + Field field; + try { + field = TestFramework.class.getDeclaredField("scenarios"); + field.setAccessible(true); + return (List)field.get(testFramework); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * Also run a simple end-to-end test to sanity check the API method. We capture the stderr to fetch the + * scenario flags. + */ + private static void runEndToEndTest() { + TestFramework testFramework = new TestFramework(); + + // Capture stderr + PrintStream originalErr = System.err; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(outputStream); + System.setErr(printStream); + + try { + testFramework + .addCrossProductScenarios(Set.of("-XX:+UseNewCode", "-XX:-UseNewCode"), + Set.of("-XX:+UseNewCode2", "-XX:-UseNewCode2")) + .addFlags() + .start(); + shouldHaveThrown(); + } catch (TestRunException e) { + // Expected. + System.setErr(originalErr); + Asserts.assertTrue(e.getMessage().contains("The following scenarios have failed: #0, #1, #2, #3.")); + String stdErr = outputStream.toString(); + Asserts.assertTrue(stdErr.contains("Scenario flags: [-XX:+UseNewCode, -XX:+UseNewCode2]")); + Asserts.assertTrue(stdErr.contains("Scenario flags: [-XX:-UseNewCode, -XX:-UseNewCode2]")); + Asserts.assertTrue(stdErr.contains("Scenario flags: [-XX:+UseNewCode, -XX:-UseNewCode2]")); + Asserts.assertTrue(stdErr.contains("Scenario flags: [-XX:-UseNewCode, -XX:+UseNewCode2]")); + Asserts.assertEQ(4, scenarioCount(stdErr)); + } + } + + public static int scenarioCount(String stdErr) { + Pattern pattern = Pattern.compile("Scenario flags"); + Matcher matcher = pattern.matcher(stdErr); + int count = 0; + while (matcher.find()) { + count++; + } + return count; } @Test - @IR(applyIf = {"TLABRefillWasteFraction", "51"}, counts = {IRNode.CALL, "1"}) - public void fail1() { - } - - @Test - @IR(applyIf = {"TLABRefillWasteFraction", "53"}, counts = {IRNode.CALL, "1"}) - public void fail2() { - } - - @Test - @IR(applyIfAnd = {"TLABRefillWasteFraction", "50", "UseNewCode", "true"}, counts = {IRNode.CALL, "1"}) - public void failPair() { + public void endToEndTest() { + throw new RuntimeException("executed test"); } }