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."""
28
from bzrlib.tests import (
31
from bzrlib.tests import test_dirstate
34
class _CompiledDirstateHelpersFeature(tests.Feature):
37
import bzrlib._dirstate_helpers_c
42
def feature_name(self):
43
return 'bzrlib._dirstate_helpers_c'
45
CompiledDirstateHelpersFeature = _CompiledDirstateHelpersFeature()
48
class TestBisectPathMixin(object):
49
"""Test that _bisect_path_*() returns the expected values.
51
_bisect_path_* is intended to work like bisect.bisect_*() except it
52
knows it is working on paths that are sorted by ('path', 'to', 'foo')
53
chunks rather than by raw 'path/to/foo'.
55
Test Cases should inherit from this and override ``get_bisect_path`` return
56
their implementation, and ``get_bisect`` to return the matching
57
bisect.bisect_* function.
60
def get_bisect_path(self):
61
"""Return an implementation of _bisect_path_*"""
62
raise NotImplementedError
65
"""Return a version of bisect.bisect_*.
67
Also, for the 'exists' check, return the offset to the real values.
68
For example bisect_left returns the index of an entry, while
69
bisect_right returns the index *after* an entry
71
:return: (bisect_func, offset)
73
raise NotImplementedError
75
def assertBisect(self, paths, split_paths, path, exists=True):
76
"""Assert that bisect_split works like bisect_left on the split paths.
78
:param paths: A list of path names
79
:param split_paths: A list of path names that are already split up by directory
80
('path/to/foo' => ('path', 'to', 'foo'))
81
:param path: The path we are indexing.
82
:param exists: The path should be present, so make sure the
83
final location actually points to the right value.
85
All other arguments will be passed along.
87
bisect_path = self.get_bisect_path()
88
self.assertIsInstance(paths, list)
89
bisect_path_idx = bisect_path(paths, path)
90
split_path = self.split_for_dirblocks([path])[0]
91
bisect_func, offset = self.get_bisect()
92
bisect_split_idx = bisect_func(split_paths, split_path)
93
self.assertEqual(bisect_split_idx, bisect_path_idx,
94
'%s disagreed. %s != %s'
96
% (bisect_path.__name__,
97
bisect_split_idx, bisect_path_idx, path)
100
self.assertEqual(path, paths[bisect_path_idx+offset])
102
def split_for_dirblocks(self, paths):
105
dirname, basename = os.path.split(path)
106
dir_split_paths.append((dirname.split('/'), basename))
107
dir_split_paths.sort()
108
return dir_split_paths
110
def test_simple(self):
111
"""In the simple case it works just like bisect_left"""
112
paths = ['', 'a', 'b', 'c', 'd']
113
split_paths = self.split_for_dirblocks(paths)
115
self.assertBisect(paths, split_paths, path, exists=True)
116
self.assertBisect(paths, split_paths, '_', exists=False)
117
self.assertBisect(paths, split_paths, 'aa', exists=False)
118
self.assertBisect(paths, split_paths, 'bb', exists=False)
119
self.assertBisect(paths, split_paths, 'cc', exists=False)
120
self.assertBisect(paths, split_paths, 'dd', exists=False)
121
self.assertBisect(paths, split_paths, 'a/a', exists=False)
122
self.assertBisect(paths, split_paths, 'b/b', exists=False)
123
self.assertBisect(paths, split_paths, 'c/c', exists=False)
124
self.assertBisect(paths, split_paths, 'd/d', exists=False)
126
def test_involved(self):
127
"""This is where bisect_path_* diverges slightly."""
128
# This is the list of paths and their contents
156
# This is the exact order that is stored by dirstate
157
# All children in a directory are mentioned before an children of
158
# children are mentioned.
159
# So all the root-directory paths, then all the
160
# first sub directory, etc.
161
paths = [# content of '/'
162
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
164
'a/a', 'a/a-a', 'a/a-z',
166
'a/z', 'a/z-a', 'a/z-z',
189
split_paths = self.split_for_dirblocks(paths)
191
for dir_parts, basename in split_paths:
192
if dir_parts == ['']:
193
sorted_paths.append(basename)
195
sorted_paths.append('/'.join(dir_parts + [basename]))
197
self.assertEqual(sorted_paths, paths)
200
self.assertBisect(paths, split_paths, path, exists=True)
203
class TestBisectPathLeft(tests.TestCase, TestBisectPathMixin):
204
"""Run all Bisect Path tests against _bisect_path_left_py."""
206
def get_bisect_path(self):
207
from bzrlib._dirstate_helpers_py import _bisect_path_left_py
208
return _bisect_path_left_py
210
def get_bisect(self):
211
return bisect.bisect_left, 0
214
class TestCompiledBisectPathLeft(TestBisectPathLeft):
215
"""Run all Bisect Path tests against _bisect_path_right_c"""
217
_test_needs_features = [CompiledDirstateHelpersFeature]
219
def get_bisect_path(self):
220
from bzrlib._dirstate_helpers_c import _bisect_path_left_c
221
return _bisect_path_left_c
224
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
225
"""Run all Bisect Path tests against _bisect_path_right_py"""
227
def get_bisect_path(self):
228
from bzrlib._dirstate_helpers_py import _bisect_path_right_py
229
return _bisect_path_right_py
231
def get_bisect(self):
232
return bisect.bisect_right, -1
235
class TestCompiledBisectPathRight(TestBisectPathRight):
236
"""Run all Bisect Path tests against _bisect_path_right_c"""
238
_test_needs_features = [CompiledDirstateHelpersFeature]
240
def get_bisect_path(self):
241
from bzrlib._dirstate_helpers_c import _bisect_path_right_c
242
return _bisect_path_right_c
245
class TestBisectDirblock(tests.TestCase):
246
"""Test that bisect_dirblock() returns the expected values.
248
bisect_dirblock is intended to work like bisect.bisect_left() except it
249
knows it is working on dirblocks and that dirblocks are sorted by ('path',
250
'to', 'foo') chunks rather than by raw 'path/to/foo'.
252
This test is parameterized by calling get_bisect_dirblock(). Child test
253
cases can override this function to test against a different
257
def get_bisect_dirblock(self):
258
"""Return an implementation of bisect_dirblock"""
259
from bzrlib._dirstate_helpers_py import bisect_dirblock_py
260
return bisect_dirblock_py
262
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
263
"""Assert that bisect_split works like bisect_left on the split paths.
265
:param dirblocks: A list of (path, [info]) pairs.
266
:param split_dirblocks: A list of ((split, path), [info]) pairs.
267
:param path: The path we are indexing.
269
All other arguments will be passed along.
271
bisect_dirblock = self.get_bisect_dirblock()
272
self.assertIsInstance(dirblocks, list)
273
bisect_split_idx = bisect_dirblock(dirblocks, path, *args, **kwargs)
274
split_dirblock = (path.split('/'), [])
275
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
277
self.assertEqual(bisect_left_idx, bisect_split_idx,
278
'bisect_split disagreed. %s != %s'
280
% (bisect_left_idx, bisect_split_idx, path)
283
def paths_to_dirblocks(self, paths):
284
"""Convert a list of paths into dirblock form.
286
Also, ensure that the paths are in proper sorted order.
288
dirblocks = [(path, []) for path in paths]
289
split_dirblocks = [(path.split('/'), []) for path in paths]
290
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
291
return dirblocks, split_dirblocks
293
def test_simple(self):
294
"""In the simple case it works just like bisect_left"""
295
paths = ['', 'a', 'b', 'c', 'd']
296
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
298
self.assertBisect(dirblocks, split_dirblocks, path)
299
self.assertBisect(dirblocks, split_dirblocks, '_')
300
self.assertBisect(dirblocks, split_dirblocks, 'aa')
301
self.assertBisect(dirblocks, split_dirblocks, 'bb')
302
self.assertBisect(dirblocks, split_dirblocks, 'cc')
303
self.assertBisect(dirblocks, split_dirblocks, 'dd')
304
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
305
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
306
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
307
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
309
def test_involved(self):
310
"""This is where bisect_left diverges slightly."""
312
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
313
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
315
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
316
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
319
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
321
self.assertBisect(dirblocks, split_dirblocks, path)
323
def test_involved_cached(self):
324
"""This is where bisect_left diverges slightly."""
326
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
327
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
329
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
330
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
334
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
336
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
339
class TestCompiledBisectDirblock(TestBisectDirblock):
340
"""Test that bisect_dirblock() returns the expected values.
342
bisect_dirblock is intended to work like bisect.bisect_left() except it
343
knows it is working on dirblocks and that dirblocks are sorted by ('path',
344
'to', 'foo') chunks rather than by raw 'path/to/foo'.
346
This runs all the normal tests that TestBisectDirblock did, but uses the
350
_test_needs_features = [CompiledDirstateHelpersFeature]
352
def get_bisect_dirblock(self):
353
from bzrlib._dirstate_helpers_c import bisect_dirblock_c
354
return bisect_dirblock_c
357
class TestCmpByDirs(tests.TestCase):
358
"""Test an implementation of cmp_by_dirs()
360
cmp_by_dirs() compares 2 paths by their directory sections, rather than as
363
Child test cases can override ``get_cmp_by_dirs`` to test a specific
367
def get_cmp_by_dirs(self):
368
"""Get a specific implementation of cmp_by_dirs."""
369
from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
370
return cmp_by_dirs_py
372
def assertCmpByDirs(self, expected, str1, str2):
373
"""Compare the two strings, in both directions.
375
:param expected: The expected comparison value. -1 means str1 comes
376
first, 0 means they are equal, 1 means str2 comes first
377
:param str1: string to compare
378
:param str2: string to compare
380
cmp_by_dirs = self.get_cmp_by_dirs()
382
self.assertEqual(str1, str2)
383
self.assertEqual(0, cmp_by_dirs(str1, str2))
384
self.assertEqual(0, cmp_by_dirs(str2, str1))
386
self.assertPositive(cmp_by_dirs(str1, str2))
387
self.assertNegative(cmp_by_dirs(str2, str1))
389
self.assertNegative(cmp_by_dirs(str1, str2))
390
self.assertPositive(cmp_by_dirs(str2, str1))
392
def test_cmp_empty(self):
393
"""Compare against the empty string."""
394
self.assertCmpByDirs(0, '', '')
395
self.assertCmpByDirs(1, 'a', '')
396
self.assertCmpByDirs(1, 'ab', '')
397
self.assertCmpByDirs(1, 'abc', '')
398
self.assertCmpByDirs(1, 'abcd', '')
399
self.assertCmpByDirs(1, 'abcde', '')
400
self.assertCmpByDirs(1, 'abcdef', '')
401
self.assertCmpByDirs(1, 'abcdefg', '')
402
self.assertCmpByDirs(1, 'abcdefgh', '')
403
self.assertCmpByDirs(1, 'abcdefghi', '')
404
self.assertCmpByDirs(1, 'test/ing/a/path/', '')
406
def test_cmp_same_str(self):
407
"""Compare the same string"""
408
self.assertCmpByDirs(0, 'a', 'a')
409
self.assertCmpByDirs(0, 'ab', 'ab')
410
self.assertCmpByDirs(0, 'abc', 'abc')
411
self.assertCmpByDirs(0, 'abcd', 'abcd')
412
self.assertCmpByDirs(0, 'abcde', 'abcde')
413
self.assertCmpByDirs(0, 'abcdef', 'abcdef')
414
self.assertCmpByDirs(0, 'abcdefg', 'abcdefg')
415
self.assertCmpByDirs(0, 'abcdefgh', 'abcdefgh')
416
self.assertCmpByDirs(0, 'abcdefghi', 'abcdefghi')
417
self.assertCmpByDirs(0, 'testing a long string', 'testing a long string')
418
self.assertCmpByDirs(0, 'x'*10000, 'x'*10000)
419
self.assertCmpByDirs(0, 'a/b', 'a/b')
420
self.assertCmpByDirs(0, 'a/b/c', 'a/b/c')
421
self.assertCmpByDirs(0, 'a/b/c/d', 'a/b/c/d')
422
self.assertCmpByDirs(0, 'a/b/c/d/e', 'a/b/c/d/e')
424
def test_simple_paths(self):
425
"""Compare strings that act like normal string comparison"""
426
self.assertCmpByDirs(-1, 'a', 'b')
427
self.assertCmpByDirs(-1, 'aa', 'ab')
428
self.assertCmpByDirs(-1, 'ab', 'bb')
429
self.assertCmpByDirs(-1, 'aaa', 'aab')
430
self.assertCmpByDirs(-1, 'aab', 'abb')
431
self.assertCmpByDirs(-1, 'abb', 'bbb')
432
self.assertCmpByDirs(-1, 'aaaa', 'aaab')
433
self.assertCmpByDirs(-1, 'aaab', 'aabb')
434
self.assertCmpByDirs(-1, 'aabb', 'abbb')
435
self.assertCmpByDirs(-1, 'abbb', 'bbbb')
436
self.assertCmpByDirs(-1, 'aaaaa', 'aaaab')
437
self.assertCmpByDirs(-1, 'a/a', 'a/b')
438
self.assertCmpByDirs(-1, 'a/b', 'b/b')
439
self.assertCmpByDirs(-1, 'a/a/a', 'a/a/b')
440
self.assertCmpByDirs(-1, 'a/a/b', 'a/b/b')
441
self.assertCmpByDirs(-1, 'a/b/b', 'b/b/b')
442
self.assertCmpByDirs(-1, 'a/a/a/a', 'a/a/a/b')
443
self.assertCmpByDirs(-1, 'a/a/a/b', 'a/a/b/b')
444
self.assertCmpByDirs(-1, 'a/a/b/b', 'a/b/b/b')
445
self.assertCmpByDirs(-1, 'a/b/b/b', 'b/b/b/b')
446
self.assertCmpByDirs(-1, 'a/a/a/a/a', 'a/a/a/a/b')
448
def test_tricky_paths(self):
449
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/cc/ef')
450
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/c/ef')
451
self.assertCmpByDirs(-1, 'ab/cd/ef', 'ab/cd-ef')
452
self.assertCmpByDirs(-1, 'ab/cd', 'ab/cd-')
453
self.assertCmpByDirs(-1, 'ab/cd', 'ab-cd')
455
def test_cmp_unicode_not_allowed(self):
456
cmp_by_dirs = self.get_cmp_by_dirs()
457
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', 'str')
458
self.assertRaises(TypeError, cmp_by_dirs, 'str', u'Unicode')
459
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', u'Unicode')
461
def test_cmp_non_ascii(self):
462
self.assertCmpByDirs(-1, '\xc2\xb5', '\xc3\xa5') # u'\xb5', u'\xe5'
463
self.assertCmpByDirs(-1, 'a', '\xc3\xa5') # u'a', u'\xe5'
464
self.assertCmpByDirs(-1, 'b', '\xc2\xb5') # u'b', u'\xb5'
465
self.assertCmpByDirs(-1, 'a/b', 'a/\xc3\xa5') # u'a/b', u'a/\xe5'
466
self.assertCmpByDirs(-1, 'b/a', 'b/\xc2\xb5') # u'b/a', u'b/\xb5'
469
class TestCompiledCmpByDirs(TestCmpByDirs):
470
"""Test the pyrex implementation of cmp_by_dirs"""
472
_test_needs_features = [CompiledDirstateHelpersFeature]
474
def get_cmp_by_dirs(self):
475
from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
479
class TestCmpPathByDirblock(tests.TestCase):
480
"""Test an implementation of _cmp_path_by_dirblock()
482
_cmp_path_by_dirblock() compares two paths using the sort order used by
483
DirState. All paths in the same directory are sorted together.
485
Child test cases can override ``get_cmp_path_by_dirblock`` to test a specific
489
def get_cmp_path_by_dirblock(self):
490
"""Get a specific implementation of _cmp_path_by_dirblock."""
491
from bzrlib._dirstate_helpers_py import _cmp_path_by_dirblock_py
492
return _cmp_path_by_dirblock_py
494
def assertCmpPathByDirblock(self, paths):
495
"""Compare all paths and make sure they evaluate to the correct order.
497
This does N^2 comparisons. It is assumed that ``paths`` is properly
500
:param paths: a sorted list of paths to compare
502
# First, make sure the paths being passed in are correct
504
dirname, basename = os.path.split(p)
505
return dirname.split('/'), basename
506
self.assertEqual(sorted(paths, key=_key), paths)
508
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
509
for idx1, path1 in enumerate(paths):
510
for idx2, path2 in enumerate(paths):
511
cmp_val = cmp_path_by_dirblock(path1, path2)
513
self.assertTrue(cmp_val < 0,
514
'%s did not state that %r came before %r, cmp=%s'
515
% (cmp_path_by_dirblock.__name__,
516
path1, path2, cmp_val))
518
self.assertTrue(cmp_val > 0,
519
'%s did not state that %r came after %r, cmp=%s'
520
% (cmp_path_by_dirblock.__name__,
521
path1, path2, cmp_val))
523
self.assertTrue(cmp_val == 0,
524
'%s did not state that %r == %r, cmp=%s'
525
% (cmp_path_by_dirblock.__name__,
526
path1, path2, cmp_val))
528
def test_cmp_simple_paths(self):
529
"""Compare against the empty string."""
530
self.assertCmpPathByDirblock(['', 'a', 'ab', 'abc', 'a/b/c', 'b/d/e'])
531
self.assertCmpPathByDirblock(['kl', 'ab/cd', 'ab/ef', 'gh/ij'])
533
def test_tricky_paths(self):
534
self.assertCmpPathByDirblock([
536
'', 'a', 'a-a', 'a=a', 'b',
538
'a/a', 'a/a-a', 'a/a=a', 'a/b',
540
'a/a/a', 'a/a/a-a', 'a/a/a=a',
541
# Contents of 'a/a/a'
542
'a/a/a/a', 'a/a/a/b',
543
# Contents of 'a/a/a-a',
544
'a/a/a-a/a', 'a/a/a-a/b',
545
# Contents of 'a/a/a=a',
546
'a/a/a=a/a', 'a/a/a=a/b',
547
# Contents of 'a/a-a'
549
# Contents of 'a/a-a/a'
550
'a/a-a/a/a', 'a/a-a/a/b',
551
# Contents of 'a/a=a'
562
self.assertCmpPathByDirblock([
564
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
566
'a/a', 'a/a-a', 'a/a-z',
568
'a/z', 'a/z-a', 'a/z-z',
592
def test_unicode_not_allowed(self):
593
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
594
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', 'str')
595
self.assertRaises(TypeError, cmp_path_by_dirblock, 'str', u'Uni')
596
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', u'Uni')
597
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', 'x/str')
598
self.assertRaises(TypeError, cmp_path_by_dirblock, 'x/str', u'x/Uni')
599
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', u'x/Uni')
601
def test_nonascii(self):
602
self.assertCmpPathByDirblock([
604
'', 'a', '\xc2\xb5', '\xc3\xa5',
606
'a/a', 'a/\xc2\xb5', 'a/\xc3\xa5',
608
'a/a/a', 'a/a/\xc2\xb5', 'a/a/\xc3\xa5',
609
# content of 'a/\xc2\xb5'
610
'a/\xc2\xb5/a', 'a/\xc2\xb5/\xc2\xb5', 'a/\xc2\xb5/\xc3\xa5',
611
# content of 'a/\xc3\xa5'
612
'a/\xc3\xa5/a', 'a/\xc3\xa5/\xc2\xb5', 'a/\xc3\xa5/\xc3\xa5',
613
# content of '\xc2\xb5'
614
'\xc2\xb5/a', '\xc2\xb5/\xc2\xb5', '\xc2\xb5/\xc3\xa5',
615
# content of '\xc2\xe5'
616
'\xc3\xa5/a', '\xc3\xa5/\xc2\xb5', '\xc3\xa5/\xc3\xa5',
620
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
621
"""Test the pyrex implementation of _cmp_path_by_dirblock"""
623
_test_needs_features = [CompiledDirstateHelpersFeature]
625
def get_cmp_by_dirs(self):
626
from bzrlib._dirstate_helpers_c import _cmp_path_by_dirblock_c
627
return _cmp_path_by_dirblock_c
630
class TestMemRChr(tests.TestCase):
631
"""Test memrchr functionality"""
633
_test_needs_features = [CompiledDirstateHelpersFeature]
635
def assertMemRChr(self, expected, s, c):
636
from bzrlib._dirstate_helpers_c import _py_memrchr
637
self.assertEqual(expected, _py_memrchr(s, c))
639
def test_missing(self):
640
self.assertMemRChr(None, '', 'a')
641
self.assertMemRChr(None, '', 'c')
642
self.assertMemRChr(None, 'abcdefghijklm', 'q')
643
self.assertMemRChr(None, 'aaaaaaaaaaaaaaaaaaaaaaa', 'b')
645
def test_single_entry(self):
646
self.assertMemRChr(0, 'abcdefghijklm', 'a')
647
self.assertMemRChr(1, 'abcdefghijklm', 'b')
648
self.assertMemRChr(2, 'abcdefghijklm', 'c')
649
self.assertMemRChr(10, 'abcdefghijklm', 'k')
650
self.assertMemRChr(11, 'abcdefghijklm', 'l')
651
self.assertMemRChr(12, 'abcdefghijklm', 'm')
653
def test_multiple(self):
654
self.assertMemRChr(10, 'abcdefjklmabcdefghijklm', 'a')
655
self.assertMemRChr(11, 'abcdefjklmabcdefghijklm', 'b')
656
self.assertMemRChr(12, 'abcdefjklmabcdefghijklm', 'c')
657
self.assertMemRChr(20, 'abcdefjklmabcdefghijklm', 'k')
658
self.assertMemRChr(21, 'abcdefjklmabcdefghijklm', 'l')
659
self.assertMemRChr(22, 'abcdefjklmabcdefghijklm', 'm')
660
self.assertMemRChr(22, 'aaaaaaaaaaaaaaaaaaaaaaa', 'a')
662
def test_with_nulls(self):
663
self.assertMemRChr(10, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'a')
664
self.assertMemRChr(11, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'b')
665
self.assertMemRChr(12, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'c')
666
self.assertMemRChr(20, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'k')
667
self.assertMemRChr(21, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'l')
668
self.assertMemRChr(22, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'm')
669
self.assertMemRChr(22, 'aaa\0\0\0aaaaaaa\0\0\0aaaaaaa', 'a')
670
self.assertMemRChr(9, '\0\0\0\0\0\0\0\0\0\0', '\0')
673
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
674
"""Test an implementation of _read_dirblocks()
676
_read_dirblocks() reads in all of the dirblock information from the disk
679
Child test cases can override ``get_read_dirblocks`` to test a specific
683
def get_read_dirblocks(self):
684
from bzrlib._dirstate_helpers_py import _read_dirblocks_py
685
return _read_dirblocks_py
687
def test_smoketest(self):
688
"""Make sure that we can create and read back a simple file."""
689
tree, state, expected = self.create_basic_dirstate()
691
state._read_header_if_needed()
692
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
693
state._dirblock_state)
694
read_dirblocks = self.get_read_dirblocks()
695
read_dirblocks(state)
696
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
697
state._dirblock_state)
699
def test_trailing_garbage(self):
700
tree, state, expected = self.create_basic_dirstate()
701
# We can modify the file as long as it hasn't been read yet.
702
f = open('dirstate', 'ab')
704
# Add bogus trailing garbage
708
e = self.assertRaises(errors.DirstateCorrupt,
709
state._read_dirblocks_if_needed)
710
# Make sure we mention the bogus characters in the error
711
self.assertContainsRe(str(e), 'bogus')
714
class TestCompiledReadDirblocks(TestReadDirblocks):
715
"""Test the pyrex implementation of _read_dirblocks"""
717
_test_needs_features = [CompiledDirstateHelpersFeature]
719
def get_read_dirblocks(self):
720
from bzrlib._dirstate_helpers_c import _read_dirblocks_c
721
return _read_dirblocks_c
724
class TestUsingCompiledIfAvailable(tests.TestCase):
725
"""Check that any compiled functions that are available are the default.
727
It is possible to have typos, etc in the import line, such that
728
_dirstate_helpers_c is actually available, but the compiled functions are
732
def test_bisect_dirblock(self):
733
if CompiledDirstateHelpersFeature.available():
734
from bzrlib._dirstate_helpers_c import bisect_dirblock_c
735
self.assertIs(bisect_dirblock_c, dirstate.bisect_dirblock)
737
from bzrlib._dirstate_helpers_py import bisect_dirblock_py
738
self.assertIs(bisect_dirblock_py, dirstate.bisect_dirblock)
740
def test__bisect_path_left(self):
741
if CompiledDirstateHelpersFeature.available():
742
from bzrlib._dirstate_helpers_c import _bisect_path_left_c
743
self.assertIs(_bisect_path_left_c, dirstate._bisect_path_left)
745
from bzrlib._dirstate_helpers_py import _bisect_path_left_py
746
self.assertIs(_bisect_path_left_py, dirstate._bisect_path_left)
748
def test__bisect_path_right(self):
749
if CompiledDirstateHelpersFeature.available():
750
from bzrlib._dirstate_helpers_c import _bisect_path_right_c
751
self.assertIs(_bisect_path_right_c, dirstate._bisect_path_right)
753
from bzrlib._dirstate_helpers_py import _bisect_path_right_py
754
self.assertIs(_bisect_path_right_py, dirstate._bisect_path_right)
756
def test_cmp_by_dirs(self):
757
if CompiledDirstateHelpersFeature.available():
758
from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
759
self.assertIs(cmp_by_dirs_c, dirstate.cmp_by_dirs)
761
from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
762
self.assertIs(cmp_by_dirs_py, dirstate.cmp_by_dirs)
764
def test__read_dirblocks(self):
765
if CompiledDirstateHelpersFeature.available():
766
from bzrlib._dirstate_helpers_c import _read_dirblocks_c
767
self.assertIs(_read_dirblocks_c, dirstate._read_dirblocks)
769
from bzrlib._dirstate_helpers_py import _read_dirblocks_py
770
self.assertIs(_read_dirblocks_py, dirstate._read_dirblocks)
772
def test_update_entry(self):
773
if CompiledDirstateHelpersFeature.available():
774
from bzrlib._dirstate_helpers_c import update_entry
775
self.assertIs(update_entry, dirstate.update_entry)
777
from bzrlib.dirstate import py_update_entry
778
self.assertIs(py_update_entry, dirstate.py_update_entry)
780
def test_process_entry(self):
781
if CompiledDirstateHelpersFeature.available():
782
from bzrlib._dirstate_helpers_c import ProcessEntryC
783
self.assertIs(ProcessEntryC, dirstate._process_entry)
785
from bzrlib.dirstate import ProcessEntryPython
786
self.assertIs(ProcessEntryPython, dirstate._process_entry)
789
class TestUpdateEntry(test_dirstate.TestCaseWithDirState):
790
"""Test the DirState.update_entry functions"""
792
def get_state_with_a(self):
793
"""Create a DirState tracking a single object named 'a'"""
794
state = test_dirstate.InstrumentedDirState.initialize('dirstate')
795
self.addCleanup(state.unlock)
796
state.add('a', 'a-id', 'file', None, '')
797
entry = state._get_entry(0, path_utf8='a')
798
self.set_update_entry()
801
def set_update_entry(self):
802
self.update_entry = dirstate.py_update_entry
804
def test_observed_sha1_cachable(self):
805
state, entry = self.get_state_with_a()
806
atime = time.time() - 10
807
self.build_tree(['a'])
808
statvalue = os.lstat('a')
809
statvalue = test_dirstate._FakeStat(statvalue.st_size, atime, atime,
810
statvalue.st_dev, statvalue.st_ino, statvalue.st_mode)
811
state._observed_sha1(entry, "foo", statvalue)
812
self.assertEqual('foo', entry[1][0][1])
813
packed_stat = dirstate.pack_stat(statvalue)
814
self.assertEqual(packed_stat, entry[1][0][4])
816
def test_observed_sha1_not_cachable(self):
817
state, entry = self.get_state_with_a()
818
oldval = entry[1][0][1]
819
oldstat = entry[1][0][4]
820
self.build_tree(['a'])
821
statvalue = os.lstat('a')
822
state._observed_sha1(entry, "foo", statvalue)
823
self.assertEqual(oldval, entry[1][0][1])
824
self.assertEqual(oldstat, entry[1][0][4])
826
def test_update_entry(self):
827
state, _ = self.get_state_with_a()
828
tree = self.make_branch_and_tree('tree')
830
empty_revid = tree.commit('empty')
831
self.build_tree(['tree/a'])
832
tree.add(['a'], ['a-id'])
833
with_a_id = tree.commit('with_a')
834
self.addCleanup(tree.unlock)
835
state.set_parent_trees(
836
[(empty_revid, tree.branch.repository.revision_tree(empty_revid))],
838
entry = state._get_entry(0, path_utf8='a')
839
self.build_tree(['a'])
840
# Add one where we don't provide the stat or sha already
841
self.assertEqual(('', 'a', 'a-id'), entry[0])
842
self.assertEqual(('f', '', 0, False, dirstate.DirState.NULLSTAT),
844
# Flush the buffers to disk
846
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
847
state._dirblock_state)
849
stat_value = os.lstat('a')
850
packed_stat = dirstate.pack_stat(stat_value)
851
link_or_sha1 = self.update_entry(state, entry, abspath='a',
852
stat_value=stat_value)
853
self.assertEqual(None, link_or_sha1)
855
# The dirblock entry should not have cached the file's sha1 (too new)
856
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
858
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
859
state._dirblock_state)
860
mode = stat_value.st_mode
861
self.assertEqual([('is_exec', mode, False)], state._log)
864
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
865
state._dirblock_state)
867
# If we do it again right away, we don't know if the file has changed
868
# so we will re-read the file. Roll the clock back so the file is
869
# guaranteed to look too new.
870
state.adjust_time(-10)
873
link_or_sha1 = self.update_entry(state, entry, abspath='a',
874
stat_value=stat_value)
875
self.assertEqual([('is_exec', mode, False)], state._log)
876
self.assertEqual(None, link_or_sha1)
877
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
878
state._dirblock_state)
879
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
883
# If it is cachable (the clock has moved forward) but new it still
884
# won't calculate the sha or cache it.
885
state.adjust_time(+20)
887
link_or_sha1 = dirstate.update_entry(state, entry, abspath='a',
888
stat_value=stat_value)
889
self.assertEqual(None, link_or_sha1)
890
self.assertEqual([('is_exec', mode, False)], state._log)
891
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
894
# If the file is no longer new, and the clock has been moved forward
895
# sufficiently, it will cache the sha.
897
state.set_parent_trees(
898
[(with_a_id, tree.branch.repository.revision_tree(with_a_id))],
900
entry = state._get_entry(0, path_utf8='a')
902
link_or_sha1 = self.update_entry(state, entry, abspath='a',
903
stat_value=stat_value)
904
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
906
self.assertEqual([('is_exec', mode, False), ('sha1', 'a')],
908
self.assertEqual(('f', link_or_sha1, 14, False, packed_stat),
911
# Subsequent calls will just return the cached value
913
link_or_sha1 = self.update_entry(state, entry, abspath='a',
914
stat_value=stat_value)
915
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
917
self.assertEqual([], state._log)
918
self.assertEqual(('f', link_or_sha1, 14, False, packed_stat),
921
def test_update_entry_symlink(self):
922
"""Update entry should read symlinks."""
923
self.requireFeature(SymlinkFeature)
924
state, entry = self.get_state_with_a()
926
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
927
state._dirblock_state)
928
os.symlink('target', 'a')
930
state.adjust_time(-10) # Make the symlink look new
931
stat_value = os.lstat('a')
932
packed_stat = dirstate.pack_stat(stat_value)
933
link_or_sha1 = self.update_entry(state, entry, abspath='a',
934
stat_value=stat_value)
935
self.assertEqual('target', link_or_sha1)
936
self.assertEqual([('read_link', 'a', '')], state._log)
937
# Dirblock is not updated (the link is too new)
938
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
940
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
941
state._dirblock_state)
943
# Because the stat_value looks new, we should re-read the target
944
link_or_sha1 = self.update_entry(state, entry, abspath='a',
945
stat_value=stat_value)
946
self.assertEqual('target', link_or_sha1)
947
self.assertEqual([('read_link', 'a', ''),
948
('read_link', 'a', ''),
950
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
952
state.adjust_time(+20) # Skip into the future, all files look old
953
link_or_sha1 = self.update_entry(state, entry, abspath='a',
954
stat_value=stat_value)
955
self.assertEqual('target', link_or_sha1)
956
# We need to re-read the link because only now can we cache it
957
self.assertEqual([('read_link', 'a', ''),
958
('read_link', 'a', ''),
959
('read_link', 'a', ''),
961
self.assertEqual([('l', 'target', 6, False, packed_stat)],
964
# Another call won't re-read the link
965
self.assertEqual([('read_link', 'a', ''),
966
('read_link', 'a', ''),
967
('read_link', 'a', ''),
969
link_or_sha1 = self.update_entry(state, entry, abspath='a',
970
stat_value=stat_value)
971
self.assertEqual('target', link_or_sha1)
972
self.assertEqual([('l', 'target', 6, False, packed_stat)],
975
def do_update_entry(self, state, entry, abspath):
976
stat_value = os.lstat(abspath)
977
return self.update_entry(state, entry, abspath, stat_value)
979
def test_update_entry_dir(self):
980
state, entry = self.get_state_with_a()
981
self.build_tree(['a/'])
982
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
984
def test_update_entry_dir_unchanged(self):
985
state, entry = self.get_state_with_a()
986
self.build_tree(['a/'])
987
state.adjust_time(+20)
988
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
989
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
990
state._dirblock_state)
992
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
993
state._dirblock_state)
994
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
995
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
996
state._dirblock_state)
998
def test_update_entry_file_unchanged(self):
999
state, _ = self.get_state_with_a()
1000
tree = self.make_branch_and_tree('tree')
1002
self.build_tree(['tree/a'])
1003
tree.add(['a'], ['a-id'])
1004
with_a_id = tree.commit('witha')
1005
self.addCleanup(tree.unlock)
1006
state.set_parent_trees(
1007
[(with_a_id, tree.branch.repository.revision_tree(with_a_id))],
1009
entry = state._get_entry(0, path_utf8='a')
1010
self.build_tree(['a'])
1011
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1012
state.adjust_time(+20)
1013
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1014
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1015
state._dirblock_state)
1017
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1018
state._dirblock_state)
1019
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1020
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1021
state._dirblock_state)
1023
def create_and_test_file(self, state, entry):
1024
"""Create a file at 'a' and verify the state finds it during update.
1026
The state should already be versioning *something* at 'a'. This makes
1027
sure that state.update_entry recognizes it as a file.
1029
self.build_tree(['a'])
1030
stat_value = os.lstat('a')
1031
packed_stat = dirstate.pack_stat(stat_value)
1033
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1034
self.assertEqual(None, link_or_sha1)
1035
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1039
def create_and_test_dir(self, state, entry):
1040
"""Create a directory at 'a' and verify the state finds it.
1042
The state should already be versioning *something* at 'a'. This makes
1043
sure that state.update_entry recognizes it as a directory.
1045
self.build_tree(['a/'])
1046
stat_value = os.lstat('a')
1047
packed_stat = dirstate.pack_stat(stat_value)
1049
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1050
self.assertIs(None, link_or_sha1)
1051
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1055
def create_and_test_symlink(self, state, entry):
1056
"""Create a symlink at 'a' and verify the state finds it.
1058
The state should already be versioning *something* at 'a'. This makes
1059
sure that state.update_entry recognizes it as a symlink.
1061
This should not be called if this platform does not have symlink
1064
# caller should care about skipping test on platforms without symlinks
1065
os.symlink('path/to/foo', 'a')
1067
stat_value = os.lstat('a')
1068
packed_stat = dirstate.pack_stat(stat_value)
1070
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1071
self.assertEqual('path/to/foo', link_or_sha1)
1072
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1076
def test_update_file_to_dir(self):
1077
"""If a file changes to a directory we return None for the sha.
1078
We also update the inventory record.
1080
state, entry = self.get_state_with_a()
1081
# The file sha1 won't be cached unless the file is old
1082
state.adjust_time(+10)
1083
self.create_and_test_file(state, entry)
1085
self.create_and_test_dir(state, entry)
1087
def test_update_file_to_symlink(self):
1088
"""File becomes a symlink"""
1089
self.requireFeature(SymlinkFeature)
1090
state, entry = self.get_state_with_a()
1091
# The file sha1 won't be cached unless the file is old
1092
state.adjust_time(+10)
1093
self.create_and_test_file(state, entry)
1095
self.create_and_test_symlink(state, entry)
1097
def test_update_dir_to_file(self):
1098
"""Directory becoming a file updates the entry."""
1099
state, entry = self.get_state_with_a()
1100
# The file sha1 won't be cached unless the file is old
1101
state.adjust_time(+10)
1102
self.create_and_test_dir(state, entry)
1104
self.create_and_test_file(state, entry)
1106
def test_update_dir_to_symlink(self):
1107
"""Directory becomes a symlink"""
1108
self.requireFeature(SymlinkFeature)
1109
state, entry = self.get_state_with_a()
1110
# The symlink target won't be cached if it isn't old
1111
state.adjust_time(+10)
1112
self.create_and_test_dir(state, entry)
1114
self.create_and_test_symlink(state, entry)
1116
def test_update_symlink_to_file(self):
1117
"""Symlink becomes a file"""
1118
self.requireFeature(SymlinkFeature)
1119
state, entry = self.get_state_with_a()
1120
# The symlink and file info won't be cached unless old
1121
state.adjust_time(+10)
1122
self.create_and_test_symlink(state, entry)
1124
self.create_and_test_file(state, entry)
1126
def test_update_symlink_to_dir(self):
1127
"""Symlink becomes a directory"""
1128
self.requireFeature(SymlinkFeature)
1129
state, entry = self.get_state_with_a()
1130
# The symlink target won't be cached if it isn't old
1131
state.adjust_time(+10)
1132
self.create_and_test_symlink(state, entry)
1134
self.create_and_test_dir(state, entry)
1136
def test__is_executable_win32(self):
1137
state, entry = self.get_state_with_a()
1138
self.build_tree(['a'])
1140
# Make sure we are using the win32 implementation of _is_executable
1141
state._is_executable = state._is_executable_win32
1143
# The file on disk is not executable, but we are marking it as though
1144
# it is. With _is_executable_win32 we ignore what is on disk.
1145
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1147
stat_value = os.lstat('a')
1148
packed_stat = dirstate.pack_stat(stat_value)
1150
state.adjust_time(-10) # Make sure everything is new
1151
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1153
# The row is updated, but the executable bit stays set.
1154
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1157
# Make the disk object look old enough to cache (but it won't cache the sha
1158
# as it is a new file).
1159
state.adjust_time(+20)
1160
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1161
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1162
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1166
class TestCompiledUpdateEntry(TestUpdateEntry):
1167
"""Test the pyrex implementation of _read_dirblocks"""
1169
_test_needs_features = [CompiledDirstateHelpersFeature]
1171
def set_update_entry(self):
1172
from bzrlib._dirstate_helpers_c import update_entry
1173
self.update_entry = update_entry