源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { dirname, join, relative, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const SCRIPT_PATH = fileURLToPath(import.meta.url);
|
||||
const REPO_ROOT = resolve(dirname(SCRIPT_PATH), '..');
|
||||
const OUTPUT_PATH = join(REPO_ROOT, 'docs', 'windows-test-inventory.md');
|
||||
const SEARCH_ROOTS = ['apps', 'packages', 'scripts'];
|
||||
|
||||
export async function collectWindowsTestSkips(root = REPO_ROOT) {
|
||||
const files = [];
|
||||
for (const searchRoot of SEARCH_ROOTS) {
|
||||
const absolute = join(root, searchRoot);
|
||||
if (existsSync(absolute)) await collectTestFiles(absolute, files);
|
||||
}
|
||||
|
||||
const entries = [];
|
||||
for (const file of files.sort()) {
|
||||
const path = relative(root, file).replaceAll('\\', '/');
|
||||
const sourceText = await readFile(file, 'utf8');
|
||||
const sourceLines = sourceText.split(/\r?\n/u);
|
||||
for (const skip of findSkipExpressions(sourceText)) {
|
||||
const expression = skip.expression.replaceAll(/\s+/gu, ' ');
|
||||
if (!excludesWindows(expression)) continue;
|
||||
const index = sourceText.slice(0, skip.offset).split(/\r?\n/u).length - 1;
|
||||
const title = nearbyTestTitle(sourceLines, index);
|
||||
entries.push({
|
||||
path,
|
||||
line: index + 1,
|
||||
title,
|
||||
expression,
|
||||
classification: classifySkip(path, title, expression),
|
||||
});
|
||||
}
|
||||
}
|
||||
return entries.sort(
|
||||
(left, right) => left.path.localeCompare(right.path) || left.line - right.line,
|
||||
);
|
||||
}
|
||||
|
||||
export function findSkipExpressions(sourceText) {
|
||||
const expressions = [];
|
||||
let index = 0;
|
||||
while (index < sourceText.length) {
|
||||
const next = skipTriviaOrLiteral(sourceText, index);
|
||||
if (next !== index) {
|
||||
index = next;
|
||||
continue;
|
||||
}
|
||||
if (!sourceText.startsWith('skip', index) || isIdentifierPart(sourceText[index - 1])) {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const afterIdentifier = index + 4;
|
||||
if (isIdentifierPart(sourceText[afterIdentifier])) {
|
||||
index = afterIdentifier;
|
||||
continue;
|
||||
}
|
||||
const colon = skipWhitespaceAndComments(sourceText, afterIdentifier);
|
||||
if (sourceText[colon] !== ':') {
|
||||
index = afterIdentifier;
|
||||
continue;
|
||||
}
|
||||
const expressionStart = skipWhitespaceAndComments(sourceText, colon + 1);
|
||||
const expressionEnd = findExpressionEnd(sourceText, expressionStart);
|
||||
const expression = sourceText.slice(expressionStart, expressionEnd).trim();
|
||||
if (expression) expressions.push({ offset: index, expression });
|
||||
index = Math.max(expressionEnd, afterIdentifier);
|
||||
}
|
||||
return expressions;
|
||||
}
|
||||
|
||||
function findExpressionEnd(sourceText, start) {
|
||||
const closing = [];
|
||||
let index = start;
|
||||
while (index < sourceText.length) {
|
||||
const next = skipTriviaOrLiteral(sourceText, index);
|
||||
if (next !== index) {
|
||||
index = next;
|
||||
continue;
|
||||
}
|
||||
const char = sourceText[index];
|
||||
if (char === '(') closing.push(')');
|
||||
else if (char === '[') closing.push(']');
|
||||
else if (char === '{') closing.push('}');
|
||||
else if (closing.at(-1) === char) closing.pop();
|
||||
else if (closing.length === 0 && (char === ',' || char === '}')) return index;
|
||||
index += 1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
function skipTriviaOrLiteral(sourceText, index) {
|
||||
const char = sourceText[index];
|
||||
if (char === '/' && sourceText[index + 1] === '/') {
|
||||
const newline = sourceText.indexOf('\n', index + 2);
|
||||
return newline === -1 ? sourceText.length : newline + 1;
|
||||
}
|
||||
if (char === '/' && sourceText[index + 1] === '*') {
|
||||
const end = sourceText.indexOf('*/', index + 2);
|
||||
return end === -1 ? sourceText.length : end + 2;
|
||||
}
|
||||
if (char === "'" || char === '"' || char === '`')
|
||||
return skipQuotedLiteral(sourceText, index, char);
|
||||
return index;
|
||||
}
|
||||
|
||||
function skipQuotedLiteral(sourceText, start, quote) {
|
||||
let index = start + 1;
|
||||
while (index < sourceText.length) {
|
||||
if (sourceText[index] === '\\') {
|
||||
index += 2;
|
||||
continue;
|
||||
}
|
||||
if (sourceText[index] === quote) return index + 1;
|
||||
index += 1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
function skipWhitespaceAndComments(sourceText, start) {
|
||||
let index = start;
|
||||
while (index < sourceText.length) {
|
||||
if (/\s/u.test(sourceText[index])) {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const next = skipTriviaOrLiteral(sourceText, index);
|
||||
if (next === index || !sourceText.startsWith('/', index)) return index;
|
||||
index = next;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
function isIdentifierPart(char) {
|
||||
return char !== undefined && /[$\w]/u.test(char);
|
||||
}
|
||||
|
||||
export function renderWindowsTestInventory(entries) {
|
||||
const counts = new Map();
|
||||
for (const entry of entries) {
|
||||
counts.set(entry.classification, (counts.get(entry.classification) ?? 0) + 1);
|
||||
}
|
||||
const lines = [
|
||||
'# Windows test skip inventory',
|
||||
'',
|
||||
'<!-- Generated by `node scripts/windows-test-inventory.mjs --write`. Do not edit manually. -->',
|
||||
'',
|
||||
'This inventory covers test declarations whose `skip` expression excludes Windows. It is a Phase 0 baseline, not permission to retain the skips indefinitely.',
|
||||
'Locations intentionally omit line numbers so unrelated edits do not invalidate the semantic inventory.',
|
||||
'',
|
||||
'## Classifications',
|
||||
'',
|
||||
'- `windows-backend-gap`: the product contract is relevant on Windows, but the Windows transport, process, crash, or sandbox evidence is missing.',
|
||||
'- `portable-candidate`: the behavior appears platform-neutral; remove the skip or document the concrete Windows blocker.',
|
||||
'- `platform-contract`: the test intentionally exercises a POSIX/macOS primitive with no direct Windows equivalent.',
|
||||
'',
|
||||
'## Summary',
|
||||
'',
|
||||
'| Classification | Count |',
|
||||
'|---|---:|',
|
||||
];
|
||||
for (const classification of ['windows-backend-gap', 'portable-candidate', 'platform-contract']) {
|
||||
lines.push(`| ${classification} | ${counts.get(classification) ?? 0} |`);
|
||||
}
|
||||
lines.push(
|
||||
'',
|
||||
`Total Windows-excluded declarations: **${entries.length}**`,
|
||||
'',
|
||||
'## Inventory',
|
||||
'',
|
||||
);
|
||||
lines.push('| Classification | Test | Skip expression |');
|
||||
lines.push('|---|---|---|');
|
||||
for (const entry of entries) {
|
||||
lines.push(
|
||||
`| ${entry.classification} | \`${escapeTable(entry.path)}\` ${escapeTable(entry.title)} | \`${escapeTable(entry.expression)}\` |`,
|
||||
);
|
||||
}
|
||||
return `${lines.join('\n')}\n`;
|
||||
}
|
||||
|
||||
export function windowsTestInventoriesMatch(current, rendered) {
|
||||
return normalizeLineEndings(current) === normalizeLineEndings(rendered);
|
||||
}
|
||||
|
||||
function normalizeLineEndings(value) {
|
||||
return value.replaceAll(/\r\n?|\n/gu, '\n');
|
||||
}
|
||||
|
||||
function classifySkip(path, title, expression) {
|
||||
const value = `${path} ${title} ${expression}`.toLowerCase();
|
||||
if (
|
||||
/process\.platform\s*!==\s*['"]darwin['"]/u.test(expression) ||
|
||||
value.includes('posix process discovery') ||
|
||||
value.includes('posix detached process-group') ||
|
||||
value.includes('posix snapshot') ||
|
||||
value.includes('posix process snapshot') ||
|
||||
value.includes('graceful sigterm') ||
|
||||
value.includes('publishes private posix endpoint') ||
|
||||
value.includes('open sqlite') ||
|
||||
value.includes('fifo') ||
|
||||
value.includes('non-utf-8 git path') ||
|
||||
value.includes('permissions') ||
|
||||
path.endsWith('/shell-env.test.ts')
|
||||
) {
|
||||
return 'platform-contract';
|
||||
}
|
||||
if (
|
||||
path.includes('/runtime-host/') ||
|
||||
value.includes('process death') ||
|
||||
value.includes('real-process crash') ||
|
||||
value.includes('sqlite-runtime-crash') ||
|
||||
value.includes('sqlite-long-term-memory-crash') ||
|
||||
value.includes('sandbox')
|
||||
) {
|
||||
return 'windows-backend-gap';
|
||||
}
|
||||
return 'portable-candidate';
|
||||
}
|
||||
|
||||
export function excludesWindows(expression) {
|
||||
const compact = stripBalancedOuterParentheses(expression.replaceAll(/\s+/gu, ' ').trim());
|
||||
// A Windows-only regression commonly uses `false` on Windows and a skip
|
||||
// reason elsewhere. Exempt only when that ternary is the complete skip
|
||||
// expression: a surrounding boolean expression may still skip on Windows.
|
||||
if (
|
||||
/^process\.platform\s*===\s*(['"])win32\1\s*\?\s*false\s*:\s*(?:'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*")$/u.test(
|
||||
compact,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
/process\.platform\s*===\s*['"]win32['"]/u.test(compact) ||
|
||||
/process\.platform\s*!==\s*['"]darwin['"]/u.test(compact)
|
||||
);
|
||||
}
|
||||
|
||||
function stripBalancedOuterParentheses(expression) {
|
||||
let compact = expression;
|
||||
while (hasCompleteOuterParentheses(compact)) compact = compact.slice(1, -1).trim();
|
||||
return compact;
|
||||
}
|
||||
|
||||
function hasCompleteOuterParentheses(expression) {
|
||||
if (!expression.startsWith('(') || !expression.endsWith(')')) return false;
|
||||
let depth = 0;
|
||||
let quote;
|
||||
let escaped = false;
|
||||
for (let index = 0; index < expression.length; index += 1) {
|
||||
const char = expression[index];
|
||||
if (quote) {
|
||||
if (escaped) escaped = false;
|
||||
else if (char === '\\') escaped = true;
|
||||
else if (char === quote) quote = undefined;
|
||||
continue;
|
||||
}
|
||||
if (char === "'" || char === '"' || char === '`') {
|
||||
quote = char;
|
||||
continue;
|
||||
}
|
||||
if (char === '(') depth += 1;
|
||||
else if (char === ')') {
|
||||
depth -= 1;
|
||||
if (depth === 0) return index === expression.length - 1;
|
||||
if (depth < 0) return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function nearbyTestTitle(lines, skipIndex) {
|
||||
const context = lines.slice(Math.max(0, skipIndex - 12), skipIndex + 1).join('\n');
|
||||
const matches = [...context.matchAll(/\b(?:test|it|describe)\s*\(\s*(['"`])([\s\S]+?)\1/gu)];
|
||||
return matches.at(-1)?.[2].replaceAll(/\s+/gu, ' ').trim() ?? '<dynamic test title>';
|
||||
}
|
||||
|
||||
async function collectTestFiles(directory, output) {
|
||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||
if (entry.name === 'node_modules' || entry.name === 'dist') continue;
|
||||
const path = join(directory, entry.name);
|
||||
if (entry.isDirectory()) await collectTestFiles(path, output);
|
||||
else if (/\.test\.(?:[cm]?js|tsx?)$/u.test(entry.name)) output.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeTable(value) {
|
||||
return String(value).replaceAll('|', '\\|').replaceAll('\n', ' ');
|
||||
}
|
||||
|
||||
async function main(args) {
|
||||
const entries = await collectWindowsTestSkips();
|
||||
const rendered = renderWindowsTestInventory(entries);
|
||||
if (args.includes('--write')) {
|
||||
await writeFile(OUTPUT_PATH, rendered);
|
||||
console.log(`wrote ${relative(REPO_ROOT, OUTPUT_PATH)} (${entries.length} declarations)`);
|
||||
return;
|
||||
}
|
||||
if (args.includes('--check')) {
|
||||
const current = existsSync(OUTPUT_PATH) ? await readFile(OUTPUT_PATH, 'utf8') : '';
|
||||
if (!windowsTestInventoriesMatch(current, rendered)) {
|
||||
throw new Error(
|
||||
'Windows test skip inventory is stale; run `npm run windows:inventory:write`',
|
||||
);
|
||||
}
|
||||
console.log(`Windows test skip inventory is current (${entries.length} declarations)`);
|
||||
return;
|
||||
}
|
||||
process.stdout.write(rendered);
|
||||
}
|
||||
|
||||
if (process.argv[1] && resolve(process.argv[1]) === SCRIPT_PATH) {
|
||||
main(process.argv.slice(2)).catch((error) => {
|
||||
console.error(error.message);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user