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
Add spam detection in test-backend output. #16165
Add spam detection in test-backend output. #16165
Conversation
@timabbott Please give a review on this approach. This was the best I was able to do after alot of failed iteration to detect spam and also point to the test that produced that spam |
zerver/lib/test_classes.py
Outdated
def __init__(self, spam_finder: SpamFinder) -> None: | ||
self.stderr_stream = sys.stderr | ||
|
||
# get shared onsole handler instance from any logger that have 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.
typo: "onsole" -> "console"
296e521
to
322654a
Compare
322654a
to
a46e323
Compare
tools/test-backend
Outdated
|
||
options = parser.parse_args() | ||
if options.ban_spam: | ||
os.environ["BAN_TEST_OUTPUT_EXTRA_SPAM"] = "1" |
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'd encourage using the same name for a variable like this, rather than having 3 versions:
- BAN_TEST_OUTPUT_EXTRA_SPAM, ban-extra-output, ban_spam.
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.
Maybe the name should be --ban-console-output
as the name and use that consistently (there should be no references to "spam" to "output_extra" in this PR)
zerver/lib/test_classes.py
Outdated
with TeeStderrAndFindSpam(spam_finder), TeeStdoutAndFindSpam(spam_finder): | ||
test_result = super(ZulipTestCase, self).run(result) | ||
if spam_finder.full_spam: | ||
exception_message = f"\n\n--- SPAM DETECTED ---\n\nTest:\n{self.id()}\n\nSpam:\n{spam_finder.full_spam}\n\n ---------------" |
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.
Let's format this using f"""
to be a more readable block. And I'd replace "Spam" with "Output" and "SPAM DETECTED" with "UNEXPECTED CONSOLE OUTPUT" and maybe some text about the goal of having test-backend
output be clean.
zerver/lib/test_helpers.py
Outdated
"^Ran [0-9]+ tests in", # Example: Ran 2139 tests in 115.659s | ||
"^Destroying test database for alias ", # Destroying test database for alias 'default'... | ||
"^Using existing clone", | ||
"^\\*\\* Skipping ", |
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.
Let's put the comments on the line before the expression, rather than the same line; it'll be more readable that way.
We may want to do a bit of work to remove some of these output lines from the test-backend
default output as a follow-up project. E.g. @showell is the "URL coverage report" something we should just delete at this point, now that we have more powerful tools for checking that we've tested all our API endpoints?
And I feel like the "Destroying test database for alias" type output could be hidden behind a verbose flag.
zerver/lib/test_helpers.py
Outdated
self.spam_finder.find_spam(lines) | ||
|
||
def flush(self) -> None: | ||
self.stdout_stream.flush() |
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 might be able to deduplicate these two classes; they're nearly the same. Not sure if it's worth it.
zerver/lib/test_helpers.py
Outdated
class SpamInTestOutputException(Exception): | ||
pass | ||
|
||
class SpamFinder: |
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 bundle probably better belongs in test_runner.py
than test_helpers.py
-- the latter is for things we might import from an individual 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.
Actually, let's put this new code in a new file, zerver/lib/test_console_output.py
; I think that'll be happier. (I'd misunderstood it as being called not from test_classes.py
).
@thedeveloperr thanks for working on this! The approach seems viable; I posted a bunch of comments on cleaning up the code. |
zerver/lib/test_classes.py
Outdated
@@ -113,6 +120,17 @@ def tearDown(self) -> None: | |||
self.mock_ldap.reset() | |||
self.mock_initialize.stop() | |||
|
|||
def run(self, result: Optional[TestResult]=None) -> Optional[TestResult]: | |||
if not settings.BAN_TEST_OUTPUT_EXTRA_SPAM: |
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'll want to add # nocoverage
on this function, I think, since one path or the other is going to be not run in every individual test run.
@timabbott Do we need test coverage for spam detection and Tee class ? It is itself called in tests so I was bit confused how I could write test for something that is called inside a test, and that too in ZulipTestCase run method. |
390f977
to
212877b
Compare
The right way to handle coverage is to put |
After moving the coverage change, I made these documentation tweaks: (zulip-py3-venv) tabbott@coset:~/zulip$ git diff
diff --git a/tools/test-backend b/tools/test-backend
index 3cee63516e..794a306aee 100755
--- a/tools/test-backend
+++ b/tools/test-backend
@@ -263,7 +263,7 @@ def main() -> None:
parser.add_argument('args', nargs='*')
parser.add_argument('--ban-console-output', dest='ban_console_output',
action="store_true",
- default=False, help='Throw error on detecting output in stdout and stderr produced by code under tests.')
+ default=False, help='Require stdout and stderr to be clean of unexpected output.')
options = parser.parse_args()
if options.ban_console_output:
diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py
index 3f4758a7de..8a6172ea5d 100644
--- a/zerver/lib/test_classes.py
+++ b/zerver/lib/test_classes.py
@@ -127,23 +127,24 @@ class ZulipTestCase(TestCase):
test_result = super(ZulipTestCase, self).run(result)
if extra_output_finder.full_extra_output:
exception_message = f"""
- ---- UNEXPECTED CONSOLE OUTPUT DETECTED ----
- Sometimes bugs are introduced where extra
- console output shows up in the test-backend
- output making it harder to read our test
- output.
- This check detects extra console output due
- to a logger or some other source printing
- to stdout or stderr. Please see the test and
- extra output detected
-
- Test:
- {self.id()}
-
- Extra Output:
- {extra_output_finder.full_extra_output}
- --------------------------------------------
- """
+---- UNEXPECTED CONSOLE OUTPUT DETECTED ----
+
+To ensure that we never miss important error output/warnings,
+we require test-backend to have clean console output.
+
+This message usually is triggered by forgotten debugging print()
+statements or new logging statements. For the latter, you can
+use `with self.assertLogs()` to capture and verify the log output;
+use `git grep assertLogs` to see dozens of correct examples.
+
+You should be able to quickly reproduce this failure with:
+
+test-backend --ban-console-output {self.id()}
+
+Output:
+{extra_output_finder.full_extra_output}
+--------------------------------------------
+"""
raise ExtraConsoleOutputInTestException(exception_message)
return test_result
With those changes, this lgtm; I'm planning to merge after looking at our generic testing docs to see if this feature needs a mention. |
This commit adds automatic detection of extra output (other than printed by testing library or tools) in stderr and stdout by code under test test-backend when it is run with flag --ban-console-output. It also prints the test that produced the extra console output. Fixes: zulip#1587.
212877b
to
cd2b6e0
Compare
I pushed back to this PR after those changes and adding another documentation commit (that also enables the option in CI); will merge if this passes CI. |
All tests passed. |
Merged, thanks for all your hard work on this @thedeveloperr! |
This commit adds automatic spam detection in output printed to
stderr and stdout by test-backend while running instances of
ZulipTestCase in parallel mode.
It also prints the test file and method that produced the
spam.
#1587
Testing Plan:
GIFs or Screenshots: