Skip to content

Commit ac8febe

Browse files
committed
feat(lftest): add run() function for declarative unit tests
1 parent fe1a1e2 commit ac8febe

2 files changed

Lines changed: 80 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
* Add GitHub Actions workflow to automatically build and deploy API documentation to GitHub Pages
1919
* Add pre-commit hooks
2020
* disk.py: add `get_owner()`
21+
* lftest.py: add `run()` function for declarative, data-driven unit tests using `subTest()`
2122
* nextcloud.py: new library
2223
* txt.py: add `exception2text()`
2324
* winrm.py: add `WINRM_CONFIGURATION_NAME` option to `run_ps()`

lftest.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,99 @@
66
# https://www.linuxfabrik.ch/
77
# License: The Unlicense, see LICENSE file.
88

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

1111
"""Provides test functions for unit tests.
1212
"""
1313

1414
import os
1515

16+
from . import base
1617
from . import disk
18+
from . import shell
1719

1820

1921
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
20-
__version__ = '2025042001'
22+
__version__ = '2026040901'
23+
24+
25+
def run(test_instance, plugin, testcase):
26+
"""Run a single testcase against a plugin and assert the results.
27+
28+
Designed to be used with unittest.TestCase.subTest() for declarative,
29+
data-driven test definitions. Each testcase is a dict describing what
30+
to run and what to expect.
31+
32+
### Parameters
33+
- **test_instance** (`unittest.TestCase`): The test instance (self)
34+
for assertions.
35+
- **plugin** (`str`): Path to the plugin executable.
36+
- **testcase** (`dict`): Test definition with keys:
37+
- `test` (`str`): --test parameter value,
38+
e.g. `'stdout/ok-healthy,,0'`.
39+
- `params` (`str`, optional): Additional plugin parameters.
40+
Default: `''`.
41+
- `assert-retc` (`int`): Expected return code (STATE_OK, etc.).
42+
- `assert-in` (`list` of `str`, optional): Strings that must
43+
appear in stdout.
44+
- `assert-not-in` (`list` of `str`, optional): Strings that must
45+
not appear in stdout.
46+
- `assert-regex` (`str`, optional): Regex pattern that must match
47+
stdout.
48+
- `assert-stderr` (`str`, optional): Expected stderr content.
49+
Default: `''`.
50+
51+
### Example
52+
>>> TESTS = [
53+
... {
54+
... 'id': 'ok-all-healthy',
55+
... 'test': 'stdout/ok-all-healthy,,0',
56+
... 'assert-retc': STATE_OK,
57+
... 'assert-in': ['Everything is ok.'],
58+
... },
59+
... {
60+
... 'id': 'crit-threshold-exceeded',
61+
... 'test': 'stdout/crit-threshold-exceeded,,0',
62+
... 'params': '--critical 50',
63+
... 'assert-retc': STATE_CRIT,
64+
... 'assert-regex': r'90.0%.*\\[CRITICAL\\]',
65+
... },
66+
... ]
67+
...
68+
... class TestCheck(unittest.TestCase):
69+
... check = '../my-plugin'
70+
...
71+
... def test(self):
72+
... for t in TESTS:
73+
... with self.subTest(id=t['id']):
74+
... lib.lftest.run(self, self.check, t)
75+
"""
76+
params = testcase.get('params', '')
77+
cmd = f'{plugin} {params} --test={testcase["test"]}'.strip()
78+
stdout, stderr, retc = base.coe(shell.shell_exec(cmd))
79+
80+
test_instance.assertEqual(
81+
retc,
82+
testcase['assert-retc'],
83+
f'Expected retc {testcase["assert-retc"]}, got {retc}',
84+
)
85+
86+
expected_stderr = testcase.get('assert-stderr', '')
87+
test_instance.assertEqual(stderr, expected_stderr)
88+
89+
for text in testcase.get('assert-in', []):
90+
test_instance.assertIn(text, stdout)
91+
92+
for text in testcase.get('assert-not-in', []):
93+
test_instance.assertNotIn(text, stdout)
94+
95+
if 'assert-regex' in testcase:
96+
test_instance.assertRegex(stdout, testcase['assert-regex'])
2197

2298

2399
def test(args):
24100
"""
25-
Returns the content of two files and the provided return code. The first file represents STDOUT,
101+
Returns the content of two files and the provided return code. The first file represents STDOUT,
26102
and the second represents STDERR. This function is useful for enabling unit tests.
27103
28104
### Parameters

0 commit comments

Comments
 (0)