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 package jdk.jpackage.test; 24 25 import java.io.File; 26 import java.io.FileNotFoundException; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.io.PrintStream; 30 import java.nio.file.FileSystems; 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.nio.file.StandardWatchEventKinds; 34 import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; 35 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; 36 import java.nio.file.WatchEvent; 37 import java.nio.file.WatchKey; 38 import java.nio.file.WatchService; 39 import java.util.Collection; 40 import java.util.Comparator; 41 import java.util.Iterator; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.concurrent.TimeUnit; 46 import java.util.concurrent.atomic.AtomicInteger; 47 import java.util.function.Supplier; 48 import java.util.stream.Collectors; 49 import jdk.jpackage.test.Functional.ThrowingConsumer; 50 import jdk.jpackage.test.Functional.ThrowingRunnable; 51 import jdk.jpackage.test.Functional.ThrowingSupplier; 52 53 final public class Test { 54 55 public static final Path TEST_SRC_ROOT = new Supplier<Path>() { 56 @Override 57 public Path get() { 58 Path root = Path.of(System.getProperty("test.src")); 59 60 for (int i = 0; i != 10; ++i) { 61 if (root.resolve("apps").toFile().isDirectory()) { 62 return root.toAbsolutePath(); 63 } 64 root = root.resolve(".."); 65 } 66 67 throw new RuntimeException("Failed to locate apps directory"); 68 } 69 }.get(); 70 71 private static class Instance implements AutoCloseable { 72 Instance(String args[]) { 73 assertCount = 0; 74 75 name = enclosingMainMethodClass().getSimpleName(); 76 extraLogStream = openLogStream(); 77 78 currentTest = this; 79 80 log(String.format("[ RUN ] %s", name)); 81 } 82 83 @Override 84 public void close() { 85 log(String.format("%s %s; checks=%d", 86 success ? "[ OK ]" : "[ FAILED ]", name, assertCount)); 87 88 if (extraLogStream != null) { 89 extraLogStream.close(); 90 } 91 } 92 93 void notifyAssert() { 94 assertCount++; 95 } 96 97 void notifySuccess() { 98 success = true; 99 } 100 101 private int assertCount; 102 private boolean success; 103 private final String name; 104 private final PrintStream extraLogStream; 105 } 106 107 public static void run(String args[], TestBody action) { 108 if (currentTest != null) { 109 throw new IllegalStateException( 110 "Unexpeced nested or concurrent Test.run() call"); 111 } 112 113 try (Instance instance = new Instance(args)) { 114 action.run(); 115 instance.notifySuccess(); 116 } catch (Exception ex) { 117 throw new RuntimeException(ex); 118 } finally { 119 currentTest = null; 120 } 121 } 122 123 public static interface TestBody { 124 public void run() throws Exception; 125 } 126 127 public static Path workDir() { 128 return Path.of("."); 129 } 130 131 static Path defaultInputDir() { 132 return workDir().resolve("input"); 133 } 134 135 static Path defaultOutputDir() { 136 return workDir().resolve("output"); 137 } 138 139 static Class enclosingMainMethodClass() { 140 StackTraceElement st[] = Thread.currentThread().getStackTrace(); 141 for (StackTraceElement ste : st) { 142 if ("main".equals(ste.getMethodName())) { 143 try { 144 return Class.forName(ste.getClassName()); 145 } catch (ClassNotFoundException ex) { 146 throw new RuntimeException(ex); 147 } 148 } 149 } 150 return null; 151 } 152 153 static boolean isWindows() { 154 return (OS.contains("win")); 155 } 156 157 static boolean isOSX() { 158 return (OS.contains("mac")); 159 } 160 161 static boolean isLinux() { 162 return ((OS.contains("nix") || OS.contains("nux"))); 163 } 164 165 static private void log(String v) { 166 System.out.println(v); 167 if (currentTest != null && currentTest.extraLogStream != null) { 168 currentTest.extraLogStream.println(v); 169 } 170 } 171 172 public static Class getTestClass () { 173 return enclosingMainMethodClass(); 174 } 175 176 public static void createPropertiesFile(Path propsFilename, 177 Collection<Map.Entry<String, String>> props) { 178 trace(String.format("Create [%s] properties file...", 179 propsFilename.toAbsolutePath().normalize())); 180 try { 181 Files.write(propsFilename, props.stream().peek(e -> trace( 182 String.format("%s=%s", e.getKey(), e.getValue()))).map( 183 e -> String.format("%s=%s", e.getKey(), e.getValue())).collect( 184 Collectors.toList())); 185 } catch (IOException ex) { 186 throw new RuntimeException(ex); 187 } 188 trace("Done"); 189 } 190 191 public static void createPropertiesFile(Path propsFilename, 192 Map.Entry<String, String>... props) { 193 createPropertiesFile(propsFilename, List.of(props)); 194 } 195 196 public static void createPropertiesFile(Path propsFilename, 197 Map<String, String> props) { 198 createPropertiesFile(propsFilename, props.entrySet()); 199 } 200 201 public static void trace(String v) { 202 if (TRACE) { 203 log("TRACE: " + v); 204 } 205 } 206 207 private static void traceAssert(String v) { 208 if (TRACE_ASSERTS) { 209 log("TRACE: " + v); 210 } 211 } 212 213 public static void error(String v) { 214 log("ERROR: " + v); 215 throw new AssertionError(v); 216 } 217 218 private static final String TEMP_FILE_PREFIX = null; 219 220 public static Path createTempDirectory() throws IOException { 221 return Files.createTempDirectory(workDir(), TEMP_FILE_PREFIX); 222 } 223 224 public static Path createTempFile(String suffix) throws IOException { 225 return Files.createTempFile(workDir(), TEMP_FILE_PREFIX, suffix); 226 } 227 228 public static void withTempFile(String suffix, ThrowingConsumer<Path> action) { 229 final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile( 230 suffix)).get(); 231 boolean keepIt = true; 232 try { 233 ThrowingConsumer.toConsumer(action).accept(tempFile); 234 keepIt = false; 235 } finally { 236 if (tempFile != null && !keepIt) { 237 ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run(); 238 } 239 } 240 } 241 242 public static void withTempDirectory(ThrowingConsumer<Path> action) { 243 final Path tempDir = ThrowingSupplier.toSupplier( 244 () -> createTempDirectory()).get(); 245 boolean keepIt = true; 246 try { 247 ThrowingConsumer.toConsumer(action).accept(tempDir); 248 keepIt = false; 249 } finally { 250 if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) { 251 deleteDirectoryRecursive(tempDir); 252 } 253 } 254 } 255 256 static void deleteDirectoryRecursive(Path path) { 257 ThrowingRunnable.toRunnable(() -> Files.walk(path).sorted( 258 Comparator.reverseOrder()).map(Path::toFile).forEach( 259 File::delete)).run(); 260 } 261 262 static void waitForFileCreated(Path fileToWaitFor, 263 long timeoutSeconds) throws IOException { 264 265 trace(String.format("Wait for file [%s] to be available", fileToWaitFor)); 266 267 WatchService ws = FileSystems.getDefault().newWatchService(); 268 269 Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent(); 270 watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY); 271 272 long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000; 273 for (;;) { 274 long timeout = waitUntil - System.currentTimeMillis(); 275 assertTrue(timeout > 0, String.format( 276 "Check timeout value %d is positive", timeout)); 277 278 WatchKey key = null; 279 try { 280 key = ws.poll(timeout, TimeUnit.MILLISECONDS); 281 } catch (InterruptedException ex) { 282 throw new RuntimeException(ex); 283 } 284 285 if (key == null) { 286 if (fileToWaitFor.toFile().exists()) { 287 trace(String.format( 288 "File [%s] is available after poll timeout expired", 289 fileToWaitFor)); 290 return; 291 } 292 assertUnexpected(String.format("Timeout expired", timeout)); 293 } 294 295 for (WatchEvent<?> event : key.pollEvents()) { 296 if (event.kind() == StandardWatchEventKinds.OVERFLOW) { 297 continue; 298 } 299 Path contextPath = (Path) event.context(); 300 if (Files.isSameFile(watchDirectory.resolve(contextPath), 301 fileToWaitFor)) { 302 trace(String.format("File [%s] is available", fileToWaitFor)); 303 return; 304 } 305 } 306 307 if (!key.reset()) { 308 assertUnexpected("Watch key invalidated"); 309 } 310 } 311 } 312 313 private static String concatMessages(String msg, String msg2) { 314 if (msg2 != null && !msg2.isBlank()) { 315 return msg + ": " + msg2; 316 } 317 return msg; 318 } 319 320 public static void assertEquals(long expected, long actual, String msg) { 321 currentTest.notifyAssert(); 322 if (expected != actual) { 323 error(concatMessages(String.format( 324 "Expected [%d]. Actual [%d]", expected, actual), 325 msg)); 326 } 327 328 traceAssert(String.format("assertEquals(%d): %s", expected, msg)); 329 } 330 331 public static void assertNotEquals(long expected, long actual, String msg) { 332 currentTest.notifyAssert(); 333 if (expected == actual) { 334 error(concatMessages(String.format("Unexpected [%d] value", actual), 335 msg)); 336 } 337 338 traceAssert(String.format("assertNotEquals(%d, %d): %s", expected, 339 actual, msg)); 340 } 341 342 public static void assertEquals(String expected, String actual, String msg) { 343 currentTest.notifyAssert(); 344 if ((actual != null && !actual.equals(expected)) 345 || (expected != null && !expected.equals(actual))) { 346 error(concatMessages(String.format( 347 "Expected [%s]. Actual [%s]", expected, actual), 348 msg)); 349 } 350 351 traceAssert(String.format("assertEquals(%s): %s", expected, msg)); 352 } 353 354 public static void assertNotEquals(String expected, String actual, String msg) { 355 currentTest.notifyAssert(); 356 if ((actual != null && !actual.equals(expected)) 357 || (expected != null && !expected.equals(actual))) { 358 359 traceAssert(String.format("assertNotEquals(%s, %s): %s", expected, 360 actual, msg)); 361 return; 362 } 363 364 error(concatMessages(String.format("Unexpected [%s] value", actual), msg)); 365 } 366 367 public static void assertNull(Object value, String msg) { 368 currentTest.notifyAssert(); 369 if (value != null) { 370 error(concatMessages(String.format("Unexpected not null value [%s]", 371 value), msg)); 372 } 373 374 traceAssert(String.format("assertNull(): %s", msg)); 375 } 376 377 public static void assertNotNull(Object value, String msg) { 378 currentTest.notifyAssert(); 379 if (value == null) { 380 error(concatMessages("Unexpected null value", msg)); 381 } 382 383 traceAssert(String.format("assertNotNull(%s): %s", value, msg)); 384 } 385 386 public static void assertTrue(boolean actual, String msg) { 387 currentTest.notifyAssert(); 388 if (!actual) { 389 error(concatMessages("Unexpected FALSE", msg)); 390 } 391 392 traceAssert(String.format("assertTrue(): %s", msg)); 393 } 394 395 public static void assertFalse(boolean actual, String msg) { 396 currentTest.notifyAssert(); 397 if (actual) { 398 error(concatMessages("Unexpected TRUE", msg)); 399 } 400 401 traceAssert(String.format("assertFalse(): %s", msg)); 402 } 403 404 public static void assertPathExists(Path path, boolean exists) { 405 if (exists) { 406 assertTrue(path.toFile().exists(), String.format( 407 "Check [%s] path exists", path)); 408 } else { 409 assertFalse(path.toFile().exists(), String.format( 410 "Check [%s] path doesn't exist", path)); 411 } 412 } 413 414 public static void assertDirectoryExists(Path path, boolean exists) { 415 assertPathExists(path, exists); 416 if (exists) { 417 assertTrue(path.toFile().isDirectory(), String.format( 418 "Check [%s] is a directory", path)); 419 } 420 } 421 422 public static void assertFileExists(Path path, boolean exists) { 423 assertPathExists(path, exists); 424 if (exists) { 425 assertTrue(path.toFile().isFile(), String.format( 426 "Check [%s] is a file", path)); 427 } 428 } 429 430 public static void assertExecutableFileExists(Path path, boolean exists) { 431 assertFileExists(path, exists); 432 if (exists) { 433 assertTrue(path.toFile().canExecute(), String.format( 434 "Check [%s] file is executable", path)); 435 } 436 } 437 438 public static void assertReadableFileExists(Path path) { 439 assertFileExists(path, true); 440 assertTrue(path.toFile().canRead(), String.format( 441 "Check [%s] file is readable", path)); 442 } 443 444 public static void assertUnexpected(String msg) { 445 currentTest.notifyAssert(); 446 error(concatMessages("Unexpected", msg)); 447 } 448 449 public static void assertStringListEquals(List<String> expected, 450 List<String> actual, String msg) { 451 currentTest.notifyAssert(); 452 453 if (expected.size() < actual.size()) { 454 // Actual string list is longer than expected 455 error(concatMessages(String.format( 456 "Actual list is longer than expected by %d elements", 457 actual.size() - expected.size()), msg)); 458 } 459 460 if (actual.size() < expected.size()) { 461 // Actual string list is shorter than expected 462 error(concatMessages(String.format( 463 "Actual list is longer than expected by %d elements", 464 expected.size() - actual.size()), msg)); 465 } 466 467 traceAssert(String.format("assertStringListEquals(): %s", msg)); 468 469 String idxFieldFormat = Functional.identity(() -> { 470 int listSize = expected.size(); 471 int width = 0; 472 while (listSize != 0) { 473 listSize = listSize / 10; 474 width++; 475 } 476 return "%" + width + "d"; 477 }).get(); 478 479 AtomicInteger counter = new AtomicInteger(0); 480 Iterator<String> actualIt = actual.iterator(); 481 expected.stream().sequential().filter(expectedStr -> actualIt.hasNext()).forEach(expectedStr -> { 482 int idx = counter.incrementAndGet(); 483 String actualStr = actualIt.next(); 484 485 if ((actualStr != null && !actualStr.equals(expectedStr)) 486 || (expectedStr != null && !expectedStr.equals(actualStr))) { 487 error(concatMessages(String.format( 488 "(" + idxFieldFormat + ") Expected [%s]. Actual [%s]", 489 idx, expectedStr, actualStr), msg)); 490 } 491 492 traceAssert(String.format( 493 "assertStringListEquals(" + idxFieldFormat + ", %s)", idx, 494 expectedStr)); 495 }); 496 } 497 498 private static PrintStream openLogStream() { 499 if (LOG_FILE == null) { 500 return null; 501 } 502 503 try { 504 return new PrintStream(new FileOutputStream(LOG_FILE.toFile(), true)); 505 } catch (FileNotFoundException ex) { 506 throw new RuntimeException(ex); 507 } 508 } 509 510 private static Instance currentTest; 511 512 private static final boolean TRACE; 513 private static final boolean TRACE_ASSERTS; 514 515 static final boolean VERBOSE_JPACKAGE; 516 517 static String getConfigProperty(String propertyName) { 518 return System.getProperty(getConfigPropertyName(propertyName)); 519 } 520 521 static String getConfigPropertyName(String propertyName) { 522 return "jpackage.test." + propertyName; 523 } 524 525 static final Path LOG_FILE = Functional.identity(() -> { 526 String val = getConfigProperty("logfile"); 527 if (val == null) { 528 return null; 529 } 530 return Path.of(val); 531 }).get(); 532 533 static { 534 String val = getConfigProperty("suppress-logging"); 535 if (val == null) { 536 TRACE = true; 537 TRACE_ASSERTS = true; 538 VERBOSE_JPACKAGE = true; 539 } else if ("all".equals(val.toLowerCase())) { 540 TRACE = false; 541 TRACE_ASSERTS = false; 542 VERBOSE_JPACKAGE = false; 543 } else { 544 Set<String> logOptions = Set.of(val.toLowerCase().split(",")); 545 TRACE = !(logOptions.contains("trace") || logOptions.contains("t")); 546 TRACE_ASSERTS = !(logOptions.contains("assert") || logOptions.contains( 547 "a")); 548 VERBOSE_JPACKAGE = !(logOptions.contains("jpackage") || logOptions.contains( 549 "jp")); 550 } 551 } 552 553 private static final String OS = System.getProperty("os.name").toLowerCase(); 554 }