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 }