From aa7e91178a16732e6ddd8940249a1bff414517b6 Mon Sep 17 00:00:00 2001 From: kuddev Date: Tue, 22 Sep 2026 13:28:10 +0800 Subject: [PATCH] ci: boot explicit -e commands through the ARM64 package --- .../workflows/windows-arm64-diagnostics.yml | 22 +---- scripts/conformance/explicit_command_boot.py | 98 +++++++++++++++++++ 2 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 scripts/conformance/explicit_command_boot.py diff --git a/.github/workflows/windows-arm64-diagnostics.yml b/.github/workflows/windows-arm64-diagnostics.yml index 7767a79..99dc929 100644 --- a/.github/workflows/windows-arm64-diagnostics.yml +++ b/.github/workflows/windows-arm64-diagnostics.yml @@ -12,6 +12,7 @@ on: paths: - .github/workflows/windows-arm64-diagnostics.yml - scripts/conformance/conpty_smoke.py + - scripts/conformance/explicit_command_boot.py permissions: contents: read @@ -20,7 +21,7 @@ permissions: env: # Dispatch is only available once the workflow exists on the default branch; # branch pushes diagnose the latest ARM64 package produced by the release run. - PACKAGE_RUN: ${{ inputs.package_run || '35686321517' }} + PACKAGE_RUN: ${{ inputs.package_run || '35687864774' }} jobs: diagnose: @@ -63,25 +64,10 @@ jobs: "{0}: {1} machine={2:x4}" -f $name, $command.Source, $machine } python -c "import platform, sys; print(sys.version, platform.machine())" - - name: Conformance with the side-loaded runtime + - name: Explicit -e commands through the application if: always() shell: pwsh - run: python scripts/conformance/windows_standard_user.py scripts/conformance/run.py --app diagnostic-runtime/pebrel.exe --platform windows-aarch64 --output diagnostic-output/sideload/windows-arm64-report.json --no-platform-golden - - name: Conformance with the in-box ConPTY - if: always() - shell: pwsh - run: | - Copy-Item -Recurse diagnostic-runtime diagnostic-runtime-inbox - Remove-Item diagnostic-runtime-inbox/runtime/conpty.dll, diagnostic-runtime-inbox/runtime/OpenConsole.exe - python scripts/conformance/windows_standard_user.py scripts/conformance/run.py --app diagnostic-runtime-inbox/pebrel.exe --platform windows-aarch64 --output diagnostic-output/inbox/windows-arm64-report.json --no-platform-golden - - name: ConPTY smoke with an ordinary token - if: always() - shell: pwsh - run: python scripts/conformance/windows_standard_user.py scripts/conformance/conpty_smoke.py --runtime diagnostic-runtime/runtime --wait 25 --prompt-script "$env:TEMP/pebrel_prompt.ps1" --output diagnostic-output/smoke-ordinary.json - - name: ConPTY smoke with the runner token - if: always() - shell: pwsh - run: python scripts/conformance/conpty_smoke.py --runtime diagnostic-runtime/runtime --wait 25 --prompt-script "$env:TEMP/pebrel_prompt.ps1" --output diagnostic-output/smoke-runner.json + run: python scripts/conformance/windows_standard_user.py scripts/conformance/explicit_command_boot.py diagnostic-runtime/pebrel.exe diagnostic-output/explicit-commands.json - uses: actions/upload-artifact@v4 if: always() with: diff --git a/scripts/conformance/explicit_command_boot.py b/scripts/conformance/explicit_command_boot.py new file mode 100644 index 0000000..8409167 --- /dev/null +++ b/scripts/conformance/explicit_command_boot.py @@ -0,0 +1,98 @@ +"""Boot Pebrel with explicit `-e` commands and dump what each pane renders. + +Separates "the shell hangs at startup" from "the PTY read path is dead": +cmd, PowerShell 5.1 and pwsh are launched through the same ConPTY and +environment path as the default tab, and every pane is read for 15s. +""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +sys.path.insert(0, os.fspath(Path(__file__).resolve().parent.parent)) +from conformance.harness import RuntimeClient # noqa: E402 + +app = Path(sys.argv[1]).resolve() +output = Path(sys.argv[2]) +commands = { + "cmd": ["cmd.exe", "/d", "/k", "echo PEBREL_SMOKE_OK"], + "powershell": ["powershell.exe", "-NoLogo", "-NoProfile", "-NoExit", "-Command", "Write-Output PEBREL_SMOKE_OK"], + "powershell-profile": ["powershell.exe", "-NoLogo", "-NoExit", "-Command", "Write-Output PEBREL_SMOKE_OK"], + "pwsh": ["pwsh.exe", "-NoLogo", "-NoProfile", "-NoExit", "-Command", "Write-Output PEBREL_SMOKE_OK"], +} +report: dict[str, object] = {} +for name, command in commands.items(): + with tempfile.TemporaryDirectory(prefix="pebrel-cmd-", ignore_cleanup_errors=True) as root: + config = Path(root) / "config" + work = Path(root) / "work" + config.mkdir() + work.mkdir() + (config / "pebrel_settings.txt").write_text( + "keep_session=0\ntray=0\nrestore_session=0\nresume_ai=0\nfetch=0\n" + "auto_check_updates=0\nai_hooks=0\nwindowing_behavior=use_new\n", + encoding="utf-8", + ) + env = dict(os.environ, PEBREL_CONFIG_DIR=os.fspath(config), NEBULA_CONFIG_DIR=os.fspath(config), NEBULA_BOOT_TRACE="1") + env.pop("PEBREL_RUNTIME_ENDPOINT", None) + log = open(Path(root) / "app.log", "wb") + process = subprocess.Popen( + [os.fspath(app), "--working-directory", os.fspath(work), "-e", *command], + cwd=work, env=env, stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT, + ) + entry: dict[str, object] = {"command": command} + port = config / "runtime.port" + deadline = time.monotonic() + 20 + client = None + while time.monotonic() < deadline and client is None: + if port.is_file(): + try: + client = RuntimeClient.from_port_file(port, timeout=5.0) + client.request("runtime.describe", timeout=1.0) + except Exception: + client = None + time.sleep(0.1) + if client is None: + entry["error"] = "runtime did not come up" + else: + text = "" + started = time.monotonic() + first_text_ms = None + while time.monotonic() - started < 15: + try: + snapshot = client.request("runtime.snapshot", timeout=5.0)["result"] + window = snapshot["windows"][0] + pane = window["tabs"][0]["panes"][0] + read = client.request( + "pane.read", {"window_id": window["id"], "pane_id": pane["id"], "lines": 40}, timeout=5.0 + )["result"] + text = read.get("text", "") + entry["exited"] = read.get("exited") + entry["cwd"] = pane.get("cwd") + if text.strip() and first_text_ms is None: + first_text_ms = round((time.monotonic() - started) * 1000) + if "PEBREL_SMOKE_OK" in text: + break + except Exception as error: + entry["read_error"] = str(error) + time.sleep(0.25) + entry["first_text_ms"] = first_text_ms + entry["marker"] = "PEBREL_SMOKE_OK" in text + entry["text"] = text.strip()[:600] + # The shell keeps the work directory as its cwd; take the whole tree down. + subprocess.run(["taskkill", "/T", "/F", "/PID", str(process.pid)], capture_output=True) + process.wait(10) + log.close() + entry["app_log"] = [ + line for line in (Path(root) / "app.log").read_text(encoding="utf-8", errors="replace").splitlines() + if "prepaint" not in line and "resize-trace" not in line + ][:40] + report[name] = entry + print(name, json.dumps({k: v for k, v in entry.items() if k != "app_log"}, ensure_ascii=False)[:400], flush=True) +output.parent.mkdir(parents=True, exist_ok=True) +output.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")