v0.3.98 ff10c69

event_tail_test.go RawHistoryFolder

1package swarm
2
3import (
4	"bytes"
5	"fmt"
6	"os"
7	"path/filepath"
8	"testing"
9)
10
11// writeTailLog writes n events "e0".."e<n-1>" and returns the path and
12// the file size.
13func writeTailLog(t *testing.T, n int) (string, int64) {
14	t.Helper()
15	path := filepath.Join(t.TempDir(), "events.jsonl")
16	log, err := OpenEventLog(path)
17	if err != nil {
18		t.Fatalf("open: %v", err)
19	}
20	for i := 0; i < n; i++ {
21		if err := log.Append(NewEvent(fmt.Sprintf("e%d", i), map[string]any{"pad": "0123456789"})); err != nil {
22			t.Fatalf("append: %v", err)
23		}
24	}
25	_ = log.Close()
26	info, err := os.Stat(path)
27	if err != nil {
28		t.Fatalf("stat: %v", err)
29	}
30	return path, info.Size()
31}
32
33// lineStarts returns the byte offset of every line in the file. Event
34// lines differ in length (timestamps drop trailing zeros), so windows
35// are computed from real offsets rather than an average line size.
36func lineStarts(t *testing.T, path string) []int64 {
37	t.Helper()
38	data, err := os.ReadFile(path)
39	if err != nil {
40		t.Fatalf("read: %v", err)
41	}
42	starts := []int64{0}
43	for i, b := range data[:len(data)-1] {
44		if b == '\n' {
45			starts = append(starts, int64(i+1))
46		}
47	}
48	if !bytes.HasSuffix(data, []byte("\n")) {
49		t.Fatalf("log does not end with a newline")
50	}
51	return starts
52}
53
54func eventTypes(evs []Event) []string {
55	out := make([]string, len(evs))
56	for i, ev := range evs {
57		out[i] = ev.Type
58	}
59	return out
60}
61
62func TestReadEventLogTailSmallFileReadsEverything(t *testing.T) {
63	path, size := writeTailLog(t, 5)
64	for _, limit := range []int64{0, size, size + 100} {
65		got, err := ReadEventLogTail(path, limit)
66		if err != nil {
67			t.Fatalf("limit %d: %v", limit, err)
68		}
69		if len(got) != 5 || got[0].Type != "e0" || got[4].Type != "e4" {
70			t.Fatalf("limit %d: got %v", limit, eventTypes(got))
71		}
72	}
73}
74
75// The tail window usually starts mid-line; that partial line must be
76// dropped rather than parsed, and every later event returned in order.
77func TestReadEventLogTailSkipsPartialFirstLine(t *testing.T) {
78	path, size := writeTailLog(t, 50)
79	starts := lineStarts(t, path)
80	// Start the window in the middle of line 46: e46 is partial.
81	mid := (starts[46] + starts[47]) / 2
82	got, err := ReadEventLogTail(path, size-mid)
83	if err != nil {
84		t.Fatalf("read: %v", err)
85	}
86	want := []string{"e47", "e48", "e49"}
87	if fmt.Sprint(eventTypes(got)) != fmt.Sprint(want) {
88		t.Fatalf("got %v, want %v", eventTypes(got), want)
89	}
90}
91
92// A window starting on a line boundary must keep its first event.
93// In particular, that event might be the only lifecycle marker in the tail.
94func TestReadEventLogTailBoundaryWindow(t *testing.T) {
95	path, size := writeTailLog(t, 10)
96	starts := lineStarts(t, path)
97	// The window starts exactly at the beginning of e6.
98	got, err := ReadEventLogTail(path, size-starts[6])
99	if err != nil {
100		t.Fatalf("read: %v", err)
101	}
102	want := []string{"e6", "e7", "e8", "e9"}
103	if fmt.Sprint(eventTypes(got)) != fmt.Sprint(want) {
104		t.Fatalf("got %v, want %v", eventTypes(got), want)
105	}
106}
107
108func TestReadEventLogTailWindowWithoutNewline(t *testing.T) {
109	path, _ := writeTailLog(t, 10)
110	got, err := ReadEventLogTail(path, 5) // inside the last line
111	if err != nil || len(got) != 0 {
112		t.Fatalf("got %v, %v; want no events and no error", eventTypes(got), err)
113	}
114}
115
116func TestReadEventLogTailMissingFile(t *testing.T) {
117	got, err := ReadEventLogTail(filepath.Join(t.TempDir(), "missing.jsonl"), 1024)
118	if err != nil || got != nil {
119		t.Fatalf("got %v, %v; want nil, nil", got, err)
120	}
121}

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 476

↑↓ moveenter openesc close