Skip to content

Commit

Permalink
devonfw/ide#1090: added AbstractIdeContextTest with better test infra…
Browse files Browse the repository at this point in the history
…structure
  • Loading branch information
hohwille committed Sep 19, 2023
1 parent 242e889 commit 6cfbb28
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 64 deletions.
16 changes: 11 additions & 5 deletions cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,26 @@ public interface FileAccess {

/**
* @param source the source {@link Path file or folder} to copy.
* @param targetDir the {@link Path} with the directory to copy {@code fileOrFolder} to.
* @param target the {@link Path} to copy {@code source} to. See {@link #copy(Path, Path, FileCopyMode)} for details.
* will always ensure that in the end you will find the same content of {@code source} in {@code target}.
*/
default void copy(Path source, Path targetDir) {
default void copy(Path source, Path target) {

copy(source, targetDir, false);
copy(source, target, FileCopyMode.COPY_TREE_FAIL_IF_EXISTS);
}

/**
* @param source the source {@link Path file or folder} to copy.
* @param targetDir the {@link Path} with the directory to copy {@code fileOrFolder} to.
* @param target the {@link Path} to copy {@code source} to. Unlike the Linux {@code cp} command this method will not
* take the filename of {@code source} and copy that to {@code target} in case that is an existing folder.
* Instead it will always be simple and stupid and just copy from {@code source} to {@code target}. Therefore
* the result is always clear and easy to predict and understand. Also you can easily rename a file to copy.
* While {@code cp my-file target} may lead to a different result than {@code cp my-file target/} this method
* will always ensure that in the end you will find the same content of {@code source} in {@code target}.
* @param fileOnly - {@code true} if {@code fileOrFolder} is expected to be a file and an exception shall be thrown if
* it is a directory, {@code false} otherwise (copy recursively).
*/
void copy(Path source, Path targetDir, boolean fileOnly);
void copy(Path source, Path target, FileCopyMode fileOnly);

/**
* @param file the ZIP file to extract.
Expand Down
38 changes: 24 additions & 14 deletions cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;

import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.log.IdeLogger;
import com.devonfw.tools.ide.url.model.file.UrlChecksum;
import com.devonfw.tools.ide.util.DateTimeUtil;
import com.devonfw.tools.ide.util.HexUtil;
Expand All @@ -45,12 +44,12 @@ public class FileAccessImpl implements FileAccess {
/**
* The constructor.
*
* @param logger the {@link IdeLogger} to use.
* @param context the {@link IdeContext} to use.
*/
public FileAccessImpl(IdeContext logger) {
public FileAccessImpl(IdeContext context) {

super();
this.context = logger;
this.context = context;
this.client = HttpClient.newBuilder().followRedirects(Redirect.ALWAYS).build();
}

Expand Down Expand Up @@ -163,37 +162,48 @@ public void move(Path source, Path targetDir) {
}

@Override
public void copy(Path source, Path targetDir, boolean fileOnly) {
public void copy(Path source, Path target, FileCopyMode mode) {

boolean fileOnly = mode.isFileOnly();
if (fileOnly) {
this.context.debug("Copying file {} to {}", source, targetDir);
this.context.debug("Copying file {} to {}", source, target);
} else {
this.context.debug("Copying {} recursively to {}", source, targetDir);
this.context.debug("Copying {} recursively to {}", source, target);
}
if (fileOnly && Files.isDirectory(source)) {
throw new IllegalStateException("Expected file but found a directory to copy at " + source);
}
if (mode.isFailIfExists()) {
if (Files.exists(target)) {
throw new IllegalStateException("Failed to copy " + source + " to already existing target " + target);
}
} else if (mode == FileCopyMode.COPY_TREE_OVERRIDE_TREE) {
delete(target);
}
try {
copyRecursive(source, targetDir);
copyRecursive(source, target, mode);
} catch (IOException e) {
throw new IllegalStateException("Failed to copy " + source + " to " + targetDir);
throw new IllegalStateException("Failed to copy " + source + " to " + target, e);
}
}

private void copyRecursive(Path source, Path targetDir) throws IOException {
private void copyRecursive(Path source, Path target, FileCopyMode mode) throws IOException {

if (Files.isDirectory(source)) {
mkdirs(targetDir);
mkdirs(target);
try (Stream<Path> childStream = Files.list(source)) {
Iterator<Path> iterator = childStream.iterator();
while (iterator.hasNext()) {
Path child = iterator.next();
copyRecursive(child, targetDir.resolve(child.getFileName()));
copyRecursive(child, target.resolve(child.getFileName()), mode);
}
}
} else if (Files.exists(source)) {
this.context.trace("Copying {} to {}", source, targetDir);
Files.copy(source, targetDir);
if (mode == FileCopyMode.COPY_TREE_OVERRIDE_FILES) {
delete(target);
}
this.context.trace("Copying {} to {}", source, target);
Files.copy(source, target);
} else {
throw new IOException("Path " + source + " does not exist.");
}
Expand Down
55 changes: 55 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/io/FileCopyMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.devonfw.tools.ide.io;

/**
* {@link Enum} with the available modes to {@link FileAccess#copy(java.nio.file.Path, java.nio.file.Path, FileCopyMode)
* copy} files and folders.
*/
public enum FileCopyMode {

/**
* Copy {@link #isFileOnly() only a single file} and {@link #isFailIfExists() fail if the target-file already exists}.
*/
COPY_FILE_FAIL_IF_EXISTS,

/** Copy {@link #isFileOnly() only a single file} and override the target-file if it already exists. */
COPY_FILE_OVERRIDE,

/** Copy {@link #isRecursive() recursively} and {@link #isFailIfExists() fail if the target-path already exists}. */
COPY_TREE_FAIL_IF_EXISTS,

/** Copy {@link #isRecursive() recursively} and override existing files but merge existing folders. */
COPY_TREE_OVERRIDE_FILES,

/**
* Copy {@link #isRecursive() recursively} and {@link FileAccess#delete(java.nio.file.Path) delete} the target-file if
* it exists before copying.
*/
COPY_TREE_OVERRIDE_TREE;

/**
* @return {@code true} if only a single file shall be copied. Will fail if a directory is given to copy,
* {@code false} otherwise (to copy folders recursively).
*/
public boolean isFileOnly() {

return (this == COPY_FILE_FAIL_IF_EXISTS) || (this == COPY_FILE_OVERRIDE);
}

/**
* @return {@code true} if files and folders shall be copied recursively, {@code false} otherwise
* ({@link #isFileOnly() copy file copy}).
*/
public boolean isRecursive() {

return !isFileOnly();
}

/**
* @return {@code true} to fail if the target file or folder already exists, {@code false} otherwise.
*/
public boolean isFailIfExists() {

return (this == COPY_FILE_FAIL_IF_EXISTS) || (this == COPY_TREE_FAIL_IF_EXISTS);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.devonfw.tools.ide.environment.EnvironmentVariables;
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.io.FileCopyMode;
import com.devonfw.tools.ide.io.TarCompression;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.process.ProcessContext;
Expand Down Expand Up @@ -306,7 +307,7 @@ private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier
}
if (linkDir != rootDir) {
assert (!linkDir.equals(rootDir));
this.context.getFileAccess().copy(toolVersionFile, linkDir, true);
this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE);
}
return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.devonfw.tools.ide.context;

import java.nio.file.Path;
import java.nio.file.Paths;

import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition;
import org.assertj.core.api.ListAssert;

import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.io.FileAccessImpl;
import com.devonfw.tools.ide.io.FileCopyMode;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.log.IdeTestLogger;

/**
* Abstract base class for tests that need mocked instances of {@link IdeContext}.
*/
public abstract class AbstractIdeContextTest extends Assertions {

/** The source {@link Path} to the test projects. */
protected static final Path PATH_PROJECTS = Paths.get("src/test/resources/ide-projects");

// will not use eclipse-target like done in maven via eclipse profile...
private static final Path PATH_PROJECTS_COPY = Paths.get("target/test-projects/");

/**
* @param projectName the (folder)name of the test project in {@link #PATH_PROJECTS}. E.g. "basic".
* @return the {@link IdeContext} pointing to that project.
*/
protected IdeContext newContext(String projectName) {

return newContext(projectName, null, true);
}

/**
* @param projectName the (folder)name of the test project in {@link #PATH_PROJECTS}. E.g. "basic".
* @param projectPath the relative path inside the test project where to create the context.
* @return the {@link IdeContext} pointing to that project.
*/
protected static IdeContext newContext(String projectName, String projectPath) {

return newContext(projectName, projectPath, true);
}

/**
* @param projectName the (folder)name of the test project in {@link #PATH_PROJECTS}. E.g. "basic".
* @param projectPath the relative path inside the test project where to create the context.
* @param copyForMutation - {@code true} to create a copy of the project that can be modified by the test,
* {@code false} otherwise (only to save resources if you are 100% sure that your test never modifies anything
* in that project.
* @return the {@link IdeContext} pointing to that project.
*/
protected static IdeContext newContext(String projectName, String projectPath, boolean copyForMutation) {

Path sourceDir = PATH_PROJECTS.resolve(projectName);
Path userDir = sourceDir;
if (projectPath != null) {
userDir = sourceDir.resolve(projectPath);
}
IdeContext context = new IdeTestContext(userDir);
if (copyForMutation) {
Path projectDir = PATH_PROJECTS_COPY.resolve(projectName);
FileAccess fileAccess = new FileAccessImpl(context);
fileAccess.delete(projectDir);
fileAccess.mkdirs(PATH_PROJECTS_COPY);
fileAccess.copy(sourceDir, projectDir, FileCopyMode.COPY_TREE_OVERRIDE_TREE);
fileAccess.copy(PATH_PROJECTS.resolve(IdeContext.FOLDER_IDE), PATH_PROJECTS_COPY.resolve(IdeContext.FOLDER_IDE),
FileCopyMode.COPY_TREE_OVERRIDE_TREE);
context = new IdeTestContext(projectDir.resolve(projectPath));
}
return context;
}

/**
* @param context the {@link IdeContext} that was created via the {@link #newContext(String) newContext} method.
* @param level the expected {@link IdeLogLevel}.
* @param message the expected {@link com.devonfw.tools.ide.log.IdeSubLogger#log(String) log message}.
*/
protected static void assertLogMessage(IdeContext context, IdeLogLevel level, String message) {

assertLogMessage(context, level, message, false);
}

/**
* @param context the {@link IdeContext} that was created via the {@link #newContext(String) newContext} method.
* @param level the expected {@link IdeLogLevel}.
* @param message the expected {@link com.devonfw.tools.ide.log.IdeSubLogger#log(String) log message}.
* @param contains - {@code true} if the given {@code message} may only be a sub-string of the log-message to assert,
* {@code false} otherwise (the entire log message including potential parameters being filled in is asserted).
*/
protected static void assertLogMessage(IdeContext context, IdeLogLevel level, String message, boolean contains) {

IdeTestLogger logger = (IdeTestLogger) context.level(IdeLogLevel.WARNING);
ListAssert<String> assertion = assertThat(logger.getMessages()).as(level.name() + "-Log messages");
if (contains) {
Condition<String> condition = new Condition<>() {
public boolean matches(String e) {

return e.contains(message);
};
};
assertion.filteredOn(condition).isNotEmpty();
} else {
assertion.contains(message);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,55 +1,19 @@
package com.devonfw.tools.ide.context;

import java.nio.file.Path;
import java.nio.file.Paths;

import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition;
import org.assertj.core.api.ListAssert;
import org.junit.jupiter.api.Test;

import com.devonfw.tools.ide.common.SystemPath;
import com.devonfw.tools.ide.environment.EnvironmentVariables;
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.log.IdeTestLogger;
import com.devonfw.tools.ide.variable.IdeVariables;

/**
* Integration test of {@link IdeContext}.
*/
@SuppressWarnings("javadoc")
public class IdeContextTest extends Assertions {

public static final Path PATH_PROJECTS = Paths.get("src/test/resources/ide-projects");

private IdeContext newContext(String path) {

Path userDir = PATH_PROJECTS.resolve(path);
return new IdeTestContext(userDir);
}

protected void assertLogMessage(IdeContext context, IdeLogLevel level, String message) {

assertLogMessage(context, level, message, false);
}

protected void assertLogMessage(IdeContext context, IdeLogLevel level, String message, boolean contains) {

IdeTestLogger logger = (IdeTestLogger) context.level(IdeLogLevel.WARNING);
ListAssert<String> assertion = assertThat(logger.getMessages()).as(level.name() + "-Log messages");
if (contains) {
Condition<String> condition = new Condition<>() {
public boolean matches(String e) {

return e.contains(message);
};
};
assertion.filteredOn(condition).isNotEmpty();
} else {
assertion.contains(message);
}
}
public class IdeContextTest extends AbstractIdeContextTest {

/**
* Test of {@link IdeContext} initialization from basic project.
Expand All @@ -58,9 +22,9 @@ public boolean matches(String e) {
public void testBasicProjectEnvironment() {

// arrange
String path = "basic/workspaces/foo-test/my-git-repo";
String path = "workspaces/foo-test/my-git-repo";
// act
IdeContext context = newContext(path);
IdeContext context = newContext("basic", path, false);
// assert
assertThat(context.getWorkspaceName()).isEqualTo("foo-test");
assertThat(IdeVariables.DOCKER_EDITION.get(context)).isEqualTo("docker");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@
import java.nio.file.Path;
import java.nio.file.Paths;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import com.devonfw.tools.ide.common.SystemInformationMock;
import com.devonfw.tools.ide.context.AbstractIdeContextTest;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.context.IdeContextTest;
import com.devonfw.tools.ide.context.IdeTestContext;

/**
* Test of {@link MacOsHelper}.
*/
public class MacOsHelperTest extends Assertions {
public class MacOsHelperTest extends AbstractIdeContextTest {

private static final IdeContext CONTEXT = new IdeTestContext(IdeContextTest.PATH_PROJECTS.resolve("basic"));
private static final IdeContext CONTEXT = newContext("basic", "", false);

private static final Path APPS_DIR = Paths.get("src/test/resources/mac-apps");

Expand Down

0 comments on commit 6cfbb28

Please sign in to comment.