blob: 093ca8ef1271f12adfc052072fd67955085ffbb6 [file] [log] [blame]
// Copyright 2021 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 main implements the cros-test-finder for finding tests based on tags.
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/golang/protobuf/jsonpb"
"go.chromium.org/chromiumos/config/go/test/api"
"chromiumos/test/execution/errors"
"chromiumos/test/util/finder"
"chromiumos/test/util/metadata"
)
const (
defaultRootPath = "/tmp/test/cros-test-finder"
defaultInputFileName = "request.json"
defaultOutputFileName = "result.json"
defaultTestMetadataDir = "/tmp/test/metadata"
)
// Version is the version info of this command. It is filled in during emerge.
var Version = "<unknown>"
// createLogFile creates a file and its parent directory for logging purpose.
func createLogFile(fullPath string) (*os.File, error) {
if err := os.MkdirAll(fullPath, 0755); err != nil {
return nil, errors.NewStatusError(errors.IOCreateError,
fmt.Errorf("failed to create directory %v: %w", fullPath, err))
}
logFullPathName := filepath.Join(fullPath, "log.txt")
// Log the full output of the command to disk.
logFile, err := os.Create(logFullPathName)
if err != nil {
return nil, errors.NewStatusError(errors.IOCreateError,
fmt.Errorf("failed to create file %v: %w", fullPath, err))
}
return logFile, nil
}
// newLogger creates a logger. Using go default logger for now.
func newLogger(logFile *os.File) *log.Logger {
mw := io.MultiWriter(logFile, os.Stderr)
return log.New(mw, "", log.LstdFlags|log.LUTC)
}
// readInput reads a CrosTestFinderRequest jsonproto file and returns a pointer to RunTestsRequest.
func readInput(fileName string) (*api.CrosTestFinderRequest, error) {
f, err := os.Open(fileName)
if err != nil {
return nil, errors.NewStatusError(errors.IOAccessError,
fmt.Errorf("fail to read file %v: %v", fileName, err))
}
req := api.CrosTestFinderRequest{}
if err := jsonpb.Unmarshal(f, &req); err != nil {
return nil, errors.NewStatusError(errors.UnmarshalError,
fmt.Errorf("fail to unmarshal file %v: %v", fileName, err))
}
return &req, nil
}
// writeOutput writes a CrosTestFinderResponse json.
func writeOutput(output string, resp *api.CrosTestFinderResponse) error {
f, err := os.Create(output)
if err != nil {
return errors.NewStatusError(errors.IOCreateError,
fmt.Errorf("fail to create file %v: %v", output, err))
}
m := jsonpb.Marshaler{}
if err := m.Marshal(f, resp); err != nil {
return errors.NewStatusError(errors.MarshalError,
fmt.Errorf("failed to marshall response to file %v: %v", output, err))
}
return nil
}
// combineTestSuiteNames combines a list of test suite names to one single name.
func combineTestSuiteNames(suites []*api.TestSuite) string {
if len(suites) == 0 {
return "CombinedSuite"
}
var names []string
for _, s := range suites {
names = append(names, s.Name)
}
return strings.Join(names, ",")
}
// metadataToTestSuite convert a list of test metadata to a test suite.
func metadataToTestSuite(name string, mdList []*api.TestCaseMetadata) *api.TestSuite {
testIds := []*api.TestCase_Id{}
for _, md := range mdList {
testIds = append(testIds, md.TestCase.Id)
}
return &api.TestSuite{
Name: name,
Spec: &api.TestSuite_TestCaseIds{
TestCaseIds: &api.TestCaseIdList{TestCaseIds: testIds},
},
}
}
func main() {
os.Exit(func() int {
t := time.Now()
defaultLogPath := filepath.Join(defaultRootPath, t.Format("20060102-150405"))
defaultRequestFile := filepath.Join(defaultRootPath, defaultInputFileName)
defaultResultFile := filepath.Join(defaultRootPath, defaultOutputFileName)
version := flag.Bool("version", false, "print version and exit")
log := flag.String("log", defaultLogPath, "specify the cros-test-finder log directory")
input := flag.String("input", defaultRequestFile, "specify the cros-test-finder request json input file")
output := flag.String("output", defaultResultFile, "specify the cros-test-finder response json output file")
metadataDir := flag.String("metadatadir", defaultTestMetadataDir,
"specify a directory that contain all test metadata proto files.")
flag.Parse()
if *version {
fmt.Println("cros-test-finder version ", Version)
return 0
}
logFile, err := createLogFile(*log)
if err != nil {
return errors.WriteError(os.Stderr, err)
}
defer logFile.Close()
logger := newLogger(logFile)
logger.Println("cros-test-finder version ", Version)
logger.Println("Reading metadata from directory: ", *metadataDir)
allTestMetadata, err := metadata.ReadDir(*metadataDir)
if err != nil {
logger.Println("Error: ", err)
return errors.WriteError(os.Stderr, err)
}
logger.Println("Reading input file: ", *input)
req, err := readInput(*input)
if err != nil {
logger.Println("Error: ", err)
return errors.WriteError(os.Stderr, err)
}
suiteName := combineTestSuiteNames(req.TestSuites)
selectedTestMetadata, err := finder.MatchedTestsForSuites(allTestMetadata.Values, req.TestSuites)
if err != nil {
logger.Println("Error: ", err)
return errors.WriteError(os.Stderr, err)
}
resultTestSuite := metadataToTestSuite(suiteName, selectedTestMetadata)
logger.Println("Writing output file: ", *output)
rspn := &api.CrosTestFinderResponse{TestSuites: []*api.TestSuite{resultTestSuite}}
if err := writeOutput(*output, rspn); err != nil {
logger.Println("Error: ", err)
return errors.WriteError(os.Stderr, err)
}
return 0
}())
}