cmd/testmatrix/main.go (202 lines of code) (raw):

// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This script generates the matrix.md file from most recent build of each // trigger: // // ```bash // go run cmd/testmatrix/main.go > matrix.md // ``` package main import ( "bufio" "context" "fmt" "html/template" "log" "os" "regexp" "sort" "strings" "cloud.google.com/go/storage" "github.com/GoogleCloudPlatform/opentelemetry-operations-e2e-testing/e2etestrunner" "github.com/alexflint/go-arg" "golang.org/x/sync/errgroup" "google.golang.org/api/cloudbuild/v1" ) const ( pass status = ":white_check_mark:" skip status = ":leftwards_arrow_with_hook:" templateTxt = `# Matrix of supported scenarios in each ops repo <table> <thead> <tr> <th>Repo Name</th> <th>Platform</th> {{- range $.Scenarios }} <th>{{ . }}</th> {{- end }} </tr> </thead> <tbody> {{- range $repoName := $.RepoNames }} {{- range $i, $platform := $.Platforms }} <tr> {{- if eq $i 0 }} <td rowspan={{ len $.Platforms }}> <a href="https://github.com/GoogleCloudPlatform/{{ $repoName }}">{{ $repoName }}</a> </td> {{- end }} <td>{{ $platform }}</td> {{- range $scenario := $.Scenarios }} <td>{{ index $.RepoToPlatformToScenario $repoName $platform $scenario }}</td> {{- end }} </tr> {{- end }} {{- end }} </tbody> </table> - *{{ .Pass }} means passing* - *{{ .Skip }} means not implemented (skipped)* ## Regenerate To regenerate this matrix, run from the repo root: ` + "```sh" + ` go run cmd/testmatrix/main.go --project-id=opentelemetry-ops-e2e > matrix.md ` + "```" + ` This will fetch recent Cloud Build logs to automatically update the statuses in this matrix. ` ) type Args struct { e2etestrunner.CmdWithProjectId } type status string type result struct { RepoName string Platform string Statuses map[string]status } var ( triggerNameRe = regexp.MustCompile(`^ops-\w+-e2e-.*$`) scenarioPassRe = regexp.MustCompile(`: --- PASS:\s+([\w_]+)`) scenarioSkipRe = regexp.MustCompile(`: --- SKIP:\s+([\w_]+)`) ) func main() { args := e2etestrunner.Args{} arg.MustParse(&args) ctx := context.Background() cloudbuildService, err := cloudbuild.NewService(ctx) if err != nil { panic(err) } storageClient, err := storage.NewClient(ctx) if err != nil { panic(err) } // Don't bother going over pages, just use a large page size and look at the // first page listTriggersRes, err := cloudbuildService.Projects.Triggers.List(args.ProjectID). Context(ctx). PageSize(128). Do() if err != nil { panic(err) } g, egCtx := errgroup.WithContext(ctx) results := make([]*result, len(listTriggersRes.Triggers)) for i, trigger := range listTriggersRes.Triggers { i := i trigger := trigger g.Go(func() error { res, err := handleTrigger(egCtx, args.ProjectID, trigger, cloudbuildService, storageClient) if err != nil { return err } results[i] = res return nil }) } if err := g.Wait(); err != nil { panic(err) } repoToPlatformToScenario := map[string]map[string]map[string]status{} repoNameSet := map[string]struct{}{} scenarioSet := map[string]struct{}{} platformSet := map[string]struct{}{} for _, result := range results { if result == nil || result.Platform == "build" { continue } repoNameSet[result.RepoName] = struct{}{} if repoToPlatformToScenario[result.RepoName] == nil { repoToPlatformToScenario[result.RepoName] = map[string]map[string]status{} } platformToScenario := repoToPlatformToScenario[result.RepoName] platformToScenario[result.Platform] = result.Statuses platformSet[result.Platform] = struct{}{} for scenario := range result.Statuses { scenarioSet[scenario] = struct{}{} } } repoNames := sortStringSet(repoNameSet) scenarios := sortStringSet(scenarioSet) platforms := sortStringSet(platformSet) template := template.Must(template.New("table").Parse(templateTxt)) err = template.Execute(os.Stdout, struct { RepoNames []string Scenarios []string Platforms []string RepoToPlatformToScenario map[string]map[string]map[string]status Pass status Skip status }{repoNames, scenarios, platforms, repoToPlatformToScenario, pass, skip}) if err != nil { panic(err) } } // handleTrigger returns the latest results for the given trigger by querying // builds and logs. func handleTrigger( ctx context.Context, projectId string, trigger *cloudbuild.BuildTrigger, cloudbuildService *cloudbuild.Service, storageClient *storage.Client, ) (*result, error) { if !triggerNameRe.MatchString(trigger.Name) { log.Printf("Skipping trigger %v which doesn't match regex", trigger.Name) return nil, nil } res := &result{ RepoName: trigger.Github.Name, Platform: trigger.Tags[1], Statuses: make(map[string]status), } // fetch the latest successful build listRes, err := cloudbuildService.Projects.Builds.List(projectId). Context(ctx). Filter(fmt.Sprintf(`trigger_id="%v" AND status="SUCCESS"`, trigger.Id)). PageSize(1). Do() if err != nil { return nil, err } if len(listRes.Builds) == 0 { log.Printf("trigger %v had no successful builds, skipping the trigger", trigger.Name) return res, nil } build := listRes.Builds[0] reader, err := storageClient.Bucket(strings.TrimPrefix(build.LogsBucket, "gs://")). Object(fmt.Sprintf("log-%v.txt", build.Id)). NewReader(ctx) if err != nil { return nil, err } defer reader.Close() scanner := bufio.NewScanner(reader) for scanner.Scan() { line := scanner.Text() if passMatches := scenarioPassRe.FindStringSubmatch(line); passMatches != nil { res.Statuses[passMatches[1]] = pass } else if skipMatches := scenarioSkipRe.FindStringSubmatch(line); skipMatches != nil { res.Statuses[skipMatches[1]] = skip } } if err := scanner.Err(); err != nil { return nil, err } return res, nil } func sortStringSet(set map[string]struct{}) []string { out := []string{} for k := range set { out = append(out, k) } sort.Strings(out) return out }