go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/culpritaction/revertculprit/comment_generator.go (about)

     1  // Copyright 2023 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package revertculprit
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  	"text/template"
    24  
    25  	"go.chromium.org/luci/bisection/model"
    26  	"go.chromium.org/luci/bisection/util"
    27  	"go.chromium.org/luci/bisection/util/datastoreutil"
    28  	"go.chromium.org/luci/common/errors"
    29  )
    30  
    31  var compileCommentTemplate = template.Must(template.New("").Parse(
    32  	`
    33  {{define "basic"}}
    34  {{if .Reason}}
    35  A revert for this change was not created because {{.Reason}}.
    36  {{end}}
    37  Sample failed build: {{.BuildURL}}
    38  
    39  If this is a false positive, please report it at {{.BugURL}}
    40  {{- end}}
    41  
    42  {{define "supportComment" -}}
    43  LUCI Bisection recommends submitting this revert because it has confirmed the target of this revert is the culprit of a build failure. See the analysis: {{.AnalysisURL}}
    44   {{- template "basic" . -}}
    45  {{end}}
    46  
    47  {{define "blameComment" -}}
    48  LUCI Bisection has identified this change as the culprit of a build failure. See the analysis: {{.AnalysisURL}}
    49  {{- template "basic" . -}}
    50  {{end}}
    51  	`))
    52  
    53  func compileFailureComment(ctx context.Context, suspect *model.Suspect, reason, templateName string) (string, error) {
    54  	bbid, err := datastoreutil.GetAssociatedBuildID(ctx, suspect)
    55  	if err != nil {
    56  		return "", err
    57  	}
    58  	// TODO(beining@): remove the hardcoded project name to support multiple LUCI projects.
    59  	analysisURL := util.ConstructCompileAnalysisURL("chromium", bbid)
    60  	buildURL := util.ConstructBuildURL(ctx, bbid)
    61  	bugURL := util.ConstructBuganizerURLForAnalysis(analysisURL, suspect.ReviewUrl)
    62  	var b bytes.Buffer
    63  	err = compileCommentTemplate.ExecuteTemplate(&b, templateName, map[string]any{
    64  		"AnalysisURL": analysisURL,
    65  		"BugURL":      bugURL,
    66  		"BuildURL":    buildURL,
    67  		"Reason":      reason,
    68  	})
    69  	if err != nil {
    70  		return "", err
    71  	}
    72  	return b.String(), nil
    73  }
    74  
    75  var testCommentTemplate = template.Must(template.New("").Parse(
    76  	`
    77  {{define "basic"}} See the analysis: {{.AnalysisURL}}
    78  
    79  Sample build with failed test: {{.BuildURL}}
    80  Affected test(s):
    81  {{.TestLinks}}
    82  {{- if gt .numTestLinksHidden 0}}
    83  and {{.numTestLinksHidden}} more ...
    84  {{- end}}
    85  {{- if .Reason}}
    86  A revert for this change was not created because {{.Reason}}.
    87  {{- end}}
    88  
    89  If this is a false positive, please report it at {{.BugURL}}
    90  {{- end}}
    91  
    92  {{ define "supportComment" -}}
    93  LUCI Bisection recommends submitting this revert because it has confirmed the target of this revert is the cause of a test failure.
    94  {{- template "basic" . -}}
    95  {{end}}
    96  
    97  {{ define "blameComment" -}}
    98  LUCI Bisection has identified this change as the cause of a test failure.
    99  {{- template "basic" . -}}
   100  {{end}}
   101  	`))
   102  
   103  const maxTestLink = 5
   104  
   105  func testFailureComment(ctx context.Context, suspect *model.Suspect, reason, templateName string) (string, error) {
   106  	tfs, err := datastoreutil.FetchTestFailuresForSuspect(ctx, suspect)
   107  	if err != nil {
   108  		return "", errors.Annotate(err, "fetch test failure for suspect").Err()
   109  	}
   110  	bbid, err := datastoreutil.GetAssociatedBuildID(ctx, suspect)
   111  	if err != nil {
   112  		return "", err
   113  	}
   114  	buildURL := util.ConstructBuildURL(ctx, bbid)
   115  	testFailures := tfs.NonDiverged()
   116  	sortTestFailures(testFailures)
   117  	testLinks := []string{}
   118  	for _, tf := range testFailures[:min(maxTestLink, len(testFailures))] {
   119  		testLinks = append(testLinks, fmt.Sprintf("[%s](%s)", tf.TestID, util.ConstructTestHistoryURL(tf.Project, tf.TestID, tf.VariantHash)))
   120  	}
   121  	analysisID := tfs.Primary().AnalysisKey.IntID()
   122  	// TODO(beining@): remove the hardcoded project name to support multiple LUCI projects.
   123  	analysisURL := util.ConstructTestAnalysisURL("chromium", analysisID)
   124  	bugURL := util.ConstructBuganizerURLForAnalysis(suspect.ReviewUrl, analysisURL)
   125  	var b bytes.Buffer
   126  	err = testCommentTemplate.ExecuteTemplate(&b, templateName, map[string]any{
   127  		"AnalysisURL":        analysisURL,
   128  		"TestLinks":          strings.Join(testLinks, "\n"),
   129  		"numTestLinksHidden": len(testFailures) - maxTestLink,
   130  		"BugURL":             bugURL,
   131  		"BuildURL":           buildURL,
   132  		"Reason":             reason,
   133  	})
   134  	if err != nil {
   135  		return "", err
   136  	}
   137  	return b.String(), nil
   138  }
   139  
   140  func sortTestFailures(tfs []*model.TestFailure) {
   141  	sort.Slice(tfs, func(i, j int) bool {
   142  		if tfs[i].TestID == tfs[j].TestID {
   143  			return tfs[i].VariantHash < tfs[j].VariantHash
   144  		}
   145  		return tfs[i].TestID < tfs[j].TestID
   146  	})
   147  }