blob: 9a1cb3acf4aee5d8318e827911a0a386859ba25c [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package bundleclient
import (
"context"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/internal/protocol"
)
// RunFixtureOutput is implemented by callers of RunFixture to receive fixture
// execution events.
type RunFixtureOutput interface {
EntityLog(ctx context.Context, ev *protocol.EntityLogEvent) error
EntityError(ctx context.Context, ev *protocol.EntityErrorEvent) error
}
// RunFixture requests a test bundle to set up a fixture.
// After successful return of RunFixture, a caller must call
// FixtureTicket.TearDown to tear down the fixture.
func (c *Client) RunFixture(ctx context.Context, name string, cfg *protocol.RunFixtureConfig, out RunFixtureOutput) (_ *FixtureTicket, retErr error) {
defer func() {
if retErr != nil {
retErr = errors.Wrapf(retErr, "failed to set up fixture %s", name)
}
}()
conn, err := c.dial(ctx, &protocol.HandshakeRequest{
BundleInitParams: &protocol.BundleInitParams{
Vars: cfg.GetTestVars(),
},
}, 0)
if err != nil {
return nil, err
}
defer func() {
if retErr != nil {
defer conn.Close(ctx)
}
}()
stream, err := protocol.NewFixtureServiceClient(conn.Conn()).RunFixture(ctx)
if err != nil {
return nil, err
}
// Set up a remote fixture.
req := &protocol.RunFixtureRequest{
Control: &protocol.RunFixtureRequest_Push{
Push: &protocol.RunFixturePushRequest{
Name: name,
Config: cfg,
},
},
}
pushErrors, err := sendAndRecv(ctx, stream, name, req, out)
if err != nil {
return nil, err
}
startErrors := make([]*protocol.Error, len(pushErrors))
for i, e := range pushErrors {
startErrors[i] = &protocol.Error{
Reason: e.GetReason(),
Location: &protocol.ErrorLocation{
File: e.GetFile(),
Line: int64(e.GetLine()),
Stack: e.GetStack(),
},
}
}
state := &protocol.StartFixtureState{
Name: name,
Errors: startErrors,
}
return &FixtureTicket{
out: out,
conn: conn,
stream: stream,
state: state,
}, nil
}
// FixtureTicket tracks the state of a fixture set up by Client.RunFixture.
type FixtureTicket struct {
out RunFixtureOutput
conn *rpcConn
stream protocol.FixtureService_RunFixtureClient
state *protocol.StartFixtureState
}
// TearDown tears down a fixture set up by Client.RunFixture and releases
// resources associated with the fixture.
// For a valid instance of FixtureTicket, TearDown must be called exactly once.
func (t *FixtureTicket) TearDown(ctx context.Context) (retErr error) {
defer func() {
if err := t.conn.Close(ctx); err != nil && retErr == nil {
retErr = err
}
if retErr != nil {
retErr = errors.Wrapf(retErr, "failed to tear down fixture %s", t.state.GetName())
}
}()
req := &protocol.RunFixtureRequest{
Control: &protocol.RunFixtureRequest_Pop{
Pop: &protocol.RunFixturePopRequest{},
},
}
if _, err := sendAndRecv(ctx, t.stream, t.state.GetName(), req, t.out); err != nil {
return err
}
return nil
}
// StartFixtureState returns a StartFixtureState suitable to drive local test
// bundles.
func (t *FixtureTicket) StartFixtureState() *protocol.StartFixtureState {
return t.state
}
func sendAndRecv(ctx context.Context, stream protocol.FixtureService_RunFixtureClient, name string, req *protocol.RunFixtureRequest, out RunFixtureOutput) ([]*protocol.RunFixtureError, error) {
// Send a request.
if err := stream.Send(req); err != nil {
return nil, err
}
// Read responses until RequestDone.
var errors []*protocol.RunFixtureError
for {
msg, err := stream.Recv()
if err != nil {
return nil, err
}
switch v := msg.Control.(type) {
case *protocol.RunFixtureResponse_Log:
ev := &protocol.EntityLogEvent{
Time: msg.GetTimestamp(),
EntityName: name,
Text: v.Log,
Level: msg.GetLevel(),
}
if err := out.EntityLog(ctx, ev); err != nil {
return nil, err
}
case *protocol.RunFixtureResponse_Error:
e := v.Error
ev := &protocol.EntityErrorEvent{
Time: msg.GetTimestamp(),
EntityName: name,
Error: &protocol.Error{
Reason: e.GetReason(),
Location: &protocol.ErrorLocation{
File: e.GetFile(),
Line: int64(e.GetLine()),
Stack: e.GetStack(),
},
},
}
if err := out.EntityError(ctx, ev); err != nil {
return nil, err
}
errors = append(errors, e)
case *protocol.RunFixtureResponse_RequestDone:
return errors, nil
}
}
}