main 6af0a61

shell_test.go RawHistoryFolder

1package executor
2
3import (
4	"context"
5	"strings"
6	"testing"
7	"time"
8
9	"github.com/bnema/archup/internal/domain/ports/mocks"
10	"go.uber.org/mock/gomock"
11)
12
13func TestShellExecutor_Execute_Success(t *testing.T) {
14	ctrl := gomock.NewController(t)
15	defer ctrl.Finish()
16	mockLogger := mocks.NewMockLogger(ctrl)
17	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
18	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
19	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
20	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
21
22	executor := NewShellExecutor(mockLogger)
23	ctx := context.Background()
24
25	output, err := executor.Execute(ctx, "echo", "hello world")
26	if err != nil {
27		t.Fatalf("expected no error, got %v", err)
28	}
29
30	expected := "hello world\n"
31	if string(output) != expected {
32		t.Errorf("expected %q, got %q", expected, string(output))
33	}
34}
35
36func TestShellExecutor_Execute_WithArgs(t *testing.T) {
37	ctrl := gomock.NewController(t)
38	defer ctrl.Finish()
39	mockLogger := mocks.NewMockLogger(ctrl)
40	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
41	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
42	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
43	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
44
45	executor := NewShellExecutor(mockLogger)
46	ctx := context.Background()
47
48	// Test with multiple arguments
49	output, err := executor.Execute(ctx, "echo", "arg1", "arg2", "arg3")
50	if err != nil {
51		t.Fatalf("expected no error, got %v", err)
52	}
53
54	if !strings.Contains(string(output), "arg1") {
55		t.Errorf("expected arg1 in output, got %s", string(output))
56	}
57}
58
59func TestShellExecutor_Execute_CommandNotFound(t *testing.T) {
60	ctrl := gomock.NewController(t)
61	defer ctrl.Finish()
62	mockLogger := mocks.NewMockLogger(ctrl)
63	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
64	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
65	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
66	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
67
68	executor := NewShellExecutor(mockLogger)
69	ctx := context.Background()
70
71	_, err := executor.Execute(ctx, "nonexistentcommand12345", "arg")
72	if err == nil {
73		t.Fatal("expected error for nonexistent command")
74	}
75}
76
77func TestShellExecutor_Execute_CommandFailure(t *testing.T) {
78	ctrl := gomock.NewController(t)
79	defer ctrl.Finish()
80	mockLogger := mocks.NewMockLogger(ctrl)
81	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
82	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
83	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
84	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
85
86	executor := NewShellExecutor(mockLogger)
87	ctx := context.Background()
88
89	// false command always exits with code 1
90	_, err := executor.Execute(ctx, "false")
91	if err == nil {
92		t.Fatal("expected error for failed command")
93	}
94}
95
96func TestShellExecutor_ExecuteWithEnv_Success(t *testing.T) {
97	ctrl := gomock.NewController(t)
98	defer ctrl.Finish()
99	mockLogger := mocks.NewMockLogger(ctrl)
100	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
101	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
102	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
103	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
104
105	executor := NewShellExecutor(mockLogger)
106	ctx := context.Background()
107
108	env := map[string]string{
109		"TEST_VAR": "test_value",
110	}
111
112	// Echo the environment variable
113	output, err := executor.ExecuteWithEnv(ctx, env, "sh", "-c", "echo $TEST_VAR")
114	if err != nil {
115		t.Fatalf("expected no error, got %v", err)
116	}
117
118	if !strings.Contains(string(output), "test_value") {
119		t.Errorf("expected test_value in output, got %s", string(output))
120	}
121}
122
123func TestShellExecutor_ExecuteWithEnv_MultipleVars(t *testing.T) {
124	ctrl := gomock.NewController(t)
125	defer ctrl.Finish()
126	mockLogger := mocks.NewMockLogger(ctrl)
127	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
128	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
129	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
130	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
131
132	executor := NewShellExecutor(mockLogger)
133	ctx := context.Background()
134
135	env := map[string]string{
136		"VAR1": "value1",
137		"VAR2": "value2",
138	}
139
140	output, err := executor.ExecuteWithEnv(ctx, env, "sh", "-c", "echo $VAR1 $VAR2")
141	if err != nil {
142		t.Fatalf("expected no error, got %v", err)
143	}
144
145	output_str := string(output)
146	if !strings.Contains(output_str, "value1") || !strings.Contains(output_str, "value2") {
147		t.Errorf("expected both values in output, got %s", output_str)
148	}
149}
150
151func TestShellExecutor_ExecuteWithStdin_Success(t *testing.T) {
152	ctrl := gomock.NewController(t)
153	defer ctrl.Finish()
154	mockLogger := mocks.NewMockLogger(ctrl)
155	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
156	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
157	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
158	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
159
160	executor := NewShellExecutor(mockLogger)
161	ctx := context.Background()
162
163	output, err := executor.ExecuteWithStdin(ctx, "hello stdin", "cat")
164	if err != nil {
165		t.Fatalf("expected no error, got %v", err)
166	}
167
168	if string(output) != "hello stdin" {
169		t.Errorf("expected stdin to be forwarded, got %q", string(output))
170	}
171}
172
173func TestShellExecutor_Execute_ContextCancellation(t *testing.T) {
174	ctrl := gomock.NewController(t)
175	defer ctrl.Finish()
176	mockLogger := mocks.NewMockLogger(ctrl)
177	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
178	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
179	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
180	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
181
182	executor := NewShellExecutor(mockLogger)
183	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
184	defer cancel()
185
186	// sleep command will be interrupted
187	_, err := executor.Execute(ctx, "sleep", "10")
188	if err == nil {
189		t.Fatal("expected error due to context cancellation")
190	}
191}
192
193func TestShellExecutor_Execute_EmptyOutput(t *testing.T) {
194	ctrl := gomock.NewController(t)
195	defer ctrl.Finish()
196	mockLogger := mocks.NewMockLogger(ctrl)
197	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
198	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
199	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
200	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
201
202	executor := NewShellExecutor(mockLogger)
203	ctx := context.Background()
204
205	// true command produces no output
206	output, err := executor.Execute(ctx, "true")
207	if err != nil {
208		t.Fatalf("expected no error, got %v", err)
209	}
210
211	if len(output) != 0 {
212		t.Errorf("expected empty output, got %s", string(output))
213	}
214}
215
216func TestShellExecutor_Execute_StderrCapture(t *testing.T) {
217	ctrl := gomock.NewController(t)
218	defer ctrl.Finish()
219	mockLogger := mocks.NewMockLogger(ctrl)
220	mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes()
221	mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
222	mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
223	mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes()
224
225	executor := NewShellExecutor(mockLogger)
226	ctx := context.Background()
227
228	// sh command that writes to stderr
229	_, err := executor.Execute(ctx, "sh", "-c", "echo error >&2; exit 1")
230	if err == nil {
231		t.Fatal("expected error")
232	}
233
234	// Verify stderr is captured in error message
235	if !strings.Contains(err.Error(), "error") {
236		t.Errorf("expected 'error' in error message, got %s", err.Error())
237	}
238}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close