v0.2.0 43088fd

sandboxtest.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3// Package sandboxtest has helpers shared by the tests of the sandbox drivers.
4package sandboxtest
5
6import (
7	"context"
8	"encoding/binary"
9	"io"
10	"net/netip"
11	"os"
12	"path/filepath"
13	"sync"
14	"testing"
15
16	"bnema.dev/ermite-runner/internal/cmdrun"
17	"bnema.dev/ermite-runner/internal/netpolicy"
18	"bnema.dev/ermite-runner/internal/sandbox"
19)
20
21// NoMountRunner runs real commands except mount and umount, which succeed
22// without doing anything: loop mounts need root, and the tests that use it
23// replace them by plain directories (mkfs still runs).
24type NoMountRunner struct{}
25
26// Run implements cmdrun.Runner.
27func (NoMountRunner) Run(ctx context.Context, in io.Reader, name string, args ...string) ([]byte, error) {
28	if name == "mount" || name == "umount" {
29		return nil, nil
30	}
31	return cmdrun.Exec{}.Run(ctx, in, name, args...)
32}
33
34// WriteStaticELF writes a minimal statically linked ELF64 file (one PT_LOAD, no
35// PT_INTERP): enough for sandbox.CheckStatic, never executed.
36func WriteStaticELF(t testing.TB, path string) {
37	t.Helper()
38	var b [64 + 56]byte
39	copy(b[:], "\x7fELF")
40	b[4], b[5], b[6] = 2, 1, 1                // 64-bit, little endian, version
41	binary.LittleEndian.PutUint16(b[16:], 2)  // ET_EXEC
42	binary.LittleEndian.PutUint16(b[18:], 62) // x86-64
43	binary.LittleEndian.PutUint32(b[20:], 1)
44	binary.LittleEndian.PutUint64(b[32:], 64) // e_phoff
45	binary.LittleEndian.PutUint16(b[52:], 64) // e_ehsize
46	binary.LittleEndian.PutUint16(b[54:], 56) // e_phentsize
47	binary.LittleEndian.PutUint16(b[56:], 1)  // e_phnum
48	binary.LittleEndian.PutUint32(b[64:], 1)  // PT_LOAD
49	if err := os.WriteFile(path, b[:], 0o755); err != nil {
50		t.Fatal(err)
51	}
52}
53
54// Plumbing is a netsetup.Plumbing that records what is attached.
55type Plumbing struct {
56	mu       sync.Mutex
57	Attached map[string]netpolicy.Guest
58	Events   []string
59	Resets   int
60	FailNext error
61}
62
63// NewPlumbing returns an empty Plumbing.
64func NewPlumbing() *Plumbing { return &Plumbing{Attached: map[string]netpolicy.Guest{}} }
65
66// EnableForwarding implements netsetup.Plumbing.
67func (p *Plumbing) EnableForwarding() error { return nil }
68
69// Attach implements netsetup.Plumbing.
70func (p *Plumbing) Attach(_ context.Context, g netpolicy.Guest) error {
71	p.mu.Lock()
72	defer p.mu.Unlock()
73	if p.FailNext != nil {
74		err := p.FailNext
75		p.FailNext = nil
76		return err
77	}
78	p.Attached[g.Iface] = g
79	p.Events = append(p.Events, "attach "+g.Iface)
80	return nil
81}
82
83// Detach implements netsetup.Plumbing.
84func (p *Plumbing) Detach(_ context.Context, iface string, _ netip.Addr) error {
85	p.mu.Lock()
86	defer p.mu.Unlock()
87	delete(p.Attached, iface)
88	p.Events = append(p.Events, "detach "+iface)
89	return nil
90}
91
92// Reset implements netsetup.Plumbing.
93func (p *Plumbing) Reset(context.Context) error {
94	p.mu.Lock()
95	defer p.mu.Unlock()
96	p.Resets++
97	p.Attached = map[string]netpolicy.Guest{}
98	return nil
99}
100
101// Close implements netsetup.Plumbing.
102func (p *Plumbing) Close() {}
103
104// Live returns the number of attached guests.
105func (p *Plumbing) Live() int {
106	p.mu.Lock()
107	defer p.mu.Unlock()
108	return len(p.Attached)
109}
110
111// Externals is a fixed, replaceable sandbox.ExternalsSource.
112type Externals struct {
113	mu  sync.Mutex
114	set []sandbox.External
115}
116
117// Set replaces the set the drivers see.
118func (e *Externals) Set(set ...sandbox.External) { e.mu.Lock(); e.set = set; e.mu.Unlock() }
119
120// Externals implements sandbox.ExternalsSource.
121func (e *Externals) Externals() []sandbox.External {
122	e.mu.Lock()
123	defer e.mu.Unlock()
124	return append([]sandbox.External(nil), e.set...)
125}
126
127// MakeExternal creates a host directory holding bin/node with content and
128// returns it as an external called name.
129func MakeExternal(t testing.TB, name, id, content string) sandbox.External {
130	t.Helper()
131	dir := filepath.Join(t.TempDir(), name)
132	if err := os.MkdirAll(filepath.Join(dir, "bin"), 0o755); err != nil {
133		t.Fatal(err)
134	}
135	if err := os.WriteFile(filepath.Join(dir, "bin", "node"), []byte(content), 0o755); err != nil {
136		t.Fatal(err)
137	}
138	if err := os.Symlink("bin/node", filepath.Join(dir, "nodejs")); err != nil {
139		t.Fatal(err)
140	}
141	return sandbox.External{Name: name, ID: id, Dir: dir}
142}

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