Skip to content

Commit a058e8d

Browse files
committed
chore(test): migrate remaining container-based unit tests
Finishes the migration started by the keycloak-version pilot (84dbef4). The four remaining hand-rolled podman test runners are replaced as follows: - keycloak-memory-usage, keycloak-stats: use the same testcontainers-python pattern as keycloak-version. All 9 Keycloak majors (18-26) pulled as upstream images, plugin runs from host against the exposed port, assertions check message shape and a valid monitoring state rather than hardcoded timings. - deb-updates: add `--test` argument with a single-fixture layout (the `apt-get update` cache refresh is skipped in test mode, only the `apt list --upgradable` output is injected). Unit tests are fixture-based and cover ok-no-updates, warn-one-pending, ok-with-high-threshold, warn-three-pending. No longer depends on the debian/ubuntu test containers' upstream mirror state. - needs-restarting: add `--test` + a hidden `--test-os-family` override so a single host can exercise both the RedHat and Debian branches through fixtures. Six test cases cover ok/warn for each OS family plus an unsupported-OS case. Runs in ~0.3s instead of several minutes of cross-distro podman builds. The `containerfiles/` directories stay in place for all four plugins so that a future maintainer can migrate them to testcontainers-python if real-OS coverage is desired. Follow-up: while writing fixtures for needs-restarting I noticed the kernel-version mismatch detection (`NEEDRESTART-KCUR` vs `KEXP`) is broken because `kcur`/`kexp` are reset inside the parse loop, so the "Running Kernel X != Installed Kernel Y" line is never printed. Left as-is here to keep the scope limited to the test migration; worth a follow-up bugfix.
1 parent 50e8926 commit a058e8d

File tree

16 files changed

+329
-1164
lines changed

16 files changed

+329
-1164
lines changed

check-plugins/deb-updates/deb-updates

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ from lib.globals import STATE_OK, STATE_UNKNOWN
2424

2525

2626
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
27-
__version__ = '2026041201'
27+
__version__ = '2026041202'
2828

2929
DESCRIPTION = """Checks for available APT package updates on Debian, Ubuntu, and compatible systems.
3030
Reports the number of pending updates and upgrades, and alerts when updates are
@@ -75,6 +75,13 @@ def parse_args():
7575
default=DEFAULT_QUERY,
7676
)
7777

78+
parser.add_argument(
79+
'--test',
80+
help=lib.args.help('--test'),
81+
dest='TEST',
82+
type=lib.args.csv,
83+
)
84+
7885
parser.add_argument(
7986
'--timeout',
8087
help=lib.args.help('--timeout') + ' Default: %(default)s (seconds)',
@@ -112,22 +119,30 @@ def main():
112119
perfdata = ''
113120

114121
# fetch data
115-
# try to update the package cache first
116-
stdout, stderr, retc = lib.base.coe(
117-
lib.shell.shell_exec('sudo apt-get update --quiet 2', timeout=args.TIMEOUT),
118-
)
119-
if retc or stderr:
120-
# Not printing stderr as it is quite verbose
121-
msg += '`apt-get update` returned with an error. '
122-
lib.base.cu(msg)
122+
if args.TEST is None:
123+
# try to update the package cache first
124+
stdout, stderr, retc = lib.base.coe(
125+
lib.shell.shell_exec('sudo apt-get update --quiet 2', timeout=args.TIMEOUT),
126+
)
127+
if retc or stderr:
128+
# Not printing stderr as it is quite verbose
129+
msg += '`apt-get update` returned with an error. '
130+
lib.base.cu(msg)
123131

124-
stdout, _, retc = lib.base.coe(
125-
lib.shell.shell_exec('apt list --upgradable', timeout=args.TIMEOUT),
126-
)
127-
if retc:
128-
# Not printing stderr as it is quite verbose
129-
msg += '`apt list --upgradable` returned with an error. '
130-
lib.base.cu(msg)
132+
stdout, _, retc = lib.base.coe(
133+
lib.shell.shell_exec('apt list --upgradable', timeout=args.TIMEOUT),
134+
)
135+
if retc:
136+
# Not printing stderr as it is quite verbose
137+
msg += '`apt list --upgradable` returned with an error. '
138+
lib.base.cu(msg)
139+
else:
140+
# skip the apt-get update refresh in test mode and read the
141+
# `apt list --upgradable` output from a fixture file
142+
stdout, _, retc = lib.lftest.test(args.TEST)
143+
if retc:
144+
msg += '`apt list --upgradable` returned with an error. '
145+
lib.base.cu(msg)
131146

132147
# strip first line "Listing... Done." (if any)
133148
first, _, rest = stdout.partition('\n')

check-plugins/deb-updates/unit-test/run

Lines changed: 37 additions & 251 deletions
Original file line numberDiff line numberDiff line change
@@ -8,274 +8,60 @@
88

99
# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md
1010

11-
import os
12-
import subprocess
13-
import time
11+
import sys
12+
sys.path.append('..')
13+
1414
import unittest
1515

16-
CHECK_PLUGIN = 'deb-updates'
17-
REMOTE_DIR = (
18-
'/tmp' # work dir in container (where to find the CHECK_PLUGIN in the container)
19-
)
20-
LOCAL_DIR = '..' # local work dir (where to find the CHECK_PLUGIN locally)
21-
TIMEOUT = 0
16+
from lib.globals import STATE_OK, STATE_WARN
17+
import lib.lftest
18+
19+
20+
# deb-updates makes two shell calls, but only `apt list --upgradable`
21+
# drives the analysis. In test mode the `apt-get update` cache refresh
22+
# is skipped and the single fixture file is used as the upgradable
23+
# list. The `containerfiles/` directory next door still holds the
24+
# legacy podman setup for cross-distro smoke coverage and can be
25+
# migrated to testcontainers-python later.
2226
TESTS = [
2327
{
24-
'containerfile': 'containerfiles/debian-v11',
25-
'podman-run-params': '',
26-
'tests': [
27-
{
28-
'run-where': 'container',
29-
'plugin-params': '',
30-
'assert-regex': r'\d+ updates available',
31-
'assert-retc': 1,
32-
},
33-
],
34-
},
35-
{
36-
'containerfile': 'containerfiles/debian-v12',
37-
'podman-run-params': '',
38-
'tests': [
39-
{
40-
'run-where': 'container',
41-
'plugin-params': '',
42-
'assert-regex': r'\d+ updates available',
43-
'assert-retc': 1,
44-
},
45-
],
28+
'id': 'ok-no-updates',
29+
'test': 'stdout/no-updates,,0',
30+
'params': '',
31+
'assert-retc': STATE_OK,
32+
'assert-in': ['No updates available'],
4633
},
4734
{
48-
'containerfile': 'containerfiles/debian-v13',
49-
'podman-run-params': '',
50-
'tests': [
51-
{
52-
'run-where': 'container',
53-
'plugin-params': '',
54-
'assert-regex': r'\d+ updates available',
55-
'assert-retc': 1,
56-
},
57-
],
35+
'id': 'warn-one-pending-upgrade',
36+
'test': 'stdout/one-pending-upgrade,,0',
37+
'params': '',
38+
'assert-retc': STATE_WARN,
39+
'assert-in': ['1 update available', 'vim-tiny'],
5840
},
5941
{
60-
'containerfile': 'containerfiles/ubuntu-v2004',
61-
'podman-run-params': '',
62-
'tests': [
63-
{
64-
'run-where': 'container',
65-
'plugin-params': '',
66-
'assert-regex': r'\d+ updates available',
67-
'assert-retc': 1,
68-
},
69-
],
42+
'id': 'ok-one-pending-upgrade-with-high-warn',
43+
'test': 'stdout/one-pending-upgrade,,0',
44+
'params': '--warning 5',
45+
'assert-retc': STATE_OK,
7046
},
7147
{
72-
'containerfile': 'containerfiles/ubuntu-v2204',
73-
'podman-run-params': '',
74-
'tests': [
75-
{
76-
'run-where': 'container',
77-
'plugin-params': '',
78-
'assert-regex': r'\d+ updates available',
79-
'assert-retc': 1,
80-
},
81-
],
82-
},
83-
{
84-
'containerfile': 'containerfiles/ubuntu-v2404',
85-
'podman-run-params': '',
86-
'tests': [
87-
{
88-
'run-where': 'container',
89-
'plugin-params': '',
90-
'assert-regex': r'\d+ updates available',
91-
'assert-retc': 1,
92-
},
93-
],
48+
'id': 'warn-three-pending-upgrades',
49+
'test': 'stdout/security-upgrades,,0',
50+
'params': '',
51+
'assert-retc': STATE_WARN,
52+
'assert-in': ['3 updates available'],
9453
},
9554
]
9655

9756

9857
class TestCheck(unittest.TestCase):
99-
def test(self):
100-
"""
101-
Test all Dockerfiles in the 'unit-test/containerfiles' directory.
102-
Builds, runs, and tests each container and validates the output, using podman.
103-
"""
104-
for testcase in TESTS:
105-
if not os.path.isfile(testcase['containerfile']):
106-
self.fail(f'Containerfile {testcase["containerfile"]} not found.')
107-
108-
print(f'\n=== Testing {os.path.basename(testcase["containerfile"])} ===')
109-
image_name = (
110-
f'lfmp-{os.path.basename(testcase["containerfile"]).replace("-v", ":")}'
111-
)
112-
container_name = f'lfmp-{os.path.basename(testcase["containerfile"])}'
11358

114-
try:
115-
# Build the container image
116-
podman_cmd = [
117-
'podman',
118-
'build',
119-
'--file',
120-
testcase['containerfile'],
121-
'--tag',
122-
image_name,
123-
]
124-
print(f'Build the container image: {" ".join(podman_cmd)}')
125-
subprocess.run(
126-
podman_cmd,
127-
check=True,
128-
stdout=subprocess.PIPE,
129-
stderr=subprocess.PIPE,
130-
)
59+
check = '../deb-updates'
13160

132-
podman_cmd = (
133-
[
134-
'podman',
135-
'run',
136-
'--name',
137-
container_name,
138-
'--replace',
139-
'--detach', # run in detached mode
140-
]
141-
+ testcase['podman-run-params'].split()
142-
+ image_name.split()
143-
)
144-
print(f'Run the container: {" ".join(podman_cmd)}')
145-
subprocess.run(
146-
podman_cmd,
147-
check=True,
148-
stdout=subprocess.PIPE,
149-
stderr=subprocess.PIPE,
150-
)
151-
152-
print('Give the container a few seconds to start up')
153-
time.sleep(3)
154-
155-
podman_cmd = [
156-
'podman',
157-
'cp',
158-
'../lib',
159-
f'{container_name}:{REMOTE_DIR}/',
160-
]
161-
print(f'Copy local files into container: {" ".join(podman_cmd)}')
162-
subprocess.run(
163-
podman_cmd,
164-
check=True,
165-
stdout=subprocess.PIPE,
166-
stderr=subprocess.PIPE,
167-
)
168-
podman_cmd = [
169-
'podman',
170-
'cp',
171-
f'../{CHECK_PLUGIN}',
172-
f'{container_name}:{REMOTE_DIR}/{CHECK_PLUGIN}',
173-
]
174-
print(f'Copy local files into container: {" ".join(podman_cmd)}')
175-
subprocess.run(
176-
podman_cmd,
177-
check=True,
178-
stdout=subprocess.PIPE,
179-
stderr=subprocess.PIPE,
180-
)
181-
182-
# Check if /tmp/venv exists in the container
183-
podman_cmd = [
184-
'podman',
185-
'exec',
186-
container_name,
187-
'sh',
188-
'-c',
189-
"test -d /tmp/venv && echo 'venv exists'",
190-
]
191-
print(
192-
f'Check if /tmp/venv exists in the container: {" ".join(podman_cmd)}'
193-
)
194-
venv_exists = (
195-
subprocess.run(
196-
podman_cmd,
197-
check=False,
198-
stdout=subprocess.PIPE,
199-
stderr=subprocess.PIPE,
200-
text=True,
201-
).stdout.strip()
202-
== 'venv exists'
203-
)
204-
print(f'Check if /tmp/venv exists in the container: {venv_exists}')
205-
206-
print('Give server daemons a few seconds to start up')
207-
time.sleep(TIMEOUT)
208-
209-
print('Run the test cases...')
210-
for test in testcase['tests']:
211-
if test['run-where'] == 'container':
212-
cmdline = (
213-
f'python3 {CHECK_PLUGIN} {test["plugin-params"]}'.strip()
214-
)
215-
if venv_exists:
216-
podman_cmd = [
217-
'podman',
218-
'exec',
219-
'--workdir',
220-
REMOTE_DIR,
221-
container_name,
222-
'sh',
223-
'-c',
224-
f'. /tmp/venv/bin/activate && {cmdline}',
225-
]
226-
print(
227-
f'Run test in container in venv: `{" ".join(podman_cmd)}`'
228-
)
229-
else:
230-
podman_cmd = [
231-
'podman',
232-
'exec',
233-
'--workdir',
234-
REMOTE_DIR,
235-
container_name,
236-
] + cmdline.split()
237-
print(f'Run test in container: `{" ".join(podman_cmd)}`')
238-
result = subprocess.run(
239-
podman_cmd,
240-
check=False, # We'll handle checking the output ourselves
241-
stdout=subprocess.PIPE,
242-
stderr=subprocess.PIPE,
243-
text=True,
244-
)
245-
else:
246-
local_cmd = [
247-
'python3',
248-
os.path.join(LOCAL_DIR, CHECK_PLUGIN),
249-
] + test['plugin-params'].strip().split()
250-
print(f'Run test locally: `{" ".join(local_cmd)}`')
251-
result = subprocess.run(
252-
local_cmd,
253-
check=False, # We'll handle checking the output ourselves
254-
stdout=subprocess.PIPE,
255-
stderr=subprocess.PIPE,
256-
text=True,
257-
)
258-
259-
# Check the output
260-
output = result.stdout + '\n' + result.stderr
261-
print(f'Script output:\n{output.strip()}')
262-
263-
# the unit test result
264-
self.assertRegex(
265-
result.stdout + result.stderr, test['assert-regex']
266-
)
267-
self.assertEqual(result.returncode, test['assert-retc'])
268-
269-
finally:
270-
# Stop and remove the container no matter what
271-
podman_cmd = ['podman', 'rm', '--force', container_name]
272-
print(f'Stop and remove container: {" ".join(podman_cmd)}')
273-
subprocess.run(
274-
podman_cmd,
275-
check=False,
276-
stdout=subprocess.PIPE,
277-
stderr=subprocess.PIPE,
278-
)
61+
def test(self):
62+
for t in TESTS:
63+
with self.subTest(id=t['id']):
64+
lib.lftest.run(self, self.check, t)
27965

28066

28167
if __name__ == '__main__':
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Listing... Done.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Listing... Done.
2+
vim-tiny/noble-updates,noble-security 2:9.1.0016-1ubuntu7.9 amd64 [upgradable from: 2:9.1.0016-1ubuntu7.8]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Listing... Done.
2+
libssl3/noble-updates,noble-security 3.0.13-0ubuntu3.5 amd64 [upgradable from: 3.0.13-0ubuntu3.4]
3+
openssl/noble-updates,noble-security 3.0.13-0ubuntu3.5 amd64 [upgradable from: 3.0.13-0ubuntu3.4]
4+
vim-tiny/noble-updates 2:9.1.0016-1ubuntu7.9 amd64 [upgradable from: 2:9.1.0016-1ubuntu7.8]

0 commit comments

Comments
 (0)