--- /dev/null 2019-12-03 13:57:00.000000000 -0500 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java 2019-12-03 13:56:58.446411000 -0500 @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2019, 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.jpackage.test; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import jdk.jpackage.test.Functional.ThrowingConsumer; +import jdk.jpackage.test.Functional.ThrowingFunction; +import jdk.jpackage.test.Functional.ThrowingRunnable; + +final class TestInstance implements ThrowingRunnable { + + static class TestDesc { + private TestDesc() { + } + + String testFullName() { + StringBuilder sb = new StringBuilder(); + sb.append(clazz.getSimpleName()); + if (instanceArgs != null) { + sb.append('(').append(instanceArgs).append(')'); + } + if (functionName != null) { + sb.append('.'); + sb.append(functionName); + if (functionArgs != null) { + sb.append('(').append(functionArgs).append(')'); + } + } + return sb.toString(); + } + + static Builder createBuilder() { + return new Builder(); + } + + static final class Builder implements Supplier { + private Builder() { + } + + Builder method(Method v) { + method = v; + return this; + } + + Builder ctorArgs(Object... v) { + ctorArgs = ofNullable(v); + return this; + } + + Builder methodArgs(Object... v) { + methodArgs = ofNullable(v); + return this; + } + + @Override + public TestDesc get() { + TestDesc desc = new TestDesc(); + if (method == null) { + desc.clazz = enclosingMainMethodClass(); + } else { + desc.clazz = method.getDeclaringClass(); + desc.functionName = method.getName(); + desc.functionArgs = formatArgs(methodArgs); + desc.instanceArgs = formatArgs(ctorArgs); + } + return desc; + } + + private static String formatArgs(List values) { + if (values == null) { + return null; + } + return values.stream().map(v -> { + if (v != null && v.getClass().isArray()) { + return String.format("%s(length=%d)", + Arrays.deepToString((Object[]) v), + Array.getLength(v)); + } + return String.format("%s", v); + }).collect(Collectors.joining(", ")); + } + + private static List ofNullable(Object... values) { + List result = new ArrayList(); + for (var v: values) { + result.add(v); + } + return result; + } + + private List ctorArgs; + private List methodArgs; + private Method method; + } + + static TestDesc create(Method m, Object... args) { + TestDesc desc = new TestDesc(); + desc.clazz = m.getDeclaringClass(); + desc.functionName = m.getName(); + if (args.length != 0) { + desc.functionArgs = Stream.of(args).map(v -> { + if (v.getClass().isArray()) { + return String.format("%s(length=%d)", + Arrays.deepToString((Object[]) v), + Array.getLength(v)); + } + return String.format("%s", v); + }).collect(Collectors.joining(", ")); + } + return desc; + } + + private Class clazz; + private String functionName; + private String functionArgs; + private String instanceArgs; + } + + TestInstance(ThrowingRunnable testBody) { + assertCount = 0; + this.testConstructor = (unused) -> null; + this.testBody = (unused) -> testBody.run(); + this.beforeActions = Collections.emptyList(); + this.afterActions = Collections.emptyList(); + this.testDesc = TestDesc.createBuilder().get(); + this.dryRun = false; + this.workDir = createWorkDirName(testDesc); + } + + TestInstance(MethodCall testBody, List beforeActions, + List afterActions, boolean dryRun) { + assertCount = 0; + this.testConstructor = v -> ((MethodCall)v).newInstance(); + this.testBody = testBody; + this.beforeActions = beforeActions; + this.afterActions = afterActions; + this.testDesc = testBody.createDescription(); + this.dryRun = dryRun; + this.workDir = createWorkDirName(testDesc); + } + + void notifyAssert() { + assertCount++; + } + + void notifySkipped(RuntimeException ex) { + skippedTestException = ex; + } + + boolean passed() { + return status == Status.Passed; + } + + boolean skipped() { + return status == Status.Skipped; + } + + boolean failed() { + return status == Status.Failed; + } + + String functionName() { + return testDesc.functionName; + } + + String baseName() { + return testDesc.clazz.getSimpleName(); + } + + String fullName() { + return testDesc.testFullName(); + } + + void rethrowIfSkipped() { + if (skippedTestException != null) { + throw skippedTestException; + } + } + + Path workDir() { + return workDir; + } + + @Override + public void run() throws Throwable { + final String fullName = fullName(); + TKit.log(String.format("[ RUN ] %s", fullName)); + try { + Object testInstance = testConstructor.apply(testBody); + beforeActions.forEach(a -> ThrowingConsumer.toConsumer(a).accept( + testInstance)); + try { + if (!dryRun) { + Files.createDirectories(workDir); + testBody.accept(testInstance); + } + } finally { + afterActions.forEach(a -> TKit.ignoreExceptions(() -> a.accept( + testInstance))); + } + status = Status.Passed; + } finally { + if (skippedTestException != null) { + status = Status.Skipped; + } else if (status == null) { + status = Status.Failed; + } + + if (!KEEP_WORK_DIR.contains(status)) { + TKit.deleteDirectoryRecursive(workDir); + } + + TKit.log(String.format("%s %s; checks=%d", status, fullName, + assertCount)); + } + } + + private static Class enclosingMainMethodClass() { + StackTraceElement st[] = Thread.currentThread().getStackTrace(); + for (StackTraceElement ste : st) { + if ("main".equals(ste.getMethodName())) { + return Functional.ThrowingSupplier.toSupplier(() -> Class.forName( + ste.getClassName())).get(); + } + } + return null; + } + + private static boolean isCalledByJavatest() { + StackTraceElement st[] = Thread.currentThread().getStackTrace(); + for (StackTraceElement ste : st) { + if (ste.getClassName().startsWith("com.sun.javatest.")) { + return true; + } + } + return false; + } + + private static Path createWorkDirName(TestDesc testDesc) { + Path result = Path.of("."); + if (!isCalledByJavatest()) { + result = result.resolve(testDesc.clazz.getSimpleName()); + } + + List components = new ArrayList<>(); + + final String testFunctionName = testDesc.functionName; + if (testFunctionName != null) { + components.add(testFunctionName); + } + + final boolean isPrametrized = Stream.of(testDesc.functionArgs, + testDesc.instanceArgs).anyMatch(Objects::nonNull); + if (isPrametrized) { + components.add(String.format("%08x", testDesc.testFullName().hashCode())); + } + + if (!components.isEmpty()) { + result = result.resolve(String.join(".", components)); + } + + return result; + } + + private enum Status { + Passed("[ OK ]"), + Failed("[ FAILED ]"), + Skipped("[ SKIPPED ]"); + + Status(String msg) { + this.msg = msg; + } + + @Override + public String toString() { + return msg; + } + + private final String msg; + } + + private int assertCount; + private Status status; + private RuntimeException skippedTestException; + private final TestDesc testDesc; + private final ThrowingFunction testConstructor; + private final ThrowingConsumer testBody; + private final List beforeActions; + private final List afterActions; + private final boolean dryRun; + private final Path workDir; + + private final static Set KEEP_WORK_DIR = Functional.identity( + () -> { + final String propertyName = "keep-work-dir"; + Set keepWorkDir = TKit.tokenizeConfigProperty( + propertyName); + if (keepWorkDir == null) { + return Set.of(Status.Failed); + } + + Predicate> isOneOf = options -> { + return !Collections.disjoint(keepWorkDir, options); + }; + + Set result = new HashSet<>(); + if (isOneOf.test(Set.of("pass", "p"))) { + result.add(Status.Passed); + } + if (isOneOf.test(Set.of("fail", "f"))) { + result.add(Status.Failed); + } + + return Collections.unmodifiableSet(result); + }).get(); + +}