8358600: Template-Framework Library: Template for TestFramework test class

Reviewed-by: chagedorn, mhaessig
This commit is contained in:
Emanuel Peter 2025-06-12 14:12:14 +00:00
parent e18277b470
commit b85fe02be5
2 changed files with 282 additions and 0 deletions

View File

@ -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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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<String> imports,
final String classpath,
final List<TemplateToken> 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();
}
}

View File

@ -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.
* <p>
* The "@compile" command for JTREG is required so that the frameworks used in the Template code
* are compiled and available for the Test-VM.
* <p>
* 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<String> ops = List.of("+", "-", "*", "&", "|");
List<TemplateToken> 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);
}
}