main 6af0a61

btrfs_test.go RawHistoryFolder

1package disk
2
3import (
4	"testing"
5)
6
7// TestNewBtrfsSubvolume tests BtrfsSubvolume creation
8func TestNewBtrfsSubvolume(t *testing.T) {
9	tests := []struct {
10		name       string
11		svName     string
12		mountPoint string
13		shouldErr  bool
14		errMsg     string
15	}{
16		{"valid root subvolume", "@", "/", false, ""},
17		{"valid home subvolume", "@home", "/home", false, ""},
18		{"valid snapshots subvolume", "@snapshots", "/.snapshots", false, ""},
19		{"valid cache subvolume", "@cache", "/var/cache", false, ""},
20		{"valid log subvolume", "@log", "/var/log", false, ""},
21		{"valid with underscore", "@my_data", "/data", false, ""},
22		{"valid with hyphen", "@my-data", "/data", false, ""},
23		{"valid with numbers", "@data123", "/data", false, ""},
24		{"empty name", "", "/", true, "name cannot be empty"},
25		{"no @ prefix", "home", "/home", true, "must start with @"},
26		{"space in name", "@ home", "/home", true, "invalid character"},
27		{"invalid char in name", "@home!", "/home", true, "invalid character"},
28		{"mount point no slash", "@home", "home", true, "must start with /"},
29		{"empty mount point", "@tmp", "", false, ""},
30		{"name too long", "@" + string(make([]byte, 64)), "/", true, "too long"},
31	}
32
33	for _, tt := range tests {
34		t.Run(tt.name, func(t *testing.T) {
35			sv, err := NewBtrfsSubvolume(tt.svName, tt.mountPoint)
36
37			if (err != nil) != tt.shouldErr {
38				t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
39			}
40
41			if tt.shouldErr && err != nil && tt.errMsg != "" {
42				if !contains(err.Error(), tt.errMsg) {
43					t.Errorf("error message %q does not contain %q", err.Error(), tt.errMsg)
44				}
45			}
46
47			if !tt.shouldErr && sv == nil {
48				t.Error("expected non-nil subvolume, got nil")
49			}
50
51			if !tt.shouldErr && sv != nil {
52				if sv.Name() != tt.svName {
53					t.Errorf("got name %q, want %q", sv.Name(), tt.svName)
54				}
55				if sv.MountPoint() != tt.mountPoint {
56					t.Errorf("got mount point %q, want %q", sv.MountPoint(), tt.mountPoint)
57				}
58			}
59		})
60	}
61}
62
63// TestBtrfsSubvolumeEquals tests subvolume equality
64func TestBtrfsSubvolumeEquals(t *testing.T) {
65	sv1, _ := NewBtrfsSubvolume("@", "/")
66	sv2, _ := NewBtrfsSubvolume("@", "/")
67	sv3, _ := NewBtrfsSubvolume("@home", "/home")
68
69	if !sv1.Equals(sv2) {
70		t.Error("identical subvolumes should be equal")
71	}
72
73	if sv1.Equals(sv3) {
74		t.Error("different subvolumes should not be equal")
75	}
76
77	if sv1.Equals(nil) {
78		t.Error("subvolume should not equal nil")
79	}
80}
81
82// TestValidateSubvolumeName tests subvolume name validation
83func TestValidateSubvolumeName(t *testing.T) {
84	tests := []struct {
85		name      string
86		svName    string
87		shouldErr bool
88	}{
89		{"valid @", "@", false},
90		{"valid @home", "@home", false},
91		{"valid @snapshots", "@snapshots", false},
92		{"valid @cache", "@cache", false},
93		{"valid @log", "@log", false},
94		{"valid with underscore", "@my_data", false},
95		{"valid with hyphen", "@my-data", false},
96		{"valid with numbers", "@data123", false},
97		{"empty", "", true},
98		{"no @ prefix", "home", true},
99		{"space in name", "@ home", true},
100		{"invalid char !", "@home!", true},
101		{"invalid char #", "@home#test", true},
102		{"too long", "@" + string(make([]byte, 64)), true},
103	}
104
105	for _, tt := range tests {
106		t.Run(tt.name, func(t *testing.T) {
107			err := ValidateSubvolumeName(tt.svName)
108			if (err != nil) != tt.shouldErr {
109				t.Errorf("got error %v, expected error=%v", err, tt.shouldErr)
110			}
111		})
112	}
113}
114
115// TestNewBtrfsLayout tests BtrfsLayout creation
116func TestNewBtrfsLayout(t *testing.T) {
117	layout := NewBtrfsLayout()
118	if layout == nil {
119		t.Fatal("expected non-nil layout, got nil")
120	}
121
122	if layout.SubvolumeCount() != 0 {
123		t.Errorf("new layout should have 0 subvolumes, got %d", layout.SubvolumeCount())
124	}
125}
126
127// TestNewStandardBtrfsLayout tests standard layout creation
128func TestNewStandardBtrfsLayout(t *testing.T) {
129	layout, err := NewStandardBtrfsLayout()
130	if err != nil {
131		t.Fatalf("failed to create standard layout: %v", err)
132	}
133
134	if layout.SubvolumeCount() != 2 {
135		t.Errorf("standard layout should have 2 subvolumes, got %d", layout.SubvolumeCount())
136	}
137
138	// Check for @ subvolume
139	root := layout.GetRootSubvolume()
140	if root == nil {
141		t.Error("standard layout should have root (@) subvolume")
142	} else {
143		if root.Name() != "@" {
144			t.Errorf("root subvolume should be named @, got %q", root.Name())
145		}
146		if root.MountPoint() != "/" {
147			t.Errorf("root subvolume should mount at /, got %q", root.MountPoint())
148		}
149	}
150
151	// Check for @home subvolume
152	home := layout.FindSubvolumeByName("@home")
153	if home == nil {
154		t.Error("standard layout should have @home subvolume")
155	} else {
156		if home.MountPoint() != "/home" {
157			t.Errorf("@home should mount at /home, got %q", home.MountPoint())
158		}
159	}
160}
161
162// TestBtrfsLayoutAddSubvolume tests adding subvolumes to layout
163func TestBtrfsLayoutAddSubvolume(t *testing.T) {
164	layout := NewBtrfsLayout()
165
166	// Add root subvolume
167	root, _ := NewBtrfsSubvolume("@", "/")
168	err := layout.AddSubvolume(root)
169	if err != nil {
170		t.Fatalf("failed to add root subvolume: %v", err)
171	}
172
173	if layout.SubvolumeCount() != 1 {
174		t.Errorf("expected 1 subvolume, got %d", layout.SubvolumeCount())
175	}
176
177	// Try adding duplicate name
178	dup, _ := NewBtrfsSubvolume("@", "/other")
179	err = layout.AddSubvolume(dup)
180	if err == nil {
181		t.Error("should not allow duplicate subvolume names")
182	}
183
184	// Try adding duplicate mount point
185	other, _ := NewBtrfsSubvolume("@other", "/")
186	err = layout.AddSubvolume(other)
187	if err == nil {
188		t.Error("should not allow duplicate mount points")
189	}
190
191	// Add valid @home
192	home, _ := NewBtrfsSubvolume("@home", "/home")
193	err = layout.AddSubvolume(home)
194	if err != nil {
195		t.Fatalf("failed to add @home subvolume: %v", err)
196	}
197
198	if layout.SubvolumeCount() != 2 {
199		t.Errorf("expected 2 subvolumes, got %d", layout.SubvolumeCount())
200	}
201
202	// Try adding nil subvolume
203	err = layout.AddSubvolume(nil)
204	if err == nil {
205		t.Error("should not allow nil subvolume")
206	}
207}
208
209// TestBtrfsLayoutFindSubvolume tests finding subvolumes
210func TestBtrfsLayoutFindSubvolume(t *testing.T) {
211	layout, _ := NewStandardBtrfsLayout()
212
213	// Find by name
214	sv := layout.FindSubvolumeByName("@")
215	if sv == nil {
216		t.Error("should find @ subvolume")
217	}
218
219	sv = layout.FindSubvolumeByName("@home")
220	if sv == nil {
221		t.Error("should find @home subvolume")
222	}
223
224	sv = layout.FindSubvolumeByName("@nonexistent")
225	if sv != nil {
226		t.Error("should not find nonexistent subvolume")
227	}
228
229	// Find by mount point
230	sv = layout.FindSubvolumeByMountPoint("/")
231	if sv == nil {
232		t.Error("should find root subvolume by mount point")
233	}
234
235	sv = layout.FindSubvolumeByMountPoint("/home")
236	if sv == nil {
237		t.Error("should find @home by mount point")
238	}
239
240	sv = layout.FindSubvolumeByMountPoint("/nonexistent")
241	if sv != nil {
242		t.Error("should not find nonexistent mount point")
243	}
244}
245
246// TestBtrfsLayoutHasRootSubvolume tests root subvolume detection
247func TestBtrfsLayoutHasRootSubvolume(t *testing.T) {
248	layout := NewBtrfsLayout()
249
250	if layout.HasRootSubvolume() {
251		t.Error("empty layout should not have root subvolume")
252	}
253
254	root, _ := NewBtrfsSubvolume("@", "/")
255	if err := layout.AddSubvolume(root); err != nil {
256		t.Fatalf("expected no error, got %v", err)
257	}
258
259	if !layout.HasRootSubvolume() {
260		t.Error("layout with @ subvolume should have root subvolume")
261	}
262}
263
264// TestBtrfsLayoutValidateLayout tests layout validation
265func TestBtrfsLayoutValidateLayout(t *testing.T) {
266	// Empty layout should fail
267	layout := NewBtrfsLayout()
268	err := layout.ValidateLayout()
269	if err == nil {
270		t.Error("empty layout should fail validation")
271	}
272
273	// Layout without root should fail
274	home, _ := NewBtrfsSubvolume("@home", "/home")
275	if err := layout.AddSubvolume(home); err != nil {
276		t.Fatalf("expected no error, got %v", err)
277	}
278	err = layout.ValidateLayout()
279	if err == nil {
280		t.Error("layout without root subvolume should fail validation")
281	}
282
283	// Layout with wrong root name should fail
284	wrongRoot, _ := NewBtrfsSubvolume("@root", "/")
285	layout2 := NewBtrfsLayout()
286	if err := layout2.AddSubvolume(wrongRoot); err != nil {
287		t.Fatalf("expected no error, got %v", err)
288	}
289	err = layout2.ValidateLayout()
290	if err == nil {
291		t.Error("layout with wrong root name should fail validation")
292	}
293
294	// Standard layout should pass
295	standardLayout, _ := NewStandardBtrfsLayout()
296	err = standardLayout.ValidateLayout()
297	if err != nil {
298		t.Errorf("standard layout should pass validation: %v", err)
299	}
300}
301
302// TestBtrfsLayoutSubvolumes tests getting subvolumes list
303func TestBtrfsLayoutSubvolumes(t *testing.T) {
304	layout, _ := NewStandardBtrfsLayout()
305	subvolumes := layout.Subvolumes()
306
307	if len(subvolumes) != 2 {
308		t.Errorf("expected 2 subvolumes, got %d", len(subvolumes))
309	}
310
311	// Verify immutability - modifying returned slice shouldn't affect layout
312	subvolumes[0] = nil
313	if layout.SubvolumeCount() != 2 {
314		t.Error("modifying returned subvolumes should not affect layout")
315	}
316}
317
318// TestBtrfsSubvolumeString tests String method
319func TestBtrfsSubvolumeString(t *testing.T) {
320	sv1, _ := NewBtrfsSubvolume("@", "/")
321	str := sv1.String()
322	if !contains(str, "@") || !contains(str, "/") {
323		t.Errorf("String() output should contain name and mount point: %s", str)
324	}
325
326	sv2, _ := NewBtrfsSubvolume("@temp", "")
327	str = sv2.String()
328	if !contains(str, "@temp") {
329		t.Errorf("String() output should contain name: %s", str)
330	}
331}
332
333// TestBtrfsLayoutString tests String method
334func TestBtrfsLayoutString(t *testing.T) {
335	layout, _ := NewStandardBtrfsLayout()
336	str := layout.String()
337	if !contains(str, "BtrfsLayout") {
338		t.Errorf("String() output should contain BtrfsLayout: %s", str)
339	}
340}
341
342// Helper function
343func contains(s, substr string) bool {
344	return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && containsHelper(s, substr))
345}
346
347func containsHelper(s, substr string) bool {
348	for i := 0; i <= len(s)-len(substr); i++ {
349		if s[i:i+len(substr)] == substr {
350			return true
351		}
352	}
353	return false
354}

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