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
diff --git a/ppa/ppa.py b/ppa/ppa.py
index 671285c..aeb7854 100755
--- a/ppa/ppa.py
+++ b/ppa/ppa.py
@@ -11,6 +11,7 @@
11"""A wrapper around a Launchpad Personal Package Archive object."""11"""A wrapper around a Launchpad Personal Package Archive object."""
1212
13import re13import re
14import sys
1415
15from functools import lru_cache16from functools import lru_cache
16from lazr.restfulclient.errors import BadRequest, NotFound17from lazr.restfulclient.errors import BadRequest, NotFound
@@ -182,7 +183,7 @@ class Ppa:
182183
183 @property184 @property
184 @lru_cache185 @lru_cache
185 def architectures(self):186 def architectures(self) -> list[str]:
186 """Returns the architectures configured to build packages in the PPA.187 """Returns the architectures configured to build packages in the PPA.
187188
188 :rtype: list[str]189 :rtype: list[str]
@@ -191,26 +192,30 @@ class Ppa:
191 try:192 try:
192 return [proc.name for proc in self.archive.processors]193 return [proc.name for proc in self.archive.processors]
193 except PpaDoesNotExist as e:194 except PpaDoesNotExist as e:
194 print(e)195 sys.stderr.write(e)
195 return None196 return None
196197
197 def set_architectures(self, architectures):198 def set_architectures(self, architectures: list[str]) -> bool:
198 """Configures the architectures used to build packages in the PPA.199 """Configures the architectures used to build packages in the PPA.
199200
200 Note that some architectures may only be available upon request201 Note that some architectures may only be available upon request
201 from Launchpad administrators. ppa.constants.ARCHES_PPA is a202 from Launchpad administrators. ppa.constants.ARCHES_PPA is a
202 list of standard architectures that don't require permissions.203 list of standard architectures that don't require permissions.
203204
205 :param list[str] architectures: List of processor architecture names
204 :rtype: bool206 :rtype: bool
205 :returns: True if architectures could be set, False on error.207 :returns: True if architectures could be set, False on error or
208 if no architectures were specified.
206 """209 """
210 if not architectures:
211 return False
207 uri_base = "https://api.launchpad.net/devel/+processors/{}"212 uri_base = "https://api.launchpad.net/devel/+processors/{}"
208 procs = [uri_base.format(arch) for arch in architectures]213 procs = [uri_base.format(arch) for arch in architectures]
209 try:214 try:
210 self.archive.setProcessors(processors=procs)215 self.archive.setProcessors(processors=procs)
211 return True216 return True
212 except PpaDoesNotExist as e:217 except PpaDoesNotExist as e:
213 print(e)218 sys.stderr.write(e)
214 return False219 return False
215220
216 def get_binaries(self, distro=None, series=None, arch=None):221 def get_binaries(self, distro=None, series=None, arch=None):
diff --git a/ppa/ppa_group.py b/ppa/ppa_group.py
index 7bccbb9..59011ea 100755
--- a/ppa/ppa_group.py
+++ b/ppa/ppa_group.py
@@ -88,7 +88,7 @@ class PpaGroup:
88 """88 """
89 return self.service.people[self.name]89 return self.service.people[self.name]
9090
91 def create(self, ppa_name='ppa', ppa_description=None, **kwargs):91 def create(self, ppa_name='ppa', ppa_description=None):
92 """Registers a new PPA with Launchpad.92 """Registers a new PPA with Launchpad.
9393
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.
@@ -104,8 +104,6 @@ class PpaGroup:
104 'description': ppa_description,104 'description': ppa_description,
105 'displayname': ppa_name,105 'displayname': ppa_name,
106 }106 }
107 for k in kwargs.keys():
108 ppa_settings[k] = kwargs[k]
109107
110 try:108 try:
111 self.team.createPPA(109 self.team.createPPA(
diff --git a/scripts/ppa b/scripts/ppa
index 164fad0..9097004 100755
--- a/scripts/ppa
+++ b/scripts/ppa
@@ -71,6 +71,7 @@ from ppa.constants import (
71 ARCHES_AUTOPKGTEST,71 ARCHES_AUTOPKGTEST,
72 URL_AUTOPKGTEST,72 URL_AUTOPKGTEST,
73)73)
74from ppa.dict import unpack_to_dict
74from ppa.io import open_url75from ppa.io import open_url
75from ppa.job import (76from ppa.job import (
76 get_waiting,77 get_waiting,
@@ -432,37 +433,8 @@ def create_config(lp: Lp, args: argparse.Namespace) -> dict[str, Any]:
432 if args.dry_run:433 if args.dry_run:
433 config['dry_run'] = True434 config['dry_run'] = True
434435
435 # TODO: Each subcommand should have its own args->config parser436 # TODO: Loading the values from the config file will need namespaced,
436 # TODO: Also, loading the values from the config file will need namespaced,
437 # so e.g. create.architectures = a,b,c437 # so e.g. create.architectures = a,b,c
438 if args.command == 'create':
439 if args.architectures is not None:
440 if args.architectures:
441 config['architectures'] = args.architectures.split(',')
442 else:
443 warn(f"Invalid architectures '{args.architectures}'")
444 return None
445 elif args.command == 'tests':
446 if args.architectures is not None:
447 if args.architectures:
448 config['architectures'] = args.architectures.split(',')
449 else:
450 warn(f"Invalid architectures '{args.architectures}'")
451 return None
452
453 if args.releases is not None:
454 if args.releases:
455 config['releases'] = args.releases.split(',')
456 else:
457 warn(f"Invalid releases '{args.releases}'")
458 return None
459 elif args.command == "set":
460 if args.architectures is not None:
461 if args.architectures:
462 config['architectures'] = args.architectures.split(',')
463 else:
464 warn(f"Invalid architectures '{args.architectures}'")
465 return None
466438
467 return config439 return config
468440
@@ -495,14 +467,18 @@ def command_create(lp, config):
495 return os.EX_USAGE467 return os.EX_USAGE
496468
497 publish = config.get('publish', None)469 publish = config.get('publish', None)
470
498 architectures = config.get('architectures', ARCHES_PPA_ALL)471 architectures = config.get('architectures', ARCHES_PPA_ALL)
472 if type(architectures) is str:
473 architectures = unpack_to_dict(architectures).keys()
499474
500 try:475 try:
501 if not config.get('dry_run', False):476 if not config.get('dry_run', False):
502 ppa_group = PpaGroup(service=lp, name=team_name)477 ppa_group = PpaGroup(service=lp, name=team_name)
503 the_ppa = ppa_group.create(ppa_name, ppa_description=description)478 the_ppa = ppa_group.create(ppa_name, ppa_description=description)
504 the_ppa.set_publish(config.get('publish', None))479 the_ppa.set_publish(publish)
505 the_ppa.set_architectures(architectures)480 if architectures:
481 the_ppa.set_architectures(architectures)
506 arch_str = ', '.join(the_ppa.architectures)482 arch_str = ', '.join(the_ppa.architectures)
507 else:483 else:
508 the_ppa = Ppa(ppa_name, team_name, description)484 the_ppa = Ppa(ppa_name, team_name, description)
@@ -645,7 +621,10 @@ def command_set(lp, config):
645 the_ppa = get_ppa(lp, config)621 the_ppa = get_ppa(lp, config)
646622
647 if 'architectures' in config:623 if 'architectures' in config:
648 the_ppa.set_architectures(config['architectures'])624 architectures = config['architectures']
625 if type(architectures) is str:
626 architectures = unpack_to_dict(architectures).keys()
627 the_ppa.set_architectures(architectures)
649628
650 if 'description' in config:629 if 'description' in config:
651 the_ppa.archive.description = config['description']630 the_ppa.archive.description = config['description']
@@ -806,6 +785,8 @@ def command_tests(lp, config):
806 return 1785 return 1
807786
808 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)787 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)
788 if type(architectures) is str:
789 architectures = unpack_to_dict(architectures).keys()
809790
810 try:791 try:
811 # Triggers792 # Triggers
diff --git a/tests/helpers.py b/tests/helpers.py
index 84b36ad..2329125 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -33,15 +33,12 @@ class ArchiveMock:
33 self.publish = True33 self.publish = True
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]
3535
36 def setProcessors(self, processors):
37 self.processors = [ProcessorMock(proc.split('/')[-1]) for proc in processors]
38
36 def lp_save(self):39 def lp_save(self):
37 return True40 return True
3841
39 def setProcessors(self, processors):
40 self.processors = []
41 for proc in processors:
42 proc_name = proc.split('/')[-1]
43 self.processors.append(ProcessorMock(proc_name))
44
4542
46class PersonMock:43class PersonMock:
47 """A stand-in for a Launchpad Person object."""44 """A stand-in for a Launchpad Person object."""
diff --git a/tests/test_scripts_ppa.py b/tests/test_scripts_ppa.py
index 2ac7599..a107f92 100644
--- a/tests/test_scripts_ppa.py
+++ b/tests/test_scripts_ppa.py
@@ -146,7 +146,7 @@ def test_create_arg_parser():
146 if isinstance(action, argparse._SubParsersAction)]146 if isinstance(action, argparse._SubParsersAction)]
147 subparsers = []147 subparsers = []
148 for subparsers_action in subparsers_actions:148 for subparsers_action in subparsers_actions:
149 for choice, subparser in subparsers_action.choices.items():149 for choice, _ in subparsers_action.choices.items():
150 subparsers.append(choice)150 subparsers.append(choice)
151 assert subparsers == [151 assert subparsers == [
152 'create',152 'create',
@@ -387,7 +387,6 @@ def test_create_config_from_args(command_line_options, expected_config):
387 args = parser.parse_args(command_line_options)387 args = parser.parse_args(command_line_options)
388 config = script.create_config(lp, args)388 config = script.create_config(lp, args)
389389
390 expected_result = script.DEFAULT_CONFIG
391 for key, value in expected_config.items():390 for key, value in expected_config.items():
392 assert key in config.keys()391 assert key in config.keys()
393 assert config[key] == value392 assert config[key] == value
@@ -451,6 +450,31 @@ def test_command_create(fake_config, monkeypatch, stdin, params, expected_ppa_co
451 assert getattr(lp_ppa, key) == value450 assert getattr(lp_ppa, key) == value
452451
453452
453@pytest.mark.parametrize('architectures, expected_processors', [
454 (None, ARCHES_PPA_DEFAULT),
455 ('a', ['a']),
456 ('a,b,c', ['a', 'b', 'c']),
457 ('a, b, c', ['a', 'b', 'c']),
458 ('amd64,arm64,armhf,i386,powerpc,ppc64el,s390x',
459 ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"]),
460])
461def test_command_create_with_architectures(monkeypatch, fake_config, architectures, expected_processors):
462 '''Checks that PPAs can be created with non-default architecture support.'''
463 lp = LpServiceMock()
464 config = {**fake_config, **{'architectures': architectures}}
465 monkeypatch.setattr("sys.stdin", io.StringIO('x'))
466 assert script.command_create(lp, config) == 0
467
468 # Retrieve the newly created PPA
469 team = lp.people[config['team_name']]
470 lp_ppa = team.getPPAByName(config['ppa_name'])
471
472 # Check processor architectures
473 assert lp_ppa.processors
474 assert type(lp_ppa.processors) is list
475 assert [proc.name for proc in lp_ppa.processors] == expected_processors
476
477
454@pytest.mark.xfail(reason="Unimplemented")478@pytest.mark.xfail(reason="Unimplemented")
455def test_command_desc(fake_config):479def test_command_desc(fake_config):
456 assert script.command_desc(fake_config) == 0480 assert script.command_desc(fake_config) == 0
@@ -514,6 +538,35 @@ def test_command_set(fake_config, params, expected_ppa_config):
514 assert getattr(lp_ppa, key) == value538 assert getattr(lp_ppa, key) == value
515539
516540
541@pytest.mark.parametrize('architectures, expected_processors', [
542 (None, ARCHES_PPA_DEFAULT),
543 ('a', ['a']),
544 ('a,b,c', ['a', 'b', 'c']),
545 ('a, b, c', ['a', 'b', 'c']),
546 ('amd64,arm64,armhf,i386,powerpc,ppc64el,s390x',
547 ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"]),
548])
549def test_command_set_architectures(fake_config, architectures, expected_processors):
550 '''Checks that existing PPAs can have their architectures changed.'''
551 lp = LpServiceMock()
552
553 # Create a default PPA, for modification later
554 team = lp.people[fake_config['team_name']]
555 team.createPPA(fake_config['ppa_name'], 'x', 'y')
556
557 # Check success of the set command
558 config = {**fake_config, **{'architectures': architectures}}
559 assert script.command_set(lp, config)
560
561 # Retrieve the PPA we created earlier
562 lp_ppa = team.getPPAByName(fake_config['ppa_name'])
563
564 # Check processor architectures
565 assert lp_ppa.processors
566 assert type(lp_ppa.processors) is list
567 assert [proc.name for proc in lp_ppa.processors] == expected_processors
568
569
517@pytest.mark.xfail(reason="Unimplemented")570@pytest.mark.xfail(reason="Unimplemented")
518def test_command_show(fake_config):571def test_command_show(fake_config):
519 assert script.command_show(fake_config) == 0572 assert script.command_show(fake_config) == 0

Subscribers

People subscribed via source and target branches

to all changes: