kunit: Add composite runtime labels

Allow named sets of base labels and exact suite names in one TOML file.
Keep KUNIT_LABEL as the selection interface and combine overlapping sets
without running a suite twice. Include an IO set for CDB, UNMAP and VDISK.

Share expansion between source listing and runtime module selection.
Validate requested sets against the supplied module manifests before any
module operation, then load their owners and dependencies as before.
Reject namespace conflicts, nested sets and incomplete selections.

List descriptions and expanded suites without a build or loaded modules.
Allow an alternate file through --label-config, record selected definitions
in reports, and cover source listing, configuration and module ordering.
This commit is contained in:
Gleb Chesnokov
2026-10-14 16:30:27 +03:00
parent 5144a3c3e2
commit a246e3fd2a
11 changed files with 313 additions and 33 deletions
+12 -1
View File
@@ -5,6 +5,7 @@ import argparse
from pathlib import Path
import re
from labels import DEFAULT_LABEL_CONFIG, expand_labels, load_composites
REGISTRATION = re.compile(
r'^scst_kunit_suite\(\s*\w+\s*,\s*"([A-Z][A-Z0-9_]*)"\s*,\s*'
@@ -27,19 +28,29 @@ def source_suites(paths):
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('directories', nargs='+', type=Path)
parser.add_argument('--label-config', type=Path, default=DEFAULT_LABEL_CONFIG,
help='composite labels TOML (default: scripts/kunit/labels.toml)')
args = parser.parse_args(argv)
try:
suites = source_suites(path for directory in args.directories
for path in directory.rglob('*.c'))
if not suites:
raise ValueError('No source suites found')
available = sorted(set(suites.values()))
composites = load_composites(args.label_config.expanduser())
expanded = {name: expand_labels(suites, [name], composites) for name in composites}
available = sorted(set(suites.values()) | composites.keys())
except (OSError, ValueError) as exc:
parser.error(str(exc))
print('Source suites (the runner validates the built modules):')
print('Available KUNIT_LABEL: ' + ' '.join(available) + ' ALL')
for name, label in sorted(suites.items()):
print(f' {label}: {name}')
if composites:
print('Composite labels:')
for name in sorted(composites):
print(f" {name}: {composites[name]['description']}".rstrip())
for suite in sorted(expanded[name]):
print(f' {suite}')
return 0
+69
View File
@@ -0,0 +1,69 @@
# SPDX-License-Identifier: GPL-2.0-only
"""Expand source or module labels and configured sets into whole suites."""
from pathlib import Path
import re
import tomllib
DEFAULT_LABEL_CONFIG = Path(__file__).with_name('labels.toml')
LABEL = re.compile(r'[A-Z][A-Z0-9_]*')
SUITE = re.compile(r'scst-[A-Za-z0-9_-]+')
def load_composites(path=DEFAULT_LABEL_CONFIG):
with path.open('rb') as source:
definitions = tomllib.load(source)
result = {}
for name, entry in definitions.items():
if not LABEL.fullmatch(name) or name == 'ALL' or not isinstance(entry, dict):
raise ValueError(f'{path}: invalid composite label {name!r}')
if entry.keys() - {'description', 'labels', 'suites'}:
raise ValueError(f'{path}: unknown fields in {name}')
description = entry.get('description', '')
if not isinstance(description, str) or '\n' in description or '\r' in description:
raise ValueError(f'{path}: {name}.description must be a single-line string')
for field, pattern in (('labels', LABEL), ('suites', SUITE)):
values = entry.get(field, [])
if not isinstance(values, list) or any(
not isinstance(value, str) or not pattern.fullmatch(value) for value in values):
raise ValueError(f'{path}: {name}.{field} must be an array of valid names')
labels, suites = entry.get('labels', []), entry.get('suites', [])
if not labels and not suites:
raise ValueError(f'{path}: composite label {name} is empty')
if invalid := set(labels) & (definitions.keys() | {'ALL'}):
raise ValueError(f'{path}: {name} must reference base labels only: {sorted(invalid)}')
result[name] = {'description': description, 'labels': labels, 'suites': suites}
return result
def expand_labels(suites, selected, composites):
"""Validate requested sets against this inventory and keep its suite order.
Unselected composites may refer to absent modules. A selected composite
must resolve completely, rather than silently running only part of it.
"""
base = set(suites.values())
if conflicts := base & composites.keys():
raise ValueError(f'Composite labels conflict with base labels: {sorted(conflicts)}')
if not selected or ('ALL' in selected and selected != ['ALL']):
raise ValueError('Select labels or ALL alone')
if selected == ['ALL']:
return list(suites)
if unknown := set(selected) - base - composites.keys():
raise ValueError(f'Unknown or unavailable KUnit labels: {sorted(unknown)}')
chosen = set()
for name in selected:
if name in base:
chosen.update(suite for suite, label in suites.items() if label == name)
continue
entry = composites[name]
if missing := set(entry['labels']) - base:
raise ValueError(f'{name}: base labels absent from this inventory: {sorted(missing)}')
if missing := set(entry['suites']) - suites.keys():
raise ValueError(f'{name}: suites absent from this inventory: {sorted(missing)}')
chosen.update(entry['suites'])
chosen.update(suite for suite, label in suites.items() if label in entry['labels'])
if not chosen:
raise ValueError('Selected labels contain no suites')
return [suite for suite in suites if suite in chosen]
+6
View File
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
# Composite labels select whole suites; base labels come from C registrations.
[IO]
description = "Command decoding, UNMAP and virtual disk checks"
labels = ["CDB", "UNMAP", "VDISK"]
+18 -11
View File
@@ -23,6 +23,8 @@ import textwrap
import time
import tomllib
from labels import DEFAULT_LABEL_CONFIG, expand_labels, load_composites
sys.path.insert(0, str(Path(__file__).resolve().parent / "upstream"))
import kunit_parser # noqa: E402
from kunit_printer import Printer # noqa: E402
@@ -323,16 +325,15 @@ def parse_labels(value):
return sorted(set(labels))
def select_modules(inventory, labels):
def select_modules(inventory, labels, composites):
names = [item["name"] for item in inventory]
if len(names) != len(set(names)):
raise RunError("Duplicate module")
suites = [suite for item in inventory for suite in item["suites"]]
if not suites or len(suites) != len(set(suites)):
raise RunError("Modules must have a nonempty, unique suite manifest")
available = {label for item in inventory for label in item["labels"].values()}
if labels != ["ALL"] and set(labels) - available:
raise RunError(f"Labels absent from supplied modules: {sorted(set(labels) - available)}")
selected = set(expand_labels({suite: label for item in inventory
for suite, label in item["labels"].items()}, labels, composites))
by_name = {item["name"]: item for item in inventory}
ordered = []
visited = set()
@@ -356,7 +357,7 @@ def select_modules(inventory, labels):
for item in inventory:
item["selected_suites"] = [suite for suite in item["suites"]
if labels == ["ALL"] or item["labels"][suite] in labels]
if suite in selected]
for item in inventory:
if item["selected_suites"]:
visit(item["name"])
@@ -477,7 +478,8 @@ def unload_owned_kunit(capture, directory, timeout, report):
raise RunError("; ".join(problems))
def execute(modules, timeout, directory, report, labels, verbose=False):
def execute(modules, timeout, directory, report, labels, verbose=False,
label_config=DEFAULT_LABEL_CONFIG):
if os.geteuid() != 0:
raise RunError("Module execution requires root in the prepared test environment")
for tool in ("modinfo", "insmod", "rmmod"):
@@ -486,7 +488,10 @@ def execute(modules, timeout, directory, report, labels, verbose=False):
inventory = [module_info(path) | {"path": str(path), "state": "not attempted"}
for path in modules]
report["modules"] = inventory
inventory = select_modules(inventory, labels)
composites = load_composites(label_config)
report["label_config"] = str(label_config)
report["composite_labels"] = {name: composites[name] for name in labels if name in composites}
inventory = select_modules(inventory, labels, composites)
report["expected_suites"] = [suite for item in inventory for suite in item["selected_suites"]]
report["started_suites"] = []
if len({item["vermagic"] for item in inventory}) != 1:
@@ -587,7 +592,9 @@ def arguments(argv=None):
parser.add_argument("modules", nargs="*", type=Path,
help="test modules and their SCST dependencies; order is determined from modinfo")
parser.add_argument("--labels", default="ALL", type=parse_labels,
help="space-separated labels to run, or ALL (default: ALL)")
help="space-separated base or composite labels, or ALL (default: ALL)")
parser.add_argument("--label-config", type=Path, default=DEFAULT_LABEL_CONFIG,
help="composite labels TOML (default: scripts/kunit/labels.toml)")
parser.add_argument("--config", type=Path, help="TOML configuration file")
parser.add_argument("--timeout", type=float, help="seconds per suite or module operation (default: 300)")
parser.add_argument("--output", type=Path, help="parent for unique report directories")
@@ -643,7 +650,7 @@ def arguments(argv=None):
if "verbose" in settings and not isinstance(settings["verbose"], bool):
parser.error("Config verbose must be a boolean")
verbose = args.verbose if args.verbose is not None else settings.get("verbose", False)
return resolved, timeout, output.resolve(), verbose, args.labels
return resolved, timeout, output.resolve(), verbose, args.label_config.expanduser().resolve(), args.labels
def show_suites(tests, report):
@@ -870,7 +877,7 @@ def color_console(text):
def main(argv=None):
try:
modules, timeout, parent, verbose, labels = arguments(argv)
modules, timeout, parent, verbose, label_config, labels = arguments(argv)
except (OSError, ValueError) as exc:
print(f"Configuration error: {exc}", file=sys.stderr)
return 2
@@ -890,7 +897,7 @@ def main(argv=None):
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as exc:
raise RunError("Another SCST KUnit runner holds the lock") from exc
execute(modules, timeout, directory, report, labels, verbose)
execute(modules, timeout, directory, report, labels, verbose, label_config)
except KeyboardInterrupt:
report["errors"].append("Interrupted; module operations may still be running; inspect their PIDs")
except (RunError, OSError, ValueError, subprocess.SubprocessError) as exc:
+28 -2
View File
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
"""Source discovery must work before building and without kernel access."""
import contextlib
import io
import os
from pathlib import Path
import subprocess
@@ -8,7 +10,7 @@ import sys
import tempfile
import unittest
from inventory import source_suites
from inventory import main, source_suites
class SourceInventoryTests(unittest.TestCase):
@@ -36,8 +38,10 @@ scst_kunit_suite(third, "OTHER", "", .test_cases = third_cases);
self.assertEqual(source_suites(root.glob('*.c')), {
'scst-NEW_LABEL-boundary': 'NEW_LABEL', 'scst-NEW_LABEL': 'NEW_LABEL',
'scst-OTHER': 'OTHER'})
config = root / 'labels.toml'
config.write_text('')
command = [sys.executable, str(Path(__file__).with_name('inventory.py')),
str(root)]
str(root), '--label-config', str(config)]
result = subprocess.run(command, cwd=tmp, env=dict(os.environ, PATH=''),
capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
@@ -56,6 +60,28 @@ scst_kunit_suite(third, "OTHER", "", .test_cases = third_cases);
with self.assertRaises(ValueError):
source_suites([path])
def test_composite_listing_includes_description_and_expanded_suites(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / 'suites.c').write_text('''
scst_kunit_suite(cdb, "CDB", "", .test_cases = cases);
scst_kunit_suite(capacity, "VDISK", "-capacity", .test_cases = cases);
scst_kunit_suite(io, "VDISK", "-io", .test_cases = cases);
''')
config = root / 'custom labels.toml'
config.write_text('[IO]\ndescription = "Selected I/O suites"\n'
'labels = ["CDB"]\nsuites = ["scst-VDISK-capacity"]\n')
with contextlib.redirect_stdout(io.StringIO()) as console:
self.assertEqual(main([str(root), '--label-config', str(config)]), 0)
self.assertIn('Available KUNIT_LABEL: CDB IO VDISK ALL', console.getvalue())
self.assertEqual(console.getvalue().split('Composite labels:\n')[1],
' IO: Selected I/O suites\n scst-CDB\n scst-VDISK-capacity\n')
config.write_text('[IO]\nlabels = ["UNKNOWN"]\n')
with contextlib.redirect_stdout(io.StringIO()), \
contextlib.redirect_stderr(io.StringIO()) as errors, self.assertRaises(SystemExit):
main([str(root), '--label-config', str(config)])
self.assertIn('IO: base labels absent', errors.getvalue())
if __name__ == '__main__':
unittest.main()
+78
View File
@@ -0,0 +1,78 @@
# SPDX-License-Identifier: GPL-2.0-only
"""Composite selection checks using source/module inventories, without a kernel."""
from pathlib import Path
import tempfile
import unittest
from labels import expand_labels, load_composites
class LabelTests(unittest.TestCase):
SUITES = {'scst-CDB': 'CDB', 'scst-LIB': 'LIB', 'scst-VDISK-capacity': 'VDISK',
'scst-VDISK-io': 'VDISK', 'scst-VDISK-sync': 'VDISK'}
def load(self, text):
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / 'labels.toml'
path.write_text(text)
return load_composites(path)
def test_union_preserves_inventory_order_and_deduplicates(self):
composites = self.load('''
[IO]
description = "Command and virtual disk checks"
labels = ["VDISK", "CDB", "CDB"]
suites = ["scst-VDISK-io"]
[CAPACITY]
suites = ["scst-VDISK-capacity"]
''')
self.assertEqual(expand_labels(self.SUITES, ['IO', 'CAPACITY', 'VDISK'], composites),
['scst-CDB', 'scst-VDISK-capacity', 'scst-VDISK-io', 'scst-VDISK-sync'])
self.assertEqual(expand_labels(self.SUITES, ['CAPACITY'], composites), ['scst-VDISK-capacity'])
self.assertEqual(expand_labels(self.SUITES, ['ALL'], composites), list(self.SUITES))
self.assertEqual(expand_labels(self.SUITES, ['LIB'], composites), ['scst-LIB'])
def test_base_labels_need_no_configuration(self):
self.assertEqual(self.load(''), {})
self.assertEqual(expand_labels({'scst-NEW': 'NEW'}, ['NEW'], {}), ['scst-NEW'])
def test_unknown_or_missing_members_never_select_a_partial_group(self):
for definition in ('labels = ["CDB", "MISSING"]',
'suites = ["scst-CDB", "scst-missing"]'):
composites = self.load('[IO]\n' + definition)
for selected in (['IO'], ['LIB', 'IO']):
with self.subTest(definition=definition, selected=selected), self.assertRaisesRegex(
ValueError, 'IO: .*absent'):
expand_labels(self.SUITES, selected, composites)
# Unselected sets must not prevent a run using a subset of modules.
self.assertEqual(expand_labels(self.SUITES, ['LIB'], composites), ['scst-LIB'])
self.assertEqual(expand_labels(self.SUITES, ['ALL'], composites), list(self.SUITES))
def test_namespace_conflicts_are_errors_even_for_all(self):
composites = self.load('[VDISK]\nlabels = ["CDB"]')
for selected in (['VDISK'], ['CDB'], ['ALL']):
with self.subTest(selected=selected), self.assertRaisesRegex(ValueError, 'conflict'):
expand_labels(self.SUITES, selected, composites)
def test_invalid_configurations(self):
for text in ('x = 1', '[ALL]\nlabels = ["CDB"]', '[lower]\nlabels = ["CDB"]',
'[IO]', '[IO]\nlabels = []\nsuites = []', '[IO]\nunknown = []',
'[IO]\nlabels = "CDB"', '[IO]\nlabels = [1]',
'[IO]\nsuites = [false]', '[IO]\nsuites = ["scst-*.case"]',
'[IO]\nsuites = ["../results"]', '[IO]\nlabels = ["ALL"]',
'[IO]\nlabels = ["IO"]', '[IO]\nlabels = ["OTHER"]\n[OTHER]\nlabels = ["CDB"]',
'[IO]\nlabels = ["CDB"]\ndescription = 1',
'[IO]\nlabels = ["CDB"]\ndescription = "line\\nline"',
'[IO]\nlabels = ["CDB"]\n[IO]\nlabels = ["LIB"]'):
with self.subTest(text=text), self.assertRaises(ValueError):
self.load(text)
def test_invalid_selections(self):
for selected in ([], ['UNKNOWN'], ['ALL', 'LIB'], ['ALL', 'ALL']):
with self.subTest(selected=selected), self.assertRaises(ValueError):
expand_labels(self.SUITES, selected, {})
if __name__ == '__main__':
unittest.main()
+22 -3
View File
@@ -12,13 +12,14 @@ import tempfile
import unittest
from inventory import source_suites
from labels import load_composites
ROOT = Path(__file__).resolve().parents[2]
MODULES = re.findall(r'[\w/-]+\.ko', (ROOT / 'scst/src/kunit/modules.mk').read_text())
SOURCE_FILES = sorted({path for module in MODULES
for path in (ROOT / module).parent.joinpath('kunit').rglob('*.c')})
SUITES = source_suites(SOURCE_FILES)
LABELS = sorted(set(SUITES.values()))
LABELS = sorted(set(SUITES.values()) | load_composites().keys())
ENTRY_POINTS = [entry for entry in ('', 'scst', 'scst/src', 'scst/src/dev_handlers', 'iscsi-scst')
if 'kunit/select.mk' in (ROOT / entry / 'Makefile').read_text()]
OWNERS = {
@@ -129,7 +130,9 @@ class SelectionTests(unittest.TestCase):
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn('Available KUNIT_LABEL: ' + ' '.join(LABELS) + ' ALL', result.stdout)
listed = {line.strip() for line in result.stdout.splitlines() if line.startswith(' ')}
self.assertEqual(listed, {f'{label}: {suite}' for suite, label in SUITES.items()})
self.assertTrue({f'{label}: {suite}' for suite, label in SUITES.items()} <= listed)
self.assertIn('Composite labels:', result.stdout)
self.assertIn(' IO: Command decoding, UNMAP and virtual disk checks', result.stdout)
self.assertNotIn('Selected', result.stdout)
self.assertNotIn('Flags:', result.stdout)
@@ -147,6 +150,22 @@ class SelectionTests(unittest.TestCase):
cwd=ROOT, env=env, capture_output=True, text=True)
return result, json.loads(result.stdout) if result.returncode == 0 else []
def test_list_and_run_accept_the_same_label_configuration(self):
with tempfile.TemporaryDirectory(prefix='kunit labels ') as tmp:
config = Path(tmp) / 'custom labels.toml'
config.write_text('[CAPACITY]\nsuites = ["scst-VDISK-capacity"]\n')
option = f'KUNIT_ARGS=--label-config "{config}"'
result = subprocess.run(['make', '-s', 'kunit-list', 'KUNIT_LABEL=UNKNOWN', option],
cwd=ROOT, capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn('CAPACITY', result.stdout.splitlines()[1])
self.assertNotIn('Selected', result.stdout)
self.assertTrue(result.stdout.endswith(' CAPACITY:\n scst-VDISK-capacity\n'))
result, args = self.runner_arguments(option, selection='CAPACITY')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(args, ['scripts/kunit/run.py', '--labels', 'CAPACITY',
'--label-config', str(config)])
def test_runner_defaults_to_all_and_receives_explicit_labels(self):
result, args = self.runner_arguments(selection=None)
self.assertEqual(result.returncode, 0, result.stderr)
@@ -188,7 +207,7 @@ class SelectionTests(unittest.TestCase):
root = Path(tmp)
paths = ['Makefile', 'scst/Makefile', 'scst/src/Makefile',
'scst/src/kunit/modules.mk', 'scst/src/kunit/select.mk',
'scripts/kunit/inventory.py',
'scripts/kunit/inventory.py', 'scripts/kunit/labels.py', 'scripts/kunit/labels.toml',
'scst/include/scst.h', 'scst/include/scst_const.h',
'scst/include/scst_user.h', 'iscsi-scst/Makefile',
'iscsi-scst/include/iscsi_scst.h',
+45 -5
View File
@@ -747,7 +747,7 @@ class LifecycleTests(unittest.TestCase):
def execute(self, logs=None, labels=None, inventory=None, load_logs=None,
loaded=(), missing=(), failure=None, unload_warning=False, preflight=None,
kernel_mode='built-in', interface_error=None, remains=(),
fail_loaded=False, failure_code=1, cleanup_failure=False, observe=None, results=None):
fail_loaded=False, failure_code=1, cleanup_failure=False, observe=None, results=None, label_config=None):
result = report()
capture = Mock(messages=[])
inventory = inventory or [manifest()]
@@ -814,8 +814,9 @@ class LifecycleTests(unittest.TestCase):
patch.object(run, 'command', side_effect=operation) as command:
try:
run.execute([Path('/mock/' + i['name'] + '.ko') for i in inventory],
10, Path(tmp), result, labels)
except run.RunError as exc:
10, Path(tmp), result, labels,
label_config=label_config or run.DEFAULT_LABEL_CONFIG)
except (run.RunError, ValueError) as exc:
result['errors'].append(str(exc))
operations = []
for call in command.call_args_list:
@@ -1030,6 +1031,45 @@ class LifecycleTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp, contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(run.finish(result, Path(tmp)), 0)
def test_composite_selection_loads_dependencies_and_runs_each_suite_once(self):
with tempfile.TemporaryDirectory() as tmp:
config = Path(tmp) / 'custom labels.toml'
config.write_text('[IO]\nlabels = ["VDISK"]\nsuites = ["scst-LIB"]\n')
result, operations, _ = self.execute(
{'scst-LIB': PASS, 'scst-VDISK-capacity': VDISK_PASS},
inventory=list(reversed(self.both_manifests())), labels=['IO', 'VDISK'],
label_config=config)
self.assertFalse(result['errors'])
self.assertEqual(operations, [('insmod', 'scst'), ('suite', 'scst-LIB'),
('insmod', 'scst_vdisk'), ('suite', 'scst-VDISK-capacity'),
('rmmod', 'scst_vdisk'), ('rmmod', 'scst')])
self.assertEqual(result['label_config'], str(config))
self.assertEqual(result['composite_labels']['IO']['suites'], ['scst-LIB'])
def test_exact_suite_group_does_not_run_other_suites_in_its_module(self):
with tempfile.TemporaryDirectory() as tmp:
config = Path(tmp) / 'labels.toml'
config.write_text('[CAPACITY]\nsuites = ["scst-VDISK-capacity"]\n')
inventory = [manifest(), manifest('scst_vdisk',
{'scst-VDISK-capacity': 'VDISK', 'scst-VDISK-sync': 'VDISK'})]
result, operations, _ = self.execute({'scst-VDISK-capacity': VDISK_PASS},
inventory=inventory, labels=['CAPACITY'], label_config=config)
self.assertFalse(result['errors'])
self.assertEqual([name for op, name in operations if op == 'suite'], ['scst-VDISK-capacity'])
self.assertEqual([name for op, name in operations if op == 'insmod'], ['scst', 'scst_vdisk'])
def test_incomplete_composite_is_rejected_before_any_module_operation(self):
with tempfile.TemporaryDirectory() as tmp:
config = Path(tmp) / 'labels.toml'
for definition in ('labels = ["LIB", "VDISK"]',
'suites = ["scst-LIB", "scst-VDISK-capacity"]'):
config.write_text('[IO]\n' + definition)
result, operations, _ = self.execute(labels=['IO'], label_config=config,
kernel_mode='load')
self.assertIn('absent', result['errors'][0])
self.assertEqual(operations, [])
self.assertEqual(result['module_state'], 'not attempted')
def test_multiple_labels_and_nonmatching_suite_names(self):
labels = {'scst-LIB': 'LIB', 'scst-CM': 'CM', 'scst-LIFECYCLE': 'LIFECYCLE',
'scst-LIFECYCLE-blocking': 'LIFECYCLE'}
@@ -1336,7 +1376,7 @@ class ConfigurationTests(unittest.TestCase):
config = root / 'settings.toml'
config.write_text('timeout_seconds=12\nverbose=true\n')
with patch.object(run, 'DEFAULT_MODULE_LIST', modules):
selected, timeout, _, verbose, labels = self.arguments(['--config', str(config)])
selected, timeout, _, verbose, _, labels = self.arguments(['--config', str(config)])
self.assertEqual(selected, [core, handler])
self.assertEqual(timeout, 12)
self.assertTrue(verbose)
@@ -1375,7 +1415,7 @@ class ConfigurationTests(unittest.TestCase):
(directory / 'scst.ko').touch()
config = directory / 'settings.toml'
config.write_text('module="scst.ko"\noutput_dir="reports"\ntimeout_seconds=9\n')
module, timeout, output, verbose, labels = self.arguments(['--config', str(config)])
module, timeout, output, verbose, _, labels = self.arguments(['--config', str(config)])
self.assertFalse(verbose)
self.assertEqual(module, [directory / 'scst.ko'])
self.assertEqual(output, directory / 'reports')