blob: 78a29268ceecb195de881f6351777e761fbb96f1 [file]
// 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 reporting
import (
"context"
"github.com/golang/protobuf/ptypes"
"google.golang.org/grpc"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/internal/run/resultsjson"
frameworkprotocol "go.chromium.org/tast/core/framework/protocol"
)
// RPCClient implements a client of the reporting gRPC service.
// nil is a valid RPCClient that discards all reports.
type RPCClient struct {
conn *grpc.ClientConn
stream frameworkprotocol.Reports_LogStreamClient
}
// NewRPCClient creates a new RPCClient that reports test results to the server
// at addr.
// If addr is an empty string, this function returns nil, which is a valid
// RPCClient that discards all reports.
func NewRPCClient(ctx context.Context, addr string) (cl *RPCClient, retErr error) {
if addr == "" {
return nil, nil
}
conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer func() {
if retErr != nil {
conn.Close()
}
}()
stream, err := frameworkprotocol.NewReportsClient(conn).LogStream(ctx)
if err != nil {
return nil, err
}
return &RPCClient{
conn: conn,
stream: stream,
}, nil
}
// Close waits until the server acknowledges delivery of all logs messages, and
// closes the underlying connection of the RPCClient.
func (c *RPCClient) Close() error {
if c == nil {
return nil
}
var firstErr error
if err := c.stream.CloseSend(); err != nil && firstErr == nil {
firstErr = err
}
if _, err := c.stream.CloseAndRecv(); err != nil && firstErr == nil {
firstErr = err
}
if err := c.conn.Close(); err != nil && firstErr == nil {
firstErr = err
}
return firstErr
}
// RPCTestLogWriter is an io.Writer that reports written data as test logs via
// the reporting gRPC service.
// nil is a valid RPCTestLogWriter that silently discards all written data.
type RPCTestLogWriter struct {
stream frameworkprotocol.Reports_LogStreamClient
testName string
logPath string
}
// Write sends given bytes to the reporting gRPC service.
func (w *RPCTestLogWriter) Write(p []byte) (n int, err error) {
if w == nil {
return len(p), nil
}
req := frameworkprotocol.LogStreamRequest{
Test: w.testName,
LogPath: w.logPath,
Data: p,
}
if err := w.stream.Send(&req); err != nil {
return 0, err
}
return len(p), nil
}
// NewTestLogWriter returns an RPCTestLogWriter that reports written data as
// test logs via the reporting gRPC service.
func (c *RPCClient) NewTestLogWriter(testName, logPath string) *RPCTestLogWriter {
if c == nil {
return nil
}
return &RPCTestLogWriter{
stream: c.stream,
testName: testName,
logPath: logPath,
}
}
// ErrTerminate is returned by ReportResult when the reporting gRPC service
// requested us to terminate testing.
var ErrTerminate = errors.New("reporting service requested to terminate")
// ReportResult reports a test result to the reporting gRPC service.
// It may return ErrTerminate if the server responded with a request to
// terminate testing.
func (c *RPCClient) ReportResult(ctx context.Context, r *resultsjson.Result) error {
if c == nil {
return nil
}
startTime, err := ptypes.TimestampProto(r.Start)
if err != nil {
return err
}
duration := ptypes.DurationProto(r.End.Sub(r.Start))
req := &frameworkprotocol.ReportResultRequest{
Test: r.Name,
SkipReason: r.SkipReason,
StartTime: startTime,
Duration: duration,
}
for _, e := range r.Errors {
ts, err := ptypes.TimestampProto(e.Time)
if err != nil {
return err
}
req.Errors = append(req.Errors, &frameworkprotocol.ErrorReport{
Time: ts,
Reason: e.Reason,
File: e.File,
Line: int32(e.Line),
Stack: e.Stack,
})
}
res, err := frameworkprotocol.NewReportsClient(c.conn).ReportResult(ctx, req)
if err != nil {
return err
}
if res.GetTerminate() {
return ErrTerminate
}
return nil
}