2013-07-21 05:36:53 +00:00
|
|
|
/*
|
|
|
|
Copyright 2013 Google Inc.
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net"
|
2014-01-19 22:35:05 +00:00
|
|
|
"net/http"
|
2013-07-21 05:36:53 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
2014-02-05 02:49:15 +00:00
|
|
|
"strings"
|
2014-02-24 15:29:33 +00:00
|
|
|
"sync/atomic"
|
2013-07-21 05:36:53 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2014-02-05 02:49:15 +00:00
|
|
|
"camlistore.org/pkg/blob"
|
2013-07-21 05:36:53 +00:00
|
|
|
"camlistore.org/pkg/osutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
// World defines an integration test world.
|
|
|
|
//
|
|
|
|
// It's used to run the actual Camlistore binaries (camlistored,
|
|
|
|
// camput, camget, camtool, etc) together in large tests, including
|
2014-01-07 04:01:07 +00:00
|
|
|
// building them, finding them, and wiring them up in an isolated way.
|
2013-07-21 05:36:53 +00:00
|
|
|
type World struct {
|
|
|
|
camRoot string // typically $GOPATH[0]/src/camlistore.org
|
|
|
|
tempDir string
|
|
|
|
listener net.Listener // randomly chosen 127.0.0.1 port for the server
|
|
|
|
port int
|
|
|
|
|
2014-02-24 15:29:33 +00:00
|
|
|
server *exec.Cmd
|
|
|
|
isRunning int32 // state of the camlistored server. Access with sync/atomic only.
|
|
|
|
serverErr error
|
|
|
|
|
2013-07-21 05:36:53 +00:00
|
|
|
cammount *os.Process
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewWorld returns a new test world.
|
|
|
|
// It requires that GOPATH is set to find the "camlistore.org" root.
|
|
|
|
func NewWorld() (*World, error) {
|
|
|
|
if os.Getenv("GOPATH") == "" {
|
|
|
|
return nil, errors.New("GOPATH environment variable isn't set; required to run Camlistore integration tests")
|
|
|
|
}
|
|
|
|
root, err := osutil.GoPackagePath("camlistore.org")
|
|
|
|
if err == os.ErrNotExist {
|
|
|
|
return nil, errors.New("Directory \"camlistore.org\" not found under GOPATH/src; can't run Camlistore integration tests.")
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error searching for \"camlistore.org\" under GOPATH: %v", err)
|
|
|
|
}
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &World{
|
|
|
|
camRoot: root,
|
|
|
|
listener: ln,
|
|
|
|
port: ln.Addr().(*net.TCPAddr).Port,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2014-02-07 23:02:22 +00:00
|
|
|
func (w *World) Addr() string {
|
|
|
|
return w.listener.Addr().String()
|
|
|
|
}
|
|
|
|
|
2013-07-21 05:36:53 +00:00
|
|
|
// Start builds the Camlistore binaries and starts a server.
|
|
|
|
func (w *World) Start() error {
|
|
|
|
var err error
|
|
|
|
w.tempDir, err = ioutil.TempDir("", "camlistore-test-")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build.
|
|
|
|
{
|
2014-08-08 23:11:53 +00:00
|
|
|
targs := []string{
|
|
|
|
"camget",
|
|
|
|
"camput",
|
|
|
|
"camtool",
|
|
|
|
"camlistored",
|
|
|
|
}
|
|
|
|
// TODO(mpl): investigate why we still rebuild camlistored everytime if run through devcam test.
|
|
|
|
// it looks like it's because we always resync the UI files and hence redo the embeds. Next CL.
|
|
|
|
var latestModtime time.Time
|
|
|
|
for _, target := range targs {
|
|
|
|
binPath := filepath.Join(w.camRoot, "bin", target)
|
|
|
|
fi, err := os.Stat(binPath)
|
|
|
|
if err != nil {
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
return fmt.Errorf("could not stat %v: %v", binPath, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
modTime := fi.ModTime()
|
|
|
|
if modTime.After(latestModtime) {
|
|
|
|
latestModtime = modTime
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
devcam test: do not "recurse" temp GOPATH, docs, couple more options.
Problem: make.go creates an isolated temp gopath ./tmp/build-gopath. The
integration tests make use of that gopath (by running make.go) to build
the tools, and run the test world in it. Similarly, devcam test uses
make.go to setup that temp gopath, and runs the tests from the source
files in that gopath. Consequently, when the integration tests are run
through devcam test, even though they're run from the temp gopath, they
would use the make.go in it, which would create a nested temp gopath
(CAMLIROOT/tmp/build-gopath/src/camlistore.org/tmp/build-gopath) in
which to run the tests.
This patch addresses this issue by creating a new flag (-envGoPath), and
the corresponding env var (CAMLI_MAKE_USEGOPATH), which tells make.go
not to create a new temporary gopath (and hence not to mirror any
files), and to rely on the already set GOPATH env var instead.
Also refactored make.go a bit, and added a couple options and doc to
devcam test.
Change-Id: Ia8a5d7a31e6e317f05218d9e18fb886001cd19cb
2014-08-06 16:47:42 +00:00
|
|
|
// TODO(mpl): when running with -v (either with go test or devcam test), append it for make.go as well
|
2014-08-08 23:11:53 +00:00
|
|
|
cmd := exec.Command("go", "run", "make.go",
|
|
|
|
fmt.Sprintf("--if_mods_since=%d", latestModtime.Unix()),
|
|
|
|
)
|
2013-07-21 05:36:53 +00:00
|
|
|
cmd.Dir = w.camRoot
|
2013-07-30 19:57:50 +00:00
|
|
|
log.Print("Running make.go to build camlistore binaries for testing...")
|
2013-07-21 05:36:53 +00:00
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error building world: %v, %s", err, string(out))
|
|
|
|
}
|
2013-07-30 19:57:50 +00:00
|
|
|
log.Print("Ran make.go.")
|
2013-07-21 05:36:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start camlistored.
|
|
|
|
{
|
|
|
|
w.server = exec.Command(
|
|
|
|
filepath.Join(w.camRoot, "bin", "camlistored"),
|
2013-08-26 23:03:24 +00:00
|
|
|
"--openbrowser=false",
|
2013-07-21 05:36:53 +00:00
|
|
|
"--configfile="+filepath.Join(w.camRoot, "pkg", "test", "testdata", "server-config.json"),
|
|
|
|
"--listen=FD:3",
|
2013-07-30 19:57:50 +00:00
|
|
|
"--pollparent=true",
|
2013-07-21 05:36:53 +00:00
|
|
|
)
|
|
|
|
var buf bytes.Buffer
|
|
|
|
w.server.Stdout = &buf
|
|
|
|
w.server.Stderr = &buf
|
|
|
|
w.server.Dir = w.tempDir
|
|
|
|
w.server.Env = append(os.Environ(),
|
2013-07-30 19:57:50 +00:00
|
|
|
"CAMLI_DEBUG=1",
|
2013-07-21 05:36:53 +00:00
|
|
|
"CAMLI_ROOT="+w.tempDir,
|
2013-07-21 19:26:05 +00:00
|
|
|
"CAMLI_SECRET_RING="+filepath.Join(w.camRoot, filepath.FromSlash("pkg/jsonsign/testdata/test-secring.gpg")),
|
2013-07-21 05:36:53 +00:00
|
|
|
"CAMLI_BASE_URL=http://127.0.0.1:"+strconv.Itoa(w.port),
|
|
|
|
)
|
|
|
|
listenerFD, err := w.listener.(*net.TCPListener).File()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
w.server.ExtraFiles = []*os.File{listenerFD}
|
|
|
|
if err := w.server.Start(); err != nil {
|
2014-02-24 15:29:33 +00:00
|
|
|
w.serverErr = fmt.Errorf("starting camlistored: %v", err)
|
|
|
|
return w.serverErr
|
2013-07-21 05:36:53 +00:00
|
|
|
}
|
2014-02-24 15:29:33 +00:00
|
|
|
atomic.StoreInt32(&w.isRunning, 1)
|
2013-07-21 05:36:53 +00:00
|
|
|
waitc := make(chan error, 1)
|
|
|
|
go func() {
|
2014-02-24 15:29:33 +00:00
|
|
|
err := w.server.Wait()
|
|
|
|
w.serverErr = fmt.Errorf("%v: %s", err, buf.String())
|
|
|
|
atomic.StoreInt32(&w.isRunning, 0)
|
|
|
|
waitc <- w.serverErr
|
2013-07-21 05:36:53 +00:00
|
|
|
}()
|
2014-01-19 22:35:05 +00:00
|
|
|
upc := make(chan bool)
|
|
|
|
timeoutc := make(chan bool)
|
|
|
|
go func() {
|
|
|
|
for i := 0; i < 100; i++ {
|
|
|
|
res, err := http.Get("http://127.0.0.1:" + strconv.Itoa(w.port))
|
|
|
|
if err == nil {
|
|
|
|
res.Body.Close()
|
|
|
|
upc <- true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
2013-07-21 05:36:53 +00:00
|
|
|
}
|
2014-02-24 15:29:33 +00:00
|
|
|
w.serverErr = errors.New(buf.String())
|
|
|
|
atomic.StoreInt32(&w.isRunning, 0)
|
2014-01-19 22:35:05 +00:00
|
|
|
timeoutc <- true
|
|
|
|
}()
|
2013-07-21 05:36:53 +00:00
|
|
|
|
2014-01-19 22:35:05 +00:00
|
|
|
select {
|
2014-02-24 15:29:33 +00:00
|
|
|
case <-waitc:
|
|
|
|
return fmt.Errorf("server exited: %v", w.serverErr)
|
2014-01-19 22:35:05 +00:00
|
|
|
case <-timeoutc:
|
2014-02-24 15:29:33 +00:00
|
|
|
return fmt.Errorf("server never became reachable: %v", w.serverErr)
|
2014-01-19 22:35:05 +00:00
|
|
|
case <-upc:
|
2014-02-24 15:29:33 +00:00
|
|
|
if err := w.Ping(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-01-19 22:35:05 +00:00
|
|
|
// Success.
|
2013-07-21 05:36:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-02-24 15:29:33 +00:00
|
|
|
// Ping returns an error if the world's camlistored is not running.
|
|
|
|
func (w *World) Ping() error {
|
|
|
|
if atomic.LoadInt32(&w.isRunning) != 1 {
|
|
|
|
return fmt.Errorf("camlistored not running: %v", w.serverErr)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-07-21 05:36:53 +00:00
|
|
|
func (w *World) Stop() {
|
2013-07-30 19:57:50 +00:00
|
|
|
if w == nil {
|
|
|
|
return
|
|
|
|
}
|
devcam test: do not "recurse" temp GOPATH, docs, couple more options.
Problem: make.go creates an isolated temp gopath ./tmp/build-gopath. The
integration tests make use of that gopath (by running make.go) to build
the tools, and run the test world in it. Similarly, devcam test uses
make.go to setup that temp gopath, and runs the tests from the source
files in that gopath. Consequently, when the integration tests are run
through devcam test, even though they're run from the temp gopath, they
would use the make.go in it, which would create a nested temp gopath
(CAMLIROOT/tmp/build-gopath/src/camlistore.org/tmp/build-gopath) in
which to run the tests.
This patch addresses this issue by creating a new flag (-envGoPath), and
the corresponding env var (CAMLI_MAKE_USEGOPATH), which tells make.go
not to create a new temporary gopath (and hence not to mirror any
files), and to rely on the already set GOPATH env var instead.
Also refactored make.go a bit, and added a couple options and doc to
devcam test.
Change-Id: Ia8a5d7a31e6e317f05218d9e18fb886001cd19cb
2014-08-06 16:47:42 +00:00
|
|
|
if err := w.server.Process.Kill(); err != nil {
|
|
|
|
log.Fatalf("killed failed: %v", err)
|
|
|
|
}
|
2013-07-21 05:36:53 +00:00
|
|
|
|
|
|
|
if d := w.tempDir; d != "" {
|
|
|
|
os.RemoveAll(d)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-05 02:49:15 +00:00
|
|
|
func (w *World) NewPermanode(t *testing.T) blob.Ref {
|
2014-02-24 18:48:28 +00:00
|
|
|
if err := w.Ping(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-02-05 02:49:15 +00:00
|
|
|
out := MustRunCmd(t, w.Cmd("camput", "permanode"))
|
|
|
|
br, ok := blob.Parse(strings.TrimSpace(out))
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("Expected permanode in camput stdout; got %q", out)
|
|
|
|
}
|
|
|
|
return br
|
|
|
|
}
|
|
|
|
|
2013-07-21 05:36:53 +00:00
|
|
|
func (w *World) Cmd(binary string, args ...string) *exec.Cmd {
|
2014-01-07 04:01:07 +00:00
|
|
|
return w.CmdWithEnv(binary, os.Environ(), args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *World) CmdWithEnv(binary string, env []string, args ...string) *exec.Cmd {
|
2014-08-05 21:49:35 +00:00
|
|
|
hasVerbose := func() bool {
|
|
|
|
for _, v := range args {
|
|
|
|
if v == "-verbose" || v == "--verbose" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
var cmd *exec.Cmd
|
2013-07-21 05:36:53 +00:00
|
|
|
switch binary {
|
|
|
|
case "camget", "camput", "camtool", "cammount":
|
2014-08-05 21:49:35 +00:00
|
|
|
// TODO(mpl): lift the camput restriction when we have a unified logging mechanism
|
|
|
|
if binary == "camput" && !hasVerbose() {
|
|
|
|
// camput and camtool are the only ones to have a -verbose flag through cmdmain
|
|
|
|
// but camtool is never used. (and cammount does not even have a -verbose).
|
|
|
|
args = append([]string{"-verbose"}, args...)
|
|
|
|
}
|
|
|
|
cmd = exec.Command(filepath.Join(w.camRoot, "bin", binary), args...)
|
2013-07-21 05:36:53 +00:00
|
|
|
clientConfigDir := filepath.Join(w.camRoot, "config", "dev-client-dir")
|
|
|
|
cmd.Env = append([]string{
|
|
|
|
"CAMLI_CONFIG_DIR=" + clientConfigDir,
|
|
|
|
// Respected by env expansions in config/dev-client-dir/client-config.json:
|
|
|
|
"CAMLI_SERVER=" + w.ServerBaseURL(),
|
2014-01-20 21:54:42 +00:00
|
|
|
"CAMLI_SECRET_RING=" + w.SecretRingFile(),
|
|
|
|
"CAMLI_KEYID=" + w.ClientIdentity(),
|
2013-07-21 05:36:53 +00:00
|
|
|
"CAMLI_AUTH=userpass:testuser:passTestWorld",
|
2014-01-07 04:01:07 +00:00
|
|
|
}, env...)
|
2013-07-21 05:36:53 +00:00
|
|
|
default:
|
|
|
|
panic("Unknown binary " + binary)
|
|
|
|
}
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *World) ServerBaseURL() string {
|
|
|
|
return fmt.Sprintf("http://127.0.0.1:%d", w.port)
|
|
|
|
}
|
|
|
|
|
|
|
|
var theWorld *World
|
|
|
|
|
|
|
|
// GetWorld returns (creating if necessary) a test singleton world.
|
|
|
|
// It calls Fatal on the provided test if there are problems.
|
|
|
|
func GetWorld(t *testing.T) *World {
|
|
|
|
w := theWorld
|
|
|
|
if w == nil {
|
|
|
|
var err error
|
|
|
|
w, err = NewWorld()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error finding test world: %v", err)
|
|
|
|
}
|
|
|
|
err = w.Start()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error starting test world: %v", err)
|
|
|
|
}
|
|
|
|
theWorld = w
|
|
|
|
}
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2013-07-30 19:57:50 +00:00
|
|
|
// GetWorldMaybe returns the current World. It might be nil.
|
|
|
|
func GetWorldMaybe(t *testing.T) *World {
|
|
|
|
return theWorld
|
|
|
|
}
|
|
|
|
|
2013-07-21 05:36:53 +00:00
|
|
|
// RunCmd runs c (which is assumed to be something short-lived, like a
|
|
|
|
// camput or camget command), capturing its stdout for return, and
|
|
|
|
// also capturing its stderr, just in the case of errors.
|
|
|
|
// If there's an error, the return error fully describes the command and
|
|
|
|
// all output.
|
|
|
|
func RunCmd(c *exec.Cmd) (output string, err error) {
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
c.Stderr = &stderr
|
|
|
|
c.Stdout = &stdout
|
|
|
|
err = c.Run()
|
|
|
|
if err != nil {
|
2014-08-05 21:49:35 +00:00
|
|
|
return "", fmt.Errorf("Error running command %+v: Stdout:\n%s\nStderr:\n%s\n", c, stdout.String(), stderr.String())
|
2013-07-21 05:36:53 +00:00
|
|
|
}
|
|
|
|
return stdout.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MustRunCmd wraps RunCmd, failing t if RunCmd returns an error.
|
|
|
|
func MustRunCmd(t *testing.T, c *exec.Cmd) string {
|
|
|
|
out, err := RunCmd(c)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
2014-01-20 21:54:42 +00:00
|
|
|
|
|
|
|
// ClientIdentity returns the GPG identity to use in World tests, suitable
|
2014-03-31 14:30:54 +00:00
|
|
|
// for setting in CAMLI_KEYID.
|
2014-01-20 21:54:42 +00:00
|
|
|
func (w *World) ClientIdentity() string {
|
|
|
|
return "26F5ABDA"
|
|
|
|
}
|
|
|
|
|
|
|
|
// SecretRingFile returns the GnuPG secret ring, suitable for setting
|
|
|
|
// in CAMLI_SECRET_RING.
|
|
|
|
func (w *World) SecretRingFile() string {
|
|
|
|
return filepath.Join(w.camRoot, "pkg", "jsonsign", "testdata", "test-secring.gpg")
|
|
|
|
}
|
2014-02-07 23:02:22 +00:00
|
|
|
|
|
|
|
// SearchHandlerPath returns the path to the search handler, with trailing slash.
|
|
|
|
func (w *World) SearchHandlerPath() string { return "/my-search/" }
|