1
# Copyright (C) 2007-2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for the compiled dirstate helpers."""
29
from bzrlib.tests import (
32
from bzrlib.tests.test_osutils import dir_reader_scenarios
33
from bzrlib.tests.scenarios import (
34
load_tests_apply_scenarios,
39
load_tests = load_tests_apply_scenarios
42
compiled_dirstate_helpers_feature = tests.ModuleAvailableFeature(
43
'bzrlib._dirstate_helpers_pyx')
46
# FIXME: we should also parametrize against SHA1Provider !
48
ue_scenarios = [('dirstate_Python',
49
{'update_entry': dirstate.py_update_entry})]
50
if compiled_dirstate_helpers_feature.available():
51
update_entry = compiled_dirstate_helpers_feature.module.update_entry
52
ue_scenarios.append(('dirstate_Pyrex', {'update_entry': update_entry}))
54
pe_scenarios = [('dirstate_Python',
55
{'_process_entry': dirstate.ProcessEntryPython})]
56
if compiled_dirstate_helpers_feature.available():
57
process_entry = compiled_dirstate_helpers_feature.module.ProcessEntryC
58
pe_scenarios.append(('dirstate_Pyrex', {'_process_entry': process_entry}))
61
class TestBisectPathMixin(object):
62
"""Test that _bisect_path_*() returns the expected values.
64
_bisect_path_* is intended to work like bisect.bisect_*() except it
65
knows it is working on paths that are sorted by ('path', 'to', 'foo')
66
chunks rather than by raw 'path/to/foo'.
68
Test Cases should inherit from this and override ``get_bisect_path`` return
69
their implementation, and ``get_bisect`` to return the matching
70
bisect.bisect_* function.
73
def get_bisect_path(self):
74
"""Return an implementation of _bisect_path_*"""
75
raise NotImplementedError
78
"""Return a version of bisect.bisect_*.
80
Also, for the 'exists' check, return the offset to the real values.
81
For example bisect_left returns the index of an entry, while
82
bisect_right returns the index *after* an entry
84
:return: (bisect_func, offset)
86
raise NotImplementedError
88
def assertBisect(self, paths, split_paths, path, exists=True):
89
"""Assert that bisect_split works like bisect_left on the split paths.
91
:param paths: A list of path names
92
:param split_paths: A list of path names that are already split up by directory
93
('path/to/foo' => ('path', 'to', 'foo'))
94
:param path: The path we are indexing.
95
:param exists: The path should be present, so make sure the
96
final location actually points to the right value.
98
All other arguments will be passed along.
100
bisect_path = self.get_bisect_path()
101
self.assertIsInstance(paths, list)
102
bisect_path_idx = bisect_path(paths, path)
103
split_path = self.split_for_dirblocks([path])[0]
104
bisect_func, offset = self.get_bisect()
105
bisect_split_idx = bisect_func(split_paths, split_path)
106
self.assertEqual(bisect_split_idx, bisect_path_idx,
107
'%s disagreed. %s != %s'
109
% (bisect_path.__name__,
110
bisect_split_idx, bisect_path_idx, path)
113
self.assertEqual(path, paths[bisect_path_idx+offset])
115
def split_for_dirblocks(self, paths):
118
dirname, basename = os.path.split(path)
119
dir_split_paths.append((dirname.split('/'), basename))
120
dir_split_paths.sort()
121
return dir_split_paths
123
def test_simple(self):
124
"""In the simple case it works just like bisect_left"""
125
paths = ['', 'a', 'b', 'c', 'd']
126
split_paths = self.split_for_dirblocks(paths)
128
self.assertBisect(paths, split_paths, path, exists=True)
129
self.assertBisect(paths, split_paths, '_', exists=False)
130
self.assertBisect(paths, split_paths, 'aa', exists=False)
131
self.assertBisect(paths, split_paths, 'bb', exists=False)
132
self.assertBisect(paths, split_paths, 'cc', exists=False)
133
self.assertBisect(paths, split_paths, 'dd', exists=False)
134
self.assertBisect(paths, split_paths, 'a/a', exists=False)
135
self.assertBisect(paths, split_paths, 'b/b', exists=False)
136
self.assertBisect(paths, split_paths, 'c/c', exists=False)
137
self.assertBisect(paths, split_paths, 'd/d', exists=False)
139
def test_involved(self):
140
"""This is where bisect_path_* diverges slightly."""
141
# This is the list of paths and their contents
169
# This is the exact order that is stored by dirstate
170
# All children in a directory are mentioned before an children of
171
# children are mentioned.
172
# So all the root-directory paths, then all the
173
# first sub directory, etc.
174
paths = [# content of '/'
175
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
177
'a/a', 'a/a-a', 'a/a-z',
179
'a/z', 'a/z-a', 'a/z-z',
202
split_paths = self.split_for_dirblocks(paths)
204
for dir_parts, basename in split_paths:
205
if dir_parts == ['']:
206
sorted_paths.append(basename)
208
sorted_paths.append('/'.join(dir_parts + [basename]))
210
self.assertEqual(sorted_paths, paths)
213
self.assertBisect(paths, split_paths, path, exists=True)
216
class TestBisectPathLeft(tests.TestCase, TestBisectPathMixin):
217
"""Run all Bisect Path tests against _bisect_path_left."""
219
def get_bisect_path(self):
220
from bzrlib._dirstate_helpers_py import _bisect_path_left
221
return _bisect_path_left
223
def get_bisect(self):
224
return bisect.bisect_left, 0
227
class TestCompiledBisectPathLeft(TestBisectPathLeft):
228
"""Run all Bisect Path tests against _bisect_path_lect"""
230
_test_needs_features = [compiled_dirstate_helpers_feature]
232
def get_bisect_path(self):
233
from bzrlib._dirstate_helpers_pyx import _bisect_path_left
234
return _bisect_path_left
237
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
238
"""Run all Bisect Path tests against _bisect_path_right"""
240
def get_bisect_path(self):
241
from bzrlib._dirstate_helpers_py import _bisect_path_right
242
return _bisect_path_right
244
def get_bisect(self):
245
return bisect.bisect_right, -1
248
class TestCompiledBisectPathRight(TestBisectPathRight):
249
"""Run all Bisect Path tests against _bisect_path_right"""
251
_test_needs_features = [compiled_dirstate_helpers_feature]
253
def get_bisect_path(self):
254
from bzrlib._dirstate_helpers_pyx import _bisect_path_right
255
return _bisect_path_right
258
class TestBisectDirblock(tests.TestCase):
259
"""Test that bisect_dirblock() returns the expected values.
261
bisect_dirblock is intended to work like bisect.bisect_left() except it
262
knows it is working on dirblocks and that dirblocks are sorted by ('path',
263
'to', 'foo') chunks rather than by raw 'path/to/foo'.
265
This test is parameterized by calling get_bisect_dirblock(). Child test
266
cases can override this function to test against a different
270
def get_bisect_dirblock(self):
271
"""Return an implementation of bisect_dirblock"""
272
from bzrlib._dirstate_helpers_py import bisect_dirblock
273
return bisect_dirblock
275
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
276
"""Assert that bisect_split works like bisect_left on the split paths.
278
:param dirblocks: A list of (path, [info]) pairs.
279
:param split_dirblocks: A list of ((split, path), [info]) pairs.
280
:param path: The path we are indexing.
282
All other arguments will be passed along.
284
bisect_dirblock = self.get_bisect_dirblock()
285
self.assertIsInstance(dirblocks, list)
286
bisect_split_idx = bisect_dirblock(dirblocks, path, *args, **kwargs)
287
split_dirblock = (path.split('/'), [])
288
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
290
self.assertEqual(bisect_left_idx, bisect_split_idx,
291
'bisect_split disagreed. %s != %s'
293
% (bisect_left_idx, bisect_split_idx, path)
296
def paths_to_dirblocks(self, paths):
297
"""Convert a list of paths into dirblock form.
299
Also, ensure that the paths are in proper sorted order.
301
dirblocks = [(path, []) for path in paths]
302
split_dirblocks = [(path.split('/'), []) for path in paths]
303
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
304
return dirblocks, split_dirblocks
306
def test_simple(self):
307
"""In the simple case it works just like bisect_left"""
308
paths = ['', 'a', 'b', 'c', 'd']
309
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
311
self.assertBisect(dirblocks, split_dirblocks, path)
312
self.assertBisect(dirblocks, split_dirblocks, '_')
313
self.assertBisect(dirblocks, split_dirblocks, 'aa')
314
self.assertBisect(dirblocks, split_dirblocks, 'bb')
315
self.assertBisect(dirblocks, split_dirblocks, 'cc')
316
self.assertBisect(dirblocks, split_dirblocks, 'dd')
317
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
318
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
319
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
320
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
322
def test_involved(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',
332
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
334
self.assertBisect(dirblocks, split_dirblocks, path)
336
def test_involved_cached(self):
337
"""This is where bisect_left diverges slightly."""
339
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
340
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
342
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
343
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
347
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
349
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
352
class TestCompiledBisectDirblock(TestBisectDirblock):
353
"""Test that bisect_dirblock() returns the expected values.
355
bisect_dirblock is intended to work like bisect.bisect_left() except it
356
knows it is working on dirblocks and that dirblocks are sorted by ('path',
357
'to', 'foo') chunks rather than by raw 'path/to/foo'.
359
This runs all the normal tests that TestBisectDirblock did, but uses the
363
_test_needs_features = [compiled_dirstate_helpers_feature]
365
def get_bisect_dirblock(self):
366
from bzrlib._dirstate_helpers_pyx import bisect_dirblock
367
return bisect_dirblock
370
class TestCmpByDirs(tests.TestCase):
371
"""Test an implementation of cmp_by_dirs()
373
cmp_by_dirs() compares 2 paths by their directory sections, rather than as
376
Child test cases can override ``get_cmp_by_dirs`` to test a specific
380
def get_cmp_by_dirs(self):
381
"""Get a specific implementation of cmp_by_dirs."""
382
from bzrlib._dirstate_helpers_py import cmp_by_dirs
385
def assertCmpByDirs(self, expected, str1, str2):
386
"""Compare the two strings, in both directions.
388
:param expected: The expected comparison value. -1 means str1 comes
389
first, 0 means they are equal, 1 means str2 comes first
390
:param str1: string to compare
391
:param str2: string to compare
393
cmp_by_dirs = self.get_cmp_by_dirs()
395
self.assertEqual(str1, str2)
396
self.assertEqual(0, cmp_by_dirs(str1, str2))
397
self.assertEqual(0, cmp_by_dirs(str2, str1))
399
self.assertPositive(cmp_by_dirs(str1, str2))
400
self.assertNegative(cmp_by_dirs(str2, str1))
402
self.assertNegative(cmp_by_dirs(str1, str2))
403
self.assertPositive(cmp_by_dirs(str2, str1))
405
def test_cmp_empty(self):
406
"""Compare against the empty string."""
407
self.assertCmpByDirs(0, '', '')
408
self.assertCmpByDirs(1, 'a', '')
409
self.assertCmpByDirs(1, 'ab', '')
410
self.assertCmpByDirs(1, 'abc', '')
411
self.assertCmpByDirs(1, 'abcd', '')
412
self.assertCmpByDirs(1, 'abcde', '')
413
self.assertCmpByDirs(1, 'abcdef', '')
414
self.assertCmpByDirs(1, 'abcdefg', '')
415
self.assertCmpByDirs(1, 'abcdefgh', '')
416
self.assertCmpByDirs(1, 'abcdefghi', '')
417
self.assertCmpByDirs(1, 'test/ing/a/path/', '')
419
def test_cmp_same_str(self):
420
"""Compare the same string"""
421
self.assertCmpByDirs(0, 'a', 'a')
422
self.assertCmpByDirs(0, 'ab', 'ab')
423
self.assertCmpByDirs(0, 'abc', 'abc')
424
self.assertCmpByDirs(0, 'abcd', 'abcd')
425
self.assertCmpByDirs(0, 'abcde', 'abcde')
426
self.assertCmpByDirs(0, 'abcdef', 'abcdef')
427
self.assertCmpByDirs(0, 'abcdefg', 'abcdefg')
428
self.assertCmpByDirs(0, 'abcdefgh', 'abcdefgh')
429
self.assertCmpByDirs(0, 'abcdefghi', 'abcdefghi')
430
self.assertCmpByDirs(0, 'testing a long string', 'testing a long string')
431
self.assertCmpByDirs(0, 'x'*10000, 'x'*10000)
432
self.assertCmpByDirs(0, 'a/b', 'a/b')
433
self.assertCmpByDirs(0, 'a/b/c', 'a/b/c')
434
self.assertCmpByDirs(0, 'a/b/c/d', 'a/b/c/d')
435
self.assertCmpByDirs(0, 'a/b/c/d/e', 'a/b/c/d/e')
437
def test_simple_paths(self):
438
"""Compare strings that act like normal string comparison"""
439
self.assertCmpByDirs(-1, 'a', 'b')
440
self.assertCmpByDirs(-1, 'aa', 'ab')
441
self.assertCmpByDirs(-1, 'ab', 'bb')
442
self.assertCmpByDirs(-1, 'aaa', 'aab')
443
self.assertCmpByDirs(-1, 'aab', 'abb')
444
self.assertCmpByDirs(-1, 'abb', 'bbb')
445
self.assertCmpByDirs(-1, 'aaaa', 'aaab')
446
self.assertCmpByDirs(-1, 'aaab', 'aabb')
447
self.assertCmpByDirs(-1, 'aabb', 'abbb')
448
self.assertCmpByDirs(-1, 'abbb', 'bbbb')
449
self.assertCmpByDirs(-1, 'aaaaa', 'aaaab')
450
self.assertCmpByDirs(-1, 'a/a', 'a/b')
451
self.assertCmpByDirs(-1, 'a/b', 'b/b')
452
self.assertCmpByDirs(-1, 'a/a/a', 'a/a/b')
453
self.assertCmpByDirs(-1, 'a/a/b', 'a/b/b')
454
self.assertCmpByDirs(-1, 'a/b/b', 'b/b/b')
455
self.assertCmpByDirs(-1, 'a/a/a/a', 'a/a/a/b')
456
self.assertCmpByDirs(-1, 'a/a/a/b', 'a/a/b/b')
457
self.assertCmpByDirs(-1, 'a/a/b/b', 'a/b/b/b')
458
self.assertCmpByDirs(-1, 'a/b/b/b', 'b/b/b/b')
459
self.assertCmpByDirs(-1, 'a/a/a/a/a', 'a/a/a/a/b')
461
def test_tricky_paths(self):
462
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/cc/ef')
463
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/c/ef')
464
self.assertCmpByDirs(-1, 'ab/cd/ef', 'ab/cd-ef')
465
self.assertCmpByDirs(-1, 'ab/cd', 'ab/cd-')
466
self.assertCmpByDirs(-1, 'ab/cd', 'ab-cd')
468
def test_cmp_unicode_not_allowed(self):
469
cmp_by_dirs = self.get_cmp_by_dirs()
470
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', 'str')
471
self.assertRaises(TypeError, cmp_by_dirs, 'str', u'Unicode')
472
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', u'Unicode')
474
def test_cmp_non_ascii(self):
475
self.assertCmpByDirs(-1, '\xc2\xb5', '\xc3\xa5') # u'\xb5', u'\xe5'
476
self.assertCmpByDirs(-1, 'a', '\xc3\xa5') # u'a', u'\xe5'
477
self.assertCmpByDirs(-1, 'b', '\xc2\xb5') # u'b', u'\xb5'
478
self.assertCmpByDirs(-1, 'a/b', 'a/\xc3\xa5') # u'a/b', u'a/\xe5'
479
self.assertCmpByDirs(-1, 'b/a', 'b/\xc2\xb5') # u'b/a', u'b/\xb5'
482
class TestCompiledCmpByDirs(TestCmpByDirs):
483
"""Test the pyrex implementation of cmp_by_dirs"""
485
_test_needs_features = [compiled_dirstate_helpers_feature]
487
def get_cmp_by_dirs(self):
488
from bzrlib._dirstate_helpers_pyx import cmp_by_dirs
492
class TestCmpPathByDirblock(tests.TestCase):
493
"""Test an implementation of _cmp_path_by_dirblock()
495
_cmp_path_by_dirblock() compares two paths using the sort order used by
496
DirState. All paths in the same directory are sorted together.
498
Child test cases can override ``get_cmp_path_by_dirblock`` to test a specific
502
def get_cmp_path_by_dirblock(self):
503
"""Get a specific implementation of _cmp_path_by_dirblock."""
504
from bzrlib._dirstate_helpers_py import _cmp_path_by_dirblock
505
return _cmp_path_by_dirblock
507
def assertCmpPathByDirblock(self, paths):
508
"""Compare all paths and make sure they evaluate to the correct order.
510
This does N^2 comparisons. It is assumed that ``paths`` is properly
513
:param paths: a sorted list of paths to compare
515
# First, make sure the paths being passed in are correct
517
dirname, basename = os.path.split(p)
518
return dirname.split('/'), basename
519
self.assertEqual(sorted(paths, key=_key), paths)
521
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
522
for idx1, path1 in enumerate(paths):
523
for idx2, path2 in enumerate(paths):
524
cmp_val = cmp_path_by_dirblock(path1, path2)
526
self.assertTrue(cmp_val < 0,
527
'%s did not state that %r came before %r, cmp=%s'
528
% (cmp_path_by_dirblock.__name__,
529
path1, path2, cmp_val))
531
self.assertTrue(cmp_val > 0,
532
'%s did not state that %r came after %r, cmp=%s'
533
% (cmp_path_by_dirblock.__name__,
534
path1, path2, cmp_val))
536
self.assertTrue(cmp_val == 0,
537
'%s did not state that %r == %r, cmp=%s'
538
% (cmp_path_by_dirblock.__name__,
539
path1, path2, cmp_val))
541
def test_cmp_simple_paths(self):
542
"""Compare against the empty string."""
543
self.assertCmpPathByDirblock(['', 'a', 'ab', 'abc', 'a/b/c', 'b/d/e'])
544
self.assertCmpPathByDirblock(['kl', 'ab/cd', 'ab/ef', 'gh/ij'])
546
def test_tricky_paths(self):
547
self.assertCmpPathByDirblock([
549
'', 'a', 'a-a', 'a=a', 'b',
551
'a/a', 'a/a-a', 'a/a=a', 'a/b',
553
'a/a/a', 'a/a/a-a', 'a/a/a=a',
554
# Contents of 'a/a/a'
555
'a/a/a/a', 'a/a/a/b',
556
# Contents of 'a/a/a-a',
557
'a/a/a-a/a', 'a/a/a-a/b',
558
# Contents of 'a/a/a=a',
559
'a/a/a=a/a', 'a/a/a=a/b',
560
# Contents of 'a/a-a'
562
# Contents of 'a/a-a/a'
563
'a/a-a/a/a', 'a/a-a/a/b',
564
# Contents of 'a/a=a'
575
self.assertCmpPathByDirblock([
577
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
579
'a/a', 'a/a-a', 'a/a-z',
581
'a/z', 'a/z-a', 'a/z-z',
605
def test_unicode_not_allowed(self):
606
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
607
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', 'str')
608
self.assertRaises(TypeError, cmp_path_by_dirblock, 'str', u'Uni')
609
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', u'Uni')
610
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', 'x/str')
611
self.assertRaises(TypeError, cmp_path_by_dirblock, 'x/str', u'x/Uni')
612
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', u'x/Uni')
614
def test_nonascii(self):
615
self.assertCmpPathByDirblock([
617
'', 'a', '\xc2\xb5', '\xc3\xa5',
619
'a/a', 'a/\xc2\xb5', 'a/\xc3\xa5',
621
'a/a/a', 'a/a/\xc2\xb5', 'a/a/\xc3\xa5',
622
# content of 'a/\xc2\xb5'
623
'a/\xc2\xb5/a', 'a/\xc2\xb5/\xc2\xb5', 'a/\xc2\xb5/\xc3\xa5',
624
# content of 'a/\xc3\xa5'
625
'a/\xc3\xa5/a', 'a/\xc3\xa5/\xc2\xb5', 'a/\xc3\xa5/\xc3\xa5',
626
# content of '\xc2\xb5'
627
'\xc2\xb5/a', '\xc2\xb5/\xc2\xb5', '\xc2\xb5/\xc3\xa5',
628
# content of '\xc2\xe5'
629
'\xc3\xa5/a', '\xc3\xa5/\xc2\xb5', '\xc3\xa5/\xc3\xa5',
633
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
634
"""Test the pyrex implementation of _cmp_path_by_dirblock"""
636
_test_needs_features = [compiled_dirstate_helpers_feature]
638
def get_cmp_by_dirs(self):
639
from bzrlib._dirstate_helpers_pyx import _cmp_path_by_dirblock
640
return _cmp_path_by_dirblock
643
class TestMemRChr(tests.TestCase):
644
"""Test memrchr functionality"""
646
_test_needs_features = [compiled_dirstate_helpers_feature]
648
def assertMemRChr(self, expected, s, c):
649
from bzrlib._dirstate_helpers_pyx import _py_memrchr
650
self.assertEqual(expected, _py_memrchr(s, c))
652
def test_missing(self):
653
self.assertMemRChr(None, '', 'a')
654
self.assertMemRChr(None, '', 'c')
655
self.assertMemRChr(None, 'abcdefghijklm', 'q')
656
self.assertMemRChr(None, 'aaaaaaaaaaaaaaaaaaaaaaa', 'b')
658
def test_single_entry(self):
659
self.assertMemRChr(0, 'abcdefghijklm', 'a')
660
self.assertMemRChr(1, 'abcdefghijklm', 'b')
661
self.assertMemRChr(2, 'abcdefghijklm', 'c')
662
self.assertMemRChr(10, 'abcdefghijklm', 'k')
663
self.assertMemRChr(11, 'abcdefghijklm', 'l')
664
self.assertMemRChr(12, 'abcdefghijklm', 'm')
666
def test_multiple(self):
667
self.assertMemRChr(10, 'abcdefjklmabcdefghijklm', 'a')
668
self.assertMemRChr(11, 'abcdefjklmabcdefghijklm', 'b')
669
self.assertMemRChr(12, 'abcdefjklmabcdefghijklm', 'c')
670
self.assertMemRChr(20, 'abcdefjklmabcdefghijklm', 'k')
671
self.assertMemRChr(21, 'abcdefjklmabcdefghijklm', 'l')
672
self.assertMemRChr(22, 'abcdefjklmabcdefghijklm', 'm')
673
self.assertMemRChr(22, 'aaaaaaaaaaaaaaaaaaaaaaa', 'a')
675
def test_with_nulls(self):
676
self.assertMemRChr(10, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'a')
677
self.assertMemRChr(11, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'b')
678
self.assertMemRChr(12, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'c')
679
self.assertMemRChr(20, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'k')
680
self.assertMemRChr(21, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'l')
681
self.assertMemRChr(22, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'm')
682
self.assertMemRChr(22, 'aaa\0\0\0aaaaaaa\0\0\0aaaaaaa', 'a')
683
self.assertMemRChr(9, '\0\0\0\0\0\0\0\0\0\0', '\0')
686
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
687
"""Test an implementation of _read_dirblocks()
689
_read_dirblocks() reads in all of the dirblock information from the disk
692
Child test cases can override ``get_read_dirblocks`` to test a specific
696
# inherits scenarios from test_dirstate
698
def get_read_dirblocks(self):
699
from bzrlib._dirstate_helpers_py import _read_dirblocks
700
return _read_dirblocks
702
def test_smoketest(self):
703
"""Make sure that we can create and read back a simple file."""
704
tree, state, expected = self.create_basic_dirstate()
706
state._read_header_if_needed()
707
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
708
state._dirblock_state)
709
read_dirblocks = self.get_read_dirblocks()
710
read_dirblocks(state)
711
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
712
state._dirblock_state)
714
def test_trailing_garbage(self):
715
tree, state, expected = self.create_basic_dirstate()
716
# On Unix, we can write extra data as long as we haven't read yet, but
717
# on Win32, if you've opened the file with FILE_SHARE_READ, trying to
718
# open it in append mode will fail.
720
f = open('dirstate', 'ab')
722
# Add bogus trailing garbage
727
e = self.assertRaises(errors.DirstateCorrupt,
728
state._read_dirblocks_if_needed)
729
# Make sure we mention the bogus characters in the error
730
self.assertContainsRe(str(e), 'bogus')
733
class TestCompiledReadDirblocks(TestReadDirblocks):
734
"""Test the pyrex implementation of _read_dirblocks"""
736
_test_needs_features = [compiled_dirstate_helpers_feature]
738
def get_read_dirblocks(self):
739
from bzrlib._dirstate_helpers_pyx import _read_dirblocks
740
return _read_dirblocks
743
class TestUsingCompiledIfAvailable(tests.TestCase):
744
"""Check that any compiled functions that are available are the default.
746
It is possible to have typos, etc in the import line, such that
747
_dirstate_helpers_pyx is actually available, but the compiled functions are
751
def test_bisect_dirblock(self):
752
if compiled_dirstate_helpers_feature.available():
753
from bzrlib._dirstate_helpers_pyx import bisect_dirblock
755
from bzrlib._dirstate_helpers_py import bisect_dirblock
756
self.assertIs(bisect_dirblock, dirstate.bisect_dirblock)
758
def test__bisect_path_left(self):
759
if compiled_dirstate_helpers_feature.available():
760
from bzrlib._dirstate_helpers_pyx import _bisect_path_left
762
from bzrlib._dirstate_helpers_py import _bisect_path_left
763
self.assertIs(_bisect_path_left, dirstate._bisect_path_left)
765
def test__bisect_path_right(self):
766
if compiled_dirstate_helpers_feature.available():
767
from bzrlib._dirstate_helpers_pyx import _bisect_path_right
769
from bzrlib._dirstate_helpers_py import _bisect_path_right
770
self.assertIs(_bisect_path_right, dirstate._bisect_path_right)
772
def test_cmp_by_dirs(self):
773
if compiled_dirstate_helpers_feature.available():
774
from bzrlib._dirstate_helpers_pyx import cmp_by_dirs
776
from bzrlib._dirstate_helpers_py import cmp_by_dirs
777
self.assertIs(cmp_by_dirs, dirstate.cmp_by_dirs)
779
def test__read_dirblocks(self):
780
if compiled_dirstate_helpers_feature.available():
781
from bzrlib._dirstate_helpers_pyx import _read_dirblocks
783
from bzrlib._dirstate_helpers_py import _read_dirblocks
784
self.assertIs(_read_dirblocks, dirstate._read_dirblocks)
786
def test_update_entry(self):
787
if compiled_dirstate_helpers_feature.available():
788
from bzrlib._dirstate_helpers_pyx import update_entry
790
from bzrlib.dirstate import update_entry
791
self.assertIs(update_entry, dirstate.update_entry)
793
def test_process_entry(self):
794
if compiled_dirstate_helpers_feature.available():
795
from bzrlib._dirstate_helpers_pyx import ProcessEntryC
796
self.assertIs(ProcessEntryC, dirstate._process_entry)
798
from bzrlib.dirstate import ProcessEntryPython
799
self.assertIs(ProcessEntryPython, dirstate._process_entry)
802
class TestUpdateEntry(test_dirstate.TestCaseWithDirState):
803
"""Test the DirState.update_entry functions"""
805
scenarios = multiply_scenarios(
806
dir_reader_scenarios(), ue_scenarios)
812
super(TestUpdateEntry, self).setUp()
813
self.overrideAttr(dirstate, 'update_entry', self.update_entry)
815
def get_state_with_a(self):
816
"""Create a DirState tracking a single object named 'a'"""
817
state = test_dirstate.InstrumentedDirState.initialize('dirstate')
818
self.addCleanup(state.unlock)
819
state.add('a', 'a-id', 'file', None, '')
820
entry = state._get_entry(0, path_utf8='a')
823
def test_observed_sha1_cachable(self):
824
state, entry = self.get_state_with_a()
826
atime = time.time() - 10
827
self.build_tree(['a'])
828
statvalue = test_dirstate._FakeStat.from_stat(os.lstat('a'))
829
statvalue.st_mtime = statvalue.st_ctime = atime
830
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
831
state._dirblock_state)
832
state._observed_sha1(entry, "foo", statvalue)
833
self.assertEqual('foo', entry[1][0][1])
834
packed_stat = dirstate.pack_stat(statvalue)
835
self.assertEqual(packed_stat, entry[1][0][4])
836
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
837
state._dirblock_state)
839
def test_observed_sha1_not_cachable(self):
840
state, entry = self.get_state_with_a()
842
oldval = entry[1][0][1]
843
oldstat = entry[1][0][4]
844
self.build_tree(['a'])
845
statvalue = os.lstat('a')
846
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
847
state._dirblock_state)
848
state._observed_sha1(entry, "foo", statvalue)
849
self.assertEqual(oldval, entry[1][0][1])
850
self.assertEqual(oldstat, entry[1][0][4])
851
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
852
state._dirblock_state)
854
def test_update_entry(self):
855
state, _ = self.get_state_with_a()
856
tree = self.make_branch_and_tree('tree')
858
empty_revid = tree.commit('empty')
859
self.build_tree(['tree/a'])
860
tree.add(['a'], ['a-id'])
861
with_a_id = tree.commit('with_a')
862
self.addCleanup(tree.unlock)
863
state.set_parent_trees(
864
[(empty_revid, tree.branch.repository.revision_tree(empty_revid))],
866
entry = state._get_entry(0, path_utf8='a')
867
self.build_tree(['a'])
868
# Add one where we don't provide the stat or sha already
869
self.assertEqual(('', 'a', 'a-id'), entry[0])
870
self.assertEqual(('f', '', 0, False, dirstate.DirState.NULLSTAT),
872
# Flush the buffers to disk
874
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
875
state._dirblock_state)
877
stat_value = os.lstat('a')
878
packed_stat = dirstate.pack_stat(stat_value)
879
link_or_sha1 = self.update_entry(state, entry, abspath='a',
880
stat_value=stat_value)
881
self.assertEqual(None, link_or_sha1)
883
# The dirblock entry should not have computed or cached the file's
884
# sha1, but it did update the files' st_size. However, this is not
885
# worth writing a dirstate file for, so we leave the state UNMODIFIED
886
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
888
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
889
state._dirblock_state)
890
mode = stat_value.st_mode
891
self.assertEqual([('is_exec', mode, False)], state._log)
894
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
895
state._dirblock_state)
897
# Roll the clock back so the file is guaranteed to look too new. We
898
# should still not compute the sha1.
899
state.adjust_time(-10)
902
link_or_sha1 = self.update_entry(state, entry, abspath='a',
903
stat_value=stat_value)
904
self.assertEqual([('is_exec', mode, False)], state._log)
905
self.assertEqual(None, link_or_sha1)
906
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
907
state._dirblock_state)
908
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
912
# If it is cachable (the clock has moved forward) but new it still
913
# won't calculate the sha or cache it.
914
state.adjust_time(+20)
916
link_or_sha1 = dirstate.update_entry(state, entry, abspath='a',
917
stat_value=stat_value)
918
self.assertEqual(None, link_or_sha1)
919
self.assertEqual([('is_exec', mode, False)], state._log)
920
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
922
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
923
state._dirblock_state)
925
# If the file is no longer new, and the clock has been moved forward
926
# sufficiently, it will cache the sha.
928
state.set_parent_trees(
929
[(with_a_id, tree.branch.repository.revision_tree(with_a_id))],
931
entry = state._get_entry(0, path_utf8='a')
933
link_or_sha1 = self.update_entry(state, entry, abspath='a',
934
stat_value=stat_value)
935
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
937
self.assertEqual([('is_exec', mode, False), ('sha1', 'a')],
939
self.assertEqual(('f', link_or_sha1, 14, False, packed_stat),
942
# Subsequent calls will just return the cached value
944
link_or_sha1 = self.update_entry(state, entry, abspath='a',
945
stat_value=stat_value)
946
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
948
self.assertEqual([], state._log)
949
self.assertEqual(('f', link_or_sha1, 14, False, packed_stat),
952
def test_update_entry_symlink(self):
953
"""Update entry should read symlinks."""
954
self.requireFeature(tests.SymlinkFeature)
955
state, entry = self.get_state_with_a()
957
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
958
state._dirblock_state)
959
os.symlink('target', 'a')
961
state.adjust_time(-10) # Make the symlink look new
962
stat_value = os.lstat('a')
963
packed_stat = dirstate.pack_stat(stat_value)
964
link_or_sha1 = self.update_entry(state, entry, abspath='a',
965
stat_value=stat_value)
966
self.assertEqual('target', link_or_sha1)
967
self.assertEqual([('read_link', 'a', '')], state._log)
968
# Dirblock is not updated (the link is too new)
969
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
971
# The file entry turned into a symlink, that is considered
972
# HASH modified worthy.
973
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
974
state._dirblock_state)
976
# Because the stat_value looks new, we should re-read the target
978
link_or_sha1 = self.update_entry(state, entry, abspath='a',
979
stat_value=stat_value)
980
self.assertEqual('target', link_or_sha1)
981
self.assertEqual([('read_link', 'a', '')], state._log)
982
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
985
state.adjust_time(+20) # Skip into the future, all files look old
987
link_or_sha1 = self.update_entry(state, entry, abspath='a',
988
stat_value=stat_value)
989
# The symlink stayed a symlink. So while it is new enough to cache, we
990
# don't bother setting the flag, because it is not really worth saving
991
# (when we stat the symlink, we'll have paged in the target.)
992
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
993
state._dirblock_state)
994
self.assertEqual('target', link_or_sha1)
995
# We need to re-read the link because only now can we cache it
996
self.assertEqual([('read_link', 'a', '')], state._log)
997
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1001
# Another call won't re-read the link
1002
self.assertEqual([], state._log)
1003
link_or_sha1 = self.update_entry(state, entry, abspath='a',
1004
stat_value=stat_value)
1005
self.assertEqual('target', link_or_sha1)
1006
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1009
def do_update_entry(self, state, entry, abspath):
1010
stat_value = os.lstat(abspath)
1011
return self.update_entry(state, entry, abspath, stat_value)
1013
def test_update_entry_dir(self):
1014
state, entry = self.get_state_with_a()
1015
self.build_tree(['a/'])
1016
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1018
def test_update_entry_dir_unchanged(self):
1019
state, entry = self.get_state_with_a()
1020
self.build_tree(['a/'])
1021
state.adjust_time(+20)
1022
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1023
# a/ used to be a file, but is now a directory, worth saving
1024
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1025
state._dirblock_state)
1027
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1028
state._dirblock_state)
1029
# No changes to a/ means not worth saving.
1030
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1031
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1032
state._dirblock_state)
1033
# Change the last-modified time for the directory
1034
t = time.time() - 100.0
1035
os.utime('a', (t, t))
1036
saved_packed_stat = entry[1][0][-1]
1037
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1038
# We *do* go ahead and update the information in the dirblocks, but we
1039
# don't bother setting IN_MEMORY_MODIFIED because it is trivial to
1041
self.assertNotEqual(saved_packed_stat, entry[1][0][-1])
1042
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1043
state._dirblock_state)
1045
def test_update_entry_file_unchanged(self):
1046
state, _ = self.get_state_with_a()
1047
tree = self.make_branch_and_tree('tree')
1049
self.build_tree(['tree/a'])
1050
tree.add(['a'], ['a-id'])
1051
with_a_id = tree.commit('witha')
1052
self.addCleanup(tree.unlock)
1053
state.set_parent_trees(
1054
[(with_a_id, tree.branch.repository.revision_tree(with_a_id))],
1056
entry = state._get_entry(0, path_utf8='a')
1057
self.build_tree(['a'])
1058
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1059
state.adjust_time(+20)
1060
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1061
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1062
state._dirblock_state)
1064
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1065
state._dirblock_state)
1066
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1067
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1068
state._dirblock_state)
1070
def test_update_entry_tree_reference(self):
1071
state = test_dirstate.InstrumentedDirState.initialize('dirstate')
1072
self.addCleanup(state.unlock)
1073
state.add('r', 'r-id', 'tree-reference', None, '')
1074
self.build_tree(['r/'])
1075
entry = state._get_entry(0, path_utf8='r')
1076
self.do_update_entry(state, entry, 'r')
1077
entry = state._get_entry(0, path_utf8='r')
1078
self.assertEqual('t', entry[1][0][0])
1080
def create_and_test_file(self, state, entry):
1081
"""Create a file at 'a' and verify the state finds it during update.
1083
The state should already be versioning *something* at 'a'. This makes
1084
sure that state.update_entry recognizes it as a file.
1086
self.build_tree(['a'])
1087
stat_value = os.lstat('a')
1088
packed_stat = dirstate.pack_stat(stat_value)
1090
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1091
self.assertEqual(None, link_or_sha1)
1092
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1096
def create_and_test_dir(self, state, entry):
1097
"""Create a directory at 'a' and verify the state finds it.
1099
The state should already be versioning *something* at 'a'. This makes
1100
sure that state.update_entry recognizes it as a directory.
1102
self.build_tree(['a/'])
1103
stat_value = os.lstat('a')
1104
packed_stat = dirstate.pack_stat(stat_value)
1106
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1107
self.assertIs(None, link_or_sha1)
1108
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1112
# FIXME: Add unicode version
1113
def create_and_test_symlink(self, state, entry):
1114
"""Create a symlink at 'a' and verify the state finds it.
1116
The state should already be versioning *something* at 'a'. This makes
1117
sure that state.update_entry recognizes it as a symlink.
1119
This should not be called if this platform does not have symlink
1122
# caller should care about skipping test on platforms without symlinks
1123
os.symlink('path/to/foo', 'a')
1125
stat_value = os.lstat('a')
1126
packed_stat = dirstate.pack_stat(stat_value)
1128
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1129
self.assertEqual('path/to/foo', link_or_sha1)
1130
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1134
def test_update_file_to_dir(self):
1135
"""If a file changes to a directory we return None for the sha.
1136
We also update the inventory record.
1138
state, entry = self.get_state_with_a()
1139
# The file sha1 won't be cached unless the file is old
1140
state.adjust_time(+10)
1141
self.create_and_test_file(state, entry)
1143
self.create_and_test_dir(state, entry)
1145
def test_update_file_to_symlink(self):
1146
"""File becomes a symlink"""
1147
self.requireFeature(tests.SymlinkFeature)
1148
state, entry = self.get_state_with_a()
1149
# The file sha1 won't be cached unless the file is old
1150
state.adjust_time(+10)
1151
self.create_and_test_file(state, entry)
1153
self.create_and_test_symlink(state, entry)
1155
def test_update_dir_to_file(self):
1156
"""Directory becoming a file updates the entry."""
1157
state, entry = self.get_state_with_a()
1158
# The file sha1 won't be cached unless the file is old
1159
state.adjust_time(+10)
1160
self.create_and_test_dir(state, entry)
1162
self.create_and_test_file(state, entry)
1164
def test_update_dir_to_symlink(self):
1165
"""Directory becomes a symlink"""
1166
self.requireFeature(tests.SymlinkFeature)
1167
state, entry = self.get_state_with_a()
1168
# The symlink target won't be cached if it isn't old
1169
state.adjust_time(+10)
1170
self.create_and_test_dir(state, entry)
1172
self.create_and_test_symlink(state, entry)
1174
def test_update_symlink_to_file(self):
1175
"""Symlink becomes a file"""
1176
self.requireFeature(tests.SymlinkFeature)
1177
state, entry = self.get_state_with_a()
1178
# The symlink and file info won't be cached unless old
1179
state.adjust_time(+10)
1180
self.create_and_test_symlink(state, entry)
1182
self.create_and_test_file(state, entry)
1184
def test_update_symlink_to_dir(self):
1185
"""Symlink becomes a directory"""
1186
self.requireFeature(tests.SymlinkFeature)
1187
state, entry = self.get_state_with_a()
1188
# The symlink target won't be cached if it isn't old
1189
state.adjust_time(+10)
1190
self.create_and_test_symlink(state, entry)
1192
self.create_and_test_dir(state, entry)
1194
def test__is_executable_win32(self):
1195
state, entry = self.get_state_with_a()
1196
self.build_tree(['a'])
1198
# Make sure we are using the win32 implementation of _is_executable
1199
state._is_executable = state._is_executable_win32
1201
# The file on disk is not executable, but we are marking it as though
1202
# it is. With _is_executable_win32 we ignore what is on disk.
1203
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1205
stat_value = os.lstat('a')
1206
packed_stat = dirstate.pack_stat(stat_value)
1208
state.adjust_time(-10) # Make sure everything is new
1209
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1211
# The row is updated, but the executable bit stays set.
1212
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1215
# Make the disk object look old enough to cache (but it won't cache the
1216
# sha as it is a new file).
1217
state.adjust_time(+20)
1218
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1219
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1220
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1223
def _prepare_tree(self):
1225
text = 'Hello World\n'
1226
tree = self.make_branch_and_tree('tree')
1227
self.build_tree_contents([('tree/a file', text)])
1228
tree.add('a file', 'a-file-id')
1229
# Note: dirstate does not sha prior to the first commit
1230
# so commit now in order for the test to work
1231
tree.commit('first')
1234
def test_sha1provider_sha1_used(self):
1235
tree, text = self._prepare_tree()
1236
state = dirstate.DirState.from_tree(tree, 'dirstate',
1237
UppercaseSHA1Provider())
1238
self.addCleanup(state.unlock)
1239
expected_sha = osutils.sha_string(text.upper() + "foo")
1240
entry = state._get_entry(0, path_utf8='a file')
1241
state._sha_cutoff_time()
1242
state._cutoff_time += 10
1243
sha1 = self.update_entry(state, entry, 'tree/a file',
1244
os.lstat('tree/a file'))
1245
self.assertEqual(expected_sha, sha1)
1247
def test_sha1provider_stat_and_sha1_used(self):
1248
tree, text = self._prepare_tree()
1250
self.addCleanup(tree.unlock)
1251
state = tree._current_dirstate()
1252
state._sha1_provider = UppercaseSHA1Provider()
1253
# If we used the standard provider, it would look like nothing has
1255
file_ids_changed = [change[0] for change
1256
in tree.iter_changes(tree.basis_tree())]
1257
self.assertEqual(['a-file-id'], file_ids_changed)
1260
class UppercaseSHA1Provider(dirstate.SHA1Provider):
1261
"""A custom SHA1Provider."""
1263
def sha1(self, abspath):
1264
return self.stat_and_sha1(abspath)[1]
1266
def stat_and_sha1(self, abspath):
1267
file_obj = file(abspath, 'rb')
1269
statvalue = os.fstat(file_obj.fileno())
1270
text = ''.join(file_obj.readlines())
1271
sha1 = osutils.sha_string(text.upper() + "foo")
1274
return statvalue, sha1
1277
class TestProcessEntry(test_dirstate.TestCaseWithDirState):
1279
scenarios = multiply_scenarios(dir_reader_scenarios(), pe_scenarios)
1282
_process_entry = None
1285
super(TestProcessEntry, self).setUp()
1286
self.overrideAttr(dirstate, '_process_entry', self._process_entry)
1288
def assertChangedFileIds(self, expected, tree):
1291
file_ids = [info[0] for info
1292
in tree.iter_changes(tree.basis_tree())]
1295
self.assertEqual(sorted(expected), sorted(file_ids))
1297
def test_exceptions_raised(self):
1298
# This is a direct test of bug #495023, it relies on osutils.is_inside
1299
# getting called in an inner function. Which makes it a bit brittle,
1300
# but at least it does reproduce the bug.
1301
tree = self.make_branch_and_tree('tree')
1302
self.build_tree(['tree/file', 'tree/dir/', 'tree/dir/sub',
1303
'tree/dir2/', 'tree/dir2/sub2'])
1304
tree.add(['file', 'dir', 'dir/sub', 'dir2', 'dir2/sub2'])
1305
tree.commit('first commit')
1307
self.addCleanup(tree.unlock)
1308
basis_tree = tree.basis_tree()
1309
def is_inside_raises(*args, **kwargs):
1310
raise RuntimeError('stop this')
1311
self.overrideAttr(osutils, 'is_inside', is_inside_raises)
1312
self.assertListRaises(RuntimeError, tree.iter_changes, basis_tree)
1314
def test_simple_changes(self):
1315
tree = self.make_branch_and_tree('tree')
1316
self.build_tree(['tree/file'])
1317
tree.add(['file'], ['file-id'])
1318
self.assertChangedFileIds([tree.get_root_id(), 'file-id'], tree)
1320
self.assertChangedFileIds([], tree)
1322
def test_sha1provider_stat_and_sha1_used(self):
1323
tree = self.make_branch_and_tree('tree')
1324
self.build_tree(['tree/file'])
1325
tree.add(['file'], ['file-id'])
1328
self.addCleanup(tree.unlock)
1329
state = tree._current_dirstate()
1330
state._sha1_provider = UppercaseSHA1Provider()
1331
self.assertChangedFileIds(['file-id'], tree)