1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package jdk.jpackage.test; 25 26 import java.lang.reflect.Array; 27 import java.lang.reflect.Method; 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.util.*; 31 import java.util.function.Predicate; 32 import java.util.function.Supplier; 33 import java.util.stream.Collectors; 34 import java.util.stream.Stream; 35 import jdk.jpackage.test.Functional.ThrowingConsumer; 36 import jdk.jpackage.test.Functional.ThrowingFunction; 37 import jdk.jpackage.test.Functional.ThrowingRunnable; 38 39 final class TestInstance implements ThrowingRunnable { 40 41 static class TestDesc { 42 private TestDesc() { 43 } 44 45 String testFullName() { 46 StringBuilder sb = new StringBuilder(); 47 sb.append(clazz.getSimpleName()); 48 if (instanceArgs != null) { 49 sb.append('(').append(instanceArgs).append(')'); 50 } 51 if (functionName != null) { 52 sb.append('.'); 53 sb.append(functionName); 54 if (functionArgs != null) { 55 sb.append('(').append(functionArgs).append(')'); 56 } 57 } 58 return sb.toString(); 59 } 60 61 static Builder createBuilder() { 62 return new Builder(); 63 } 64 65 static final class Builder implements Supplier<TestDesc> { 66 private Builder() { 67 } 68 69 Builder method(Method v) { 70 method = v; 71 return this; 72 } 73 74 Builder ctorArgs(Object... v) { 75 ctorArgs = ofNullable(v); 76 return this; 77 } 78 79 Builder methodArgs(Object... v) { 80 methodArgs = ofNullable(v); 81 return this; 82 } 83 84 @Override 85 public TestDesc get() { 86 TestDesc desc = new TestDesc(); 87 if (method == null) { 88 desc.clazz = enclosingMainMethodClass(); 89 } else { 90 desc.clazz = method.getDeclaringClass(); 91 desc.functionName = method.getName(); 92 desc.functionArgs = formatArgs(methodArgs); 93 desc.instanceArgs = formatArgs(ctorArgs); 94 } 95 return desc; 96 } 97 98 private static String formatArgs(List<Object> values) { 99 if (values == null) { 100 return null; 101 } 102 return values.stream().map(v -> { 103 if (v != null && v.getClass().isArray()) { 104 return String.format("%s(length=%d)", 105 Arrays.deepToString((Object[]) v), 106 Array.getLength(v)); 107 } 108 return String.format("%s", v); 109 }).collect(Collectors.joining(", ")); 110 } 111 112 private static List<Object> ofNullable(Object... values) { 113 List<Object> result = new ArrayList(); 114 for (var v: values) { 115 result.add(v); 116 } 117 return result; 118 } 119 120 private List<Object> ctorArgs; 121 private List<Object> methodArgs; 122 private Method method; 123 } 124 125 static TestDesc create(Method m, Object... args) { 126 TestDesc desc = new TestDesc(); 127 desc.clazz = m.getDeclaringClass(); 128 desc.functionName = m.getName(); 129 if (args.length != 0) { 130 desc.functionArgs = Stream.of(args).map(v -> { 131 if (v.getClass().isArray()) { 132 return String.format("%s(length=%d)", 133 Arrays.deepToString((Object[]) v), 134 Array.getLength(v)); 135 } 136 return String.format("%s", v); 137 }).collect(Collectors.joining(", ")); 138 } 139 return desc; 140 } 141 142 private Class clazz; 143 private String functionName; 144 private String functionArgs; 145 private String instanceArgs; 146 } 147 148 TestInstance(ThrowingRunnable testBody) { 149 assertCount = 0; 150 this.testConstructor = (unused) -> null; 151 this.testBody = (unused) -> testBody.run(); 152 this.beforeActions = Collections.emptyList(); 153 this.afterActions = Collections.emptyList(); 154 this.testDesc = TestDesc.createBuilder().get(); 155 this.dryRun = false; 156 this.workDir = createWorkDirName(testDesc); 157 } 158 159 TestInstance(MethodCall testBody, List<ThrowingConsumer> beforeActions, 160 List<ThrowingConsumer> afterActions, boolean dryRun) { 161 assertCount = 0; 162 this.testConstructor = v -> ((MethodCall)v).newInstance(); 163 this.testBody = testBody; 164 this.beforeActions = beforeActions; 165 this.afterActions = afterActions; 166 this.testDesc = testBody.createDescription(); 167 this.dryRun = dryRun; 168 this.workDir = createWorkDirName(testDesc); 169 } 170 171 void notifyAssert() { 172 assertCount++; 173 } 174 175 void notifySkipped(RuntimeException ex) { 176 skippedTestException = ex; 177 } 178 179 boolean passed() { 180 return status == Status.Passed; 181 } 182 183 boolean skipped() { 184 return status == Status.Skipped; 185 } 186 187 boolean failed() { 188 return status == Status.Failed; 189 } 190 191 String functionName() { 192 return testDesc.functionName; 193 } 194 195 String baseName() { 196 return testDesc.clazz.getSimpleName(); 197 } 198 199 String fullName() { 200 return testDesc.testFullName(); 201 } 202 203 void rethrowIfSkipped() { 204 if (skippedTestException != null) { 205 throw skippedTestException; 206 } 207 } 208 209 Path workDir() { 210 return workDir; 211 } 212 213 @Override 214 public void run() throws Throwable { 215 final String fullName = fullName(); 216 TKit.log(String.format("[ RUN ] %s", fullName)); 217 try { 218 Object testInstance = testConstructor.apply(testBody); 219 beforeActions.forEach(a -> ThrowingConsumer.toConsumer(a).accept( 220 testInstance)); 221 try { 222 if (!dryRun) { 223 Files.createDirectories(workDir); 224 testBody.accept(testInstance); 225 } 226 } finally { 227 afterActions.forEach(a -> TKit.ignoreExceptions(() -> a.accept( 228 testInstance))); 229 } 230 status = Status.Passed; 231 } finally { 232 if (skippedTestException != null) { 233 status = Status.Skipped; 234 } else if (status == null) { 235 status = Status.Failed; 236 } 237 238 if (!KEEP_WORK_DIR.contains(status)) { 239 TKit.deleteDirectoryRecursive(workDir); 240 } 241 242 TKit.log(String.format("%s %s; checks=%d", status, fullName, 243 assertCount)); 244 } 245 } 246 247 private static Class enclosingMainMethodClass() { 248 StackTraceElement st[] = Thread.currentThread().getStackTrace(); 249 for (StackTraceElement ste : st) { 250 if ("main".equals(ste.getMethodName())) { 251 return Functional.ThrowingSupplier.toSupplier(() -> Class.forName( 252 ste.getClassName())).get(); 253 } 254 } 255 return null; 256 } 257 258 private static boolean isCalledByJavatest() { 259 StackTraceElement st[] = Thread.currentThread().getStackTrace(); 260 for (StackTraceElement ste : st) { 261 if (ste.getClassName().startsWith("com.sun.javatest.")) { 262 return true; 263 } 264 } 265 return false; 266 } 267 268 private static Path createWorkDirName(TestDesc testDesc) { 269 Path result = Path.of("."); 270 if (!isCalledByJavatest()) { 271 result = result.resolve(testDesc.clazz.getSimpleName()); 272 } 273 274 List<String> components = new ArrayList<>(); 275 276 final String testFunctionName = testDesc.functionName; 277 if (testFunctionName != null) { 278 components.add(testFunctionName); 279 } 280 281 final boolean isPrametrized = Stream.of(testDesc.functionArgs, 282 testDesc.instanceArgs).anyMatch(Objects::nonNull); 283 if (isPrametrized) { 284 components.add(String.format("%08x", testDesc.testFullName().hashCode())); 285 } 286 287 if (!components.isEmpty()) { 288 result = result.resolve(String.join(".", components)); 289 } 290 291 return result; 292 } 293 294 private enum Status { 295 Passed("[ OK ]"), 296 Failed("[ FAILED ]"), 297 Skipped("[ SKIPPED ]"); 298 299 Status(String msg) { 300 this.msg = msg; 301 } 302 303 @Override 304 public String toString() { 305 return msg; 306 } 307 308 private final String msg; 309 } 310 311 private int assertCount; 312 private Status status; 313 private RuntimeException skippedTestException; 314 private final TestDesc testDesc; 315 private final ThrowingFunction testConstructor; 316 private final ThrowingConsumer testBody; 317 private final List<ThrowingConsumer> beforeActions; 318 private final List<ThrowingConsumer> afterActions; 319 private final boolean dryRun; 320 private final Path workDir; 321 322 private final static Set<Status> KEEP_WORK_DIR = Functional.identity( 323 () -> { 324 final String propertyName = "keep-work-dir"; 325 Set<String> keepWorkDir = TKit.tokenizeConfigProperty( 326 propertyName); 327 if (keepWorkDir == null) { 328 return Set.of(Status.Failed); 329 } 330 331 Predicate<Set<String>> isOneOf = options -> { 332 return !Collections.disjoint(keepWorkDir, options); 333 }; 334 335 Set<Status> result = new HashSet<>(); 336 if (isOneOf.test(Set.of("pass", "p"))) { 337 result.add(Status.Passed); 338 } 339 if (isOneOf.test(Set.of("fail", "f"))) { 340 result.add(Status.Failed); 341 } 342 343 return Collections.unmodifiableSet(result); 344 }).get(); 345 346 }