Merge ~cjwatson/launchpad:code-import-worker-argparse into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: b6ffa72b9a56bc821302e0b36a3e79dc2c469d0f
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:code-import-worker-argparse
Merge into: launchpad:master
Diff against target: 340 lines (+202/-31)
3 files modified
lib/lp/codehosting/codeimport/tests/test_worker.py (+110/-5)
lib/lp/codehosting/codeimport/worker.py (+91/-26)
lib/lp/codehosting/codeimport/workermonitor.py (+1/-0)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+391808@code.launchpad.net

Commit message

Convert CodeImportSourceDetails.fromArguments to argparse

Description of the change

This requires some temporary compatibility code, since the scheduler still uses the old serialisation of arguments, but in the long run this will be much better than the previous fragile and hard-to-extend scheme.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/codehosting/codeimport/tests/test_worker.py b/lib/lp/codehosting/codeimport/tests/test_worker.py
2index 9daf357..1c046ed 100644
3--- a/lib/lp/codehosting/codeimport/tests/test_worker.py
4+++ b/lib/lp/codehosting/codeimport/tests/test_worker.py
5@@ -53,6 +53,7 @@ from CVS import (
6 )
7 from dulwich.repo import Repo as GitRepo
8 from fixtures import FakeLogger
9+from pymacaroons import Macaroon
10 import scandir
11 import six
12 import subvertpy
13@@ -892,7 +893,7 @@ class TestActualImportMixin:
14 config.root, 'scripts', 'code-import-worker.py')
15 output = tempfile.TemporaryFile()
16 retcode = subprocess.call(
17- [script_path, '--access-policy=anything'] + arguments,
18+ [script_path, '--access-policy=anything', '--'] + arguments,
19 stderr=output, stdout=output)
20 self.assertEqual(retcode, 0)
21
22@@ -930,11 +931,11 @@ class TestActualImportMixin:
23 config.root, 'scripts', 'code-import-worker.py')
24 output = tempfile.TemporaryFile()
25 retcode = subprocess.call(
26- [script_path, '--access-policy=anything'] + arguments,
27+ [script_path, '--access-policy=anything', '--'] + arguments,
28 stderr=output, stdout=output)
29 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS)
30 retcode = subprocess.call(
31- [script_path, '--access-policy=anything'] + arguments,
32+ [script_path, '--access-policy=anything', '--'] + arguments,
33 stderr=output, stdout=output)
34 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE)
35
36@@ -993,6 +994,25 @@ class TestCVSImport(WorkerTest, CSCVSActualImportMixin):
37 ]
38
39
40+class TestCVSImportArgparse(TestCVSImport):
41+ """Like `TestCVSImport`, but with argparse-style arguments."""
42+
43+ def makeWorkerArguments(self, module_name, files, stacked_on_url=None):
44+ """Make CVS worker arguments pointing at a real CVS repo."""
45+ cvs_server = CVSServer(self.makeTemporaryDirectory())
46+ cvs_server.start_server()
47+ self.addCleanup(cvs_server.stop_server)
48+
49+ cvs_server.makeModule('trunk', [('README', 'original\n')])
50+
51+ self.foreign_commit_count = 2
52+
53+ return [
54+ str(self.factory.getUniqueInteger()), 'cvs', 'bzr',
55+ cvs_server.getRoot(), '--cvs-module', 'trunk',
56+ ]
57+
58+
59 class SubversionImportHelpers:
60 """Implementations of `makeForeignCommit` and `makeSourceDetails` for svn.
61 """
62@@ -1222,6 +1242,28 @@ class TestGitImport(WorkerTest, TestActualImportMixin,
63 self.assertEqual(lastrev.message, "Message for other")
64
65
66+class TestGitImportArgparse(TestGitImport):
67+ """Like `TestGitImport`, but with argparse-style arguments."""
68+
69+ def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
70+ """Make Git worker arguments pointing at a real Git repo."""
71+ repository_store = self.makeTemporaryDirectory()
72+ git_server = GitServer(repository_store)
73+ git_server.start_server()
74+ self.addCleanup(git_server.stop_server)
75+
76+ git_server.makeRepo('source', files)
77+ self.foreign_commit_count = 1
78+
79+ arguments = [
80+ str(self.factory.getUniqueInteger()), 'git', 'bzr',
81+ git_server.get_url('source'),
82+ ]
83+ if stacked_on_url is not None:
84+ arguments.extend(['--stacked-on', stacked_on_url])
85+ return arguments
86+
87+
88 class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
89 TestActualImportMixin, PullingImportWorkerTests):
90
91@@ -1283,6 +1325,27 @@ class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
92 self.assertEqual(content, cache_file.read())
93
94
95+class TestBzrSvnImportArgparse(TestBzrSvnImport):
96+ """Like `TestBzrSvnImport`, but with argparse-style arguments."""
97+
98+ def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
99+ """Make SVN worker arguments pointing at a real SVN repo."""
100+ svn_server = SubversionServer(self.makeTemporaryDirectory())
101+ svn_server.start_server()
102+ self.addCleanup(svn_server.stop_server)
103+
104+ svn_branch_url = svn_server.makeBranch(branch_name, files)
105+ svn_branch_url = svn_branch_url.replace('://localhost/', ':///')
106+ self.foreign_commit_count = 2
107+ arguments = [
108+ str(self.factory.getUniqueInteger()), self.rcstype, 'bzr',
109+ svn_branch_url,
110+ ]
111+ if stacked_on_url is not None:
112+ arguments.extend(['--stacked-on', stacked_on_url])
113+ return arguments
114+
115+
116 class TestBzrImport(WorkerTest, TestActualImportMixin,
117 PullingImportWorkerTests):
118
119@@ -1363,11 +1426,41 @@ class TestBzrImport(WorkerTest, TestActualImportMixin,
120 branch.repository.get_revision(branch.last_revision()).committer)
121
122
123+class TestBzrImportArgparse(TestBzrImport):
124+ """Like `TestBzrImport`, but with argparse-style arguments."""
125+
126+ def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
127+ """Make Bzr worker arguments pointing at a real Bzr repo."""
128+ repository_path = self.makeTemporaryDirectory()
129+ bzr_server = BzrServer(repository_path)
130+ bzr_server.start_server()
131+ self.addCleanup(bzr_server.stop_server)
132+
133+ bzr_server.makeRepo(files)
134+ self.foreign_commit_count = 1
135+
136+ arguments = [
137+ str(self.factory.getUniqueInteger()), 'bzr', 'bzr',
138+ bzr_server.get_url(),
139+ ]
140+ if stacked_on_url is not None:
141+ arguments.extend(['--stacked-on', stacked_on_url])
142+ return arguments
143+
144+
145 class TestGitToGitImportWorker(TestCase):
146
147+ def makeWorkerArguments(self):
148+ """Make Git worker arguments, pointing at a fake URL for now."""
149+ return [
150+ 'git-unique-name', 'git:git',
151+ self.factory.getUniqueURL(scheme='git'),
152+ Macaroon().serialize(),
153+ ]
154+
155 def test_throttleProgress(self):
156- source_details = self.factory.makeCodeImportSourceDetails(
157- rcstype="git", target_rcstype="git")
158+ source_details = CodeImportSourceDetails.fromArguments(
159+ self.makeWorkerArguments())
160 logger = BufferLogger()
161 worker = GitToGitImportWorker(
162 source_details, logger, AcceptAnythingPolicy())
163@@ -1410,6 +1503,18 @@ class TestGitToGitImportWorker(TestCase):
164 ContainsAll([u"%d ...\r" % i for i in (9, 19, 29, 39, 49)]))
165
166
167+class TestGitToGitImportWorkerArgparse(TestCase):
168+ """Like `TestGitToGitImportWorker`, but with argparse-style arguments."""
169+
170+ def makeWorkerArguments(self):
171+ """Make Git worker arguments, pointing at a fake URL for now."""
172+ return [
173+ 'git-unique-name', 'git', 'git',
174+ self.factory.getUniqueURL(scheme='git'),
175+ '--macaroon', Macaroon().serialize(),
176+ ]
177+
178+
179 class CodeImportBranchOpenPolicyTests(TestCase):
180
181 def setUp(self):
182diff --git a/lib/lp/codehosting/codeimport/worker.py b/lib/lp/codehosting/codeimport/worker.py
183index 5aacf29..bb3b559 100644
184--- a/lib/lp/codehosting/codeimport/worker.py
185+++ b/lib/lp/codehosting/codeimport/worker.py
186@@ -19,6 +19,7 @@ __all__ = [
187 'get_default_bazaar_branch_store',
188 ]
189
190+from argparse import ArgumentParser
191 import io
192 import os
193 import shutil
194@@ -281,6 +282,17 @@ def get_default_bazaar_branch_store():
195 get_transport_from_url(config.codeimport.bazaar_branch_store))
196
197
198+class ArgumentParserError(Exception):
199+ """An argument parsing error."""
200+
201+
202+class TolerantArgumentParser(ArgumentParser):
203+ """An `ArgumentParser` that raises exceptions on errors."""
204+
205+ def error(self, message):
206+ raise ArgumentParserError(message)
207+
208+
209 class CodeImportSourceDetails:
210 """The information needed to process an import.
211
212@@ -322,37 +334,90 @@ class CodeImportSourceDetails:
213 def fromArguments(cls, arguments):
214 """Convert command line-style arguments to an instance."""
215 # Keep this in sync with CodeImportJob.makeWorkerArguments.
216+ parser = TolerantArgumentParser(description='Code import worker.')
217+ parser.add_argument(
218+ 'target_id', help='Target branch ID or repository unique name')
219+ parser.add_argument(
220+ 'rcstype', choices=('bzr', 'bzr-svn', 'cvs', 'git'),
221+ help='Source revision control system type')
222+ parser.add_argument(
223+ 'target_rcstype', choices=('bzr', 'git'),
224+ help='Target revision control system type')
225+ parser.add_argument(
226+ 'url', help='Source URL (or $CVSROOT for rcstype cvs)')
227+ parser.add_argument(
228+ '--cvs-module', help='CVS module (only valid for rcstype cvs)')
229+ parser.add_argument(
230+ '--stacked-on',
231+ help='Stacked-on URL (only valid for target_rcstype bzr)')
232+ parser.add_argument(
233+ '--macaroon', type=Macaroon.deserialize,
234+ help=(
235+ 'Macaroon authorising push (only valid for target_rcstype '
236+ 'git)'))
237 arguments = list(arguments)
238- target_id = arguments.pop(0)
239- rcstype = arguments.pop(0)
240- if ':' not in rcstype:
241- raise AssertionError(
242- "'%s' does not contain both source and target types." %
243- rcstype)
244- rcstype, target_rcstype = rcstype.split(':', 1)
245- if rcstype in ['bzr-svn', 'git', 'bzr']:
246- url = arguments.pop(0)
247+ try:
248+ args = parser.parse_args(arguments)
249+ target_id = args.target_id
250+ rcstype = args.rcstype
251+ target_rcstype = args.target_rcstype
252+ url = args.url if rcstype in ('bzr', 'bzr-svn', 'git') else None
253+ if rcstype == 'cvs':
254+ if args.cvs_module is None:
255+ parser.error('rcstype cvs requires --cvs-module')
256+ cvs_root = args.url
257+ cvs_module = args.cvs_module
258+ else:
259+ cvs_root = None
260+ cvs_module = None
261 if target_rcstype == 'bzr':
262 try:
263- stacked_on_url = arguments.pop(0)
264- except IndexError:
265+ target_id = int(target_id)
266+ except ValueError:
267+ parser.error(
268+ 'rcstype bzr requires target_id to be an integer')
269+ stacked_on_url = args.stacked_on
270+ macaroon = None
271+ elif target_rcstype == 'git':
272+ if args.macaroon is None:
273+ parser.error('target_rcstype git requires --macaroon')
274+ stacked_on_url = None
275+ macaroon = args.macaroon
276+ except ArgumentParserError:
277+ # XXX cjwatson 2020-10-05: Remove this old-style argument
278+ # handling once the scheduler always passes something argparse
279+ # can handle.
280+ target_id = arguments.pop(0)
281+ rcstype = arguments.pop(0)
282+ if ':' not in rcstype:
283+ raise AssertionError(
284+ "'%s' does not contain both source and target types." %
285+ rcstype)
286+ rcstype, target_rcstype = rcstype.split(':', 1)
287+ if rcstype in ['bzr-svn', 'git', 'bzr']:
288+ url = arguments.pop(0)
289+ if target_rcstype == 'bzr':
290+ try:
291+ stacked_on_url = arguments.pop(0)
292+ except IndexError:
293+ stacked_on_url = None
294+ else:
295 stacked_on_url = None
296- else:
297+ cvs_root = cvs_module = None
298+ elif rcstype == 'cvs':
299+ url = None
300 stacked_on_url = None
301- cvs_root = cvs_module = None
302- elif rcstype == 'cvs':
303- url = None
304- stacked_on_url = None
305- [cvs_root, cvs_module] = arguments
306- else:
307- raise AssertionError("Unknown rcstype %r." % rcstype)
308- if target_rcstype == 'bzr':
309- target_id = int(target_id)
310- macaroon = None
311- elif target_rcstype == 'git':
312- macaroon = Macaroon.deserialize(arguments.pop(0))
313- else:
314- raise AssertionError("Unknown target_rcstype %r." % target_rcstype)
315+ [cvs_root, cvs_module] = arguments
316+ else:
317+ raise AssertionError("Unknown rcstype %r." % rcstype)
318+ if target_rcstype == 'bzr':
319+ target_id = int(target_id)
320+ macaroon = None
321+ elif target_rcstype == 'git':
322+ macaroon = Macaroon.deserialize(arguments.pop(0))
323+ else:
324+ raise AssertionError(
325+ "Unknown target_rcstype %r." % target_rcstype)
326 return cls(
327 target_id, rcstype, target_rcstype, url, cvs_root, cvs_module,
328 stacked_on_url, macaroon)
329diff --git a/lib/lp/codehosting/codeimport/workermonitor.py b/lib/lp/codehosting/codeimport/workermonitor.py
330index 80cbe4b..8441978 100644
331--- a/lib/lp/codehosting/codeimport/workermonitor.py
332+++ b/lib/lp/codehosting/codeimport/workermonitor.py
333@@ -252,6 +252,7 @@ class CodeImportWorkerMonitor:
334 args = [interpreter, self.path_to_script]
335 if self._access_policy is not None:
336 args.append("--access-policy=%s" % self._access_policy)
337+ args.append('--')
338 command = args + worker_arguments
339 self._logger.info(
340 "Launching worker child process %s.", command)

Subscribers

People subscribed via source and target branches

to status/vote changes: