blob: a942567a37b023f335d0917a90c4f76565787cee [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package driver
import (
"fmt"
"io"
"io/ioutil"
"os"
"sync"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
tnProto "go.chromium.org/chromiumos/config/go/api/test/harness/tnull/v1"
results "go.chromium.org/chromiumos/config/go/api/test/results/v2"
rtd "go.chromium.org/chromiumos/config/go/api/test/rtd/v1"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/sync/parallel"
)
type progressSinkInternalClient struct {
config *rtd.ProgressSinkClientConfig
driver *Driver
failureReason error
}
func (c *progressSinkInternalClient) archiveArtifact(req *rtd.ArchiveArtifactRequest) *rtd.ArchiveArtifactResponse {
c.failureReason = errors.Reason("'archiveArtifact' Not yet implemented").Err()
c.printErr(c.failureReason)
return nil
}
func (c *progressSinkInternalClient) reportLog(req *rtd.ReportLogRequest) *rtd.ReportLogResponse {
c.failureReason = errors.Reason("'reportLog' Not yet implemented").Err()
c.printErr(c.failureReason)
return nil
}
func (c *progressSinkInternalClient) reportResult(req *rtd.ReportResultRequest) *rtd.ReportResultResponse {
c.failureReason = errors.Reason("'reportResult' Not yet implemented").Err()
c.printErr(c.failureReason)
return nil
}
func (c *progressSinkInternalClient) debugLog(req proto.Message, resp proto.Message) *rtd.ReportLogResponse {
// This form of the method is temporary and serves as a stand-in for an actual RTS connection
localDebug := c.driver.debugErr
m := jsonpb.Marshaler{EmitDefaults: true, Indent: " "}
fmt.Fprintln(localDebug, "Request:")
if err := m.Marshal(localDebug, req); err != nil {
err = errors.Annotate(err, "failed to marshal request:").Err()
fmt.Fprintln(localDebug, err)
}
fmt.Fprintln(localDebug, "Response:")
if err := m.Marshal(localDebug, resp); err != nil {
err = errors.Annotate(err, "failed to marshal response:").Err()
fmt.Fprintln(localDebug, err)
}
return nil
}
func (c *progressSinkInternalClient) printErr(err error) {
// This method is temporary and serves as a stand-in for an actual RTS connection
c.failureReason = err
fmt.Fprintln(c.driver.debugErr, err)
}
// Driver is a single-instance object which runs all the operations for a test
type Driver struct {
artifacts []*tnProto.MockArtifact
emitResult sync.Once
client *progressSinkInternalClient
config *rtd.ProgressSinkClientConfig
logs []*tnProto.MockLog
result *results.Result
debugErr io.Writer
}
func (d *Driver) SetDebugErrDestination(dest io.Writer) {
d.debugErr = dest
}
// Setup sets config and prepares a result/logs/artifacts. It is called only once.
func (d *Driver) Setup(testName string, setup tnProto.SetupStep) error {
d.config = setup.Config
d.client = &progressSinkInternalClient{config: setup.Config, driver: d}
d.result = setup.Result
d.logs = setup.Logs
d.artifacts = setup.Artifacts
return d.client.failureReason
}
// Archive sends all the stored mock artifacts to the RPC service, in parallel
func (d *Driver) Archive(requestName string) error {
return parallel.WorkPool(0, func(fchan chan<- func() error) {
for _, a := range d.artifacts {
a := a
fchan <- func() error {
tmp, err := ioutil.TempFile("", "")
if err != nil {
return errors.Annotate(err, "archiving %s", a.Name).Err()
}
ioutil.WriteFile(tmp.Name(), a.FileBytes, os.ModePerm)
req := &rtd.ArchiveArtifactRequest{
Name: a.Name,
Request: requestName,
LocalPath: tmp.Name(),
}
resp := d.client.archiveArtifact(req)
d.client.debugLog(req, resp)
return d.client.failureReason
}
}
})
}
// Log reports all the stored mock logs to the RPC service, in parallel
func (d *Driver) Log(requestName string) error {
return parallel.WorkPool(0, func(fchan chan<- func() error) {
for _, l := range d.logs {
l := l
for _, m := range l.Messages {
m := m
fchan <- func() error {
req := &rtd.ReportLogRequest{
Name: l.Name,
Request: requestName,
Data: []byte(m),
}
resp := d.client.reportLog(req)
d.client.debugLog(req, resp)
return d.client.failureReason
}
}
}
})
}
// Result reports the already-stored result to the RPC service
func (d *Driver) Result(requestName string) error {
var wasFirstTime bool
d.emitResult.Do(func() {
req := &rtd.ReportResultRequest{
Request: requestName,
Result: d.result,
}
resp := d.client.reportResult(req)
d.client.debugLog(req, resp)
wasFirstTime = true
})
if !wasFirstTime {
return errors.Reason("Result cannot be called twice.").Err()
}
return d.client.failureReason
}