-
Notifications
You must be signed in to change notification settings - Fork 534
8336332: Rework tests to avoid unrelated stderr output #1897
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
8336332: Rework tests to avoid unrelated stderr output #1897
Conversation
👋 Welcome back angorya! A progress list of the required criteria for merging this PR into |
@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:
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
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 |
Webrevs
|
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? |
good point. |
The try (var util = ErrorLoggingUtility.suppressStdErr()) {
// test something
// ...
//
util.checkWarning(NullPointerException.class);
} // internal state is restored at the end of the block |
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 Plus, it's a test utility. I did try to explain the usage in javadoc - maybe that can be further clarified? |
Why wouldn't it work? If you really need to, you can store the Whether
Yes, it's only a test utility, but the API is really suboptimal no matter how I look at it. |
These are all valid points, but keep in mind that 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 ( |
Two general comments before I start looking at the PR:
These are "headless" tests, not headful. |
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:
What do you think? |
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.
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. |
There was a problem hiding this 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; |
There was a problem hiding this comment.
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.
if(!errors.equals(exp)) { | ||
stderr.println("Mismatch in thrown exceptions:\n expected=" + exp + "\n observed=" + errors); | ||
stderr.println(text); | ||
} |
There was a problem hiding this comment.
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.
"(?:" + | ||
// catches lines starting with things like "Exception in thread "main" java.lang.RuntimeException:" | ||
"^" + | ||
"(?:" + |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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. |
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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` |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable.
There was a problem hiding this comment.
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.
private static Map<String, Integer> toMap(Object... expected) { | ||
HashMap<String, Integer> m = new HashMap<>(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 MultiMap
s nor counting sets.
There was a problem hiding this comment.
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.
doTestCommon(implicitExit, reEnableImplicitExit, stageShown, | ||
ThrowableType.NONE, appShouldExit); | ||
doTestCommon(implicitExit, reEnableImplicitExit, stageShown, ThrowableType.NONE, appShouldExit); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
regex: I can try commenting what each part means |
There was a problem hiding this 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.
private static Map<String, Integer> toMap(Object... expected) { | ||
HashMap<String, Integer> m = new HashMap<>(); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" ?
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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.
forEach((c) -> { | ||
if (c != null) { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 :-)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
off topic, but it is more efficient - check out
https://github.com/andy-goryachev-oracle/Test/blob/main/src/goryachev/research/MoreEfficient.java
There was a problem hiding this comment.
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( | ||
"(?:" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks reasonable to me. :)
} finally { | ||
OutputRedirect.checkAndRestoreStderr(ClassCastException.class); | ||
} |
There was a problem hiding this comment.
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()
.
There was a problem hiding this comment.
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()
There was a problem hiding this 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}
assertEquals(0, latch.getCount()); | ||
} finally { |
There was a problem hiding this comment.
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.
assertEquals(0, latch.getCount()); | ||
} finally { |
There was a problem hiding this comment.
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.
"Exception is snapshot callback" is coming from Why in the world do we have unsuppressible System.err.println() in there?? |
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: |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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" ?
forEach((c) -> { | ||
if (c != null) { |
There was a problem hiding this comment.
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.
We have many such places in the JavaFX runtime. We already have a task filed to examine these: JDK-8320266. |
@arapte Can you be the second reviewer? |
There was a problem hiding this 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.
thank you for reviewing! /integrate |
Going to push as commit f4b3d55.
Your commit was automatically rebased without conflicts. |
@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. |
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 eitherOutputRedirect.checkStderr()
orOutputRedirect.checkAndRestoreStderr()
(ideally, in thefinally
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
OutputRedirect
facilityMiscellaneous
ErrorLoggingUtiltity
name will be fixed in a followup https://bugs.openjdk.org/browse/JDK-8367995Progress
Issue
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