新增发布 artifact URL browser gate

This commit is contained in:
2026-06-16 10:24:05 +08:00
parent 56175e29cc
commit 904f906f86
15 changed files with 271 additions and 10 deletions

View File

@@ -85,6 +85,7 @@ wasm-port/tools/verify_no_standalone_cnc_semantics.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
wasm-port/tests/host/verify_host_smokes.sh
```
@@ -134,6 +135,7 @@ browser_ini_control_page_smoke=ok
browser_ini_launch_smoke=ok
browser_ini_workflow_overview_smoke=ok
browser_ini_shell_integration_workflow_smoke=ok
browser_release_artifact_url_workflow_smoke=ok
```
## Blocked runtime families
@@ -157,6 +159,7 @@ must not be run on hosts that do not provide the required LinuxCNC runtime.
- Sim-config inventory has `unexpected_fail=0`.
- UI Node smoke passes.
- Browser INI panel smoke passes.
- Release artifact URL browser workflow smoke passes.
- Host aggregate smoke passes.
- `verify_project_release_gate.sh` passes.
- SDK exports match `runtime/sdk/README.md`.

View File

@@ -34,6 +34,12 @@ const REQUIRED_RELEASE_GATES = [
command: "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
expectedOutput: "browser_ini_shell_integration_workflow_smoke=ok",
},
{
id: "release-artifact-url-browser",
label: "Release artifact URL browser workflow smoke",
command: "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
expectedOutput: "browser_release_artifact_url_workflow_smoke=ok",
},
{
id: "ui-node-smokes",
label: "UI Node smokes",

View File

@@ -338,7 +338,7 @@ TOOL_TABLE = browser-shell-tool.tbl
"L4-PYTHON-REMAP",
],
promotedBlockedFamilies: [],
gates: Array.from({ length: 9 }, (_, index) => ({ id: `gate-${index}`, passed: true })),
gates: Array.from({ length: 10 }, (_, index) => ({ id: `gate-${index}`, passed: true })),
rows: [
{
id: "project-release-gate",

View File

@@ -93,7 +93,7 @@
"L4-PYTHON-REMAP",
],
promotedBlockedFamilies: [],
gates: Array.from({ length: 9 }, (_, index) => ({ id: `gate-${index}`, passed: true })),
gates: Array.from({ length: 10 }, (_, index) => ({ id: `gate-${index}`, passed: true })),
rows: [
{
id: "project-release-gate",
@@ -161,7 +161,7 @@
"workflow overview release artifact validation API name",
);
assertEqual(releaseArtifactValidation.ready, true, "workflow overview release artifact validation readiness");
assertEqual(releaseArtifactValidation.gateCount, 9, "workflow overview release artifact validation gates");
assertEqual(releaseArtifactValidation.gateCount, 10, "workflow overview release artifact validation gates");
assertEqual(releaseArtifactDisplay.statusText, "Ready", "workflow overview release artifact display status");
assertEqual(releaseArtifactRenderState.statusLine, "Ready: No blocking reasons", "workflow overview release artifact render status");
assertEqual(

View File

@@ -0,0 +1,86 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC Release Artifact URL Workflow Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
}
}
async function loadWorkflowOverviewFrame() {
const frame = document.createElement("iframe");
frame.src = "../../runtime/ui/ini-panel/workflow-overview.html";
document.body.appendChild(frame);
await new Promise((resolve, reject) => {
frame.addEventListener("load", resolve, { once: true });
frame.addEventListener("error", reject, { once: true });
});
return frame.contentDocument;
}
try {
const workflowDoc = await loadWorkflowOverviewFrame();
const workflowApi = workflowDoc.defaultView.linuxCncIniPanelWorkflowOverviewApi;
if (!workflowApi?.loadWorkflowOverviewReleaseReadinessArtifactUrl) {
throw new Error("workflow overview API did not expose release artifact URL workflow");
}
const missingUrl = await workflowApi.loadWorkflowOverviewReleaseReadinessArtifactUrl();
assertEqual(missingUrl.ready, false, "missing URL workflow readiness");
assertEqual(missingUrl.statusLine, "Blocked: artifact-url", "missing URL status");
const missingDom = await workflowApi.loadWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "../../tests/fixtures/project-release-readiness-ready.json",
mount: true,
});
assertEqual(missingDom.ready, false, "missing DOM workflow readiness");
assertEqual(missingDom.validation.ready, true, "missing DOM validation readiness");
assertEqual(missingDom.missing.join(","), "mount,status,rows", "missing DOM reasons");
const releaseArtifactNode = workflowDoc.createElement("section");
releaseArtifactNode.dataset.workflowOverviewReleaseReadinessArtifact = "";
const releaseArtifactStatus = workflowDoc.createElement("p");
releaseArtifactStatus.dataset.workflowOverviewReleaseReadinessArtifactStatus = "";
const releaseArtifactRows = workflowDoc.createElement("dl");
releaseArtifactRows.dataset.workflowOverviewReleaseReadinessArtifactRows = "";
releaseArtifactNode.append(releaseArtifactStatus, releaseArtifactRows);
workflowDoc.body.append(releaseArtifactNode);
const readyWorkflow = await workflowApi.loadWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "../../tests/fixtures/project-release-readiness-ready.json",
mount: true,
});
assertEqual(
readyWorkflow.apiName,
"ini-panel-shell-workflow-overview-release-readiness-artifact-url-workflow",
"ready workflow API name",
);
assertEqual(readyWorkflow.ready, true, "ready workflow readiness");
assertEqual(readyWorkflow.httpStatus, 200, "ready workflow HTTP status");
assertEqual(readyWorkflow.validation.gateCount, 10, "ready workflow gate count");
assertEqual(readyWorkflow.renderState.statusLine, "Ready: No blocking reasons", "ready workflow status");
assertEqual(readyWorkflow.mountResult.ready, true, "ready workflow mount readiness");
assertEqual(readyWorkflow.mountResult.renderResult.rowCount, 3, "ready workflow mount rows");
assertEqual(
releaseArtifactStatus.textContent,
"Ready: No blocking reasons",
"ready workflow rendered status",
);
document.getElementById("status").textContent = "browser_release_artifact_url_workflow_smoke=ok";
console.log("browser_release_artifact_url_workflow_smoke=ok");
} catch (error) {
document.getElementById("status").textContent =
`browser_release_artifact_url_workflow_smoke=failed: ${error.message}`;
console.error(error);
throw error;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
if [[ -z "$CHROMIUM" ]]; then
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
exit 1
fi
if [[ "${SKIP_INI_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_ini_panel.sh"
fi
if [[ "${SKIP_INTERP_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_wasm_core.sh"
fi
TMP_DIR="$(mktemp -d)"
PORT_FILE="$TMP_DIR/port"
SERVER_LOG="$TMP_DIR/server.log"
CHROME_PROFILE="$TMP_DIR/chrome-profile"
mkdir -p "$CHROME_PROFILE"
cleanup() {
if [[ -n "${SERVER_PID:-}" ]]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
import functools
import http.server
import pathlib
import socketserver
import sys
root = pathlib.Path(sys.argv[1])
port_file = pathlib.Path(sys.argv[2])
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
httpd.serve_forever()
PY
SERVER_PID=$!
for _ in $(seq 1 100); do
[[ -s "$PORT_FILE" ]] && break
sleep 0.05
done
if [[ ! -s "$PORT_FILE" ]]; then
echo "release artifact URL workflow browser smoke HTTP server did not start" >&2
cat "$SERVER_LOG" >&2 || true
exit 1
fi
PORT="$(cat "$PORT_FILE")"
URL="http://127.0.0.1:$PORT/tests/browser/release_artifact_url_workflow_smoke.html"
OUT="$TMP_DIR/chromium.out"
"$CHROMIUM" \
--headless=new \
--disable-gpu \
--no-sandbox \
--user-data-dir="$CHROME_PROFILE" \
--virtual-time-budget=10000 \
--dump-dom \
"$URL" >"$OUT" 2>&1
if ! grep -Fq "browser_release_artifact_url_workflow_smoke=ok" "$OUT"; then
echo "release artifact URL workflow browser smoke failed" >&2
sed -n '1,220p' "$OUT" >&2
exit 1
fi
echo "browser_release_artifact_url_workflow_smoke=ok"

View File

@@ -51,6 +51,7 @@ for (const phrase of [
"project_release_gate=ok",
"sim_configs_wasm_node_inventory_unexpected_fail=0",
"browser_ini_shell_integration_workflow_smoke=ok",
"browser_release_artifact_url_workflow_smoke=ok",
"project_release_handoff_docs_node_smoke=ok",
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
@@ -74,6 +75,7 @@ for (const phrase of [
"verify_interp_wasm.sh",
"verify_sim_configs_inventory_wasm.sh",
"verify_ini_panel_browser.sh",
"verify_release_artifact_url_workflow_browser.sh",
"verify_ui_node_smokes.sh",
"verify_host_smokes.sh",
"write_project_release_readiness_artifact.mjs",

View File

@@ -71,6 +71,14 @@
"status": "passed",
"passed": true
},
{
"id": "release-artifact-url-browser",
"label": "Release artifact URL browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
"expectedOutput": "browser_release_artifact_url_workflow_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",

View File

@@ -12,6 +12,7 @@ git diff --check
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_interp_wasm.sh"
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_sim_configs_inventory_wasm.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_ini_panel_browser.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_release_artifact_url_workflow_browser.sh"
"$ROOT_DIR/tests/ui/node/verify_ui_node_smokes.sh"
"$ROOT_DIR/tests/host/verify_host_smokes.sh"

View File

@@ -20,6 +20,6 @@ assert.equal(validation.phase, "ready");
assert.deepEqual(validation.missing, []);
assert.equal(validation.artifactApiName, "project-release-readiness-report");
assert.equal(validation.artifactVersion, 1);
assert.equal(validation.gateCount, 9);
assert.equal(validation.gateCount, 10);
console.log("project_release_readiness_artifact_node_smoke=ok");

View File

@@ -15,6 +15,7 @@ const passedGateResults = {
"interp-wasm": true,
"sim-config-inventory": true,
"ini-panel-browser": true,
"release-artifact-url-browser": true,
"ui-node-smokes": true,
"host-smokes": true,
"project-release-gate": true,
@@ -28,6 +29,7 @@ const report = createProjectReleaseReadinessReport({
"interp_wasm_node_smoke=ok",
"sim_configs_wasm_node_inventory_unexpected_fail=0",
"browser_ini_shell_integration_workflow_smoke=ok",
"browser_release_artifact_url_workflow_smoke=ok",
"ui_node_smokes=ok",
"host_wasm_opfs_browser_smokes=ok",
"project_release_gate=ok",

View File

@@ -222,6 +222,7 @@ const releaseReadinessArtifact = parseProjectReleaseReadinessArtifactJson(JSON.s
"interp-wasm": true,
"sim-config-inventory": true,
"ini-panel-browser": true,
"release-artifact-url-browser": true,
"ui-node-smokes": true,
"host-smokes": true,
"project-release-gate": true,
@@ -236,7 +237,7 @@ assert.deepEqual(createProjectReleaseReadinessArtifactValidation(releaseReadines
missing: [],
artifactApiName: "project-release-readiness-report",
artifactVersion: 1,
gateCount: 9,
gateCount: 10,
blockedRuntimeFamilies: [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",

View File

@@ -1308,7 +1308,7 @@ const releaseReadinessArtifactJson = JSON.stringify({
"L4-PYTHON-REMAP",
],
promotedBlockedFamilies: [],
gates: Array.from({ length: 9 }, (_, index) => ({ id: `gate-${index}`, passed: true })),
gates: Array.from({ length: 10 }, (_, index) => ({ id: `gate-${index}`, passed: true })),
rows: [
{
id: "project-release-gate",
@@ -1326,7 +1326,7 @@ assert.equal(
);
assert.equal(releaseArtifactValidation.ready, true);
assert.equal(releaseArtifactValidation.validation.ready, true);
assert.equal(releaseArtifactValidation.gateCount, 9);
assert.equal(releaseArtifactValidation.gateCount, 10);
const releaseArtifactDisplay = createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel(
releaseArtifactValidation,
);