~james/core

core/spindle/nixcache/usage_test.go -rw-r--r-- 2.5 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 14 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package nixcache

import (
	"context"
	"errors"
	"fmt"
	"slices"
	"sync"
	"testing"
	"time"
)

type useReports struct {
	mu      sync.Mutex
	calls   [][]string
	caches  []string
	err     error
	flushed chan struct{}
}

func newUseReports() *useReports { return &useReports{flushed: make(chan struct{}, 16)} }

func (r *useReports) record(_ context.Context, cache string, hashes []string) error {
	r.mu.Lock()
	slices.Sort(hashes)
	r.calls = append(r.calls, hashes)
	r.caches = append(r.caches, cache)
	r.mu.Unlock()
	r.flushed <- struct{}{}
	return r.err
}

func (r *useReports) all() [][]string {
	r.mu.Lock()
	defer r.mu.Unlock()
	return slices.Clone(r.calls)
}

func TestUseBatcherFlushesDedupedOnTheInterval(t *testing.T) {
	reports := newUseReports()
	b := newUseBatcher("plc-a", reports.record, nil, 10*time.Millisecond, useMaxPending)
	defer b.Close()

	b.Add("b")
	b.Add("a")
	b.Add("b")
	select {
	case <-reports.flushed:
	case <-time.After(5 * time.Second):
		t.Fatal("no report within the interval")
	}
	if got, want := reports.all(), [][]string{{"a", "b"}}; !slices.EqualFunc(got, want, slices.Equal) {
		t.Fatalf("reports = %v, want %v", got, want)
	}
	if reports.caches[0] != "plc-a" {
		t.Fatalf("cache = %q, want plc-a", reports.caches[0])
	}
}

func TestUseBatcherBoundsPending(t *testing.T) {
	reports := newUseReports()
	b := newUseBatcher("plc-a", reports.record, nil, time.Hour, 3)
	for i := range 1000 {
		b.Add(fmt.Sprintf("h%04d", i))
	}
	b.Close()
	if got, want := reports.all(), [][]string{{"h0000", "h0001", "h0002"}}; !slices.EqualFunc(got, want, slices.Equal) {
		t.Fatalf("reports = %v, want %v", got, want)
	}
}

func TestUseBatcherDropsAFailedReport(t *testing.T) {
	reports := newUseReports()
	reports.err = errors.New("mill down")
	b := newUseBatcher("plc-a", reports.record, nil, time.Hour, useMaxPending)
	b.Add("a")
	b.Close()
	if got := len(reports.all()); got != 1 {
		t.Fatalf("calls = %d, want 1 and no retry", got)
	}
}

func TestUseBatcherAddDoesNotWaitForAFlush(t *testing.T) {
	block := make(chan struct{})
	b := newUseBatcher("plc-a", func(context.Context, string, []string) error {
		<-block
		return nil
	}, nil, 5*time.Millisecond, useMaxPending)
	defer func() { close(block); b.Close() }()

	b.Add("a")
	time.Sleep(50 * time.Millisecond)
	done := make(chan struct{})
	go func() { b.Add("b"); close(done) }()
	select {
	case <-done:
	case <-time.After(5 * time.Second):
		t.Fatal("Add blocked behind a flush in flight")
	}
}

func TestNilUseBatcherIsInert(t *testing.T) {
	var b *UseBatcher
	b.Add("a")
	if err := b.Close(); err != nil {
		t.Fatal(err)
	}
}