[autocorrelator] Filter out try builds with matching change num

Per go/ci-cq-auto-correlator, ignore try builds which originate from
the same change as -change-num.

In practice, this means that if a tryjob fails on fxrev.dev/123456, and
previously failed on the same change before with the same failure
message, that failure should not increase the confidence of a false
negative.

Bug: 67694
Change-Id: I118f15ef6feb8ff4a0e533037ff55327337c9d02
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/infra/+/471665
Fuchsia-Auto-Submit: Anthony Fandrianto <atyfto@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Reviewed-by: Anirudh Mathukumilli <rudymathu@google.com>
diff --git a/cmd/autocorrelator/check_try.go b/cmd/autocorrelator/check_try.go
index 6f39f20..705632b 100644
--- a/cmd/autocorrelator/check_try.go
+++ b/cmd/autocorrelator/check_try.go
@@ -27,7 +27,7 @@
 
 func cmdCheckTry(authOpts auth.Options) *subcommands.Command {
 	return &subcommands.Command{
-		UsageLine: "check-try -builder <project/bucket/builder> -build-status <build-status-code> -search-range <search-range> -summary-markdown-path <path/to/summary/markdown> -json-output <json-output>",
+		UsageLine: "check-try -builder <project/bucket/builder> -change-num <change-num> -build-status <build-status-code> -search-range <search-range> -summary-markdown-path <path/to/summary/markdown> -json-output <json-output>",
 		ShortDesc: "Compare text similarity between a summary markdown and recent try builds' summary markdowns.",
 		LongDesc:  "Compare text similarity between the provided -summary-markdown and the summary markdown of -search-range recent builds of a try -builder.",
 		CommandRun: func() subcommands.CommandRun {
@@ -43,6 +43,7 @@
 	// TODO(atyfto): Fix the buildbucket library to return a BuilderID instead
 	// of a flag.
 	builder             buildbucket.BuilderIDFlag
+	changeNum           int64
 	buildStatus         int
 	searchRange         int
 	summaryMarkdownPath string
@@ -56,6 +57,7 @@
 	// otherwise we can falsely imply systemic failure when a single CL is
 	// responsible for many failures.
 	c.Flags.Var(&c.builder, "builder", "Fully-qualified Buildbucket try builder name to inspect.")
+	c.Flags.Int64Var(&c.changeNum, "change-num", 0, "Change number to filter out.")
 	c.Flags.IntVar(&c.buildStatus, "build-status", 20, "Build status to filter on. Defaults to FAILURE.")
 	c.Flags.IntVar(&c.searchRange, "search-range", 10, "Inspect up to this many recent builds.")
 	c.Flags.StringVar(&c.summaryMarkdownPath, "summary-markdown-path", "", "Path to summary markdown input file.")
@@ -69,6 +71,9 @@
 	if &c.builder == nil {
 		return errors.New("-builder is required")
 	}
+	if c.changeNum == 0 {
+		return errors.New("-change-num is required")
+	}
 	if c.summaryMarkdownPath == "" {
 		return errors.New("-summary-markdown-path is required")
 	}
@@ -89,9 +94,13 @@
 // The `comparator` argument is a text-based comparator that returns a
 // similarity score between the input summary markdown and a build's summary
 // markdown.
-func checkTry(builds []*buildbucketpb.Build, status buildbucketpb.Status, summaryMarkdown string, comparator compare.TextComparator) []*findings.SummarySimilarity {
+func checkTry(builds []*buildbucketpb.Build, changeNum int64, status buildbucketpb.Status, summaryMarkdown string, comparator compare.TextComparator) []*findings.SummarySimilarity {
 	var fs []*findings.SummarySimilarity
 	for _, build := range builds {
+		// If we find a build with an identical change number, skip it.
+		if changeNum == build.Input.GerritChanges[0].Change {
+			continue
+		}
 		// If we encounter a green build, add the finding: the existence of a
 		// green build should reduce the confidence we have in "try" being
 		// systemically busted.
@@ -138,6 +147,7 @@
 		},
 		Fields: &field_mask.FieldMask{Paths: []string{
 			"builds.*.id",
+			"builds.*.input.gerrit_changes",
 			"builds.*.status",
 			"builds.*.summary_markdown",
 		}},
@@ -156,7 +166,7 @@
 	if err != nil {
 		return fmt.Errorf("could not read summary markdown input: %v", err)
 	}
-	fs := checkTry(builds, buildbucketpb.Status(c.buildStatus), string(summaryMarkdown), compare.LevenshteinComparator{Opts: levenshtein.DefaultOptions})
+	fs := checkTry(builds, c.changeNum, buildbucketpb.Status(c.buildStatus), string(summaryMarkdown), compare.LevenshteinComparator{Opts: levenshtein.DefaultOptions})
 
 	// Emit summary similarity findings to -json-output.
 	out := os.Stdout
diff --git a/cmd/autocorrelator/check_try_test.go b/cmd/autocorrelator/check_try_test.go
index 2a1ed0d..9a003e2 100644
--- a/cmd/autocorrelator/check_try_test.go
+++ b/cmd/autocorrelator/check_try_test.go
@@ -18,6 +18,7 @@
 
 	var tests = []struct {
 		builds          []*buildbucketpb.Build
+		changeNum       int64
 		status          buildbucketpb.Status
 		summaryMarkdown string
 		expected        []*findings.SummarySimilarity
@@ -28,11 +29,38 @@
 					Id:              int64(999999),
 					Status:          buildbucketpb.Status_SUCCESS,
 					SummaryMarkdown: "",
+					Input: &buildbucketpb.Build_Input{
+						GerritChanges: []*buildbucketpb.GerritChange{
+							{
+								Change: int64(111111),
+							},
+						},
+					},
 				},
 				{
 					Id:              int64(999998),
 					Status:          buildbucketpb.Status_FAILURE,
 					SummaryMarkdown: "ERROR",
+					Input: &buildbucketpb.Build_Input{
+						GerritChanges: []*buildbucketpb.GerritChange{
+							{
+								Change: int64(222222),
+							},
+						},
+					},
+				},
+				// This matches the input change number, and should be ignored.
+				{
+					Id:              int64(999998),
+					Status:          buildbucketpb.Status_FAILURE,
+					SummaryMarkdown: "ERROR",
+					Input: &buildbucketpb.Build_Input{
+						GerritChanges: []*buildbucketpb.GerritChange{
+							{
+								Change: int64(123456),
+							},
+						},
+					},
 				},
 				// This does not match the input status of FAILURE, and should
 				// be ignored.
@@ -40,9 +68,17 @@
 					Id:              int64(999997),
 					Status:          buildbucketpb.Status_INFRA_FAILURE,
 					SummaryMarkdown: "ERROR: checkout failed",
+					Input: &buildbucketpb.Build_Input{
+						GerritChanges: []*buildbucketpb.GerritChange{
+							{
+								Change: int64(333333),
+							},
+						},
+					},
 				},
 			},
 			status:          buildbucketpb.Status_FAILURE,
+			changeNum:       int64(123456),
 			summaryMarkdown: "ERROR",
 			expected: []*findings.SummarySimilarity{
 				{
@@ -61,7 +97,7 @@
 
 	for _, test := range tests {
 		comparator := mockComparator{}
-		ss := checkTry(test.builds, test.status, test.summaryMarkdown, comparator)
+		ss := checkTry(test.builds, test.changeNum, test.status, test.summaryMarkdown, comparator)
 		if diff := cmp.Diff(test.expected, ss); diff != "" {
 			t.Fatalf("different (-want +got):\n%s", diff)
 		}