返回 DeepSeek-Reasonix
shell_bootstrap_test.go
根目录 / desktop / shell_bootstrap_test.go
1 //go:build !windows
2
3 package main
4
5 import (
6 "os"
7 "path/filepath"
8 "runtime"
9 "strings"
10 "testing"
11 "time"
12
13 "reasonix/internal/installlayout"
14 )
15
16 func writeShellBootstrapFixture(t *testing.T, goos string) (exe, shell string) {
17 t.Helper()
18 root := t.TempDir()
19 name := installlayout.ShellExecutableNameFor(goos)
20 if goos == "darwin" {
21 dir := filepath.Join(root, "Reasonix.app", "Contents", "MacOS")
22 exe, shell = filepath.Join(dir, "reasonix-desktop"), filepath.Join(dir, name)
23 } else {
24 exe, shell = filepath.Join(root, "reasonix-desktop"), filepath.Join(root, installlayout.AppShellDirName, name)
25 }
26 if err := os.MkdirAll(filepath.Dir(exe), 0o755); err != nil {
27 t.Fatal(err)
28 }
29 if err := os.WriteFile(exe, []byte("service"), 0o755); err != nil {
30 t.Fatal(err)
31 }
32 return exe, shell
33 }
34
35 func TestBootstrapShellExecutableBesideResolvesPerPlatform(t *testing.T) {
36 for _, goos := range []string{"linux", "windows", "darwin"} {
37 exe, shell := writeShellBootstrapFixture(t, goos)
38 if got, ok := shellExecutableBeside(exe, goos); ok {
39 t.Fatalf("%s: resolved %s without an installed shell", goos, got)
40 }
41 if err := os.MkdirAll(filepath.Dir(shell), 0o755); err != nil {
42 t.Fatal(err)
43 }
44 if err := os.WriteFile(shell, []byte("shell"), 0o755); err != nil {
45 t.Fatal(err)
46 }
47 got, ok := shellExecutableBeside(exe, goos)
48 if !ok || got != shell {
49 t.Fatalf("%s: shell = %q ok=%v, want %q", goos, got, ok, shell)
50 }
51 }
52 }
53
54 func TestMacBundleContentsRecognizesSupportedServiceLayouts(t *testing.T) {
55 app := filepath.Join(t.TempDir(), "parent.app cache", "应用.app", "Contents")
56 for _, relative := range []string{"MacOS/reasonix-desktop", "Resources/service/reasonix-desktop"} {
57 exe := filepath.Join(app, filepath.FromSlash(relative))
58 if got, ok := macAppContentsForExecutable(exe); !ok || got != app {
59 t.Fatalf("%s: contents = %q ok=%v, want %q", relative, got, ok, app)
60 }
61 }
62 for _, exe := range []string{
63 filepath.Join(app, "Resources", "reasonix-desktop"),
64 filepath.Join(t.TempDir(), "reasonix-desktop"),
65 "relative/reasonix-desktop",
66 } {
67 if got, ok := macAppContentsForExecutable(exe); ok {
68 t.Fatalf("unexpected contents %q for %q", got, exe)
69 }
70 }
71 }
72
73 func TestMacResourcesServiceFindsBundleShell(t *testing.T) {
74 contents := filepath.Join(t.TempDir(), "Reasonix.app", "Contents")
75 exe := filepath.Join(contents, "Resources", "service", "reasonix-desktop")
76 want := filepath.Join(contents, "MacOS", "Reasonix")
77 if got := shellPathForExecutable(exe, "darwin"); got != want {
78 t.Fatalf("shell = %q, want %q", got, want)
79 }
80 }
81
82 func TestBootstrapShellExecutableBesideNeverReturnsItself(t *testing.T) {
83 dir := filepath.Join(t.TempDir(), "Reasonix.app", "Contents", "MacOS")
84 if err := os.MkdirAll(dir, 0o755); err != nil {
85 t.Fatal(err)
86 }
87 exe := filepath.Join(dir, installlayout.ShellExecutableNameFor("darwin"))
88 if err := os.WriteFile(exe, []byte("wails desktop"), 0o755); err != nil {
89 t.Fatal(err)
90 }
91 if got, ok := shellExecutableBeside(exe, "darwin"); ok {
92 t.Fatalf("resolved the running executable %s as the shell", got)
93 }
94 }
95
96 func TestBootstrapShellStartsDetachedShellWithServiceEnvAndArgs(t *testing.T) {
97 exe, shell := writeShellBootstrapFixture(t, runtime.GOOS)
98 out := filepath.Join(t.TempDir(), "shell.out")
99 script := "#!/bin/sh\nprintf '%s\\n' \"$REASONIX_DESKTOP_SERVICE\" \"$@\" > \"$REASONIX_TEST_SHELL_OUT\"\n"
100 if err := os.MkdirAll(filepath.Dir(shell), 0o755); err != nil {
101 t.Fatal(err)
102 }
103 if err := os.WriteFile(shell, []byte(script), 0o755); err != nil {
104 t.Fatal(err)
105 }
106 env := append(os.Environ(), "REASONIX_TEST_SHELL_OUT="+out, shellServiceEnv+"=stale")
107 handled, code := bootstrapShell(exe, runtime.GOOS, []string{"--flag", "two words"}, env)
108 if !handled || code != 0 {
109 t.Fatalf("bootstrap handled=%v code=%d", handled, code)
110 }
111 deadline := time.Now().Add(10 * time.Second)
112 want := []string{exe, "--flag", "two words"}
113 var raw []byte
114 for {
115 var err error
116 if raw, err = os.ReadFile(out); err == nil {
117 got := strings.Split(strings.TrimSuffix(string(raw), "\n"), "\n")
118 if strings.Join(got, "\x00") == strings.Join(want, "\x00") {
119 break
120 }
121 }
122 if time.Now().After(deadline) {
123 got := strings.Split(strings.TrimSuffix(string(raw), "\n"), "\n")
124 t.Fatalf("shell saw %q, want %q (read error: %v)", got, want, err)
125 }
126 time.Sleep(20 * time.Millisecond)
127 }
128 }
129
130 func TestBootstrapShellSkipsWithoutShellBesideBinary(t *testing.T) {
131 if handled, _ := bootstrapShell(filepath.Join(t.TempDir(), "missing"), runtime.GOOS, nil, nil); handled {
132 t.Fatal("bootstrap handled a binary without a shell beside it")
133 }
134 }
135
136 func TestBootstrapShellIgnoresHostLaunchModes(t *testing.T) {
137 for _, args := range [][]string{{"--host-rpc"}, {"-emit-contract", t.TempDir()}, {"--emit-contract=" + t.TempDir()}} {
138 if handled, _ := maybeBootstrapShell(args); handled {
139 t.Fatalf("args %v must never bootstrap the shell", args)
140 }
141 }
142 }
143
144 func TestLinuxBootstrapMatchesPackagedLayouts(t *testing.T) {
145 for exe, want := range map[string]string{
146 "/opt/reasonix/reasonix-desktop": "/opt/reasonix/app/Reasonix",
147 "/opt/reasonix/versions/v1.39.0/reasonix-desktop": "/opt/reasonix/versions/v1.39.0/app/Reasonix",
148 "/usr/bin/reasonix-desktop": "/usr/lib/reasonix/app/Reasonix",
149 } {
150 if got := shellPathForExecutable(exe, "linux"); got != want {
151 t.Errorf("%s: got %s want %s", exe, got, want)
152 }
153 }
154 }
155
155 lines GO