// Copyright 2024 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package triage import ( "fmt" "testing" "time" "github.com/google/syzkaller/pkg/debugtracer" "github.com/google/syzkaller/pkg/vcs" "github.com/google/syzkaller/syz-cluster/pkg/api" "github.com/stretchr/testify/assert" ) func TestCommitSelector(t *testing.T) { allApply := map[string]bool{"head": true, "build": true} tests := []struct { name string ops TreeOps series *api.Series last *api.Build result SelectResult }{ { name: "fresh series, no last build", series: &api.Series{PublishedAt: date("2020-Jan-15")}, ops: newTestGitOps(&vcs.Commit{Hash: "head", CommitDate: date("2020-Jan-10")}, allApply), result: SelectResult{Commit: "head"}, }, { name: "fresh series with a fresh last build", series: &api.Series{PublishedAt: date("2020-Jan-15")}, ops: newTestGitOps(&vcs.Commit{Hash: "head", CommitDate: date("2020-Jan-10")}, allApply), last: &api.Build{CommitHash: "build", CommitDate: date("2020-Jan-06")}, result: SelectResult{Commit: "build"}, }, { name: "fresh series with a too old last build", series: &api.Series{PublishedAt: date("2020-Jan-15")}, ops: newTestGitOps(&vcs.Commit{Hash: "head", CommitDate: date("2020-Jan-10")}, allApply), last: &api.Build{CommitHash: "build", CommitDate: date("2019-Dec-20")}, result: SelectResult{Commit: "head"}, }, { name: "slightly old series, no last build", series: &api.Series{PublishedAt: date("2020-Jan-15")}, ops: newTestGitOps(&vcs.Commit{Hash: "head", CommitDate: date("2020-Jan-20")}, allApply), result: SelectResult{Commit: "head"}, }, { name: "a too old series", series: &api.Series{PublishedAt: date("2020-Jan-15")}, ops: newTestGitOps(&vcs.Commit{Hash: "head", CommitDate: date("2020-Feb-15")}, allApply), result: SelectResult{Reason: reasonSeriesTooOld}, }, { name: "doesn't apply to the known build", series: &api.Series{PublishedAt: date("2020-Jan-15")}, ops: newTestGitOps( &vcs.Commit{Hash: "head", CommitDate: date("2020-Jan-13")}, map[string]bool{"head": true, "build": false}, ), last: &api.Build{CommitHash: "build", CommitDate: date("2020-Jan-10")}, result: SelectResult{Commit: "head"}, }, { name: "doesn't apply anywhere", series: &api.Series{PublishedAt: date("2020-Jan-15")}, ops: newTestGitOps( &vcs.Commit{Hash: "head", CommitDate: date("2020-Jan-13")}, nil, ), last: &api.Build{CommitHash: "build", CommitDate: date("2020-Jan-10")}, result: SelectResult{Reason: reasonNotApplies}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { selector := NewCommitSelector(test.ops, &debugtracer.NullTracer{}) result, err := selector.Select(test.series, testTree, test.last) assert.NoError(t, err) assert.Equal(t, test.result, result) }) } } func TestFromBaseCommits(t *testing.T) { trees := []*api.Tree{ { Name: "A", Branch: "master", EmailLists: []string{"list_A"}, }, { Name: "B", Branch: "master", EmailLists: []string{"list_B"}, }, { Name: "C", Branch: "main", EmailLists: []string{"list_C"}, }, { Name: "D", Branch: "main", EmailLists: nil, }, } commits := []*vcs.BaseCommit{ { Commit: &vcs.Commit{Hash: "first"}, Branches: []string{"C/main"}, }, { Commit: &vcs.Commit{Hash: "second"}, Branches: []string{"A/other", "B/other"}, }, { Commit: &vcs.Commit{Hash: "third"}, Branches: []string{"A/master", "A/other"}, }, } t.Run("best branch", func(t *testing.T) { tree, commit := FromBaseCommits(&api.Series{ Cc: []string{"list_A"}, }, commits, trees) assert.Equal(t, "A", tree.Name) assert.Equal(t, "third", commit) }) t.Run("best tree", func(t *testing.T) { // Even though C/main matches perfectly, there's a commit // in the higher prio tree B. tree, commit := FromBaseCommits(&api.Series{ Cc: []string{"list_B", "list_C"}, }, commits, trees) assert.Equal(t, "B", tree.Name) assert.Equal(t, "second", commit) }) t.Run("any tree", func(t *testing.T) { // If no trees matching by Cc'd list are in the base commit list, // consider all trees. commits := []*vcs.BaseCommit{ { Commit: &vcs.Commit{Hash: "first"}, Branches: []string{"B/main"}, }, { Commit: &vcs.Commit{Hash: "second"}, Branches: []string{"C/main"}, }, } tree, commit := FromBaseCommits(&api.Series{ Cc: []string{"list_A"}, }, commits, trees) assert.Equal(t, "B", tree.Name) assert.Equal(t, "first", commit) }) } func date(date string) time.Time { t, err := time.Parse("2006-Jan-02", date) if err != nil { panic(err) } return t } var testTree = &api.Tree{} // all tests will use the same tree type testGitOps struct { applies map[string]bool head map[*api.Tree]*vcs.Commit } func newTestGitOps(head *vcs.Commit, applies map[string]bool) *testGitOps { return &testGitOps{ applies: applies, head: map[*api.Tree]*vcs.Commit{ testTree: head, }, } } func (ops *testGitOps) HeadCommit(tree *api.Tree) (*vcs.Commit, error) { return ops.head[tree], nil } func (ops *testGitOps) ApplySeries(commit string, _ [][]byte) error { if ops.applies[commit] { return nil } return fmt.Errorf("didn't apply") }