Skip to content

Commit ed2efe3

Browse files
committed
test: add new content-cache exec mount tests
These test all of the new behavior: - Checks for old default no content cache - Checks for old read-only and no-output allowed content cache - Checks for new root selector allowed content cache - Checks for new caller options that allow enabling/disabling it Signed-off-by: Justin Chadwell <me@jedevc.com>
1 parent 0eb25a6 commit ed2efe3

1 file changed

Lines changed: 179 additions & 45 deletions

File tree

solver/llbsolver/ops/exec_test.go

Lines changed: 179 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,6 @@ func TestExecOpCacheMap(t *testing.T) {
4040
xMatch bool
4141
}
4242

43-
newExecOp := func(opts ...func(*ExecOp)) *ExecOp {
44-
op := &ExecOp{op: &pb.ExecOp{Meta: &pb.Meta{}}}
45-
for _, opt := range opts {
46-
opt(op)
47-
}
48-
return op
49-
}
50-
51-
withNewMount := func(p string, cache *pb.CacheOpt) func(*ExecOp) {
52-
return func(op *ExecOp) {
53-
m := &pb.Mount{
54-
Dest: p,
55-
Input: pb.InputIndex(op.numInputs),
56-
// Generate a new selector for each mount since this should not effect the cache key.
57-
// This helps exercise that code path.
58-
Selector: identity.NewID(),
59-
}
60-
if cache != nil {
61-
m.CacheOpt = cache
62-
m.MountType = pb.MountType_CACHE
63-
}
64-
op.op.Mounts = append(op.op.Mounts, m)
65-
op.numInputs++
66-
}
67-
}
68-
69-
withEmptyMounts := func(op *ExecOp) {
70-
op.op.Mounts = []*pb.Mount{}
71-
}
72-
7343
testCases := []testCase{
7444
{name: "empty", op1: newExecOp(), op2: newExecOp(), xMatch: true},
7545
{
@@ -87,50 +57,50 @@ func TestExecOpCacheMap(t *testing.T) {
8757
{
8858
name: "non-nil but empty mounts vs with mounts should not match",
8959
op1: newExecOp(withEmptyMounts),
90-
op2: newExecOp(withNewMount("/foo", nil)),
60+
op2: newExecOp(withNewMount("/foo")),
9161
xMatch: false,
9262
},
9363
{
9464
name: "mounts to different paths should not match",
95-
op1: newExecOp(withNewMount("/foo", nil)),
96-
op2: newExecOp(withNewMount("/bar", nil)),
65+
op1: newExecOp(withNewMount("/foo")),
66+
op2: newExecOp(withNewMount("/bar")),
9767
xMatch: false,
9868
},
9969
{
10070
name: "mounts to same path should match",
101-
op1: newExecOp(withNewMount("/foo", nil)),
102-
op2: newExecOp(withNewMount("/foo", nil)),
71+
op1: newExecOp(withNewMount("/foo")),
72+
op2: newExecOp(withNewMount("/foo")),
10373
xMatch: true,
10474
},
10575
{
10676
name: "cache mount should not match non-cache mount at same path",
107-
op1: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "someID"})),
108-
op2: newExecOp(withNewMount("/foo", nil)),
77+
op1: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "someID"}))),
78+
op2: newExecOp(withNewMount("/foo")),
10979
xMatch: false,
11080
},
11181
{
11282
name: "different cache id's at the same path should match",
113-
op1: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "someID"})),
114-
op2: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "someOtherID"})),
83+
op1: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "someID"}))),
84+
op2: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "someOtherID"}))),
11585
xMatch: true,
11686
},
11787
{
11888
// This is a special case for default dockerfile cache mounts for backwards compatibility.
11989
name: "default dockerfile cache mount should not match the same cache mount but with different sharing",
120-
op1: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "/foo"})),
121-
op2: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "/foo", Sharing: pb.CacheSharingOpt_LOCKED})),
90+
op1: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "/foo"}))),
91+
op2: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "/foo", Sharing: pb.CacheSharingOpt_LOCKED}))),
12292
xMatch: false,
12393
},
12494
{
12595
name: "cache mounts with the same ID but different sharing options should match",
126-
op1: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "someID", Sharing: 0})),
127-
op2: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "someID", Sharing: 1})),
96+
op1: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "someID", Sharing: 0}))),
97+
op2: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "someID", Sharing: 1}))),
12898
xMatch: true,
12999
},
130100
{
131101
name: "cache mounts with different IDs and different sharing should match at the same path",
132-
op1: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "someID", Sharing: 0})),
133-
op2: newExecOp(withNewMount("/foo", &pb.CacheOpt{ID: "someOtherID", Sharing: 1})),
102+
op1: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "someID", Sharing: 0}))),
103+
op2: newExecOp(withNewMount("/foo", withCache(&pb.CacheOpt{ID: "someOtherID", Sharing: 1}))),
134104
xMatch: true,
135105
},
136106
}
@@ -157,3 +127,167 @@ func TestExecOpCacheMap(t *testing.T) {
157127
})
158128
}
159129
}
130+
131+
func TestExecOpContentCache(t *testing.T) {
132+
type testCase struct {
133+
name string
134+
op *ExecOp
135+
136+
// cacheByDefault is whether content-caching is enabled by default for this mount
137+
cacheByDefault bool
138+
// cacheIsSafe is whether content-cachine can be safely enabled for this mount
139+
cacheIsSafe bool
140+
}
141+
142+
testCases := []testCase{
143+
{
144+
name: "with sub mount",
145+
op: newExecOp(withNewMount("/foo", withSelector("/bar"))),
146+
cacheByDefault: false,
147+
cacheIsSafe: false,
148+
},
149+
{
150+
name: "with read-only sub mount",
151+
op: newExecOp(withNewMount("/foo", withSelector("/bar"), withReadonly())),
152+
cacheByDefault: true,
153+
cacheIsSafe: true,
154+
},
155+
{
156+
name: "with no-output sub mount",
157+
op: newExecOp(withNewMount("/foo", withSelector("/bar"), withoutOutput())),
158+
cacheByDefault: true,
159+
cacheIsSafe: true,
160+
},
161+
{
162+
name: "with root sub mount",
163+
op: newExecOp(withNewMount("/foo", withSelector("/"))),
164+
cacheByDefault: true,
165+
cacheIsSafe: true,
166+
},
167+
{
168+
name: "with root mount",
169+
op: newExecOp(withNewMount("/", withSelector("/bar"))),
170+
cacheByDefault: false,
171+
cacheIsSafe: false,
172+
},
173+
{
174+
name: "with root read-only mount",
175+
op: newExecOp(withNewMount("/", withSelector("/bar"), withReadonly())),
176+
cacheByDefault: false,
177+
cacheIsSafe: true,
178+
},
179+
{
180+
name: "with root no-output mount",
181+
op: newExecOp(withNewMount("/", withSelector("/bar"), withoutOutput())),
182+
cacheByDefault: false,
183+
cacheIsSafe: true,
184+
},
185+
{
186+
name: "with root mount",
187+
op: newExecOp(withNewMount("/", withSelector("/"))),
188+
cacheByDefault: false,
189+
cacheIsSafe: true,
190+
},
191+
}
192+
193+
ctx := context.Background()
194+
for _, tc := range testCases {
195+
tc := tc
196+
t.Run(tc.name, func(t *testing.T) {
197+
t.Parallel()
198+
199+
// default is always valid, and can sometimes have slow-cache
200+
m, ok, err := tc.op.CacheMap(ctx, session.NewGroup(t.Name()), 1)
201+
require.NoError(t, err)
202+
require.True(t, ok)
203+
for _, dep := range m.Deps {
204+
if tc.cacheByDefault {
205+
require.NotZero(t, dep.ComputeDigestFunc)
206+
} else {
207+
require.Zero(t, dep.ComputeDigestFunc)
208+
}
209+
}
210+
211+
// off is always valid, and never has slow-cache
212+
for _, mnt := range tc.op.op.Mounts {
213+
mnt.ContentCache = pb.MountContentCache_OFF
214+
}
215+
m, ok, err = tc.op.CacheMap(ctx, session.NewGroup(t.Name()), 1)
216+
require.NoError(t, err)
217+
require.True(t, ok)
218+
for _, dep := range m.Deps {
219+
require.Zero(t, dep.ComputeDigestFunc)
220+
}
221+
222+
// on is sometimes valid, and always has slow-cache if valid
223+
for _, mnt := range tc.op.op.Mounts {
224+
mnt.ContentCache = pb.MountContentCache_ON
225+
}
226+
m, ok, err = tc.op.CacheMap(ctx, session.NewGroup(t.Name()), 1)
227+
if tc.cacheIsSafe {
228+
require.NoError(t, err)
229+
require.True(t, ok)
230+
for _, dep := range m.Deps {
231+
require.NotZero(t, dep.ComputeDigestFunc)
232+
}
233+
} else {
234+
require.False(t, ok)
235+
require.ErrorContains(t, err, "invalid mount")
236+
}
237+
})
238+
}
239+
}
240+
241+
func newExecOp(opts ...func(*ExecOp)) *ExecOp {
242+
op := &ExecOp{op: &pb.ExecOp{Meta: &pb.Meta{}}}
243+
for _, opt := range opts {
244+
opt(op)
245+
}
246+
return op
247+
}
248+
249+
func withEmptyMounts(op *ExecOp) {
250+
op.op.Mounts = []*pb.Mount{}
251+
}
252+
253+
func withNewMount(p string, opts ...func(*pb.Mount)) func(*ExecOp) {
254+
return func(op *ExecOp) {
255+
m := &pb.Mount{
256+
Dest: p,
257+
Input: pb.InputIndex(op.numInputs),
258+
// Generate a new selector for each mount since this should not effect the cache key.
259+
// This helps exercise that code path.
260+
Selector: identity.NewID(),
261+
}
262+
for _, opt := range opts {
263+
opt(m)
264+
}
265+
op.op.Mounts = append(op.op.Mounts, m)
266+
op.numInputs++
267+
}
268+
}
269+
270+
func withSelector(selector string) func(*pb.Mount) {
271+
return func(m *pb.Mount) {
272+
m.Selector = selector
273+
}
274+
}
275+
276+
func withCache(cache *pb.CacheOpt) func(*pb.Mount) {
277+
return func(m *pb.Mount) {
278+
m.CacheOpt = cache
279+
m.MountType = pb.MountType_CACHE
280+
}
281+
}
282+
283+
func withReadonly() func(*pb.Mount) {
284+
return func(m *pb.Mount) {
285+
m.Readonly = true
286+
}
287+
}
288+
289+
func withoutOutput() func(*pb.Mount) {
290+
return func(m *pb.Mount) {
291+
m.Output = pb.SkipOutput
292+
}
293+
}

0 commit comments

Comments
 (0)