github.com/getgauge/gauge@v1.6.9/runner/runner.go (about)

     1  /*----------------------------------------------------------------
     2   *  Copyright (c) ThoughtWorks, Inc.
     3   *  Licensed under the Apache License, Version 2.0
     4   *  See LICENSE in the project root for license information.
     5   *----------------------------------------------------------------*/
     6  
     7  package runner
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"net"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"runtime"
    17  	"strconv"
    18  	"strings"
    19  
    20  	"github.com/getgauge/common"
    21  	"github.com/getgauge/gauge-proto/go/gauge_messages"
    22  	"github.com/getgauge/gauge/config"
    23  	"github.com/getgauge/gauge/conn"
    24  	"github.com/getgauge/gauge/logger"
    25  	"github.com/getgauge/gauge/manifest"
    26  	"github.com/getgauge/gauge/plugin"
    27  	"github.com/getgauge/gauge/version"
    28  )
    29  
    30  type Runner interface {
    31  	ExecuteAndGetStatus(m *gauge_messages.Message) *gauge_messages.ProtoExecutionResult
    32  	ExecuteMessageWithTimeout(m *gauge_messages.Message) (*gauge_messages.Message, error)
    33  	Alive() bool
    34  	Kill() error
    35  	Connection() net.Conn
    36  	IsMultithreaded() bool
    37  	Info() *RunnerInfo
    38  	Pid() int
    39  }
    40  
    41  type RunnerInfo struct {
    42  	Id          string
    43  	Name        string
    44  	Version     string
    45  	Description string
    46  	Run         struct {
    47  		Windows []string
    48  		Linux   []string
    49  		Darwin  []string
    50  	}
    51  	Init struct {
    52  		Windows []string
    53  		Linux   []string
    54  		Darwin  []string
    55  	}
    56  	Lib                 string
    57  	Multithreaded       bool
    58  	GaugeVersionSupport version.VersionSupport
    59  	LspLangId           string
    60  	GRPCSupport         bool
    61  	ConceptMessages     bool
    62  	Killed				bool
    63  }
    64  
    65  func ExecuteInitHookForRunner(language string) error {
    66  	if err := config.SetProjectRoot([]string{}); err != nil {
    67  		return err
    68  	}
    69  	runnerInfo, err := GetRunnerInfo(language)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	var command []string
    74  	switch runtime.GOOS {
    75  	case "windows":
    76  		command = runnerInfo.Init.Windows
    77  	case "darwin":
    78  		command = runnerInfo.Init.Darwin
    79  	default:
    80  		command = runnerInfo.Init.Linux
    81  	}
    82  
    83  	languageJSONFilePath, err := plugin.GetLanguageJSONFilePath(language)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	runnerDir := filepath.Dir(languageJSONFilePath)
    89  	logger.Debugf(true, "Running init hook command => %s", command)
    90  	writer := logger.NewLogWriter(language, true, 0)
    91  	cmd, err := common.ExecuteCommand(command, runnerDir, writer.Stdout, writer.Stderr)
    92  
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	return cmd.Wait()
    98  }
    99  
   100  func GetRunnerInfo(language string) (*RunnerInfo, error) {
   101  	runnerInfo := new(RunnerInfo)
   102  	languageJSONFilePath, err := plugin.GetLanguageJSONFilePath(language)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	contents, err := common.ReadFileContents(languageJSONFilePath)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	err = json.Unmarshal([]byte(contents), &runnerInfo)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	return runnerInfo, nil
   117  }
   118  
   119  func errorResult(message string) *gauge_messages.ProtoExecutionResult {
   120  	return &gauge_messages.ProtoExecutionResult{Failed: true, ErrorMessage: message, RecoverableError: false}
   121  }
   122  
   123  func runRunnerCommand(manifest *manifest.Manifest, port string, debug bool, writer *logger.LogWriter) (*exec.Cmd, *RunnerInfo, error) {
   124  	var r RunnerInfo
   125  	runnerDir, err := getLanguageJSONFilePath(manifest, &r)
   126  	if err != nil {
   127  		return nil, nil, err
   128  	}
   129  	compatibilityErr := version.CheckCompatibility(version.CurrentGaugeVersion, &r.GaugeVersionSupport)
   130  	if compatibilityErr != nil {
   131  		return nil, nil, fmt.Errorf("Compatibility error. %s", compatibilityErr.Error())
   132  	}
   133  	command := getOsSpecificCommand(&r)
   134  	env := getCleanEnv(port, os.Environ(), debug, getPluginPaths())
   135  	cmd, err := common.ExecuteCommandWithEnv(command, runnerDir, writer.Stdout, writer.Stderr, env)
   136  	return cmd, &r, err
   137  }
   138  
   139  func getPluginPaths() (paths []string) {
   140  	for _, p := range plugin.PluginsWithoutScope() {
   141  		paths = append(paths, p.Path)
   142  	}
   143  	return
   144  }
   145  
   146  func getLanguageJSONFilePath(manifest *manifest.Manifest, r *RunnerInfo) (string, error) {
   147  	languageJSONFilePath, err := plugin.GetLanguageJSONFilePath(manifest.Language)
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  	contents, err := common.ReadFileContents(languageJSONFilePath)
   152  	if err != nil {
   153  		return "", err
   154  	}
   155  	err = json.Unmarshal([]byte(contents), r)
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  	return filepath.Dir(languageJSONFilePath), nil
   160  }
   161  
   162  func (r *LegacyRunner) waitAndGetErrorMessage() {
   163  	go func() {
   164  		pState, err := r.Cmd.Process.Wait()
   165  		r.mutex.Lock()
   166  		r.Cmd.ProcessState = pState
   167  		r.mutex.Unlock()
   168  		if err != nil {
   169  			logger.Debugf(true, "Runner exited with error: %s", err)
   170  			r.errorChannel <- fmt.Errorf("Runner exited with error: %s", err.Error())
   171  		}
   172  		if !pState.Success() {
   173  			r.errorChannel <- fmt.Errorf("Runner with pid %d quit unexpectedly(%s)", pState.Pid(), pState.String())
   174  		}
   175  	}()
   176  }
   177  
   178  func getCleanEnv(port string, env []string, debug bool, pathToAdd []string) []string {
   179  	isPresent := false
   180  	for i, k := range env {
   181  		key := strings.TrimSpace(strings.Split(k, "=")[0])
   182  		//clear environment variable common.GaugeInternalPortEnvName
   183  		if key == common.GaugeInternalPortEnvName {
   184  			isPresent = true
   185  			env[i] = common.GaugeInternalPortEnvName + "=" + port
   186  		} else if strings.ToUpper(key) == "PATH" {
   187  			path := os.Getenv("PATH")
   188  			for _, p := range pathToAdd {
   189  				path += string(os.PathListSeparator) + p
   190  			}
   191  			env[i] = "PATH=" + path
   192  		}
   193  	}
   194  	if !isPresent {
   195  		env = append(env, common.GaugeInternalPortEnvName+"="+port)
   196  	}
   197  	if debug {
   198  		env = append(env, "debugging=true")
   199  	}
   200  	return env
   201  }
   202  
   203  func getOsSpecificCommand(r *RunnerInfo) []string {
   204  	var command []string
   205  	switch runtime.GOOS {
   206  	case "windows":
   207  		command = r.Run.Windows
   208  	case "darwin":
   209  		command = r.Run.Darwin
   210  	default:
   211  		command = r.Run.Linux
   212  	}
   213  	return command
   214  }
   215  
   216  func Start(manifest *manifest.Manifest, stream int, killChannel chan bool, debug bool) (Runner, error) {
   217  	ri, err := GetRunnerInfo(manifest.Language)
   218  	if err == nil && ri.GRPCSupport {
   219  		return StartGrpcRunner(manifest, os.Stdout, os.Stderr, config.RunnerRequestTimeout(), true)
   220  	}
   221  
   222  	writer := logger.NewLogWriter(manifest.Language, true, stream)
   223  	port, err := conn.GetPortFromEnvironmentVariable(common.GaugePortEnvName)
   224  	if err != nil {
   225  		port = 0
   226  	}
   227  	handler, err := conn.NewGaugeConnectionHandler(port, nil)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	logger.Debugf(true, "Starting %s runner", manifest.Language)
   232  	runner, err := StartLegacyRunner(manifest, strconv.Itoa(handler.ConnectionPortNumber()), writer, killChannel, debug)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	err = connect(handler, runner)
   237  	return runner, err
   238  }
   239  
   240  func connect(h *conn.GaugeConnectionHandler, runner *LegacyRunner) error {
   241  	connection, connErr := h.AcceptConnection(config.RunnerConnectionTimeout(), runner.errorChannel)
   242  	if connErr != nil {
   243  		logger.Debugf(true, "Runner connection error: %s", connErr)
   244  		if err := runner.killRunner(); err != nil {
   245  			logger.Debugf(true, "Error while killing runner: %s", err)
   246  		}
   247  		return connErr
   248  	}
   249  	runner.connection = connection
   250  	logger.Debug(true, "Established connection to runner.")
   251  	return nil
   252  }