v2.0.0 ae3e752

reload_test.go RawHistoryFolder

1package cmd
2
3import (
4	"bytes"
5	"fmt"
6	"os"
7	"path/filepath"
8	"syscall"
9	"testing"
10
11	"github.com/spf13/cobra"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/require"
14)
15
16func TestReloadCommand(t *testing.T) {
17	// Test reload command structure
18	assert.Equal(t, "reload", reloadCmd.Use)
19	assert.Contains(t, reloadCmd.Short, "Reload")
20	assert.Contains(t, reloadCmd.Long, "configuration")
21	assert.Contains(t, reloadCmd.Long, "environment variables")
22}
23
24func TestReloadCmdStructure(t *testing.T) {
25	// Verify command is properly registered
26	found := false
27	for _, cmd := range rootCmd.Commands() {
28		if cmd.Name() == "reload" {
29			found = true
30			break
31		}
32	}
33	assert.True(t, found, "reload command should be registered with root command")
34}
35
36func TestReloadCmdHelp(t *testing.T) {
37	var output bytes.Buffer
38	
39	// Create a copy of the command to avoid affecting the global command
40	cmd := &cobra.Command{
41		Use:   reloadCmd.Use,
42		Short: reloadCmd.Short,
43		Long:  reloadCmd.Long,
44		Run:   reloadCmd.Run,
45	}
46	
47	cmd.SetOut(&output)
48	cmd.SetErr(&output)
49	cmd.SetArgs([]string{"--help"})
50	
51	err := cmd.Execute()
52	assert.NoError(t, err)
53	
54	helpOutput := output.String()
55	assert.Contains(t, helpOutput, "Reload")
56	assert.Contains(t, helpOutput, "configuration")
57	assert.Contains(t, helpOutput, "environment variables")
58	assert.Contains(t, helpOutput, "containers")
59}
60
61func TestFindGordonPidFile(t *testing.T) {
62	tests := []struct {
63		name        string
64		setupFunc   func(t *testing.T) (string, func()) // returns temp dir and cleanup func
65		expectedFound bool
66	}{
67		{
68			name: "PID file exists in temp directory",
69			setupFunc: func(t *testing.T) (string, func()) {
70				tempDir := t.TempDir()
71				pidFile := filepath.Join(tempDir, "gordon.pid")
72				err := os.WriteFile(pidFile, []byte("12345"), 0644)
73				require.NoError(t, err)
74				
75				cleanup := func() {
76					// Cleanup function for consistency
77				}
78				return pidFile, cleanup
79			},
80			expectedFound: false, // Won't find it because we can't easily mock os.TempDir
81		},
82		{
83			name: "PID file exists in home directory",
84			setupFunc: func(t *testing.T) (string, func()) {
85				// Create temp directory to simulate home
86				tempDir := t.TempDir()
87				pidFile := filepath.Join(tempDir, ".gordon.pid")
88				err := os.WriteFile(pidFile, []byte("12345"), 0644)
89				require.NoError(t, err)
90				
91				// Mock home directory
92				originalHome := os.Getenv("HOME")
93				os.Setenv("HOME", tempDir)
94				
95				cleanup := func() {
96					os.Setenv("HOME", originalHome)
97				}
98				return pidFile, cleanup
99			},
100			expectedFound: true,
101		},
102		{
103			name: "no PID file exists",
104			setupFunc: func(t *testing.T) (string, func()) {
105				// Set home to a temp directory with no PID file
106				tempDir := t.TempDir()
107				originalHome := os.Getenv("HOME")
108				os.Setenv("HOME", tempDir)
109				
110				cleanup := func() {
111					os.Setenv("HOME", originalHome)
112				}
113				return "", cleanup
114			},
115			expectedFound: false,
116		},
117	}
118
119	for _, tt := range tests {
120		t.Run(tt.name, func(t *testing.T) {
121			expectedPidFile, cleanup := tt.setupFunc(t)
122			defer cleanup()
123			
124			result := findGordonPidFile()
125			
126			if tt.expectedFound {
127				assert.NotEmpty(t, result)
128				assert.Equal(t, expectedPidFile, result)
129			} else {
130				assert.Empty(t, result)
131			}
132		})
133	}
134}
135
136func TestRunReload_NoPidFile(t *testing.T) {
137	// Set up environment with no PID file
138	tempDir := t.TempDir()
139	originalHome := os.Getenv("HOME")
140	os.Setenv("HOME", tempDir)
141	defer os.Setenv("HOME", originalHome)
142
143	// Capture stdout by redirecting it
144	oldStdout := os.Stdout
145	r, w, _ := os.Pipe()
146	os.Stdout = w
147	
148	done := make(chan string)
149	go func() {
150		var buf bytes.Buffer
151		buf.ReadFrom(r)
152		done <- buf.String()
153	}()
154	
155	// Run reload command
156	runReload(reloadCmd, []string{})
157	
158	// Restore stdout and get output
159	w.Close()
160	os.Stdout = oldStdout
161	outputStr := <-done
162	
163	// Should indicate PID file not found
164	assert.Contains(t, outputStr, "PID file not found")
165	assert.Contains(t, outputStr, "Is Gordon running?")
166}
167
168func TestRunReload_WithValidPidFile(t *testing.T) {
169	// Create a test PID file
170	tempDir := t.TempDir()
171	pidFile := filepath.Join(tempDir, ".gordon.pid")
172	
173	// Write current process PID to file (so it's valid)
174	pid := os.Getpid()
175	err := os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", pid)), 0644)
176	require.NoError(t, err)
177	
178	// Mock home directory
179	originalHome := os.Getenv("HOME")
180	os.Setenv("HOME", tempDir)
181	defer os.Setenv("HOME", originalHome)
182
183	// Capture stdout
184	var output bytes.Buffer
185	reloadCmd.SetOut(&output)
186	
187	// We can't easily test the actual signal sending to another process,
188	// but we can test the PID file reading logic
189	
190	// Test PID file reading
191	foundPidFile := findGordonPidFile()
192	assert.Equal(t, pidFile, foundPidFile)
193	
194	// Test PID parsing
195	pidBytes, err := os.ReadFile(pidFile)
196	require.NoError(t, err)
197	
198	var parsedPid int
199	_, err = fmt.Sscanf(string(pidBytes), "%d", &parsedPid)
200	require.NoError(t, err)
201	assert.Equal(t, pid, parsedPid)
202}
203
204func TestRunReload_InvalidPidFile(t *testing.T) {
205	// Create a PID file with invalid content
206	tempDir := t.TempDir()
207	pidFile := filepath.Join(tempDir, ".gordon.pid")
208	
209	// Write invalid PID content
210	err := os.WriteFile(pidFile, []byte("invalid_pid"), 0644)
211	require.NoError(t, err)
212	
213	// Mock home directory
214	originalHome := os.Getenv("HOME")
215	os.Setenv("HOME", tempDir)
216	defer os.Setenv("HOME", originalHome)
217
218	// Capture stdout by redirecting it
219	oldStdout := os.Stdout
220	r, w, _ := os.Pipe()
221	os.Stdout = w
222	
223	done := make(chan string)
224	go func() {
225		var buf bytes.Buffer
226		buf.ReadFrom(r)
227		done <- buf.String()
228	}()
229	
230	// Run reload command
231	runReload(reloadCmd, []string{})
232	
233	// Restore stdout and get output
234	w.Close()
235	os.Stdout = oldStdout
236	outputStr := <-done
237	
238	// Should indicate parsing error
239	assert.Contains(t, outputStr, "Error parsing Gordon PID")
240}
241
242func TestRunReload_UnreadablePidFile(t *testing.T) {
243	// Create a PID file that can't be read
244	tempDir := t.TempDir()
245	pidFile := filepath.Join(tempDir, ".gordon.pid")
246	
247	// Create file but make it unreadable (if possible)
248	err := os.WriteFile(pidFile, []byte("12345"), 0000)
249	require.NoError(t, err)
250	
251	// Mock home directory
252	originalHome := os.Getenv("HOME")
253	os.Setenv("HOME", tempDir)
254	defer os.Setenv("HOME", originalHome)
255
256	// Capture stdout
257	var output bytes.Buffer
258	reloadCmd.SetOut(&output)
259	
260	// Run reload command
261	runReload(reloadCmd, []string{})
262	
263	// Should indicate read error (on systems that enforce permissions)
264	outputStr := output.String()
265	// Note: This test might not fail on all systems due to permission handling
266	if len(outputStr) > 0 {
267		// If we get output, it should contain an error message
268		assert.True(t, 
269			contains(outputStr, "Error reading") || 
270			contains(outputStr, "Error parsing") ||
271			contains(outputStr, "Error finding") ||
272			contains(outputStr, "Error sending"),
273			"Should contain some error message, got: %s", outputStr)
274	}
275}
276
277func TestRunReload_NonExistentProcess(t *testing.T) {
278	// Create a PID file with a PID that doesn't exist
279	tempDir := t.TempDir()
280	pidFile := filepath.Join(tempDir, ".gordon.pid")
281	
282	// Use a PID that's very unlikely to exist (high number)
283	nonExistentPid := 999999
284	err := os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", nonExistentPid)), 0644)
285	require.NoError(t, err)
286	
287	// Mock home directory
288	originalHome := os.Getenv("HOME")
289	os.Setenv("HOME", tempDir)
290	defer os.Setenv("HOME", originalHome)
291
292	// Capture stdout by redirecting it
293	oldStdout := os.Stdout
294	r, w, _ := os.Pipe()
295	os.Stdout = w
296	
297	done := make(chan string)
298	go func() {
299		var buf bytes.Buffer
300		buf.ReadFrom(r)
301		done <- buf.String()
302	}()
303	
304	// Run reload command
305	runReload(reloadCmd, []string{})
306	
307	// Restore stdout and get output
308	w.Close()
309	os.Stdout = oldStdout
310	outputStr := <-done
311	
312	// Should indicate process not found or signal sending error
313	assert.True(t, 
314		contains(outputStr, "Error finding") || 
315		contains(outputStr, "Error sending") ||
316		contains(outputStr, "not be running"),
317		"Should contain process-related error, got: %s", outputStr)
318}
319
320func TestFindGordonPidFile_AllLocations(t *testing.T) {
321	// Test the locations that findGordonPidFile checks
322	locations := []string{
323		"/tmp/gordon.pid",
324		"/var/run/gordon.pid",
325	}
326	
327	// Add temp dir location
328	tempDirLocation := filepath.Join(os.TempDir(), "gordon.pid")
329	locations = append(locations, tempDirLocation)
330	
331	// Add home directory location
332	if homeDir, err := os.UserHomeDir(); err == nil {
333		homeLocation := filepath.Join(homeDir, ".gordon.pid")
334		locations = append(locations, homeLocation)
335	}
336	
337	// Verify that the function checks these locations
338	// We can't easily test all of them without root access,
339	// but we can verify the function doesn't panic
340	assert.NotPanics(t, func() {
341		result := findGordonPidFile()
342		// Result might be empty if no PID file exists, which is fine
343		_ = result
344	})
345}
346
347func TestSignalSending_Concepts(t *testing.T) {
348	// Test the signal sending concepts used in runReload
349	
350	// Test that we can find our own process
351	process, err := os.FindProcess(os.Getpid())
352	assert.NoError(t, err)
353	assert.NotNil(t, process)
354	
355	// Test signal types
356	signals := []syscall.Signal{
357		syscall.SIGUSR1,
358		syscall.SIGTERM,
359		syscall.SIGINT,
360	}
361	
362	for _, sig := range signals {
363		assert.NotNil(t, sig)
364	}
365}
366
367func TestPidFileValidation(t *testing.T) {
368	tests := []struct {
369		name      string
370		content   string
371		expectErr bool
372	}{
373		{
374			name:      "valid PID",
375			content:   "12345",
376			expectErr: false,
377		},
378		{
379			name:      "invalid PID - letters",
380			content:   "abc",
381			expectErr: true,
382		},
383		{
384			name:      "invalid PID - empty",
385			content:   "",
386			expectErr: true,
387		},
388		{
389			name:      "invalid PID - negative",
390			content:   "-123",
391			expectErr: false, // Parsing succeeds but PID is invalid
392		},
393		{
394			name:      "valid PID with whitespace",
395			content:   "  12345  \n",
396			expectErr: false,
397		},
398	}
399
400	for _, tt := range tests {
401		t.Run(tt.name, func(t *testing.T) {
402			var pid int
403			_, err := fmt.Sscanf(tt.content, "%d", &pid)
404			
405			if tt.expectErr {
406				assert.Error(t, err)
407			} else {
408				assert.NoError(t, err)
409				if tt.content != "" && tt.content != "  12345  \n" && tt.content != "-123" {
410					assert.Greater(t, pid, 0)
411				}
412			}
413		})
414	}
415}
416
417// Helper function to check if string contains substring (case-insensitive)
418func contains(s, substr string) bool {
419	return len(s) >= len(substr) && 
420		   (s == substr || 
421		    len(s) > len(substr) && 
422		    (s[:len(substr)] == substr || 
423		     s[len(s)-len(substr):] == substr ||
424		     searchSubstring(s, substr)))
425}
426
427func searchSubstring(s, substr string) bool {
428	for i := 0; i <= len(s)-len(substr); i++ {
429		if s[i:i+len(substr)] == substr {
430			return true
431		}
432	}
433	return false
434}

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 7

Tags 106

↑↓ moveenter openesc close