Turn the current utility used by policy helper commands that allows getting gateway.Client lazily into reusable package. This is preparation for using this utility also in the policy verification, outside of commands. Only expose SourceMetaResolver as other methods of gateway client are not needed atm by any callers. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
194 lines
4.8 KiB
Go
194 lines
4.8 KiB
Go
package sourcemeta
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/moby/buildkit/client/llb/sourceresolver"
|
|
"github.com/moby/buildkit/solver/pb"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type fakeMetaResolver struct {
|
|
calls atomic.Int32
|
|
resp *sourceresolver.MetaResponse
|
|
err error
|
|
}
|
|
|
|
func (f *fakeMetaResolver) ResolveSourceMetadata(ctx context.Context, op *pb.SourceOp, opt sourceresolver.Opt) (*sourceresolver.MetaResponse, error) {
|
|
f.calls.Add(1)
|
|
return f.resp, f.err
|
|
}
|
|
|
|
func TestResolverCloseNoopBeforeResolve(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var called atomic.Int32
|
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) 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 := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) 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.calls.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<- sourceresolver.MetaResolver) error {
|
|
ready <- &fakeMetaResolver{resp: &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<- sourceresolver.MetaResolver) 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<- sourceresolver.MetaResolver) 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 := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) 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.calls.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 := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
|
started := make(chan struct{})
|
|
release := make(chan struct{})
|
|
|
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) 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())
|
|
}
|