finder - add flexible sources port from infra/infra
When we move cros-test to infra/infra we can remove this forked code and use that as the true source
BUG=None
TEST=build
Change-Id: I6f9afb6c2b87588a4e64e2c774c7111aa246039e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/6272939
Reviewed-by: Derek Beckett <dbeckett@chromium.org>
Reviewed-by: Billy Zhao <billyzhao@chromium.org>
Tested-by: Derek Beckett <dbeckett@chromium.org>
Commit-Queue: Derek Beckett <dbeckett@chromium.org>
diff --git a/src/go.chromium.org/chromiumos/test/execution/cmd/cros-test/cli/testexecserver.go b/src/go.chromium.org/chromiumos/test/execution/cmd/cros-test/cli/testexecserver.go
index 8296882..250aed4 100644
--- a/src/go.chromium.org/chromiumos/test/execution/cmd/cros-test/cli/testexecserver.go
+++ b/src/go.chromium.org/chromiumos/test/execution/cmd/cros-test/cli/testexecserver.go
@@ -65,7 +65,20 @@
// runTests runs the requested tests.
func runTests(ctx context.Context, logger *log.Logger, resultRootDir, tlwAddr string, metadataList *api.TestCaseMetadataList, req *api.CrosTestRequest) (*api.CrosTestResponse, error) {
- matchedMdList, err := finder.MatchedTestsForSuites(metadataList.Values, req.TestSuites)
+ md := metadataList.GetValues()
+ shouldPullGcsMD := isMoblySuite(req)
+
+ if shouldPullGcsMD {
+ src, err := finder.GetSourceData(context.Background(), "mobly_priv_artifacts/out")
+ if err != nil {
+ fmt.Printf("err %s", err)
+ }
+ metadata := finder.TranslateMoblySrcToMetadata(src)
+ md = append(md, metadata...)
+
+ }
+
+ matchedMdList, err := finder.MatchedTestsForSuites(md, req.TestSuites)
if err != nil {
return nil, statuserrors.NewStatusError(statuserrors.InvalidArgument,
fmt.Errorf("failed to match test metadata: %v", err))
@@ -157,3 +170,25 @@
}
return nil
}
+
+func isMoblySuite(req *api.CrosTestRequest) bool {
+ for _, suite := range req.GetTestSuites() {
+ md := suite.GetExecutionMetadata()
+ if mdFlagPresent(md, "moblySuite") {
+
+ return true
+ }
+ }
+ return false
+}
+
+func mdFlagPresent(metadata *api.ExecutionMetadata, flagName string) bool {
+ if metadata != nil && len(metadata.Args) > 0 {
+ for _, arg := range metadata.Args {
+ if arg.Flag == flagName {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/src/go.chromium.org/chromiumos/test/util/finder/flexible_sources.go b/src/go.chromium.org/chromiumos/test/util/finder/flexible_sources.go
new file mode 100644
index 0000000..e30e8cf
--- /dev/null
+++ b/src/go.chromium.org/chromiumos/test/util/finder/flexible_sources.go
@@ -0,0 +1,174 @@
+// 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
+}