Skip to content

Commit

Permalink
all: run integration test on longtest builders in CI
Browse files Browse the repository at this point in the history
Remove the 'ignore' build constraint from integration_test.go and
arrange things such that the integration test runs on longtest
(but not longtest-race, for now anyway) builders. The existing
scripts for running the invocation test locally are preserved
and can be used as before.

This change relies on the builders being configured to place pre-built
protoc and conformance_test_runner binaries in $PATH (CL 547116); thanks
to Michael for making them available via a CIPD package.

Apply a few improvements to downloadFile and downloadArchive that came
about from earlier prototypes that used them and uncovered some issues.

Fixes golang/go#64066.

Change-Id: I48b163e3ea9c441b748071da340d3e37282cc22b
Co-authored-by: Michael Stapelberg <stapelberg@golang.org>
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/541123
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Michael Stapelberg <stapelberg@google.com>
  • Loading branch information
dmitshur and stapelberg committed Dec 15, 2023
1 parent e8baad6 commit 8ed73c7
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ jobs:
~/.cache/bazel
key: ${{ runner.os }}-${{ hashFiles('integration_test.go') }}
- name: Test
run: go test -v -timeout=60m -count=1 -failfast integration_test.go
run: go test -run='^TestIntegration$' -v -timeout=60m -count=1 -failfast "$@"
105 changes: 85 additions & 20 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build ignore
// +build ignore

package main

import (
Expand All @@ -16,13 +13,16 @@ import (
"flag"
"fmt"
"io"
"io/fs"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"runtime"
"runtime/debug"
"strings"
"sync"
"testing"
Expand All @@ -43,8 +43,8 @@ var (
"1.17.13",
"1.18.10",
"1.19.13",
"1.20.7",
"1.21.1",
"1.20.12",
"1.21.5",
}
}()
golangLatest = golangVersions[len(golangVersions)-1]
Expand All @@ -65,7 +65,25 @@ var (
protobufPath string
)

func Test(t *testing.T) {
func TestIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
if os.Getenv("GO_BUILDER_NAME") != "" {
// To start off, run on longtest builders, not longtest-race ones.
if race() {
t.Skip("skipping integration test in race mode on builders")
}
// When on a builder, run even if it's not explicitly requested
// provided our caller isn't already running it.
if os.Getenv("GO_PROTOBUF_INTEGRATION_TEST_RUNNING") == "1" {
t.Skip("protobuf integration test is already running, skipping nested invocation")
}
os.Setenv("GO_PROTOBUF_INTEGRATION_TEST_RUNNING", "1")
} else if flag.Lookup("test.run").Value.String() != "^TestIntegration$" {
t.Skip("not running integration test if not explicitly requested via test.bash")
}

mustInitDeps(t)
mustHandleFlags(t)

Expand Down Expand Up @@ -236,9 +254,7 @@ func mustInitDeps(t *testing.T) {
}
finishWork()

// Download and build the protobuf toolchain.
// We avoid downloading the pre-compiled binaries since they do not contain
// the conformance test runner.
// Get the protobuf toolchain.
protobufPath = startWork("protobuf-" + protobufVersion)
if _, err := os.Stat(protobufPath); err != nil {
fmt.Printf("download %v\n", filepath.Base(protobufPath))
Expand All @@ -250,16 +266,32 @@ func mustInitDeps(t *testing.T) {
command{Dir: testDir}.mustRun(t, "git", "clone", "https://github.com/protocolbuffers/protobuf", "protobuf-"+protobufVersion)
command{Dir: protobufPath}.mustRun(t, "git", "checkout", checkoutVersion)

fmt.Printf("build %v\n", filepath.Base(protobufPath))
env := os.Environ()
if runtime.GOOS == "darwin" {
// Adding this environment variable appears to be necessary for macOS builds.
env = append(env, "CC=clang")
if os.Getenv("GO_BUILDER_NAME") != "" {
// If this is running on the Go build infrastructure,
// use pre-built versions of these binaries that the
// builders are configured to provide in $PATH.
protocPath, err := exec.LookPath("protoc")
check(err)
confTestRunnerPath, err := exec.LookPath("conformance_test_runner")
check(err)
check(os.MkdirAll(filepath.Join(protobufPath, "bazel-bin", "conformance"), 0775))
check(os.Symlink(protocPath, filepath.Join(protobufPath, "bazel-bin", "protoc")))
check(os.Symlink(confTestRunnerPath, filepath.Join(protobufPath, "bazel-bin", "conformance", "conformance_test_runner")))
} else {
// In other environments, download and build the protobuf toolchain.
// We avoid downloading the pre-compiled binaries since they do not contain
// the conformance test runner.
fmt.Printf("build %v\n", filepath.Base(protobufPath))
env := os.Environ()
if runtime.GOOS == "darwin" {
// Adding this environment variable appears to be necessary for macOS builds.
env = append(env, "CC=clang")
}
command{
Dir: protobufPath,
Env: env,
}.mustRun(t, "bazel", "build", ":protoc", "//conformance:conformance_test_runner")
}
command{
Dir: protobufPath,
Env: env,
}.mustRun(t, "bazel", "build", ":protoc", "//conformance:conformance_test_runner")
}
check(os.Setenv("PROTOBUF_ROOT", protobufPath)) // for generate-protos
registerBinary("conform-test-runner", filepath.Join(protobufPath, "bazel-bin", "conformance", "conformance_test_runner"))
Expand Down Expand Up @@ -298,17 +330,23 @@ func mustInitDeps(t *testing.T) {
check(os.Setenv("GOCACHE", filepath.Join(repoRoot, ".gocache")))
}

func downloadFile(check func(error), dstPath, srcURL string) {
func downloadFile(check func(error), dstPath, srcURL string, perm fs.FileMode) {
resp, err := http.Get(srcURL)
check(err)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
check(fmt.Errorf("GET %q: non-200 OK status code: %v body: %q", srcURL, resp.Status, body))
}

check(os.MkdirAll(filepath.Dir(dstPath), 0775))
f, err := os.Create(dstPath)
f, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
check(err)

_, err = io.Copy(f, resp.Body)
check(err)

check(f.Close())
}

func downloadArchive(check func(error), dstPath, srcURL, skipPrefix, wantSHA256 string) {
Expand All @@ -317,6 +355,10 @@ func downloadArchive(check func(error), dstPath, srcURL, skipPrefix, wantSHA256
resp, err := http.Get(srcURL)
check(err)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
check(fmt.Errorf("GET %q: non-200 OK status code: %v body: %q", srcURL, resp.Status, body))
}

var r io.Reader = resp.Body
if wantSHA256 != "" {
Expand Down Expand Up @@ -493,3 +535,26 @@ func mustRunCommand(t *testing.T, args ...string) string {
t.Helper()
return command{}.mustRun(t, args...)
}

// race is an approximation of whether the race detector is on.
// It's used to skip the integration test on builders, without
// preventing the integration test from running under the race
// detector as a '//go:build !race' build constraint would.
func race() bool {
bi, ok := debug.ReadBuildInfo()
if !ok {
return false
}
// Use reflect because the debug.BuildInfo.Settings field
// isn't available in Go 1.17.
s := reflect.ValueOf(bi).Elem().FieldByName("Settings")
if !s.IsValid() {
return false
}
for i := 0; i < s.Len(); i++ {
if s.Index(i).FieldByName("Key").String() == "-race" {
return s.Index(i).FieldByName("Value").String() == "true"
}
}
return false
}
3 changes: 1 addition & 2 deletions regenerate.bash
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

cd "$(git rev-parse --show-toplevel)"
go test -v -timeout=60m -count=1 integration_test.go "$@" -regenerate
go test google.golang.org/protobuf -run='^TestIntegration$' -v -timeout=60m -count=1 "$@" -regenerate
exit $?
2 changes: 1 addition & 1 deletion release.bash
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fi
git commit -a -m "all: release $(version_string)"

# Build release binaries.
go test -timeout=60m -count=1 integration_test.go "$@" -buildRelease
go test google.golang.org/protobuf -run='^TestIntegration$' -timeout=60m -count=1 "$@" -buildRelease

# Create commit to start development after release.
VERSION_PRERELEASE="${VERSION_PRERELEASE}.devel" # append ".devel"
Expand Down
3 changes: 1 addition & 2 deletions test.bash
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

cd "$(git rev-parse --show-toplevel)"
go test -v -timeout=60m -count=1 integration_test.go -failfast "$@"
go test google.golang.org/protobuf -run='^TestIntegration$' -v -timeout=60m -count=1 -failfast "$@"
exit $?

0 comments on commit 8ed73c7

Please sign in to comment.