From 3f6ddbe7933438f6c5353856134df8e5a63e84fa Mon Sep 17 00:00:00 2001 From: Ouchen Date: Thu, 1 Feb 2024 15:56:55 +0100 Subject: [PATCH 01/38] First implementation of ToolCommandlet --- .../ide/commandlet/CommandletManagerImpl.java | 4 +- .../com/devonfw/tools/ide/tool/jmc/Jmc.java | 52 +++++++++++++++++++ cli/src/main/resources/nls/Ide.properties | 1 + cli/src/main/resources/nls/Ide_de.properties | 1 + 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index e8fbefaae..c722e8942 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -11,12 +11,14 @@ import com.devonfw.tools.ide.property.Property; import com.devonfw.tools.ide.tool.aws.Aws; import com.devonfw.tools.ide.tool.az.Azure; +import com.devonfw.tools.ide.tool.cobigen.Cobigen; import com.devonfw.tools.ide.tool.eclipse.Eclipse; import com.devonfw.tools.ide.tool.gcviewer.GcViewer; import com.devonfw.tools.ide.tool.gh.Gh; import com.devonfw.tools.ide.tool.gradle.Gradle; import com.devonfw.tools.ide.tool.helm.Helm; import com.devonfw.tools.ide.tool.java.Java; +import com.devonfw.tools.ide.tool.jmc.Jmc; import com.devonfw.tools.ide.tool.kotlinc.Kotlinc; import com.devonfw.tools.ide.tool.kotlinc.KotlincNative; import com.devonfw.tools.ide.tool.mvn.Mvn; @@ -25,7 +27,6 @@ import com.devonfw.tools.ide.tool.quarkus.Quarkus; import com.devonfw.tools.ide.tool.terraform.Terraform; import com.devonfw.tools.ide.tool.vscode.Vscode; -import com.devonfw.tools.ide.tool.cobigen.Cobigen; /** * Implementation of {@link CommandletManager}. @@ -78,6 +79,7 @@ public CommandletManagerImpl(IdeContext context) { add(new Azure(context)); add(new Aws(context)); add(new Cobigen(context)); + add(new Jmc(context)); } private void add(Commandlet commandlet) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java new file mode 100644 index 000000000..a08e3a38b --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -0,0 +1,52 @@ +package com.devonfw.tools.ide.tool.jmc; + +import java.io.File; +import java.nio.file.Path; +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.tool.LocalToolCommandlet; +import com.devonfw.tools.ide.tool.ToolCommandlet; + +//TODO: How to start command as background Process? + +/** + * {@link ToolCommandlet} for JDK Mission Control, An advanced set of tools for managing, monitoring, profiling, and troubleshooting Java applications. + */ +public class Jmc extends LocalToolCommandlet { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + * method. + */ + public Jmc(IdeContext context) { + + super(context,"jmc",Set.of(Tag.JAVA)); //TODO ASK IF CORRECT Tag is used + } + + @Override + public void postInstall() { + + super.postInstall(); + + Path toolPath = getToolPath(); + Path oldBinaryPath = toolPath.resolve("JDK Mission Control"); + FileAccess fileAccess = context.getFileAccess(); + + moveFilesAndDirs(toolPath.toFile(),oldBinaryPath.toFile()); + fileAccess.delete(oldBinaryPath); + } + + private void moveFilesAndDirs(File toolPathDir, File oldBinaryDir) { + + FileAccess fileAccess = context.getFileAccess(); + for (File fileOrDir : oldBinaryDir.listFiles()) { + fileAccess.move(fileOrDir.toPath(),new File(toolPathDir, fileOrDir.getName()).toPath()); + } + } + +} \ No newline at end of file diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index a07f3d352..36a8e305c 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -15,6 +15,7 @@ cmd-helm=Tool commandlet for Helm (Kubernetes Package Manager) cmd-help=Prints this help. cmd-install=Install the selected tool. cmd-java=Tool commandlet for Java (OpenJDK) +cmd-jmc=Tool commandlet for JDK Mission Control cmd-kotlinc=Tool commandlet for Kotlin. cmd-kotlincnative=Tool commandlet for Kotlin-Native. cmd-list-version=List the available versions of the selected tool. diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index 82b1711b8..6317d522a 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -13,6 +13,7 @@ cmd-helm=Werkzeug Kommando für Helm (Kubernetes Package Manager) cmd-help=Zeigt diese Hilfe an. cmd-install=Installiert das selektierte Werkzeug. cmd-java=Werkzeug Kommando für Java (OpenJDK) +cmd-jmc=Werkzeug Kommando für JDK Mission Control cmd-kotlinc=Werkzeug Kommando für Kotlin. cmd-kotlincnative=Werkzeug Kommando für Kotlin-Native. cmd-list-version=Listet die verfügbaren Versionen des selektierten Werkzeugs auf. From 86c8eb9ef549db1ac04e68333e9aaf26cda8d4f3 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 2 Feb 2024 09:42:50 +0100 Subject: [PATCH 02/38] jmc implementation Add tags --- cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index a08e3a38b..c77e4aa74 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -25,7 +25,7 @@ public class Jmc extends LocalToolCommandlet { */ public Jmc(IdeContext context) { - super(context,"jmc",Set.of(Tag.JAVA)); //TODO ASK IF CORRECT Tag is used + super(context,"jmc",Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE)); //TODO ASK IF CORRECT Tag is used } @Override From 201b01b819a5d23216044d05e18f048b487c866b Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 2 Feb 2024 09:47:18 +0100 Subject: [PATCH 03/38] Add tag Add tag --- cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index c77e4aa74..e7ff9f03c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -25,7 +25,7 @@ public class Jmc extends LocalToolCommandlet { */ public Jmc(IdeContext context) { - super(context,"jmc",Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE)); //TODO ASK IF CORRECT Tag is used + super(context,"jmc",Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE, Tag.JVM)); } @Override From bde7dc44477552b2c4827e2469773ac9298a06a5 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 2 Feb 2024 13:01:37 +0100 Subject: [PATCH 04/38] Add parameter for background process Add parameter for background process and add TODO comments --- .../devonfw/tools/ide/context/AbstractIdeContext.java | 6 +++--- .../com/devonfw/tools/ide/process/ProcessContext.java | 6 ++++-- .../devonfw/tools/ide/process/ProcessContextImpl.java | 7 ++++++- .../java/com/devonfw/tools/ide/tool/ToolCommandlet.java | 7 ++++--- .../java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java | 2 +- .../devonfw/tools/ide/tool/ide/IdeToolCommandlet.java | 2 +- .../main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java | 9 +++++++-- .../com/devonfw/tools/ide/tool/terraform/Terraform.java | 2 +- 8 files changed, 27 insertions(+), 14 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index c6387fc4d..7dc3fc134 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; import java.time.Duration; import java.util.HashMap; import java.util.Iterator; @@ -13,7 +14,6 @@ import java.util.Map; import java.util.Objects; import java.util.function.Function; -import java.nio.file.attribute.FileTime; import com.devonfw.tools.ide.cli.CliArgument; import com.devonfw.tools.ide.cli.CliArguments; @@ -613,7 +613,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { } ProcessContext pc = newProcess().directory(target).executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0"); if (Files.isDirectory(target.resolve(".git"))) { - ProcessResult result = pc.addArg("remote").run(true); + ProcessResult result = pc.addArg("remote").run(true, false); List remotes = result.getOut(); if (remotes.isEmpty()) { String message = "This is a local git repo with no remote - if you did this for testing, you may continue...\n" @@ -621,7 +621,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { askToContinue(message); } else { pc.errorHandling(ProcessErrorHandling.WARNING); - result = pc.addArg("pull").run(false); + result = pc.addArg("pull").run(false, false); if (!result.isSuccessful()) { String message = "Failed to update git repository at " + target; if (this.offlineMode) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java index bcab1cfb9..1e1df5620 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java @@ -133,7 +133,7 @@ default ProcessContext addArgs(List... args) { */ default int run() { - return run(false).getExitCode(); + return run(false, false).getExitCode(); } /** @@ -144,8 +144,10 @@ default int run() { * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out * and err). + * @param isBackgroundProcess {@code true}, The process of the command will be run as background process {@code false} otherwise + * it will be run as foreground process. * @return the {@link ProcessResult}. */ - ProcessResult run(boolean capture); + ProcessResult run(boolean capture, boolean isBackgroundProcess); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 21f6a9a0e..0ac33c526 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -97,7 +97,12 @@ public ProcessContext withEnvVar(String key, String value) { } @Override - public ProcessResult run(boolean capture) { + public ProcessResult run(boolean capture, boolean isBackgroundProcess) { + + //TODO: Implement background process functionality + if (isBackgroundProcess){ + throw new UnsupportedOperationException("Background processes are currently not supported!"); + } if (this.executable == null) { throw new IllegalStateException("Missing executable to run process!"); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index 4f8f0a0d8..841397286 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -82,7 +82,7 @@ public final Set getTags() { @Override public void run() { - runTool(null, this.arguments.asArray()); + runTool(false,null, this.arguments.asArray()); } /** @@ -93,7 +93,7 @@ public void run() { * without touching and IDE installation and used to run. * @param args the commandline arguments to run the tool. */ - public void runTool(VersionIdentifier toolVersion, String... args) { + public void runTool(boolean isBackgroundProcess, VersionIdentifier toolVersion, String... args) { Path binaryPath; Path toolPath = Paths.get(getBinaryName()); @@ -105,7 +105,8 @@ public void runTool(VersionIdentifier toolVersion, String... args) { } ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath) .addArgs(args); - pc.run(); + + pc.run(false, isBackgroundProcess); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java index 5d91ad304..c98794ff4 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java @@ -81,7 +81,7 @@ protected ProcessResult runEclipse(boolean log, String... args) { Path javaPath = getCommandlet(Java.class).getToolBinPath(); pc.addArg("-vm").addArg(javaPath); pc.addArgs(args); - return pc.run(log); + return pc.run(log, false); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java index 2cd8ec18b..4bb4800c8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java @@ -207,7 +207,7 @@ public void run() { */ protected void runIde(String... args) { - runTool(null, args); + runTool(false,null, args); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index e7ff9f03c..8466c8e1b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -10,8 +10,6 @@ import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; -//TODO: How to start command as background Process? - /** * {@link ToolCommandlet} for JDK Mission Control, An advanced set of tools for managing, monitoring, profiling, and troubleshooting Java applications. */ @@ -28,6 +26,13 @@ public Jmc(IdeContext context) { super(context,"jmc",Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE, Tag.JVM)); } + @Override + public void run() { + + //TODO: pass isBackgroundProcess Parameter as true when implemented + runTool(false,null, this.arguments.asArray()); + } + @Override public void postInstall() { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java index 745ececde..e99d1264b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java @@ -26,6 +26,6 @@ public Terraform(IdeContext context) { protected void postInstall() { super.postInstall(); - runTool(null, "-install-autocomplete"); + runTool(false, null, "-install-autocomplete"); } } From 6da4cc6e43fd4e8b2f4612843232f3c3e4a71fbb Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 2 Feb 2024 13:13:56 +0100 Subject: [PATCH 05/38] Fix typo Fix typo --- cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index 8466c8e1b..705aa8425 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -29,7 +29,7 @@ public Jmc(IdeContext context) { @Override public void run() { - //TODO: pass isBackgroundProcess Parameter as true when implemented + //TODO: pass isBackgroundProcess parameter as true when implemented runTool(false,null, this.arguments.asArray()); } From 9617b1666d46e09c5acbbc330395e458f6507e8e Mon Sep 17 00:00:00 2001 From: Ouchen Date: Mon, 5 Feb 2024 13:38:16 +0100 Subject: [PATCH 06/38] Add test Add test and minor changes for debugging --- .../com/devonfw/tools/ide/tool/jmc/Jmc.java | 20 ++-- .../com/devonfw/tools/ide/Jmc/JmcTest.java | 95 ++++++++++++++++++ ....openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz | Bin 0 -> 267 bytes ...g.openjdk.jmc-8.3.0-win32.win32.x86_64.zip | Bin 0 -> 543 bytes .../_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 | 1 + .../_ide/urls/jmc/jmc/8.3.0/linux_x64.urls | 1 + .../_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 | 1 + .../_ide/urls/jmc/jmc/8.3.0/mac_x64.urls | 1 + .../_ide/urls/jmc/jmc/8.3.0/status.json | 20 ++++ .../_ide/urls/jmc/jmc/8.3.0/windows_x64.urls | 1 + .../jmc/jmc/8.3.0/windows_x64.urls.sha256 | 1 + 11 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java create mode 100644 cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz create mode 100644 cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-win32.win32.x86_64.zip create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/status.json create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls create mode 100644 cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index 705aa8425..c9e99889d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -11,26 +11,26 @@ import com.devonfw.tools.ide.tool.ToolCommandlet; /** - * {@link ToolCommandlet} for JDK Mission Control, An advanced set of tools for managing, monitoring, profiling, and troubleshooting Java applications. + * {@link ToolCommandlet} for JDK Mission + * Control, An advanced set of tools for managing, monitoring, profiling, and troubleshooting Java applications. */ public class Jmc extends LocalToolCommandlet { /** * The constructor. * - * @param context the {@link IdeContext}. - * method. + * @param context the {@link IdeContext}. method. */ public Jmc(IdeContext context) { - super(context,"jmc",Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE, Tag.JVM)); + super(context, "jmc", Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE, Tag.JVM)); } @Override public void run() { - //TODO: pass isBackgroundProcess parameter as true when implemented - runTool(false,null, this.arguments.asArray()); + // TODO: pass isBackgroundProcess parameter as true when implemented + runTool(false, null, this.arguments.asArray()); } @Override @@ -38,19 +38,21 @@ public void postInstall() { super.postInstall(); + // if(context.getSystemInfo().isWindows() || context.getSystemInfo().isLinux()) { Path toolPath = getToolPath(); Path oldBinaryPath = toolPath.resolve("JDK Mission Control"); FileAccess fileAccess = context.getFileAccess(); - - moveFilesAndDirs(toolPath.toFile(),oldBinaryPath.toFile()); + moveFilesAndDirs(toolPath.toFile(), oldBinaryPath.toFile()); fileAccess.delete(oldBinaryPath); + // } + } private void moveFilesAndDirs(File toolPathDir, File oldBinaryDir) { FileAccess fileAccess = context.getFileAccess(); for (File fileOrDir : oldBinaryDir.listFiles()) { - fileAccess.move(fileOrDir.toPath(),new File(toolPathDir, fileOrDir.getName()).toPath()); + fileAccess.move(fileOrDir.toPath(), new File(toolPathDir, fileOrDir.getName()).toPath()); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java new file mode 100644 index 000000000..2faf8d72f --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java @@ -0,0 +1,95 @@ +package com.devonfw.tools.ide.Jmc; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.commandlet.InstallCommandlet; +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; + +public class JmcTest extends AbstractIdeContextTest { + + private static WireMockServer server; + + // TODO USE TEMPDIR eventually instead of adding ressources to repo??? ASK + private static Path resourcePath = Paths.get("src/test/resources"); + + @BeforeAll + static void setUp() throws IOException { + + server = new WireMockServer(WireMockConfiguration.wireMockConfig().port(1111)); + server.start(); + } + + @AfterAll + static void tearDown() throws IOException { + + server.shutdownServer(); + } + + private void mockWebServer() throws IOException { + + String windowsFilename = "org.openjdk.jmc-8.3.0-win32.win32.x86_64.zip"; + String linuxFilename = "org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz"; + + Path windowsFilePath = resourcePath.resolve("__files").resolve(windowsFilename); // TODO USE TEMPDIR also for mock + // files??? + String windowsLength = String.valueOf(Files.size(windowsFilePath)); + server.stubFor( + get(urlPathEqualTo("/installTest/windows")).willReturn(aResponse().withHeader("Content-Type", "application/zip") + .withHeader("Content-Length", windowsLength).withStatus(200).withBodyFile(windowsFilename))); + + Path linuxFilePath = resourcePath.resolve("__files").resolve(linuxFilename); + String linuxLength = String.valueOf(Files.size(linuxFilePath)); + server.stubFor( + get(urlPathEqualTo("/installTest/linux")).willReturn(aResponse().withHeader("Content-Type", "application/gz") + .withHeader("Content-Length", linuxLength).withStatus(200).withBodyFile(linuxFilename))); + + // TODO MAKE MAC CORRECT + /* + * server.stubFor( get(urlPathEqualTo("/installTest/macOS")).willReturn(aResponse().withHeader("Content-Type", + * "application/gz") .withHeader("Content-Length", + * linuxLength).withStatus(200).withBodyFile("jmc-8.3.0_linux-x64.tar.gz"))); + */ + + } + + @Test + public void jmcPostInstallShouldMoveFilesToParentDir() throws IOException { + + // arrange + String path = "workspaces/foo-test/my-git-repo"; + IdeContext context = newContext("basic", path, true); + InstallCommandlet install = context.getCommandletManager().getCommandlet(InstallCommandlet.class); + install.tool.setValueAsString("jmc", context); + mockWebServer(); + // act + install.run(); + + // assert + assertThat(context.getSoftwarePath().resolve("jmc")).exists(); + assertThat(context.getSoftwarePath().resolve("jmc/InstallTest.txt")).hasContent("This is a test file."); + assertThat(context.getSoftwarePath().resolve("jmc/HelloWorld.txt")).hasContent("Hello World!"); + + if (context.getSystemInfo().isWindows()) { + assertThat(context.getSoftwarePath().resolve("jmc/jmc.cmd")).exists(); + } else if (context.getSystemInfo().isLinux()) { + assertThat(context.getSoftwarePath().resolve("jmc/jmc")).exists(); + } + // TODO MAC || context.getSystemInfo().isMac() + assertThat(context.getSoftwarePath().resolve("JDK Mission Control")).doesNotExist(); + } + +} \ No newline at end of file diff --git a/cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz b/cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..931b8471369fdcad3ea9384c59feee0ee429b1d6 GIT binary patch literal 267 zcmV+m0rdVKiwFP#i@;?B0PWVhYQr!T2H-On`VOag07sH_>D~;zP`U&<8`mW&vIUk5 z>D%|jP}(8j#qkiI4}*+0^Xos>QdY8I% zP8+SPr2b1yTWDYbEARig{2!YT;(P4F$2`A;$^7NN`7bo(ua!_d|Ccakjbqq%Jone< zT>rn?8s;?Fe{D|vH%TG~$6)XDc62D+ntAv6qXtf^TMVab|v=f^&XeNl|`|zDH_KPJVcP zQBDfTR4`8g%vFS##s$>J!0@ZeDdN^7D-A^;8-%5S7@KKXxygFTxhbj18TkrIE~UA- zl?p&11q(f6Jp%=xt>u|{Df#8aN&()COd<@pJqt9RfssK0L;(Ss*O4`2dQ%0g1)|v* z$j0VOR8Mn3En;MlU| literal 0 HcmV?d00001 diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 new file mode 100644 index 000000000..ba966d272 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.sha256 @@ -0,0 +1 @@ +cf666655da9bc097a7413af6cc5e9d930bc1f9267410613707f5e4aa724e3bf9 \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls new file mode 100644 index 000000000..42e8cf6cc --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/installTest/linux \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 new file mode 100644 index 000000000..438dd2a03 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 @@ -0,0 +1 @@ +c2de7dfbd9f8faaa21b4cdd8518f826dd558c9ab24a0616b3ed28437a674a97b \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls new file mode 100644 index 000000000..384fe79a4 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/installTest/macOS \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/status.json b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/status.json new file mode 100644 index 000000000..b58452d90 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/status.json @@ -0,0 +1,20 @@ +{ + "manual" : true, + "urls" : { + "-680270697" : { + "success" : { + "timestamp" : "2023-04-28T16:27:32.819394600Z" + } + }, + "-896197542" : { + "success" : { + "timestamp" : "2023-04-28T16:27:47.658175400Z" + } + }, + "-310367019" : { + "success" : { + "timestamp" : "2023-04-28T16:28:02.221367500Z" + } + } + } +} \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls new file mode 100644 index 000000000..93010df08 --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls @@ -0,0 +1 @@ +http://localhost:1111/installTest/windows \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 new file mode 100644 index 000000000..83cc5866b --- /dev/null +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls.sha256 @@ -0,0 +1 @@ +5cbb836ceb159788f03aed5d2da9debb8fa269139dc0e1f6ffff671ac5367e6b \ No newline at end of file From d4169ad6d4168b96438436d85cd998ebcab80c38 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Mon, 5 Feb 2024 16:10:52 +0100 Subject: [PATCH 07/38] Refactor test and commandlet for mac os --- .../com/devonfw/tools/ide/tool/jmc/Jmc.java | 14 ++++---- .../com/devonfw/tools/ide/Jmc/JmcTest.java | 33 +++++++++++-------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index c9e99889d..9491ffe32 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -38,13 +38,13 @@ public void postInstall() { super.postInstall(); - // if(context.getSystemInfo().isWindows() || context.getSystemInfo().isLinux()) { - Path toolPath = getToolPath(); - Path oldBinaryPath = toolPath.resolve("JDK Mission Control"); - FileAccess fileAccess = context.getFileAccess(); - moveFilesAndDirs(toolPath.toFile(), oldBinaryPath.toFile()); - fileAccess.delete(oldBinaryPath); - // } + if (context.getSystemInfo().isWindows() || context.getSystemInfo().isLinux()) { + Path toolPath = getToolPath(); + Path oldBinaryPath = toolPath.resolve("JDK Mission Control"); + FileAccess fileAccess = context.getFileAccess(); + moveFilesAndDirs(toolPath.toFile(), oldBinaryPath.toFile()); + fileAccess.delete(oldBinaryPath); + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java index 2faf8d72f..b6550df6f 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java @@ -23,7 +23,6 @@ public class JmcTest extends AbstractIdeContextTest { private static WireMockServer server; - // TODO USE TEMPDIR eventually instead of adding ressources to repo??? ASK private static Path resourcePath = Paths.get("src/test/resources"); @BeforeAll @@ -43,9 +42,10 @@ private void mockWebServer() throws IOException { String windowsFilename = "org.openjdk.jmc-8.3.0-win32.win32.x86_64.zip"; String linuxFilename = "org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz"; + String macOSFilename = "org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz"; + + Path windowsFilePath = resourcePath.resolve("__files").resolve(windowsFilename); - Path windowsFilePath = resourcePath.resolve("__files").resolve(windowsFilename); // TODO USE TEMPDIR also for mock - // files??? String windowsLength = String.valueOf(Files.size(windowsFilePath)); server.stubFor( get(urlPathEqualTo("/installTest/windows")).willReturn(aResponse().withHeader("Content-Type", "application/zip") @@ -57,17 +57,16 @@ private void mockWebServer() throws IOException { get(urlPathEqualTo("/installTest/linux")).willReturn(aResponse().withHeader("Content-Type", "application/gz") .withHeader("Content-Length", linuxLength).withStatus(200).withBodyFile(linuxFilename))); - // TODO MAKE MAC CORRECT - /* - * server.stubFor( get(urlPathEqualTo("/installTest/macOS")).willReturn(aResponse().withHeader("Content-Type", - * "application/gz") .withHeader("Content-Length", - * linuxLength).withStatus(200).withBodyFile("jmc-8.3.0_linux-x64.tar.gz"))); - */ + Path macOSFilePath = resourcePath.resolve("__files").resolve(macOSFilename); + String maxOSLength = String.valueOf(Files.size(macOSFilePath)); + server.stubFor( + get(urlPathEqualTo("/installTest/macOS")).willReturn(aResponse().withHeader("Content-Type", "application/gz") + .withHeader("Content-Length", maxOSLength).withStatus(200).withBodyFile(macOSFilename))); } @Test - public void jmcPostInstallShouldMoveFilesToParentDir() throws IOException { + public void jmcPostInstallShouldMoveFilesIfRequired() throws IOException { // arrange String path = "workspaces/foo-test/my-git-repo"; @@ -81,15 +80,23 @@ public void jmcPostInstallShouldMoveFilesToParentDir() throws IOException { // assert assertThat(context.getSoftwarePath().resolve("jmc")).exists(); assertThat(context.getSoftwarePath().resolve("jmc/InstallTest.txt")).hasContent("This is a test file."); - assertThat(context.getSoftwarePath().resolve("jmc/HelloWorld.txt")).hasContent("Hello World!"); if (context.getSystemInfo().isWindows()) { assertThat(context.getSoftwarePath().resolve("jmc/jmc.cmd")).exists(); } else if (context.getSystemInfo().isLinux()) { assertThat(context.getSoftwarePath().resolve("jmc/jmc")).exists(); } - // TODO MAC || context.getSystemInfo().isMac() - assertThat(context.getSoftwarePath().resolve("JDK Mission Control")).doesNotExist(); + + if (context.getSystemInfo().isWindows() || context.getSystemInfo().isLinux()) { + assertThat(context.getSoftwarePath().resolve("jmc/HelloWorld.txt")).hasContent("Hello World!"); + assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control")).doesNotExist(); + } + + if (context.getSystemInfo().isMac()) { + assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control.app")).exists(); + assertThat(context.getSoftwarePath().resolve("jmc/JDK Mission Control.app/Contents")).exists(); + } + } } \ No newline at end of file From efe4ff5c71bdfa586776dce82ab180fefe7e8e0f Mon Sep 17 00:00:00 2001 From: Ouchen Date: Mon, 5 Feb 2024 16:14:12 +0100 Subject: [PATCH 08/38] Add "mock file" for jmc on MAC OS --- ...g.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz | Bin 0 -> 449 bytes .../_ide/urls/jmc/jmc/8.3.0/mac_x64.sha256 | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz diff --git a/cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz b/cli/src/test/resources/__files/org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..134e89a5401586872e81a7bcaa4041c128db9884 GIT binary patch literal 449 zcmV;y0Y3g8iwFRG_`qcZ0PU7tOT#b}hI2jeKjar=Cux#XFTC&zoF5<};+>Qkwb-?z z?O;FO)44g_9PFd((B)|%p{JyT_I*w}X;DT=a_@>#l*5v310WEQ#u3wTg0f6%qm(r& z2y`0n(g^KdS@ zxuYj!>4KHKYj( Date: Tue, 6 Feb 2024 13:29:06 +0100 Subject: [PATCH 09/38] Add ascii doc stuff --- documentation/IDEasy-usage.asciidoc | 1 + documentation/jmc.asciidoc | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 documentation/jmc.asciidoc diff --git a/documentation/IDEasy-usage.asciidoc b/documentation/IDEasy-usage.asciidoc index 95b182c73..3e49c261c 100644 --- a/documentation/IDEasy-usage.asciidoc +++ b/documentation/IDEasy-usage.asciidoc @@ -15,6 +15,7 @@ include::variables.asciidoc[leveloffset=2] include::cli.asciidoc[leveloffset=2] include::docker-desktop-alternative.asciidoc[leveloffset=3] +include::jmc.asciidoc[leveloffset=3] <<<< diff --git a/documentation/jmc.asciidoc b/documentation/jmc.asciidoc new file mode 100644 index 000000000..75f4918b6 --- /dev/null +++ b/documentation/jmc.asciidoc @@ -0,0 +1,14 @@ +:toc: +toc::[] + +# Java Mission Control + +The `jmc` commandlet allows to install and setup https://www.oracle.com/java/technologies/jdk-mission-control.html[Java Mission Control]. To learn more about Java Mission Control, please go https://docs.oracle.com/en/java/java-components/jdk-mission-control/index.html[here]. + +The arguments (`devon jmc «args»`) are explained by the following table: + +[options="header"] +|======================= +|*Command* |*Meaning* +|`install jmc` |install Java Mission Control (or update and verify) +|`jmc «args»` |run Java Mission Control with the given `«args»` \ No newline at end of file From f9f202cc73247e503773c7e66d1ddb34385e8a81 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 6 Feb 2024 13:41:19 +0100 Subject: [PATCH 10/38] Improve javadoc suggestions --- .../java/com/devonfw/tools/ide/process/ProcessContext.java | 4 ++-- .../main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java | 4 +++- cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java index 1e1df5620..191ba706e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java @@ -144,8 +144,8 @@ default int run() { * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out * and err). - * @param isBackgroundProcess {@code true}, The process of the command will be run as background process {@code false} otherwise - * it will be run as foreground process. + * @param isBackgroundProcess {@code true}, the process of the command will be run as background process, + * {@code false} otherwise it will be run as foreground process. * @return the {@link ProcessResult}. */ ProcessResult run(boolean capture, boolean isBackgroundProcess); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index 841397286..20266a52b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -82,12 +82,14 @@ public final Set getTags() { @Override public void run() { - runTool(false,null, this.arguments.asArray()); + runTool(false, null, this.arguments.asArray()); } /** * Ensures the tool is installed and then runs this tool with the given arguments. * + * @param isBackgroundProcess {@code true}, the process of the command will be run as background process, + * {@code false} otherwise it will be run as foreground process. * @param toolVersion the explicit version (pattern) to run. Typically {@code null} to ensure the configured version * is installed and use that one. Otherwise, the specified version will be installed in the software repository * without touching and IDE installation and used to run. diff --git a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java index b6550df6f..4d34d4134 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java @@ -19,6 +19,9 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +/** + * Integration test of {@link com.devonfw.tools.ide.tool.jmc.Jmc}. + */ public class JmcTest extends AbstractIdeContextTest { private static WireMockServer server; From 417fe6d35ee5af06d69cc1712014af1bb9659566 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 6 Feb 2024 13:58:11 +0100 Subject: [PATCH 11/38] Link issue id to todo comments --- .../com/devonfw/tools/ide/process/ProcessContextImpl.java | 4 ++-- cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 0ac33c526..a0ca0dda9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -99,8 +99,8 @@ public ProcessContext withEnvVar(String key, String value) { @Override public ProcessResult run(boolean capture, boolean isBackgroundProcess) { - //TODO: Implement background process functionality - if (isBackgroundProcess){ + // TODO https://github.com/devonfw/IDEasy/issues/9: Implement background process functionality + if (isBackgroundProcess) { throw new UnsupportedOperationException("Background processes are currently not supported!"); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index 9491ffe32..d552456b6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -29,7 +29,7 @@ public Jmc(IdeContext context) { @Override public void run() { - // TODO: pass isBackgroundProcess parameter as true when implemented + // TODO https://github.com/devonfw/IDEasy/issues/9: pass isBackgroundProcess parameter as true when implemented runTool(false, null, this.arguments.asArray()); } From 25b6e51237e93f3b157521bbff90f786c085056d Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 7 Feb 2024 09:18:09 +0100 Subject: [PATCH 12/38] Refactor jmcTest --- .../com/devonfw/tools/ide/Jmc/JmcTest.java | 24 +++++++++---------- .../_ide/urls/jmc/jmc/8.3.0/linux_x64.urls | 2 +- .../_ide/urls/jmc/jmc/8.3.0/mac_x64.urls | 2 +- .../_ide/urls/jmc/jmc/8.3.0/windows_x64.urls | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java index 4d34d4134..6e330af6f 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java @@ -47,24 +47,24 @@ private void mockWebServer() throws IOException { String linuxFilename = "org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz"; String macOSFilename = "org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz"; - Path windowsFilePath = resourcePath.resolve("__files").resolve(windowsFilename); + Path windowsFilePathJmc = resourcePath.resolve("__files").resolve(windowsFilename); - String windowsLength = String.valueOf(Files.size(windowsFilePath)); + String windowsLengthJmc = String.valueOf(Files.size(windowsFilePathJmc)); server.stubFor( - get(urlPathEqualTo("/installTest/windows")).willReturn(aResponse().withHeader("Content-Type", "application/zip") - .withHeader("Content-Length", windowsLength).withStatus(200).withBodyFile(windowsFilename))); + get(urlPathEqualTo("/jmcTest/windows")).willReturn(aResponse().withHeader("Content-Type", "application/zip") + .withHeader("Content-Length", windowsLengthJmc).withStatus(200).withBodyFile(windowsFilename))); - Path linuxFilePath = resourcePath.resolve("__files").resolve(linuxFilename); - String linuxLength = String.valueOf(Files.size(linuxFilePath)); + Path linuxFilePathJmc = resourcePath.resolve("__files").resolve(linuxFilename); + String linuxLengthJmc = String.valueOf(Files.size(linuxFilePathJmc)); server.stubFor( - get(urlPathEqualTo("/installTest/linux")).willReturn(aResponse().withHeader("Content-Type", "application/gz") - .withHeader("Content-Length", linuxLength).withStatus(200).withBodyFile(linuxFilename))); + get(urlPathEqualTo("/jmcTest/linux")).willReturn(aResponse().withHeader("Content-Type", "application/gz") + .withHeader("Content-Length", linuxLengthJmc).withStatus(200).withBodyFile(linuxFilename))); - Path macOSFilePath = resourcePath.resolve("__files").resolve(macOSFilename); - String maxOSLength = String.valueOf(Files.size(macOSFilePath)); + Path macOSFilePathJmc = resourcePath.resolve("__files").resolve(macOSFilename); + String maxOSLengthJmc = String.valueOf(Files.size(macOSFilePathJmc)); server.stubFor( - get(urlPathEqualTo("/installTest/macOS")).willReturn(aResponse().withHeader("Content-Type", "application/gz") - .withHeader("Content-Length", maxOSLength).withStatus(200).withBodyFile(macOSFilename))); + get(urlPathEqualTo("/jmcTest/macOS")).willReturn(aResponse().withHeader("Content-Type", "application/gz") + .withHeader("Content-Length", maxOSLengthJmc).withStatus(200).withBodyFile(macOSFilename))); } diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls index 42e8cf6cc..a0da161ed 100644 --- a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/linux_x64.urls @@ -1 +1 @@ -http://localhost:1111/installTest/linux \ No newline at end of file +http://localhost:1111/jmcTest/linux \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls index 384fe79a4..ef4e100c8 100644 --- a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/mac_x64.urls @@ -1 +1 @@ -http://localhost:1111/installTest/macOS \ No newline at end of file +http://localhost:1111/jmcTest/macOS \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls index 93010df08..ce3f2f5ad 100644 --- a/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls +++ b/cli/src/test/resources/ide-projects/_ide/urls/jmc/jmc/8.3.0/windows_x64.urls @@ -1 +1 @@ -http://localhost:1111/installTest/windows \ No newline at end of file +http://localhost:1111/jmcTest/windows \ No newline at end of file From 9955032a96aedc24941f4d0027b7d378a05caa3f Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 7 Feb 2024 09:28:03 +0100 Subject: [PATCH 13/38] Fix LocalToolCommandLet logic --- .../com/devonfw/tools/ide/tool/LocalToolCommandlet.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index d1e58180a..08168b7e9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -20,6 +20,8 @@ */ public abstract class LocalToolCommandlet extends ToolCommandlet { + private boolean doInstallationFlag = false; + /** * The constructor. * @@ -60,13 +62,14 @@ public Path getToolBinPath() { protected boolean doInstall(boolean silent) { VersionIdentifier configuredVersion = getConfiguredVersion(); + // get installed version before installInRepo actually may install the software + VersionIdentifier installedVersion = getInstalledVersion(); // install configured version of our tool in the software repository if not already installed ToolInstallation installation = installInRepo(configuredVersion); // check if we already have this version installed (linked) locally in IDE_HOME/software - VersionIdentifier installedVersion = getInstalledVersion(); VersionIdentifier resolvedVersion = installation.resolvedVersion(); - if (resolvedVersion.equals(installedVersion)) { + if (resolvedVersion.equals(installedVersion) && !doInstallationFlag) { IdeLogLevel level = silent ? IdeLogLevel.DEBUG : IdeLogLevel.INFO; this.context.level(level).log("Version {} of tool {} is already installed", installedVersion, getToolWithEdition()); @@ -156,6 +159,8 @@ public ToolInstallation installInRepo(VersionIdentifier version, String edition, } catch (IOException e) { throw new IllegalStateException("Failed to write version file " + toolVersionFile, e); } + // this flag results in above conditions to be true if isForceMode is true or if the tool version file is missing + doInstallationFlag = true; return createToolInstallation(toolPath, resolvedVersion, toolVersionFile); } From 2334b869446dc8d7ae56fc51e1b92af6b13c6143 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 7 Feb 2024 16:02:53 +0100 Subject: [PATCH 14/38] Add java requirement installation for JMC --- cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index d552456b6..07de4b1eb 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -9,6 +9,7 @@ import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.java.Java; /** * {@link ToolCommandlet} for JDK Mission @@ -26,6 +27,13 @@ public Jmc(IdeContext context) { super(context, "jmc", Set.of(Tag.JAVA, Tag.QA, Tag.ANALYSE, Tag.JVM)); } + @Override + public boolean doInstall(boolean silent) { + + getCommandlet(Java.class).install(); + return super.doInstall(silent); + } + @Override public void run() { From ca60b424a558c9f1432e3fa32b273f9f48d12175 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 7 Feb 2024 16:04:38 +0100 Subject: [PATCH 15/38] Make test more clean and neat and consider Java requirement for JMC --- .../com/devonfw/tools/ide/Jmc/JmcTest.java | 46 ++++++++++++------- .../ide/context/AbstractIdeTestContext.java | 5 +- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java index 6e330af6f..2132b9c8a 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/Jmc/JmcTest.java @@ -43,29 +43,41 @@ static void tearDown() throws IOException { private void mockWebServer() throws IOException { - String windowsFilename = "org.openjdk.jmc-8.3.0-win32.win32.x86_64.zip"; - String linuxFilename = "org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz"; - String macOSFilename = "org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz"; - - Path windowsFilePathJmc = resourcePath.resolve("__files").resolve(windowsFilename); - + String windowsFilenameJmc = "org.openjdk.jmc-8.3.0-win32.win32.x86_64.zip"; + String linuxFilenameJmc = "org.openjdk.jmc-8.3.0-linux.gtk.x86_64.tar.gz"; + String macOSFilenameJmc = "org.openjdk.jmc-8.3.0-macosx.cocoa.x86_64.tar.gz"; + String windowsFilenameJava = "java-17.0.6-windows-x64.zip"; + String linuxFilenameJava = "java-17.0.6-linux-x64.tgz"; + String resourceFilesDirName = "__files"; + + Path windowsFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(windowsFilenameJmc); String windowsLengthJmc = String.valueOf(Files.size(windowsFilePathJmc)); - server.stubFor( - get(urlPathEqualTo("/jmcTest/windows")).willReturn(aResponse().withHeader("Content-Type", "application/zip") - .withHeader("Content-Length", windowsLengthJmc).withStatus(200).withBodyFile(windowsFilename))); - Path linuxFilePathJmc = resourcePath.resolve("__files").resolve(linuxFilename); + Path linuxFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(linuxFilenameJmc); String linuxLengthJmc = String.valueOf(Files.size(linuxFilePathJmc)); - server.stubFor( - get(urlPathEqualTo("/jmcTest/linux")).willReturn(aResponse().withHeader("Content-Type", "application/gz") - .withHeader("Content-Length", linuxLengthJmc).withStatus(200).withBodyFile(linuxFilename))); - Path macOSFilePathJmc = resourcePath.resolve("__files").resolve(macOSFilename); + Path macOSFilePathJmc = resourcePath.resolve(resourceFilesDirName).resolve(macOSFilenameJmc); String maxOSLengthJmc = String.valueOf(Files.size(macOSFilePathJmc)); - server.stubFor( - get(urlPathEqualTo("/jmcTest/macOS")).willReturn(aResponse().withHeader("Content-Type", "application/gz") - .withHeader("Content-Length", maxOSLengthJmc).withStatus(200).withBodyFile(macOSFilename))); + Path windowsFilePathJava = resourcePath.resolve(resourceFilesDirName).resolve(windowsFilenameJava); + String windowsLengthJava = String.valueOf(Files.size(windowsFilePathJava)); + + Path linuxFilePathJava = resourcePath.resolve(resourceFilesDirName).resolve(linuxFilenameJava); + String linuxLengthJava = String.valueOf(Files.size(linuxFilePathJava)); + + setupMockServerResponse("/jmcTest/windows", "application/zip", windowsLengthJmc, windowsFilenameJmc); + setupMockServerResponse("/jmcTest/linux", "application/gz", linuxLengthJmc, linuxFilenameJmc); + setupMockServerResponse("/jmcTest/macOS", "application/gz", maxOSLengthJmc, macOSFilenameJmc); + setupMockServerResponse("/installTest/windows", "application/zip", windowsLengthJava, windowsFilenameJava); + setupMockServerResponse("/installTest/linux", "application/tgz", linuxLengthJava, linuxFilenameJava); + setupMockServerResponse("/installTest/macOS", "application/tgz", linuxLengthJava, linuxFilenameJava); + + } + + private void setupMockServerResponse(String testUrl, String contentType, String contentLength, String bodyFile) { + + server.stubFor(get(urlPathEqualTo(testUrl)).willReturn(aResponse().withHeader("Content-Type", contentType) + .withHeader("Content-Length", contentLength).withStatus(200).withBodyFile(bodyFile))); } @Test diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java index d47ab227f..97e63143a 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java @@ -63,7 +63,10 @@ public IdeProgressBar prepareProgressBar(String taskName, long size) { IdeProgressBarTestImpl progressBar = new IdeProgressBarTestImpl(taskName, size); IdeProgressBarTestImpl duplicate = this.progressBarMap.put(taskName, progressBar); - assert duplicate == null; + // If we have multiple downloads, we may have an existing "Downloading" key + if (!taskName.equals("Downloading")) { + assert duplicate == null; + } return progressBar; } From 21f997dcb7fafd94e47f0a91618b7072c5cb9541 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 9 Feb 2024 15:51:49 +0100 Subject: [PATCH 16/38] Add first implementation of background processes Also refactored big run method of ProcessContextImpl and wrote tests using mockito --- .../tools/ide/process/ProcessContextImpl.java | 141 +++++++----- .../com/devonfw/tools/ide/tool/jmc/Jmc.java | 3 +- .../ide/process/ProcessContextImplTest.java | 213 ++++++++++++++++++ pom.xml | 33 +-- 4 files changed, 323 insertions(+), 67 deletions(-) create mode 100644 cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index a0ca0dda9..adce4e92b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -99,9 +99,9 @@ public ProcessContext withEnvVar(String key, String value) { @Override public ProcessResult run(boolean capture, boolean isBackgroundProcess) { - // TODO https://github.com/devonfw/IDEasy/issues/9: Implement background process functionality - if (isBackgroundProcess) { - throw new UnsupportedOperationException("Background processes are currently not supported!"); + if (capture && isBackgroundProcess) { + throw new IllegalStateException( + "It is not possible for the main process to capture the streams of the subprocess (background process) !"); } if (this.executable == null) { @@ -110,72 +110,49 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { String executableName = this.executable.toString(); // pragmatic solution to avoid copying lists/arrays this.arguments.add(0, executableName); - String fileExtension = FilenameUtil.getExtension(executableName); - boolean isBashScript = "sh".equals(fileExtension) || hasSheBang(this.executable); - if (isBashScript) { - String bash = "bash"; - if (this.context.getSystemInfo().isWindows()) { - String findBashOnWindowsResult = findBashOnWindows(); - if (findBashOnWindowsResult != null) { - bash = findBashOnWindowsResult; - } - } - this.arguments.add(0, bash); - } + + checkAndHandlePossibleBashScript(executableName); + this.processBuilder.command(this.arguments); if (this.context.debug().isEnabled()) { String message = createCommandMessage(" ..."); this.context.debug(message); } + try { + if (capture) { this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE); + } else if (isBackgroundProcess) { + // TODO ASK if correct + this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD); } + + // start + Process process = this.processBuilder.start(); + List out = null; List err = null; - Process process = this.processBuilder.start(); if (capture) { out = new ArrayList<>(); err = new ArrayList<>(); - try (BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream())); - BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { - String outLine = ""; - String errLine = ""; - while ((outLine != null) || (errLine != null)) { - if (outLine != null) { - outLine = outReader.readLine(); - if (outLine != null) { - out.add(outLine); - } - } - if (errLine != null) { - errLine = errReader.readLine(); - if (errLine != null) { - err.add(errLine); - } - } - } - } + handleCapture(process, out, err); } - int exitCode = process.waitFor(); - ProcessResult result = new ProcessResultImpl(exitCode, out, err); - if (!result.isSuccessful() && (this.errorHandling != ProcessErrorHandling.NONE)) { - String message = createCommandMessage(" failed with exit code " + exitCode + "!"); - if (this.errorHandling == ProcessErrorHandling.THROW) { - throw new CliException(message, exitCode); - } - IdeSubLogger level; - if (this.errorHandling == ProcessErrorHandling.ERROR) { - level = this.context.error(); - } else if (this.errorHandling == ProcessErrorHandling.WARNING) { - level = this.context.warning(); - } else { - level = this.context.error(); - level.log("Internal error: Undefined error handling {}", this.errorHandling); - } - level.log(message); + + // Exit code for background process? + int exitCode; + if (isBackgroundProcess) { + // TODO: Ask if a background process shall get its own process result or if we should assume success? + exitCode = ProcessResult.SUCCESS; + } else { + exitCode = process.waitFor(); } + + ProcessResult result = new ProcessResultImpl(exitCode, out, err); + performLogOnError(result, exitCode); + return result; + } catch (Exception e) { String msg = e.getMessage(); if ((msg == null) || msg.isEmpty()) { @@ -273,4 +250,64 @@ private String findBashOnWindows() { throw new IllegalStateException("Could not find Bash. Please install Git for Windows and rerun."); } + private void handleCapture(Process process, List out, List err) throws IOException { + + try (BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream())); + BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String outLine = ""; + String errLine = ""; + while ((outLine != null) || (errLine != null)) { + if (outLine != null) { + outLine = outReader.readLine(); + if (outLine != null) { + out.add(outLine); + } + } + if (errLine != null) { + errLine = errReader.readLine(); + if (errLine != null) { + err.add(errLine); + } + } + } + } + } + + private void checkAndHandlePossibleBashScript(String executableName) { + + String fileExtension = FilenameUtil.getExtension(executableName); + boolean isBashScript = "sh".equals(fileExtension) || hasSheBang(this.executable); + if (isBashScript) { + String bash = "bash"; + if (this.context.getSystemInfo().isWindows()) { + String findBashOnWindowsResult = findBashOnWindows(); + if (findBashOnWindowsResult != null) { + bash = findBashOnWindowsResult; + } + } + this.arguments.add(0, bash); + } + + } + + private void performLogOnError(ProcessResult result, int exitCode) { + + if (!result.isSuccessful() && (this.errorHandling != ProcessErrorHandling.NONE)) { + String message = createCommandMessage(" failed with exit code " + exitCode + "!"); + if (this.errorHandling == ProcessErrorHandling.THROW) { + throw new CliException(message, exitCode); + } + IdeSubLogger level; + if (this.errorHandling == ProcessErrorHandling.ERROR) { + level = this.context.error(); + } else if (this.errorHandling == ProcessErrorHandling.WARNING) { + level = this.context.warning(); + } else { + level = this.context.error(); + level.log("Internal error: Undefined error handling {}", this.errorHandling); + } + level.log(message); + } + } + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index 07de4b1eb..dc9b39f00 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -37,8 +37,7 @@ public boolean doInstall(boolean silent) { @Override public void run() { - // TODO https://github.com/devonfw/IDEasy/issues/9: pass isBackgroundProcess parameter as true when implemented - runTool(false, null, this.arguments.asArray()); + runTool(true, null, this.arguments.asArray()); } @Override diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java new file mode 100644 index 000000000..df4b055a4 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -0,0 +1,213 @@ +package com.devonfw.tools.ide.process; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.lang.reflect.Field; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.platform.commons.util.ReflectionUtils; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.log.IdeLogLevel; + +public class ProcessContextImplTest extends AbstractIdeContextTest { + + private ProcessContextImpl underTest; + + private Process processMock; + + private ProcessBuilder mockProcessBuilder; + + private IdeContext context; + + private final String projectPath = "workspaces/foo-test/my-git-repo"; + + @BeforeEach + public void setUp() throws Exception { + + mockProcessBuilder = mock(ProcessBuilder.class); + context = newContext("basic", projectPath, false); + underTest = new ProcessContextImpl(context); + + Field field = ReflectionUtils.findFields(ProcessContextImpl.class, f -> f.getName().equals("processBuilder"), + ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0); + + field.setAccessible(true); + field.set(underTest, mockProcessBuilder); + field.setAccessible(false); + + // underTest needs executable + Field underTestExecutable = ReflectionUtils.findFields(ProcessContextImpl.class, + f -> f.getName().equals("executable"), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0); + underTestExecutable.setAccessible(true); + underTestExecutable.set(underTest, PATH_PROJECTS.resolve("_ide/software/nonExistingBinaryForTesting")); + underTestExecutable.setAccessible(false); + + processMock = mock(Process.class); + when(mockProcessBuilder.start()).thenReturn(processMock); + + when(mockProcessBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE)).thenReturn(mockProcessBuilder); + when(mockProcessBuilder.redirectError(ProcessBuilder.Redirect.PIPE)).thenReturn(mockProcessBuilder); + when(mockProcessBuilder.redirectOutput(ProcessBuilder.Redirect.DISCARD)).thenReturn(mockProcessBuilder); + when(mockProcessBuilder.redirectError(ProcessBuilder.Redirect.DISCARD)).thenReturn(mockProcessBuilder); + when(mockProcessBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT)).thenReturn(mockProcessBuilder); + when(mockProcessBuilder.redirectError(ProcessBuilder.Redirect.INHERIT)).thenReturn(mockProcessBuilder); + + } + + @Test + public void streamCapturingAndBackgroundProcessShouldThrowIllegalState() { + + // arrange + String expectedMessage = "It is not possible for the main process to capture the streams of the subprocess (background process) !"; + + // act & assert + Exception exception = assertThrows(IllegalStateException.class, () -> { + underTest.run(true, true); + }); + + String actualMessage = exception.getMessage(); + + assertEquals(actualMessage, expectedMessage); + } + + @Test + public void missingExecutableShouldThrowIllegalState() { + + // arrange + String expectedMessage = "Missing executable to run process!"; + + // act & assert + Exception exception = assertThrows(IllegalStateException.class, () -> { + underTest.run(false, false); + }); + + String actualMessage = exception.getMessage(); + + assertEquals(actualMessage, expectedMessage); + } + + @Test + public void onSuccessfulProcessStartReturnSuccessResult() throws Exception { + + // arrange + + when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS); + + // act + ProcessResult result = underTest.run(false, false); + + // assert + assertTrue(result.isSuccessful()); + + } + + @Test + public void enablingCaptureShouldRedirectAndCaptureStreamsCorrectly() throws Exception { + + // arrange + when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS); + String outputText = "hello world"; + String errorText = "error"; + + try (InputStream outputStream = new ByteArrayInputStream(outputText.getBytes()); + InputStream errorStream = new ByteArrayInputStream(errorText.getBytes())) { + + when(processMock.getInputStream()).thenReturn(outputStream); + + when(processMock.getErrorStream()).thenReturn(errorStream); + + // act + ProcessResult result = underTest.run(true, false); + + // assert + verify(mockProcessBuilder) + .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); + + verify(mockProcessBuilder) + .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); + + assertEquals(outputText, result.getOut().get(0)); + assertEquals(errorText, result.getErr().get(0)); + } + } + + @Test + public void enablingBackgroundProcessShouldNotBeAwaitedAndShouldNotPassStreams() throws Exception { + + // arrange + when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS); + + // act + ProcessResult result = underTest.run(false, true); + + // assert + verify(mockProcessBuilder) + .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); + + verify(mockProcessBuilder) + .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); + + verify(processMock, never()).waitFor(); + + assertNull(result.getOut()); + assertNull(result.getErr()); + + } + + @Test + public void unsuccessfulProcessShouldThrowIllegalState() throws Exception { + + // arrange + when(processMock.waitFor()).thenReturn(ProcessResult.TOOL_NOT_INSTALLED); + + underTest.errorHandling(ProcessErrorHandling.THROW); + + // act & assert + assertThrows(IllegalStateException.class, () -> { + underTest.run(false, false); + }); + + } + + @ParameterizedTest + @EnumSource(value = ProcessErrorHandling.class, names = { "WARNING", "ERROR" }) + public void ProcessWarningAndErrorShouldBeLogged(ProcessErrorHandling processErrorHandling) throws Exception { + + // arrange + when(processMock.waitFor()).thenReturn(ProcessResult.TOOL_NOT_INSTALLED); + underTest.errorHandling(processErrorHandling); + String expectedMessage = "failed with exit code "; + // act + underTest.run(false, false); + + // assert + IdeLogLevel level = convertToIdeLogLevel(processErrorHandling); + assertLogMessage((IdeTestContext) context, level, expectedMessage); + } + + private IdeLogLevel convertToIdeLogLevel(ProcessErrorHandling processErrorHandling) { + + return switch (processErrorHandling) { + case NONE, THROW -> null; + case WARNING -> IdeLogLevel.WARNING; + case ERROR -> IdeLogLevel.ERROR; + }; + } + +} diff --git a/pom.xml b/pom.xml index 0a51732b8..524bbc6dc 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -33,6 +34,12 @@ 3.24.2 test + + org.mockito + mockito-core + 5.10.0 + test + @@ -99,74 +106,74 @@ Jörg Hohwiller hohwille@users.sourceforge.net Capgemini - + admin designer developer +1 - + trippl Thomas Rippl - + developer +1 - + markusschuh Markus Schuh Capgemini - + contributor +1 - + maybeec Malte Brunnlieb Capgemini - + contributor +1 - + ediekman Erik Diekmann Capgemini - + contributor +1 - + nricheton Nicolas Richeton Capgemini - + contributor +1 - + From 4761dc4de4c2c342f2f5e509fe1b44344c4e8a39 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 9 Feb 2024 15:55:36 +0100 Subject: [PATCH 17/38] remove unnecessary comments --- .../java/com/devonfw/tools/ide/process/ProcessContextImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index adce4e92b..036918c63 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -128,7 +128,6 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD); } - // start Process process = this.processBuilder.start(); List out = null; @@ -139,7 +138,6 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { handleCapture(process, out, err); } - // Exit code for background process? int exitCode; if (isBackgroundProcess) { // TODO: Ask if a background process shall get its own process result or if we should assume success? From e9e285e9156ec15f275aedae519c5497ddb83771 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 9 Feb 2024 16:48:48 +0100 Subject: [PATCH 18/38] Fix test --- .../tools/ide/process/ProcessContextImplTest.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index df4b055a4..5998a0793 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -87,11 +87,17 @@ public void streamCapturingAndBackgroundProcessShouldThrowIllegalState() { } @Test - public void missingExecutableShouldThrowIllegalState() { + public void missingExecutableShouldThrowIllegalState() throws Exception { // arrange String expectedMessage = "Missing executable to run process!"; + Field underTestExecutable = ReflectionUtils.findFields(ProcessContextImpl.class, + f -> f.getName().equals("executable"), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0); + underTestExecutable.setAccessible(true); + underTestExecutable.set(underTest, null); + underTestExecutable.setAccessible(false); + // act & assert Exception exception = assertThrows(IllegalStateException.class, () -> { underTest.run(false, false); @@ -192,13 +198,13 @@ public void ProcessWarningAndErrorShouldBeLogged(ProcessErrorHandling processErr // arrange when(processMock.waitFor()).thenReturn(ProcessResult.TOOL_NOT_INSTALLED); underTest.errorHandling(processErrorHandling); - String expectedMessage = "failed with exit code "; + String expectedMessage = "failed with exit code 4!"; // act underTest.run(false, false); // assert IdeLogLevel level = convertToIdeLogLevel(processErrorHandling); - assertLogMessage((IdeTestContext) context, level, expectedMessage); + assertLogMessage((IdeTestContext) context, level, expectedMessage, true); } private IdeLogLevel convertToIdeLogLevel(ProcessErrorHandling processErrorHandling) { From 8bb848f7f6db03e5e5215f19decd8b370e3ca59d Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 13 Feb 2024 11:20:31 +0100 Subject: [PATCH 19/38] Add experimental implementation of background processes --- .../tools/ide/process/ProcessContextImpl.java | 32 +++++++++++++++---- .../ide/process/ProcessContextImplTest.java | 21 ++++++------ 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 036918c63..00cf2ce99 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Stream; import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.IdeContext; @@ -111,9 +112,8 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { // pragmatic solution to avoid copying lists/arrays this.arguments.add(0, executableName); - checkAndHandlePossibleBashScript(executableName); + boolean isBashScript = checkAndHandlePossibleBashScript(executableName); - this.processBuilder.command(this.arguments); if (this.context.debug().isEnabled()) { String message = createCommandMessage(" ..."); this.context.debug(message); @@ -124,10 +124,11 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { if (capture) { this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE); } else if (isBackgroundProcess) { - // TODO ASK if correct - this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD); + modifyArgumentsOnBackgroundProcess(isBashScript); } + this.processBuilder.command(this.arguments); + Process process = this.processBuilder.start(); List out = null; @@ -271,7 +272,7 @@ private void handleCapture(Process process, List out, List err) } } - private void checkAndHandlePossibleBashScript(String executableName) { + private boolean checkAndHandlePossibleBashScript(String executableName) { String fileExtension = FilenameUtil.getExtension(executableName); boolean isBashScript = "sh".equals(fileExtension) || hasSheBang(this.executable); @@ -285,7 +286,7 @@ private void checkAndHandlePossibleBashScript(String executableName) { } this.arguments.add(0, bash); } - + return isBashScript; } private void performLogOnError(ProcessResult result, int exitCode) { @@ -308,4 +309,23 @@ private void performLogOnError(ProcessResult result, int exitCode) { } } + private void modifyArgumentsOnBackgroundProcess(boolean isBashScript) { + + if (context.getSystemInfo().isWindows() && !isBashScript) { + + this.context.warning( + "Currently starting the process as background process in windows will result in lost standard output!"); + + this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE); + List windowsArgs = List.of("cmd.exe", "/c", "start", "cmd.exe", "/k", "start", "/b"); + List newArgs = Stream.concat(windowsArgs.stream(), this.arguments.stream()).toList(); + this.arguments.clear(); + this.arguments.addAll(newArgs); + + } else { + // just assume unix system for know + this.arguments.add("&"); + } + } + } diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index 5998a0793..12bc4f87d 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -1,9 +1,6 @@ package com.devonfw.tools.ide.process; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -83,7 +80,7 @@ public void streamCapturingAndBackgroundProcessShouldThrowIllegalState() { String actualMessage = exception.getMessage(); - assertEquals(actualMessage, expectedMessage); + assertThat(actualMessage).isEqualTo(expectedMessage); } @Test @@ -105,7 +102,7 @@ public void missingExecutableShouldThrowIllegalState() throws Exception { String actualMessage = exception.getMessage(); - assertEquals(actualMessage, expectedMessage); + assertThat(actualMessage).isEqualTo(expectedMessage); } @Test @@ -119,7 +116,7 @@ public void onSuccessfulProcessStartReturnSuccessResult() throws Exception { ProcessResult result = underTest.run(false, false); // assert - assertTrue(result.isSuccessful()); + assertThat(result.isSuccessful()).isTrue(); } @@ -148,8 +145,8 @@ public void enablingCaptureShouldRedirectAndCaptureStreamsCorrectly() throws Exc verify(mockProcessBuilder) .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); - assertEquals(outputText, result.getOut().get(0)); - assertEquals(errorText, result.getErr().get(0)); + assertThat(outputText).isEqualTo(result.getOut().get(0)); + assertThat(errorText).isEqualTo(result.getErr().get(0)); } } @@ -164,15 +161,15 @@ public void enablingBackgroundProcessShouldNotBeAwaitedAndShouldNotPassStreams() // assert verify(mockProcessBuilder) - .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); + .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); verify(mockProcessBuilder) - .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); + .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); verify(processMock, never()).waitFor(); - assertNull(result.getOut()); - assertNull(result.getErr()); + assertThat(result.getOut()).isNull(); + assertThat(result.getErr()).isNull(); } From 560e4bbad355f54f2d273c4fd398549ef479cb07 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 13 Feb 2024 16:34:29 +0100 Subject: [PATCH 20/38] Implement workaround idea for windows background process starting without losing output --- .../tools/ide/process/ProcessContextImpl.java | 18 +++++++++++++----- .../tools/ide/tool/eclipse/Eclipse.java | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index b683b2601..15fb97cd6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -11,7 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.stream.Stream; +import java.util.stream.Collectors; import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.IdeContext; @@ -101,7 +101,7 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { if (capture && isBackgroundProcess) { throw new IllegalStateException( - "It is not possible for the main process to capture the streams of the subprocess (background process) !"); + "It is not possible for the main process to capture the streams of the subprocess (in a background process) !"); } if (this.executable == null) { @@ -313,12 +313,20 @@ private void modifyArgumentsOnBackgroundProcess(boolean isBashScript) { if (context.getSystemInfo().isWindows() && !isBashScript) { this.context.warning( - "Currently starting the process as background process in windows will result in lost standard output!"); + "Currently starting the process as background process in windows will use 'more' command to redirect output to a new cmd window!"); this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE); - List windowsArgs = List.of("cmd.exe", "/c", "start", "cmd.exe", "/k", "start", "/b"); - List newArgs = Stream.concat(windowsArgs.stream(), this.arguments.stream()).toList(); + List windowsArgs = List.of("cmd.exe", "/c", "start", "cmd.exe", "/k"); + + String currentCommandToRunInCmd = this.arguments.stream().map(Object::toString).collect(Collectors.joining(" ")); + currentCommandToRunInCmd += " | more"; + this.arguments.clear(); + + List newArgs = new ArrayList<>(); + newArgs.addAll(windowsArgs); + newArgs.add(currentCommandToRunInCmd); + this.arguments.addAll(newArgs); } else { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java index 133c65d31..266a21ded 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java @@ -80,7 +80,7 @@ protected ProcessResult runEclipse(boolean log, String... args) { Path javaPath = getCommandlet(Java.class).getToolBinPath(); pc.addArg("-vm").addArg(javaPath); pc.addArgs(args); - return pc.run(log, false); + return pc.run(log, true); } @Override From 2b1722e36b50db7ac459c6af8aa5537fd08297c6 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 14 Feb 2024 08:20:06 +0100 Subject: [PATCH 21/38] Improve JavaDoc for ProcessContext and minor changes --- .../java/com/devonfw/tools/ide/process/ProcessContext.java | 7 +++++-- .../com/devonfw/tools/ide/process/ProcessContextImpl.java | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java index f2c6cc4a0..d1463c6b2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java @@ -158,8 +158,11 @@ default ProcessResult run(boolean capture) { * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out * and err). - * @param runInBackground {@code true}, the process of the command will be run as background process, {@code false} - * otherwise (it will be run as foreground process). + * @param runInBackground {@code true}, the process of the command will be run like an background process, + * {@code false} otherwise (it will be run as foreground process). Technically the parent process will simply + * not await its child process. In Unix systems the equivalent of appending an '&' is used. On Windows cmd is + * used to start the process as externally and to capture the child process output. It is recommended to set + * this option to {@code true} when a UI-Application is started, which may act dependent of its parent process. * @return the {@link ProcessResult}. */ ProcessResult run(boolean capture, boolean runInBackground); diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 15fb97cd6..eff9d0734 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -323,8 +323,7 @@ private void modifyArgumentsOnBackgroundProcess(boolean isBashScript) { this.arguments.clear(); - List newArgs = new ArrayList<>(); - newArgs.addAll(windowsArgs); + List newArgs = new ArrayList<>(windowsArgs); newArgs.add(currentCommandToRunInCmd); this.arguments.addAll(newArgs); From e80e21e1cface53c1608d58f4e0bbd5d3cf86f3b Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 14 Feb 2024 08:21:17 +0100 Subject: [PATCH 22/38] Fix typo --- .../main/java/com/devonfw/tools/ide/process/ProcessContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java index d1463c6b2..d0ab991cd 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java @@ -158,7 +158,7 @@ default ProcessResult run(boolean capture) { * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out * and err). - * @param runInBackground {@code true}, the process of the command will be run like an background process, + * @param runInBackground {@code true}, the process of the command will be run like a background process, * {@code false} otherwise (it will be run as foreground process). Technically the parent process will simply * not await its child process. In Unix systems the equivalent of appending an '&' is used. On Windows cmd is * used to start the process as externally and to capture the child process output. It is recommended to set From dec1e0bf254f9c3ce3942432d05723602a04c2f8 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 14 Feb 2024 08:49:01 +0100 Subject: [PATCH 23/38] Fix test --- .../com/devonfw/tools/ide/process/ProcessContextImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index 12bc4f87d..71a00a2a2 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -71,7 +71,7 @@ public void setUp() throws Exception { public void streamCapturingAndBackgroundProcessShouldThrowIllegalState() { // arrange - String expectedMessage = "It is not possible for the main process to capture the streams of the subprocess (background process) !"; + String expectedMessage = "It is not possible for the main process to capture the streams of the subprocess (in a background process) !"; // act & assert Exception exception = assertThrows(IllegalStateException.class, () -> { From e336ac8a5778832ae27828cc787e890ade8a1c11 Mon Sep 17 00:00:00 2001 From: MustaphaOuchen <98693422+MustaphaOuchen@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:25:55 +0100 Subject: [PATCH 24/38] Fix typo Co-authored-by: salimbouch <145128725+salimbouch@users.noreply.github.com> --- .../java/com/devonfw/tools/ide/process/ProcessContextImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index eff9d0734..a0997f182 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -329,7 +329,7 @@ private void modifyArgumentsOnBackgroundProcess(boolean isBashScript) { this.arguments.addAll(newArgs); } else { - // just assume unix system for know + // just assume unix system for now this.arguments.add("&"); } } From c55b7611a03c1544df54cab4943dca409d0a2ab1 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 20 Feb 2024 11:16:42 +0100 Subject: [PATCH 25/38] Fix test and force inherit streams on unix systems --- .../tools/ide/process/ProcessContextImpl.java | 1 + .../ide/process/ProcessContextImplTest.java | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index a0997f182..7d17086ec 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -329,6 +329,7 @@ private void modifyArgumentsOnBackgroundProcess(boolean isBashScript) { this.arguments.addAll(newArgs); } else { + this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); // just assume unix system for now this.arguments.add("&"); } diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index 71a00a2a2..4f0f433c0 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -160,11 +160,21 @@ public void enablingBackgroundProcessShouldNotBeAwaitedAndShouldNotPassStreams() ProcessResult result = underTest.run(false, true); // assert - verify(mockProcessBuilder) - .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); + if (context.getSystemInfo().isWindows()) { + verify(mockProcessBuilder) + .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); + + verify(mockProcessBuilder) + .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); - verify(mockProcessBuilder) - .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); + } else { + verify(mockProcessBuilder) + .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + + verify(mockProcessBuilder) + .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + + } verify(processMock, never()).waitFor(); From 6fdd2a00ed1df73be63bdde25fde6c5935dbe692 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 20 Feb 2024 11:34:23 +0100 Subject: [PATCH 26/38] minor refactor of variable place --- .../com/devonfw/tools/ide/process/ProcessContextImplTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index 4f0f433c0..373cba6bf 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -32,12 +32,11 @@ public class ProcessContextImplTest extends AbstractIdeContextTest { private IdeContext context; - private final String projectPath = "workspaces/foo-test/my-git-repo"; - @BeforeEach public void setUp() throws Exception { mockProcessBuilder = mock(ProcessBuilder.class); + String projectPath = "workspaces/foo-test/my-git-repo"; context = newContext("basic", projectPath, false); underTest = new ProcessContextImpl(context); From c8658408c31d07d890153e9be627055b1896dda6 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 20 Feb 2024 15:54:26 +0100 Subject: [PATCH 27/38] Add first refactoring after review WORK IN PROGRESS --- .../tools/ide/process/ProcessContextImpl.java | 46 +++++++++++-------- .../tools/ide/process/ProcessMode.java | 6 +++ .../ide/process/ProcessContextImplTest.java | 31 +++++++------ 3 files changed, 49 insertions(+), 34 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 7d17086ec..d1b53ee3d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -123,7 +123,7 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { if (capture) { this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE); } else if (isBackgroundProcess) { - modifyArgumentsOnBackgroundProcess(isBashScript); + modifyArgumentsOnBackgroundProcess(); } this.processBuilder.command(this.arguments); @@ -139,8 +139,8 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { } int exitCode; + // TODO: Ask if a background process shall get its own process result or if we should assume success? if (isBackgroundProcess) { - // TODO: Ask if a background process shall get its own process result or if we should assume success? exitCode = ProcessResult.SUCCESS; } else { exitCode = process.waitFor(); @@ -308,31 +308,39 @@ private void performLogOnError(ProcessResult result, int exitCode) { } } - private void modifyArgumentsOnBackgroundProcess(boolean isBashScript) { + private void modifyArgumentsOnBackgroundProcess() { - if (context.getSystemInfo().isWindows() && !isBashScript) { + String commandToRunInBackground = this.arguments.stream().map(Object::toString).collect(Collectors.joining(" ")); - this.context.warning( - "Currently starting the process as background process in windows will use 'more' command to redirect output to a new cmd window!"); + String bash = "bash"; - this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE); - List windowsArgs = List.of("cmd.exe", "/c", "start", "cmd.exe", "/k"); + // try to use bash in windows to start the process + if (context.getSystemInfo().isWindows()) { - String currentCommandToRunInCmd = this.arguments.stream().map(Object::toString).collect(Collectors.joining(" ")); - currentCommandToRunInCmd += " | more"; + String findBashOnWindowsResult = findBashOnWindows(); + if (findBashOnWindowsResult != null) { - this.arguments.clear(); + bash = findBashOnWindowsResult; + // windows path must be converted to unix format and executable + commandToRunInBackground = commandToRunInBackground.replace('\\', '/'); + commandToRunInBackground = "/" + commandToRunInBackground.substring(0, 1).toLowerCase() + + commandToRunInBackground.substring(2); - List newArgs = new ArrayList<>(windowsArgs); - newArgs.add(currentCommandToRunInCmd); + } else { + // TODO IMPLEMENT start cmd window? + context.warning("Cannot start background process! No bash installation found, output will be discarded."); + this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD); + return; + } + } - this.arguments.addAll(newArgs); + this.arguments.clear(); + this.arguments.add(0, bash); + this.arguments.add("-c"); + // todo adding disowning and silencing output with 1>/dev/null 2>/dev/null + commandToRunInBackground += " & disown"; + this.arguments.add(commandToRunInBackground); - } else { - this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); - // just assume unix system for now - this.arguments.add("&"); - } } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java new file mode 100644 index 000000000..68a963857 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java @@ -0,0 +1,6 @@ +package com.devonfw.tools.ide.process; + +//TODO USE ENUM +public enum ProcessMode { + BACKGROUND, BACKGROUND_SILENT, FOREGROUND, FOREGROUND_SILENT +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index 373cba6bf..87bbb6a8c 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -159,21 +159,22 @@ public void enablingBackgroundProcessShouldNotBeAwaitedAndShouldNotPassStreams() ProcessResult result = underTest.run(false, true); // assert - if (context.getSystemInfo().isWindows()) { - verify(mockProcessBuilder) - .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); - - verify(mockProcessBuilder) - .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); - - } else { - verify(mockProcessBuilder) - .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); - - verify(mockProcessBuilder) - .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); - - } + // TODO MAKE BETTER AFTER DESIRED FUNCTIONALITY IS FULFILLED + /* + * if (context.getSystemInfo().isWindows()) { verify(mockProcessBuilder) .redirectOutput((ProcessBuilder.Redirect) + * argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); + * + * verify(mockProcessBuilder) .redirectError((ProcessBuilder.Redirect) argThat(arg -> + * arg.equals(ProcessBuilder.Redirect.PIPE))); + * + * } else { verify(mockProcessBuilder) .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> + * arg.equals(ProcessBuilder.Redirect.INHERIT))); + * + * verify(mockProcessBuilder) .redirectError((ProcessBuilder.Redirect) argThat(arg -> + * arg.equals(ProcessBuilder.Redirect.INHERIT))); + * + * } + */ verify(processMock, never()).waitFor(); From 68c3b1bdecd142365e4d6bf2d23c72935a1506f2 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 21 Feb 2024 11:04:48 +0100 Subject: [PATCH 28/38] Refactoring after review, using enums, added JavaDoc with concrete explanation --- .../tools/ide/context/AbstractIdeContext.java | 5 +- .../tools/ide/process/ProcessContext.java | 28 +------- .../tools/ide/process/ProcessContextImpl.java | 61 +++++++++++------ .../tools/ide/process/ProcessMode.java | 38 ++++++++++- .../tools/ide/tool/ToolCommandlet.java | 40 ++++++----- .../tools/ide/tool/eclipse/Eclipse.java | 8 ++- .../tools/ide/tool/ide/IdeToolCommandlet.java | 3 +- .../com/devonfw/tools/ide/tool/jmc/Jmc.java | 11 +-- .../tools/ide/tool/terraform/Terraform.java | 3 +- .../ide/process/ProcessContextImplTest.java | 68 ++++++++----------- 10 files changed, 151 insertions(+), 114 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index 9cb1533a3..15dd89189 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -40,6 +40,7 @@ import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessContextImpl; import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; import com.devonfw.tools.ide.property.Property; import com.devonfw.tools.ide.repo.CustomToolRepository; @@ -612,7 +613,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { } ProcessContext pc = newProcess().directory(target).executable("git").withEnvVar("GIT_TERMINAL_PROMPT", "0"); if (Files.isDirectory(target.resolve(".git"))) { - ProcessResult result = pc.addArg("remote").run(true, false); + ProcessResult result = pc.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); List remotes = result.getOut(); if (remotes.isEmpty()) { String message = "This is a local git repo with no remote - if you did this for testing, you may continue...\n" @@ -620,7 +621,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { askToContinue(message); } else { pc.errorHandling(ProcessErrorHandling.WARNING); - result = pc.addArg("pull").run(false, false); + result = pc.addArg("pull").run(ProcessMode.DEFAULT); if (!result.isSuccessful()) { String message = "Failed to update git repository at " + target; if (this.offlineMode) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java index d0ab991cd..5630b4931 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContext.java @@ -132,7 +132,7 @@ default ProcessContext addArgs(List... args) { */ default int run() { - return run(false, false).getExitCode(); + return run(ProcessMode.DEFAULT).getExitCode(); } /** @@ -140,31 +140,9 @@ default int run() { * arguments}. Will reset the {@link #addArgs(String...) arguments} but not the {@link #executable(Path) command} for * sub-sequent calls. * - * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and - * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out - * and err). + * @param processMode {@link ProcessMode} * @return the {@link ProcessResult}. */ - default ProcessResult run(boolean capture) { - - return run(capture, false); - } - - /** - * Runs the previously configured {@link #executable(Path) command} with the configured {@link #addArgs(String...) - * arguments}. Will reset the {@link #addArgs(String...) arguments} but not the {@link #executable(Path) command} for - * sub-sequent calls. - * - * @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and - * {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out - * and err). - * @param runInBackground {@code true}, the process of the command will be run like a background process, - * {@code false} otherwise (it will be run as foreground process). Technically the parent process will simply - * not await its child process. In Unix systems the equivalent of appending an '&' is used. On Windows cmd is - * used to start the process as externally and to capture the child process output. It is recommended to set - * this option to {@code true} when a UI-Application is started, which may act dependent of its parent process. - * @return the {@link ProcessResult}. - */ - ProcessResult run(boolean capture, boolean runInBackground); + ProcessResult run(ProcessMode processMode); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index d1b53ee3d..fc75b6c9c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -44,8 +44,6 @@ public ProcessContextImpl(IdeContext context) { super(); this.context = context; this.processBuilder = new ProcessBuilder(); - // TODO needs to be configurable for GUI - this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); this.errorHandling = ProcessErrorHandling.THROW; Map environment = this.processBuilder.environment(); for (VariableLine var : this.context.getVariables().collectExportedVariables()) { @@ -97,13 +95,15 @@ public ProcessContext withEnvVar(String key, String value) { } @Override - public ProcessResult run(boolean capture, boolean isBackgroundProcess) { + public ProcessResult run(ProcessMode processMode) { - if (capture && isBackgroundProcess) { - throw new IllegalStateException( - "It is not possible for the main process to capture the streams of the subprocess (in a background process) !"); + // TODO ProcessMode needs to be configurable for GUI + if (processMode == ProcessMode.DEFAULT) { + this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); } + boolean isBackgroundProcess = processMode == ProcessMode.BACKGROUND || processMode == ProcessMode.BACKGROUND_SILENT; + if (this.executable == null) { throw new IllegalStateException("Missing executable to run process!"); } @@ -111,7 +111,7 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { // pragmatic solution to avoid copying lists/arrays this.arguments.add(0, executableName); - boolean isBashScript = checkAndHandlePossibleBashScript(executableName); + checkAndHandlePossibleBashScript(executableName); if (this.context.debug().isEnabled()) { String message = createCommandMessage(" ..."); @@ -120,10 +120,10 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { try { - if (capture) { + if (processMode == ProcessMode.DEFAULT_CAPTURE) { this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE); } else if (isBackgroundProcess) { - modifyArgumentsOnBackgroundProcess(); + modifyArgumentsOnBackgroundProcess(processMode); } this.processBuilder.command(this.arguments); @@ -132,7 +132,7 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) { List out = null; List err = null; - if (capture) { + if (processMode == ProcessMode.DEFAULT_CAPTURE) { out = new ArrayList<>(); err = new ArrayList<>(); handleCapture(process, out, err); @@ -271,7 +271,7 @@ private void handleCapture(Process process, List out, List err) } } - private boolean checkAndHandlePossibleBashScript(String executableName) { + private void checkAndHandlePossibleBashScript(String executableName) { String fileExtension = FilenameUtil.getExtension(executableName); boolean isBashScript = "sh".equals(fileExtension) || hasSheBang(this.executable); @@ -285,7 +285,6 @@ private boolean checkAndHandlePossibleBashScript(String executableName) { } this.arguments.add(0, bash); } - return isBashScript; } private void performLogOnError(ProcessResult result, int exitCode) { @@ -308,9 +307,16 @@ private void performLogOnError(ProcessResult result, int exitCode) { } } - private void modifyArgumentsOnBackgroundProcess() { + private void modifyArgumentsOnBackgroundProcess(ProcessMode processMode) { + + String commandToRunInBackground = ""; - String commandToRunInBackground = this.arguments.stream().map(Object::toString).collect(Collectors.joining(" ")); + if (processMode == ProcessMode.BACKGROUND) { + this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); + } else if (processMode == ProcessMode.BACKGROUND_SILENT) { + this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD); + } else + throw new IllegalStateException("Cannot handle non background process mode!"); String bash = "bash"; @@ -321,26 +327,39 @@ private void modifyArgumentsOnBackgroundProcess() { if (findBashOnWindowsResult != null) { bash = findBashOnWindowsResult; + // windows path must be converted to unix format and executable - commandToRunInBackground = commandToRunInBackground.replace('\\', '/'); - commandToRunInBackground = "/" + commandToRunInBackground.substring(0, 1).toLowerCase() - + commandToRunInBackground.substring(2); + String executablePath = this.arguments.get(0); + executablePath = convertWindowsPathToUnixPath(executablePath); + + commandToRunInBackground = this.arguments.subList(1, this.arguments.size()).stream().map(Object::toString) + .collect(Collectors.joining(" ")); + + commandToRunInBackground = executablePath + " " + commandToRunInBackground; } else { - // TODO IMPLEMENT start cmd window? - context.warning("Cannot start background process! No bash installation found, output will be discarded."); + context.warning( + "Cannot start background process in windows! No bash installation found, output will be discarded."); this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD); return; } + } else { + commandToRunInBackground = this.arguments.stream().map(Object::toString).collect(Collectors.joining(" ")); } this.arguments.clear(); - this.arguments.add(0, bash); + this.arguments.add(bash); this.arguments.add("-c"); - // todo adding disowning and silencing output with 1>/dev/null 2>/dev/null commandToRunInBackground += " & disown"; this.arguments.add(commandToRunInBackground); } + private String convertWindowsPathToUnixPath(String windowsPathString) { + + String unixPath = windowsPathString.replace('\\', '/'); + unixPath = "/" + unixPath.substring(0, 1).toLowerCase() + unixPath.substring(2); + return unixPath; + } + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java index 68a963857..c36c0eca8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java @@ -1,6 +1,40 @@ package com.devonfw.tools.ide.process; -//TODO USE ENUM +/** + * The ProcessMode defines how to start the command process and how output streams are handled using + * {@link ProcessBuilder}. Modes that can be used: {@link #BACKGROUND} {@link #BACKGROUND_SILENT} {@link #DEFAULT} + * {@link #DEFAULT_CAPTURE} + */ public enum ProcessMode { - BACKGROUND, BACKGROUND_SILENT, FOREGROUND, FOREGROUND_SILENT + /** + * The process of the command will be run like a background process. Technically the parent process will simply not + * await its child process and a shell is used to start the process. The parent process will get the output of the + * child process using {@link ProcessBuilder.Redirect#INHERIT}. In Unix systems the equivalent of appending an '& + * disown' is used to detach the subprocess from its parent process. In Unix terms, the shell will not send a SIGHUP + * signal but the process remains connected to the terminal so that output is still received. (Only '&' is not used + * because it just removes awaiting but not sending of SIGHUP. Using nohup would simply result in redirecting output + * to a nohup.out file.) + */ + BACKGROUND, + /** + * Like {@link #BACKGROUND}. Instead of redirecting the output to the parent process, the output is redirected to the + * 'null file' using {@link ProcessBuilder.Redirect#DISCARD}. + */ + BACKGROUND_SILENT, + /** + * The process will be started according {@link ProcessBuilder.Redirect#INHERIT} without any detaching of parent + * process and child process. This setting makes the child process dependant from the parent process! (If you close + * the parent process the child process will also be terminated.) + */ + DEFAULT, + /** + * The process will be started according {@link ProcessBuilder.Redirect#PIPE} and the standard output and standard + * error streams will be captured from the parent process. In other words, they will be printed in the console of the + * parent process. This setting makes the child process dependant from the parent process! (If you close the parent + * process the child process will also be terminated.) + */ + DEFAULT_CAPTURE + + // TODO ADD EXTERNAL_WINDOW_MODE IN FUTURE + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index be36fbc78..4193c221b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -19,6 +19,7 @@ import com.devonfw.tools.ide.os.MacOsHelper; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.property.StringListProperty; import com.devonfw.tools.ide.util.FilenameUtil; import com.devonfw.tools.ide.version.VersionIdentifier; @@ -81,20 +82,19 @@ public final Set getTags() { @Override public void run() { - runTool(false, null, this.arguments.asArray()); + runTool(ProcessMode.DEFAULT, null, this.arguments.asArray()); } /** * Ensures the tool is installed and then runs this tool with the given arguments. * - * @param runInBackground {@code true}, the process of the command will be run as background process, {@code false} - * otherwise (it will be run as foreground process). + * @param processMode see {@link ProcessMode} * @param toolVersion the explicit version (pattern) to run. Typically {@code null} to ensure the configured version * is installed and use that one. Otherwise, the specified version will be installed in the software repository * without touching and IDE installation and used to run. * @param args the command-line arguments to run the tool. */ - public void runTool(boolean runInBackground, VersionIdentifier toolVersion, String... args) { + public void runTool(ProcessMode processMode, VersionIdentifier toolVersion, String... args) { Path binaryPath; Path toolPath = Path.of(getBinaryName()); @@ -104,19 +104,20 @@ public void runTool(boolean runInBackground, VersionIdentifier toolVersion, Stri } else { throw new UnsupportedOperationException("Not yet implemented!"); } - ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath).addArgs(args); + ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.WARNING).executable(binaryPath) + .addArgs(args); - pc.run(false, runInBackground); + pc.run(processMode); } /** * @param toolVersion the explicit {@link VersionIdentifier} of the tool to run. * @param args the command-line arguments to run the tool. - * @see ToolCommandlet#runTool(boolean, VersionIdentifier, String...) + * @see ToolCommandlet#runTool(ProcessMode, VersionIdentifier, String...) */ public void runTool(VersionIdentifier toolVersion, String... args) { - runTool(false, toolVersion, args); + runTool(ProcessMode.DEFAULT, toolVersion, args); } /** @@ -210,11 +211,12 @@ private Path getProperInstallationSubDirOf(Path path) { try (Stream stream = Files.list(path)) { Path[] subFiles = stream.toArray(Path[]::new); if (subFiles.length == 0) { - throw new CliException("The downloaded package for the tool " + this.tool + " seems to be empty as you can check in the extracted folder " + path); + throw new CliException("The downloaded package for the tool " + this.tool + + " seems to be empty as you can check in the extracted folder " + path); } else if (subFiles.length == 1) { String filename = subFiles[0].getFileName().toString(); - if (!filename.equals(IdeContext.FOLDER_BIN) && !filename.equals(IdeContext.FOLDER_CONTENTS) && !filename.endsWith(".app") - && Files.isDirectory(subFiles[0])) { + if (!filename.equals(IdeContext.FOLDER_BIN) && !filename.equals(IdeContext.FOLDER_CONTENTS) + && !filename.endsWith(".app") && Files.isDirectory(subFiles[0])) { return getProperInstallationSubDirOf(subFiles[0]); } } @@ -384,8 +386,10 @@ public String getInstalledEdition(Path toolPath) { } return edition; } catch (IOException e) { - throw new IllegalStateException("Couldn't determine the edition of " + getName() + " from the directory structure of its software path " + toolPath - + ", assuming the name of the parent directory of the real path of the software path to be the edition " + "of the tool.", e); + throw new IllegalStateException("Couldn't determine the edition of " + getName() + + " from the directory structure of its software path " + toolPath + + ", assuming the name of the parent directory of the real path of the software path to be the edition " + + "of the tool.", e); } } @@ -450,8 +454,9 @@ public void setVersion(VersionIdentifier version, boolean hint) { this.context.info("{}={} has been set in {}", name, version, settingsVariables.getSource()); EnvironmentVariables declaringVariables = variables.findVariable(name); if ((declaringVariables != null) && (declaringVariables != settingsVariables)) { - this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name, - declaringVariables.getSource()); + this.context.warning( + "The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", + name, declaringVariables.getSource()); } if (hint) { this.context.info("To install that version call the following command:"); @@ -494,8 +499,9 @@ public void setEdition(String edition, boolean hint) { this.context.info("{}={} has been set in {}", name, edition, settingsVariables.getSource()); EnvironmentVariables declaringVariables = variables.findVariable(name); if ((declaringVariables != null) && (declaringVariables != settingsVariables)) { - this.context.warning("The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", name, - declaringVariables.getSource()); + this.context.warning( + "The variable {} is overridden in {}. Please remove the overridden declaration in order to make the change affect.", + name, declaringVariables.getSource()); } if (hint) { this.context.info("To install that edition call the following command:"); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java index 266a21ded..46c1760c3 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java @@ -15,6 +15,7 @@ import com.devonfw.tools.ide.log.IdeLogLevel; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet; import com.devonfw.tools.ide.tool.ide.PluginDescriptor; @@ -80,7 +81,12 @@ protected ProcessResult runEclipse(boolean log, String... args) { Path javaPath = getCommandlet(Java.class).getToolBinPath(); pc.addArg("-vm").addArg(javaPath); pc.addArgs(args); - return pc.run(log, true); + + if (log) { + return pc.run(ProcessMode.BACKGROUND_SILENT); + } else { + return pc.run(ProcessMode.BACKGROUND); + } } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java index 4bb4800c8..ac7a2a1f6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java @@ -15,6 +15,7 @@ import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; import com.devonfw.tools.ide.tool.eclipse.Eclipse; @@ -207,7 +208,7 @@ public void run() { */ protected void runIde(String... args) { - runTool(false,null, args); + runTool(ProcessMode.DEFAULT, null, args); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java index 0c316cfbf..97d2f339c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/jmc/Jmc.java @@ -10,9 +10,9 @@ import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; -import com.devonfw.tools.ide.tool.java.Java; /** * {@link ToolCommandlet} for JDK Mission @@ -33,7 +33,8 @@ public Jmc(IdeContext context) { @Override public boolean doInstall(boolean silent) { - // TODO https://github.com/devonfw/IDEasy/issues/209 currently outcommented as this breaks the tests, real fix needed asap + // TODO https://github.com/devonfw/IDEasy/issues/209 currently outcommented as this breaks the tests, real fix + // needed asap // getCommandlet(Java.class).install(); return super.doInstall(silent); } @@ -41,7 +42,7 @@ public boolean doInstall(boolean silent) { @Override public void run() { - runTool(true, null, this.arguments.asArray()); + runTool(ProcessMode.BACKGROUND, null, this.arguments.asArray()); } @Override @@ -57,7 +58,9 @@ public void postInstall() { moveFilesAndDirs(oldBinaryPath, toolPath); fileAccess.delete(oldBinaryPath); } else { - this.context.info("JMC binary folder not found at {} - ignoring as this legacy problem may be resolved in newer versions.", oldBinaryPath); + this.context.info( + "JMC binary folder not found at {} - ignoring as this legacy problem may be resolved in newer versions.", + oldBinaryPath); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java index e99d1264b..33e49e34f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/terraform/Terraform.java @@ -4,6 +4,7 @@ import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.tool.LocalToolCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; @@ -26,6 +27,6 @@ public Terraform(IdeContext context) { protected void postInstall() { super.postInstall(); - runTool(false, null, "-install-autocomplete"); + runTool(ProcessMode.DEFAULT, null, "-install-autocomplete"); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index 87bbb6a8c..d00004b8f 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -66,22 +66,6 @@ public void setUp() throws Exception { } - @Test - public void streamCapturingAndBackgroundProcessShouldThrowIllegalState() { - - // arrange - String expectedMessage = "It is not possible for the main process to capture the streams of the subprocess (in a background process) !"; - - // act & assert - Exception exception = assertThrows(IllegalStateException.class, () -> { - underTest.run(true, true); - }); - - String actualMessage = exception.getMessage(); - - assertThat(actualMessage).isEqualTo(expectedMessage); - } - @Test public void missingExecutableShouldThrowIllegalState() throws Exception { @@ -96,7 +80,7 @@ public void missingExecutableShouldThrowIllegalState() throws Exception { // act & assert Exception exception = assertThrows(IllegalStateException.class, () -> { - underTest.run(false, false); + underTest.run(ProcessMode.DEFAULT); }); String actualMessage = exception.getMessage(); @@ -112,9 +96,14 @@ public void onSuccessfulProcessStartReturnSuccessResult() throws Exception { when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS); // act - ProcessResult result = underTest.run(false, false); + ProcessResult result = underTest.run(ProcessMode.DEFAULT); // assert + verify(mockProcessBuilder) + .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + + verify(mockProcessBuilder) + .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); assertThat(result.isSuccessful()).isTrue(); } @@ -135,7 +124,7 @@ public void enablingCaptureShouldRedirectAndCaptureStreamsCorrectly() throws Exc when(processMock.getErrorStream()).thenReturn(errorStream); // act - ProcessResult result = underTest.run(true, false); + ProcessResult result = underTest.run(ProcessMode.DEFAULT_CAPTURE); // assert verify(mockProcessBuilder) @@ -149,32 +138,31 @@ public void enablingCaptureShouldRedirectAndCaptureStreamsCorrectly() throws Exc } } - @Test - public void enablingBackgroundProcessShouldNotBeAwaitedAndShouldNotPassStreams() throws Exception { + @ParameterizedTest + @EnumSource(value = ProcessMode.class, names = { "BACKGROUND", "BACKGROUND_SILENT" }) + public void enablingBackgroundProcessShouldNotBeAwaitedAndShouldNotPassStreams(ProcessMode processMode) + throws Exception { // arrange when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS); // act - ProcessResult result = underTest.run(false, true); + ProcessResult result = underTest.run(processMode); // assert - // TODO MAKE BETTER AFTER DESIRED FUNCTIONALITY IS FULFILLED - /* - * if (context.getSystemInfo().isWindows()) { verify(mockProcessBuilder) .redirectOutput((ProcessBuilder.Redirect) - * argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); - * - * verify(mockProcessBuilder) .redirectError((ProcessBuilder.Redirect) argThat(arg -> - * arg.equals(ProcessBuilder.Redirect.PIPE))); - * - * } else { verify(mockProcessBuilder) .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> - * arg.equals(ProcessBuilder.Redirect.INHERIT))); - * - * verify(mockProcessBuilder) .redirectError((ProcessBuilder.Redirect) argThat(arg -> - * arg.equals(ProcessBuilder.Redirect.INHERIT))); - * - * } - */ + if (processMode == ProcessMode.BACKGROUND) { + verify(mockProcessBuilder) + .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + + verify(mockProcessBuilder) + .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + } else if (processMode == ProcessMode.BACKGROUND_SILENT) { + verify(mockProcessBuilder) + .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); + + verify(mockProcessBuilder) + .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); + } verify(processMock, never()).waitFor(); @@ -193,7 +181,7 @@ public void unsuccessfulProcessShouldThrowIllegalState() throws Exception { // act & assert assertThrows(IllegalStateException.class, () -> { - underTest.run(false, false); + underTest.run(ProcessMode.DEFAULT); }); } @@ -207,7 +195,7 @@ public void ProcessWarningAndErrorShouldBeLogged(ProcessErrorHandling processErr underTest.errorHandling(processErrorHandling); String expectedMessage = "failed with exit code 4!"; // act - underTest.run(false, false); + underTest.run(ProcessMode.DEFAULT); // assert IdeLogLevel level = convertToIdeLogLevel(processErrorHandling); From 8313cf7ab70f91f83421caec8a761418c2dae2db Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 21 Feb 2024 11:11:06 +0100 Subject: [PATCH 29/38] Fix mismatch of ProcessMode in eclipse commandlet --- .../main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java index 46c1760c3..5fe2f5bab 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java @@ -83,9 +83,9 @@ protected ProcessResult runEclipse(boolean log, String... args) { pc.addArgs(args); if (log) { - return pc.run(ProcessMode.BACKGROUND_SILENT); - } else { return pc.run(ProcessMode.BACKGROUND); + } else { + return pc.run(ProcessMode.BACKGROUND_SILENT); } } From f288cf0e7849870fade9d67a96fb5864234a32b1 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Wed, 21 Feb 2024 12:12:22 +0100 Subject: [PATCH 30/38] Add issue id to todo comment --- .../main/java/com/devonfw/tools/ide/process/ProcessMode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java index c36c0eca8..5cf9b9ae6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java @@ -35,6 +35,6 @@ public enum ProcessMode { */ DEFAULT_CAPTURE - // TODO ADD EXTERNAL_WINDOW_MODE IN FUTURE + // TODO ADD EXTERNAL_WINDOW_MODE IN FUTURE Issue: https://github.com/devonfw/IDEasy/issues/218 } From fdcdece94f48ea027d34b05bc79e70be0b3fb586 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 23 Feb 2024 15:58:59 +0100 Subject: [PATCH 31/38] Remove todo --- .../java/com/devonfw/tools/ide/process/ProcessContextImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index fc75b6c9c..186dfde36 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -139,7 +139,6 @@ public ProcessResult run(ProcessMode processMode) { } int exitCode; - // TODO: Ask if a background process shall get its own process result or if we should assume success? if (isBackgroundProcess) { exitCode = ProcessResult.SUCCESS; } else { From c70c70a625f2f73a004900629792fff6fce6c671 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 23 Feb 2024 16:01:38 +0100 Subject: [PATCH 32/38] Add curly brackets --- .../java/com/devonfw/tools/ide/process/ProcessContextImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 186dfde36..ef7276782 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -314,8 +314,9 @@ private void modifyArgumentsOnBackgroundProcess(ProcessMode processMode) { this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); } else if (processMode == ProcessMode.BACKGROUND_SILENT) { this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD); - } else + } else { throw new IllegalStateException("Cannot handle non background process mode!"); + } String bash = "bash"; From 2bcfa03885545c700f1b77bf15105b3466fc947c Mon Sep 17 00:00:00 2001 From: Ouchen Date: Fri, 23 Feb 2024 16:28:29 +0100 Subject: [PATCH 33/38] Add isBackgound() method in enum und use new logic --- .../tools/ide/process/ProcessContextImpl.java | 6 ++---- .../com/devonfw/tools/ide/process/ProcessMode.java | 13 ++++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index ef7276782..1cc49a38d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -102,8 +102,6 @@ public ProcessResult run(ProcessMode processMode) { this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); } - boolean isBackgroundProcess = processMode == ProcessMode.BACKGROUND || processMode == ProcessMode.BACKGROUND_SILENT; - if (this.executable == null) { throw new IllegalStateException("Missing executable to run process!"); } @@ -122,7 +120,7 @@ public ProcessResult run(ProcessMode processMode) { if (processMode == ProcessMode.DEFAULT_CAPTURE) { this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE); - } else if (isBackgroundProcess) { + } else if (processMode.isBackground()) { modifyArgumentsOnBackgroundProcess(processMode); } @@ -139,7 +137,7 @@ public ProcessResult run(ProcessMode processMode) { } int exitCode; - if (isBackgroundProcess) { + if (processMode.isBackground()) { exitCode = ProcessResult.SUCCESS; } else { exitCode = process.waitFor(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java index 5cf9b9ae6..478b9fba3 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessMode.java @@ -33,7 +33,18 @@ public enum ProcessMode { * parent process. This setting makes the child process dependant from the parent process! (If you close the parent * process the child process will also be terminated.) */ - DEFAULT_CAPTURE + DEFAULT_CAPTURE; + + /** + * Method to check if the ProcessMode is a background process. + * + * @return {@code true} if the {@link ProcessMode} is {@link ProcessMode#BACKGROUND} or + * {@link ProcessMode#BACKGROUND_SILENT}, {@code false} if not. + */ + public boolean isBackground() { + + return this == BACKGROUND || this == BACKGROUND_SILENT; + } // TODO ADD EXTERNAL_WINDOW_MODE IN FUTURE Issue: https://github.com/devonfw/IDEasy/issues/218 From 3e35af8738b3d9fc408b2c6dfb65303f2b01755e Mon Sep 17 00:00:00 2001 From: Ouchen Date: Mon, 26 Feb 2024 16:24:52 +0100 Subject: [PATCH 34/38] Add javadoc comment --- .../test/java/com/devonfw/tools/ide/common/SystemPathTest.java | 2 ++ .../com/devonfw/tools/ide/process/ProcessContextImplTest.java | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java diff --git a/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java b/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java new file mode 100644 index 000000000..d596e8cfb --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java @@ -0,0 +1,2 @@ +package com.devonfw.tools.ide.common;public class SystemPathTest { +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index d00004b8f..86223fab2 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -22,6 +22,9 @@ import com.devonfw.tools.ide.context.IdeTestContext; import com.devonfw.tools.ide.log.IdeLogLevel; +/** + * Unit tests of {@link ProcessContextImpl}. + */ public class ProcessContextImplTest extends AbstractIdeContextTest { private ProcessContextImpl underTest; From 38951e71a0af93c0d827713627c2bdda1e6766a9 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Mon, 26 Feb 2024 16:25:31 +0100 Subject: [PATCH 35/38] Refactor building of unix arguments by considuering possible passed paths --- .../devonfw/tools/ide/common/SystemPath.java | 43 ++++++++++++---- .../tools/ide/process/ProcessContextImpl.java | 41 +++++++++------- .../tools/ide/common/SystemPathTest.java | 49 ++++++++++++++++++- 3 files changed, 104 insertions(+), 29 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java index a3b5415af..b0691b5d2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java +++ b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java @@ -9,6 +9,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import java.util.stream.Stream; import com.devonfw.tools.ide.context.IdeContext; @@ -67,7 +68,8 @@ public SystemPath(String envPath, Path softwarePath, char pathSeparator, IdeCont } else { Path duplicate = this.tool2pathMap.putIfAbsent(tool, path); if (duplicate != null) { - context.warning("Duplicated tool path for tool: {} at path: {} with duplicated path: {}.", tool, path, duplicate); + context.warning("Duplicated tool path for tool: {} at path: {} with duplicated path: {}.", tool, path, + duplicate); } } } @@ -203,22 +205,45 @@ public String toString(boolean bash) { return sb.toString(); } - private void appendPath(Path path, StringBuilder sb, char separator, boolean bash) { + private static void appendPath(Path path, StringBuilder sb, char separator, boolean bash) { if (sb.length() > 0) { sb.append(separator); } String pathString = path.toString(); if (bash && (pathString.length() > 3) && (pathString.charAt(1) == ':')) { - char slash = pathString.charAt(2); - if ((slash == '\\') || (slash == '/')) { - char drive = Character.toLowerCase(pathString.charAt(0)); - if ((drive >= 'a') && (drive <= 'z')) { - pathString = "/" + drive + pathString.substring(2).replace('\\', '/'); - } - } + pathString = convertWindowsPathToUnixPath(pathString); } sb.append(pathString); } + /** + * Method to convert a valid Windows path string representation to its corresponding one in Unix format. + * + * @param pathString The Windows path string to convert. + * @return The converted Unix path string. + */ + public static String convertWindowsPathToUnixPath(String pathString) { + + char slash = pathString.charAt(2); + if ((slash == '\\') || (slash == '/')) { + char drive = Character.toLowerCase(pathString.charAt(0)); + if ((drive >= 'a') && (drive <= 'z')) { + pathString = "/" + drive + pathString.substring(2).replace('\\', '/'); + } + } + return pathString; + } + + /** + * Method to validate if a given path string is a Windows path or not + * + * @param pathString The string to check if it is a Windows path string. + * @return {@code true} if it is a valid windows path string, else {@code false}. + */ + public static boolean isValidWindowsPath(String pathString) { + + String windowsFilePathRegEx = "([a-zA-Z]:)?(\\\\[a-zA-Z0-9\\s_.-]+)+\\\\?"; + return Pattern.matches(windowsFilePathRegEx, pathString); + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java index 1cc49a38d..7a9117379 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.common.SystemPath; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.environment.VariableLine; import com.devonfw.tools.ide.log.IdeSubLogger; @@ -306,8 +307,6 @@ private void performLogOnError(ProcessResult result, int exitCode) { private void modifyArgumentsOnBackgroundProcess(ProcessMode processMode) { - String commandToRunInBackground = ""; - if (processMode == ProcessMode.BACKGROUND) { this.processBuilder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); } else if (processMode == ProcessMode.BACKGROUND_SILENT) { @@ -326,25 +325,16 @@ private void modifyArgumentsOnBackgroundProcess(ProcessMode processMode) { bash = findBashOnWindowsResult; - // windows path must be converted to unix format and executable - String executablePath = this.arguments.get(0); - executablePath = convertWindowsPathToUnixPath(executablePath); - - commandToRunInBackground = this.arguments.subList(1, this.arguments.size()).stream().map(Object::toString) - .collect(Collectors.joining(" ")); - - commandToRunInBackground = executablePath + " " + commandToRunInBackground; - } else { context.warning( "Cannot start background process in windows! No bash installation found, output will be discarded."); this.processBuilder.redirectOutput(Redirect.DISCARD).redirectError(Redirect.DISCARD); return; } - } else { - commandToRunInBackground = this.arguments.stream().map(Object::toString).collect(Collectors.joining(" ")); } + String commandToRunInBackground = buildCommandToRunInBackground(); + this.arguments.clear(); this.arguments.add(bash); this.arguments.add("-c"); @@ -353,11 +343,24 @@ private void modifyArgumentsOnBackgroundProcess(ProcessMode processMode) { } - private String convertWindowsPathToUnixPath(String windowsPathString) { + private String buildCommandToRunInBackground() { - String unixPath = windowsPathString.replace('\\', '/'); - unixPath = "/" + unixPath.substring(0, 1).toLowerCase() + unixPath.substring(2); - return unixPath; - } + if (context.getSystemInfo().isWindows()) { -} + StringBuilder stringBuilder = new StringBuilder(); + + for (String argument : this.arguments) { + + if (SystemPath.isValidWindowsPath(argument)) { + argument = SystemPath.convertWindowsPathToUnixPath(argument); + } + + stringBuilder.append(argument); + stringBuilder.append(" "); + } + return stringBuilder.toString().trim(); + } else { + return this.arguments.stream().map(Object::toString).collect(Collectors.joining(" ")); + } + } +} \ No newline at end of file diff --git a/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java b/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java index d596e8cfb..0b8656bee 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/common/SystemPathTest.java @@ -1,2 +1,49 @@ -package com.devonfw.tools.ide.common;public class SystemPathTest { +package com.devonfw.tools.ide.common; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Unit tests of {@link SystemPath}. + */ +public class SystemPathTest { + + @ParameterizedTest + @ValueSource(strings = { "C:\\Users\\User\\Documents\\My Pictures\\photo.jpg", + "C:\\Windows\\System32\\drivers\\etc.sys", "D:\\Projects\\ProjectA\\source\\main.py" }) + public void SystemPathShouldRecognizeWindowsPaths(String pathStringToTest) { + + // act + boolean testResult = SystemPath.isValidWindowsPath(pathStringToTest); + assertThat(testResult).isTrue(); + + } + + @ParameterizedTest + @ValueSource(strings = { "-kill", "none", "--help", "/usr/local/bin/firefox.exe" }) + public void SystemPathShouldRecognizeNonWindowsPaths(String pathStringToTest) { + + // act + boolean testResult = SystemPath.isValidWindowsPath(pathStringToTest); + assertThat(testResult).isFalse(); + + } + + @Test + public void SystemPathShouldConvertWindowsPathToUnixPath() { + + // arrange + String windowsPathString = "C:\\Users\\User\\test.exe"; + String expectedUnixPathString = "/c/Users/User/test.exe"; + + // act + String resultPath = SystemPath.convertWindowsPathToUnixPath(windowsPathString); + + // assert + assertThat(resultPath).isEqualTo(expectedUnixPathString); + } + } From a1ca068b614e6f2fbae4f97e87bffab762f24981 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 27 Feb 2024 10:05:23 +0100 Subject: [PATCH 36/38] Refactor eclipse commandlet to use processMode --- .../tools/ide/tool/eclipse/Eclipse.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java index 5fe2f5bab..917f55d01 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java @@ -40,7 +40,7 @@ public Eclipse(IdeContext context) { protected void runIde(String... args) { install(true); - runEclipse(false, + runEclipse(ProcessMode.BACKGROUND, CliArgument.prepend(args, "gui", "-showlocation", this.context.getIdeHome().getFileName().toString())); } @@ -54,22 +54,21 @@ public boolean install(boolean silent) { /** * Runs eclipse application. * - * @param log - {@code true} to run in log mode without opening a window and capture the output, {@code false} - * otherwise (run in GUI mode). + * @param processMode - the {@link ProcessMode}. * @param args the individual arguments to pass to eclipse. * @return the {@link ProcessResult}. */ - protected ProcessResult runEclipse(boolean log, String... args) { + protected ProcessResult runEclipse(ProcessMode processMode, String... args) { Path toolPath = Path.of(getBinaryName()); ProcessContext pc = this.context.newProcess(); - if (log) { + if (processMode == ProcessMode.DEFAULT_CAPTURE) { pc.errorHandling(ProcessErrorHandling.ERROR); } pc.executable(toolPath); Path configurationPath = getPluginsInstallationPath().resolve("configuration"); this.context.getFileAccess().mkdirs(configurationPath); - if (log) { + if (processMode == ProcessMode.DEFAULT_CAPTURE) { pc.addArg("-consoleLog").addArg("-nosplash"); } else { pc.addArg("-clean"); @@ -82,18 +81,15 @@ protected ProcessResult runEclipse(boolean log, String... args) { pc.addArg("-vm").addArg(javaPath); pc.addArgs(args); - if (log) { - return pc.run(ProcessMode.BACKGROUND); - } else { - return pc.run(ProcessMode.BACKGROUND_SILENT); - } + return pc.run(processMode); + } @Override public void installPlugin(PluginDescriptor plugin) { - ProcessResult result = runEclipse(true, "org.eclipse.equinox.p2.director", "-repository", plugin.getUrl(), - "-installIU", plugin.getId()); + ProcessResult result = runEclipse(ProcessMode.DEFAULT_CAPTURE, "org.eclipse.equinox.p2.director", "-repository", + plugin.getUrl(), "-installIU", plugin.getId()); if (result.isSuccessful()) { for (String line : result.getOut()) { if (line.contains("Overall install request is satisfiable")) { From 8c3797c46bc356b89462b647c8fbbd36a3705447 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 27 Feb 2024 11:07:13 +0100 Subject: [PATCH 37/38] Make git context to use processMode --- .../tools/ide/context/GitContextImpl.java | 19 ++++++++++--------- .../context/GitContextProcessContextMock.java | 3 ++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java index cc2f4568c..c21898906 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java @@ -17,6 +17,7 @@ import com.devonfw.tools.ide.log.IdeSubLogger; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; /** @@ -96,7 +97,7 @@ public void pullOrClone(String gitRepoUrl, Path targetRepository) { initializeProcessContext(targetRepository); if (Files.isDirectory(targetRepository.resolve(".git"))) { // checks for remotes - ProcessResult result = this.processContext.addArg("remote").run(true, false); + ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); List remotes = result.getOut(); if (remotes.isEmpty()) { String message = targetRepository @@ -183,7 +184,7 @@ public void clone(GitUrl gitRepoUrl, Path targetRepository) { this.processContext.addArg("-q"); } this.processContext.addArgs("--recursive", parsedUrl, "--config", "core.autocrlf=false", "."); - result = this.processContext.run(true, false); + result = this.processContext.run(ProcessMode.DEFAULT_CAPTURE); if (!result.isSuccessful()) { this.context.warning("Git failed to clone {} into {}.", parsedUrl, targetRepository); } @@ -198,7 +199,7 @@ public void pull(Path targetRepository) { initializeProcessContext(targetRepository); ProcessResult result; // pull from remote - result = this.processContext.addArg("--no-pager").addArg("pull").run(true, false); + result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); if (!result.isSuccessful()) { Map remoteAndBranchName = retrieveRemoteAndBranchName(); @@ -211,7 +212,7 @@ public void pull(Path targetRepository) { private Map retrieveRemoteAndBranchName() { Map remoteAndBranchName = new HashMap<>(); - ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(true, false); + ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); List remotes = remoteResult.getOut(); if (!remotes.isEmpty()) { for (String remote : remotes) { @@ -242,14 +243,14 @@ public void reset(Path targetRepository, String remoteName, String branchName) { initializeProcessContext(targetRepository); ProcessResult result; // check for changed files - result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(true, false); + result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); if (!result.isSuccessful()) { // reset to origin/master context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, remoteName, branchName); - result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName).run(true, - false); + result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) + .run(ProcessMode.DEFAULT_CAPTURE); if (!result.isSuccessful()) { context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); @@ -265,12 +266,12 @@ public void cleanup(Path targetRepository) { ProcessResult result; // check for untracked files result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") - .run(true, false); + .run(ProcessMode.DEFAULT_CAPTURE); if (!result.getOut().isEmpty()) { // delete untracked files context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); - result = this.processContext.addArg("clean").addArg("-df").run(true, false); + result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); if (!result.isSuccessful()) { context.warning("Git failed to clean the repository {}.", targetRepository); diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextProcessContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextProcessContextMock.java index f79a2f6bb..eb257edff 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextProcessContextMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextProcessContextMock.java @@ -10,6 +10,7 @@ import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; import com.devonfw.tools.ide.process.ProcessResultImpl; @@ -75,7 +76,7 @@ public ProcessContext withEnvVar(String key, String value) { } @Override - public ProcessResult run(boolean capture, boolean isBackgroundProcess) { + public ProcessResult run(ProcessMode processMode) { Path gitFolderPath = this.directory.resolve(".git"); // deletes a newly added folder From 63b3c70b0da264ab38c80fa8fc6656e8d9b1bdf3 Mon Sep 17 00:00:00 2001 From: Ouchen Date: Tue, 27 Feb 2024 11:31:36 +0100 Subject: [PATCH 38/38] Minor renaming in test --- .../ide/process/ProcessContextImplTest.java | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java index 86223fab2..025161d07 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/process/ProcessContextImplTest.java @@ -27,7 +27,7 @@ */ public class ProcessContextImplTest extends AbstractIdeContextTest { - private ProcessContextImpl underTest; + private ProcessContextImpl processConttextUnderTest; private Process processMock; @@ -41,20 +41,21 @@ public void setUp() throws Exception { mockProcessBuilder = mock(ProcessBuilder.class); String projectPath = "workspaces/foo-test/my-git-repo"; context = newContext("basic", projectPath, false); - underTest = new ProcessContextImpl(context); + processConttextUnderTest = new ProcessContextImpl(context); Field field = ReflectionUtils.findFields(ProcessContextImpl.class, f -> f.getName().equals("processBuilder"), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0); field.setAccessible(true); - field.set(underTest, mockProcessBuilder); + field.set(processConttextUnderTest, mockProcessBuilder); field.setAccessible(false); // underTest needs executable Field underTestExecutable = ReflectionUtils.findFields(ProcessContextImpl.class, f -> f.getName().equals("executable"), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0); underTestExecutable.setAccessible(true); - underTestExecutable.set(underTest, PATH_PROJECTS.resolve("_ide/software/nonExistingBinaryForTesting")); + underTestExecutable.set(processConttextUnderTest, + PATH_PROJECTS.resolve("_ide/software/nonExistingBinaryForTesting")); underTestExecutable.setAccessible(false); processMock = mock(Process.class); @@ -78,12 +79,12 @@ public void missingExecutableShouldThrowIllegalState() throws Exception { Field underTestExecutable = ReflectionUtils.findFields(ProcessContextImpl.class, f -> f.getName().equals("executable"), ReflectionUtils.HierarchyTraversalMode.TOP_DOWN).get(0); underTestExecutable.setAccessible(true); - underTestExecutable.set(underTest, null); + underTestExecutable.set(processConttextUnderTest, null); underTestExecutable.setAccessible(false); // act & assert Exception exception = assertThrows(IllegalStateException.class, () -> { - underTest.run(ProcessMode.DEFAULT); + processConttextUnderTest.run(ProcessMode.DEFAULT); }); String actualMessage = exception.getMessage(); @@ -99,14 +100,14 @@ public void onSuccessfulProcessStartReturnSuccessResult() throws Exception { when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS); // act - ProcessResult result = underTest.run(ProcessMode.DEFAULT); + ProcessResult result = processConttextUnderTest.run(ProcessMode.DEFAULT); // assert - verify(mockProcessBuilder) - .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + verify(mockProcessBuilder).redirectOutput( + (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); - verify(mockProcessBuilder) - .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + verify(mockProcessBuilder).redirectError( + (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); assertThat(result.isSuccessful()).isTrue(); } @@ -127,14 +128,14 @@ public void enablingCaptureShouldRedirectAndCaptureStreamsCorrectly() throws Exc when(processMock.getErrorStream()).thenReturn(errorStream); // act - ProcessResult result = underTest.run(ProcessMode.DEFAULT_CAPTURE); + ProcessResult result = processConttextUnderTest.run(ProcessMode.DEFAULT_CAPTURE); // assert - verify(mockProcessBuilder) - .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); + verify(mockProcessBuilder).redirectOutput( + (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); - verify(mockProcessBuilder) - .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); + verify(mockProcessBuilder).redirectError( + (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.PIPE))); assertThat(outputText).isEqualTo(result.getOut().get(0)); assertThat(errorText).isEqualTo(result.getErr().get(0)); @@ -150,21 +151,21 @@ public void enablingBackgroundProcessShouldNotBeAwaitedAndShouldNotPassStreams(P when(processMock.waitFor()).thenReturn(ProcessResult.SUCCESS); // act - ProcessResult result = underTest.run(processMode); + ProcessResult result = processConttextUnderTest.run(processMode); // assert if (processMode == ProcessMode.BACKGROUND) { - verify(mockProcessBuilder) - .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + verify(mockProcessBuilder).redirectOutput( + (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); - verify(mockProcessBuilder) - .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); + verify(mockProcessBuilder).redirectError( + (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.INHERIT))); } else if (processMode == ProcessMode.BACKGROUND_SILENT) { - verify(mockProcessBuilder) - .redirectOutput((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); + verify(mockProcessBuilder).redirectOutput( + (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); - verify(mockProcessBuilder) - .redirectError((ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); + verify(mockProcessBuilder).redirectError( + (ProcessBuilder.Redirect) argThat(arg -> arg.equals(ProcessBuilder.Redirect.DISCARD))); } verify(processMock, never()).waitFor(); @@ -180,11 +181,11 @@ public void unsuccessfulProcessShouldThrowIllegalState() throws Exception { // arrange when(processMock.waitFor()).thenReturn(ProcessResult.TOOL_NOT_INSTALLED); - underTest.errorHandling(ProcessErrorHandling.THROW); + processConttextUnderTest.errorHandling(ProcessErrorHandling.THROW); // act & assert assertThrows(IllegalStateException.class, () -> { - underTest.run(ProcessMode.DEFAULT); + processConttextUnderTest.run(ProcessMode.DEFAULT); }); } @@ -195,10 +196,10 @@ public void ProcessWarningAndErrorShouldBeLogged(ProcessErrorHandling processErr // arrange when(processMock.waitFor()).thenReturn(ProcessResult.TOOL_NOT_INSTALLED); - underTest.errorHandling(processErrorHandling); + processConttextUnderTest.errorHandling(processErrorHandling); String expectedMessage = "failed with exit code 4!"; // act - underTest.run(ProcessMode.DEFAULT); + processConttextUnderTest.run(ProcessMode.DEFAULT); // assert IdeLogLevel level = convertToIdeLogLevel(processErrorHandling);