dev/testsreporter/reporter.go (192 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. package testsreporter import ( "context" "fmt" "regexp" "strings" ) type reporter struct { ghCli *ghCli maxPreviousLinks int verbose bool } type reporterOptions struct { GhCli *ghCli MaxPreviousLinks int Verbose bool } func newReporter(options reporterOptions) reporter { return reporter{ ghCli: options.GhCli, maxPreviousLinks: options.MaxPreviousLinks, verbose: options.Verbose, } } func (r reporter) Report(ctx context.Context, issue *githubIssue, resultError failureObserver) error { links, nextIssue, err := r.updateLinks(ctx, issue, resultError.FirstBuild()) if err != nil { return fmt.Errorf("failed to update links from the error: %w", err) } resultError.UpdateLinks(*links) formatter := resultsFormatter{ result: resultError, maxPreviousLinks: r.maxPreviousLinks, } description, err := formatter.Description() if err != nil { return err } summary, err := formatter.Summary() if err != nil { return fmt.Errorf("failed to render issue summary: %w", err) } nextIssue.SetDescription(description) nextIssue.AddLabels(resultError.Labels()) fmt.Println() fmt.Println("---- Issue ----") fmt.Printf("Title: %q\n", formatter.Title()) fmt.Printf("Teams: %q\n", strings.Join(formatter.Owners(), ", ")) fmt.Printf("Labels: %s\n", strings.Join(nextIssue.Labels(), ", ")) fmt.Printf("Summary:\n%s", summary) fmt.Println("----") fmt.Println() if r.verbose { fmt.Println("---- Full Description ----") fmt.Print(description) fmt.Println("----") fmt.Println() } return r.createOrUpdateIssue(ctx, nextIssue) } func (r reporter) createOrUpdateIssue(ctx context.Context, issue *githubIssue) error { if issue.number == 0 { fmt.Println("Issue not found, creating a new one...") if err := r.ghCli.Create(ctx, issue); err != nil { return fmt.Errorf("failed to create issue (title: %s): %w", issue.title, err) } return nil } fmt.Printf("Updating issue %s...\n", issue.url) if err := r.ghCli.Update(ctx, issue); err != nil { return fmt.Errorf("failed to update issue (title: %s): %w", issue.title, err) } return nil } // updateLinks returns the links to buildkite and Github updated depending on whether or not // it existed a previous Github issue func (r reporter) updateLinks(ctx context.Context, issue *githubIssue, currentBuild string) (*errorLinks, *githubIssue, error) { nextIssue := issue links := errorLinks{ currentIssueURL: "", firstBuild: currentBuild, previousBuilds: []string{}, closedIssueURL: "", } // Look for an existing issue found, prevIssue, err := r.ghCli.Exists(ctx, issue, true) if err != nil { return nil, nil, fmt.Errorf("failed to check if issue already exists: %w", err) } if found { nextIssue = prevIssue fmt.Printf("Found existing open issue: %s\n", prevIssue.URL()) links.currentIssueURL = prevIssue.URL() // Retrieve information from the Issue description (first build, closed issue, previous links) firstBuild, err := firstBuildLinkFromDescription(prevIssue) if err != nil { return nil, nil, fmt.Errorf("failed to read first link from issue (title: %s): %w", issue.title, err) } fmt.Printf("First build found: %s\n", firstBuild) links.firstBuild = firstBuild closedIssueURL, err := closedIssueFromDescription(prevIssue) if err != nil { return nil, nil, fmt.Errorf("failed to read closed issue from issue (title: %s): %w", issue.title, err) } links.closedIssueURL = closedIssueURL if firstBuild == currentBuild { fmt.Println("First time failing, no need to update previous build links.") } else { previousLinks, err := previousBuildLinksFromDescription(prevIssue) if err != nil { return nil, nil, fmt.Errorf("failed to read previous links from issue (title: %s): %w", issue.title, err) } previousLinks = updatePreviousLinks(previousLinks, currentBuild, r.maxPreviousLinks) links.previousBuilds = previousLinks } } else { fmt.Println("No open issue found for this error.") // is there any closed issue closedIssueURL, err := r.closedIssueURL(ctx, issue) if err != nil { return nil, nil, fmt.Errorf("failed to check if there is a closed issue: %w", err) } links.closedIssueURL = closedIssueURL } return &links, nextIssue, nil } func (r reporter) closedIssueURL(ctx context.Context, issue *githubIssue) (string, error) { found, closedIssue, err := r.ghCli.Exists(ctx, issue, false) if err != nil { return "", fmt.Errorf("failed to check if there is a closed issue: %w", err) } if found { return closedIssue.URL(), nil } return "", nil } func updatePreviousLinks(previousLinks []string, currentBuild string, maxPreviousLinks int) []string { newLinks := []string{} newLinks = append(newLinks, previousLinks...) newLinks = append(newLinks, currentBuild) if len(newLinks) > maxPreviousLinks { firstIndex := len(newLinks) - maxPreviousLinks newLinks = newLinks[firstIndex:] } return newLinks } func firstBuildLinkFromDescription(issue *githubIssue) (string, error) { description := issue.description re := regexp.MustCompile(`First build failed: (?P<url>https://buildkite\.com/elastic/integrations(-serverless)?/builds/\d+)`) links := []string{} for _, matches := range re.FindAllStringSubmatch(description, -1) { for i, name := range re.SubexpNames() { if i == 0 || name != "url" { continue } links = append(links, matches[i]) } } if len(links) != 1 { return "", fmt.Errorf("incorrect number of links found for the first build: %d", len(links)) } return links[0], nil } func closedIssueFromDescription(issue *githubIssue) (string, error) { description := issue.description re := regexp.MustCompile(`Latest issue closed for the same test: (?P<url>https://github\.com/elastic/integrations/issues/\d+)`) links := []string{} for _, matches := range re.FindAllStringSubmatch(description, -1) { for i, name := range re.SubexpNames() { if i == 0 || name != "url" { continue } links = append(links, matches[i]) } } if len(links) > 1 { return "", fmt.Errorf("incorrect number of issues found for the previous closed issue: %d", len(links)) } if len(links) == 0 { return "", nil } return links[0], nil } func previousBuildLinksFromDescription(issue *githubIssue) ([]string, error) { description := issue.description re := regexp.MustCompile(`- (?P<url>https://buildkite\.com/elastic/integrations(-serverless)?/builds/\d+)`) links := []string{} for _, matches := range re.FindAllStringSubmatch(description, -1) { for i, name := range re.SubexpNames() { if i == 0 || name != "url" { continue } links = append(links, matches[i]) } } return links, nil }