8358600: Template-Framework Library: Template for TestFramework test class
Reviewed-by: chagedorn, mhaessig
This commit is contained in:
parent
e18277b470
commit
b85fe02be5
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user