Merge ppa-dev-tools:fix-all-proposed-triggers into ppa-dev-tools:main

Proposed by Bryce Harrington
Status: Merged
Merge reported by: Bryce Harrington
Merged at revision: cc47e76902f940fa9a2b1dcaa97795df9390d96c
Proposed branch: ppa-dev-tools:fix-all-proposed-triggers
Merge into: ppa-dev-tools:main
Diff against target: 278 lines (+127/-28)
4 files modified
ppa/trigger.py (+2/-2)
scripts/ppa (+6/-5)
tests/test_result.py (+90/-4)
tests/test_trigger.py (+29/-17)
Reviewer Review Type Date Requested Status
Athos Ribeiro (community) Approve
PpaDevTools Developers Pending
Canonical Server Reporter Pending
Review via email: mp+463178@code.launchpad.net

Description of the change

The fix for LP: #2044608 is quite simple, however test coverage of the code in question was missing. This branch provides those tests both for the trigger-related functionality in the Results class, and for the Trigger class itself.

Additionally, this includes a fix for a recent regression in a branch that improved exit codes. A standard python exit code documented in the os module turns out to be platform-specific, and Linux is not one of those platforms... Without this fix, the command line examples will fail.

Anyway, testing is as usual:
  $ make check
or:
  $ pytest-3

To verify the CLI operation live, I ran against seb's PPA:
  $ ./scripts/ppa tests -L ppa:seb128/update-manager-sru -a amd64 -p distro-info

Try it with and without -L, and with and without --show-rdepends. It should show the line for the all-proposed links in all cases.

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

LGTM. Thanks, Bryce :)

It seems this branch needs to be rebased on the main branch though (I reviewed and tested these changes after a local rebase).

I no longer se duplicated entries. Instead, I see the all-proposed URLs as intended.

And thanks for writing all those tests.

I am also seeing some issues (unrelated to the changes proposed here):

- The UTF-8 characters seem to be broken here (I am on noble). I could find no good past version of the package in the development history, so I suppose this is due to some new/inconsistent package in my system. Once it is safe to pull the latest noble changes in, I will test again and report issues as needed.
- I see 4 failing tests when running the test suite. I am unsure if this is related to the bullet point above, but git-bisect shows me that 5d7cbc9 is the first "bad" revision here. I suppose they are due to the "file:///home/bryce/src/PpaDevTools/" hardcoded entries in the test suite.
- Finally, lately I have been seeing error messages when running the `tests` sub-command. These does not seem to affect the subcommand usability, but the error messages are always present. They look like this:

Error: Could not retrieve data from https://autopkgtest.ubuntu.com/results/autopkgtest-focal-athos-ribeiro-phpmyadmin-php83/?format=plain: HTTP Error 404: Not FoundError: Could not retrieve data from https://autopkgtest.ubuntu.com/results/autopkgtest-jammy-athos-ribeiro-phpmyadmin-php83/?format=plain: HTTP Error 404: Not FoundError: Could not retrieve data from https://autopkgtest.ubuntu.com/results/autopkgtest-mantic-athos-ribeiro-phpmyadmin-php83/?format=plain: HTTP Error 404: Not FoundError: Could not retrieve data from https://autopkgtest.ubuntu.com/results/autopkgtest-trusty-athos-ribeiro-phpmyadmin-php83/?format=plain: HTTP Error 404: Not FoundError: Could not retrieve data from https://autopkgtest.ubuntu.com/results/autopkgtest-xenial-athos-ribeiro-phpmyadmin-php83/?format=plain: HTTP Error 404: Not FoundError: Could not retrieve data from https://autopkgtest.ubuntu.com/results/autopkgtest-bionic-athos-ribeiro-phpmyadmin-php83/?f

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

Hi Athos, thanks for the review.

Yes both those issues are resolved by the other MP up for review:
  https://code.launchpad.net/~bryce/ppa-dev-tools/+git/ppa-dev-tools-1/+merge/462844

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

Landed:

Writing objects: 100% (24/24), 6.12 KiB | 3.06 MiB/s, done.
Total 24 (delta 17), reused 0 (delta 0), pack-reused 0
To git+ssh://git.launchpad.net/ppa-dev-tools
   023e7f3..5dc684c main -> main

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/ppa/trigger.py b/ppa/trigger.py
2index 4345508..8f1680a 100755
3--- a/ppa/trigger.py
4+++ b/ppa/trigger.py
5@@ -173,8 +173,8 @@ def show_triggers(package, version, triggers, status,
6 title = ''
7 if show_trigger_names:
8 title = trigger.test_package
9- url = trigger.action_url + "&all-proposed=1"
10- print(f" + {trigger.package}@{trigger.arch}: {url}šŸ’")
11+ url = trigger.action_url + "&all-proposed=1"
12+ print(f" + {title}@{trigger.arch}: {url}šŸ’")
13
14 else:
15 for trigger in triggers:
16diff --git a/scripts/ppa b/scripts/ppa
17index 8484979..2172740 100755
18--- a/scripts/ppa
19+++ b/scripts/ppa
20@@ -94,6 +94,7 @@ from ppa.trigger import get_triggers, show_triggers
21 import ppa.debug
22 from ppa.debug import dbg, warn, error
23
24+EX_NOTFOUND = 127
25 EX_KEYBOARD_INTERRUPT = 130
26
27
28@@ -804,7 +805,7 @@ def command_set(lp: Lp, config: dict[str, str]) -> int:
29 return os.EX_NOPERM
30 except PpaNotFoundError as e:
31 print(e)
32- return os.EX_NOTFOUND
33+ return EX_NOTFOUND
34 except ValueError as e:
35 print(f"Error: {e}")
36 return os.EX_USAGE
37@@ -868,7 +869,7 @@ def command_show(lp: Lp, config: dict[str, str]) -> int:
38 return os.EX_OK
39 except PpaNotFoundError as e:
40 print(e)
41- return os.EX_NOTFOUND
42+ return EX_NOTFOUND
43 except KeyboardInterrupt:
44 return EX_KEYBOARD_INTERRUPT
45 print("Unhandled error")
46@@ -939,7 +940,7 @@ def command_wait(lp: Lp, config: dict[str, str]) -> int:
47 return os.EX_OK
48 except PpaNotFoundError as e:
49 print(e)
50- return os.EX_NOTFOUND
51+ return EX_NOTFOUND
52 except ValueError as e:
53 print(f"Error: {e}")
54 return os.EX_USAGE
55@@ -967,7 +968,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
56 apt_repository = Repository(cache_dir=local_dists_path)
57 except FileNotFoundError as e:
58 error(f'Missing checkout\n{LOCAL_REPOSITORY_MIRRORING_DIRECTIONS}: {e}')
59- return os.EX_NOTFOUND
60+ return EX_NOTFOUND
61
62 releases = config.get('releases', None)
63 if releases is None:
64@@ -989,7 +990,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
65 the_ppa = get_ppa(lp, config)
66 if not the_ppa.exists():
67 error(f"PPA {the_ppa.name} does not exist for user {the_ppa.owner_name}")
68- return os.EX_NOTFOUND
69+ return EX_NOTFOUND
70
71 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)
72 if isinstance(architectures, str):
73diff --git a/tests/data/results-chrony-armhf.log.gz b/tests/data/results-chrony-armhf.log.gz
74new file mode 100644
75index 0000000..1813920
76Binary files /dev/null and b/tests/data/results-chrony-armhf.log.gz differ
77diff --git a/tests/test_result.py b/tests/test_result.py
78index c7e2510..a3597af 100644
79--- a/tests/test_result.py
80+++ b/tests/test_result.py
81@@ -17,7 +17,6 @@ import time
82 import gzip
83 import json
84 import pytest
85-# from mock import Mock
86
87 sys.path.insert(0, os.path.realpath(
88 os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")))
89@@ -126,10 +125,49 @@ def test_log(tmp_path):
90 assert result.log == "abcde"
91
92
93-def test_triggers():
94+@pytest.mark.parametrize('filename, expected_triggers', [
95+ ('results-six-s390x.log.gz', ['pygobject/3.42.2-2', 'six/1.16.0-4']),
96+ ('results-chrony-armhf.log.gz', ['dpkg/1.22.6ubuntu5'])
97+])
98+def test_triggers(filename, expected_triggers):
99 """Checks that autopkgtest triggers can be extracted from test result logs."""
100- result = Result(f"file://{DATA_DIR}/results-six-s390x.log.gz", None, None, None, None)
101- assert result.triggers == ['pygobject/3.42.2-2', 'six/1.16.0-4']
102+ result = Result(f"file://{DATA_DIR}/{filename}", None, None, None, None)
103+ assert result.triggers == expected_triggers
104+
105+
106+@pytest.mark.parametrize('triggers, name, expected', [
107+ ([], None, []),
108+ (['x/1'], None, ["Trigger(package='x', version='1', arch=None, series=None, ppa=None, test_package='x')"]),
109+ (['x/1'], 'x', ["Trigger(package='x', version='1', arch=None, series=None, ppa=None, test_package='x')"]),
110+ pytest.param(
111+ ['x/1'], 'z', [],
112+ marks=pytest.mark.xfail(reason="Unimplemented")
113+ ),
114+ (
115+ ['x/1', 'y/2', 'z/3'],
116+ None,
117+ [
118+ "Trigger(package='x', version='1', arch=None, series=None, ppa=None, test_package='x')",
119+ "Trigger(package='y', version='2', arch=None, series=None, ppa=None, test_package='y')",
120+ "Trigger(package='z', version='3', arch=None, series=None, ppa=None, test_package='z')"
121+ ]
122+ ),
123+ pytest.param(
124+ ['x/1', 'y/2', 'z/3'],
125+ 'y',
126+ [
127+ "Trigger(package='y', version='2', arch=None, series=None, ppa=None, test_package='y')"
128+ ],
129+ marks=pytest.mark.xfail(reason="Unimplemented"),
130+ ),
131+])
132+def test_get_triggers(monkeypatch, triggers, name, expected):
133+ """Checks retrieval of Trigger objects from autopkgtest results."""
134+ result = Result('url', None, None, None, None)
135+ monkeypatch.setattr(Result, "triggers", triggers)
136+
137+ triggers = result.get_triggers(name)
138+ assert [repr(t) for t in triggers] == expected
139
140
141 @pytest.mark.parametrize('log_text, subtest_name, expected', [
142@@ -212,6 +250,54 @@ def test_get_subtests(tmp_path, log_text: str, subtest_name: str, expected: dict
143 assert {s.desc: s.status for s in subtests} == expected
144
145
146+@pytest.mark.parametrize('subtest_states, error_message, expected', [
147+ ([], None, 'PASS'),
148+ (['PASS'], None, 'PASS'),
149+ (['FAIL'], None, 'FAIL'),
150+ (['PASS', 'FAIL'], None, 'FAIL'),
151+ (['FAIL', 'PASS'], None, 'FAIL'),
152+ (['PASS', 'PASS', 'PASS'], None, 'PASS'),
153+ (['PASS'], 'x', 'BAD'),
154+ (['FAIL'], 'x', 'BAD'),
155+])
156+def test_status(monkeypatch, subtest_states, error_message, expected):
157+ """Checks retrieval of status from autopkgtest results."""
158+ result = Result("file://tmp/x", None, None, None, None)
159+ result.error_message = error_message
160+
161+ # Add subtests with given states
162+ subtests = [Subtest(f"... {state}...") for state in subtest_states]
163+ monkeypatch.setattr(Result, "get_subtests", lambda x: subtests)
164+
165+ assert result.status == expected
166+
167+
168+@pytest.mark.parametrize('status, expected', [
169+ ('PASS', "āœ…"),
170+ ('FAIL', "āŒ"),
171+ ('BAD', "ā›”"),
172+])
173+def test_status_icon(monkeypatch, status, expected):
174+ """Checks generation of correct icon based on autopkgtest results."""
175+ result = Result("file://tmp/x", None, None, None, None)
176+ monkeypatch.setattr(Result, "status", status)
177+
178+ assert result.status_icon == expected
179+
180+
181+@pytest.mark.parametrize('status, expected_exception', [
182+ (None, KeyError),
183+ ('x', KeyError),
184+])
185+def test_status_icon_error(monkeypatch, status, expected_exception):
186+ """Checks generation of correct icon based on autopkgtest results."""
187+ result = Result("file://tmp/x", None, None, None, None)
188+ monkeypatch.setattr(Result, "status", status)
189+
190+ with pytest.raises(expected_exception):
191+ print(result.status_icon)
192+
193+
194 @pytest.mark.parametrize('log_text, series, arch, source, expected_dict', [
195 (
196 # Empty/invalid log should return empty triggers & subtests.
197diff --git a/tests/test_trigger.py b/tests/test_trigger.py
198index 1441b85..5e42f71 100644
199--- a/tests/test_trigger.py
200+++ b/tests/test_trigger.py
201@@ -137,40 +137,52 @@ def test_get_triggers(params, expected):
202
203 @pytest.mark.parametrize('triggers, params, expected_in_stdout', [
204 (
205- # Basic function parameters
206+ # Basic function parameters, no triggers
207 [],
208- {'package': 'a', 'version': 'b', 'status': 'c'},
209- [" - Source ", "source/a/b", ": c"]
210+ {'package': 'pkg', 'version': '123', 'status': 'OK'},
211+ [" - Source \x1b]8;;https://launchpad.net/ubuntu/+source/pkg/123\x1b\\pkg/123\x1b]8;;\x1b\\: OK\n"]
212 ),
213 (
214- # Specified trigger
215- [('a', '1')],
216- {'show_trigger_urls': True},
217- [" + @amd64: ", "trigger=a%2F1"]
218+ # Specified trigger (clickable)
219+ [('pkg', '123', 'i386')],
220+ {'show_trigger_urls': False},
221+ [
222+ "&trigger=pkg%2F123&ppa=z\x1b\\Trigger basic @i386ā™»ļø \x1b]8;;\x1b",
223+ "&trigger=pkg%2F123&ppa=z&all-proposed=1\x1b\\Trigger all-proposed @i386šŸ’\x1b]8;;\x1b\\\n"
224+ ]
225 ),
226 (
227- # Display trigger URLS
228- [('a', '1')],
229+ # Specified trigger (display plain URLs)
230+ [('pkg', '123', 'i386')],
231 {'show_trigger_urls': True},
232- ["https://autopkgtest.ubuntu.com/request.cgi?release=y&package=x&arch=amd64&trigger=a%2F1&ppa=z"]
233+ [
234+ "&trigger=pkg%2F123&ppa=zā™»ļø \n",
235+ "&trigger=pkg%2F123&ppa=z&all-proposed=1šŸ’\n"
236+ ]
237 ),
238 (
239 # Display names of packages in trigger lines
240- [('a', '1')],
241+ [('pkg', '123', 'i386')],
242 {'show_trigger_names': True},
243- ["Trigger basic", "Trigger all-proposed"]
244+ ["Trigger basic x@i386ā™»ļø ", "Trigger all-proposed x@i386šŸ’"]
245+ ),
246+ (
247+ # Omit package names if specified
248+ [('pkg', '123', 'i386')],
249+ {'show_trigger_names': False},
250+ ["Trigger basic @i386", "Trigger all-proposed @i386"]
251 ),
252 (
253 # Display names of packages in trigger lines when trigger URLs are shown
254- [('a', '1')],
255+ [('pkg', '123', 'i386')],
256 {'show_trigger_urls': True, 'show_trigger_names': True},
257- ["a@amd64: https:", "trigger=a%2F1"]
258+ ["x@i386: https:", "trigger=pkg%2F123"]
259 ),
260 (
261 # Multiple triggers
262- [('a', '1'), ('b', '2')],
263+ [('pkg', '123', 'i386'), ('lib', '321', 'i386')],
264 {'show_trigger_urls': True},
265- ["trigger=a%2F1", "trigger=b%2F2"]
266+ ["trigger=pkg%2F123", "trigger=lib%2F321"]
267 ),
268 ])
269 def test_show_triggers(capfd, triggers, params, expected_in_stdout):
270@@ -179,7 +191,7 @@ def test_show_triggers(capfd, triggers, params, expected_in_stdout):
271 params.setdefault('triggers', [])
272 params.setdefault('status', 'x')
273 for t in triggers:
274- params['triggers'].append(Trigger(t[0], t[1], 'amd64', 'y', 'z', 'x'))
275+ params['triggers'].append(Trigger(t[0], t[1], t[2], 'y', 'z', 'x'))
276 show_triggers(**params)
277 out, err = capfd.readouterr()
278 print(out)

Subscribers

People subscribed via source and target branches

to all changes: