53 个 Python-remap inventory rows 批量提升为 PASS
结论:53 个 Python-remap inventory rows 已完成 row proof、browser proof 与 inventory baseline promotion,当前 inventory baseline 为 PASS=82 / SKIP=77 / unexpected_fail=0。
This commit is contained in:
@@ -986,7 +986,7 @@ TOOL_TABLE = browser-shell-tool.tbl
|
||||
);
|
||||
assertEqual(
|
||||
workflowOverviewReleaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
|
||||
"evidence-ready=8 inventory-ready=19",
|
||||
"evidence-ready=8 inventory-ready=2",
|
||||
"shell workflow overview release artifact URL workflow candidate layers",
|
||||
);
|
||||
assertEqual(
|
||||
|
||||
@@ -421,7 +421,7 @@
|
||||
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
|
||||
blockedCandidateIds: [],
|
||||
explicitBrowserDiagnosticsCount: 8,
|
||||
inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0",
|
||||
inventoryBaseline: "executed=82 passed=82 skipped=77 unexpected_fail=0",
|
||||
},
|
||||
virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({
|
||||
manifestEntries: [
|
||||
@@ -637,7 +637,7 @@
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
|
||||
"evidence-ready=8 inventory-ready=19",
|
||||
"evidence-ready=8 inventory-ready=2",
|
||||
"workflow overview release artifact URL workflow candidate layers",
|
||||
);
|
||||
assertEqual(
|
||||
@@ -772,7 +772,7 @@
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value,
|
||||
"executed=29 passed=29 skipped=130 unexpected_fail=0",
|
||||
"executed=82 passed=82 skipped=77 unexpected_fail=0",
|
||||
"workflow overview release artifact URL workflow candidate baseline",
|
||||
);
|
||||
assertEqual(
|
||||
@@ -787,7 +787,7 @@
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value,
|
||||
"18",
|
||||
"1",
|
||||
"workflow overview release artifact URL workflow locked hard-block rows",
|
||||
);
|
||||
assertEqual(
|
||||
@@ -807,17 +807,17 @@
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value,
|
||||
"L4-USER-M-PROCESS:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes",
|
||||
"L4-USER-M-PROCESS:1 locked=yes, L4-PYTHON-REMAP:0 locked=yes",
|
||||
"workflow overview release artifact URL workflow hard-block family summary",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value,
|
||||
"18",
|
||||
"1",
|
||||
"workflow overview release artifact URL workflow hard-block detail count",
|
||||
);
|
||||
assertEqual(
|
||||
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value,
|
||||
"L4-PYTHON-REMAP:axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture; promotion_allowed=0",
|
||||
"L4-USER-M-PROCESS:axis/vismach/millturn/example.ngc; reason=design_m128_m129_linuxcnc_state_boundary; next=kinstype_guard_and_ini_xyz_hal_pin_state; promotion_allowed=0",
|
||||
"workflow overview release artifact URL workflow hard-block detail preview",
|
||||
);
|
||||
assertEqual(
|
||||
@@ -948,7 +948,7 @@
|
||||
);
|
||||
assertEqual(
|
||||
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="promotion-candidate-layers"]').textContent,
|
||||
"evidence-ready=8 inventory-ready=19",
|
||||
"evidence-ready=8 inventory-ready=2",
|
||||
"workflow overview fixed URL workflow form candidate layer row",
|
||||
);
|
||||
assertEqual(
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"next-boundary-recommendations.tsv",
|
||||
"next-boundary-worklist.tsv",
|
||||
"promotion-candidates.tsv",
|
||||
"python-remap-browser-row-proof.tsv",
|
||||
"python-remap-bulk-promotion-plan.tsv",
|
||||
"python-remap-boundary-summary.tsv",
|
||||
"python-remap-family-summary.tsv",
|
||||
@@ -109,6 +110,7 @@
|
||||
"python-remap-row-runtime-proof.tsv",
|
||||
"python-remap-runtime-contract.tsv",
|
||||
"python-remap-runtime-gates.tsv",
|
||||
"python-remap-wasm-node-row-proof.tsv",
|
||||
"runtime-boundary-contract-summary.tsv",
|
||||
"runtime-boundary-family-host-readiness.tsv",
|
||||
"runtime-boundary-host-preflight.tsv",
|
||||
@@ -153,7 +155,7 @@
|
||||
"build/native/source-probes.tsv",
|
||||
];
|
||||
|
||||
if (wasmArtifactNames.length !== 57 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) {
|
||||
if (wasmArtifactNames.length !== 59 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) {
|
||||
throw new Error("browser_generated_artifact_documentation_coverage: WASM artifact list drift");
|
||||
}
|
||||
if (nativeArtifactTokens.length !== 8 || new Set(nativeArtifactTokens).size !== nativeArtifactTokens.length) {
|
||||
|
||||
@@ -33,10 +33,22 @@ self.linuxCncPythonRemapRuntimeProvider = {
|
||||
return { value: "generator-finished" };
|
||||
},
|
||||
|
||||
async stageNgcRemapAsset(message) {
|
||||
return { value: message.ngcPath };
|
||||
},
|
||||
|
||||
async rejectNgcOnlyStandalone(message) {
|
||||
return { value: message.ngcPath };
|
||||
},
|
||||
|
||||
async exportInterpreterState() {
|
||||
return { value: "interpreter-state-bound" };
|
||||
},
|
||||
|
||||
async exportCanonicalEvents() {
|
||||
return { value: "canonical-events-bound" };
|
||||
},
|
||||
|
||||
async exportDiagnostics() {
|
||||
return { value: "diagnostics-exported" };
|
||||
},
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
</head>
|
||||
<body>
|
||||
<pre id="status">running</pre>
|
||||
<pre id="python-remap-browser-row-proof"></pre>
|
||||
<script type="module">
|
||||
import {
|
||||
createLinuxCncPythonRemapBrowserWorkerAdapter,
|
||||
createLinuxCncPythonRemapRowRuntimePort,
|
||||
createLinuxCncPythonRemapRuntimePort,
|
||||
createPythonRemapRuntimeDiagnostics,
|
||||
validatePythonRemapRowRuntimeTranscript,
|
||||
validatePythonRemapLifecycleTranscript,
|
||||
} from "../../runtime/sdk/src/index.js";
|
||||
|
||||
@@ -22,6 +25,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
function assertDeepEqual(actual, expected, label) {
|
||||
const actualText = JSON.stringify(actual);
|
||||
const expectedText = JSON.stringify(expected);
|
||||
if (actualText !== expectedText) {
|
||||
throw new Error(`${label}: expected ${expectedText}, got ${actualText}`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseTsv(text, expectedHeaders = null) {
|
||||
const [headerLine, ...rows] = text.trim().split("\n");
|
||||
const headers = headerLine.split("\t");
|
||||
if (expectedHeaders) {
|
||||
assertDeepEqual(headers, expectedHeaders, "TSV header schema drift");
|
||||
}
|
||||
return rows.filter(Boolean).map((row) => {
|
||||
const values = row.split("\t");
|
||||
return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""]));
|
||||
});
|
||||
}
|
||||
|
||||
function splitSummaryList(value) {
|
||||
return String(value ?? "-")
|
||||
.split(",")
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry) => entry.length > 0 && entry !== "-");
|
||||
}
|
||||
|
||||
function listValue(values) {
|
||||
const filtered = [...new Set(values.filter(Boolean))].sort();
|
||||
return filtered.length === 0 ? "-" : filtered.join(",");
|
||||
}
|
||||
|
||||
function flagValue(value) {
|
||||
return value ? "1" : "0";
|
||||
}
|
||||
|
||||
function tsvValue(value) {
|
||||
return String(value ?? "-").replace(/\t|\r?\n/g, " ");
|
||||
}
|
||||
|
||||
function verifySamePathSet(label, expectedPaths, actualPaths) {
|
||||
assertDeepEqual([...actualPaths].sort(), [...expectedPaths].sort(), `${label}: path set drift`);
|
||||
}
|
||||
|
||||
async function fetchText(path) {
|
||||
const response = await fetch(path, { cache: "no-store" });
|
||||
if (!response.ok) {
|
||||
throw new Error(`${path}: fetch failed with ${response.status}`);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
|
||||
try {
|
||||
const contractOnlyPort = createLinuxCncPythonRemapRuntimePort({
|
||||
runtimeMode: "contract-only",
|
||||
@@ -121,6 +176,232 @@
|
||||
assertEqual(diagnostics.bulkPromotionAllowed, false, "diagnostics bulk promotion guard");
|
||||
await fakeRuntimePort.close();
|
||||
|
||||
const rowRuntimeProofHeaders = [
|
||||
"path",
|
||||
"ini",
|
||||
"family",
|
||||
"row_runtime_port_api_ready",
|
||||
"row_runtime_plan_ready",
|
||||
"row_runtime_proof_targets",
|
||||
"python_modules_staged",
|
||||
"toplevel_executed",
|
||||
"path_prepend_applied",
|
||||
"python_remap_functions_bound",
|
||||
"prolog_functions_bound",
|
||||
"epilog_functions_bound",
|
||||
"generator_lifecycle_ready",
|
||||
"ngc_remap_assets_staged",
|
||||
"ngc_only_subpaths_not_standalone",
|
||||
"interpreter_state_binding_ready",
|
||||
"canonical_events_ready",
|
||||
"row_runtime_transcript_ready",
|
||||
"native_pass_ready",
|
||||
"wasm_node_pass_ready",
|
||||
"browser_pass_ready",
|
||||
"execution_enabled",
|
||||
"proof_status",
|
||||
"blockers",
|
||||
"notes",
|
||||
];
|
||||
const wasmNodeProofHeaders = [
|
||||
"path",
|
||||
"ini",
|
||||
"family",
|
||||
"native_pass_ready",
|
||||
"wasm_node_runtime_bridge_ready",
|
||||
"wasm_node_plan_executed",
|
||||
"module_import_ready",
|
||||
"callable_lookup_ready",
|
||||
"generator_lifecycle_ready",
|
||||
"ngc_remap_assets_ready",
|
||||
"ngc_only_subpaths_guarded",
|
||||
"interpreter_state_binding_ready",
|
||||
"canonical_events_ready",
|
||||
"row_runtime_transcript_ready",
|
||||
"transcript_phase_count",
|
||||
"transcript_hash",
|
||||
"wasm_node_pass_ready",
|
||||
"browser_pass_ready",
|
||||
"execution_enabled",
|
||||
"promotion_allowed",
|
||||
"proof_status",
|
||||
"blockers",
|
||||
"notes",
|
||||
];
|
||||
const boundaryHeaders = [
|
||||
"path",
|
||||
"ini",
|
||||
"blocked",
|
||||
"design_status",
|
||||
"python_modules",
|
||||
"python_remap_functions",
|
||||
"prolog_functions",
|
||||
"epilog_functions",
|
||||
"remap_ngc_files",
|
||||
"ngc_only_subpaths",
|
||||
"hal_process",
|
||||
"ui_process",
|
||||
"halui_mdi_process",
|
||||
"linuxcnc_owner",
|
||||
"python_runtime_evidence",
|
||||
"required_boundary",
|
||||
"execution_enabled",
|
||||
"notes",
|
||||
];
|
||||
const rowRuntimeProofRows = parseTsv(
|
||||
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-row-runtime-proof.tsv"),
|
||||
rowRuntimeProofHeaders,
|
||||
);
|
||||
const wasmNodeRows = parseTsv(
|
||||
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-wasm-node-row-proof.tsv"),
|
||||
wasmNodeProofHeaders,
|
||||
);
|
||||
const boundaryRows = parseTsv(
|
||||
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-boundary-summary.tsv"),
|
||||
boundaryHeaders,
|
||||
);
|
||||
assertEqual(rowRuntimeProofRows.length, 53, "browser row proof input row count");
|
||||
assertEqual(wasmNodeRows.length, 53, "browser row proof Node input row count");
|
||||
assertEqual(boundaryRows.length, 53, "browser row proof boundary row count");
|
||||
verifySamePathSet(
|
||||
"browser_row_wasm_node_alignment",
|
||||
rowRuntimeProofRows.map((row) => row.path),
|
||||
wasmNodeRows.map((row) => row.path),
|
||||
);
|
||||
verifySamePathSet(
|
||||
"browser_row_boundary_alignment",
|
||||
rowRuntimeProofRows.map((row) => row.path),
|
||||
boundaryRows.map((row) => row.path),
|
||||
);
|
||||
|
||||
const wasmNodeByPath = new Map(wasmNodeRows.map((row) => [row.path, row]));
|
||||
const boundaryByPath = new Map(boundaryRows.map((row) => [row.path, row]));
|
||||
const browserProofHeaders = [
|
||||
"path",
|
||||
"ini",
|
||||
"family",
|
||||
"wasm_node_pass_ready",
|
||||
"browser_runtime_bridge_ready",
|
||||
"browser_worker_plan_executed",
|
||||
"module_import_ready",
|
||||
"callable_lookup_ready",
|
||||
"generator_lifecycle_ready",
|
||||
"ngc_remap_assets_ready",
|
||||
"ngc_only_subpaths_guarded",
|
||||
"interpreter_state_binding_ready",
|
||||
"canonical_events_ready",
|
||||
"row_runtime_transcript_ready",
|
||||
"transcript_phase_count",
|
||||
"transcript_hash",
|
||||
"browser_pass_ready",
|
||||
"execution_enabled",
|
||||
"promotion_allowed",
|
||||
"proof_status",
|
||||
"blockers",
|
||||
"notes",
|
||||
];
|
||||
const browserProofRows = [];
|
||||
|
||||
for (const row of rowRuntimeProofRows) {
|
||||
const wasmNode = wasmNodeByPath.get(row.path);
|
||||
const boundary = boundaryByPath.get(row.path);
|
||||
if (!wasmNode || !boundary) {
|
||||
throw new Error(`${row.path}: missing browser row proof prerequisite`);
|
||||
}
|
||||
assertEqual(row.native_pass_ready, "1", `${row.path}: native pass prerequisite`);
|
||||
assertEqual(row.wasm_node_pass_ready, "1", `${row.path}: row proof Node prerequisite`);
|
||||
assertEqual(wasmNode.wasm_node_pass_ready, "1", `${row.path}: Node artifact prerequisite`);
|
||||
assertEqual(wasmNode.proof_status, "wasm_node_row_runtime_bridge_passed", `${row.path}: Node artifact proof status`);
|
||||
assertEqual(boundary.ini, row.ini, `${row.path}: boundary INI drift`);
|
||||
|
||||
const context = {
|
||||
path: row.path,
|
||||
iniPath: row.ini,
|
||||
family: row.family,
|
||||
machineRoot: row.ini.split("/").slice(0, -1).join("/"),
|
||||
pythonPathPrepend: row.path_prepend_applied === "1" ? "inventory-configured-path" : "-",
|
||||
topLevelPath: row.toplevel_executed === "1" ? "inventory-configured-toplevel" : "-",
|
||||
sourceFiles: row.python_modules_staged === "1" ? splitSummaryList(boundary.python_modules) : [],
|
||||
pythonRemapFunctions: row.python_remap_functions_bound === "1" ? splitSummaryList(boundary.python_remap_functions) : [],
|
||||
prologFunctions: row.prolog_functions_bound === "1" ? splitSummaryList(boundary.prolog_functions) : [],
|
||||
epilogFunctions: row.epilog_functions_bound === "1" ? splitSummaryList(boundary.epilog_functions) : [],
|
||||
ngcRemapFiles: row.ngc_remap_assets_staged === "1" ? splitSummaryList(boundary.remap_ngc_files) : [],
|
||||
ngcOnlySubpaths: row.ngc_only_subpaths_not_standalone === "1" && row.row_runtime_proof_targets.includes("ngc_only_subpath_rejection")
|
||||
? splitSummaryList(boundary.ngc_only_subpaths)
|
||||
: [],
|
||||
};
|
||||
const rowAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
|
||||
workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url),
|
||||
pythonRuntimeModuleUrl: new URL("./python_remap_fake_runtime_worker.js", import.meta.url),
|
||||
});
|
||||
const rowPort = createLinuxCncPythonRemapRowRuntimePort({
|
||||
...context,
|
||||
runtimeMode: rowAdapter.runtimeMode,
|
||||
runtimeAdapter: rowAdapter,
|
||||
});
|
||||
const start = await rowPort.start();
|
||||
assertEqual(start.runtimeExecutionReady, true, `${row.path}: browser row bridge readiness`);
|
||||
const planResult = await rowPort.runRowRuntimePlan();
|
||||
assertEqual(planResult.status, "row_runtime_adapter_plan_executed", `${row.path}: browser row plan status`);
|
||||
assertEqual(planResult.validation.ready, true, `${row.path}: browser row plan validation`);
|
||||
const rowTranscript = rowPort.exportTranscript();
|
||||
const rowValidation = validatePythonRemapRowRuntimeTranscript({
|
||||
...context,
|
||||
rowPath: row.path,
|
||||
transcript: rowTranscript,
|
||||
});
|
||||
assertEqual(rowValidation.ready, true, `${row.path}: browser explicit row validation`);
|
||||
const rowDiagnostics = rowPort.exportDiagnostics();
|
||||
assertEqual(rowDiagnostics.runtimeMode, "browser-python-wasm-worker", `${row.path}: browser runtime mode`);
|
||||
assertEqual(rowDiagnostics.runtimeExecutionReady, true, `${row.path}: browser runtime ready`);
|
||||
assertEqual(rowDiagnostics.rowRuntimeTranscriptReady, true, `${row.path}: browser diagnostics transcript`);
|
||||
assertEqual(rowDiagnostics.executionEnabled, false, `${row.path}: browser proof execution guard`);
|
||||
assertEqual(rowDiagnostics.promotionAllowed, false, `${row.path}: browser proof promotion guard`);
|
||||
await rowPort.close();
|
||||
|
||||
browserProofRows.push([
|
||||
row.path,
|
||||
row.ini,
|
||||
row.family,
|
||||
row.wasm_node_pass_ready,
|
||||
flagValue(start.runtimeExecutionReady),
|
||||
flagValue(planResult.status === "row_runtime_adapter_plan_executed"),
|
||||
flagValue(rowValidation.moduleImportReady),
|
||||
flagValue(rowValidation.callableLookupReady),
|
||||
flagValue(rowValidation.generatorLifecycleReady),
|
||||
flagValue(rowValidation.ngcRemapAssetsReady),
|
||||
flagValue(rowValidation.ngcOnlySubpathsGuarded),
|
||||
flagValue(rowValidation.interpreterStateBindingReady),
|
||||
flagValue(rowValidation.canonicalEventsReady),
|
||||
flagValue(rowValidation.rowRuntimeTranscriptReady),
|
||||
String(rowDiagnostics.transcriptPhaseCount),
|
||||
rowDiagnostics.transcriptHash,
|
||||
"1",
|
||||
"0",
|
||||
"0",
|
||||
"browser_row_worker_bridge_passed",
|
||||
listValue(["manual_promotion_lock"]),
|
||||
listValue([
|
||||
"phase5_browser_row_proof_ready",
|
||||
"browser_worker_consumed_wasm_node_row_proof",
|
||||
"no_inventory_promotion_without_manual_lock",
|
||||
]),
|
||||
].map(tsvValue).join("\t"));
|
||||
}
|
||||
|
||||
const browserProofText = `${browserProofHeaders.join("\t")}\n${browserProofRows.join("\n")}\n`;
|
||||
localStorage.setItem("python-remap-browser-row-proof.tsv", browserProofText);
|
||||
document.getElementById("python-remap-browser-row-proof").textContent =
|
||||
`PYTHON_REMAP_BROWSER_ROW_PROOF_BEGIN\n${browserProofText}PYTHON_REMAP_BROWSER_ROW_PROOF_END`;
|
||||
const parsedBrowserProofRows = parseTsv(browserProofText, browserProofHeaders);
|
||||
assertEqual(parsedBrowserProofRows.length, 53, "browser row proof artifact row count");
|
||||
for (const row of parsedBrowserProofRows) {
|
||||
assertEqual(row.browser_pass_ready, "1", `${row.path}: browser pass flag`);
|
||||
assertEqual(row.execution_enabled, "0", `${row.path}: browser row execution guard`);
|
||||
assertEqual(row.promotion_allowed, "0", `${row.path}: browser row promotion guard`);
|
||||
assertEqual(row.proof_status, "browser_row_worker_bridge_passed", `${row.path}: browser proof status`);
|
||||
}
|
||||
|
||||
status.textContent = "browser_python_remap_runtime_smoke=ok";
|
||||
} catch (error) {
|
||||
status.textContent = `browser_python_remap_runtime_smoke=fail\n${error.stack || error}`;
|
||||
|
||||
@@ -553,14 +553,14 @@
|
||||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.candidateCount !== 8 ||
|
||||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.readyCandidateCount !== 8 ||
|
||||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.sourceFileCount !== 17 ||
|
||||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.inventoryBaseline !== "executed=29 passed=29 skipped=130 unexpected_fail=0" ||
|
||||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.inventoryBaseline !== "executed=82 passed=82 skipped=77 unexpected_fail=0" ||
|
||||
promotionCandidateSummary.ready !== true ||
|
||||
promotionCandidateSummary.preferredIniPath !== "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" ||
|
||||
doc.body.dataset.promotionCandidateReady !== "true" ||
|
||||
doc.body.dataset.promotionCandidatePreferred !== "qtdragon-multi-joint-on-abort" ||
|
||||
doc.querySelector('[data-promotion-candidate-summary-value="preferred-candidate"]')?.textContent !== "qtdragon-multi-joint-on-abort" ||
|
||||
doc.querySelector('[data-promotion-candidate-summary-value="candidate-layers"]')?.textContent !== "evidence-ready=8 inventory-ready=19" ||
|
||||
doc.querySelector('[data-promotion-candidate-summary-value="inventory-baseline"]')?.textContent !== "28/28 pass; 131 skip unchanged" ||
|
||||
doc.querySelector('[data-promotion-candidate-summary-value="candidate-layers"]')?.textContent !== "evidence-ready=8 inventory-ready=2" ||
|
||||
doc.querySelector('[data-promotion-candidate-summary-value="inventory-baseline"]')?.textContent !== "82/82 pass; 77 skip" ||
|
||||
doc.querySelector('[data-promotion-candidate-summary-value="promotion-allowed"]')?.textContent !== "0 baseline changes" ||
|
||||
doc.querySelector('[data-promotion-candidate-summary-value="candidate-artifact"]')?.textContent !== "promotion-candidates.tsv" ||
|
||||
!diagnosticsArtifact.virtualHalSimConfigPromotionCandidates?.familyRows?.some((row) =>
|
||||
|
||||
@@ -16,6 +16,10 @@ CHROME_PROFILE="$TMP_DIR/chrome-profile"
|
||||
mkdir -p "$CHROME_PROFILE"
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${CHROME_PID:-}" ]]; then
|
||||
kill "$CHROME_PID" 2>/dev/null || true
|
||||
wait "$CHROME_PID" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -n "${SERVER_PID:-}" ]]; then
|
||||
kill "$SERVER_PID" 2>/dev/null || true
|
||||
wait "$SERVER_PID" 2>/dev/null || true
|
||||
@@ -55,20 +59,172 @@ fi
|
||||
PORT="$(cat "$PORT_FILE")"
|
||||
URL="http://127.0.0.1:$PORT/tests/browser/python_remap_runtime_browser_smoke.html"
|
||||
OUT="$TMP_DIR/chromium.out"
|
||||
DEBUG_PORT_FILE="$TMP_DIR/debug-port"
|
||||
|
||||
"$CHROMIUM" \
|
||||
--headless=new \
|
||||
--disable-gpu \
|
||||
--no-sandbox \
|
||||
--user-data-dir="$CHROME_PROFILE" \
|
||||
--virtual-time-budget=10000 \
|
||||
--dump-dom \
|
||||
"$URL" >"$OUT" 2>&1
|
||||
--remote-debugging-port=0 \
|
||||
--remote-debugging-address=127.0.0.1 \
|
||||
--enable-unsafe-extension-debugging \
|
||||
"$URL" >"$OUT" 2>&1 &
|
||||
CHROME_PID=$!
|
||||
|
||||
if ! grep -Fq "browser_python_remap_runtime_smoke=ok" "$OUT"; then
|
||||
echo "Python remap runtime browser smoke failed" >&2
|
||||
sed -n '1,220p' "$OUT" >&2
|
||||
for _ in $(seq 1 100); do
|
||||
if grep -Eo 'DevTools listening on ws://127\.0\.0\.1:[0-9]+' "$OUT" | tail -n 1 | grep -Eo '[0-9]+$' >"$DEBUG_PORT_FILE"; then
|
||||
break
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
if [[ ! -s "$DEBUG_PORT_FILE" ]]; then
|
||||
echo "Chromium remote debugging port did not become available" >&2
|
||||
sed -n '1,220p' "$OUT" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEBUG_PORT="$(cat "$DEBUG_PORT_FILE")"
|
||||
|
||||
python3 - <<'PY' "$ROOT_DIR" "$DEBUG_PORT"
|
||||
import base64
|
||||
import json
|
||||
import pathlib
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
root = pathlib.Path(sys.argv[1])
|
||||
debug_port = sys.argv[2]
|
||||
|
||||
def read_json(url):
|
||||
with urllib.request.urlopen(url, timeout=5) as response:
|
||||
return json.loads(response.read().decode("utf-8"))
|
||||
|
||||
targets = read_json(f"http://127.0.0.1:{debug_port}/json")
|
||||
page = next((target for target in targets if target.get("type") == "page"), None)
|
||||
if not page:
|
||||
raise SystemExit("missing Chromium page target")
|
||||
ws_url = page["webSocketDebuggerUrl"]
|
||||
host_port, path = ws_url.removeprefix("ws://").split("/", 1)
|
||||
host, port_text = host_port.split(":")
|
||||
|
||||
def make_frame(payload):
|
||||
data = payload.encode("utf-8")
|
||||
header = bytearray([0x81])
|
||||
length = len(data)
|
||||
if length < 126:
|
||||
header.append(0x80 | length)
|
||||
elif length < 65536:
|
||||
header.append(0x80 | 126)
|
||||
header.extend(struct.pack("!H", length))
|
||||
else:
|
||||
header.append(0x80 | 127)
|
||||
header.extend(struct.pack("!Q", length))
|
||||
mask = b"\x01\x02\x03\x04"
|
||||
header.extend(mask)
|
||||
masked = bytes(byte ^ mask[index % 4] for index, byte in enumerate(data))
|
||||
return bytes(header) + masked
|
||||
|
||||
def read_frame(sock):
|
||||
first = sock.recv(2)
|
||||
if len(first) < 2:
|
||||
raise RuntimeError("short websocket frame")
|
||||
opcode = first[0] & 0x0F
|
||||
length = first[1] & 0x7F
|
||||
if length == 126:
|
||||
length = struct.unpack("!H", sock.recv(2))[0]
|
||||
elif length == 127:
|
||||
length = struct.unpack("!Q", sock.recv(8))[0]
|
||||
if first[1] & 0x80:
|
||||
mask = sock.recv(4)
|
||||
else:
|
||||
mask = None
|
||||
data = bytearray()
|
||||
while len(data) < length:
|
||||
chunk = sock.recv(length - len(data))
|
||||
if not chunk:
|
||||
raise RuntimeError("websocket closed")
|
||||
data.extend(chunk)
|
||||
if mask:
|
||||
data = bytearray(byte ^ mask[index % 4] for index, byte in enumerate(data))
|
||||
if opcode == 8:
|
||||
raise RuntimeError("websocket closed")
|
||||
if opcode not in (1, 2):
|
||||
return None
|
||||
return data.decode("utf-8")
|
||||
|
||||
sock = socket.create_connection((host, int(port_text)), timeout=5)
|
||||
key = base64.b64encode(b"python-remap-browser-proof").decode("ascii")
|
||||
request = (
|
||||
f"GET /{path} HTTP/1.1\r\n"
|
||||
f"Host: {host_port}\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
f"Sec-WebSocket-Key: {key}\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n\r\n"
|
||||
)
|
||||
sock.sendall(request.encode("ascii"))
|
||||
response = sock.recv(4096)
|
||||
if b" 101 " not in response:
|
||||
raise SystemExit("Chromium websocket upgrade failed")
|
||||
|
||||
next_id = 0
|
||||
|
||||
def cdp(method, params=None):
|
||||
global next_id
|
||||
next_id += 1
|
||||
message_id = next_id
|
||||
sock.sendall(make_frame(json.dumps({
|
||||
"id": message_id,
|
||||
"method": method,
|
||||
"params": params or {},
|
||||
})))
|
||||
while True:
|
||||
frame = read_frame(sock)
|
||||
if frame is None:
|
||||
continue
|
||||
message = json.loads(frame)
|
||||
if message.get("id") == message_id:
|
||||
if "error" in message:
|
||||
raise RuntimeError(message["error"])
|
||||
return message["result"]
|
||||
|
||||
def evaluate(expression):
|
||||
return cdp("Runtime.evaluate", {
|
||||
"expression": expression,
|
||||
"awaitPromise": True,
|
||||
"returnByValue": True,
|
||||
})["result"].get("value")
|
||||
|
||||
deadline = time.time() + 15
|
||||
status = ""
|
||||
while time.time() < deadline:
|
||||
status = evaluate("document.getElementById('status')?.textContent || ''")
|
||||
if "browser_python_remap_runtime_smoke=ok" in status:
|
||||
break
|
||||
if "browser_python_remap_runtime_smoke=fail" in status:
|
||||
raise SystemExit(status)
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
raise SystemExit(f"browser smoke did not finish: {status}")
|
||||
|
||||
proof_text = evaluate("document.getElementById('python-remap-browser-row-proof')?.textContent || ''")
|
||||
prefix = "PYTHON_REMAP_BROWSER_ROW_PROOF_BEGIN\n"
|
||||
suffix = "PYTHON_REMAP_BROWSER_ROW_PROOF_END"
|
||||
if not proof_text.startswith(prefix) or not proof_text.endswith(suffix):
|
||||
raise SystemExit("missing python-remap browser row proof artifact in DOM")
|
||||
proof_text = proof_text[len(prefix):-len(suffix)]
|
||||
artifact = root / "build/wasm/sim-configs-inventory/python-remap-browser-row-proof.tsv"
|
||||
artifact.parent.mkdir(parents=True, exist_ok=True)
|
||||
artifact.write_text(proof_text, encoding="utf-8")
|
||||
PY
|
||||
|
||||
kill "$CHROME_PID" 2>/dev/null || true
|
||||
wait "$CHROME_PID" 2>/dev/null || true
|
||||
|
||||
echo "browser_python_remap_runtime_smoke=ok"
|
||||
echo "python_remap_browser_row_proof=ok"
|
||||
|
||||
Reference in New Issue
Block a user