Skip to content

Commit 0ab57e9

Browse files
kalyazinclaude
andcommitted
test(orch): cover MemfdIdentitySource.Slice and provisional fallback
Extend the MemfdIdentitySource test to exercise Slice (identity read while mapped, and BytesNotAvailableError after release), and add a test that buildProvisionalMemfile declines (returns nil, nil, nil) for a non-memfd-dedup source or when disabled, so the caller falls back to the deduped header. Also cover build.File.DurableHeader: it returns the live header when no durable future is set and otherwise waits for the deduped future, which is what keeps a pause from parenting off a provisional header, and SwapHeaderIfCurrent, which swaps only when the current header still matches so a late provisional swap can't clobber a header the upload already finalized. Signed-off-by: Nikita Kalyazin <nikita.kalyazin@e2b.dev> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4f23871 commit 0ab57e9

3 files changed

Lines changed: 128 additions & 1 deletion

File tree

packages/orchestrator/pkg/sandbox/block/memfd_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,12 +464,18 @@ func TestMemfdIdentitySource_ServesThenReleases(t *testing.T) {
464464
require.Equal(t, int(ps), n)
465465
require.Equal(t, data[page*ps:(page+1)*ps], b)
466466
}
467+
// Slice takes the same identity path and returns a copy of the memfd bytes.
468+
sl, err := src.Slice(2*ps, ps)
469+
require.NoError(t, err)
470+
require.Equal(t, data[2*ps:3*ps], sl)
467471
require.True(t, src.IsCached(t.Context(), 0, ps*4))
468472

469473
require.NoError(t, d.releaseMemfd())
470474

471-
_, err := src.ReadAt(make([]byte, ps), 0)
472475
var bna BytesNotAvailableError
476+
_, err = src.ReadAt(make([]byte, ps), 0)
477+
require.ErrorAs(t, err, &bna)
478+
_, err = src.Slice(0, ps)
473479
require.ErrorAs(t, err, &bna)
474480
require.False(t, src.IsCached(t.Context(), 0, ps))
475481
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//go:build linux
2+
3+
package build
4+
5+
import (
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/e2b-dev/infra/packages/shared/pkg/storage/header"
12+
"github.com/e2b-dev/infra/packages/shared/pkg/utils"
13+
)
14+
15+
// DurableHeader must return the live header when no durable future is set, and
16+
// otherwise wait for and return the durable (deduped) future's value — this is
17+
// what keeps a Pause from parenting a snapshot off a provisional header.
18+
func TestFile_DurableHeaderPrefersDurableFuture(t *testing.T) {
19+
t.Parallel()
20+
21+
live := &header.Header{}
22+
var f File
23+
f.header.Store(live)
24+
25+
// No durable future: returns the live header immediately.
26+
got, err := f.DurableHeader(t.Context())
27+
require.NoError(t, err)
28+
require.Same(t, live, got)
29+
30+
// With a durable future: blocks until it resolves, then returns it (not live).
31+
durable := utils.NewSetOnce[*header.Header]()
32+
f.SetDurableHeader(durable)
33+
34+
type res struct {
35+
h *header.Header
36+
err error
37+
}
38+
ch := make(chan res, 1)
39+
go func() {
40+
h, e := f.DurableHeader(t.Context())
41+
ch <- res{h, e}
42+
}()
43+
44+
select {
45+
case <-ch:
46+
t.Fatal("DurableHeader returned before the durable future resolved")
47+
case <-time.After(50 * time.Millisecond):
48+
}
49+
50+
deduped := &header.Header{}
51+
require.NoError(t, durable.SetValue(deduped))
52+
53+
r := <-ch
54+
require.NoError(t, r.err)
55+
require.Same(t, deduped, r.h)
56+
require.NotSame(t, live, r.h)
57+
}
58+
59+
// SwapHeaderIfCurrent must replace the header only when it still matches, so a
60+
// late provisional→deduped swap cannot clobber a header another writer (e.g. an
61+
// upload finalizing the build) has already installed.
62+
func TestFile_SwapHeaderIfCurrent(t *testing.T) {
63+
t.Parallel()
64+
65+
provisional := &header.Header{}
66+
var f File
67+
f.header.Store(provisional)
68+
69+
deduped := &header.Header{}
70+
finalized := &header.Header{}
71+
72+
// Current still matches → swaps.
73+
require.True(t, f.SwapHeaderIfCurrent(provisional, deduped))
74+
require.Same(t, deduped, f.Header())
75+
76+
// Current already advanced (upload finalized it) → no-op, newer header kept.
77+
f.header.Store(finalized)
78+
require.False(t, f.SwapHeaderIfCurrent(provisional, deduped))
79+
require.Same(t, finalized, f.Header())
80+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//go:build linux
2+
3+
package sandbox
4+
5+
import (
6+
"context"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
// fakeDiffSource is a block.DiffSource that is not a *block.DedupedMemfdCache,
13+
// so buildProvisionalMemfile must decline to produce a provisional header.
14+
type fakeDiffSource struct{}
15+
16+
func (fakeDiffSource) Close() error { return nil }
17+
func (fakeDiffSource) ReadAt([]byte, int64) (int, error) { return 0, nil }
18+
func (fakeDiffSource) Slice(int64, int64) ([]byte, error) { return nil, nil }
19+
func (fakeDiffSource) Size() (int64, error) { return 0, nil }
20+
func (fakeDiffSource) FileSize(context.Context) (int64, error) { return 0, nil }
21+
func (fakeDiffSource) BlockSize() int64 { return 0 }
22+
func (fakeDiffSource) Path(context.Context) (string, error) { return "", nil }
23+
24+
// buildProvisionalMemfile must fall back (return nil, nil, nil) when it can't
25+
// apply, so the caller keeps using the deduped header: a non-memfd-dedup source,
26+
// and a disabled flag.
27+
func TestBuildProvisionalMemfile_FallsBack(t *testing.T) {
28+
t.Parallel()
29+
30+
// Source is not a *block.DedupedMemfdCache → no provisional serving.
31+
h, d, swapDone := buildProvisionalMemfile(t.Context(), fakeDiffSource{}, true, nil, nil, nil)
32+
require.Nil(t, h)
33+
require.Nil(t, d)
34+
require.Nil(t, swapDone)
35+
36+
// Disabled flag → no provisional serving.
37+
h, d, swapDone = buildProvisionalMemfile(t.Context(), fakeDiffSource{}, false, nil, nil, nil)
38+
require.Nil(t, h)
39+
require.Nil(t, d)
40+
require.Nil(t, swapDone)
41+
}

0 commit comments

Comments
 (0)