v3.2.0 4fc01fa

ports_test.go RawHistoryFolder

1package out_test
2
3import (
4	"context"
5	"errors"
6	"sync"
7	"testing"
8	"time"
9
10	"github.com/bnema/gordon/internal/boundaries/out"
11	"github.com/bnema/gordon/internal/boundaries/out/mocks"
12)
13
14// The generated mocks must always satisfy their boundary ports.
15var (
16	_ out.PruneProtectionStore = (*mocks.MockPruneProtectionStore)(nil)
17	_ out.PruneRuntime         = (*mocks.MockPruneRuntime)(nil)
18	_ out.GCBarrier            = (*mocks.MockGCBarrier)(nil)
19)
20
21// referenceBarrier is the minimal shape any GCBarrier implementation
22// must have: shared leases exclude exclusive leases, exclusive leases
23// exclude everything, and a blocked acquisition honors context
24// cancellation instead of wedging shutdown.
25type referenceBarrier struct {
26	mu        sync.Mutex
27	shared    int
28	exclusive bool
29	// released is closed whenever a lease is released so waiters retry.
30	released chan struct{}
31}
32
33func newReferenceBarrier() *referenceBarrier {
34	return &referenceBarrier{released: make(chan struct{})}
35}
36
37type referenceLease struct {
38	barrier   *referenceBarrier
39	exclusive bool
40	once      sync.Once
41}
42
43func (l *referenceLease) Release() {
44	l.once.Do(func() {
45		l.barrier.mu.Lock()
46		if l.exclusive {
47			l.barrier.exclusive = false
48		} else {
49			l.barrier.shared--
50		}
51		close(l.barrier.released)
52		l.barrier.released = make(chan struct{})
53		l.barrier.mu.Unlock()
54	})
55}
56
57func (b *referenceBarrier) acquire(ctx context.Context, exclusive bool) (out.GCLease, error) {
58	for {
59		b.mu.Lock()
60		if b.canAcquire(exclusive) {
61			if exclusive {
62				b.exclusive = true
63			} else {
64				b.shared++
65			}
66			b.mu.Unlock()
67			return &referenceLease{barrier: b, exclusive: exclusive}, nil
68		}
69		wait := b.released
70		b.mu.Unlock()
71
72		select {
73		case <-ctx.Done():
74			return nil, ctx.Err()
75		case <-wait:
76		}
77	}
78}
79
80func (b *referenceBarrier) canAcquire(exclusive bool) bool {
81	if exclusive {
82		return !b.exclusive && b.shared == 0
83	}
84	return !b.exclusive
85}
86
87func (b *referenceBarrier) AcquireShared(ctx context.Context) (out.GCLease, error) {
88	return b.acquire(ctx, false)
89}
90
91func (b *referenceBarrier) AcquireExclusive(ctx context.Context) (out.GCLease, error) {
92	return b.acquire(ctx, true)
93}
94
95// releaseAll exercises the contract through the interface, so a change
96// to the port breaks this test rather than silently changing semantics.
97func acquireShared(t *testing.T, barrier out.GCBarrier, ctx context.Context) out.GCLease {
98	t.Helper()
99	lease, err := barrier.AcquireShared(ctx)
100	if err != nil {
101		t.Fatalf("AcquireShared: %v", err)
102	}
103	return lease
104}
105
106func acquireExclusive(t *testing.T, barrier out.GCBarrier, ctx context.Context) out.GCLease {
107	t.Helper()
108	lease, err := barrier.AcquireExclusive(ctx)
109	if err != nil {
110		t.Fatalf("AcquireExclusive: %v", err)
111	}
112	return lease
113}
114
115func TestGCBarrierContractExcludesExclusiveDuringShared(t *testing.T) {
116	barrier := newReferenceBarrier()
117	ctx := context.Background()
118
119	shared := acquireShared(t, barrier, ctx)
120	defer shared.Release()
121
122	waitCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
123	defer cancel()
124	if _, err := barrier.AcquireExclusive(waitCtx); !errors.Is(err, context.DeadlineExceeded) {
125		t.Fatalf("exclusive acquisition during a shared lease returned %v, want deadline exceeded", err)
126	}
127
128	shared.Release()
129	exclusive := acquireExclusive(t, barrier, ctx)
130	exclusive.Release()
131
132	// Release is idempotent: a double release must not corrupt counts.
133	shared.Release()
134	again := acquireShared(t, barrier, ctx)
135	again.Release()
136}
137
138func TestGCBarrierContractExcludesSharedDuringExclusive(t *testing.T) {
139	barrier := newReferenceBarrier()
140	ctx := context.Background()
141
142	exclusive := acquireExclusive(t, barrier, ctx)
143	defer exclusive.Release()
144
145	waitCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
146	defer cancel()
147	if _, err := barrier.AcquireShared(waitCtx); !errors.Is(err, context.DeadlineExceeded) {
148		t.Fatalf("shared acquisition during an exclusive lease returned %v, want deadline exceeded", err)
149	}
150
151	exclusive.Release()
152	shared := acquireShared(t, barrier, ctx)
153	shared.Release()
154}
155
156func TestGCBarrierContractPreCanceledContext(t *testing.T) {
157	barrier := newReferenceBarrier()
158	held := acquireExclusive(t, barrier, context.Background())
159	defer held.Release()
160
161	ctx, cancel := context.WithCancel(context.Background())
162	cancel()
163	if _, err := barrier.AcquireShared(ctx); !errors.Is(err, context.Canceled) {
164		t.Fatalf("canceled acquisition returned %v, want context.Canceled", err)
165	}
166}

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 7

Tags 106

↑↓ moveenter openesc close