# test_diff.py -- Tests for diff functionality.
# Copyright (C) 2025 Dulwich contributors
#
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
# General Public License as published by the Free Software Foundation; version 2.0
# or (at your option) any later version. You can redistribute it and/or
# modify it under the terms of either of these two licenses.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# You should have received a copy of the licenses; if not, see
# for a copy of the GNU General Public License
# and for a copy of the Apache
# License, Version 2.0.
#
"""Tests for diff functionality."""
import io
import unittest
from dulwich.diff import ColorizedDiffStream
from . import TestCase
class ColorizedDiffStreamTests(TestCase):
"""Tests for ColorizedDiffStream."""
def setUp(self):
super().setUp()
self.output = io.BytesIO()
@unittest.skipUnless(
ColorizedDiffStream.is_available(), "Rich not available for colorization"
)
def test_write_simple_diff(self):
"""Test writing a simple diff with colorization."""
stream = ColorizedDiffStream(self.output)
diff_content = b"""--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
unchanged line
-removed line
+added line
another unchanged line
"""
stream.write(diff_content)
stream.flush()
# We can't easily test the exact colored output without mocking Rich,
# but we can test that the stream writes something and doesn't crash
result = self.output.getvalue()
self.assertGreater(len(result), 0)
@unittest.skipUnless(
ColorizedDiffStream.is_available(), "Rich not available for colorization"
)
def test_write_line_by_line(self):
"""Test writing diff content line by line."""
stream = ColorizedDiffStream(self.output)
lines = [
b"--- a/file.txt\n",
b"+++ b/file.txt\n",
b"@@ -1,2 +1,2 @@\n",
b"-old line\n",
b"+new line\n",
]
for line in lines:
stream.write(line)
stream.flush()
result = self.output.getvalue()
self.assertGreater(len(result), 0)
@unittest.skipUnless(
ColorizedDiffStream.is_available(), "Rich not available for colorization"
)
def test_writelines(self):
"""Test writelines method."""
stream = ColorizedDiffStream(self.output)
lines = [
b"--- a/file.txt\n",
b"+++ b/file.txt\n",
b"@@ -1,1 +1,1 @@\n",
b"-old\n",
b"+new\n",
]
stream.writelines(lines)
stream.flush()
result = self.output.getvalue()
self.assertGreater(len(result), 0)
@unittest.skipUnless(
ColorizedDiffStream.is_available(), "Rich not available for colorization"
)
def test_partial_line_buffering(self):
"""Test that partial lines are buffered correctly."""
stream = ColorizedDiffStream(self.output)
# Write partial line
stream.write(b"+partial")
# Output should be empty as line is not complete
result = self.output.getvalue()
self.assertEqual(len(result), 0)
# Complete the line
stream.write(b" line\n")
result = self.output.getvalue()
self.assertGreater(len(result), 0)
@unittest.skipUnless(
ColorizedDiffStream.is_available(), "Rich not available for colorization"
)
def test_unicode_handling(self):
"""Test handling of unicode content in diffs."""
stream = ColorizedDiffStream(self.output)
# UTF-8 encoded content
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()
stream.write(unicode_diff)
stream.flush()
result = self.output.getvalue()
self.assertGreater(len(result), 0)
def test_is_available_static_method(self):
"""Test is_available static method."""
# This should not raise an error regardless of Rich availability
result = ColorizedDiffStream.is_available()
self.assertIsInstance(result, bool)
@unittest.skipIf(
ColorizedDiffStream.is_available(), "Rich is available, skipping fallback test"
)
def test_rich_not_available(self):
"""Test behavior when Rich is not available."""
# When Rich is not available, we can't instantiate ColorizedDiffStream
# This test only runs when Rich is not available
with self.assertRaises(ImportError):
ColorizedDiffStream(self.output)
class MockColorizedDiffStreamTests(TestCase):
"""Tests for ColorizedDiffStream using a mock when Rich is not available."""
def setUp(self):
super().setUp()
self.output = io.BytesIO()
def test_fallback_behavior_with_mock(self):
"""Test that we can handle cases where Rich is not available."""
# This test demonstrates how the CLI handles the case where Rich is unavailable
if not ColorizedDiffStream.is_available():
# When Rich is not available, we should use the raw stream
stream = self.output
else:
# When Rich is available, we can use ColorizedDiffStream
stream = ColorizedDiffStream(self.output)
diff_content = b"+added line\n-removed line\n"
if hasattr(stream, "write"):
stream.write(diff_content)
if hasattr(stream, "flush"):
stream.flush()
# Test that some output was produced
result = self.output.getvalue()
self.assertGreaterEqual(len(result), 0)