1package shell
2
3import (
4 "context"
5 "runtime"
6 "strings"
7 "testing"
8 "time"
9)
10
11// Benchmark to measure CPU efficiency
12func BenchmarkShellQuickCommands(b *testing.B) {
13 shell := newPersistentShell(b.TempDir())
14
15 b.ReportAllocs()
16
17 for b.Loop() {
18 _, _, err := shell.Exec(context.Background(), "echo test")
19 exitCode := ExitCode(err)
20 if err != nil || exitCode != 0 {
21 b.Fatalf("Command failed: %v, exit code: %d", err, exitCode)
22 }
23 }
24}
25
26func TestTestTimeout(t *testing.T) {
27 ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond)
28 t.Cleanup(cancel)
29
30 shell := newPersistentShell(t.TempDir())
31 _, _, err := shell.Exec(ctx, "sleep 10")
32 if status := ExitCode(err); status == 0 {
33 t.Fatalf("Expected non-zero exit status, got %d", status)
34 }
35 if !IsInterrupt(err) {
36 t.Fatalf("Expected command to be interrupted, but it was not")
37 }
38 if err == nil {
39 t.Fatalf("Expected an error due to timeout, but got none")
40 }
41}
42
43func TestTestCancel(t *testing.T) {
44 ctx, cancel := context.WithCancel(t.Context())
45 cancel() // immediately cancel the context
46
47 shell := newPersistentShell(t.TempDir())
48 _, _, err := shell.Exec(ctx, "sleep 10")
49 if status := ExitCode(err); status == 0 {
50 t.Fatalf("Expected non-zero exit status, got %d", status)
51 }
52 if !IsInterrupt(err) {
53 t.Fatalf("Expected command to be interrupted, but it was not")
54 }
55 if err == nil {
56 t.Fatalf("Expected an error due to cancel, but got none")
57 }
58}
59
60func TestRunCommandError(t *testing.T) {
61 shell := newPersistentShell(t.TempDir())
62 _, _, err := shell.Exec(t.Context(), "nopenopenope")
63 if status := ExitCode(err); status == 0 {
64 t.Fatalf("Expected non-zero exit status, got %d", status)
65 }
66 if IsInterrupt(err) {
67 t.Fatalf("Expected command to not be interrupted, but it was")
68 }
69 if err == nil {
70 t.Fatalf("Expected an error, got nil")
71 }
72}
73
74func TestRunContinuity(t *testing.T) {
75 shell := newPersistentShell(t.TempDir())
76 shell.Exec(t.Context(), "export FOO=bar")
77 dst := t.TempDir()
78 shell.Exec(t.Context(), "cd "+dst)
79 out, _, _ := shell.Exec(t.Context(), "echo $FOO ; pwd")
80 expect := "bar\n" + dst + "\n"
81 if out != expect {
82 t.Fatalf("Expected output %q, got %q", expect, out)
83 }
84}
85
86// New tests for Windows shell support
87
88func TestShellTypeDetection(t *testing.T) {
89 shell := &PersistentShell{}
90
91 tests := []struct {
92 command string
93 expected ShellType
94 windowsOnly bool
95 }{
96 // Windows-specific commands
97 {"dir", ShellTypeCmd, true},
98 {"type file.txt", ShellTypeCmd, true},
99 {"copy file1.txt file2.txt", ShellTypeCmd, true},
100 {"del file.txt", ShellTypeCmd, true},
101 {"md newdir", ShellTypeCmd, true},
102 {"tasklist", ShellTypeCmd, true},
103
104 // PowerShell commands
105 {"Get-Process", ShellTypePowerShell, true},
106 {"Get-ChildItem", ShellTypePowerShell, true},
107 {"Set-Location C:\\", ShellTypePowerShell, true},
108 {"Get-Content file.txt | Where-Object {$_ -match 'pattern'}", ShellTypePowerShell, true},
109 {"$files = Get-ChildItem", ShellTypePowerShell, true},
110
111 // Unix/cross-platform commands
112 {"ls -la", ShellTypePOSIX, false},
113 {"cat file.txt", ShellTypePOSIX, false},
114 {"grep pattern file.txt", ShellTypePOSIX, false},
115 {"echo hello", ShellTypePOSIX, false},
116 {"git status", ShellTypePOSIX, false},
117 {"go build", ShellTypePOSIX, false},
118 }
119
120 for _, test := range tests {
121 t.Run(test.command, func(t *testing.T) {
122 result := shell.determineShellType(test.command)
123
124 if test.windowsOnly && runtime.GOOS != "windows" {
125 // On non-Windows systems, everything should use POSIX
126 if result != ShellTypePOSIX {
127 t.Errorf("On non-Windows, command %q should use POSIX shell, got %v", test.command, result)
128 }
129 } else if runtime.GOOS == "windows" {
130 // On Windows, check the expected shell type
131 if result != test.expected {
132 t.Errorf("Command %q should use %v shell, got %v", test.command, test.expected, result)
133 }
134 }
135 })
136 }
137}
138
139func TestWindowsCDHandling(t *testing.T) {
140 if runtime.GOOS != "windows" {
141 t.Skip("Windows CD handling test only runs on Windows")
142 }
143
144 shell := &PersistentShell{
145 cwd: "C:\\Users",
146 env: []string{},
147 }
148
149 tests := []struct {
150 command string
151 expectedCwd string
152 shouldError bool
153 }{
154 {"cd ..", "C:\\", false},
155 {"cd Documents", "C:\\Users\\Documents", false},
156 {"cd C:\\Windows", "C:\\Windows", false},
157 {"cd", "", true}, // Missing argument
158 }
159
160 for _, test := range tests {
161 t.Run(test.command, func(t *testing.T) {
162 originalCwd := shell.cwd
163 stdout, stderr, err := shell.handleWindowsCD(test.command)
164
165 if test.shouldError {
166 if err == nil {
167 t.Errorf("Command %q should have failed", test.command)
168 }
169 } else {
170 if err != nil {
171 t.Errorf("Command %q failed: %v", test.command, err)
172 }
173 if shell.cwd != test.expectedCwd {
174 t.Errorf("Command %q: expected cwd %q, got %q", test.command, test.expectedCwd, shell.cwd)
175 }
176 }
177
178 // Reset for next test
179 shell.cwd = originalCwd
180 _ = stdout
181 _ = stderr
182 })
183 }
184}
185
186func TestCrossPlatformExecution(t *testing.T) {
187 shell := newPersistentShell(".")
188 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
189 defer cancel()
190
191 // Test a simple command that should work on all platforms
192 stdout, stderr, err := shell.Exec(ctx, "echo hello")
193 if err != nil {
194 t.Fatalf("Echo command failed: %v, stderr: %s", err, stderr)
195 }
196
197 if stdout == "" {
198 t.Error("Echo command produced no output")
199 }
200
201 // The output should contain "hello" regardless of platform
202 if !strings.Contains(strings.ToLower(stdout), "hello") {
203 t.Errorf("Echo output should contain 'hello', got: %q", stdout)
204 }
205}
206
207func TestWindowsNativeCommands(t *testing.T) {
208 if runtime.GOOS != "windows" {
209 t.Skip("Windows native command test only runs on Windows")
210 }
211
212 shell := newPersistentShell(".")
213 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
214 defer cancel()
215
216 // Test Windows dir command
217 stdout, stderr, err := shell.Exec(ctx, "dir")
218 if err != nil {
219 t.Fatalf("Dir command failed: %v, stderr: %s", err, stderr)
220 }
221
222 if stdout == "" {
223 t.Error("Dir command produced no output")
224 }
225}