test_diff.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. # test_diff.py -- Tests for diff functionality.
  2. # Copyright (C) 2025 Dulwich contributors
  3. #
  4. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  5. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  6. # General Public License as published by the Free Software Foundation; version 2.0
  7. # or (at your option) any later version. You can redistribute it and/or
  8. # modify it under the terms of either of these two licenses.
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. # You should have received a copy of the licenses; if not, see
  17. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  18. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  19. # License, Version 2.0.
  20. #
  21. """Tests for diff functionality."""
  22. import io
  23. import unittest
  24. from dulwich.diff import ColorizedDiffStream
  25. from . import TestCase
  26. class ColorizedDiffStreamTests(TestCase):
  27. """Tests for ColorizedDiffStream."""
  28. def setUp(self):
  29. super().setUp()
  30. self.output = io.BytesIO()
  31. @unittest.skipUnless(
  32. ColorizedDiffStream.is_available(), "Rich not available for colorization"
  33. )
  34. def test_write_simple_diff(self):
  35. """Test writing a simple diff with colorization."""
  36. stream = ColorizedDiffStream(self.output)
  37. diff_content = b"""--- a/file.txt
  38. +++ b/file.txt
  39. @@ -1,3 +1,3 @@
  40. unchanged line
  41. -removed line
  42. +added line
  43. another unchanged line
  44. """
  45. stream.write(diff_content)
  46. stream.flush()
  47. # We can't easily test the exact colored output without mocking Rich,
  48. # but we can test that the stream writes something and doesn't crash
  49. result = self.output.getvalue()
  50. self.assertGreater(len(result), 0)
  51. @unittest.skipUnless(
  52. ColorizedDiffStream.is_available(), "Rich not available for colorization"
  53. )
  54. def test_write_line_by_line(self):
  55. """Test writing diff content line by line."""
  56. stream = ColorizedDiffStream(self.output)
  57. lines = [
  58. b"--- a/file.txt\n",
  59. b"+++ b/file.txt\n",
  60. b"@@ -1,2 +1,2 @@\n",
  61. b"-old line\n",
  62. b"+new line\n",
  63. ]
  64. for line in lines:
  65. stream.write(line)
  66. stream.flush()
  67. result = self.output.getvalue()
  68. self.assertGreater(len(result), 0)
  69. @unittest.skipUnless(
  70. ColorizedDiffStream.is_available(), "Rich not available for colorization"
  71. )
  72. def test_writelines(self):
  73. """Test writelines method."""
  74. stream = ColorizedDiffStream(self.output)
  75. lines = [
  76. b"--- a/file.txt\n",
  77. b"+++ b/file.txt\n",
  78. b"@@ -1,1 +1,1 @@\n",
  79. b"-old\n",
  80. b"+new\n",
  81. ]
  82. stream.writelines(lines)
  83. stream.flush()
  84. result = self.output.getvalue()
  85. self.assertGreater(len(result), 0)
  86. @unittest.skipUnless(
  87. ColorizedDiffStream.is_available(), "Rich not available for colorization"
  88. )
  89. def test_partial_line_buffering(self):
  90. """Test that partial lines are buffered correctly."""
  91. stream = ColorizedDiffStream(self.output)
  92. # Write partial line
  93. stream.write(b"+partial")
  94. # Output should be empty as line is not complete
  95. result = self.output.getvalue()
  96. self.assertEqual(len(result), 0)
  97. # Complete the line
  98. stream.write(b" line\n")
  99. result = self.output.getvalue()
  100. self.assertGreater(len(result), 0)
  101. @unittest.skipUnless(
  102. ColorizedDiffStream.is_available(), "Rich not available for colorization"
  103. )
  104. def test_unicode_handling(self):
  105. """Test handling of unicode content in diffs."""
  106. stream = ColorizedDiffStream(self.output)
  107. # UTF-8 encoded content
  108. unicode_diff = "--- a/ünïcödë.txt\n+++ b/ünïcödë.txt\n@@ -1,1 +1,1 @@\n-ōld\n+nëw\n".encode()
  109. stream.write(unicode_diff)
  110. stream.flush()
  111. result = self.output.getvalue()
  112. self.assertGreater(len(result), 0)
  113. def test_is_available_static_method(self):
  114. """Test is_available static method."""
  115. # This should not raise an error regardless of Rich availability
  116. result = ColorizedDiffStream.is_available()
  117. self.assertIsInstance(result, bool)
  118. @unittest.skipIf(
  119. ColorizedDiffStream.is_available(), "Rich is available, skipping fallback test"
  120. )
  121. def test_rich_not_available(self):
  122. """Test behavior when Rich is not available."""
  123. # When Rich is not available, we can't instantiate ColorizedDiffStream
  124. # This test only runs when Rich is not available
  125. with self.assertRaises(ImportError):
  126. ColorizedDiffStream(self.output)
  127. class MockColorizedDiffStreamTests(TestCase):
  128. """Tests for ColorizedDiffStream using a mock when Rich is not available."""
  129. def setUp(self):
  130. super().setUp()
  131. self.output = io.BytesIO()
  132. def test_fallback_behavior_with_mock(self):
  133. """Test that we can handle cases where Rich is not available."""
  134. # This test demonstrates how the CLI handles the case where Rich is unavailable
  135. if not ColorizedDiffStream.is_available():
  136. # When Rich is not available, we should use the raw stream
  137. stream = self.output
  138. else:
  139. # When Rich is available, we can use ColorizedDiffStream
  140. stream = ColorizedDiffStream(self.output)
  141. diff_content = b"+added line\n-removed line\n"
  142. if hasattr(stream, "write"):
  143. stream.write(diff_content)
  144. if hasattr(stream, "flush"):
  145. stream.flush()
  146. # Test that some output was produced
  147. result = self.output.getvalue()
  148. self.assertGreaterEqual(len(result), 0)