Skip to content

Commit 1898afe

Browse files
committed
perf(test): overlap PowerShell installer exit checks
1 parent ac24461 commit 1898afe

1 file changed

Lines changed: 97 additions & 62 deletions

File tree

test/scripts/install-ps1.test.ts

Lines changed: 97 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Install Ps1 tests cover install ps1 script behavior.
2-
import { spawnSync } from "node:child_process";
3-
import { chmodSync, readFileSync, writeFileSync } from "node:fs";
2+
import { spawn, spawnSync } from "node:child_process";
3+
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4+
import { tmpdir } from "node:os";
45
import { join } from "node:path";
56
import { beforeAll, describe, expect, it } from "vitest";
67
import { createScriptTestHarness } from "./test-helpers";
@@ -66,12 +67,35 @@ describe("install.ps1 failure handling", () => {
6667
const source = readFileSync(SCRIPT_PATH, "utf8");
6768
const powershell = findPowerShell();
6869
const runIfPowerShell = powershell ? it : it.skip;
70+
const runConcurrentIfPowerShell = powershell ? it.concurrent : it.skip;
6971
const runPowerShell = (args: string[]) => {
7072
if (!powershell) {
7173
throw new Error("PowerShell is not available");
7274
}
7375
return spawnSync(powershell, args, { encoding: "utf8" });
7476
};
77+
const runPowerShellAsync = (args: string[]) => {
78+
if (!powershell) {
79+
throw new Error("PowerShell is not available");
80+
}
81+
return new Promise<{ status: number | null; stderr: string; stdout: string }>(
82+
(resolve, reject) => {
83+
const child = spawn(powershell, args, { stdio: ["ignore", "pipe", "pipe"] });
84+
let stdout = "";
85+
let stderr = "";
86+
child.stdout.setEncoding("utf8");
87+
child.stderr.setEncoding("utf8");
88+
child.stdout.on("data", (chunk: string) => {
89+
stdout += chunk;
90+
});
91+
child.stderr.on("data", (chunk: string) => {
92+
stderr += chunk;
93+
});
94+
child.once("error", reject);
95+
child.once("close", (status) => resolve({ status, stderr, stdout }));
96+
},
97+
);
98+
};
7599
const batchedPowerShellResults = new Map<string, { error: string; ok: boolean }>();
76100

77101
beforeAll(() => {
@@ -640,68 +664,79 @@ describe("install.ps1 failure handling", () => {
640664
expect(mainBody).toContain("Invoke-InteractiveOpenClawCommand onboard");
641665
});
642666

643-
runIfPowerShell("fails install when interactive onboarding exits non-zero", () => {
644-
const tempDir = harness.createTempDir("openclaw-install-ps1-");
645-
const scriptPath = join(tempDir, "install.ps1");
646-
const scriptWithoutEntryPoint = source.replace(ENTRYPOINT_RE, "");
647-
writeFileSync(
648-
scriptPath,
649-
[
650-
scriptWithoutEntryPoint,
651-
"",
652-
"function Write-Banner { }",
653-
"function Ensure-ExecutionPolicy { return $true }",
654-
"function Check-Node { return $true }",
655-
"function Check-ExistingOpenClaw { return $false }",
656-
"function Get-NpmCommandPath { return 'npm.cmd' }",
657-
"function Install-OpenClaw { return $true }",
658-
"function Ensure-OpenClawOnPath { return $true }",
659-
"function Add-ToUserPath { param([string]$Path) }",
660-
"function Get-OpenClawCommandPath { return 'cmd.exe' }",
661-
"function Start-Process {",
662-
" param([string]$FilePath, [string[]]$ArgumentList, [switch]$NoNewWindow, [switch]$Wait, [switch]$PassThru)",
663-
" [pscustomobject]@{ ExitCode = 17 }",
664-
"}",
665-
"$InstallMethod = 'npm'",
666-
"$NoOnboard = $false",
667-
"",
668-
...ENTRYPOINT_LINES,
669-
"",
670-
].join("\n"),
671-
);
672-
chmodSync(scriptPath, 0o755);
673-
674-
const result = runPowerShell([
675-
"-NoLogo",
676-
"-NoProfile",
677-
"-ExecutionPolicy",
678-
"Bypass",
679-
"-File",
680-
scriptPath,
681-
]);
682-
683-
expect(result.status).toBe(1);
684-
expect(`${result.stdout}\n${result.stderr}`).toContain(
685-
"openclaw onboard failed with exit code 17",
686-
);
687-
});
667+
runConcurrentIfPowerShell(
668+
"fails install when interactive onboarding exits non-zero",
669+
async () => {
670+
const tempDir = mkdtempSync(join(tmpdir(), "openclaw-install-ps1-"));
671+
const scriptPath = join(tempDir, "install.ps1");
672+
try {
673+
const scriptWithoutEntryPoint = source.replace(ENTRYPOINT_RE, "");
674+
writeFileSync(
675+
scriptPath,
676+
[
677+
scriptWithoutEntryPoint,
678+
"",
679+
"function Write-Banner { }",
680+
"function Ensure-ExecutionPolicy { return $true }",
681+
"function Check-Node { return $true }",
682+
"function Check-ExistingOpenClaw { return $false }",
683+
"function Get-NpmCommandPath { return 'npm.cmd' }",
684+
"function Install-OpenClaw { return $true }",
685+
"function Ensure-OpenClawOnPath { return $true }",
686+
"function Add-ToUserPath { param([string]$Path) }",
687+
"function Get-OpenClawCommandPath { return 'cmd.exe' }",
688+
"function Start-Process {",
689+
" param([string]$FilePath, [string[]]$ArgumentList, [switch]$NoNewWindow, [switch]$Wait, [switch]$PassThru)",
690+
" [pscustomobject]@{ ExitCode = 17 }",
691+
"}",
692+
"$InstallMethod = 'npm'",
693+
"$NoOnboard = $false",
694+
"",
695+
...ENTRYPOINT_LINES,
696+
"",
697+
].join("\n"),
698+
);
699+
chmodSync(scriptPath, 0o755);
700+
701+
const result = await runPowerShellAsync([
702+
"-NoLogo",
703+
"-NoProfile",
704+
"-ExecutionPolicy",
705+
"Bypass",
706+
"-File",
707+
scriptPath,
708+
]);
709+
710+
expect(result.status).toBe(1);
711+
expect(`${result.stdout}\n${result.stderr}`).toContain(
712+
"openclaw onboard failed with exit code 17",
713+
);
714+
} finally {
715+
rmSync(tempDir, { force: true, recursive: true });
716+
}
717+
},
718+
);
688719

689-
runIfPowerShell("exits non-zero when run as a script file", () => {
690-
const tempDir = harness.createTempDir("openclaw-install-ps1-");
720+
runConcurrentIfPowerShell("exits non-zero when run as a script file", async () => {
721+
const tempDir = mkdtempSync(join(tmpdir(), "openclaw-install-ps1-"));
691722
const scriptPath = join(tempDir, "install.ps1");
692-
writeFileSync(scriptPath, createFailingNodeFixture(source));
693-
chmodSync(scriptPath, 0o755);
694-
695-
const result = runPowerShell([
696-
"-NoLogo",
697-
"-NoProfile",
698-
"-ExecutionPolicy",
699-
"Bypass",
700-
"-File",
701-
scriptPath,
702-
]);
703-
704-
expect(result.status).toBe(1);
723+
try {
724+
writeFileSync(scriptPath, createFailingNodeFixture(source));
725+
chmodSync(scriptPath, 0o755);
726+
727+
const result = await runPowerShellAsync([
728+
"-NoLogo",
729+
"-NoProfile",
730+
"-ExecutionPolicy",
731+
"Bypass",
732+
"-File",
733+
scriptPath,
734+
]);
735+
736+
expect(result.status).toBe(1);
737+
} finally {
738+
rmSync(tempDir, { force: true, recursive: true });
739+
}
705740
});
706741

707742
runIfPowerShell("throws without killing the caller when run as a scriptblock", () => {

0 commit comments

Comments
 (0)