~launchpad-pqm/launchpad/devel

11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
1
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
8687.15.34 by Karl Fogel
Add license header blocks to .py, .zcml, and .pt files that don't have it
2
# GNU Affero General Public License version 3 (see the file LICENSE).
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
3
4
"""Tests for `DirectBranchCommit`."""
5
6
__metaclass__ = type
7
12001.3.25 by j.c.sackett
Fixed tests.
8
from zope.security.proxy import removeSecurityProxy
9
11592.1.3 by Jeroen Vermeulen
Review changes.
10
from canonical.testing.layers import (
11
    DatabaseFunctionalLayer,
12
    ZopelessDatabaseLayer,
13
    )
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
14
from lp.code.model.directbranchcommit import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
15
    ConcurrentUpdateError,
16
    DirectBranchCommit,
17
    )
18
from lp.testing import (
19
    map_branch_contents,
20
    TestCaseWithFactory,
21
    )
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
22
from lp.testing.fakemethod import FakeMethod
23
24
25
class DirectBranchCommitTestCase:
8963.9.1 by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed.
26
    """Base class for `DirectBranchCommit` tests."""
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
27
    db_branch = None
28
    committer = None
29
30
    def setUp(self):
8963.9.1 by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed.
31
        super(DirectBranchCommitTestCase, self).setUp()
9590.1.102 by Michael Hudson
undo some preliminary rosetta changes
32
        self.useBzrBranches(direct_database=True)
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
33
34
        self.series = self.factory.makeProductSeries()
9590.1.102 by Michael Hudson
undo some preliminary rosetta changes
35
        self.db_branch, self.tree = self.create_branch_and_tree()
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
36
37
        self.series.translations_branch = self.db_branch
38
39
        self._setUpCommitter()
8963.9.2 by Jeroen Vermeulen
Review changes.
40
        self.addCleanup(self._tearDownCommitter)
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
41
42
    def _setUpCommitter(self, update_last_scanned_id=True):
8963.9.1 by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed.
43
        """Clean up any existing `DirectBranchCommit`, set up a new one."""
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
44
        if self.committer:
45
            self.committer.unlock()
46
8871.3.5 by Jeroen Vermeulen
As per mwhudson's suggestion, keep bzrserver out of DirectBranchCommit entirely.
47
        self.committer = DirectBranchCommit(self.db_branch)
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
48
        if update_last_scanned_id:
9375.3.4 by Jeroen Vermeulen
Test broke because DirectBranchCommit being set up now reads last_scanned_id before the test setup pretends to scan.
49
            self.committer.last_scanned_id = (
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
50
                self.committer.bzrbranch.last_revision())
51
8963.9.2 by Jeroen Vermeulen
Review changes.
52
    def _tearDownCommitter(self):
53
        if self.committer:
54
            self.committer.unlock()
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
55
56
    def _getContents(self):
57
        """Return branch contents as dict mapping filenames to contents."""
9590.1.102 by Michael Hudson
undo some preliminary rosetta changes
58
        return map_branch_contents(self.committer.db_branch.getBzrBranch())
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
59
8963.9.1 by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed.
60
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
61
class TestDirectBranchCommit(DirectBranchCommitTestCase, TestCaseWithFactory):
8963.9.1 by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed.
62
    """Test `DirectBranchCommit`."""
63
64
    layer = ZopelessDatabaseLayer
65
11592.1.2 by Jeroen Vermeulen
Test previously untested default committer.
66
    def test_defaults_to_branch_owner(self):
67
        # If no committer is given, DirectBranchCommits defaults to
68
        # attributing changes to the branch owner.
69
        self.assertEqual(self.db_branch.owner, self.committer.committer)
70
7675.310.5 by Aaron Bentley
Updates from review.
71
    def test_DirectBranchCommit_empty_initial_commit_noop(self):
72
        # An empty initial commit to a branch is a no-op.
7675.310.3 by Aaron Bentley
Clean up DirectBranchCommit.
73
        self.assertEqual('null:', self.tree.branch.last_revision())
74
        self.committer.commit('')
75
        self.assertEqual({}, self._getContents())
76
        self.assertEqual('null:', self.tree.branch.last_revision())
77
78
    def _addInitialCommit(self):
79
        self.committer._getDir('')
80
        rev_id = self.committer.commit('Commit creation of root dir.')
81
        self._setUpCommitter()
82
        return rev_id
83
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
84
    def test_DirectBranchCommit_commits_no_changes(self):
7675.310.5 by Aaron Bentley
Updates from review.
85
        # Committing nothing to an empty branch leaves its tree empty.
7675.310.3 by Aaron Bentley
Clean up DirectBranchCommit.
86
        self.assertEqual('null:', self.tree.branch.last_revision())
87
        old_rev_id = self.tree.branch.last_revision()
88
        self._addInitialCommit()
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
89
        self.committer.commit('')
90
        self.assertEqual({}, self._getContents())
7675.310.3 by Aaron Bentley
Clean up DirectBranchCommit.
91
        self.assertNotEqual(old_rev_id, self.tree.branch.last_revision())
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
92
93
    def test_DirectBranchCommit_rejects_change_after_commit(self):
94
        # Changes are not accepted after commit.
95
        self.committer.commit('')
96
        self.assertRaises(AssertionError, self.committer.writeFile, 'x', 'y')
97
98
    def test_DirectBranchCommit_adds_file(self):
99
        # DirectBranchCommit can add a new file to the branch.
100
        self.committer.writeFile('file.txt', 'contents')
101
        self.committer.commit('')
102
        self.assertEqual({'file.txt': 'contents'}, self._getContents())
103
9222.3.6 by Aaron Bentley
Merge preserving changes.
104
    def test_commit_returns_revision_id(self):
105
        # DirectBranchCommit.commit returns the new revision_id.
106
        self.committer.writeFile('file.txt', 'contents')
107
        revision_id = self.committer.commit('')
108
        branch_revision_id = self.committer.bzrbranch.last_revision()
109
        self.assertEqual(branch_revision_id, revision_id)
110
11486.2.3 by Aaron Bentley
Add support for merge_parent to DirectBranchCommit
111
    def test_commit_uses_merge_parents(self):
112
        # DirectBranchCommit.commit returns uses merge parents
113
        self._tearDownCommitter()
114
        # Merge parents cannot be specified for initial commit, so do an
115
        # empty commit.
116
        self.tree.commit('foo', committer='foo@bar', rev_id='foo')
117
        committer = DirectBranchCommit(
118
            self.db_branch, merge_parents=['parent-1', 'parent-2'])
119
        committer.last_scanned_id = (
120
            committer.bzrbranch.last_revision())
121
        committer.writeFile('file.txt', 'contents')
122
        revision_id = committer.commit('')
123
        branch_revision_id = committer.bzrbranch.last_revision()
124
        branch_revision = committer.bzrbranch.repository.get_revision(
125
            branch_revision_id)
126
        self.assertEqual(
127
            ['parent-1', 'parent-2'], branch_revision.parent_ids[1:])
128
8963.11.1 by Jeroen Vermeulen
Test abort as well. Gave some locking trouble, but as artefact of test cleanup.
129
    def test_DirectBranchCommit_aborts_cleanly(self):
130
        # If a DirectBranchCommit is not committed, its changes do not
131
        # go into the branch.
132
        self.committer.writeFile('oldfile.txt', 'already here')
133
        self.committer.commit('')
134
        self._setUpCommitter()
135
        self.committer.writeFile('newfile.txt', 'adding this')
136
        self._setUpCommitter()
137
        self.assertEqual({'oldfile.txt': 'already here'}, self._getContents())
138
        self.committer.unlock()
139
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
140
    def test_DirectBranchCommit_updates_file(self):
141
        # DirectBranchCommit can replace a file in the branch.
142
        self.committer.writeFile('file.txt', 'contents')
143
        self.committer.commit('')
144
        self._setUpCommitter()
145
        self.committer.writeFile('file.txt', 'changed')
146
        self.committer.commit('')
147
        self.assertEqual({'file.txt': 'changed'}, self._getContents())
148
149
    def test_DirectBranchCommit_creates_directories(self):
150
        # Files can be in subdirectories.
151
        self.committer.writeFile('a/b/c.txt', 'ctext')
152
        self.committer.commit('')
153
        self.assertEqual({'a/b/c.txt': 'ctext'}, self._getContents())
154
8657.7.3 by Jeroen Vermeulen
Last-minute fix: got id instead of trans id for existing parent directories.
155
    def test_DirectBranchCommit_adds_directories(self):
156
        # Creating a subdirectory of an existing directory also works.
157
        self.committer.writeFile('a/n.txt', 'aa')
158
        self.committer.commit('')
159
        self._setUpCommitter()
160
        self.committer.writeFile('a/b/m.txt', 'aa/bb')
161
        self.committer.commit('')
162
        expected = {
163
            'a/n.txt': 'aa',
164
            'a/b/m.txt': 'aa/bb',
165
        }
166
        self.assertEqual(expected, self._getContents())
167
8963.9.1 by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed.
168
    def test_DirectBranchCommit_reuses_new_directories(self):
169
        # If a directory doesn't exist in the committed branch, creating
170
        # it twice would be an error.  DirectBranchCommit doesn't do
171
        # that.
172
        self.committer.writeFile('foo/x.txt', 'x')
173
        self.committer.writeFile('foo/y.txt', 'y')
174
        self.committer.commit('')
175
        expected = {
176
            'foo/x.txt': 'x',
177
            'foo/y.txt': 'y',
178
        }
179
        self.assertEqual(expected, self._getContents())
180
8657.7.1 by Jeroen Vermeulen
DirectBranchCommit, for commit-translations-to-branch work.
181
    def test_DirectBranchCommit_writes_new_file_twice(self):
182
        # If you write the same new file multiple times before
183
        # committing, the original wins.
184
        self.committer.writeFile('x.txt', 'aaa')
185
        self.committer.writeFile('x.txt', 'bbb')
186
        self.committer.commit('')
187
        self.assertEqual({'x.txt': 'aaa'}, self._getContents())
188
189
    def test_DirectBranchCommit_updates_file_twice(self):
190
        # If you update the same file multiple times before committing,
191
        # the original wins.
192
        self.committer.writeFile('y.txt', 'aaa')
193
        self.committer.commit('')
194
        self._setUpCommitter()
195
        self.committer.writeFile('y.txt', 'bbb')
196
        self.committer.writeFile('y.txt', 'ccc')
197
        self.committer.commit('')
198
        self.assertEqual({'y.txt': 'bbb'}, self._getContents())
199
200
    def test_DirectBranchCommit_detects_race_condition(self):
201
        # If the branch has been updated since it was last scanned,
202
        # attempting to commit to it will raise ConcurrentUpdateError.
203
        self.committer.writeFile('hi.c', 'main(){puts("hi world");}')
204
        self.committer.commit('')
205
        self._setUpCommitter(False)
206
        self.committer.writeFile('hi.py', 'print "hi world"')
207
        self.assertRaises(ConcurrentUpdateError, self.committer.commit, '')
208
10850.3.1 by Jeroen Vermeulen
Porting production-devel fix to devel.
209
    def test_DirectBranchCommit_records_committed_revision_id(self):
210
        # commit() records the committed revision in the database record for
211
        # the branch.
212
        self.committer.writeFile('hi.c', 'main(){puts("hi world");}')
213
        revid = self.committer.commit('')
214
        self.assertEqual(revid, self.db_branch.last_mirrored_id)
215
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
216
    def test_commit_uses_getBzrCommitterID(self):
217
        # commit() passes self.getBzrCommitterID() to bzr as the
218
        # committer.
219
        bzr_id = self.factory.getUniqueString()
220
        self.committer.getBzrCommitterID = FakeMethod(result=bzr_id)
221
        fake_commit = FakeMethod()
222
        self.committer.transform_preview.commit = fake_commit
223
224
        self.committer.writeFile('x', 'y')
225
        self.committer.commit('')
226
227
        commit_args, commit_kwargs = fake_commit.calls[-1]
228
        self.assertEqual(bzr_id, commit_kwargs['committer'])
229
230
231
class TestGetDir(DirectBranchCommitTestCase, TestCaseWithFactory):
8963.9.1 by Jeroen Vermeulen
Fixed directory creation/lookup; used to create the same directories again every time they were needed.
232
    """Test `DirectBranchCommit._getDir`."""
233
234
    layer = ZopelessDatabaseLayer
235
236
    def test_getDir_creates_root(self):
237
        # An id is created even for the branch root directory.
238
        self.assertFalse('' in self.committer.path_ids)
239
        root_id = self.committer._getDir('')
240
        self.assertNotEqual(None, root_id)
241
        self.assertTrue('' in self.committer.path_ids)
242
        self.assertEqual(self.committer.path_ids[''], root_id)
243
244
    def test_getDir_creates_dir(self):
245
        # _getDir will create a new directory, under the root.
246
        self.assertFalse('dir' in self.committer.path_ids)
247
        dir_id = self.committer._getDir('dir')
248
        self.assertTrue('' in self.committer.path_ids)
249
        self.assertTrue('dir' in self.committer.path_ids)
250
        self.assertEqual(self.committer.path_ids['dir'], dir_id)
251
        self.assertNotEqual(self.committer.path_ids[''], dir_id)
252
253
    def test_getDir_creates_subdir(self):
254
        # _getDir will create nested directories.
255
        subdir_id = self.committer._getDir('dir/subdir')
256
        self.assertTrue('' in self.committer.path_ids)
257
        self.assertTrue('dir' in self.committer.path_ids)
258
        self.assertTrue('dir/subdir' in self.committer.path_ids)
259
        self.assertEqual(self.committer.path_ids['dir/subdir'], subdir_id)
260
261
    def test_getDir_finds_existing_dir(self):
262
        # _getDir finds directories that already existed in a previously
263
        # committed version of the branch.
264
        existing_id = self.committer._getDir('po')
265
        self._setUpCommitter()
266
        dir_id = self.committer._getDir('po')
267
        self.assertEqual(existing_id, dir_id)
268
269
    def test_getDir_creates_dir_in_existing_dir(self):
270
        # _getDir creates directories inside ones that already existed
271
        # in a previously committed version of the branch.
272
        existing_id = self.committer._getDir('po')
273
        self._setUpCommitter()
274
        new_dir_id = self.committer._getDir('po/main/files')
275
        self.assertTrue('po/main' in self.committer.path_ids)
276
        self.assertTrue('po/main/files' in self.committer.path_ids)
277
        self.assertEqual(self.committer.path_ids['po/main/files'], new_dir_id)
278
279
    def test_getDir_reuses_new_id(self):
280
        # If a directory was newly created, _getDir will reuse its id.
281
        dir_id = self.committer._getDir('foo/bar')
282
        self.assertEqual(dir_id, self.committer._getDir('foo/bar'))
283
284
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
285
class TestGetBzrCommitterID(TestCaseWithFactory):
286
    """Test `DirectBranchCommitter.getBzrCommitterID`."""
287
11592.1.3 by Jeroen Vermeulen
Review changes.
288
    layer = DatabaseFunctionalLayer
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
289
290
    def setUp(self):
291
        super(TestGetBzrCommitterID, self).setUp()
292
        self.useBzrBranches(direct_database=True)
293
294
    def _makeBranch(self, **kwargs):
295
        """Create a branch in the database and in bzr."""
296
        db_branch, tree = self.create_branch_and_tree(**kwargs)
297
        return db_branch
298
11592.1.4 by Jeroen Vermeulen
Changes for Noodles' review.
299
    def test_uses_committer_id(self):
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
300
        # getBzrCommitterID uses the committer string if provided.
301
        bzr_id = self.factory.getUniqueString()
11592.1.3 by Jeroen Vermeulen
Review changes.
302
        committer = DirectBranchCommit(
11592.1.4 by Jeroen Vermeulen
Changes for Noodles' review.
303
            self._makeBranch(), committer_id=bzr_id)
11592.1.3 by Jeroen Vermeulen
Review changes.
304
        self.addCleanup(committer.unlock)
305
        self.assertEqual(bzr_id, committer.getBzrCommitterID())
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
306
307
    def test_uses_committer_email(self):
308
        # getBzrCommitterID returns the committing person's email address
309
        # if available (and if no committer string is given).
310
        branch = self._makeBranch()
11592.1.3 by Jeroen Vermeulen
Review changes.
311
        committer = DirectBranchCommit(branch)
312
        self.addCleanup(committer.unlock)
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
313
        self.assertIn(
12001.3.23 by j.c.sackett
Updated tests and stories. Last round of this, I hope.
314
            removeSecurityProxy(branch.owner).preferredemail.email,
315
            committer.getBzrCommitterID())
11592.1.1 by Jeroen Vermeulen
Safer bzr ids in DirectBranchCommit, more appropriate ones in translations_to_branch.
316
317
    def test_falls_back_to_noreply(self):
318
        # If all else fails, getBzrCommitterID uses the noreply
319
        # address.
320
        team = self.factory.makeTeam()
321
        self.assertIs(None, team.preferredemail)
322
        branch = self._makeBranch(owner=team)
11592.1.3 by Jeroen Vermeulen
Review changes.
323
        committer = DirectBranchCommit(branch)
324
        self.addCleanup(committer.unlock)
325
        self.assertIn('noreply', committer.getBzrCommitterID())