github.com/bazelbuild/rules_webtesting@v0.2.0/go/wtl/wtl.go (about) 1 // Copyright 2016 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Binary launcher is used to manage the envrionment for web tests and start the underlying test. 16 package wtl 17 18 import ( 19 "context" 20 "flag" 21 "fmt" 22 "log" 23 "os" 24 "os/exec" 25 "os/signal" 26 "syscall" 27 "time" 28 29 "github.com/bazelbuild/rules_webtesting/go/bazel" 30 "github.com/bazelbuild/rules_webtesting/go/cmdhelper" 31 "github.com/bazelbuild/rules_webtesting/go/errors" 32 "github.com/bazelbuild/rules_webtesting/go/metadata" 33 "github.com/bazelbuild/rules_webtesting/go/wtl/diagnostics" 34 "github.com/bazelbuild/rules_webtesting/go/wtl/environment" 35 "github.com/bazelbuild/rules_webtesting/go/wtl/environment/external" 36 "github.com/bazelbuild/rules_webtesting/go/wtl/environment/local" 37 "github.com/bazelbuild/rules_webtesting/go/wtl/environment/sauce" 38 "github.com/bazelbuild/rules_webtesting/go/wtl/proxy" 39 "github.com/bazelbuild/rules_webtesting/go/wtl/proxy/driverhub" 40 "github.com/bazelbuild/rules_webtesting/go/wtl/proxy/driverhub/drivermu" 41 "github.com/bazelbuild/rules_webtesting/go/wtl/proxy/driverhub/googlescreenshot" 42 "github.com/bazelbuild/rules_webtesting/go/wtl/proxy/driverhub/quithandler" 43 "github.com/bazelbuild/rules_webtesting/go/wtl/proxy/driverhub/scripttimeout" 44 "github.com/bazelbuild/rules_webtesting/go/wtl/proxy/healthz" 45 ) 46 47 type envProvider func(m *metadata.Metadata, d diagnostics.Diagnostics) (environment.Env, error) 48 49 var envProviders = map[string]envProvider{} 50 51 func init() { 52 // Configure Environments. 53 RegisterEnvProviderFunc("external", external.NewEnv) 54 RegisterEnvProviderFunc("local", local.NewEnv) 55 RegisterEnvProviderFunc("sauce", sauce.NewEnv) 56 57 // Configure HTTP Handlers 58 proxy.AddHTTPHandlerProvider("/wd/hub/", driverhub.HTTPHandlerProvider) 59 proxy.AddHTTPHandlerProvider("/healthz", healthz.HTTPHandlerProvider) 60 61 // Configure WebDriver handlers. 62 driverhub.HandlerProviderFunc(quithandler.ProviderFunc) 63 driverhub.HandlerProviderFunc(scripttimeout.ProviderFunc) 64 driverhub.HandlerProviderFunc(googlescreenshot.ProviderFunc) 65 66 // drivermu should always be last. 67 driverhub.HandlerProviderFunc(drivermu.ProviderFunc) 68 } 69 70 // RegisterEnvProviderFunc adds a new env provider. 71 func RegisterEnvProviderFunc(name string, p envProvider) { 72 envProviders[name] = p 73 } 74 75 // Run runs the test. 76 func Run(d diagnostics.Diagnostics, testPath, mdPath string, debuggerPort int) int { 77 ctx := context.Background() 78 79 testTerminated := make(chan os.Signal) 80 signal.Notify(testTerminated, syscall.SIGTERM, syscall.SIGINT) 81 82 proxyStarted := make(chan error) 83 envStarted := make(chan error) 84 testFinished := make(chan int) 85 envShutdown := make(chan error) 86 87 mdFile, err := bazel.Runfile(mdPath) 88 if err != nil { 89 log.Print(err) 90 return 127 91 } 92 93 md, err := metadata.FromFile(mdFile, nil) 94 if err != nil { 95 d.Severe(err) 96 return 127 97 } 98 99 if debuggerPort != 0 { 100 md.DebuggerPort = debuggerPort 101 } 102 103 testExe, err := bazel.Runfile(testPath) 104 if err != nil { 105 d.Severe(err) 106 return 127 107 } 108 109 env, err := buildEnv(md, d) 110 if err != nil { 111 d.Severe(err) 112 return 127 113 } 114 115 p, err := proxy.New(env, md, d) 116 if err != nil { 117 d.Severe(err) 118 return 127 119 } 120 121 tmpDir, err := bazel.NewTmpDir("test") 122 if err != nil { 123 d.Severe(err) 124 return 127 125 } 126 127 testCmd := exec.Command(testExe, flag.Args()...) 128 129 envVars := map[string]string{ 130 "WEB_TEST_HTTP_SERVER": fmt.Sprintf("http://%s", p.HTTPAddress), 131 "WEB_TEST_WEBDRIVER_SERVER": fmt.Sprintf("http://%s/wd/hub", p.HTTPAddress), 132 "TEST_TMPDIR": tmpDir, 133 "WEB_TEST_TMPDIR": bazel.TestTmpDir(), 134 "WEB_TEST_TARGET": testPath, 135 } 136 137 if p.HTTPSAddress != "" { 138 envVars["WEB_TEST_HTTPS_SERVER"] = fmt.Sprintf("https://%s", p.HTTPSAddress) 139 } 140 141 testCmd.Env = cmdhelper.BulkUpdateEnv(os.Environ(), envVars) 142 143 testCmd.Stdout = os.Stdout 144 testCmd.Stderr = os.Stderr 145 testCmd.Stdin = os.Stdin 146 147 go func() { 148 envStarted <- env.SetUp(ctx) 149 }() 150 151 shutdownFunc := func() { 152 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 153 defer cancel() 154 // When the environment shutdowns or fails to shutdown, a message will be sent to envShutdown. 155 go func() { 156 var errors []error 157 158 if err := p.Shutdown(ctx); err != nil { 159 errors = append(errors, err) 160 } 161 if err := env.TearDown(ctx); err != nil { 162 errors = append(errors, err) 163 } 164 switch len(errors) { 165 case 0: 166 envShutdown <- nil 167 case 1: 168 envShutdown <- errors[0] 169 default: 170 envShutdown <- fmt.Errorf("errors shutting down environment: %v", errors) 171 } 172 }() 173 174 select { 175 case <-testTerminated: 176 d.Warning(errors.New("WTL", "test timed out during environment shutdown.")) 177 case <-ctx.Done(): 178 d.Warning(errors.New("WTL", "environment shutdown took longer than 5 seconds.")) 179 case err := <-envShutdown: 180 if err != nil { 181 d.Warning(err) 182 } 183 } 184 } 185 186 go func() { 187 proxyStarted <- p.Start(ctx) 188 }() 189 190 for done := false; !done; { 191 select { 192 case <-testTerminated: 193 return 0x8f 194 case err := <-proxyStarted: 195 if err != nil { 196 d.Severe(err) 197 return 127 198 } 199 done = true 200 case err := <-envStarted: 201 if err != nil { 202 d.Severe(err) 203 return 127 204 } 205 defer shutdownFunc() 206 } 207 } 208 209 go func() { 210 if status := testCmd.Run(); status != nil { 211 log.Printf("test failed %v", status) 212 if ee, ok := err.(*exec.ExitError); ok { 213 if ws, ok := ee.Sys().(syscall.WaitStatus); ok { 214 testFinished <- ws.ExitStatus() 215 return 216 } 217 } 218 testFinished <- 1 219 return 220 } 221 testFinished <- 0 222 }() 223 224 for { 225 select { 226 case <-testTerminated: 227 return 0x8f 228 case err := <-envStarted: 229 if err != nil { 230 d.Severe(err) 231 return 127 232 } 233 defer shutdownFunc() 234 case status := <-testFinished: 235 return status 236 } 237 } 238 } 239 240 func buildEnv(m *metadata.Metadata, d diagnostics.Diagnostics) (environment.Env, error) { 241 p, ok := envProviders[m.Environment] 242 if !ok { 243 return nil, fmt.Errorf("unknown environment: %s", m.Environment) 244 } 245 return p(m, d) 246 }