Skip to content

Conversation

andy-goryachev-oracle
Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle commented Sep 11, 2025

This PR removes unrelated stderr output in the headless test logs by redirecting it to an in-memory buffer. Exceptions found in the buffer can be checked against the expected list.

In the case when any mismatch is detected, whether the type or the number of exceptions of particular type, the accumulated buffer gets dumped to stderr and the test fails.

How To

To redirect stderr and later check the exceptions, surround your code with

OutputRedirect.suppressStderr() and either OutputRedirect.checkStderr() or OutputRedirect.checkAndRestoreStderr() (ideally, in the finally block).

To simply undo redirection, without checking, call OutputRedirect.restoreStderr().

To add the check to all the tests in the file, one can call the above mentioned methods inside @BeforeEach and @AfterEach.

Changes

  • added OutputRedirect facility

Miscellaneous

ErrorLoggingUtiltity name will be fixed in a followup https://bugs.openjdk.org/browse/JDK-8367995


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8336332: Rework tests to avoid unrelated stderr output (Enhancement - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1897/head:pull/1897
$ git checkout pull/1897

Update a local copy of the PR:
$ git checkout pull/1897
$ git pull https://git.openjdk.org/jfx.git pull/1897/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1897

View PR using the GUI difftool:
$ git pr show -t 1897

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1897.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Sep 11, 2025

👋 Welcome back angorya! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Sep 11, 2025

@andy-goryachev-oracle This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8336332: Rework tests to avoid unrelated stderr output

Reviewed-by: kcr, arapte

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 5 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@andy-goryachev-oracle andy-goryachev-oracle marked this pull request as ready for review September 12, 2025 16:01
@openjdk openjdk bot added the rfr Ready for review label Sep 12, 2025
@mlbridge
Copy link

mlbridge bot commented Sep 12, 2025

@kevinrushforth
Copy link
Member

I see that you focused on javafx.base, which is where many of the problems are. Do you plan to update this PR for the other modules and for system tests (the latter also has several exceptions and lots of noise), or will you file a follow-up issue to take up after this one is done?

@andy-goryachev-oracle
Copy link
Contributor Author

good point.
no, this PR is only for the headful tests as they cause the most grief when analyzing the failures.

@mstr2
Copy link
Collaborator

mstr2 commented Sep 12, 2025

The ErrorLoggingUtility class manages static state, and requires users to follow a protocol to avoid messing up its internal state. Have you considered using a try-with-resources scope to make the API easier to use, for example:

try (var util = ErrorLoggingUtility.suppressStdErr()) {
    // test something
    // ...
    //

    util.checkWarning(NullPointerException.class);
} // internal state is restored at the end of the block

@andy-goryachev-oracle
Copy link
Contributor Author

Good point. It already has a static state (error log record), and on top of that try with resources will not work in the bulk use case of @BeforeEach and @AfterEach

Plus, it's a test utility.

I did try to explain the usage in javadoc - maybe that can be further clarified?

@mstr2
Copy link
Collaborator

mstr2 commented Sep 12, 2025

Good point. It already has a static state (error log record), and on top of that try with resources will not work in the bulk use case of @BeforeEach and @AfterEach

Why wouldn't it work? If you really need to, you can store the AutoClosable in @BeforeEach and then call the close method in @AfterEach. But I would also prefer to not scatter around code in before-each and after-each methods, but to have each test be as stand-alone as possible, especially when it comes to scoping.

Whether ErrorLoggingUtility has static state is also immaterial (that would only be important if tests were to be run in parallel), what matters is the API. Using an auto-closable scope has the following advantages:

  1. Using the tool is easy, as you can only call methods that are applicable for the state of the test. If you want to check the log output, you need to have an active scope instance.
  2. It can't be misused, as you can't call methods in the wrong order.
  3. The static state will always be well-defined.

Yes, it's only a test utility, but the API is really suboptimal no matter how I look at it.

@andy-goryachev-oracle
Copy link
Contributor Author

These are all valid points, but keep in mind that System.setErr() is static to begin with. All the system and headful tests are single threaded by design, and this is rather unlikely to change.

Whether to use BeforeEach/AfterEach or standalone test is up to the test developers (this PR contains both cases actually). I would say re-designing the existing tests to be standalone (SelectBindingTest for example) is out of scope for this PR.

@kevinrushforth
Copy link
Member

Two general comments before I start looking at the PR:

  1. Since you are only addressing the javafx.base tests in this PR, please update the JBS title (and PR title) to reflect that.

  2. In the description you wrote:

This PR removes unrelated stderr output in the headful test logs

These are "headless" tests, not headful.

@andy-goryachev-oracle
Copy link
Contributor Author

  1. Since you are only addressing the javafx.base

so that's where it gets complicated: turns out the utility gets used in more tests (graphics, fxml, see #1905 ), so two things came up:

  1. the utility needs to be moved to another package
  2. the utility probably needs to be decoupled from Logging, as it creates unneeded dependency with --add-exports javafx.base/com.sun.javafx.binding=ALL-UNNAMED in 8367567: Rework system tests to suppress unrelated stderr output #1905
  3. perhaps the two PRs need to be merged into one larger one

What do you think?

@kevinrushforth
Copy link
Member

  1. Since you are only addressing the javafx.base

so that's where it gets complicated: turns out the utility gets used in more tests (graphics, fxml, see #1905 ), so two things came up:

  1. the utility needs to be moved to another package
  2. the utility probably needs to be decoupled from Logging, as it creates unneeded dependency with --add-exports javafx.base/com.sun.javafx.binding=ALL-UNNAMED in 8367567: Rework system tests to suppress unrelated stderr output #1905

Yes, I think this seems best. Rather than extend the existing javafx.base ErrorLoggingUtility, which is dependent on the (IMO rather hacky) Logging class in javafx.base itself, a separate test utility that just focuses on capturing and parsing stderr and has no dependencies on logging seems best.

  1. perhaps the two PRs need to be merged into one larger one

Maybe that would be best, although I think it would be OK to keep them separate as long as you do a proof of concept that other modules can use it without needing changes to the exports, etc.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

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

I left a couple quick comments in line. I won't do a thorough review until you decide whether to create a new utility class (which would have the advantage of removing a number of unrelated changes in this PR).


private static PrintStream stderr;
private static AccumulatingPrintStream stderrCapture;
private static int checked;
Copy link
Member

Choose a reason for hiding this comment

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

This is unused (only ever written) and can be removed.

Comment on lines 124 to 127
if(!errors.equals(exp)) {
stderr.println("Mismatch in thrown exceptions:\n expected=" + exp + "\n observed=" + errors);
stderr.println(text);
}
Copy link
Member

Choose a reason for hiding this comment

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

A mismatch should fail the test.

Comment on lines 181 to 184
"(?:" +
// catches lines starting with things like "Exception in thread "main" java.lang.RuntimeException:"
"^" +
"(?:" +
Copy link
Member

Choose a reason for hiding this comment

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

This might be easier to read and maintain with text blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's a complete gibberish as continuous text. not an easy thing to parse mentally.

@andy-goryachev-oracle andy-goryachev-oracle marked this pull request as draft September 16, 2025 20:29
@andy-goryachev-oracle
Copy link
Contributor Author

thanks for suggestions! I'll revert back to DRAFT since there will be more structural changes, then we'll see if we can separate the two PRs or combine them.

@openjdk openjdk bot removed the rfr Ready for review label Sep 16, 2025
Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

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

Thanks for reverting the renaming. It's much easier to review now.

I left a few inline comments. I'll test it and then also finish my review. The regex is a bit of a head scratcher (as they always are), so I'll probably just do a quick "eh, looks OK as long as it works" :)

@BeforeEach
public void setUp() throws Exception {
OutputRedirect.suppressStderr();
ErrorLoggingUtiltity.reset();
Copy link
Member

Choose a reason for hiding this comment

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

Moving the call to reset to a @BeforeEach method is an unrelated change. It seems safer to restore the @BeforeAll method with just the call to reset.

@BeforeEach
public void beforeEach() {
OutputRedirect.suppressStderr();
ErrorLoggingUtiltity.reset();
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as in earlier class: it seems best to leave the reset in a @BeforeAll method, unless there is a compelling reason to change it.

}

/// Returns the captured output, if any, or `null`.
/// @return the captured output string, or `null`
Copy link
Member

Choose a reason for hiding this comment

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

The empty string might be a better choice if there is no output.

Copy link
Contributor Author

@andy-goryachev-oracle andy-goryachev-oracle Sep 18, 2025

Choose a reason for hiding this comment

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

this method is not used anymore, will remove.

Map<String, Integer> errors = findErrors(text);
Map<String, Integer> exp = toMap(expected);
if (!errors.equals(exp)) {
stderr.println("Mismatch in thrown exceptions:\n expected=" + exp + "\n observed=" + errors);
Copy link
Member

Choose a reason for hiding this comment

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

This should cause the test to fail. Set err = true here.

m.put(name, Integer.valueOf(v + 1));
}
} else {
throw new IllegalArgumentException("must specify either Class<? extends Throwable>: " + c);
Copy link
Member

Choose a reason for hiding this comment

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

Typo: remove the "either" since there isn't another one in this case.

return null;
}

// should I leave this test here? to test the test?
Copy link
Member

Choose a reason for hiding this comment

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

Seems reasonable.

Copy link
Member

Choose a reason for hiding this comment

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

Although, I would remove or reword the comment asking whether you should leave it if you've decided to do so.

Comment on lines +140 to +141
private static Map<String, Integer> toMap(Object... expected) {
HashMap<String, Integer> m = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: Given how they are used, you might consider using a Set (which could be created as either a LinkedHashSet or TreeSet) instead. This would simplify the logic a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I need the counts.
JDK has neither MultiMaps nor counting sets.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, nm. I missed that the value is a count of that particular exception class.

Comment on lines 140 to 141
doTestCommon(implicitExit, reEnableImplicitExit, stageShown,
ThrowableType.NONE, appShouldExit);
doTestCommon(implicitExit, reEnableImplicitExit, stageShown, ThrowableType.NONE, appShouldExit);
Copy link
Member

Choose a reason for hiding this comment

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

Minor: This is an unrelated formatting change in a method that is otherwise untouched.

private final int testExitCode = ERROR_NONE;

private void doTestLaunchModule(String appModulePath, String testAppName) throws Exception {
private void doTestLaunchModule(String appModulePath, String testAppName, Object ... expected) throws Exception {
Copy link
Member

Choose a reason for hiding this comment

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

No caller of this method passes anything for the newly added Object ... expected varargs parameter. I recommend reverting the addition of the parameter.

@andy-goryachev-oracle
Copy link
Contributor Author

regex: I can try commenting what each part means

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

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

Last batch of comments. Initial testing looks good.

Comment on lines +140 to +141
private static Map<String, Integer> toMap(Object... expected) {
HashMap<String, Integer> m = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

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

Oh, nm. I missed that the value is a count of that particular exception class.

} else {
throw new IllegalArgumentException("must specify Class<? extends Throwable>: " + c);
}
} else if(x instanceof String) {
Copy link
Member

Choose a reason for hiding this comment

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

Minor: space after if

@BeforeAll
public static void setUpClass() {
System.err.println("SelectBindingTest : log messages are expected from these tests.");
public static void setUpClass() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

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

Minor: this method previously didn't have throws Exception and probably doesn't need it.

Copy link
Member

Choose a reason for hiding this comment

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

Are you going to revert the "throws Exception" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return null;
}

// should I leave this test here? to test the test?
Copy link
Member

Choose a reason for hiding this comment

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

Although, I would remove or reword the comment asking whether you should leave it if you've decided to do so.

Comment on lines 160 to 161
forEach((c) -> {
if (c != null) {
Copy link
Member

Choose a reason for hiding this comment

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

Minor suggestion: filter for non-null Strings before the forEach rather than checking for non-null in the forEach ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my version is more efficient :-)

Copy link
Member

Choose a reason for hiding this comment

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

I doubt it. Anyway, it doesn't really matter all that much.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

It might be interesting to see micro-benchmark results, but yeah, off topic.

///
/// `java.lang.NullPointerException: ...`
private static final Pattern EXCEPTION_PATTERN = Pattern.compile(
"(?:" +
Copy link
Member

Choose a reason for hiding this comment

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

Looks reasonable to me. :)

Comment on lines 242 to 244
} finally {
OutputRedirect.checkAndRestoreStderr(ClassCastException.class);
}
Copy link
Member

Choose a reason for hiding this comment

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

This fails for me, which is not surprising since the snapshot is asynchronous. You probably want to move everything (the suppressStderr and the try/finally with the checkAndRestoreStderr) out of the runAndWait, but even that might not be sufficient without a delay after latch.getCount().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you are right, I missed the runAndWait()

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

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

Shapshot1Test still fails for me. This time the exceptions are being captured in the print stream, but the parsing of the output reports 2 exceptions instead of the expected 1:

    Mismatch in thrown exceptions:
      expected={java.lang.ClassCastException=1}
      observed={java.lang.ClassCastException=1, Exception=1}

Comment on lines 252 to 253
assertEquals(0, latch.getCount());
} finally {
Copy link
Member

Choose a reason for hiding this comment

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

Since the ClassCast exception happens after the latch countdown, I recommend a sleep (of, say, 100 msec) after checking that the latch count is 0.

Comment on lines 381 to 382
assertEquals(0, latch.getCount());
} finally {
Copy link
Member

Choose a reason for hiding this comment

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

Same here: a short sleep after the assertEquals.

@andy-goryachev-oracle
Copy link
Contributor Author

"Exception is snapshot callback" is coming from
Node:2421 and Scene:1612

Why in the world do we have unsuppressible System.err.println() in there??

@andy-goryachev-oracle
Copy link
Contributor Author

I decided to remove Snapshot1Test from this PR: there is enough value added already, and there is more work needed to remove stderr output coming from CssParser anyway, so let's postpone it until the next test sprint.

follow up:
JDK-8368023
[TestBug] Rework tests to clean up stderr

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

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

Looks good. My testing shows that there are no more "failed" messages for a successful build and that the number of Exception messages are reduced. The remainder can be handled by the follow-on bug you filed.

@BeforeAll
public static void setUpClass() {
System.err.println("SelectBindingTest : log messages are expected from these tests.");
public static void setUpClass() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

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

Are you going to revert the "throws Exception" ?

Comment on lines 160 to 161
forEach((c) -> {
if (c != null) {
Copy link
Member

Choose a reason for hiding this comment

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

It might be interesting to see micro-benchmark results, but yeah, off topic.

@kevinrushforth
Copy link
Member

"Exception is snapshot callback" is coming from Node:2421 and Scene:1612

Why in the world do we have unsuppressible System.err.println() in there??

We have many such places in the JavaFX runtime. We already have a task filed to examine these: JDK-8320266.

@kevinrushforth
Copy link
Member

@arapte Can you be the second reviewer?

Copy link
Member

@arapte arapte left a comment

Choose a reason for hiding this comment

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

LGTM+, The log looks good now.

@openjdk openjdk bot added the ready Ready to be integrated label Sep 23, 2025
@andy-goryachev-oracle
Copy link
Contributor Author

thank you for reviewing!

/integrate

@openjdk
Copy link

openjdk bot commented Sep 23, 2025

Going to push as commit f4b3d55.
Since your change was applied there have been 5 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Sep 23, 2025
@openjdk openjdk bot closed this Sep 23, 2025
@openjdk openjdk bot removed ready Ready to be integrated rfr Ready for review labels Sep 23, 2025
@openjdk
Copy link

openjdk bot commented Sep 23, 2025

@andy-goryachev-oracle Pushed as commit f4b3d55.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@andy-goryachev-oracle andy-goryachev-oracle deleted the 8336332.failed branch September 23, 2025 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

4 participants