import assert from 'node:assert/strict'; import {copyFileSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync} from 'node:fs'; import {spawnSync} from 'node:child_process'; import {createHash} from 'node:crypto'; import {dirname, join, relative, resolve} from 'node:path'; assert.equal(process.argv.length, 3, 'usage: node block-errors-local.mjs '); const root = process.cwd(); const predecessorPath = resolve(process.argv[2]); const predecessor = JSON.parse(readFileSync(predecessorPath, 'utf8')); assert.equal(predecessor.status, 'K1515_BLOCK_TIMEOUTS_DEVELOPMENT_MATRIX_VALIDATED'); const dir = mkdtempSync(join(root, 'reports/os-implementation/K1520-block-errors-local-')); const sha = path => createHash('sha256').update(readFileSync(path)).digest('hex'); const artifact = path => ({path: relative(root, path), sha256: sha(path)}); const copy = (source, target) => { mkdirSync(dirname(target), {recursive: true}); copyFileSync(join(root, source), target); return artifact(target); }; const source = join(dir, 'source'); const model = join(source, 'os-block-requests/src/lib.rs'); const fixture = join(source, 'kernel/tests/os/block_io.rs'); const contract = join(source, 'kernel/BLOCK_IO_REQUEST_CONTRACT.md'); const sources = { model: copy('os-block-requests/src/lib.rs', model), fixture: copy('kernel/tests/os/block_io.rs', fixture), contract: copy('kernel/BLOCK_IO_REQUEST_CONTRACT.md', contract), timeoutContract: copy('kernel/BLOCK_IO_TIMEOUT_CONTRACT.md', join(source, 'kernel/BLOCK_IO_TIMEOUT_CONTRACT.md')), errorsContract: copy('kernel/BLOCK_IO_ERRORS_CONTRACT.md', join(source, 'kernel/BLOCK_IO_ERRORS_CONTRACT.md')), driverReference: copy('kernel/src/virtio_ro_turkfs.rs', join(source, 'kernel/src/virtio_ro_turkfs.rs')), harness: artifact(join(root, 'scripts/os-acceptance/block-errors-local.mjs')), }; const run = (name, program, args, expected) => { const result = spawnSync(program, args, {cwd: root, encoding: 'utf8'}); const log = join(dir, `${name}.log`); writeFileSync(log, `${result.stdout ?? ''}${result.stderr ?? ''}`); assert.equal(result.status, expected, `${name}: expected exit ${expected}, observed ${result.status}; ${artifact(log).path}`); return {exitCode: result.status, log: artifact(log)}; }; const binary = join(dir, 'block-requests-tests'); const tests = { compile: run('host-compile', 'rustc', ['--edition=2024', '--test', fixture, '-o', binary], 0), green: run('host-tests', binary, [], 0), }; const greenText = readFileSync(join(dir, 'host-tests.log'), 'utf8'); assert.match(greenText, /os_block_io_requests_positive \.\.\. ok/); assert.match(greenText, /os_block_io_requests_negative \.\.\. ok/); assert.match(greenText, /os_block_io_timeouts_positive \.\.\. ok/); assert.match(greenText, /os_block_io_timeouts_negative \.\.\. ok/); assert.match(greenText, /os_block_io_errors_positive \.\.\. ok/); assert.match(greenText, /os_block_io_errors_negative \.\.\. ok/); assert.match(greenText, /6 passed; 0 failed/); const noStdRoot = join(source, 'no-std-root.rs'); writeFileSync(noStdRoot, '#![no_std]\n#[path = "os-block-requests/src/lib.rs"]\npub mod block_io;\n'); sources.noStdRoot = artifact(noStdRoot); tests.target = run('aarch64-no-std-check', 'rustc', ['--edition=2024', '--crate-type=lib', '--target=aarch64-unknown-none', '--emit=metadata', noStdRoot, '-o', join(dir, 'model.rmeta')], 0); const mutantRoot = join(dir, 'mutant'); const mutantModel = join(mutantRoot, 'os-block-requests/src/lib.rs'); const mutantFixture = join(mutantRoot, 'kernel/tests/os/block_io.rs'); copy('kernel/tests/os/block_io.rs', mutantFixture); mkdirSync(dirname(mutantModel), {recursive: true}); const original = readFileSync(model, 'utf8'); const lengthGuard = 'if used_length != 513 { return Err(Error::ShortRead); }'; assert.equal(original.split(lengthGuard).length, 2, 'full sector length guard must occur once'); writeFileSync(mutantModel, original.replace(lengthGuard, 'if false { return Err(Error::ShortRead); }')); const mutant = artifact(mutantModel); const mutantBinary = join(dir, 'block-requests-mutant-tests'); tests.mutantCompile = run('mutant-compile', 'rustc', ['--edition=2024', '--test', mutantFixture, '-o', mutantBinary], 0); tests.red = run('mutant-tests', mutantBinary, [], 101); assert.match(readFileSync(join(dir, 'mutant-tests.log'), 'utf8'), /os_block_io_errors_negative \.\.\. FAILED/); const report = { schema: 'turkos-k1520-block-errors-local/v1', status: 'K1516_K1519_LOCAL_VALIDATED', guestAccepted: false, productAccepted: false, predecessor: artifact(predecessorPath), sources, mutant, tests: {...tests, greenCount: 6, mutationRejected: true}, scope: 'A used length of 257 is ShortRead, status 1 is IoError, status 2 is Unsupported, and only 513 bytes with status zero complete a sector. Host tests preserve ownership on error, reset before release and retry; AArch64 no_std compilation and a removed-length-guard mutant are recorded. Guest VFS error propagation remains for K1520.' }; const reportPath = join(dir, 'report.json'); writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n'); console.log(JSON.stringify({status: report.status, report: relative(root, reportPath), tests: report.tests.greenCount, mutationRejected: report.tests.mutationRejected}));