Merge ppa-dev-tools:set-command-basic-options into ppa-dev-tools:main
- Git
- lp:ppa-dev-tools
- set-command-basic-options
- Merge into main
Status: | Merged |
---|---|
Merge reported by: | Bryce Harrington |
Merged at revision: | 321446afc19cda9b835c1e2514605a97eb42e6f2 |
Proposed branch: | ppa-dev-tools:set-command-basic-options |
Merge into: | ppa-dev-tools:main |
Diff against target: |
462 lines (+222/-52) 6 files modified
ppa/constants.py (+2/-1) ppa/ppa_group.py (+9/-7) scripts/ppa (+120/-5) tests/helpers.py (+16/-0) tests/test_ppa_group.py (+0/-18) tests/test_scripts_ppa.py (+75/-21) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andreas Hasenack (community) | Approve | ||
Canonical Server Reporter | Pending | ||
Review via email: mp+434785@code.launchpad.net |
Commit message
Description of the change
True testing of all this stuff requires running against Launchpad, which
the unit tests don't do. Here's some example commandlines I used in my
own testing:
# Basic creation
$ ./scripts/ppa create test-set-command
# Architectures
$ ./scripts/ppa set test-set-command --architecture armhf,powerpc,s390x
$ ./scripts/ppa set test-set-command --default-
$ ./scripts/ppa set test-set-command --all-architectures
After running each command, load up the PPA webpage in the browser and
verify the settings took. I didn't run into any problems with any of
these, and would expect them to Just Work(tm) for everyone, so if you
spot any issues let me know.
Andreas Hasenack (ahasenack) : | # |
Andreas Hasenack (ahasenack) : | # |
Andreas Hasenack (ahasenack) : | # |
Andreas Hasenack (ahasenack) : | # |
Bryce Harrington (bryce) : | # |
Bryce Harrington (bryce) wrote : | # |
Bryce Harrington (bryce) wrote : | # |
Total 0 (delta 0), reused 0 (delta 0)
To git+ssh:
01d9b53..321446a main -> main
Preview Diff
1 | diff --git a/ppa/constants.py b/ppa/constants.py |
2 | index f6e7b01..4270392 100644 |
3 | --- a/ppa/constants.py |
4 | +++ b/ppa/constants.py |
5 | @@ -12,7 +12,8 @@ |
6 | """Global constants""" |
7 | |
8 | ARCHES_ALL = ["amd64", "arm64", "armhf", "armel", "i386", "powerpc", "ppc64el", "s390x", "riscv64"] |
9 | -ARCHES_PPA = ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"] |
10 | +ARCHES_PPA_DEFAULT = ["amd64", "i386"] |
11 | +ARCHES_PPA_ALL = ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"] |
12 | ARCHES_PPA_EXTRA = ["riscv64"] |
13 | ARCHES_AUTOPKGTEST = ["amd64", "arm64", "armhf", "i386", "ppc64el", "s390x"] |
14 | |
15 | diff --git a/ppa/ppa_group.py b/ppa/ppa_group.py |
16 | index 622cf76..7bccbb9 100755 |
17 | --- a/ppa/ppa_group.py |
18 | +++ b/ppa/ppa_group.py |
19 | @@ -88,7 +88,7 @@ class PpaGroup: |
20 | """ |
21 | return self.service.people[self.name] |
22 | |
23 | - def create(self, ppa_name='ppa', ppa_description=None): |
24 | + def create(self, ppa_name='ppa', ppa_description=None, **kwargs): |
25 | """Registers a new PPA with Launchpad. |
26 | |
27 | If a description is not provided a default one will be generated. |
28 | @@ -100,16 +100,18 @@ class PpaGroup: |
29 | |
30 | :raises PpaAlreadyExists: Raised if a PPA by this name already exists in Launchpad. |
31 | """ |
32 | - ppa_displayname = '' |
33 | - if ppa_description is None: |
34 | - ppa_description = ppa_name |
35 | - ppa_displayname = ppa_name |
36 | + ppa_settings = { |
37 | + 'description': ppa_description, |
38 | + 'displayname': ppa_name, |
39 | + } |
40 | + for k in kwargs.keys(): |
41 | + ppa_settings[k] = kwargs[k] |
42 | |
43 | try: |
44 | self.team.createPPA( |
45 | name=ppa_name, |
46 | - description=ppa_description, |
47 | - displayname=ppa_displayname) |
48 | + **ppa_settings |
49 | + ) |
50 | self.team.lp_save() |
51 | except BadRequest as e: |
52 | if "You already have a PPA" in o2str(e.content): |
53 | diff --git a/scripts/ppa b/scripts/ppa |
54 | index a3bad54..fbf65f2 100755 |
55 | --- a/scripts/ppa |
56 | +++ b/scripts/ppa |
57 | @@ -65,7 +65,8 @@ if '__file__' in globals(): |
58 | |
59 | from ppa._version import __version__ |
60 | from ppa.constants import ( |
61 | - ARCHES_PPA, |
62 | + ARCHES_PPA_ALL, |
63 | + ARCHES_PPA_DEFAULT, |
64 | ARCHES_AUTOPKGTEST, |
65 | URL_AUTOPKGTEST, |
66 | ) |
67 | @@ -137,6 +138,69 @@ def add_global_options(parser) -> None: |
68 | help="Minimize output during processing") |
69 | |
70 | |
71 | +def add_basic_config_options(parser) -> None: |
72 | + """Adds to a parser the command line options to configure the PPA. |
73 | + |
74 | + The config options are supported by the 'create' and 'set' command, |
75 | + to allow configuring the PPA at creation time, or after, respectively. |
76 | + |
77 | + These options represent what can be set as a user from the Launchpad |
78 | + web interface. |
79 | + """ |
80 | + # Architectures |
81 | + parser.add_argument( |
82 | + '-a', '--arches', '--arch', '--architectures', |
83 | + dest="architectures", |
84 | + action='store', |
85 | + default=None, |
86 | + help="Comma-separated list of hardware architectures to use" |
87 | + ) |
88 | + parser.add_argument( |
89 | + '--all-arches', '--all-architectures', |
90 | + dest="architectures", |
91 | + action='store_const', |
92 | + const=','.join(ARCHES_PPA_ALL), |
93 | + help="Enable all available architectures for the PPA" |
94 | + ) |
95 | + parser.add_argument( |
96 | + '--default-arches', '--default-architectures', |
97 | + dest="architectures", |
98 | + action='store_const', |
99 | + const=','.join(ARCHES_PPA_DEFAULT), |
100 | + help="Enable all available architectures for the PPA" |
101 | + ) |
102 | + |
103 | + # Displayname |
104 | + parser.add_argument( |
105 | + '--displayname', |
106 | + dest="displayname", |
107 | + action='store', |
108 | + help="A short title for the PPA's web page." |
109 | + ) |
110 | + |
111 | + # Description |
112 | + parser.add_argument( |
113 | + '--description', |
114 | + dest="description", |
115 | + action='store', |
116 | + help="A short description of the archive. URLs will be rendered as links. (See also 'ppa desc'.)" |
117 | + ) |
118 | + |
119 | + # Enable/Disable uploading |
120 | + parser.add_argument( |
121 | + '--enable', |
122 | + dest="set_enabled", |
123 | + action='store_true', |
124 | + help="Accept and build packages uploaded to the PPA." |
125 | + ) |
126 | + parser.add_argument( |
127 | + '--disable', |
128 | + dest="set_disabled", |
129 | + action='store_true', |
130 | + help="Do not accept or build packages uploaded to the PPA." |
131 | + ) |
132 | + |
133 | + |
134 | def create_arg_parser(): |
135 | """Sets up the command line parser object. |
136 | |
137 | @@ -164,9 +228,7 @@ def create_arg_parser(): |
138 | create_parser.add_argument('ppa_name', metavar='ppa-name', |
139 | action='store', |
140 | help="Name of the PPA to be created") |
141 | - create_parser.add_argument('-a', '--arches', '--arch', '--architectures', |
142 | - dest="architectures", action='store', |
143 | - help="Comma-separated list of hardware architectures to use") |
144 | + add_basic_config_options(create_parser) |
145 | |
146 | # Desc Command |
147 | desc_parser = subparser.add_parser( |
148 | @@ -207,6 +269,19 @@ def create_arg_parser(): |
149 | nargs='?', default='me', |
150 | help="Name of the PPA to list") |
151 | |
152 | + # Set Command |
153 | + set_parser = subparser.add_parser( |
154 | + 'set', |
155 | + argument_default=argparse.SUPPRESS, |
156 | + help='set help', |
157 | + prog=progname, |
158 | + ) |
159 | + add_global_options(set_parser) |
160 | + set_parser.add_argument('ppa_name', metavar='ppa-name', |
161 | + action='store', |
162 | + help="Name of the PPA to be set config values on") |
163 | + add_basic_config_options(set_parser) |
164 | + |
165 | # Show Command |
166 | show_parser = subparser.add_parser( |
167 | 'show', |
168 | @@ -351,6 +426,13 @@ def create_config(lp, args): |
169 | else: |
170 | warn(f"Invalid releases '{args.releases}'") |
171 | return None |
172 | + elif args.command == "set": |
173 | + if args.architectures is not None: |
174 | + if args.architectures: |
175 | + config['architectures'] = args.architectures.split(',') |
176 | + else: |
177 | + warn(f"Invalid architectures '{args.architectures}'") |
178 | + return None |
179 | |
180 | return config |
181 | |
182 | @@ -382,7 +464,7 @@ def command_create(lp, config): |
183 | warn("Could not determine team name") |
184 | return os.EX_USAGE |
185 | |
186 | - architectures = config.get('architectures', ARCHES_PPA) |
187 | + architectures = config.get('architectures', ARCHES_PPA_ALL) |
188 | |
189 | try: |
190 | if not config.get('dry_run', False): |
191 | @@ -519,6 +601,38 @@ def command_exists(lp, config): |
192 | return 1 |
193 | |
194 | |
195 | +def command_set(lp, config): |
196 | + """Sets one or more properties of PPA in Launchpad. |
197 | + |
198 | + :param Lp lp: The Launchpad wrapper object. |
199 | + :param dict config: Configuration param:value map. |
200 | + :rtype: int |
201 | + :returns: Status code OK (0) on success, non-zero on error. |
202 | + """ |
203 | + try: |
204 | + the_ppa = get_ppa(lp, config) |
205 | + |
206 | + if 'architectures' in config: |
207 | + the_ppa.set_architectures(config['architectures']) |
208 | + |
209 | + if 'description' in config: |
210 | + the_ppa.archive.description = config['description'] |
211 | + |
212 | + if 'displayname' in config: |
213 | + the_ppa.archive.displayname = config['displayname'] |
214 | + |
215 | + return the_ppa.archive.lp_save() |
216 | + except PpaDoesNotExist as e: |
217 | + print(e) |
218 | + except ValueError as e: |
219 | + print(f"Error: {e}") |
220 | + return os.EX_USAGE |
221 | + except KeyboardInterrupt: |
222 | + return 2 |
223 | + print("Unhandled error") |
224 | + return 1 |
225 | + |
226 | + |
227 | def command_show(lp, config): |
228 | """Displays details about the given PPA. |
229 | |
230 | @@ -752,6 +866,7 @@ COMMANDS = { |
231 | 'desc': (command_desc, None), |
232 | 'destroy': (command_destroy, None), |
233 | 'list': (command_list, None), |
234 | + 'set': (command_set, None), |
235 | 'show': (command_show, None), |
236 | 'status': (command_status, None), |
237 | 'tests': (command_tests, None), |
238 | diff --git a/tests/helpers.py b/tests/helpers.py |
239 | index 30a82bb..c6a55eb 100644 |
240 | --- a/tests/helpers.py |
241 | +++ b/tests/helpers.py |
242 | @@ -18,6 +18,16 @@ from ppa.ppa import Ppa |
243 | from ppa.ppa_group import PpaAlreadyExists |
244 | |
245 | |
246 | +class ArchiveMock: |
247 | + """A stand-in for a Launchpad Archive object.""" |
248 | + def __init__(self, name, description): |
249 | + self.displayname = name |
250 | + self.description = description |
251 | + |
252 | + def lp_save(self): |
253 | + return True |
254 | + |
255 | + |
256 | class PersonMock: |
257 | """A stand-in for a Launchpad Person object.""" |
258 | def __init__(self, name): |
259 | @@ -32,6 +42,12 @@ class PersonMock: |
260 | self._ppas.append(new_ppa) |
261 | return True |
262 | |
263 | + def getPPAByName(self, name): |
264 | + for ppa in self._ppas: |
265 | + if ppa.name == name: |
266 | + return ArchiveMock(name, ppa.description) |
267 | + return None |
268 | + |
269 | def lp_save(self): |
270 | return True |
271 | |
272 | diff --git a/tests/test_ppa_group.py b/tests/test_ppa_group.py |
273 | index 9bfd382..d9d83de 100644 |
274 | --- a/tests/test_ppa_group.py |
275 | +++ b/tests/test_ppa_group.py |
276 | @@ -69,24 +69,6 @@ def test_create_with_team(): |
277 | assert ppa.address == 'ppa:test_team_name/ppa_test_name' |
278 | |
279 | |
280 | -def test_create_for_lpbug(): |
281 | - """Check associating a bug # when creating a PPA.""" |
282 | - ppa_group = PpaGroup(service=LpServiceMock(), name='me') |
283 | - lpbug = '1234567' |
284 | - ppa = ppa_group.create('lp' + lpbug) |
285 | - assert ppa is not None |
286 | - assert lpbug in ppa.description |
287 | - |
288 | - |
289 | -def test_create_for_merge_proposal(): |
290 | - """Check associating a merge proposal when creating a PPA.""" |
291 | - ppa_group = PpaGroup(service=LpServiceMock(), name='me') |
292 | - version = '1.2.3-4' |
293 | - ppa = ppa_group.create('merge.' + version) |
294 | - assert ppa is not None |
295 | - assert version in ppa.description |
296 | - |
297 | - |
298 | def test_list_ppas(): |
299 | """Check listing the PPAs for a PPA group.""" |
300 | test_ppa_list = ['a', 'b', 'c', 'd'] |
301 | diff --git a/tests/test_scripts_ppa.py b/tests/test_scripts_ppa.py |
302 | index e393bd8..10acc30 100644 |
303 | --- a/tests/test_scripts_ppa.py |
304 | +++ b/tests/test_scripts_ppa.py |
305 | @@ -11,6 +11,7 @@ |
306 | """ppa command-line script tests""" |
307 | |
308 | import os |
309 | +import io |
310 | import sys |
311 | import types |
312 | |
313 | @@ -22,6 +23,12 @@ SCRIPT_NAME = "ppa" |
314 | BASE_PATH = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) |
315 | sys.path.insert(0, BASE_PATH) |
316 | |
317 | +from ppa.constants import ( |
318 | + ARCHES_PPA_ALL, |
319 | + ARCHES_PPA_DEFAULT |
320 | +) |
321 | +from tests.helpers import LpServiceMock |
322 | + |
323 | if '.pybuild' in BASE_PATH: |
324 | python_version = '.'.join([str(v) for v in sys.version_info[0:2]]) |
325 | scripts_path = os.path.join( |
326 | @@ -41,7 +48,7 @@ loader.exec_module(script) |
327 | def fake_config(): |
328 | return { |
329 | 'ppa_name': 'testing', |
330 | - 'team_name': 'tester', |
331 | + 'team_name': 'me', |
332 | 'wait_seconds': 0.1 |
333 | } |
334 | |
335 | @@ -145,6 +152,7 @@ def test_create_arg_parser(): |
336 | 'desc', |
337 | 'destroy', |
338 | 'list', |
339 | + 'set', |
340 | 'show', |
341 | 'status', |
342 | 'tests', |
343 | @@ -152,36 +160,80 @@ def test_create_arg_parser(): |
344 | ] |
345 | |
346 | |
347 | -def test_create_arg_parser_create(): |
348 | - """Checks argument parsing for the 'create' command.""" |
349 | +@pytest.mark.parametrize('command', ['create', 'set']) |
350 | +def test_create_arg_parser_basic_config(command): |
351 | + """Checks argument parsing for the basic PPA config options. |
352 | + |
353 | + This test covers the set of options used by 'create' and 'set' for |
354 | + configuring various properties of the PPA's behaviors, such as |
355 | + dependencies, publication policy, access control, etc. It does not |
356 | + cover settings that require Launchpad administrator involvement. |
357 | + |
358 | + The testing checks only that the options are being received and |
359 | + registered as expected, and does not cover the processing of the |
360 | + inputs nor the actual underlying functionality. |
361 | + """ |
362 | parser = script.create_arg_parser() |
363 | |
364 | - # Check ppa_name |
365 | - args = parser.parse_args(['create', 'test-ppa']) |
366 | + # Check command and ppa_name |
367 | + args = parser.parse_args([command, 'test-ppa']) |
368 | + assert args.command == command |
369 | assert args.ppa_name == 'test-ppa' |
370 | |
371 | # Check that command args can come before or after the ppa name |
372 | - args = parser.parse_args(['create', 'test-ppa', '-a', 'x']) |
373 | + args = parser.parse_args([command, 'test-ppa', '-a', 'x']) |
374 | assert args.architectures == 'x' |
375 | args.architectures = None |
376 | - args = parser.parse_args(['create', '-a', 'x', 'test-ppa']) |
377 | + args = parser.parse_args([command, '-a', 'x', 'test-ppa']) |
378 | assert args.architectures == 'x' |
379 | args.architectures = None |
380 | |
381 | + # Check --all-arches and --default-arches |
382 | + args = parser.parse_args([command, 'test-ppa', '--all-arches']) |
383 | + assert args.architectures == ','.join(ARCHES_PPA_ALL) |
384 | + args.architectures = None |
385 | + args = parser.parse_args([command, 'test-ppa', '--all-architectures']) |
386 | + assert args.architectures == ','.join(ARCHES_PPA_ALL) |
387 | + args.architectures = None |
388 | + args = parser.parse_args([command, 'test-ppa', '--default-arches']) |
389 | + assert args.architectures == ','.join(ARCHES_PPA_DEFAULT) |
390 | + args.architectures = None |
391 | + args = parser.parse_args([command, 'test-ppa', '--default-architectures']) |
392 | + assert args.architectures == ','.join(ARCHES_PPA_DEFAULT) |
393 | + args.architectures = None |
394 | + |
395 | # Check -a, --arch, --arches, --architectures |
396 | - args = parser.parse_args(['create', 'test-ppa', '-a', 'x']) |
397 | + args = parser.parse_args([command, 'test-ppa', '-a', 'x']) |
398 | assert args.architectures == 'x' |
399 | args.architectures = None |
400 | - args = parser.parse_args(['create', 'test-ppa', '--arch', 'x']) |
401 | + args = parser.parse_args([command, 'test-ppa', '--arch', 'x']) |
402 | assert args.architectures == 'x' |
403 | args.architectures = None |
404 | - args = parser.parse_args(['create', 'test-ppa', '--arches', 'x']) |
405 | + args = parser.parse_args([command, 'test-ppa', '--arches', 'x']) |
406 | assert args.architectures == 'x' |
407 | args.architectures = None |
408 | - args = parser.parse_args(['create', 'test-ppa', '--architectures', 'a,b,c']) |
409 | + args = parser.parse_args([command, 'test-ppa', '--architectures', 'a,b,c']) |
410 | assert args.architectures == 'a,b,c' |
411 | args.architectures = None |
412 | |
413 | + # Check --displayname |
414 | + args = parser.parse_args([command, 'test-ppa', '--displayname', 'x']) |
415 | + assert args.displayname == 'x' |
416 | + args.displayname = None |
417 | + |
418 | + # Check --description |
419 | + args = parser.parse_args([command, 'test-ppa', '--description', 'x']) |
420 | + assert args.description == 'x' |
421 | + args.description = None |
422 | + |
423 | + # Check --enable / -D|--disable |
424 | + args = parser.parse_args([command, 'test-ppa', '--enable']) |
425 | + assert args.set_enabled is True |
426 | + args.set_enabled = False |
427 | + args = parser.parse_args([command, 'test-ppa', '--disable']) |
428 | + assert args.set_disabled is True |
429 | + args.set_disabled = False |
430 | + |
431 | |
432 | def test_create_arg_parser_show(): |
433 | """Checks argument parsing for the 'show' command.""" |
434 | @@ -312,16 +364,18 @@ def test_create_config(): |
435 | pass |
436 | |
437 | |
438 | -@pytest.mark.xfail(reason="Unimplemented") |
439 | -def test_command_create(fake_config): |
440 | - assert script.command_create(fake_config) == 0 |
441 | - |
442 | - # TODO: Specify a ppa_name and verify it gets used properly |
443 | - # TODO: Specify a team name |
444 | - # TODO: Verify if no team name specified, the lp_user is used |
445 | - # instead |
446 | - # TODO: Verify the ppa_address is set as expected |
447 | - pass |
448 | +def test_command_create(fake_config, monkeypatch): |
449 | + lp = LpServiceMock() |
450 | + monkeypatch.setattr("sys.stdin", io.StringIO('test description')) |
451 | + assert script.command_create(lp, fake_config) == 0 |
452 | + |
453 | + the_ppa = lp.me.getPPAByName(fake_config['ppa_name']) |
454 | + |
455 | + # Check basic attributes (further coverage is in test_ppa_group.py) |
456 | + assert the_ppa.displayname == fake_config['ppa_name'] |
457 | + assert the_ppa.description == 'test description' |
458 | + |
459 | + # TODO: Check processors |
460 | |
461 | |
462 | @pytest.mark.xfail(reason="Unimplemented") |
Fixed help text for --disable