~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test__dirstate_helpers.py

  • Committer: Robert Collins
  • Date: 2007-07-25 00:52:21 UTC
  • mfrom: (2650 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2651.
  • Revision ID: robertc@robertcollins.net-20070725005221-0ysm6il5mqnme3wz
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests for the compiled dirstate helpers."""
 
18
 
 
19
import bisect
 
20
import os
 
21
 
 
22
from bzrlib import (
 
23
    dirstate,
 
24
    tests,
 
25
    )
 
26
from bzrlib.tests import test_dirstate
 
27
 
 
28
 
 
29
class _CompiledDirstateHelpersFeature(tests.Feature):
 
30
    def _probe(self):
 
31
        try:
 
32
            import bzrlib._dirstate_helpers_c
 
33
        except ImportError:
 
34
            return False
 
35
        return True
 
36
 
 
37
    def feature_name(self):
 
38
        return 'bzrlib._dirstate_helpers_c'
 
39
 
 
40
CompiledDirstateHelpersFeature = _CompiledDirstateHelpersFeature()
 
41
 
 
42
 
 
43
class TestBisectPathMixin(object):
 
44
    """Test that _bisect_path_*() returns the expected values.
 
45
 
 
46
    _bisect_path_* is intended to work like bisect.bisect_*() except it
 
47
    knows it is working on paths that are sorted by ('path', 'to', 'foo')
 
48
    chunks rather than by raw 'path/to/foo'.
 
49
 
 
50
    Test Cases should inherit from this and override ``get_bisect_path`` return
 
51
    their implementation, and ``get_bisect`` to return the matching
 
52
    bisect.bisect_* function.
 
53
    """
 
54
 
 
55
    def get_bisect_path(self):
 
56
        """Return an implementation of _bisect_path_*"""
 
57
        raise NotImplementedError
 
58
 
 
59
    def get_bisect(self):
 
60
        """Return a version of bisect.bisect_*.
 
61
 
 
62
        Also, for the 'exists' check, return the offset to the real values.
 
63
        For example bisect_left returns the index of an entry, while
 
64
        bisect_right returns the index *after* an entry
 
65
 
 
66
        :return: (bisect_func, offset)
 
67
        """
 
68
        raise NotImplementedError
 
69
 
 
70
    def assertBisect(self, paths, split_paths, path, exists=True):
 
71
        """Assert that bisect_split works like bisect_left on the split paths.
 
72
 
 
73
        :param paths: A list of path names
 
74
        :param split_paths: A list of path names that are already split up by directory
 
75
            ('path/to/foo' => ('path', 'to', 'foo'))
 
76
        :param path: The path we are indexing.
 
77
        :param exists: The path should be present, so make sure the
 
78
            final location actually points to the right value.
 
79
 
 
80
        All other arguments will be passed along.
 
81
        """
 
82
        bisect_path = self.get_bisect_path()
 
83
        self.assertIsInstance(paths, list)
 
84
        bisect_path_idx = bisect_path(paths, path)
 
85
        split_path = self.split_for_dirblocks([path])[0]
 
86
        bisect_func, offset = self.get_bisect()
 
87
        bisect_split_idx = bisect_func(split_paths, split_path)
 
88
        self.assertEqual(bisect_split_idx, bisect_path_idx,
 
89
                         '%s disagreed. %s != %s'
 
90
                         ' for key %r'
 
91
                         % (bisect_path.__name__,
 
92
                            bisect_split_idx, bisect_path_idx, path)
 
93
                         )
 
94
        if exists:
 
95
            self.assertEqual(path, paths[bisect_path_idx+offset])
 
96
 
 
97
    def split_for_dirblocks(self, paths):
 
98
        dir_split_paths = []
 
99
        for path in paths:
 
100
            dirname, basename = os.path.split(path)
 
101
            dir_split_paths.append((dirname.split('/'), basename))
 
102
        dir_split_paths.sort()
 
103
        return dir_split_paths
 
104
 
 
105
    def test_simple(self):
 
106
        """In the simple case it works just like bisect_left"""
 
107
        paths = ['', 'a', 'b', 'c', 'd']
 
108
        split_paths = self.split_for_dirblocks(paths)
 
109
        for path in paths:
 
110
            self.assertBisect(paths, split_paths, path, exists=True)
 
111
        self.assertBisect(paths, split_paths, '_', exists=False)
 
112
        self.assertBisect(paths, split_paths, 'aa', exists=False)
 
113
        self.assertBisect(paths, split_paths, 'bb', exists=False)
 
114
        self.assertBisect(paths, split_paths, 'cc', exists=False)
 
115
        self.assertBisect(paths, split_paths, 'dd', exists=False)
 
116
        self.assertBisect(paths, split_paths, 'a/a', exists=False)
 
117
        self.assertBisect(paths, split_paths, 'b/b', exists=False)
 
118
        self.assertBisect(paths, split_paths, 'c/c', exists=False)
 
119
        self.assertBisect(paths, split_paths, 'd/d', exists=False)
 
120
 
 
121
    def test_involved(self):
 
122
        """This is where bisect_path_* diverges slightly."""
 
123
        # This is the list of paths and their contents
 
124
        # a/
 
125
        #   a/
 
126
        #     a
 
127
        #     z
 
128
        #   a-a/
 
129
        #     a
 
130
        #   a-z/
 
131
        #     z
 
132
        #   a=a/
 
133
        #     a
 
134
        #   a=z/
 
135
        #     z
 
136
        #   z/
 
137
        #     a
 
138
        #     z
 
139
        #   z-a
 
140
        #   z-z
 
141
        #   z=a
 
142
        #   z=z
 
143
        # a-a/
 
144
        #   a
 
145
        # a-z/
 
146
        #   z
 
147
        # a=a/
 
148
        #   a
 
149
        # a=z/
 
150
        #   z
 
151
        # This is the exact order that is stored by dirstate
 
152
        # All children in a directory are mentioned before an children of
 
153
        # children are mentioned.
 
154
        # So all the root-directory paths, then all the
 
155
        # first sub directory, etc.
 
156
        paths = [# content of '/'
 
157
                 '', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
 
158
                 # content of 'a/'
 
159
                 'a/a', 'a/a-a', 'a/a-z',
 
160
                 'a/a=a', 'a/a=z',
 
161
                 'a/z', 'a/z-a', 'a/z-z',
 
162
                 'a/z=a', 'a/z=z',
 
163
                 # content of 'a/a/'
 
164
                 'a/a/a', 'a/a/z',
 
165
                 # content of 'a/a-a'
 
166
                 'a/a-a/a',
 
167
                 # content of 'a/a-z'
 
168
                 'a/a-z/z',
 
169
                 # content of 'a/a=a'
 
170
                 'a/a=a/a',
 
171
                 # content of 'a/a=z'
 
172
                 'a/a=z/z',
 
173
                 # content of 'a/z/'
 
174
                 'a/z/a', 'a/z/z',
 
175
                 # content of 'a-a'
 
176
                 'a-a/a',
 
177
                 # content of 'a-z'
 
178
                 'a-z/z',
 
179
                 # content of 'a=a'
 
180
                 'a=a/a',
 
181
                 # content of 'a=z'
 
182
                 'a=z/z',
 
183
                ]
 
184
        split_paths = self.split_for_dirblocks(paths)
 
185
        sorted_paths = []
 
186
        for dir_parts, basename in split_paths:
 
187
            if dir_parts == ['']:
 
188
                sorted_paths.append(basename)
 
189
            else:
 
190
                sorted_paths.append('/'.join(dir_parts + [basename]))
 
191
 
 
192
        self.assertEqual(sorted_paths, paths)
 
193
 
 
194
        for path in paths:
 
195
            self.assertBisect(paths, split_paths, path, exists=True)
 
196
 
 
197
 
 
198
class TestBisectPathLeft(tests.TestCase, TestBisectPathMixin):
 
199
    """Run all Bisect Path tests against _bisect_path_left_py."""
 
200
 
 
201
    def get_bisect_path(self):
 
202
        from bzrlib._dirstate_helpers_py import _bisect_path_left_py
 
203
        return _bisect_path_left_py
 
204
 
 
205
    def get_bisect(self):
 
206
        return bisect.bisect_left, 0
 
207
 
 
208
 
 
209
class TestCompiledBisectPathLeft(TestBisectPathLeft):
 
210
    """Run all Bisect Path tests against _bisect_path_right_c"""
 
211
 
 
212
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
213
 
 
214
    def get_bisect_path(self):
 
215
        from bzrlib._dirstate_helpers_c import _bisect_path_left_c
 
216
        return _bisect_path_left_c
 
217
 
 
218
 
 
219
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
 
220
    """Run all Bisect Path tests against _bisect_path_right_py"""
 
221
 
 
222
    def get_bisect_path(self):
 
223
        from bzrlib._dirstate_helpers_py import _bisect_path_right_py
 
224
        return _bisect_path_right_py
 
225
 
 
226
    def get_bisect(self):
 
227
        return bisect.bisect_right, -1
 
228
 
 
229
 
 
230
class TestCompiledBisectPathRight(TestBisectPathRight):
 
231
    """Run all Bisect Path tests against _bisect_path_right_c"""
 
232
 
 
233
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
234
 
 
235
    def get_bisect_path(self):
 
236
        from bzrlib._dirstate_helpers_c import _bisect_path_right_c
 
237
        return _bisect_path_right_c
 
238
 
 
239
 
 
240
class TestBisectDirblock(tests.TestCase):
 
241
    """Test that bisect_dirblock() returns the expected values.
 
242
 
 
243
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
244
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
245
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
246
 
 
247
    This test is parameterized by calling get_bisect_dirblock(). Child test
 
248
    cases can override this function to test against a different
 
249
    implementation.
 
250
    """
 
251
 
 
252
    def get_bisect_dirblock(self):
 
253
        """Return an implementation of bisect_dirblock"""
 
254
        from bzrlib._dirstate_helpers_py import bisect_dirblock_py
 
255
        return bisect_dirblock_py
 
256
 
 
257
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
 
258
        """Assert that bisect_split works like bisect_left on the split paths.
 
259
 
 
260
        :param dirblocks: A list of (path, [info]) pairs.
 
261
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
 
262
        :param path: The path we are indexing.
 
263
 
 
264
        All other arguments will be passed along.
 
265
        """
 
266
        bisect_dirblock = self.get_bisect_dirblock()
 
267
        self.assertIsInstance(dirblocks, list)
 
268
        bisect_split_idx = bisect_dirblock(dirblocks, path, *args, **kwargs)
 
269
        split_dirblock = (path.split('/'), [])
 
270
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
 
271
                                             *args)
 
272
        self.assertEqual(bisect_left_idx, bisect_split_idx,
 
273
                         'bisect_split disagreed. %s != %s'
 
274
                         ' for key %r'
 
275
                         % (bisect_left_idx, bisect_split_idx, path)
 
276
                         )
 
277
 
 
278
    def paths_to_dirblocks(self, paths):
 
279
        """Convert a list of paths into dirblock form.
 
280
 
 
281
        Also, ensure that the paths are in proper sorted order.
 
282
        """
 
283
        dirblocks = [(path, []) for path in paths]
 
284
        split_dirblocks = [(path.split('/'), []) for path in paths]
 
285
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
 
286
        return dirblocks, split_dirblocks
 
287
 
 
288
    def test_simple(self):
 
289
        """In the simple case it works just like bisect_left"""
 
290
        paths = ['', 'a', 'b', 'c', 'd']
 
291
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
292
        for path in paths:
 
293
            self.assertBisect(dirblocks, split_dirblocks, path)
 
294
        self.assertBisect(dirblocks, split_dirblocks, '_')
 
295
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
 
296
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
 
297
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
 
298
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
 
299
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
 
300
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
 
301
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
 
302
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
 
303
 
 
304
    def test_involved(self):
 
305
        """This is where bisect_left diverges slightly."""
 
306
        paths = ['', 'a',
 
307
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
308
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
309
                 'a-a', 'a-z',
 
310
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
311
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
312
                 'z-a', 'z-z',
 
313
                ]
 
314
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
315
        for path in paths:
 
316
            self.assertBisect(dirblocks, split_dirblocks, path)
 
317
 
 
318
    def test_involved_cached(self):
 
319
        """This is where bisect_left diverges slightly."""
 
320
        paths = ['', 'a',
 
321
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
322
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
323
                 'a-a', 'a-z',
 
324
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
325
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
326
                 'z-a', 'z-z',
 
327
                ]
 
328
        cache = {}
 
329
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
330
        for path in paths:
 
331
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
 
332
 
 
333
 
 
334
class TestCompiledBisectDirblock(TestBisectDirblock):
 
335
    """Test that bisect_dirblock() returns the expected values.
 
336
 
 
337
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
338
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
339
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
340
 
 
341
    This runs all the normal tests that TestBisectDirblock did, but uses the
 
342
    compiled version.
 
343
    """
 
344
 
 
345
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
346
 
 
347
    def get_bisect_dirblock(self):
 
348
        from bzrlib._dirstate_helpers_c import bisect_dirblock_c
 
349
        return bisect_dirblock_c
 
350
 
 
351
 
 
352
class TestCmpByDirs(tests.TestCase):
 
353
    """Test an implementation of cmp_by_dirs()
 
354
 
 
355
    cmp_by_dirs() compares 2 paths by their directory sections, rather than as
 
356
    plain strings.
 
357
 
 
358
    Child test cases can override ``get_cmp_by_dirs`` to test a specific
 
359
    implementation.
 
360
    """
 
361
 
 
362
    def get_cmp_by_dirs(self):
 
363
        """Get a specific implementation of cmp_by_dirs."""
 
364
        from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
 
365
        return cmp_by_dirs_py
 
366
 
 
367
    def assertCmpByDirs(self, expected, str1, str2):
 
368
        """Compare the two strings, in both directions.
 
369
 
 
370
        :param expected: The expected comparison value. -1 means str1 comes
 
371
            first, 0 means they are equal, 1 means str2 comes first
 
372
        :param str1: string to compare
 
373
        :param str2: string to compare
 
374
        """
 
375
        cmp_by_dirs = self.get_cmp_by_dirs()
 
376
        if expected == 0:
 
377
            self.assertEqual(str1, str2)
 
378
            self.assertEqual(0, cmp_by_dirs(str1, str2))
 
379
            self.assertEqual(0, cmp_by_dirs(str2, str1))
 
380
        elif expected > 0:
 
381
            self.assertPositive(cmp_by_dirs(str1, str2))
 
382
            self.assertNegative(cmp_by_dirs(str2, str1))
 
383
        else:
 
384
            self.assertNegative(cmp_by_dirs(str1, str2))
 
385
            self.assertPositive(cmp_by_dirs(str2, str1))
 
386
 
 
387
    def test_cmp_empty(self):
 
388
        """Compare against the empty string."""
 
389
        self.assertCmpByDirs(0, '', '')
 
390
        self.assertCmpByDirs(1, 'a', '')
 
391
        self.assertCmpByDirs(1, 'ab', '')
 
392
        self.assertCmpByDirs(1, 'abc', '')
 
393
        self.assertCmpByDirs(1, 'abcd', '')
 
394
        self.assertCmpByDirs(1, 'abcde', '')
 
395
        self.assertCmpByDirs(1, 'abcdef', '')
 
396
        self.assertCmpByDirs(1, 'abcdefg', '')
 
397
        self.assertCmpByDirs(1, 'abcdefgh', '')
 
398
        self.assertCmpByDirs(1, 'abcdefghi', '')
 
399
        self.assertCmpByDirs(1, 'test/ing/a/path/', '')
 
400
 
 
401
    def test_cmp_same_str(self):
 
402
        """Compare the same string"""
 
403
        self.assertCmpByDirs(0, 'a', 'a')
 
404
        self.assertCmpByDirs(0, 'ab', 'ab')
 
405
        self.assertCmpByDirs(0, 'abc', 'abc')
 
406
        self.assertCmpByDirs(0, 'abcd', 'abcd')
 
407
        self.assertCmpByDirs(0, 'abcde', 'abcde')
 
408
        self.assertCmpByDirs(0, 'abcdef', 'abcdef')
 
409
        self.assertCmpByDirs(0, 'abcdefg', 'abcdefg')
 
410
        self.assertCmpByDirs(0, 'abcdefgh', 'abcdefgh')
 
411
        self.assertCmpByDirs(0, 'abcdefghi', 'abcdefghi')
 
412
        self.assertCmpByDirs(0, 'testing a long string', 'testing a long string')
 
413
        self.assertCmpByDirs(0, 'x'*10000, 'x'*10000)
 
414
        self.assertCmpByDirs(0, 'a/b', 'a/b')
 
415
        self.assertCmpByDirs(0, 'a/b/c', 'a/b/c')
 
416
        self.assertCmpByDirs(0, 'a/b/c/d', 'a/b/c/d')
 
417
        self.assertCmpByDirs(0, 'a/b/c/d/e', 'a/b/c/d/e')
 
418
 
 
419
    def test_simple_paths(self):
 
420
        """Compare strings that act like normal string comparison"""
 
421
        self.assertCmpByDirs(-1, 'a', 'b')
 
422
        self.assertCmpByDirs(-1, 'aa', 'ab')
 
423
        self.assertCmpByDirs(-1, 'ab', 'bb')
 
424
        self.assertCmpByDirs(-1, 'aaa', 'aab')
 
425
        self.assertCmpByDirs(-1, 'aab', 'abb')
 
426
        self.assertCmpByDirs(-1, 'abb', 'bbb')
 
427
        self.assertCmpByDirs(-1, 'aaaa', 'aaab')
 
428
        self.assertCmpByDirs(-1, 'aaab', 'aabb')
 
429
        self.assertCmpByDirs(-1, 'aabb', 'abbb')
 
430
        self.assertCmpByDirs(-1, 'abbb', 'bbbb')
 
431
        self.assertCmpByDirs(-1, 'aaaaa', 'aaaab')
 
432
        self.assertCmpByDirs(-1, 'a/a', 'a/b')
 
433
        self.assertCmpByDirs(-1, 'a/b', 'b/b')
 
434
        self.assertCmpByDirs(-1, 'a/a/a', 'a/a/b')
 
435
        self.assertCmpByDirs(-1, 'a/a/b', 'a/b/b')
 
436
        self.assertCmpByDirs(-1, 'a/b/b', 'b/b/b')
 
437
        self.assertCmpByDirs(-1, 'a/a/a/a', 'a/a/a/b')
 
438
        self.assertCmpByDirs(-1, 'a/a/a/b', 'a/a/b/b')
 
439
        self.assertCmpByDirs(-1, 'a/a/b/b', 'a/b/b/b')
 
440
        self.assertCmpByDirs(-1, 'a/b/b/b', 'b/b/b/b')
 
441
        self.assertCmpByDirs(-1, 'a/a/a/a/a', 'a/a/a/a/b')
 
442
 
 
443
    def test_tricky_paths(self):
 
444
        self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/cc/ef')
 
445
        self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/c/ef')
 
446
        self.assertCmpByDirs(-1, 'ab/cd/ef', 'ab/cd-ef')
 
447
        self.assertCmpByDirs(-1, 'ab/cd', 'ab/cd-')
 
448
        self.assertCmpByDirs(-1, 'ab/cd', 'ab-cd')
 
449
 
 
450
    def test_cmp_unicode_not_allowed(self):
 
451
        cmp_by_dirs = self.get_cmp_by_dirs()
 
452
        self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', 'str')
 
453
        self.assertRaises(TypeError, cmp_by_dirs, 'str', u'Unicode')
 
454
        self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', u'Unicode')
 
455
 
 
456
    def test_cmp_non_ascii(self):
 
457
        self.assertCmpByDirs(-1, '\xc2\xb5', '\xc3\xa5') # u'\xb5', u'\xe5'
 
458
        self.assertCmpByDirs(-1, 'a', '\xc3\xa5') # u'a', u'\xe5'
 
459
        self.assertCmpByDirs(-1, 'b', '\xc2\xb5') # u'b', u'\xb5'
 
460
        self.assertCmpByDirs(-1, 'a/b', 'a/\xc3\xa5') # u'a/b', u'a/\xe5'
 
461
        self.assertCmpByDirs(-1, 'b/a', 'b/\xc2\xb5') # u'b/a', u'b/\xb5'
 
462
 
 
463
 
 
464
class TestCompiledCmpByDirs(TestCmpByDirs):
 
465
    """Test the pyrex implementation of cmp_by_dirs"""
 
466
 
 
467
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
468
 
 
469
    def get_cmp_by_dirs(self):
 
470
        from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
 
471
        return cmp_by_dirs_c
 
472
 
 
473
 
 
474
class TestCmpPathByDirblock(tests.TestCase):
 
475
    """Test an implementation of _cmp_path_by_dirblock()
 
476
 
 
477
    _cmp_path_by_dirblock() compares two paths using the sort order used by
 
478
    DirState. All paths in the same directory are sorted together.
 
479
 
 
480
    Child test cases can override ``get_cmp_path_by_dirblock`` to test a specific
 
481
    implementation.
 
482
    """
 
483
 
 
484
    def get_cmp_path_by_dirblock(self):
 
485
        """Get a specific implementation of _cmp_path_by_dirblock."""
 
486
        from bzrlib._dirstate_helpers_py import _cmp_path_by_dirblock_py
 
487
        return _cmp_path_by_dirblock_py
 
488
 
 
489
    def assertCmpPathByDirblock(self, paths):
 
490
        """Compare all paths and make sure they evaluate to the correct order.
 
491
 
 
492
        This does N^2 comparisons. It is assumed that ``paths`` is properly
 
493
        sorted list.
 
494
 
 
495
        :param paths: a sorted list of paths to compare
 
496
        """
 
497
        # First, make sure the paths being passed in are correct
 
498
        def _key(p):
 
499
            dirname, basename = os.path.split(p)
 
500
            return dirname.split('/'), basename
 
501
        self.assertEqual(sorted(paths, key=_key), paths)
 
502
 
 
503
        cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
 
504
        for idx1, path1 in enumerate(paths):
 
505
            for idx2, path2 in enumerate(paths):
 
506
                cmp_val = cmp_path_by_dirblock(path1, path2)
 
507
                if idx1 < idx2:
 
508
                    self.assertTrue(cmp_val < 0,
 
509
                        '%s did not state that %r came before %r, cmp=%s'
 
510
                        % (cmp_path_by_dirblock.__name__,
 
511
                           path1, path2, cmp_val))
 
512
                elif idx1 > idx2:
 
513
                    self.assertTrue(cmp_val > 0,
 
514
                        '%s did not state that %r came after %r, cmp=%s'
 
515
                        % (cmp_path_by_dirblock.__name__,
 
516
                           path1, path2, cmp_val))
 
517
                else: # idx1 == idx2
 
518
                    self.assertTrue(cmp_val == 0,
 
519
                        '%s did not state that %r == %r, cmp=%s'
 
520
                        % (cmp_path_by_dirblock.__name__,
 
521
                           path1, path2, cmp_val))
 
522
 
 
523
    def test_cmp_simple_paths(self):
 
524
        """Compare against the empty string."""
 
525
        self.assertCmpPathByDirblock(['', 'a', 'ab', 'abc', 'a/b/c', 'b/d/e'])
 
526
        self.assertCmpPathByDirblock(['kl', 'ab/cd', 'ab/ef', 'gh/ij'])
 
527
 
 
528
    def test_tricky_paths(self):
 
529
        self.assertCmpPathByDirblock([
 
530
            # Contents of ''
 
531
            '', 'a', 'a-a', 'a=a', 'b',
 
532
            # Contents of 'a'
 
533
            'a/a', 'a/a-a', 'a/a=a', 'a/b',
 
534
            # Contents of 'a/a'
 
535
            'a/a/a', 'a/a/a-a', 'a/a/a=a',
 
536
            # Contents of 'a/a/a'
 
537
            'a/a/a/a', 'a/a/a/b',
 
538
            # Contents of 'a/a/a-a',
 
539
            'a/a/a-a/a', 'a/a/a-a/b',
 
540
            # Contents of 'a/a/a=a',
 
541
            'a/a/a=a/a', 'a/a/a=a/b',
 
542
            # Contents of 'a/a-a'
 
543
            'a/a-a/a',
 
544
            # Contents of 'a/a-a/a'
 
545
            'a/a-a/a/a', 'a/a-a/a/b',
 
546
            # Contents of 'a/a=a'
 
547
            'a/a=a/a',
 
548
            # Contents of 'a/b'
 
549
            'a/b/a', 'a/b/b',
 
550
            # Contents of 'a-a',
 
551
            'a-a/a', 'a-a/b',
 
552
            # Contents of 'a=a',
 
553
            'a=a/a', 'a=a/b',
 
554
            # Contents of 'b',
 
555
            'b/a', 'b/b',
 
556
            ])
 
557
        self.assertCmpPathByDirblock([
 
558
                 # content of '/'
 
559
                 '', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
 
560
                 # content of 'a/'
 
561
                 'a/a', 'a/a-a', 'a/a-z',
 
562
                 'a/a=a', 'a/a=z',
 
563
                 'a/z', 'a/z-a', 'a/z-z',
 
564
                 'a/z=a', 'a/z=z',
 
565
                 # content of 'a/a/'
 
566
                 'a/a/a', 'a/a/z',
 
567
                 # content of 'a/a-a'
 
568
                 'a/a-a/a',
 
569
                 # content of 'a/a-z'
 
570
                 'a/a-z/z',
 
571
                 # content of 'a/a=a'
 
572
                 'a/a=a/a',
 
573
                 # content of 'a/a=z'
 
574
                 'a/a=z/z',
 
575
                 # content of 'a/z/'
 
576
                 'a/z/a', 'a/z/z',
 
577
                 # content of 'a-a'
 
578
                 'a-a/a',
 
579
                 # content of 'a-z'
 
580
                 'a-z/z',
 
581
                 # content of 'a=a'
 
582
                 'a=a/a',
 
583
                 # content of 'a=z'
 
584
                 'a=z/z',
 
585
                ])
 
586
 
 
587
    def test_unicode_not_allowed(self):
 
588
        cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
 
589
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', 'str')
 
590
        self.assertRaises(TypeError, cmp_path_by_dirblock, 'str', u'Uni')
 
591
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', u'Uni')
 
592
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', 'x/str')
 
593
        self.assertRaises(TypeError, cmp_path_by_dirblock, 'x/str', u'x/Uni')
 
594
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', u'x/Uni')
 
595
 
 
596
    def test_nonascii(self):
 
597
        self.assertCmpPathByDirblock([
 
598
            # content of '/'
 
599
            '', 'a', '\xc2\xb5', '\xc3\xa5',
 
600
            # content of 'a'
 
601
            'a/a', 'a/\xc2\xb5', 'a/\xc3\xa5',
 
602
            # content of 'a/a'
 
603
            'a/a/a', 'a/a/\xc2\xb5', 'a/a/\xc3\xa5',
 
604
            # content of 'a/\xc2\xb5'
 
605
            'a/\xc2\xb5/a', 'a/\xc2\xb5/\xc2\xb5', 'a/\xc2\xb5/\xc3\xa5',
 
606
            # content of 'a/\xc3\xa5'
 
607
            'a/\xc3\xa5/a', 'a/\xc3\xa5/\xc2\xb5', 'a/\xc3\xa5/\xc3\xa5',
 
608
            # content of '\xc2\xb5'
 
609
            '\xc2\xb5/a', '\xc2\xb5/\xc2\xb5', '\xc2\xb5/\xc3\xa5',
 
610
            # content of '\xc2\xe5'
 
611
            '\xc3\xa5/a', '\xc3\xa5/\xc2\xb5', '\xc3\xa5/\xc3\xa5',
 
612
            ])
 
613
 
 
614
 
 
615
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
 
616
    """Test the pyrex implementation of _cmp_path_by_dirblock"""
 
617
 
 
618
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
619
 
 
620
    def get_cmp_by_dirs(self):
 
621
        from bzrlib._dirstate_helpers_c import _cmp_path_by_dirblock_c
 
622
        return _cmp_path_by_dirblock_c
 
623
 
 
624
 
 
625
class TestMemRChr(tests.TestCase):
 
626
    """Test memrchr functionality"""
 
627
 
 
628
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
629
 
 
630
    def assertMemRChr(self, expected, s, c):
 
631
        from bzrlib._dirstate_helpers_c import _py_memrchr
 
632
        self.assertEqual(expected, _py_memrchr(s, c))
 
633
 
 
634
    def test_missing(self):
 
635
        self.assertMemRChr(None, '', 'a')
 
636
        self.assertMemRChr(None, '', 'c')
 
637
        self.assertMemRChr(None, 'abcdefghijklm', 'q')
 
638
        self.assertMemRChr(None, 'aaaaaaaaaaaaaaaaaaaaaaa', 'b')
 
639
 
 
640
    def test_single_entry(self):
 
641
        self.assertMemRChr(0, 'abcdefghijklm', 'a')
 
642
        self.assertMemRChr(1, 'abcdefghijklm', 'b')
 
643
        self.assertMemRChr(2, 'abcdefghijklm', 'c')
 
644
        self.assertMemRChr(10, 'abcdefghijklm', 'k')
 
645
        self.assertMemRChr(11, 'abcdefghijklm', 'l')
 
646
        self.assertMemRChr(12, 'abcdefghijklm', 'm')
 
647
 
 
648
    def test_multiple(self):
 
649
        self.assertMemRChr(10, 'abcdefjklmabcdefghijklm', 'a')
 
650
        self.assertMemRChr(11, 'abcdefjklmabcdefghijklm', 'b')
 
651
        self.assertMemRChr(12, 'abcdefjklmabcdefghijklm', 'c')
 
652
        self.assertMemRChr(20, 'abcdefjklmabcdefghijklm', 'k')
 
653
        self.assertMemRChr(21, 'abcdefjklmabcdefghijklm', 'l')
 
654
        self.assertMemRChr(22, 'abcdefjklmabcdefghijklm', 'm')
 
655
        self.assertMemRChr(22, 'aaaaaaaaaaaaaaaaaaaaaaa', 'a')
 
656
 
 
657
    def test_with_nulls(self):
 
658
        self.assertMemRChr(10, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'a')
 
659
        self.assertMemRChr(11, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'b')
 
660
        self.assertMemRChr(12, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'c')
 
661
        self.assertMemRChr(20, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'k')
 
662
        self.assertMemRChr(21, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'l')
 
663
        self.assertMemRChr(22, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'm')
 
664
        self.assertMemRChr(22, 'aaa\0\0\0aaaaaaa\0\0\0aaaaaaa', 'a')
 
665
        self.assertMemRChr(9, '\0\0\0\0\0\0\0\0\0\0', '\0')
 
666
 
 
667
 
 
668
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
 
669
    """Test an implementation of _read_dirblocks()
 
670
 
 
671
    _read_dirblocks() reads in all of the dirblock information from the disk
 
672
    file.
 
673
 
 
674
    Child test cases can override ``get_read_dirblocks`` to test a specific
 
675
    implementation.
 
676
    """
 
677
 
 
678
    def get_read_dirblocks(self):
 
679
        from bzrlib._dirstate_helpers_py import _read_dirblocks_py
 
680
        return _read_dirblocks_py
 
681
 
 
682
    def test_smoketest(self):
 
683
        """Make sure that we can create and read back a simple file."""
 
684
        tree, state, expected = self.create_basic_dirstate()
 
685
        del tree
 
686
        state._read_header_if_needed()
 
687
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
688
                         state._dirblock_state)
 
689
        read_dirblocks = self.get_read_dirblocks()
 
690
        read_dirblocks(state)
 
691
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
692
                         state._dirblock_state)
 
693
 
 
694
 
 
695
class TestCompiledReadDirblocks(TestReadDirblocks):
 
696
    """Test the pyrex implementation of _read_dirblocks"""
 
697
 
 
698
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
699
 
 
700
    def get_read_dirblocks(self):
 
701
        from bzrlib._dirstate_helpers_c import _read_dirblocks_c
 
702
        return _read_dirblocks_c
 
703
 
 
704
 
 
705
class TestUsingCompiledIfAvailable(tests.TestCase):
 
706
    """Check that any compiled functions that are available are the default.
 
707
 
 
708
    It is possible to have typos, etc in the import line, such that
 
709
    _dirstate_helpers_c is actually available, but the compiled functions are
 
710
    not being used.
 
711
    """
 
712
 
 
713
    def test_bisect_dirblock(self):
 
714
        if CompiledDirstateHelpersFeature.available():
 
715
            from bzrlib._dirstate_helpers_c import bisect_dirblock_c
 
716
            self.assertIs(bisect_dirblock_c, dirstate.bisect_dirblock)
 
717
        else:
 
718
            from bzrlib._dirstate_helpers_py import bisect_dirblock_py
 
719
            self.assertIs(bisect_dirblock_py, dirstate.bisect_dirblock)
 
720
 
 
721
    def test__bisect_path_left(self):
 
722
        if CompiledDirstateHelpersFeature.available():
 
723
            from bzrlib._dirstate_helpers_c import _bisect_path_left_c
 
724
            self.assertIs(_bisect_path_left_c, dirstate._bisect_path_left)
 
725
        else:
 
726
            from bzrlib._dirstate_helpers_py import _bisect_path_left_py
 
727
            self.assertIs(_bisect_path_left_py, dirstate._bisect_path_left)
 
728
 
 
729
    def test__bisect_path_right(self):
 
730
        if CompiledDirstateHelpersFeature.available():
 
731
            from bzrlib._dirstate_helpers_c import _bisect_path_right_c
 
732
            self.assertIs(_bisect_path_right_c, dirstate._bisect_path_right)
 
733
        else:
 
734
            from bzrlib._dirstate_helpers_py import _bisect_path_right_py
 
735
            self.assertIs(_bisect_path_right_py, dirstate._bisect_path_right)
 
736
 
 
737
    def test_cmp_by_dirs(self):
 
738
        if CompiledDirstateHelpersFeature.available():
 
739
            from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
 
740
            self.assertIs(cmp_by_dirs_c, dirstate.cmp_by_dirs)
 
741
        else:
 
742
            from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
 
743
            self.assertIs(cmp_by_dirs_py, dirstate.cmp_by_dirs)
 
744
 
 
745
    def test__read_dirblocks(self):
 
746
        if CompiledDirstateHelpersFeature.available():
 
747
            from bzrlib._dirstate_helpers_c import _read_dirblocks_c
 
748
            self.assertIs(_read_dirblocks_c, dirstate._read_dirblocks)
 
749
        else:
 
750
            from bzrlib._dirstate_helpers_py import _read_dirblocks_py
 
751
            self.assertIs(_read_dirblocks_py, dirstate._read_dirblocks)
 
752