main 33a243c

disk_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package disk
4
5import (
6	"context"
7	"errors"
8	"os"
9	"path/filepath"
10	"strings"
11	"testing"
12
13	"bnema.dev/ermite-runner/internal/cmdrun"
14)
15
16func TestCreateScratchIsSparseExclusiveAndNoFollow(t *testing.T) {
17	dir := t.TempDir()
18	run := &cmdrun.Fake{}
19	p := filepath.Join(dir, "scratch.ext4")
20	if err := CreateScratch(context.Background(), run, p, 64, os.Getuid()); err != nil {
21		t.Fatal(err)
22	}
23	st, _ := os.Stat(p)
24	if st.Size() != 64<<20 || st.Mode().Perm() != 0o600 {
25		t.Fatalf("%v", st)
26	}
27	if got := run.Lines(); len(got) != 1 || !strings.Contains(got[0], "mkfs.ext4") || !strings.Contains(got[0], "^has_journal") {
28		t.Fatalf("mkfs call: %v", got)
29	}
30	if err := CreateScratch(context.Background(), run, p, 64, -1); err == nil {
31		t.Fatal("an existing file must be refused")
32	}
33	victim := filepath.Join(dir, "victim")
34	_ = os.WriteFile(victim, []byte("x"), 0o600)
35	link := filepath.Join(dir, "link")
36	_ = os.Symlink(victim, link)
37	if err := CreateScratch(context.Background(), run, link, 1, -1); err == nil {
38		t.Fatal("a symlink must be refused")
39	}
40	// mkfs failure is reported.
41	bad := &cmdrun.Fake{Handle: func(cmdrun.Call) ([]byte, error) { return nil, errors.New("boom") }}
42	if err := CreateScratch(context.Background(), bad, filepath.Join(dir, "x.ext4"), 1, -1); err == nil || !strings.Contains(err.Error(), "format scratch disk") {
43		t.Fatalf("%v", err)
44	}
45}
46
47// C14: the mkfs.ext4 version is checked.
48func TestCheckMkfs(t *testing.T) {
49	ver := func(out string, err error) cmdrun.Runner {
50		return &cmdrun.Fake{Handle: func(cmdrun.Call) ([]byte, error) { return []byte(out), err }}
51	}
52	for name, tc := range map[string]struct {
53		r  cmdrun.Runner
54		ok bool
55	}{
56		"current":      {ver("mke2fs 1.47.4 (6-Mar-2025)\n", nil), true},
57		"exit 1 on -V": {ver("mke2fs 1.46.5 (30-Dec-2021)\nUsing EXT2FS Library version 1.46.5\n", errors.New("exit status 1")), true},
58		"minimum":      {ver("mke2fs 1.43 (17-May-2016)\n", nil), true},
59		"too old":      {ver("mke2fs 1.42.13 (17-May-2015)\n", nil), false},
60		"major 0":      {ver("mke2fs 0.9 (1-Jan-2000)\n", nil), false},
61		"garbage":      {ver("hello\n", nil), false},
62		"missing":      {ver("", errors.New("executable file not found")), false},
63	} {
64		err := CheckMkfs(context.Background(), tc.r)
65		if (err == nil) != tc.ok {
66			t.Errorf("%s: err=%v ok=%v", name, err, tc.ok)
67		}
68	}
69	if err := CheckMkfs(context.Background(), ver("mke2fs 1.42.9 (4-Feb-2014)", nil)); err == nil || !strings.Contains(err.Error(), "1.43") {
70		t.Errorf("the error must name the minimum: %v", err)
71	}
72}
73
74const mountinfo = `22 28 0:21 / /sys rw,nosuid shared:7 - sysfs sysfs rw
7536 28 7:0 / /var/lib/ermite-runner/containers/er00112233/scratch rw,nosuid,nodev shared:9 - ext4 /dev/loop0 rw
7637 28 7:1 / /srv/with\040space rw - ext4 /dev/loop1 rw
77`
78
79func TestMounterIsMountedAndUnmount(t *testing.T) {
80	mi := filepath.Join(t.TempDir(), "mountinfo")
81	_ = os.WriteFile(mi, []byte(mountinfo), 0o644)
82	f := &cmdrun.Fake{}
83	m := Mounter{Run: f, Mountinfo: mi}
84	for dir, want := range map[string]bool{
85		"/var/lib/ermite-runner/containers/er00112233/scratch": true,
86		"/srv/with space": true,
87		"/var/lib/ermite-runner/containers/er00112233": false,
88		"/nowhere": false,
89	} {
90		if got, err := m.IsMounted(dir); err != nil || got != want {
91			t.Errorf("%s: %v %v", dir, got, err)
92		}
93	}
94	if err := m.Unmount(context.Background(), "/nowhere"); err != nil || len(f.Calls) != 0 {
95		t.Fatalf("a directory that is not mounted needs no umount: %v %v", err, f.Lines())
96	}
97	if err := m.Unmount(context.Background(), "/var/lib/ermite-runner/containers/er00112233/scratch"); err != nil {
98		t.Fatal(err)
99	}
100	if got := f.Lines(); len(got) != 1 || got[0] != "umount /var/lib/ermite-runner/containers/er00112233/scratch" {
101		t.Fatalf("%v", got)
102	}
103	if err := m.Mount(context.Background(), "/s/scratch.ext4", "/s/mnt"); err != nil {
104		t.Fatal(err)
105	}
106	if last := f.Lines()[1]; !strings.Contains(last, "loop,noatime") || !strings.HasSuffix(last, "/s/scratch.ext4 /s/mnt") {
107		t.Fatalf("mount call: %s", last)
108	}
109	bad := Mounter{Run: &cmdrun.Fake{Handle: func(cmdrun.Call) ([]byte, error) { return nil, errors.New("busy") }}, Mountinfo: mi}
110	if err := bad.Unmount(context.Background(), "/srv/with space"); err == nil {
111		t.Fatal("umount failure must be reported")
112	}
113	if _, err := (Mounter{Mountinfo: "/nonexistent/mountinfo"}).IsMounted("/x"); err == nil {
114		t.Fatal("an unreadable mountinfo must be an error, not 'not mounted'")
115	}
116}
117
118func TestUnescapeMountinfo(t *testing.T) {
119	for in, want := range map[string]string{
120		`/plain`: "/plain", `/a\040b`: "/a b", `/tab\011x`: "/tab\tx", `/bs\134x`: `/bs\x`, `/bad\9zz`: `/bad\9zz`, `/end\04`: `/end\04`,
121	} {
122		if got := unescapeMountinfo(in); got != want {
123			t.Errorf("%q: got %q want %q", in, got, want)
124		}
125	}
126}

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 4

Tags 11

↑↓ moveenter openesc close