github.com/getgauge/gauge@v1.6.9/runner/legacyRunner.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  	"fmt"
    11  	"io"
    12  	"net"
    13  	"os/exec"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/getgauge/gauge-proto/go/gauge_messages"
    18  	"github.com/getgauge/gauge/config"
    19  	"github.com/getgauge/gauge/conn"
    20  	"github.com/getgauge/gauge/logger"
    21  	"github.com/getgauge/gauge/manifest"
    22  )
    23  
    24  type LegacyRunner struct {
    25  	mutex         *sync.Mutex
    26  	Cmd           *exec.Cmd
    27  	connection    net.Conn
    28  	errorChannel  chan error
    29  	multiThreaded bool
    30  	lostContact   bool
    31  	info          *RunnerInfo
    32  }
    33  
    34  func (r *LegacyRunner) Alive() bool {
    35  	r.mutex.Lock()
    36  	ps := r.Cmd.ProcessState
    37  	r.mutex.Unlock()
    38  	return ps == nil || !ps.Exited()
    39  }
    40  
    41  func (r *LegacyRunner) EnsureConnected() bool {
    42  	if r.lostContact {
    43  		return false
    44  	}
    45  	c := r.connection
    46  	err := c.SetReadDeadline(time.Now())
    47  	if err != nil {
    48  		logger.Fatalf(true, "Unable to SetReadDeadLine on runner: %s", err.Error())
    49  	}
    50  	var one []byte
    51  	_, err = c.Read(one)
    52  	if err == io.EOF {
    53  		r.lostContact = true
    54  		logger.Fatalf(true, "Connection to runner with Pid %d lost. The runner probably quit unexpectedly. Inspect logs for potential reasons. Error : %s", r.Cmd.Process.Pid, err.Error())
    55  	}
    56  	opErr, ok := err.(*net.OpError)
    57  	if ok && !(opErr.Temporary() || opErr.Timeout()) {
    58  		r.lostContact = true
    59  		logger.Fatalf(true, "Connection to runner with Pid %d lost. The runner probably quit unexpectedly. Inspect logs for potential reasons. Error : %s", r.Cmd.Process.Pid, err.Error())
    60  	}
    61  	var zero time.Time
    62  	err = c.SetReadDeadline(zero)
    63  	if err != nil {
    64  		logger.Fatalf(true, "Unable to SetReadDeadLine on runner: %s", err.Error())
    65  	}
    66  
    67  	return true
    68  }
    69  
    70  func (r *LegacyRunner) IsMultithreaded() bool {
    71  	return r.multiThreaded
    72  }
    73  
    74  func (r *LegacyRunner) Kill() error {
    75  	if r.Alive() {
    76  		defer r.connection.Close()
    77  		logger.Debug(true, "Sending kill message to runner.")
    78  		err := conn.SendProcessKillMessage(r.connection)
    79  		if err != nil {
    80  			return err
    81  		}
    82  		exited := make(chan bool, 1)
    83  		go func() {
    84  			for {
    85  				if r.Alive() {
    86  					time.Sleep(100 * time.Millisecond)
    87  				} else {
    88  					exited <- true
    89  					return
    90  				}
    91  			}
    92  		}()
    93  
    94  		select {
    95  		case done := <-exited:
    96  			if done {
    97  				logger.Debugf(true, "Runner with PID:%d has exited", r.Cmd.Process.Pid)
    98  				return nil
    99  			}
   100  		case <-time.After(config.PluginKillTimeout()):
   101  			logger.Warningf(true, "Killing runner with PID:%d forcefully", r.Cmd.Process.Pid)
   102  			return r.killRunner()
   103  		}
   104  	}
   105  	return nil
   106  }
   107  
   108  func (r *LegacyRunner) Connection() net.Conn {
   109  	return r.connection
   110  }
   111  
   112  func (r *LegacyRunner) killRunner() error {
   113  	return r.Cmd.Process.Kill()
   114  }
   115  
   116  // Info gives the information about runner
   117  func (r *LegacyRunner) Info() *RunnerInfo {
   118  	return r.info
   119  }
   120  
   121  func (r *LegacyRunner) Pid() int {
   122  	return r.Cmd.Process.Pid
   123  }
   124  
   125  // ExecuteAndGetStatus invokes the runner with a request and waits for response. error is thrown only when unable to connect to runner
   126  func (r *LegacyRunner) ExecuteAndGetStatus(message *gauge_messages.Message) *gauge_messages.ProtoExecutionResult {
   127  	if !r.EnsureConnected() {
   128  		return nil
   129  	}
   130  	response, err := conn.GetResponseForMessageWithTimeout(message, r.connection, 0)
   131  	if err != nil {
   132  		return &gauge_messages.ProtoExecutionResult{Failed: true, ErrorMessage: err.Error()}
   133  	}
   134  
   135  	if response.GetMessageType() == gauge_messages.Message_ExecutionStatusResponse {
   136  		executionResult := response.GetExecutionStatusResponse().GetExecutionResult()
   137  		if executionResult == nil {
   138  			errMsg := "ProtoExecutionResult obtained is nil"
   139  			logger.Error(true, errMsg)
   140  			return errorResult(errMsg)
   141  		}
   142  		return executionResult
   143  	}
   144  	errMsg := fmt.Sprintf("Expected ExecutionStatusResponse. Obtained: %s", response.GetMessageType())
   145  	logger.Error(true, errMsg)
   146  	return errorResult(errMsg)
   147  }
   148  
   149  func (r *LegacyRunner) ExecuteMessageWithTimeout(message *gauge_messages.Message) (*gauge_messages.Message, error) {
   150  	r.EnsureConnected()
   151  	return conn.GetResponseForMessageWithTimeout(message, r.Connection(), config.RunnerRequestTimeout())
   152  }
   153  
   154  // StartLegacyRunner looks for a runner configuration inside the runner directory
   155  // finds the runner configuration matching to the manifest and executes the commands for the current OS
   156  func StartLegacyRunner(manifest *manifest.Manifest, port string, outputStreamWriter *logger.LogWriter, killChannel chan bool, debug bool) (*LegacyRunner, error) {
   157  	cmd, r, err := runRunnerCommand(manifest, port, debug, outputStreamWriter)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	go func() {
   162  		<-killChannel
   163  		err := cmd.Process.Kill()
   164  		if err != nil {
   165  			logger.Errorf(false, "Unable to kill %s with PID %d : %s", cmd.Path, cmd.Process.Pid, err.Error())
   166  		}
   167  	}()
   168  	// Wait for the process to exit so we will get a detailed error message
   169  	errChannel := make(chan error)
   170  	testRunner := &LegacyRunner{info: r, Cmd: cmd, errorChannel: errChannel, mutex: &sync.Mutex{}, multiThreaded: r.Multithreaded}
   171  	testRunner.waitAndGetErrorMessage()
   172  	return testRunner, nil
   173  }