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