v0.2.0 43088fd

core_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package sandbox
4
5import (
6	"bytes"
7	"context"
8	"errors"
9	"path/filepath"
10	"strings"
11	"testing"
12	"time"
13)
14
15// S3: one runner per host, whatever its state directory.
16func TestHostLockIsExclusiveAndReleasable(t *testing.T) {
17	p := filepath.Join(t.TempDir(), "run", "ermite-runner.lock")
18	a, err := AcquireHostLock(p)
19	if err != nil {
20		t.Fatal(err)
21	}
22	if _, err := AcquireHostLock(p); err == nil || !strings.Contains(err.Error(), "another ermite-runner is running on this host") {
23		t.Fatalf("a second runner must be refused with a clear message, got %v", err)
24	}
25	if err := a.Release(); err != nil {
26		t.Fatal(err)
27	}
28	if err := a.Release(); err != nil {
29		t.Fatalf("double release: %v", err)
30	}
31	b, err := AcquireHostLock(p)
32	if err != nil {
33		t.Fatalf("the lock must be free after Release: %v", err)
34	}
35	_ = b.Release()
36	var nilLock *HostLock
37	if err := nilLock.Release(); err != nil {
38		t.Fatal(err)
39	}
40}
41
42func TestHostLockErrorNamesTheOverride(t *testing.T) {
43	_, err := AcquireHostLock("/proc/nope/ermite.lock")
44	if err == nil || !strings.Contains(err.Error(), EnvLockFile) {
45		t.Fatalf("got %v", err)
46	}
47}
48
49func TestConfigNewSettings(t *testing.T) {
50	c, err := LoadConfig(env(nil))
51	if err != nil {
52		t.Fatal(err)
53	}
54	if c.JobPids != 32768 || c.DenyPrivileged || c.LockFile != "/run/ermite-runner.lock" ||
55		c.DiskBandwidthMiB != 400 || c.DiskIOPS != 20000 || c.NetMbit != 1000 {
56		t.Fatalf("%+v", c)
57	}
58	c, err = LoadConfig(env(map[string]string{
59		EnvJobPids: "1000", EnvDenyPrivileged: "1", EnvLockFile: "/tmp/x.lock",
60		EnvJobDiskBW: "0", EnvJobDiskIOPS: "5000", EnvJobNetBandwidth: "100",
61	}))
62	if err != nil {
63		t.Fatal(err)
64	}
65	if c.JobPids != 1000 || !c.DenyPrivileged || c.LockFile != "/tmp/x.lock" || c.DiskBandwidthMiB != 0 || c.DiskIOPS != 5000 || c.NetMbit != 100 {
66		t.Fatalf("%+v", c)
67	}
68	for name, m := range map[string]map[string]string{
69		"pids low":    {EnvJobPids: "3"},
70		"deny":        {EnvDenyPrivileged: "maybe"},
71		"lock rel":    {EnvLockFile: "run/x.lock"},
72		"bw negative": {EnvJobDiskBW: "-1"},
73		"net not int": {EnvJobNetBandwidth: "fast"},
74	} {
75		if _, err := LoadConfig(env(m)); err == nil {
76			t.Errorf("%s accepted", name)
77		}
78	}
79}
80
81// C8: leaked slots stay taken until ReleaseLeaked; Busy ignores them.
82func TestSlotsLeakedLifecycle(t *testing.T) {
83	s := NewSlots(3)
84	a, _ := s.Acquire()
85	b, _ := s.Acquire()
86	s.MarkLeaked(a)
87	if s.InUse() != 2 || s.Busy() != 1 {
88		t.Fatalf("InUse=%d Busy=%d", s.InUse(), s.Busy())
89	}
90	c, _ := s.Acquire()
91	if c == a || c == b {
92		t.Fatalf("a leaked slot must not be handed out again: %d", c)
93	}
94	s.Release(a) // a normal release also clears the leak flag
95	if s.InUse() != 2 {
96		t.Fatal(s.InUse())
97	}
98	s.MarkLeaked(b)
99	s.ReleaseLeaked()
100	if s.InUse() != 1 || s.Busy() != 1 {
101		t.Fatalf("after ReleaseLeaked InUse=%d Busy=%d", s.InUse(), s.Busy())
102	}
103}
104
105func TestErrLeakedIsMatchable(t *testing.T) {
106	err := errors.Join(errors.New("remove container: busy"), ErrLeaked)
107	if !errors.Is(err, ErrLeaked) {
108		t.Fatal("errors.Is must see ErrLeaked through a join")
109	}
110}
111
112// C7: Abort cancels a boot in progress and forbids a later one.
113func TestBootGate(t *testing.T) {
114	var g BootGate
115	ctx, err := g.Begin(context.Background())
116	if err != nil {
117		t.Fatal(err)
118	}
119	go func() { time.Sleep(20 * time.Millisecond); g.Abort() }()
120	select {
121	case <-ctx.Done():
122		if !errors.Is(context.Cause(ctx), ErrAborted) {
123			t.Fatalf("cause %v", context.Cause(ctx))
124		}
125	case <-time.After(2 * time.Second):
126		t.Fatal("Abort did not cancel the boot context")
127	}
128	g.End()
129	if _, err := g.Begin(context.Background()); !errors.Is(err, ErrAborted) {
130		t.Fatalf("Begin after Abort: %v", err)
131	}
132	// Parent cancellation still works, and End releases the context.
133	var g2 BootGate
134	parent, cancel := context.WithCancel(context.Background())
135	c2, _ := g2.Begin(parent)
136	cancel()
137	<-c2.Done()
138	g2.End()
139	g2.End()
140}
141
142func TestWarnIfLowSpace(t *testing.T) {
143	var buf bytes.Buffer
144	ctx := logCtx(&buf)
145	WarnIfLowSpace(ctx, "/x", 2, 64*1024, func(string) (int64, error) { return 10 * 1024, nil })
146	if !strings.Contains(buf.String(), "less free space") || !strings.Contains(buf.String(), `"worst_case_mib":131072`) {
147		t.Fatalf("expected a warning, got %q", buf.String())
148	}
149	buf.Reset()
150	WarnIfLowSpace(ctx, "/x", 2, 1024, func(string) (int64, error) { return 10 * 1024, nil })
151	if buf.Len() != 0 {
152		t.Fatalf("no warning expected: %q", buf.String())
153	}
154	WarnIfLowSpace(ctx, "/x", 1, 1, func(string) (int64, error) { return 0, errors.New("statfs failed") })
155	if !strings.Contains(buf.String(), "cannot check") {
156		t.Fatalf("a statfs error must be logged: %q", buf.String())
157	}
158	if n, err := FreeMiB(t.TempDir()); err != nil || n <= 0 {
159		t.Fatalf("FreeMiB: %d %v", n, err)
160	}
161}

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