Skip to content

Commit e4355c8

Browse files
Amadeus-22claude
andauthored
Blacklist: Match the source version stored by "Ignore the current update" (#1112)
"Ignore the current update for this package" stores source=source_version (since 7a79b61), but the update list and mintupdate-cli compared that version with a binary version. Where the two differ (epochs such as bsdutils 1:2.39.3-9ubuntu6.6, -signed packages such as grub2-signed 1.202.5 / grub-efi-amd64-signed 1.202.5+2.12-1ubuntu7.3, binNMUs) the update was not ignored, or only partly ignored: for util-linux only bsdutils stayed in the list. Move the matching rule to one is_blacklisted() helper in aptUpdater.py that accepts the source version as well as the binary version, so versions typed in the preferences keep working, and use it from mintupdate-cli instead of its own copy. mintupdate-cli now checks every package of an update and skips the whole update when one matches. Add tests/test_blacklist.py, run with /usr/bin/python3 -m unittest discover -s tests Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 174bf63 commit e4355c8

3 files changed

Lines changed: 160 additions & 26 deletions

File tree

‎tests/test_blacklist.py‎

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/python3
2+
3+
# Blacklist matching in the update list (AptUpdater) and in mintupdate-cli.
4+
# Run with: /usr/bin/python3 -m unittest discover -s tests
5+
# Nothing is read from APT or dconf: packages are stubs and GSettings uses
6+
# the memory backend.
7+
8+
import contextlib
9+
import io
10+
import os
11+
import runpy
12+
import sys
13+
import unittest
14+
from types import SimpleNamespace
15+
from unittest import mock
16+
17+
os.environ["GSETTINGS_BACKEND"] = "memory"
18+
myPath = os.path.dirname(os.path.abspath(__file__))
19+
libPath = os.path.join(myPath, "..", "usr", "lib", "linuxmint", "mintUpdate")
20+
sys.path.insert(0, libPath)
21+
22+
import aptUpdater
23+
from aptUpdater import AptUpdater
24+
25+
def make_package(name, source_name, source_version, version, old_version):
26+
candidate = SimpleNamespace(source_name=source_name, source_version=source_version,
27+
version=version, size=1, raw_description="", description="",
28+
origins=[], section="admin")
29+
return SimpleNamespace(name=name, candidate=candidate, is_installed=True,
30+
installed=SimpleNamespace(version=old_version))
31+
32+
# bsdutils has an epoch, the other util-linux binaries don't
33+
UTIL_LINUX = [
34+
make_package("util-linux", "util-linux", "2.39.3-9ubuntu6.6", "2.39.3-9ubuntu6.6", "2.39.3-9ubuntu6"),
35+
make_package("mount", "util-linux", "2.39.3-9ubuntu6.6", "2.39.3-9ubuntu6.6", "2.39.3-9ubuntu6"),
36+
make_package("bsdutils", "util-linux", "2.39.3-9ubuntu6.6", "1:2.39.3-9ubuntu6.6", "1:2.39.3-9ubuntu6"),
37+
]
38+
# -signed source packages have their own version, unrelated to the binary one
39+
GRUB_SIGNED = [
40+
make_package("grub-efi-amd64-signed", "grub2-signed", "1.202.5", "1.202.5+2.12-1ubuntu7.3", "1.202+2.12-1ubuntu7"),
41+
]
42+
PACKAGES = UTIL_LINUX + GRUB_SIGNED
43+
44+
class FakeSettings():
45+
46+
def __init__(self, blacklist):
47+
self.blacklist = blacklist
48+
49+
def get_strv(self, key):
50+
return list(self.blacklist)
51+
52+
class FakeAptUpdater(AptUpdater):
53+
# Real AptUpdater.add_update() on stub packages, with an empty GUI blacklist
54+
# unless one is given
55+
56+
gui_blacklist = []
57+
58+
def __init__(self, ui_window=None):
59+
super().__init__(ui_window)
60+
self.settings = FakeSettings(self.gui_blacklist)
61+
62+
def load_cache(self):
63+
self.cache = {package.name: package for package in PACKAGES}
64+
65+
def find_changes(self):
66+
self.updates = {}
67+
for package in PACKAGES:
68+
self.add_update(package)
69+
70+
def listed_updates(gui_blacklist):
71+
FakeAptUpdater.gui_blacklist = gui_blacklist
72+
updater = FakeAptUpdater()
73+
updater.load_cache()
74+
updater.find_changes()
75+
return {source_name: sorted(update.package_names) for source_name, update in updater.updates.items()}
76+
77+
def run_cli(*args):
78+
# Runs mintupdate-cli.py as a script, on the stub packages, without /etc/mintupdate.blacklist
79+
real_exists = os.path.exists
80+
def exists(path):
81+
return False if path == "/etc/mintupdate.blacklist" else real_exists(path)
82+
FakeAptUpdater.gui_blacklist = []
83+
output = io.StringIO()
84+
with mock.patch.object(aptUpdater, "AptUpdater", FakeAptUpdater), \
85+
mock.patch("os.path.exists", exists), \
86+
mock.patch.object(sys, "argv", ["mintupdate-cli"] + list(args)), \
87+
contextlib.redirect_stdout(output):
88+
runpy.run_path(os.path.join(libPath, "mintupdate-cli.py"), run_name="__main__")
89+
return [line.split()[1] for line in output.getvalue().splitlines()]
90+
91+
class TestUpdateList(unittest.TestCase):
92+
93+
def test_nothing_ignored(self):
94+
self.assertEqual(listed_updates([]), {
95+
"util-linux": ["bsdutils", "mount", "util-linux"],
96+
"grub2-signed": ["grub-efi-amd64-signed"],
97+
})
98+
99+
def test_ignore_current_update_epoch(self):
100+
# "Ignore the current update" stores source=source_version
101+
self.assertNotIn("util-linux", listed_updates(["util-linux=2.39.3-9ubuntu6.6"]))
102+
103+
def test_ignore_current_update_signed(self):
104+
self.assertNotIn("grub2-signed", listed_updates(["grub2-signed=1.202.5"]))
105+
106+
def test_ignore_binary_version(self):
107+
# A version typed in the preferences, as shown in the update list
108+
self.assertNotIn("grub2-signed", listed_updates(["grub2-signed=1.202.5+2.12-1ubuntu7.3"]))
109+
110+
def test_ignore_all_versions_pattern(self):
111+
self.assertNotIn("grub2-signed", listed_updates(["grub2-*"]))
112+
113+
def test_other_version_not_ignored(self):
114+
self.assertEqual(listed_updates(["util-linux=2.39.3-9ubuntu6", "grub2-signed=1.202"]),
115+
listed_updates([]))
116+
117+
class TestCli(unittest.TestCase):
118+
119+
def test_nothing_ignored(self):
120+
self.assertEqual(run_cli("list"), ["grub2-signed", "util-linux"])
121+
122+
def test_ignore_current_update_epoch(self):
123+
self.assertEqual(run_cli("list", "-i", "util-linux=2.39.3-9ubuntu6.6"), ["grub2-signed"])
124+
125+
def test_ignore_current_update_signed(self):
126+
self.assertEqual(run_cli("list", "-i", "grub2-signed=1.202.5"), ["util-linux"])
127+
128+
def test_ignore_binary_version(self):
129+
self.assertEqual(run_cli("list", "-i", "grub2-signed=1.202.5+2.12-1ubuntu7.3"), ["util-linux"])
130+
131+
def test_ignore_all_versions_pattern(self):
132+
self.assertEqual(run_cli("list", "-i", "grub2-*,util-*"), [])
133+
134+
def test_other_version_not_ignored(self):
135+
self.assertEqual(run_cli("list", "-i", "util-linux=2.39.3-9ubuntu6,grub2-signed=1.202"),
136+
["grub2-signed", "util-linux"])
137+
138+
if __name__ == "__main__":
139+
unittest.main()

‎usr/lib/linuxmint/mintUpdate/aptUpdater.py‎

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@
3333
# packages which description is incorrect in Ubuntu (usually those which were replaced by snap dependencies)
3434
NON_TRANSLATED_PKGS = ["firefox", "thunderbird"]
3535

36+
def is_blacklisted(blacklist, candidate):
37+
# Blacklist entries are "source" or "source=version", where source can be
38+
# a pattern. "Ignore the current update" stores the source version, while
39+
# a version typed in the preferences is usually the binary version shown
40+
# in the update list. Both are accepted: they differ for epochs, -signed
41+
# packages and binNMUs.
42+
for blacklist_entry in blacklist:
43+
if "=" in blacklist_entry:
44+
(bl_pkg, bl_ver) = blacklist_entry.split("=", 1)
45+
else:
46+
bl_pkg = blacklist_entry
47+
bl_ver = None
48+
if fnmatch.fnmatch(candidate.source_name, bl_pkg) and \
49+
(not bl_ver or bl_ver in (candidate.source_version, candidate.version)):
50+
return True
51+
return False
52+
3653
class AptUpdater():
3754

3855
def __init__(self, ui_window=None):
@@ -250,17 +267,6 @@ def find_changes(self):
250267
if (pkg.marked_upgrade or pkg.marked_downgrade):
251268
self.add_update(pkg)
252269

253-
def is_blacklisted(self, source_name, version):
254-
for blacklist in self.settings.get_strv("blacklisted-packages"):
255-
if "=" in blacklist:
256-
(bl_pkg, bl_ver) = blacklist.split("=", 1)
257-
else:
258-
bl_pkg = blacklist
259-
bl_ver = None
260-
if fnmatch.fnmatch(source_name, bl_pkg) and (not bl_ver or bl_ver == version):
261-
return True
262-
return False
263-
264270
def get_kernel_version_from_meta_package(self, pkg):
265271
for dependency in pkg.dependencies:
266272
if not dependency.target_versions or dependency.rawtype != "Depends":
@@ -300,7 +306,7 @@ def add_update(self, package, kernel_update=False, test_version=None):
300306
source_name = package.candidate.source_name
301307

302308
# ignore packages blacklisted by the user
303-
if self.is_blacklisted(package.candidate.source_name, package.candidate.version):
309+
if is_blacklisted(self.settings.get_strv("blacklisted-packages"), package.candidate):
304310
return
305311

306312
if source_name in PRIORITY_UPDATES:

‎usr/lib/linuxmint/mintUpdate/mintupdate-cli.py‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,16 @@
11
#!/usr/bin/python3
22

33
import argparse
4-
import fnmatch
54
import os
65
import subprocess
76
import sys
87
import traceback
98

10-
from aptUpdater import AptUpdater
9+
from aptUpdater import AptUpdater, is_blacklisted
1110
from Classes import PRIORITY_UPDATES
1211

1312
if __name__ == "__main__":
1413

15-
def is_blacklisted(blacklisted_packages, source_name, version):
16-
for blacklist in blacklisted_packages:
17-
if "=" in blacklist:
18-
(bl_pkg, bl_ver) = blacklist.split("=", 1)
19-
else:
20-
bl_pkg = blacklist
21-
bl_ver = None
22-
if fnmatch.fnmatch(source_name, bl_pkg) and (not bl_ver or bl_ver == version):
23-
return True
24-
return False
25-
2614
parser = argparse.ArgumentParser(prog="mintupdate-cli")
2715
parser.add_argument("command", help="command to run (possible commands are: list, upgrade)")
2816

@@ -65,7 +53,8 @@ def is_blacklisted(blacklisted_packages, source_name, version):
6553
continue
6654
elif args.only_security and update.type != "security":
6755
continue
68-
elif is_blacklisted(blacklisted, update.real_source_name, update.new_version):
56+
elif any(is_blacklisted(blacklisted, check.cache[name].candidate) for name in update.package_names):
57+
# skip the whole update, not just the matching packages
6958
continue
7059
else:
7160
updates.append(update)

0 commit comments

Comments
 (0)