Merge ppa-dev-tools:command-tests into ppa-dev-tools:main

Proposed by Bryce Harrington
Status: Merged
Merge reported by: Bryce Harrington
Merged at revision: 8abb18213477cb686676621a204a83a436235c57
Proposed branch: ppa-dev-tools:command-tests
Merge into: ppa-dev-tools:main
Diff against target: 268 lines (+164/-8)
7 files modified
AUTHORS.md (+1/-0)
ppa/constants.py (+20/-0)
ppa/io.py (+33/-0)
ppa/job.py (+31/-5)
ppa/ppa.py (+3/-3)
scripts/ppa (+44/-0)
tests/test_io.py (+32/-0)
Reviewer Review Type Date Requested Status
Athos Ribeiro (community) Approve
Canonical Server Reporter Pending
Review via email: mp+428951@code.launchpad.net

Description of the change

Adds the command 'ppa tests' which for now just reports the running and waiting tests. (Later, Results will be added as well.)

Since running and waiting info is only available while a PPA's tests are in process, to check the functionality you'll need to queue up some fresh tests for your favorite PPA; e.g.:

    $ lp-test-ppa ppa:bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 -r kinetic --showurl

Trigger several, then wait a few minutes and they should start appearing in output:

    $ ./scripts/ppa tests ppa:bryce/dovecot-merge-v1e2.3.19.1adfsg1-2
Running:
    time pkg release arch ppa trigger
    10 dovecot kinetic armhf bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 dovecot/1:2.3.19.1+dfsg1-2ubuntu2~kinetic1
    120 dovecot kinetic armhf bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 dovecot/1:2.3.19.1+dfsg1-2ubuntu2~kinetic1
    270 dovecot kinetic amd64 bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 dovecot/1:2.3.19.1+dfsg1-2ubuntu2~kinetic1
    30 dovecot kinetic arm64 bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 dovecot/1:2.3.19.1+dfsg1-2ubuntu2~kinetic1
    30 dovecot kinetic ppc64el bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 dovecot/1:2.3.19.1+dfsg1-2ubuntu2~kinetic1
Waiting:
    Q-num pkg release arch ppa trigger
    1 dovecot kinetic s390x bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 dovecot/1:2.3.19.1+dfsg1-2ubuntu2~kinetic1
    1 dovecot kinetic armhf bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 dovecot/1:2.3.19.1+dfsg1-2ubuntu2~kinetic1
    1 dovecot kinetic amd64 bryce/dovecot-merge-v1e2.3.19.1adfsg1-2 dovecot/1:2.3.19.1+dfsg1-2ubuntu2~kinetic1

To post a comment you must log in.
Revision history for this message
Athos Ribeiro (athos-ribeiro) wrote :

LGTM! Thanks, Bryce.

I included a couple inline nitpicks. As usual, feel free to ignore some or all of them. This looks good as is :)

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

Thanks for the review. Updated, merged and landed:

$ git push origin main
Total 0 (delta 0), reused 0 (delta 0)
To git+ssh://git.launchpad.net/ppa-dev-tools
   8fcdc4f..84609b3 main -> main

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/AUTHORS.md b/AUTHORS.md
index 9712ae3..6138e91 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -1 +1,2 @@
1Bryce Harrington <bryce@canonical.com>1Bryce Harrington <bryce@canonical.com>
2Athos Ribeiro <athos.ribeiro@canonical.com>
diff --git a/ppa/constants.py b/ppa/constants.py
2new file mode 1006443new file mode 100644
index 0000000..f6e7b01
--- /dev/null
+++ b/ppa/constants.py
@@ -0,0 +1,20 @@
1#!/usr/bin/env python3
2# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
3
4# Copyright (C) 2022 Authors
5#
6# Released under GNU GPLv2 or later, read the file 'LICENSE.GPLv2+' for
7# more information.
8#
9# Authors:
10# Bryce Harrington <bryce@canonical.com>
11
12"""Global constants"""
13
14ARCHES_ALL = ["amd64", "arm64", "armhf", "armel", "i386", "powerpc", "ppc64el", "s390x", "riscv64"]
15ARCHES_PPA = ["amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el", "s390x"]
16ARCHES_PPA_EXTRA = ["riscv64"]
17ARCHES_AUTOPKGTEST = ["amd64", "arm64", "armhf", "i386", "ppc64el", "s390x"]
18
19URL_LPAPI = "https://api.launchpad.net/devel"
20URL_AUTOPKGTEST = "https://autopkgtest.ubuntu.com"
diff --git a/ppa/io.py b/ppa/io.py
0new file mode 10064421new file mode 100644
index 0000000..bef1fa0
--- /dev/null
+++ b/ppa/io.py
@@ -0,0 +1,33 @@
1#!/usr/bin/env python3
2# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
3
4# Copyright (C) 2022 Authors
5#
6# Released under GNU GPLv2 or later, read the file 'LICENSE.GPLv2+' for
7# more information.
8#
9# Authors:
10# Bryce Harrington <bryce@canonical.com>
11
12"""Utilities for reading input and writing output to external locations."""
13
14import sys
15import urllib.request
16
17
18def open_url(url, desc="data"):
19 """Opens a remote URL for reading.
20
21 :rtype: urllib.request.Request
22 :returns: A request object for the stream to read from, or None on error.
23 """
24 request = urllib.request.Request(url)
25 request.add_header('Cache-Control', 'max-age=0')
26 try:
27 return urllib.request.urlopen(request)
28 except urllib.error.HTTPError as e:
29 # 401 here means nothing is published or ran yet.
30 # This is a rather common case, so skip mention of it
31 if e.code != 401:
32 sys.stderr.write(f"Error: Could not retrieve {desc} from {url}: {e}")
33 return None
diff --git a/ppa/job.py b/ppa/job.py
index 61d6fb2..2ae06ba 100755
--- a/ppa/job.py
+++ b/ppa/job.py
@@ -14,10 +14,7 @@
14from typing import Iterator14from typing import Iterator
15import json15import json
1616
17# Global constants17from .constants import URL_AUTOPKGTEST
18ARCHES = ["amd64", "s390x", "ppc64el", "arm64", "armhf", "riscv64"]
19URL_LPAPI = "https://api.launchpad.net/devel"
20URL_AUTOPKGTEST = "https://autopkgtest.ubuntu.com"
2118
2219
23class Job:20class Job:
@@ -142,6 +139,36 @@ def get_waiting(response, series=None, ppa=None) -> Iterator[Job]:
142 yield job139 yield job
143140
144141
142def show_running(jobs):
143 """Prints the active (running and waiting) tests"""
144 rformat = " %-8s %-40s %-8s %-8s %-40s %s"
145
146 n = 0
147 for n, e in enumerate(jobs, start=1):
148 if n == 1:
149 print("Running:")
150 ppa_str = ','.join(e.ppas)
151 trigger_str = ','.join(e.triggers)
152 print(rformat % ("time", "pkg", "release", "arch", "ppa", "trigger"))
153 print(rformat % (str(e.submit_time), e.source_package, e.series, e.arch, ppa_str, trigger_str))
154 if n == 0:
155 print("Running: (none)")
156
157
158def show_waiting(jobs):
159 """Prints the active (running and waiting) tests"""
160 rformat = " %-8s %-40s %-8s %-8s %-40s %s"
161
162 n = 0
163 for n, e in enumerate(jobs, start=1):
164 if n == 1:
165 print("Waiting:")
166 print(rformat % ("Q-num", "pkg", "release", "arch", "ppa", "trigger"))
167 print(rformat % (e.number, e.source_package, e.series, e.arch, ','.join(e.ppas), ','.join(e.triggers)))
168 if n == 0:
169 print("Waiting: (none)")
170
171
145if __name__ == "__main__":172if __name__ == "__main__":
146 import os173 import os
147 from urllib.request import urlopen174 from urllib.request import urlopen
@@ -180,4 +207,3 @@ if __name__ == "__main__":
180 response = urlopen(f"file://{root_dir}/tests/data/queues-20220822.json")207 response = urlopen(f"file://{root_dir}/tests/data/queues-20220822.json")
181 for job in get_waiting(response, 'kinetic', ppa):208 for job in get_waiting(response, 'kinetic', ppa):
182 print(job)209 print(job)
183
diff --git a/ppa/ppa.py b/ppa/ppa.py
index 62e0d58..0b90fd6 100755
--- a/ppa/ppa.py
+++ b/ppa/ppa.py
@@ -12,6 +12,8 @@ from textwrap import indent
12from functools import lru_cache12from functools import lru_cache
13from lazr.restfulclient.errors import BadRequest, NotFound13from lazr.restfulclient.errors import BadRequest, NotFound
1414
15from .constants import ARCHES_PPA
16
15def ppa_address_split(ppa_address, default_team='me'):17def ppa_address_split(ppa_address, default_team='me'):
16 """Parse an address for a ppa into its team and name components18 """Parse an address for a ppa into its team and name components
17 """19 """
@@ -52,8 +54,6 @@ class Ppa:
52 This object proxies a PPA, allowing lazy initialization and caching54 This object proxies a PPA, allowing lazy initialization and caching
53 of data from the remote.55 of data from the remote.
54 """56 """
55 # TODO: May need to load this from a (cached?) query
56 ALL_ARCHITECTURES = [ 'amd64', 'arm64', 'armel', 'armhf', 'i386', 'powerpc', 'ppc64el', 's390x']
57 def __init__(self, ppa_name, team_name, ppa_description=None, service=None):57 def __init__(self, ppa_name, team_name, ppa_description=None, service=None):
58 """Initializes a new Ppa object for a given PPA58 """Initializes a new Ppa object for a given PPA
5959
@@ -145,7 +145,7 @@ class Ppa:
145 def architectures(self):145 def architectures(self):
146 return [ proc.name for proc in self.archive.processors ]146 return [ proc.name for proc in self.archive.processors ]
147147
148 def set_architectures(self, architectures=ALL_ARCHITECTURES):148 def set_architectures(self, architectures=ARCHES_PPA):
149 assert self._service149 assert self._service
150 uri_base = "https://api.launchpad.net/devel/+processors/{}"150 uri_base = "https://api.launchpad.net/devel/+processors/{}"
151 procs = [ uri_base.format(arch) for arch in architectures ]151 procs = [ uri_base.format(arch) for arch in architectures ]
diff --git a/scripts/ppa b/scripts/ppa
index 1697999..56ff827 100755
--- a/scripts/ppa
+++ b/scripts/ppa
@@ -62,6 +62,15 @@ if '__file__' in globals():
62 os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")))62 os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")))
6363
64from ppa._version import __version__64from ppa._version import __version__
65from ppa.constants import URL_AUTOPKGTEST
66from ppa.io import open_url
67from ppa.job import (
68 Job,
69 get_waiting,
70 show_waiting,
71 get_running,
72 show_running
73)
65from ppa.lp import Lp74from ppa.lp import Lp
66from ppa.ppa import Ppa75from ppa.ppa import Ppa
67from ppa.ppa_group import PpaGroup, PpaAlreadyExists76from ppa.ppa_group import PpaGroup, PpaAlreadyExists
@@ -440,6 +449,40 @@ def command_wait(lp, config):
440 return 1449 return 1
441450
442451
452def command_tests(lp, config):
453 """Displays testing status for the PPA.
454
455 :param Lp lp: The Launchpad wrapper object.
456 :param dict config: Configuration param:value map.
457 :rtype: Int
458 :returns: Status code 0 on success, non-zero on error.
459 """
460 if not lp:
461 return 1
462
463 try:
464 ppa = get_ppa(lp, config)
465 target = "%s/%s" % (ppa.team_name, ppa.name)
466 release = 'kinetic'
467
468 # Running Queue
469 response = open_url(f"{URL_AUTOPKGTEST}/static/running.json", "running autopkgtests")
470 if response:
471 show_running(sorted(get_running(response, series=release, ppa=target),
472 key=lambda k: str(k.submit_time)))
473
474 # Waiting Queue
475 response = open_url(f"{URL_AUTOPKGTEST}/queues.json", "waiting autopkgtests")
476 if response:
477 show_waiting(get_waiting(response, series=release, ppa=target))
478
479 return 0
480 except KeyboardInterrupt:
481 return 2
482 print("Unhandled error")
483 return 1
484
485
443COMMANDS = {486COMMANDS = {
444 'create': (command_create, None),487 'create': (command_create, None),
445 'desc': (command_desc, None),488 'desc': (command_desc, None),
@@ -447,6 +490,7 @@ COMMANDS = {
447 'list': (command_list, None),490 'list': (command_list, None),
448 'show': (command_show, None),491 'show': (command_show, None),
449 'status': (command_status, None),492 'status': (command_status, None),
493 'tests': (command_tests, None),
450 'wait': (command_wait, None),494 'wait': (command_wait, None),
451 }495 }
452496
diff --git a/tests/test_io.py b/tests/test_io.py
453new file mode 100644497new file mode 100644
index 0000000..fce6611
--- /dev/null
+++ b/tests/test_io.py
@@ -0,0 +1,32 @@
1#!/usr/bin/env python3
2# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
3
4# Author: Bryce Harrington <bryce@canonical.com>
5#
6# Copyright (C) 2022 Bryce W. Harrington
7#
8# Released under GNU GPLv2 or later, read the file 'LICENSE.GPLv2+' for
9# more information.
10
11"""Tests for utilities used to read and write data externally."""
12
13import os
14import sys
15import urllib
16import pytest
17
18sys.path.insert(0, os.path.realpath(
19 os.path.join(os.path.dirname(__file__), "..")))
20
21from ppa.io import open_url
22
23
24def test_open_url(tmp_path):
25 """Checks that the open_url() object reads from a valid URL."""
26 f = tmp_path / "open_url.txt"
27 f.write_text("abcde")
28
29 request = open_url(f"file://{f}")
30 assert request
31 assert type(request) == urllib.response.addinfourl
32 assert request.read().decode() == 'abcde'

Subscribers

People subscribed via source and target branches

to all changes: