Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#734: Improve Process Result Model #773

Merged
merged 36 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e25575f
Add single log list
alfeilex Nov 19, 2024
8e66739
Merge branch '#734-improve-process-result' of https://github.com/alfe…
alfeilex Nov 19, 2024
5c3cccb
Merge branch 'devonfw:main' into #734-improve-process-result
alfeilex Nov 19, 2024
4d5a556
Add LogEvent Record
alfeilex Nov 19, 2024
daf4b45
Add LogEvent List to capture Logs
alfeilex Nov 19, 2024
05879d1
Merge branch '#734-improve-process-result' of https://github.com/alfe…
alfeilex Nov 19, 2024
292aa5b
Merge branch 'devonfw:main' into #734-improve-process-result
alfeilex Nov 20, 2024
41a42ce
Merge remote-tracking branch 'origin/main' into #734-improve-process-…
alfeilex Dec 10, 2024
3da75c7
add general List of output messages and rename logevent to outputmess…
alfeilex Dec 10, 2024
540a9b0
Merge branch 'devonfw:main' into #734-improve-process-result
alfeilex Dec 10, 2024
c2b7021
refactor
alfeilex Dec 10, 2024
4530860
refactor processResultImpl in tooldummy
alfeilex Dec 10, 2024
05da168
merge
alfeilex Dec 10, 2024
88c8ef1
change List of Strings for getOutputMessages
alfeilex Dec 10, 2024
4700583
update Test with output message list
alfeilex Dec 11, 2024
f0c5b9e
add test
alfeilex Dec 11, 2024
4daec09
remove forgotten outs in ProcessContextGitMock
alfeilex Dec 11, 2024
4bbb603
add CHANGELOG.adoc
alfeilex Dec 11, 2024
af6add1
Merge branch 'main' into #734-improve-process-result
jan-vcapgemini Dec 12, 2024
8491143
Merge branch 'devonfw:main' into #734-improve-process-result
alfeilex Dec 16, 2024
d7e176e
refactor to lazy getter and remove err and out attributes
alfeilex Dec 17, 2024
ffd6ced
change from synchronized approach to ConcurrentLinkedQueue
alfeilex Dec 17, 2024
4c5f98a
refactor doLog function to output level based stdout and stderr
alfeilex Dec 17, 2024
dce5d5a
add ProcessResult to ProcessContextGitMock and remove redundant
alfeilex Dec 17, 2024
39de3a4
Merge branch 'main' into #734-improve-process-result
alfeilex Dec 17, 2024
04bd6d5
Merge branch 'main' into #734-improve-process-result
alfeilex Jan 7, 2025
e1c88b5
change to Void and return null
alfeilex Jan 7, 2025
34fa698
add sleep 1 between echos
alfeilex Jan 7, 2025
6100259
Merge branch 'main' into #734-improve-process-result
alfeilex Jan 7, 2025
e9e6dd4
Merge branch 'devonfw:main' into #734-improve-process-result
alfeilex Jan 14, 2025
a32b83d
add x permission to log-order.sh
alfeilex Jan 14, 2025
8b91016
Merge branch 'main' into #734-improve-process-result
hohwille Jan 14, 2025
9df8953
refactor exitCode as local variable
alfeilex Jan 14, 2025
bca3a12
removed processResult from Mock
alfeilex Jan 14, 2025
0ecec18
Merge branch 'main' into #734-improve-process-result
alfeilex Jan 14, 2025
5b2ba88
resolve comment
alfeilex Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Then run the `setup` and all should work fine.

Release with new features and bugfixes:

* https://github.com/devonfw/IDEasy/issues/734[#734]: Improve ProcessResult: get out and err in order
* https://github.com/devonfw/IDEasy/issues/764[#764]: Fix IDEasy in CMD
* https://github.com/devonfw/IDEasy/issues/774[#774]: HTTP proxy support not working properly
* https://github.com/devonfw/IDEasy/issues/792[#792]: Honor new variable IDE_OPTIONS in ide command wrapper
Expand Down
11 changes: 11 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/process/OutputMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.devonfw.tools.ide.process;

/**
* Represent an output message that can be either from stdout or stderr.
*
* @param error A boolean flag that indicates whether the output message is from stdout or stderr
* @param message A string containing the outout message
*/
public record OutputMessage(boolean error, String message) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;

import com.devonfw.tools.ide.cli.CliProcessException;
Expand Down Expand Up @@ -168,17 +169,16 @@ public ProcessResult run(ProcessMode processMode) {

this.processBuilder.command(args);

List<String> out = null;
List<String> err = null;
ConcurrentLinkedQueue<OutputMessage> output = new ConcurrentLinkedQueue<>();

Process process = this.processBuilder.start();

try {
if (processMode == ProcessMode.DEFAULT_CAPTURE) {
CompletableFuture<List<String>> outFut = readInputStream(process.getInputStream());
CompletableFuture<List<String>> errFut = readInputStream(process.getErrorStream());
out = outFut.get();
err = errFut.get();
CompletableFuture<Void> outFut = readInputStream(process.getInputStream(), false, output);
CompletableFuture<Void> errFut = readInputStream(process.getErrorStream(), true, output);
outFut.get();
errFut.get();
}

int exitCode;
Expand All @@ -189,7 +189,8 @@ public ProcessResult run(ProcessMode processMode) {
exitCode = process.waitFor();
}

ProcessResult result = new ProcessResultImpl(this.executable.getFileName().toString(), command, exitCode, out, err);
List<OutputMessage> finalOutput = new ArrayList<>(output);
ProcessResult result = new ProcessResultImpl(this.executable.getFileName().toString(), command, exitCode, finalOutput);

performLogging(result, exitCode, interpreter);

Expand Down Expand Up @@ -218,14 +219,22 @@ public ProcessResult run(ProcessMode processMode) {
* "https://stackoverflow.com/questions/14165517/processbuilder-forwarding-stdout-and-stderr-of-started-processes-without-blocki/57483714#57483714">StackOverflow</a>
*
* @param is {@link InputStream}.
* @param errorStream to identify if the output came from stdout or stderr
* @return {@link CompletableFuture}.
*/
private static CompletableFuture<List<String>> readInputStream(InputStream is) {
private static CompletableFuture<Void> readInputStream(InputStream is, boolean errorStream, ConcurrentLinkedQueue<OutputMessage> outputMessages) {

return CompletableFuture.supplyAsync(() -> {

try (InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr)) {
return br.lines().toList();

String line;
while ((line = br.readLine()) != null) {
OutputMessage outputMessage = new OutputMessage(errorStream, line);
outputMessages.add(outputMessage);
}

return null;
} catch (Throwable e) {
throw new RuntimeException("There was a problem while executing the program", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ default boolean isSuccessful() {
*/
List<String> getErr();

/**
* @return the {@link List} with {@link OutputMessage} that captured on standard out and standard error lines. Will be {@code null} if not captured but
* redirected.
*/
List<OutputMessage> getOutputMessages();

/**
* Logs output and error messages on the provided log level.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,23 @@ public class ProcessResultImpl implements ProcessResult {

private final int exitCode;

private final List<String> out;

private final List<String> err;
private final List<OutputMessage> outputMessages;

/**
* The constructor.
*
* @param executable the {@link #getExecutable() executable}.
* @param command the {@link #getCommand() command}.
* @param exitCode the {@link #getExitCode() exit code}.
* @param out the {@link #getOut() out}.
* @param err the {@link #getErr() err}.
* @param outputMessages {@link #getOutputMessages() output Messages}.
*/
public ProcessResultImpl(String executable, String command, int exitCode, List<String> out, List<String> err) {
public ProcessResultImpl(String executable, String command, int exitCode, List<OutputMessage> outputMessages) {

super();
this.executable = executable;
this.command = command;
this.exitCode = exitCode;
this.out = Objects.requireNonNullElse(out, Collections.emptyList());
this.err = Objects.requireNonNullElse(err, Collections.emptyList());
this.outputMessages = Objects.requireNonNullElse(outputMessages, Collections.emptyList());
}

@Override
Expand All @@ -63,13 +59,20 @@ public int getExitCode() {
@Override
public List<String> getOut() {

return this.out;
return this.outputMessages.stream().filter(outputMessage -> !outputMessage.error()).map(OutputMessage::message).toList();
}

@Override
public List<String> getErr() {

return this.err;
return this.outputMessages.stream().filter(OutputMessage::error).map(OutputMessage::message).toList();
}

@Override
public List<OutputMessage> getOutputMessages() {

return this.outputMessages;

}

@Override
Expand All @@ -80,22 +83,23 @@ public void log(IdeLogLevel level, IdeContext context) {
@Override
public void log(IdeLogLevel outLevel, IdeContext context, IdeLogLevel errorLevel) {

if (!this.out.isEmpty()) {
doLog(outLevel, this.out, context);
}
if (!this.err.isEmpty()) {
doLog(errorLevel, this.err, context);
if (!this.outputMessages.isEmpty()) {
for (OutputMessage outputMessage : this.outputMessages) {
if (outputMessage.error()) {
doLog(errorLevel, outputMessage.message(), context);
} else {
doLog(outLevel, outputMessage.message(), context);
}
}
}
}

private void doLog(IdeLogLevel level, List<String> lines, IdeContext context) {
for (String line : lines) {
// remove !MESSAGE from log message
if (line.startsWith("!MESSAGE ")) {
line = line.substring(9);
}
context.level(level).log(line);
private void doLog(IdeLogLevel level, String message, IdeContext context) {
// remove !MESSAGE from log message
if (message.startsWith("!MESSAGE ")) {
message = message.substring(9);
}
context.level(level).log(message);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.ArrayList;
import java.util.List;

import com.devonfw.tools.ide.process.OutputMessage;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessErrorHandling;
import com.devonfw.tools.ide.process.ProcessMode;
Expand All @@ -21,59 +22,25 @@ public class ProcessContextGitMock implements ProcessContext {

private final List<String> arguments;

private final List<String> errors;

private final List<String> outs;

private final LocalDateTime now;

private int exitCode;

private final Path directory;

private List<OutputMessage> outputMessages;

/**
* @param directory the {@link Path} to the git repository.
*/
public ProcessContextGitMock(Path directory) {

this.arguments = new ArrayList<>();
this.errors = new ArrayList<>();
this.outs = new ArrayList<>();
this.exitCode = ProcessResult.SUCCESS;
this.directory = directory;
this.now = LocalDateTime.now();
this.outputMessages = new ArrayList<OutputMessage>();
}

/**
* @return the mocked {@link ProcessResult#getExitCode() exit code}.
*/
public int getExitCode() {

return this.exitCode;
}

/**
* @param exitCode the {@link #getExitCode() exit code}.
*/
public void setExitCode(int exitCode) {

this.exitCode = exitCode;
}

/**
* @return the {@link List} of mocked error messages.
*/
public List<String> getErrors() {

return errors;
}

/**
* @return the {@link List} of mocked out messages.
*/
public List<String> getOuts() {

return outs;
public void addOutputMessage(OutputMessage message) {
this.outputMessages.add(message);
}

@Override
Expand Down Expand Up @@ -121,6 +88,7 @@ public ProcessContext withPathEntry(Path path) {
@Override
public ProcessResult run(ProcessMode processMode) {

int exitCode = ProcessResult.SUCCESS;
StringBuilder command = new StringBuilder("git");
for (String arg : this.arguments) {
command.append(' ');
Expand All @@ -131,16 +99,15 @@ public ProcessResult run(ProcessMode processMode) {
if (this.arguments.contains("clean")) {
try {
Files.deleteIfExists(this.directory.resolve("new-folder"));
this.exitCode = 0;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// part of git cleanup checks if a new directory 'new-folder' exists
if (this.arguments.contains("ls-files")) {
if (Files.exists(this.directory.resolve("new-folder"))) {
this.outs.add("new-folder");
this.exitCode = 0;
OutputMessage outputMessage = new OutputMessage(false, "new-folder");
this.outputMessages.add(outputMessage);
}
}
if (this.arguments.contains("clone")) {
Expand All @@ -149,14 +116,13 @@ public ProcessResult run(ProcessMode processMode) {
Path newFile = Files.createFile(gitFolderPath.resolve("url"));
// 3rd argument = repository Url
Files.writeString(newFile, this.arguments.get(2));
this.exitCode = 0;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// always consider that files were changed
if (this.arguments.contains("diff-index")) {
this.exitCode = 1;
exitCode = 1;
}
// changes file back to initial state (uses reference file in .git folder)
if (this.arguments.contains("reset")) {
Expand All @@ -165,7 +131,7 @@ public ProcessResult run(ProcessMode processMode) {
Files.copy(gitFolderPath.resolve("objects").resolve("referenceFile"), this.directory.resolve("trackedFile"),
StandardCopyOption.REPLACE_EXISTING);
}
this.exitCode = 0;
exitCode = ProcessResult.SUCCESS;
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand All @@ -175,13 +141,13 @@ public ProcessResult run(ProcessMode processMode) {
Files.createDirectories(gitFolderPath);
Path newFile = Files.createFile(gitFolderPath.resolve("update"));
Files.writeString(newFile, this.now.toString());
this.exitCode = 0;
exitCode = ProcessResult.SUCCESS;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
this.arguments.clear();
return new ProcessResultImpl("git", command.toString(), this.exitCode, this.outs, this.errors);
return new ProcessResultImpl("git", command.toString(), exitCode, this.outputMessages);
}

}
13 changes: 9 additions & 4 deletions cli/src/test/java/com/devonfw/tools/ide/git/GitContextTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.devonfw.tools.ide.context.ProcessContextGitMock;
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.io.FileAccessImpl;
import com.devonfw.tools.ide.process.OutputMessage;

/**
* Test of {@link GitContext}.
Expand Down Expand Up @@ -71,7 +72,8 @@ public void testRunGitClone(@TempDir Path tempDir) {
// arrange
String gitRepoUrl = "https://github.com/test";
IdeTestContext context = newGitContext(tempDir);
this.processContext.getOuts().add("test-remote");
OutputMessage outputMessage = new OutputMessage(false, "test-remote");
this.processContext.addOutputMessage(outputMessage);
// act
context.getGitContext().pullOrClone(GitUrl.of(gitRepoUrl), tempDir);
// assert
Expand All @@ -89,7 +91,8 @@ public void testRunGitPullWithoutForce(@TempDir Path tempDir) {
// arrange
String gitRepoUrl = "https://github.com/test";
IdeTestContext context = newGitContext(tempDir);
this.processContext.getOuts().add("test-remote");
OutputMessage outputMessage = new OutputMessage(false, "test-remote");
this.processContext.addOutputMessage(outputMessage);
FileAccess fileAccess = new FileAccessImpl(context);
Path gitFolderPath = tempDir.resolve(".git");
fileAccess.mkdirs(gitFolderPath);
Expand Down Expand Up @@ -129,7 +132,8 @@ public void testRunGitPullWithForceStartsReset(@TempDir Path tempDir) {
throw new RuntimeException(e);
}
IdeTestContext context = newGitContext(tempDir);
this.processContext.getOuts().add("test-remote");
OutputMessage outputMessage = new OutputMessage(false, "test-remote");
this.processContext.addOutputMessage(outputMessage);
// act
context.getGitContext().pullOrCloneAndResetIfNeeded(new GitUrl(gitRepoUrl, "master"), tempDir, "origin");
// assert
Expand All @@ -147,7 +151,8 @@ public void testRunGitPullWithForceStartsCleanup(@TempDir Path tempDir) {
// arrange
String gitRepoUrl = "https://github.com/test";
IdeTestContext context = newGitContext(tempDir);
this.processContext.getOuts().add("test-remote");
OutputMessage outputMessage = new OutputMessage(false, "test-remote");
this.processContext.addOutputMessage(outputMessage);
GitContext gitContext = context.getGitContext();
FileAccess fileAccess = context.getFileAccess();
Path gitFolderPath = tempDir.resolve(".git");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,18 @@ public void enablingCaptureShouldRedirectAndCaptureStreamsWithErrorsCorrectly()
assertThat(context).log(IdeLogLevel.ERROR).hasMessageContaining("another error message to stderr");
}

@Test
public void defaultCaptureShouldCaptureStreamsWithCorrectOrder() {
// arrange
IdeTestContext context = newContext(PROJECT_BASIC, null, false);

// act
context.newProcess().executable(TEST_RESOURCES.resolve("process-context").resolve("log-order.sh")).run();

// assert
assertThat(context).log(IdeLogLevel.INFO).hasEntries("out1", "err1", "out2", "err2");
}

private IdeLogLevel convertToIdeLogLevel(ProcessErrorHandling processErrorHandling) {

return switch (processErrorHandling) {
Expand Down
Loading
Loading