github.com/sentienttechnologies/studio-go-runner@v0.0.0-20201118202441-6d21f2ced8ee/internal/runner/run_python.go (about)

     1  // Copyright 2020 (c) Cognizant Digital Business, Evolutionary AI. All rights reserved. Issued under the Apache 2.0 License.
     2  
     3  package runner
     4  
     5  import (
     6  	"context"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/go-stack/stack"
    12  	"github.com/jjeffery/kv"
    13  )
    14  
    15  // PythonRun will use the set of test files to start a python process that will return
    16  // the console output and/or an error.  The caller can specify the max size of the buffer
    17  // used to hold the last (keepLines) of lines from the console.  If tmpDir is specified
    18  // that directory will be used to run the process and will not be removed after the
    19  // function completes, in the case it is blank then the function will generate a directory
    20  // run the python in it then remove it.
    21  //
    22  func PythonRun(testFiles map[string]os.FileMode, tmpDir string, keepLines uint) (output []string, err kv.Error) {
    23  
    24  	output = []string{}
    25  
    26  	// I fthe optional temporary directory is not supplied then we create,
    27  	// use it and then remove it, this allows callers to load data into the
    28  	// directory they supply if they wish
    29  	if len(tmpDir) == 0 {
    30  		// Create a new TMPDIR because the python pip tends to leave dirt behind
    31  		// when doing pip builds etc
    32  		t, errGo := ioutil.TempDir("", "")
    33  		if errGo != nil {
    34  			return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
    35  		}
    36  		defer func() {
    37  			os.RemoveAll(t)
    38  		}()
    39  		tmpDir = t
    40  	}
    41  
    42  	expectedScript := ""
    43  
    44  	for fn, mode := range testFiles {
    45  		assetFN, errGo := filepath.Abs(fn)
    46  		if errGo != nil {
    47  			return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
    48  		}
    49  
    50  		destFN, errGo := filepath.Abs(filepath.Join(tmpDir, filepath.Base(assetFN)))
    51  		if errGo != nil {
    52  			return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
    53  		}
    54  
    55  		if _, err := CopyFile(assetFN, destFN); err != nil {
    56  			return nil, err
    57  		}
    58  
    59  		if errGo = os.Chmod(destFN, mode); errGo != nil {
    60  			return nil, kv.Wrap(errGo).With("destFN", destFN, "stack", stack.Trace().TrimRuntime())
    61  		}
    62  
    63  		if mode == 0700 {
    64  			expectedScript = destFN
    65  		}
    66  	}
    67  
    68  	// Save the output from the run using the last say 10 lines as a default otherwise
    69  	// use the callers specified number of lines if they specified any
    70  	if keepLines == 0 {
    71  		keepLines = 20
    72  	}
    73  	output = make([]string, 0, keepLines)
    74  
    75  	// Now setup is done execute the experiment
    76  	dataC := make(chan *string, 1)
    77  	go func() {
    78  		for {
    79  			select {
    80  			case line := <-dataC:
    81  				if line == nil {
    82  					return
    83  				}
    84  				// Push to the back of the stack of lines, then pop from the front
    85  				output = append(output, *line)
    86  				if len(output) > int(keepLines) {
    87  					output = output[1:]
    88  				}
    89  			}
    90  		}
    91  	}()
    92  
    93  	// When running python change directory into the temporary sandbox we are using
    94  	originalDir, errGo := os.Getwd()
    95  	if errGo != nil {
    96  		return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
    97  	}
    98  
    99  	if tmpDir != originalDir {
   100  		if errGo = os.Chdir(tmpDir); errGo != nil {
   101  			return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
   102  		}
   103  		// Once the python run is done we jump back to the original directory
   104  		defer os.Chdir(originalDir)
   105  	}
   106  
   107  	return output, CmdRun(context.TODO(), expectedScript, dataC)
   108  }