1
# Copyright (C) 2007, 2008 Canonical Ltd
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.
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.
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
17
"""Tests for the compiled dirstate helpers."""
27
from bzrlib.tests import (
30
from bzrlib.tests import test_dirstate
33
class _CompiledDirstateHelpersFeature(tests.Feature):
36
import bzrlib._dirstate_helpers_c
41
def feature_name(self):
42
return 'bzrlib._dirstate_helpers_c'
44
CompiledDirstateHelpersFeature = _CompiledDirstateHelpersFeature()
47
class TestBisectPathMixin(object):
48
"""Test that _bisect_path_*() returns the expected values.
50
_bisect_path_* is intended to work like bisect.bisect_*() except it
51
knows it is working on paths that are sorted by ('path', 'to', 'foo')
52
chunks rather than by raw 'path/to/foo'.
54
Test Cases should inherit from this and override ``get_bisect_path`` return
55
their implementation, and ``get_bisect`` to return the matching
56
bisect.bisect_* function.
59
def get_bisect_path(self):
60
"""Return an implementation of _bisect_path_*"""
61
raise NotImplementedError
64
"""Return a version of bisect.bisect_*.
66
Also, for the 'exists' check, return the offset to the real values.
67
For example bisect_left returns the index of an entry, while
68
bisect_right returns the index *after* an entry
70
:return: (bisect_func, offset)
72
raise NotImplementedError
74
def assertBisect(self, paths, split_paths, path, exists=True):
75
"""Assert that bisect_split works like bisect_left on the split paths.
77
:param paths: A list of path names
78
:param split_paths: A list of path names that are already split up by directory
79
('path/to/foo' => ('path', 'to', 'foo'))
80
:param path: The path we are indexing.
81
:param exists: The path should be present, so make sure the
82
final location actually points to the right value.
84
All other arguments will be passed along.
86
bisect_path = self.get_bisect_path()
87
self.assertIsInstance(paths, list)
88
bisect_path_idx = bisect_path(paths, path)
89
split_path = self.split_for_dirblocks([path])[0]
90
bisect_func, offset = self.get_bisect()
91
bisect_split_idx = bisect_func(split_paths, split_path)
92
self.assertEqual(bisect_split_idx, bisect_path_idx,
93
'%s disagreed. %s != %s'
95
% (bisect_path.__name__,
96
bisect_split_idx, bisect_path_idx, path)
99
self.assertEqual(path, paths[bisect_path_idx+offset])
101
def split_for_dirblocks(self, paths):
104
dirname, basename = os.path.split(path)
105
dir_split_paths.append((dirname.split('/'), basename))
106
dir_split_paths.sort()
107
return dir_split_paths
109
def test_simple(self):
110
"""In the simple case it works just like bisect_left"""
111
paths = ['', 'a', 'b', 'c', 'd']
112
split_paths = self.split_for_dirblocks(paths)
114
self.assertBisect(paths, split_paths, path, exists=True)
115
self.assertBisect(paths, split_paths, '_', exists=False)
116
self.assertBisect(paths, split_paths, 'aa', exists=False)
117
self.assertBisect(paths, split_paths, 'bb', exists=False)
118
self.assertBisect(paths, split_paths, 'cc', exists=False)
119
self.assertBisect(paths, split_paths, 'dd', exists=False)
120
self.assertBisect(paths, split_paths, 'a/a', exists=False)
121
self.assertBisect(paths, split_paths, 'b/b', exists=False)
122
self.assertBisect(paths, split_paths, 'c/c', exists=False)
123
self.assertBisect(paths, split_paths, 'd/d', exists=False)
125
def test_involved(self):
126
"""This is where bisect_path_* diverges slightly."""
127
# This is the list of paths and their contents
155
# This is the exact order that is stored by dirstate
156
# All children in a directory are mentioned before an children of
157
# children are mentioned.
158
# So all the root-directory paths, then all the
159
# first sub directory, etc.
160
paths = [# content of '/'
161
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
163
'a/a', 'a/a-a', 'a/a-z',
165
'a/z', 'a/z-a', 'a/z-z',
188
split_paths = self.split_for_dirblocks(paths)
190
for dir_parts, basename in split_paths:
191
if dir_parts == ['']:
192
sorted_paths.append(basename)
194
sorted_paths.append('/'.join(dir_parts + [basename]))
196
self.assertEqual(sorted_paths, paths)
199
self.assertBisect(paths, split_paths, path, exists=True)
202
class TestBisectPathLeft(tests.TestCase, TestBisectPathMixin):
203
"""Run all Bisect Path tests against _bisect_path_left_py."""
205
def get_bisect_path(self):
206
from bzrlib._dirstate_helpers_py import _bisect_path_left_py
207
return _bisect_path_left_py
209
def get_bisect(self):
210
return bisect.bisect_left, 0
213
class TestCompiledBisectPathLeft(TestBisectPathLeft):
214
"""Run all Bisect Path tests against _bisect_path_right_c"""
216
_test_needs_features = [CompiledDirstateHelpersFeature]
218
def get_bisect_path(self):
219
from bzrlib._dirstate_helpers_c import _bisect_path_left_c
220
return _bisect_path_left_c
223
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
224
"""Run all Bisect Path tests against _bisect_path_right_py"""
226
def get_bisect_path(self):
227
from bzrlib._dirstate_helpers_py import _bisect_path_right_py
228
return _bisect_path_right_py
230
def get_bisect(self):
231
return bisect.bisect_right, -1
234
class TestCompiledBisectPathRight(TestBisectPathRight):
235
"""Run all Bisect Path tests against _bisect_path_right_c"""
237
_test_needs_features = [CompiledDirstateHelpersFeature]
239
def get_bisect_path(self):
240
from bzrlib._dirstate_helpers_c import _bisect_path_right_c
241
return _bisect_path_right_c
244
class TestBisectDirblock(tests.TestCase):
245
"""Test that bisect_dirblock() returns the expected values.
247
bisect_dirblock is intended to work like bisect.bisect_left() except it
248
knows it is working on dirblocks and that dirblocks are sorted by ('path',
249
'to', 'foo') chunks rather than by raw 'path/to/foo'.
251
This test is parameterized by calling get_bisect_dirblock(). Child test
252
cases can override this function to test against a different
256
def get_bisect_dirblock(self):
257
"""Return an implementation of bisect_dirblock"""
258
from bzrlib._dirstate_helpers_py import bisect_dirblock_py
259
return bisect_dirblock_py
261
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
262
"""Assert that bisect_split works like bisect_left on the split paths.
264
:param dirblocks: A list of (path, [info]) pairs.
265
:param split_dirblocks: A list of ((split, path), [info]) pairs.
266
:param path: The path we are indexing.
268
All other arguments will be passed along.
270
bisect_dirblock = self.get_bisect_dirblock()
271
self.assertIsInstance(dirblocks, list)
272
bisect_split_idx = bisect_dirblock(dirblocks, path, *args, **kwargs)
273
split_dirblock = (path.split('/'), [])
274
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
276
self.assertEqual(bisect_left_idx, bisect_split_idx,
277
'bisect_split disagreed. %s != %s'
279
% (bisect_left_idx, bisect_split_idx, path)
282
def paths_to_dirblocks(self, paths):
283
"""Convert a list of paths into dirblock form.
285
Also, ensure that the paths are in proper sorted order.
287
dirblocks = [(path, []) for path in paths]
288
split_dirblocks = [(path.split('/'), []) for path in paths]
289
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
290
return dirblocks, split_dirblocks
292
def test_simple(self):
293
"""In the simple case it works just like bisect_left"""
294
paths = ['', 'a', 'b', 'c', 'd']
295
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
297
self.assertBisect(dirblocks, split_dirblocks, path)
298
self.assertBisect(dirblocks, split_dirblocks, '_')
299
self.assertBisect(dirblocks, split_dirblocks, 'aa')
300
self.assertBisect(dirblocks, split_dirblocks, 'bb')
301
self.assertBisect(dirblocks, split_dirblocks, 'cc')
302
self.assertBisect(dirblocks, split_dirblocks, 'dd')
303
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
304
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
305
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
306
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
308
def test_involved(self):
309
"""This is where bisect_left diverges slightly."""
311
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
312
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
314
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
315
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
318
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
320
self.assertBisect(dirblocks, split_dirblocks, path)
322
def test_involved_cached(self):
323
"""This is where bisect_left diverges slightly."""
325
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
326
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
328
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
329
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
333
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
335
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
338
class TestCompiledBisectDirblock(TestBisectDirblock):
339
"""Test that bisect_dirblock() returns the expected values.
341
bisect_dirblock is intended to work like bisect.bisect_left() except it
342
knows it is working on dirblocks and that dirblocks are sorted by ('path',
343
'to', 'foo') chunks rather than by raw 'path/to/foo'.
345
This runs all the normal tests that TestBisectDirblock did, but uses the
349
_test_needs_features = [CompiledDirstateHelpersFeature]
351
def get_bisect_dirblock(self):
352
from bzrlib._dirstate_helpers_c import bisect_dirblock_c
353
return bisect_dirblock_c
356
class TestCmpByDirs(tests.TestCase):
357
"""Test an implementation of cmp_by_dirs()
359
cmp_by_dirs() compares 2 paths by their directory sections, rather than as
362
Child test cases can override ``get_cmp_by_dirs`` to test a specific
366
def get_cmp_by_dirs(self):
367
"""Get a specific implementation of cmp_by_dirs."""
368
from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
369
return cmp_by_dirs_py
371
def assertCmpByDirs(self, expected, str1, str2):
372
"""Compare the two strings, in both directions.
374
:param expected: The expected comparison value. -1 means str1 comes
375
first, 0 means they are equal, 1 means str2 comes first
376
:param str1: string to compare
377
:param str2: string to compare
379
cmp_by_dirs = self.get_cmp_by_dirs()
381
self.assertEqual(str1, str2)
382
self.assertEqual(0, cmp_by_dirs(str1, str2))
383
self.assertEqual(0, cmp_by_dirs(str2, str1))
385
self.assertPositive(cmp_by_dirs(str1, str2))
386
self.assertNegative(cmp_by_dirs(str2, str1))
388
self.assertNegative(cmp_by_dirs(str1, str2))
389
self.assertPositive(cmp_by_dirs(str2, str1))
391
def test_cmp_empty(self):
392
"""Compare against the empty string."""
393
self.assertCmpByDirs(0, '', '')
394
self.assertCmpByDirs(1, 'a', '')
395
self.assertCmpByDirs(1, 'ab', '')
396
self.assertCmpByDirs(1, 'abc', '')
397
self.assertCmpByDirs(1, 'abcd', '')
398
self.assertCmpByDirs(1, 'abcde', '')
399
self.assertCmpByDirs(1, 'abcdef', '')
400
self.assertCmpByDirs(1, 'abcdefg', '')
401
self.assertCmpByDirs(1, 'abcdefgh', '')
402
self.assertCmpByDirs(1, 'abcdefghi', '')
403
self.assertCmpByDirs(1, 'test/ing/a/path/', '')
405
def test_cmp_same_str(self):
406
"""Compare the same string"""
407
self.assertCmpByDirs(0, 'a', 'a')
408
self.assertCmpByDirs(0, 'ab', 'ab')
409
self.assertCmpByDirs(0, 'abc', 'abc')
410
self.assertCmpByDirs(0, 'abcd', 'abcd')
411
self.assertCmpByDirs(0, 'abcde', 'abcde')
412
self.assertCmpByDirs(0, 'abcdef', 'abcdef')
413
self.assertCmpByDirs(0, 'abcdefg', 'abcdefg')
414
self.assertCmpByDirs(0, 'abcdefgh', 'abcdefgh')
415
self.assertCmpByDirs(0, 'abcdefghi', 'abcdefghi')
416
self.assertCmpByDirs(0, 'testing a long string', 'testing a long string')
417
self.assertCmpByDirs(0, 'x'*10000, 'x'*10000)
418
self.assertCmpByDirs(0, 'a/b', 'a/b')
419
self.assertCmpByDirs(0, 'a/b/c', 'a/b/c')
420
self.assertCmpByDirs(0, 'a/b/c/d', 'a/b/c/d')
421
self.assertCmpByDirs(0, 'a/b/c/d/e', 'a/b/c/d/e')
423
def test_simple_paths(self):
424
"""Compare strings that act like normal string comparison"""
425
self.assertCmpByDirs(-1, 'a', 'b')
426
self.assertCmpByDirs(-1, 'aa', 'ab')
427
self.assertCmpByDirs(-1, 'ab', 'bb')
428
self.assertCmpByDirs(-1, 'aaa', 'aab')
429
self.assertCmpByDirs(-1, 'aab', 'abb')
430
self.assertCmpByDirs(-1, 'abb', 'bbb')
431
self.assertCmpByDirs(-1, 'aaaa', 'aaab')
432
self.assertCmpByDirs(-1, 'aaab', 'aabb')
433
self.assertCmpByDirs(-1, 'aabb', 'abbb')
434
self.assertCmpByDirs(-1, 'abbb', 'bbbb')
435
self.assertCmpByDirs(-1, 'aaaaa', 'aaaab')
436
self.assertCmpByDirs(-1, 'a/a', 'a/b')
437
self.assertCmpByDirs(-1, 'a/b', 'b/b')
438
self.assertCmpByDirs(-1, 'a/a/a', 'a/a/b')
439
self.assertCmpByDirs(-1, 'a/a/b', 'a/b/b')
440
self.assertCmpByDirs(-1, 'a/b/b', 'b/b/b')
441
self.assertCmpByDirs(-1, 'a/a/a/a', 'a/a/a/b')
442
self.assertCmpByDirs(-1, 'a/a/a/b', 'a/a/b/b')
443
self.assertCmpByDirs(-1, 'a/a/b/b', 'a/b/b/b')
444
self.assertCmpByDirs(-1, 'a/b/b/b', 'b/b/b/b')
445
self.assertCmpByDirs(-1, 'a/a/a/a/a', 'a/a/a/a/b')
447
def test_tricky_paths(self):
448
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/cc/ef')
449
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/c/ef')
450
self.assertCmpByDirs(-1, 'ab/cd/ef', 'ab/cd-ef')
451
self.assertCmpByDirs(-1, 'ab/cd', 'ab/cd-')
452
self.assertCmpByDirs(-1, 'ab/cd', 'ab-cd')
454
def test_cmp_unicode_not_allowed(self):
455
cmp_by_dirs = self.get_cmp_by_dirs()
456
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', 'str')
457
self.assertRaises(TypeError, cmp_by_dirs, 'str', u'Unicode')
458
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', u'Unicode')
460
def test_cmp_non_ascii(self):
461
self.assertCmpByDirs(-1, '\xc2\xb5', '\xc3\xa5') # u'\xb5', u'\xe5'
462
self.assertCmpByDirs(-1, 'a', '\xc3\xa5') # u'a', u'\xe5'
463
self.assertCmpByDirs(-1, 'b', '\xc2\xb5') # u'b', u'\xb5'
464
self.assertCmpByDirs(-1, 'a/b', 'a/\xc3\xa5') # u'a/b', u'a/\xe5'
465
self.assertCmpByDirs(-1, 'b/a', 'b/\xc2\xb5') # u'b/a', u'b/\xb5'
468
class TestCompiledCmpByDirs(TestCmpByDirs):
469
"""Test the pyrex implementation of cmp_by_dirs"""
471
_test_needs_features = [CompiledDirstateHelpersFeature]
473
def get_cmp_by_dirs(self):
474
from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
478
class TestCmpPathByDirblock(tests.TestCase):
479
"""Test an implementation of _cmp_path_by_dirblock()
481
_cmp_path_by_dirblock() compares two paths using the sort order used by
482
DirState. All paths in the same directory are sorted together.
484
Child test cases can override ``get_cmp_path_by_dirblock`` to test a specific
488
def get_cmp_path_by_dirblock(self):
489
"""Get a specific implementation of _cmp_path_by_dirblock."""
490
from bzrlib._dirstate_helpers_py import _cmp_path_by_dirblock_py
491
return _cmp_path_by_dirblock_py
493
def assertCmpPathByDirblock(self, paths):
494
"""Compare all paths and make sure they evaluate to the correct order.
496
This does N^2 comparisons. It is assumed that ``paths`` is properly
499
:param paths: a sorted list of paths to compare
501
# First, make sure the paths being passed in are correct
503
dirname, basename = os.path.split(p)
504
return dirname.split('/'), basename
505
self.assertEqual(sorted(paths, key=_key), paths)
507
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
508
for idx1, path1 in enumerate(paths):
509
for idx2, path2 in enumerate(paths):
510
cmp_val = cmp_path_by_dirblock(path1, path2)
512
self.assertTrue(cmp_val < 0,
513
'%s did not state that %r came before %r, cmp=%s'
514
% (cmp_path_by_dirblock.__name__,
515
path1, path2, cmp_val))
517
self.assertTrue(cmp_val > 0,
518
'%s did not state that %r came after %r, cmp=%s'
519
% (cmp_path_by_dirblock.__name__,
520
path1, path2, cmp_val))
522
self.assertTrue(cmp_val == 0,
523
'%s did not state that %r == %r, cmp=%s'
524
% (cmp_path_by_dirblock.__name__,
525
path1, path2, cmp_val))
527
def test_cmp_simple_paths(self):
528
"""Compare against the empty string."""
529
self.assertCmpPathByDirblock(['', 'a', 'ab', 'abc', 'a/b/c', 'b/d/e'])
530
self.assertCmpPathByDirblock(['kl', 'ab/cd', 'ab/ef', 'gh/ij'])
532
def test_tricky_paths(self):
533
self.assertCmpPathByDirblock([
535
'', 'a', 'a-a', 'a=a', 'b',
537
'a/a', 'a/a-a', 'a/a=a', 'a/b',
539
'a/a/a', 'a/a/a-a', 'a/a/a=a',
540
# Contents of 'a/a/a'
541
'a/a/a/a', 'a/a/a/b',
542
# Contents of 'a/a/a-a',
543
'a/a/a-a/a', 'a/a/a-a/b',
544
# Contents of 'a/a/a=a',
545
'a/a/a=a/a', 'a/a/a=a/b',
546
# Contents of 'a/a-a'
548
# Contents of 'a/a-a/a'
549
'a/a-a/a/a', 'a/a-a/a/b',
550
# Contents of 'a/a=a'
561
self.assertCmpPathByDirblock([
563
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
565
'a/a', 'a/a-a', 'a/a-z',
567
'a/z', 'a/z-a', 'a/z-z',
591
def test_unicode_not_allowed(self):
592
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
593
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', 'str')
594
self.assertRaises(TypeError, cmp_path_by_dirblock, 'str', u'Uni')
595
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', u'Uni')
596
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', 'x/str')
597
self.assertRaises(TypeError, cmp_path_by_dirblock, 'x/str', u'x/Uni')
598
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', u'x/Uni')
600
def test_nonascii(self):
601
self.assertCmpPathByDirblock([
603
'', 'a', '\xc2\xb5', '\xc3\xa5',
605
'a/a', 'a/\xc2\xb5', 'a/\xc3\xa5',
607
'a/a/a', 'a/a/\xc2\xb5', 'a/a/\xc3\xa5',
608
# content of 'a/\xc2\xb5'
609
'a/\xc2\xb5/a', 'a/\xc2\xb5/\xc2\xb5', 'a/\xc2\xb5/\xc3\xa5',
610
# content of 'a/\xc3\xa5'
611
'a/\xc3\xa5/a', 'a/\xc3\xa5/\xc2\xb5', 'a/\xc3\xa5/\xc3\xa5',
612
# content of '\xc2\xb5'
613
'\xc2\xb5/a', '\xc2\xb5/\xc2\xb5', '\xc2\xb5/\xc3\xa5',
614
# content of '\xc2\xe5'
615
'\xc3\xa5/a', '\xc3\xa5/\xc2\xb5', '\xc3\xa5/\xc3\xa5',
619
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
620
"""Test the pyrex implementation of _cmp_path_by_dirblock"""
622
_test_needs_features = [CompiledDirstateHelpersFeature]
624
def get_cmp_by_dirs(self):
625
from bzrlib._dirstate_helpers_c import _cmp_path_by_dirblock_c
626
return _cmp_path_by_dirblock_c
629
class TestMemRChr(tests.TestCase):
630
"""Test memrchr functionality"""
632
_test_needs_features = [CompiledDirstateHelpersFeature]
634
def assertMemRChr(self, expected, s, c):
635
from bzrlib._dirstate_helpers_c import _py_memrchr
636
self.assertEqual(expected, _py_memrchr(s, c))
638
def test_missing(self):
639
self.assertMemRChr(None, '', 'a')
640
self.assertMemRChr(None, '', 'c')
641
self.assertMemRChr(None, 'abcdefghijklm', 'q')
642
self.assertMemRChr(None, 'aaaaaaaaaaaaaaaaaaaaaaa', 'b')
644
def test_single_entry(self):
645
self.assertMemRChr(0, 'abcdefghijklm', 'a')
646
self.assertMemRChr(1, 'abcdefghijklm', 'b')
647
self.assertMemRChr(2, 'abcdefghijklm', 'c')
648
self.assertMemRChr(10, 'abcdefghijklm', 'k')
649
self.assertMemRChr(11, 'abcdefghijklm', 'l')
650
self.assertMemRChr(12, 'abcdefghijklm', 'm')
652
def test_multiple(self):
653
self.assertMemRChr(10, 'abcdefjklmabcdefghijklm', 'a')
654
self.assertMemRChr(11, 'abcdefjklmabcdefghijklm', 'b')
655
self.assertMemRChr(12, 'abcdefjklmabcdefghijklm', 'c')
656
self.assertMemRChr(20, 'abcdefjklmabcdefghijklm', 'k')
657
self.assertMemRChr(21, 'abcdefjklmabcdefghijklm', 'l')
658
self.assertMemRChr(22, 'abcdefjklmabcdefghijklm', 'm')
659
self.assertMemRChr(22, 'aaaaaaaaaaaaaaaaaaaaaaa', 'a')
661
def test_with_nulls(self):
662
self.assertMemRChr(10, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'a')
663
self.assertMemRChr(11, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'b')
664
self.assertMemRChr(12, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'c')
665
self.assertMemRChr(20, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'k')
666
self.assertMemRChr(21, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'l')
667
self.assertMemRChr(22, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'm')
668
self.assertMemRChr(22, 'aaa\0\0\0aaaaaaa\0\0\0aaaaaaa', 'a')
669
self.assertMemRChr(9, '\0\0\0\0\0\0\0\0\0\0', '\0')
672
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
673
"""Test an implementation of _read_dirblocks()
675
_read_dirblocks() reads in all of the dirblock information from the disk
678
Child test cases can override ``get_read_dirblocks`` to test a specific
682
def get_read_dirblocks(self):
683
from bzrlib._dirstate_helpers_py import _read_dirblocks_py
684
return _read_dirblocks_py
686
def test_smoketest(self):
687
"""Make sure that we can create and read back a simple file."""
688
tree, state, expected = self.create_basic_dirstate()
690
state._read_header_if_needed()
691
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
692
state._dirblock_state)
693
read_dirblocks = self.get_read_dirblocks()
694
read_dirblocks(state)
695
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
696
state._dirblock_state)
698
def test_trailing_garbage(self):
699
tree, state, expected = self.create_basic_dirstate()
700
# We can modify the file as long as it hasn't been read yet.
701
f = open('dirstate', 'ab')
703
# Add bogus trailing garbage
707
e = self.assertRaises(errors.DirstateCorrupt,
708
state._read_dirblocks_if_needed)
709
# Make sure we mention the bogus characters in the error
710
self.assertContainsRe(str(e), 'bogus')
713
class TestCompiledReadDirblocks(TestReadDirblocks):
714
"""Test the pyrex implementation of _read_dirblocks"""
716
_test_needs_features = [CompiledDirstateHelpersFeature]
718
def get_read_dirblocks(self):
719
from bzrlib._dirstate_helpers_c import _read_dirblocks_c
720
return _read_dirblocks_c
723
class TestUsingCompiledIfAvailable(tests.TestCase):
724
"""Check that any compiled functions that are available are the default.
726
It is possible to have typos, etc in the import line, such that
727
_dirstate_helpers_c is actually available, but the compiled functions are
731
def test_bisect_dirblock(self):
732
if CompiledDirstateHelpersFeature.available():
733
from bzrlib._dirstate_helpers_c import bisect_dirblock_c
734
self.assertIs(bisect_dirblock_c, dirstate.bisect_dirblock)
736
from bzrlib._dirstate_helpers_py import bisect_dirblock_py
737
self.assertIs(bisect_dirblock_py, dirstate.bisect_dirblock)
739
def test__bisect_path_left(self):
740
if CompiledDirstateHelpersFeature.available():
741
from bzrlib._dirstate_helpers_c import _bisect_path_left_c
742
self.assertIs(_bisect_path_left_c, dirstate._bisect_path_left)
744
from bzrlib._dirstate_helpers_py import _bisect_path_left_py
745
self.assertIs(_bisect_path_left_py, dirstate._bisect_path_left)
747
def test__bisect_path_right(self):
748
if CompiledDirstateHelpersFeature.available():
749
from bzrlib._dirstate_helpers_c import _bisect_path_right_c
750
self.assertIs(_bisect_path_right_c, dirstate._bisect_path_right)
752
from bzrlib._dirstate_helpers_py import _bisect_path_right_py
753
self.assertIs(_bisect_path_right_py, dirstate._bisect_path_right)
755
def test_cmp_by_dirs(self):
756
if CompiledDirstateHelpersFeature.available():
757
from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
758
self.assertIs(cmp_by_dirs_c, dirstate.cmp_by_dirs)
760
from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
761
self.assertIs(cmp_by_dirs_py, dirstate.cmp_by_dirs)
763
def test__read_dirblocks(self):
764
if CompiledDirstateHelpersFeature.available():
765
from bzrlib._dirstate_helpers_c import _read_dirblocks_c
766
self.assertIs(_read_dirblocks_c, dirstate._read_dirblocks)
768
from bzrlib._dirstate_helpers_py import _read_dirblocks_py
769
self.assertIs(_read_dirblocks_py, dirstate._read_dirblocks)
771
def test_update_entry(self):
772
if CompiledDirstateHelpersFeature.available():
773
from bzrlib._dirstate_helpers_c import update_entry
774
self.assertIs(update_entry, dirstate.update_entry)
776
from bzrlib.dirstate import py_update_entry
777
self.assertIs(py_update_entry, dirstate.py_update_entry)
779
def test_process_entry(self):
780
if CompiledDirstateHelpersFeature.available():
781
from bzrlib._dirstate_helpers_c import ProcessEntryC
782
self.assertIs(ProcessEntryC, dirstate._process_entry)
784
from bzrlib.dirstate import ProcessEntryPython
785
self.assertIs(ProcessEntryPython, dirstate._process_entry)
788
class TestUpdateEntry(test_dirstate.TestCaseWithDirState):
789
"""Test the DirState.update_entry functions"""
791
def get_state_with_a(self):
792
"""Create a DirState tracking a single object named 'a'"""
793
state = test_dirstate.InstrumentedDirState.initialize('dirstate')
794
self.addCleanup(state.unlock)
795
state.add('a', 'a-id', 'file', None, '')
796
entry = state._get_entry(0, path_utf8='a')
797
self.set_update_entry()
800
def set_update_entry(self):
801
self.update_entry = dirstate.py_update_entry
803
def test_update_entry(self):
804
state, entry = self.get_state_with_a()
805
self.build_tree(['a'])
806
# Add one where we don't provide the stat or sha already
807
self.assertEqual(('', 'a', 'a-id'), entry[0])
808
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
810
# Flush the buffers to disk
812
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
813
state._dirblock_state)
815
stat_value = os.lstat('a')
816
packed_stat = dirstate.pack_stat(stat_value)
817
link_or_sha1 = self.update_entry(state, entry, abspath='a',
818
stat_value=stat_value)
819
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
822
# The dirblock entry should not cache the file's sha1
823
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
825
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
826
state._dirblock_state)
827
mode = stat_value.st_mode
828
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
831
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
832
state._dirblock_state)
834
# If we do it again right away, we don't know if the file has changed
835
# so we will re-read the file. Roll the clock back so the file is
836
# guaranteed to look too new.
837
state.adjust_time(-10)
839
link_or_sha1 = self.update_entry(state, entry, abspath='a',
840
stat_value=stat_value)
841
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
842
('sha1', 'a'), ('is_exec', mode, False),
844
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
846
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
847
state._dirblock_state)
848
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
852
# However, if we move the clock forward so the file is considered
853
# "stable", it should just cache the value.
854
state.adjust_time(+20)
855
link_or_sha1 = self.update_entry(state, entry, abspath='a',
856
stat_value=stat_value)
857
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
859
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
860
('sha1', 'a'), ('is_exec', mode, False),
861
('sha1', 'a'), ('is_exec', mode, False),
863
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
866
# Subsequent calls will just return the cached value
867
link_or_sha1 = self.update_entry(state, entry, abspath='a',
868
stat_value=stat_value)
869
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
871
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
872
('sha1', 'a'), ('is_exec', mode, False),
873
('sha1', 'a'), ('is_exec', mode, False),
875
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
878
def test_update_entry_symlink(self):
879
"""Update entry should read symlinks."""
880
self.requireFeature(SymlinkFeature)
881
state, entry = self.get_state_with_a()
883
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
884
state._dirblock_state)
885
os.symlink('target', 'a')
887
state.adjust_time(-10) # Make the symlink look new
888
stat_value = os.lstat('a')
889
packed_stat = dirstate.pack_stat(stat_value)
890
link_or_sha1 = self.update_entry(state, entry, abspath='a',
891
stat_value=stat_value)
892
self.assertEqual('target', link_or_sha1)
893
self.assertEqual([('read_link', 'a', '')], state._log)
894
# Dirblock is not updated (the link is too new)
895
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
897
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
898
state._dirblock_state)
900
# Because the stat_value looks new, we should re-read the target
901
link_or_sha1 = self.update_entry(state, entry, abspath='a',
902
stat_value=stat_value)
903
self.assertEqual('target', link_or_sha1)
904
self.assertEqual([('read_link', 'a', ''),
905
('read_link', 'a', ''),
907
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
909
state.adjust_time(+20) # Skip into the future, all files look old
910
link_or_sha1 = self.update_entry(state, entry, abspath='a',
911
stat_value=stat_value)
912
self.assertEqual('target', link_or_sha1)
913
# We need to re-read the link because only now can we cache it
914
self.assertEqual([('read_link', 'a', ''),
915
('read_link', 'a', ''),
916
('read_link', 'a', ''),
918
self.assertEqual([('l', 'target', 6, False, packed_stat)],
921
# Another call won't re-read the link
922
self.assertEqual([('read_link', 'a', ''),
923
('read_link', 'a', ''),
924
('read_link', 'a', ''),
926
link_or_sha1 = self.update_entry(state, entry, abspath='a',
927
stat_value=stat_value)
928
self.assertEqual('target', link_or_sha1)
929
self.assertEqual([('l', 'target', 6, False, packed_stat)],
932
def do_update_entry(self, state, entry, abspath):
933
stat_value = os.lstat(abspath)
934
return self.update_entry(state, entry, abspath, stat_value)
936
def test_update_entry_dir(self):
937
state, entry = self.get_state_with_a()
938
self.build_tree(['a/'])
939
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
941
def test_update_entry_dir_unchanged(self):
942
state, entry = self.get_state_with_a()
943
self.build_tree(['a/'])
944
state.adjust_time(+20)
945
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
946
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
947
state._dirblock_state)
949
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
950
state._dirblock_state)
951
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
952
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
953
state._dirblock_state)
955
def test_update_entry_file_unchanged(self):
956
state, entry = self.get_state_with_a()
957
self.build_tree(['a'])
958
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
959
state.adjust_time(+20)
960
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
961
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
962
state._dirblock_state)
964
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
965
state._dirblock_state)
966
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
967
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
968
state._dirblock_state)
970
def create_and_test_file(self, state, entry):
971
"""Create a file at 'a' and verify the state finds it.
973
The state should already be versioning *something* at 'a'. This makes
974
sure that state.update_entry recognizes it as a file.
976
self.build_tree(['a'])
977
stat_value = os.lstat('a')
978
packed_stat = dirstate.pack_stat(stat_value)
980
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
981
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
983
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
987
def create_and_test_dir(self, state, entry):
988
"""Create a directory at 'a' and verify the state finds it.
990
The state should already be versioning *something* at 'a'. This makes
991
sure that state.update_entry recognizes it as a directory.
993
self.build_tree(['a/'])
994
stat_value = os.lstat('a')
995
packed_stat = dirstate.pack_stat(stat_value)
997
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
998
self.assertIs(None, link_or_sha1)
999
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1003
def create_and_test_symlink(self, state, entry):
1004
"""Create a symlink at 'a' and verify the state finds it.
1006
The state should already be versioning *something* at 'a'. This makes
1007
sure that state.update_entry recognizes it as a symlink.
1009
This should not be called if this platform does not have symlink
1012
# caller should care about skipping test on platforms without symlinks
1013
os.symlink('path/to/foo', 'a')
1015
stat_value = os.lstat('a')
1016
packed_stat = dirstate.pack_stat(stat_value)
1018
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1019
self.assertEqual('path/to/foo', link_or_sha1)
1020
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1024
def test_update_file_to_dir(self):
1025
"""If a file changes to a directory we return None for the sha.
1026
We also update the inventory record.
1028
state, entry = self.get_state_with_a()
1029
# The file sha1 won't be cached unless the file is old
1030
state.adjust_time(+10)
1031
self.create_and_test_file(state, entry)
1033
self.create_and_test_dir(state, entry)
1035
def test_update_file_to_symlink(self):
1036
"""File becomes a symlink"""
1037
self.requireFeature(SymlinkFeature)
1038
state, entry = self.get_state_with_a()
1039
# The file sha1 won't be cached unless the file is old
1040
state.adjust_time(+10)
1041
self.create_and_test_file(state, entry)
1043
self.create_and_test_symlink(state, entry)
1045
def test_update_dir_to_file(self):
1046
"""Directory becoming a file updates the entry."""
1047
state, entry = self.get_state_with_a()
1048
# The file sha1 won't be cached unless the file is old
1049
state.adjust_time(+10)
1050
self.create_and_test_dir(state, entry)
1052
self.create_and_test_file(state, entry)
1054
def test_update_dir_to_symlink(self):
1055
"""Directory becomes a symlink"""
1056
self.requireFeature(SymlinkFeature)
1057
state, entry = self.get_state_with_a()
1058
# The symlink target won't be cached if it isn't old
1059
state.adjust_time(+10)
1060
self.create_and_test_dir(state, entry)
1062
self.create_and_test_symlink(state, entry)
1064
def test_update_symlink_to_file(self):
1065
"""Symlink becomes a file"""
1066
self.requireFeature(SymlinkFeature)
1067
state, entry = self.get_state_with_a()
1068
# The symlink and file info won't be cached unless old
1069
state.adjust_time(+10)
1070
self.create_and_test_symlink(state, entry)
1072
self.create_and_test_file(state, entry)
1074
def test_update_symlink_to_dir(self):
1075
"""Symlink becomes a directory"""
1076
self.requireFeature(SymlinkFeature)
1077
state, entry = self.get_state_with_a()
1078
# The symlink target won't be cached if it isn't old
1079
state.adjust_time(+10)
1080
self.create_and_test_symlink(state, entry)
1082
self.create_and_test_dir(state, entry)
1084
def test__is_executable_win32(self):
1085
state, entry = self.get_state_with_a()
1086
self.build_tree(['a'])
1088
# Make sure we are using the win32 implementation of _is_executable
1089
state._is_executable = state._is_executable_win32
1091
# The file on disk is not executable, but we are marking it as though
1092
# it is. With _is_executable_win32 we ignore what is on disk.
1093
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1095
stat_value = os.lstat('a')
1096
packed_stat = dirstate.pack_stat(stat_value)
1098
state.adjust_time(-10) # Make sure everything is new
1099
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1101
# The row is updated, but the executable bit stays set.
1102
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1105
# Make the disk object look old enough to cache
1106
state.adjust_time(+20)
1107
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1108
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1109
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1112
class TestCompiledUpdateEntry(TestUpdateEntry):
1113
"""Test the pyrex implementation of _read_dirblocks"""
1115
_test_needs_features = [CompiledDirstateHelpersFeature]
1117
def set_update_entry(self):
1118
from bzrlib._dirstate_helpers_c import update_entry
1119
self.update_entry = update_entry