go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/compilefailureanalysis/heuristic/changelog_analyzer_test.go (about)

     1  // Copyright 2022 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 heuristic
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	. "github.com/smartystreets/goconvey/convey"
    22  	"go.chromium.org/luci/bisection/model"
    23  )
    24  
    25  func TestChangeLogAnalyzer(t *testing.T) {
    26  	t.Parallel()
    27  
    28  	Convey("AreRelelatedExtensions", t, func() {
    29  		So(AreRelelatedExtensions("c", "cpp"), ShouldBeTrue)
    30  		So(AreRelelatedExtensions("py", "pyc"), ShouldBeTrue)
    31  		So(AreRelelatedExtensions("gyp", "gypi"), ShouldBeTrue)
    32  		So(AreRelelatedExtensions("c", "py"), ShouldBeFalse)
    33  		So(AreRelelatedExtensions("abc", "xyz"), ShouldBeFalse)
    34  	})
    35  
    36  	Convey("NormalizeObjectFilePath", t, func() {
    37  		data := map[string]string{
    38  			"obj/a/T.x.o":   "a/x.o",
    39  			"obj/a/T.x.y.o": "a/x.y.o",
    40  			"x.o":           "x.o",
    41  			"obj/a/x.obj":   "a/x.obj",
    42  			"a.cc.obj":      "a.cc.obj",
    43  			"T.a.c.o":       "a.c.o",
    44  			"T.a.o":         "a.o",
    45  			"T.a.b.c":       "T.a.b.c",
    46  		}
    47  		for k, v := range data {
    48  			So(NormalizeObjectFilePath(k), ShouldEqual, v)
    49  		}
    50  	})
    51  
    52  	Convey("AnalyzeOneChangeLog", t, func() {
    53  		c := context.Background()
    54  		signal := &model.CompileFailureSignal{
    55  			Files: map[string][]int{
    56  				"src/a/b/x.cc":       {27},
    57  				"obj/content/util.o": {},
    58  			},
    59  			Edges: []*model.CompileFailureEdge{
    60  				{
    61  					Dependencies: []string{
    62  						"x/y/aa_impl_mac.cc",
    63  						"y/z/bb_impl.cc",
    64  					},
    65  				},
    66  			},
    67  		}
    68  		signal.CalculateDependencyMap(c)
    69  		Convey("Changelog from a non-blamable email", func() {
    70  			cl := &model.ChangeLog{
    71  				Author: model.ChangeLogActor{
    72  					Email: "chrome-release-bot@chromium.org",
    73  				},
    74  			}
    75  
    76  			justification, err := AnalyzeOneChangeLog(c, signal, cl)
    77  			So(err, ShouldBeNil)
    78  			So(justification, ShouldResemble, &model.SuspectJustification{IsNonBlamable: true})
    79  		})
    80  
    81  		Convey("Changelog did not touch any file", func() {
    82  			cl := &model.ChangeLog{
    83  				ChangeLogDiffs: []model.ChangeLogDiff{
    84  					{
    85  						Type:    model.ChangeType_ADD,
    86  						NewPath: "some_file.cc",
    87  					},
    88  				},
    89  			}
    90  			justification, err := AnalyzeOneChangeLog(c, signal, cl)
    91  			So(err, ShouldBeNil)
    92  			So(justification, ShouldResemble, &model.SuspectJustification{})
    93  		})
    94  
    95  		Convey("Changelog touched relevant files", func() {
    96  			cl := &model.ChangeLog{
    97  				ChangeLogDiffs: []model.ChangeLogDiff{
    98  					{
    99  						Type:    model.ChangeType_MODIFY,
   100  						OldPath: "content/util.c",
   101  						NewPath: "content/util.c",
   102  					},
   103  					{
   104  						Type:    model.ChangeType_ADD,
   105  						NewPath: "dir/a/b/x.cc",
   106  					},
   107  					{
   108  						Type:    model.ChangeType_RENAME,
   109  						OldPath: "unrelated_file_1.cc",
   110  						NewPath: "unrelated_file_2.cc",
   111  					},
   112  					{
   113  						Type:    model.ChangeType_DELETE,
   114  						OldPath: "x/y/aa.h",
   115  					},
   116  					{
   117  						Type:    model.ChangeType_MODIFY,
   118  						OldPath: "y/z/bb.cc",
   119  						NewPath: "y/z/bb.cc",
   120  					},
   121  				},
   122  			}
   123  			justification, err := AnalyzeOneChangeLog(c, signal, cl)
   124  			So(err, ShouldBeNil)
   125  			So(justification, ShouldResemble, &model.SuspectJustification{
   126  				Items: []*model.SuspectJustificationItem{
   127  					{
   128  						Score:    10,
   129  						FilePath: "dir/a/b/x.cc",
   130  						Reason:   `The file "dir/a/b/x.cc" was added and it was in the failure log.`,
   131  						Type:     model.JustificationType_FAILURELOG,
   132  					},
   133  					{
   134  						Score:    2,
   135  						FilePath: "content/util.c",
   136  						Reason:   "The file \"content/util.c\" was modified. It was related to the file obj/content/util.o which was in the failure log.",
   137  						Type:     model.JustificationType_FAILURELOG,
   138  					},
   139  					{
   140  						Score:    1,
   141  						FilePath: "x/y/aa.h",
   142  						Reason:   "The file \"x/y/aa.h\" was deleted. It was related to the dependency x/y/aa_impl_mac.cc.",
   143  						Type:     model.JustificationType_DEPENDENCY,
   144  					},
   145  					{
   146  						Score:    1,
   147  						FilePath: "y/z/bb.cc",
   148  						Reason:   "The file \"y/z/bb.cc\" was modified. It was related to the dependency y/z/bb_impl.cc.",
   149  						Type:     model.JustificationType_DEPENDENCY,
   150  					},
   151  				},
   152  			})
   153  		})
   154  	})
   155  
   156  	Convey("AnalyzeChangeLogs", t, func() {
   157  		c := context.Background()
   158  		signal := &model.CompileFailureSignal{
   159  			Files: map[string][]int{
   160  				"src/a/b/x.cc":       {27},
   161  				"obj/content/util.o": {},
   162  			},
   163  		}
   164  
   165  		Convey("Results should be sorted", func() {
   166  			cls := []*model.ChangeLog{
   167  				{
   168  					Commit:  "abcd",
   169  					Message: "First blah blah\nReviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/123\n bla",
   170  					ChangeLogDiffs: []model.ChangeLogDiff{
   171  						{
   172  							Type:    model.ChangeType_MODIFY,
   173  							NewPath: "content/util.c",
   174  						},
   175  					},
   176  				},
   177  				{
   178  					Commit:  "efgh",
   179  					Message: "Second blah blah\nReviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/456\n bla",
   180  					ChangeLogDiffs: []model.ChangeLogDiff{
   181  						{
   182  							Type:    model.ChangeType_RENAME,
   183  							OldPath: "unrelated_file_1.cc",
   184  							NewPath: "unrelated_file_2.cc",
   185  						},
   186  					},
   187  				},
   188  				{
   189  					Commit:  "wxyz",
   190  					Message: "Third blah blah\nReviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/789\n bla",
   191  					ChangeLogDiffs: []model.ChangeLogDiff{
   192  						{
   193  							Type:    model.ChangeType_ADD,
   194  							NewPath: "dir/a/b/x.cc",
   195  						},
   196  					},
   197  				},
   198  			}
   199  
   200  			analysisResult, err := AnalyzeChangeLogs(c, signal, cls)
   201  			So(err, ShouldBeNil)
   202  			So(analysisResult, ShouldResemble, &model.HeuristicAnalysisResult{
   203  				Items: []*model.HeuristicAnalysisResultItem{
   204  					{
   205  						Commit:      "wxyz",
   206  						ReviewUrl:   "https://chromium-review.googlesource.com/c/chromium/src/+/789",
   207  						ReviewTitle: "Third blah blah",
   208  						Justification: &model.SuspectJustification{
   209  							Items: []*model.SuspectJustificationItem{
   210  								{
   211  									Score:    10,
   212  									FilePath: "dir/a/b/x.cc",
   213  									Reason:   `The file "dir/a/b/x.cc" was added and it was in the failure log.`,
   214  									Type:     model.JustificationType_FAILURELOG,
   215  								},
   216  							},
   217  						},
   218  					},
   219  					{
   220  						Commit:      "abcd",
   221  						ReviewUrl:   "https://chromium-review.googlesource.com/c/chromium/src/+/123",
   222  						ReviewTitle: "First blah blah",
   223  						Justification: &model.SuspectJustification{
   224  							Items: []*model.SuspectJustificationItem{
   225  								{
   226  									Score:    2,
   227  									FilePath: "content/util.c",
   228  									Reason:   "The file \"content/util.c\" was modified. It was related to the file obj/content/util.o which was in the failure log.",
   229  									Type:     model.JustificationType_FAILURELOG,
   230  								},
   231  							},
   232  						},
   233  					},
   234  				},
   235  			})
   236  		})
   237  	})
   238  
   239  }