Files
buildx/util/sourcemeta/resolver_test.go
T
Tonis Tiigi 5c3551beeb policy: add GitHub attestation verification
Add github_attestation and github_release_attestation policy support.
Fetch GitHub attestation bundles (including bundle_url .json.sn decode)
and verify against input.http.checksum.

Wire source metadata resolver progress through resolver options and add
ResolveState support for policy HTTP attestation fetches.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2026-02-20 08:56:40 -08:00

227 lines
5.8 KiB
Go

package sourcemeta
import (
"context"
"errors"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/llb/sourceresolver"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/solver/pb"
"github.com/stretchr/testify/require"
)
type fakeGatewayClient struct {
metaCalls atomic.Int32
solveCalls atomic.Int32
metaResp *sourceresolver.MetaResponse
metaErr error
solveRes *gwclient.Result
solveErr error
}
func (f *fakeGatewayClient) ResolveSourceMetadata(context.Context, *pb.SourceOp, sourceresolver.Opt) (*sourceresolver.MetaResponse, error) {
f.metaCalls.Add(1)
return f.metaResp, f.metaErr
}
func (f *fakeGatewayClient) Solve(context.Context, gwclient.SolveRequest) (*gwclient.Result, error) {
f.solveCalls.Add(1)
if f.solveRes == nil && f.solveErr == nil {
return nil, errors.New("unexpected solve call")
}
return f.solveRes, f.solveErr
}
func TestResolverCloseNoopBeforeResolve(t *testing.T) {
t.Parallel()
var called atomic.Int32
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
called.Add(1)
return nil
})
require.NoError(t, r.Close())
require.EqualValues(t, 0, called.Load())
}
func TestResolverResolveOpensOnce(t *testing.T) {
t.Parallel()
var runs atomic.Int32
mr := &fakeGatewayClient{metaResp: &sourceresolver.MetaResponse{}}
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
runs.Add(1)
ready <- mr
<-ctx.Done()
return context.Cause(ctx)
})
op := &pb.SourceOp{}
_, err := r.ResolveSourceMetadata(t.Context(), op, sourceresolver.Opt{})
require.NoError(t, err)
_, err = r.ResolveSourceMetadata(t.Context(), op, sourceresolver.Opt{})
require.NoError(t, err)
require.EqualValues(t, 1, runs.Load())
require.EqualValues(t, 2, mr.metaCalls.Load())
require.NoError(t, r.Close())
}
func TestResolverCloseAfterOpenCancelsBuild(t *testing.T) {
t.Parallel()
var canceled atomic.Bool
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
ready <- &fakeGatewayClient{metaResp: &sourceresolver.MetaResponse{}}
<-ctx.Done()
canceled.Store(true)
return context.Cause(ctx)
})
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
require.NoError(t, err)
require.NoError(t, r.Close())
require.True(t, canceled.Load())
}
func TestResolverOpenFailureIsSticky(t *testing.T) {
t.Parallel()
expected := errors.New("boom")
var runs atomic.Int32
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
runs.Add(1)
return expected
})
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
require.ErrorIs(t, err, expected)
_, err = r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
require.ErrorIs(t, err, expected)
require.EqualValues(t, 1, runs.Load())
require.ErrorIs(t, r.Close(), expected)
}
func TestResolverCloseIgnoresTerminalContextErrors(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
err error
}{
{name: "canceled", err: context.Canceled},
{name: "deadline", err: context.DeadlineExceeded},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
return tc.err
})
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
require.ErrorIs(t, err, tc.err)
require.NoError(t, r.Close())
})
}
}
func TestResolverConcurrentResolveUsesSingleOpen(t *testing.T) {
t.Parallel()
var runs atomic.Int32
mr := &fakeGatewayClient{metaResp: &sourceresolver.MetaResponse{}}
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
runs.Add(1)
ready <- mr
<-ctx.Done()
return context.Cause(ctx)
})
const n = 16
errCh := make(chan error, n)
var wg sync.WaitGroup
wg.Add(n)
for range n {
go func() {
defer wg.Done()
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
errCh <- err
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
require.NoError(t, err)
}
require.EqualValues(t, 1, runs.Load())
require.EqualValues(t, n, mr.metaCalls.Load())
done := make(chan struct{})
closeErr := make(chan error, 1)
go func() {
defer close(done)
closeErr <- r.Close()
}()
select {
case <-done:
require.NoError(t, <-closeErr)
case <-time.After(2 * time.Second):
t.Fatal("close timed out")
}
}
func TestResolverFirstCanceledContextDoesNotPoisonFutureCalls(t *testing.T) {
t.Parallel()
mr := &fakeGatewayClient{metaResp: &sourceresolver.MetaResponse{}}
started := make(chan struct{})
release := make(chan struct{})
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
close(started)
<-release
ready <- mr
<-ctx.Done()
return context.Cause(ctx)
})
canceledCtx, cancel := context.WithCancelCause(t.Context())
cancel(context.Canceled)
_, err := r.ResolveSourceMetadata(canceledCtx, &pb.SourceOp{}, sourceresolver.Opt{})
require.ErrorIs(t, err, context.Canceled)
<-started
close(release)
_, err = r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
require.NoError(t, err)
require.NoError(t, r.Close())
}
func TestResolverResolveStateUsesSolve(t *testing.T) {
t.Parallel()
mr := &fakeGatewayClient{solveErr: errors.New("solve boom")}
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
ready <- mr
<-ctx.Done()
return context.Cause(ctx)
})
st := llb.Scratch()
_, err := r.ResolveState(t.Context(), st)
require.EqualError(t, err, "solve boom")
require.EqualValues(t, 1, mr.solveCalls.Load())
require.NoError(t, r.Close())
}
var _ gatewayResolver = (*fakeGatewayClient)(nil)
var _ sourceresolver.MetaResolver = (*fakeGatewayClient)(nil)