github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/test/world.go (about)

     1  /*
     2  Copyright 2013 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package test
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"log"
    25  	"net"
    26  	"net/http"
    27  	"os"
    28  	"os/exec"
    29  	"path/filepath"
    30  	"strconv"
    31  	"testing"
    32  	"time"
    33  
    34  	"camlistore.org/pkg/osutil"
    35  )
    36  
    37  // World defines an integration test world.
    38  //
    39  // It's used to run the actual Camlistore binaries (camlistored,
    40  // camput, camget, camtool, etc) together in large tests, including
    41  // building them, finding them, and wiring them up in an isolated way.
    42  type World struct {
    43  	camRoot  string // typically $GOPATH[0]/src/camlistore.org
    44  	tempDir  string
    45  	listener net.Listener // randomly chosen 127.0.0.1 port for the server
    46  	port     int
    47  
    48  	server   *exec.Cmd
    49  	cammount *os.Process
    50  }
    51  
    52  // NewWorld returns a new test world.
    53  // It requires that GOPATH is set to find the "camlistore.org" root.
    54  func NewWorld() (*World, error) {
    55  	if os.Getenv("GOPATH") == "" {
    56  		return nil, errors.New("GOPATH environment variable isn't set; required to run Camlistore integration tests")
    57  	}
    58  	root, err := osutil.GoPackagePath("camlistore.org")
    59  	if err == os.ErrNotExist {
    60  		return nil, errors.New("Directory \"camlistore.org\" not found under GOPATH/src; can't run Camlistore integration tests.")
    61  	}
    62  	if err != nil {
    63  		return nil, fmt.Errorf("Error searching for \"camlistore.org\" under GOPATH: %v", err)
    64  	}
    65  	ln, err := net.Listen("tcp", "127.0.0.1:0")
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	return &World{
    71  		camRoot:  root,
    72  		listener: ln,
    73  		port:     ln.Addr().(*net.TCPAddr).Port,
    74  	}, nil
    75  }
    76  
    77  // Start builds the Camlistore binaries and starts a server.
    78  func (w *World) Start() error {
    79  	var err error
    80  	w.tempDir, err = ioutil.TempDir("", "camlistore-test-")
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	// Build.
    86  	{
    87  		cmd := exec.Command("go", "run", "make.go")
    88  		cmd.Dir = w.camRoot
    89  		log.Print("Running make.go to build camlistore binaries for testing...")
    90  		out, err := cmd.CombinedOutput()
    91  		if err != nil {
    92  			return fmt.Errorf("Error building world: %v, %s", err, string(out))
    93  		}
    94  		log.Print("Ran make.go.")
    95  	}
    96  
    97  	// Start camlistored.
    98  	{
    99  		w.server = exec.Command(
   100  			filepath.Join(w.camRoot, "bin", "camlistored"),
   101  			"--openbrowser=false",
   102  			"--configfile="+filepath.Join(w.camRoot, "pkg", "test", "testdata", "server-config.json"),
   103  			"--listen=FD:3",
   104  			"--pollparent=true",
   105  		)
   106  		var buf bytes.Buffer
   107  		w.server.Stdout = &buf
   108  		w.server.Stderr = &buf
   109  		w.server.Dir = w.tempDir
   110  		w.server.Env = append(os.Environ(),
   111  			"CAMLI_DEBUG=1",
   112  			"CAMLI_ROOT="+w.tempDir,
   113  			"CAMLI_SECRET_RING="+filepath.Join(w.camRoot, filepath.FromSlash("pkg/jsonsign/testdata/test-secring.gpg")),
   114  			"CAMLI_BASE_URL=http://127.0.0.1:"+strconv.Itoa(w.port),
   115  		)
   116  		listenerFD, err := w.listener.(*net.TCPListener).File()
   117  		if err != nil {
   118  			return err
   119  		}
   120  		w.server.ExtraFiles = []*os.File{listenerFD}
   121  		if err := w.server.Start(); err != nil {
   122  			return fmt.Errorf("Starting camlistored: %v", err)
   123  		}
   124  		waitc := make(chan error, 1)
   125  		go func() {
   126  			waitc <- w.server.Wait()
   127  		}()
   128  		upc := make(chan bool)
   129  		timeoutc := make(chan bool)
   130  		go func() {
   131  			for i := 0; i < 100; i++ {
   132  				res, err := http.Get("http://127.0.0.1:" + strconv.Itoa(w.port))
   133  				if err == nil {
   134  					res.Body.Close()
   135  					upc <- true
   136  					return
   137  				}
   138  				time.Sleep(50 * time.Millisecond)
   139  			}
   140  			timeoutc <- true
   141  		}()
   142  
   143  		select {
   144  		case err := <-waitc:
   145  			return fmt.Errorf("server exited: %v: %s", err, buf.String())
   146  		case <-timeoutc:
   147  			return errors.New("server never became reachable")
   148  		case <-upc:
   149  			// Success.
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  func (w *World) Stop() {
   156  	if w == nil {
   157  		return
   158  	}
   159  	w.server.Process.Kill()
   160  
   161  	if d := w.tempDir; d != "" {
   162  		os.RemoveAll(d)
   163  	}
   164  }
   165  
   166  func (w *World) Cmd(binary string, args ...string) *exec.Cmd {
   167  	return w.CmdWithEnv(binary, os.Environ(), args...)
   168  }
   169  
   170  func (w *World) CmdWithEnv(binary string, env []string, args ...string) *exec.Cmd {
   171  	cmd := exec.Command(filepath.Join(w.camRoot, "bin", binary), args...)
   172  	switch binary {
   173  	case "camget", "camput", "camtool", "cammount":
   174  		clientConfigDir := filepath.Join(w.camRoot, "config", "dev-client-dir")
   175  		cmd.Env = append([]string{
   176  			"CAMLI_CONFIG_DIR=" + clientConfigDir,
   177  			// Respected by env expansions in config/dev-client-dir/client-config.json:
   178  			"CAMLI_SERVER=" + w.ServerBaseURL(),
   179  			"CAMLI_SECRET_RING=" + w.SecretRingFile(),
   180  			"CAMLI_KEYID=" + w.ClientIdentity(),
   181  			"CAMLI_AUTH=userpass:testuser:passTestWorld",
   182  		}, env...)
   183  	default:
   184  		panic("Unknown binary " + binary)
   185  	}
   186  	return cmd
   187  }
   188  
   189  func (w *World) ServerBaseURL() string {
   190  	return fmt.Sprintf("http://127.0.0.1:%d", w.port)
   191  }
   192  
   193  var theWorld *World
   194  
   195  // GetWorld returns (creating if necessary) a test singleton world.
   196  // It calls Fatal on the provided test if there are problems.
   197  func GetWorld(t *testing.T) *World {
   198  	w := theWorld
   199  	if w == nil {
   200  		var err error
   201  		w, err = NewWorld()
   202  		if err != nil {
   203  			t.Fatalf("Error finding test world: %v", err)
   204  		}
   205  		err = w.Start()
   206  		if err != nil {
   207  			t.Fatalf("Error starting test world: %v", err)
   208  		}
   209  		theWorld = w
   210  	}
   211  	return w
   212  }
   213  
   214  // GetWorldMaybe returns the current World. It might be nil.
   215  func GetWorldMaybe(t *testing.T) *World {
   216  	return theWorld
   217  }
   218  
   219  // RunCmd runs c (which is assumed to be something short-lived, like a
   220  // camput or camget command), capturing its stdout for return, and
   221  // also capturing its stderr, just in the case of errors.
   222  // If there's an error, the return error fully describes the command and
   223  // all output.
   224  func RunCmd(c *exec.Cmd) (output string, err error) {
   225  	var stdout, stderr bytes.Buffer
   226  	c.Stderr = &stderr
   227  	c.Stdout = &stdout
   228  	err = c.Run()
   229  	if err != nil {
   230  		return "", fmt.Errorf("Error running command %+v: Stdout:\n%s\nStderrr:\n%s\n", c, stdout.String(), stderr.String())
   231  	}
   232  	return stdout.String(), nil
   233  }
   234  
   235  // MustRunCmd wraps RunCmd, failing t if RunCmd returns an error.
   236  func MustRunCmd(t *testing.T, c *exec.Cmd) string {
   237  	out, err := RunCmd(c)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	return out
   242  }
   243  
   244  // ClientIdentity returns the GPG identity to use in World tests, suitable
   245  // for setting in CAMLI_CLIENT_IDENTITY.
   246  func (w *World) ClientIdentity() string {
   247  	return "26F5ABDA"
   248  }
   249  
   250  // SecretRingFile returns the GnuPG secret ring, suitable for setting
   251  // in CAMLI_SECRET_RING.
   252  func (w *World) SecretRingFile() string {
   253  	return filepath.Join(w.camRoot, "pkg", "jsonsign", "testdata", "test-secring.gpg")
   254  }