Repository: zulip/zulip

Issue:
support concurrent backend testing for generate_emails
A couple of backend unittests are failing when executed concurrently, and it seems to be related to generating emails.

The [CZO](https://chat.zulip.org/#narrow/stream/43-automated-testing/topic/parallel.20testing.20on.2064.20core.20node) has a by @timabbott has a reasonable hypothesis about what might be happening. @andersk reduced the symptoms to a very simple test case:

```
tools/test-backend --parallel=2 \
    zerver.tests.test_example.TestDevelopmentEmailsLog.test_generate_emails \
    zerver.tests.test_email_log.EmailLogTest.test_forward_address_details
```

As I investigate/fix, I'll post updates here, so they're searchable in the future.



Discussion:
@zulipbot claim
Hello @asah!

Thanks for your interest in Zulip! You have attempted to claim an issue without the label "help wanted". You can only claim and submit pull requests for issues with the [help wanted](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+no%3Aassignee+label%3A%22help+wanted%22) label.

If this is your first time here, we recommend reading our [guide for new contributors](https://zulip.readthedocs.io/en/latest/overview/contributing.html) before getting started.

This is perfectly reproducible for me, which means it's only a matter of time - PR coming. thx @andersk 

Resolving patch:
diff --git a/zproject/email_backends.py b/zproject/email_backends.py
--- a/zproject/email_backends.py
+++ b/zproject/email_backends.py
@@ -82,11 +82,19 @@ def prepare_email_messages_for_forwarding(email_messages: List[EmailMultiAlterna
 
             email_message.to = [get_forward_address()]
 
+    # This wrapper function exists to allow tests easily to mock the
+    # step of trying to send the emails. Previously, we had mocked
+    # Django's connection.send_messages(), which caused unexplained
+    # test failures when running test-backend at very high
+    # concurrency.
+    def _do_send_messages(self, email_messages: List[EmailMultiAlternatives]) -> int:
+        return super().send_messages(email_messages)  # nocoverage
+
     def send_messages(self, email_messages: List[EmailMultiAlternatives]) -> int:
         num_sent = len(email_messages)
         if get_forward_address():
             self.prepare_email_messages_for_forwarding(email_messages)
-            num_sent = super().send_messages(email_messages)
+            num_sent = self._do_send_messages(email_messages)
 
         if settings.DEVELOPMENT_LOG_EMAILS:
             for email in email_messages:


Test patch:
diff --git a/zerver/tests/test_email_log.py b/zerver/tests/test_email_log.py
--- a/zerver/tests/test_email_log.py
+++ b/zerver/tests/test_email_log.py
@@ -10,7 +10,7 @@
 class EmailLogTest(ZulipTestCase):
     def test_generate_and_clear_email_log(self) -> None:
         with self.settings(EMAIL_BACKEND="zproject.email_backends.EmailLogBackEnd"), mock.patch(
-            "zproject.email_backends.EmailBackend.send_messages"
+            "zproject.email_backends.EmailLogBackEnd._do_send_messages", lambda *args: 1
         ), self.assertLogs(level="INFO") as m, self.settings(DEVELOPMENT_LOG_EMAILS=True):
             result = self.client_get("/emails/generate/")
             self.assertEqual(result.status_code, 302)
@@ -35,11 +35,12 @@ def test_forward_address_details(self) -> None:
 
         self.assertEqual(get_forward_address(), forward_address)
 
-        with self.settings(EMAIL_BACKEND="zproject.email_backends.EmailLogBackEnd"):
-            with mock.patch("zproject.email_backends.EmailBackend.send_messages"):
-                result = self.client_get("/emails/generate/")
-                self.assertEqual(result.status_code, 302)
-                self.assertIn("emails", result["Location"])
-                result = self.client_get(result["Location"])
-                self.assert_in_success_response([forward_address], result)
+        with self.settings(EMAIL_BACKEND="zproject.email_backends.EmailLogBackEnd"), mock.patch(
+            "zproject.email_backends.EmailLogBackEnd._do_send_messages", lambda *args: 1
+        ):
+            result = self.client_get("/emails/generate/")
+            self.assertEqual(result.status_code, 302)
+            self.assertIn("emails", result["Location"])
+            result = self.client_get(result["Location"])
+            self.assert_in_success_response([forward_address], result)
         os.remove(settings.FORWARD_ADDRESS_CONFIG_FILE)
diff --git a/zerver/tests/test_example.py b/zerver/tests/test_example.py
--- a/zerver/tests/test_example.py
+++ b/zerver/tests/test_example.py
@@ -401,8 +401,9 @@ def test_generate_emails(self) -> None:
         # https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLogs
         with self.settings(EMAIL_BACKEND="zproject.email_backends.EmailLogBackEnd"), self.settings(
             DEVELOPMENT_LOG_EMAILS=True
-        ), self.assertLogs(level="INFO") as logger:
-
+        ), self.assertLogs(level="INFO") as logger, mock.patch(
+            "zproject.email_backends.EmailLogBackEnd._do_send_messages", lambda *args: 1
+        ):
             result = self.client_get(
                 "/emails/generate/"
             )  # Generates emails and redirects to /emails/