*: revendor
This commit is contained in:
119
vendor/github.com/cockroachdb/cockroach-go/testserver/binaries.go
generated
vendored
Normal file
119
vendor/github.com/cockroachdb/cockroach-go/testserver/binaries.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
package testserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
awsBaseURL = "https://s3.amazonaws.com/cockroach/cockroach"
|
||||
latestSuffix = "LATEST"
|
||||
localBinaryPath = "/var/tmp"
|
||||
finishedFileMode = 0555
|
||||
)
|
||||
|
||||
func binaryName() string {
|
||||
return fmt.Sprintf("cockroach.%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func binaryNameWithSha(sha string) string {
|
||||
return fmt.Sprintf("%s.%s", binaryName(), sha)
|
||||
}
|
||||
|
||||
func binaryPath(sha string) string {
|
||||
return filepath.Join(localBinaryPath, binaryNameWithSha(sha))
|
||||
}
|
||||
|
||||
func latestMarkerURL() string {
|
||||
return fmt.Sprintf("%s/%s.%s", awsBaseURL, binaryName(), latestSuffix)
|
||||
}
|
||||
|
||||
func binaryURL(sha string) string {
|
||||
return fmt.Sprintf("%s/%s.%s", awsBaseURL, binaryName(), sha)
|
||||
}
|
||||
|
||||
func findLatestSha() (string, error) {
|
||||
markerURL := latestMarkerURL()
|
||||
marker, err := http.Get(markerURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not download %s: %s", markerURL)
|
||||
}
|
||||
if marker.StatusCode == 404 {
|
||||
return "", fmt.Errorf("for 404 from GET %s: make sure OS and ARCH are supported",
|
||||
markerURL)
|
||||
} else if marker.StatusCode != 200 {
|
||||
return "", fmt.Errorf("bad response got GET %s: %d (%s)",
|
||||
markerURL, marker.StatusCode, marker.Status)
|
||||
}
|
||||
|
||||
defer marker.Body.Close()
|
||||
body, err := ioutil.ReadAll(marker.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(body)), nil
|
||||
}
|
||||
|
||||
func downloadFile(url, filePath string) error {
|
||||
output, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0200)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating %s: %s", filePath, "-", err)
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
log.Printf("downloading %s to %s, this may take some time", url, filePath)
|
||||
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error downloading %s: %s", url, err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
return fmt.Errorf("error downloading %s: %d (%s)", url, response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
_, err = io.Copy(output, response.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("problem downloading %s to %s: %s", url, filePath, err)
|
||||
}
|
||||
|
||||
// Download was successful, add the rw bits.
|
||||
return os.Chmod(filePath, finishedFileMode)
|
||||
}
|
||||
|
||||
func downloadLatestBinary() (string, error) {
|
||||
sha, err := findLatestSha()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
localFile := binaryPath(sha)
|
||||
for {
|
||||
finfo, err := os.Stat(localFile)
|
||||
if err != nil {
|
||||
// File does not exist: download it.
|
||||
break
|
||||
}
|
||||
// File already present: check mode.
|
||||
if finfo.Mode().Perm() == finishedFileMode {
|
||||
return localFile, nil
|
||||
}
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
|
||||
err = downloadFile(binaryURL(sha), localFile)
|
||||
if err != nil {
|
||||
_ = os.Remove(localFile)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return localFile, nil
|
||||
}
|
415
vendor/github.com/cockroachdb/cockroach-go/testserver/testserver.go
generated
vendored
Normal file
415
vendor/github.com/cockroachdb/cockroach-go/testserver/testserver.go
generated
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
// Copyright 2016 The Cockroach Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License.
|
||||
//
|
||||
// Author: Marc Berhault (marc@cockroachlabs.com)
|
||||
|
||||
// Package testserver provides helpers to run a cockroach binary within tests.
|
||||
// It automatically downloads the latest cockroach binary for your platform
|
||||
// (Linux-amd64 and Darwin-amd64 only for now), or attempts to run "cockroach"
|
||||
// from your PATH.
|
||||
//
|
||||
// A normal invocation is (check err every time):
|
||||
// ts, err := testserver.NewTestServer()
|
||||
// err = ts.Start()
|
||||
// defer ts.Stop()
|
||||
// url := ts.PGURL()
|
||||
//
|
||||
// To use, run as follows:
|
||||
// import "github.com/cockroachdb/cockroach-go/testserver"
|
||||
// import "testing"
|
||||
// import "time"
|
||||
//
|
||||
// func TestRunServer(t *testing.T) {
|
||||
// ts, err := testserver.NewTestServer()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// err := ts.Start()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer ts.Stop()
|
||||
//
|
||||
// url := ts.PGURL()
|
||||
// if url != nil {
|
||||
// t.FatalF("url not found")
|
||||
// }
|
||||
// t.Logf("URL: %s", url.String())
|
||||
//
|
||||
// db, err := sql.Open("postgres", url.String())
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }
|
||||
package testserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sqlURLRegexp = regexp.MustCompile("sql:\\s+(postgresql:.+)\n")
|
||||
|
||||
const (
|
||||
stateNew = iota
|
||||
stateRunning = iota
|
||||
stateStopped = iota
|
||||
stateFailed = iota
|
||||
|
||||
socketPort = 26257
|
||||
socketFileBase = ".s.PGSQL"
|
||||
)
|
||||
|
||||
// TestServer is a helper to run a real cockroach node.
|
||||
type TestServer struct {
|
||||
mu sync.RWMutex
|
||||
state int
|
||||
baseDir string
|
||||
pgURL *url.URL
|
||||
cmd *exec.Cmd
|
||||
args []string
|
||||
stdout string
|
||||
stderr string
|
||||
stdoutBuf logWriter
|
||||
stderrBuf logWriter
|
||||
}
|
||||
|
||||
// NewDBForTest creates a new CockroachDB TestServer instance and
|
||||
// opens a SQL database connection to it. Returns a sql *DB instance a
|
||||
// shutdown function. The caller is responsible for executing the
|
||||
// returned shutdown function on exit.
|
||||
func NewDBForTest(t *testing.T) (*sql.DB, func()) {
|
||||
return NewDBForTestWithDatabase(t, "")
|
||||
}
|
||||
|
||||
// NewDBForTestWithDatabase creates a new CockroachDB TestServer
|
||||
// instance and opens a SQL database connection to it. If database is
|
||||
// specified, the returned connection will explicitly connect to
|
||||
// it. Returns a sql *DB instance a shutdown function. The caller is
|
||||
// responsible for executing the returned shutdown function on exit.
|
||||
func NewDBForTestWithDatabase(t *testing.T, database string) (*sql.DB, func()) {
|
||||
ts, err := NewTestServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = ts.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
url := ts.PGURL()
|
||||
if url == nil {
|
||||
t.Fatalf("url not found")
|
||||
}
|
||||
if len(database) > 0 {
|
||||
url.Path = database
|
||||
}
|
||||
|
||||
db, err := sql.Open("postgres", url.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ts.WaitForInit(db)
|
||||
|
||||
return db, func() {
|
||||
_ = db.Close()
|
||||
ts.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// NewTestServer creates a new TestServer, but does not start it.
|
||||
// The cockroach binary for your OS and ARCH is downloaded automatically.
|
||||
// If the download fails, we attempt just call "cockroach", hoping it is
|
||||
// found in your path.
|
||||
func NewTestServer() (*TestServer, error) {
|
||||
cockroachBinary, err := downloadLatestBinary()
|
||||
if err == nil {
|
||||
log.Printf("Using automatically-downloaded binary: %s", cockroachBinary)
|
||||
} else {
|
||||
log.Printf("Attempting to use cockroach binary from your PATH")
|
||||
cockroachBinary = "cockroach"
|
||||
}
|
||||
|
||||
// Force "/tmp/" so avoid OSX's really long temp directory names
|
||||
// which get us over the socket filename length limit.
|
||||
baseDir, err := ioutil.TempDir("/tmp", "cockroach-testserver")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create temp directory: %s", err)
|
||||
}
|
||||
|
||||
logDir := filepath.Join(baseDir, "logs")
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("could not create logs directory: %s: %s", logDir, err)
|
||||
}
|
||||
|
||||
options := url.Values{
|
||||
"host": []string{baseDir},
|
||||
}
|
||||
pgurl := &url.URL{
|
||||
Scheme: "postgres",
|
||||
User: url.User("root"),
|
||||
Host: fmt.Sprintf(":%d", socketPort),
|
||||
RawQuery: options.Encode(),
|
||||
}
|
||||
socketPath := filepath.Join(baseDir, fmt.Sprintf("%s.%d", socketFileBase, socketPort))
|
||||
|
||||
args := []string{
|
||||
cockroachBinary,
|
||||
"start",
|
||||
"--logtostderr",
|
||||
"--insecure",
|
||||
"--port=0",
|
||||
"--http-port=0",
|
||||
"--socket=" + socketPath,
|
||||
"--store=" + baseDir,
|
||||
}
|
||||
|
||||
ts := &TestServer{
|
||||
baseDir: baseDir,
|
||||
pgURL: pgurl,
|
||||
args: args,
|
||||
stdout: filepath.Join(logDir, "cockroach.stdout"),
|
||||
stderr: filepath.Join(logDir, "cockroach.stderr"),
|
||||
}
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
// Stdout returns the entire contents of the process' stdout.
|
||||
func (ts *TestServer) Stdout() string {
|
||||
return ts.stdoutBuf.String()
|
||||
}
|
||||
|
||||
// Stderr returns the entire contents of the process' stderr.
|
||||
func (ts *TestServer) Stderr() string {
|
||||
return ts.stderrBuf.String()
|
||||
}
|
||||
|
||||
// PGURL returns the postgres connection URL to reach the started
|
||||
// cockroach node.
|
||||
// It loops until the expected unix socket file exists.
|
||||
// This does not timeout, relying instead on test timeouts.
|
||||
func (ts *TestServer) PGURL() *url.URL {
|
||||
socketPath := filepath.Join(ts.baseDir, fmt.Sprintf("%s.%d", socketFileBase, socketPort))
|
||||
for {
|
||||
if _, err := os.Stat(socketPath); err == nil {
|
||||
return ts.pgURL
|
||||
}
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForInit repeatedly looks up the list of databases until
|
||||
// the "system" database exists. It ignores all errors as we are
|
||||
// waiting for the process to start and complete initialization.
|
||||
// This does not timeout, relying instead on test timeouts.
|
||||
func (ts *TestServer) WaitForInit(db *sql.DB) {
|
||||
for {
|
||||
// We issue a query that fails both on connection errors and on the
|
||||
// system database not existing.
|
||||
if _, err := db.Query("SHOW DATABASES"); err == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
}
|
||||
|
||||
// Start runs the process, returning an error on any problems,
|
||||
// including being unable to start, but not unexpected failure.
|
||||
// It should only be called once in the lifetime of a TestServer object.
|
||||
func (ts *TestServer) Start() error {
|
||||
ts.mu.Lock()
|
||||
if ts.state != stateNew {
|
||||
ts.mu.Unlock()
|
||||
return errors.New("Start() can only be called once")
|
||||
}
|
||||
ts.state = stateRunning
|
||||
ts.mu.Unlock()
|
||||
|
||||
ts.cmd = exec.Command(ts.args[0], ts.args[1:]...)
|
||||
ts.cmd.Env = []string{"COCKROACH_MAX_OFFSET=1ns"}
|
||||
|
||||
if len(ts.stdout) > 0 {
|
||||
wr, err := newFileLogWriter(ts.stdout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open file %s: %s", ts.stdout, err)
|
||||
}
|
||||
ts.stdoutBuf = wr
|
||||
}
|
||||
ts.cmd.Stdout = ts.stdoutBuf
|
||||
|
||||
if len(ts.stderr) > 0 {
|
||||
wr, err := newFileLogWriter(ts.stderr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open file %s: %s", ts.stderr, err)
|
||||
}
|
||||
ts.stderrBuf = wr
|
||||
}
|
||||
ts.cmd.Stderr = ts.stderrBuf
|
||||
|
||||
for k, v := range defaultEnv() {
|
||||
ts.cmd.Env = append(ts.cmd.Env, k+"="+v)
|
||||
}
|
||||
|
||||
err := ts.cmd.Start()
|
||||
if ts.cmd.Process != nil {
|
||||
log.Printf("process %d started: %s", ts.cmd.Process.Pid, strings.Join(ts.args, " "))
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf(err.Error())
|
||||
ts.stdoutBuf.Close()
|
||||
ts.stderrBuf.Close()
|
||||
|
||||
ts.mu.Lock()
|
||||
ts.state = stateFailed
|
||||
ts.mu.Unlock()
|
||||
|
||||
return fmt.Errorf("failure starting process: %s", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
ts.cmd.Wait()
|
||||
|
||||
ts.stdoutBuf.Close()
|
||||
ts.stderrBuf.Close()
|
||||
|
||||
ps := ts.cmd.ProcessState
|
||||
sy := ps.Sys().(syscall.WaitStatus)
|
||||
|
||||
log.Printf("Process %d exited with status %d", ps.Pid(), sy.ExitStatus())
|
||||
log.Printf(ps.String())
|
||||
|
||||
ts.mu.Lock()
|
||||
if sy.ExitStatus() == 0 {
|
||||
ts.state = stateStopped
|
||||
} else {
|
||||
ts.state = stateFailed
|
||||
}
|
||||
ts.mu.Unlock()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop kills the process if it is still running and cleans its directory.
|
||||
// It should only be called once in the lifetime of a TestServer object.
|
||||
// Logs fatal if the process has already failed.
|
||||
func (ts *TestServer) Stop() {
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
|
||||
if ts.state == stateNew {
|
||||
log.Fatal("Stop() called, but Start() was never called")
|
||||
}
|
||||
if ts.state == stateFailed {
|
||||
log.Fatalf("Stop() called, but process exited unexpectedly. Stdout:\n%s\nStderr:\n%s\n",
|
||||
ts.Stdout(), ts.Stderr())
|
||||
return
|
||||
}
|
||||
|
||||
if ts.state != stateStopped {
|
||||
// Only call kill if not running. It could have exited properly.
|
||||
ts.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// Only cleanup on intentional stops.
|
||||
_ = os.RemoveAll(ts.baseDir)
|
||||
}
|
||||
|
||||
type logWriter interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
String() string
|
||||
Len() int64
|
||||
Close()
|
||||
}
|
||||
|
||||
type fileLogWriter struct {
|
||||
filename string
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func newFileLogWriter(file string) (*fileLogWriter, error) {
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &fileLogWriter{
|
||||
filename: file,
|
||||
file: f,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w fileLogWriter) Close() {
|
||||
w.file.Close()
|
||||
}
|
||||
|
||||
func (w fileLogWriter) Write(p []byte) (n int, err error) {
|
||||
return w.file.Write(p)
|
||||
}
|
||||
|
||||
func (w fileLogWriter) String() string {
|
||||
b, err := ioutil.ReadFile(w.filename)
|
||||
if err == nil {
|
||||
return string(b)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w fileLogWriter) Len() int64 {
|
||||
s, err := os.Stat(w.filename)
|
||||
if err == nil {
|
||||
return s.Size()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func defaultEnv() map[string]string {
|
||||
vars := map[string]string{}
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
if _, ok := vars["USER"]; !ok {
|
||||
vars["USER"] = u.Username
|
||||
}
|
||||
if _, ok := vars["UID"]; !ok {
|
||||
vars["UID"] = u.Uid
|
||||
}
|
||||
if _, ok := vars["GID"]; !ok {
|
||||
vars["GID"] = u.Gid
|
||||
}
|
||||
if _, ok := vars["HOME"]; !ok {
|
||||
vars["HOME"] = u.HomeDir
|
||||
}
|
||||
}
|
||||
if _, ok := vars["PATH"]; !ok {
|
||||
vars["PATH"] = os.Getenv("PATH")
|
||||
}
|
||||
return vars
|
||||
}
|
35
vendor/github.com/cockroachdb/cockroach-go/testserver/testserver_test.go
generated
vendored
Normal file
35
vendor/github.com/cockroachdb/cockroach-go/testserver/testserver_test.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2016 The Cockroach Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License.
|
||||
//
|
||||
// Author: Marc Berhault (marc@cockroachlabs.com)
|
||||
|
||||
package testserver_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
// Needed for postgres driver test.
|
||||
"github.com/cockroachdb/cockroach-go/testserver"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func TestRunServer(t *testing.T) {
|
||||
db, stop := testserver.NewDBForTest(t)
|
||||
defer stop()
|
||||
|
||||
_, err := db.Exec("SELECT 1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user