Merge ppa-dev-tools:set-command-architecture-option into ppa-dev-tools:main
- Git
- lp:ppa-dev-tools
- set-command-architecture-option
- Merge into main
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) |
Related bugs: |
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 |
Commit message
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().
- 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.
Bryce Harrington (bryce) wrote : | # |
Thanks, pushed!
Total 40 (delta 28), reused 0 (delta 0), pack-reused 0
To git+ssh:
0ff0e30..4bb2310 main -> main
Preview Diff
1 | diff --git a/ppa/ppa.py b/ppa/ppa.py | |||
2 | index 671285c..aeb7854 100755 | |||
3 | --- a/ppa/ppa.py | |||
4 | +++ b/ppa/ppa.py | |||
5 | @@ -11,6 +11,7 @@ | |||
6 | 11 | """A wrapper around a Launchpad Personal Package Archive object.""" | 11 | """A wrapper around a Launchpad Personal Package Archive object.""" |
7 | 12 | 12 | ||
8 | 13 | import re | 13 | import re |
9 | 14 | import sys | ||
10 | 14 | 15 | ||
11 | 15 | from functools import lru_cache | 16 | from functools import lru_cache |
12 | 16 | from lazr.restfulclient.errors import BadRequest, NotFound | 17 | from lazr.restfulclient.errors import BadRequest, NotFound |
13 | @@ -182,7 +183,7 @@ class Ppa: | |||
14 | 182 | 183 | ||
15 | 183 | @property | 184 | @property |
16 | 184 | @lru_cache | 185 | @lru_cache |
18 | 185 | def architectures(self): | 186 | def architectures(self) -> list[str]: |
19 | 186 | """Returns the architectures configured to build packages in the PPA. | 187 | """Returns the architectures configured to build packages in the PPA. |
20 | 187 | 188 | ||
21 | 188 | :rtype: list[str] | 189 | :rtype: list[str] |
22 | @@ -191,26 +192,30 @@ class Ppa: | |||
23 | 191 | try: | 192 | try: |
24 | 192 | return [proc.name for proc in self.archive.processors] | 193 | return [proc.name for proc in self.archive.processors] |
25 | 193 | except PpaDoesNotExist as e: | 194 | except PpaDoesNotExist as e: |
27 | 194 | print(e) | 195 | sys.stderr.write(e) |
28 | 195 | return None | 196 | return None |
29 | 196 | 197 | ||
31 | 197 | def set_architectures(self, architectures): | 198 | def set_architectures(self, architectures: list[str]) -> bool: |
32 | 198 | """Configures the architectures used to build packages in the PPA. | 199 | """Configures the architectures used to build packages in the PPA. |
33 | 199 | 200 | ||
34 | 200 | Note that some architectures may only be available upon request | 201 | Note that some architectures may only be available upon request |
35 | 201 | from Launchpad administrators. ppa.constants.ARCHES_PPA is a | 202 | from Launchpad administrators. ppa.constants.ARCHES_PPA is a |
36 | 202 | list of standard architectures that don't require permissions. | 203 | list of standard architectures that don't require permissions. |
37 | 203 | 204 | ||
38 | 205 | :param list[str] architectures: List of processor architecture names | ||
39 | 204 | :rtype: bool | 206 | :rtype: bool |
41 | 205 | :returns: True if architectures could be set, False on error. | 207 | :returns: True if architectures could be set, False on error or |
42 | 208 | if no architectures were specified. | ||
43 | 206 | """ | 209 | """ |
44 | 210 | if not architectures: | ||
45 | 211 | return False | ||
46 | 207 | uri_base = "https://api.launchpad.net/devel/+processors/{}" | 212 | uri_base = "https://api.launchpad.net/devel/+processors/{}" |
47 | 208 | procs = [uri_base.format(arch) for arch in architectures] | 213 | procs = [uri_base.format(arch) for arch in architectures] |
48 | 209 | try: | 214 | try: |
49 | 210 | self.archive.setProcessors(processors=procs) | 215 | self.archive.setProcessors(processors=procs) |
50 | 211 | return True | 216 | return True |
51 | 212 | except PpaDoesNotExist as e: | 217 | except PpaDoesNotExist as e: |
53 | 213 | print(e) | 218 | sys.stderr.write(e) |
54 | 214 | return False | 219 | return False |
55 | 215 | 220 | ||
56 | 216 | def get_binaries(self, distro=None, series=None, arch=None): | 221 | def get_binaries(self, distro=None, series=None, arch=None): |
57 | diff --git a/ppa/ppa_group.py b/ppa/ppa_group.py | |||
58 | index 7bccbb9..59011ea 100755 | |||
59 | --- a/ppa/ppa_group.py | |||
60 | +++ b/ppa/ppa_group.py | |||
61 | @@ -88,7 +88,7 @@ class PpaGroup: | |||
62 | 88 | """ | 88 | """ |
63 | 89 | return self.service.people[self.name] | 89 | return self.service.people[self.name] |
64 | 90 | 90 | ||
66 | 91 | def create(self, ppa_name='ppa', ppa_description=None, **kwargs): | 91 | def create(self, ppa_name='ppa', ppa_description=None): |
67 | 92 | """Registers a new PPA with Launchpad. | 92 | """Registers a new PPA with Launchpad. |
68 | 93 | 93 | ||
69 | 94 | If a description is not provided a default one will be generated. | 94 | If a description is not provided a default one will be generated. |
70 | @@ -104,8 +104,6 @@ class PpaGroup: | |||
71 | 104 | 'description': ppa_description, | 104 | 'description': ppa_description, |
72 | 105 | 'displayname': ppa_name, | 105 | 'displayname': ppa_name, |
73 | 106 | } | 106 | } |
74 | 107 | for k in kwargs.keys(): | ||
75 | 108 | ppa_settings[k] = kwargs[k] | ||
76 | 109 | 107 | ||
77 | 110 | try: | 108 | try: |
78 | 111 | self.team.createPPA( | 109 | self.team.createPPA( |
79 | diff --git a/scripts/ppa b/scripts/ppa | |||
80 | index 164fad0..9097004 100755 | |||
81 | --- a/scripts/ppa | |||
82 | +++ b/scripts/ppa | |||
83 | @@ -71,6 +71,7 @@ from ppa.constants import ( | |||
84 | 71 | ARCHES_AUTOPKGTEST, | 71 | ARCHES_AUTOPKGTEST, |
85 | 72 | URL_AUTOPKGTEST, | 72 | URL_AUTOPKGTEST, |
86 | 73 | ) | 73 | ) |
87 | 74 | from ppa.dict import unpack_to_dict | ||
88 | 74 | from ppa.io import open_url | 75 | from ppa.io import open_url |
89 | 75 | from ppa.job import ( | 76 | from ppa.job import ( |
90 | 76 | get_waiting, | 77 | get_waiting, |
91 | @@ -432,37 +433,8 @@ def create_config(lp: Lp, args: argparse.Namespace) -> dict[str, Any]: | |||
92 | 432 | if args.dry_run: | 433 | if args.dry_run: |
93 | 433 | config['dry_run'] = True | 434 | config['dry_run'] = True |
94 | 434 | 435 | ||
97 | 435 | # TODO: Each subcommand should have its own args->config parser | 436 | # TODO: Loading the values from the config file will need namespaced, |
96 | 436 | # TODO: Also, loading the values from the config file will need namespaced, | ||
98 | 437 | # so e.g. create.architectures = a,b,c | 437 | # so e.g. create.architectures = a,b,c |
99 | 438 | if args.command == 'create': | ||
100 | 439 | if args.architectures is not None: | ||
101 | 440 | if args.architectures: | ||
102 | 441 | config['architectures'] = args.architectures.split(',') | ||
103 | 442 | else: | ||
104 | 443 | warn(f"Invalid architectures '{args.architectures}'") | ||
105 | 444 | return None | ||
106 | 445 | elif args.command == 'tests': | ||
107 | 446 | if args.architectures is not None: | ||
108 | 447 | if args.architectures: | ||
109 | 448 | config['architectures'] = args.architectures.split(',') | ||
110 | 449 | else: | ||
111 | 450 | warn(f"Invalid architectures '{args.architectures}'") | ||
112 | 451 | return None | ||
113 | 452 | |||
114 | 453 | if args.releases is not None: | ||
115 | 454 | if args.releases: | ||
116 | 455 | config['releases'] = args.releases.split(',') | ||
117 | 456 | else: | ||
118 | 457 | warn(f"Invalid releases '{args.releases}'") | ||
119 | 458 | return None | ||
120 | 459 | elif args.command == "set": | ||
121 | 460 | if args.architectures is not None: | ||
122 | 461 | if args.architectures: | ||
123 | 462 | config['architectures'] = args.architectures.split(',') | ||
124 | 463 | else: | ||
125 | 464 | warn(f"Invalid architectures '{args.architectures}'") | ||
126 | 465 | return None | ||
127 | 466 | 438 | ||
128 | 467 | return config | 439 | return config |
129 | 468 | 440 | ||
130 | @@ -495,14 +467,18 @@ def command_create(lp, config): | |||
131 | 495 | return os.EX_USAGE | 467 | return os.EX_USAGE |
132 | 496 | 468 | ||
133 | 497 | publish = config.get('publish', None) | 469 | publish = config.get('publish', None) |
134 | 470 | |||
135 | 498 | architectures = config.get('architectures', ARCHES_PPA_ALL) | 471 | architectures = config.get('architectures', ARCHES_PPA_ALL) |
136 | 472 | if type(architectures) is str: | ||
137 | 473 | architectures = unpack_to_dict(architectures).keys() | ||
138 | 499 | 474 | ||
139 | 500 | try: | 475 | try: |
140 | 501 | if not config.get('dry_run', False): | 476 | if not config.get('dry_run', False): |
141 | 502 | ppa_group = PpaGroup(service=lp, name=team_name) | 477 | ppa_group = PpaGroup(service=lp, name=team_name) |
142 | 503 | the_ppa = ppa_group.create(ppa_name, ppa_description=description) | 478 | the_ppa = ppa_group.create(ppa_name, ppa_description=description) |
145 | 504 | the_ppa.set_publish(config.get('publish', None)) | 479 | the_ppa.set_publish(publish) |
146 | 505 | the_ppa.set_architectures(architectures) | 480 | if architectures: |
147 | 481 | the_ppa.set_architectures(architectures) | ||
148 | 506 | arch_str = ', '.join(the_ppa.architectures) | 482 | arch_str = ', '.join(the_ppa.architectures) |
149 | 507 | else: | 483 | else: |
150 | 508 | the_ppa = Ppa(ppa_name, team_name, description) | 484 | the_ppa = Ppa(ppa_name, team_name, description) |
151 | @@ -645,7 +621,10 @@ def command_set(lp, config): | |||
152 | 645 | the_ppa = get_ppa(lp, config) | 621 | the_ppa = get_ppa(lp, config) |
153 | 646 | 622 | ||
154 | 647 | if 'architectures' in config: | 623 | if 'architectures' in config: |
156 | 648 | the_ppa.set_architectures(config['architectures']) | 624 | architectures = config['architectures'] |
157 | 625 | if type(architectures) is str: | ||
158 | 626 | architectures = unpack_to_dict(architectures).keys() | ||
159 | 627 | the_ppa.set_architectures(architectures) | ||
160 | 649 | 628 | ||
161 | 650 | if 'description' in config: | 629 | if 'description' in config: |
162 | 651 | the_ppa.archive.description = config['description'] | 630 | the_ppa.archive.description = config['description'] |
163 | @@ -806,6 +785,8 @@ def command_tests(lp, config): | |||
164 | 806 | return 1 | 785 | return 1 |
165 | 807 | 786 | ||
166 | 808 | architectures = config.get('architectures', ARCHES_AUTOPKGTEST) | 787 | architectures = config.get('architectures', ARCHES_AUTOPKGTEST) |
167 | 788 | if type(architectures) is str: | ||
168 | 789 | architectures = unpack_to_dict(architectures).keys() | ||
169 | 809 | 790 | ||
170 | 810 | try: | 791 | try: |
171 | 811 | # Triggers | 792 | # Triggers |
172 | diff --git a/tests/helpers.py b/tests/helpers.py | |||
173 | index 84b36ad..2329125 100644 | |||
174 | --- a/tests/helpers.py | |||
175 | +++ b/tests/helpers.py | |||
176 | @@ -33,15 +33,12 @@ class ArchiveMock: | |||
177 | 33 | self.publish = True | 33 | self.publish = True |
178 | 34 | self.processors = [ProcessorMock(proc_name) for proc_name in ARCHES_PPA_DEFAULT] | 34 | self.processors = [ProcessorMock(proc_name) for proc_name in ARCHES_PPA_DEFAULT] |
179 | 35 | 35 | ||
180 | 36 | def setProcessors(self, processors): | ||
181 | 37 | self.processors = [ProcessorMock(proc.split('/')[-1]) for proc in processors] | ||
182 | 38 | |||
183 | 36 | def lp_save(self): | 39 | def lp_save(self): |
184 | 37 | return True | 40 | return True |
185 | 38 | 41 | ||
186 | 39 | def setProcessors(self, processors): | ||
187 | 40 | self.processors = [] | ||
188 | 41 | for proc in processors: | ||
189 | 42 | proc_name = proc.split('/')[-1] | ||
190 | 43 | self.processors.append(ProcessorMock(proc_name)) | ||
191 | 44 | |||
192 | 45 | 42 | ||
193 | 46 | class PersonMock: | 43 | class PersonMock: |
194 | 47 | """A stand-in for a Launchpad Person object.""" | 44 | """A stand-in for a Launchpad Person object.""" |
195 | diff --git a/tests/test_scripts_ppa.py b/tests/test_scripts_ppa.py | |||
196 | index 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 | 146 | if isinstance(action, argparse._SubParsersAction)] | 146 | if isinstance(action, argparse._SubParsersAction)] |
201 | 147 | subparsers = [] | 147 | subparsers = [] |
202 | 148 | for subparsers_action in subparsers_actions: | 148 | for subparsers_action in subparsers_actions: |
204 | 149 | for choice, subparser in subparsers_action.choices.items(): | 149 | for choice, _ in subparsers_action.choices.items(): |
205 | 150 | subparsers.append(choice) | 150 | subparsers.append(choice) |
206 | 151 | assert subparsers == [ | 151 | assert subparsers == [ |
207 | 152 | 'create', | 152 | 'create', |
208 | @@ -387,7 +387,6 @@ def test_create_config_from_args(command_line_options, expected_config): | |||
209 | 387 | args = parser.parse_args(command_line_options) | 387 | args = parser.parse_args(command_line_options) |
210 | 388 | config = script.create_config(lp, args) | 388 | config = script.create_config(lp, args) |
211 | 389 | 389 | ||
212 | 390 | expected_result = script.DEFAULT_CONFIG | ||
213 | 391 | for key, value in expected_config.items(): | 390 | for key, value in expected_config.items(): |
214 | 392 | assert key in config.keys() | 391 | assert key in config.keys() |
215 | 393 | assert config[key] == value | 392 | assert config[key] == value |
216 | @@ -451,6 +450,31 @@ def test_command_create(fake_config, monkeypatch, stdin, params, expected_ppa_co | |||
217 | 451 | assert getattr(lp_ppa, key) == value | 450 | assert getattr(lp_ppa, key) == value |
218 | 452 | 451 | ||
219 | 453 | 452 | ||
220 | 453 | @pytest.mark.parametrize('architectures, expected_processors', [ | ||
221 | 454 | (None, ARCHES_PPA_DEFAULT), | ||
222 | 455 | ('a', ['a']), | ||
223 | 456 | ('a,b,c', ['a', 'b', 'c']), | ||
224 | 457 | ('a, b, c', ['a', 'b', 'c']), | ||
225 | 458 | ('amd64,arm64,armhf,i386,powerpc,ppc64el,s390x', | ||
226 | 459 | ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"]), | ||
227 | 460 | ]) | ||
228 | 461 | def test_command_create_with_architectures(monkeypatch, fake_config, architectures, expected_processors): | ||
229 | 462 | '''Checks that PPAs can be created with non-default architecture support.''' | ||
230 | 463 | lp = LpServiceMock() | ||
231 | 464 | config = {**fake_config, **{'architectures': architectures}} | ||
232 | 465 | monkeypatch.setattr("sys.stdin", io.StringIO('x')) | ||
233 | 466 | assert script.command_create(lp, config) == 0 | ||
234 | 467 | |||
235 | 468 | # Retrieve the newly created PPA | ||
236 | 469 | team = lp.people[config['team_name']] | ||
237 | 470 | lp_ppa = team.getPPAByName(config['ppa_name']) | ||
238 | 471 | |||
239 | 472 | # Check processor architectures | ||
240 | 473 | assert lp_ppa.processors | ||
241 | 474 | assert type(lp_ppa.processors) is list | ||
242 | 475 | assert [proc.name for proc in lp_ppa.processors] == expected_processors | ||
243 | 476 | |||
244 | 477 | |||
245 | 454 | @pytest.mark.xfail(reason="Unimplemented") | 478 | @pytest.mark.xfail(reason="Unimplemented") |
246 | 455 | def test_command_desc(fake_config): | 479 | def test_command_desc(fake_config): |
247 | 456 | assert script.command_desc(fake_config) == 0 | 480 | assert script.command_desc(fake_config) == 0 |
248 | @@ -514,6 +538,35 @@ def test_command_set(fake_config, params, expected_ppa_config): | |||
249 | 514 | assert getattr(lp_ppa, key) == value | 538 | assert getattr(lp_ppa, key) == value |
250 | 515 | 539 | ||
251 | 516 | 540 | ||
252 | 541 | @pytest.mark.parametrize('architectures, expected_processors', [ | ||
253 | 542 | (None, ARCHES_PPA_DEFAULT), | ||
254 | 543 | ('a', ['a']), | ||
255 | 544 | ('a,b,c', ['a', 'b', 'c']), | ||
256 | 545 | ('a, b, c', ['a', 'b', 'c']), | ||
257 | 546 | ('amd64,arm64,armhf,i386,powerpc,ppc64el,s390x', | ||
258 | 547 | ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"]), | ||
259 | 548 | ]) | ||
260 | 549 | def test_command_set_architectures(fake_config, architectures, expected_processors): | ||
261 | 550 | '''Checks that existing PPAs can have their architectures changed.''' | ||
262 | 551 | lp = LpServiceMock() | ||
263 | 552 | |||
264 | 553 | # Create a default PPA, for modification later | ||
265 | 554 | team = lp.people[fake_config['team_name']] | ||
266 | 555 | team.createPPA(fake_config['ppa_name'], 'x', 'y') | ||
267 | 556 | |||
268 | 557 | # Check success of the set command | ||
269 | 558 | config = {**fake_config, **{'architectures': architectures}} | ||
270 | 559 | assert script.command_set(lp, config) | ||
271 | 560 | |||
272 | 561 | # Retrieve the PPA we created earlier | ||
273 | 562 | lp_ppa = team.getPPAByName(fake_config['ppa_name']) | ||
274 | 563 | |||
275 | 564 | # Check processor architectures | ||
276 | 565 | assert lp_ppa.processors | ||
277 | 566 | assert type(lp_ppa.processors) is list | ||
278 | 567 | assert [proc.name for proc in lp_ppa.processors] == expected_processors | ||
279 | 568 | |||
280 | 569 | |||
281 | 517 | @pytest.mark.xfail(reason="Unimplemented") | 570 | @pytest.mark.xfail(reason="Unimplemented") |
282 | 518 | def test_command_show(fake_config): | 571 | def test_command_show(fake_config): |
283 | 519 | assert script.command_show(fake_config) == 0 | 572 | assert script.command_show(fake_config) == 0 |
LGTM!
Thanks, Bryce