diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile index 101b1b560..fc690ce40 100644 --- a/scripts/ci/Dockerfile +++ b/scripts/ci/Dockerfile @@ -35,7 +35,7 @@ FROM runtime AS build ARG SUITE ARG SOURCE_COMMIT RUN dnf install -y --setopt=install_weak_deps=False \ - gcc elfutils-libelf-devel openssl openssl-devel perl-ExtUtils-MakeMaker git \ + gcc elfutils-libelf-devel openssl openssl-devel perl-ExtUtils-MakeMaker git patch \ /tmp/kernel/kernel-devel-*.rpm && dnf clean all COPY . /scst WORKDIR /scst diff --git a/scripts/kernel/specialize-patch b/scripts/kernel/specialize-patch index 7aa0dee57..1a8e7ae29 100755 --- a/scripts/kernel/specialize-patch +++ b/scripts/kernel/specialize-patch @@ -191,10 +191,12 @@ function evaluate(stmnt, pattern, arg, op, result) { gsub("defined\\(CONFIG_SCST_STRICT_SERIALIZING\\)", "0", stmnt) + # Undefined vendor version macros evaluate to zero in #if expressions. + # Keep an operand so that subtraction works with both "-0" and "- 0". if (RHEL_MAJOR == "") { gsub("defined\\(RHEL_MAJOR\\)", "0", stmnt) - gsub("RHEL_MAJOR", "", stmnt) + gsub("RHEL_MAJOR", "0", stmnt) } else { @@ -217,7 +219,7 @@ function evaluate(stmnt, pattern, arg, op, result) { if (RHEL_MAJOR == "" || RHEL_MINOR == "") { gsub("defined\\(RHEL_RELEASE_CODE\\)", "0", stmnt) - gsub("RHEL_RELEASE_CODE", "", stmnt) + gsub("RHEL_RELEASE_CODE", "0", stmnt) gsub("RHEL_RELEASE_VERSION\\([^,)]*,[ ]*[^,)]*\\)", "0", stmnt) } else @@ -230,7 +232,7 @@ function evaluate(stmnt, pattern, arg, op, result) { if (MOFED_MAJOR == "") { gsub("defined\\(MOFED_MAJOR\\)", "0", stmnt) - gsub("MOFED_MAJOR", "", stmnt) + gsub("MOFED_MAJOR", "0", stmnt) } else { gsub("defined\\(MOFED_MAJOR\\)", "1", stmnt) gsub("MOFED_MAJOR", MOFED_MAJOR, stmnt) @@ -238,7 +240,7 @@ function evaluate(stmnt, pattern, arg, op, result) { if (MOFED_MINOR == "") { gsub("defined\\(MOFED_MINOR\\)", "0", stmnt) - gsub("MOFED_MINOR", "", stmnt) + gsub("MOFED_MINOR", "0", stmnt) } else { gsub("defined\\(MOFED_MINOR\\)", "1", stmnt) gsub("MOFED_MINOR", MOFED_MINOR, stmnt) @@ -511,7 +513,7 @@ function process_preprocessor_statement(evaluated, condition) { else if (evaluated ~ "^\\+#elif") { if (if_discarded[if_nesting_level]) { - sub("^\\+#elif", "+#if", input_line[0]) + sub("^\\+[[:blank:]]*#[[:blank:]]*elif", "+#if", input_line[0]) if (debug) printf "/* debug specialize-patch: (g0) -> %s */\n", evaluated } @@ -755,7 +757,7 @@ BEGIN { else { delete_next_blank_line = 0 - if (match($0, "^\\+ *#")) + if (match($0, "^\\+[[:blank:]]*#")) { if (debug) printf "/* debug specialize-patch: line %d: %s*/\n", NR, $0 diff --git a/scripts/kunit/test_generator.py b/scripts/kunit/test_generator.py index eab4d7121..9ca0491ab 100644 --- a/scripts/kunit/test_generator.py +++ b/scripts/kunit/test_generator.py @@ -12,6 +12,122 @@ ROOT = Path(__file__).resolve().parents[2] class GeneratorTests(unittest.TestCase): + def specialize(self, source, version, blank, variables=()): + patch = ('--- /dev/null\t2026-10-21\n' + '+++ linux/drivers/scst/probe.c\t2026-10-21\n' + f'@@ -0,0 +1,{len(source)} @@\n' + + ''.join('+' + line + '\n' for line in source)) + command = [str(ROOT / 'scripts/kernel/specialize-patch'), + '-v', 'kernel_version=' + version, + '-v', f'blank_deleted_code={blank}'] + for variable in variables: + command.extend(['-v', variable]) + result = subprocess.run(command, input=patch, capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + # Applying the patch also verifies its rewritten hunk counts. + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / 'drivers/scst').mkdir(parents=True) + applied = subprocess.run( + ['patch', '--batch', '-s', '-p1', '-d', str(root)], + input=result.stdout, capture_output=True, text=True) + self.assertEqual(applied.returncode, 0, + applied.stdout + applied.stderr + result.stdout) + output = (root / 'drivers/scst/probe.c').read_text().splitlines() + return [line for line in output if line] + + def test_specialize_preserves_branch_selection_with_indented_directives(self): + for version, branch in [('3.18', 'legacy'), ('7.3', 'modern')]: + for indent, gap in [('', ''), (' ', ' '), ('\t', ''), ('\t ', ' \t')]: + for blank in (0, 1): + with self.subTest(version=version, indent=indent, gap=gap, blank=blank): + directive = indent + '#' + gap + source = [ + directive + 'if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)', + 'int modern;', + directive + 'else', + 'int legacy;', + directive + 'endif', + directive + 'ifdef CONFIG_SCST_SPECIALIZE_TEST', + directive + 'if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)', + 'int nested_modern;', + directive + 'else', + 'int nested_legacy;', + directive + 'endif', + directive + 'endif', + directive + 'if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)', + 'int unsupported;', + directive + 'elif defined(CONFIG_SCST_SPECIALIZE_TEST)', + 'int optional;', + directive + 'endif', + 'int tail;', + ] + self.assertEqual(self.specialize(source, version, blank), [ + f'int {branch};', + directive + 'ifdef CONFIG_SCST_SPECIALIZE_TEST', + f'int nested_{branch};', + directive + 'endif', + '#if defined(CONFIG_SCST_SPECIALIZE_TEST)', + 'int optional;', + directive + 'endif', + 'int tail;', + ]) + + def test_specialize_selects_flush_api_with_vendor_arithmetic(self): + # Match vdisk_blockio_flush(), including its continued conditions. + source = [ + '#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 8, 0) && \\', + '\t(!defined(RHEL_MAJOR) || RHEL_MAJOR - 0 < 8 || \\', + '\t RHEL_MAJOR - 0 == 8 && RHEL_MINOR - 0 < 4)', + 'res = blkdev_issue_flush(bdev, gfp_mask, NULL);', + '#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) && \\', + '\t(!defined(RHEL_MAJOR) || RHEL_MAJOR - 0 < 8 || \\', + '\t RHEL_MAJOR - 0 == 8 && RHEL_MINOR - 0 < 6)', + 'res = blkdev_issue_flush(bdev, gfp_mask);', + '#else', + 'res = blkdev_issue_flush(bdev);', + '#endif', + ] + cases = [ + ('3.18', (), 'bdev, gfp_mask, NULL'), + ('5.7', (), 'bdev, gfp_mask, NULL'), + ('5.8', (), 'bdev, gfp_mask'), + ('5.11', (), 'bdev, gfp_mask'), + ('5.12', (), 'bdev'), + ('6.18.57', (), 'bdev'), + ('7.3', (), 'bdev'), + ('3.10', ('RHEL_MAJOR=7', 'RHEL_MINOR=9'), 'bdev, gfp_mask, NULL'), + ('4.18', ('RHEL_MAJOR=8', 'RHEL_MINOR=3'), 'bdev, gfp_mask, NULL'), + ('4.18', ('RHEL_MAJOR=8', 'RHEL_MINOR=4'), 'bdev, gfp_mask'), + ('4.18', ('RHEL_MAJOR=8', 'RHEL_MINOR=5'), 'bdev, gfp_mask'), + ('4.18', ('RHEL_MAJOR=8', 'RHEL_MINOR=6'), 'bdev'), + ('5.14', ('RHEL_MAJOR=9', 'RHEL_MINOR=0'), 'bdev'), + ] + for version, variables, arguments in cases: + for subtraction in ('-0', '- 0'): + for blank in (0, 1): + with self.subTest(version=version, variables=variables, + subtraction=subtraction, blank=blank): + fixture = [line.replace('- 0', subtraction) for line in source] + self.assertEqual(self.specialize(fixture, version, blank, variables), + [f'res = blkdev_issue_flush({arguments});']) + + def test_specialize_treats_absent_vendor_versions_as_zero(self): + for macro in ('RHEL_MAJOR', 'RHEL_MINOR', 'RHEL_RELEASE_CODE', + 'MOFED_MAJOR', 'MOFED_MINOR'): + for subtraction in ('', ' -0', ' - 0'): + for blank in (0, 1): + with self.subTest(macro=macro, subtraction=subtraction, blank=blank): + source = [ + f'#if defined({macro}) || {macro}{subtraction} != 0', + 'int vendor;', + '#else', + 'int upstream;', + '#endif', + ] + self.assertEqual(self.specialize(source, '6.18.57', blank), + ['int upstream;']) + def test_qla_helpers_resolve_siblings_but_keep_caller_outputs(self): with tempfile.TemporaryDirectory(prefix='qla helpers ') as tmp: root = Path(tmp)