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
diff --git a/ppa/trigger.py b/ppa/trigger.py
index 4345508..8f1680a 100755
--- a/ppa/trigger.py
+++ b/ppa/trigger.py
@@ -173,8 +173,8 @@ def show_triggers(package, version, triggers, status,
173 title = ''173 title = ''
174 if show_trigger_names:174 if show_trigger_names:
175 title = trigger.test_package175 title = trigger.test_package
176 url = trigger.action_url + "&all-proposed=1"176 url = trigger.action_url + "&all-proposed=1"
177 print(f" + {trigger.package}@{trigger.arch}: {url}šŸ’")177 print(f" + {title}@{trigger.arch}: {url}šŸ’")
178178
179 else:179 else:
180 for trigger in triggers:180 for trigger in triggers:
diff --git a/scripts/ppa b/scripts/ppa
index 8484979..2172740 100755
--- a/scripts/ppa
+++ b/scripts/ppa
@@ -94,6 +94,7 @@ from ppa.trigger import get_triggers, show_triggers
94import ppa.debug94import ppa.debug
95from ppa.debug import dbg, warn, error95from ppa.debug import dbg, warn, error
9696
97EX_NOTFOUND = 127
97EX_KEYBOARD_INTERRUPT = 13098EX_KEYBOARD_INTERRUPT = 130
9899
99100
@@ -804,7 +805,7 @@ def command_set(lp: Lp, config: dict[str, str]) -> int:
804 return os.EX_NOPERM805 return os.EX_NOPERM
805 except PpaNotFoundError as e:806 except PpaNotFoundError as e:
806 print(e)807 print(e)
807 return os.EX_NOTFOUND808 return EX_NOTFOUND
808 except ValueError as e:809 except ValueError as e:
809 print(f"Error: {e}")810 print(f"Error: {e}")
810 return os.EX_USAGE811 return os.EX_USAGE
@@ -868,7 +869,7 @@ def command_show(lp: Lp, config: dict[str, str]) -> int:
868 return os.EX_OK869 return os.EX_OK
869 except PpaNotFoundError as e:870 except PpaNotFoundError as e:
870 print(e)871 print(e)
871 return os.EX_NOTFOUND872 return EX_NOTFOUND
872 except KeyboardInterrupt:873 except KeyboardInterrupt:
873 return EX_KEYBOARD_INTERRUPT874 return EX_KEYBOARD_INTERRUPT
874 print("Unhandled error")875 print("Unhandled error")
@@ -939,7 +940,7 @@ def command_wait(lp: Lp, config: dict[str, str]) -> int:
939 return os.EX_OK940 return os.EX_OK
940 except PpaNotFoundError as e:941 except PpaNotFoundError as e:
941 print(e)942 print(e)
942 return os.EX_NOTFOUND943 return EX_NOTFOUND
943 except ValueError as e:944 except ValueError as e:
944 print(f"Error: {e}")945 print(f"Error: {e}")
945 return os.EX_USAGE946 return os.EX_USAGE
@@ -967,7 +968,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
967 apt_repository = Repository(cache_dir=local_dists_path)968 apt_repository = Repository(cache_dir=local_dists_path)
968 except FileNotFoundError as e:969 except FileNotFoundError as e:
969 error(f'Missing checkout\n{LOCAL_REPOSITORY_MIRRORING_DIRECTIONS}: {e}')970 error(f'Missing checkout\n{LOCAL_REPOSITORY_MIRRORING_DIRECTIONS}: {e}')
970 return os.EX_NOTFOUND971 return EX_NOTFOUND
971972
972 releases = config.get('releases', None)973 releases = config.get('releases', None)
973 if releases is None:974 if releases is None:
@@ -989,7 +990,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
989 the_ppa = get_ppa(lp, config)990 the_ppa = get_ppa(lp, config)
990 if not the_ppa.exists():991 if not the_ppa.exists():
991 error(f"PPA {the_ppa.name} does not exist for user {the_ppa.owner_name}")992 error(f"PPA {the_ppa.name} does not exist for user {the_ppa.owner_name}")
992 return os.EX_NOTFOUND993 return EX_NOTFOUND
993994
994 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)995 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)
995 if isinstance(architectures, str):996 if isinstance(architectures, str):
diff --git a/tests/data/results-chrony-armhf.log.gz b/tests/data/results-chrony-armhf.log.gz
996new file mode 100644997new file mode 100644
index 0000000..1813920
997Binary files /dev/null and b/tests/data/results-chrony-armhf.log.gz differ998Binary files /dev/null and b/tests/data/results-chrony-armhf.log.gz differ
diff --git a/tests/test_result.py b/tests/test_result.py
index c7e2510..a3597af 100644
--- a/tests/test_result.py
+++ b/tests/test_result.py
@@ -17,7 +17,6 @@ import time
17import gzip17import gzip
18import json18import json
19import pytest19import pytest
20# from mock import Mock
2120
22sys.path.insert(0, os.path.realpath(21sys.path.insert(0, os.path.realpath(
23 os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")))22 os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")))
@@ -126,10 +125,49 @@ def test_log(tmp_path):
126 assert result.log == "abcde"125 assert result.log == "abcde"
127126
128127
129def test_triggers():128@pytest.mark.parametrize('filename, expected_triggers', [
129 ('results-six-s390x.log.gz', ['pygobject/3.42.2-2', 'six/1.16.0-4']),
130 ('results-chrony-armhf.log.gz', ['dpkg/1.22.6ubuntu5'])
131])
132def test_triggers(filename, expected_triggers):
130 """Checks that autopkgtest triggers can be extracted from test result logs."""133 """Checks that autopkgtest triggers can be extracted from test result logs."""
131 result = Result(f"file://{DATA_DIR}/results-six-s390x.log.gz", None, None, None, None)134 result = Result(f"file://{DATA_DIR}/{filename}", None, None, None, None)
132 assert result.triggers == ['pygobject/3.42.2-2', 'six/1.16.0-4']135 assert result.triggers == expected_triggers
136
137
138@pytest.mark.parametrize('triggers, name, expected', [
139 ([], None, []),
140 (['x/1'], None, ["Trigger(package='x', version='1', arch=None, series=None, ppa=None, test_package='x')"]),
141 (['x/1'], 'x', ["Trigger(package='x', version='1', arch=None, series=None, ppa=None, test_package='x')"]),
142 pytest.param(
143 ['x/1'], 'z', [],
144 marks=pytest.mark.xfail(reason="Unimplemented")
145 ),
146 (
147 ['x/1', 'y/2', 'z/3'],
148 None,
149 [
150 "Trigger(package='x', version='1', arch=None, series=None, ppa=None, test_package='x')",
151 "Trigger(package='y', version='2', arch=None, series=None, ppa=None, test_package='y')",
152 "Trigger(package='z', version='3', arch=None, series=None, ppa=None, test_package='z')"
153 ]
154 ),
155 pytest.param(
156 ['x/1', 'y/2', 'z/3'],
157 'y',
158 [
159 "Trigger(package='y', version='2', arch=None, series=None, ppa=None, test_package='y')"
160 ],
161 marks=pytest.mark.xfail(reason="Unimplemented"),
162 ),
163])
164def test_get_triggers(monkeypatch, triggers, name, expected):
165 """Checks retrieval of Trigger objects from autopkgtest results."""
166 result = Result('url', None, None, None, None)
167 monkeypatch.setattr(Result, "triggers", triggers)
168
169 triggers = result.get_triggers(name)
170 assert [repr(t) for t in triggers] == expected
133171
134172
135@pytest.mark.parametrize('log_text, subtest_name, expected', [173@pytest.mark.parametrize('log_text, subtest_name, expected', [
@@ -212,6 +250,54 @@ def test_get_subtests(tmp_path, log_text: str, subtest_name: str, expected: dict
212 assert {s.desc: s.status for s in subtests} == expected250 assert {s.desc: s.status for s in subtests} == expected
213251
214252
253@pytest.mark.parametrize('subtest_states, error_message, expected', [
254 ([], None, 'PASS'),
255 (['PASS'], None, 'PASS'),
256 (['FAIL'], None, 'FAIL'),
257 (['PASS', 'FAIL'], None, 'FAIL'),
258 (['FAIL', 'PASS'], None, 'FAIL'),
259 (['PASS', 'PASS', 'PASS'], None, 'PASS'),
260 (['PASS'], 'x', 'BAD'),
261 (['FAIL'], 'x', 'BAD'),
262])
263def test_status(monkeypatch, subtest_states, error_message, expected):
264 """Checks retrieval of status from autopkgtest results."""
265 result = Result("file://tmp/x", None, None, None, None)
266 result.error_message = error_message
267
268 # Add subtests with given states
269 subtests = [Subtest(f"... {state}...") for state in subtest_states]
270 monkeypatch.setattr(Result, "get_subtests", lambda x: subtests)
271
272 assert result.status == expected
273
274
275@pytest.mark.parametrize('status, expected', [
276 ('PASS', "āœ…"),
277 ('FAIL', "āŒ"),
278 ('BAD', "ā›”"),
279])
280def test_status_icon(monkeypatch, status, expected):
281 """Checks generation of correct icon based on autopkgtest results."""
282 result = Result("file://tmp/x", None, None, None, None)
283 monkeypatch.setattr(Result, "status", status)
284
285 assert result.status_icon == expected
286
287
288@pytest.mark.parametrize('status, expected_exception', [
289 (None, KeyError),
290 ('x', KeyError),
291])
292def test_status_icon_error(monkeypatch, status, expected_exception):
293 """Checks generation of correct icon based on autopkgtest results."""
294 result = Result("file://tmp/x", None, None, None, None)
295 monkeypatch.setattr(Result, "status", status)
296
297 with pytest.raises(expected_exception):
298 print(result.status_icon)
299
300
215@pytest.mark.parametrize('log_text, series, arch, source, expected_dict', [301@pytest.mark.parametrize('log_text, series, arch, source, expected_dict', [
216 (302 (
217 # Empty/invalid log should return empty triggers & subtests.303 # Empty/invalid log should return empty triggers & subtests.
diff --git a/tests/test_trigger.py b/tests/test_trigger.py
index 1441b85..5e42f71 100644
--- a/tests/test_trigger.py
+++ b/tests/test_trigger.py
@@ -137,40 +137,52 @@ def test_get_triggers(params, expected):
137137
138@pytest.mark.parametrize('triggers, params, expected_in_stdout', [138@pytest.mark.parametrize('triggers, params, expected_in_stdout', [
139 (139 (
140 # Basic function parameters140 # Basic function parameters, no triggers
141 [],141 [],
142 {'package': 'a', 'version': 'b', 'status': 'c'},142 {'package': 'pkg', 'version': '123', 'status': 'OK'},
143 [" - Source ", "source/a/b", ": c"]143 [" - Source \x1b]8;;https://launchpad.net/ubuntu/+source/pkg/123\x1b\\pkg/123\x1b]8;;\x1b\\: OK\n"]
144 ),144 ),
145 (145 (
146 # Specified trigger146 # Specified trigger (clickable)
147 [('a', '1')],147 [('pkg', '123', 'i386')],
148 {'show_trigger_urls': True},148 {'show_trigger_urls': False},
149 [" + @amd64: ", "trigger=a%2F1"]149 [
150 "&trigger=pkg%2F123&ppa=z\x1b\\Trigger basic @i386ā™»ļø \x1b]8;;\x1b",
151 "&trigger=pkg%2F123&ppa=z&all-proposed=1\x1b\\Trigger all-proposed @i386šŸ’\x1b]8;;\x1b\\\n"
152 ]
150 ),153 ),
151 (154 (
152 # Display trigger URLS155 # Specified trigger (display plain URLs)
153 [('a', '1')],156 [('pkg', '123', 'i386')],
154 {'show_trigger_urls': True},157 {'show_trigger_urls': True},
155 ["https://autopkgtest.ubuntu.com/request.cgi?release=y&package=x&arch=amd64&trigger=a%2F1&ppa=z"]158 [
159 "&trigger=pkg%2F123&ppa=zā™»ļø \n",
160 "&trigger=pkg%2F123&ppa=z&all-proposed=1šŸ’\n"
161 ]
156 ),162 ),
157 (163 (
158 # Display names of packages in trigger lines164 # Display names of packages in trigger lines
159 [('a', '1')],165 [('pkg', '123', 'i386')],
160 {'show_trigger_names': True},166 {'show_trigger_names': True},
161 ["Trigger basic", "Trigger all-proposed"]167 ["Trigger basic x@i386ā™»ļø ", "Trigger all-proposed x@i386šŸ’"]
168 ),
169 (
170 # Omit package names if specified
171 [('pkg', '123', 'i386')],
172 {'show_trigger_names': False},
173 ["Trigger basic @i386", "Trigger all-proposed @i386"]
162 ),174 ),
163 (175 (
164 # Display names of packages in trigger lines when trigger URLs are shown176 # Display names of packages in trigger lines when trigger URLs are shown
165 [('a', '1')],177 [('pkg', '123', 'i386')],
166 {'show_trigger_urls': True, 'show_trigger_names': True},178 {'show_trigger_urls': True, 'show_trigger_names': True},
167 ["a@amd64: https:", "trigger=a%2F1"]179 ["x@i386: https:", "trigger=pkg%2F123"]
168 ),180 ),
169 (181 (
170 # Multiple triggers182 # Multiple triggers
171 [('a', '1'), ('b', '2')],183 [('pkg', '123', 'i386'), ('lib', '321', 'i386')],
172 {'show_trigger_urls': True},184 {'show_trigger_urls': True},
173 ["trigger=a%2F1", "trigger=b%2F2"]185 ["trigger=pkg%2F123", "trigger=lib%2F321"]
174 ),186 ),
175])187])
176def test_show_triggers(capfd, triggers, params, expected_in_stdout):188def test_show_triggers(capfd, triggers, params, expected_in_stdout):
@@ -179,7 +191,7 @@ def test_show_triggers(capfd, triggers, params, expected_in_stdout):
179 params.setdefault('triggers', [])191 params.setdefault('triggers', [])
180 params.setdefault('status', 'x')192 params.setdefault('status', 'x')
181 for t in triggers:193 for t in triggers:
182 params['triggers'].append(Trigger(t[0], t[1], 'amd64', 'y', 'z', 'x'))194 params['triggers'].append(Trigger(t[0], t[1], t[2], 'y', 'z', 'x'))
183 show_triggers(**params)195 show_triggers(**params)
184 out, err = capfd.readouterr()196 out, err = capfd.readouterr()
185 print(out)197 print(out)

Subscribers

People subscribed via source and target branches

to all changes: