mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8333568: Test that jpackage doesn't modify R/O files/directories
Reviewed-by: almatvee
This commit is contained in:
parent
8a8893ec03
commit
cc3a366e2a
@ -23,10 +23,18 @@
|
||||
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.mapping;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
@ -46,7 +54,6 @@ import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher;
|
||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||
import jdk.jpackage.internal.util.function.ThrowingFunction;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
@ -78,6 +85,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
prerequisiteActions = new Actions(cmd.prerequisiteActions);
|
||||
verifyActions = new Actions(cmd.verifyActions);
|
||||
appLayoutAsserts = cmd.appLayoutAsserts;
|
||||
readOnlyPathAsserts = cmd.readOnlyPathAsserts;
|
||||
outputValidators = cmd.outputValidators;
|
||||
executeInDirectory = cmd.executeInDirectory;
|
||||
winMsiLogFile = cmd.winMsiLogFile;
|
||||
@ -843,10 +851,13 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
}
|
||||
}
|
||||
|
||||
Executor.Result result = new JPackageCommand(this)
|
||||
.adjustArgumentsBeforeExecution()
|
||||
.createExecutor()
|
||||
.execute(expectedExitCode);
|
||||
final var copy = new JPackageCommand(this).adjustArgumentsBeforeExecution();
|
||||
|
||||
final var directoriesAssert = new ReadOnlyPathsAssert(copy);
|
||||
|
||||
Executor.Result result = copy.createExecutor().execute(expectedExitCode);
|
||||
|
||||
directoriesAssert.updateAndAssert();
|
||||
|
||||
for (final var outputValidator: outputValidators) {
|
||||
outputValidator.accept(result.getOutput().iterator());
|
||||
@ -903,6 +914,136 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
return macro.value(this);
|
||||
}
|
||||
|
||||
private static final class ReadOnlyPathsAssert {
|
||||
ReadOnlyPathsAssert(JPackageCommand cmd) {
|
||||
this.asserts = cmd.readOnlyPathAsserts.stream().map(a -> {
|
||||
return a.getPaths(cmd).stream().map(dir -> {
|
||||
return Map.entry(a, dir);
|
||||
});
|
||||
}).flatMap(x -> x).collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));
|
||||
snapshots = createSnapshots();
|
||||
}
|
||||
|
||||
void updateAndAssert() {
|
||||
final var newSnapshots = createSnapshots();
|
||||
for (final var a : asserts.keySet().stream().sorted().toList()) {
|
||||
final var snapshopGroup = snapshots.get(a);
|
||||
final var newSnapshopGroup = newSnapshots.get(a);
|
||||
for (int i = 0; i < snapshopGroup.size(); i++) {
|
||||
TKit.PathSnapshot.assertEquals(snapshopGroup.get(i), newSnapshopGroup.get(i),
|
||||
String.format("Check jpackage didn't modify ${%s}=[%s]", a, asserts.get(a).get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<ReadOnlyPathAssert, List<TKit.PathSnapshot>> createSnapshots() {
|
||||
return asserts.entrySet().stream()
|
||||
.map(e -> {
|
||||
return Map.entry(e.getKey(), e.getValue().stream().map(TKit.PathSnapshot::new).toList());
|
||||
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private final Map<ReadOnlyPathAssert, List<Path>> asserts;
|
||||
private final Map<ReadOnlyPathAssert, List<TKit.PathSnapshot>> snapshots;
|
||||
}
|
||||
|
||||
public static enum ReadOnlyPathAssert{
|
||||
APP_IMAGE(new Builder("--app-image").enable(cmd -> {
|
||||
// External app image should be R/O unless it is an app image signing on macOS.
|
||||
return !(TKit.isOSX() && MacHelper.signPredefinedAppImage(cmd));
|
||||
}).create()),
|
||||
APP_CONTENT(new Builder("--app-content").multiple().create()),
|
||||
RESOURCE_DIR(new Builder("--resource-dir").create()),
|
||||
MAC_DMG_CONTENT(new Builder("--mac-dmg-content").multiple().create()),
|
||||
RUNTIME_IMAGE(new Builder("--runtime-image").create());
|
||||
|
||||
ReadOnlyPathAssert(Function<JPackageCommand, List<Path>> getPaths) {
|
||||
this.getPaths = getPaths;
|
||||
}
|
||||
|
||||
List<Path> getPaths(JPackageCommand cmd) {
|
||||
return getPaths.apply(cmd).stream().toList();
|
||||
}
|
||||
|
||||
private final static class Builder {
|
||||
|
||||
Builder(String argName) {
|
||||
this.argName = Objects.requireNonNull(argName);
|
||||
}
|
||||
|
||||
Builder multiple() {
|
||||
multiple = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder enable(Predicate<JPackageCommand> v) {
|
||||
enable = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Function<JPackageCommand, List<Path>> create() {
|
||||
return cmd -> {
|
||||
if (enable != null && !enable.test(cmd)) {
|
||||
return List.of();
|
||||
} else {
|
||||
final List<Optional<Path>> dirs;
|
||||
if (multiple) {
|
||||
dirs = Stream.of(cmd.getAllArgumentValues(argName))
|
||||
.map(Builder::tokenizeValue)
|
||||
.flatMap(x -> x)
|
||||
.map(Builder::toExistingFile).toList();
|
||||
} else {
|
||||
dirs = Optional.ofNullable(cmd.getArgumentValue(argName))
|
||||
.map(Builder::toExistingFile).map(List::of).orElseGet(List::of);
|
||||
}
|
||||
|
||||
final var mutablePaths = Stream.of("--temp", "--dest")
|
||||
.map(cmd::getArgumentValue)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Builder::toExistingFile)
|
||||
.filter(Optional::isPresent).map(Optional::orElseThrow)
|
||||
.collect(toSet());
|
||||
|
||||
return dirs.stream()
|
||||
.filter(Optional::isPresent).map(Optional::orElseThrow)
|
||||
.filter(Predicate.not(mutablePaths::contains))
|
||||
.toList();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Optional<Path> toExistingFile(String path) {
|
||||
Objects.requireNonNull(path);
|
||||
try {
|
||||
return Optional.of(Path.of(path)).filter(Files::exists).map(Path::toAbsolutePath);
|
||||
} catch (InvalidPathException ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<String> tokenizeValue(String str) {
|
||||
return Stream.of(str.split(","));
|
||||
}
|
||||
|
||||
private Predicate<JPackageCommand> enable;
|
||||
private final String argName;
|
||||
private boolean multiple;
|
||||
}
|
||||
|
||||
private final Function<JPackageCommand, List<Path>> getPaths;
|
||||
}
|
||||
|
||||
public JPackageCommand setReadOnlyPathAsserts(ReadOnlyPathAssert... asserts) {
|
||||
readOnlyPathAsserts = Set.of(asserts);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageCommand excludeReadOnlyPathAssert(ReadOnlyPathAssert... asserts) {
|
||||
var asSet = Set.of(asserts);
|
||||
return setReadOnlyPathAsserts(readOnlyPathAsserts.stream().filter(Predicate.not(
|
||||
asSet::contains)).toArray(ReadOnlyPathAssert[]::new));
|
||||
}
|
||||
|
||||
public static enum AppLayoutAssert {
|
||||
APP_IMAGE_FILE(JPackageCommand::assertAppImageFile),
|
||||
PACKAGE_FILE(JPackageCommand::assertPackageFile),
|
||||
@ -1320,6 +1461,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
private final Actions verifyActions;
|
||||
private Path executeInDirectory;
|
||||
private Path winMsiLogFile;
|
||||
private Set<ReadOnlyPathAssert> readOnlyPathAsserts = Set.of(ReadOnlyPathAssert.values());
|
||||
private Set<AppLayoutAssert> appLayoutAsserts = Set.of(AppLayoutAssert.values());
|
||||
private List<Consumer<Iterator<String>>> outputValidators = new ArrayList<>();
|
||||
private static boolean defaultWithToolProvider;
|
||||
|
||||
@ -34,6 +34,7 @@ import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
@ -1130,6 +1131,47 @@ public final class TKit {
|
||||
new FileOutputStream(LOG_FILE.toFile(), true))).get();
|
||||
}
|
||||
|
||||
public record PathSnapshot(List<String> contentHashes) {
|
||||
public PathSnapshot {
|
||||
contentHashes.forEach(Objects::requireNonNull);
|
||||
}
|
||||
|
||||
public PathSnapshot(Path path) {
|
||||
this(hashRecursive(path));
|
||||
}
|
||||
|
||||
public static void assertEquals(PathSnapshot a, PathSnapshot b, String msg) {
|
||||
assertStringListEquals(a.contentHashes(), b.contentHashes(), msg);
|
||||
}
|
||||
|
||||
private static List<String> hashRecursive(Path path) {
|
||||
try {
|
||||
try (final var walk = Files.walk(path)) {
|
||||
return walk.sorted().map(p -> {
|
||||
final String hash;
|
||||
if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) {
|
||||
hash = "";
|
||||
} else {
|
||||
hash = hashFile(p);
|
||||
}
|
||||
return String.format("%s#%s", path.relativize(p), hash);
|
||||
}).toList();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static String hashFile(Path path) {
|
||||
try {
|
||||
final var time = Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS);
|
||||
return time.toString();
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static TestInstance currentTest;
|
||||
private static PrintStream extraLogStream;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user