Merge ppa-dev-tools:set-command-basic-options into ppa-dev-tools:main

Proposed by Bryce Harrington
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)
Reviewer Review Type Date Requested Status
Andreas Hasenack (community) Approve
Canonical Server Reporter Pending
Review via email: mp+434785@code.launchpad.net

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-architectures
    $ ./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.

To post a comment you must log in.
Revision history for this message
Andreas Hasenack (ahasenack) :
Revision history for this message
Andreas Hasenack (ahasenack) :
Revision history for this message
Andreas Hasenack (ahasenack) :
Revision history for this message
Andreas Hasenack (ahasenack) :
review: Needs Information
Revision history for this message
Bryce Harrington (bryce) :
Revision history for this message
Bryce Harrington (bryce) wrote :

Fixed help text for --disable

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

+1

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

Total 0 (delta 0), reused 0 (delta 0)
To git+ssh://git.launchpad.net/ppa-dev-tools
   01d9b53..321446a main -> main

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/ppa/constants.py b/ppa/constants.py
2index 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
15diff --git a/ppa/ppa_group.py b/ppa/ppa_group.py
16index 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):
53diff --git a/scripts/ppa b/scripts/ppa
54index 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),
238diff --git a/tests/helpers.py b/tests/helpers.py
239index 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
272diff --git a/tests/test_ppa_group.py b/tests/test_ppa_group.py
273index 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']
301diff --git a/tests/test_scripts_ppa.py b/tests/test_scripts_ppa.py
302index 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")

Subscribers

People subscribed via source and target branches

to all changes: