blob: 67727708dc42d0f96ffd70bada589db4c6b32ca6 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"fmt"
"reflect"
"testing"
"v.io/jiri"
"v.io/jiri/gerrit"
"v.io/jiri/jiritest"
"v.io/jiri/project"
"v.io/jiri/tool"
"v.io/x/devtools/tooldata"
)
func TestMultiPartCLSet(t *testing.T) {
set := NewMultiPartCLSet()
checkMultiPartCLSet(t, -1, map[int]gerrit.Change{}, set)
// Add a non-multipart cl.
cl := genCL(1000, 1, "relase.go.core")
if err := set.addCL(cl); err == nil {
t.Fatalf("expected addCL(%v) to fail and it did not", cl)
}
checkMultiPartCLSet(t, -1, map[int]gerrit.Change{}, set)
// Add a multi part cl.
cl.MultiPart = &gerrit.MultiPartCLInfo{
Topic: "test",
Index: 1,
Total: 2,
}
if err := set.addCL(cl); err != nil {
t.Fatalf("addCL(%v) failed: %v", cl, err)
}
checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
1: cl,
}, set)
// Test incomplete.
if expected, got := false, set.complete(); expected != got {
t.Fatalf("want %v, got %v", expected, got)
}
// Add another multi part cl with the wrong "Total" number.
cl2 := genMultiPartCL(1050, 2, "release.js.core", "test", 2, 3)
if err := set.addCL(cl2); err == nil {
t.Fatalf("expected addCL(%v) to fail and it did not", cl)
}
checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
1: cl,
}, set)
// Add another multi part cl with duplicated "Index" number.
cl3 := genMultiPartCL(1052, 2, "release.js.core", "Test", 1, 2)
if err := set.addCL(cl3); err == nil {
t.Fatalf("expected addCL(%v) to fail and it did not", cl)
}
checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
1: cl,
}, set)
// Add another multi part cl with the wrong "Topic".
cl4 := genMultiPartCL(1062, 2, "release.js.core", "test123", 1, 2)
if err := set.addCL(cl4); err == nil {
t.Fatalf("expected addCL(%v) to fail and it did not", cl)
}
checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
1: cl,
}, set)
// Add a valid multi part cl.
cl5 := genMultiPartCL(1072, 2, "release.js.core", "test", 2, 2)
if err := set.addCL(cl5); err != nil {
t.Fatalf("addCL(%v) failed: %v", cl, err)
}
checkMultiPartCLSet(t, 2, map[int]gerrit.Change{
1: cl,
2: cl5,
}, set)
// Test complete.
if expected, got := true, set.complete(); expected != got {
t.Fatalf("want %v, got %v", expected, got)
}
// Test cls.
if expected, got := (clList{cl, cl5}), set.cls(); !reflect.DeepEqual(expected, got) {
t.Fatalf("want %v, got %v", expected, got)
}
}
func checkMultiPartCLSet(t *testing.T, expectedTotal int, expectedCLsByPart map[int]gerrit.Change, set *multiPartCLSet) {
if expectedTotal != set.expectedTotal {
t.Fatalf("total: want %v, got %v", expectedTotal, set.expectedTotal)
}
if !reflect.DeepEqual(expectedCLsByPart, set.parts) {
t.Fatalf("clsByPart: want %+v, got %+v", expectedCLsByPart, set.parts)
}
}
func TestNewOpenCLs(t *testing.T) {
jirix, cleanup := jiritest.NewX(t)
defer cleanup()
nonMultiPartCLs := clList{
genCL(1010, 1, "release.go.core"),
genCL(1020, 2, "release.go.tools"),
genCL(1030, 3, "release.js.core"),
genMultiPartCL(1000, 1, "release.js.core", "T1", 1, 2),
genMultiPartCL(1001, 1, "release.go.core", "T1", 2, 2),
genMultiPartCL(1002, 2, "release.go.core", "T2", 2, 2),
genMultiPartCL(1001, 2, "release.go.core", "T1", 2, 2),
}
multiPartCLs := clList{
// Multi part CLs.
// The first two form a complete set for topic T1.
// The third one looks like the second one, but has a different topic.
// The last one has a larger patchset than the second one.
genMultiPartCL(1000, 1, "release.js.core", "T1", 1, 2),
genMultiPartCL(1001, 1, "release.go.core", "T1", 2, 2),
genMultiPartCL(1002, 2, "release.go.core", "T2", 2, 2),
genMultiPartCL(1001, 2, "release.go.core", "T1", 2, 2),
}
type testCase struct {
prevCLsMap clRefMap
curCLs clList
expected []clList
}
testCases := []testCase{
////////////////////////////////
// Tests for non-multipart CLs.
// Both prevCLsMap and curCLs are empty.
testCase{
prevCLsMap: clRefMap{},
curCLs: clList{},
expected: []clList{},
},
// prevCLsMap is empty, curCLs is not.
testCase{
prevCLsMap: clRefMap{},
curCLs: clList{nonMultiPartCLs[0], nonMultiPartCLs[1]},
expected: []clList{clList{nonMultiPartCLs[0]}, clList{nonMultiPartCLs[1]}},
},
// prevCLsMap is not empty, curCLs is.
testCase{
prevCLsMap: clRefMap{nonMultiPartCLs[0].Reference(): nonMultiPartCLs[0]},
curCLs: clList{},
expected: []clList{},
},
// prevCLsMap and curCLs are not empty, and they have overlapping refs.
testCase{
prevCLsMap: clRefMap{
nonMultiPartCLs[0].Reference(): nonMultiPartCLs[0],
nonMultiPartCLs[1].Reference(): nonMultiPartCLs[1],
},
curCLs: clList{nonMultiPartCLs[1], nonMultiPartCLs[2]},
expected: []clList{clList{nonMultiPartCLs[2]}},
},
// prevCLsMap and curCLs are not empty, and they have NO overlapping refs.
testCase{
prevCLsMap: clRefMap{nonMultiPartCLs[0].Reference(): nonMultiPartCLs[0]},
curCLs: clList{nonMultiPartCLs[1]},
expected: []clList{clList{nonMultiPartCLs[1]}},
},
////////////////////////////////
// Tests for multi part CLs.
// len(curCLs) > len(prevCLsMap).
// And the CLs in curCLs have different topics.
testCase{
prevCLsMap: clRefMap{multiPartCLs[0].Reference(): multiPartCLs[0]},
curCLs: clList{multiPartCLs[0], multiPartCLs[2]},
expected: []clList{},
},
// len(curCLs) > len(prevCLsMap).
// And the CLs in curCLs form a complete multi part cls set.
testCase{
prevCLsMap: clRefMap{multiPartCLs[0].Reference(): multiPartCLs[0]},
curCLs: clList{multiPartCLs[0], multiPartCLs[1]},
expected: []clList{clList{multiPartCLs[0], multiPartCLs[1]}},
},
// len(curCLs) == len(prevCLsMap).
// And cl[6] has a larger patchset than multiPartCLs[4] with identical cl number.
testCase{
prevCLsMap: clRefMap{
multiPartCLs[0].Reference(): multiPartCLs[0],
multiPartCLs[1].Reference(): multiPartCLs[1],
},
curCLs: clList{multiPartCLs[0], multiPartCLs[3]},
expected: []clList{clList{multiPartCLs[0], multiPartCLs[3]}},
},
////////////////////////////////
// Tests for mixed.
testCase{
prevCLsMap: clRefMap{
multiPartCLs[0].Reference(): multiPartCLs[0],
multiPartCLs[1].Reference(): multiPartCLs[1],
},
curCLs: clList{nonMultiPartCLs[0], multiPartCLs[0], multiPartCLs[3]},
expected: []clList{
clList{nonMultiPartCLs[0]},
clList{multiPartCLs[0], multiPartCLs[3]},
},
},
}
for index, test := range testCases {
got := newOpenCLs(jirix, test.prevCLsMap, test.curCLs)
if !reflect.DeepEqual(test.expected, got) {
t.Fatalf("case %d: want: %v, got: %v", index, test.expected, got)
}
}
}
func TestSendCLListsToPresubmitTest(t *testing.T) {
fake, cleanup := jiritest.NewFakeJiriRoot(t)
defer cleanup()
// Create a fake configuration file.
config := tooldata.NewConfig(
tooldata.ProjectTestsOpt(map[string][]string{
"release.go.core": []string{"go", "javascript"},
}),
tooldata.ProjectTestsOpt(map[string][]string{
"release.js.core": []string{"javascript"},
}),
)
if err := tooldata.SaveConfig(fake.X, config); err != nil {
t.Fatalf("%v", err)
}
clLists := []clList{
clList{
genCL(1000, 1, "release.js.core"),
},
clList{
genCLWithMoreData(2000, 1, "release.js.core", gerrit.PresubmitTestTypeNone, "vj@google.com"),
},
clList{
genCLWithMoreData(2010, 1, "release.js.core", gerrit.PresubmitTestTypeAll, "foo@bar.com"),
},
clList{
genMultiPartCL(1001, 1, "release.js.core", "t", 1, 2),
genMultiPartCL(1002, 1, "release.go.core", "t", 2, 2),
},
clList{
genMultiPartCL(1003, 1, "release.js.core", "t", 1, 3),
genMultiPartCL(1004, 1, "release.go.core", "t", 2, 3),
genMultiPartCLWithMoreData(1005, 1, "release.go.core", "t", 3, 3, "foo@bar.com"),
},
clList{
genCL(3000, 1, "non-existent-project"),
},
clList{
genMultiPartCL(1005, 1, "release.js.core", "t", 1, 2),
genMultiPartCL(1006, 1, "non-existent-project", "t", 2, 2),
},
}
sender := clsSender{
clLists: clLists,
projects: project.Projects{
project.ProjectKey("release.go.core"): project.Project{
Name: "release.go.core",
},
project.ProjectKey("release.js.core"): project.Project{
Name: "release.js.core",
},
},
// Mock out the removeOutdatedBuilds function.
removeOutdatedFn: func(jirix *jiri.X, cls clNumberToPatchsetMap) []error { return nil },
// Mock out the addPresubmitTestBuild function.
// It will return error for the first clList.
addPresubmitFn: func(jirix *jiri.X, cls clList, tests []string) error {
if reflect.DeepEqual(cls, clLists[0]) {
return fmt.Errorf("err")
} else {
return nil
}
},
// Mock out postMessage function.
postMessageFn: func(jirix *jiri.X, message string, refs []string, success bool) error { return nil },
}
var buf bytes.Buffer
f := false
fake.X.Context = tool.NewContext(tool.ContextOpts{
Stdout: &buf,
Stderr: &buf,
Verbose: &f,
})
if err := sender.sendCLListsToPresubmitTest(fake.X); err != nil {
t.Fatalf("want no error, got: %v", err)
}
// Check output and return value.
want := `[VANADIUM PRESUBMIT] FAIL: Add http://go/vcl/1000/1
[VANADIUM PRESUBMIT] addPresubmitTestBuild failed: err
[VANADIUM PRESUBMIT] SKIP: Add http://go/vcl/2000/1 (presubmit=none)
[VANADIUM PRESUBMIT] SKIP: Add http://go/vcl/2010/1 (non-google owner)
[VANADIUM PRESUBMIT] PASS: Add http://go/vcl/1001/1, http://go/vcl/1002/1
[VANADIUM PRESUBMIT] SKIP: Add http://go/vcl/1003/1, http://go/vcl/1004/1, http://go/vcl/1005/1 (non-google owner)
[VANADIUM PRESUBMIT] project="non-existent-project" (refs/changes/xx/3000/1) not found. Skipped.
[VANADIUM PRESUBMIT] SKIP: Empty CL set
[VANADIUM PRESUBMIT] project="non-existent-project" (refs/changes/xx/1006/1) not found. Skipped.
[VANADIUM PRESUBMIT] PASS: Add http://go/vcl/1005/1
`
if got := buf.String(); want != got {
t.Fatalf("GOT:\n%v\nWANT:\n%v", got, want)
}
if got, want := sender.clsSent, 3; got != want {
t.Fatalf("numSentCLs: got %d, want %d", got, want)
}
}
func TestGetTestsToRun(t *testing.T) {
fake, cleanup := jiritest.NewFakeJiriRoot(t)
defer cleanup()
// Create a fake configuration file.
config := tooldata.NewConfig(
tooldata.ProjectTestsOpt(map[string][]string{
"release.go.core": []string{"go", "javascript"},
}),
tooldata.TestGroupsOpt(map[string][]string{
"go": []string{"vanadium-go-build", "vanadium-go-test", "vanadium-go-race"},
}),
tooldata.TestPartsOpt(map[string][]string{
"vanadium-go-race": []string{"v.io/x/ref/services/device/...", "v.io/x/ref/runtime/..."},
}),
)
if err := tooldata.SaveConfig(fake.X, config); err != nil {
t.Fatalf("%v", err)
}
expected := []string{
"javascript",
"vanadium-go-build",
"vanadium-go-race-part0",
"vanadium-go-race-part1",
"vanadium-go-race-part2",
"vanadium-go-test",
}
sender := clsSender{}
got, err := sender.getTestsToRun(fake.X, []string{"release.go.core"})
if err != nil {
t.Fatalf("want no errors, got: %v", err)
}
if !reflect.DeepEqual(got, expected) {
t.Fatalf("want %v, got %v", expected, got)
}
}
func TestIsBuildOutdated(t *testing.T) {
type testCase struct {
refs string
cls clNumberToPatchsetMap
outdated bool
}
testCases := []testCase{
// Builds with a single ref.
testCase{
refs: "refs/changes/10/1000/2",
cls: clNumberToPatchsetMap{1000: 2},
outdated: true,
},
testCase{
refs: "refs/changes/10/1000/2",
cls: clNumberToPatchsetMap{1000: 1},
outdated: false,
},
// Builds with multiple refs.
//
// Overlapping cls.
testCase{
refs: "refs/changes/10/1001/2",
cls: clNumberToPatchsetMap{1001: 3, 2000: 2},
outdated: true,
},
// The other case with overlapping cl.
testCase{
refs: "refs/changes/10/1000/2:refs/changes/10/2000/2",
cls: clNumberToPatchsetMap{1001: 2, 2000: 2},
outdated: true,
},
// Both refs don't match.
testCase{
refs: "refs/changes/10/1000/2:refs/changes/10/2000/2",
cls: clNumberToPatchsetMap{1001: 2, 2000: 2},
outdated: true,
},
// Both patchsets in "cls" are smaller.
testCase{
refs: "refs/changes/10/1000/2:refs/changes/10/2000/2",
cls: clNumberToPatchsetMap{1000: 1, 2000: 1},
outdated: false,
},
// One of the patchsets in "cls" is larger than the one in "refs".
testCase{
refs: "refs/changes/10/1000/2:refs/changes/10/2000/2",
cls: clNumberToPatchsetMap{1000: 3, 2000: 2},
outdated: true,
},
// Both patchsets in "cls" are the same as the ones in "refs".
testCase{
refs: "refs/changes/10/1000/2:refs/changes/10/2000/2",
cls: clNumberToPatchsetMap{1000: 2, 2000: 2},
outdated: true,
},
}
for i, test := range testCases {
outdated, err := isBuildOutdated(test.refs, test.cls)
if err != nil {
t.Fatalf("want no errors, got: %v", err)
}
if expected, got := test.outdated, outdated; expected != got {
t.Fatalf("%d: want %v, got %v", i, expected, got)
}
}
}
func TestParseRefString(t *testing.T) {
type testCase struct {
ref string
expectErr bool
expectedCL int
expectedPatchSet int
}
testCases := []testCase{
// Normal case
testCase{
ref: "ref/changes/12/3412/2",
expectedCL: 3412,
expectedPatchSet: 2,
},
// Error cases
testCase{
ref: "ref/123",
expectErr: true,
},
testCase{
ref: "ref/changes/12/a/2",
expectErr: true,
},
testCase{
ref: "ref/changes/12/3412/a",
expectErr: true,
},
}
for _, test := range testCases {
cl, patchset, err := parseRefString(test.ref)
if test.expectErr {
if err == nil {
t.Fatalf("want errors, got: %v", err)
}
} else {
if err != nil {
t.Fatalf("want no errors, got: %v", err)
}
if cl != test.expectedCL {
t.Fatalf("want %v, got %v", test.expectedCL, cl)
}
if patchset != test.expectedPatchSet {
t.Fatalf("want %v, got %v", test.expectedPatchSet, patchset)
}
}
}
}
func genCL(clNumber, patchset int, project string) gerrit.Change {
return genCLWithMoreData(clNumber, patchset, project, gerrit.PresubmitTestTypeAll, "vj@google.com")
}
func genCLWithMoreData(clNumber, patchset int, project string, presubmit gerrit.PresubmitTestType, ownerEmail string) gerrit.Change {
change := gerrit.Change{
Current_revision: "r",
Revisions: gerrit.Revisions{
"r": gerrit.Revision{
Fetch: gerrit.Fetch{
Http: gerrit.Http{
Ref: fmt.Sprintf("refs/changes/xx/%d/%d", clNumber, patchset),
},
},
},
},
Project: project,
Change_id: "",
PresubmitTest: presubmit,
Owner: gerrit.Owner{
Email: ownerEmail,
},
}
return change
}
func genMultiPartCL(clNumber, patchset int, project, topic string, index, total int) gerrit.Change {
return genMultiPartCLWithMoreData(clNumber, patchset, project, topic, index, total, "vj@google.com")
}
func genMultiPartCLWithMoreData(clNumber, patchset int, project, topic string, index, total int, ownerEmail string) gerrit.Change {
return gerrit.Change{
Current_revision: "r",
Revisions: gerrit.Revisions{
"r": gerrit.Revision{
Fetch: gerrit.Fetch{
Http: gerrit.Http{
Ref: fmt.Sprintf("refs/changes/xx/%d/%d", clNumber, patchset),
},
},
},
},
Project: project,
Change_id: "",
Owner: gerrit.Owner{
Email: ownerEmail,
},
MultiPart: &gerrit.MultiPartCLInfo{
Topic: topic,
Index: index,
Total: total,
},
}
}