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 := NewShell(&Options{WorkingDir: 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 := NewShell(&Options{WorkingDir: 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 := NewShell(&Options{WorkingDir: 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 := NewShell(&Options{WorkingDir: 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 := NewShell(&Options{WorkingDir: 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 := NewShell(&Options{
145 WorkingDir: "C:\\Users",
146 })
147
148 tests := []struct {
149 command string
150 expectedCwd string
151 shouldError bool
152 }{
153 {"cd ..", "C:\\", false},
154 {"cd Documents", "C:\\Users\\Documents", false},
155 {"cd C:\\Windows", "C:\\Windows", false},
156 {"cd", "", true}, // Missing argument
157 }
158
159 for _, test := range tests {
160 t.Run(test.command, func(t *testing.T) {
161 originalCwd := shell.GetWorkingDir()
162 stdout, stderr, err := shell.handleWindowsCD(test.command)
163
164 if test.shouldError {
165 if err == nil {
166 t.Errorf("Command %q should have failed", test.command)
167 }
168 } else {
169 if err != nil {
170 t.Errorf("Command %q failed: %v", test.command, err)
171 }
172 if shell.GetWorkingDir() != test.expectedCwd {
173 t.Errorf("Command %q: expected cwd %q, got %q", test.command, test.expectedCwd, shell.GetWorkingDir())
174 }
175 }
176
177 // Reset for next test
178 shell.SetWorkingDir(originalCwd)
179 _ = stdout
180 _ = stderr
181 })
182 }
183}
184
185func TestCrossPlatformExecution(t *testing.T) {
186 shell := NewShell(&Options{WorkingDir: "."})
187 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
188 defer cancel()
189
190 // Test a simple command that should work on all platforms
191 stdout, stderr, err := shell.Exec(ctx, "echo hello")
192 if err != nil {
193 t.Fatalf("Echo command failed: %v, stderr: %s", err, stderr)
194 }
195
196 if stdout == "" {
197 t.Error("Echo command produced no output")
198 }
199
200 // The output should contain "hello" regardless of platform
201 if !strings.Contains(strings.ToLower(stdout), "hello") {
202 t.Errorf("Echo output should contain 'hello', got: %q", stdout)
203 }
204}
205
206func TestWindowsNativeCommands(t *testing.T) {
207 if runtime.GOOS != "windows" {
208 t.Skip("Windows native command test only runs on Windows")
209 }
210
211 shell := NewShell(&Options{WorkingDir: "."})
212 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
213 defer cancel()
214
215 // Test Windows dir command
216 stdout, stderr, err := shell.Exec(ctx, "dir")
217 if err != nil {
218 t.Fatalf("Dir command failed: %v, stderr: %s", err, stderr)
219 }
220
221 if stdout == "" {
222 t.Error("Dir command produced no output")
223 }
224}