RFR: 8344942: Template-Based Testing Framework [v71]

Christian Hagedorn chagedorn at openjdk.org
Mon Jun 2 13:58:22 UTC 2025


On Mon, 2 Jun 2025 03:30:24 GMT, Emanuel Peter <epeter at openjdk.org> wrote:

>> **Goal**
>> We want to generate Java source code:
>> - Make it easy to generate variants of tests. E.g. for each offset, for each operator, for each type, etc.
>> - Enable the generation of domain specific fuzzers (e.g. random expressions and statements).
>> 
>> Note: with the Template Library draft I was already able to find a [list of bugs](https://bugs.openjdk.org/issues/?jql=labels%20%3D%20template-framework%20ORDER%20BY%20created%20DESC%2C%20summary%20DESC).
>> 
>> **How to get started**
>> When reviewing, please start by looking at:
>> https://github.com/openjdk/jdk/blob/d21a8aabaf3b191e851b6997c11bb30fcd0f942f/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java#L60-L76
>> 
>> We have a Template with two arguments. They are typed (Integer and String). We then apply the arguments `template.withArgs(42, "7")`, producing a `TemplateWithArgs`. This can then be `render`ed to a String. And then that can be compiled and executed with the CompileFramework.
>> 
>> Second, look at this advanced test:
>> https://github.com/openjdk/jdk/blob/77079807042fc5a3af04e0ccccad4ecd89e21cdb/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java#L102-L119
>> 
>> And then for a "tutorial", look at:
>> `test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java`
>> 
>> It shows these features:
>> - The `body` of a Template is essentially a list of `Token`s that are concatenated.
>> - Templates can be nested: a `TemplateWithArgs` is also a `Token`.
>> - We can use `#name` replacements to directly format values into the String. If we had proper String Templates in Java, we would not need this feature.
>> - We can use `$var` to make variable names unique: if we applied the same template twice, we would get variable collisions. `$var` is then replaced with e.g. `var_7` in one template use and `var_42` in the other template use.
>> - The use of `Hook`s to insert code into outer (earlier) code locations. This is useful, for example, to insert fields on demand.
>> - The use of recursive templates, and `fuel` to limit the recursion.
>> - `Name`s: useful to register field and variable names in code scopes.
>> 
>> Next, look at the documentation in. This file is the heart of the Template Framework, and describes all the important features.
>> https://github.com/openjdk/jdk/blob/d21a8aabaf3b191e851b6997c11bb30fcd0f942f/test/hotspot/jtreg/compiler/lib/template_framework/Template.java#L31-L76
>> 
>> For a better experience, you may want...
>
> Emanuel Peter has updated the pull request with a new target base due to a merge or a rebase. The pull request now contains 91 commits:
> 
>  - Merge branch 'master' into JDK-8344942-TemplateFramework-v3
>  - validation tests
>  - dollar and hashtag parsing validatiaon
>  - wip refactor parsing dollar and hashtag
>  - more fixes from Christian
>  - more improvements
>  - more suggestions applied
>  - good practice
>  - rename template arguments
>  - more from Christian
>  - ... and 81 more: https://git.openjdk.org/jdk/compare/90d6ad01...cb7037e7

I worked my way through the rest of the implementation. Impressive work Emanuel! I left some more mostly minor comments. But otherwise, this looks great!

test/hotspot/jtreg/compiler/lib/template_framework/Code.java line 26:

> 24: package compiler.lib.template_framework;
> 25: 
> 26: import java.util.ArrayList;

Unused:
Suggestion:

test/hotspot/jtreg/compiler/lib/template_framework/Code.java line 33:

> 31:  * All the {@link String}s are later collected in a {@link StringBuilder}. If we used a {@link StringBuilder}
> 32:  * directly to collect the {@link String}s, we could not as easily insert code at an "earlier" position, i.e.
> 33:  * reaching out to a {@link Hook#set}.

Suggestion:

 * reaching out to a {@link Hook#anchor}.

test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java line 37:

> 35:  * When a {@link Hook} is {@link Hook#set}, this separates the Template into an outer and inner
> 36:  * {@link CodeFrame}, ensuring that names that are {@link Template#addName}'d inside the inner frame
> 37:  * are only available inside that frame.

Still references old method names. Suggestion:

Suggestion:

 * The {@link CodeFrame} represents a frame (i.e. scope) of code, appending {@link Code} to the {@code 'codeList'}
 * as {@link Token}s are rendered, and adding names to the {@link NameSet}s with {@link Template#addStructuralName}/
 * {@link Template#addDataName}. {@link Hook}s can be added to a frame, which allows code to be inserted at that
 * location later. When a {@link Hook} is {@link Hook#anchor}ed, it separates the Template into an outer and inner
 * {@link CodeFrame}, ensuring that names that are added inside the inner frame are only available inside that frame.

test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java line 52:

> 50: class CodeFrame {
> 51:     public final CodeFrame parent;
> 52:     private final List<Code> codeList = new ArrayList<Code>();

Suggestion:

    private final List<Code> codeList = new ArrayList<>();

test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java line 58:

> 56:      * The {@link NameSet} is used for variable and fields etc.
> 57:      */
> 58:     final NameSet names;

I think this can also be made private:

Suggestion:

    private final NameSet names;

test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java line 70:

> 68:         } else {
> 69:             // New NameSet, to make sure we have a nested scope for the names.
> 70:             this.names     = new NameSet(parent.names);

Indentation is off:
Suggestion:

            this.names = parent.names;
        } else {
            // New NameSet, to make sure we have a nested scope for the names.
            this.names = new NameSet(parent.names);

test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java line 92:

> 90:     /**
> 91:      * Creates a special frame, which has a {@link #parent} but uses the {@link NameSet}
> 92:      * from the parent frame, allowing {@link Template#defineName} to persist in the outer

`defineName` -> `addName`?

test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java line 96:

> 94:      * where we would possibly want to make field or variable definitions during the insertion
> 95:      * that are not just local to the insertion but affect the {@link CodeFrame} that we
> 96:      * {@link Hook#set} earlier and are now {@link Hook#insert}ing into.

Suggestion:

     * {@link Hook#anchor} earlier and are now {@link Hook#insert}ing into.

test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java line 118:

> 116:     }
> 117: 
> 118:     boolean hasHook(Hook hook) {

Can be made private:
Suggestion:

    private boolean hasHook(Hook hook) {

test/hotspot/jtreg/compiler/lib/template_framework/DataName.java line 33:

> 31:  * count, list or even sample random {@link DataName}s. Every {@link DataName} has a {@link DataName.Type},
> 32:  * so that sampling can be restricted to these types.
> 33:  *

Suggestion:

 *
 * <p>

test/hotspot/jtreg/compiler/lib/template_framework/DataName.java line 123:

> 121:                 if (mutability == Mutability.IMMUTABLE && dn.mutable()) { return false; }
> 122:                 if (subtype != null && !dn.type().isSubtypeOf(subtype)) { return false; }
> 123:                 if (supertype != null && !supertype.isSubtypeOf(dn.type())) { return false; }

I suggest to use the full term:
Suggestion:

                if (!(name instanceof DataName dataName)) { return false; }
                if (mutability == Mutability.MUTABLE && !dataName.mutable()) { return false; }
                if (mutability == Mutability.IMMUTABLE && dataName.mutable()) { return false; }
                if (subtype != null && !dataName.type().isSubtypeOf(subtype)) { return false; }
                if (supertype != null && !supertype.isSubtypeOf(dataName.type())) { return false; }

test/hotspot/jtreg/compiler/lib/template_framework/DataName.java line 134:

> 132:          * @return The filtered {@link View}.
> 133:          * @throws UnsupportedOperationException If this {@link View} was already filtered with
> 134:          *                                       {@link subtypeOf} or {@link exactOf}.

Also for links at methods below:
Suggestion:

         *                                       {@link #subtypeOf} or {@link #exactOf}.

test/hotspot/jtreg/compiler/lib/template_framework/DataName.java line 144:

> 142: 
> 143:         /**
> 144:          * Create a filtered {@link View}, where all {@link DataName}s must be subtypes of {@code type}.

Suggestion:

         * Create a filtered {@link View}, where all {@link DataName}s must be supertypes of {@code type}.

test/hotspot/jtreg/compiler/lib/template_framework/DataName.java line 181:

> 179:          */
> 180:         public DataName sample() {
> 181:             DataName n = (DataName)Renderer.getCurrent().sampleName(predicate());

Do you really need this cast? Can't you just return a `Name`. From the uses it seems that you only call interface methods from `Name` at the use-sites.

test/hotspot/jtreg/compiler/lib/template_framework/Hook.java line 34:

> 32:  * "back" or to some outer scope, e.g. while generating code for a method, one can reach out
> 33:  * to the class scope to insert fields.
> 34:  *

Suggestion:

 *
 * <p>

test/hotspot/jtreg/compiler/lib/template_framework/Name.java line 35:

> 33:      * The name of the name, that can be used in code.
> 34:      *
> 35:      * @return The {@String} name of the name, that can be used in code.

Suggestion:

     * @return The {@link String} name of the name, that can be used in code.

test/hotspot/jtreg/compiler/lib/template_framework/Name.java line 54:

> 52:     int weight();
> 53: 
> 54:     public interface Type {

Implicitly public:
Suggestion:

    interface Type {

test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java line 38:

> 36:  */
> 37: class NameSet {
> 38:     static final Random RANDOM = Utils.getRandomInstance();

Suggestion:

    private static final Random RANDOM = Utils.getRandomInstance();

test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java line 58:

> 56: 
> 57:     private long weight(Predicate predicate) {
> 58:         long w = names.stream().filter(n -> predicate.check(n)).mapToInt(Name::weight).sum();

Suggestion:

        long w = names.stream().filter(predicate::check).mapToInt(Name::weight).sum();

test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java line 64:

> 62: 
> 63:     public int count(Predicate predicate) {
> 64:         int c = (int)names.stream().filter(n -> predicate.check(n)).count();

Suggestion:

        int c = (int)names.stream().filter(predicate::check).count();

test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java line 70:

> 68: 
> 69:     public boolean hasAny(Predicate predicate) {
> 70:         return names.stream().anyMatch(n -> predicate.check(n)) ||

Suggestion:

        return names.stream().anyMatch(predicate::check) ||

test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java line 77:

> 75:         List<Name> list = (parent != null) ? parent.toList(predicate)
> 76:                                            : new ArrayList<>();
> 77:         list.addAll(names.stream().filter(n -> predicate.check(n)).toList());

Suggestion:

        list.addAll(names.stream().filter(predicate::check).toList());

test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java line 88:

> 86:         if (w <= 0) {
> 87:             return null;
> 88:         }

Shouldn't the weight always be positive?

test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java line 66:

> 64:             // another non-capturing group.
> 65:             "(?:\\{" +
> 66:                 // capturing group for "name" inside of "{name}"

Suggestion:

                // capturing group for "name" inside "{name}"

test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java line 199:

> 197:     /**
> 198:      * Formats values to {@link String} with the goal of using them in Java code.
> 199:      * By default we use the overrides of {@link Object#toString}.

Suggestion:

     * By default, we use the overrides of {@link Object#toString}.

test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java line 266:

> 264:             case StringToken(String s) -> {
> 265:                 renderStringWithDollarAndHashtagReplacements(s);
> 266:             }

Suggestion:

            case StringToken(String s) -> renderStringWithDollarAndHashtagReplacements(s);

test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java line 321:

> 319:                 callerCodeFrame.addCode(currentCodeFrame.getCode());
> 320:                 currentCodeFrame = callerCodeFrame;
> 321:             }

For readability:
Suggestion:

            case HookInsertToken(Hook hook, TemplateToken templateToken) -> {
                // Switch to hook CodeFrame.
                CodeFrame callerCodeFrame = currentCodeFrame;
                CodeFrame hookCodeFrame = codeFrameForHook(hook);

                // Use a transparent nested CodeFrame. We need a CodeFrame so that the code generated
                // by the TemplateToken can be collected, and hook insertions from it can still
                // be made to the hookCodeFrame before the code from the TemplateToken is added to
                // the hookCodeFrame.
                // But the CodeFrame must be transparent, so that its name definitions go out to
                // the hookCodeFrame, and are not limited to the CodeFrame for the TemplateToken.
                currentCodeFrame = CodeFrame.makeTransparentForNames(hookCodeFrame);

                renderTemplateToken(templateToken);

                hookCodeFrame.addCode(currentCodeFrame.getCode());

                // Switch back from hook CodeFrame to caller CodeFrame.
                currentCodeFrame = callerCodeFrame;
            }
            case TemplateToken templateToken -> {
                // Use a nested CodeFrame.
                CodeFrame callerCodeFrame = currentCodeFrame;
                currentCodeFrame = CodeFrame.make(currentCodeFrame);

                renderTemplateToken(templateToken);

                callerCodeFrame.addCode(currentCodeFrame.getCode());
                currentCodeFrame = callerCodeFrame;
            }

test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java line 324:

> 322:             case AddNameToken(Name name) -> {
> 323:                 currentCodeFrame.addName(name);
> 324:             }

Suggestion:

            case AddNameToken(Name name) -> currentCodeFrame.addName(name);

test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java line 338:

> 336:     }
> 337: 
> 338:     private void renderStringWithDollarAndHashtagReplacements(String s) {

Hard to grasp the logic of that method. But I trust you on that :-) I leave it up to you if you want to improve readability to extract some of the logic to separate methods such that this method becomes easier to understand.

test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java line 33:

> 31:  * count, list or even sample random {@link StructuralName}s. Every {@link StructuralName} has a {@link StructuralName.Type},
> 32:  * so that sampling can be restricted to these types.
> 33:  *

Suggestion:

 *
 * <p>

test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java line 47:

> 45:      */
> 46:     public StructuralName {
> 47:     }

Is this required? Is it not automatically added? Same for `DataName`.

test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java line 68:

> 66:          */
> 67:         boolean isSubtypeOf(StructuralName.Type other);
> 68:     }

This is identical to `DataName.Type`. What is the benefit of having separate interfaces `DataName.Type` and `StructuralName.Type`? Couldn't we just move `isSubtypeOf()` directly to the `Name.Type` interface and use that one below and for the fields and expose that one instead to the user? This would mean that you can update all `DataName/StructuralName.Type` to `Name.Type`. I have not checked if this is fully possible but it just occurred to me when reviewing this duplicated interface now.

test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java line 96:

> 94:                 if (!(name instanceof StructuralName dn)) { return false; }
> 95:                 if (subtype != null && !dn.type().isSubtypeOf(subtype)) { return false; }
> 96:                 if (supertype != null && !supertype.isSubtypeOf(dn.type())) { return false; }

Suggestion:

                if (!(name instanceof StructuralName structuralName)) { return false; }
                if (subtype != null && !structuralName.type().isSubtypeOf(subtype)) { return false; }
                if (supertype != null && !supertype.isSubtypeOf(structuralName.type())) { return false; }

test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java line 107:

> 105:          * @return The filtered {@link View}.
> 106:          * @throws UnsupportedOperationException If this {@link View} was already filtered with
> 107:          *                                       {@link subtypeOf} or {@link exactOf}.

Same here and in methods below:
Suggestion:

         *                                       {@link #subtypeOf} or {@link #exactOf}.

test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java line 117:

> 115: 
> 116:         /**
> 117:          * Create a filtered {@link View}, where all {@link StructuralName}s must be subtypes of {@code type}.

Suggestion:

         * Create a filtered {@link View}, where all {@link StructuralName}s must be supertypes of {@code type}.

test/hotspot/jtreg/compiler/lib/template_framework/TemplateBinding.java line 43:

> 41:      * Creates a new {@link TemplateBinding} that has no Template bound to it yet.
> 42:      */
> 43:     public TemplateBinding() {}

Can also be removed since it's the default constructor that is automatically added for you.
Suggestion:

test/hotspot/jtreg/compiler/lib/template_framework/Token.java line 31:

> 29: 
> 30: /**
> 31:  * The {@link Template#body} and {@link Hook#set} are given a list of tokens, which are either

Suggestion:

 * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either

test/hotspot/jtreg/compiler/lib/template_framework/Token.java line 74:

> 72:             case Float s   -> outputList.add(new StringToken(Renderer.format(s)));
> 73:             case Boolean s -> outputList.add(new StringToken(Renderer.format(s)));
> 74:             case List l    -> parseList(l, outputList);

Not sure if we should use a raw `List` here. Would `List<?>` work as well? Would then need to update `parseList(List<Object> inputList ...)` to `List<?>` as well.

test/hotspot/jtreg/compiler/lib/template_framework/library/Hooks.java line 32:

> 30:  */
> 31: public abstract class Hooks {
> 32:     private Hooks() {} // Avoid instantiation and need for documentation.

With `abstract` you cannot call the constructor. But you could make `Hooks` final instead of abstract and keep the private constructor.

-------------

PR Review: https://git.openjdk.org/jdk/pull/24217#pullrequestreview-2888138689
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121190567
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121189211
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121045420
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121041625
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121043407
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121047490
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121166922
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121167248
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121170840
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121066303
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121094470
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121065215
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121100321
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121117160
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121184120
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121002671
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121005423
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121050914
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121052214
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121054013
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121054604
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121054802
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121074504
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121194420
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121221834
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121206168
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121217757
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121220490
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121228275
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121119033
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121122026
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121143144
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121124204
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121124996
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2121125964
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2120995640
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2120976577
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2120983233
PR Review Comment: https://git.openjdk.org/jdk/pull/24217#discussion_r2120989645


More information about the hotspot-compiler-dev mailing list