blob: a7342c145178bad0826069860dd57683aa00d359 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 main
import (
"archive/tar"
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"go.fuchsia.dev/fuchsia/tools/lib/tarutil"
"go.fuchsia.dev/fuchsia/tools/testing/runtests"
"go.fuchsia.dev/fuchsia/tools/testing/tap/lib"
"go.fuchsia.dev/fuchsia/tools/testing/testrunner/lib"
)
// testOutput manages the test runner's output drivers. Upon completion, if tar output is
// initialized, a TAR archive containing all other outputs is produced.
type testOutputs struct {
dataDir string
summary runtests.TestSummary
tap *tap.Producer
tw *tar.Writer
}
func createTestOutputs(producer *tap.Producer, dataDir, archivePath string) (*testOutputs, error) {
var tw *tar.Writer
if archivePath != "" {
f, err := os.Create(archivePath)
if err != nil {
return nil, fmt.Errorf("failed to create file %q: %v", archivePath, err)
}
tw = tar.NewWriter(f)
}
return &testOutputs{
dataDir: dataDir,
tap: producer,
tw: tw,
}, nil
}
// Record writes the test result to initialized outputs.
func (o *testOutputs) record(result testrunner.TestResult) error {
pathInArchive := filepath.Join(result.Name, runtests.TestOutputFilename)
// Strip any leading //.
pathInArchive = strings.TrimLeft(pathInArchive, "//")
duration := result.EndTime.Sub(result.StartTime)
if duration <= 0 {
return fmt.Errorf("test %q must have positive duration: (start, end) = (%v, %v)", result.Name, result.StartTime, result.EndTime)
}
o.summary.Tests = append(o.summary.Tests, runtests.TestDetails{
Name: result.Name,
GNLabel: result.GNLabel,
OutputFile: pathInArchive,
Result: result.Result,
StartTime: result.StartTime,
// TODO(fxbug.dev/43518): when 1.13 is available, spell this as `duration.Milliseconds()`.
DurationMillis: duration.Nanoseconds() / (1000 * 1000),
DataSinks: result.DataSinks,
})
desc := fmt.Sprintf("%s (%v)", result.Name, duration)
o.tap.Ok(result.Result == runtests.TestSuccess, desc)
if o.tw != nil {
stdout := bytes.NewReader(result.Stdout)
stderr := bytes.NewReader(result.Stderr)
stdio := io.MultiReader(stdout, stderr)
size := stdout.Size() + stderr.Size()
if err := tarutil.TarFromReader(o.tw, stdio, pathInArchive, size); err != nil {
return fmt.Errorf("failed to stdio file for test %q: %v", result.Name, err)
}
for _, sinks := range result.DataSinks {
for _, sink := range sinks {
sinkSrc := filepath.Join(o.dataDir, sink.File)
if err := tarutil.TarFile(o.tw, sinkSrc, sink.File); err != nil {
return fmt.Errorf("failed to tar data sink %q: %v", sink.Name, err)
}
}
}
}
return nil
}
// Close stops the recording of test outputs; it must be called to finalize them.
func (o *testOutputs) Close() error {
if o.tw == nil {
return nil
}
bytes, err := json.Marshal(o.summary)
if err != nil {
return err
}
if err := tarutil.TarBytes(o.tw, bytes, runtests.TestSummaryFilename); err != nil {
return err
}
return o.tw.Close()
}