File: test_lfs_integration.py

package info (click to toggle)
dulwich 1.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 7,388 kB
  • sloc: python: 99,991; makefile: 163; sh: 67
file content (150 lines) | stat: -rw-r--r-- 5,771 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# test_lfs_integration.py -- Integration tests for LFS with filters
# Copyright (C) 2024 Jelmer Vernooij <jelmer@jelmer.uk>
#
# 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
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
# License, Version 2.0.
#

"""Integration tests for LFS with the filter system."""

import shutil
import tempfile

from dulwich.config import ConfigDict
from dulwich.filters import FilterBlobNormalizer, FilterRegistry
from dulwich.lfs import LFSFilterDriver, LFSStore
from dulwich.objects import Blob

from . import TestCase


class LFSFilterIntegrationTests(TestCase):
    def setUp(self) -> None:
        super().setUp()
        # Suppress LFS warnings during these integration tests
        import logging

        self._old_level = logging.getLogger("dulwich.lfs").level
        logging.getLogger("dulwich.lfs").setLevel(logging.ERROR)
        # Create temporary directory for LFS store
        self.test_dir = tempfile.mkdtemp()
        self.addCleanup(shutil.rmtree, self.test_dir)

        # Set up LFS store and filter
        self.lfs_store = LFSStore.create(self.test_dir)
        self.lfs_filter = LFSFilterDriver(self.lfs_store)

        # Set up filter registry and normalizer
        self.config = ConfigDict()
        self.registry = FilterRegistry(self.config)
        self.registry.register_driver("lfs", self.lfs_filter)

        # Set up gitattributes to use LFS for .bin files
        from dulwich.attrs import GitAttributes, Pattern

        patterns = [
            (Pattern(b"*.bin"), {b"filter": b"lfs"}),
        ]
        self.gitattributes = GitAttributes(patterns)

        from dulwich.filters import FilterContext

        filter_context = FilterContext(self.registry)
        self.normalizer = FilterBlobNormalizer(
            self.config, self.gitattributes, filter_context=filter_context
        )

    def tearDown(self) -> None:
        # Restore original logging level
        import logging

        logging.getLogger("dulwich.lfs").setLevel(self._old_level)
        super().tearDown()

    def test_lfs_round_trip(self) -> None:
        """Test complete LFS round trip through filter normalizer."""
        # Create a blob with binary content
        original_content = b"This is a large binary file content" * 100
        blob = Blob()
        blob.data = original_content

        # Checkin: should convert to LFS pointer
        checked_in = self.normalizer.checkin_normalize(blob, b"large.bin")

        # Verify it's an LFS pointer
        self.assertLess(len(checked_in.data), len(original_content))
        self.assertTrue(
            checked_in.data.startswith(b"version https://git-lfs.github.com/spec/v1")
        )

        # Checkout: should restore original content
        checked_out = self.normalizer.checkout_normalize(checked_in, b"large.bin")

        # Verify we got back the original content
        self.assertEqual(checked_out.data, original_content)

    def test_non_lfs_file(self) -> None:
        """Test that non-LFS files pass through unchanged."""
        # Create a text file (not matching *.bin pattern)
        content = b"This is a regular text file"
        blob = Blob()
        blob.data = content

        # Both operations should return the original blob
        checked_in = self.normalizer.checkin_normalize(blob, b"file.txt")
        self.assertIs(checked_in, blob)

        checked_out = self.normalizer.checkout_normalize(blob, b"file.txt")
        self.assertIs(checked_out, blob)

    def test_lfs_pointer_file(self) -> None:
        """Test handling of files that are already LFS pointers."""
        # Create an LFS pointer manually
        from dulwich.lfs import LFSPointer

        # First store some content
        content = b"Content to be stored in LFS"
        sha = self.lfs_store.write_object([content])

        # Create pointer
        pointer = LFSPointer(sha, len(content))
        blob = Blob()
        blob.data = pointer.to_bytes()

        # Checkin should recognize it's already a pointer and not change it
        checked_in = self.normalizer.checkin_normalize(blob, b"data.bin")
        self.assertIs(checked_in, blob)

        # Checkout should expand it
        checked_out = self.normalizer.checkout_normalize(blob, b"data.bin")
        self.assertEqual(checked_out.data, content)

    def test_missing_lfs_object(self) -> None:
        """Test handling of LFS pointer with missing object."""
        from dulwich.lfs import LFSPointer

        # Create pointer to non-existent object
        pointer = LFSPointer(
            "0000000000000000000000000000000000000000000000000000000000000000", 1234
        )
        blob = Blob()
        blob.data = pointer.to_bytes()

        # Checkout should return the pointer as-is when object is missing
        with self.assertLogs("dulwich.lfs", level="WARNING"):
            checked_out = self.normalizer.checkout_normalize(blob, b"missing.bin")
        self.assertEqual(checked_out.data, blob.data)