Merge ~racb/git-ubuntu:importer-add-tests into git-ubuntu:master

Proposed by Robie Basak
Status: Merged
Merged at revision: 4585dc8073d0c5cedbbfddda58ae1d746d0b9a7c
Proposed branch: ~racb/git-ubuntu:importer-add-tests
Merge into: git-ubuntu:master
Diff against target: 3009 lines (+1951/-647)
8 files modified
gitubuntu/git_repository_test.py (+32/-115)
gitubuntu/importer_tag_test.py (+744/-0)
gitubuntu/importer_test.py (+783/-277)
gitubuntu/repo_builder.py (+77/-245)
gitubuntu/repo_builder_test.py (+284/-0)
gitubuntu/source_builder.py (+11/-5)
gitubuntu/source_builder_test.py (+18/-5)
pytest.ini (+2/-0)
Reviewer Review Type Date Requested Status
Bryce Harrington Approve
Server Team CI bot continuous-integration Approve
Canonical Server Pending
Andreas Hasenack Pending
Robie Basak Pending
Review via email: mp+375096@code.launchpad.net

This proposal supersedes a proposal from 2018-04-16.

Commit message

Make jenkins happy

Description of the change

Add tests, some of which are xfail pending future changes.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:a1c88d7eca4aaf6d235332efa7cd26b8238b35bb
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/409/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    FAILED: Unit Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/409/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal

PASSED: Continuous integration, rev:6fc24f6fe4f178cffbb4930c0c93958f12423f75
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/410/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    SUCCESS: Unit Tests
    SUCCESS: Integration Tests
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/410/rebuild

review: Approve (continuous-integration)
Revision history for this message
Andreas Hasenack (ahasenack) wrote : Posted in a previous version of this proposal

I'll have to continue tomorrow, these tests are complex to grasp.

Revision history for this message
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal

PASSED: Continuous integration, rev:d99073ccf1c0a3e067fbbfc6341731ee9219b291
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/411/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    SUCCESS: Unit Tests
    SUCCESS: Integration Tests
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/411/rebuild

review: Approve (continuous-integration)
Revision history for this message
Robie Basak (racb) : Posted in a previous version of this proposal
Revision history for this message
Nish Aravamudan (nacc) wrote : Posted in a previous version of this proposal
Download full text (3.6 KiB)

On Tue, May 22, 2018 at 6:48 AM, Robie Basak <email address hidden>
wrote:

>
>
> Diff comments:
>
> > diff --git a/doc/SPECIFICATION.importer b/doc/SPECIFICATION.importer
> > new file mode 100644
> > index 0000000..8c7f80c
> > --- /dev/null
> > +++ b/doc/SPECIFICATION.importer
> > @@ -0,0 +1,72 @@
> > +When importing a publishing record, there are two potential logical
> > +consequences:
> > +
> > + 1) Some new commit is created, with an associated tag; and/or
> > + 2) Some branch is adjusted to point at some commit
> > +
> > +In the following, existing tags may be 'import', 'upload' or 'applied'.
> > +Based upon SPECIFICATION.tags, 'import' and 'applied' tags are lists
> > +tags, which might be a single import tag or a list of reimport tags for
>

Should this be "lists of tags"?
>

Yep, typo. Want me to just push on top?

> > +any given version.
> > +
> > +If an existing tag with the same Git tree is found as a new publish
> > +event, we should reuse the tag, and adjust the corresponding branch to
> > +the same tagged commit.
> > +
> > +If no existing tag with same Git tree is found, a new import/applied tag
> > +should be created, as appropriate, and the corresponding branch should
> > +be adjusted to the the new tagged commit.
> > +
> > +If a new commit is created, appropriate commit parents are found in the
> > +existing Git commit graph. A changelog parent is the nearest (reverse
> > +chronologically) tagged version from debian/changelog of the publishing
> > +record.
> > +
> > +For patches-unapplied imports, parents are:
> > + 1) import-tagged changelog parents.
> > +
> > +For patches-applied imports, parents are:
> > + 1) applied-tagged changelog parents
> > + 2) import-tagged patches-unapplied commit of the same version as
> > + this publishing record.
> > +
> > +Logically, therefore, it is assumed for any given version that the
> > +patches-unapplied import occurs before the patches-applied import.
> > +
> > +Specific patches-unapplied cases, where in the following a tag exists
> > +for a specific version:
> > +
> > +1) An existing import tag (or reimport tag) with the same Git tree
> > + - Reuse import tag
> > +
> > +2) An existing import tag with a different Git tree and an existing
> > + upload tag with the same Git tree
> > + - Reuse upload tag, create reimport tag
> > +
> > +3) An existing import tag with a different Git tree and an existing
> > + upload tag with a different Git tree
> > + - Create reimport tag
> > +
> > +4) An existing import tag with a different Git tree and no upload tag
> > + - Create reimport tag
> > +
> > +5) No import tag and an existing upload tag with the same Git tree
> > + - Reuse upload tag, create import tag
> > +
> > +6) No import tag and an existing upload tag with a different Git tree
> > + - Create import tag
> > +
> > +7) No import tags or upload tags
> > + - Create import tag
> > +
> > +Specific patches-applied cases, where in the following a tag exists
> > +for a specific version:
> > +
> > +1) An existing applied tag (or reimport tag) with the same Git tree
> > + - Reuse applied tag
> > +
> > +2) An existing applied tag with a different Git tree
> > ...

Read more...

Revision history for this message
Robie Basak (racb) wrote : Posted in a previous version of this proposal

After a long absence I'm looking at making progress on the specification, tests and importer changes again. Nish, what's the level of involvement you'd like right now? I will happily take over if you're unavailable.

For now, I noticed something, comment inline, in part as a note to myself. I'm not intending to send you work without talking to you first and I'd be happy to fix up these review comments myself given how long it's been.

If you want less involvement Nish, I'm not sure yet if I intend to adjust this branch, or cherry-pick into a new one to land this work in more pieces. If you'd like more involvement, let's coordinate to avoid any wasted effort. Thanks!

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:ece36b58b615c4843b99779eefc032c75479ffea
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/397/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    SUCCESS: Unit Tests
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/397//rebuild

review: Approve (continuous-integration)
Revision history for this message
Bryce Harrington (bryce) wrote :

Since this changeset is entirely just test code, there should be no risk to production code.

The xfail_strict=true is noteworthy in that it potentially could break CI if there were inconsistently marked xfail tests, but CI has passed so this is not a concern. It enforces a conventional understanding of xfail, so it's a good change to have in the testsuite. I've doublechecked each of the xfail cases has an LP# associated with it, and reviewed each bug report and test case.

Additional comments below. Main one is suggesting addition of a helper routine mk_commit() to refactor some repetitive setup code. Mostly copyedits and verbage suggestions beyond that.

I'm still working on wrapping my head around the test cases themselves, so will have more feedback to come.

review: Needs Fixing
Revision history for this message
Bryce Harrington (bryce) wrote :

A bit more feedback, and I think I finally follow the mechanics of the test cases. A few more refactoring suggestions, some spelling copyedits, and I spotted an incorrect documentation for one of the test cases.

One thing I've not verified is if this covers all the necessary cases for the importer's logic. But I figure it's not like we can't keep adding to this, and expect there will be more cases to add as you delve into the importer logic. The main thing is that this gives a strong structure to add onto.

Also, while I've run the tests for this branch in the past, I haven't done so recently. I did check that jenkins appears to be running them:

#394 gitubuntu/importer_test.py .............................
#397 gitubuntu/importer_test.py ................................x...xx

The first corresponds to current trunk, the second this branch; the increase in tests and the xfails in the second case confirm to me the tests were run and passed.

review: Needs Fixing
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:5e9c1e9fcb5503d18f3c2b56aac86b15d38d29e8
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/424/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    SUCCESS: Unit Tests
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/424//rebuild

review: Approve (continuous-integration)
Revision history for this message
Robie Basak (racb) wrote :
Download full text (25.3 KiB)

Thank you for the review!

Some of your suggested refactorings also apply to tests already present,
so for consistency, particular as some of your requests were for the
purposes of consistency, I have also made matching consistency changes
to tests that were already there. To make this practical I have had to
do some heavy rebasing. Most of the consistency-type refactorings are
squashed into the main "Add importer tests" commit now. Changes to the
previously existing tests appear before "Add importer tests" in the
patchset and are individually committed and documented. Other review
changes I've left separate for your review convenience, ready to squash
with --autosquash before merging.

On Tue, Nov 05, 2019 at 04:15:47AM -0000, Bryce Harrington wrote:
> > +# When importing a publishing record, there are two potential logical
> > +# consequences:
> > +#
> > +# 1) Some new commit is created, with an associated tag; and/or
> > +# 2) Some branch is adjusted to point at some commit
> > +#
> > +# If an existing tag with the same Git tree is found as a new publish
> > +# event, we should reuse the tag, and adjust the corresponding branch to
> > +# the same tagged commit.
> > +#
> > +# If no existing tag with same Git tree is found, a new tree and a new
>
> s/b "with the same"

Fixed in commit 196ec89.

> > +# import/applied tag that points to it should be created, as appropriate,
> > +# and the corresponding branch should be adjusted to the the new tagged
> > +# commit.
> > +#
> > +# If a new commit is created, appropriate commit parents are found in the
> > +# existing Git commit graph. A changelog parent is the nearest (reverse
> > +# chronologically) tagged version from debian/changelog of the publishing
> > +# record.
>
> s/b "from the debian/changelog of"

Fixed in commit 196ec89.

> > +@pytest.mark.parametrize(
> > + 'name, input_repo, expected_changes, compare_refs, exist_refs, reuse', [
> > + # 1) An existing import tag (or reimport tag) with the same Git tree
> > + # - Reuse import tag
> > + pytest.param(
> > + 'Case 1',
> > + repo_builder.Repo(
> > + commit_list=[
> > + repo_builder.Commit(
> > + tree=repo_builder.SourceTree(
> > + source_builder.Source(
> > + source_builder.SourceSpec(
> > + version='1-1',
> > + native=False,
> > + )
> > + )
> > + ),
> > + name='import'
> > + ),
>
> This stanza of code to create a commit is repeated throughout this
> test suite with slight alterations. I think the tests would read
> easier if this was generated by a helper routine. I'm imagining
> something like:
>
> def mk_commit(name, with_file=False):
> file_contents = None
> if with_file:
> file_contents = {
> 'debian/random': 'The {} tag contents'.format( name ),
> }
>
> return repo_builder.Commit(
> tree=repo_builder.SourceTree(
> ...

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:68d61822e69cb4849a43298f32e19a202968c06b
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/427/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    SUCCESS: Unit Tests
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/427//rebuild

review: Approve (continuous-integration)
Revision history for this message
Bryce Harrington (bryce) wrote :

This looks good and is nearly ready to land.

This is a quite healthy refactoring, that makes the test cases significantly more readable. The count of lines being deleted is a good sign.

I found one typo and one whitespace issue (below) but those are super minor niggles, feel free to ignore them or just fix as you land if you want.

I've verified the main suggests from the previous review are addressed, and I like how this has turned out. I've done a full read-thru of the patch and didn't spot any other issues to flag, codewise. If any further code errors exist they can get addressed as they surface.

I've run tests against the branch in lxc:

- [√] python3 ./setup.py check
- [√] python3 ./setup.py build
- [x] pytest-3 .

There are a just couple test failures on the importer and tag tests, that aren't present on the master branch:

git-ubuntu-eoan+19.10:~/src/GitUbuntu/review-mp375096-importer-add-tests/usd-importer$ pytest-3 .
=========================================================== test session starts ============================================================
platform linux -- Python 3.7.5rc1, pytest-3.10.1, py-1.8.0, pluggy-0.12.0
rootdir: /home/bryce/src/GitUbuntu/review-mp375096-importer-add-tests/usd-importer, inifile: pytest.ini
collected 218 items

gitubuntu/apt_repo_test.py ....................... [ 10%]
gitubuntu/git_repository_test.py .......................................................... [ 37%]
gitubuntu/importer_service_test.py ............ [ 42%]
gitubuntu/importer_tag_test.py .xxxxx...xF [ 47%]
gitubuntu/importer_test.py ................................x..Fxx [ 65%]
gitubuntu/integration_test.py .. [ 66%]
gitubuntu/repo_builder_test.py .......................... [ 77%]
gitubuntu/scriptutils_test.py ...... [ 80%]
gitubuntu/source_builder_test.py ..................... [ 90%]
gitubuntu/source_information_test.py ..................... [100%]

Full output of the test log is here: https://paste.ubuntu.com/p/4d54ckwFtv/

I've been poking at it myself but am not sure what the failure is. Guessing at least one failure is maybe due to date formatting, and might merely be a locale issue? Since jenkins isn't seeing failures I can't rule out that it's not something with my container setup.

review: Needs Fixing
Revision history for this message
Robie Basak (racb) wrote :

The verbose pytest output is quite tricky to read, so I spent a while going through it. The two failures are separated at line 852. Both of the test failures appear to have the same cause: a RuntimeError raised because a generator raised a StopIteration. This is caused by quilt returning an exit code of 2 at importer.py:942.

It looks like the behaviour changed according to https://www.python.org/dev/peps/pep-0479/#transition-plan, so I think that the difference in your environment is just that you are using a newer version of Python than our CI (and the snap).

There is an underlying bug here: it's incorrect for a generator to raise a StopIteration; it should just return when done (and this was the case with older versions of Python too). But given that this definitely doesn't affect production (as the snap's version is fixed), CI would catch it if this changes, and these are your only identified test failures, I think it's OK to land this branch as-is. We can fix the failures in your test environment by replaces the StopIteration raises with returns in the generators in a separate branch.

I've fixed the typos, so I think this is ready to land. Thank you for the reviews!

Revision history for this message
Robie Basak (racb) wrote :

Final branch candidate to land rebased and force pushed.

Bryce, could you confirm you're happy with my assessment in the previous comment and this is OK to land, please?

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:b5ac84c74cb0a9d64a1f7adc1fc5f12af3cd6ed3
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/429/
Executed test runs:
    SUCCESS: VM Setup
    SUCCESS: Build
    SUCCESS: Unit Tests
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/429//rebuild

review: Approve (continuous-integration)
Revision history for this message
Bryce Harrington (bryce) wrote :

Yep, thanks for investigating, I did suspect it was something specific to my environment (the lxc container is eoan-based, I do need to redo it.) In fact I'm certain I've looked into the StopIteration issue once before (Nish chimed in on my comments about it on one of the bugs), and I seem to recall having similar ideas on a fix for it. I agree it can be treated as a separate problem. Land away!

review: Approve
Revision history for this message
Bryce Harrington (bryce) wrote :

For reference, the StopIteration bug I mentioned looking at previously is https://bugs.launchpad.net/usd-importer/+bug/1847133

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/gitubuntu/git_repository_test.py b/gitubuntu/git_repository_test.py
2index 64c6876..01d3525 100644
3--- a/gitubuntu/git_repository_test.py
4+++ b/gitubuntu/git_repository_test.py
5@@ -558,44 +558,21 @@ def test_repo_derive_env_change(repo):
6 (
7 'Common case',
8 Repo(
9- commit_list=[
10- Commit(
11- tree=SourceTree(
12- Source(
13- SourceSpec(
14- version='1-1',
15- native=False,
16- )
17- ),
18- ),
19- name='old/debian',
20+ commits=[
21+ Commit.from_spec(
22+ name='old/debian'
23 ),
24- Commit(
25- tree=SourceTree(
26- Source(
27- SourceSpec(
28- changelog_versions=['1-1ubuntu1', '1-1',],
29- native=False,
30- )
31- ),
32- ),
33- parents=[Placeholder('old/debian'),],
34+ Commit.from_spec(
35+ parents=[Placeholder('old/debian')],
36 name='old/ubuntu',
37+ changelog_versions=['1-1ubuntu1', '1-1'],
38 ),
39- Commit(
40- tree=SourceTree(
41- Source(
42- SourceSpec(
43- changelog_versions=['2-1', '1-1',],
44- native=False,
45- )
46- ),
47- ),
48- parents=[Placeholder('old/debian'),],
49+ Commit.from_spec(
50+ parents=[Placeholder('old/debian')],
51 name='new/debian',
52+ changelog_versions=['2-1', '1-1'],
53 ),
54 ],
55- branches={},
56 tags={
57 'pkg/import/1-1': Placeholder('old/debian'),
58 'pkg/import/1-1ubuntu1': Placeholder('old/ubuntu'),
59@@ -609,56 +586,26 @@ def test_repo_derive_env_change(repo):
60 (
61 'Ubuntu delta based on a NMU',
62 Repo(
63- commit_list=[
64- Commit(
65- tree=SourceTree(
66- Source(
67- SourceSpec(
68- version='1-1',
69- native=False,
70- )
71- ),
72- ),
73- name='fork_point',
74+ commits=[
75+ Commit.from_spec(
76+ name='fork_point'
77 ),
78- Commit(
79- tree=SourceTree(
80- Source(
81- SourceSpec(
82- changelog_versions=['1-1.1', '1-1',],
83- native=False,
84- )
85- ),
86- ),
87+ Commit.from_spec(
88 parents=[Placeholder('fork_point')],
89 name='old/debian',
90+ changelog_versions=['1-1.1', '1-1'],
91 ),
92- Commit(
93- tree=SourceTree(
94- Source(
95- SourceSpec(
96- changelog_versions=['1-1.1ubuntu1', '1-1.1', '1-1',],
97- native=False,
98- )
99- ),
100- ),
101- parents=[Placeholder('old/debian'),],
102+ Commit.from_spec(
103+ parents=[Placeholder('old/debian')],
104 name='old/ubuntu',
105+ changelog_versions=['1-1.1ubuntu1', '1-1.1', '1-1'],
106 ),
107- Commit(
108- tree=SourceTree(
109- Source(
110- SourceSpec(
111- changelog_versions=['2-1', '1-1',],
112- native=False,
113- )
114- ),
115- ),
116- parents=[Placeholder('fork_point'),],
117+ Commit.from_spec(
118+ parents=[Placeholder('fork_point')],
119 name='new/debian',
120+ changelog_versions=['2-1', '1-1'],
121 ),
122 ],
123- branches={},
124 tags={
125 'pkg/import/1-1': Placeholder('fork_point'),
126 'pkg/import/1-1.1': Placeholder('old/debian'),
127@@ -673,56 +620,26 @@ def test_repo_derive_env_change(repo):
128 (
129 'Ubuntu upstream version head of Debian',
130 Repo(
131- commit_list=[
132- Commit(
133- tree=SourceTree(
134- Source(
135- SourceSpec(
136- version='1-1',
137- native=False,
138- )
139- ),
140- ),
141- name='old/debian',
142+ commits=[
143+ Commit.from_spec(
144+ name='old/debian'
145 ),
146- Commit(
147- tree=SourceTree(
148- Source(
149- SourceSpec(
150- changelog_versions=['1-1ubuntu1', '1-1',],
151- native=False,
152- )
153- ),
154- ),
155- parents=[Placeholder('old/debian'),],
156+ Commit.from_spec(
157+ parents=[Placeholder('old/debian')],
158 name='mid_ubuntu',
159+ changelog_versions=['1-1ubuntu1', '1-1'],
160 ),
161- Commit(
162- tree=SourceTree(
163- Source(
164- SourceSpec(
165- changelog_versions=['2-0ubuntu1', '1-1ubuntu1', '1-1',],
166- native=False,
167- )
168- ),
169- ),
170- parents=[Placeholder('mid_ubuntu'),],
171+ Commit.from_spec(
172+ parents=[Placeholder('mid_ubuntu')],
173 name='old/ubuntu',
174+ changelog_versions=['2-0ubuntu1', '1-1ubuntu1', '1-1'],
175 ),
176- Commit(
177- tree=SourceTree(
178- Source(
179- SourceSpec(
180- changelog_versions=['3-1', '1-1',],
181- native=False,
182- )
183- ),
184- ),
185- parents=[Placeholder('old/debian'),],
186+ Commit.from_spec(
187+ parents=[Placeholder('old/debian')],
188 name='new/debian',
189+ changelog_versions=['3-1', '1-1'],
190 ),
191 ],
192- branches={},
193 tags={
194 'pkg/import/1-1': Placeholder('old/debian'),
195 'pkg/import/1-1ubuntu1': Placeholder('mid_ubuntu'),
196diff --git a/gitubuntu/importer_tag_test.py b/gitubuntu/importer_tag_test.py
197new file mode 100644
198index 0000000..901ab10
199--- /dev/null
200+++ b/gitubuntu/importer_tag_test.py
201@@ -0,0 +1,744 @@
202+# This file contains tests additional to importer_test.py grouped here because
203+# they are considerable on their own.
204+
205+from unittest.mock import (
206+ patch,
207+)
208+
209+import pygit2
210+import pytest
211+
212+import gitubuntu.repo_builder as repo_builder
213+from gitubuntu.repo_builder import Commit, Placeholder
214+import gitubuntu.repo_comparator as repo_comparator
215+import gitubuntu.source_builder as source_builder
216+
217+import gitubuntu.importer as target
218+
219+from gitubuntu.test_fixtures import repo
220+from gitubuntu.importer_test import MockSPI, patch_get_commit_environment
221+
222+
223+# When importing a publishing record, there are two potential logical
224+# consequences:
225+#
226+# 1) Some new commit is created, with an associated tag; and/or
227+# 2) Some branch is adjusted to point at some commit
228+#
229+# If an existing tag with the same Git tree is found as a new publish
230+# event, we should reuse the tag, and adjust the corresponding branch to
231+# the same tagged commit.
232+#
233+# If no existing tag with the same Git tree is found, a new tree and a new
234+# import/applied tag that points to it should be created, as appropriate, and
235+# the corresponding branch should be adjusted to the the new tagged commit.
236+#
237+# If a new commit is created, appropriate commit parents are found in the
238+# existing Git commit graph. A changelog parent is the nearest (reverse
239+# chronologically) tagged version from the debian/changelog of the publishing
240+# record.
241+
242+@pytest.mark.parametrize(
243+ [
244+ 'input_repo',
245+ 'expected_output_refs',
246+ 'validation_repo_delta',
247+ 'validation_repo_expected_identical_refs',
248+ 'reuse',
249+ ],
250+ [
251+ # 1) An existing import tag (or reimport tag) with the same Git tree
252+ # - Reuse import tag
253+ pytest.param(
254+ # input_repo:
255+ repo_builder.Repo(
256+ commits=[Commit.from_spec(name='import')],
257+ branches={
258+ 'importer/ubuntu/trusty-proposed': Placeholder('import'),
259+ },
260+ tags={'importer/import/1-1': Placeholder('import')},
261+ ),
262+ # expected_output_refs:
263+ [
264+ 'refs/heads/do-not-push',
265+ 'refs/tags/importer/upstream/ubuntu/1.gz',
266+ 'refs/heads/importer/importer/ubuntu/dsc',
267+ 'refs/heads/importer/importer/ubuntu/pristine-tar',
268+ ],
269+ # validation_repo_delta:
270+ {
271+ 'update_branches': {
272+ 'importer/ubuntu/trusty': Placeholder('import'),
273+ },
274+ },
275+ # validation_repo_expected_identical_refs:
276+ [
277+ 'refs/heads/importer/ubuntu/trusty-proposed',
278+ 'refs/heads/importer/ubuntu/trusty',
279+ 'refs/tags/importer/import/1-1',
280+ ],
281+ # reuse:
282+ True,
283+ ),
284+ pytest.param(
285+ # input_repo:
286+ repo_builder.Repo(
287+ commits=[
288+ Commit.from_spec(
289+ name='import',
290+ mutate='import tag contents',
291+ ),
292+ Commit.from_spec(name='reimport'),
293+ ],
294+ branches={
295+ 'importer/ubuntu/trusty-proposed': Placeholder('import'),
296+ },
297+ tags={
298+ 'importer/import/1-1': Placeholder('import'),
299+ 'importer/reimport/import/1-1/0': Placeholder('import'),
300+ 'importer/reimport/import/1-1/1': Placeholder('reimport'),
301+ },
302+ ),
303+ # expected_output_refs:
304+ [
305+ 'refs/heads/do-not-push',
306+ 'refs/tags/importer/upstream/ubuntu/1.gz',
307+ 'refs/heads/importer/importer/ubuntu/dsc',
308+ 'refs/heads/importer/importer/ubuntu/pristine-tar',
309+ ],
310+ # validation_repo_delta:
311+ {
312+ 'update_branches': {
313+ 'importer/ubuntu/trusty': Placeholder('reimport'),
314+ },
315+ },
316+ # validation_repo_expected_identical_refs:
317+ [
318+ 'refs/heads/importer/ubuntu/trusty-proposed',
319+ 'refs/heads/importer/ubuntu/trusty',
320+ 'refs/tags/importer/import/1-1',
321+ 'refs/tags/importer/reimport/import/1-1/0',
322+ 'refs/tags/importer/reimport/import/1-1/1',
323+ ],
324+ # reuse:
325+ True,
326+
327+ marks=pytest.mark.xfail(reason='LP: #1761331'),
328+ ),
329+
330+ # 2) An existing import tag with a different Git tree and an existing
331+ # upload tag with the same Git tree
332+ # - Reuse upload tag, create reimport tag
333+ pytest.param(
334+ # input_repo:
335+ repo_builder.Repo(
336+ commits=[
337+ Commit.from_spec(
338+ name='import',
339+ mutate='The import tag contents',
340+ ),
341+ Commit.from_spec(name='upload'),
342+ ],
343+ branches={
344+ 'importer/ubuntu/trusty-proposed': Placeholder('import'),
345+ },
346+ tags={
347+ 'importer/import/1-1': Placeholder('import'),
348+ 'importer/upload/1-1': Placeholder('upload'),
349+ },
350+ ),
351+ # expected_output_refs:
352+ [
353+ 'refs/heads/do-not-push',
354+ 'refs/tags/importer/upstream/ubuntu/1.gz',
355+ 'refs/heads/importer/importer/ubuntu/dsc',
356+ 'refs/heads/importer/importer/ubuntu/pristine-tar',
357+ ],
358+ # validation_repo_delta:
359+ {
360+ 'update_tags': {
361+ 'importer/reimport/import/1-1/0': Placeholder('import'),
362+ 'importer/reimport/import/1-1/1': Placeholder('upload'),
363+ },
364+ 'update_branches': {
365+ 'importer/ubuntu/trusty': Placeholder('upload'),
366+ },
367+ },
368+ # validation_repo_expected_identical_refs:
369+ [
370+ 'refs/heads/importer/ubuntu/trusty-proposed',
371+ 'refs/heads/importer/ubuntu/trusty',
372+ 'refs/tags/importer/import/1-1',
373+ 'refs/tags/importer/upload/1-1',
374+ 'refs/tags/importer/reimport/import/1-1/0',
375+ 'refs/tags/importer/reimport/import/1-1/1',
376+ ],
377+ # reuse:
378+ True,
379+
380+ marks=pytest.mark.xfail(reason='LP: #1754194'),
381+ ),
382+
383+ # 3) An existing import tag with a different Git tree and an existing
384+ # upload tag with a different Git tree
385+ # - Create reimport tag
386+ pytest.param(
387+ # input_repo:
388+ repo_builder.Repo(
389+ commits=[
390+ Commit.from_spec(
391+ name='import',
392+ mutate='The import tag contents',
393+ ),
394+ Commit.from_spec(
395+ name='import',
396+ mutate='The upload tag contents',
397+ ),
398+ ],
399+ branches={
400+ 'importer/ubuntu/trusty-proposed': Placeholder('import'),
401+ },
402+ tags={
403+ 'importer/import/1-1': Placeholder('import'),
404+ 'importer/upload/1-1': Placeholder('upload'),
405+ },
406+ ),
407+ # expected_output_refs:
408+ [
409+ 'refs/heads/do-not-push',
410+ 'refs/tags/importer/upstream/ubuntu/1.gz',
411+ 'refs/heads/importer/importer/ubuntu/dsc',
412+ 'refs/heads/importer/importer/ubuntu/pristine-tar',
413+ ],
414+ # validation_repo_delta:
415+ {
416+ 'add_commits': [Commit.from_spec(name='reimport')],
417+ 'update_tags': {
418+ 'importer/reimport/import/1-1/0': Placeholder('import'),
419+ 'importer/reimport/import/1-1/1': Placeholder('reimport'),
420+ },
421+ 'update_branches': {
422+ 'importer/ubuntu/trusty': Placeholder('reimport'),
423+ },
424+ },
425+ # validation_repo_expected_identical_refs:
426+ [
427+ 'refs/heads/importer/ubuntu/trusty-proposed',
428+ 'refs/heads/importer/ubuntu/trusty',
429+ 'refs/tags/importer/import/1-1',
430+ 'refs/tags/importer/upload/1-1',
431+ 'refs/tags/importer/reimport/import/1-1/0',
432+ 'refs/tags/importer/reimport/import/1-1/1',
433+ ],
434+ # reuse:
435+ False,
436+
437+ marks=pytest.mark.xfail(reason='LP: #1754507'),
438+ ),
439+
440+ # 4) An existing import tag with a different Git tree and no upload tag
441+ # - Create reimport tag
442+ pytest.param(
443+ # input_repo:
444+ repo_builder.Repo(
445+ commits=[
446+ Commit.from_spec(
447+ name='import',
448+ mutate='The import tag contents',
449+ ),
450+ ],
451+ branches={
452+ 'importer/ubuntu/trusty-proposed': Placeholder('import'),
453+ },
454+ tags={'importer/import/1-1': Placeholder('import')},
455+ ),
456+ # expected_output_refs:
457+ [
458+ 'refs/heads/do-not-push',
459+ 'refs/tags/importer/upstream/ubuntu/1.gz',
460+ 'refs/heads/importer/importer/ubuntu/dsc',
461+ 'refs/heads/importer/importer/ubuntu/pristine-tar',
462+ ],
463+ # validation_repo_delta:
464+ {
465+ 'add_commits': [Commit.from_spec(name='reimport')],
466+ 'update_tags': {
467+ 'importer/reimport/import/1-1/0': Placeholder('import'),
468+ 'importer/reimport/import/1-1/1': Placeholder('reimport'),
469+ },
470+ 'update_branches': {
471+ 'importer/ubuntu/trusty': Placeholder('reimport'),
472+ },
473+ },
474+ # validation_repo_expected_identical_refs:
475+ [
476+ 'refs/heads/importer/ubuntu/trusty-proposed',
477+ 'refs/heads/importer/ubuntu/trusty',
478+ 'refs/tags/importer/import/1-1',
479+ 'refs/tags/importer/reimport/import/1-1/0',
480+ 'refs/tags/importer/reimport/import/1-1/1',
481+ ],
482+ # reuse:
483+ False,
484+
485+ marks=pytest.mark.xfail(reason='LP: #1754706'),
486+ ),
487+
488+ # 5) No import tag and an existing upload tag with the same Git tree
489+ # - Reuse upload tag, create import tag
490+ pytest.param(
491+ # input_repo:
492+ repo_builder.Repo(
493+ commits=[Commit.from_spec(name='upload')],
494+ tags={'importer/upload/1-1': Placeholder('upload')},
495+ ),
496+ # expected_output_refs:
497+ [
498+ 'refs/heads/do-not-push',
499+ 'refs/tags/importer/upstream/ubuntu/1.gz',
500+ 'refs/heads/importer/importer/ubuntu/dsc',
501+ 'refs/heads/importer/importer/ubuntu/pristine-tar',
502+ ],
503+ # validation_repo_delta:
504+ {
505+ 'update_tags': {
506+ 'importer/import/1-1': Placeholder('upload'),
507+ },
508+ 'update_branches': {
509+ 'importer/ubuntu/trusty': Placeholder('upload'),
510+ },
511+ },
512+ # validation_repo_expected_identical_refs:
513+ [
514+ 'refs/heads/importer/ubuntu/trusty',
515+ 'refs/tags/importer/import/1-1',
516+ 'refs/tags/importer/upload/1-1',
517+ ],
518+ # reuse:
519+ True,
520+
521+ marks=pytest.mark.xfail(reason='LP: #1734883'),
522+ ),
523+
524+ # 6) No import tag and an existing upload tag with a different Git tree
525+ # - Create import tag
526+ pytest.param(
527+ # input_repo:
528+ repo_builder.Repo(
529+ commits=[
530+ Commit.from_spec(
531+ name='upload',
532+ mutate='The upload tag contents',
533+ ),
534+ ],
535+ tags={'importer/upload/1-1': Placeholder('upload')},
536+ ),
537+ # expected_output_refs:
538+ [
539+ 'refs/heads/do-not-push',
540+ 'refs/tags/importer/upstream/ubuntu/1.gz',
541+ 'refs/heads/importer/importer/ubuntu/dsc',
542+ 'refs/heads/importer/importer/ubuntu/pristine-tar',
543+ ],
544+ # validation_repo_delta:
545+ {
546+ 'add_commits': [
547+ Commit.from_spec(
548+ name='publish',
549+ message='Test commit (new)',
550+ ),
551+ ],
552+ 'update_tags': {
553+ 'importer/import/1-1': Placeholder('publish'),
554+ },
555+ 'update_branches': {
556+ 'importer/ubuntu/trusty': Placeholder('publish'),
557+ },
558+ },
559+ # validation_repo_expected_identical_refs:
560+ [
561+ 'refs/heads/importer/ubuntu/trusty',
562+ 'refs/tags/importer/import/1-1',
563+ 'refs/tags/importer/upload/1-1',
564+ ],
565+ # reuse:
566+ False,
567+ ),
568+
569+ # 7) No import tags or upload tags
570+ # - Create import tag
571+ pytest.param(
572+ # input_repo:
573+ repo_builder.Repo(),
574+ # expected_output_refs:
575+ [
576+ 'refs/heads/do-not-push',
577+ 'refs/tags/importer/upstream/ubuntu/1.gz',
578+ 'refs/heads/importer/importer/ubuntu/dsc',
579+ 'refs/heads/importer/importer/ubuntu/pristine-tar',
580+ ],
581+ # validation_repo_delta:
582+ {
583+ 'add_commits': [
584+ Commit.from_spec(
585+ name='publish',
586+ message='Test commit (new)',
587+ ),
588+ ],
589+ 'update_tags': {
590+ 'importer/import/1-1': Placeholder('publish'),
591+ },
592+ 'update_branches': {
593+ 'importer/ubuntu/trusty': Placeholder('publish'),
594+ },
595+ },
596+ # validation_repo_expected_identical_refs:
597+ [
598+ 'refs/heads/importer/ubuntu/trusty',
599+ 'refs/tags/importer/import/1-1'
600+ ],
601+ # reuse:
602+ False,
603+ ),
604+ ]
605+)
606+@patch('gitubuntu.importer.get_import_tag_msg')
607+@patch('gitubuntu.importer.get_import_commit_msg')
608+def test_import_unapplied_spi_tags(
609+ get_import_commit_msg_mock,
610+ get_import_tag_msg_mock,
611+ repo,
612+ input_repo,
613+ expected_output_refs,
614+ validation_repo_delta,
615+ validation_repo_expected_identical_refs,
616+ reuse,
617+):
618+ """Test that unapplied tags are correctly created, adjusted and/or reused
619+
620+ :param unittest.mock.Mock get_import_commit_msg_mock: mock of the function
621+ that determines the commit message to use for a given import
622+ :param unittest.mock.Mock get_import_tag_msg_mock: mock of the function
623+ that determines the tag message to use for a given import
624+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
625+ temporary output repository
626+ :param repo_builder.Repo input_repo: input repository data
627+ :param list(str) expected_output_refs: refs that must exist in the output
628+ repository
629+ :param dict validation_repo_delta: how to transform the input
630+ repository into a "validation repository", expressed as a dict to
631+ provide as **kwargs to gitubuntu.repo_builder.Repo.copy() against the
632+ input repository. The validation repository is then used for the
633+ purposes of comparison against the output repository.
634+ :param list(str) validation_repo_expected_identical_refs: refs that must be
635+ identical between the validation repository and the output repository
636+ :param bool reuse: if set, the output ref importer/ubuntu/trusty must be
637+ one supplied by the input repository (assumed to have a commit message
638+ of "Test commit") and not one created by the importer in this run
639+ (arranged by this test to have a different commit message)
640+
641+ The input repository data is written into the output repository and then a
642+ fake non-native source package publication of version 1-1 in the Trusty
643+ release pocket is imported into it by calling import_unapplied_spi()
644+ directly. expected_output_refs, validation_repo_expected_identical_refs and
645+ reuse are then asserted. It is further asserted that no other refs exist in
646+ the output repository except for those listed in expected_output_refs and
647+ validation_repo_expected_identical_refs.
648+ """
649+ # Match the repo_builder objects
650+ get_import_tag_msg_mock.return_value = 'Test tag'
651+ # Importantly, the following commit message must not be the same as the
652+ # commit messages used by the test input repository commits, so that we can
653+ # later detect the difference between commits that were already there and
654+ # new commits created by the importer for the purposes of asserting the
655+ # reuse parameter correctly.
656+ get_import_commit_msg_mock.return_value = b'Test commit (new)'
657+
658+ input_repo.write(repo.raw_repo)
659+
660+ publish_spec = source_builder.SourceSpec(
661+ version='1-1',
662+ native=False,
663+ )
664+
665+ with patch_get_commit_environment(repo):
666+ with source_builder.Source(publish_spec) as dsc_path:
667+ target.import_unapplied_spi(
668+ repo=repo,
669+ spi=MockSPI(dsc_path, publish_spec.version),
670+ namespace='importer',
671+ skip_orig=False,
672+ parent_overrides={},
673+ )
674+
675+ if reuse:
676+ # Test that the previous commit was reused. In this case, the
677+ # commit message should be one created by the repo_builder.Commit()
678+ # default, and not one created by the importer.
679+ tip_ref = repo.raw_repo.lookup_reference(
680+ 'refs/heads/importer/ubuntu/trusty'
681+ )
682+ message = tip_ref.peel(pygit2.Commit).message
683+ assert message == 'Test commit'
684+
685+ validation_repo = input_repo.copy(**validation_repo_delta)
686+
687+ # Verify validation_repo_expected_identical_refs
688+ assert repo_comparator.equals(
689+ repoA=repo.raw_repo,
690+ repoB=validation_repo,
691+ test_refs=validation_repo_expected_identical_refs,
692+ )
693+
694+ # Verify expected_output_refs
695+ for ref in expected_output_refs:
696+ assert repo.raw_repo.lookup_reference(ref)
697+
698+ # Verify that no other refs exist
699+ all_expected_output_refs = (
700+ frozenset(validation_repo_expected_identical_refs) |
701+ frozenset(expected_output_refs)
702+ )
703+ all_actual_output_refs = frozenset(repo.raw_repo.listall_references())
704+ assert all_expected_output_refs == all_actual_output_refs
705+
706+
707+@pytest.mark.parametrize(
708+ [
709+ 'input_repo',
710+ 'validation_repo_delta',
711+ 'validation_repo_expected_treewise_refs',
712+ 'reuse',
713+ ],
714+ [
715+ # 1) An existing applied tag (or reimport tag) with the same Git tree
716+ # - Reuse applied tag
717+ pytest.param(
718+ # input_repo:
719+ repo_builder.Repo(
720+ commits=[
721+ Commit.from_spec(name='unapplied', has_patches=True),
722+ Commit.from_spec(name='applied', patches_applied=True),
723+ ],
724+ branches={
725+ 'importer/ubuntu/trusty': Placeholder('unapplied'),
726+ 'importer/applied/ubuntu/trusty': Placeholder('applied'),
727+ },
728+ tags={
729+ 'importer/import/1-1': Placeholder('unapplied'),
730+ 'importer/applied/1-1': Placeholder('applied'),
731+ },
732+ ),
733+ # validation_repo_delta:
734+ {
735+ # no output repository delta
736+ },
737+ # validation_repo_expected_treewise_refs:
738+ [
739+ 'refs/heads/importer/ubuntu/trusty',
740+ 'refs/heads/importer/applied/ubuntu/trusty',
741+ 'refs/tags/importer/import/1-1',
742+ 'refs/tags/importer/applied/1-1',
743+ ],
744+ # reuse:
745+ True,
746+ ),
747+
748+ # 2) An existing applied tag with a different Git tree
749+ # - Create reimport tag
750+ pytest.param(
751+ # input_repo:
752+ repo_builder.Repo(
753+ commits=[
754+ Commit.from_spec(
755+ name='unapplied',
756+ has_patches=True,
757+ mutate='import tag contents',
758+ ),
759+ Commit.from_spec(
760+ name='unapplied_reimport',
761+ has_patches=True,
762+ ),
763+ Commit.from_spec(
764+ name='applied',
765+ patches_applied=True,
766+ mutate='import tag contents',
767+ )
768+ ],
769+ branches={'importer/ubuntu/trusty': Placeholder('unapplied')},
770+ tags={
771+ 'importer/import/1-1':
772+ Placeholder('unapplied'),
773+ 'importer/reimport/import/1-1/0':
774+ Placeholder('unapplied'),
775+ 'importer/reimport/import/1-1/1':
776+ Placeholder('unapplied_reimport'),
777+ 'importer/applied/1-1':
778+ Placeholder('applied'),
779+ },
780+ ),
781+ # validation_repo_delta:
782+ {
783+ 'add_commits': [
784+ Commit.from_spec(
785+ name='applied_reimport',
786+ patches_applied=True,
787+ parents=[Placeholder('unapplied_reimport')],
788+ ),
789+ ],
790+ 'update_tags': {
791+ 'importer/reimport/applied/1-1/0':
792+ Placeholder('applied'),
793+ 'importer/reimport/applied/1-1/1':
794+ Placeholder('applied_reimport'),
795+ },
796+ 'update_branches': {
797+ 'importer/applied/ubuntu/trusty':
798+ Placeholder('applied_reimport'),
799+ },
800+ },
801+ # validation_repo_expected_treewise_refs:
802+ [
803+ 'refs/heads/importer/ubuntu/trusty',
804+ 'refs/heads/importer/applied/ubuntu/trusty',
805+ 'refs/tags/importer/import/1-1',
806+ 'refs/tags/importer/reimport/import/1-1/0',
807+ 'refs/tags/importer/reimport/import/1-1/1',
808+ 'refs/tags/importer/applied/1-1',
809+ 'refs/tags/importer/reimport/applied/1-1/0',
810+ 'refs/tags/importer/reimport/applied/1-1/1',
811+ ],
812+ # reuse:
813+ False,
814+
815+ marks=pytest.mark.xfail(reason='LP: #1755247'),
816+ ),
817+
818+ # 3) No applied tags
819+ # - Create applied tag
820+ pytest.param(
821+ # input_repo:
822+ repo_builder.Repo(
823+ commits=[Commit.from_spec(name='unapplied', has_patches=True)],
824+ branches={'importer/ubuntu/trusty': Placeholder('unapplied')},
825+ tags={'importer/import/1-1': Placeholder('unapplied')},
826+ ),
827+ # validation_repo_delta:
828+ {
829+ 'add_commits': [
830+ Commit.from_spec(
831+ name='applied',
832+ patches_applied=True,
833+ parents=[Placeholder('unapplied')],
834+ ),
835+ ],
836+ 'update_tags': {
837+ 'importer/applied/1-1': Placeholder('applied')
838+ },
839+ 'update_branches': {
840+ 'importer/applied/ubuntu/trusty': Placeholder('applied'),
841+ },
842+ },
843+ # validation_repo_expected_treewise_refs:
844+ [
845+ 'refs/heads/importer/ubuntu/trusty',
846+ 'refs/heads/importer/applied/ubuntu/trusty',
847+ 'refs/tags/importer/import/1-1',
848+ 'refs/tags/importer/applied/1-1'
849+ ],
850+ # reuse:
851+ False,
852+ ),
853+ ]
854+)
855+@patch('gitubuntu.importer.get_import_tag_msg')
856+@patch('gitubuntu.importer.get_import_commit_msg')
857+def test_import_applied_spi_tags(
858+ get_import_commit_msg_mock,
859+ get_import_tag_msg_mock,
860+ repo,
861+ input_repo,
862+ validation_repo_delta,
863+ validation_repo_expected_treewise_refs,
864+ reuse,
865+):
866+ """Test that applied tags are correctly created, adjusted and/or reused
867+
868+ :param unittest.mock.Mock get_import_commit_msg_mock: mock of the function
869+ that determines the commit message to use for a given import
870+ :param unittest.mock.Mock get_import_tag_msg_mock: mock of the function
871+ that determines the tag message to use for a given import
872+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
873+ temporary output repository
874+ :param repo_builder.Repo input_repo: input repository data
875+ :param dict validation_repo_delta: how to transform the input repository
876+ into a "validation repository", expressed as a dict to
877+ provide as **kwargs to gitubuntu.repo_builder.Repo.copy() against the
878+ input repository. The validation repository is then used for the
879+ purposes of comparison against the output repository.
880+ :param list(str) validation_repo_expected_treewise_refs: refs whose trees
881+ must be identical between the validation repository and the output
882+ repository
883+ :param bool reuse: if set, the output ref importer/ubuntu/trusty must be
884+ one supplied by the input repository (assumed to have a commit message
885+ of "Test commit") and not one created by the importer in this run
886+ (arranged by this test to have a different commit message)
887+
888+ The input repository data is written into the output repository and then a
889+ fake non-native source package publication of version 1-1 in the Trusty
890+ release pocket is imported into it by calling import_applied_spi()
891+ directly. reuse and validation_repo_expected_treewise_refs are then
892+ asserted.
893+
894+ This is similar to test_unapplied_spi_tags except that it calls
895+ import_applied_spi() instead of import_unapplied_spi() and only treewise
896+ ref comparisons are made.
897+ """
898+ # Match the repo_builder objects
899+ get_import_tag_msg_mock.return_value = 'Test tag'
900+ # Importantly, the following commit message must not be the same as the
901+ # commit messages used by the test input repository commits, so that we can
902+ # later detect the difference between commits that were already there and
903+ # new commits created by the importer for the purposes of asserting the
904+ # reuse parameter correctly.
905+ get_import_commit_msg_mock.return_value = b'Test commit (new)'
906+
907+ input_repo.write(repo.raw_repo)
908+
909+ publish_spec = source_builder.SourceSpec(
910+ version='1-1',
911+ native=False,
912+ has_patches=True,
913+ )
914+
915+ with patch_get_commit_environment(repo):
916+ with source_builder.Source(publish_spec) as dsc_path:
917+ target.import_applied_spi(
918+ repo=repo,
919+ spi=MockSPI(dsc_path, publish_spec.version),
920+ namespace='importer',
921+ allow_applied_failures=False,
922+ parent_overrides={},
923+ )
924+
925+ if reuse:
926+ # Test that the previous commit was reused. In this case,
927+ # the commit message should be one created by the
928+ # repo_builder.Commit() default, and not one created by the
929+ # importer.
930+ tip_ref = repo.raw_repo.lookup_reference(
931+ 'refs/heads/importer/ubuntu/trusty'
932+ )
933+ message = tip_ref.peel(pygit2.Commit).message
934+ assert message == 'Test commit'
935+
936+ validation_repo = input_repo.copy(**validation_repo_delta)
937+ assert repo_comparator.equals(
938+ repoA=repo.raw_repo,
939+ repoB=validation_repo,
940+ test_refs=validation_repo_expected_treewise_refs,
941+ # this should be Commit, but we do not have a method yet
942+ # to obtain the stage-wise `quilt push` commits for a
943+ # patches-applied import
944+ comparison_type=repo_comparator.RepoComparisonType.Tree,
945+ )
946diff --git a/gitubuntu/importer_test.py b/gitubuntu/importer_test.py
947index f9537c4..0ce014a 100644
948--- a/gitubuntu/importer_test.py
949+++ b/gitubuntu/importer_test.py
950@@ -12,6 +12,7 @@ import tempfile
951 import pygit2
952
953 import gitubuntu.repo_builder as repo_builder
954+from gitubuntu.repo_builder import Placeholder
955 import gitubuntu.repo_comparator as repo_comparator
956 import gitubuntu.source_builder as source_builder
957
958@@ -120,10 +121,7 @@ def test_get_import_commit_msg(
959 target.get_changelog_for_commit = Mock()
960 target.get_changelog_for_commit.return_value = b''
961
962- publish_spec = source_builder.SourceSpec(
963- version='1-1',
964- native=False,
965- )
966+ publish_spec = source_builder.SourceSpec()
967 publish_source = source_builder.Source(publish_spec)
968
969 publish_oid = repo_builder.SourceTree(publish_source).write(repo.raw_repo)
970@@ -144,42 +142,27 @@ def test_get_import_commit_msg(
971
972
973 @pytest.mark.parametrize(
974- 'input_data, expected', [
975+ 'input_repo, expected', [
976 (
977- repo_builder.Repo(
978- commit_list=[],
979- branches={},
980- tags={},
981- ),
982+ repo_builder.Repo(),
983 [],
984 ),
985 (
986 repo_builder.Repo(
987- commit_list=[],
988- branches={},
989- tags={
990- 'importer/import/1-1': repo_builder.Commit(repo_builder.Tree({})),
991- },
992+ tags={'importer/import/1-1': repo_builder.Commit()},
993 ),
994 ['refs/tags/importer/import/1-1'],
995 ),
996 (
997 repo_builder.Repo(
998- commit_list=[
999- repo_builder.Commit(
1000- tree=repo_builder.Tree({}),
1001- name='import'
1002- ),
1003- repo_builder.Commit(
1004- tree=repo_builder.Tree({}),
1005- name='reimport1'
1006- ),
1007+ commits=[
1008+ repo_builder.Commit(name='import'),
1009+ repo_builder.Commit(name='reimport1'),
1010 ],
1011- branches={},
1012 tags={
1013- 'importer/import/1-1': repo_builder.Commit(repo_builder.Tree({})),
1014- 'importer/reimport/import/1-1/0': repo_builder.Commit(repo_builder.Tree({})),
1015- 'importer/reimport/import/1-1/1': repo_builder.Commit(repo_builder.Tree({})),
1016+ 'importer/import/1-1': repo_builder.Commit(),
1017+ 'importer/reimport/import/1-1/0': repo_builder.Commit(),
1018+ 'importer/reimport/import/1-1/1': repo_builder.Commit(),
1019 },
1020 ),
1021 [
1022@@ -189,8 +172,8 @@ def test_get_import_commit_msg(
1023 ),
1024 ],
1025 )
1026-def test_get_existing_import_tags(repo, input_data, expected):
1027- input_data.write(repo.raw_repo)
1028+def test_get_existing_import_tags(repo, input_repo, expected):
1029+ input_repo.write(repo.raw_repo)
1030
1031 assert [
1032 ref.name for ref in
1033@@ -199,42 +182,27 @@ def test_get_existing_import_tags(repo, input_data, expected):
1034
1035
1036 @pytest.mark.parametrize(
1037- 'input_data, expected', [
1038+ 'input_repo, expected', [
1039 (
1040- repo_builder.Repo(
1041- commit_list=[],
1042- branches={},
1043- tags={},
1044- ),
1045+ repo_builder.Repo(),
1046 [],
1047 ),
1048 (
1049 repo_builder.Repo(
1050- commit_list=[],
1051- branches={},
1052- tags={
1053- 'importer/applied/1-1': repo_builder.Commit(repo_builder.Tree({})),
1054- },
1055+ tags={'importer/applied/1-1': repo_builder.Commit()},
1056 ),
1057 ['refs/tags/importer/applied/1-1'],
1058 ),
1059 (
1060 repo_builder.Repo(
1061- commit_list=[
1062- repo_builder.Commit(
1063- tree=repo_builder.Tree({}),
1064- name='applied'
1065- ),
1066- repo_builder.Commit(
1067- tree=repo_builder.Tree({}),
1068- name='reimport1'
1069- ),
1070+ commits=[
1071+ repo_builder.Commit(name='applied'),
1072+ repo_builder.Commit(name='reimport1'),
1073 ],
1074- branches={},
1075 tags={
1076- 'importer/applied/1-1': repo_builder.Commit(repo_builder.Tree({})),
1077- 'importer/reimport/applied/1-1/0': repo_builder.Commit(repo_builder.Tree({})),
1078- 'importer/reimport/applied/1-1/1': repo_builder.Commit(repo_builder.Tree({})),
1079+ 'importer/applied/1-1': repo_builder.Commit(),
1080+ 'importer/reimport/applied/1-1/0': repo_builder.Commit(),
1081+ 'importer/reimport/applied/1-1/1': repo_builder.Commit(),
1082 },
1083 ),
1084 [
1085@@ -244,8 +212,8 @@ def test_get_existing_import_tags(repo, input_data, expected):
1086 ),
1087 ],
1088 )
1089-def test_get_existing_applied_tags(repo, input_data, expected):
1090- input_data.write(repo.raw_repo)
1091+def test_get_existing_applied_tags(repo, input_repo, expected):
1092+ input_repo.write(repo.raw_repo)
1093
1094 assert [
1095 ref.name for ref in
1096@@ -254,24 +222,20 @@ def test_get_existing_applied_tags(repo, input_data, expected):
1097
1098
1099 @pytest.mark.parametrize(
1100- 'input_data, expected_changes, test_refs', [
1101+ [
1102+ 'input_repo',
1103+ 'validation_repo_delta',
1104+ 'validation_repo_expected_identical_refs',
1105+ ],
1106+ [
1107 (
1108- repo_builder.Repo(
1109- commit_list=[],
1110- branches={},
1111- tags={},
1112- ),
1113+ repo_builder.Repo(),
1114 {
1115- 'commit_list_to_add': [
1116- repo_builder.Commit(
1117- tree=repo_builder.Tree({}),
1118- name='import'
1119- ),
1120+ 'add_commits': [
1121+ repo_builder.Commit(name='import'),
1122 ],
1123- 'tags_to_add': {
1124- 'importer/import/1-1': repo_builder.Placeholder('import'),
1125- },
1126- 'branches_to_update': {
1127+ 'update_tags': {
1128+ 'importer/import/1-1': Placeholder('import'),
1129 },
1130 },
1131 [
1132@@ -280,29 +244,16 @@ def test_get_existing_applied_tags(repo, input_data, expected):
1133 ),
1134 (
1135 repo_builder.Repo(
1136- commit_list=[
1137- repo_builder.Commit(
1138- tree=repo_builder.Tree({}),
1139- name='import'
1140- ),
1141- ],
1142- branches={},
1143- tags={
1144- 'importer/import/1-1': repo_builder.Placeholder('import'),
1145- },
1146+ commits=[repo_builder.Commit(name='import')],
1147+ tags={'importer/import/1-1': Placeholder('import')},
1148 ),
1149 {
1150- 'commit_list_to_add': [
1151- repo_builder.Commit(
1152- tree=repo_builder.Tree({}),
1153- name='reimport'
1154- ),
1155+ 'add_commits': [
1156+ repo_builder.Commit(name='reimport'),
1157 ],
1158- 'tags_to_add': {
1159- 'importer/reimport/import/1-1/0': repo_builder.Placeholder('import'),
1160- 'importer/reimport/import/1-1/1': repo_builder.Placeholder('reimport'),
1161- },
1162- 'branches_to_update': {
1163+ 'update_tags': {
1164+ 'importer/reimport/import/1-1/0': Placeholder('import'),
1165+ 'importer/reimport/import/1-1/1': Placeholder('reimport'),
1166 },
1167 },
1168 [
1169@@ -313,34 +264,22 @@ def test_get_existing_applied_tags(repo, input_data, expected):
1170 ),
1171 (
1172 repo_builder.Repo(
1173- commit_list=[
1174- repo_builder.Commit(
1175- tree=repo_builder.Tree({}),
1176- name='import'
1177- ),
1178- repo_builder.Commit(
1179- tree=repo_builder.Tree({}),
1180- name='reimport1'
1181- ),
1182+ commits=[
1183+ repo_builder.Commit(name='import'),
1184+ repo_builder.Commit(name='reimport1'),
1185 ],
1186- branches={},
1187 tags={
1188- 'importer/import/1-1': repo_builder.Placeholder('import'),
1189- 'importer/reimport/import/1-1/0': repo_builder.Placeholder('import'),
1190- 'importer/reimport/import/1-1/1': repo_builder.Placeholder('reimport1'),
1191+ 'importer/import/1-1': Placeholder('import'),
1192+ 'importer/reimport/import/1-1/0': Placeholder('import'),
1193+ 'importer/reimport/import/1-1/1': Placeholder('reimport1'),
1194 },
1195 ),
1196 {
1197- 'commit_list_to_add': [
1198- repo_builder.Commit(
1199- tree=repo_builder.Tree({}),
1200- name='reimport2'
1201- ),
1202+ 'add_commits': [
1203+ repo_builder.Commit(name='reimport2'),
1204 ],
1205- 'tags_to_add': {
1206- 'importer/reimport/import/1-1/2': repo_builder.Placeholder('reimport2'),
1207- },
1208- 'branches_to_update': {
1209+ 'update_tags': {
1210+ 'importer/reimport/import/1-1/2': Placeholder('reimport2'),
1211 },
1212 },
1213 [
1214@@ -352,45 +291,55 @@ def test_get_existing_applied_tags(repo, input_data, expected):
1215 ),
1216 ],
1217 )
1218-def test_create_import_tag(repo, input_data, expected_changes, test_refs):
1219- publish_commit_str = str(repo.raw_repo.get(repo_builder.Commit(
1220- repo_builder.Tree({})
1221- ).write(repo.raw_repo)).peel(pygit2.Commit).id)
1222-
1223- # copy before write, so that placeholders are still present
1224- expected_result = input_data.copy(**expected_changes)
1225+def test_create_import_tag(
1226+ repo,
1227+ input_repo,
1228+ validation_repo_delta,
1229+ validation_repo_expected_identical_refs,
1230+):
1231+ """
1232+ Unit test that create_import_tag creates the correct import tag
1233
1234- input_data.write(repo.raw_repo)
1235+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
1236+ temporary output repository
1237+ :param repo_builder.Repo input_repo: input repository data
1238+ :param dict validation_repo_delta: how to transform the input repository
1239+ into a "validation repository", expressed as a dict to
1240+ provide as **kwargs to gitubuntu.repo_builder.Repo.copy() against the
1241+ input repository. The validation repository is then used for the
1242+ purposes of comparison against the output repository.
1243+ :param list(str) validation_repo_expected_identical_refs: refs that must be
1244+ identical between the validation repository and the output repository
1245+ """
1246+ publish_commit_str = str(
1247+ repo.raw_repo.get(
1248+ repo_builder.Commit().write(repo.raw_repo)
1249+ ).peel(pygit2.Commit).id
1250+ )
1251+ input_repo.write(repo.raw_repo)
1252
1253 target.create_import_tag(repo, publish_commit_str, '1-1', 'importer')
1254
1255+ validation_repo = input_repo.copy(**validation_repo_delta)
1256 assert repo_comparator.equals(
1257 repoA=repo.raw_repo,
1258- repoB=expected_result,
1259- test_refs=test_refs,
1260+ repoB=validation_repo,
1261+ test_refs=validation_repo_expected_identical_refs,
1262 )
1263
1264
1265 @pytest.mark.parametrize(
1266- 'input_data, expected_changes, test_refs', [
1267+ [
1268+ 'input_repo',
1269+ 'validation_repo_delta',
1270+ 'validation_repo_expected_identical_refs',
1271+ ],
1272+ [
1273 (
1274- repo_builder.Repo(
1275- commit_list=[],
1276- branches={},
1277- tags={},
1278- ),
1279+ repo_builder.Repo(),
1280 {
1281- 'commit_list_to_add': [
1282- repo_builder.Commit(
1283- tree=repo_builder.Tree({}),
1284- name='import'
1285- ),
1286- ],
1287- 'tags_to_add': {
1288- 'importer/applied/1-1': repo_builder.Placeholder('import'),
1289- },
1290- 'branches_to_update': {
1291- },
1292+ 'add_commits': [repo_builder.Commit(name='import')],
1293+ 'update_tags': {'importer/applied/1-1': Placeholder('import')},
1294 },
1295 [
1296 'refs/tags/importer/applied/1-1',
1297@@ -398,29 +347,14 @@ def test_create_import_tag(repo, input_data, expected_changes, test_refs):
1298 ),
1299 (
1300 repo_builder.Repo(
1301- commit_list=[
1302- repo_builder.Commit(
1303- tree=repo_builder.Tree({}),
1304- name='import'
1305- ),
1306- ],
1307- branches={},
1308- tags={
1309- 'importer/applied/1-1': repo_builder.Placeholder('import'),
1310- },
1311+ commits=[repo_builder.Commit(name='import')],
1312+ tags={'importer/applied/1-1': Placeholder('import')},
1313 ),
1314 {
1315- 'commit_list_to_add': [
1316- repo_builder.Commit(
1317- tree=repo_builder.Tree({}),
1318- name='reimport'
1319- ),
1320- ],
1321- 'tags_to_add': {
1322- 'importer/reimport/applied/1-1/0': repo_builder.Placeholder('import'),
1323- 'importer/reimport/applied/1-1/1': repo_builder.Placeholder('reimport'),
1324- },
1325- 'branches_to_update': {
1326+ 'add_commits': [repo_builder.Commit(name='reimport')],
1327+ 'update_tags': {
1328+ 'importer/reimport/applied/1-1/0': Placeholder('import'),
1329+ 'importer/reimport/applied/1-1/1': Placeholder('reimport'),
1330 },
1331 },
1332 [
1333@@ -431,34 +365,22 @@ def test_create_import_tag(repo, input_data, expected_changes, test_refs):
1334 ),
1335 (
1336 repo_builder.Repo(
1337- commit_list=[
1338- repo_builder.Commit(
1339- tree=repo_builder.Tree({}),
1340- name='import'
1341- ),
1342- repo_builder.Commit(
1343- tree=repo_builder.Tree({}),
1344- name='reimport1'
1345- ),
1346+ commits=[
1347+ repo_builder.Commit(name='import'),
1348+ repo_builder.Commit(name='reimport1'),
1349 ],
1350- branches={},
1351 tags={
1352- 'importer/applied/1-1': repo_builder.Placeholder('import'),
1353- 'importer/reimport/applied/1-1/0': repo_builder.Placeholder('import'),
1354- 'importer/reimport/applied/1-1/1': repo_builder.Placeholder('reimport1'),
1355+ 'importer/applied/1-1': Placeholder('import'),
1356+ 'importer/reimport/applied/1-1/0': Placeholder('import'),
1357+ 'importer/reimport/applied/1-1/1': Placeholder('reimport1'),
1358 },
1359 ),
1360 {
1361- 'commit_list_to_add': [
1362- repo_builder.Commit(
1363- tree=repo_builder.Tree({}),
1364- name='reimport2'
1365- ),
1366+ 'add_commits': [
1367+ repo_builder.Commit(name='reimport2')
1368 ],
1369- 'tags_to_add': {
1370- 'importer/reimport/applied/1-1/2': repo_builder.Placeholder('reimport2'),
1371- },
1372- 'branches_to_update': {
1373+ 'update_tags': {
1374+ 'importer/reimport/applied/1-1/2': Placeholder('reimport2'),
1375 },
1376 },
1377 [
1378@@ -470,33 +392,55 @@ def test_create_import_tag(repo, input_data, expected_changes, test_refs):
1379 ),
1380 ],
1381 )
1382-def test_create_applied_tag(repo, input_data, expected_changes, test_refs):
1383- publish_commit_str = str(repo.raw_repo.get(repo_builder.Commit(
1384- repo_builder.Tree({})
1385- ).write(repo.raw_repo)).peel(pygit2.Commit).id)
1386+def test_create_applied_tag(
1387+ repo,
1388+ input_repo,
1389+ validation_repo_delta,
1390+ validation_repo_expected_identical_refs,
1391+):
1392+ """
1393+ Unit test that create_applied_tag creates the correct import tag
1394
1395- # copy before write, so that placeholders are still present
1396- expected_result = input_data.copy(**expected_changes)
1397+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
1398+ temporary output repository
1399+ :param repo_builder.Repo input_repo: input repository data
1400+ :param dict validation_repo_delta: how to transform the input repository
1401+ into a "validation repository", expressed as a dict to
1402+ provide as **kwargs to gitubuntu.repo_builder.Repo.copy() against the
1403+ input repository. The validation repository is then used for the
1404+ purposes of comparison against the output repository.
1405+ :param list(str) validation_repo_expected_identical_refs: refs that must be
1406+ identical between the validation repository and the output repository
1407+ """
1408+ publish_commit_str = str(
1409+ repo.raw_repo.get(
1410+ repo_builder.Commit().write(repo.raw_repo)
1411+ ).peel(pygit2.Commit).id
1412+ )
1413
1414- input_data.write(repo.raw_repo)
1415+ input_repo.write(repo.raw_repo)
1416
1417 target.create_applied_tag(repo, publish_commit_str, '1-1', 'importer')
1418
1419+ validation_repo = input_repo.copy(**validation_repo_delta)
1420 assert repo_comparator.equals(
1421 repoA=repo.raw_repo,
1422- repoB=expected_result,
1423- test_refs=test_refs,
1424+ repoB=validation_repo,
1425+ test_refs=validation_repo_expected_identical_refs,
1426 )
1427
1428
1429-@pytest.mark.parametrize('input_data, parent_overrides, changelog_versions, patches_applied, expected_ref',
1430+@pytest.mark.parametrize(
1431+ [
1432+ 'input_repo',
1433+ 'parent_overrides',
1434+ 'changelog_versions',
1435+ 'patches_applied',
1436+ 'expected_ref',
1437+ ],
1438 [
1439 (
1440- repo_builder.Repo(
1441- commit_list=[],
1442- branches={},
1443- tags={},
1444- ),
1445+ repo_builder.Repo(),
1446 {},
1447 ['1-2', '1-1',],
1448 False,
1449@@ -504,24 +448,8 @@ def test_create_applied_tag(repo, input_data, expected_changes, test_refs):
1450 ),
1451 (
1452 repo_builder.Repo(
1453- commit_list=[
1454- repo_builder.Commit(
1455- tree=repo_builder.SourceTree(
1456- source_builder.Source(
1457- source_builder.SourceSpec(
1458- version='1-1',
1459- native=False,
1460- )
1461- )
1462- ),
1463- name='import',
1464- ),
1465- ],
1466- branches={
1467- },
1468- tags={
1469- 'importer/import/1-1': repo_builder.Placeholder('import'),
1470- },
1471+ commits=[repo_builder.Commit.from_spec(name='import')],
1472+ tags={'importer/import/1-1': Placeholder('import')},
1473 ),
1474 {},
1475 ['1-2', '1-1'],
1476@@ -530,24 +458,8 @@ def test_create_applied_tag(repo, input_data, expected_changes, test_refs):
1477 ),
1478 (
1479 repo_builder.Repo(
1480- commit_list=[
1481- repo_builder.Commit(
1482- tree=repo_builder.SourceTree(
1483- source_builder.Source(
1484- source_builder.SourceSpec(
1485- version='1-1',
1486- native=False,
1487- )
1488- )
1489- ),
1490- name='import',
1491- ),
1492- ],
1493- branches={
1494- },
1495- tags={
1496- 'importer/import/1-1': repo_builder.Placeholder('import'),
1497- },
1498+ commits=[repo_builder.Commit.from_spec(name='import')],
1499+ tags={'importer/import/1-1': Placeholder('import')},
1500 ),
1501 {},
1502 ['1-3', '1-2', '1-1'],
1503@@ -555,11 +467,7 @@ def test_create_applied_tag(repo, input_data, expected_changes, test_refs):
1504 'refs/tags/importer/import/1-1',
1505 ),
1506 (
1507- repo_builder.Repo(
1508- commit_list=[],
1509- branches={},
1510- tags={},
1511- ),
1512+ repo_builder.Repo(),
1513 {},
1514 ['1-2', '1-1',],
1515 True,
1516@@ -567,24 +475,8 @@ def test_create_applied_tag(repo, input_data, expected_changes, test_refs):
1517 ),
1518 (
1519 repo_builder.Repo(
1520- commit_list=[
1521- repo_builder.Commit(
1522- tree=repo_builder.SourceTree(
1523- source_builder.Source(
1524- source_builder.SourceSpec(
1525- version='1-1',
1526- native=False,
1527- )
1528- )
1529- ),
1530- name='applied',
1531- ),
1532- ],
1533- branches={
1534- },
1535- tags={
1536- 'importer/applied/1-1': repo_builder.Placeholder('applied'),
1537- },
1538+ commits=[repo_builder.Commit.from_spec(name='applied')],
1539+ tags={'importer/applied/1-1': Placeholder('applied')},
1540 ),
1541 {},
1542 ['1-2', '1-1'],
1543@@ -593,24 +485,8 @@ def test_create_applied_tag(repo, input_data, expected_changes, test_refs):
1544 ),
1545 (
1546 repo_builder.Repo(
1547- commit_list=[
1548- repo_builder.Commit(
1549- tree=repo_builder.SourceTree(
1550- source_builder.Source(
1551- source_builder.SourceSpec(
1552- version='1-1',
1553- native=False,
1554- )
1555- )
1556- ),
1557- name='applied',
1558- ),
1559- ],
1560- branches={
1561- },
1562- tags={
1563- 'importer/applied/1-1': repo_builder.Placeholder('applied'),
1564- },
1565+ commits=[repo_builder.Commit.from_spec(name='applied')],
1566+ tags={'importer/applied/1-1': Placeholder('applied')},
1567 ),
1568 {},
1569 ['1-3', '1-2', '1-1'],
1570@@ -621,13 +497,13 @@ def test_create_applied_tag(repo, input_data, expected_changes, test_refs):
1571 )
1572 def test_get_changelog_parent_commit(
1573 repo,
1574- input_data,
1575+ input_repo,
1576 parent_overrides,
1577 changelog_versions,
1578 patches_applied,
1579 expected_ref,
1580 ):
1581- input_data.write(repo.raw_repo)
1582+ input_repo.write(repo.raw_repo)
1583 assert target.get_changelog_parent_commit(
1584 repo,
1585 'importer',
1586@@ -697,3 +573,633 @@ def test_importer_close_repository_on_exception(main_with_repo_mock):
1587 with pytest.raises(MockError):
1588 target.main(pkgname='dummy', owner='dummy', repo=repo)
1589 repo.close.assert_called()
1590+
1591+
1592+def patch_get_commit_environment(repo):
1593+ """Patch a repository to use constant metadata
1594+
1595+ :param GitUbuntuRepository repo: the repository whose behaviour will be
1596+ patched.
1597+ :rtype: contextmanager
1598+ :returns: a context manager that, when entered, will cause the repository
1599+ object to use constant metadata for created commits when metadata is
1600+ looked up against a treeish.
1601+ """
1602+ return patch.object(
1603+ repo,
1604+ 'get_commit_environment',
1605+ return_value={
1606+ 'GIT_AUTHOR_NAME':'Test Builder',
1607+ 'GIT_AUTHOR_EMAIL':'test@example.com',
1608+ 'GIT_AUTHOR_DATE':'1970-01-01T00:00:00Z',
1609+ 'GIT_COMMITTER_NAME':'Test Builder',
1610+ 'GIT_COMMITTER_EMAIL':'test@example.com',
1611+ 'GIT_COMMITTER_DATE':'1970-01-01T00:00:00Z',
1612+ },
1613+ )
1614+
1615+
1616+def MockSPI(dsc_path, version):
1617+ """Construct a Mock SourcePackageInformation object
1618+
1619+ In the following test cases we often need a Mock object with sufficient
1620+ property information to enable an import. This function constructs such an
1621+ object.
1622+
1623+ :param str dsc_path: the path to a dsc file. This will be returned by the
1624+ dsc_pathname attribute.
1625+ :param str version: the package version string. This will be returned by
1626+ the version attribute.
1627+ :rtype: Mock
1628+ :returns: a Mock object ready to use
1629+ """
1630+ spi = Mock()
1631+ spi.dsc_pathname = dsc_path
1632+ spi.distribution_name = 'Ubuntu'
1633+ spi.version = version
1634+ spi.date_published = '1970-01-01T00:00:00Z'
1635+ head_name = Mock(name='head_name')
1636+ head_name.return_value = 'importer/ubuntu/trusty'
1637+ spi.head_name = head_name
1638+ applied_head_name = Mock(name='applied_head_name')
1639+ applied_head_name.return_value = 'importer/applied/ubuntu/trusty'
1640+ spi.applied_head_name = applied_head_name
1641+ return spi
1642+
1643+
1644+@patch('gitubuntu.importer.get_import_tag_msg')
1645+@patch('gitubuntu.importer.get_import_commit_msg')
1646+def test_import_unapplied_spi_quilt_patches(
1647+ get_import_commit_msg_mock,
1648+ get_import_tag_msg_mock,
1649+ repo,
1650+):
1651+ """Test that a package with quilt patches is imported with correct
1652+ unapplied refs
1653+
1654+ :param unittest.mock.Mock get_import_commit_msg_mock: mock of the function
1655+ that determines the commit message to use for a given import
1656+ :param unittest.mock.Mock get_import_tag_msg_mock: mock of the function
1657+ that determines the tag message to use for a given import
1658+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
1659+ temporary output repository
1660+ """
1661+ # Match the repo_builder objects
1662+ get_import_tag_msg_mock.return_value = 'Test tag'
1663+ get_import_commit_msg_mock.return_value = b'Test commit'
1664+
1665+ publish_spec = source_builder.SourceSpec(has_patches=True)
1666+
1667+ input_repo = repo_builder.Repo()
1668+ input_repo.write(repo.raw_repo)
1669+ expected_result = repo_builder.Repo(
1670+ commits=[
1671+ repo_builder.Commit(
1672+ tree=repo_builder.SourceTree(
1673+ source_builder.Source(publish_spec)
1674+ ),
1675+ name='publish'
1676+ ),
1677+ ],
1678+ tags={'importer/import/1-1': Placeholder('publish')},
1679+ branches={'importer/ubuntu/trusty': Placeholder('publish')},
1680+ )
1681+
1682+ with patch_get_commit_environment(repo):
1683+ with source_builder.Source(publish_spec) as dsc_path:
1684+ # import_unapplied_spi currently assumes it is called from the
1685+ # repository directory (pristine-tar and other commands rely on
1686+ # this)
1687+ target.import_unapplied_spi(
1688+ repo=repo,
1689+ spi=MockSPI(dsc_path, publish_spec.version),
1690+ namespace='importer',
1691+ skip_orig=False,
1692+ parent_overrides={},
1693+ )
1694+
1695+ assert repo_comparator.equals(
1696+ repoA=repo.raw_repo,
1697+ repoB=expected_result,
1698+ test_refs=[
1699+ 'refs/heads/importer/ubuntu/trusty',
1700+ 'refs/tags/importer/import/1-1',
1701+ ],
1702+ )
1703+
1704+
1705+@pytest.mark.parametrize(
1706+ [
1707+ 'input_repo',
1708+ 'changelog_versions',
1709+ 'validation_repo_delta',
1710+ 'validation_repo_expected_identical_refs',
1711+ ],
1712+ [
1713+ pytest.param(
1714+ repo_builder.Repo(
1715+ commits=[repo_builder.Commit.from_spec(name='import')],
1716+ tags={'importer/import/1-1': Placeholder('import')},
1717+ ),
1718+ ['1-2', '1-1'],
1719+ {
1720+ 'add_commits': [
1721+ repo_builder.Commit.from_spec(
1722+ name='publish',
1723+ parents=[Placeholder('import')],
1724+ changelog_versions=['1-2', '1-1'],
1725+ ),
1726+ ],
1727+ 'update_tags': {'importer/import/1-2': Placeholder('publish')},
1728+ },
1729+ [
1730+ 'refs/tags/importer/import/1-1',
1731+ 'refs/tags/importer/import/1-2',
1732+ ],
1733+ ),
1734+ pytest.param(
1735+ repo_builder.Repo(
1736+ commits=[repo_builder.Commit.from_spec(name='import')],
1737+ tags={'importer/import/1-1': Placeholder('import')},
1738+ ),
1739+ ['1-3', '1-2', '1-1'],
1740+ {
1741+ 'add_commits': [
1742+ repo_builder.Commit.from_spec(
1743+ parents=[Placeholder('import')],
1744+ name='publish',
1745+ changelog_versions=['1-3', '1-2', '1-1'],
1746+ ),
1747+ ],
1748+ 'update_tags': {'importer/import/1-3': Placeholder('publish')},
1749+ },
1750+ [
1751+ 'refs/tags/importer/import/1-1',
1752+ 'refs/tags/importer/import/1-3',
1753+ ],
1754+ ),
1755+ pytest.param(
1756+ repo_builder.Repo(
1757+ commits=[
1758+ repo_builder.Commit.from_spec(name='import'),
1759+ repo_builder.Commit.from_spec(
1760+ name='reimport',
1761+ mutate='Reimport tag contents',
1762+ ),
1763+ ],
1764+ tags={
1765+ 'importer/import/1-1': Placeholder('import'),
1766+ 'importer/reimport/import/1-1/0': Placeholder('import'),
1767+ 'importer/reimport/import/1-1/1': Placeholder('reimport'),
1768+ },
1769+ ),
1770+ ['1-2', '1-1'],
1771+ {
1772+ 'add_commits': [
1773+ repo_builder.Commit.from_spec(
1774+ parents=[
1775+ Placeholder('import'),
1776+ Placeholder('reimport'),
1777+ ],
1778+ name='publish',
1779+ changelog_versions=['1-2', '1-1'],
1780+ ),
1781+ ],
1782+ 'update_tags': {'importer/import/1-2': Placeholder('publish')},
1783+ },
1784+ [
1785+ 'refs/tags/importer/import/1-1',
1786+ 'refs/tags/importer/reimport/import/1-1/0',
1787+ 'refs/tags/importer/reimport/import/1-1/1',
1788+ 'refs/tags/importer/import/1-2',
1789+ ],
1790+ marks=pytest.mark.xfail(reason='LP: #1761332'),
1791+ ),
1792+ ]
1793+)
1794+@patch('gitubuntu.importer.get_import_tag_msg')
1795+@patch('gitubuntu.importer.get_import_commit_msg')
1796+def test_import_unapplied_spi_parenting(
1797+ get_import_commit_msg_mock,
1798+ get_import_tag_msg_mock,
1799+ repo,
1800+ input_repo,
1801+ changelog_versions,
1802+ validation_repo_delta,
1803+ validation_repo_expected_identical_refs,
1804+):
1805+ """Test that unapplied import commits have the correct parents
1806+
1807+ :param unittest.mock.Mock get_import_commit_msg_mock: mock of the function
1808+ that determines the commit message to use for a given import
1809+ :param unittest.mock.Mock get_import_tag_msg_mock: mock of the function
1810+ that determines the tag message to use for a given import
1811+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
1812+ temporary output repository
1813+ :param repo_builder.Repo input_repo: input repository data
1814+ :param list(str) changelog_versions: the versions in the changelog of a
1815+ fake package to test import
1816+ :param dict validation_repo_delta: how to transform the input
1817+ repository into a "validation repository", expressed as a dict to
1818+ provide as **kwargs to gitubuntu.repo_builder.Repo.copy() against the
1819+ input repository. The validation repository is then used for the
1820+ purposes of comparison against the output repository.
1821+ :param list(str) validation_repo_expected_identical_refs: refs that must be
1822+ identical between the validation repository and the output repository
1823+
1824+ Verify that if an import of a package is made into input_repo where the
1825+ package being imported has the given changelog_versions, then the output
1826+ repository has commits with the parents we expect. This is tested by
1827+ comparing specific output references against the validation repository.
1828+ """
1829+
1830+ # Match the repo_builder objects
1831+ get_import_tag_msg_mock.return_value = 'Test tag'
1832+ get_import_commit_msg_mock.return_value = b'Test commit'
1833+
1834+ input_repo.write(repo.raw_repo)
1835+
1836+ publish_spec = source_builder.SourceSpec(
1837+ changelog_versions=changelog_versions,
1838+ )
1839+
1840+ with patch_get_commit_environment(repo):
1841+ with source_builder.Source(publish_spec) as dsc_path:
1842+ # import_unapplied_spi currently assumes it is called from the
1843+ # repository directory (pristine-tar and other commands rely on
1844+ # this)
1845+ target.import_unapplied_spi(
1846+ repo=repo,
1847+ spi=MockSPI(dsc_path, publish_spec.version),
1848+ namespace='importer',
1849+ skip_orig=False,
1850+ parent_overrides={},
1851+ )
1852+
1853+ # we would like to check the commit hashes, but we cannot
1854+ # currently, as the commit messages are specific to the importer
1855+ # and not codified
1856+ validation_repo = input_repo.copy(**validation_repo_delta)
1857+ assert repo_comparator.equals(
1858+ repoA=repo.raw_repo,
1859+ repoB=validation_repo,
1860+ test_refs=validation_repo_expected_identical_refs,
1861+ )
1862+
1863+
1864+@patch('gitubuntu.importer.get_import_tag_msg')
1865+@patch('gitubuntu.importer.get_import_commit_msg')
1866+def test_import_unapplied_spi_parent_override(
1867+ get_import_commit_msg_mock,
1868+ get_import_tag_msg_mock,
1869+ repo,
1870+):
1871+ """Test import_unapplied_spi() parent_override functionality
1872+
1873+ Test that if parent_overrides is used in the import_unapplied_spi call then
1874+ the resulting commit correctly uses the overridden parents specified.
1875+
1876+ :param unittest.mock.Mock get_import_commit_msg_mock: mock of the function
1877+ that determines the commit message to use for a given import
1878+ :param unittest.mock.Mock get_import_tag_msg_mock: mock of the function
1879+ that determines the tag message to use for a given import
1880+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
1881+ temporary output repository
1882+ :param repo_builder.Repo input_repo: input repository data
1883+ """
1884+ # Match the repo_builder objects
1885+ get_import_tag_msg_mock.return_value = 'Test tag'
1886+ get_import_commit_msg_mock.return_value = b'Test commit'
1887+
1888+ input_repo = repo_builder.Repo(
1889+ commits=[
1890+ repo_builder.Commit.from_spec(name='import1-1', version='1-1'),
1891+ repo_builder.Commit.from_spec(name='import1-2', version='1-2'),
1892+ ],
1893+ tags={
1894+ 'importer/import/1-1': Placeholder('import1-1'),
1895+ 'importer/import/1-2': Placeholder('import1-2'),
1896+ },
1897+ )
1898+ input_repo.write(repo.raw_repo)
1899+
1900+ publish_spec = source_builder.SourceSpec(
1901+ changelog_versions=['2-1', '1-1'],
1902+ )
1903+
1904+ with patch_get_commit_environment(repo):
1905+ with source_builder.Source(publish_spec) as dsc_path:
1906+ # import_unapplied_spi currently assumes it is called from the
1907+ # repository directory (pristine-tar and other commands rely on
1908+ # this)
1909+ target.import_unapplied_spi(
1910+ repo=repo,
1911+ spi=MockSPI(dsc_path, publish_spec.version),
1912+ namespace='importer',
1913+ skip_orig=False,
1914+ parent_overrides={'2-1': {'changelog_parent': '1-2'}},
1915+ )
1916+
1917+ validation_repo = input_repo.copy(
1918+ add_commits=[
1919+ repo_builder.Commit.from_spec(
1920+ parents=[Placeholder('import1-2')],
1921+ name='publish',
1922+ changelog_versions=['2-1', '1-1'],
1923+ ),
1924+ ],
1925+ update_tags={
1926+ 'importer/import/2-1': Placeholder('publish'),
1927+ },
1928+ )
1929+ validation_repo_expected_identical_refs = [
1930+ 'refs/tags/importer/import/1-1',
1931+ 'refs/tags/importer/import/1-2',
1932+ 'refs/tags/importer/import/2-1',
1933+ ]
1934+ assert repo_comparator.equals(
1935+ repoA=repo.raw_repo,
1936+ repoB=validation_repo,
1937+ test_refs=validation_repo_expected_identical_refs,
1938+ )
1939+
1940+
1941+def test_import_unapplied_spi_parent_override_failure(repo):
1942+ """
1943+ Test override_parents ParentOverrideError raise
1944+
1945+ When a parent override is specified but the specified version doesn't have
1946+ an import tag, an exception should be raised.
1947+
1948+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
1949+ temporary output repository
1950+ """
1951+ repo_builder.Repo(
1952+ commits=[repo_builder.Commit.from_spec(name='import1-1')],
1953+ tags={'importer/import/1-1': Placeholder('import1-1')},
1954+ ).write(repo.raw_repo)
1955+
1956+ with pytest.raises(target.ParentOverrideError):
1957+ target.override_parents(
1958+ parent_overrides={'2-1': {'changelog_parent': '1-2'}},
1959+ repo=repo,
1960+ version='2-1',
1961+ namespace='importer',
1962+ )
1963+
1964+
1965+@pytest.mark.parametrize(
1966+ 'input_repo, expected_ancestor_commits, expected_parent_commits', [
1967+ # In general, these tests do not set applied commit parents in the
1968+ # input repository since we have no mechanism to do that correctly, but
1969+ # this doesn't matter for the purposes of these tests.
1970+
1971+ # if only one import tag exists, then it is the parent
1972+ pytest.param(
1973+ repo_builder.Repo(
1974+ commits=[
1975+ repo_builder.Commit.from_spec(
1976+ name='unapplied1',
1977+ has_patches=True,
1978+ ),
1979+ repo_builder.Commit.from_spec(
1980+ parents=[Placeholder('unapplied1')],
1981+ name='unapplied2',
1982+ changelog_versions=['1-2', '1-1'],
1983+ has_patches=True,
1984+ ),
1985+ repo_builder.Commit.from_spec(
1986+ name='applied1',
1987+ patches_applied=True,
1988+ ),
1989+ ],
1990+ # no branches: technically not possible but branches are not
1991+ # relevant to the test
1992+ branches={},
1993+ tags={
1994+ 'importer/import/1-1': Placeholder('unapplied1'),
1995+ 'importer/import/1-2': Placeholder('unapplied2'),
1996+ 'importer/applied/1-1': Placeholder('applied1'),
1997+ },
1998+ ),
1999+ ['refs/tags/importer/import/1-2'],
2000+ ['refs/tags/importer/applied/1-1'],
2001+ ),
2002+
2003+ # if multiple import tags exist, then do they all end up as parents?
2004+ pytest.param(
2005+ repo_builder.Repo(
2006+ commits=[
2007+ repo_builder.Commit.from_spec(
2008+ name='unapplied1',
2009+ has_patches=True,
2010+ ),
2011+ repo_builder.Commit.from_spec(
2012+ parents=[Placeholder('unapplied1')],
2013+ name='unapplied2',
2014+ changelog_versions=['1-2', '1-1'],
2015+ has_patches=True,
2016+ ),
2017+ repo_builder.Commit.from_spec(
2018+ parents=[Placeholder('unapplied1')],
2019+ name='unapplied2reimport',
2020+ changelog_versions=['1-2', '1-1'],
2021+ has_patches=True,
2022+ mutate='reimport tag',
2023+ ),
2024+ repo_builder.Commit.from_spec(
2025+ name='applied1',
2026+ patches_applied=True,
2027+ ),
2028+ ],
2029+ # no branches: technically not possible but branches are not
2030+ # relevant to the test
2031+ branches={},
2032+ tags={
2033+ 'importer/import/1-1':
2034+ Placeholder('unapplied1'),
2035+ 'importer/import/1-2':
2036+ Placeholder('unapplied2'),
2037+ 'importer/reimport/import/1-2/0':
2038+ Placeholder('unapplied2'),
2039+ 'importer/reimport/import/1-2/1':
2040+ Placeholder('unapplied2reimport'),
2041+ 'importer/applied/1-1':
2042+ Placeholder('applied1'),
2043+ },
2044+ ),
2045+ [
2046+ 'refs/tags/importer/reimport/import/1-2/0',
2047+ 'refs/tags/importer/reimport/import/1-2/1',
2048+ ],
2049+ [
2050+ 'refs/tags/importer/applied/1-1',
2051+ ],
2052+ marks=pytest.mark.xfail(reason='LP: #1755247'),
2053+ ),
2054+
2055+ # do we correctly create a reimport tag because a different import
2056+ # already exists?
2057+ pytest.param(
2058+ repo_builder.Repo(
2059+ commits=[
2060+ repo_builder.Commit.from_spec(
2061+ name='unapplied1',
2062+ has_patches=True,
2063+ ),
2064+ repo_builder.Commit.from_spec(
2065+ name='unapplied1_reimport',
2066+ has_patches=True,
2067+ mutate='reimport contents',
2068+ ),
2069+ repo_builder.Commit.from_spec(
2070+ parents=[Placeholder('unapplied1')],
2071+ name='unapplied2',
2072+ changelog_versions=['1-2', '1-1'],
2073+ has_patches=True,
2074+ ),
2075+ repo_builder.Commit.from_spec(
2076+ name='applied1',
2077+ patches_applied=True,
2078+ ),
2079+ repo_builder.Commit.from_spec(
2080+ name='applied1_reimport',
2081+ patches_applied=True,
2082+ mutate='reimport contents',
2083+ ),
2084+ ],
2085+ # no branches: technically not possible but branches are not
2086+ # relevant to the test
2087+ branches={},
2088+ tags={
2089+ 'importer/import/1-1':
2090+ Placeholder('unapplied1'),
2091+ 'importer/reimport/import/1-1/0':
2092+ Placeholder('unapplied1'),
2093+ 'importer/reimport/import/1-1/1':
2094+ Placeholder('unapplied1_reimport'),
2095+ 'importer/import/1-2':
2096+ Placeholder('unapplied2'),
2097+ 'importer/applied/1-1':
2098+ Placeholder('applied1'),
2099+ 'importer/reimport/applied/1-1/0':
2100+ Placeholder('applied1'),
2101+ 'importer/reimport/applied/1-1/1':
2102+ Placeholder('applied1_reimport'),
2103+ },
2104+ ),
2105+ [
2106+ 'refs/tags/importer/reimport/import/1-2/0',
2107+ 'refs/tags/importer/reimport/import/1-2/1',
2108+ ],
2109+ [
2110+ 'refs/tags/importer/reimport/applied/1-1/0',
2111+ 'refs/tags/importer/reimport/applied/1-1/1',
2112+ ],
2113+ marks=pytest.mark.xfail(reason='LP: #1755247'),
2114+ ),
2115+ ],
2116+)
2117+@patch('gitubuntu.importer.get_import_tag_msg')
2118+@patch('gitubuntu.importer.get_import_commit_msg')
2119+def test_import_applied_spi_parenting(
2120+ get_import_commit_msg_mock,
2121+ get_import_tag_msg_mock,
2122+ repo,
2123+ input_repo,
2124+ expected_ancestor_commits,
2125+ expected_parent_commits,
2126+):
2127+ """Test that applied import commits have the right parents
2128+
2129+ :param unittest.mock.Mock get_import_commit_msg_mock: mock of the function
2130+ that determines the commit message to use for a given import
2131+ :param unittest.mock.Mock get_import_tag_msg_mock: mock of the function
2132+ that determines the tag message to use for a given import
2133+ :param repo gitubuntu.git_repository.GitUbuntuRepository: fixture to hold a
2134+ temporary output repository
2135+ :param repo_builder.Repo input_repo: input repository data
2136+ :param list(str) expected_ancestor_commits: list of commit-ish strings that
2137+ must be ancestors of the 'applied/1-2' tag following the applied import
2138+ :param list(str) expected_parent_commits: list of commit-ish strings that
2139+ must be parents of the 'applied/1-2' tag following the applied import.
2140+
2141+ A fake package with version '1-2' that has a changelog parent of '1-1' is
2142+ imported on top of the provided input_repo. The test fails if any
2143+ of the expected_ancestor_commits or expected_parent_commits are not
2144+ present.
2145+
2146+ This test is ugly because we do not yet have a programmatic way
2147+ to get the interstitial commits of the patch applications.
2148+ """
2149+ # Match the repo_builder objects
2150+ get_import_tag_msg_mock.return_value = 'Test tag'
2151+ get_import_commit_msg_mock.return_value = b'Test commit'
2152+
2153+ input_repo.write(repo.raw_repo)
2154+
2155+ publish_spec = source_builder.SourceSpec(
2156+ changelog_versions=['1-2', '1-1'],
2157+ has_patches=True,
2158+ )
2159+
2160+ with patch_get_commit_environment(repo):
2161+ with source_builder.Source(publish_spec) as dsc_path:
2162+ target.import_applied_spi(
2163+ repo=repo,
2164+ spi=MockSPI(dsc_path, publish_spec.version),
2165+ namespace='importer',
2166+ allow_applied_failures=False,
2167+ parent_overrides={},
2168+ )
2169+
2170+ applied_tag = repo.raw_repo.lookup_reference(
2171+ 'refs/tags/importer/applied/1-2',
2172+ )
2173+ applied_commit = applied_tag.peel(pygit2.Commit)
2174+ applied_commit_parents = applied_commit.parents
2175+
2176+ # convert refs to commits
2177+ expected_ancestor_commit_hashes = [
2178+ str(
2179+ repo.raw_repo.lookup_reference(
2180+ expected_ancestor
2181+ ).peel(pygit2.Commit).id
2182+ )
2183+ for expected_ancestor in expected_ancestor_commits
2184+ ]
2185+ expected_parent_commit_hashes = [
2186+ str(
2187+ repo.raw_repo.lookup_reference(
2188+ expected_parent
2189+ ).peel(pygit2.Commit).id
2190+ )
2191+ for expected_parent in expected_parent_commits
2192+ ]
2193+
2194+ missing_ancestor_commit_hashes = []
2195+ for ancestor_commit_hash in expected_ancestor_commit_hashes:
2196+ # We use merge_base as an ancestry test. If a merge base against an
2197+ # expected ancestor isn't the expected ancestor itself, then it isn't
2198+ # an ancestor.
2199+ merge_base = repo.raw_repo.merge_base(
2200+ repo.raw_repo.get(ancestor_commit_hash).id,
2201+ applied_commit.id,
2202+ )
2203+ if str(
2204+ repo.raw_repo.get(merge_base).peel(pygit2.Commit).id
2205+ ) != ancestor_commit_hash:
2206+ missing_ancestor_commit_hashes.append(ancestor_commit_hash)
2207+
2208+ missing_parent_commit_hashes = []
2209+ for parent_commit_hash in expected_parent_commit_hashes:
2210+ for parent in applied_commit_parents:
2211+ if str(parent.id) == parent_commit_hash:
2212+ break
2213+ else:
2214+ missing_parent_commit_hashes.append(parent_commit_hash)
2215+
2216+ assert (
2217+ not missing_ancestor_commit_hashes and
2218+ not missing_parent_commit_hashes
2219+ )
2220diff --git a/gitubuntu/repo_builder.py b/gitubuntu/repo_builder.py
2221index 94bc62a..c779c71 100644
2222--- a/gitubuntu/repo_builder.py
2223+++ b/gitubuntu/repo_builder.py
2224@@ -1,21 +1,10 @@
2225 import binascii
2226 import copy
2227-import functools
2228-import tempfile
2229-import unittest
2230
2231 import pygit2
2232-import pytest
2233
2234 import gitubuntu.importer
2235-from gitubuntu.test_fixtures import (
2236- repo,
2237- pygit2_repo,
2238-)
2239-
2240-# Only for tests; can move to repo_builder_test when the move is done
2241-import gitubuntu.git_repository
2242-from gitubuntu.source_builder import Source
2243+from gitubuntu.source_builder import Source, SourceSpec
2244
2245
2246 """Build test git repositories as data structures
2247@@ -206,12 +195,62 @@ class SourceTree(NamedMixin, WriteMixin):
2248
2249
2250 class Commit(NamedMixin, WriteMixin):
2251- def __init__(self, tree, parents=None, message=None, **kwargs):
2252+ def __init__(self, tree=None, parents=None, message=None, **kwargs):
2253+ """Construct a Commit object
2254+
2255+ :param Tree tree: the tree contained by the commit. If None, an empty
2256+ tree is used.
2257+ :param list(Commit) parents: the commit objects that are the parents of
2258+ this commit. The list may be empty if the commit is to have no
2259+ parent.
2260+ :param str message: the commit message.
2261+ :param **kwargs: other parameters supplied to superclasses.
2262+ """
2263 super().__init__(**kwargs)
2264- self.tree = tree
2265+ self.tree = tree or Tree({})
2266 self.parents = parents or []
2267 self.message = 'Test commit' if message is None else message
2268
2269+ @classmethod
2270+ def from_spec(
2271+ cls,
2272+ parents=None,
2273+ message=None,
2274+ name=None,
2275+ patches_applied=False,
2276+ **kwargs,
2277+ ):
2278+ """Construct a Commit object containing a test source package
2279+
2280+ :param list(Commit) parents: the commit objects that are the parents of
2281+ this commit. The list may be empty if the commit is to have no
2282+ parent.
2283+ :param str message: the commit message.
2284+ :param str name: the value for the name attribute of the constructed
2285+ object, used for matching with Placeholder objects after
2286+ construction.
2287+ :param bool patches_applied: whether the tree inside the commit should
2288+ have patches applied. Passed to the SourceTree constructor. If
2289+ True, then the test source package is generated with patches using
2290+ the has_patches argument to the SourceSpec constructor, unless
2291+ overridden by kwargs.
2292+ :param **kwargs: additional parameters are passed to the SourceSpec
2293+ constructor to customise the test source package used for the tree
2294+ of the commit.
2295+ """
2296+ spec_args = {'has_patches': patches_applied}
2297+ spec_args.update(kwargs)
2298+
2299+ return cls(
2300+ tree=SourceTree(
2301+ Source(SourceSpec(**spec_args)),
2302+ patches_applied=patches_applied,
2303+ ),
2304+ parents=parents,
2305+ message=message,
2306+ name=name,
2307+ )
2308+
2309 def replace(self, old, new):
2310 for i, parent in enumerate(self.parents):
2311 if parent is old:
2312@@ -248,10 +287,10 @@ class Repo:
2313 This is affectively a commit container that exists for the
2314 convenience of writing them to a git repository at once.
2315 """
2316- def __init__(self, commit_list, branches=None, tags=None):
2317+ def __init__(self, commits=None, branches=None, tags=None):
2318 """Construct a Repo instance
2319
2320- :param list(Commit) commit_list: the commits this Repo should
2321+ :param list(Commit) commits: the commits this Repo should
2322 contain. These may be related to each other with parenting
2323 relationships, but do not have to be.
2324 :param dict(str: Commit or Placeholder) branches: the branches
2325@@ -262,7 +301,7 @@ class Repo:
2326 should contain. A tag has a name and a target to point to.
2327 The target can be a Commit object or Placeholder object.
2328 """
2329- self.commit_list = commit_list
2330+ self.commit_list = commits or list()
2331 self.branches = branches or dict()
2332 self.tags = tags or dict()
2333
2334@@ -308,11 +347,28 @@ class Repo:
2335 for _, tag in self.tags.items():
2336 yield self, tag
2337
2338- def copy(self, commit_list_to_add, branches_to_update, tags_to_add):
2339+ def copy(self, add_commits=None, update_branches=None, update_tags=None):
2340+ """Clone the Repo, optionally with some changes
2341+
2342+ :param list(Commit) add_commits: commits to add to the cloned Repo
2343+ :param dict(str: Commit or Placeholder) branches: branches to update in
2344+ the cloned Repo.
2345+ :param dict(str: Commit or Placeholder) tags: tags to update in the
2346+ cloned Repo.
2347+
2348+ "update" is in the sense of a dict object's update method: this allows
2349+ for both the definition of new branches and tags, and updates to
2350+ existing branches and tags.
2351+
2352+ There is currently no facility to delete branches or tags.
2353+ """
2354 new_repo = copy.deepcopy(self)
2355- new_repo.commit_list.extend(commit_list_to_add)
2356- new_repo.branches.update(branches_to_update)
2357- new_repo.tags.update(tags_to_add)
2358+ if add_commits:
2359+ new_repo.commit_list.extend(add_commits)
2360+ if update_branches:
2361+ new_repo.branches.update(update_branches)
2362+ if update_tags:
2363+ new_repo.tags.update(update_tags)
2364 return new_repo
2365
2366
2367@@ -336,227 +392,3 @@ def replace_placeholders(top):
2368 for parent, obj in top.walk():
2369 if isinstance(obj, Placeholder):
2370 parent.replace(obj, find_node(top, obj.target_name))
2371-
2372-
2373-class TestObjectCreation(unittest.TestCase):
2374- def setUp(self):
2375- self.git_dir = tempfile.TemporaryDirectory()
2376- self.repo = pygit2.init_repository(self.git_dir.name)
2377-
2378- def tearDown(self):
2379- self.repo = None
2380- self.git_dir.cleanup()
2381-
2382- def testBlobCreation(self):
2383- ref = Blob(b'foo').write(self.repo)
2384- assert self.repo.get(ref).data == b'foo'
2385-
2386- def testExecutableBlobTreeCreation(self):
2387- tree_ref = Tree({'foo': ExecutableBlob(b'bar')}).write(self.repo)
2388- tree_entry = self.repo.get(tree_ref)['foo']
2389- assert self.repo.get(tree_entry.id).data == b'bar'
2390- assert tree_entry.filemode == pygit2.GIT_FILEMODE_BLOB_EXECUTABLE
2391-
2392- def testSymlinkTreeCreation(self):
2393- tree_ref = Tree({'foo': Symlink(b'bar')}).write(self.repo)
2394- tree_entry = self.repo.get(tree_ref)['foo']
2395- assert tree_entry.filemode == pygit2.GIT_FILEMODE_LINK
2396-
2397- def testCommitCreation(self):
2398- tree = Tree({'foo': Blob(b'bar')})
2399- tree_ref = tree.write(self.repo)
2400- commit_ref = Commit(tree).write(self.repo)
2401- commit = self.repo.get(commit_ref)
2402- assert commit.tree_id == tree_ref
2403-
2404- def testCommitMessage(self):
2405- ref = Commit(Tree({}), message='foo').write(self.repo)
2406- commit = self.repo.get(ref)
2407- assert commit.message == 'foo'
2408-
2409- def testCommitParents(self):
2410- tree = Tree({})
2411- top = Commit(tree, message='top')
2412- top_ref = top.write(self.repo)
2413- child_a = Commit(tree, parents=[top], message='child_a')
2414- child_a_ref = child_a.write(self.repo)
2415- child_b = Commit(tree, parents=[top], message='child_b')
2416- child_b_ref = child_b.write(self.repo)
2417- merge = Commit(tree, parents=[child_a, child_b], message='merge')
2418- merge_ref = merge.write(self.repo)
2419-
2420- merge_commit = self.repo.get(merge_ref)
2421- assert merge_commit.parent_ids == [child_a_ref, child_b_ref]
2422-
2423- child_a_commit = self.repo.get(child_a_ref)
2424- assert child_a_commit.parent_ids == [top_ref]
2425-
2426- top_commit = self.repo.get(top_ref)
2427- assert top_commit.parent_ids == []
2428-
2429- def testNameSearch(self):
2430- inner_tree = Tree({'foo': Blob(b'bar', name='blob')}, name='inner')
2431- outer_tree = Tree({'baz': inner_tree}, name='outer')
2432- commit = Commit(outer_tree, name='commit')
2433- assert find_node(commit, 'commit') is commit
2434- assert find_node(commit, 'inner') is inner_tree
2435- assert find_node(commit, 'outer') is outer_tree
2436- assert isinstance(find_node(commit, 'blob'), Blob)
2437- with pytest.raises(KeyError):
2438- find_node(commit, 'absent')
2439-
2440- def testRepo(self):
2441- graph = Repo([
2442- Commit(Tree({}), parents=[Placeholder('parent')]),
2443- Commit(Tree({}), name='parent'),
2444- ])
2445- child_ref = graph.write(self.repo)
2446- child = self.repo.get(child_ref)
2447- assert child.parent_ids == [graph.commit_list[1].write(self.repo)]
2448-
2449- def testRepoBranchesTags(self):
2450- graph = Repo(
2451- commit_list=[
2452- Commit(
2453- Tree({}),
2454- parents=[Placeholder('parent')],
2455- name='child',
2456- ),
2457- Commit(Tree({}), name='parent'),
2458- ],
2459- branches={
2460- 'branch1': Placeholder('parent'),
2461- 'branch2': Commit(Tree({'foo': Blob(b'qux')})),
2462- },
2463- tags={
2464- 'tag1': Placeholder('child'),
2465- 'tag2': Commit(Tree({'foo': Blob(b'quz')})),
2466- },
2467- )
2468- child_ref = graph.write(self.repo)
2469- child = self.repo.get(child_ref)
2470- assert self.repo.lookup_reference(
2471- 'refs/heads/branch1'
2472- ).peel(pygit2.Commit).id == graph.commit_list[1].write(self.repo)
2473- assert self.repo.lookup_reference('refs/heads/branch2')
2474- assert self.repo.lookup_reference(
2475- 'refs/tags/tag1'
2476- ).peel(pygit2.Commit).id == graph.commit_list[0].write(self.repo)
2477- assert self.repo.lookup_reference('refs/tags/tag2')
2478- assert child.parent_ids == [graph.commit_list[1].write(self.repo)]
2479-
2480-
2481-def test_source_tree(pygit2_repo):
2482- commit_str = Commit(SourceTree(Source())).write(pygit2_repo)
2483- commit = pygit2_repo.get(commit_str)
2484- assert gitubuntu.git_repository.follow_symlinks_to_blob(
2485- repo=pygit2_repo,
2486- treeish_object=commit,
2487- path='debian/changelog',
2488- )
2489-
2490-
2491-@pytest.mark.parametrize('cls', [
2492- functools.partial(Blob, b'qux'),
2493- functools.partial(ExecutableBlob, b'qux'),
2494- functools.partial(Symlink, 'target'),
2495- functools.partial(Tree, {}),
2496- functools.partial(SourceTree, {}),
2497-])
2498-def test_replace_placeholders(cls):
2499- common_blob = cls(name='name')
2500- top = Tree({'foo': common_blob, 'bar': Placeholder('name')})
2501- assert top.entries['foo'] is not top.entries['bar']
2502- replace_placeholders(top)
2503- assert top.entries['foo'] is top.entries['bar']
2504- assert isinstance(top.entries['bar'], type(common_blob))
2505-
2506-
2507-# The following test uses replace directly, because
2508-# replace_placeholders' use of find_node will raise its own KeyError.
2509-@pytest.mark.parametrize('testobj', [
2510- Tree({}),
2511- Commit(Tree({})),
2512- Repo(commit_list=[]),
2513-])
2514-def test_replace_missing_placeholder(testobj):
2515- commit = Commit(Tree({}))
2516- with pytest.raises(KeyError):
2517- testobj.replace(Placeholder('name'), commit)
2518-
2519-
2520-def test_repo_placeholder():
2521- empty_tree = Tree({})
2522- graph = Repo(
2523- commit_list=[
2524- Commit(empty_tree, message='top', name='top'),
2525- Commit(
2526- empty_tree,
2527- message='child',
2528- parents=[Placeholder('top')],
2529- name='child',
2530- ),
2531- ],
2532- branches={
2533- 'branch1': Placeholder('top'),
2534- 'branch2': Placeholder('child'),
2535- },
2536- tags={
2537- 'tag1': Placeholder('child'),
2538- 'tag2': Placeholder('top'),
2539- },
2540- )
2541- replace_placeholders(graph)
2542- assert graph.commit_list[1].parents[0] is graph.commit_list[0]
2543- assert graph.branches['branch1'] is graph.commit_list[0]
2544- assert graph.branches['branch2'] is graph.commit_list[1]
2545- assert graph.tags['tag2'] is graph.commit_list[0]
2546- assert graph.tags['tag1'] is graph.commit_list[1]
2547-
2548-
2549-def test_copy():
2550- graph = Repo(
2551- commit_list=[
2552- Commit(Tree({}), message='top', name='top'),
2553- Commit(
2554- Tree({}),
2555- message='child',
2556- parents=[Placeholder('top')],
2557- name='child',
2558- ),
2559- ],
2560- branches={
2561- 'branch1': Placeholder('top'),
2562- 'branch2': Placeholder('child'),
2563- },
2564- tags={
2565- 'tag1': Placeholder('child'),
2566- },
2567- )
2568- copy_graph = graph.copy(
2569- commit_list_to_add=[
2570- Commit(Tree({}), message='new', name='new'),
2571- ],
2572- branches_to_update={
2573- 'branch2': Placeholder('new'),
2574- 'branch3': Placeholder('new'),
2575- },
2576- tags_to_add={
2577- 'tag2': Placeholder('new'),
2578- 'tag3': Placeholder('top'),
2579- }
2580- )
2581- replace_placeholders(graph)
2582- replace_placeholders(copy_graph)
2583-
2584- assert graph.commit_list[1].parents[0] is graph.commit_list[0]
2585- assert graph.branches['branch1'] is graph.commit_list[0]
2586- assert graph.tags['tag1'] is graph.commit_list[1]
2587-
2588- assert copy_graph.commit_list[2].message == 'new'
2589- assert copy_graph.branches['branch1'] is copy_graph.commit_list[0]
2590- assert copy_graph.branches['branch2'] is copy_graph.commit_list[2]
2591- assert copy_graph.branches['branch3'] is copy_graph.commit_list[2]
2592- assert copy_graph.tags['tag1'] is copy_graph.commit_list[1]
2593- assert copy_graph.tags['tag2'] is copy_graph.commit_list[2]
2594- assert copy_graph.tags['tag3'] is copy_graph.commit_list[0]
2595diff --git a/gitubuntu/repo_builder_test.py b/gitubuntu/repo_builder_test.py
2596new file mode 100644
2597index 0000000..bfe053f
2598--- /dev/null
2599+++ b/gitubuntu/repo_builder_test.py
2600@@ -0,0 +1,284 @@
2601+import functools
2602+import tempfile
2603+import unittest
2604+from unittest.mock import sentinel
2605+
2606+import pygit2
2607+import pytest
2608+
2609+import gitubuntu.git_repository
2610+from gitubuntu.repo_builder import (
2611+ Blob,
2612+ Commit,
2613+ ExecutableBlob,
2614+ Placeholder,
2615+ Repo,
2616+ Source,
2617+ SourceTree,
2618+ Symlink,
2619+ Tree,
2620+ find_node,
2621+ replace_placeholders,
2622+)
2623+from gitubuntu.test_fixtures import (
2624+ repo,
2625+ pygit2_repo,
2626+)
2627+
2628+
2629+class TestObjectCreation(unittest.TestCase):
2630+ def setUp(self):
2631+ self.git_dir = tempfile.TemporaryDirectory()
2632+ self.repo = pygit2.init_repository(self.git_dir.name)
2633+
2634+ def tearDown(self):
2635+ self.repo = None
2636+ self.git_dir.cleanup()
2637+
2638+ def testBlobCreation(self):
2639+ ref = Blob(b'foo').write(self.repo)
2640+ assert self.repo.get(ref).data == b'foo'
2641+
2642+ def testExecutableBlobTreeCreation(self):
2643+ tree_ref = Tree({'foo': ExecutableBlob(b'bar')}).write(self.repo)
2644+ tree_entry = self.repo.get(tree_ref)['foo']
2645+ assert self.repo.get(tree_entry.id).data == b'bar'
2646+ assert tree_entry.filemode == pygit2.GIT_FILEMODE_BLOB_EXECUTABLE
2647+
2648+ def testSymlinkTreeCreation(self):
2649+ tree_ref = Tree({'foo': Symlink(b'bar')}).write(self.repo)
2650+ tree_entry = self.repo.get(tree_ref)['foo']
2651+ assert tree_entry.filemode == pygit2.GIT_FILEMODE_LINK
2652+
2653+ def testCommitCreation(self):
2654+ tree = Tree({'foo': Blob(b'bar')})
2655+ tree_ref = tree.write(self.repo)
2656+ commit_ref = Commit(tree).write(self.repo)
2657+ commit = self.repo.get(commit_ref)
2658+ assert commit.tree_id == tree_ref
2659+
2660+ def testCommitMessage(self):
2661+ ref = Commit(Tree({}), message='foo').write(self.repo)
2662+ commit = self.repo.get(ref)
2663+ assert commit.message == 'foo'
2664+
2665+ def testCommitParents(self):
2666+ tree = Tree({})
2667+ top = Commit(tree, message='top')
2668+ top_ref = top.write(self.repo)
2669+ child_a = Commit(tree, parents=[top], message='child_a')
2670+ child_a_ref = child_a.write(self.repo)
2671+ child_b = Commit(tree, parents=[top], message='child_b')
2672+ child_b_ref = child_b.write(self.repo)
2673+ merge = Commit(tree, parents=[child_a, child_b], message='merge')
2674+ merge_ref = merge.write(self.repo)
2675+
2676+ merge_commit = self.repo.get(merge_ref)
2677+ assert merge_commit.parent_ids == [child_a_ref, child_b_ref]
2678+
2679+ child_a_commit = self.repo.get(child_a_ref)
2680+ assert child_a_commit.parent_ids == [top_ref]
2681+
2682+ top_commit = self.repo.get(top_ref)
2683+ assert top_commit.parent_ids == []
2684+
2685+ def testNameSearch(self):
2686+ inner_tree = Tree({'foo': Blob(b'bar', name='blob')}, name='inner')
2687+ outer_tree = Tree({'baz': inner_tree}, name='outer')
2688+ commit = Commit(outer_tree, name='commit')
2689+ assert find_node(commit, 'commit') is commit
2690+ assert find_node(commit, 'inner') is inner_tree
2691+ assert find_node(commit, 'outer') is outer_tree
2692+ assert isinstance(find_node(commit, 'blob'), Blob)
2693+ with pytest.raises(KeyError):
2694+ find_node(commit, 'absent')
2695+
2696+ def testRepo(self):
2697+ graph = Repo([
2698+ Commit(Tree({}), parents=[Placeholder('parent')]),
2699+ Commit(Tree({}), name='parent'),
2700+ ])
2701+ child_ref = graph.write(self.repo)
2702+ child = self.repo.get(child_ref)
2703+ assert child.parent_ids == [graph.commit_list[1].write(self.repo)]
2704+
2705+ def testRepoBranchesTags(self):
2706+ graph = Repo(
2707+ commits=[
2708+ Commit(
2709+ Tree({}),
2710+ parents=[Placeholder('parent')],
2711+ name='child',
2712+ ),
2713+ Commit(Tree({}), name='parent'),
2714+ ],
2715+ branches={
2716+ 'branch1': Placeholder('parent'),
2717+ 'branch2': Commit(Tree({'foo': Blob(b'qux')})),
2718+ },
2719+ tags={
2720+ 'tag1': Placeholder('child'),
2721+ 'tag2': Commit(Tree({'foo': Blob(b'quz')})),
2722+ },
2723+ )
2724+ child_ref = graph.write(self.repo)
2725+ child = self.repo.get(child_ref)
2726+ assert self.repo.lookup_reference(
2727+ 'refs/heads/branch1'
2728+ ).peel(pygit2.Commit).id == graph.commit_list[1].write(self.repo)
2729+ assert self.repo.lookup_reference('refs/heads/branch2')
2730+ assert self.repo.lookup_reference(
2731+ 'refs/tags/tag1'
2732+ ).peel(pygit2.Commit).id == graph.commit_list[0].write(self.repo)
2733+ assert self.repo.lookup_reference('refs/tags/tag2')
2734+ assert child.parent_ids == [graph.commit_list[1].write(self.repo)]
2735+
2736+
2737+def test_source_tree(pygit2_repo):
2738+ commit_str = Commit(SourceTree(Source())).write(pygit2_repo)
2739+ commit = pygit2_repo.get(commit_str)
2740+ assert gitubuntu.git_repository.follow_symlinks_to_blob(
2741+ repo=pygit2_repo,
2742+ treeish_object=commit,
2743+ path='debian/changelog',
2744+ )
2745+
2746+
2747+@pytest.mark.parametrize('cls', [
2748+ functools.partial(Blob, b'qux'),
2749+ functools.partial(ExecutableBlob, b'qux'),
2750+ functools.partial(Symlink, 'target'),
2751+ functools.partial(Tree, {}),
2752+ functools.partial(SourceTree, {}),
2753+])
2754+def test_replace_placeholders(cls):
2755+ common_blob = cls(name='name')
2756+ top = Tree({'foo': common_blob, 'bar': Placeholder('name')})
2757+ assert top.entries['foo'] is not top.entries['bar']
2758+ replace_placeholders(top)
2759+ assert top.entries['foo'] is top.entries['bar']
2760+ assert isinstance(top.entries['bar'], type(common_blob))
2761+
2762+
2763+# The following test uses replace directly, because
2764+# replace_placeholders' use of find_node will raise its own KeyError.
2765+@pytest.mark.parametrize('testobj', [
2766+ Tree({}),
2767+ Commit(Tree({})),
2768+ Repo(),
2769+])
2770+def test_replace_missing_placeholder(testobj):
2771+ commit = Commit(Tree({}))
2772+ with pytest.raises(KeyError):
2773+ testobj.replace(Placeholder('name'), commit)
2774+
2775+
2776+def test_repo_placeholder():
2777+ empty_tree = Tree({})
2778+ graph = Repo(
2779+ commits=[
2780+ Commit(empty_tree, message='top', name='top'),
2781+ Commit(
2782+ empty_tree,
2783+ message='child',
2784+ parents=[Placeholder('top')],
2785+ name='child',
2786+ ),
2787+ ],
2788+ branches={
2789+ 'branch1': Placeholder('top'),
2790+ 'branch2': Placeholder('child'),
2791+ },
2792+ tags={
2793+ 'tag1': Placeholder('child'),
2794+ 'tag2': Placeholder('top'),
2795+ },
2796+ )
2797+ replace_placeholders(graph)
2798+ assert graph.commit_list[1].parents[0] is graph.commit_list[0]
2799+ assert graph.branches['branch1'] is graph.commit_list[0]
2800+ assert graph.branches['branch2'] is graph.commit_list[1]
2801+ assert graph.tags['tag2'] is graph.commit_list[0]
2802+ assert graph.tags['tag1'] is graph.commit_list[1]
2803+
2804+
2805+def test_copy():
2806+ graph = Repo(
2807+ commits=[
2808+ Commit(Tree({}), message='top', name='top'),
2809+ Commit(
2810+ Tree({}),
2811+ message='child',
2812+ parents=[Placeholder('top')],
2813+ name='child',
2814+ ),
2815+ ],
2816+ branches={
2817+ 'branch1': Placeholder('top'),
2818+ 'branch2': Placeholder('child'),
2819+ },
2820+ tags={
2821+ 'tag1': Placeholder('child'),
2822+ },
2823+ )
2824+ copy_graph = graph.copy(
2825+ add_commits=[
2826+ Commit(Tree({}), message='new', name='new'),
2827+ ],
2828+ update_branches={
2829+ 'branch2': Placeholder('new'),
2830+ 'branch3': Placeholder('new'),
2831+ },
2832+ update_tags={
2833+ 'tag2': Placeholder('new'),
2834+ 'tag3': Placeholder('top'),
2835+ }
2836+ )
2837+ replace_placeholders(graph)
2838+ replace_placeholders(copy_graph)
2839+
2840+ assert graph.commit_list[1].parents[0] is graph.commit_list[0]
2841+ assert graph.branches['branch1'] is graph.commit_list[0]
2842+ assert graph.tags['tag1'] is graph.commit_list[1]
2843+
2844+ assert copy_graph.commit_list[2].message == 'new'
2845+ assert copy_graph.branches['branch1'] is copy_graph.commit_list[0]
2846+ assert copy_graph.branches['branch2'] is copy_graph.commit_list[2]
2847+ assert copy_graph.branches['branch3'] is copy_graph.commit_list[2]
2848+ assert copy_graph.tags['tag1'] is copy_graph.commit_list[1]
2849+ assert copy_graph.tags['tag2'] is copy_graph.commit_list[2]
2850+ assert copy_graph.tags['tag3'] is copy_graph.commit_list[0]
2851+
2852+
2853+def test_commit_from_spec_parents():
2854+ """Argument parents should end up in Commit object parents attribute"""
2855+ commit = Commit.from_spec(parents=sentinel.parents).parents
2856+ assert commit == sentinel.parents
2857+
2858+
2859+def test_commit_from_spec_message():
2860+ """Argument message should end up in Commit object message attribute"""
2861+ commit = Commit.from_spec(message=sentinel.message).message
2862+ assert commit == sentinel.message
2863+
2864+
2865+def test_commit_from_spec_name():
2866+ """Argument name should end up in Commit object name attribute"""
2867+ assert Commit.from_spec(name=sentinel.name).name == sentinel.name
2868+
2869+
2870+@pytest.mark.parametrize('patches_applied', [True, False])
2871+def test_commit_from_spec_patches_applied(patches_applied):
2872+ """Check behaviour of patches_applied argument"""
2873+ commit = Commit.from_spec(patches_applied=patches_applied)
2874+ # Underlying SourceTree object should now have a matching patches_applied
2875+ # attribute
2876+ assert commit.tree.patches_applied == patches_applied
2877+ # Underlying SourceSpec object should now have a matching has_patches
2878+ # attribute
2879+ assert commit.tree.source.spec.has_patches == patches_applied
2880+
2881+
2882+def test_commit_from_spec_kwargs():
2883+ """Arbitrary arguments should result in adjusted behaviour in SourceSpec"""
2884+ assert Commit.from_spec(native=True).tree.source.spec.native
2885diff --git a/gitubuntu/source_builder.py b/gitubuntu/source_builder.py
2886index 024b569..dac9670 100644
2887--- a/gitubuntu/source_builder.py
2888+++ b/gitubuntu/source_builder.py
2889@@ -62,11 +62,12 @@ NEW_FILE_PATCH_TEMPLATE = """
2890
2891 class SourceSpec:
2892 """A high level abstraction of the attributes of a test source package"""
2893- version = '1'
2894- native = True
2895+ version = '1-1'
2896+ native = False
2897 has_patches = False
2898 changelog_versions = None
2899 file_contents = None
2900+ mutate = False
2901 reserved_files = [
2902 'debian/changelog',
2903 'debian/control',
2904@@ -88,6 +89,8 @@ class SourceSpec:
2905 package should contain the specified file names with the
2906 specified contents. This is used to control the tree hashes
2907 of generated source packages.
2908+ :param mutate: if bool(mutate) is True, then this will add a file
2909+ called debian/mutate containing the data str(mutate).
2910
2911 Keyword arguments to the constructor map directly to class instances
2912 properties. Properties may be manipulated after construction.
2913@@ -112,8 +115,8 @@ class SourceSpec:
2914
2915 # If the version was not explicitly set, toggle the default for
2916 # non-native packages
2917- if 'version' not in kwargs and not self.native:
2918- self.version = '1-1'
2919+ if 'version' not in kwargs and self.native:
2920+ self.version = '1'
2921
2922 if self.native and '-' in self.version:
2923 raise ValueError("Version must not have a '-' in a native package")
2924@@ -198,7 +201,10 @@ class SourceFiles:
2925 :returns: a mapping of filename to content
2926 :rtype: dict(str, str)
2927 """
2928- return self.spec.file_contents
2929+ result = dict(self.spec.file_contents)
2930+ if self.spec.mutate:
2931+ result['debian/mutate'] = str(self.spec.mutate)
2932+ return result
2933
2934 @property
2935 def source_format(self):
2936diff --git a/gitubuntu/source_builder_test.py b/gitubuntu/source_builder_test.py
2937index e18e05f..9131a98 100644
2938--- a/gitubuntu/source_builder_test.py
2939+++ b/gitubuntu/source_builder_test.py
2940@@ -53,12 +53,12 @@ def test_source_is_created():
2941
2942
2943 def test_source_create_with_version(repo):
2944- version = get_spec_changelog_version(repo, version='3')
2945+ version = get_spec_changelog_version(repo, version='3', native=True)
2946 assert version == '3'
2947
2948
2949 def test_source_create_with_versions(repo):
2950- source_spec = target.SourceSpec(changelog_versions=['3', '4'])
2951+ source_spec = target.SourceSpec(changelog_versions=['3', '4'], native=True)
2952 with target.Source(source_spec) as f:
2953 tree_hash = importer.dsc_to_tree_hash(repo.raw_repo, f)
2954 changelog = repo.get_changelog_from_treeish(tree_hash)
2955@@ -110,6 +110,19 @@ def test_source_create_fails_with_absolute_path(repo):
2956 pass
2957
2958
2959+def test_source_mutate():
2960+ """mutate kwarg results in a debian/mutate file
2961+
2962+ If mutate is used, then a file called 'debian/mutate' should be generated
2963+ with the same contents.
2964+ """
2965+ source = target.Source(target.SourceSpec(mutate='foo'))
2966+
2967+ # It is sufficient to just verify spec_files contains what we want here as
2968+ # the spec_files functionality is checked in a separate test already.
2969+ assert source.files.spec_files['debian/mutate'] == 'foo'
2970+
2971+
2972 @pytest.mark.parametrize('native,expected', [
2973 (True, b"3.0 (native)\n"),
2974 (False, b"3.0 (quilt)\n"),
2975@@ -124,7 +137,7 @@ def test_source_native_source_format(repo, native, expected):
2976 assert blob.data == expected
2977
2978 def test_source_quilt_no_patches(repo):
2979- with target.Source(target.SourceSpec(native=False)) as dsc_path:
2980+ with target.Source(target.SourceSpec()) as dsc_path:
2981 top = dsc_path_to_tree(repo, dsc_path)
2982 debian_entry = top['debian']
2983 assert debian_entry.type == 'tree'
2984@@ -134,7 +147,7 @@ def test_source_quilt_no_patches(repo):
2985
2986
2987 def test_source_quilt_with_patches(repo):
2988- spec = target.SourceSpec(native=False, has_patches=True)
2989+ spec = target.SourceSpec(has_patches=True)
2990 with target.Source(spec) as dsc_path:
2991 top = dsc_path_to_tree(repo, dsc_path)
2992 expected_files = ['series', 'a', 'b']
2993@@ -155,7 +168,7 @@ def test_source_quilt_with_patches(repo):
2994
2995
2996 def test_source_quilt_with_patches_applied(repo):
2997- spec = target.SourceSpec(native=False, has_patches=True)
2998+ spec = target.SourceSpec(has_patches=True)
2999 with target.Source(spec) as dsc_path:
3000 top = dsc_path_to_tree(repo, dsc_path, patches_applied=True)
3001 expected_files = [
3002diff --git a/pytest.ini b/pytest.ini
3003new file mode 100644
3004index 0000000..d61d029
3005--- /dev/null
3006+++ b/pytest.ini
3007@@ -0,0 +1,2 @@
3008+[pytest]
3009+xfail_strict=true

Subscribers

People subscribed via source and target branches