--- /dev/null 2018-05-14 10:15:02.574213890 -0700
+++ new/test/hotspot/jtreg/vmTestbase/vm/runtime/defmeth/shared/builder/TestBuilder.java 2018-05-21 10:53:12.645502496 -0700
@@ -0,0 +1,529 @@
+/*
+ * Copyright (c) 2013, 2018, 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 vm.runtime.defmeth.shared.builder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.*;
+
+import nsk.share.Pair;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+import nsk.share.TestFailure;
+import vm.runtime.defmeth.shared.ClassFileGenerator;
+import vm.runtime.defmeth.shared.Constants;
+import vm.runtime.defmeth.shared.DefMethTest;
+import vm.runtime.defmeth.shared.DefMethTestFailure;
+import vm.runtime.defmeth.shared.ExecutionMode;
+import vm.runtime.defmeth.shared.executor.GeneratedTest;
+import vm.runtime.defmeth.shared.MemoryClassLoader;
+import vm.runtime.defmeth.shared.executor.MHInvokeWithArgsTest;
+import vm.runtime.defmeth.shared.Printer;
+import vm.runtime.defmeth.shared.executor.ReflectionTest;
+import vm.runtime.defmeth.shared.executor.TestExecutor;
+import vm.runtime.defmeth.shared.Util;
+
+import vm.runtime.defmeth.shared.data.Clazz;
+import vm.runtime.defmeth.shared.data.ConcreteClass;
+import vm.runtime.defmeth.shared.data.ConcreteClassLazyAdapter;
+import vm.runtime.defmeth.shared.data.Interface;
+import vm.runtime.defmeth.shared.data.InterfaceLazyAdapter;
+import vm.runtime.defmeth.shared.data.Tester;
+
+/**
+ * Builder for test cases.
+ *
+ * Simplifies construction of test cases.
+ *
+ * Example:
+ *
+ * TestBuilder b = new TestBuilder();
+ *
+ * Interface I = b.intf("I").build();
+ * ConcreteClazz C = b.clazz("C").implements(I).build();
+ *
+ * b.test().callSite(I, C, "hashCode","()I").returns(0).build();
+ *
+ * b.run();
+ *
+ *
+ * produces
+ *
+ *
+ * interface I {}
+ *
+ * class C implements I {}
+ *
+ * class Test1 {
+ * public void test() {
+ * I i = new C();
+ * if (c.hashCode() != 0) {
+ * throw new RuntimeException("Expected 0");
+ * }
+ * }
+ * }
+ *
+ */
+public class TestBuilder {
+ // Class file major version
+ // Used for separate compilation scenarios
+ public final int minMajorVer;
+
+ // Additional access flags for all methods
+ public final int accFlags;
+
+ // Redefine classes as part of testing
+ public final boolean redefineClasses;
+
+ // Redefine classes using Retransformation API
+ public final boolean retransformClasses;
+
+ public final ExecutionMode executionMode;
+
+ // GeneratedTest counter which is used to name the tests.
+ private int testNo = 0;
+
+ // List of classes constructed for the testcase so far
+ private List elements = new ArrayList<>();
+ public List getElements() {
+ return new ArrayList<>(elements);
+ }
+
+ // List of tests constructed as part of the testcase
+ private List tests = new ArrayList<>();
+
+ // Elements under construction
+ private Set> underConstruction = new LinkedHashSet<>();
+
+ private DefMethTest testInstance;
+
+ TestBuilder(DefMethTest testInstance, int minMajorVer, int accFlags,
+ boolean redefineClasses, boolean retransformClasses, ExecutionMode executionMode) {
+ this.testInstance = testInstance;
+ this.minMajorVer = minMajorVer;
+ this.accFlags = accFlags;
+ this.redefineClasses = redefineClasses;
+ this.retransformClasses = retransformClasses;
+ this.executionMode = executionMode;
+ }
+
+ /**
+ * Factory method for Interface builder.
+ *
+ * @param name
+ * @return class builder
+ */
+ public InterfaceBuilder intf(String name) {
+ InterfaceBuilder b = new InterfaceBuilder(this).name(name);
+ underConstruction.add(b);
+ return b;
+ }
+
+ /**
+ * Factory method for Clazz builder.
+ *
+ * @param name
+ * @return class builder
+ */
+ public ConcreteClassBuilder clazz(String name) {
+ ConcreteClassBuilder b = new ConcreteClassBuilder(this).name(name).ver(minMajorVer);
+ underConstruction.add(b);
+ return b;
+ }
+
+ /**
+ * Factory method for Tester builder.
+ *
+ * @return test builder
+ */
+ public TesterBuilder test() {
+ TesterBuilder b = new TesterBuilder(++testNo, this);
+ underConstruction.add(b);
+ return b;
+ }
+
+ /**
+ * Find previously constructed class by it's name.
+ *
+ * The method is considered safe: if it fails to find a class, it throws
+ * IllegalArgumentException.
+ *
+ * @param name
+ * @return
+ */
+ public Clazz lookup(String name) {
+ for (Clazz clz : elements) {
+ if (clz.name().equals(name)) {
+ return clz;
+ }
+ }
+
+ throw new IllegalArgumentException("Unknown element: " + name);
+ }
+
+ /**
+ * Lazy binding of {@code Clazz} instance
+ *
+ * @param name
+ * @return
+ */
+ public ConcreteClass clazzByName(String name) {
+ return new ConcreteClassLazyAdapter(this, name);
+ }
+
+ /**
+ * Lazy binding of {@code Interface} instance
+ *
+ * @param name
+ * @return
+ */
+ public Interface intfByName(String name) {
+ return new InterfaceLazyAdapter(this, name);
+ }
+
+ /**
+ * Construct corresponding {@code Clazz} instance for a {@code Class}.
+ *
+ * @param cls
+ * @return
+ */
+ public Clazz toClazz(Class cls) {
+ String name = cls.getName();
+
+ if (hasElement(name)) {
+ return lookup(name);
+ } else {
+ return clazz(name).build();
+ }
+ }
+
+ /**
+ * Construct corresponding {@code ConcreteClass} instance for a {@code Class}.
+ *
+ * Throws {@code IllegalArgumentException} if {@code Class} can't be
+ * represented as {@code ConcreteClass}
+ *
+ * @param cls
+ * @return
+ */
+ public ConcreteClass toConcreteClass(Class cls) {
+ if (!cls.isInterface()) {
+ return (ConcreteClass)toClazz(cls);
+ } else {
+ throw new IllegalArgumentException(cls.getName());
+ }
+ }
+
+ /**
+ * Factory method for Method builder.
+ *
+ * @return method builder
+ */
+ /* package-private */ MethodBuilder method() {
+ return new MethodBuilder(this);
+ }
+
+ /**
+ * Factory method for Data.DefaultMethod builder.
+ *
+ * @param name method name
+ * @return
+ */
+ public MethodBuilder defaultMethod(String name) {
+ return method().name(name).type(MethodType.DEFAULT);
+ }
+
+ /**
+ * Factory method for Data.AbstractMethod builder.
+ *
+ * @param name
+ * @return
+ */
+ public MethodBuilder abstractMethod(String name) {
+ return method().name(name).type(MethodType.ABSTRACT);
+ }
+
+ /**
+ * Factory method for Data.ConcreteMethod builder.
+ *
+ * @param name
+ * @return
+ */
+ public MethodBuilder concreteMethod(String name) {
+ return method().name(name).type(MethodType.CONCRETE);
+ }
+
+ /**
+ * Signal that {@code Builder>} instance won't be used anymore.
+ *
+ * @param builder
+ */
+ /* package-private */ void finishConstruction(Builder> builder) {
+ if (underConstruction.contains(builder)) {
+ underConstruction.remove(builder);
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Register class with the test builder, so it'll be enumerated during
+ * hierarchy traversals (e.g. class file generation, hierarchy printing).
+ *
+ * @param clz
+ * @return
+ */
+ public TestBuilder register(Clazz clz) {
+ elements.add(clz);
+ return this;
+ }
+
+ /**
+ * Register class with the test builder as a test, so it'll be enumerated during
+ * hierarchy traversals and executed during test runs.
+ *
+ * @param test
+ * @return
+ */
+ public TestBuilder register(Tester test) {
+ tests.add(test);
+ return this;
+ }
+
+ /**
+ * Check whether a class with some name has been already constructed.
+ *
+ * @param name
+ * @return whether a class with the same name has been already created
+ */
+ /* package-private */ boolean hasElement(String name) {
+ for (Clazz clz : elements) {
+ if (clz.name().equals(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Create all classes and return a class loader which can load them.
+ *
+ * @return class loader instance which loads all generated classes
+ */
+ public MemoryClassLoader build() {
+ Map classes = produce();
+
+ MemoryClassLoader cl = new MemoryClassLoader(classes);
+
+ return cl;
+ }
+
+ /**
+ * Produce class files for a set of {@code Clazz} instances.
+ *
+ * @return
+ */
+ public Map produce() {
+ if (!underConstruction.isEmpty()) {
+ throw new IllegalStateException("Some of the classes haven't been fully constructed");
+ }
+
+ List items = new ArrayList<>();
+ items.addAll(elements);
+ items.addAll(tests);
+
+ return produce(52, items);
+ }
+
+ /**
+ * Produce class files for {@Clazz} instances from {@code elements}.
+ *
+ * @param defaultMajorVer
+ * @param elements
+ * @return
+ */
+ private Map produce(int defaultMajorVer, List extends Clazz> elements) {
+ LinkedHashMap classes = new LinkedHashMap<>();
+
+ if (Constants.PRINT_TESTS) {
+ System.out.printf("\nTEST: %s\n\n", Util.getTestName());
+ }
+
+ for (Clazz clazz : elements) {
+ if (Constants.PRINT_TESTS) {
+ System.out.println(Printer.print(clazz));
+ }
+
+ if (clazz instanceof Tester &&
+ (executionMode == ExecutionMode.REFLECTION ||
+ executionMode == ExecutionMode.INVOKE_WITH_ARGS)) {
+ // No need to generate testers for reflection cases
+ continue;
+ }
+
+ Pair p = produceClassFile(defaultMajorVer, executionMode, clazz);
+ classes.put(p.first, p.second);
+ }
+
+ if (Constants.PRINT_ASSEMBLY) {
+ System.out.println("\nDISASSEMBLY");
+
+ for (byte[] cf : classes.values()) {
+ Util.printClassFile(cf);
+ System.out.println();
+ }
+ }
+
+ if (Constants.ASMIFY) {
+ System.out.println("\nASM");
+
+ for (byte[] cf : classes.values()) {
+ Util.asmifyClassFile(cf);
+ System.out.println();
+ }
+ }
+
+ if (Constants.DUMP_CLASSES) {
+ try {
+ File dumpDir = new File("DUMP_CLASS_FILES", testInstance.shortTestName);
+ if (!dumpDir.exists()) {
+ dumpDir.mkdirs();
+ }
+
+ for (Map.Entry entry : classes.entrySet()) {
+ String name = entry.getKey();
+ byte[] classFile = entry.getValue();
+ File dumpFile = new File(dumpDir, name+".class");
+ dumpFile.getParentFile().mkdirs();
+ try (FileOutputStream file = new FileOutputStream(dumpFile)) {
+ file.write(classFile);
+ }
+ }
+ } catch (Exception e) {
+ throw new Error(e);
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Produce class file from {@code Clazz} instance.
+ *
+ * @param defaultMajorVer
+ * @param clazz
+ * @return
+ */
+ public static Pair produceClassFile(int defaultMajorVer,
+ ExecutionMode execMode, Clazz clazz) {
+ int majorVer = clazz.ver() != 0 ? clazz.ver() : defaultMajorVer;
+
+ ClassFileGenerator cfg = new ClassFileGenerator(majorVer, ACC_PUBLIC, execMode);
+ clazz.visit(cfg);
+
+ byte[] classFile = cfg.getClassFile();
+
+ return Pair.of(clazz.name(), classFile);
+ }
+
+ /**
+ * Make all preparations for execution.
+ *
+ * @return
+ */
+ public TestExecutor prepare() {
+ return prepare(build());
+ }
+
+ private TestExecutor prepare(MemoryClassLoader cl) {
+ if (redefineClasses) {
+ try {
+ cl.modifyClasses(/* retransform = */ false);
+ } catch (TestFailure e) {
+ testInstance.getLog().info(e.getMessage());
+ throw e;
+ }
+ }
+
+ if (retransformClasses) {
+ try {
+ cl.modifyClasses(/* redefine = */ true);
+ } catch (TestFailure e) {
+ testInstance.getLog().info(e.getMessage());
+ throw e;
+ }
+ }
+
+ switch (executionMode) {
+ case DIRECT:
+ case INDY:
+ case INVOKE_EXACT:
+ case INVOKE_GENERIC:
+ // Run tests using direct invocation methods
+ return new GeneratedTest(cl, testInstance, tests);
+ case REFLECTION:
+ // Use reflection for testing
+ return new ReflectionTest(cl, this, testInstance, tests);
+ case INVOKE_WITH_ARGS:
+ return new MHInvokeWithArgsTest(cl, testInstance, tests);
+ default:
+ throw new Error("Unknown execution mode: " + executionMode);
+ }
+ }
+
+ public interface LoaderConstructor {
+ public MemoryClassLoader construct(Map< String,byte[]> classFiles);
+ }
+
+ /**
+ * Customize class loader construction.
+ *
+ * @param constructLoader
+ * @return
+ */
+ public TestExecutor prepare(LoaderConstructor constructLoader) {
+ return prepare(constructLoader.construct(produce()));
+ }
+
+ /**
+ * Construct a test with all necessary classes and execute all tests
+ * from it.
+ */
+ public void run() {
+ if (tests.isEmpty()) {
+ throw new IllegalStateException("No tests to run");
+ }
+
+ TestExecutor executor = prepare();
+
+ List> errors = executor.run();
+
+ if (!errors.isEmpty()) {
+ throw new DefMethTestFailure(errors);
+ }
+
+ }
+}