Skip to content

Commit ec97732

Browse files
Alexey Semenyukalexeysemenyukoracle
authored andcommitted
8333568: Test that jpackage doesn't modify R/O files/directories
Reviewed-by: almatvee
1 parent 6e96b82 commit ec97732

File tree

2 files changed

+189
-5
lines changed

2 files changed

+189
-5
lines changed

test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,18 @@
2323

2424
package jdk.jpackage.test;
2525

26+
import static java.util.stream.Collectors.groupingBy;
27+
import static java.util.stream.Collectors.mapping;
28+
import static java.util.stream.Collectors.toList;
29+
import static java.util.stream.Collectors.toMap;
30+
import static java.util.stream.Collectors.toSet;
31+
import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher;
32+
2633
import java.io.FileOutputStream;
2734
import java.io.IOException;
2835
import java.nio.charset.StandardCharsets;
2936
import java.nio.file.Files;
37+
import java.nio.file.InvalidPathException;
3038
import java.nio.file.Path;
3139
import java.security.SecureRandom;
3240
import java.util.ArrayList;
@@ -46,7 +54,6 @@
4654
import java.util.regex.Pattern;
4755
import java.util.stream.Collectors;
4856
import java.util.stream.Stream;
49-
import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher;
5057
import jdk.jpackage.internal.util.function.ThrowingConsumer;
5158
import jdk.jpackage.internal.util.function.ThrowingFunction;
5259
import jdk.jpackage.internal.util.function.ThrowingRunnable;
@@ -78,6 +85,7 @@ public JPackageCommand(JPackageCommand cmd) {
7885
prerequisiteActions = new Actions(cmd.prerequisiteActions);
7986
verifyActions = new Actions(cmd.verifyActions);
8087
appLayoutAsserts = cmd.appLayoutAsserts;
88+
readOnlyPathAsserts = cmd.readOnlyPathAsserts;
8189
outputValidators = cmd.outputValidators;
8290
executeInDirectory = cmd.executeInDirectory;
8391
winMsiLogFile = cmd.winMsiLogFile;
@@ -843,10 +851,13 @@ public Executor.Result execute(int expectedExitCode) {
843851
}
844852
}
845853

846-
Executor.Result result = new JPackageCommand(this)
847-
.adjustArgumentsBeforeExecution()
848-
.createExecutor()
849-
.execute(expectedExitCode);
854+
final var copy = new JPackageCommand(this).adjustArgumentsBeforeExecution();
855+
856+
final var directoriesAssert = new ReadOnlyPathsAssert(copy);
857+
858+
Executor.Result result = copy.createExecutor().execute(expectedExitCode);
859+
860+
directoriesAssert.updateAndAssert();
850861

851862
for (final var outputValidator: outputValidators) {
852863
outputValidator.accept(result.getOutput().iterator());
@@ -903,6 +914,136 @@ public String macroValue(Macro macro) {
903914
return macro.value(this);
904915
}
905916

917+
private static final class ReadOnlyPathsAssert {
918+
ReadOnlyPathsAssert(JPackageCommand cmd) {
919+
this.asserts = cmd.readOnlyPathAsserts.stream().map(a -> {
920+
return a.getPaths(cmd).stream().map(dir -> {
921+
return Map.entry(a, dir);
922+
});
923+
}).flatMap(x -> x).collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));
924+
snapshots = createSnapshots();
925+
}
926+
927+
void updateAndAssert() {
928+
final var newSnapshots = createSnapshots();
929+
for (final var a : asserts.keySet().stream().sorted().toList()) {
930+
final var snapshopGroup = snapshots.get(a);
931+
final var newSnapshopGroup = newSnapshots.get(a);
932+
for (int i = 0; i < snapshopGroup.size(); i++) {
933+
TKit.PathSnapshot.assertEquals(snapshopGroup.get(i), newSnapshopGroup.get(i),
934+
String.format("Check jpackage didn't modify ${%s}=[%s]", a, asserts.get(a).get(i)));
935+
}
936+
}
937+
}
938+
939+
private Map<ReadOnlyPathAssert, List<TKit.PathSnapshot>> createSnapshots() {
940+
return asserts.entrySet().stream()
941+
.map(e -> {
942+
return Map.entry(e.getKey(), e.getValue().stream().map(TKit.PathSnapshot::new).toList());
943+
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
944+
}
945+
946+
private final Map<ReadOnlyPathAssert, List<Path>> asserts;
947+
private final Map<ReadOnlyPathAssert, List<TKit.PathSnapshot>> snapshots;
948+
}
949+
950+
public static enum ReadOnlyPathAssert{
951+
APP_IMAGE(new Builder("--app-image").enable(cmd -> {
952+
// External app image should be R/O unless it is an app image signing on macOS.
953+
return !(TKit.isOSX() && MacHelper.signPredefinedAppImage(cmd));
954+
}).create()),
955+
APP_CONTENT(new Builder("--app-content").multiple().create()),
956+
RESOURCE_DIR(new Builder("--resource-dir").create()),
957+
MAC_DMG_CONTENT(new Builder("--mac-dmg-content").multiple().create()),
958+
RUNTIME_IMAGE(new Builder("--runtime-image").create());
959+
960+
ReadOnlyPathAssert(Function<JPackageCommand, List<Path>> getPaths) {
961+
this.getPaths = getPaths;
962+
}
963+
964+
List<Path> getPaths(JPackageCommand cmd) {
965+
return getPaths.apply(cmd).stream().toList();
966+
}
967+
968+
private final static class Builder {
969+
970+
Builder(String argName) {
971+
this.argName = Objects.requireNonNull(argName);
972+
}
973+
974+
Builder multiple() {
975+
multiple = true;
976+
return this;
977+
}
978+
979+
Builder enable(Predicate<JPackageCommand> v) {
980+
enable = v;
981+
return this;
982+
}
983+
984+
Function<JPackageCommand, List<Path>> create() {
985+
return cmd -> {
986+
if (enable != null && !enable.test(cmd)) {
987+
return List.of();
988+
} else {
989+
final List<Optional<Path>> dirs;
990+
if (multiple) {
991+
dirs = Stream.of(cmd.getAllArgumentValues(argName))
992+
.map(Builder::tokenizeValue)
993+
.flatMap(x -> x)
994+
.map(Builder::toExistingFile).toList();
995+
} else {
996+
dirs = Optional.ofNullable(cmd.getArgumentValue(argName))
997+
.map(Builder::toExistingFile).map(List::of).orElseGet(List::of);
998+
}
999+
1000+
final var mutablePaths = Stream.of("--temp", "--dest")
1001+
.map(cmd::getArgumentValue)
1002+
.filter(Objects::nonNull)
1003+
.map(Builder::toExistingFile)
1004+
.filter(Optional::isPresent).map(Optional::orElseThrow)
1005+
.collect(toSet());
1006+
1007+
return dirs.stream()
1008+
.filter(Optional::isPresent).map(Optional::orElseThrow)
1009+
.filter(Predicate.not(mutablePaths::contains))
1010+
.toList();
1011+
}
1012+
};
1013+
}
1014+
1015+
private static Optional<Path> toExistingFile(String path) {
1016+
Objects.requireNonNull(path);
1017+
try {
1018+
return Optional.of(Path.of(path)).filter(Files::exists).map(Path::toAbsolutePath);
1019+
} catch (InvalidPathException ex) {
1020+
return Optional.empty();
1021+
}
1022+
}
1023+
1024+
private static Stream<String> tokenizeValue(String str) {
1025+
return Stream.of(str.split(","));
1026+
}
1027+
1028+
private Predicate<JPackageCommand> enable;
1029+
private final String argName;
1030+
private boolean multiple;
1031+
}
1032+
1033+
private final Function<JPackageCommand, List<Path>> getPaths;
1034+
}
1035+
1036+
public JPackageCommand setReadOnlyPathAsserts(ReadOnlyPathAssert... asserts) {
1037+
readOnlyPathAsserts = Set.of(asserts);
1038+
return this;
1039+
}
1040+
1041+
public JPackageCommand excludeReadOnlyPathAssert(ReadOnlyPathAssert... asserts) {
1042+
var asSet = Set.of(asserts);
1043+
return setReadOnlyPathAsserts(readOnlyPathAsserts.stream().filter(Predicate.not(
1044+
asSet::contains)).toArray(ReadOnlyPathAssert[]::new));
1045+
}
1046+
9061047
public static enum AppLayoutAssert {
9071048
APP_IMAGE_FILE(JPackageCommand::assertAppImageFile),
9081049
PACKAGE_FILE(JPackageCommand::assertPackageFile),
@@ -1320,6 +1461,7 @@ public void run() {
13201461
private final Actions verifyActions;
13211462
private Path executeInDirectory;
13221463
private Path winMsiLogFile;
1464+
private Set<ReadOnlyPathAssert> readOnlyPathAsserts = Set.of(ReadOnlyPathAssert.values());
13231465
private Set<AppLayoutAssert> appLayoutAsserts = Set.of(AppLayoutAssert.values());
13241466
private List<Consumer<Iterator<String>>> outputValidators = new ArrayList<>();
13251467
private static boolean defaultWithToolProvider;

test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.lang.reflect.InvocationTargetException;
3535
import java.nio.file.FileSystems;
3636
import java.nio.file.Files;
37+
import java.nio.file.LinkOption;
3738
import java.nio.file.Path;
3839
import java.nio.file.StandardCopyOption;
3940
import java.nio.file.StandardWatchEventKinds;
@@ -1130,6 +1131,47 @@ private static PrintStream openLogStream() {
11301131
new FileOutputStream(LOG_FILE.toFile(), true))).get();
11311132
}
11321133

1134+
public record PathSnapshot(List<String> contentHashes) {
1135+
public PathSnapshot {
1136+
contentHashes.forEach(Objects::requireNonNull);
1137+
}
1138+
1139+
public PathSnapshot(Path path) {
1140+
this(hashRecursive(path));
1141+
}
1142+
1143+
public static void assertEquals(PathSnapshot a, PathSnapshot b, String msg) {
1144+
assertStringListEquals(a.contentHashes(), b.contentHashes(), msg);
1145+
}
1146+
1147+
private static List<String> hashRecursive(Path path) {
1148+
try {
1149+
try (final var walk = Files.walk(path)) {
1150+
return walk.sorted().map(p -> {
1151+
final String hash;
1152+
if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) {
1153+
hash = "";
1154+
} else {
1155+
hash = hashFile(p);
1156+
}
1157+
return String.format("%s#%s", path.relativize(p), hash);
1158+
}).toList();
1159+
}
1160+
} catch (IOException ex) {
1161+
throw new UncheckedIOException(ex);
1162+
}
1163+
}
1164+
1165+
private static String hashFile(Path path) {
1166+
try {
1167+
final var time = Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS);
1168+
return time.toString();
1169+
} catch (IOException ex) {
1170+
throw new UncheckedIOException(ex);
1171+
}
1172+
}
1173+
}
1174+
11331175
private static TestInstance currentTest;
11341176
private static PrintStream extraLogStream;
11351177

0 commit comments

Comments
 (0)