diff --git a/updater/intellij.platform.updater.iml b/updater/intellij.platform.updater.iml index 588a63511148..878da71854f9 100644 --- a/updater/intellij.platform.updater.iml +++ b/updater/intellij.platform.updater.iml @@ -10,8 +10,7 @@ - - + \ No newline at end of file diff --git a/updater/src/com/intellij/updater/Patch.java b/updater/src/com/intellij/updater/Patch.java index 215adde5051d..9caf822db5cf 100644 --- a/updater/src/com/intellij/updater/Patch.java +++ b/updater/src/com/intellij/updater/Patch.java @@ -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 digestFiles(File dir, Set ignoredFiles) throws IOException { + return digestFiles(dir.toPath(), ignoredFiles); + } + + public Map digestFiles(Path dir, Set ignoredFiles) throws IOException { Map 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); } diff --git a/updater/src/com/intellij/updater/Utils.java b/updater/src/com/intellij/updater/Utils.java index 9d450dac5108..e7ae9ef513af 100644 --- a/updater/src/com/intellij/updater/Utils.java +++ b/updater/src/com/intellij/updater/Utils.java @@ -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); diff --git a/updater/testData/bin/focuskiller.dll b/updater/testData/bin/focuskiller.dll deleted file mode 100644 index c1f8e043939a..000000000000 Binary files a/updater/testData/bin/focuskiller.dll and /dev/null differ diff --git a/updater/testSrc/com/intellij/updater/DigesterTest.java b/updater/testSrc/com/intellij/updater/DigesterTest.java index 7cb268782dea..9763dcf57d23 100644 --- a/updater/testSrc/com/intellij/updater/DigesterTest.java +++ b/updater/testSrc/com/intellij/updater/DigesterTest.java @@ -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); } } diff --git a/updater/testSrc/com/intellij/updater/PatchApplyingRevertingTest.java b/updater/testSrc/com/intellij/updater/PatchApplyingRevertingTest.java index b7aa838dcdd1..1221d9e97c88 100644 --- a/updater/testSrc/com/intellij/updater/PatchApplyingRevertingTest.java +++ b/updater/testSrc/com/intellij/updater/PatchApplyingRevertingTest.java @@ -1,12 +1,12 @@ // 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.openapi.util.io.NioFiles; -import com.intellij.util.containers.ContainerUtil; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +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.ByteArrayInputStream; import java.io.File; @@ -15,119 +15,118 @@ import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.nio.file.StandardCopyOption; +import java.util.*; import java.util.function.BiConsumer; import java.util.stream.Collectors; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; +import static com.intellij.updater.UpdaterTestCase.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; -@SuppressWarnings({"JUnit3StyleTestMethodInJUnit4Class", "DuplicateExpressions"}) -public abstract class PatchApplyingRevertingTest extends PatchTestCase { - @SuppressWarnings("JUnitTestCaseWithNoTests") - public static final class StandardModeTest extends PatchApplyingRevertingTest { } +@UpdaterTest +abstract class PatchApplyingRevertingTest { + static final class StandardModeTest extends PatchApplyingRevertingTest { } + static final class NoBackupTest extends PatchApplyingRevertingTest { } - @SuppressWarnings("JUnitTestCaseWithNoTests") - public static final class NoBackupTest extends PatchApplyingRevertingTest { } + private static final class CancellableUI extends ConsoleUpdaterUI { + private boolean cancelled = false; - private File myFile; - private PatchSpec myPatchSpec; - private boolean myDoBackup; + private CancellableUI() { + super(false); + } - private PatchApplyingRevertingTest() { } - - @Before - @Override - public void before() throws Exception { - super.before(); - myFile = getTempFile("patch.zip"); - myPatchSpec = new PatchSpec() - .setOldFolder(myOlderDir.getAbsolutePath()) - .setNewFolder(myNewerDir.getAbsolutePath()); - myDoBackup = !(this instanceof NoBackupTest); - } - - @Test - public void testCreatingAndApplying() throws Exception { - assertAppliedAndReverted(); - } - - @Test - public void testCreatingAndApplyingStrict() throws Exception { - myPatchSpec.setStrict(true); - assertAppliedAndReverted(); - } - - @Test - public void testCreatingAndApplyingOnADifferentRoot() throws Exception { - myPatchSpec.setRoot("bin/"); - myPatchSpec.setStrict(true); - createPatch(); - - assertAppliedAndReverted(PatchFileCreator.prepareAndValidate(myFile, new File(myOlderDir, "bin"), TEST_UI)); - } - - @Test - public void testCreatingAndFailingOnADifferentRoot() throws Exception { - myPatchSpec.setRoot("bin/"); - myPatchSpec.setStrict(true); - createPatch(); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, new File(myOlderDir, "bin"), TEST_UI); - preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(preparationResult.patch)); - assertNotApplied(preparationResult); - } - - @Test - public void testReverting() throws Exception { - createPatch(); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(preparationResult.patch)); - assertNotApplied(preparationResult); - } - - @Test - public void testRevertedWhenFileToDeleteIsLocked() throws Exception { - IoTestUtil.assumeWindows(); - doLockedFileTest(); - } - - @Test - public void testRevertedWhenFileToUpdateIsLocked() throws Exception { - IoTestUtil.assumeWindows(); - FileUtil.writeToFile(new File(myNewerDir, "bin/idea.bat"), "new text"); - doLockedFileTest(); - } - - private void doLockedFileTest() throws Exception { - createPatch(); - - try (RandomAccessFile raf = new RandomAccessFile(new File(myOlderDir, "bin/idea.bat"), "rw")) { - int b = raf.read(); - raf.seek(0); - raf.write(b); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - assertNotApplied(preparationResult); + @Override + public void checkCancelled() throws OperationCancelledException { + if (cancelled) throw new OperationCancelledException(); } } - @Test - public void testRevertedWhenDeleteFailed() throws Exception { - createPatch(); + @TempDir Path tempDir; + @UpdaterTestData Path dataDir; - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - PatchAction original = findAction(preparationResult.patch, "bin/idea.bat"); + private final CancellableUI testUI = new CancellableUI(); + private final boolean doBackup = this instanceof StandardModeTest; + + private PatchApplyingRevertingTest() { } + + @BeforeEach void clearUIState() { + testUI.cancelled = false; + } + + @Test void creatingAndApplying() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + + assertAppliedAndReverted(dirs); + } + + @Test void creatingAndApplyingStrict() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true)); + + assertAppliedAndReverted(dirs, patchFile); + } + + @Test void creatingAndApplyingOnADifferentRoot() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true).setRoot("bin/")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.resolve("bin").toFile(), testUI); + + assertAppliedAndReverted(dirs, preparationResult); + } + + @Test void creatingAndFailingOnADifferentRoot() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true).setRoot("bin/")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.resolve("bin").toFile(), testUI); + preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(preparationResult.patch)); + + assertNotApplied(dirs, preparationResult); + } + + @Test void reverting() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); + preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(preparationResult.patch)); + + assertNotApplied(dirs, preparationResult); + } + + @Test @EnabledOnOs(OS.WINDOWS) void revertedWhenFileToDeleteIsLocked() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + doLockedFileTest(dirs); + } + + @Test @EnabledOnOs(OS.WINDOWS) void revertedWhenFileToUpdateIsLocked() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.writeString(dirs.newDir.resolve("bin/idea.bat"), "new text"); + doLockedFileTest(dirs); + } + + private void doLockedFileTest(UpdaterTestCase.Directories dirs) throws Exception { + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + + try (var raf = new RandomAccessFile(dirs.oldDir.resolve("bin/idea.bat").toFile(), "rw")) { + var b = raf.read(); + raf.seek(0); + raf.write(b); + + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); + assertNotApplied(dirs, preparationResult); + } + } + + @Test void revertedWhenDeleteFailed() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); + + var original = findAction(preparationResult.patch, "bin/idea.bat"); assertThat(original).isInstanceOf(DeleteAction.class); - List actions = preparationResult.patch.getActions(); + + var actions = preparationResult.patch.getActions(); actions.set(actions.indexOf(original), new DeleteAction(preparationResult.patch, original.getPath(), original.getChecksum()) { @Override protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException { @@ -135,18 +134,19 @@ public abstract class PatchApplyingRevertingTest extends PatchTestCase { } }); - assertNotApplied(preparationResult); + assertNotApplied(dirs, preparationResult); } - @Test - public void testRevertedWhenUpdateFailed() throws Exception { - FileUtil.writeToFile(new File(myNewerDir, "bin/idea.bat"), "new text"); - createPatch(); + @Test void revertedWhenUpdateFailed() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.writeString(dirs.newDir.resolve("bin/idea.bat"), "new text"); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - PatchAction original = findAction(preparationResult.patch, "bin/idea.bat"); + var original = findAction(preparationResult.patch, "bin/idea.bat"); assertThat(original).isInstanceOf(UpdateAction.class); - List actions = preparationResult.patch.getActions(); + + var actions = preparationResult.patch.getActions(); actions.set(actions.indexOf(original), new UpdateAction(preparationResult.patch, original.getPath(), original.getChecksum()) { @Override protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException { @@ -154,570 +154,492 @@ public abstract class PatchApplyingRevertingTest extends PatchTestCase { } }); - assertNotApplied(preparationResult); + assertNotApplied(dirs, preparationResult); } - @Test - public void testCancelledAtBackingUp() throws Exception { - createPatch(); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - List actions = preparationResult.patch.getActions(); + @Test void cancelledAtBackingUp() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); + var actions = preparationResult.patch.getActions(); actions.add(new MyFailOnApplyPatchAction(preparationResult.patch) { @Override protected void doBackup(File toFile, File backupFile) { - TEST_UI.cancelled = true; + testUI.cancelled = true; } }); - assertNotApplied(preparationResult); + assertNotApplied(dirs, preparationResult); } - @Test - public void testCancelledAtApplying() throws Exception { - createPatch(); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - List actions = preparationResult.patch.getActions(); + @Test void cancelledAtApplying() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); + var actions = preparationResult.patch.getActions(); actions.add(new MyFailOnApplyPatchAction(preparationResult.patch) { @Override protected void doApply(ZipFile patchFile, File backupDir, File toFile) { - TEST_UI.cancelled = true; + testUI.cancelled = true; } }); - assertNotApplied(preparationResult); + assertNotApplied(dirs, preparationResult); } - @Test - public void testApplyingWithAbsentFileToDelete() throws Exception { - createPatch(); - - FileUtil.delete(new File(myOlderDir, "bin/idea.bat")); - - assertAppliedAndReverted(); + @Test void applyingWithAbsentFileToDelete() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.delete(dirs.oldDir.resolve("bin/idea.bat")); + assertAppliedAndReverted(dirs); } - @Test - public void testApplyingWithAbsentFileToUpdateStrict() throws Exception { - myPatchSpec.setStrict(true); - createPatch(); + @Test void applyingWithAbsentFileToUpdateStrict() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true)); + Files.delete(dirs.oldDir.resolve("lib/annotations.jar")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - FileUtil.delete(new File(myOlderDir, "lib/annotations.jar")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).containsExactly( new ValidationResult(ValidationResult.Kind.ERROR, - "lib/annotations.jar", - ValidationResult.Action.UPDATE, - "Absent", - ValidationResult.Option.NONE)); + "lib/annotations.jar", + ValidationResult.Action.UPDATE, + "Absent", + ValidationResult.Option.NONE)); } - @Test - public void testApplyingWithAbsentOptionalFile() throws Exception { - FileUtil.writeToFile(new File(myNewerDir, "bin/idea.bat"), "new content".getBytes(StandardCharsets.UTF_8)); + @Test void applyingWithAbsentOptionalFile() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.writeString(dirs.newDir.resolve("bin/idea.bat"), "new content"); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setOptionalFiles(List.of("bin/idea.bat"))); + Files.delete(dirs.oldDir.resolve("bin/idea.bat")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - myPatchSpec.setOptionalFiles(List.of("bin/idea.bat")); - createPatch(); - - FileUtil.delete(new File(myOlderDir, "bin/idea.bat")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).isEmpty(); - assertAppliedAndReverted(preparationResult, (original, target) -> target.remove("bin/idea.bat")); + assertAppliedAndReverted(dirs, preparationResult, (original, target) -> target.remove("bin/idea.bat")); } - @Test - public void testApplyingWithAbsentOptionalDirectory() throws Exception { - Files.createDirectory(myOlderDir.toPath().resolve("opt")); - Files.write(myOlderDir.toPath().resolve("opt/file.txt"), "previous content".getBytes(StandardCharsets.UTF_8)); - Files.createDirectory(myNewerDir.toPath().resolve("opt")); - Files.write(myNewerDir.toPath().resolve("opt/file.txt"), "new content".getBytes(StandardCharsets.UTF_8)); - Files.write(myNewerDir.toPath().resolve("opt/another.txt"), "content".getBytes(StandardCharsets.UTF_8)); + @Test void applyingWithAbsentOptionalDirectory() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.createDirectory(dirs.oldDir.resolve("opt")); + Files.writeString(dirs.oldDir.resolve("opt/file.txt"), "previous content"); + Files.createDirectory(dirs.newDir.resolve("opt")); + Files.writeString(dirs.newDir.resolve("opt/file.txt"), "new content"); + Files.writeString(dirs.newDir.resolve("opt/another.txt"), "content"); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setOptionalFiles(List.of("opt/file.txt"))); + Utils.delete(dirs.oldDir.resolve("opt")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - myPatchSpec.setOptionalFiles(List.of("opt/file.txt")); - createPatch(); - - FileUtil.delete(myOlderDir.toPath().resolve("opt")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).isEmpty(); - assertAppliedAndReverted(preparationResult, (original, target) -> { + assertAppliedAndReverted(dirs, preparationResult, (original, target) -> { target.remove("opt/"); target.remove("opt/file.txt"); target.remove("opt/another.txt"); }); } - @Test - public void testRevertingWithAbsentFileToDelete() throws Exception { - createPatch(); - - FileUtil.delete(new File(myOlderDir, "bin/idea.bat")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); + @Test void revertingWithAbsentFileToDelete() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + Files.delete(dirs.oldDir.resolve("bin/idea.bat")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(preparationResult.patch)); - assertNotApplied(preparationResult); + + assertNotApplied(dirs, preparationResult); } - @Test - public void testApplyingWithCriticalFiles() throws Exception { - myPatchSpec.setCriticalFiles(List.of("lib/annotations.jar")); - assertAppliedAndReverted(); + @Test void applyingWithCriticalFiles() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setCriticalFiles(List.of("lib/annotations.jar"))); + + assertAppliedAndReverted(dirs, patchFile); } - @Test - public void testApplyingWithModifiedCriticalFiles() throws Exception { - myPatchSpec.setStrict(true); - myPatchSpec.setCriticalFiles(List.of("lib/annotations.jar")); - createPatch(); + @Test void applyingWithModifiedCriticalFiles() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true).setCriticalFiles(List.of("lib/annotations.jar"))); + modifyFile(dirs.oldDir.resolve("lib/annotations.jar")); - modifyFile(new File(myOlderDir, "lib/annotations.jar")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs, patchFile); } - @Test - public void testApplyingWithRemovedCriticalFiles() throws Exception { - myPatchSpec.setStrict(true); - myPatchSpec.setCriticalFiles(List.of("lib/annotations.jar")); - createPatch(); + @Test void applyingWithRemovedCriticalFiles() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true).setCriticalFiles(List.of("lib/annotations.jar"))); + Files.delete(dirs.oldDir.resolve("lib/annotations.jar")); - FileUtil.delete(new File(myOlderDir, "lib/annotations.jar")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs, patchFile); } - @Test - public void testApplyingWithRemovedNonCriticalFilesWithStrict() throws Exception { - myPatchSpec.setStrict(true); - createPatch(); + @Test void applyingWithRemovedNonCriticalFilesWithStrict() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true)); + Files.delete(dirs.oldDir.resolve("lib/annotations.jar")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - FileUtil.delete(new File(myOlderDir, "lib/annotations.jar")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).containsExactly( new ValidationResult(ValidationResult.Kind.ERROR, - "lib/annotations.jar", - ValidationResult.Action.UPDATE, - "Absent", - ValidationResult.Option.NONE)); + "lib/annotations.jar", + ValidationResult.Action.UPDATE, + "Absent", + ValidationResult.Option.NONE)); } - @Test - public void testApplyingWithRemovedNonCriticalFilesWithoutStrict() throws Exception { - createPatch(); + @Test void applyingWithRemovedNonCriticalFilesWithoutStrict() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + Files.delete(dirs.oldDir.resolve("lib/annotations.jar")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - FileUtil.delete(new File(myOlderDir, "lib/annotations.jar")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).containsExactly( new ValidationResult(ValidationResult.Kind.ERROR, - "lib/annotations.jar", - ValidationResult.Action.UPDATE, - "Absent", - ValidationResult.Option.IGNORE)); + "lib/annotations.jar", + ValidationResult.Action.UPDATE, + "Absent", + ValidationResult.Option.IGNORE)); } - @Test - public void testApplyingWithModifiedCriticalFilesAndDifferentRoot() throws Exception { - myPatchSpec.setStrict(true); - myPatchSpec.setRoot("lib/"); - myPatchSpec.setCriticalFiles(List.of("lib/annotations.jar")); - createPatch(); + @Test void applyingWithModifiedCriticalFilesAndDifferentRoot() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var spec = createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true).setRoot("lib/").setCriticalFiles(List.of("lib/annotations.jar")); + var patchFile = createPatch(spec); + modifyFile(dirs.oldDir.resolve("lib/annotations.jar")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.resolve("lib/").toFile(), testUI); - modifyFile(new File(myOlderDir, "lib/annotations.jar")); - - assertAppliedAndReverted(PatchFileCreator.prepareAndValidate(myFile, new File(myOlderDir, "lib/"), TEST_UI)); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testApplyingWithCaseChangedNames() throws Exception { - FileUtil.rename(new File(myOlderDir, "Readme.txt"), new File(myOlderDir, "README.txt")); - assertAppliedAndReverted(); + @Test void applyingWithCaseChangedNames() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.move(dirs.oldDir.resolve("Readme.txt"), dirs.oldDir.resolve("README.txt")); + + assertAppliedAndReverted(dirs); } - @Test - public void testCreatingAndApplyingWhenDirectoryBecomesFile() throws Exception { - File file = new File(myOlderDir, "Readme.txt"); - FileUtil.delete(file); - FileUtil.createDirectory(file); + @Test void creatingAndApplyingWhenDirectoryBecomesFile() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var file = dirs.oldDir.resolve("Readme.txt"); + Files.delete(file); + Files.createDirectory(file); + Files.createFile(file.resolve("subFile.txt")); + Files.createFile(Files.createDirectory(file.resolve("subDir")).resolve("subFile.txt")); + Files.copy(dirs.oldDir.resolve("lib/boot.jar"), dirs.oldDir.resolve("lib/boot_with_directory_becomes_file.jar"), StandardCopyOption.REPLACE_EXISTING); - FileUtil.writeToFile(new File(file, "subFile.txt"), ""); - FileUtil.writeToFile(new File(file, "subDir/subFile.txt"), ""); - - FileUtil.copy(new File(myOlderDir, "lib/boot.jar"), new File(myOlderDir, "lib/boot_with_directory_becomes_file.jar")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void testCreatingAndApplyingWhenFileBecomesDirectory() throws Exception { - File file = new File(myOlderDir, "bin"); - FileUtil.delete(file); - FileUtil.writeToFile(file, ""); + @Test void creatingAndApplyingWhenFileBecomesDirectory() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var file = dirs.oldDir.resolve("bin"); + Utils.delete(file); + Files.createFile(file); + Files.copy(dirs.oldDir.resolve("lib/boot_with_directory_becomes_file.jar"), dirs.oldDir.resolve("lib/boot.jar"), StandardCopyOption.REPLACE_EXISTING); - FileUtil.copy(new File(myOlderDir, "lib/boot_with_directory_becomes_file.jar"), new File(myOlderDir, "lib/boot.jar")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void testConsideringOptions() throws Exception { - createPatch(); + @Test void consideringOptions() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); + var options = preparationResult.patch.getActions().stream().collect(Collectors.toMap(PatchAction::getPath, a -> ValidationResult.Option.IGNORE)); - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - Map options = - preparationResult.patch.getActions().stream().collect(Collectors.toMap(PatchAction::getPath, a -> ValidationResult.Option.IGNORE)); - assertNotApplied(preparationResult, options); + assertNotApplied(dirs, preparationResult, options); } - @Test - public void testApplyWhenCommonFileChanges() throws Exception { - createPatch(); + @Test void applyWhenCommonFileChanges() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + Files.copy(dirs.oldDir.resolve("lib/bootstrap.jar"), dirs.oldDir.resolve("lib/boot.jar"), StandardCopyOption.REPLACE_EXISTING); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - FileUtil.copy(new File(myOlderDir, "lib/bootstrap.jar"), new File(myOlderDir, "lib/boot.jar")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).isEmpty(); - assertAppliedAndReverted(preparationResult, (original, target) -> target.put("lib/boot.jar", CHECKSUMS.BOOTSTRAP_JAR)); + assertAppliedAndReverted(dirs, preparationResult, (original, target) -> target.put("lib/boot.jar", UpdaterTestCase.BOOTSTRAP_JAR)); } - @Test - public void testApplyWhenCommonFileChangesStrict() throws Exception { - myPatchSpec.setStrict(true); - createPatch(); + @Test void applyWhenCommonFileChangesStrict() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true)); + Files.copy(dirs.oldDir.resolve("lib/bootstrap.jar"), dirs.oldDir.resolve("lib/boot.jar"), StandardCopyOption.REPLACE_EXISTING); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - FileUtil.copy(new File(myOlderDir, "lib/bootstrap.jar"), new File(myOlderDir, "lib/boot.jar")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).containsExactly( new ValidationResult(ValidationResult.Kind.ERROR, - "lib/boot.jar", - ValidationResult.Action.VALIDATE, - "Modified", - ValidationResult.Option.NONE)); + "lib/boot.jar", + ValidationResult.Action.VALIDATE, + "Modified", + ValidationResult.Option.NONE)); } - @Test - public void testApplyWhenCommonFileChangesStrictFile() throws Exception { - myPatchSpec.setStrictFiles(List.of("lib/annotations.jar")); - createPatch(); + @Test void applyWhenCommonFileChangesStrictFile() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrictFiles(List.of("lib/annotations.jar"))); + Files.copy(dirs.oldDir.resolve("lib/bootstrap.jar"), dirs.oldDir.resolve("lib/annotations.jar"), StandardCopyOption.REPLACE_EXISTING); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - FileUtil.copy(new File(myOlderDir, "lib/bootstrap.jar"), new File(myOlderDir, "lib/annotations.jar")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).containsExactly( new ValidationResult(ValidationResult.Kind.ERROR, - "lib/annotations.jar", - ValidationResult.Action.UPDATE, - "Modified", - ValidationResult.Option.NONE)); + "lib/annotations.jar", + ValidationResult.Action.UPDATE, + "Modified", + ValidationResult.Option.NONE)); } - @Test - public void testApplyWhenNewFileExists() throws Exception { - createPatch(); + @Test void applyWhenNewFileExists() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + Files.copy(dirs.oldDir.resolve("Readme.txt"), dirs.oldDir.resolve("new_file.txt")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - FileUtil.copy(new File(myOlderDir, "Readme.txt"), new File(myOlderDir, "new_file.txt")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).isEmpty(); - assertAppliedAndReverted(preparationResult, (original, target) -> target.put("new_file.txt", CHECKSUMS.README_TXT)); + assertAppliedAndReverted(dirs, preparationResult, (original, target) -> target.put("new_file.txt", UpdaterTestCase.README_TXT)); } - @Test - public void testApplyWhenNewFileExistsStrict() throws Exception { - myPatchSpec.setStrict(true); - myPatchSpec.setDeleteFiles(List.of("lib/java_pid.*\\.hprof")); + @Test void applyWhenNewFileExistsStrict() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true).setDeleteFiles(List.of("lib/java_pid.*\\.hprof"))); + Files.writeString(dirs.oldDir.resolve("new_file.txt"), "hello"); + Files.writeString(dirs.oldDir.resolve("lib/java_pid1234.hprof"), "bye!"); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - createPatch(); - - FileUtil.writeToFile(new File(myOlderDir, "new_file.txt"), "hello"); - FileUtil.writeToFile(new File(myOlderDir, "lib/java_pid1234.hprof"), "bye!"); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).containsExactly( new ValidationResult(ValidationResult.Kind.CONFLICT, - "new_file.txt", - ValidationResult.Action.VALIDATE, - "Unexpected file", - ValidationResult.Option.DELETE)); - assertAppliedAndReverted(preparationResult); + "new_file.txt", + ValidationResult.Action.VALIDATE, + "Unexpected file", + ValidationResult.Option.DELETE)); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testApplyWhenNewDeletableFileExistsStrict() throws Exception { - myPatchSpec.setStrict(true); - myPatchSpec.setDeleteFiles(List.of("lib/java_pid.*\\.hprof")); + @Test void applyWhenNewDeletableFileExistsStrict() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true).setDeleteFiles(List.of("lib/java_pid.*\\.hprof"))); + Files.writeString(dirs.oldDir.resolve("lib/java_pid1234.hprof"), "bye!"); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - createPatch(); - - FileUtil.writeToFile(new File(myOlderDir, "lib/java_pid1234.hprof"), "bye!"); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).isEmpty(); - assertAppliedAndReverted(preparationResult); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testApplyWhenNewDirectoryExistsStrict() throws Exception { - myPatchSpec.setStrict(true); - FileUtil.writeToFile(new File(myOlderDir, "delete/delete_me.txt"), "bye!"); + @Test void applyWhenNewDirectoryExistsStrict() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Utils.writeString(dirs.oldDir.resolve("delete/delete_me.txt"), "bye!"); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true)); + Utils.writeString(dirs.oldDir.resolve("unexpected_new_dir/unexpected.txt"), "bye!"); + Files.createDirectory(dirs.oldDir.resolve("newDir")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - createPatch(); - - FileUtil.writeToFile(new File(myOlderDir, "unexpected_new_dir/unexpected.txt"), "bye!"); - - FileUtil.createDirectory(new File(myOlderDir, "newDir")); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).containsExactly( new ValidationResult(ValidationResult.Kind.CONFLICT, - "unexpected_new_dir/unexpected.txt", - ValidationResult.Action.VALIDATE, - "Unexpected file", - ValidationResult.Option.DELETE), + "unexpected_new_dir/unexpected.txt", + ValidationResult.Action.VALIDATE, + "Unexpected file", + ValidationResult.Option.DELETE), new ValidationResult(ValidationResult.Kind.CONFLICT, - "unexpected_new_dir/", - ValidationResult.Action.VALIDATE, - "Unexpected file", - ValidationResult.Option.DELETE), + "unexpected_new_dir/", + ValidationResult.Action.VALIDATE, + "Unexpected file", + ValidationResult.Option.DELETE), new ValidationResult(ValidationResult.Kind.CONFLICT, - "newDir/", - ValidationResult.Action.CREATE, - "Already exists", - ValidationResult.Option.REPLACE)); - FileUtil.delete(new File(myOlderDir, "newDir")); - assertAppliedAndReverted(preparationResult); + "newDir/", + ValidationResult.Action.CREATE, + "Already exists", + ValidationResult.Option.REPLACE)); + + Utils.delete(dirs.oldDir.resolve("newDir")); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testMoveFileByContent() throws Exception { - myPatchSpec.setStrict(true); - FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "old_content"); - FileUtil.writeToFile(new File(myOlderDir, "a/deleted/file/that/is/a/copy/move.me"), "new_content"); - FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "new_content"); - createPatch(); + @Test void moveFileByContent() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Utils.writeString(dirs.oldDir.resolve("move/from/this/directory/move.me"), "old_content"); + Utils.writeString(dirs.oldDir.resolve("a/deleted/file/that/is/a/copy/move.me"), "new_content"); + Utils.writeString(dirs.newDir.resolve("move/to/this/directory/move.me"), "new_content"); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true)); + var hash = Digester.digestStream(new ByteArrayInputStream("new_content".getBytes(StandardCharsets.UTF_8))); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - long hash = Digester.digestStream(new ByteArrayInputStream("new_content".getBytes(StandardCharsets.UTF_8))); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(findAction(preparationResult.patch, "move/to/this/directory/move.me")) .isEqualTo(new UpdateAction(preparationResult.patch, "move/to/this/directory/move.me", "a/deleted/file/that/is/a/copy/move.me", hash, true)); - - assertAppliedAndReverted(preparationResult); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testMoveCriticalFileByContent() throws Exception { - myPatchSpec.setStrict(true); - myPatchSpec.setCriticalFiles(List.of("a/deleted/file/that/is/a/copy/move.me")); - FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "old_content"); - FileUtil.writeToFile(new File(myOlderDir, "a/deleted/file/that/is/a/copy/move.me"), "new_content"); - FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "new_content"); - createPatch(); + @Test void moveCriticalFileByContent() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Utils.writeString(dirs.oldDir.resolve("move/from/this/directory/move.me"), "old_content"); + Utils.writeString(dirs.oldDir.resolve("a/deleted/file/that/is/a/copy/move.me"), "new_content"); + Utils.writeString(dirs.newDir.resolve("move/to/this/directory/move.me"), "new_content"); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true).setCriticalFiles(List.of("a/deleted/file/that/is/a/copy/move.me"))); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - assertThat(findAction(preparationResult.patch, "move/to/this/directory/move.me")) - .isInstanceOf(CreateAction.class); - - assertAppliedAndReverted(preparationResult); + assertThat(findAction(preparationResult.patch, "move/to/this/directory/move.me")).isInstanceOf(CreateAction.class); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testDontMoveFromDirectoryToFile() throws Exception { - myPatchSpec.setStrict(true); - FileUtil.createDirectory(new File(myOlderDir, "from/move.me")); - FileUtil.writeToFile(new File(myNewerDir, "move/to/move.me"), "different"); - createPatch(); - + @Test void noMoveFromDirectoryToFile() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.createDirectories(dirs.oldDir.resolve("from/move.me")); + Utils.writeString(dirs.newDir.resolve("move/to/move.me"), "different"); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true)); // creating a patch would have crashed if the directory had been chosen - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); + assertThat(preparationResult.validationResults).isEmpty(); assertThat(findAction(preparationResult.patch, "move/to/move.me")).isInstanceOf(CreateAction.class); assertThat(findAction(preparationResult.patch, "from/move.me/")).isInstanceOf(DeleteAction.class); - - assertAppliedAndReverted(preparationResult); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testMoveFileByLocation() throws Exception { - myPatchSpec.setStrict(true); - FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "they"); - FileUtil.writeToFile(new File(myOlderDir, "not/from/this/one/move.me"), "are"); - FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "different"); - createPatch(); + @Test void moveFileByLocation() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Utils.writeString(dirs.oldDir.resolve("move/from/this/directory/move.me"), "they"); + Utils.writeString(dirs.oldDir.resolve("not/from/this/one/move.me"), "are"); + Utils.writeString(dirs.newDir.resolve("move/to/this/directory/move.me"), "different"); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setStrict(true)); + var hash = Digester.digestStream(new ByteArrayInputStream("they".getBytes(StandardCharsets.UTF_8))); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - long hash = Digester.digestStream(new ByteArrayInputStream("they".getBytes(StandardCharsets.UTF_8))); - - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(findAction(preparationResult.patch, "move/to/this/directory/move.me")) .isEqualTo(new UpdateAction(preparationResult.patch, "move/to/this/directory/move.me", "move/from/this/directory/move.me", hash, false)); - - assertAppliedAndReverted(preparationResult); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testSymlinkAdded() throws Exception { - IoTestUtil.assumeSymLinkCreationIsSupported(); + @Test void symlinkAdded() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.createSymbolicLink(dirs.newDir.resolve("Readme.link"), Path.of("Readme.txt")); - Files.createSymbolicLink(new File(myNewerDir, "Readme.link").toPath(), Path.of("Readme.txt")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void testSymlinkRemoved() throws Exception { - IoTestUtil.assumeSymLinkCreationIsSupported(); + @Test void symlinkRemoved() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.createSymbolicLink(dirs.oldDir.resolve("Readme.link"), Path.of("Readme.txt")); - Files.createSymbolicLink(new File(myOlderDir, "Readme.link").toPath(), Path.of("Readme.txt")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void testSymlinkRenamed() throws Exception { - IoTestUtil.assumeSymLinkCreationIsSupported(); + @SuppressWarnings("DuplicateExpressions") + @Test void symlinkRenamed() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.createSymbolicLink(dirs.oldDir.resolve("Readme.link"), Path.of("Readme.txt")); + Files.createSymbolicLink(dirs.newDir.resolve("Readme.lnk"), Path.of("Readme.txt")); - Files.createSymbolicLink(new File(myOlderDir, "Readme.link").toPath(), Path.of("Readme.txt")); - Files.createSymbolicLink(new File(myNewerDir, "Readme.lnk").toPath(), Path.of("Readme.txt")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void testSymlinkRetargeted() throws Exception { - IoTestUtil.assumeSymLinkCreationIsSupported(); + @Test void symlinkRetargeted() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + Files.createSymbolicLink(dirs.oldDir.resolve("Readme.link"), Path.of("Readme.txt")); + Files.createSymbolicLink(dirs.newDir.resolve("Readme.link"), Path.of("./Readme.txt")); - Files.createSymbolicLink(new File(myOlderDir, "Readme.link").toPath(), Path.of("Readme.txt")); - Files.createSymbolicLink(new File(myNewerDir, "Readme.link").toPath(), Path.of("./Readme.txt")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @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); - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @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); - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void testReadOnlyFilesAreDeletable() throws Exception { - File file = new File(myOlderDir, "bin/read_only_to_delete"); - FileUtil.writeToFile(file, "bye"); - assertTrue(file.setWritable(false, false)); + @Test void readOnlyFilesAreDeletable() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, true); + var file = dirs.oldDir.resolve("bin/read_only_to_delete"); + Files.writeString(file, "bye"); + UpdaterTestCase.setReadOnly(file); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - createPatch(); - PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).isEmpty(); - assertAppliedAndReverted(preparationResult); + assertAppliedAndReverted(dirs, preparationResult); } - @Test - public void testExecutableFlagChange() throws Exception { - assumeFalse("Windows-allergic", Utils.IS_WINDOWS); + @Test @DisabledOnOs(OS.WINDOWS) void executableFlagChange() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, false); + Files.writeString(dirs.oldDir.resolve("bin/to_become_executable"), "to_become_executable"); + Files.writeString(dirs.oldDir.resolve("bin/to_become_plain"), "to_become_plain"); + Utils.setExecutable(dirs.oldDir.resolve("bin/to_become_plain")); + Files.writeString(dirs.newDir.resolve("bin/to_become_executable"), "to_become_executable"); + Files.writeString(dirs.newDir.resolve("bin/to_become_plain"), "to_become_plain"); + Utils.setExecutable(dirs.newDir.resolve("bin/to_become_executable")); - FileUtil.writeToFile(new File(myOlderDir, "bin/to_become_executable"), "to_become_executable"); - FileUtil.writeToFile(new File(myOlderDir, "bin/to_become_plain"), "to_become_plain"); - Utils.setExecutable(new File(myOlderDir, "bin/to_become_plain"), true); - resetNewerDir(); - Utils.setExecutable(new File(myNewerDir, "bin/to_become_plain"), false); - Utils.setExecutable(new File(myNewerDir, "bin/to_become_executable"), true); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void fileToSymlinks() throws Exception { - IoTestUtil.assumeSymLinkCreationIsSupported(); + @Test void fileToSymlinks() 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")); - resetNewerDir(); - Files.move(new File(myNewerDir, "Readme.txt").toPath(), new File(myNewerDir, "Readme.md").toPath()); - Files.createSymbolicLink(new File(myNewerDir, "Readme.txt").toPath(), Path.of("Readme.md")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void multipleDirectorySymlinks() throws Exception { - IoTestUtil.assumeSymLinkCreationIsSupported(); + @SuppressWarnings("DuplicateExpressions") + @Test void multipleDirectorySymlinks() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, false); - resetNewerDir(); + randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Libraries/lib1.dylib")); + randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Libraries/lib2.dylib")); + randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Resources/r1.bin")); + 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")); + Files.createDirectories(dirs.oldDir.resolve("Home/Frameworks")); + Files.createSymbolicLink(dirs.oldDir.resolve("Home/Frameworks/A.framework"), Path.of("../../A.framework")); - randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib1.dylib")); - randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib2.dylib")); - randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Resources/r1.bin")); - 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")); - Files.createDirectories(myOlderDir.toPath().resolve("Home/Frameworks")); - Files.createSymbolicLink(myOlderDir.toPath().resolve("Home/Frameworks/A.framework"), Path.of("../../A.framework")); + 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")); - 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")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void symlinksToFilesAndDirectories() throws Exception { - IoTestUtil.assumeSymLinkCreationIsSupported(); + @Test void symlinksToFilesAndDirectories() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, false); - resetNewerDir(); + randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Libraries/lib1.dylib")); + randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Libraries/lib2.dylib")); + randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Resources/r1/res.bin")); + randomFile(dirs.oldDir.resolve("A.framework/Versions/A/Resources/r2/res.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(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib1.dylib")); - randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib2.dylib")); - randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Resources/r1/res.bin")); - randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Resources/r2/res.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(dirs.newDir.resolve("A.framework/Libraries/lib1.dylib")); + randomFile(dirs.newDir.resolve("A.framework/Libraries/lib2.dylib")); + randomFile(dirs.newDir.resolve("A.framework/Resources/r1/res.bin")); + randomFile(dirs.newDir.resolve("A.framework/Resources/r2/res.bin")); - randomFile(myNewerDir.toPath().resolve("A.framework/Libraries/lib1.dylib")); - randomFile(myNewerDir.toPath().resolve("A.framework/Libraries/lib2.dylib")); - randomFile(myNewerDir.toPath().resolve("A.framework/Resources/r1/res.bin")); - randomFile(myNewerDir.toPath().resolve("A.framework/Resources/r2/res.bin")); - - assertAppliedAndReverted(); + assertAppliedAndReverted(dirs); } - @Test - public void creatingParentDirectoriesForMissingCriticalFiles() throws Exception { - randomFile(myOlderDir.toPath().resolve("plugins/some/lib/plugin.jar")); - randomFile(myOlderDir.toPath().resolve("plugins/other/lib/plugin.jar")); - resetNewerDir(); - randomFile(myNewerDir.toPath().resolve("plugins/some/lib/plugin.jar")); + @Test void creatingParentDirectoriesForMissingCriticalFiles() throws Exception { + var dirs = prepareDirectories(tempDir, dataDir, false); + randomFile(dirs.oldDir.resolve("plugins/some/lib/plugin.jar")); + randomFile(dirs.oldDir.resolve("plugins/other/lib/plugin.jar")); + randomFile(dirs.newDir.resolve("plugins/some/lib/plugin.jar")); + Utils.copy(dirs.oldDir.resolve("plugins/other/lib/plugin.jar"), dirs.newDir.resolve("plugins/other/lib/plugin.jar"), false); + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir).setCriticalFiles(List.of("plugins/some/lib/plugin.jar"))); + Utils.delete(dirs.oldDir.resolve("plugins/some")); + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); - myPatchSpec.setCriticalFiles(List.of("plugins/some/lib/plugin.jar")); - createPatch(); - - NioFiles.deleteRecursively(myOlderDir.toPath().resolve("plugins/some")); - - var preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); assertThat(preparationResult.validationResults).containsExactly( new ValidationResult(ValidationResult.Kind.CONFLICT, "plugins/some/lib/plugin.jar", @@ -725,69 +647,77 @@ public abstract class PatchApplyingRevertingTest extends PatchTestCase { "Absent", ValidationResult.Option.REPLACE, ValidationResult.Option.KEEP) ); - assertAppliedAndReverted(preparationResult, (original, target) -> { + assertAppliedAndReverted(dirs, preparationResult, (original, target) -> { original.put("plugins/some/", Digester.DIRECTORY); original.put("plugins/some/lib/", Digester.DIRECTORY); }); } - @Override - protected Patch createPatch() throws IOException { - assertFalse(myFile.exists()); - var patch = PatchFileCreator.create(myPatchSpec, myFile, null); - assertTrue(myFile.exists()); - return patch; - } - - private static PatchAction findAction(Patch patch, String path) { - return ContainerUtil.find(patch.getActions(), a -> a.getPath().equals(path)); - } - - private static void modifyFile(File file) throws IOException { - try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + private static void modifyFile(Path file) throws IOException { + try (RandomAccessFile raf = new RandomAccessFile(file.toFile(), "rw")) { raf.seek(20); raf.write(42); } } - private void assertNotApplied(PatchFileCreator.PreparationResult preparationResult) throws Exception { - assertNotApplied(preparationResult, Collections.emptyMap()); + private Path createPatch(PatchSpec spec) throws IOException { + var patchFile = tempDir.resolve("patch.zip"); + PatchFileCreator.create(spec, patchFile.toFile(), null); + assertThat(patchFile).isRegularFile(); + return patchFile; } - private void assertNotApplied(PatchFileCreator.PreparationResult preparationResult, - Map options) throws Exception { - Patch patch = preparationResult.patch; - File backupDir = myDoBackup ? getTempFile("backup") : null; - Map original = digest(patch, myOlderDir); - - PatchFileCreator.ApplicationResult applicationResult = PatchFileCreator.apply(preparationResult, options, backupDir, TEST_UI); - assertFalse(applicationResult.applied); - - if (myDoBackup) { - PatchFileCreator.revert(preparationResult, applicationResult.appliedActions, backupDir, TEST_UI); - assertThat(digest(patch, myOlderDir)).containsExactlyEntriesOf(original); - } + @SuppressWarnings({"SSBasedInspection", "RedundantSuppression"}) + private static PatchAction findAction(Patch patch, String path) { + return patch.getActions().stream().filter(a -> a.getPath().equals(path)).findFirst().orElse(null); } - private void assertAppliedAndReverted() throws Exception { - if (!myFile.exists()) { - createPatch(); - } - var preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); - assertAppliedAndReverted(preparationResult, (original, target) -> {}); + private void assertNotApplied(UpdaterTestCase.Directories dirs, PatchFileCreator.PreparationResult preparationResult) throws Exception { + assertNotApplied(dirs, preparationResult, Map.of()); } - private void assertAppliedAndReverted(PatchFileCreator.PreparationResult preparationResult) throws Exception { - assertAppliedAndReverted(preparationResult, (original, target) -> {}); - } - - private void assertAppliedAndReverted(PatchFileCreator.PreparationResult preparationResult, - BiConsumer, Map> adjuster) throws Exception { + private void assertNotApplied( + UpdaterTestCase.Directories dirs, + PatchFileCreator.PreparationResult preparationResult, + Map options + ) throws Exception { var patch = preparationResult.patch; - var original = digest(patch, myOlderDir); - var target = digest(patch, myNewerDir); + var backupDir = doBackup ? tempDir.resolve("backup").toFile() : null; + var original = digest(patch, dirs.oldDir); + + var applicationResult = PatchFileCreator.apply(preparationResult, options, backupDir, testUI); + assertThat(applicationResult.applied).isFalse(); + + if (doBackup) { + PatchFileCreator.revert(preparationResult, applicationResult.appliedActions, backupDir, testUI); + assertThat(digest(patch, dirs.oldDir)).containsExactlyEntriesOf(original); + } + } + + private void assertAppliedAndReverted(UpdaterTestCase.Directories dirs) throws Exception { + var patchFile = createPatch(createPatchSpec(dirs.oldDir, dirs.newDir)); + assertAppliedAndReverted(dirs, patchFile); + } + + private void assertAppliedAndReverted(UpdaterTestCase.Directories dirs, Path patchFile) throws IOException, OperationCancelledException { + var preparationResult = PatchFileCreator.prepareAndValidate(patchFile.toFile(), dirs.oldDir.toFile(), testUI); + assertAppliedAndReverted(dirs, preparationResult); + } + + private void assertAppliedAndReverted(UpdaterTestCase.Directories dirs, PatchFileCreator.PreparationResult preparationResult) throws IOException { + assertAppliedAndReverted(dirs, preparationResult, (original, target) -> {}); + } + + private void assertAppliedAndReverted( + UpdaterTestCase.Directories dirs, + PatchFileCreator.PreparationResult preparationResult, + BiConsumer, Map> adjuster + ) throws IOException { + var patch = preparationResult.patch; + var original = digest(patch, dirs.oldDir); + var target = digest(patch, dirs.newDir); adjuster.accept(original, target); - var backupDir = myDoBackup ? getTempFile("backup") : null; + var backupDir = doBackup ? tempDir.resolve("backup").toFile() : null; var options = new HashMap(); for (var each : preparationResult.validationResults) { @@ -800,19 +730,23 @@ public abstract class PatchApplyingRevertingTest extends PatchTestCase { } } - var applicationResult = PatchFileCreator.apply(preparationResult, options, backupDir, TEST_UI); + var applicationResult = PatchFileCreator.apply(preparationResult, options, backupDir, testUI); if (applicationResult.error != null) { throw new AssertionError("patch failed", applicationResult.error); } assertThat(applicationResult.applied).isTrue(); - assertThat(digest(patch, myOlderDir)).containsExactlyEntriesOf(target); + assertThat(digest(patch, dirs.oldDir)).containsExactlyEntriesOf(target); - if (myDoBackup) { - PatchFileCreator.revert(preparationResult, applicationResult.appliedActions, backupDir, TEST_UI); - assertThat(digest(patch, myOlderDir)).containsExactlyEntriesOf(original); + if (doBackup) { + PatchFileCreator.revert(preparationResult, applicationResult.appliedActions, backupDir, testUI); + assertThat(digest(patch, dirs.oldDir)).containsExactlyEntriesOf(original); } } + private static Map digest(Patch patch, Path dir) throws IOException { + return new TreeMap<>(patch.digestFiles(dir, Set.of())); + } + private static class MyFailOnApplyPatchAction extends PatchAction { MyFailOnApplyPatchAction(Patch patch) { super(patch, "_dummy_file_", Digester.INVALID); diff --git a/updater/testSrc/com/intellij/updater/PatchCreationTest.java b/updater/testSrc/com/intellij/updater/PatchCreationTest.java index 7b01ae11ebcd..067a482d7370 100644 --- a/updater/testSrc/com/intellij/updater/PatchCreationTest.java +++ b/updater/testSrc/com/intellij/updater/PatchCreationTest.java @@ -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 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 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)); } } diff --git a/updater/testSrc/com/intellij/updater/PatchTestCase.java b/updater/testSrc/com/intellij/updater/PatchTestCase.java deleted file mode 100644 index 03f5c3b4e4ba..000000000000 --- a/updater/testSrc/com/intellij/updater/PatchTestCase.java +++ /dev/null @@ -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 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 digest(Patch patch, File dir) throws IOException { - return new TreeMap<>(patch.digestFiles(dir, Set.of())); - } - - protected static List sortActions(List actions) { - return sort(actions, a -> a.getClass().getSimpleName().charAt(0), Comparator.comparing(PatchAction::getPath)); - } - - protected static List sortResults(List results) { - return sort(results, r -> r.action, Comparator.comparing(r -> r.path)); - } - - private static List sort(List list, Function classifier, Comparator sorter) { - // splits the list into groups - Collection> 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(); - } -} diff --git a/updater/testSrc/com/intellij/updater/RunnerTest.java b/updater/testSrc/com/intellij/updater/RunnerTest.java index 92302a267caf..d5d33b711c8a 100644 --- a/updater/testSrc/com/intellij/updater/RunnerTest.java +++ b/updater/testSrc/com/intellij/updater/RunnerTest.java @@ -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(); } -} \ No newline at end of file +} diff --git a/updater/testSrc/com/intellij/updater/SymlinkPatchTest.java b/updater/testSrc/com/intellij/updater/SymlinkPatchTest.java deleted file mode 100644 index 774ecf28b1a8..000000000000 --- a/updater/testSrc/com/intellij/updater/SymlinkPatchTest.java +++ /dev/null @@ -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)); - } -} diff --git a/updater/testSrc/com/intellij/updater/UpdaterTestCase.java b/updater/testSrc/com/intellij/updater/UpdaterTestCase.java index 1da5ab94b386..ed1e0e39d8d4 100644 --- a/updater/testSrc/com/intellij/updater/UpdaterTestCase.java +++ b/updater/testSrc/com/intellij/updater/UpdaterTestCase.java @@ -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("") + .setNewFolder(newDir.toString()).setNewVersionDescription(""); + } + + static List sortActions(List actions) { + return sort(actions, a -> a.getClass().getSimpleName().charAt(0), Comparator.comparing(PatchAction::getPath)); + } + + static List sortResults(List results) { + return sort(results, r -> r.action, Comparator.comparing(r -> r.path)); + } + + private static List sort(List list, Function classifier, Comparator 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; + } } diff --git a/updater/testSrc/com/intellij/updater/UtilsTest.java b/updater/testSrc/com/intellij/updater/UtilsTest.java index f6ee79245b7b..e491c8dc1f2b 100644 --- a/updater/testSrc/com/intellij/updater/UtilsTest.java +++ b/updater/testSrc/com/intellij/updater/UtilsTest.java @@ -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(); } }