Merge ~racb/git-ubuntu:importer-cleanup-tmpdir-always into git-ubuntu:master

Proposed by Robie Basak
Status: Merged
Merged at revision: edb18ab27ff3790fb8ac03fd0e7730e5e32acbef
Proposed branch: ~racb/git-ubuntu:importer-cleanup-tmpdir-always
Merge into: git-ubuntu:master
Diff against target: 384 lines (+227/-63)
4 files modified
gitubuntu/git_repository.py (+21/-1)
gitubuntu/importer.py (+117/-62)
gitubuntu/test_git_repository.py (+27/-0)
gitubuntu/test_importer.py (+62/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Andreas Hasenack Approve
Nish Aravamudan Approve
Robie Basak Pending
Review via email: mp+345617@code.launchpad.net

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

Commit message

Make jenkins happy

Description of the change

This supersedes Nish's MP as agreed.

I've retained test_importer_main_cleanup_on_exception for now, but I think that the combination of test_importer_close_repository_on_exception and the GitUbuntuRepository.close() method tests makes it redundant, so I propose to drop it. It is passing so we can leave it if you wish though.

I have just realised that I've obliterated Nish's careful separation of getting a test failing with xfail first. I can restore that separation and rebase tomorrow if needed.

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:61bc3182f1846b165c4bdf6453c6557625613282
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/413/
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/413/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:2f2562347fb0dd678242843c0f90d31723a6a953
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/415/
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/415/rebuild

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

On Mon, Apr 23, 2018 at 10:41 AM, Robie Basak <email address hidden> wrote:
> Review: Needs Fixing
>

Code fixes addressed in latest commit.

>> +
>> def get_changelog_for_commit(
>> repo,
>> tree_hash,
>> diff --git a/gitubuntu/test_importer.py b/gitubuntu/test_importer.py
>> index b7f24a1..bf7d5ff 100644
>> --- a/gitubuntu/test_importer.py
>> +++ b/gitubuntu/test_importer.py
>> @@ -633,3 +635,39 @@ def test_get_changelog_parent_commit(
>> str(repo.raw_repo.lookup_reference(expected_ref).peel(pygit2.Commit).id)
>> if expected_ref else expected_ref
>> )
>> +
>> +@patch('gitubuntu.git_repository.GitUbuntuRepository.add_base_remotes')
>> +def test_importer_main_cleanup_on_exception(add_base_remotes_mock):
>
> I'm a little concerned that this test involves knowing so much about the internals
> that it'll be more of a pain to maintain than its usefulness in detecting a real
> problem in the future. I'm not sure I have a better answer. I'm not sure it's worth
> having a test at all in this case. I'd be interested in others' views on this.
> I'm on the fence.

Perhaps we have different definitions of 'so much' :) I simply need a
way to cause main() to exit early. I'm not sure how else to test error
paths, withoiut invoking the network and/or making Launchpad
publishing data or faking a filesystem.

I'm also not sure how to square 'not worth having a test' with ...
everything new should have a test.

>> + # leverage that the importer creates temporary directories
>> + local_tmpdir = tempfile.mkdtemp()
>> + assert not os.listdir(local_tmpdir)
>> +
>> + orig_mkdtemp = tempfile.mkdtemp
>> + def new_mkdtemp(suffix=None, prefix=None, dir=None):
>> + return orig_mkdtemp(suffix, prefix, dir=local_tmpdir)
>> +
>> + # patch here rather than as a decorator so we can use the original
>> + # function above
>> + with patch('tempfile.mkdtemp') as mkdtemp_mock:
>
> What if the function under test (basically the entire program) creates temporary
> files in some other way, or leaves junk somewhere else?

That would be a code change in the importer, no? And if that was to
happen, afaict, this test would presumably break.

I suppose we could change the git_repository code (refactor) to not
invoke mkdtemp but a helper function to create the repository and mock
that?

The real reason for this, and not just passing a directory in
directly, is that in the current code passing a directory argument
implies no_clean. So we wouldn't clenaup in that case.

We could alternatively abstract out the "should I clean" decision
(which would take the directory and no_clean cli values as arguments)
and then mock that only. Then we would be able to pass in a directory
argument and expect it to be removed in the error path regardless of
what else the importer writes.

Note, that your point above about 'junk' is outside the scope of what
I'm testing here. If we do create other junk, then that would (imo) be
a different test. What I'm testing here is that the repository created
by the importer is removed on failure/error.

>> + mkdtemp_mock.side_effect = new_mkdtemp
>> +
>> + add_base_remotes_mock.side_effe...

Read more...

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:a2c0452080602798d969e80a513620815fd897c6
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/419/
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/419/rebuild

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

On Mon, Apr 23, 2018 at 07:51:19PM -0000, Nish Aravamudan wrote:
> I'm also not sure how to square 'not worth having a test' with ...
> everything new should have a test.

I read it as "everything new should have a test where it makes sense in
the code to do so". Pragmatically, there's no point in writing a test if
we don't see a way to write it such that the potential benefit outweighs
the cost. In this case I think this is quite close to the line. But
given that you've written it and it works, I'm thinking that there's no
harm done in landing it. Perhaps if it causes a future problem, I'll
propose to remove it then rather than fixing it.

> > What if the function under test (basically the entire program) creates temporary
> > files in some other way, or leaves junk somewhere else?
>
> That would be a code change in the importer, no? And if that was to
> happen, afaict, this test would presumably break.

I think in this case, depending on the specifics, the test is likely to
continue passing while not testing what it is supposed to be testing.
This is why I think it is of limited benefit for us to have it.

> I suppose we could change the git_repository code (refactor) to not
> invoke mkdtemp but a helper function to create the repository and mock
> that?

We could, but then we'd need to remember in the future that this is
required to make the test work. Refactoring it later without that in
mind will result to the same I-broke-it-but-the-test-still-passes
result.

> The real reason for this, and not just passing a directory in
> directly, is that in the current code passing a directory argument
> implies no_clean. So we wouldn't clenaup in that case.
>
> We could alternatively abstract out the "should I clean" decision
> (which would take the directory and no_clean cli values as arguments)
> and then mock that only. Then we would be able to pass in a directory
> argument and expect it to be removed in the error path regardless of
> what else the importer writes.

What if we give GitUbuntuRepository a close() method, move to it a
no_clean (or more clearly delete_on_close) attribute, and give the class
the responsibility of cleaning up?

We could then separately unit test that the class does clean up and does
not clean up as required based on the attribute, and that the main
function does correctly call the close() method on some specific causes
of failure.

> Note, that your point above about 'junk' is outside the scope of what
> I'm testing here. If we do create other junk, then that would (imo) be
> a different test. What I'm testing here is that the repository created
> by the importer is removed on failure/error.

Fair enough. I hadn't got that from
test_importer_main_cleanup_on_exception. We should make it clear then
that this is exactly the scope of the test (by name or docstring).

> > What if the function fails before it does anything due to the way it is
> > called from this test? Might be better to look for a more specifically
> > defined exception (which we raise from the mock) rather than doing
> > a catch-all on it.
>
> Yeah I can make this a RuntimeError.

I suggest something even more specific like:

    class MockError(Runt...

Read more...

Revision history for this message
Nish Aravamudan (nacc) wrote : Posted in a previous version of this proposal
Download full text (4.1 KiB)

On Tue, Apr 24, 2018 at 3:27 AM, Robie Basak <email address hidden>
wrote:

> On Mon, Apr 23, 2018 at 07:51:19PM -0000, Nish Aravamudan wrote:
> > I'm also not sure how to square 'not worth having a test' with ...
> > everything new should have a test.
>
> I read it as "everything new should have a test where it makes sense in
> the code to do so". Pragmatically, there's no point in writing a test if
> we don't see a way to write it such that the potential benefit outweighs
> the cost. In this case I think this is quite close to the line. But
> given that you've written it and it works, I'm thinking that there's no
> harm done in landing it. Perhaps if it causes a future problem, I'll
> propose to remove it then rather than fixing it.
>

Yes that makes sense. On some level, I wanted a programmatic way to check
that my changes did what I wanted and a unit test for it made sense, even
if it's fugly :) I am also happy to not land the test if it's too fragile
in your opinion, as I think it demonstrates the fix for the purpose of
review still.

> > > What if the function under test (basically the entire program) creates
> temporary
> > > files in some other way, or leaves junk somewhere else?
> >
> > That would be a code change in the importer, no? And if that was to
> > happen, afaict, this test would presumably break.
>
> I think in this case, depending on the specifics, the test is likely to
> continue passing while not testing what it is supposed to be testing.
> This is why I think it is of limited benefit for us to have it.
>

Ok -- I'm having trouble following the potential hypothetical, but I
believe you and understand your perspective.

>
> > I suppose we could change the git_repository code (refactor) to not
> > invoke mkdtemp but a helper function to create the repository and mock
> > that?
>
> We could, but then we'd need to remember in the future that this is
> required to make the test work. Refactoring it later without that in
> mind will result to the same I-broke-it-but-the-test-still-passes
> result.
>

Right, I meant before landing it now, but I see your point.

> > The real reason for this, and not just passing a directory in
> > directly, is that in the current code passing a directory argument
> > implies no_clean. So we wouldn't clenaup in that case.
> >
> > We could alternatively abstract out the "should I clean" decision
> > (which would take the directory and no_clean cli values as arguments)
> > and then mock that only. Then we would be able to pass in a directory
> > argument and expect it to be removed in the error path regardless of
> > what else the importer writes.
>
> What if we give GitUbuntuRepository a close() method, move to it a
> no_clean (or more clearly delete_on_close) attribute, and give the class
> the responsibility of cleaning up?
>
> We could then separately unit test that the class does clean up and does
> not clean up as required based on the attribute, and that the main
> function does correctly call the close() method on some specific causes
> of failure.
>

This seems reasonable and I can work on that today, I think.

> > Note, that your point above about 'junk' is outside the scope of w...

Read more...

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

Quickly, to correct an error:

On Wed, Apr 25, 2018 at 06:54:23PM -0000, Nish Aravamudan wrote:
> > > > What if the function fails before it does anything due to the way it is
> > > > called from this test? Might be better to look for a more specifically
> > > > defined exception (which we raise from the mock) rather than doing
> > > > a catch-all on it.
> > >
> > > Yeah I can make this a RuntimeError.
> >
> > I suggest something even more specific like:
> >
> > class MockError(RuntimeError): pass
> >
> > ...because otherwise a real RuntimeError would elide the test in the
> > same way as my objection to using Exception.
> >
>
> Of course!

I realised later that deriving from RuntimeError would also elide a real
RuntimeError being caught. I concluded that MockError should derive from
either BaseException or Exception (need to look at the definitions).

Revision history for this message
Thomas Ward (teward) wrote : Posted in a previous version of this proposal

I can confirm based on a cursory overview of the code that MockError having a superclass of RuntimeError will result in any `except RuntimeError` captures picking up MockError in that class.

And per Robie's comment, MockError should probably inherit from Exception or BaseException if you want it to be handled as its own class of exceptions outside that of RuntimeError.

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:d68d0377995365debd619b1ec6e92b2f1de18349
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/420/
Executed test runs:
    SUCCESS: VM Setup
    FAILED: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/420/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:e465ec974b624a8362a1a81cc70072f59d72c5e7
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/422/
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/422/rebuild

review: Approve (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:ea7747e172173001f658b637c657f3d00e8fd56e
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/423/
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/423/rebuild

review: Approve (continuous-integration)
Revision history for this message
Robie Basak (racb) wrote : Posted in a previous version of this proposal

Sorry I've been so long in responding to this. I was tasked on other projects. I've finally been able to context switch back to git-ubuntu and hope to be on it for the next week or so.

I've made a bunch of comments inline. I'm sorry I didn't notice the kwargs thing before; I realise that was present in my original review.

In any case, given that it's been so long, I don't think it's fair to ask you to take all of this on again. So I've prepared a branch that addresses my comments, which I can post as a separate MP and supersede this MP if you're happy with that as an approach to proceeding?

In that branch I've also written a proposed replacement for your test_importer_main_cleanup_on_exception called test_importer_close_repository_on_exception. I've left test_importer_main_cleanup_on_exception there for now, but I propose (weakly) that it can be dropped because test_importer_close_repository_on_exception is simpler and combined with your unit tests on GitUbuntuRepository.close() establish the same regression check that test_importer_main_cleanup_on_exception does.

I've pushed my branch to https://code.launchpad.net/~racb/usd-importer/+git/usd-importer/+ref/importer-cleanup-tmpdir-always - feel free to resubmit this MP using that branch if you'd like, or I can do it, or we can find some other way forward.

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

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

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

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

> I have just realised that I've obliterated Nish's careful separation of getting a test failing with xfail first. I can restore that separation and rebase tomorrow if needed.

I spent some time this morning looking at doing this, but came to the conclusion that it isn't worth the effort on this occasion. The tests as finally written won't work in that form prior to the refactor. So I'd have to write some other kind of tests (perhaps Nish's original ones) and then change the tests to the new form later.

Having said that, if you'd like it done in that form or another, I can still do it - just let me know.

Revision history for this message
Nish Aravamudan (nacc) wrote :

I think I'm fine with you landing this as-is (ish). I think you need a rebase once you land the build fix into master, at a minimum, and perhaps you need to squash that last commit in somewhere. But the overall changes look fine to me.

review: Approve
Revision history for this message
Andreas Hasenack (ahasenack) :
review: Needs Information
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Robie Basak (racb) :
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Thanks for the inline explanation, +1.

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

Thanks! I've rebased on master to pick up a CI fix, and squashed e975828 into d9764fe while I was there. No other changes.

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

PASSED: Continuous integration, rev:edb18ab27ff3790fb8ac03fd0e7730e5e32acbef
https://jenkins.ubuntu.com/server/job/git-ubuntu-ci/5/
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/5/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/gitubuntu/git_repository.py b/gitubuntu/git_repository.py
2index 509b8d9..9d2ca66 100644
3--- a/gitubuntu/git_repository.py
4+++ b/gitubuntu/git_repository.py
5@@ -874,7 +874,13 @@ class GitUbuntuRepository:
6
7 """
8
9- def __init__(self, local_dir, lp_user=None, fetch_proto=None):
10+ def __init__(
11+ self,
12+ local_dir,
13+ lp_user=None,
14+ fetch_proto=None,
15+ delete_on_close=True,
16+ ):
17 """
18 If fetch_proto is None, the default value from
19 gitubuntu.__main__ will be used (top_level_defaults.proto).
20@@ -935,6 +941,20 @@ class GitUbuntuRepository:
21 fetch_proto = top_level_defaults.proto
22
23 self._fetch_proto = fetch_proto
24+ self._delete_on_close = delete_on_close
25+
26+ def close(self):
27+ """Free resources associated with this instance
28+
29+ If delete_on_close was True on instance construction, local_dir (as
30+ specified on instance construction) will be deleted.
31+
32+ After this method is called, the instance is invalid and can no longer
33+ be used.
34+ """
35+ if self.raw_repo and self._delete_on_close:
36+ shutil.rmtree(self.local_dir)
37+ self.raw_repo = None
38
39 def create_orphan_branch(self, branch_name, msg):
40 if self.get_head_by_name(branch_name) is None:
41diff --git a/gitubuntu/importer.py b/gitubuntu/importer.py
42index 8bb1ca2..d6b9db8 100644
43--- a/gitubuntu/importer.py
44+++ b/gitubuntu/importer.py
45@@ -25,7 +25,6 @@
46 # + the last imported version from debian/changelog
47
48 import argparse
49-import atexit
50 import functools
51 import getpass
52 import logging
53@@ -130,30 +129,19 @@ def dsc_to_tree_hash(pygit2_repo, dsc_path, patches_applied=False):
54 )
55
56
57-def cleanup(no_clean, local_dir):
58- '''Remove a local directory, conditionally
59-
60- no_clean: if True, do not remove @local_dir and print a message instead
61- local_dir: directory to remove
62- '''
63- if not no_clean:
64- shutil.rmtree(local_dir)
65- else:
66- logging.info('Leaving %s as directed' % local_dir)
67-
68-
69 # XXX: need a namedtuple to hold common arguments
70-def main(
71+def _main_with_repo(
72+ repo,
73 pkgname,
74 owner,
75- no_clean,
76- no_fetch,
77- no_push,
78- active_series_only,
79- skip_orig,
80- skip_applied,
81- reimport,
82- allow_applied_failures,
83+ no_clean=False,
84+ no_fetch=False,
85+ no_push=False,
86+ active_series_only=False,
87+ skip_orig=False,
88+ skip_applied=False,
89+ reimport=False,
90+ allow_applied_failures=False,
91 directory=None,
92 dl_cache=None,
93 user=None,
94@@ -163,41 +151,14 @@ def main(
95 retries=top_level_defaults.retries,
96 retry_backoffs=top_level_defaults.retry_backoffs,
97 ):
98- """Main entry point to the importer
99+ """Internal main function of the importer
100
101 Arguments:
102- @pkgname: string name of source package
103- @owner: string owner of repository
104- @no_clean: if True, do not delete the local directory
105- @no_fetch: if True, do not fetch the target repository before
106- importing
107- @no_push: if True, do not push to the target repository
108- @active_series_only: if True, only import active series
109- @skip_orig: if True, do not attempt to use gbp to import orig
110- tarballs
111- @skip_applied: if True, do not attempt to import patches-applied
112- publishes
113- @reimport: if True, import the source package from scratch and
114- delete/recreate the target repository.
115- @allow_import_failures: if True, and patches fail to apply for any
116- publish, that patches-applied import will be skipped rather than an
117- error
118- @directory: string path for local repository
119- @user: string user to authenticate to Launchpad as
120- @proto: string protocol to use (one of 'http', 'https', 'git')
121- @dl_cache: string path where downloaded files should be cached
122- @pullfile: string path to file specifying pull overrides
123- @parentfile: string path to file specifying parent overrides
124- @retries: integer number of download retries to attempt
125- @retry_backoffs: list of backoff durations to use between retries
126-
127- If directory is None, a temporary directory is created and used.
128+ @repo: GitUbuntuRepository to import to. Unlike the main function, this
129+ object will not be closed when this function returns.
130
131- If user is None, value of `git config gitubuntu.lpuser` will be
132- used.
133-
134- If dl_cache is None, CACHE_PATH in the local repository will be
135- used.
136+ For the meaning of all other arguments, see the docstring of the main
137+ function.
138
139 Returns 0 on successful import (which includes non-fatal failures);
140 1 otherwise.
141@@ -207,14 +168,6 @@ def main(
142 else:
143 namespace = owner
144
145- logging.info('Ubuntu Server Team importer v%s' % VERSION)
146-
147- repo = GitUbuntuRepository(directory, user, proto)
148- if not directory:
149- logging.info('Using git repository at %s', repo.local_dir)
150-
151- atexit.register(cleanup, no_clean, repo.local_dir)
152-
153 repo.add_base_remotes(pkgname, repo_owner=owner)
154 if not no_fetch and not reimport:
155 try:
156@@ -431,6 +384,108 @@ def main(
157 return 0
158
159
160+# XXX: need a namedtuple to hold common arguments
161+def main(
162+ pkgname,
163+ owner,
164+ no_clean=False,
165+ no_fetch=False,
166+ no_push=False,
167+ active_series_only=False,
168+ skip_orig=False,
169+ skip_applied=False,
170+ reimport=False,
171+ allow_applied_failures=False,
172+ directory=None,
173+ dl_cache=None,
174+ user=None,
175+ proto=top_level_defaults.proto,
176+ pullfile=top_level_defaults.pullfile,
177+ parentfile=top_level_defaults.parentfile,
178+ retries=top_level_defaults.retries,
179+ retry_backoffs=top_level_defaults.retry_backoffs,
180+ repo=None,
181+):
182+ """Main entry point to the importer
183+
184+ Arguments:
185+ @pkgname: string name of source package
186+ @owner: string owner of repository
187+ @no_clean: if True, do not delete the local directory
188+ @no_fetch: if True, do not fetch the target repository before
189+ importing
190+ @no_push: if True, do not push to the target repository
191+ @active_series_only: if True, only import active series
192+ @skip_orig: if True, do not attempt to use gbp to import orig
193+ tarballs
194+ @skip_applied: if True, do not attempt to import patches-applied
195+ publishes
196+ @reimport: if True, import the source package from scratch and
197+ delete/recreate the target repository.
198+ @allow_applied_failures: if True, and patches fail to apply for any
199+ publish, that patches-applied import will be skipped rather than an
200+ error
201+ @directory: string path for local repository
202+ @user: string user to authenticate to Launchpad as
203+ @proto: string protocol to use (one of 'http', 'https', 'git')
204+ @dl_cache: string path where downloaded files should be cached
205+ @pullfile: string path to file specifying pull overrides
206+ @parentfile: string path to file specifying parent overrides
207+ @retries: integer number of download retries to attempt
208+ @retry_backoffs: list of backoff durations to use between retries
209+ @repo: if specified, a GitUbuntuRepository instance. If not specified, a
210+ GitUbuntuRepository instance will be created using the provided parameters.
211+ Regardless of whether it was specified or not, the instance will be closed
212+ before this function returns.
213+
214+ If directory is None, a temporary directory is created and used.
215+
216+ If user is None, value of `git config gitubuntu.lpuser` will be
217+ used.
218+
219+ If dl_cache is None, CACHE_PATH in the local repository will be
220+ used.
221+
222+ Returns 0 on successful import (which includes non-fatal failures);
223+ 1 otherwise.
224+ """
225+ logging.info('Ubuntu Server Team importer v%s' % VERSION)
226+
227+ if not repo:
228+ repo = GitUbuntuRepository(
229+ directory,
230+ user,
231+ proto,
232+ delete_on_close=not no_clean,
233+ )
234+ if not directory:
235+ logging.info('Using git repository at %s', repo.local_dir)
236+
237+ try:
238+ _main_with_repo(
239+ repo=repo,
240+ pkgname=pkgname,
241+ owner=owner,
242+ no_clean=no_clean,
243+ no_fetch=no_fetch,
244+ no_push=no_push,
245+ active_series_only=active_series_only,
246+ skip_orig=skip_orig,
247+ skip_applied=skip_applied,
248+ reimport=reimport,
249+ allow_applied_failures=allow_applied_failures,
250+ directory=directory,
251+ user=user,
252+ dl_cache=dl_cache,
253+ proto=proto,
254+ pullfile=pullfile,
255+ parentfile=parentfile,
256+ retries=retries,
257+ retry_backoffs=retry_backoffs,
258+ )
259+ finally:
260+ repo.close()
261+
262 def get_changelog_for_commit(
263 repo,
264 tree_hash,
265diff --git a/gitubuntu/test_git_repository.py b/gitubuntu/test_git_repository.py
266index dc30994..2bbb25d 100644
267--- a/gitubuntu/test_git_repository.py
268+++ b/gitubuntu/test_git_repository.py
269@@ -1,6 +1,7 @@
270 import copy
271 import itertools
272 import os
273+import shutil
274 import tempfile
275 import unittest
276 import unittest.mock
277@@ -680,3 +681,29 @@ def test_repo_find_ubuntu_merge(
278 ) == str(
279 repo.get_commitish(expected).peel(pygit2.Commit).id
280 )
281+
282+
283+def test_repo_does_cleanup():
284+ path = tempfile.mkdtemp()
285+ try:
286+ repo = target.GitUbuntuRepository(
287+ path,
288+ delete_on_close=True,
289+ )
290+ repo.close()
291+ assert not os.path.exists(path)
292+ finally:
293+ shutil.rmtree(path, ignore_errors=True)
294+
295+
296+def test_repo_does_not_cleanup():
297+ path = tempfile.mkdtemp()
298+ try:
299+ repo = target.GitUbuntuRepository(
300+ path,
301+ delete_on_close=False,
302+ )
303+ repo.close()
304+ assert os.path.exists(path)
305+ finally:
306+ shutil.rmtree(path, ignore_errors=True)
307diff --git a/gitubuntu/test_importer.py b/gitubuntu/test_importer.py
308index b7f24a1..df011e3 100644
309--- a/gitubuntu/test_importer.py
310+++ b/gitubuntu/test_importer.py
311@@ -4,7 +4,10 @@ from unittest.mock import (
312 sentinel,
313 )
314
315+import os
316 import pytest
317+import shutil
318+import tempfile
319
320 import gitubuntu.repo_builder as repo_builder
321 import gitubuntu.repo_comparator as repo_comparator
322@@ -633,3 +636,62 @@ def test_get_changelog_parent_commit(
323 str(repo.raw_repo.lookup_reference(expected_ref).peel(pygit2.Commit).id)
324 if expected_ref else expected_ref
325 )
326+
327+@patch('gitubuntu.git_repository.GitUbuntuRepository.add_base_remotes')
328+def test_importer_main_cleanup_on_exception(add_base_remotes_mock):
329+ """Ensure that importer.main cleans up the temporary directory on errors
330+
331+ If there are other files used/created by the importer outside of the
332+ repository, they are not tested for removal by this test.
333+ """
334+ # leverage that the importer creates temporary directories
335+ local_tmpdir = tempfile.mkdtemp()
336+ try:
337+ assert not os.listdir(local_tmpdir)
338+
339+ orig_mkdtemp = tempfile.mkdtemp
340+ def new_mkdtemp(suffix=None, prefix=None, dir=None):
341+ return orig_mkdtemp(suffix, prefix, dir=local_tmpdir)
342+
343+ # patch here rather than as a decorator so we can use the original
344+ # function above
345+ with patch('tempfile.mkdtemp') as mkdtemp_mock:
346+ class MockError(Exception): pass
347+
348+ mkdtemp_mock.side_effect = new_mkdtemp
349+
350+ add_base_remotes_mock.side_effect = MockError()
351+ with pytest.raises(MockError):
352+ target.main(pkgname='dummy', owner='dummy')
353+
354+ assert not os.listdir(local_tmpdir)
355+ finally:
356+ shutil.rmtree(local_tmpdir)
357+
358+
359+@patch('gitubuntu.importer._main_with_repo')
360+def test_importer_close_repository_on_exception(main_with_repo_mock):
361+ """Ensure that importer.main closes the repository in error cases
362+
363+ Together with unit tests on the behaviour of GitUbuntuRepository.close(),
364+ this ensures that the repository directory is correctly cleaned up when
365+ required.
366+
367+ This is effectively a white box test as we rely on the following
368+ implementation details of importer.main:
369+
370+ * That it calls _main_with_repo to do the actual work such that we can
371+ mock failure of its entire call with an exception.
372+
373+ * That it will still function enough to correctly close the repository
374+ on _main_with_repo failure and when given no other arguments.
375+
376+ If these implementation assumptions stop being true, this test will stop
377+ passing and will need reworking.
378+ """
379+ class MockError(Exception): pass
380+ main_with_repo_mock.side_effect = MockError()
381+ repo = Mock()
382+ with pytest.raises(MockError):
383+ target.main(pkgname='dummy', owner='dummy', repo=repo)
384+ repo.close.assert_called()

Subscribers

People subscribed via source and target branches