blob: e30e8cf0e4141400c933d2aabb2fd62d00cd2bd8 [file] [log] [blame]
// Copyright 2025 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package finder find all matched tests from test metadata based on test criteria.
package finder
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"path"
"sort"
"strings"
"cloud.google.com/go/storage"
"go.chromium.org/chromiumos/config/go/test/api"
)
func GetSourceData(ctx context.Context, gcsBasePath string) ([][]byte, error) {
client, err := storage.NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("storage.NewClient: %w", err)
}
defer client.Close()
bucketName, pf, err := ExtractBucketAndPrefixFromPath(gcsBasePath)
if err != nil {
return nil, err
}
bucket := client.Bucket(bucketName)
// Currently G3Mobly content is fetched off the latest.
// TODO, we probably should expose a way to pass in a desired path to allow repeatability on tests here.
newestDir, err := FindNewestDirInGcsBucket(ctx, gcsBasePath, bucket, pf)
if err != nil {
return nil, err
}
data, err := PullAllFilesFromGcsDir(ctx, bucket, newestDir, ".json")
if err != nil {
return nil, err
}
return data, nil
}
func FindNewestDirInGcsBucket(ctx context.Context, gcsBasePath string, bucket *storage.BucketHandle, pf string) (string, error) {
// List subdirectories under the prefix directory.
it := bucket.Objects(ctx, &storage.Query{Prefix: pf, Delimiter: "/"})
var subdirs []string
for {
attrs, err := it.Next()
if errors.Is(err, io.EOF) {
fmt.Println("eof")
break
}
if err != nil {
break
}
if attrs.Prefix != "" && strings.HasPrefix(path.Base(attrs.Prefix), "cros") {
subdirs = append(subdirs, attrs.Prefix)
}
}
if len(subdirs) == 0 {
return "", fmt.Errorf("no subdirectories found under %s", gcsBasePath)
}
// Sort subdirectories to find the newest one (assuming lexicographical order corresponds to time).
sort.Strings(subdirs)
newestDir := subdirs[len(subdirs)-1]
return newestDir, nil
}
// PullAllFilesFromGcsDir will grab all the files from a dir matching the postfix
func PullAllFilesFromGcsDir(ctx context.Context, bucket *storage.BucketHandle, dir string, postfix string) ([][]byte, error) {
it := bucket.Objects(ctx, &storage.Query{Prefix: dir})
var data [][]byte // Accumulate data from all files
for {
attrs, err := it.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
break
}
fmt.Println(attrs.Name)
if strings.HasSuffix(attrs.Name, postfix) {
r, err := bucket.Object(attrs.Name).NewReader(ctx)
if err != nil {
return nil, fmt.Errorf("creating reader for %s: %w", attrs.Name, err)
}
defer r.Close()
fileBytes, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("reading data from %s: %w", attrs.Name, err)
}
data = append(data, fileBytes) // Append data from current file
}
}
if data == nil {
return nil, fmt.Errorf("no files found in newest directory %s", dir)
}
return data, nil
}
func ExtractBucketAndPrefixFromPath(gcsPath string) (bucketName, prefix string, err error) {
parts := strings.SplitN(gcsPath, "/", 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("invalid GCS path: %s", gcsPath)
}
bucketName = parts[0]
prefix = fmt.Sprintf("%s/", parts[1])
return
}
type TestInfo struct {
Name string `json:"name"`
Tags []string `json:"tags"`
ExtraInfo []ExtraInfo `json:"extra_info"`
}
type ExtraInfo struct {
ExecutableName string `json:"executable_name"`
}
func TranslateMoblySrcToMetadata(src [][]byte) (metaData []*api.TestCaseMetadata) {
var testInfoList []TestInfo
for _, srcInfo := range src {
var testInfoListLocal []TestInfo
err := json.Unmarshal(srcInfo, &testInfoListLocal)
testInfoList = append(testInfoList, testInfoListLocal...)
if err != nil {
return nil
}
}
for _, testInfo := range testInfoList {
tc := createTestCaseFromTestInfo(testInfo)
metaData = append(metaData, tc)
}
return metaData
}
func createTestCaseFromTestInfo(testInfo TestInfo) *api.TestCaseMetadata {
return &api.TestCaseMetadata{
TestCase: &api.TestCase{
Name: testInfo.Name,
Tags: formatTags(testInfo.Tags),
},
TestCaseInfo: &api.TestCaseInfo{
ExtraInfo: createExtraInfo(testInfo),
},
}
}
func formatTags(tags []string) (tagList []*api.TestCase_Tag) {
for _, tag := range tags {
tagList = append(tagList, &api.TestCase_Tag{Value: tag})
}
return tagList
}
func createExtraInfo(testInfo TestInfo) map[string]string {
extraInfo := make(map[string]string)
extraInfo["executable_name"] = testInfo.ExtraInfo[0].ExecutableName
return extraInfo
}