Merge ppa-dev-tools:set-command-dependencies into ppa-dev-tools:main
- Git
- lp:ppa-dev-tools
- set-command-dependencies
- Merge into main
Status: | Merged | ||||
---|---|---|---|---|---|
Merge reported by: | Bryce Harrington | ||||
Merged at revision: | 731e1db3e6065a5951108673437b588e689e14e8 | ||||
Proposed branch: | ppa-dev-tools:set-command-dependencies | ||||
Merge into: | ppa-dev-tools:main | ||||
Diff against target: |
472 lines (+197/-32) 5 files modified
ppa/lp.py (+10/-2) ppa/ppa.py (+122/-4) scripts/ppa (+36/-18) tests/helpers.py (+5/-0) tests/test_scripts_ppa.py (+24/-8) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Lena Voytek (community) | Approve | ||
Canonical Server packageset reviewers | Pending | ||
Canonical Server Reporter | Pending | ||
Review via email: mp+441265@code.launchpad.net |
Commit message
Description of the change
This adds support for the --ppa-dependencies option to the create and set commands, to permit adding one or more PPAs for satisfying the given PPA's build dependencies.
I've also added the start of a handy smoketest for the ppa/ppa.py module to run through the basic settings. I plan to expand this to exercise more of the module but for now just tried to establish the basic structure and operation. I've sorted out making it use the 'qastaging' test instance of Launchpad to be able to validate the launchpad operations without actually impacting anything in production. Note that the data from qastaging is quite old and in fact may be missing your user account if you registered within the last few years, so I hope it works but YMMV. If not, you can flip staging off by editing the smoketest thusly:
lp = Lp('smoketest', staging=False)
There's also a few small cleanup/refactors in separate commits.
Anyway, as usual the smoketest can be run via:
$ python3 -m ppa.ppa
And all testing run via `pytest-3` or:
$ make check
Bryce Harrington (bryce) wrote : | # |
Lena Voytek (lvoytek) wrote : | # |
Sorry for only getting to this today, got distracted on Friday. Code looks good to me! Added a few comments for cleanup
Bryce Harrington (bryce) wrote : | # |
Thanks for the review! One comment below, the rest of the suggestions are incorporated, I'll squash and land the branch directly.
Bryce Harrington (bryce) wrote : | # |
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To git+ssh:
2e6b5d7..d71ef8d main -> main
Preview Diff
1 | diff --git a/ppa/lp.py b/ppa/lp.py | |||
2 | index 1762345..17b225a 100644 | |||
3 | --- a/ppa/lp.py | |||
4 | +++ b/ppa/lp.py | |||
5 | @@ -39,16 +39,24 @@ class Lp: | |||
6 | 39 | 39 | ||
7 | 40 | _real_instance = None | 40 | _real_instance = None |
8 | 41 | 41 | ||
10 | 42 | def __init__(self, application_name, service=Launchpad): | 42 | def __init__(self, application_name, service=Launchpad, staging=False): |
11 | 43 | """Create a Launchpad service object.""" | 43 | """Create a Launchpad service object.""" |
12 | 44 | self._app_name = application_name | 44 | self._app_name = application_name |
13 | 45 | self._service = service | 45 | self._service = service |
14 | 46 | if staging: | ||
15 | 47 | self._service_root = 'qastaging' | ||
16 | 48 | self.ROOT_URL = 'https://qastaging.launchpad.net/' | ||
17 | 49 | self.API_ROOT_URL = 'https://api.qastaging.launchpad.net/devel/' | ||
18 | 50 | self.BUGS_ROOT_URL = 'https://bugs.qastaging.launchpad.net/' | ||
19 | 51 | self.CODE_ROOT_URL = 'https://code.qastaging.launchpad.net/' | ||
20 | 52 | else: | ||
21 | 53 | self._service_root = 'production' | ||
22 | 46 | 54 | ||
23 | 47 | def _get_instance(self): | 55 | def _get_instance(self): |
24 | 48 | """Authenticate to Launchpad.""" | 56 | """Authenticate to Launchpad.""" |
25 | 49 | return self._service.login_with( | 57 | return self._service.login_with( |
26 | 50 | application_name=self._app_name, | 58 | application_name=self._app_name, |
28 | 51 | service_root='production', | 59 | service_root=self._service_root, |
29 | 52 | allow_access_levels=['WRITE_PRIVATE'], | 60 | allow_access_levels=['WRITE_PRIVATE'], |
30 | 53 | version='devel', # Need devel for copyPackage. | 61 | version='devel', # Need devel for copyPackage. |
31 | 54 | ) | 62 | ) |
32 | diff --git a/ppa/ppa.py b/ppa/ppa.py | |||
33 | index 1536b7d..52894c4 100755 | |||
34 | --- a/ppa/ppa.py | |||
35 | +++ b/ppa/ppa.py | |||
36 | @@ -14,7 +14,7 @@ import re | |||
37 | 14 | import sys | 14 | import sys |
38 | 15 | 15 | ||
39 | 16 | from functools import lru_cache | 16 | from functools import lru_cache |
41 | 17 | from lazr.restfulclient.errors import BadRequest, NotFound | 17 | from lazr.restfulclient.errors import BadRequest, NotFound, Unauthorized |
42 | 18 | 18 | ||
43 | 19 | 19 | ||
44 | 20 | class PpaDoesNotExist(BaseException): | 20 | class PpaDoesNotExist(BaseException): |
45 | @@ -141,7 +141,7 @@ class Ppa: | |||
46 | 141 | :rtype: str | 141 | :rtype: str |
47 | 142 | :returns: The url of the PPA. | 142 | :returns: The url of the PPA. |
48 | 143 | """ | 143 | """ |
50 | 144 | return "https://launchpad.net/~{}/+archive/ubuntu/{}".format(self.team_name, self.ppa_name) | 144 | return self.archive.web_link |
51 | 145 | 145 | ||
52 | 146 | @property | 146 | @property |
53 | 147 | def description(self): | 147 | def description(self): |
54 | @@ -209,8 +209,10 @@ class Ppa: | |||
55 | 209 | """ | 209 | """ |
56 | 210 | if not architectures: | 210 | if not architectures: |
57 | 211 | return False | 211 | return False |
60 | 212 | uri_base = "https://api.launchpad.net/devel/+processors/{}" | 212 | base = self._service.API_ROOT_URL.rstrip('/') |
61 | 213 | procs = [uri_base.format(arch) for arch in architectures] | 213 | procs = [] |
62 | 214 | for arch in architectures: | ||
63 | 215 | procs.append(f'{base}/+processors/{arch}') | ||
64 | 214 | try: | 216 | try: |
65 | 215 | self.archive.setProcessors(processors=procs) | 217 | self.archive.setProcessors(processors=procs) |
66 | 216 | return True | 218 | return True |
67 | @@ -218,6 +220,55 @@ class Ppa: | |||
68 | 218 | sys.stderr.write(e) | 220 | sys.stderr.write(e) |
69 | 219 | return False | 221 | return False |
70 | 220 | 222 | ||
71 | 223 | @property | ||
72 | 224 | @lru_cache | ||
73 | 225 | def dependencies(self) -> list[str]: | ||
74 | 226 | """Returns the additional PPAs configured for building packages in this PPA. | ||
75 | 227 | |||
76 | 228 | :rtype: list[str] | ||
77 | 229 | :returns: List of PPA addresses | ||
78 | 230 | """ | ||
79 | 231 | ppa_addresses = [] | ||
80 | 232 | for dep in self.archive.dependencies: | ||
81 | 233 | ppa_dep = dep.dependency | ||
82 | 234 | ppa_addresses.append(ppa_dep.reference) | ||
83 | 235 | return ppa_addresses | ||
84 | 236 | |||
85 | 237 | def set_dependencies(self, ppa_addresses: list[str]): | ||
86 | 238 | """Configures the additional PPAs used to build packages in this PPA. | ||
87 | 239 | |||
88 | 240 | This removes any existing PPA dependencies and adds the ones | ||
89 | 241 | in the corresponding list. If any of these new PPAs cannot be | ||
90 | 242 | found, this routine bails out without changing the current set. | ||
91 | 243 | |||
92 | 244 | :param list[str] ppa_addresses: Additional PPAs to add | ||
93 | 245 | """ | ||
94 | 246 | base = self._service.API_ROOT_URL.rstrip('/') | ||
95 | 247 | new_ppa_deps = [] | ||
96 | 248 | for ppa_address in ppa_addresses: | ||
97 | 249 | team_name, ppa_name = ppa_address_split(ppa_address) | ||
98 | 250 | new_ppa_dep = f'{base}/~{team_name}/+archive/ubuntu/{ppa_name}' | ||
99 | 251 | new_ppa_deps.append(new_ppa_dep) | ||
100 | 252 | |||
101 | 253 | # TODO: Remove all existing dependencies | ||
102 | 254 | # for ppa_dep in self.archive.dependencies: | ||
103 | 255 | # the_ppa.removeArchiveDependency(ppa_dep) | ||
104 | 256 | |||
105 | 257 | # TODO: Not sure what to pass here, maybe a string ala 'main'? | ||
106 | 258 | component = None | ||
107 | 259 | |||
108 | 260 | # TODO: Allow setting alternate pockets | ||
109 | 261 | # TODO: Maybe for convenience it should be same as what's set for main archive? | ||
110 | 262 | pocket = 'Release' | ||
111 | 263 | |||
112 | 264 | for ppa_dep in new_ppa_deps: | ||
113 | 265 | self.archive.addArchiveDependency( | ||
114 | 266 | component=component, | ||
115 | 267 | dependency=ppa_dep, | ||
116 | 268 | pocket=pocket) | ||
117 | 269 | # TODO: Error checking | ||
118 | 270 | # This can throw ArchiveDependencyError if the ppa_address does not fit the_ppa | ||
119 | 271 | |||
120 | 221 | def get_binaries(self, distro=None, series=None, arch=None): | 272 | def get_binaries(self, distro=None, series=None, arch=None): |
121 | 222 | """Retrieves the binary packages available in the PPA. | 273 | """Retrieves the binary packages available in the PPA. |
122 | 223 | 274 | ||
123 | @@ -450,3 +501,70 @@ def get_ppa(lp, config): | |||
124 | 450 | ppa_name=config.get('ppa_name', None), | 501 | ppa_name=config.get('ppa_name', None), |
125 | 451 | team_name=config.get('team_name', None), | 502 | team_name=config.get('team_name', None), |
126 | 452 | service=lp) | 503 | service=lp) |
127 | 504 | |||
128 | 505 | |||
129 | 506 | if __name__ == "__main__": | ||
130 | 507 | import pprint | ||
131 | 508 | import random | ||
132 | 509 | import string | ||
133 | 510 | from .lp import Lp | ||
134 | 511 | from .ppa_group import PpaGroup | ||
135 | 512 | |||
136 | 513 | pp = pprint.PrettyPrinter(indent=4) | ||
137 | 514 | |||
138 | 515 | print('##########################') | ||
139 | 516 | print('## Ppa class smoke test ##') | ||
140 | 517 | print('##########################') | ||
141 | 518 | print() | ||
142 | 519 | |||
143 | 520 | rndstr = str(''.join(random.choices(string.ascii_lowercase, k=6))) | ||
144 | 521 | dep_name = f'dependency-ppa-{rndstr}' | ||
145 | 522 | smoketest_ppa_name = f'test-ppa-{rndstr}' | ||
146 | 523 | |||
147 | 524 | lp = Lp('smoketest', staging=True) | ||
148 | 525 | ppa_group = PpaGroup(service=lp, name=lp.me.name) | ||
149 | 526 | |||
150 | 527 | dep_ppa = ppa_group.create(dep_name, ppa_description=dep_name) | ||
151 | 528 | the_ppa = ppa_group.create(smoketest_ppa_name, ppa_description=smoketest_ppa_name) | ||
152 | 529 | ppa_dependencies = [f'ppa:{lp.me.name}/{dep_name}'] | ||
153 | 530 | |||
154 | 531 | try: | ||
155 | 532 | the_ppa.set_publish(True) | ||
156 | 533 | |||
157 | 534 | if not the_ppa.exists(): | ||
158 | 535 | print("Error: PPA does not exist") | ||
159 | 536 | sys.exit(1) | ||
160 | 537 | the_ppa.set_description("This is a testing PPA and can be deleted") | ||
161 | 538 | the_ppa.set_publish(False) | ||
162 | 539 | the_ppa.set_architectures(["amd64", "arm64"]) | ||
163 | 540 | the_ppa.set_dependencies(ppa_dependencies) | ||
164 | 541 | |||
165 | 542 | print() | ||
166 | 543 | print(f"name: {the_ppa.name}") | ||
167 | 544 | print(f"address: {the_ppa.address}") | ||
168 | 545 | print(f"str(ppa): {the_ppa}") | ||
169 | 546 | print(f"reference: {the_ppa.archive.reference}") | ||
170 | 547 | print(f"self_link: {the_ppa.archive.self_link}") | ||
171 | 548 | print(f"web_link: {the_ppa.archive.web_link}") | ||
172 | 549 | print(f"description: {the_ppa.description}") | ||
173 | 550 | print(f"has_packages: {the_ppa.has_packages()}") | ||
174 | 551 | print(f"architectures: {'/'.join(the_ppa.architectures)}") | ||
175 | 552 | print(f"dependencies: {','.join(the_ppa.dependencies)}") | ||
176 | 553 | print(f"url: {the_ppa.url}") | ||
177 | 554 | print() | ||
178 | 555 | |||
179 | 556 | except BadRequest as e: | ||
180 | 557 | print(f"Error: (BadRequest) {str(e.content.decode('utf-8'))}") | ||
181 | 558 | except Unauthorized as e: | ||
182 | 559 | print(f"Error: (Unauthorized) {e}") | ||
183 | 560 | |||
184 | 561 | answer = 'x' | ||
185 | 562 | while answer not in ['y', 'n']: | ||
186 | 563 | answer = input('Ready to cleanup (i.e. delete) temporary test PPAs? (y/n) ') | ||
187 | 564 | answer = answer[0].lower() | ||
188 | 565 | |||
189 | 566 | if answer == 'y': | ||
190 | 567 | print(" Cleaning up temporary test PPAs...") | ||
191 | 568 | the_ppa.destroy() | ||
192 | 569 | dep_ppa.destroy() | ||
193 | 570 | print(" ...Done") | ||
194 | diff --git a/scripts/ppa b/scripts/ppa | |||
195 | index 9097004..29413ea 100755 | |||
196 | --- a/scripts/ppa | |||
197 | +++ b/scripts/ppa | |||
198 | @@ -206,6 +206,13 @@ def add_basic_config_options(parser: argparse.ArgumentParser) -> None: | |||
199 | 206 | help="Do not accept or build packages uploaded to the PPA." | 206 | help="Do not accept or build packages uploaded to the PPA." |
200 | 207 | ) | 207 | ) |
201 | 208 | 208 | ||
202 | 209 | # Dependencies | ||
203 | 210 | parser.add_argument( | ||
204 | 211 | '--ppa-dependencies', '--ppa-depends', | ||
205 | 212 | dest="ppa_dependencies", action='store', | ||
206 | 213 | help="The set of other PPAs this PPA should use for satisfying build dependencies." | ||
207 | 214 | ) | ||
208 | 215 | |||
209 | 209 | parser.add_argument( | 216 | parser.add_argument( |
210 | 210 | '--publish', | 217 | '--publish', |
211 | 211 | dest="publish", action='store_true', | 218 | dest="publish", action='store_true', |
212 | @@ -443,11 +450,11 @@ def create_config(lp: Lp, args: argparse.Namespace) -> dict[str, Any]: | |||
213 | 443 | ### Commands ### | 450 | ### Commands ### |
214 | 444 | ################ | 451 | ################ |
215 | 445 | 452 | ||
217 | 446 | def command_create(lp, config): | 453 | def command_create(lp: Lp, config: dict[str, str]) -> int: |
218 | 447 | """Creates a new PPA in Launchpad. | 454 | """Creates a new PPA in Launchpad. |
219 | 448 | 455 | ||
220 | 449 | :param Lp lp: The Launchpad wrapper object. | 456 | :param Lp lp: The Launchpad wrapper object. |
222 | 450 | :param dict config: Configuration param:value map. | 457 | :param dict[str, str] config: Configuration param:value map. |
223 | 451 | :rtype: int | 458 | :rtype: int |
224 | 452 | :returns: Status code OK (0) on success, non-zero on error. | 459 | :returns: Status code OK (0) on success, non-zero on error. |
225 | 453 | """ | 460 | """ |
226 | @@ -480,6 +487,12 @@ def command_create(lp, config): | |||
227 | 480 | if architectures: | 487 | if architectures: |
228 | 481 | the_ppa.set_architectures(architectures) | 488 | the_ppa.set_architectures(architectures) |
229 | 482 | arch_str = ', '.join(the_ppa.architectures) | 489 | arch_str = ', '.join(the_ppa.architectures) |
230 | 490 | |||
231 | 491 | if 'ppa_dependencies' in config: | ||
232 | 492 | # Split value on comma | ||
233 | 493 | ppa_addresses = unpack_to_dict(config.get('ppa_dependencies')) | ||
234 | 494 | the_ppa.set_dependencies(ppa_addresses) | ||
235 | 495 | |||
236 | 483 | else: | 496 | else: |
237 | 484 | the_ppa = Ppa(ppa_name, team_name, description) | 497 | the_ppa = Ppa(ppa_name, team_name, description) |
238 | 485 | arch_str = ', '.join(architectures) | 498 | arch_str = ', '.join(architectures) |
239 | @@ -505,7 +518,7 @@ def command_create(lp, config): | |||
240 | 505 | return 1 | 518 | return 1 |
241 | 506 | 519 | ||
242 | 507 | 520 | ||
244 | 508 | def command_desc(lp, config): | 521 | def command_desc(lp: Lp, config: dict[str, str]) -> int: |
245 | 509 | """Sets the description for a PPA. | 522 | """Sets the description for a PPA. |
246 | 510 | 523 | ||
247 | 511 | :param dict config: Configuration param:value map. | 524 | :param dict config: Configuration param:value map. |
248 | @@ -534,11 +547,11 @@ def command_desc(lp, config): | |||
249 | 534 | return 1 | 547 | return 1 |
250 | 535 | 548 | ||
251 | 536 | 549 | ||
253 | 537 | def command_destroy(lp, config): | 550 | def command_destroy(lp: Lp, config: dict[str, str]) -> int: |
254 | 538 | """Destroys the PPA. | 551 | """Destroys the PPA. |
255 | 539 | 552 | ||
256 | 540 | :param Lp lp: The Launchpad wrapper object. | 553 | :param Lp lp: The Launchpad wrapper object. |
258 | 541 | :param dict config: Configuration param:value map. | 554 | :param dict[str, str] config: Configuration param:value map. |
259 | 542 | :rtype: int | 555 | :rtype: int |
260 | 543 | :returns: Status code OK (0) on success, non-zero on error. | 556 | :returns: Status code OK (0) on success, non-zero on error. |
261 | 544 | """ | 557 | """ |
262 | @@ -554,11 +567,11 @@ def command_destroy(lp, config): | |||
263 | 554 | return 1 | 567 | return 1 |
264 | 555 | 568 | ||
265 | 556 | 569 | ||
267 | 557 | def command_list(lp, config, filter_func=None): | 570 | def command_list(lp: Lp, config: dict[str, str], filter_func=None) -> int: |
268 | 558 | """Lists the PPAs for the user or team. | 571 | """Lists the PPAs for the user or team. |
269 | 559 | 572 | ||
270 | 560 | :param Lp lp: The Launchpad wrapper object. | 573 | :param Lp lp: The Launchpad wrapper object. |
272 | 561 | :param dict config: Configuration param:value map. | 574 | :param dict[str, str] config: Configuration param:value map. |
273 | 562 | :rtype: int | 575 | :rtype: int |
274 | 563 | :returns: Status code OK (0) on success, non-zero on error. | 576 | :returns: Status code OK (0) on success, non-zero on error. |
275 | 564 | """ | 577 | """ |
276 | @@ -592,11 +605,11 @@ def command_list(lp, config, filter_func=None): | |||
277 | 592 | return 1 | 605 | return 1 |
278 | 593 | 606 | ||
279 | 594 | 607 | ||
281 | 595 | def command_exists(lp, config): | 608 | def command_exists(lp: Lp, config: dict[str, str]) -> int: |
282 | 596 | """Checks if the named PPA exists in Launchpad. | 609 | """Checks if the named PPA exists in Launchpad. |
283 | 597 | 610 | ||
284 | 598 | :param Lp lp: The Launchpad wrapper object. | 611 | :param Lp lp: The Launchpad wrapper object. |
286 | 599 | :param dict config: Configuration param:value map. | 612 | :param dict[str, str] config: Configuration param:value map. |
287 | 600 | :rtype: int | 613 | :rtype: int |
288 | 601 | :returns: Status code OK (0) on success, non-zero on error. | 614 | :returns: Status code OK (0) on success, non-zero on error. |
289 | 602 | """ | 615 | """ |
290 | @@ -609,11 +622,11 @@ def command_exists(lp, config): | |||
291 | 609 | return 1 | 622 | return 1 |
292 | 610 | 623 | ||
293 | 611 | 624 | ||
295 | 612 | def command_set(lp, config): | 625 | def command_set(lp: Lp, config: dict[str, str]) -> int: |
296 | 613 | """Sets one or more properties of PPA in Launchpad. | 626 | """Sets one or more properties of PPA in Launchpad. |
297 | 614 | 627 | ||
298 | 615 | :param Lp lp: The Launchpad wrapper object. | 628 | :param Lp lp: The Launchpad wrapper object. |
300 | 616 | :param dict config: Configuration param:value map. | 629 | :param dict[str, str] config: Configuration param:value map. |
301 | 617 | :rtype: int | 630 | :rtype: int |
302 | 618 | :returns: Status code OK (0) on success, non-zero on error. | 631 | :returns: Status code OK (0) on success, non-zero on error. |
303 | 619 | """ | 632 | """ |
304 | @@ -632,6 +645,11 @@ def command_set(lp, config): | |||
305 | 632 | if 'displayname' in config: | 645 | if 'displayname' in config: |
306 | 633 | the_ppa.archive.displayname = config['displayname'] | 646 | the_ppa.archive.displayname = config['displayname'] |
307 | 634 | 647 | ||
308 | 648 | if 'ppa_dependencies' in config: | ||
309 | 649 | # Split value on comma | ||
310 | 650 | ppa_addresses = unpack_to_dict(config.get('ppa_dependencies')) | ||
311 | 651 | the_ppa.set_dependencies(ppa_addresses) | ||
312 | 652 | |||
313 | 635 | if 'publish' in config: | 653 | if 'publish' in config: |
314 | 636 | the_ppa.archive.publish = config.get('publish') | 654 | the_ppa.archive.publish = config.get('publish') |
315 | 637 | 655 | ||
316 | @@ -647,7 +665,7 @@ def command_set(lp, config): | |||
317 | 647 | return 1 | 665 | return 1 |
318 | 648 | 666 | ||
319 | 649 | 667 | ||
321 | 650 | def command_show(lp, config): | 668 | def command_show(lp: Lp, config: dict[str, str]) -> int: |
322 | 651 | """Displays details about the given PPA. | 669 | """Displays details about the given PPA. |
323 | 652 | 670 | ||
324 | 653 | :param Lp lp: The Launchpad wrapper object. | 671 | :param Lp lp: The Launchpad wrapper object. |
325 | @@ -705,11 +723,11 @@ def command_show(lp, config): | |||
326 | 705 | return 1 | 723 | return 1 |
327 | 706 | 724 | ||
328 | 707 | 725 | ||
330 | 708 | def command_status(lp, config): | 726 | def command_status(lp: Lp, config: dict[str, str]) -> int: |
331 | 709 | """Displays current status of the given ppa. | 727 | """Displays current status of the given ppa. |
332 | 710 | 728 | ||
333 | 711 | :param Lp lp: The Launchpad wrapper object. | 729 | :param Lp lp: The Launchpad wrapper object. |
335 | 712 | :param dict config: Configuration param:value map. | 730 | :param dict[str, str] config: Configuration param:value map. |
336 | 713 | :rtype: int | 731 | :rtype: int |
337 | 714 | :returns: Status code OK (0) on success, non-zero on error. | 732 | :returns: Status code OK (0) on success, non-zero on error. |
338 | 715 | """ | 733 | """ |
339 | @@ -729,11 +747,11 @@ def command_status(lp, config): | |||
340 | 729 | return 1 | 747 | return 1 |
341 | 730 | 748 | ||
342 | 731 | 749 | ||
344 | 732 | def command_wait(lp, config): | 750 | def command_wait(lp: Lp, config: dict[str, str]) -> int: |
345 | 733 | """Polls the PPA build status and block until all builds are finished and published. | 751 | """Polls the PPA build status and block until all builds are finished and published. |
346 | 734 | 752 | ||
347 | 735 | :param Lp lp: The Launchpad wrapper object. | 753 | :param Lp lp: The Launchpad wrapper object. |
349 | 736 | :param dict config: Configuration param:value map. | 754 | :param dict[str, str] config: Configuration param:value map. |
350 | 737 | :rtype: int | 755 | :rtype: int |
351 | 738 | :returns: Status code OK (0) on success, non-zero on error. | 756 | :returns: Status code OK (0) on success, non-zero on error. |
352 | 739 | """ | 757 | """ |
353 | @@ -761,11 +779,11 @@ def command_wait(lp, config): | |||
354 | 761 | return 1 | 779 | return 1 |
355 | 762 | 780 | ||
356 | 763 | 781 | ||
358 | 764 | def command_tests(lp, config): | 782 | def command_tests(lp: Lp, config: dict[str, str]) -> int: |
359 | 765 | """Displays testing status for the PPA. | 783 | """Displays testing status for the PPA. |
360 | 766 | 784 | ||
361 | 767 | :param Lp lp: The Launchpad wrapper object. | 785 | :param Lp lp: The Launchpad wrapper object. |
363 | 768 | :param dict config: Configuration param:value map. | 786 | :param dict[str, str] config: Configuration param:value map. |
364 | 769 | :rtype: int | 787 | :rtype: int |
365 | 770 | :returns: Status code OK (0) on success, non-zero on error. | 788 | :returns: Status code OK (0) on success, non-zero on error. |
366 | 771 | """ | 789 | """ |
367 | diff --git a/tests/helpers.py b/tests/helpers.py | |||
368 | index 2329125..29fa151 100644 | |||
369 | --- a/tests/helpers.py | |||
370 | +++ b/tests/helpers.py | |||
371 | @@ -85,6 +85,11 @@ class LaunchpadMock: | |||
372 | 85 | 85 | ||
373 | 86 | class LpServiceMock: | 86 | class LpServiceMock: |
374 | 87 | """A stand-in for the Lp service object.""" | 87 | """A stand-in for the Lp service object.""" |
375 | 88 | ROOT_URL = 'https://mocklaunchpad.net/' | ||
376 | 89 | API_ROOT_URL = 'https://api.mocklaunchpad.net/devel/' | ||
377 | 90 | BUGS_ROOT_URL = 'https://bugs.mocklaunchpad.net/' | ||
378 | 91 | CODE_ROOT_URL = 'https://code.mocklaunchpad.net/' | ||
379 | 92 | |||
380 | 88 | def __init__(self): | 93 | def __init__(self): |
381 | 89 | self.launchpad = LaunchpadMock() | 94 | self.launchpad = LaunchpadMock() |
382 | 90 | 95 | ||
383 | diff --git a/tests/test_scripts_ppa.py b/tests/test_scripts_ppa.py | |||
384 | index a107f92..78aaeb3 100644 | |||
385 | --- a/tests/test_scripts_ppa.py | |||
386 | +++ b/tests/test_scripts_ppa.py | |||
387 | @@ -235,6 +235,14 @@ def test_create_arg_parser_basic_config(command): | |||
388 | 235 | assert args.set_disabled is True | 235 | assert args.set_disabled is True |
389 | 236 | args.set_disabled = False | 236 | args.set_disabled = False |
390 | 237 | 237 | ||
391 | 238 | # Check --ppa-dependencies <PPA[,...]> | ||
392 | 239 | args = parser.parse_args([command, 'test-ppa', '--ppa-dependencies', 'a,b,c']) | ||
393 | 240 | assert args.ppa_dependencies == "a,b,c" | ||
394 | 241 | args.ppa_dependencies = None | ||
395 | 242 | args = parser.parse_args([command, 'test-ppa', '--ppa-depends', 'a,b,c']) | ||
396 | 243 | assert args.ppa_dependencies == "a,b,c" | ||
397 | 244 | args.ppa_dependencies = None | ||
398 | 245 | |||
399 | 238 | # Check --publish | 246 | # Check --publish |
400 | 239 | args = parser.parse_args([command, 'test-ppa', '--publish']) | 247 | args = parser.parse_args([command, 'test-ppa', '--publish']) |
401 | 240 | assert args.publish is True | 248 | assert args.publish is True |
402 | @@ -477,7 +485,8 @@ def test_command_create_with_architectures(monkeypatch, fake_config, architectur | |||
403 | 477 | 485 | ||
404 | 478 | @pytest.mark.xfail(reason="Unimplemented") | 486 | @pytest.mark.xfail(reason="Unimplemented") |
405 | 479 | def test_command_desc(fake_config): | 487 | def test_command_desc(fake_config): |
407 | 480 | assert script.command_desc(fake_config) == 0 | 488 | lp = LpServiceMock() |
408 | 489 | assert script.command_desc(lp, fake_config) == 0 | ||
409 | 481 | # TODO: Assert that if --dry-run specified, there are no actual | 490 | # TODO: Assert that if --dry-run specified, there are no actual |
410 | 482 | # changes requested of launchpad | 491 | # changes requested of launchpad |
411 | 483 | # TODO: Verify the description gets set as expected | 492 | # TODO: Verify the description gets set as expected |
412 | @@ -485,29 +494,33 @@ def test_command_desc(fake_config): | |||
413 | 485 | 494 | ||
414 | 486 | @pytest.mark.xfail(reason="Unimplemented") | 495 | @pytest.mark.xfail(reason="Unimplemented") |
415 | 487 | def test_command_destroy(fake_config): | 496 | def test_command_destroy(fake_config): |
416 | 497 | lp = LpServiceMock() | ||
417 | 488 | # TODO: Create a fake ppa to be destroyed | 498 | # TODO: Create a fake ppa to be destroyed |
419 | 489 | assert script.command_destroy(fake_config) == 0 | 499 | assert script.command_destroy(lp, fake_config) == 0 |
420 | 490 | # TODO: Verify the ppa is requested to be deleted | 500 | # TODO: Verify the ppa is requested to be deleted |
421 | 491 | 501 | ||
422 | 492 | 502 | ||
423 | 493 | @pytest.mark.xfail(reason="Unimplemented") | 503 | @pytest.mark.xfail(reason="Unimplemented") |
424 | 494 | def test_command_list(fake_config): | 504 | def test_command_list(fake_config): |
425 | 505 | lp = LpServiceMock() | ||
426 | 495 | # TODO: Create a fake ppa with contents to be listed | 506 | # TODO: Create a fake ppa with contents to be listed |
428 | 496 | assert script.command_list(fake_config) == 0 | 507 | assert script.command_list(lp, fake_config) == 0 |
429 | 497 | # TODO: Verify the ppa addresses get listed | 508 | # TODO: Verify the ppa addresses get listed |
430 | 498 | 509 | ||
431 | 499 | 510 | ||
432 | 500 | @pytest.mark.xfail(reason="Unimplemented") | 511 | @pytest.mark.xfail(reason="Unimplemented") |
433 | 501 | def test_command_exists(fake_config): | 512 | def test_command_exists(fake_config): |
434 | 513 | lp = LpServiceMock() | ||
435 | 502 | # TODO: Create fake ppa that exists | 514 | # TODO: Create fake ppa that exists |
437 | 503 | assert script.command_exists(fake_config) == 0 | 515 | assert script.command_exists(lp, fake_config) == 0 |
438 | 504 | # TODO: Verify this returns true when the ppa does exist | 516 | # TODO: Verify this returns true when the ppa does exist |
439 | 505 | 517 | ||
440 | 506 | 518 | ||
441 | 507 | @pytest.mark.xfail(reason="Unimplemented") | 519 | @pytest.mark.xfail(reason="Unimplemented") |
442 | 508 | def test_command_not_exists(fake_config): | 520 | def test_command_not_exists(fake_config): |
443 | 521 | lp = LpServiceMock() | ||
444 | 509 | # TODO: Verify this returns true when the ppa does not exist | 522 | # TODO: Verify this returns true when the ppa does not exist |
446 | 510 | assert script.command_exists(fake_config) == 1 | 523 | assert script.command_exists(lp, fake_config) == 1 |
447 | 511 | 524 | ||
448 | 512 | 525 | ||
449 | 513 | @pytest.mark.parametrize('params, expected_ppa_config', [ | 526 | @pytest.mark.parametrize('params, expected_ppa_config', [ |
450 | @@ -569,16 +582,19 @@ def test_command_set_architectures(fake_config, architectures, expected_processo | |||
451 | 569 | 582 | ||
452 | 570 | @pytest.mark.xfail(reason="Unimplemented") | 583 | @pytest.mark.xfail(reason="Unimplemented") |
453 | 571 | def test_command_show(fake_config): | 584 | def test_command_show(fake_config): |
455 | 572 | assert script.command_show(fake_config) == 0 | 585 | lp = LpServiceMock() |
456 | 586 | assert script.command_show(lp, fake_config) == 0 | ||
457 | 573 | 587 | ||
458 | 574 | 588 | ||
459 | 575 | @pytest.mark.xfail(reason="Unimplemented") | 589 | @pytest.mark.xfail(reason="Unimplemented") |
460 | 576 | def test_command_status(fake_config): | 590 | def test_command_status(fake_config): |
462 | 577 | assert script.command_status(fake_config) == 0 | 591 | lp = LpServiceMock() |
463 | 592 | assert script.command_status(lp, fake_config) == 0 | ||
464 | 578 | # TODO: Capture stdout and compare with expected | 593 | # TODO: Capture stdout and compare with expected |
465 | 579 | 594 | ||
466 | 580 | 595 | ||
467 | 581 | @pytest.mark.xfail(reason="Unimplemented") | 596 | @pytest.mark.xfail(reason="Unimplemented") |
468 | 582 | def test_command_wait(fake_config): | 597 | def test_command_wait(fake_config): |
469 | 598 | lp = LpServiceMock() | ||
470 | 583 | # TODO: Set wait period to 1 sec | 599 | # TODO: Set wait period to 1 sec |
472 | 584 | assert script.command_wait(fake_config) == 0 | 600 | assert script.command_wait(lp, fake_config) == 0 |
Example output for the smoke test:
$ python3 -m ppa.ppa ####### ####### ##### ####### ####### #####
#######
## Ppa class smoke test ##
#######
setting desc to 'This is a testing PPA and can be deleted'
desc is now 'This is a testing PPA and can be deleted'
name: test-ppa-dxtefi test-ppa- dxtefi ppa-dxtefi ubuntu/ test-ppa- dxtefi /api.qastaging. launchpad. net/devel/ ~bryce/ +archive/ ubuntu/ test-ppa- dxtefi /qastaging. launchpad. net/~bryce/ +archive/ ubuntu/ test-ppa- dxtefi ubuntu/ dependency- ppa-dxtefi /qastaging. launchpad. net/~bryce/ +archive/ ubuntu/ test-ppa- dxtefi
address: ppa:bryce/
str(ppa): bryce/test-
reference: ~bryce/
self_link: https:/
web_link: https:/
description: This is a testing PPA and can be deleted
has_packages: False
architectures: amd64/arm64
dependencies: ~bryce/
url: https:/
Ready to cleanup (i.e. delete) temporary PPAs? (y/n) y
...Cleaning up test ppa...