github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/pkg/fakes/exec.go (about)

     1  package fakes
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync/atomic"
     7  	"testing"
     8  
     9  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/client"
    10  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/digest"
    11  	"google.golang.org/grpc/codes"
    12  	"google.golang.org/grpc/status"
    13  	"google.golang.org/protobuf/proto"
    14  
    15  	// Redundant imports are required for the google3 mirror. Aliases should not be changed.
    16  	regrpc "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
    17  	repb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
    18  	oppb "google.golang.org/genproto/googleapis/longrunning"
    19  	anypb "google.golang.org/protobuf/types/known/anypb"
    20  )
    21  
    22  // Exec implements the complete RE execution interface for a single execution, returning a fixed
    23  // result or an error.
    24  type Exec struct {
    25  	// Execution will check the action cache first, and update the action cache upon completion.
    26  	ac *ActionCache
    27  	// The action will be fetched from the CAS at start of execution, and outputs will be put in the
    28  	// CAS upon execution completion.
    29  	cas *CAS
    30  	// Fake result of an execution.
    31  	// The returned completed result, if any.
    32  	ActionResult *repb.ActionResult
    33  	// Returned completed execution status, if not Ok.
    34  	Status *status.Status
    35  	// Whether action was fake-fetched from the action cache upon execution (simulates a race between
    36  	// two executions).
    37  	Cached bool
    38  	// Any blobs that will be put in the CAS after the fake execution completes.
    39  	OutputBlobs [][]byte
    40  	// Name of the logstream to write stdout to.
    41  	StdOutStreamName string
    42  	// Name of the logstream to write stderr to.
    43  	StdErrStreamName string
    44  	// Number of Execute calls.
    45  	numExecCalls int32
    46  	// Used for errors.
    47  	t testing.TB
    48  	// The digest of the fake action.
    49  	adg digest.Digest
    50  }
    51  
    52  // NewExec returns a new empty Exec.
    53  func NewExec(t testing.TB, ac *ActionCache, cas *CAS) *Exec {
    54  	c := &Exec{t: t, ac: ac, cas: cas}
    55  	c.Clear()
    56  	return c
    57  }
    58  
    59  // Clear removes all preset results from the fake.
    60  func (s *Exec) Clear() {
    61  	s.ActionResult = nil
    62  	s.Status = nil
    63  	s.Cached = false
    64  	s.OutputBlobs = nil
    65  	atomic.StoreInt32(&s.numExecCalls, 0)
    66  }
    67  
    68  // ExecuteCalls returns the total number of Execute calls.
    69  func (s *Exec) ExecuteCalls() int {
    70  	return int(atomic.LoadInt32(&s.numExecCalls))
    71  }
    72  
    73  func fakeOPName(adg digest.Digest) string {
    74  	return "fake-action-" + adg.String()
    75  }
    76  
    77  func (s *Exec) fakeExecution(dg digest.Digest, skipCacheLookup bool) (*oppb.Operation, error) {
    78  	ar := s.ActionResult
    79  	st := s.Status
    80  	cached := s.Cached
    81  	// Check action cache first, unless instructed not to.
    82  	if !skipCacheLookup {
    83  		cr := s.ac.Get(dg)
    84  		if cr != nil {
    85  			ar = cr
    86  			st = nil
    87  			cached = true
    88  		}
    89  	}
    90  	// Fetch action from CAS.
    91  	blob, ok := s.cas.Get(dg)
    92  	if !ok {
    93  		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("action blob with digest %v not in the cas", dg))
    94  	}
    95  	apb := &repb.Action{}
    96  	if err := proto.Unmarshal(blob, apb); err != nil {
    97  		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("error unmarshalling %v as Action", blob))
    98  	}
    99  	if !apb.DoNotCache {
   100  		s.ac.Put(dg, ar)
   101  	}
   102  	for _, out := range s.OutputBlobs {
   103  		s.cas.Put(out)
   104  	}
   105  	execResp := &repb.ExecuteResponse{
   106  		Result:       ar,
   107  		Status:       st.Proto(),
   108  		CachedResult: cached,
   109  	}
   110  	any, err := anypb.New(execResp)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	return &oppb.Operation{
   115  		Name:   fakeOPName(dg),
   116  		Done:   true,
   117  		Result: &oppb.Operation_Response{Response: any},
   118  	}, nil
   119  }
   120  
   121  // GetCapabilities returns the fake capabilities.
   122  func (s *Exec) GetCapabilities(ctx context.Context, req *repb.GetCapabilitiesRequest) (res *repb.ServerCapabilities, err error) {
   123  	dgFn := digest.GetDigestFunction()
   124  	res = &repb.ServerCapabilities{
   125  		ExecutionCapabilities: &repb.ExecutionCapabilities{
   126  			DigestFunction: dgFn,
   127  			ExecEnabled:    true,
   128  		},
   129  		CacheCapabilities: &repb.CacheCapabilities{
   130  			DigestFunctions: []repb.DigestFunction_Value{dgFn},
   131  			ActionCacheUpdateCapabilities: &repb.ActionCacheUpdateCapabilities{
   132  				UpdateEnabled: true,
   133  			},
   134  			MaxBatchTotalSizeBytes:      client.DefaultMaxBatchSize,
   135  			SymlinkAbsolutePathStrategy: repb.SymlinkAbsolutePathStrategy_DISALLOWED,
   136  		},
   137  	}
   138  	return res, nil
   139  }
   140  
   141  // Execute returns the saved result ActionResult, or a Status. It also puts it in the action cache
   142  // unless the execute request specified
   143  func (s *Exec) Execute(req *repb.ExecuteRequest, stream regrpc.Execution_ExecuteServer) (err error) {
   144  	dg, err := digest.NewFromProto(req.ActionDigest)
   145  	if err != nil {
   146  		return status.Error(codes.InvalidArgument, fmt.Sprintf("invalid digest received: %v", req.ActionDigest))
   147  	}
   148  	if dg != s.adg {
   149  		s.t.Errorf("unexpected action digest received by fake: expected %v, got %v", s.adg, dg)
   150  		return status.Error(codes.InvalidArgument, fmt.Sprintf("unexpected digest received: %v", req.ActionDigest))
   151  	}
   152  	if s.StdOutStreamName != "" || s.StdErrStreamName != "" {
   153  		md, err := anypb.New(&repb.ExecuteOperationMetadata{
   154  			StdoutStreamName: s.StdOutStreamName,
   155  			StderrStreamName: s.StdErrStreamName,
   156  		})
   157  		if err != nil {
   158  			return err
   159  		}
   160  		if err := stream.Send(&oppb.Operation{Name: fakeOPName(dg), Metadata: md}); err != nil {
   161  			return err
   162  		}
   163  	}
   164  	if op, err := s.fakeExecution(dg, req.SkipCacheLookup); err != nil {
   165  		return err
   166  	} else if err = stream.Send(op); err != nil {
   167  		return err
   168  	}
   169  	atomic.AddInt32(&s.numExecCalls, 1)
   170  	return nil
   171  }
   172  
   173  func (s *Exec) WaitExecution(req *repb.WaitExecutionRequest, stream regrpc.Execution_WaitExecutionServer) (err error) {
   174  	if req.Name != fakeOPName(s.adg) {
   175  		return status.Errorf(codes.NotFound, "requested operation %v not found", req.Name)
   176  	}
   177  	if op, err := s.fakeExecution(s.adg, true); err != nil {
   178  		return err
   179  	} else {
   180  		return stream.Send(op)
   181  	}
   182  }