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 }