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  }