Skip to content

Commit

Permalink
devonfw#139: improved test, removed makeSymlinkRelative
Browse files Browse the repository at this point in the history
  • Loading branch information
MattesMrzik committed Nov 29, 2023
1 parent 66f09cf commit 75eb709
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 103 deletions.
18 changes: 0 additions & 18 deletions cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,6 @@ public interface FileAccess {
*/
void move(Path source, Path targetDir);

/**
* Symbolic links can point to relative or absolute paths. Here the link is converted to be relative. If the target of
* the link is again a link, then that lead is not followed.
*
* @param link the {@link Path} of the symbolic link.
*/
void makeSymlinkRelative(Path link);

/**
* Symbolic links can point to relative or absolute paths. Here the link is converted to be relative.
*
* @param link the {@link Path} of the symbolic link.
* @param followTarget - {@code true} if the target of the link is again a link, then that lead is followed, until the
* target is not a link. - {@code false} if the target of the link is again a link, then that lead is not
* followed.
*/
void makeSymlinkRelative(Path link, boolean followTarget);

/**
* @param source the source {@link Path} to link to.
* @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
Expand Down
42 changes: 11 additions & 31 deletions cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
Expand Down Expand Up @@ -285,30 +286,6 @@ private void copyRecursive(Path source, Path target, FileCopyMode mode) throws I
}
}

@Override
public void makeSymlinkRelative(Path link) {

makeSymlinkRelative(link, false);
}

@Override
public void makeSymlinkRelative(Path link, boolean followTarget) {

if (!Files.isSymbolicLink(link)) {
throw new IllegalStateException(
"Can't call makeSymlinkRelative on " + link + " since it is not a symbolic link.");
}
Path linkTarget;
try {
linkTarget = followTarget ? link.toRealPath() : Files.readSymbolicLink(link);
} catch (IOException e) {
throw new RuntimeException("For link " + link + " the call to "
+ (followTarget ? "toRealPath" : "readSymbolicLink") + " in method makeSymlinkRelative failed.", e);
}
delete(link); // delete old absolute link
symlink(link, linkTarget); // and replace it by the new relative link
}

@Override
public void symlink(Path source, Path targetLink, boolean relative) {

Expand All @@ -333,15 +310,18 @@ public void symlink(Path source, Path targetLink, boolean relative) {
if (Files.isSymbolicLink(targetLink)) {
this.context.debug("Deleting symbolic link to be re-created at {}", targetLink);
Files.delete(targetLink);
} else {
BasicFileAttributes attr = Files.readAttributes(targetLink, BasicFileAttributes.class,
LinkOption.NOFOLLOW_LINKS);
if (attr.isOther() && attr.isDirectory() && this.context.getSystemInfo().isWindows()) {
this.context.debug("Deleting symbolic link (junction) to be re-created at {}", targetLink);
Files.delete(targetLink);
}
}
}
try { // since broken junctions are not detected by Files.exists(brokenJunction)
BasicFileAttributes attr = Files.readAttributes(targetLink, BasicFileAttributes.class,
LinkOption.NOFOLLOW_LINKS);
if (attr.isOther() && attr.isDirectory() && this.context.getSystemInfo().isWindows()) {
this.context.debug("Deleting symbolic link (junction) to be re-created at {}", targetLink);
Files.delete(targetLink);
}
} catch (NoSuchFileException e) {
// ignore, since there is no previous file at the location, which is fine
}
Files.createSymbolicLink(targetLink, relative ? relativeSource : source);
} catch (FileSystemException e) {
if (this.context.getSystemInfo().isWindows()) {
Expand Down
102 changes: 48 additions & 54 deletions cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;

import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -39,13 +40,15 @@ public void testSymlinkNotRelative(@TempDir Path tempDir) {
FileAccess fileAccess = new FileAccessImpl(context);
Path dir = tempDir.resolve("parent");
createDirs(fileAccess, dir);
boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);
boolean relative = false;

// act
createSymlinks(fileAccess, dir, false);
createSymlinks(fileAccess, dir, relative);

// assert
assertSymlinksExist(dir);
assertSymlinksWork(dir, false, false);
assertSymlinksWork(dir, readLinks);
}

@Test
Expand All @@ -54,42 +57,42 @@ public void testSymlinkAbsoluteAsFallback(@TempDir Path tempDir) {
// arrange
IdeContext context = IdeTestContextMock.get();
if (!windowsJunctionsAreUsed(context, tempDir)) {
info("Can not check the Test: testSymlinkAbsoluteAsFallback since windows junctions are not used.");
info("Can not check the Test: testSymlinkAbsoluteAsFallback since windows junctions are not used and fallback "
+ "from relative to absolute paths as link target is not used.");
return;
}
FileAccess fileAccess = new FileAccessImpl(context);
Path dir = tempDir.resolve("parent");
createDirs(fileAccess, dir);
boolean readLinks = false; // bc windows junctions are used, which can't be read with Files.readSymbolicLink(link);
boolean relative = true; // set to true, such that the fallback to absolute paths is used since junctions are used

// act
createSymlinks(fileAccess, dir, true);
createSymlinks(fileAccess, dir, relative);

// assert
assertSymlinksExist(dir);
assertSymlinksWork(dir, false, false);
assertSymlinksWork(dir, readLinks);
}

@Test
public void testAbsoluteLinksBreakAfterMoving(@TempDir Path tempDir) throws IOException {

// arrange
IdeContext context = IdeTestContextMock.get();
if (windowsJunctionsAreUsed(context, tempDir)) {
info("Can not check the Test: testAbsoluteLinksBreakAfterMoving since windows junctions are used.");
return;
}
FileAccess fileAccess = new FileAccessImpl(context);
Path dir = tempDir.resolve("parent");
createDirs(fileAccess, dir);
createSymlinks(fileAccess, dir, false);
boolean readLinks = !windowsJunctionsAreUsed(context, tempDir);

// act
Path sibling = dir.resolveSibling("parent2");
fileAccess.move(dir, sibling);

// assert
assertSymlinksExist(sibling);
assertSymlinksAreBroken(sibling);
assertSymlinksAreBroken(sibling, readLinks);
}

@Test
Expand All @@ -105,21 +108,15 @@ public void testRelativeLinksWorkAfterMoving(@TempDir Path tempDir) {
Path dir = tempDir.resolve("parent");
createDirs(fileAccess, dir);
createSymlinks(fileAccess, dir, true);
boolean readLinks = true; // junctions are not used, so links can be read with Files.readSymbolicLink(link);

// act
Path sibling = dir.resolveSibling("parent2");
fileAccess.move(dir, sibling);

// assert
assertSymlinksExist(sibling);
assertSymlinksWork(sibling, true, false);
}

public void testMakeSymlinksRelative(@TempDir Path tempDir) {

// test follow target true and false

// test on junctions, dir and file,
assertSymlinksWork(sibling, readLinks);
}

@Test
Expand Down Expand Up @@ -180,37 +177,42 @@ private void assertSymlinksExist(Path dir) {
assertThat(dir.resolve("link10")).existsNoFollowLinks();
}

private void assertSymlinksAreBroken(Path dir) throws IOException {

assertSymlinkIsBroken(dir.resolve("d1/d11/link1"));
assertSymlinkIsBroken(dir.resolve("d1/d11/link2"));
assertSymlinkIsBroken(dir.resolve("d1/d11/link3"));
assertSymlinkIsBroken(dir.resolve("d1/d11/link4"));
assertSymlinkIsBroken(dir.resolve("d1/d11/link5"));
assertSymlinkIsBroken(dir.resolve("d1/d11/link6"));
assertSymlinkIsBroken(dir.resolve("d1/d11/link7"));
assertSymlinkIsBroken(dir.resolve("d2/d22/link8"));
assertSymlinkIsBroken(dir.resolve("d2/link9"));
assertSymlinkIsBroken(dir.resolve("link10"));
private void assertSymlinksAreBroken(Path dir, boolean readLinks) throws IOException {

assertSymlinkIsBroken(dir.resolve("d1/d11/link1"), readLinks);
assertSymlinkIsBroken(dir.resolve("d1/d11/link2"), readLinks);
assertSymlinkIsBroken(dir.resolve("d1/d11/link3"), readLinks);
assertSymlinkIsBroken(dir.resolve("d1/d11/link4"), readLinks);
assertSymlinkIsBroken(dir.resolve("d1/d11/link5"), readLinks);
assertSymlinkIsBroken(dir.resolve("d1/d11/link6"), readLinks);
assertSymlinkIsBroken(dir.resolve("d1/d11/link7"), readLinks);
assertSymlinkIsBroken(dir.resolve("d2/d22/link8"), readLinks);
assertSymlinkIsBroken(dir.resolve("d2/link9"), readLinks);
assertSymlinkIsBroken(dir.resolve("link10"), readLinks);
}

private void assertSymlinkIsBroken(Path link) throws IOException {
private void assertSymlinkIsBroken(Path link, boolean readLinks) throws IOException {

Path realPath = link.toRealPath();
if (Files.exists(realPath)) {
fail("The link target " + realPath + " (from toRealPath) should not exist");
try {
Path realPath = link.toRealPath();
if (Files.exists(realPath)) {
fail("The link target " + realPath + " (from toRealPath) should not exist");
}
} catch (IOException e) { // toRealPath() throws exception for junctions
assertThat(e).isInstanceOf(NoSuchFileException.class);
}
Path readPath = Files.readSymbolicLink(link);
if (!Files.exists(readPath)) {
fail("The link target " + readPath + " (from readSymbolicLink) should not exist");
if (readLinks) {
Path readPath = Files.readSymbolicLink(link);
if (!Files.exists(readPath)) {
fail("The link target " + readPath + " (from readSymbolicLink) should not exist");
}
}
}

/***
*
* @param readLinks - {@code true} only pass this when junctions are not used.
/*
* only pass readLinks = true when junctions are not used.
*/
private void assertSymlinksWork(Path dir, boolean readLinks, boolean followTarget) {
private void assertSymlinksWork(Path dir, boolean readLinks) {

assertSymlinkToRealPath(dir.resolve("d1/d11/link1"), dir.resolve("d1"));
assertSymlinkToRealPath(dir.resolve("d1/d11/link2"), dir.resolve("d1/d11"));
Expand All @@ -231,15 +233,9 @@ private void assertSymlinksWork(Path dir, boolean readLinks, boolean followTarge
assertSymlinkRead(dir.resolve("d1/d11/link5"), dir.resolve("d2"));
assertSymlinkRead(dir.resolve("d1/d11/link6"), dir.resolve("d2/d22"));
assertSymlinkRead(dir.resolve("d1/d11/link7"), dir.resolve("d2/d22/d222"));
if (followTarget) {
assertSymlinkRead(dir.resolve("d2/d22/link8"), dir.resolve("d1"));
assertSymlinkRead(dir.resolve("d2/link9"), dir.resolve("d1"));
assertSymlinkRead(dir.resolve("link10"), dir.resolve("d1"));
} else {
assertSymlinkRead(dir.resolve("d2/d22/link8"), dir.resolve("d1/d11/link1"));
assertSymlinkRead(dir.resolve("d2/link9"), dir.resolve("d1/d11/link1"));
assertSymlinkRead(dir.resolve("link10"), dir.resolve("d2/link9"));
}
assertSymlinkRead(dir.resolve("d2/d22/link8"), dir.resolve("d1/d11/link1"));
assertSymlinkRead(dir.resolve("d2/link9"), dir.resolve("d1/d11/link1"));
assertSymlinkRead(dir.resolve("link10"), dir.resolve("d2/link9"));
}
}

Expand All @@ -249,18 +245,16 @@ private void assertSymlinkToRealPath(Path link, Path trueTarget) {
try {
realPath = link.toRealPath();
} catch (IOException e) {
fail("Could not call toRealPath on link: " + link, e); // TODO this may also be moved to method declarations
// "throws IOException"
fail("Could not call toRealPath on link: " + link, e);
}
assertThat(realPath).exists();
assertThat(realPath).existsNoFollowLinks();
assertThat(realPath).isEqualTo(trueTarget);

}

private void assertSymlinkRead(Path link, Path trueTarget) {

// only call this on relative links
// only call this method if junctions are not used

Path readPath = null;
try {
Expand Down

0 comments on commit 75eb709

Please sign in to comment.