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 19 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.

*/
public record LogEvent(boolean error, String message) {

}

This comment was marked as outdated.

synchronized (logs) {
LogEvent logEvent = new LogEvent(errorStream, line);
logs.add(logEvent);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure, if I am using LogEvent correctly. In this implementation, a single LogEvent instance represents one line from either stout or sterr. I don't see another solution to capture the entire log while keeping the order of the log streams.

Copy link
Member Author

Choose a reason for hiding this comment

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

Now its OutputMessage instead of LogEvent. We can still discuss the name

@coveralls
Copy link
Collaborator

coveralls commented Dec 11, 2024

Pull Request Test Coverage Report for Build 12293812522

Details

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

Files with Coverage Reduction New Missed Lines %
com/devonfw/tools/ide/process/ProcessResultImpl.java 5 78.95%
com/devonfw/tools/ide/process/ProcessContextImpl.java 17 80.26%
Totals Coverage Status
Change from base Build 12293756306: 0.03%
Covered Lines: 6632
Relevant Lines: 9532

💛 - Coveralls

this.err = Objects.requireNonNullElse(err, Collections.emptyList());
this.outputMessages = Objects.requireNonNullElse(outputMessages, Collections.emptyList());
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 Author

Choose a reason for hiding this comment

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

Should we fill the out and err lists in the constructor or elsewhere?

Copy link
Member

Choose a reason for hiding this comment

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

Good point:
We could compute out and err lazily in the getters so if these getters are never called and we have a larger output, this may be pointless waste otherwise...

if (!this.err.isEmpty()) {
doLog(errorLevel, this.err, context);
if (!this.outputMessages.isEmpty()) {
doLog(outLevel, getOutputMessages().stream().map(OutputMessage::message).collect(Collectors.toList()), context);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Are two separate log levels still necessary with a single lists of captures?

Copy link
Member

Choose a reason for hiding this comment

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

There are special cases at least Eclipse for where we want to make err visible to the user but have out on debug that is hidden by default but can be enabled.
I would therefore recommend to:

  1. first check if outLevel == errorLevel and then do as you suggest/implemented
  2. otherwise simply use the old approach but then also first check if the log-level is actually enabled. So if it is disabled, we do nothing (not even calling getOut() or getErr() what would compute the extra List).

@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.

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();

this.err = Objects.requireNonNullElse(err, Collections.emptyList());
this.outputMessages = Objects.requireNonNullElse(outputMessages, Collections.emptyList());
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.

Good point:
We could compute out and err lazily in the getters so if these getters are never called and we have a larger output, this may be pointless waste otherwise...

if (!this.err.isEmpty()) {
doLog(errorLevel, this.err, context);
if (!this.outputMessages.isEmpty()) {
doLog(outLevel, getOutputMessages().stream().map(OutputMessage::message).collect(Collectors.toList()), context);
}
Copy link
Member

Choose a reason for hiding this comment

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

There are special cases at least Eclipse for where we want to make err visible to the user but have out on debug that is hidden by default but can be enabled.
I would therefore recommend to:

  1. first check if outLevel == errorLevel and then do as you suggest/implemented
  2. otherwise simply use the old approach but then also first check if the log-level is actually enabled. So if it is disabled, we do nothing (not even calling getOut() or getErr() what would compute the extra List).

*/
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.).

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