blob: 61f765c2aefba16ac9ddb54862dc95b41cb7d7d7 [file] [log] [blame]
// Copyright 2020 The Go 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 task
import (
"context"
"fmt"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-github/v48/github"
"golang.org/x/build/gerrit"
"golang.org/x/build/internal/workflow"
)
func TestInterpretNextRelease(t *testing.T) {
tests := []struct {
name string
tags []string
bump string
want semversion
}{
{
name: "next minor version of v0.0.0 is v0.1.0",
tags: []string{"gopls/v0.0.0"},
bump: "next minor",
want: semversion{Major: 0, Minor: 1, Patch: 0},
},
{
name: "pre-release versions should be ignored",
tags: []string{"gopls/v0.0.0", "gopls/v0.1.0-pre.1", "gopls/v0.1.0-pre.2"},
bump: "next minor",
want: semversion{Major: 0, Minor: 1, Patch: 0},
},
{
name: "next patch version of v0.2.2 is v0.2.3",
tags: []string{"gopls/0.1.1", "gopls/0.2.0", "gopls/0.2.1", "gopls/v0.2.2"},
bump: "next patch",
want: semversion{Major: 0, Minor: 2, Patch: 3},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tools := NewFakeRepo(t, "tools")
commit := tools.Commit(map[string]string{
"go.mod": "module golang.org/x/tools\n",
"go.sum": "\n",
})
for _, tag := range tc.tags {
tools.Tag(tag, commit)
}
gerrit := NewFakeGerrit(t, tools)
tasks := &ReleaseGoplsTasks{
Gerrit: gerrit,
}
got, err := tasks.interpretNextRelease(&workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}}, tc.bump)
if err != nil {
t.Fatalf("interpretNextRelease(%q) should not return error, but return %v", tc.bump, err)
}
if tc.want != got {
t.Errorf("interpretNextRelease(%q) = %v, want %v", tc.bump, tc.want, got)
}
})
}
}
func TestPossibleGoplsVersions(t *testing.T) {
tests := []struct {
name string
tags []string
want []string
}{
{
name: "any one version tag should have three possible next versions",
tags: []string{"gopls/v1.2.3"},
want: []string{"v1.2.4", "v1.3.0", "v2.0.0"},
},
{
name: "1.2.0 should be skipped because 1.2.3 already exist",
tags: []string{"gopls/v1.2.3", "gopls/v1.1.0"},
want: []string{"v1.1.1", "v1.2.4", "v1.3.0", "v2.0.0"},
},
{
name: "2.0.0 should be skipped because 2.1.3 already exist",
tags: []string{"gopls/v1.2.3", "gopls/v2.1.3"},
want: []string{"v1.2.4", "v1.3.0", "v2.1.4", "v2.2.0", "v3.0.0"},
},
{
name: "1.2.0 is still consider valid version because there is no 1.2.X",
tags: []string{"gopls/v1.1.3", "gopls/v1.3.2", "gopls/v2.1.2"},
want: []string{"v1.1.4", "v1.2.0", "v1.3.3", "v1.4.0", "v2.1.3", "v2.2.0", "v3.0.0"},
},
{
name: "2.0.0 is still consider valid version because there is no 2.X.X",
tags: []string{"gopls/v1.2.3", "gopls/v3.1.2"},
want: []string{"v1.2.4", "v1.3.0", "v2.0.0", "v3.1.3", "v3.2.0", "v4.0.0"},
},
{
name: "pre-release version tag should not have any effect on the next version",
tags: []string{"gopls/v0.16.1-pre.1", "gopls/v0.16.1-pre.2", "gopls/v0.16.0"},
want: []string{"v0.16.1", "v0.17.0", "v1.0.0"},
},
{
name: "other unrelated tag should not have any effect on the next version",
tags: []string{"v0.9.2", "v0.9.3", "v0.23.0", "gopls/v0.16.0"},
want: []string{"v0.16.1", "v0.17.0", "v1.0.0"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tools := NewFakeRepo(t, "tools")
commit := tools.Commit(map[string]string{
"go.mod": "module golang.org/x/tools\n",
"go.sum": "\n",
})
for _, tag := range tc.tags {
tools.Tag(tag, commit)
}
gerrit := NewFakeGerrit(t, tools)
tasks := &ReleaseGoplsTasks{
Gerrit: gerrit,
}
got, err := tasks.possibleGoplsVersions(&workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}})
if err != nil {
t.Fatalf("possibleGoplsVersions() should not return error, but return %v", err)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("possibleGoplsVersions() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestCreateBranchIfMinor(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
version string
existingBranch string
wantErr bool
wantBranch string
}{
{
name: "should create a release branch for a minor release",
version: "v1.2.0",
wantErr: false,
wantBranch: "gopls-release-branch.1.2",
},
{
name: "should return nil if the release branch already exist for a minor release",
version: "v1.2.0",
existingBranch: "gopls-release-branch.1.2",
wantErr: false,
},
{
name: "should not create a release branch for a patch release",
version: "v1.2.4",
existingBranch: "gopls-release-branch.1.2",
wantErr: false,
wantBranch: "",
},
{
name: "should throw error for patch release if release branch is missing",
version: "v1.3.1",
wantErr: true,
wantBranch: "",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tools := NewFakeRepo(t, "tools")
_ = tools.Commit(map[string]string{
"go.mod": "module golang.org/x/tools\n",
"go.sum": "\n",
})
_ = tools.Commit(map[string]string{
"README.md": "THIS IS READ ME.",
})
gerritClient := NewFakeGerrit(t, tools)
masterHead, err := gerritClient.ReadBranchHead(ctx, "tools", "master")
if err != nil {
t.Fatalf("ReadBranchHead should be able to get revision of master branch's head: %v", err)
}
if tc.existingBranch != "" {
if _, err := gerritClient.CreateBranch(ctx, "tools", tc.existingBranch, gerrit.BranchInput{Revision: masterHead}); err != nil {
t.Fatalf("failed to create the branch %q: %v", tc.existingBranch, err)
}
}
tasks := &ReleaseGoplsTasks{
Gerrit: gerritClient,
}
semv, _ := parseSemver(tc.version)
err = tasks.createBranchIfMinor(&workflow.TaskContext{Context: ctx, Logger: &testLogger{t, ""}}, semv)
if tc.wantErr && err == nil {
t.Errorf("createBranchIfMinor() should return error but return nil")
} else if !tc.wantErr && err != nil {
t.Errorf("createBranchIfMinor() should return nil but return err: %v", err)
}
// Created branch should have same revision as master branch's head.
if tc.wantBranch != "" {
gotRevision, err := gerritClient.ReadBranchHead(ctx, "tools", tc.wantBranch)
if err != nil {
t.Errorf("ReadBranchHead should be able to get revision of %s branch's head: %v", tc.wantBranch, err)
}
if masterHead != gotRevision {
t.Errorf("createBranchIfMinor() = %q, want %q", gotRevision, masterHead)
}
}
})
}
}
func TestUpdateCodeReviewConfig(t *testing.T) {
ctx := context.Background()
testcases := []struct {
name string
version string
config string
wantCommit bool
wantConfig string
}{
{
name: "should update the codereview.cfg with version 1.2 for input minor release 1.2.0",
version: "v1.2.0",
config: "foo",
wantCommit: true,
wantConfig: `issuerepo: golang/go
branch: gopls-release-branch.1.2
parent-branch: master
`,
},
{
name: "should update the codereview.cfg with version 1.2 for input patch release 1.2.3",
version: "v1.2.3",
config: "foo",
wantCommit: true,
wantConfig: `issuerepo: golang/go
branch: gopls-release-branch.1.2
parent-branch: master
`,
},
{
name: "no need to update the config for a minor release 1.3.0",
version: "v1.3.0",
config: `issuerepo: golang/go
branch: gopls-release-branch.1.3
parent-branch: master
`,
wantCommit: false,
wantConfig: `issuerepo: golang/go
branch: gopls-release-branch.1.3
parent-branch: master
`,
},
{
name: "no need to update the config for a patch release 1.3.3",
version: "v1.3.3",
config: `issuerepo: golang/go
branch: gopls-release-branch.1.3
parent-branch: master
`,
wantCommit: false,
wantConfig: `issuerepo: golang/go
branch: gopls-release-branch.1.3
parent-branch: master
`,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tools := NewFakeRepo(t, "tools")
_ = tools.Commit(map[string]string{
"go.mod": "module golang.org/x/tools\n",
"go.sum": "\n",
})
_ = tools.Commit(map[string]string{
"codereview.cfg": tc.config,
})
gerritClient := NewFakeGerrit(t, tools)
headMaster, err := gerritClient.ReadBranchHead(ctx, "tools", "master")
if err != nil {
t.Fatalf("ReadBranchHead should be able to get revision of master branch's head: %v", err)
}
configMaster, err := gerritClient.ReadFile(ctx, "tools", headMaster, "codereview.cfg")
if err != nil {
t.Fatalf("ReadFile should be able to read the codereview.cfg file from master branch head: %v", err)
}
semv, _ := parseSemver(tc.version)
releaseBranch := goplsReleaseBranchName(semv)
if _, err := gerritClient.CreateBranch(ctx, "tools", releaseBranch, gerrit.BranchInput{Revision: headMaster}); err != nil {
t.Fatalf("failed to create the branch %q: %v", releaseBranch, err)
}
headRelease, err := gerritClient.ReadBranchHead(ctx, "tools", releaseBranch)
if err != nil {
t.Fatalf("ReadBranchHead should be able to get revision of release branch's head: %v", err)
}
tasks := &ReleaseGoplsTasks{
Gerrit: gerritClient,
CloudBuild: NewFakeCloudBuild(t, gerritClient, "", nil, fakeGo),
}
_, err = tasks.updateCodeReviewConfig(&workflow.TaskContext{Context: ctx, Logger: &testLogger{t, ""}}, semv, nil, 0)
if err != nil {
t.Fatalf("updateCodeReviewConfig() returns error: %v", err)
}
// master branch's head commit should not change.
headMasterAfter, err := gerritClient.ReadBranchHead(ctx, "tools", "master")
if err != nil {
t.Fatalf("ReadBranchHead() should be able to get revision of master branch's head: %v", err)
}
if headMasterAfter != headMaster {
t.Errorf("updateCodeReviewConfig() should not change master branch's head, got = %s want = %s", headMasterAfter, headMaster)
}
// master branch's head codereview.cfg content should not change.
configMasterAfter, err := gerritClient.ReadFile(ctx, "tools", headMasterAfter, "codereview.cfg")
if err != nil {
t.Fatalf("ReadFile() should be able to read the codereview.cfg file from master branch head: %v", err)
}
if diff := cmp.Diff(configMaster, configMasterAfter); diff != "" {
t.Errorf("updateCodeReviewConfig() should not change codereview.cfg content in master branch (-want +got) \n %s", diff)
}
// verify the release branch commit have the expected behavior.
headReleaseAfter, err := gerritClient.ReadBranchHead(ctx, "tools", releaseBranch)
if err != nil {
t.Fatalf("ReadBranchHead() should be able to get revision of master branch's head: %v", err)
}
if tc.wantCommit && headReleaseAfter == headRelease {
t.Errorf("updateCodeReviewConfig() should have one commit to release branch, head of branch got = %s want = %s", headRelease, headReleaseAfter)
} else if !tc.wantCommit && headReleaseAfter != headRelease {
t.Errorf("updateCodeReviewConfig() should have not change release branch's head, got = %s want = %s", headRelease, headReleaseAfter)
}
// verify the release branch configreview.cfg have the expected content.
configReleaseAfter, err := gerritClient.ReadFile(ctx, "tools", headReleaseAfter, "codereview.cfg")
if err != nil {
t.Fatalf("ReadFile() should be able to read the codereview.cfg file from release branch head: %v", err)
}
if diff := cmp.Diff(tc.wantConfig, string(configReleaseAfter)); diff != "" {
t.Errorf("codereview.cfg mismatch (-want +got) \n %s", diff)
}
})
}
}
func TestNextPrerelease(t *testing.T) {
ctx := context.Background()
testcases := []struct {
name string
tags []string
version string
want string
}{
{
name: "next pre-release is 2",
tags: []string{"gopls/v0.16.0-pre.0", "gopls/v0.16.0-pre.1"},
version: "v0.16.0",
want: "pre.2",
},
{
name: "next pre-release is 2 regardless of other minor or patch version",
tags: []string{"gopls/v0.16.0-pre.0", "gopls/v0.16.0-pre.1", "gopls/v0.16.1-pre.1", "gopls/v0.2.0-pre.3"},
version: "v0.16.0",
want: "pre.2",
},
{
name: "next pre-release is 2 regardless of non-int prerelease version",
tags: []string{"gopls/v0.16.0-pre.0", "gopls/v0.16.0-pre.1", "gopls/v0.16.0-pre.foo", "gopls/v0.16.0-pre.bar"},
version: "v0.16.0",
want: "pre.2",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tools := NewFakeRepo(t, "tools")
commit := tools.Commit(map[string]string{
"go.mod": "module golang.org/x/tools\n",
"go.sum": "\n",
})
for _, tag := range tc.tags {
tools.Tag(tag, commit)
}
gerrit := NewFakeGerrit(t, tools)
tasks := &ReleaseGoplsTasks{
Gerrit: gerrit,
}
semv, ok := parseSemver(tc.version)
if !ok {
t.Fatalf("parseSemver(%q) should success", tc.version)
}
got, err := tasks.nextPrereleaseVersion(&workflow.TaskContext{Context: ctx, Logger: &testLogger{t, ""}}, semv)
if err != nil {
t.Fatalf("nextPrerelease(%q) should not return error: %v", tc.version, err)
}
if tc.want != got {
t.Errorf("nextPrerelease(%q) = %v want %v", tc.version, got, tc.want)
}
})
}
}
func TestFindOrCreateReleaseIssue(t *testing.T) {
ctx := context.Background()
testcases := []struct {
name string
version string
create bool
fakeGithub FakeGitHub
wantErr bool
wantIssue int64
}{
{
name: "milestone does not exist",
version: "v0.16.2",
create: true,
wantErr: true,
wantIssue: 0,
},
{
name: "irrelevant milestone exist",
version: "v0.16.2",
create: true,
fakeGithub: FakeGitHub{
Milestones: map[int]string{1: "gopls/v0.16.1"},
},
wantErr: true,
wantIssue: 0,
},
{
name: "milestone exist, issue is missing, create true, workflow should create this issue",
version: "v0.16.2",
create: true,
fakeGithub: FakeGitHub{
Milestones: map[int]string{1: "gopls/v0.16.2"},
},
wantErr: false,
wantIssue: 1,
},
{
name: "milestone exist, issue is missing, create false, workflow error out",
version: "v0.16.2",
create: false,
fakeGithub: FakeGitHub{
Milestones: map[int]string{1: "gopls/v0.16.2"},
},
wantErr: true,
wantIssue: 0,
},
{
name: "milestone exist, issue exist, create true, workflow should reuse the issue",
version: "v0.16.2",
create: true,
fakeGithub: FakeGitHub{
Milestones: map[int]string{1: "gopls/v0.16.2"},
Issues: map[int]*github.Issue{2: {Number: github.Int(2), Title: github.String("x/tools/gopls: release version v0.16.2"), Milestone: &github.Milestone{ID: github.Int64(1)}}},
},
wantErr: false,
wantIssue: 2,
},
{
name: "milestone exist, issue exist, create false, workflow should reuse the issue",
version: "v0.16.2",
create: false,
fakeGithub: FakeGitHub{
Milestones: map[int]string{1: "gopls/v0.16.2"},
Issues: map[int]*github.Issue{2: {Number: github.Int(2), Title: github.String("x/tools/gopls: release version v0.16.2"), Milestone: &github.Milestone{ID: github.Int64(1)}}},
},
wantErr: false,
wantIssue: 2,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tasks := &ReleaseGoplsTasks{
Github: &tc.fakeGithub,
}
semv, ok := parseSemver(tc.version)
if !ok {
t.Fatalf("parseSemver(%q) should success", tc.version)
}
gotIssue, err := tasks.findOrCreateGitHubIssue(&workflow.TaskContext{Context: ctx, Logger: &testLogger{t, ""}}, semv, tc.create)
if tc.wantErr && err == nil {
t.Errorf("createReleaseIssue(%s) should return error but return nil", tc.version)
} else if !tc.wantErr && err != nil {
t.Errorf("createReleaseIssue(%s) should return nil but return err: %v", tc.version, err)
}
if tc.wantIssue != gotIssue {
t.Errorf("createReleaseIssue(%s) = %v, want %v", tc.version, gotIssue, tc.wantIssue)
}
})
}
}
func TestGoplsPrereleaseFlow(t *testing.T) {
mustHaveShell(t)
testcases := []struct {
name string
// The fields below are the prepared states before running the gopls
// pre-release flow.
// commitTags specifies a sequence of (possibly) tagged commits.
// For each entry, a new commit is created, and if the entry is
// non empty that commit is tagged with the entry value.
commitTags []string
// If set, create the release branch before starting the workflow.
createBranch bool
config string
semv semversion
// fields below are the desired states.
wantVersion string
wantConfig string
wantCommits int
}{
{
name: "update all three file through two commits",
commitTags: []string{"gopls/v0.0.0"},
createBranch: true,
config: " ",
semv: semversion{Major: 0, Minor: 1, Patch: 0},
wantVersion: "v0.1.0-pre.1",
wantConfig: `issuerepo: golang/go
branch: gopls-release-branch.0.1
parent-branch: master
`,
wantCommits: 2,
},
{
name: "codereview.cfg already have expected content, update go.mod and go.sum with one commit",
commitTags: []string{"gopls/v0.0.0"},
createBranch: true,
config: `issuerepo: golang/go
branch: gopls-release-branch.0.1
parent-branch: master
`,
semv: semversion{Major: 0, Minor: 1, Patch: 0},
wantVersion: "v0.1.0-pre.1",
wantConfig: `issuerepo: golang/go
branch: gopls-release-branch.0.1
parent-branch: master
`,
wantCommits: 1,
},
{
name: "create the branch for minor version",
commitTags: []string{"gopls/v0.11.0"},
createBranch: false,
config: ` `,
semv: semversion{Major: 0, Minor: 12, Patch: 0},
wantVersion: "v0.12.0-pre.1",
wantConfig: `issuerepo: golang/go
branch: gopls-release-branch.0.12
parent-branch: master
`,
wantCommits: 2,
},
{
name: "workflow should increment the pre-release number to 4",
commitTags: []string{"gopls/v0.8.2", "gopls/v0.8.3-pre.1", "gopls/v0.8.3-pre.2", "gopls/v0.8.3-pre.3"},
createBranch: true,
config: " ",
semv: semversion{Major: 0, Minor: 8, Patch: 3},
wantVersion: "v0.8.3-pre.4",
wantConfig: `issuerepo: golang/go
branch: gopls-release-branch.0.8
parent-branch: master
`,
wantCommits: 2,
},
}
for _, tc := range testcases {
runTestWithInput := func(input map[string]any) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
vscodego := NewFakeRepo(t, "vscode-go")
initial := vscodego.Commit(map[string]string{"extension/src/goToolsInformation.ts": "foo"})
vscodego.Branch("release-v0.44", initial)
tools := NewFakeRepo(t, "tools")
beforeHead := tools.Commit(map[string]string{
"gopls/go.mod": "module golang.org/x/tools\n",
"gopls/go.sum": "\n",
"codereview.cfg": tc.config,
})
// Create the release branch and make a few commits to the master branch.
// Var beforeHead is used to track the commit of release branch's head
// before trigger the gopls pre-release run. If we do not need to create a
// release branch, beforeHead will point to the initial commit in the
// master branch.
if len(tc.commitTags) != 0 {
for i, tag := range tc.commitTags {
commit := tools.CommitOnBranch("master", map[string]string{
"README.md": fmt.Sprintf("THIS IS READ ME FOR %v.", i),
})
beforeHead = commit
if tag != "" {
tools.Tag(tag, commit)
}
}
}
if tc.createBranch {
tools.Branch(goplsReleaseBranchName(tc.semv), beforeHead)
}
gerrit := NewFakeGerrit(t, tools, vscodego)
// fakeGo handles multiple arguments in gopls pre-release flow.
// - go get will write fake go.sum and go.mod to simulate pining the
// x/tools dependency.
// - go install will write a fake script in bin/gopls and grant execute
// permission to it to simulate gopls installation.
// - go env will return the current dir so gopls will point to the fake
// script that is written by go install.
// - go run will write "bar" content to file in vscode-go project
// containing gopls versions.
// - go mod will exit without any error.
var fakeGo = fmt.Sprintf(`#!/bin/bash -exu
case "$1" in
"get")
echo -n "test go sum" > go.sum
echo -n "test go mod" > go.mod
;;
"install")
mkdir bin
# write following content to bin/gopls
# make sure the gopls version and gopls references have return code 0.
cat <<EOF > bin/gopls
#!/bin/bash -exu
case "\$1" in
"version")
echo %q
;;
"references")
exit 0
;;
*)
echo unexpected command "\$@"
exit 1
;;
esac
EOF
# Make the bin/gopls script executable
chmod +x bin/gopls
;;
"env")
echo "."
;;
"mod")
exit 0
;;
"run")
echo -n "bar" > extension/src/goToolsInformation.ts
exit 0
;;
*)
echo unexpected command $@
exit 1
;;
esac`, tc.wantVersion)
var gotSubject string // subject of the announcement email that was sent
tasks := &ReleaseGoplsTasks{
Gerrit: gerrit,
CloudBuild: NewFakeCloudBuild(t, gerrit, "", nil, fakeGo),
Github: &FakeGitHub{
Milestones: map[int]string{
1: fmt.Sprintf("gopls/v%v.%v.%v", tc.semv.Major, tc.semv.Minor, tc.semv.Patch),
},
},
SendMail: func(h MailHeader, c MailContent) error {
gotSubject = c.Subject
return nil
},
ApproveAction: func(tc *workflow.TaskContext) error { return nil },
}
wd := tasks.NewPrereleaseDefinition()
w, err := workflow.Start(wd, input)
if err != nil {
t.Fatal(err)
}
outputs, err := w.Run(ctx, &verboseListener{t: t})
if err != nil {
t.Fatal(err)
}
// Verify that workflow will create the release branch for minor releases.
// The release branch is created before the flow run for patch releases.
afterHead, err := gerrit.ReadBranchHead(ctx, "tools", goplsReleaseBranchName(tc.semv))
if err != nil {
t.Error(err)
}
// Verify that workflow return the expected pre-release version.
if got := outputs["version"]; got != tc.wantVersion {
t.Errorf("Output: got \"version\" %q, want %q", got, tc.wantVersion)
}
// Verify the content of following files are expected.
contentChecks := []struct {
repo string
branch string
path string
want string
}{
{
repo: "tools",
branch: goplsReleaseBranchName(tc.semv),
path: "codereview.cfg",
want: tc.wantConfig,
},
{
repo: "tools",
branch: goplsReleaseBranchName(tc.semv),
path: "gopls/go.sum",
want: "test go sum",
},
{
repo: "tools",
branch: goplsReleaseBranchName(tc.semv),
path: "gopls/go.mod",
want: "test go mod",
},
{
repo: "vscode-go",
branch: "master",
path: "extension/src/goToolsInformation.ts",
want: "bar",
},
{
repo: "vscode-go",
branch: "release-v0.44",
path: "extension/src/goToolsInformation.ts",
want: "foo",
},
}
for _, check := range contentChecks {
commit, err := gerrit.ReadBranchHead(ctx, check.repo, check.branch)
if err != nil {
t.Fatal(err)
}
got, err := gerrit.ReadFile(ctx, check.repo, commit, check.path)
if err != nil {
t.Fatal(err)
}
if string(got) != check.want {
t.Errorf("Content of %q = %q, want %q", check.path, got, check.want)
}
}
// Verify the commits merged to release branch after the flow execution.
beforeIndex, afterIndex := 0, 0
for i, commit := range tools.History() {
if commit == afterHead {
afterIndex = i
}
if commit == beforeHead {
beforeIndex = i
}
}
if committed := beforeIndex - afterIndex; committed != tc.wantCommits {
t.Errorf("%v commits merged to release branch after the pre-release flow executed, but want %v commits", committed, tc.wantCommits)
}
// Verify the pre-release tag is created and it's pointing to the head of
// the release branch.
info, err := gerrit.GetTag(ctx, "tools", fmt.Sprintf("gopls/%s", tc.wantVersion))
if err != nil {
t.Fatal(err)
}
if info.Revision != afterHead {
t.Errorf("the pre-release tag points to commit %s, should point to the head commit of release branch %s", info.Revision, afterHead)
}
if wantSubject := "Gopls " + tc.wantVersion + " is released"; gotSubject != wantSubject {
// The full email content is checked by TestAnnouncementMail.
t.Errorf("NewPrereleaseDefinition().Run(): got email subject %q, want %q", gotSubject, wantSubject)
}
}
t.Run("manual input version: "+tc.name, func(t *testing.T) {
runTestWithInput(map[string]any{
reviewersParam.Name: []string(nil),
"explicit version (optional)": fmt.Sprintf("v%v.%v.%v", tc.semv.Major, tc.semv.Minor, tc.semv.Patch),
"next version": "use explicit version",
})
})
versionBump := "next patch"
if tc.semv.Patch == 0 {
versionBump = "next minor"
}
t.Run("interpret version "+versionBump+" : "+tc.name, func(t *testing.T) {
runTestWithInput(map[string]any{
reviewersParam.Name: []string(nil),
"explicit version (optional)": "",
"next version": versionBump,
})
})
}
}
func TestTagRelease(t *testing.T) {
ctx := context.Background()
testcases := []struct {
name string
tags []string
version string
wantErr bool
}{
{
name: "should add the release tag v0.1.0 to the commit with tag v0.1.0-pre.2",
tags: []string{
"gopls/v0.1.0-pre.1",
"gopls/v0.1.0-pre.2",
},
version: "v0.1.0-pre.2",
wantErr: false,
},
{
name: "should add the release tag v0.12.0 to the commit with tag v0.12.0-pre.1",
tags: []string{
"gopls/v0.12.0-pre.1",
"gopls/v0.12.0-pre.2",
},
version: "v0.12.0-pre.1",
wantErr: false,
},
{
name: "should error if the pre-release tag does not exist",
tags: []string{
"gopls/v0.12.0-pre.1",
"gopls/v0.12.0-pre.2",
},
version: "v0.12.0-pre.3",
wantErr: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tools := NewFakeRepo(t, "tools")
_ = tools.Commit(map[string]string{
"go.mod": "module golang.org/x/tools\n",
"go.sum": "\n",
})
for i, tag := range tc.tags {
commit := tools.Commit(map[string]string{
"README.md": fmt.Sprintf("THIS IS READ ME FOR %v.", i),
})
tools.Tag(tag, commit)
}
tasks := &ReleaseGoplsTasks{
Gerrit: NewFakeGerrit(t, tools),
}
semv, _ := parseSemver(tc.version)
err := tasks.tagRelease(&workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}}, semv, semv.Pre)
if tc.wantErr && err == nil {
t.Errorf("tagRelease(%q) should return error but return nil", tc.version)
} else if !tc.wantErr && err != nil {
t.Errorf("tagRelease(%q) should return nil but return err: %v", tc.version, err)
}
if !tc.wantErr {
releaseTag := fmt.Sprintf("gopls/v%v.%v.%v", semv.Major, semv.Minor, semv.Patch)
release, err := tasks.Gerrit.GetTag(ctx, "tools", releaseTag)
if err != nil {
t.Errorf("release tag %q should be added after tagRelease(%q): %v", releaseTag, tc.version, err)
}
prereleaseTag := fmt.Sprintf("gopls/%s", tc.version)
prerelease, err := tasks.Gerrit.GetTag(ctx, "tools", prereleaseTag)
if err != nil {
t.Fatalf("failed to get tag %q: %v", prereleaseTag, err)
}
// verify the release tag and the input pre-release tag point to the same
// commit.
if release.Revision != prerelease.Revision {
t.Errorf("tagRelease(%s) add the release tag to commit %s, but should add to commit %s", tc.version, prerelease.Revision, release.Revision)
}
}
})
}
}
func TestExecuteAndMonitorChange(t *testing.T) {
mustHaveShell(t)
testcases := []struct {
name string
branch string
script string
watch []string
want map[string]string
}{
{
name: "write all three files with different content",
branch: "master",
script: `echo -n "foo" > file_a
echo -n "foo" > file_b
echo -n "foo" > file_c
`,
watch: []string{"file_a", "file_b", "file_c"},
want: map[string]string{"file_a": "foo", "file_b": "foo", "file_c": "foo"},
},
{
name: "ignore file_c changes",
branch: "master",
script: `echo -n "foo" > file_a
echo -n "foo" > file_b
echo -n "foo" > file_c
`,
watch: []string{"file_a", "file_b"},
want: map[string]string{"file_a": "foo", "file_b": "foo"},
},
{
name: "write two files with different content",
branch: "master",
script: `echo -n "foo" > file_a
echo -n "foo" > file_b
`,
watch: []string{"file_a", "file_b", "file_c"},
want: map[string]string{"file_a": "foo", "file_b": "foo"},
},
{
name: "write one file with different content in foo branch",
branch: "foo",
script: `echo -n "foo" > file_a`,
watch: []string{"file_a", "file_b", "file_c"},
want: map[string]string{"file_a": "foo"},
},
{
name: "create a file in foo branch",
branch: "foo",
script: `echo -n "foo" > file_d`,
watch: []string{"file_a", "file_b", "file_c", "file_d"},
want: map[string]string{"file_d": "foo"},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tools := NewFakeRepo(t, "tools")
initial := tools.Commit(map[string]string{
"gopls/go.mod": "module golang.org/x/tools\n",
"gopls/go.sum": "\n",
"file_a": "file_a",
"file_b": "file_b",
"file_c": "file_c",
})
if tc.branch != "master" {
tools.Branch(tc.branch, initial)
}
cloudBuild := NewFakeCloudBuild(t, NewFakeGerrit(t, tools), "", nil, fakeGo)
ctx := &workflow.TaskContext{
Context: context.Background(),
Logger: &testLogger{t, ""},
}
got, err := executeAndMonitorChange(ctx, cloudBuild, "tools", tc.branch, tc.script, tc.watch)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("executeAndMonitorChange() = %v want = %v", got, tc.want)
}
})
}
}