char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somcode: register PowerShell only for Windows conversation environments3ea88e4

main
5.5 KiB161 linesraw
1import { assert, assertEquals, assertRejects, assertStringIncludes } from "@std/assert";
2import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3import { mkdir, writeFile } from "node:fs/promises";
4import { join } from "node:path";
5import { defineExtension } from "@berry/api";
6import { localEnvironment } from "@berry/api/environment.ts";
7import { rootedEnvironment, testExtension } from "@berry/api/testing.ts";
8import { tempDir } from "@berry/util";
9import code from "./mod.ts";
10import { powershellCommand } from "./commands.ts";
11
12Deno.test("PowerShell registers only in Windows conversations, never globally", async () => {
13  await using root = await tempDir();
14  const registered: string[] = [];
15  const ext = testExtension(
16    defineExtension({
17      ...code,
18      setup(api) {
19        const contribute = code.setup({
20          ...api,
21          tool(tool) {
22            assert(tool.name !== "powershell");
23            api.tool(tool);
24          },
25        });
26        return (conv) =>
27          contribute?.({
28            ...conv,
29            tool(tool) {
30              registered.push(tool.name);
31              conv.tool(tool);
32            },
33          });
34      },
35    }),
36    { env: rootedEnvironment(root.path) },
37  );
38  const probe = async (platform: string) => {
39    registered.length = 0;
40    await assertRejects(
41      () => ext.call("absent", {}, { env: { ...ext.env, platform } }),
42      Error,
43      "No tool named absent",
44    );
45    return registered.includes("powershell");
46  };
47  assertEquals(await probe("linux"), false);
48  assertEquals(await probe("win32"), true);
49  assertEquals(await probe("linux"), false);
50});
51
52Deno.test("PowerShell follows the target environment and prefers PS7 before falling back to PS5", async () => {
53  await using root = await tempDir();
54  for (
55    const dir of [
56      "bin dir",
57      "legacy",
58      "program files/PowerShell/7",
59      "windows/System32/WindowsPowerShell/v1.0",
60    ]
61  ) {
62    await mkdir(join(root.path, dir), { recursive: true });
63  }
64  const binaries = [
65    "bin dir/pwsh.EXE",
66    "program files/PowerShell/7/pwsh.exe",
67    "legacy/powershell.EXE",
68    "windows/System32/WindowsPowerShell/v1.0/powershell.exe",
69  ];
70  for (const file of binaries) await writeFile(join(root.path, file), "");
71
72  let invocation: string | readonly string[] = "";
73  const env = {
74    ...rootedEnvironment(root.path, {
75      variables: {
76        pAtH: '"/bin dir";/legacy',
77        PaThExT: ".EXE",
78        ProgramFiles: "/program files",
79        SystemRoot: "/windows",
80      },
81    }),
82    platform: "win32",
83    exec: (command: string | readonly string[]) => {
84      invocation = command;
85      return Promise.resolve({ ok: true as const, value: { exitCode: 0 } });
86    },
87  };
88  const ext = testExtension(code, { env });
89  await ext.call("powershell", { command: "Get-Location", timeout: 10 });
90  assert(Array.isArray(invocation));
91  assertEquals(invocation[0], "/bin dir/pwsh.EXE");
92
93  await Deno.remove(join(root.path, binaries[0]));
94  await ext.call("powershell", { command: "Get-Location", timeout: 10 });
95  assertEquals(invocation[0], "/program files/PowerShell/7/pwsh.exe");
96
97  await Deno.remove(join(root.path, binaries[1]));
98  await ext.call("powershell", { command: "Get-Location", timeout: 10 });
99  assertEquals(invocation[0], "/legacy/powershell.EXE");
100
101  await Deno.remove(join(root.path, binaries[2]));
102  await ext.call("powershell", { command: "Get-Location", timeout: 10 });
103  assertEquals(invocation[0], "/windows/System32/WindowsPowerShell/v1.0/powershell.exe");
104
105  await Deno.remove(join(root.path, binaries[3]));
106  await assertRejects(
107    () => ext.call("powershell", { command: "Get-Location", timeout: 10 }),
108    Error,
109    "PowerShell not found",
110  );
111  await assertRejects(
112    () =>
113      ext.call("powershell", { command: "Get-Location", timeout: 10 }, {
114        env: { ...env, platform: "linux" },
115      }),
116    Error,
117    "No tool named powershell",
118  );
119});
120
121let hasPowerShell = true;
122try {
123  await powershellCommand(localEnvironment(Deno.cwd()), "");
124} catch {
125  hasPowerShell = false;
126}
127
128Deno.test({
129  name:
130    "PowerShell runs literal Unicode scripts, preserves native failures, and spills UTF-8 output",
131  ignore: !hasPowerShell,
132  async fn() {
133    await using root = await tempDir();
134    const env = localEnvironment(root.path);
135    const run = async (command: string, timeout = 10) => {
136      let output = "";
137      const result = await env.exec(await powershellCommand(env, command), {
138        timeout,
139        onOutput: (chunk) => output += chunk,
140        spill: { afterBytes: 50 * 1024, afterLines: 2000 },
141      }, BACKGROUND_CONTEXT);
142      assert(result.ok, result.ok ? "" : result.error.message);
143      return { ...result.value, output };
144    };
145    const success = await run(`'héllo 🐈 "quotes" \\ backslash'; (Get-Location).Path`);
146    assertEquals(success.exitCode, 0);
147    assertStringIncludes(success.output, 'héllo 🐈 "quotes" \\ backslash');
148    assertStringIncludes(success.output, root.path);
149    const deno = Deno.execPath().replaceAll("'", "''");
150    assertEquals((await run(`& '${deno}' eval 'Deno.exit(23)'`)).exitCode, 23);
151    assertEquals((await run("Write-Error 'failure'")).exitCode, 1);
152    const long = await run("1..2500 | ForEach-Object { '🐈' + $_ }");
153    assert(long.spillPath);
154    assertStringIncludes(await Deno.readTextFile(long.spillPath), "🐈2500");
155    const timedOut = await env.exec(await powershellCommand(env, "Start-Sleep -Seconds 5"), {
156      timeout: 0.2,
157    }, BACKGROUND_CONTEXT);
158    assert(!timedOut.ok);
159    assertEquals(timedOut.error.code, "timeout");
160  },
161});