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

Open
wants to merge 25 commits into
base: main
Choose a base branch
from

Conversation

alfeilex
Copy link
Member

@alfeilex alfeilex commented Nov 19, 2024

Fixes: #734

This PR improves the process result capturing of stdout and stderr output by preserving the order of the output.

@coveralls
Copy link
Collaborator

coveralls commented Dec 11, 2024

Pull Request Test Coverage Report for Build 12377261111

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 29 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.03%) to 67.507%

Files with Coverage Reduction New Missed Lines %
com/devonfw/tools/ide/process/ProcessResultImpl.java 5 80.0%
com/devonfw/tools/ide/process/ProcessContextImpl.java 24 79.66%
Totals Coverage Status
Change from base Build 12375027220: 0.03%
Covered Lines: 6662
Relevant Lines: 9528

💛 - Coveralls

@alfeilex alfeilex marked this pull request as ready for review December 11, 2024 14:19
Copy link
Member

@hohwille hohwille left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alfeilex thanks for your PR. Already on its way and looking good. Thanks 👍
I left some review comments for improvement. Please have a look.


String line;
while ((line = br.readLine()) != null) {
synchronized (outputMessages) {
Copy link
Member

@hohwille hohwille Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may create a DeadLock. Java default synchronization is very inefficient and we will have two threads continuously blocking each other.
Consider using a concurrent collection.
I would suggest to use ConcurrentLinkedQueue for collecting the OutputMessages and when completed, convert to a regular List.

Copy link
Member Author

@alfeilex alfeilex Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using ConcurrentLinkedQueue , and at first, it worked fine. However, after running my test case several times, I noticed that the order sometimes changed, causing err1 to appear first in the list of output messages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OMG. Sorry, if I caused a mess here.
Lets have a look at this in January after vacation time.
Maybe we can resolve the problem and make it work deterministic and reliable...
In the worst case, we have to revert the Concurrent-Collection approach and need to go back to your original solution using synchronized (or better using ReentrantLock).

Comment on lines 44 to 45
this.out = this.outputMessages.stream().filter(outputMessage -> !outputMessage.error()).map(OutputMessage::message).collect(Collectors.toList());
this.err = this.outputMessages.stream().filter(OutputMessage::error).map(OutputMessage::message).collect(Collectors.toList());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small simplification:

Suggested change
this.out = this.outputMessages.stream().filter(outputMessage -> !outputMessage.error()).map(OutputMessage::message).collect(Collectors.toList());
this.err = this.outputMessages.stream().filter(OutputMessage::error).map(OutputMessage::message).collect(Collectors.toList());
this.out = this.outputMessages.stream().filter(outputMessage -> !outputMessage.error()).map(OutputMessage::message).toList();
this.err = this.outputMessages.stream().filter(OutputMessage::error).map(OutputMessage::message).toList();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/
public List<String> getErrors() {

return errors;
return this.outputMessages.stream().filter(OutputMessage::error).map(OutputMessage::message).collect(Collectors.toList());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work. Current code is calling getErrors() or getOuts() in order to add mocked messages to such List.
Streams will create immutable collections that cannot be modified after creation.
In case we leave it like this, also use toList() simplification in all places.

Could you discuss with @jan-vcapgemini who IMHO created this class about the design?
Wouldn't it be smarter to create a regular ProcessResult here and make it accessible so instead of tests calling
processContextGitMock.getErrors().add("fake error message") we could call processContextGitMock.getProcessResult().getOutputMessages().add(new OutputMessage("fake error message", true))
to make it work.
There is a lot of pointless redundancy in this class related to ProcessResultImpl that can be avoided this way.

BTW: I just noticed that you already found this problem and had to update an existing test-case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want a convenience method for adding faked out or err messages, you could still provide them here but delegating to the ProcessResult. Also this way we could even allow a setter for the mocked ProcessResult that also gives us more flexibility (resetting the result for a next faked git call, etc.).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -181,7 +182,8 @@ public ProcessResult run(ProcessMode processMode) {
}
}
this.arguments.clear();
return new ProcessResultImpl("git", command.toString(), this.exitCode, this.outs, this.errors);
setProcessResult(getExitCode(), this.processResult.getOutputMessages());
return this.processResult;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setExitCode() and getExitCode() are currently not used in the implementation. The exitcode is set differently in conditions with e.g. this.exitcode = 0

What is here the best solution? Is it the right way to use getters and setters inside a class, also for mock classes or can we just assign the attributes with values directly?

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After several runs, you will see that the order is not deterministic, probably because the choice of the first stdout or stderr thread is not fixed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If synchronized is to inefficent and we stick to ConcurrentLinkedQueue then we have to accept the compromise that the order is not 100% guaranteed. Also, the test-case doesn't work and testing the order like in the implemented test-case isn't possible and has to be removed before merging

OutputMessage outputMessage = new OutputMessage(errorStream, line);
outputMessages.add(outputMessage);
}

return br.lines().toList();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we try to read the lines again from the buffered reader after it has already been consumed to the end?
IMHO you should change the signature from CompletableFuture<List<String>> to CompletableFuture<Void> and simply return null here.

@hohwille hohwille added this to the release:2025.01.001 milestone Dec 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Team Review
Development

Successfully merging this pull request may close these issues.

Improve ProcessResult: get out and err in order
4 participants