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

Add spam detection in test-backend output. #16165

Merged
merged 2 commits into from Aug 27, 2020

Conversation

thedeveloperr
Copy link
Collaborator

@thedeveloperr thedeveloperr commented Aug 19, 2020

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

  • Add flag to disable the spam detection.

Testing Plan:

  1. Added print statements in test cases randomly and confirmed if the it's being caught.
  2. Removed few commits that suppressed logs using assertLogs and ensured that spam was caught by the script and right test that printed the spam.

GIFs or Screenshots:
image

@thedeveloperr
Copy link
Collaborator Author

@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

def __init__(self, spam_finder: SpamFinder) -> None:
self.stderr_stream = sys.stderr

# get shared onsole handler instance from any logger that have it
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: "onsole" -> "console"

@thedeveloperr thedeveloperr force-pushed the test-backend-spam-detection branch 2 times, most recently from 296e521 to 322654a Compare August 23, 2020 11:35
@thedeveloperr thedeveloperr changed the title WIP test_classes: Add spam detection in test-backend output. =test_classes: Add spam detection in test-backend output. Aug 24, 2020
@thedeveloperr thedeveloperr changed the title =test_classes: Add spam detection in test-backend output. Add spam detection in test-backend output. Aug 24, 2020

options = parser.parse_args()
if options.ban_spam:
os.environ["BAN_TEST_OUTPUT_EXTRA_SPAM"] = "1"
Copy link
Sponsor Member

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.

Copy link
Sponsor Member

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)

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 ---------------"
Copy link
Sponsor Member

@timabbott timabbott Aug 25, 2020

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.

"^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 ",
Copy link
Sponsor Member

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.

self.spam_finder.find_spam(lines)

def flush(self) -> None:
self.stdout_stream.flush()
Copy link
Sponsor Member

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.

class SpamInTestOutputException(Exception):
pass

class SpamFinder:
Copy link
Sponsor Member

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.

Copy link
Sponsor Member

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

@timabbott
Copy link
Sponsor Member

@thedeveloperr thanks for working on this! The approach seems viable; I posted a bunch of comments on cleaning up the code.

@@ -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:
Copy link
Sponsor Member

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.

@thedeveloperr
Copy link
Collaborator Author

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

@thedeveloperr thedeveloperr force-pushed the test-backend-spam-detection branch 2 times, most recently from 390f977 to 212877b Compare August 26, 2020 23:22
@timabbott
Copy link
Sponsor Member

The right way to handle coverage is to put # nocoverage on the def line itself, I think.

@timabbott
Copy link
Sponsor Member

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.

thedeveloperr and others added 2 commits August 26, 2020 16:52
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.
@timabbott
Copy link
Sponsor Member

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.

@thedeveloperr
Copy link
Collaborator Author

All tests passed.

@timabbott timabbott merged commit dbf3894 into zulip:master Aug 27, 2020
@timabbott
Copy link
Sponsor Member

Merged, thanks for all your hard work on this @thedeveloperr!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants