~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

(jameinel) (bug #780544) when updating the WT,
 allow it to be done with a fast delta,
 rather than setting the state from scratch. (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
18
18
 
19
19
import os
 
20
import tempfile
20
21
 
21
22
from bzrlib import (
 
23
    bzrdir,
22
24
    dirstate,
23
25
    errors,
24
26
    inventory,
25
27
    memorytree,
26
28
    osutils,
27
29
    revision as _mod_revision,
 
30
    revisiontree,
28
31
    tests,
 
32
    workingtree_4,
29
33
    )
 
34
from bzrlib.transport import memory
30
35
from bzrlib.tests import test_osutils
31
36
from bzrlib.tests.scenarios import load_tests_apply_scenarios
32
37
 
2421
2426
        self.assertTrue(len(statvalue) >= 10)
2422
2427
        self.assertEqual(len(text), statvalue.st_size)
2423
2428
        self.assertEqual(expected_sha, sha1)
 
2429
 
 
2430
 
 
2431
class _Repo(object):
 
2432
    """A minimal api to get InventoryRevisionTree to work."""
 
2433
 
 
2434
    def __init__(self):
 
2435
        default_format = bzrdir.format_registry.make_bzrdir('default')
 
2436
        self._format = default_format.repository_format
 
2437
 
 
2438
    def lock_read(self):
 
2439
        pass
 
2440
 
 
2441
    def unlock(self):
 
2442
        pass
 
2443
 
 
2444
 
 
2445
class TestUpdateBasisByDelta(tests.TestCase):
 
2446
 
 
2447
    def path_to_ie(self, path, file_id, rev_id, dir_ids):
 
2448
        if path.endswith('/'):
 
2449
            is_dir = True
 
2450
            path = path[:-1]
 
2451
        else:
 
2452
            is_dir = False
 
2453
        dirname, basename = osutils.split(path)
 
2454
        try:
 
2455
            dir_id = dir_ids[dirname]
 
2456
        except KeyError:
 
2457
            dir_id = osutils.basename(dirname) + '-id'
 
2458
        if is_dir:
 
2459
            ie = inventory.InventoryDirectory(file_id, basename, dir_id)
 
2460
            dir_ids[path] = file_id
 
2461
        else:
 
2462
            ie = inventory.InventoryFile(file_id, basename, dir_id)
 
2463
            ie.text_size = 0
 
2464
            ie.text_sha1 = ''
 
2465
        ie.revision = rev_id
 
2466
        return ie
 
2467
 
 
2468
    def create_tree_from_shape(self, rev_id, shape):
 
2469
        dir_ids = {'': 'root-id'}
 
2470
        inv = inventory.Inventory('root-id', rev_id)
 
2471
        for path, file_id in shape:
 
2472
            if path == '':
 
2473
                # Replace the root entry
 
2474
                del inv._byid[inv.root.file_id]
 
2475
                inv.root.file_id = file_id
 
2476
                inv._byid[file_id] = inv.root
 
2477
                dir_ids[''] = file_id
 
2478
                continue
 
2479
            inv.add(self.path_to_ie(path, file_id, rev_id, dir_ids))
 
2480
        return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
 
2481
 
 
2482
    def create_empty_dirstate(self):
 
2483
        fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
 
2484
        self.addCleanup(os.remove, path)
 
2485
        os.close(fd)
 
2486
        state = dirstate.DirState.initialize(path)
 
2487
        self.addCleanup(state.unlock)
 
2488
        return state
 
2489
 
 
2490
    def create_inv_delta(self, delta, rev_id):
 
2491
        """Translate a 'delta shape' into an actual InventoryDelta"""
 
2492
        dir_ids = {'': 'root-id'}
 
2493
        inv_delta = []
 
2494
        for old_path, new_path, file_id in delta:
 
2495
            if old_path is not None and old_path.endswith('/'):
 
2496
                # Don't have to actually do anything for this, because only
 
2497
                # new_path creates InventoryEntries
 
2498
                old_path = old_path[:-1]
 
2499
            if new_path is None: # Delete
 
2500
                inv_delta.append((old_path, None, file_id, None))
 
2501
                continue
 
2502
            ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
 
2503
            inv_delta.append((old_path, new_path, file_id, ie))
 
2504
        return inv_delta
 
2505
 
 
2506
    def assertUpdate(self, active, basis, target):
 
2507
        """Assert that update_basis_by_delta works how we want.
 
2508
 
 
2509
        Set up a DirState object with active_shape for tree 0, basis_shape for
 
2510
        tree 1. Then apply the delta from basis_shape to target_shape,
 
2511
        and assert that the DirState is still valid, and that its stored
 
2512
        content matches the target_shape.
 
2513
        """
 
2514
        active_tree = self.create_tree_from_shape('active', active)
 
2515
        basis_tree = self.create_tree_from_shape('basis', basis)
 
2516
        target_tree = self.create_tree_from_shape('target', target)
 
2517
        state = self.create_empty_dirstate()
 
2518
        state.set_state_from_scratch(active_tree.inventory,
 
2519
            [('basis', basis_tree)], [])
 
2520
        delta = target_tree.inventory._make_delta(basis_tree.inventory)
 
2521
        state.update_basis_by_delta(delta, 'target')
 
2522
        state._validate()
 
2523
        dirstate_tree = workingtree_4.DirStateRevisionTree(state,
 
2524
            'target', _Repo())
 
2525
        # The target now that delta has been applied should match the
 
2526
        # RevisionTree
 
2527
        self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
 
2528
        # And the dirblock state should be identical to the state if we created
 
2529
        # it from scratch.
 
2530
        state2 = self.create_empty_dirstate()
 
2531
        state2.set_state_from_scratch(active_tree.inventory,
 
2532
            [('target', target_tree)], [])
 
2533
        self.assertEqual(state2._dirblocks, state._dirblocks)
 
2534
        return state
 
2535
 
 
2536
    def assertBadDelta(self, active, basis, delta):
 
2537
        """Test that we raise InconsistentDelta when appropriate.
 
2538
 
 
2539
        :param active: The active tree shape
 
2540
        :param basis: The basis tree shape
 
2541
        :param delta: A description of the delta to apply. Similar to the form
 
2542
            for regular inventory deltas, but omitting the InventoryEntry.
 
2543
            So adding a file is: (None, 'path', 'file-id')
 
2544
            Adding a directory is: (None, 'path/', 'dir-id')
 
2545
            Renaming a dir is: ('old/', 'new/', 'dir-id')
 
2546
            etc.
 
2547
        """
 
2548
        active_tree = self.create_tree_from_shape('active', active)
 
2549
        basis_tree = self.create_tree_from_shape('basis', basis)
 
2550
        inv_delta = self.create_inv_delta(delta, 'target')
 
2551
        state = self.create_empty_dirstate()
 
2552
        state.set_state_from_scratch(active_tree.inventory,
 
2553
            [('basis', basis_tree)], [])
 
2554
        self.assertRaises(errors.InconsistentDelta,
 
2555
            state.update_basis_by_delta, inv_delta, 'target')
 
2556
        ## try:
 
2557
        ##     state.update_basis_by_delta(inv_delta, 'target')
 
2558
        ## except errors.InconsistentDelta, e:
 
2559
        ##     import pdb; pdb.set_trace()
 
2560
        ## else:
 
2561
        ##     import pdb; pdb.set_trace()
 
2562
        self.assertTrue(state._changes_aborted)
 
2563
 
 
2564
    def test_remove_file_matching_active_state(self):
 
2565
        state = self.assertUpdate(
 
2566
            active=[],
 
2567
            basis =[('file', 'file-id')],
 
2568
            target=[],
 
2569
            )
 
2570
 
 
2571
    def test_remove_file_present_in_active_state(self):
 
2572
        state = self.assertUpdate(
 
2573
            active=[('file', 'file-id')],
 
2574
            basis =[('file', 'file-id')],
 
2575
            target=[],
 
2576
            )
 
2577
 
 
2578
    def test_remove_file_present_elsewhere_in_active_state(self):
 
2579
        state = self.assertUpdate(
 
2580
            active=[('other-file', 'file-id')],
 
2581
            basis =[('file', 'file-id')],
 
2582
            target=[],
 
2583
            )
 
2584
 
 
2585
    def test_remove_file_active_state_has_diff_file(self):
 
2586
        state = self.assertUpdate(
 
2587
            active=[('file', 'file-id-2')],
 
2588
            basis =[('file', 'file-id')],
 
2589
            target=[],
 
2590
            )
 
2591
 
 
2592
    def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
 
2593
        state = self.assertUpdate(
 
2594
            active=[('file', 'file-id-2'),
 
2595
                    ('other-file', 'file-id')],
 
2596
            basis =[('file', 'file-id')],
 
2597
            target=[],
 
2598
            )
 
2599
 
 
2600
    def test_add_file_matching_active_state(self):
 
2601
        state = self.assertUpdate(
 
2602
            active=[('file', 'file-id')],
 
2603
            basis =[],
 
2604
            target=[('file', 'file-id')],
 
2605
            )
 
2606
 
 
2607
    def test_add_file_missing_in_active_state(self):
 
2608
        state = self.assertUpdate(
 
2609
            active=[],
 
2610
            basis =[],
 
2611
            target=[('file', 'file-id')],
 
2612
            )
 
2613
 
 
2614
    def test_add_file_elsewhere_in_active_state(self):
 
2615
        state = self.assertUpdate(
 
2616
            active=[('other-file', 'file-id')],
 
2617
            basis =[],
 
2618
            target=[('file', 'file-id')],
 
2619
            )
 
2620
 
 
2621
    def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
 
2622
        state = self.assertUpdate(
 
2623
            active=[('other-file', 'file-id'),
 
2624
                    ('file', 'file-id-2')],
 
2625
            basis =[],
 
2626
            target=[('file', 'file-id')],
 
2627
            )
 
2628
 
 
2629
    def test_rename_file_matching_active_state(self):
 
2630
        state = self.assertUpdate(
 
2631
            active=[('other-file', 'file-id')],
 
2632
            basis =[('file', 'file-id')],
 
2633
            target=[('other-file', 'file-id')],
 
2634
            )
 
2635
 
 
2636
    def test_rename_file_missing_in_active_state(self):
 
2637
        state = self.assertUpdate(
 
2638
            active=[],
 
2639
            basis =[('file', 'file-id')],
 
2640
            target=[('other-file', 'file-id')],
 
2641
            )
 
2642
 
 
2643
    def test_rename_file_present_elsewhere_in_active_state(self):
 
2644
        state = self.assertUpdate(
 
2645
            active=[('third', 'file-id')],
 
2646
            basis =[('file', 'file-id')],
 
2647
            target=[('other-file', 'file-id')],
 
2648
            )
 
2649
 
 
2650
    def test_rename_file_active_state_has_diff_source_file(self):
 
2651
        state = self.assertUpdate(
 
2652
            active=[('file', 'file-id-2')],
 
2653
            basis =[('file', 'file-id')],
 
2654
            target=[('other-file', 'file-id')],
 
2655
            )
 
2656
 
 
2657
    def test_rename_file_active_state_has_diff_target_file(self):
 
2658
        state = self.assertUpdate(
 
2659
            active=[('other-file', 'file-id-2')],
 
2660
            basis =[('file', 'file-id')],
 
2661
            target=[('other-file', 'file-id')],
 
2662
            )
 
2663
 
 
2664
    def test_rename_file_active_has_swapped_files(self):
 
2665
        state = self.assertUpdate(
 
2666
            active=[('file', 'file-id'),
 
2667
                    ('other-file', 'file-id-2')],
 
2668
            basis= [('file', 'file-id'),
 
2669
                    ('other-file', 'file-id-2')],
 
2670
            target=[('file', 'file-id-2'),
 
2671
                    ('other-file', 'file-id')])
 
2672
 
 
2673
    def test_rename_file_basis_has_swapped_files(self):
 
2674
        state = self.assertUpdate(
 
2675
            active=[('file', 'file-id'),
 
2676
                    ('other-file', 'file-id-2')],
 
2677
            basis= [('file', 'file-id-2'),
 
2678
                    ('other-file', 'file-id')],
 
2679
            target=[('file', 'file-id'),
 
2680
                    ('other-file', 'file-id-2')])
 
2681
 
 
2682
    def test_rename_directory_with_contents(self):
 
2683
        state = self.assertUpdate( # active matches basis
 
2684
            active=[('dir1/', 'dir-id'),
 
2685
                    ('dir1/file', 'file-id')],
 
2686
            basis= [('dir1/', 'dir-id'),
 
2687
                    ('dir1/file', 'file-id')],
 
2688
            target=[('dir2/', 'dir-id'),
 
2689
                    ('dir2/file', 'file-id')])
 
2690
        state = self.assertUpdate( # active matches target
 
2691
            active=[('dir2/', 'dir-id'),
 
2692
                    ('dir2/file', 'file-id')],
 
2693
            basis= [('dir1/', 'dir-id'),
 
2694
                    ('dir1/file', 'file-id')],
 
2695
            target=[('dir2/', 'dir-id'),
 
2696
                    ('dir2/file', 'file-id')])
 
2697
        state = self.assertUpdate( # active empty
 
2698
            active=[],
 
2699
            basis= [('dir1/', 'dir-id'),
 
2700
                    ('dir1/file', 'file-id')],
 
2701
            target=[('dir2/', 'dir-id'),
 
2702
                    ('dir2/file', 'file-id')])
 
2703
        state = self.assertUpdate( # active present at other location
 
2704
            active=[('dir3/', 'dir-id'),
 
2705
                    ('dir3/file', 'file-id')],
 
2706
            basis= [('dir1/', 'dir-id'),
 
2707
                    ('dir1/file', 'file-id')],
 
2708
            target=[('dir2/', 'dir-id'),
 
2709
                    ('dir2/file', 'file-id')])
 
2710
        state = self.assertUpdate( # active has different ids
 
2711
            active=[('dir1/', 'dir1-id'),
 
2712
                    ('dir1/file', 'file1-id'),
 
2713
                    ('dir2/', 'dir2-id'),
 
2714
                    ('dir2/file', 'file2-id')],
 
2715
            basis= [('dir1/', 'dir-id'),
 
2716
                    ('dir1/file', 'file-id')],
 
2717
            target=[('dir2/', 'dir-id'),
 
2718
                    ('dir2/file', 'file-id')])
 
2719
 
 
2720
    def test_invalid_file_not_present(self):
 
2721
        state = self.assertBadDelta(
 
2722
            active=[('file', 'file-id')],
 
2723
            basis= [('file', 'file-id')],
 
2724
            delta=[('other-file', 'file', 'file-id')])
 
2725
 
 
2726
    def test_invalid_new_id_same_path(self):
 
2727
        # The bad entry comes after
 
2728
        state = self.assertBadDelta(
 
2729
            active=[('file', 'file-id')],
 
2730
            basis= [('file', 'file-id')],
 
2731
            delta=[(None, 'file', 'file-id-2')])
 
2732
        # The bad entry comes first
 
2733
        state = self.assertBadDelta(
 
2734
            active=[('file', 'file-id-2')],
 
2735
            basis=[('file', 'file-id-2')],
 
2736
            delta=[(None, 'file', 'file-id')])
 
2737
 
 
2738
    def test_invalid_existing_id(self):
 
2739
        state = self.assertBadDelta(
 
2740
            active=[('file', 'file-id')],
 
2741
            basis= [('file', 'file-id')],
 
2742
            delta=[(None, 'file', 'file-id')])
 
2743
 
 
2744
    def test_invalid_parent_missing(self):
 
2745
        state = self.assertBadDelta(
 
2746
            active=[],
 
2747
            basis= [],
 
2748
            delta=[(None, 'path/path2', 'file-id')])
 
2749
        # Note: we force the active tree to have the directory, by knowing how
 
2750
        #       path_to_ie handles entries with missing parents
 
2751
        state = self.assertBadDelta(
 
2752
            active=[('path/', 'path-id')],
 
2753
            basis= [],
 
2754
            delta=[(None, 'path/path2', 'file-id')])
 
2755
        state = self.assertBadDelta(
 
2756
            active=[('path/', 'path-id'),
 
2757
                    ('path/path2', 'file-id')],
 
2758
            basis= [],
 
2759
            delta=[(None, 'path/path2', 'file-id')])
 
2760
 
 
2761
    def test_renamed_dir_same_path(self):
 
2762
        # We replace the parent directory, with another parent dir. But the C
 
2763
        # file doesn't look like it has been moved.
 
2764
        state = self.assertUpdate(# Same as basis
 
2765
            active=[('dir/', 'A-id'),
 
2766
                    ('dir/B', 'B-id')],
 
2767
            basis= [('dir/', 'A-id'),
 
2768
                    ('dir/B', 'B-id')],
 
2769
            target=[('dir/', 'C-id'),
 
2770
                    ('dir/B', 'B-id')])
 
2771
        state = self.assertUpdate(# Same as target
 
2772
            active=[('dir/', 'C-id'),
 
2773
                    ('dir/B', 'B-id')],
 
2774
            basis= [('dir/', 'A-id'),
 
2775
                    ('dir/B', 'B-id')],
 
2776
            target=[('dir/', 'C-id'),
 
2777
                    ('dir/B', 'B-id')])
 
2778
        state = self.assertUpdate(# empty active
 
2779
            active=[],
 
2780
            basis= [('dir/', 'A-id'),
 
2781
                    ('dir/B', 'B-id')],
 
2782
            target=[('dir/', 'C-id'),
 
2783
                    ('dir/B', 'B-id')])
 
2784
        state = self.assertUpdate(# different active
 
2785
            active=[('dir/', 'D-id'),
 
2786
                    ('dir/B', 'B-id')],
 
2787
            basis= [('dir/', 'A-id'),
 
2788
                    ('dir/B', 'B-id')],
 
2789
            target=[('dir/', 'C-id'),
 
2790
                    ('dir/B', 'B-id')])
 
2791
 
 
2792
    def test_parent_child_swap(self):
 
2793
        state = self.assertUpdate(# Same as basis
 
2794
            active=[('A/', 'A-id'),
 
2795
                    ('A/B/', 'B-id'),
 
2796
                    ('A/B/C', 'C-id')],
 
2797
            basis= [('A/', 'A-id'),
 
2798
                    ('A/B/', 'B-id'),
 
2799
                    ('A/B/C', 'C-id')],
 
2800
            target=[('A/', 'B-id'),
 
2801
                    ('A/B/', 'A-id'),
 
2802
                    ('A/B/C', 'C-id')])
 
2803
        state = self.assertUpdate(# Same as target
 
2804
            active=[('A/', 'B-id'),
 
2805
                    ('A/B/', 'A-id'),
 
2806
                    ('A/B/C', 'C-id')],
 
2807
            basis= [('A/', 'A-id'),
 
2808
                    ('A/B/', 'B-id'),
 
2809
                    ('A/B/C', 'C-id')],
 
2810
            target=[('A/', 'B-id'),
 
2811
                    ('A/B/', 'A-id'),
 
2812
                    ('A/B/C', 'C-id')])
 
2813
        state = self.assertUpdate(# empty active
 
2814
            active=[],
 
2815
            basis= [('A/', 'A-id'),
 
2816
                    ('A/B/', 'B-id'),
 
2817
                    ('A/B/C', 'C-id')],
 
2818
            target=[('A/', 'B-id'),
 
2819
                    ('A/B/', 'A-id'),
 
2820
                    ('A/B/C', 'C-id')])
 
2821
        state = self.assertUpdate(# different active
 
2822
            active=[('D/', 'A-id'),
 
2823
                    ('D/E/', 'B-id'),
 
2824
                    ('F', 'C-id')],
 
2825
            basis= [('A/', 'A-id'),
 
2826
                    ('A/B/', 'B-id'),
 
2827
                    ('A/B/C', 'C-id')],
 
2828
            target=[('A/', 'B-id'),
 
2829
                    ('A/B/', 'A-id'),
 
2830
                    ('A/B/C', 'C-id')])
 
2831
 
 
2832
    def test_change_root_id(self):
 
2833
        state = self.assertUpdate( # same as basis
 
2834
            active=[('', 'root-id'),
 
2835
                    ('file', 'file-id')],
 
2836
            basis= [('', 'root-id'),
 
2837
                    ('file', 'file-id')],
 
2838
            target=[('', 'target-root-id'),
 
2839
                    ('file', 'file-id')])
 
2840
        state = self.assertUpdate( # same as target
 
2841
            active=[('', 'target-root-id'),
 
2842
                    ('file', 'file-id')],
 
2843
            basis= [('', 'root-id'),
 
2844
                    ('file', 'file-id')],
 
2845
            target=[('', 'target-root-id'),
 
2846
                    ('file', 'root-id')])
 
2847
        state = self.assertUpdate( # all different
 
2848
            active=[('', 'active-root-id'),
 
2849
                    ('file', 'file-id')],
 
2850
            basis= [('', 'root-id'),
 
2851
                    ('file', 'file-id')],
 
2852
            target=[('', 'target-root-id'),
 
2853
                    ('file', 'root-id')])
 
2854
 
 
2855
    def test_change_file_absent_in_active(self):
 
2856
        state = self.assertUpdate(
 
2857
            active=[],
 
2858
            basis= [('file', 'file-id')],
 
2859
            target=[('file', 'file-id')])
 
2860
 
 
2861
    def test_invalid_changed_file(self):
 
2862
        state = self.assertBadDelta( # Not present in basis
 
2863
            active=[('file', 'file-id')],
 
2864
            basis= [],
 
2865
            delta=[('file', 'file', 'file-id')])
 
2866
        state = self.assertBadDelta( # present at another location in basis
 
2867
            active=[('file', 'file-id')],
 
2868
            basis= [('other-file', 'file-id')],
 
2869
            delta=[('file', 'file', 'file-id')])