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}