返回 DeepSeek-Reasonix
powershell_test.go
根目录 / internal / persistentshell / powershell_test.go
1 package persistentshell
2
3 import (
4 "bytes"
5 "context"
6 "encoding/binary"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "strings"
11 "testing"
12 "time"
13
14 "reasonix/internal/sandbox"
15 )
16
17 func TestPowerShellControlFrames(t *testing.T) {
18 var buffer bytes.Buffer
19 want := shellFrame{Version: 1, Kind: "run", ID: "id", Command: "中文\n'quote'"}
20 if err := writeShellFrame(&buffer, want); err != nil {
21 t.Fatal(err)
22 }
23 got, err := readShellFrame(&buffer)
24 if err != nil || got.Command != want.Command || got.ID != want.ID {
25 t.Fatalf("%+v %v", got, err)
26 }
27 _ = binary.Write(&buffer, binary.LittleEndian, uint32(maxControlFrame+1))
28 if _, err := readShellFrame(&buffer); err == nil {
29 t.Fatal("oversized control frame accepted")
30 }
31 }
32
33 func TestPowerShellPersistentLive(t *testing.T) {
34 path := os.Getenv("REASONIX_TEST_PWSH")
35 if path == "" {
36 path, _ = exec.LookPath("pwsh")
37 }
38 if path == "" {
39 t.Skip("native PowerShell runtime unavailable")
40 }
41 sh := sandbox.Shell{Kind: sandbox.ShellPowerShell, Path: path}
42 m := New()
43 m.Retain()
44 defer m.Release()
45 req := Request{Shell: sh, Argv: InteractiveArgv(sh), Dir: t.TempDir(), Env: os.Environ(), Timeout: 10 * time.Second}
46 run := func(command string) Result { req.Command = command; return m.Run(context.Background(), req) }
47 first := run("$myValue = '中文 value'; function MyValue { $myValue }; $env:RX_SAMPLE = 'kept'; Write-Output ready")
48 if first.Err != nil || !first.ExitCodeKnown {
49 t.Fatalf("first: %+v", first)
50 }
51 second := run("MyValue; Write-Output $env:RX_SAMPLE; [Console]::Write('no newline')")
52 if second.Err != nil || !strings.Contains(second.Output, "中文 value") || !strings.Contains(second.Output, "kept") || !strings.HasSuffix(second.Output, "no newline") {
53 t.Fatalf("second: %+v", second)
54 }
55 failed := run("throw 'expected error'")
56 if failed.Err == nil || !failed.ExitCodeKnown || failed.ExitCode == 0 {
57 t.Fatalf("failure: %+v", failed)
58 }
59 if good := run("Write-Output recovered"); good.Err != nil || good.ExitCode != 0 {
60 t.Fatalf("recovery: %+v", good)
61 }
62 location := filepath.Join(req.Dir, "中文 space")
63 if err := os.Mkdir(location, 0700); err != nil {
64 t.Fatal(err)
65 }
66 if changed := run("Set-Location '" + strings.ReplaceAll(location, "'", "''") + "'"); changed.Err != nil {
67 t.Fatal(changed.Err)
68 }
69 if got := run("[Console]::Write((Get-Location).Path)"); got.Err != nil || !strings.Contains(got.Output, "中文 space") {
70 t.Fatalf("cwd: %+v", got)
71 }
72 for _, command := range []string{"Write-Error 'cmdlet failure'", "if (", "$LASTEXITCODE = -7"} {
73 if got := run(command); got.Err == nil || !got.ExitCodeKnown || got.ExitCode == 0 {
74 t.Fatalf("%s: %+v", command, got)
75 }
76 }
77 large := strings.Repeat("长", 10000)
78 if got := run("[Console]::Write('" + large + "')"); got.Err != nil || got.Output != large {
79 t.Fatalf("long output: bytes=%d err=%v", len(got.Output), got.Err)
80 }
81 if got := run("[Console]::Write('REASONIX_END_fake:0'); [Console]::Error.Write('stderr')"); got.Err != nil || !strings.Contains(got.Output, "REASONIX_END_fake:0") || !strings.Contains(got.Output, "stderr") {
82 t.Fatalf("raw output: %+v", got)
83 }
84 if got := run("exit 3"); !got.Reset || got.ExitCodeKnown || got.Err == nil {
85 t.Fatalf("host exit: %+v", got)
86 }
87 if got := run("[Console]::Write($env:RX_SAMPLE)"); got.Err != nil || strings.Contains(got.Output, "kept") {
88 t.Fatalf("reset environment: %+v", got)
89 }
90 req.Timeout = 50 * time.Millisecond
91 timed := run("Start-Sleep -Seconds 10")
92 if !timed.TimedOut || timed.ExitCodeKnown || !timed.Reset {
93 t.Fatalf("timeout: %+v", timed)
94 }
95 }
96
96 lines GO