8339366: [jittester] Make it possible to generate tests without execution

Reviewed-by: lmesnik
This commit is contained in:
Evgeny Nikitin 2024-09-09 19:55:45 +00:00 committed by Leonid Mesnik
parent 6b5958d661
commit 559fc711e0
7 changed files with 188 additions and 80 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2024, 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
@ -23,13 +23,6 @@
package jdk.test.lib.jittester;
import jdk.test.lib.util.Pair;
import jdk.test.lib.jittester.factories.IRNodeBuilder;
import jdk.test.lib.jittester.types.TypeKlass;
import jdk.test.lib.jittester.utils.FixedTrees;
import jdk.test.lib.jittester.utils.OptionResolver;
import jdk.test.lib.jittester.utils.OptionResolver.Option;
import jdk.test.lib.jittester.utils.PseudoRandom;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
@ -39,57 +32,6 @@ import java.util.function.Function;
public class Automatic {
public static final int MINUTES_TO_WAIT = Integer.getInteger("jdk.test.lib.jittester", 3);
private static Pair<IRNode, IRNode> generateIRTree(String name) {
ProductionLimiter.resetTimer();
SymbolTable.removeAll();
TypeList.removeAll();
IRNodeBuilder builder = new IRNodeBuilder()
.setPrefix(name)
.setName(name)
.setLevel(0);
Long complexityLimit = ProductionParams.complexityLimit.value();
IRNode privateClasses = null;
if (!ProductionParams.disableClasses.value()) {
long privateClassComlexity = (long) (complexityLimit * PseudoRandom.random());
try {
privateClasses = builder.setComplexityLimit(privateClassComlexity)
.getClassDefinitionBlockFactory()
.produce();
} catch (ProductionFailedException ex) {
ex.printStackTrace(System.out);
}
}
long mainClassComplexity = (long) (complexityLimit * PseudoRandom.random());
IRNode mainClass = null;
try {
mainClass = builder.setComplexityLimit(mainClassComplexity)
.getMainKlassFactory()
.produce();
TypeKlass aClass = new TypeKlass(name);
mainClass.getChild(1).addChild(FixedTrees.generateMainOrExecuteMethod(aClass, true));
mainClass.getChild(1).addChild(FixedTrees.generateMainOrExecuteMethod(aClass, false));
} catch (ProductionFailedException ex) {
ex.printStackTrace(System.out);
}
return new Pair<>(mainClass, privateClasses);
}
private static void initializeTestGenerator(String[] params) {
OptionResolver parser = new OptionResolver();
Option<String> propertyFileOpt = parser.addStringOption('p', "property-file",
"conf/default.properties", "File to read properties from");
ProductionParams.register(parser);
parser.parse(params, propertyFileOpt);
PseudoRandom.reset(ProductionParams.seed.value());
TypesParser.parseTypesAndMethods(ProductionParams.classesFile.value(),
ProductionParams.excludeMethodsFile.value());
if (ProductionParams.specificSeed.isSet()) {
PseudoRandom.setCurrentSeed(ProductionParams.specificSeed.value());
}
}
private static List<TestsGenerator> getTestGenerators() {
List<TestsGenerator> result = new ArrayList<>();
Class<?> factoryClass;
@ -109,7 +51,9 @@ public class Automatic {
}
public static void main(String[] args) {
initializeTestGenerator(args);
ProductionParams.initializeFromCmdline(args);
IRTreeGenerator.initializeWithProductionParams();
int counter = 0;
System.out.printf("Generating %d tests...%n", ProductionParams.numberOfTests.value());
System.out.printf(" %13s | %8s | %8s | %8s |%n", "start time", "count", "generat",
@ -121,7 +65,7 @@ public class Automatic {
try {
System.out.print("[" + LocalTime.now() + "] |");
String name = "Test_" + counter;
Pair<IRNode, IRNode> irTree = generateIRTree(name);
var test = IRTreeGenerator.generateIRTree(name);
System.out.printf(" %8d |", counter);
long maxWaitTime = TimeUnit.MINUTES.toMillis(MINUTES_TO_WAIT);
double generationTime = System.currentTimeMillis() - start;
@ -129,7 +73,7 @@ public class Automatic {
start = System.currentTimeMillis();
Thread generatorThread = new Thread(() -> {
for (TestsGenerator generator : generators) {
generator.accept(irTree.first, irTree.second);
generator.accept(test);
}
});
generatorThread.start();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2024, 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
@ -47,16 +47,17 @@ class ByteCodeGenerator extends TestsGenerator {
}
@Override
public void accept(IRNode mainClass, IRNode privateClasses) {
generateClassFiles(mainClass, privateClasses);
generateSeparateJtregHeader(mainClass);
public void accept(IRTreeGenerator.Test test) {
IRNode mainClass = test.mainClass();
generateClassFiles(mainClass, test.privateClasses());
generateSeparateJtregHeader(test.seed(), mainClass);
compilePrinter();
generateGoldenOut(mainClass.getName());
}
private void generateSeparateJtregHeader(IRNode mainClass) {
private void generateSeparateJtregHeader(long seed, IRNode mainClass) {
String mainClassName = mainClass.getName();
writeFile(generatorDir, mainClassName + ".java", getJtregHeader(mainClassName));
writeFile(generatorDir, mainClassName + ".java", getJtregHeader(mainClassName, seed));
}
private void generateClassFiles(IRNode mainClass, IRNode privateClasses) {
@ -94,4 +95,17 @@ class ByteCodeGenerator extends TestsGenerator {
ex.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
ProductionParams.initializeFromCmdline(args);
IRTreeGenerator.initializeWithProductionParams();
ByteCodeGenerator generator = new ByteCodeGenerator();
for (String mainClass : ProductionParams.mainClassNames.value()) {
var test = IRTreeGenerator.generateIRTree(mainClass);
generator.generateClassFiles(test.mainClass(), test.privateClasses());
generator.generateSeparateJtregHeader(test.seed(), test.mainClass());
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2024, 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 jdk.test.lib.jittester;
import jdk.test.lib.jittester.factories.IRNodeBuilder;
import jdk.test.lib.jittester.types.TypeKlass;
import jdk.test.lib.jittester.utils.FixedTrees;
import jdk.test.lib.jittester.utils.PseudoRandom;
/**
* Generates IR trees for fuzzy test classes.
*/
public class IRTreeGenerator {
/**
* Generated Test - main and private classes trees along with random seed used for generation.
*/
public record Test (long seed, IRNode mainClass, IRNode privateClasses) {};
/**
* Generates IR trees for main and private classes.
*
* @param name main class name
* @return a pair (main class; private classes)
*/
public static Test generateIRTree(String name) {
long seed = PseudoRandom.getCurrentSeed();
ProductionLimiter.resetTimer();
//NB: SymbolTable is a widely-used singleton, hence all the locking.
SymbolTable.removeAll();
TypeList.removeAll();
IRNodeBuilder builder = new IRNodeBuilder()
.setPrefix(name)
.setName(name)
.setLevel(0);
Long complexityLimit = ProductionParams.complexityLimit.value();
IRNode privateClasses = null;
if (!ProductionParams.disableClasses.value()) {
long privateClassComlexity = (long) (complexityLimit * PseudoRandom.random());
try {
privateClasses = builder.setComplexityLimit(privateClassComlexity)
.getClassDefinitionBlockFactory()
.produce();
} catch (ProductionFailedException ex) {
ex.printStackTrace(System.out);
}
}
long mainClassComplexity = (long) (complexityLimit * PseudoRandom.random());
IRNode mainClass = null;
try {
mainClass = builder.setComplexityLimit(mainClassComplexity)
.getMainKlassFactory()
.produce();
TypeKlass aClass = new TypeKlass(name);
mainClass.getChild(1).addChild(FixedTrees.generateMainOrExecuteMethod(aClass, true));
mainClass.getChild(1).addChild(FixedTrees.generateMainOrExecuteMethod(aClass, false));
} catch (ProductionFailedException ex) {
ex.printStackTrace(System.out);
}
return new Test(seed, mainClass, privateClasses);
}
/**
* Initializes the generator from ProductionParams static class.
*/
public static void initializeWithProductionParams() {
TypesParser.parseTypesAndMethods(ProductionParams.classesFile.value(),
ProductionParams.excludeMethodsFile.value());
if (ProductionParams.specificSeed.isSet()) {
PseudoRandom.setCurrentSeed(ProductionParams.specificSeed.value());
}
}
}

View File

@ -42,19 +42,20 @@ public class JavaCodeGenerator extends TestsGenerator {
}
@Override
public void accept(IRNode mainClass, IRNode privateClasses) {
public void accept(IRTreeGenerator.Test test) {
IRNode mainClass = test.mainClass();
String mainClassName = mainClass.getName();
generateSources(mainClass, privateClasses);
generateSources(test.seed(), mainClass, test.privateClasses());
compilePrinter();
compileJavaFile(mainClassName);
generateGoldenOut(mainClassName);
}
private void generateSources(IRNode mainClass, IRNode privateClasses) {
private void generateSources(long seed, IRNode mainClass, IRNode privateClasses) {
String mainClassName = mainClass.getName();
StringBuilder code = new StringBuilder();
JavaCodeVisitor vis = new JavaCodeVisitor();
code.append(getJtregHeader(mainClassName));
code.append(getJtregHeader(mainClassName, seed));
if (privateClasses != null) {
code.append(privateClasses.accept(vis));
}
@ -79,7 +80,19 @@ public class JavaCodeGenerator extends TestsGenerator {
}
}
private static String[] generatePrerunAction(String mainClassName) {
protected static String[] generatePrerunAction(String mainClassName) {
return new String[] {"@compile " + mainClassName + ".java"};
}
public static void main(String[] args) throws Exception {
ProductionParams.initializeFromCmdline(args);
IRTreeGenerator.initializeWithProductionParams();
JavaCodeGenerator generator = new JavaCodeGenerator();
for (String mainClass : ProductionParams.mainClassNames.value()) {
var test = IRTreeGenerator.generateIRTree(mainClass);
generator.generateSources(test.seed(), test.mainClass(), test.privateClasses());
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2024, 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
@ -23,11 +23,15 @@
package jdk.test.lib.jittester;
import java.util.List;
import jdk.test.lib.jittester.utils.OptionResolver;
import jdk.test.lib.jittester.utils.OptionResolver.Option;
import jdk.test.lib.jittester.utils.PseudoRandom;
public class ProductionParams {
public static Option<List<String>> mainClassNames = null;
public static Option<Integer> productionLimit = null;
public static Option<Integer> productionLimitSeconds = null;
public static Option<Integer> dataMemberLimit = null;
@ -72,6 +76,7 @@ public class ProductionParams {
// workaraound: to reduce chance throwing ArrayIndexOutOfBoundsException
public static Option<Integer> chanceExpressionIndex = null;
public static Option<String> testbaseDir = null;
public static Option<String> tempDir = null;
public static Option<Integer> numberOfTests = null;
public static Option<String> seed = null;
public static Option<Long> specificSeed = null;
@ -81,6 +86,7 @@ public class ProductionParams {
public static Option<String> generatorsFactories = null;
public static void register(OptionResolver optionResolver) {
mainClassNames = optionResolver.addRepeatingOption('k', "main-class", "", "Main class name");
productionLimit = optionResolver.addIntegerOption('l', "production-limit", 100, "Limit on steps in the production of an expression");
productionLimitSeconds = optionResolver.addIntegerOption("production-limit-seconds", 600, "Limit the time a test generation may take");
dataMemberLimit = optionResolver.addIntegerOption('v', "data-member-limit", 10, "Upper limit on data members");
@ -124,6 +130,7 @@ public class ProductionParams {
enableFinalizers = optionResolver.addBooleanOption("enable-finalizers", "Enable finalizers (for stress testing)");
chanceExpressionIndex = optionResolver.addIntegerOption("chance-expression-index", 0, "A non negative decimal integer used to restrict chane of generating expression in array index while creating or accessing by index");
testbaseDir = optionResolver.addStringOption("testbase-dir", ".", "Testbase dir");
tempDir = optionResolver.addStringOption("temp-dir", ".", "Temp dir path");
numberOfTests = optionResolver.addIntegerOption('n', "number-of-tests", 0, "Number of test classes to generate");
seed = optionResolver.addStringOption("seed", "", "Random seed");
specificSeed = optionResolver.addLongOption('z', "specificSeed", 0L, "A seed to be set for specific test generation(regular seed still needed for initialization)");
@ -132,4 +139,18 @@ public class ProductionParams {
generators = optionResolver.addStringOption("generators", "", "Comma-separated list of generator names");
generatorsFactories = optionResolver.addStringOption("generatorsFactories", "", "Comma-separated list of generators factories class names");
}
/**
* Initializes from the given command-line args
*
* @param args command-line arguments to use for initialization
*/
public static void initializeFromCmdline(String[] args) {
OptionResolver parser = new OptionResolver();
Option<String> propertyFileOpt = parser.addStringOption('p', "property-file",
"conf/default.properties", "File to read properties from");
ProductionParams.register(parser);
parser.parse(args, propertyFileOpt);
PseudoRandom.reset(ProductionParams.seed.value());
}
}

View File

@ -30,13 +30,12 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import jdk.test.lib.jittester.types.TypeKlass;
import jdk.test.lib.jittester.utils.PseudoRandom;
public abstract class TestsGenerator implements BiConsumer<IRNode, IRNode> {
public abstract class TestsGenerator implements Consumer<IRTreeGenerator.Test> {
private static final int DEFAULT_JTREG_TIMEOUT = 120;
protected static final String JAVA_BIN = getJavaPath();
protected static final String JAVAC = Paths.get(JAVA_BIN, "javac").toString();
@ -121,9 +120,9 @@ public abstract class TestsGenerator implements BiConsumer<IRNode, IRNode> {
}
}
protected String getJtregHeader(String mainClassName) {
protected String getJtregHeader(String mainClassName, long seed) {
String synopsis = "seed = '" + ProductionParams.seed.value() + "'"
+ ", specificSeed = '" + PseudoRandom.getCurrentSeed() + "'";
+ ", specificSeed = '" + seed + "'";
StringBuilder header = new StringBuilder();
header.append("/*\n * @test\n * @summary ")
.append(synopsis)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2024, 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
@ -156,6 +156,12 @@ public class OptionResolver {
return addBooleanOption(null, name, false, description);
}
public Option<List<String>> addRepeatingOption(Character key, String name, String defaultValue, String description) {
final Option<List<String>> option = new RepeatingOption(key, name, defaultValue, description);
register(option);
return option;
}
private void register(Option<?> option) {
if (options.put("--" + option.longName, option) != null) {
throw new RuntimeException("Option is already registered for key " + option.longName);
@ -264,6 +270,20 @@ public class OptionResolver {
}
}
private class RepeatingOption extends Option<List<String>> {
List<String> accumulated = new ArrayList<String>();
RepeatingOption(Character s, String l, String v, String d) {
super(s, l, List.of(v), d);
}
@Override
public List<String> parseFromString(String arg) {
accumulated.add(arg);
return accumulated;
}
}
public Collection<Option<?>> getRegisteredOptions() {
return Collections.unmodifiableSet(new LinkedHashSet<>(options.values()));
}