--- /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 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); + } + + } +}