mirror of
https://github.com/SCST-project/scst.git
synced 2026-10-22 14:12:33 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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',
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user