From b85fe02be5966b72ea1a92bfb3faf088d310219a Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 12 Jun 2025 14:12:14 +0000 Subject: [PATCH] 8358600: Template-Framework Library: Template for TestFramework test class Reviewed-by: chagedorn, mhaessig --- .../library/TestFrameworkClass.java | 119 +++++++++++++ .../examples/TestWithTestFrameworkClass.java | 163 ++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java new file mode 100644 index 00000000000..5194b75af43 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework.library; + +import java.util.List; +import java.util.Set; + +import compiler.lib.ir_framework.TestFramework; +import compiler.lib.compile_framework.CompileFramework; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; + +/** + * This class provides a {@link #render} method that can be used to simplify generating + * source code when using the {@link TestFramework} (also known as IR Framework) to run + * a list of tests. + * + *

+ * The idea is that the user only has to generate the code for the individual tests, + * and can then pass the corresponding list of {@link TemplateToken}s to this + * provided {@link #render} method which generates the surrounding class and the main + * method that invokes the {@link TestFramework}, so that all the generated tests + * are run. + */ +public final class TestFrameworkClass { + + // Ensure there can be no instance, and we do not have to document the constructor. + private TestFrameworkClass() {} + + /** + * This method renders a list of {@code testTemplateTokens} into the body of a class + * and generates a {@code main} method which launches the {@link TestFramework} + * to run the generated tests. + * + *

+ * The generated {@code main} method is to be invoked with a {@code vmFlags} argument, + * which must be a {@link String[]}, specifying the VM flags for the Test VM, in which + * the tests will be run. Thus, one can generate the test class once, and invoke its + * {@code main} method multiple times, each time with a different set of VM flags. + * + *

+ * The internal {@link Template} sets the {@link Hooks#CLASS_HOOK} for the scope of + * all test methods. + * + * @param packageName The package name of the test class. + * @param className The name of the test class. + * @param imports A set of imports. + * @param classpath The classpath from {@link CompileFramework#getEscapedClassPathOfCompiledClasses}, + * so that the Test VM has access to the class files that are compiled from the + * generated source code. + * @param testTemplateTokens The list of tests to be generated into the test class. + * Every test must be annotated with {@code @Test}, so that + * the {@link TestFramework} can later find and run them. + * @return The generated source code of the test class as a {@link String}. + */ + public static String render(final String packageName, + final String className, + final Set imports, + final String classpath, + final List testTemplateTokens) { + var template = Template.make(() -> body( + let("packageName", packageName), + let("className", className), + let("classpath", classpath), + """ + package #packageName; + // --- IMPORTS start --- + import compiler.lib.ir_framework.*; + """, + imports.stream().map(i -> "import " + i + ";\n").toList(), + """ + // --- IMPORTS end --- + public class #className { + // --- CLASS_HOOK insertions start --- + """, + Hooks.CLASS_HOOK.anchor( + """ + // --- CLASS_HOOK insertions end --- + public static void main(String[] vmFlags) { + TestFramework framework = new TestFramework(#className.class); + framework.addFlags("-classpath", "#classpath"); + framework.addFlags(vmFlags); + framework.start(); + } + // --- LIST OF TESTS start --- + """, + testTemplateTokens + ), + """ + // --- LIST OF TESTS end --- + } + """ + )); + return template.render(); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java new file mode 100644 index 00000000000..813f2976ef2 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test TestFrameworkClass.TEMPLATE which allows generating many tests and running them with the IR TestFramework. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @compile ../../../compiler/lib/ir_framework/TestFramework.java + * @compile ../../../compiler/lib/generators/Generators.java + * @compile ../../../compiler/lib/verify/Verify.java + * @run driver template_framework.examples.TestWithTestFrameworkClass + */ + +package template_framework.examples; + +import java.util.List; +import java.util.Set; + +import compiler.lib.compile_framework.CompileFramework; + +import compiler.lib.generators.Generators; + +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; + +import compiler.lib.template_framework.library.Hooks; +import compiler.lib.template_framework.library.TestFrameworkClass; + +/** + * This is a basic IR verification test, in combination with Generators for random input generation + * and Verify for output verification. + *

+ * The "@compile" command for JTREG is required so that the frameworks used in the Template code + * are compiled and available for the Test-VM. + *

+ * Additionally, we must set the classpath for the Test VM, so that it has access to all compiled + * classes (see {@link CompileFramework#getEscapedClassPathOfCompiledClasses}). + */ +public class TestWithTestFrameworkClass { + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add a java source file. + comp.addJavaSourceCode("p.xyz.InnerTest", generate(comp)); + + // Compile the source file. + comp.compile(); + + // p.xyz.InnterTest.main(new String[] {}); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {}}); + + // We can also pass VM flags for the Test VM. + // p.xyz.InnterTest.main(new String[] {"-Xbatch"}); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {"-Xbatch"}}); + } + + // Generate a source Java file as String + public static String generate(CompileFramework comp) { + // A simple template that adds a comment. + var commentTemplate = Template.make(() -> body( + """ + // Comment inserted from test method to class hook. + """ + )); + + // We define a Test-Template: + // - static fields for inputs: INPUT_A and INPUT_B + // - Data generated with Generators and hashtag replacement #con1. + // - GOLD value precomputed with dedicated call to test. + // - This ensures that the GOLD value is computed in the interpreter + // most likely, since the test method is not yet compiled. + // This allows us later to compare to the results of the compiled + // code. + // The input data is cloned, so that the original INPUT_A is never + // modified and can serve as identical input in later calls to test. + // - In the Setup method, we clone the input data, since the input data + // could be modified inside the test method. + // - The test method makes use of hashtag replacements (#con2 and #op). + // - The Check method verifies the results of the test method with the + // GOLD value. + var testTemplate = Template.make("op", (String op) -> body( + let("size", Generators.G.safeRestrict(Generators.G.ints(), 10_000, 20_000).next()), + let("con1", Generators.G.ints().next()), + let("con2", Generators.G.safeRestrict(Generators.G.ints(), 1, Integer.MAX_VALUE).next()), + """ + // --- $test start --- + // $test with size=#size and op=#op + private static int[] $INPUT_A = new int[#size]; + static { + Generators.G.fill(Generators.G.ints(), $INPUT_A); + } + private static int $INPUT_B = #con1; + private static Object $GOLD = $test($INPUT_A.clone(), $INPUT_B); + + @Setup + public static Object[] $setup() { + // Must make sure to clone input arrays, if it is mutated in the test. + return new Object[] {$INPUT_A.clone(), $INPUT_B}; + } + + @Test + @Arguments(setup = "$setup") + public static Object $test(int[] a, int b) { + for (int i = 0; i < a.length; i++) { + int con = #con2; + a[i] = (a[i] * con) #op b; + } + return a; + } + + @Check(test = "$test") + public static void $check(Object result) { + Verify.checkEQ(result, $GOLD); + } + // --- $test end --- + """, + // Good to know: we can insert to the class hook, which is set for the + // TestFrameworkClass scope: + Hooks.CLASS_HOOK.insert(commentTemplate.asToken()) + )); + + // Create a test for each operator. + List ops = List.of("+", "-", "*", "&", "|"); + List testTemplateTokens = ops.stream().map(testTemplate::asToken).toList(); + + // Create the test class, which runs all testTemplateTokens. + return TestFrameworkClass.render( + // package and class name. + "p.xyz", "InnerTest", + // Set of imports. + Set.of("compiler.lib.generators.*", + "compiler.lib.verify.*"), + // classpath, so the Test VM has access to the compiled class files. + comp.getEscapedClassPathOfCompiledClasses(), + // The list of tests. + testTemplateTokens); + } +}