Merge ppa-dev-tools:set-command-architecture-option into ppa-dev-tools:main

Proposed by Bryce Harrington
Status: Merged
Merge reported by: Bryce Harrington
Merged at revision: cf6a8427699148b3b3055eb3dc810b0712632624
Proposed branch: ppa-dev-tools:set-command-architecture-option
Merge into: ppa-dev-tools:main
Diff against target: 283 lines (+83/-49)
5 files modified
ppa/ppa.py (+10/-5)
ppa/ppa_group.py (+1/-3)
scripts/ppa (+14/-33)
tests/helpers.py (+3/-6)
tests/test_scripts_ppa.py (+55/-2)
Reviewer Review Type Date Requested Status
Athos Ribeiro (community) Approve
Canonical Server packageset reviewers Pending
Canonical Server Reporter Pending
Review via email: mp+439599@code.launchpad.net

Description of the change

Continuation of work on the ppa create/set command. This branch focuses on the addition of tests for covering --architecture's implementation, along with some assorted cleanups.

This also brings in the first user for the recently added unpack_to_dict().

To post a comment you must log in.
9f21837... by Bryce Harrington

Fix invalid split of architectures for unittests

For regular use, architectures were being split into a list by the
create_config() routine. However, in the unittests we're skipping that
functionality and passing config directly, so the architecture string
isn't split, thus breaking the test cases.

Since it was already intended to move the architecture splitting
behavior out of create_config() to command-specific parsing, do that now
for all commands (create, set, and tests) that support an architecture
option.

cf6a842... by Bryce Harrington

Use unpack_to_dict() for splitting --architecture values

This is a bit overkill for handling architecture strings, but since
unpack_to_dict() is intended to be used for parsing all handling of
comma-separated values in the CLI, it will be most consistent if it's
used here as well.

Revision history for this message
Athos Ribeiro (athos-ribeiro) wrote :

LGTM!

Thanks, Bryce

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

Thanks, pushed!

Total 40 (delta 28), reused 0 (delta 0), pack-reused 0
To git+ssh://git.launchpad.net/ppa-dev-tools
   0ff0e30..4bb2310 main -> main

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/ppa/ppa.py b/ppa/ppa.py
2index 671285c..aeb7854 100755
3--- a/ppa/ppa.py
4+++ b/ppa/ppa.py
5@@ -11,6 +11,7 @@
6 """A wrapper around a Launchpad Personal Package Archive object."""
7
8 import re
9+import sys
10
11 from functools import lru_cache
12 from lazr.restfulclient.errors import BadRequest, NotFound
13@@ -182,7 +183,7 @@ class Ppa:
14
15 @property
16 @lru_cache
17- def architectures(self):
18+ def architectures(self) -> list[str]:
19 """Returns the architectures configured to build packages in the PPA.
20
21 :rtype: list[str]
22@@ -191,26 +192,30 @@ class Ppa:
23 try:
24 return [proc.name for proc in self.archive.processors]
25 except PpaDoesNotExist as e:
26- print(e)
27+ sys.stderr.write(e)
28 return None
29
30- def set_architectures(self, architectures):
31+ def set_architectures(self, architectures: list[str]) -> bool:
32 """Configures the architectures used to build packages in the PPA.
33
34 Note that some architectures may only be available upon request
35 from Launchpad administrators. ppa.constants.ARCHES_PPA is a
36 list of standard architectures that don't require permissions.
37
38+ :param list[str] architectures: List of processor architecture names
39 :rtype: bool
40- :returns: True if architectures could be set, False on error.
41+ :returns: True if architectures could be set, False on error or
42+ if no architectures were specified.
43 """
44+ if not architectures:
45+ return False
46 uri_base = "https://api.launchpad.net/devel/+processors/{}"
47 procs = [uri_base.format(arch) for arch in architectures]
48 try:
49 self.archive.setProcessors(processors=procs)
50 return True
51 except PpaDoesNotExist as e:
52- print(e)
53+ sys.stderr.write(e)
54 return False
55
56 def get_binaries(self, distro=None, series=None, arch=None):
57diff --git a/ppa/ppa_group.py b/ppa/ppa_group.py
58index 7bccbb9..59011ea 100755
59--- a/ppa/ppa_group.py
60+++ b/ppa/ppa_group.py
61@@ -88,7 +88,7 @@ class PpaGroup:
62 """
63 return self.service.people[self.name]
64
65- def create(self, ppa_name='ppa', ppa_description=None, **kwargs):
66+ def create(self, ppa_name='ppa', ppa_description=None):
67 """Registers a new PPA with Launchpad.
68
69 If a description is not provided a default one will be generated.
70@@ -104,8 +104,6 @@ class PpaGroup:
71 'description': ppa_description,
72 'displayname': ppa_name,
73 }
74- for k in kwargs.keys():
75- ppa_settings[k] = kwargs[k]
76
77 try:
78 self.team.createPPA(
79diff --git a/scripts/ppa b/scripts/ppa
80index 164fad0..9097004 100755
81--- a/scripts/ppa
82+++ b/scripts/ppa
83@@ -71,6 +71,7 @@ from ppa.constants import (
84 ARCHES_AUTOPKGTEST,
85 URL_AUTOPKGTEST,
86 )
87+from ppa.dict import unpack_to_dict
88 from ppa.io import open_url
89 from ppa.job import (
90 get_waiting,
91@@ -432,37 +433,8 @@ def create_config(lp: Lp, args: argparse.Namespace) -> dict[str, Any]:
92 if args.dry_run:
93 config['dry_run'] = True
94
95- # TODO: Each subcommand should have its own args->config parser
96- # TODO: Also, loading the values from the config file will need namespaced,
97+ # TODO: Loading the values from the config file will need namespaced,
98 # so e.g. create.architectures = a,b,c
99- if args.command == 'create':
100- if args.architectures is not None:
101- if args.architectures:
102- config['architectures'] = args.architectures.split(',')
103- else:
104- warn(f"Invalid architectures '{args.architectures}'")
105- return None
106- elif args.command == 'tests':
107- if args.architectures is not None:
108- if args.architectures:
109- config['architectures'] = args.architectures.split(',')
110- else:
111- warn(f"Invalid architectures '{args.architectures}'")
112- return None
113-
114- if args.releases is not None:
115- if args.releases:
116- config['releases'] = args.releases.split(',')
117- else:
118- warn(f"Invalid releases '{args.releases}'")
119- return None
120- elif args.command == "set":
121- if args.architectures is not None:
122- if args.architectures:
123- config['architectures'] = args.architectures.split(',')
124- else:
125- warn(f"Invalid architectures '{args.architectures}'")
126- return None
127
128 return config
129
130@@ -495,14 +467,18 @@ def command_create(lp, config):
131 return os.EX_USAGE
132
133 publish = config.get('publish', None)
134+
135 architectures = config.get('architectures', ARCHES_PPA_ALL)
136+ if type(architectures) is str:
137+ architectures = unpack_to_dict(architectures).keys()
138
139 try:
140 if not config.get('dry_run', False):
141 ppa_group = PpaGroup(service=lp, name=team_name)
142 the_ppa = ppa_group.create(ppa_name, ppa_description=description)
143- the_ppa.set_publish(config.get('publish', None))
144- the_ppa.set_architectures(architectures)
145+ the_ppa.set_publish(publish)
146+ if architectures:
147+ the_ppa.set_architectures(architectures)
148 arch_str = ', '.join(the_ppa.architectures)
149 else:
150 the_ppa = Ppa(ppa_name, team_name, description)
151@@ -645,7 +621,10 @@ def command_set(lp, config):
152 the_ppa = get_ppa(lp, config)
153
154 if 'architectures' in config:
155- the_ppa.set_architectures(config['architectures'])
156+ architectures = config['architectures']
157+ if type(architectures) is str:
158+ architectures = unpack_to_dict(architectures).keys()
159+ the_ppa.set_architectures(architectures)
160
161 if 'description' in config:
162 the_ppa.archive.description = config['description']
163@@ -806,6 +785,8 @@ def command_tests(lp, config):
164 return 1
165
166 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)
167+ if type(architectures) is str:
168+ architectures = unpack_to_dict(architectures).keys()
169
170 try:
171 # Triggers
172diff --git a/tests/helpers.py b/tests/helpers.py
173index 84b36ad..2329125 100644
174--- a/tests/helpers.py
175+++ b/tests/helpers.py
176@@ -33,15 +33,12 @@ class ArchiveMock:
177 self.publish = True
178 self.processors = [ProcessorMock(proc_name) for proc_name in ARCHES_PPA_DEFAULT]
179
180+ def setProcessors(self, processors):
181+ self.processors = [ProcessorMock(proc.split('/')[-1]) for proc in processors]
182+
183 def lp_save(self):
184 return True
185
186- def setProcessors(self, processors):
187- self.processors = []
188- for proc in processors:
189- proc_name = proc.split('/')[-1]
190- self.processors.append(ProcessorMock(proc_name))
191-
192
193 class PersonMock:
194 """A stand-in for a Launchpad Person object."""
195diff --git a/tests/test_scripts_ppa.py b/tests/test_scripts_ppa.py
196index 2ac7599..a107f92 100644
197--- a/tests/test_scripts_ppa.py
198+++ b/tests/test_scripts_ppa.py
199@@ -146,7 +146,7 @@ def test_create_arg_parser():
200 if isinstance(action, argparse._SubParsersAction)]
201 subparsers = []
202 for subparsers_action in subparsers_actions:
203- for choice, subparser in subparsers_action.choices.items():
204+ for choice, _ in subparsers_action.choices.items():
205 subparsers.append(choice)
206 assert subparsers == [
207 'create',
208@@ -387,7 +387,6 @@ def test_create_config_from_args(command_line_options, expected_config):
209 args = parser.parse_args(command_line_options)
210 config = script.create_config(lp, args)
211
212- expected_result = script.DEFAULT_CONFIG
213 for key, value in expected_config.items():
214 assert key in config.keys()
215 assert config[key] == value
216@@ -451,6 +450,31 @@ def test_command_create(fake_config, monkeypatch, stdin, params, expected_ppa_co
217 assert getattr(lp_ppa, key) == value
218
219
220+@pytest.mark.parametrize('architectures, expected_processors', [
221+ (None, ARCHES_PPA_DEFAULT),
222+ ('a', ['a']),
223+ ('a,b,c', ['a', 'b', 'c']),
224+ ('a, b, c', ['a', 'b', 'c']),
225+ ('amd64,arm64,armhf,i386,powerpc,ppc64el,s390x',
226+ ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"]),
227+])
228+def test_command_create_with_architectures(monkeypatch, fake_config, architectures, expected_processors):
229+ '''Checks that PPAs can be created with non-default architecture support.'''
230+ lp = LpServiceMock()
231+ config = {**fake_config, **{'architectures': architectures}}
232+ monkeypatch.setattr("sys.stdin", io.StringIO('x'))
233+ assert script.command_create(lp, config) == 0
234+
235+ # Retrieve the newly created PPA
236+ team = lp.people[config['team_name']]
237+ lp_ppa = team.getPPAByName(config['ppa_name'])
238+
239+ # Check processor architectures
240+ assert lp_ppa.processors
241+ assert type(lp_ppa.processors) is list
242+ assert [proc.name for proc in lp_ppa.processors] == expected_processors
243+
244+
245 @pytest.mark.xfail(reason="Unimplemented")
246 def test_command_desc(fake_config):
247 assert script.command_desc(fake_config) == 0
248@@ -514,6 +538,35 @@ def test_command_set(fake_config, params, expected_ppa_config):
249 assert getattr(lp_ppa, key) == value
250
251
252+@pytest.mark.parametrize('architectures, expected_processors', [
253+ (None, ARCHES_PPA_DEFAULT),
254+ ('a', ['a']),
255+ ('a,b,c', ['a', 'b', 'c']),
256+ ('a, b, c', ['a', 'b', 'c']),
257+ ('amd64,arm64,armhf,i386,powerpc,ppc64el,s390x',
258+ ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"]),
259+])
260+def test_command_set_architectures(fake_config, architectures, expected_processors):
261+ '''Checks that existing PPAs can have their architectures changed.'''
262+ lp = LpServiceMock()
263+
264+ # Create a default PPA, for modification later
265+ team = lp.people[fake_config['team_name']]
266+ team.createPPA(fake_config['ppa_name'], 'x', 'y')
267+
268+ # Check success of the set command
269+ config = {**fake_config, **{'architectures': architectures}}
270+ assert script.command_set(lp, config)
271+
272+ # Retrieve the PPA we created earlier
273+ lp_ppa = team.getPPAByName(fake_config['ppa_name'])
274+
275+ # Check processor architectures
276+ assert lp_ppa.processors
277+ assert type(lp_ppa.processors) is list
278+ assert [proc.name for proc in lp_ppa.processors] == expected_processors
279+
280+
281 @pytest.mark.xfail(reason="Unimplemented")
282 def test_command_show(fake_config):
283 assert script.command_show(fake_config) == 0

Subscribers

People subscribed via source and target branches

to all changes: