[tests] untying updater tests from the platform and migrating to NIO 2 / JUnit 5

GitOrigin-RevId: 2ed7da4770b3923a15a70d427d3db6cfc1ad7bab
This commit is contained in:
Roman Shevchenko
2023-12-06 20:59:00 +01:00
committed by intellij-monorepo-bot
parent 4fff099128
commit da32cdba62
12 changed files with 1075 additions and 1244 deletions

View File

@@ -10,8 +10,7 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="jetbrains-annotations" level="project" />
<orderEntry type="library" name="jna" level="project" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
</component>
</module>

View File

@@ -3,6 +3,7 @@ package com.intellij.updater;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.*;
@@ -367,12 +368,20 @@ public class Patch {
return Digester.digestRegularFile(toFile);
}
public long digestFile(Path file) throws IOException {
return Digester.digest(file);
}
public Map<String, Long> digestFiles(File dir, Set<String> ignoredFiles) throws IOException {
return digestFiles(dir.toPath(), ignoredFiles);
}
public Map<String, Long> digestFiles(Path dir, Set<String> ignoredFiles) throws IOException {
Map<String, Long> result = new LinkedHashMap<>();
Utils.collectRelativePaths(dir.toPath()).parallelStream().forEachOrdered(path -> {
Utils.collectRelativePaths(dir).parallelStream().forEachOrdered(path -> {
if (!ignoredFiles.contains(path)) {
try {
long hash = digestFile(new File(dir, path));
long hash = digestFile(dir.resolve(path));
synchronized (result) {
result.put(path, hash);
}

View File

@@ -22,6 +22,8 @@ public final class Utils {
private static final CopyOption[] COPY_STANDARD = {LinkOption.NOFOLLOW_LINKS, StandardCopyOption.COPY_ATTRIBUTES};
private static final CopyOption[] COPY_REPLACE = {LinkOption.NOFOLLOW_LINKS, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING};
private static final CopyOption[] MOVE_STANDARD = {LinkOption.NOFOLLOW_LINKS, StandardCopyOption.ATOMIC_MOVE};
private static final CopyOption[] MOVE_REPLACE = {LinkOption.NOFOLLOW_LINKS, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING};
private static final boolean WIN_UNPRIVILEGED = IS_WINDOWS && Boolean.getBoolean("idea.unprivileged.process");
@@ -63,9 +65,12 @@ public final class Utils {
}
public static void delete(File file) throws IOException {
Path start = file.toPath();
if (Files.exists(start, LinkOption.NOFOLLOW_LINKS)) {
Files.walkFileTree(start, new SimpleFileVisitor<>() {
delete(file.toPath());
}
public static void delete(Path file) throws IOException {
if (Files.exists(file, LinkOption.NOFOLLOW_LINKS)) {
Files.walkFileTree(file, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
tryDelete(file);
@@ -119,13 +124,13 @@ public final class Utils {
return file.canExecute();
}
public static void setExecutable(File file) throws IOException {
setExecutable(file, true);
public static void setExecutable(Path file) throws IOException {
setExecutable(file.toFile());
}
public static void setExecutable(File file, boolean executable) throws IOException {
public static void setExecutable(File file) throws IOException {
LOG.info("Setting executable permissions for: " + file);
if (!file.setExecutable(executable, false)) {
if (!file.setExecutable(true, false)) {
throw new IOException("Cannot set executable permissions for: " + file);
}
}
@@ -145,11 +150,13 @@ public final class Utils {
}
public static void copy(File from, File to, boolean overwrite) throws IOException {
String message = from + (overwrite ? " over " : " into ") + to;
LOG.info(message);
copy(from.toPath(), to.toPath(), overwrite);
}
Path src = from.toPath(), dst = to.toPath();
BasicFileAttributes attrs = Files.readAttributes(src, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
public static void copy(Path src, Path dst, boolean overwrite) throws IOException {
LOG.info(src + (overwrite ? " over " : " into ") + dst);
var attrs = Files.readAttributes(src, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
if (attrs.isDirectory()) {
Files.createDirectories(dst);
}
@@ -193,6 +200,16 @@ public final class Utils {
});
}
public static void move(Path src, Path dst, boolean overwrite) throws IOException {
Files.createDirectories(dst.getParent());
Files.move(src, dst, overwrite ? MOVE_REPLACE : MOVE_STANDARD);
}
public static void writeString(Path file, String data) throws IOException {
Files.createDirectories(file.getParent());
Files.writeString(file, data);
}
public static void copyFileToStream(File from, OutputStream out) throws IOException {
try (InputStream in = new BufferedInputStream(new FileInputStream(from))) {
copyStream(in, out);

Binary file not shown.

View File

@@ -1,72 +1,67 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import com.intellij.openapi.util.io.IoTestUtil;
import com.intellij.openapi.util.io.NioFiles;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermissions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assume.assumeFalse;
public class DigesterTest extends UpdaterTestCase {
@Test
public void testBasics() throws Exception {
Path binDir = dataDir.toPath().resolve("bin"), libDir = dataDir.toPath().resolve("lib");
@UpdaterTest
class DigesterTest {
@UpdaterTestData Path dataDir;
@Test void basics() throws Exception {
var binDir = dataDir.resolve("bin");
var libDir = dataDir.resolve("lib");
assertThat(Digester.digest(binDir)).isEqualTo(Digester.DIRECTORY);
assertThat(Digester.digest(libDir)).isEqualTo(Digester.DIRECTORY);
assertThat(Digester.digest(dataDir.toPath().resolve("Readme.txt"))).isEqualTo(CHECKSUMS.README_TXT);
assertThat(Digester.digest(binDir.resolve("idea.bat"))).isEqualTo(CHECKSUMS.IDEA_BAT);
assertThat(Digester.digest(libDir.resolve("annotations.jar"))).isEqualTo(CHECKSUMS.ANNOTATIONS_JAR);
assertThat(Digester.digest(libDir.resolve("annotations_changed.jar"))).isEqualTo(CHECKSUMS.ANNOTATIONS_CHANGED_JAR);
assertThat(Digester.digest(libDir.resolve("boot.jar"))).isEqualTo(CHECKSUMS.BOOT_JAR);
assertThat(Digester.digest(libDir.resolve("boot_with_directory_becomes_file.jar"))).isEqualTo(CHECKSUMS.BOOT_CHANGED_JAR);
assertThat(Digester.digest(libDir.resolve("bootstrap.jar"))).isEqualTo(CHECKSUMS.BOOTSTRAP_JAR);
assertThat(Digester.digest(libDir.resolve("bootstrap_deleted.jar"))).isEqualTo(CHECKSUMS.BOOTSTRAP_DELETED_JAR);
assertThat(Digester.digest(libDir.resolve("annotations.jar"))).isEqualTo(UpdaterTestCase.ANNOTATIONS_JAR);
assertThat(Digester.digest(libDir.resolve("annotations_changed.jar"))).isEqualTo(UpdaterTestCase.ANNOTATIONS_CHANGED_JAR);
assertThat(Digester.digest(libDir.resolve("boot.jar"))).isEqualTo(UpdaterTestCase.BOOT_JAR);
assertThat(Digester.digest(libDir.resolve("boot_with_directory_becomes_file.jar"))).isEqualTo(UpdaterTestCase.BOOT_CHANGED_JAR);
assertThat(Digester.digest(libDir.resolve("bootstrap.jar"))).isEqualTo(UpdaterTestCase.BOOTSTRAP_JAR);
assertThat(Digester.digest(libDir.resolve("bootstrap_deleted.jar"))).isEqualTo(UpdaterTestCase.BOOTSTRAP_DELETED_JAR);
}
@Test
public void testHelpers() {
assertThat(Digester.isFile(CHECKSUMS.README_TXT)).isTrue();
assertThat(Digester.isFile(CHECKSUMS.ANNOTATIONS_JAR)).isTrue();
@Test void helpers() {
assertThat(Digester.isFile(UpdaterTestCase.README_TXT)).isTrue();
assertThat(Digester.isFile(UpdaterTestCase.ANNOTATIONS_JAR)).isTrue();
assertThat(Digester.isFile(Digester.INVALID)).isFalse();
assertThat(Digester.isFile(Digester.DIRECTORY)).isFalse();
assertThat(Digester.isSymlink(CHECKSUMS.LINK_TO_README_TXT)).isTrue();
assertThat(Digester.isSymlink(CHECKSUMS.README_TXT)).isFalse();
assertThat(Digester.isSymlink(UpdaterTestCase.LINK_TO_README_TXT)).isTrue();
assertThat(Digester.isSymlink(UpdaterTestCase.README_TXT)).isFalse();
assertThat(Digester.isSymlink(Digester.INVALID)).isFalse();
assertThat(Digester.isSymlink(Digester.DIRECTORY)).isFalse();
}
@Test
public void testSymlinks() throws Exception {
IoTestUtil.assumeSymLinkCreationIsSupported();
@Test void symlinks(@TempDir Path tempDir) throws Exception {
var simpleLink = Files.createSymbolicLink(tempDir.resolve("Readme.simple.link"), Path.of("Readme.txt"));
assertThat(Digester.digest(simpleLink)).isEqualTo(UpdaterTestCase.LINK_TO_README_TXT);
Path simpleLink = Files.createSymbolicLink(getTempFile("Readme.simple.link").toPath(), Path.of("Readme.txt"));
Path relativeLink = Files.createSymbolicLink(getTempFile("Readme.relative.link").toPath(), Path.of("./Readme.txt"));
Path absoluteLink = Files.createSymbolicLink(getTempFile("Readme.absolute.link").toPath(), dataDir.toPath().resolve("Readme.txt"));
assertThat(Digester.digest(simpleLink)).isEqualTo(CHECKSUMS.LINK_TO_README_TXT);
assertThat(Digester.digest(relativeLink)).isEqualTo(CHECKSUMS.LINK_TO_DOT_README_TXT);
var relativeLink = Files.createSymbolicLink(tempDir.resolve("Readme.relative.link"), Path.of("./Readme.txt"));
assertThat(Digester.digest(relativeLink))
.isEqualTo(File.separatorChar == '\\' ? UpdaterTestCase.LINK_TO_DOT_README_TXT_DOS : UpdaterTestCase.LINK_TO_DOT_README_TXT_UNIX);
var absoluteLink = Files.createSymbolicLink(tempDir.resolve("Readme.absolute.link"), dataDir.resolve("Readme.txt"));
assertThatThrownBy(() -> Digester.digest(absoluteLink))
.isInstanceOf(IOException.class)
.hasMessageStartingWith("An absolute link");
}
@Test
public void testExecutables() throws Exception {
assumeFalse("Windows-allergic", Utils.IS_WINDOWS);
Path testFile = Files.copy(dataDir.toPath().resolve("bin/idea.bat"), tempDir.getRoot().toPath().resolve("idea.bat"));
assertThat(Digester.digest(testFile)).isEqualTo(CHECKSUMS.IDEA_BAT);
NioFiles.setExecutable(testFile);
assertThat(Digester.digest(testFile)).isEqualTo(CHECKSUMS.IDEA_BAT | Digester.EXECUTABLE);
@Test @DisabledOnOs(OS.WINDOWS) void executables(@TempDir Path tempDir) throws Exception {
var testFile = Files.copy(dataDir.resolve("bin/idea.bat"), tempDir.resolve("idea.bat"));
assertThat(Digester.digest(testFile)).isEqualTo(UpdaterTestCase.IDEA_BAT);
Files.setPosixFilePermissions(testFile, PosixFilePermissions.fromString("r-xr-xr-x"));
assertThat(Digester.digest(testFile)).isEqualTo(UpdaterTestCase.IDEA_BAT | Digester.EXECUTABLE);
}
}

View File

@@ -1,78 +1,70 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.*;
import java.nio.channels.FileLock;
import java.nio.charset.StandardCharsets;
import java.io.File;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.intellij.openapi.util.io.IoTestUtil.assumeSymLinkCreationIsSupported;
import static com.intellij.updater.UpdaterTestCase.*;
import static org.assertj.core.api.Assertions.assertThat;
public class PatchCreationTest extends PatchTestCase {
@Test
public void testDigestFiles() throws Exception {
Patch patch = createPatch();
Map<String, Long> checkSums = digest(patch, myOlderDir);
assertThat(checkSums).hasSize(10);
@UpdaterTest
class PatchCreationTest {
@TempDir Path tempDir;
@UpdaterTestData Path dataDir;
private final ConsoleUpdaterUI testUI = new ConsoleUpdaterUI(false);
@Test void digestFiles() throws Exception {
var patch = new Patch(createPatchSpec(dataDir, dataDir));
assertThat(patch.digestFiles(dataDir, Set.of())).hasSize(11);
}
@Test
public void testBasics() throws Exception {
Patch patch = createPatch();
@Test void basics() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, true);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "bin/idea.bat", CHECKSUMS.IDEA_BAT),
new DeleteAction(patch, "bin/idea.bat", UpdaterTestCase.IDEA_BAT),
new CreateAction(patch, "newDir/"),
new CreateAction(patch, "newDir/newFile.txt"),
new UpdateAction(patch, "Readme.txt", CHECKSUMS.README_TXT),
new UpdateAction(patch, "lib/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR),
new UpdateAction(patch, "lib/bootstrap.jar", CHECKSUMS.BOOTSTRAP_JAR));
new UpdateAction(patch, "Readme.txt", UpdaterTestCase.README_TXT),
new UpdateAction(patch, "lib/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR),
new UpdateAction(patch, "lib/bootstrap.jar", UpdaterTestCase.BOOTSTRAP_JAR));
}
@Test
public void testCreatingWithIgnoredFiles() throws Exception {
PatchSpec spec = new PatchSpec()
.setOldFolder(myOlderDir.getAbsolutePath())
.setNewFolder(myNewerDir.getAbsolutePath())
.setIgnoredFiles(List.of("Readme.txt", "bin/idea.bat"));
Patch patch = new Patch(spec);
@Test void creatingWithIgnoredFiles() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, true);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir).setIgnoredFiles(List.of("Readme.txt", "bin/idea.bat")));
assertThat(sortActions(patch.getActions())).containsExactly(
new CreateAction(patch, "newDir/"),
new CreateAction(patch, "newDir/newFile.txt"),
new UpdateAction(patch, "lib/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR),
new UpdateAction(patch, "lib/bootstrap.jar", CHECKSUMS.BOOTSTRAP_JAR));
new UpdateAction(patch, "lib/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR),
new UpdateAction(patch, "lib/bootstrap.jar", UpdaterTestCase.BOOTSTRAP_JAR));
}
@Test
public void testValidation() throws Exception {
FileUtil.delete(new File(myNewerDir, "bin/focuskiller.dll"));
FileUtil.copy(new File(myOlderDir, "bin/focuskiller.dll"), new File(myNewerDir, "newDir/focuskiller.dll"));
Patch patch = createPatch();
@Test void validation() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, true);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
Files.writeString(dirs.oldDir.resolve("bin/idea.bat"), "changed");
Files.createDirectory(dirs.oldDir.resolve("extraDir"));
Files.writeString(dirs.oldDir.resolve("extraDir/extraFile.txt"), "");
Files.createDirectory(dirs.oldDir.resolve("newDir"));
Files.writeString(dirs.oldDir.resolve("newDir/newFile.txt"), "");
Files.writeString(dirs.oldDir.resolve("Readme.txt"), "changed");
Files.writeString(dirs.oldDir.resolve("lib/annotations.jar"), "changed");
Files.delete(dirs.oldDir.resolve("lib/bootstrap.jar"));
FileUtil.writeToFile(new File(myOlderDir, "bin/idea.bat"), "changed");
FileUtil.writeToFile(new File(myOlderDir, "bin/focuskiller.dll"), "changed");
FileUtil.createDirectory(new File(myOlderDir, "extraDir"));
FileUtil.writeToFile(new File(myOlderDir, "extraDir/extraFile.txt"), "");
FileUtil.createDirectory(new File(myOlderDir, "newDir"));
FileUtil.writeToFile(new File(myOlderDir, "newDir/newFile.txt"), "");
FileUtil.writeToFile(new File(myOlderDir, "Readme.txt"), "changed");
FileUtil.writeToFile(new File(myOlderDir, "lib/annotations.jar"), "changed");
FileUtil.delete(new File(myOlderDir, "lib/bootstrap.jar"));
assertThat(sortResults(patch.validate(myOlderDir, TEST_UI))).containsExactly(
new ValidationResult(ValidationResult.Kind.CONFLICT,
"bin/focuskiller.dll",
ValidationResult.Action.DELETE,
"Modified",
ValidationResult.Option.DELETE, ValidationResult.Option.KEEP),
assertThat(sortResults(patch.validate(dirs.oldDir.toFile(), testUI))).containsExactly(
new ValidationResult(ValidationResult.Kind.CONFLICT,
"bin/idea.bat",
ValidationResult.Action.DELETE,
@@ -93,11 +85,6 @@ public class PatchCreationTest extends PatchTestCase {
ValidationResult.Action.UPDATE,
"Modified",
ValidationResult.Option.IGNORE),
new ValidationResult(ValidationResult.Kind.ERROR,
"bin/focuskiller.dll",
ValidationResult.Action.UPDATE,
"Modified",
ValidationResult.Option.IGNORE),
new ValidationResult(ValidationResult.Kind.ERROR,
"lib/annotations.jar",
ValidationResult.Action.UPDATE,
@@ -110,61 +97,67 @@ public class PatchCreationTest extends PatchTestCase {
ValidationResult.Option.IGNORE));
}
@Test
public void testValidatingCaseOnlyRename() throws Exception {
Patch patch = createCaseOnlyRenamePatch();
assertThat(patch.validate(myOlderDir, TEST_UI)).isEmpty();
@Test void validatingCaseOnlyRename() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, true);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
simulateCaseOnlyRename(patch);
assertThat(patch.validate(dirs.oldDir.toFile(), testUI)).isEmpty();
}
@Test
public void testValidatingCaseOnlyRenameWithConflict() throws Exception {
assertThat(Runner.isCaseSensitiveFs()).isEqualTo(SystemInfo.isFileSystemCaseSensitive);
@Test void validatingCaseOnlyRenameWithConflict() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, true);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
simulateCaseOnlyRename(patch);
Files.writeString(dirs.oldDir.resolve("bin/IDEA.bat"), Files.readString(dirs.oldDir.resolve("bin/idea.bat")));
Patch patch = createCaseOnlyRenamePatch();
FileUtil.writeToFile(new File(myOlderDir, "bin/IDEA.bat"), FileUtil.loadFileBytes(new File(myOlderDir, "bin/idea.bat")));
List<ValidationResult> results = patch.validate(myOlderDir, TEST_UI);
if (SystemInfo.isFileSystemCaseSensitive) {
var results = patch.validate(dirs.oldDir.toFile(), testUI);
if (Runner.isCaseSensitiveFs()) {
assertThat(results).containsExactly(
new ValidationResult(ValidationResult.Kind.CONFLICT,
"bin/IDEA.bat",
ValidationResult.Action.CREATE,
"Already exists",
ValidationResult.Option.REPLACE, ValidationResult.Option.KEEP));
"bin/IDEA.bat",
ValidationResult.Action.CREATE,
"Already exists",
ValidationResult.Option.REPLACE, ValidationResult.Option.KEEP));
}
else {
assertThat(results).isEmpty();
}
}
@Test
public void testValidationWithOptionalFiles() throws Exception {
Patch patch1 = createPatch();
FileUtil.copy(new File(myOlderDir, "lib/boot.jar"), new File(myOlderDir, "lib/annotations.jar"));
assertThat(patch1.validate(myOlderDir, TEST_UI)).containsExactly(
private static void simulateCaseOnlyRename(Patch patch) {
assertThat(patch.getActions().get(0))
.isInstanceOf(DeleteAction.class)
.hasFieldOrPropertyWithValue("path", "bin/idea.bat");
patch.getActions().add(1, new CreateAction(patch, "bin/IDEA.bat")); // simulates rename "idea.bat" -> "IDEA.bat"
}
@Test void validationWithOptionalFiles() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, true);
var patch1 = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
Files.copy(dirs.oldDir.resolve("lib/boot.jar"), dirs.oldDir.resolve("lib/annotations.jar"), StandardCopyOption.REPLACE_EXISTING);
assertThat(patch1.validate(dirs.oldDir.toFile(), testUI)).containsExactly(
new ValidationResult(ValidationResult.Kind.ERROR,
"lib/annotations.jar",
ValidationResult.Action.UPDATE,
"Modified",
ValidationResult.Option.IGNORE));
PatchSpec spec = new PatchSpec()
.setOldFolder(myOlderDir.getAbsolutePath())
.setNewFolder(myNewerDir.getAbsolutePath())
.setOptionalFiles(List.of("lib/annotations.jar"));
Patch patch2 = new Patch(spec);
FileUtil.delete(new File(myOlderDir, "lib/annotations.jar"));
assertThat(patch2.validate(myOlderDir, TEST_UI)).isEmpty();
var patch2 = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir).setOptionalFiles(List.of("lib/annotations.jar")));
Files.delete(dirs.oldDir.resolve("lib/annotations.jar"));
assertThat(patch2.validate(dirs.oldDir.toFile(), testUI)).isEmpty();
}
@Test
public void testValidatingNonAccessibleFiles() throws Exception {
Patch patch = createPatch();
File f = new File(myOlderDir, "Readme.txt");
try (FileOutputStream s = new FileOutputStream(f, true); FileLock ignored = s.getChannel().lock()) {
String message = Utils.IS_WINDOWS ? "Locked by: [" + ProcessHandle.current().pid() + "] OpenJDK Platform binary" : "Access denied";
ValidationResult.Option option = Utils.IS_WINDOWS ? ValidationResult.Option.KILL_PROCESS : ValidationResult.Option.IGNORE;
assertThat(patch.validate(myOlderDir, TEST_UI)).containsExactly(
@Test void validatingNonAccessibleFiles() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, true);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
var file = dirs.oldDir.resolve("Readme.txt");
try (var channel = FileChannel.open(file, StandardOpenOption.APPEND); var ignored = channel.lock()) {
var message = Utils.IS_WINDOWS ? "Locked by: [" + ProcessHandle.current().pid() + "] OpenJDK Platform binary" : "Access denied";
var option = Utils.IS_WINDOWS ? ValidationResult.Option.KILL_PROCESS : ValidationResult.Option.IGNORE;
assertThat(patch.validate(dirs.oldDir.toFile(), testUI)).containsExactly(
new ValidationResult(ValidationResult.Kind.ERROR,
"Readme.txt",
ValidationResult.Action.UPDATE,
@@ -173,122 +166,112 @@ public class PatchCreationTest extends PatchTestCase {
}
}
@Test
public void testZipFileMove() throws Exception {
resetNewerDir();
FileUtil.rename(new File(myNewerDir, "lib/annotations.jar"), new File(myNewerDir, "lib/redist/annotations.jar"));
@Test void zipFileMove() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Utils.move(dirs.newDir.resolve("lib/annotations.jar"), dirs.newDir.resolve("lib/redist/annotations.jar"), false);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
Patch patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "lib/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR),
new DeleteAction(patch, "lib/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR),
new CreateAction(patch, "lib/redist/"),
new UpdateAction(patch, "lib/redist/annotations.jar", "lib/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR, true));
new UpdateAction(patch, "lib/redist/annotations.jar", "lib/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR, true));
}
@Test
public void testZipFileMoveWithUpdate() throws Exception {
resetNewerDir();
FileUtil.delete(new File(myNewerDir, "lib/annotations.jar"));
FileUtil.copy(new File(dataDir, "lib/annotations_changed.jar"), new File(myNewerDir, "lib/redist/annotations.jar"));
@Test void zipFileMoveWithUpdate() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.delete(dirs.newDir.resolve("lib/annotations.jar"));
Utils.copy(dataDir.resolve("lib/annotations_changed.jar"), dirs.newDir.resolve("lib/redist/annotations.jar"), false);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
Patch patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "lib/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR),
new DeleteAction(patch, "lib/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR),
new CreateAction(patch, "lib/redist/"),
new UpdateAction(patch, "lib/redist/annotations.jar", "lib/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR, false));
new UpdateAction(patch, "lib/redist/annotations.jar", "lib/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR, false));
}
@Test
public void testZipFileMoveWithAlternatives() throws Exception {
FileUtil.copy(new File(myOlderDir, "lib/annotations.jar"), new File(myOlderDir, "lib64/annotations.jar"));
resetNewerDir();
FileUtil.rename(new File(myNewerDir, "lib/annotations.jar"), new File(myNewerDir, "lib/redist/annotations.jar"));
FileUtil.rename(new File(myNewerDir, "lib64/annotations.jar"), new File(myNewerDir, "lib64/redist/annotations.jar"));
@Test void zipFileMoveWithAlternatives() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Utils.copy(dirs.oldDir.resolve("lib/annotations.jar"), dirs.oldDir.resolve("lib64/annotations.jar"), false);
Utils.copy(dirs.newDir.resolve("lib/annotations.jar"), dirs.newDir.resolve("lib64/redist/annotations.jar"), false);
Utils.move(dirs.newDir.resolve("lib/annotations.jar"), dirs.newDir.resolve("lib/redist/annotations.jar"), false);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
Patch patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "lib/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR),
new DeleteAction(patch, "lib64/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR),
new DeleteAction(patch, "lib/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR),
new DeleteAction(patch, "lib64/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR),
new CreateAction(patch, "lib/redist/"),
new CreateAction(patch, "lib64/redist/"),
new UpdateAction(patch, "lib/redist/annotations.jar", "lib/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR, true),
new UpdateAction(patch, "lib64/redist/annotations.jar", "lib64/annotations.jar", CHECKSUMS.ANNOTATIONS_JAR, true));
new UpdateAction(patch, "lib/redist/annotations.jar", "lib/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR, true),
new UpdateAction(patch, "lib64/redist/annotations.jar", "lib64/annotations.jar", UpdaterTestCase.ANNOTATIONS_JAR, true));
}
@Test
public void testNoOptionalFileMove1() throws Exception {
resetNewerDir();
FileUtil.copy(new File(dataDir, "lib/annotations.jar"), new File(myOlderDir, "lib/annotations.bin"));
FileUtil.copy(new File(dataDir, "lib/annotations_changed.jar"), new File(myOlderDir, "lib64/annotations.bin"));
FileUtil.copy(new File(dataDir, "lib/annotations.jar"), new File(myNewerDir, "lib/redist/annotations.bin"));
FileUtil.copy(new File(dataDir, "lib/annotations.jar"), new File(myNewerDir, "lib64/redist/annotations.bin"));
@Test void noOptionalFileMove1() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Utils.copy(dataDir.resolve("lib/annotations.jar"), dirs.oldDir.resolve("lib/annotations.bin"), false);
Utils.copy(dataDir.resolve("lib/annotations_changed.jar"), dirs.oldDir.resolve("lib64/annotations.bin"), false);
Utils.copy(dataDir.resolve("lib/annotations.jar"), dirs.newDir.resolve("lib/redist/annotations.bin"), false);
Utils.copy(dataDir.resolve("lib/annotations.jar"), dirs.newDir.resolve("lib64/redist/annotations.bin"), false);
Patch patch = createPatch(spec -> spec.setOptionalFiles(List.of("lib/annotations.bin", "lib/redist/annotations.bin")));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir).setOptionalFiles(List.of("lib/annotations.bin", "lib/redist/annotations.bin")));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "lib/annotations.bin", CHECKSUMS.ANNOTATIONS_JAR),
new DeleteAction(patch, "lib64/annotations.bin", CHECKSUMS.ANNOTATIONS_CHANGED_JAR),
new DeleteAction(patch, "lib/annotations.bin", UpdaterTestCase.ANNOTATIONS_JAR),
new DeleteAction(patch, "lib64/annotations.bin", UpdaterTestCase.ANNOTATIONS_CHANGED_JAR),
new CreateAction(patch, "lib/redist/"),
new CreateAction(patch, "lib64/redist/"),
new UpdateAction(patch, "lib/redist/annotations.bin", "lib/annotations.bin", CHECKSUMS.ANNOTATIONS_JAR, true),
new UpdateAction(patch, "lib64/redist/annotations.bin", "lib64/annotations.bin", CHECKSUMS.ANNOTATIONS_CHANGED_JAR, false));
new UpdateAction(patch, "lib/redist/annotations.bin", "lib/annotations.bin", UpdaterTestCase.ANNOTATIONS_JAR, true),
new UpdateAction(patch, "lib64/redist/annotations.bin", "lib64/annotations.bin", UpdaterTestCase.ANNOTATIONS_CHANGED_JAR, false));
}
@Test
public void testNoOptionalFileMove2() throws Exception {
resetNewerDir();
FileUtil.copy(new File(dataDir, "lib/annotations_changed.jar"), new File(myOlderDir, "lib/annotations.bin"));
FileUtil.copy(new File(dataDir, "lib/annotations.jar"), new File(myOlderDir, "lib64/annotations.bin"));
FileUtil.copy(new File(dataDir, "lib/annotations.jar"), new File(myNewerDir, "lib/redist/annotations.bin"));
FileUtil.copy(new File(dataDir, "lib/annotations.jar"), new File(myNewerDir, "lib64/redist/annotations.bin"));
@Test void noOptionalFileMove2() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Utils.copy(dataDir.resolve("lib/annotations_changed.jar"), dirs.oldDir.resolve("lib/annotations.bin"), false);
Utils.copy(dataDir.resolve("lib/annotations.jar"), dirs.oldDir.resolve("lib64/annotations.bin"), false);
Utils.copy(dataDir.resolve("lib/annotations.jar"), dirs.newDir.resolve("lib/redist/annotations.bin"), false);
Utils.copy(dataDir.resolve("lib/annotations.jar"), dirs.newDir.resolve("lib64/redist/annotations.bin"), false);
Patch patch = createPatch(spec -> spec.setOptionalFiles(List.of("lib/annotations.bin", "lib/redist/annotations.bin")));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir).setOptionalFiles(List.of("lib/annotations.bin", "lib/redist/annotations.bin")));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "lib/annotations.bin", CHECKSUMS.ANNOTATIONS_CHANGED_JAR),
new DeleteAction(patch, "lib64/annotations.bin", CHECKSUMS.ANNOTATIONS_JAR),
new DeleteAction(patch, "lib/annotations.bin", UpdaterTestCase.ANNOTATIONS_CHANGED_JAR),
new DeleteAction(patch, "lib64/annotations.bin", UpdaterTestCase.ANNOTATIONS_JAR),
new CreateAction(patch, "lib/redist/"),
new CreateAction(patch, "lib64/redist/"),
new UpdateAction(patch, "lib/redist/annotations.bin", "lib64/annotations.bin", CHECKSUMS.ANNOTATIONS_JAR, true),
new UpdateAction(patch, "lib64/redist/annotations.bin", "lib64/annotations.bin", CHECKSUMS.ANNOTATIONS_JAR, true));
new UpdateAction(patch, "lib/redist/annotations.bin", "lib64/annotations.bin", UpdaterTestCase.ANNOTATIONS_JAR, true),
new UpdateAction(patch, "lib64/redist/annotations.bin", "lib64/annotations.bin", UpdaterTestCase.ANNOTATIONS_JAR, true));
}
@Test
public void testSaveLoad() throws Exception {
Patch original = createPatch();
File f = getTempFile("file");
try (FileOutputStream out = new FileOutputStream(f)) {
@Test void testSaveLoad() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
var original = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
var f = tempDir.resolve("file");
try (var out = Files.newOutputStream(f)) {
original.write(out);
}
Patch recreated;
try (FileInputStream in = new FileInputStream(f)) {
try (var in = Files.newInputStream(f)) {
recreated = new Patch(in);
}
assertThat(recreated.getActions()).isEqualTo(original.getActions());
}
@Test
public void testNoSymlinkNoise() throws IOException {
assumeSymLinkCreationIsSupported();
@SuppressWarnings("DuplicateExpressions")
@Test void noSymlinkNoise() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.writeString(dirs.oldDir.resolve("bin/_target"), "test");
Files.createSymbolicLink(dirs.oldDir.resolve("bin/_link"), Path.of("_target"));
Files.writeString(dirs.newDir.resolve("bin/_target"), "test");
Files.createSymbolicLink(dirs.newDir.resolve("bin/_link"), Path.of("_target"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
Files.write(new File(myOlderDir, "bin/_target").toPath(), "test".getBytes(StandardCharsets.UTF_8));
Files.createSymbolicLink(new File(myOlderDir, "bin/_link").toPath(), Path.of("_target"));
resetNewerDir();
Patch patch = createPatch();
assertThat(patch.getActions()).isEmpty();
}
@Test
public void testSymlinkDereferenceAndMove() throws IOException {
assumeSymLinkCreationIsSupported();
@Test void testSymlinkDereferenceAndMove() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
var checksum = randomFile(dirs.oldDir.resolve("bin/mac_lib.jnilib"));
Files.createSymbolicLink(dirs.oldDir.resolve("bin/mac_lib.dylib"), Path.of("mac_lib.jnilib"));
Utils.copy(dirs.oldDir.resolve("bin/mac_lib.jnilib"), dirs.newDir.resolve("plugins/whatever/bin/mac_lib.dylib"), false);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
long checksum = randomFile(myOlderDir.toPath().resolve("bin/mac_lib.jnilib"));
Files.createSymbolicLink(myOlderDir.toPath().resolve("bin/mac_lib.dylib"), Path.of("mac_lib.jnilib"));
resetNewerDir();
Utils.delete(new File(myNewerDir, "bin/mac_lib.dylib"));
Files.createDirectories(new File(myNewerDir, "plugins/whatever/bin").toPath());
Files.move(new File(myNewerDir, "bin/mac_lib.jnilib").toPath(), new File(myNewerDir, "plugins/whatever/bin/mac_lib.dylib").toPath());
Patch patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "bin/mac_lib.dylib", linkHash("mac_lib.jnilib")),
new DeleteAction(patch, "bin/mac_lib.jnilib", checksum),
@@ -298,57 +281,151 @@ public class PatchCreationTest extends PatchTestCase {
new CreateAction(patch, "plugins/whatever/bin/mac_lib.dylib"));
}
@Test
public void testValidatingSymlinkToDirectory() throws Exception {
assumeSymLinkCreationIsSupported();
@Test void validatingSymlinkToDirectory() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.createDirectories(dirs.oldDir.resolve("other_dir"));
Files.createSymbolicLink(dirs.oldDir.resolve("dir"), Path.of("other_dir"));
Files.createDirectories(dirs.newDir.resolve("dir"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
resetNewerDir();
Files.createDirectories(myOlderDir.toPath().resolve("other_dir"));
Path target = Path.of("other_dir");
Files.createSymbolicLink(myOlderDir.toPath().resolve("dir"), target);
Files.createDirectories(myNewerDir.toPath().resolve("dir"));
Patch patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "dir", linkHash("other_dir")),
new DeleteAction(patch, "other_dir/", Digester.DIRECTORY),
new CreateAction(patch, "dir/"));
assertThat(patch.validate(myOlderDir, TEST_UI)).isEmpty();
assertThat(patch.validate(dirs.oldDir.toFile(), testUI)).isEmpty();
}
@Test
public void testValidatingMultipleSymlinkConversion() throws Exception {
assumeSymLinkCreationIsSupported();
resetNewerDir();
randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib.dylib"));
randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Resources/r/res.bin"));
@Test void validatingMultipleSymlinkConversion() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Libraries/lib.dylib"));
randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Resources/r/res.bin"));
Path target2 = Path.of("A");
Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Versions/Current"), target2);
Files.createSymbolicLink(dirs.oldDir.resolve("A.framework/Versions/Current"), target2);
Path target1 = Path.of("Versions/Current/Libraries");
Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Libraries"), target1);
Files.createSymbolicLink(dirs.oldDir.resolve("A.framework/Libraries"), target1);
Path target = Path.of("Versions/Current/Resources");
Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Resources"), target);
Files.createSymbolicLink(dirs.oldDir.resolve("A.framework/Resources"), target);
randomFile(dirs.newDir.resolve("A.framework/Libraries/lib.dylib"));
randomFile(dirs.newDir.resolve("A.framework/Resources/r/res.bin"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
randomFile(myNewerDir.toPath().resolve("A.framework/Libraries/lib.dylib"));
randomFile(myNewerDir.toPath().resolve("A.framework/Resources/r/res.bin"));
Patch patch = createPatch();
assertThat(patch.validate(myOlderDir, TEST_UI)).isEmpty();
assertThat(patch.validate(dirs.oldDir.toFile(), testUI)).isEmpty();
}
private Patch createCaseOnlyRenamePatch() throws IOException {
Patch patch = createPatch();
assertThat(patch.getActions().get(0))
.isInstanceOf(DeleteAction.class)
.hasFieldOrPropertyWithValue("path", "bin/idea.bat");
patch.getActions().add(1, new CreateAction(patch, "bin/IDEA.bat")); // simulates rename "idea.bat" -> "IDEA.bat"
return patch;
@SuppressWarnings("DuplicateExpressions")
@Test void same() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.createSymbolicLink(dirs.oldDir.resolve("Readme.link"), Path.of("Readme.txt"));
Files.createSymbolicLink(dirs.newDir.resolve("Readme.link"), Path.of("Readme.txt"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(patch.getActions()).containsExactly();
}
private static long linkHash(String target) throws IOException {
return Digester.digestStream(new ByteArrayInputStream(target.getBytes(StandardCharsets.UTF_8))) | Digester.SYM_LINK;
@Test void create() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.createSymbolicLink(dirs.newDir.resolve("Readme.link"), Path.of("Readme.txt"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(sortActions(patch.getActions())).containsExactly(
new CreateAction(patch, "Readme.link"));
}
@Test void delete() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.createSymbolicLink(dirs.oldDir.resolve("Readme.link"), Path.of("Readme.txt"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.link", UpdaterTestCase.LINK_TO_README_TXT));
}
@Test void rename() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
var target = Path.of("Readme.txt");
Files.createSymbolicLink(dirs.oldDir.resolve("Readme.lnk"), target);
Files.createSymbolicLink(dirs.newDir.resolve("Readme.link"), target);
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.lnk", UpdaterTestCase.LINK_TO_README_TXT),
new CreateAction(patch, "Readme.link"));
}
@Test void retarget() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.createSymbolicLink(dirs.oldDir.resolve("Readme.link"), Path.of("Readme.txt"));
Files.createSymbolicLink(dirs.newDir.resolve("Readme.link"), Path.of("./Readme.txt"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.link", UpdaterTestCase.LINK_TO_README_TXT),
new CreateAction(patch, "Readme.link"));
}
@Test void renameAndRetarget() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.createSymbolicLink(dirs.oldDir.resolve("Readme.lnk"), Path.of("./Readme.txt"));
Files.createSymbolicLink(dirs.newDir.resolve("Readme.link"), Path.of("Readme.txt"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.lnk",
File.separatorChar == '\\' ? UpdaterTestCase.LINK_TO_DOT_README_TXT_DOS : UpdaterTestCase.LINK_TO_DOT_README_TXT_UNIX),
new CreateAction(patch, "Readme.link"));
}
@Test void fileToLink() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
Files.move(dirs.newDir.resolve("Readme.txt"), dirs.newDir.resolve("Readme.md"));
Files.createSymbolicLink(dirs.newDir.resolve("Readme.txt"), Path.of("Readme.md"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.txt", UpdaterTestCase.README_TXT),
new CreateAction(patch, "Readme.md"),
new CreateAction(patch, "Readme.txt"));
}
@SuppressWarnings("DuplicateExpressions")
@Test void multipleDirectorySymlinks() throws Exception {
var dirs = prepareDirectories(tempDir, dataDir, false);
var l1 = randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Libraries/lib1.dylib"));
var l2 = randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Libraries/lib2.dylib"));
var r1 = randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Resources/r1.bin"));
var r2 = randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Resources/r2.bin"));
Files.createSymbolicLink(dirs.oldDir.resolve("A.framework/Versions/Current"), Path.of("A"));
Files.createSymbolicLink(dirs.oldDir.resolve("A.framework/Libraries"), Path.of("Versions/Current/Libraries"));
Files.createSymbolicLink(dirs.oldDir.resolve("A.framework/Resources"), Path.of("Versions/Current/Resources"));
randomFile(dirs.newDir.resolve("A.framework/Versions/A/Libraries/lib1.dylib"));
randomFile(dirs.newDir.resolve("A.framework/Versions/A/Libraries/lib2.dylib"));
randomFile(dirs.newDir.resolve("A.framework/Versions/A/Resources/r1.bin"));
randomFile(dirs.newDir.resolve("A.framework/Versions/A/Resources/r2.bin"));
randomFile(dirs.newDir.resolve("A.framework/Versions/B/Libraries/lib1.dylib"));
randomFile(dirs.newDir.resolve("A.framework/Versions/B/Libraries/lib2.dylib"));
randomFile(dirs.newDir.resolve("A.framework/Versions/B/Resources/r1.bin"));
randomFile(dirs.newDir.resolve("A.framework/Versions/B/Resources/r2.bin"));
Files.createSymbolicLink(dirs.newDir.resolve("A.framework/Versions/Previous"), Path.of("A"));
Files.createSymbolicLink(dirs.newDir.resolve("A.framework/Versions/Current"), Path.of("B"));
Files.createSymbolicLink(dirs.newDir.resolve("A.framework/Libraries"), Path.of("Versions/Current/Libraries"));
Files.createSymbolicLink(dirs.newDir.resolve("A.framework/Resources"), Path.of("Versions/Current/Resources"));
var patch = new Patch(createPatchSpec(dirs.oldDir, dirs.newDir));
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "A.framework/Versions/Current", linkHash("A")),
new CreateAction(patch, "A.framework/Versions/B/"),
new CreateAction(patch, "A.framework/Versions/B/Libraries/"),
new CreateAction(patch, "A.framework/Versions/B/Libraries/lib1.dylib"),
new CreateAction(patch, "A.framework/Versions/B/Libraries/lib2.dylib"),
new CreateAction(patch, "A.framework/Versions/B/Resources/"),
new CreateAction(patch, "A.framework/Versions/B/Resources/r1.bin"),
new CreateAction(patch, "A.framework/Versions/B/Resources/r2.bin"),
new CreateAction(patch, "A.framework/Versions/Current"),
new CreateAction(patch, "A.framework/Versions/Previous"),
new UpdateAction(patch, "A.framework/Versions/A/Libraries/lib1.dylib", l1),
new UpdateAction(patch, "A.framework/Versions/A/Libraries/lib2.dylib", l2),
new UpdateAction(patch, "A.framework/Versions/A/Resources/r1.bin", r1),
new UpdateAction(patch, "A.framework/Versions/A/Resources/r2.bin", r2));
}
}

View File

@@ -1,98 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import com.intellij.openapi.util.io.FileUtil;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.zip.CRC32;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
public abstract class PatchTestCase extends UpdaterTestCase {
protected File myNewerDir;
protected File myOlderDir;
@Override
public void before() throws Exception {
super.before();
myOlderDir = getTempFile("oldDir");
myNewerDir = getTempFile("newDir");
FileUtil.copyDir(dataDir, myOlderDir);
FileUtil.copyDir(dataDir, myNewerDir);
FileUtil.delete(new File(myNewerDir, "bin/idea.bat"));
FileUtil.writeToFile(new File(myNewerDir, "Readme.txt"), "hello");
FileUtil.writeToFile(new File(myNewerDir, "newDir/newFile.txt"), "hello");
FileUtil.delete(new File(myOlderDir, "lib/annotations_changed.jar"));
FileUtil.delete(new File(myNewerDir, "lib/annotations.jar"));
FileUtil.rename(new File(myNewerDir, "lib/annotations_changed.jar"),
new File(myNewerDir, "lib/annotations.jar"));
FileUtil.delete(new File(myOlderDir, "lib/bootstrap_deleted.jar"));
FileUtil.delete(new File(myNewerDir, "lib/bootstrap.jar"));
FileUtil.rename(new File(myNewerDir, "lib/bootstrap_deleted.jar"),
new File(myNewerDir, "lib/bootstrap.jar"));
}
protected Patch createPatch() throws IOException {
return createPatch(Function.identity());
}
protected Patch createPatch(Function<PatchSpec, PatchSpec> tuner) throws IOException {
PatchSpec spec = new PatchSpec()
.setOldFolder(myOlderDir.getAbsolutePath())
.setNewFolder(myNewerDir.getAbsolutePath());
return new Patch(tuner.apply(spec));
}
protected void resetNewerDir() throws IOException {
Utils.delete(myNewerDir);
Utils.copyDirectory(myOlderDir.toPath(), myNewerDir.toPath());
}
protected static Map<String, Long> digest(Patch patch, File dir) throws IOException {
return new TreeMap<>(patch.digestFiles(dir, Set.of()));
}
protected static List<PatchAction> sortActions(List<PatchAction> actions) {
return sort(actions, a -> a.getClass().getSimpleName().charAt(0), Comparator.comparing(PatchAction::getPath));
}
protected static List<ValidationResult> sortResults(List<ValidationResult> results) {
return sort(results, r -> r.action, Comparator.comparing(r -> r.path));
}
private static <T> List<T> sort(List<T> list, Function<T, ?> classifier, Comparator<T> sorter) {
// splits the list into groups
Collection<List<T>> groups = list.stream().collect(groupingBy(classifier, LinkedHashMap::new, toList())).values();
// verifies the list is monotonic
assertThat(list).isEqualTo(groups.stream().flatMap(Collection::stream).collect(toList()));
// sorts group elements and concatenates groups into a list
return groups.stream()
.flatMap(elements -> elements.stream().sorted(sorter))
.collect(toList());
}
protected static long randomFile(Path file) throws IOException {
Random rnd = new Random();
int size = (1 + rnd.nextInt(1023)) * 1024;
byte[] data = new byte[size];
rnd.nextBytes(data);
Files.createDirectories(file.getParent());
Files.write(file, data);
CRC32 crc32 = new CRC32();
crc32.update(data);
return crc32.getValue();
}
}

View File

@@ -1,16 +1,15 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class RunnerTest {
@Test
public void testExtractingFiles() {
String[] args = {"bar", "ignored=xxx;yyy;zzz/zzz", "critical=", "ignored=aaa", "baz", "critical=ccc"};
class RunnerTest {
@Test void extractingArgs() {
var args = new String[]{"bar", "ignored=xxx;yyy;zzz/zzz", "critical=", "ignored=aaa", "baz", "critical=ccc"};
assertThat(Runner.extractArguments(args, "ignored")).containsExactly("xxx", "yyy", "zzz/zzz", "aaa");
assertThat(Runner.extractArguments(args, "critical")).containsExactly("ccc");
assertThat(Runner.extractArguments(args, "unknown")).isEmpty();
}
}
}

View File

@@ -1,135 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import com.intellij.openapi.util.io.IoTestUtil;
import org.junit.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
public class SymlinkPatchTest extends PatchTestCase {
@Override
public void before() throws Exception {
IoTestUtil.assumeSymLinkCreationIsSupported();
super.before();
resetNewerDir();
}
@Test
public void same() throws Exception {
var target = Path.of("Readme.txt");
Files.createSymbolicLink(myOlderDir.toPath().resolve("Readme.link"), target);
Files.createSymbolicLink(myNewerDir.toPath().resolve("Readme.link"), target);
assertThat(createPatch().getActions()).containsExactly();
}
@Test
public void create() throws Exception {
Files.createSymbolicLink(myNewerDir.toPath().resolve("Readme.link"), Path.of("Readme.txt"));
var patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new CreateAction(patch, "Readme.link"));
}
@Test
public void delete() throws Exception {
Files.createSymbolicLink(myOlderDir.toPath().resolve("Readme.link"), Path.of("Readme.txt"));
var patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.link", CHECKSUMS.LINK_TO_README_TXT));
}
@Test
public void rename() throws Exception {
var target = Path.of("Readme.txt");
Files.createSymbolicLink(myOlderDir.toPath().resolve("Readme.lnk"), target);
Files.createSymbolicLink(myNewerDir.toPath().resolve("Readme.link"), target);
var patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.lnk", CHECKSUMS.LINK_TO_README_TXT),
new CreateAction(patch, "Readme.link"));
}
@Test
public void retarget() throws Exception {
Files.createSymbolicLink(myOlderDir.toPath().resolve("Readme.link"), Path.of("Readme.txt"));
Files.createSymbolicLink(myNewerDir.toPath().resolve("Readme.link"), Path.of("./Readme.txt"));
var patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.link", CHECKSUMS.LINK_TO_README_TXT),
new CreateAction(patch, "Readme.link"));
}
@Test
public void renameAndRetarget() throws Exception {
Files.createSymbolicLink(myOlderDir.toPath().resolve("Readme.lnk"), Path.of("./Readme.txt"));
Files.createSymbolicLink(myNewerDir.toPath().resolve("Readme.link"), Path.of("Readme.txt"));
var patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.lnk", CHECKSUMS.LINK_TO_DOT_README_TXT),
new CreateAction(patch, "Readme.link"));
}
@Test
public void fileToLink() throws Exception {
Files.move(myNewerDir.toPath().resolve("Readme.txt"), myNewerDir.toPath().resolve("Readme.md"));
Files.createSymbolicLink(myNewerDir.toPath().resolve("Readme.txt"), Path.of("Readme.md"));
var patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "Readme.txt", CHECKSUMS.README_TXT),
new CreateAction(patch, "Readme.md"),
new CreateAction(patch, "Readme.txt"));
}
@Test
@SuppressWarnings("DuplicateExpressions")
public void multipleDirectorySymlinks() throws Exception {
var l1 = randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib1.dylib"));
var l2 = randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib2.dylib"));
var r1 = randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Resources/r1.bin"));
var r2 = randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Resources/r2.bin"));
Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Versions/Current"), Path.of("A"));
Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Libraries"), Path.of("Versions/Current/Libraries"));
Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Resources"), Path.of("Versions/Current/Resources"));
randomFile(myNewerDir.toPath().resolve("A.framework/Versions/A/Libraries/lib1.dylib"));
randomFile(myNewerDir.toPath().resolve("A.framework/Versions/A/Libraries/lib2.dylib"));
randomFile(myNewerDir.toPath().resolve("A.framework/Versions/A/Resources/r1.bin"));
randomFile(myNewerDir.toPath().resolve("A.framework/Versions/A/Resources/r2.bin"));
randomFile(myNewerDir.toPath().resolve("A.framework/Versions/B/Libraries/lib1.dylib"));
randomFile(myNewerDir.toPath().resolve("A.framework/Versions/B/Libraries/lib2.dylib"));
randomFile(myNewerDir.toPath().resolve("A.framework/Versions/B/Resources/r1.bin"));
randomFile(myNewerDir.toPath().resolve("A.framework/Versions/B/Resources/r2.bin"));
Files.createSymbolicLink(myNewerDir.toPath().resolve("A.framework/Versions/Previous"), Path.of("A"));
Files.createSymbolicLink(myNewerDir.toPath().resolve("A.framework/Versions/Current"), Path.of("B"));
Files.createSymbolicLink(myNewerDir.toPath().resolve("A.framework/Libraries"), Path.of("Versions/Current/Libraries"));
Files.createSymbolicLink(myNewerDir.toPath().resolve("A.framework/Resources"), Path.of("Versions/Current/Resources"));
var patch = createPatch();
assertThat(sortActions(patch.getActions())).containsExactly(
new DeleteAction(patch, "A.framework/Versions/Current", 2305843012767948427L), // = crc32("A") | SYM_LINK
new CreateAction(patch, "A.framework/Versions/B/"),
new CreateAction(patch, "A.framework/Versions/B/Libraries/"),
new CreateAction(patch, "A.framework/Versions/B/Libraries/lib1.dylib"),
new CreateAction(patch, "A.framework/Versions/B/Libraries/lib2.dylib"),
new CreateAction(patch, "A.framework/Versions/B/Resources/"),
new CreateAction(patch, "A.framework/Versions/B/Resources/r1.bin"),
new CreateAction(patch, "A.framework/Versions/B/Resources/r2.bin"),
new CreateAction(patch, "A.framework/Versions/Current"),
new CreateAction(patch, "A.framework/Versions/Previous"),
new UpdateAction(patch, "A.framework/Versions/A/Libraries/lib1.dylib", l1),
new UpdateAction(patch, "A.framework/Versions/A/Libraries/lib2.dylib", l2),
new UpdateAction(patch, "A.framework/Versions/A/Resources/r1.bin", r1),
new UpdateAction(patch, "A.framework/Versions/A/Resources/r2.bin", r2));
}
}

View File

@@ -1,74 +1,168 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.testFramework.rules.TempDirectory;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.io.File;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
public abstract class UpdaterTestCase {
static {
UtilsTest.setRequiredDiskSpace();
import static org.assertj.core.api.Assertions.assertThat;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(UpdaterTestCase.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface UpdaterTest { }
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface UpdaterTestData { }
final class UpdaterTestCase implements BeforeAllCallback, AfterEachCallback {
static final long README_TXT = 7256327L;
static final long IDEA_BAT = 1681106766L;
static final long ANNOTATIONS_JAR = 2525796836L;
static final long ANNOTATIONS_CHANGED_JAR = 2587736223L;
static final long BOOT_JAR = 2697993201L;
static final long BOOT_CHANGED_JAR = 2957038758L;
static final long BOOTSTRAP_JAR = 2745721972L;
static final long BOOTSTRAP_DELETED_JAR = 811764767L;
static final long LINK_TO_README_TXT = 2305843011042707672L;
static final long LINK_TO_DOT_README_TXT_DOS = 2305843011210142148L;
static final long LINK_TO_DOT_README_TXT_UNIX = 2305843009503057206L;
private static Path ourDataDir;
@Override
public void beforeAll(ExtensionContext context) throws Exception {
System.setProperty("idea.required.space", Long.toString(20_000_000));
if (ourDataDir == null) {
var dir = Path.of("community/updater/testData");
if (!Files.exists(dir)) dir = Path.of("updater/testData");
if (!Files.exists(dir)) dir = Path.of("testData");
if (!Files.exists(dir)) throw new IllegalStateException("Cannot find test data directory under " + Path.of(".").toAbsolutePath());
ourDataDir = dir.toAbsolutePath();
}
Runner.checkCaseSensitivity(ourDataDir.toString());
@SuppressWarnings("OptionalGetWithoutIsPresent") var testInstance = context.getTestInstance().get();
var testClass = testInstance.getClass();
while (!testClass.isAnnotationPresent(UpdaterTest.class)) testClass = testClass.getSuperclass();
for (var field : testClass.getDeclaredFields()) {
if (field.isAnnotationPresent(UpdaterTestData.class)) {
field.set(testInstance, ourDataDir);
}
}
}
protected static class TestUpdaterUI extends ConsoleUpdaterUI {
public boolean cancelled = false;
protected TestUpdaterUI() { super(false); }
@Override public void startProcess(String title) { }
@Override public void checkCancelled() throws OperationCancelledException { if (cancelled) throw new OperationCancelledException(); }
@Override public void showError(String message) { }
}
@Rule public TempDirectory tempDir = new TempDirectory();
protected File dataDir;
protected TestUpdaterUI TEST_UI;
protected CheckSums CHECKSUMS;
@Before
public void before() throws Exception {
dataDir = PathManagerEx.findFileUnderCommunityHome("updater/testData");
Runner.checkCaseSensitivity(dataDir.getPath());
TEST_UI = new TestUpdaterUI();
CHECKSUMS = new CheckSums(
new File(dataDir, "Readme.txt").length() == 7132,
File.separatorChar == '\\');
}
@After
public void after() throws Exception {
@Override
public void afterEach(ExtensionContext context) throws Exception {
Utils.cleanup();
}
public File getTempFile(String fileName) {
return new File(tempDir.getRoot(), fileName);
}
@SuppressWarnings("FieldMayBeStatic")
protected static final class CheckSums {
public final long README_TXT;
public final long IDEA_BAT;
public final long ANNOTATIONS_JAR = 2525796836L;
public final long ANNOTATIONS_CHANGED_JAR = 2587736223L;
public final long BOOT_JAR = 2697993201L;
public final long BOOT_CHANGED_JAR = 2957038758L;
public final long BOOTSTRAP_JAR = 2745721972L;
public final long BOOTSTRAP_DELETED_JAR = 811764767L;
public final long LINK_TO_README_TXT = 2305843011042707672L;
public final long LINK_TO_DOT_README_TXT;
public CheckSums(boolean crLfs, boolean backwardSlashes) {
README_TXT = crLfs ? 1272723667L : 7256327L;
IDEA_BAT = crLfs ? 3535428112L : 1681106766L;
LINK_TO_DOT_README_TXT = backwardSlashes ? 2305843011210142148L : 2305843009503057206L;
static void setReadOnly(Path file) throws IOException {
if (Utils.IS_WINDOWS) {
Files.getFileAttributeView(file, DosFileAttributeView.class).setReadOnly(true);
}
else {
Files.setPosixFilePermissions(file, PosixFilePermissions.fromString("r--------"));
}
}
final static class Directories {
final Path oldDir, newDir;
Directories(Path oldDir, Path newDir) {
this.oldDir = oldDir;
this.newDir = newDir;
}
}
static Directories prepareDirectories(Path tempDir, Path dataDir, boolean mangle) throws IOException {
var oldDir = Files.createDirectory(tempDir.resolve("oldDir"));
Utils.copyDirectory(dataDir, oldDir);
Files.writeString(oldDir.resolve("Readme.txt"), Files.readString(dataDir.resolve("Readme.txt")).replace("\r\n", "\n"));
Files.writeString(oldDir.resolve("bin/idea.bat"), Files.readString(dataDir.resolve("bin/idea.bat")).replace("\r\n", "\n"));
Files.delete(oldDir.resolve("lib/annotations_changed.jar"));
Files.delete(oldDir.resolve("lib/bootstrap_deleted.jar"));
var newDir = Files.createDirectory(tempDir.resolve("newDir"));
if (mangle) {
Utils.copyDirectory(dataDir, newDir);
Files.writeString(newDir.resolve("Readme.txt"), "hello");
Files.delete(newDir.resolve("bin/idea.bat"));
Utils.writeString(newDir.resolve("newDir/newFile.txt"), "hello");
Files.delete(newDir.resolve("lib/annotations.jar"));
Files.move(newDir.resolve("lib/annotations_changed.jar"), newDir.resolve("lib/annotations.jar"));
Files.delete(newDir.resolve("lib/bootstrap.jar"));
Files.move(newDir.resolve("lib/bootstrap_deleted.jar"), newDir.resolve("lib/bootstrap.jar"));
}
else {
Utils.copyDirectory(oldDir, newDir);
}
return new Directories(oldDir, newDir);
}
static PatchSpec createPatchSpec(Path oldDir, Path newDir) {
return new PatchSpec()
.setOldFolder(oldDir.toString()).setOldVersionDescription("<old>")
.setNewFolder(newDir.toString()).setNewVersionDescription("<new>");
}
static List<PatchAction> sortActions(List<PatchAction> actions) {
return sort(actions, a -> a.getClass().getSimpleName().charAt(0), Comparator.comparing(PatchAction::getPath));
}
static List<ValidationResult> sortResults(List<ValidationResult> results) {
return sort(results, r -> r.action, Comparator.comparing(r -> r.path));
}
private static <T> List<T> sort(List<T> list, Function<T, ?> classifier, Comparator<T> sorter) {
// splits the list into groups
var groups = list.stream().collect(Collectors.groupingBy(classifier, LinkedHashMap::new, Collectors.toList())).values();
// verifies the list is monotonic
assertThat(list).isEqualTo(groups.stream().flatMap(Collection::stream).collect(Collectors.toList()));
// sorts group elements and concatenates groups into a list
return groups.stream()
.flatMap(elements -> elements.stream().sorted(sorter))
.collect(Collectors.toList());
}
static long randomFile(Path file) throws IOException {
var rnd = new Random();
var size = (1 + rnd.nextInt(1023)) * 1024;
var data = new byte[size];
rnd.nextBytes(data);
Files.createDirectories(file.getParent());
Files.write(file, data);
var crc32 = new CRC32();
crc32.update(data);
return crc32.getValue();
}
static long linkHash(String target) throws IOException {
return Digester.digestStream(new ByteArrayInputStream(target.getBytes(StandardCharsets.UTF_8))) | Digester.SYM_LINK;
}
}

View File

@@ -1,153 +1,93 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.IoTestUtil;
import com.intellij.testFramework.rules.TempDirectory;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeFalse;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class UtilsTest {
static {
setRequiredDiskSpace();
@UpdaterTest
class UtilsTest {
@TempDir Path tempDir;
@Test void delete() throws Exception {
var file = Files.createFile(tempDir.resolve("temp_file"));
Utils.delete(file);
assertThat(file).doesNotExist();
}
static void setRequiredDiskSpace() {
System.setProperty("idea.required.space", Long.toString(20_000_000));
@Test void deleteReadonlyFile() throws Exception {
var dir = Files.createDirectory(tempDir.resolve("temp_dir"));
var file = Files.createFile(dir.resolve("temp_file"));
UpdaterTestCase.setReadOnly(file);
Utils.delete(dir);
assertThat(dir).doesNotExist();
}
@Rule public TempDirectory tempDir = new TempDirectory();
@Test
public void testDelete() throws Exception {
File f = tempDir.newFile("temp_file");
assertTrue(f.exists());
Utils.delete(f);
assertFalse(f.exists());
@Test @EnabledOnOs(OS.WINDOWS) void deleteLockedFileOnWindows() throws Exception {
var file = Files.createFile(tempDir.resolve("temp_file"));
var timing = new AtomicLong(0L);
assertThatThrownBy(() -> {
try (var os = Files.newOutputStream(file)) {
// This locks the file on Windows, preventing it from being deleted. Utils.delete() will retry for about 100 ms.
os.write("test".getBytes(StandardCharsets.UTF_8));
var t = System.nanoTime();
try {
Utils.delete(file);
}
finally {
timing.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t));
}
}
}).hasMessage("Cannot delete: " + file.toAbsolutePath());
assertThat(timing.get()).as("Utils.delete took " + timing + " ms, which is less than expected").isGreaterThanOrEqualTo(95);
}
@Test
public void testDeleteReadonlyFile() throws Exception {
File f = tempDir.newFile("temp_dir/temp_file");
assertTrue(f.setWritable(false, false));
File d = f.getParentFile();
assertTrue(d.exists());
Utils.delete(d);
assertFalse(d.exists());
}
@Test
public void testDeleteLockedFileOnWindows() {
IoTestUtil.assumeWindows();
File f = tempDir.newFile("temp_file");
assertTrue(f.exists());
long ts = 0;
try (OutputStream os = new FileOutputStream(f)) {
// This locks the file on Windows, preventing it from being deleted.
// Utils.delete() will retry for about 100 ms.
@Test @DisabledOnOs(OS.WINDOWS) void deleteLockedFileOnUnix() throws Exception {
var file = Files.createFile(tempDir.resolve("temp_file"));
try (var os = Files.newOutputStream(file)) {
os.write("test".getBytes(StandardCharsets.UTF_8));
ts = System.nanoTime();
Utils.delete(f);
fail("Utils.delete did not fail with the expected IOException on Windows");
}
catch (IOException e) {
ts = (System.nanoTime() - ts) / 1_000_000;
assertEquals("Cannot delete: " + f.getAbsolutePath(), e.getMessage());
assertThat(ts).as("Utils.delete took " + ts + " ms, which is less than expected").isGreaterThanOrEqualTo(95);
Utils.delete(file);
}
}
@Test
public void testDeleteLockedFileOnUnix() throws Exception {
assumeFalse("Windows-allergic", Utils.IS_WINDOWS);
File f = tempDir.newFile("temp_file");
assertTrue(f.exists());
try (OutputStream os = new FileOutputStream(f)) {
os.write("test".getBytes(StandardCharsets.UTF_8));
Utils.delete(f);
}
}
@Test
public void testRecursiveDelete() throws Exception {
File topDir = tempDir.newDirectory("temp_dir");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
File file = new File(topDir, "dir" + i + "/file" + j);
FileUtil.writeToFile(file, "test");
assertTrue(file.exists());
@Test void recursiveDelete() throws Exception {
var topDir = Files.createDirectory(tempDir.resolve("temp_dir"));
for (var i = 0; i < 3; i++) {
var subDir = Files.createDirectory(topDir.resolve("dir" + i));
for (var j = 0; j < 3; j++) {
Files.writeString(subDir.resolve("file" + j), "test");
}
}
Utils.delete(topDir);
assertFalse(topDir.exists());
assertThat(topDir).doesNotExist();
}
@Test
public void testNonRecursiveSymlinkDelete() throws Exception {
IoTestUtil.assumeSymLinkCreationIsSupported();
File dir = tempDir.newDirectory("temp_dir");
File file = new File(dir, "file");
FileUtil.writeToFile(file, "test");
assertThat(dir.listFiles()).containsExactly(file);
File link = new File(tempDir.getRoot(), "link");
Path link1 = link.toPath();
Files.createSymbolicLink(link1, dir.toPath().getFileName());
assertTrue(Utils.isLink(link));
assertThat(link.listFiles()).hasSize(1);
@Test void nonRecursiveSymlinkDelete() throws Exception {
var dir = Files.createDirectory(tempDir.resolve("temp_dir"));
var file = Files.createFile(dir.resolve("file"));
var link = Files.createSymbolicLink(tempDir.resolve("link"), dir.getFileName());
Utils.delete(link);
assertFalse(link.exists());
assertThat(dir.listFiles()).containsExactly(file);
assertThat(link).doesNotExist();
assertThat(file).exists();
}
@Test
public void testDeleteDanglingSymlink() throws Exception {
IoTestUtil.assumeSymLinkCreationIsSupported();
File dir = tempDir.newDirectory("temp_dir");
File link = new File(dir, "link");
Path link1 = link.toPath();
Path target = Path.of("dangling");
Files.createSymbolicLink(link1, target);
assertThat(dir.listFiles()).containsExactly(link);
@Test void deleteDanglingSymlink() throws Exception {
var dir = Files.createDirectory(tempDir.resolve("temp_dir"));
var link = Files.createSymbolicLink(dir.resolve("link"), Path.of("dangling"));
Utils.delete(link);
assertThat(dir.listFiles()).isEmpty();
assertThat(dir).isEmptyDirectory();
}
}