8 "github.com/charmbracelet/crush/internal/shell"
9 "github.com/stretchr/testify/require"
12func TestBackgroundShell_Integration(t *testing.T) {
15 workingDir := t.TempDir()
16 ctx := context.Background()
18 // Start a background shell
19 bgManager := shell.GetBackgroundShellManager()
20 bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'hello background' && echo 'done'", "")
21 require.NoError(t, err)
22 require.NotEmpty(t, bgShell.ID)
24 // Wait for completion
28 stdout, stderr, done, err := bgShell.GetOutput()
29 require.NoError(t, err)
30 require.Contains(t, stdout, "hello background")
31 require.Contains(t, stdout, "done")
33 require.Empty(t, stderr)
36 bgManager.Kill(bgShell.ID)
39func TestBackgroundShell_Kill(t *testing.T) {
42 workingDir := t.TempDir()
43 ctx := context.Background()
45 // Start a long-running background shell
46 bgManager := shell.GetBackgroundShellManager()
47 bgShell, err := bgManager.Start(ctx, workingDir, nil, "sleep 100", "")
48 require.NoError(t, err)
51 err = bgManager.Kill(bgShell.ID)
52 require.NoError(t, err)
55 _, ok := bgManager.Get(bgShell.ID)
58 // Verify the shell is done
59 require.True(t, bgShell.IsDone())
62func TestBackgroundShell_MultipleOutputCalls(t *testing.T) {
65 workingDir := t.TempDir()
66 ctx := context.Background()
68 // Start a background shell
69 bgManager := shell.GetBackgroundShellManager()
70 bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'step 1' && echo 'step 2' && echo 'step 3'", "")
71 require.NoError(t, err)
72 defer bgManager.Kill(bgShell.ID)
74 // Check that we can call GetOutput multiple times while running
76 _, _, done, _ := bgShell.GetOutput()
80 time.Sleep(10 * time.Millisecond)
83 // Wait for completion
86 // Multiple calls after completion should return the same result
87 stdout1, _, done1, _ := bgShell.GetOutput()
88 require.True(t, done1)
89 require.Contains(t, stdout1, "step 1")
90 require.Contains(t, stdout1, "step 2")
91 require.Contains(t, stdout1, "step 3")
93 stdout2, _, done2, _ := bgShell.GetOutput()
94 require.True(t, done2)
95 require.Equal(t, stdout1, stdout2, "Multiple GetOutput calls should return same result")
98func TestBackgroundShell_EmptyOutput(t *testing.T) {
101 workingDir := t.TempDir()
102 ctx := context.Background()
104 // Start a background shell with no output
105 bgManager := shell.GetBackgroundShellManager()
106 bgShell, err := bgManager.Start(ctx, workingDir, nil, "sleep 0.1", "")
107 require.NoError(t, err)
108 defer bgManager.Kill(bgShell.ID)
110 // Wait for completion
113 stdout, stderr, done, err := bgShell.GetOutput()
114 require.NoError(t, err)
115 require.Empty(t, stdout)
116 require.Empty(t, stderr)
117 require.True(t, done)
120func TestBackgroundShell_ExitCode(t *testing.T) {
123 workingDir := t.TempDir()
124 ctx := context.Background()
126 // Start a background shell that exits with non-zero code
127 bgManager := shell.GetBackgroundShellManager()
128 bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'failing' && exit 42", "")
129 require.NoError(t, err)
130 defer bgManager.Kill(bgShell.ID)
132 // Wait for completion
135 stdout, _, done, execErr := bgShell.GetOutput()
136 require.True(t, done)
137 require.Contains(t, stdout, "failing")
138 require.Error(t, execErr)
140 exitCode := shell.ExitCode(execErr)
141 require.Equal(t, 42, exitCode)
144func TestBackgroundShell_WithBlockFuncs(t *testing.T) {
147 workingDir := t.TempDir()
148 ctx := context.Background()
150 blockFuncs := []shell.BlockFunc{
151 shell.CommandsBlocker([]string{"curl", "wget"}),
154 // Start a background shell with a blocked command
155 bgManager := shell.GetBackgroundShellManager()
156 bgShell, err := bgManager.Start(ctx, workingDir, blockFuncs, "curl example.com", "")
157 require.NoError(t, err)
158 defer bgManager.Kill(bgShell.ID)
160 // Wait for completion
163 stdout, stderr, done, execErr := bgShell.GetOutput()
164 require.True(t, done)
166 // The command should have been blocked, check stderr or error
168 // Error might contain the message
169 require.Contains(t, execErr.Error(), "not allowed")
171 // Or it might be in stderr
172 output := stdout + stderr
173 require.Contains(t, output, "not allowed")
177func TestBackgroundShell_StdoutAndStderr(t *testing.T) {
180 workingDir := t.TempDir()
181 ctx := context.Background()
183 // Start a background shell with both stdout and stderr
184 bgManager := shell.GetBackgroundShellManager()
185 bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'stdout message' && echo 'stderr message' >&2", "")
186 require.NoError(t, err)
187 defer bgManager.Kill(bgShell.ID)
189 // Wait for completion
192 stdout, stderr, done, err := bgShell.GetOutput()
193 require.NoError(t, err)
194 require.True(t, done)
195 require.Contains(t, stdout, "stdout message")
196 require.Contains(t, stderr, "stderr message")
199func TestBackgroundShell_ConcurrentAccess(t *testing.T) {
202 workingDir := t.TempDir()
203 ctx := context.Background()
205 // Start a background shell
206 bgManager := shell.GetBackgroundShellManager()
207 bgShell, err := bgManager.Start(ctx, workingDir, nil, "for i in 1 2 3 4 5; do echo \"line $i\"; sleep 0.05; done", "")
208 require.NoError(t, err)
209 defer bgManager.Kill(bgShell.ID)
211 // Access output concurrently from multiple goroutines
212 done := make(chan struct{})
213 errors := make(chan error, 10)
222 _, _, _, err := bgShell.GetOutput()
226 dir := bgShell.WorkingDir
230 time.Sleep(10 * time.Millisecond)
236 // Let it run for a bit
237 time.Sleep(300 * time.Millisecond)
240 // Check for any errors
242 case err := <-errors:
243 t.Fatalf("Concurrent access caused error: %v", err)
244 case <-time.After(100 * time.Millisecond):
245 // No errors - success
249func TestBackgroundShell_List(t *testing.T) {
252 workingDir := t.TempDir()
253 ctx := context.Background()
255 bgManager := shell.GetBackgroundShellManager()
257 // Start multiple background shells
258 shells := make([]*shell.BackgroundShell, 3)
260 bgShell, err := bgManager.Start(ctx, workingDir, nil, "sleep 1", "")
261 require.NoError(t, err)
266 ids := bgManager.List()
268 // Verify all our shells are in the list
269 for _, sh := range shells {
270 require.Contains(t, ids, sh.ID, "Shell %s not found in list", sh.ID)
274 for _, sh := range shells {
275 bgManager.Kill(sh.ID)
279func TestBackgroundShell_AutoBackground(t *testing.T) {
282 workingDir := t.TempDir()
283 ctx := context.Background()
285 // Test that a quick command completes synchronously
286 t.Run("quick command completes synchronously", func(t *testing.T) {
288 bgManager := shell.GetBackgroundShellManager()
289 bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'quick'", "")
290 require.NoError(t, err)
292 // Wait threshold time
293 time.Sleep(5 * time.Second)
295 // Should be done by now
296 stdout, stderr, done, err := bgShell.GetOutput()
297 require.NoError(t, err)
298 require.True(t, done, "Quick command should be done")
299 require.Contains(t, stdout, "quick")
300 require.Empty(t, stderr)
303 bgManager.Kill(bgShell.ID)
306 // Test that a long command stays in background
307 t.Run("long command stays in background", func(t *testing.T) {
309 bgManager := shell.GetBackgroundShellManager()
310 bgShell, err := bgManager.Start(ctx, workingDir, nil, "sleep 20 && echo '20 seconds completed'", "")
311 require.NoError(t, err)
312 defer bgManager.Kill(bgShell.ID)
314 // Wait threshold time
315 time.Sleep(5 * time.Second)
317 // Should still be running
318 stdout, stderr, done, err := bgShell.GetOutput()
319 require.NoError(t, err)
320 require.False(t, done, "Long command should still be running")
321 require.Empty(t, stdout, "No output yet from sleep command")
322 require.Empty(t, stderr)
324 // Verify we can get the shell from manager
325 retrieved, ok := bgManager.Get(bgShell.ID)
326 require.True(t, ok, "Should be able to retrieve background shell")
327 require.Equal(t, bgShell.ID, retrieved.ID)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list