test_lfs_integration.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. # test_lfs_integration.py -- Integration tests for LFS with filters
  2. # Copyright (C) 2024 Jelmer Vernooij <jelmer@jelmer.uk>
  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 public 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. """Integration tests for LFS with the filter system."""
  22. import shutil
  23. import tempfile
  24. from dulwich.config import ConfigDict
  25. from dulwich.filters import FilterBlobNormalizer, FilterRegistry
  26. from dulwich.lfs import LFSFilterDriver, LFSStore
  27. from dulwich.objects import Blob
  28. from . import TestCase
  29. class LFSFilterIntegrationTests(TestCase):
  30. def setUp(self) -> None:
  31. super().setUp()
  32. # Create temporary directory for LFS store
  33. self.test_dir = tempfile.mkdtemp()
  34. self.addCleanup(shutil.rmtree, self.test_dir)
  35. # Set up LFS store and filter
  36. self.lfs_store = LFSStore.create(self.test_dir)
  37. self.lfs_filter = LFSFilterDriver(self.lfs_store)
  38. # Set up filter registry and normalizer
  39. self.config = ConfigDict()
  40. self.registry = FilterRegistry(self.config)
  41. self.registry.register_driver("lfs", self.lfs_filter)
  42. # Set up gitattributes to use LFS for .bin files
  43. self.gitattributes = {
  44. b"*.bin": {b"filter": b"lfs"},
  45. }
  46. self.normalizer = FilterBlobNormalizer(
  47. self.config, self.gitattributes, self.registry
  48. )
  49. def test_lfs_round_trip(self) -> None:
  50. """Test complete LFS round trip through filter normalizer."""
  51. # Create a blob with binary content
  52. original_content = b"This is a large binary file content" * 100
  53. blob = Blob()
  54. blob.data = original_content
  55. # Checkin: should convert to LFS pointer
  56. checked_in = self.normalizer.checkin_normalize(blob, b"large.bin")
  57. # Verify it's an LFS pointer
  58. self.assertLess(len(checked_in.data), len(original_content))
  59. self.assertTrue(
  60. checked_in.data.startswith(b"version https://git-lfs.github.com/spec/v1")
  61. )
  62. # Checkout: should restore original content
  63. checked_out = self.normalizer.checkout_normalize(checked_in, b"large.bin")
  64. # Verify we got back the original content
  65. self.assertEqual(checked_out.data, original_content)
  66. def test_non_lfs_file(self) -> None:
  67. """Test that non-LFS files pass through unchanged."""
  68. # Create a text file (not matching *.bin pattern)
  69. content = b"This is a regular text file"
  70. blob = Blob()
  71. blob.data = content
  72. # Both operations should return the original blob
  73. checked_in = self.normalizer.checkin_normalize(blob, b"file.txt")
  74. self.assertIs(checked_in, blob)
  75. checked_out = self.normalizer.checkout_normalize(blob, b"file.txt")
  76. self.assertIs(checked_out, blob)
  77. def test_lfs_pointer_file(self) -> None:
  78. """Test handling of files that are already LFS pointers."""
  79. # Create an LFS pointer manually
  80. from dulwich.lfs import LFSPointer
  81. # First store some content
  82. content = b"Content to be stored in LFS"
  83. sha = self.lfs_store.write_object([content])
  84. # Create pointer
  85. pointer = LFSPointer(sha, len(content))
  86. blob = Blob()
  87. blob.data = pointer.to_bytes()
  88. # Checkin should recognize it's already a pointer and not change it
  89. checked_in = self.normalizer.checkin_normalize(blob, b"data.bin")
  90. self.assertIs(checked_in, blob)
  91. # Checkout should expand it
  92. checked_out = self.normalizer.checkout_normalize(blob, b"data.bin")
  93. self.assertEqual(checked_out.data, content)
  94. def test_missing_lfs_object(self) -> None:
  95. """Test handling of LFS pointer with missing object."""
  96. from dulwich.lfs import LFSPointer
  97. # Create pointer to non-existent object
  98. pointer = LFSPointer(
  99. "0000000000000000000000000000000000000000000000000000000000000000", 1234
  100. )
  101. blob = Blob()
  102. blob.data = pointer.to_bytes()
  103. # Checkout should return the pointer as-is when object is missing
  104. checked_out = self.normalizer.checkout_normalize(blob, b"missing.bin")
  105. self.assertEqual(checked_out.data, blob.data)