main 6af0a61

mount_test.go RawHistoryFolder

1package disk
2
3import (
4	"testing"
5)
6
7// TestNewMountOptions tests MountOptions creation
8func TestNewMountOptions(t *testing.T) {
9	tests := []struct {
10		name      string
11		options   map[string]string
12		shouldErr bool
13	}{
14		{"valid empty", map[string]string{}, false},
15		{"valid with values", map[string]string{"compress": "zstd", "noatime": ""}, false},
16		{"valid subvol", map[string]string{"subvol": "@"}, false},
17		{"nil options", nil, true},
18		{"empty key", map[string]string{"": "value"}, true},
19		{"key with space", map[string]string{"no time": ""}, true},
20		{"key with comma", map[string]string{"no,time": ""}, true},
21		{"key with equals", map[string]string{"no=time": ""}, true},
22		{"value with space", map[string]string{"compress": "zstd level"}, true},
23		{"value with comma", map[string]string{"compress": "zstd,level"}, true},
24	}
25
26	for _, tt := range tests {
27		t.Run(tt.name, func(t *testing.T) {
28			opts, err := NewMountOptions(tt.options)
29
30			if (err != nil) != tt.shouldErr {
31				t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
32			}
33
34			if !tt.shouldErr && opts == nil {
35				t.Error("expected non-nil options, got nil")
36			}
37
38			// Verify immutability - original map should not affect internal state
39			if !tt.shouldErr && tt.options != nil {
40				tt.options["test"] = "value"
41				if opts.Has("test") {
42					t.Error("modifying original map should not affect MountOptions")
43				}
44			}
45		})
46	}
47}
48
49// TestNewBtrfsMountOptions tests standard Btrfs mount options creation
50func TestNewBtrfsMountOptions(t *testing.T) {
51	tests := []struct {
52		name      string
53		subvolume string
54	}{
55		{"without subvolume", ""},
56		{"with @ subvolume", "@"},
57		{"with @home subvolume", "@home"},
58	}
59
60	for _, tt := range tests {
61		t.Run(tt.name, func(t *testing.T) {
62			opts, err := NewBtrfsMountOptions(tt.subvolume)
63			if err != nil {
64				t.Fatalf("failed to create Btrfs mount options: %v", err)
65			}
66
67			if !opts.Has("noatime") {
68				t.Error("Btrfs mount options should have noatime")
69			}
70
71			if !opts.Has("compress") {
72				t.Error("Btrfs mount options should have compress")
73			}
74
75			compress, _ := opts.Get("compress")
76			if compress != "zstd" {
77				t.Errorf("compress should be zstd, got %q", compress)
78			}
79
80			if tt.subvolume != "" {
81				if !opts.Has("subvol") {
82					t.Error("should have subvol option when subvolume is specified")
83				}
84				subvol, _ := opts.Get("subvol")
85				if subvol != tt.subvolume {
86					t.Errorf("subvol should be %q, got %q", tt.subvolume, subvol)
87				}
88			} else {
89				if opts.Has("subvol") {
90					t.Error("should not have subvol option when subvolume is empty")
91				}
92			}
93		})
94	}
95}
96
97// TestMountOptionsHas tests Has method
98func TestMountOptionsHas(t *testing.T) {
99	opts, _ := NewMountOptions(map[string]string{
100		"noatime":  "",
101		"compress": "zstd",
102	})
103
104	if !opts.Has("noatime") {
105		t.Error("should have noatime option")
106	}
107
108	if !opts.Has("compress") {
109		t.Error("should have compress option")
110	}
111
112	if opts.Has("nonexistent") {
113		t.Error("should not have nonexistent option")
114	}
115}
116
117// TestMountOptionsGet tests Get method
118func TestMountOptionsGet(t *testing.T) {
119	opts, _ := NewMountOptions(map[string]string{
120		"noatime":  "",
121		"compress": "zstd",
122	})
123
124	// Get existing option with value
125	value, exists := opts.Get("compress")
126	if !exists {
127		t.Error("compress option should exist")
128	}
129	if value != "zstd" {
130		t.Errorf("compress value should be zstd, got %q", value)
131	}
132
133	// Get existing option without value
134	value, exists = opts.Get("noatime")
135	if !exists {
136		t.Error("noatime option should exist")
137	}
138	if value != "" {
139		t.Errorf("noatime value should be empty, got %q", value)
140	}
141
142	// Get nonexistent option
143	_, exists = opts.Get("nonexistent")
144	if exists {
145		t.Error("nonexistent option should not exist")
146	}
147}
148
149// TestMountOptionsToString tests ToString method
150func TestMountOptionsToString(t *testing.T) {
151	tests := []struct {
152		name     string
153		options  map[string]string
154		expected []string // all possible orderings
155	}{
156		{
157			"empty",
158			map[string]string{},
159			[]string{""},
160		},
161		{
162			"single flag",
163			map[string]string{"noatime": ""},
164			[]string{"noatime"},
165		},
166		{
167			"single value",
168			map[string]string{"compress": "zstd"},
169			[]string{"compress=zstd"},
170		},
171		{
172			"multiple options",
173			map[string]string{"noatime": "", "compress": "zstd"},
174			[]string{
175				"noatime,compress=zstd",
176				"compress=zstd,noatime",
177			},
178		},
179	}
180
181	for _, tt := range tests {
182		t.Run(tt.name, func(t *testing.T) {
183			opts, _ := NewMountOptions(tt.options)
184			result := opts.ToString()
185
186			// Check if result matches any expected ordering
187			found := false
188			for _, expected := range tt.expected {
189				if result == expected {
190					found = true
191					break
192				}
193			}
194
195			if !found {
196				t.Errorf("ToString() = %q, want one of %v", result, tt.expected)
197			}
198		})
199	}
200}
201
202// TestMountOptionsEquals tests Equals method
203func TestMountOptionsEquals(t *testing.T) {
204	opts1, _ := NewMountOptions(map[string]string{"noatime": "", "compress": "zstd"})
205	opts2, _ := NewMountOptions(map[string]string{"noatime": "", "compress": "zstd"})
206	opts3, _ := NewMountOptions(map[string]string{"noatime": ""})
207	opts4, _ := NewMountOptions(map[string]string{"noatime": "", "compress": "lzo"})
208
209	if !opts1.Equals(opts2) {
210		t.Error("identical options should be equal")
211	}
212
213	if opts1.Equals(opts3) {
214		t.Error("options with different keys should not be equal")
215	}
216
217	if opts1.Equals(opts4) {
218		t.Error("options with different values should not be equal")
219	}
220
221	if opts1.Equals(nil) {
222		t.Error("options should not equal nil")
223	}
224}
225
226// TestMountOptionsOptions tests Options method (immutability)
227func TestMountOptionsOptions(t *testing.T) {
228	opts, _ := NewMountOptions(map[string]string{"noatime": "", "compress": "zstd"})
229	options := opts.Options()
230
231	// Modify returned map
232	options["test"] = "value"
233
234	// Verify internal state is not affected
235	if opts.Has("test") {
236		t.Error("modifying returned options should not affect MountOptions")
237	}
238}
239
240// TestParseMountOptions tests ParseMountOptions function
241func TestParseMountOptions(t *testing.T) {
242	tests := []struct {
243		name       string
244		optionsStr string
245		shouldErr  bool
246		expected   map[string]string
247	}{
248		{"empty", "", false, map[string]string{}},
249		{"single flag", "noatime", false, map[string]string{"noatime": ""}},
250		{"single value", "compress=zstd", false, map[string]string{"compress": "zstd"}},
251		{"multiple options", "noatime,compress=zstd", false, map[string]string{"noatime": "", "compress": "zstd"}},
252		{"with subvol", "noatime,compress=zstd,subvol=@", false, map[string]string{"noatime": "", "compress": "zstd", "subvol": "@"}},
253		{"with spaces", " noatime , compress=zstd ", false, map[string]string{"noatime": "", "compress": "zstd"}},
254		{"invalid format", "compress=", false, map[string]string{"compress": ""}},
255		{"extra commas", "noatime,,compress=zstd", false, map[string]string{"noatime": "", "compress": "zstd"}},
256	}
257
258	for _, tt := range tests {
259		t.Run(tt.name, func(t *testing.T) {
260			opts, err := ParseMountOptions(tt.optionsStr)
261
262			if (err != nil) != tt.shouldErr {
263				t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
264			}
265
266			if !tt.shouldErr && opts != nil {
267				for key, expectedValue := range tt.expected {
268					value, exists := opts.Get(key)
269					if !exists {
270						t.Errorf("expected key %q to exist", key)
271					}
272					if value != expectedValue {
273						t.Errorf("key %q: got value %q, want %q", key, value, expectedValue)
274					}
275				}
276
277				// Verify no extra keys
278				gotOptions := opts.Options()
279				if len(gotOptions) != len(tt.expected) {
280					t.Errorf("got %d options, want %d", len(gotOptions), len(tt.expected))
281				}
282			}
283		})
284	}
285}
286
287// TestNewMountPoint tests MountPoint creation
288func TestNewMountPoint(t *testing.T) {
289	validOpts, _ := NewMountOptions(map[string]string{"noatime": ""})
290
291	tests := []struct {
292		name      string
293		path      string
294		device    string
295		options   *MountOptions
296		shouldErr bool
297	}{
298		{"valid root", "/", "/dev/sda1", validOpts, false},
299		{"valid home", "/home", "/dev/sda2", validOpts, false},
300		{"valid boot", "/boot", "/dev/sda1", validOpts, false},
301		{"empty path", "", "/dev/sda1", validOpts, true},
302		{"path no slash", "home", "/dev/sda1", validOpts, true},
303		{"empty device", "/home", "", validOpts, true},
304		{"nil options", "/home", "/dev/sda1", nil, true},
305	}
306
307	for _, tt := range tests {
308		t.Run(tt.name, func(t *testing.T) {
309			mp, err := NewMountPoint(tt.path, tt.device, tt.options)
310
311			if (err != nil) != tt.shouldErr {
312				t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
313			}
314
315			if !tt.shouldErr && mp == nil {
316				t.Error("expected non-nil mount point, got nil")
317			}
318
319			if !tt.shouldErr && mp != nil {
320				if mp.Path() != tt.path {
321					t.Errorf("got path %q, want %q", mp.Path(), tt.path)
322				}
323				if mp.Device() != tt.device {
324					t.Errorf("got device %q, want %q", mp.Device(), tt.device)
325				}
326				if !mp.Options().Equals(tt.options) {
327					t.Error("options should match")
328				}
329			}
330		})
331	}
332}
333
334// TestMountPointIsRoot tests IsRoot method
335func TestMountPointIsRoot(t *testing.T) {
336	opts, _ := NewMountOptions(map[string]string{"noatime": ""})
337
338	root, _ := NewMountPoint("/", "/dev/sda1", opts)
339	if !root.IsRoot() {
340		t.Error("/ should be root mount point")
341	}
342
343	home, _ := NewMountPoint("/home", "/dev/sda2", opts)
344	if home.IsRoot() {
345		t.Error("/home should not be root mount point")
346	}
347}
348
349// TestMountPointEquals tests Equals method
350func TestMountPointEquals(t *testing.T) {
351	opts1, _ := NewMountOptions(map[string]string{"noatime": ""})
352	opts2, _ := NewMountOptions(map[string]string{"noatime": ""})
353	opts3, _ := NewMountOptions(map[string]string{"compress": "zstd"})
354
355	mp1, _ := NewMountPoint("/", "/dev/sda1", opts1)
356	mp2, _ := NewMountPoint("/", "/dev/sda1", opts2)
357	mp3, _ := NewMountPoint("/home", "/dev/sda2", opts1)
358	mp4, _ := NewMountPoint("/", "/dev/sda1", opts3)
359
360	if !mp1.Equals(mp2) {
361		t.Error("identical mount points should be equal")
362	}
363
364	if mp1.Equals(mp3) {
365		t.Error("mount points with different paths should not be equal")
366	}
367
368	if mp1.Equals(mp4) {
369		t.Error("mount points with different options should not be equal")
370	}
371
372	if mp1.Equals(nil) {
373		t.Error("mount point should not equal nil")
374	}
375}
376
377// TestValidateMountPoint tests ValidateMountPoint function
378func TestValidateMountPoint(t *testing.T) {
379	tests := []struct {
380		name      string
381		path      string
382		shouldErr bool
383	}{
384		{"valid root", "/", false},
385		{"valid home", "/home", false},
386		{"valid nested", "/var/log", false},
387		{"empty", "", true},
388		{"no slash", "home", true},
389		{"double slash", "/home//data", true},
390		{"dot dot", "/home/../data", true},
391		{"too long", "/" + string(make([]byte, 256)), true},
392	}
393
394	for _, tt := range tests {
395		t.Run(tt.name, func(t *testing.T) {
396			err := ValidateMountPoint(tt.path)
397			if (err != nil) != tt.shouldErr {
398				t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
399			}
400		})
401	}
402}
403
404// TestMountOptionsString tests String method
405func TestMountOptionsString(t *testing.T) {
406	opts, _ := NewMountOptions(map[string]string{"noatime": ""})
407	str := opts.String()
408	if !contains(str, "MountOptions") {
409		t.Errorf("String() should contain MountOptions: %s", str)
410	}
411}
412
413// TestMountPointString tests String method
414func TestMountPointString(t *testing.T) {
415	opts, _ := NewMountOptions(map[string]string{"noatime": ""})
416	mp, _ := NewMountPoint("/", "/dev/sda1", opts)
417	str := mp.String()
418	if !contains(str, "MountPoint") || !contains(str, "/") || !contains(str, "/dev/sda1") {
419		t.Errorf("String() should contain path and device: %s", str)
420	}
421}

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