| /* |
| * Copyright (c) 2019, 2021, 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.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.nio.file.FileSystems; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.StandardWatchEventKinds; |
| import java.nio.file.StandardCopyOption; |
| import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; |
| import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; |
| import java.nio.file.WatchEvent; |
| import java.nio.file.WatchKey; |
| import java.nio.file.WatchService; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.function.BiPredicate; |
| import java.util.function.Consumer; |
| 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.ExceptionBox; |
| import jdk.jpackage.test.Functional.ThrowingConsumer; |
| import jdk.jpackage.test.Functional.ThrowingRunnable; |
| import jdk.jpackage.test.Functional.ThrowingSupplier; |
| |
| final public class TKit { |
| |
| private static final String OS = System.getProperty("os.name").toLowerCase(); |
| |
| public static final Path TEST_SRC_ROOT = Functional.identity(() -> { |
| Path root = Path.of(System.getProperty("test.src")); |
| |
| for (int i = 0; i != 10; ++i) { |
| if (root.resolve("apps").toFile().isDirectory()) { |
| return root.normalize().toAbsolutePath(); |
| } |
| root = root.resolve(".."); |
| } |
| |
| throw new RuntimeException("Failed to locate apps directory"); |
| }).get(); |
| |
| public static final Path SRC_ROOT = Functional.identity(() -> { |
| return TEST_SRC_ROOT.resolve("../../../../src/jdk.jpackage").normalize().toAbsolutePath(); |
| }).get(); |
| |
| public final static String ICON_SUFFIX = Functional.identity(() -> { |
| if (isOSX()) { |
| return ".icns"; |
| } |
| |
| if (isLinux()) { |
| return ".png"; |
| } |
| |
| if (isWindows()) { |
| return ".ico"; |
| } |
| |
| throw throwUnknownPlatformError(); |
| }).get(); |
| |
| static void withExtraLogStream(ThrowingRunnable action) { |
| if (extraLogStream != null) { |
| ThrowingRunnable.toRunnable(action).run(); |
| } else { |
| try (PrintStream logStream = openLogStream()) { |
| extraLogStream = logStream; |
| ThrowingRunnable.toRunnable(action).run(); |
| } finally { |
| extraLogStream = null; |
| } |
| } |
| } |
| |
| static void runTests(List<TestInstance> tests) { |
| if (currentTest != null) { |
| throw new IllegalStateException( |
| "Unexpeced nested or concurrent Test.run() call"); |
| } |
| |
| withExtraLogStream(() -> { |
| tests.stream().forEach(test -> { |
| currentTest = test; |
| try { |
| ignoreExceptions(test).run(); |
| } finally { |
| currentTest = null; |
| if (extraLogStream != null) { |
| extraLogStream.flush(); |
| } |
| } |
| }); |
| }); |
| } |
| |
| static Runnable ignoreExceptions(ThrowingRunnable action) { |
| return () -> { |
| try { |
| try { |
| action.run(); |
| } catch (Throwable ex) { |
| unbox(ex); |
| } |
| } catch (Throwable throwable) { |
| printStackTrace(throwable); |
| } |
| }; |
| } |
| |
| static void unbox(Throwable throwable) throws Throwable { |
| try { |
| throw throwable; |
| } catch (ExceptionBox | InvocationTargetException ex) { |
| unbox(ex.getCause()); |
| } |
| } |
| |
| public static Path workDir() { |
| return currentTest.workDir(); |
| } |
| |
| static String getCurrentDefaultAppName() { |
| // Construct app name from swapping and joining test base name |
| // and test function name. |
| // Say the test name is `FooTest.testBasic`. Then app name would be `BasicFooTest`. |
| String appNamePrefix = currentTest.functionName(); |
| if (appNamePrefix != null && appNamePrefix.startsWith("test")) { |
| appNamePrefix = appNamePrefix.substring("test".length()); |
| } |
| return Stream.of(appNamePrefix, currentTest.baseName()).filter( |
| v -> v != null && !v.isEmpty()).collect(Collectors.joining()); |
| } |
| |
| public static boolean isWindows() { |
| return (OS.contains("win")); |
| } |
| |
| public static boolean isOSX() { |
| return (OS.contains("mac")); |
| } |
| |
| public static boolean isLinux() { |
| return ((OS.contains("nix") || OS.contains("nux"))); |
| } |
| |
| public static boolean isLinuxAPT() { |
| if (!isLinux()) { |
| return false; |
| } |
| File aptFile = new File("/usr/bin/apt-get"); |
| return aptFile.exists(); |
| } |
| |
| private static String addTimestamp(String msg) { |
| SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); |
| Date time = new Date(System.currentTimeMillis()); |
| return String.format("[%s] %s", sdf.format(time), msg); |
| } |
| |
| static void log(String v) { |
| v = addTimestamp(v); |
| System.out.println(v); |
| if (extraLogStream != null) { |
| extraLogStream.println(v); |
| } |
| } |
| |
| static Path removeRootFromAbsolutePath(Path v) { |
| if (!v.isAbsolute()) { |
| throw new IllegalArgumentException(); |
| } |
| |
| if (v.getNameCount() == 0) { |
| return Path.of(""); |
| } |
| return v.subpath(0, v.getNameCount()); |
| } |
| |
| public static void createTextFile(Path filename, Collection<String> lines) { |
| createTextFile(filename, lines.stream()); |
| } |
| |
| public static void createTextFile(Path filename, Stream<String> lines) { |
| trace(String.format("Create [%s] text file...", |
| filename.toAbsolutePath().normalize())); |
| ThrowingRunnable.toRunnable(() -> Files.write(filename, |
| lines.peek(TKit::trace).collect(Collectors.toList()))).run(); |
| trace("Done"); |
| } |
| |
| public static void createPropertiesFile(Path propsFilename, |
| Collection<Map.Entry<String, String>> props) { |
| trace(String.format("Create [%s] properties file...", |
| propsFilename.toAbsolutePath().normalize())); |
| ThrowingRunnable.toRunnable(() -> Files.write(propsFilename, |
| props.stream().map(e -> String.join("=", e.getKey(), |
| e.getValue())).peek(TKit::trace).collect(Collectors.toList()))).run(); |
| trace("Done"); |
| } |
| |
| public static void createPropertiesFile(Path propsFilename, |
| Map.Entry<String, String>... props) { |
| createPropertiesFile(propsFilename, List.of(props)); |
| } |
| |
| public static void createPropertiesFile(Path propsFilename, |
| Map<String, String> props) { |
| createPropertiesFile(propsFilename, props.entrySet()); |
| } |
| |
| public static void trace(String v) { |
| if (TRACE) { |
| log("TRACE: " + v); |
| } |
| } |
| |
| private static void traceAssert(String v) { |
| if (TRACE_ASSERTS) { |
| log("TRACE: " + v); |
| } |
| } |
| |
| public static void error(String v) { |
| log("ERROR: " + v); |
| throw new AssertionError(v); |
| } |
| |
| private final static String TEMP_FILE_PREFIX = null; |
| |
| private static Path createUniqueFileName(String defaultName) { |
| final String[] nameComponents; |
| |
| int separatorIdx = defaultName.lastIndexOf('.'); |
| final String baseName; |
| if (separatorIdx == -1) { |
| baseName = defaultName; |
| nameComponents = new String[]{baseName}; |
| } else { |
| baseName = defaultName.substring(0, separatorIdx); |
| nameComponents = new String[]{baseName, defaultName.substring( |
| separatorIdx + 1)}; |
| } |
| |
| final Path basedir = workDir(); |
| int i = 0; |
| for (; i < 100; ++i) { |
| Path path = basedir.resolve(String.join(".", nameComponents)); |
| if (!path.toFile().exists()) { |
| return path; |
| } |
| nameComponents[0] = String.format("%s.%d", baseName, i); |
| } |
| throw new IllegalStateException(String.format( |
| "Failed to create unique file name from [%s] basename after %d attempts", |
| baseName, i)); |
| } |
| |
| public static Path createTempDirectory(String role) throws IOException { |
| if (role == null) { |
| return Files.createTempDirectory(workDir(), TEMP_FILE_PREFIX); |
| } |
| return Files.createDirectory(createUniqueFileName(role)); |
| } |
| |
| public static Path createTempFile(Path templateFile) throws |
| IOException { |
| return Files.createFile(createUniqueFileName( |
| templateFile.getFileName().toString())); |
| } |
| |
| public static Path withTempFile(Path templateFile, |
| ThrowingConsumer<Path> action) { |
| final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile( |
| templateFile)).get(); |
| boolean keepIt = true; |
| try { |
| ThrowingConsumer.toConsumer(action).accept(tempFile); |
| keepIt = false; |
| return tempFile; |
| } finally { |
| if (tempFile != null && !keepIt) { |
| ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run(); |
| } |
| } |
| } |
| |
| public static Path withTempDirectory(String role, |
| ThrowingConsumer<Path> action) { |
| final Path tempDir = ThrowingSupplier.toSupplier( |
| () -> createTempDirectory(role)).get(); |
| boolean keepIt = true; |
| try { |
| ThrowingConsumer.toConsumer(action).accept(tempDir); |
| keepIt = false; |
| return tempDir; |
| } finally { |
| if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) { |
| deleteDirectoryRecursive(tempDir, ""); |
| } |
| } |
| } |
| |
| private static class DirectoryCleaner implements Consumer<Path> { |
| DirectoryCleaner traceMessage(String v) { |
| msg = v; |
| return this; |
| } |
| |
| DirectoryCleaner contentsOnly(boolean v) { |
| contentsOnly = v; |
| return this; |
| } |
| |
| @Override |
| public void accept(Path root) { |
| if (msg == null) { |
| if (contentsOnly) { |
| msg = String.format("Cleaning [%s] directory recursively", |
| root); |
| } else { |
| msg = String.format("Deleting [%s] directory recursively", |
| root); |
| } |
| } |
| |
| if (!msg.isEmpty()) { |
| trace(msg); |
| } |
| |
| List<Throwable> errors = new ArrayList<>(); |
| try { |
| final List<Path> paths; |
| if (contentsOnly) { |
| try (var pathStream = Files.walk(root, 0)) { |
| paths = pathStream.collect(Collectors.toList()); |
| } |
| } else { |
| paths = List.of(root); |
| } |
| |
| for (var path : paths) { |
| try (var pathStream = Files.walk(path)) { |
| pathStream |
| .sorted(Comparator.reverseOrder()) |
| .sequential() |
| .forEachOrdered(file -> { |
| try { |
| if (isWindows()) { |
| Files.setAttribute(file, "dos:readonly", false); |
| } |
| Files.delete(file); |
| } catch (IOException ex) { |
| errors.add(ex); |
| } |
| }); |
| } |
| } |
| |
| } catch (IOException ex) { |
| errors.add(ex); |
| } |
| errors.forEach(error -> trace(error.toString())); |
| } |
| |
| private String msg; |
| private boolean contentsOnly; |
| } |
| |
| public static boolean deleteIfExists(Path path) throws IOException { |
| if (isWindows()) { |
| if (path.toFile().exists()) { |
| Files.setAttribute(path, "dos:readonly", false); |
| } |
| } |
| return Files.deleteIfExists(path); |
| } |
| |
| /** |
| * Deletes contents of the given directory recursively. Shortcut for |
| * <code>deleteDirectoryContentsRecursive(path, null)</code> |
| * |
| * @param path path to directory to clean |
| */ |
| public static void deleteDirectoryContentsRecursive(Path path) { |
| deleteDirectoryContentsRecursive(path, null); |
| } |
| |
| /** |
| * Deletes contents of the given directory recursively. If <code>path<code> is not a |
| * directory, request is silently ignored. |
| * |
| * @param path path to directory to clean |
| * @param msg log message. If null, the default log message is used. If |
| * empty string, no log message will be saved. |
| */ |
| public static void deleteDirectoryContentsRecursive(Path path, String msg) { |
| if (path.toFile().isDirectory()) { |
| new DirectoryCleaner().contentsOnly(true).traceMessage(msg).accept( |
| path); |
| } |
| } |
| |
| /** |
| * Deletes the given directory recursively. Shortcut for |
| * <code>deleteDirectoryRecursive(path, null)</code> |
| * |
| * @param path path to directory to delete |
| */ |
| public static void deleteDirectoryRecursive(Path path) { |
| deleteDirectoryRecursive(path, null); |
| } |
| |
| /** |
| * Deletes the given directory recursively. If <code>path<code> is not a |
| * directory, request is silently ignored. |
| * |
| * @param path path to directory to delete |
| * @param msg log message. If null, the default log message is used. If |
| * empty string, no log message will be saved. |
| */ |
| public static void deleteDirectoryRecursive(Path path, String msg) { |
| if (path.toFile().isDirectory()) { |
| new DirectoryCleaner().traceMessage(msg).accept(path); |
| } |
| } |
| |
| public static RuntimeException throwUnknownPlatformError() { |
| if (isWindows() || isLinux() || isOSX()) { |
| throw new IllegalStateException( |
| "Platform is known. throwUnknownPlatformError() called by mistake"); |
| } |
| throw new IllegalStateException("Unknown platform"); |
| } |
| |
| public static RuntimeException throwSkippedException(String reason) { |
| trace("Skip the test: " + reason); |
| RuntimeException ex = ThrowingSupplier.toSupplier( |
| () -> (RuntimeException) Class.forName("jtreg.SkippedException").getConstructor( |
| String.class).newInstance(reason)).get(); |
| |
| currentTest.notifySkipped(ex); |
| throw ex; |
| } |
| |
| public static Path createRelativePathCopy(final Path file) { |
| Path fileCopy = ThrowingSupplier.toSupplier(() -> { |
| Path localPath = createTempFile(file); |
| Files.copy(file, localPath, StandardCopyOption.REPLACE_EXISTING); |
| return localPath; |
| }).get().toAbsolutePath().normalize(); |
| |
| final Path basePath = Path.of(".").toAbsolutePath().normalize(); |
| try { |
| return basePath.relativize(fileCopy); |
| } catch (IllegalArgumentException ex) { |
| // May happen on Windows: java.lang.IllegalArgumentException: 'other' has different root |
| trace(String.format("Failed to relativize [%s] at [%s]", fileCopy, |
| basePath)); |
| printStackTrace(ex); |
| } |
| return file; |
| } |
| |
| static void waitForFileCreated(Path fileToWaitFor, |
| long timeoutSeconds) throws IOException { |
| |
| trace(String.format("Wait for file [%s] to be available", |
| fileToWaitFor.toAbsolutePath())); |
| |
| WatchService ws = FileSystems.getDefault().newWatchService(); |
| |
| Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent(); |
| watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY); |
| |
| long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000; |
| for (;;) { |
| long timeout = waitUntil - System.currentTimeMillis(); |
| assertTrue(timeout > 0, String.format( |
| "Check timeout value %d is positive", timeout)); |
| |
| WatchKey key = ThrowingSupplier.toSupplier(() -> ws.poll(timeout, |
| TimeUnit.MILLISECONDS)).get(); |
| if (key == null) { |
| if (fileToWaitFor.toFile().exists()) { |
| trace(String.format( |
| "File [%s] is available after poll timeout expired", |
| fileToWaitFor)); |
| return; |
| } |
| assertUnexpected(String.format("Timeout expired", timeout)); |
| } |
| |
| for (WatchEvent<?> event : key.pollEvents()) { |
| if (event.kind() == StandardWatchEventKinds.OVERFLOW) { |
| continue; |
| } |
| Path contextPath = (Path) event.context(); |
| if (Files.isSameFile(watchDirectory.resolve(contextPath), |
| fileToWaitFor)) { |
| trace(String.format("File [%s] is available", fileToWaitFor)); |
| return; |
| } |
| } |
| |
| if (!key.reset()) { |
| assertUnexpected("Watch key invalidated"); |
| } |
| } |
| } |
| |
| static void printStackTrace(Throwable throwable) { |
| if (extraLogStream != null) { |
| throwable.printStackTrace(extraLogStream); |
| } |
| throwable.printStackTrace(); |
| } |
| |
| private static String concatMessages(String msg, String msg2) { |
| if (msg2 != null && !msg2.isBlank()) { |
| return msg + ": " + msg2; |
| } |
| return msg; |
| } |
| |
| public static void assertEquals(long expected, long actual, String msg) { |
| currentTest.notifyAssert(); |
| if (expected != actual) { |
| error(concatMessages(String.format( |
| "Expected [%d]. Actual [%d]", expected, actual), |
| msg)); |
| } |
| |
| traceAssert(String.format("assertEquals(%d): %s", expected, msg)); |
| } |
| |
| public static void assertNotEquals(long expected, long actual, String msg) { |
| currentTest.notifyAssert(); |
| if (expected == actual) { |
| error(concatMessages(String.format("Unexpected [%d] value", actual), |
| msg)); |
| } |
| |
| traceAssert(String.format("assertNotEquals(%d, %d): %s", expected, |
| actual, msg)); |
| } |
| |
| public static void assertEquals(String expected, String actual, String msg) { |
| currentTest.notifyAssert(); |
| if ((actual != null && !actual.equals(expected)) |
| || (expected != null && !expected.equals(actual))) { |
| error(concatMessages(String.format( |
| "Expected [%s]. Actual [%s]", expected, actual), |
| msg)); |
| } |
| |
| traceAssert(String.format("assertEquals(%s): %s", expected, msg)); |
| } |
| |
| public static void assertNotEquals(String expected, String actual, String msg) { |
| currentTest.notifyAssert(); |
| if ((actual != null && !actual.equals(expected)) |
| || (expected != null && !expected.equals(actual))) { |
| |
| traceAssert(String.format("assertNotEquals(%s, %s): %s", expected, |
| actual, msg)); |
| return; |
| } |
| |
| error(concatMessages(String.format("Unexpected [%s] value", actual), msg)); |
| } |
| |
| public static void assertNull(Object value, String msg) { |
| currentTest.notifyAssert(); |
| if (value != null) { |
| error(concatMessages(String.format("Unexpected not null value [%s]", |
| value), msg)); |
| } |
| |
| traceAssert(String.format("assertNull(): %s", msg)); |
| } |
| |
| public static void assertNotNull(Object value, String msg) { |
| currentTest.notifyAssert(); |
| if (value == null) { |
| error(concatMessages("Unexpected null value", msg)); |
| } |
| |
| traceAssert(String.format("assertNotNull(%s): %s", value, msg)); |
| } |
| |
| public static void assertTrue(boolean actual, String msg) { |
| assertTrue(actual, msg, null); |
| } |
| |
| public static void assertFalse(boolean actual, String msg) { |
| assertFalse(actual, msg, null); |
| } |
| |
| public static void assertTrue(boolean actual, String msg, Runnable onFail) { |
| currentTest.notifyAssert(); |
| if (!actual) { |
| if (onFail != null) { |
| onFail.run(); |
| } |
| error(concatMessages("Failed", msg)); |
| } |
| |
| traceAssert(String.format("assertTrue(): %s", msg)); |
| } |
| |
| public static void assertFalse(boolean actual, String msg, Runnable onFail) { |
| currentTest.notifyAssert(); |
| if (actual) { |
| if (onFail != null) { |
| onFail.run(); |
| } |
| error(concatMessages("Failed", msg)); |
| } |
| |
| traceAssert(String.format("assertFalse(): %s", msg)); |
| } |
| |
| public static void assertPathExists(Path path, boolean exists) { |
| if (exists) { |
| assertTrue(path.toFile().exists(), String.format( |
| "Check [%s] path exists", path)); |
| } else { |
| assertFalse(path.toFile().exists(), String.format( |
| "Check [%s] path doesn't exist", path)); |
| } |
| } |
| |
| public static void assertPathNotEmptyDirectory(Path path) { |
| if (Files.isDirectory(path)) { |
| ThrowingRunnable.toRunnable(() -> { |
| try (var files = Files.list(path)) { |
| TKit.assertFalse(files.findFirst().isEmpty(), String.format |
| ("Check [%s] is not an empty directory", path)); |
| } |
| }).run(); |
| } |
| } |
| |
| public static void assertDirectoryExists(Path path) { |
| assertPathExists(path, true); |
| assertTrue(path.toFile().isDirectory(), String.format( |
| "Check [%s] is a directory", path)); |
| } |
| |
| public static void assertFileExists(Path path) { |
| assertPathExists(path, true); |
| assertTrue(path.toFile().isFile(), String.format("Check [%s] is a file", |
| path)); |
| } |
| |
| public static void assertExecutableFileExists(Path path) { |
| assertFileExists(path); |
| assertTrue(path.toFile().canExecute(), String.format( |
| "Check [%s] file is executable", path)); |
| } |
| |
| public static void assertReadableFileExists(Path path) { |
| assertFileExists(path); |
| assertTrue(path.toFile().canRead(), String.format( |
| "Check [%s] file is readable", path)); |
| } |
| |
| public static void assertUnexpected(String msg) { |
| currentTest.notifyAssert(); |
| error(concatMessages("Unexpected", msg)); |
| } |
| |
| public static void assertStringListEquals(List<String> expected, |
| List<String> actual, String msg) { |
| currentTest.notifyAssert(); |
| |
| traceAssert(String.format("assertStringListEquals(): %s", msg)); |
| |
| String idxFieldFormat = Functional.identity(() -> { |
| int listSize = expected.size(); |
| int width = 0; |
| while (listSize != 0) { |
| listSize = listSize / 10; |
| width++; |
| } |
| return "%" + width + "d"; |
| }).get(); |
| |
| AtomicInteger counter = new AtomicInteger(0); |
| Iterator<String> actualIt = actual.iterator(); |
| expected.stream().sequential().filter(expectedStr -> actualIt.hasNext()).forEach(expectedStr -> { |
| int idx = counter.incrementAndGet(); |
| String actualStr = actualIt.next(); |
| |
| if ((actualStr != null && !actualStr.equals(expectedStr)) |
| || (expectedStr != null && !expectedStr.equals(actualStr))) { |
| error(concatMessages(String.format( |
| "(" + idxFieldFormat + ") Expected [%s]. Actual [%s]", |
| idx, expectedStr, actualStr), msg)); |
| } |
| |
| traceAssert(String.format( |
| "assertStringListEquals(" + idxFieldFormat + ", %s)", idx, |
| expectedStr)); |
| }); |
| |
| if (expected.size() < actual.size()) { |
| // Actual string list is longer than expected |
| error(concatMessages(String.format( |
| "Actual list is longer than expected by %d elements", |
| actual.size() - expected.size()), msg)); |
| } |
| |
| if (actual.size() < expected.size()) { |
| // Actual string list is shorter than expected |
| error(concatMessages(String.format( |
| "Actual list is longer than expected by %d elements", |
| expected.size() - actual.size()), msg)); |
| } |
| } |
| |
| public final static class TextStreamVerifier { |
| TextStreamVerifier(String value) { |
| this.value = value; |
| predicate(String::contains); |
| } |
| |
| public TextStreamVerifier label(String v) { |
| label = v; |
| return this; |
| } |
| |
| public TextStreamVerifier predicate(BiPredicate<String, String> v) { |
| predicate = v; |
| return this; |
| } |
| |
| public TextStreamVerifier negate() { |
| negate = true; |
| return this; |
| } |
| |
| public TextStreamVerifier orElseThrow(RuntimeException v) { |
| return orElseThrow(() -> v); |
| } |
| |
| public TextStreamVerifier orElseThrow(Supplier<RuntimeException> v) { |
| createException = v; |
| return this; |
| } |
| |
| public void apply(Stream<String> lines) { |
| String matchedStr = lines.filter(line -> predicate.test(line, value)).findFirst().orElse( |
| null); |
| String labelStr = Optional.ofNullable(label).orElse("output"); |
| if (negate) { |
| String msg = String.format( |
| "Check %s doesn't contain [%s] string", labelStr, value); |
| if (createException == null) { |
| assertNull(matchedStr, msg); |
| } else { |
| trace(msg); |
| if (matchedStr != null) { |
| throw createException.get(); |
| } |
| } |
| } else { |
| String msg = String.format("Check %s contains [%s] string", |
| labelStr, value); |
| if (createException == null) { |
| assertNotNull(matchedStr, msg); |
| } else { |
| trace(msg); |
| if (matchedStr == null) { |
| throw createException.get(); |
| } |
| } |
| } |
| } |
| |
| private BiPredicate<String, String> predicate; |
| private String label; |
| private boolean negate; |
| private Supplier<RuntimeException> createException; |
| final private String value; |
| } |
| |
| public static TextStreamVerifier assertTextStream(String what) { |
| return new TextStreamVerifier(what); |
| } |
| |
| private static PrintStream openLogStream() { |
| if (LOG_FILE == null) { |
| return null; |
| } |
| |
| return ThrowingSupplier.toSupplier(() -> new PrintStream( |
| new FileOutputStream(LOG_FILE.toFile(), true))).get(); |
| } |
| |
| private static TestInstance currentTest; |
| private static PrintStream extraLogStream; |
| |
| private static final boolean TRACE; |
| private static final boolean TRACE_ASSERTS; |
| |
| static final boolean VERBOSE_JPACKAGE; |
| static final boolean VERBOSE_TEST_SETUP; |
| |
| static String getConfigProperty(String propertyName) { |
| return System.getProperty(getConfigPropertyName(propertyName)); |
| } |
| |
| static String getConfigPropertyName(String propertyName) { |
| return "jpackage.test." + propertyName; |
| } |
| |
| static List<String> tokenizeConfigPropertyAsList(String propertyName) { |
| final String val = TKit.getConfigProperty(propertyName); |
| if (val == null) { |
| return null; |
| } |
| return Stream.of(val.toLowerCase().split(",")) |
| .map(String::strip) |
| .filter(Predicate.not(String::isEmpty)) |
| .collect(Collectors.toList()); |
| } |
| |
| static Set<String> tokenizeConfigProperty(String propertyName) { |
| List<String> tokens = tokenizeConfigPropertyAsList(propertyName); |
| if (tokens == null) { |
| return null; |
| } |
| return tokens.stream().collect(Collectors.toSet()); |
| } |
| |
| static final Path LOG_FILE = Functional.identity(() -> { |
| String val = getConfigProperty("logfile"); |
| if (val == null) { |
| return null; |
| } |
| return Path.of(val); |
| }).get(); |
| |
| static { |
| Set<String> logOptions = tokenizeConfigProperty("suppress-logging"); |
| if (logOptions == null) { |
| TRACE = true; |
| TRACE_ASSERTS = true; |
| VERBOSE_JPACKAGE = true; |
| VERBOSE_TEST_SETUP = true; |
| } else if (logOptions.contains("all")) { |
| TRACE = false; |
| TRACE_ASSERTS = false; |
| VERBOSE_JPACKAGE = false; |
| VERBOSE_TEST_SETUP = false; |
| } else { |
| Predicate<Set<String>> isNonOf = options -> { |
| return Collections.disjoint(logOptions, options); |
| }; |
| |
| TRACE = isNonOf.test(Set.of("trace", "t")); |
| TRACE_ASSERTS = isNonOf.test(Set.of("assert", "a")); |
| VERBOSE_JPACKAGE = isNonOf.test(Set.of("jpackage", "jp")); |
| VERBOSE_TEST_SETUP = isNonOf.test(Set.of("init", "i")); |
| } |
| } |
| } |