Merge ~racb/git-ubuntu:importer-cleanup-tmpdir-always into git-ubuntu:master
- Git
- lp:~racb/git-ubuntu
- importer-cleanup-tmpdir-always
- Merge into master
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) |
Related bugs: |
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_
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.
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:2f2562347fb
https:/
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:/
Robie Basak (racb) : Posted in a previous version of this proposal | # |
Nish Aravamudan (nacc) wrote : Posted in a previous version of this proposal | # |
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_
>> repo,
>> tree_hash,
>> diff --git a/gitubuntu/
>> index b7f24a1..bf7d5ff 100644
>> --- a/gitubuntu/
>> +++ b/gitubuntu/
>> @@ -633,3 +635,39 @@ def test_get_
>> str(repo.
>> if expected_ref else expected_ref
>> )
>> +
>> +@patch(
>> +def test_importer_
>
> 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(
>> +
>> + orig_mkdtemp = tempfile.mkdtemp
>> + def new_mkdtemp(
>> + return orig_mkdtemp(
>> +
>> + # patch here rather than as a decorator so we can use the original
>> + # function above
>> + with patch('
>
> 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_
>> +
>> + add_base_
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:a2c04520806
https:/
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:/
Robie Basak (racb) wrote : Posted in a previous version of this proposal | # |
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-
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_
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...
Nish Aravamudan (nacc) wrote : Posted in a previous version of this proposal | # |
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-
> 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...
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(
> >
> > ...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).
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.
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:d68d0377995
https:/
Executed test runs:
SUCCESS: VM Setup
FAILED: Build
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:e465ec974b6
https:/
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:/
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:ea7747e1721
https:/
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:/
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_
I've pushed my branch to https:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:b2043b09d61
https:/
Executed test runs:
SUCCESS: VM Setup
FAILED: Build
Click here to trigger a rebuild:
https:/
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.
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.
Andreas Hasenack (ahasenack) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:e9758286a26
https:/
Executed test runs:
SUCCESS: VM Setup
FAILED: Build
Click here to trigger a rebuild:
https:/
Robie Basak (racb) : | # |
Andreas Hasenack (ahasenack) wrote : | # |
Thanks for the inline explanation, +1.
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.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:edb18ab27ff
https:/
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:/
Preview Diff
1 | diff --git a/gitubuntu/git_repository.py b/gitubuntu/git_repository.py |
2 | index 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: |
41 | diff --git a/gitubuntu/importer.py b/gitubuntu/importer.py |
42 | index 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, |
265 | diff --git a/gitubuntu/test_git_repository.py b/gitubuntu/test_git_repository.py |
266 | index 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) |
307 | diff --git a/gitubuntu/test_importer.py b/gitubuntu/test_importer.py |
308 | index 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() |
FAILED: Continuous integration, rev:61bc3182f18 46b165c4bdf6453 c6557625613282 /jenkins. ubuntu. com/server/ job/git- ubuntu- ci/413/
https:/
Executed test runs:
SUCCESS: VM Setup
SUCCESS: Build
FAILED: Unit Tests
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/git- ubuntu- ci/413/ rebuild
https:/