From fb274f11e22534164e55415db712ceeff1d44116 Mon Sep 17 00:00:00 2001
From: Michael Blahay <mblahay@gmail.com>
Date: Thu, 27 Apr 2023 11:54:45 -0600
Subject: [PATCH 1/6] Correcting message display issue causing output of
 assertMultiLineEqual to be garbled.

---
 Lib/test/test_unittest/test_assertions.py |  6 +--
 Lib/test/test_unittest/test_case.py       | 60 +++++++++++++++++++++++
 Lib/unittest/case.py                      | 36 ++++++++++----
 3 files changed, 90 insertions(+), 12 deletions(-)

diff --git a/Lib/test/test_unittest/test_assertions.py b/Lib/test/test_unittest/test_assertions.py
index 6557104b81fc0f..5c1a28ecda5b49 100644
--- a/Lib/test/test_unittest/test_assertions.py
+++ b/Lib/test/test_unittest/test_assertions.py
@@ -273,9 +273,9 @@ def testAssertDictEqual(self):
 
     def testAssertMultiLineEqual(self):
         self.assertMessages('assertMultiLineEqual', ("", "foo"),
-                            [r"\+ foo$", "^oops$",
-                             r"\+ foo$",
-                             r"\+ foo : oops$"])
+                            [r"\+ foo\n$", "^oops$",
+                             r"\+ foo\n$",
+                             r"\+ foo\n : oops$"])
 
     def testAssertLess(self):
         self.assertMessages('assertLess', (2, 1),
diff --git a/Lib/test/test_unittest/test_case.py b/Lib/test/test_unittest/test_case.py
index dd5ff6d553e61d..ed5eb5609a5dd1 100644
--- a/Lib/test/test_unittest/test_case.py
+++ b/Lib/test/test_unittest/test_case.py
@@ -1149,6 +1149,66 @@ def testAssertEqualSingleLine(self):
             error = str(e).split('\n', 1)[1]
             self.assertEqual(sample_text_error, error)
 
+    def testAssertEqualwithEmptyString(self):
+        '''Verify when there is an empty string involved, the diff output
+         does not treat the empty string as a single empty line. It should
+         instead be handled as a non-line.
+        '''
+        sample_text = ''
+        revised_sample_text = 'unladen swallows fly quickly'
+        sample_text_error = '''\
++ unladen swallows fly quickly
+'''
+        try:
+            self.assertEqual(sample_text, revised_sample_text)
+        except self.failureException as e:
+            # need to remove the first line of the error message
+            error = str(e).split('\n', 1)[1]
+            self.assertEqual(sample_text_error, error)
+
+    def testAssertEqualMultipleLinesMissingNewlineTerminator(self):
+        '''Verifying format of diff output from assertEqual involving strings
+         with multiple lines, but missing the terminating newline on both.
+        '''
+        sample_text = 'laden swallows\nfly sloely'
+        revised_sample_text = 'laden swallows\nfly slowly'
+        sample_text_error = '''\
+  laden swallows
+- fly sloely
+?        ^
++ fly slowly
+?        ^
+'''
+        try:
+            self.assertEqual(sample_text, revised_sample_text)
+        except self.failureException as e:
+            # need to remove the first line of the error message
+            error = str(e).split('\n', 1)[1]
+            self.assertEqual(sample_text_error, error)
+
+    def testAssertEqualMultipleLinesMismatchedNewlinesTerminators(self):
+        '''Verifying format of diff output from assertEqual involving strings
+         with multiple lines and mismatched newlines. The output should
+         include a - on it's own line to indicate the newline difference
+         between the two strings
+        '''
+        sample_text = 'laden swallows\nfly sloely\n'
+        revised_sample_text = 'laden swallows\nfly slowly'
+        sample_text_error = '''\
+  laden swallows
+- fly sloely
+?        ^
++ fly slowly
+?        ^
+-\x20
+'''
+        try:
+            self.assertEqual(sample_text, revised_sample_text)
+        except self.failureException as e:
+            # need to remove the first line of the error message
+            error = str(e).split('\n', 1)[1]
+            self.assertEqual(sample_text_error, error)
+
     def testEqualityBytesWarning(self):
         if sys.flags.bytes_warning:
             def bytes_warning():
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index 018f22e7ce0c73..c3fd661c9a4ca5 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -1216,20 +1216,38 @@ def assertCountEqual(self, first, second, msg=None):
             self.fail(msg)
 
     def assertMultiLineEqual(self, first, second, msg=None):
-        """Assert that two multi-line strings are equal."""
-        self.assertIsInstance(first, str, 'First argument is not a string')
-        self.assertIsInstance(second, str, 'Second argument is not a string')
+        """Assert that two multi-line strings are equal.
+         If the assertion fails, then provide an error message with a detailed
+         diff output
+        """
+        self.assertIsInstance(first, str, "First argument is not a string")
+        self.assertIsInstance(second, str, "Second argument is not a string")
 
         if first != second:
-            # don't use difflib if the strings are too long
+            # Don't use difflib if the strings are too long
             if (len(first) > self._diffThreshold or
                 len(second) > self._diffThreshold):
                 self._baseAssertEqual(first, second, msg)
-            firstlines = first.splitlines(keepends=True)
-            secondlines = second.splitlines(keepends=True)
-            if len(firstlines) == 1 and first.strip('\r\n') == first:
-                firstlines = [first + '\n']
-                secondlines = [second + '\n']
+
+            # Append \n to both strings if either is missing the \n.
+            # This allows the final ndiff to show the \n difference. The
+            # exception here is if the string is empty, in which case no
+            # \n should be added
+            first_presplit = first
+            second_presplit = second
+            if first != '' and second != '':
+                if first[-1] != '\n' or second[-1] != '\n':
+                    first_presplit += '\n'
+                    second_presplit += '\n'
+            elif first == '' and second != '' and second[-1] != '\n':
+                second_presplit += '\n'
+            elif second == '' and first != '' and first[-1] != '\n':
+                first_presplit += '\n'
+
+            firstlines = first_presplit.splitlines(keepends=True)
+            secondlines = second_presplit.splitlines(keepends=True)
+
+            # Generate the message and diff, then raise the exception
             standardMsg = '%s != %s' % _common_shorten_repr(first, second)
             diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines))
             standardMsg = self._truncateMessage(standardMsg, diff)

From 58f7f6223b788fc5cfb57991870ac1df23837ac0 Mon Sep 17 00:00:00 2001
From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com>
Date: Thu, 27 Apr 2023 18:46:32 +0000
Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?=
 =?UTF-8?q?rb=5Fit.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst   | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst

diff --git a/Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst b/Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst
new file mode 100644
index 00000000000000..a3ae55c32b408e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst
@@ -0,0 +1 @@
+Correcting message display issue causing output of assertEqual to be garbled when provided string inputs.

From 046dcb4f4e3acddcdcdf8b11449c5c27a023588c Mon Sep 17 00:00:00 2001
From: Michael Blahay <mblahay@users.noreply.github.com>
Date: Tue, 2 May 2023 20:53:38 -0600
Subject: [PATCH 3/6] Update Lib/unittest/case.py

Adding period missing in doc string for assertMultiLineEqual.

Co-authored-by: Carl Meyer <carl@oddbird.net>
---
 Lib/unittest/case.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index c3fd661c9a4ca5..bcc1ffde3ae2f5 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -1217,8 +1217,9 @@ def assertCountEqual(self, first, second, msg=None):
 
     def assertMultiLineEqual(self, first, second, msg=None):
         """Assert that two multi-line strings are equal.
-         If the assertion fails, then provide an error message with a detailed
-         diff output
+
+         If the assertion fails, provide an error message with a detailed
+         diff output.
         """
         self.assertIsInstance(first, str, "First argument is not a string")
         self.assertIsInstance(second, str, "Second argument is not a string")

From cc191c77056fc93d1e880e4ee65f9c5e7f66e811 Mon Sep 17 00:00:00 2001
From: Michael Blahay <mblahay@users.noreply.github.com>
Date: Tue, 2 May 2023 21:09:59 -0600
Subject: [PATCH 4/6] Update
 Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst

Modifying news entry to be more precise

Co-authored-by: Carl Meyer <carl@oddbird.net>
---
 .../next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst b/Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst
index a3ae55c32b408e..bf29b64793b933 100644
--- a/Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst
+++ b/Misc/NEWS.d/next/Library/2023-04-27-18-46-31.gh-issue-68968.E3tnhy.rst
@@ -1 +1 @@
-Correcting message display issue causing output of assertEqual to be garbled when provided string inputs.
+Fixed garbled output of :meth:`~unittest.TestCase.assertEqual` when an input lacks final newline.

From 1990b4de09688ba4d8decc9d53223bc9ab3a93f0 Mon Sep 17 00:00:00 2001
From: Michael Blahay <mblahay@users.noreply.github.com>
Date: Wed, 3 May 2023 18:21:49 -0600
Subject: [PATCH 5/6] Update Lib/unittest/case.py

Per carljm suggestion, removing unnecessary conditions.

Co-authored-by: Carl Meyer <carl@oddbird.net>
---
 Lib/unittest/case.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index bcc1ffde3ae2f5..116670bbd7fab1 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -1236,13 +1236,13 @@ def assertMultiLineEqual(self, first, second, msg=None):
             # \n should be added
             first_presplit = first
             second_presplit = second
-            if first != '' and second != '':
+            if first and second:
                 if first[-1] != '\n' or second[-1] != '\n':
                     first_presplit += '\n'
                     second_presplit += '\n'
-            elif first == '' and second != '' and second[-1] != '\n':
+            elif second and second[-1] != '\n':
                 second_presplit += '\n'
-            elif second == '' and first != '' and first[-1] != '\n':
+            elif first and first[-1] != '\n':
                 first_presplit += '\n'
 
             firstlines = first_presplit.splitlines(keepends=True)

From 914e920f03dd29e97d4e2d8f648e1dff51624d53 Mon Sep 17 00:00:00 2001
From: Michael Blahay <mblahay@users.noreply.github.com>
Date: Wed, 3 May 2023 18:35:47 -0600
Subject: [PATCH 6/6] Update case.py

Reverting back to single line doc string for assertMultiLineEqual.
---
 Lib/unittest/case.py | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index 116670bbd7fab1..001b640dc43ad6 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -1216,11 +1216,7 @@ def assertCountEqual(self, first, second, msg=None):
             self.fail(msg)
 
     def assertMultiLineEqual(self, first, second, msg=None):
-        """Assert that two multi-line strings are equal.
-
-         If the assertion fails, provide an error message with a detailed
-         diff output.
-        """
+        """Assert that two multi-line strings are equal."""
         self.assertIsInstance(first, str, "First argument is not a string")
         self.assertIsInstance(second, str, "Second argument is not a string")