github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/updater/command/command_test.go (about)

     1  // Copyright 2016 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package command
     5  
     6  import (
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/keybase/go-logging"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  var testLog = &logging.Logger{Module: "test"}
    22  
    23  func TestExecEmpty(t *testing.T) {
    24  	result, err := Exec("", nil, time.Second, testLog)
    25  	assert.EqualError(t, err, "No command")
    26  	assert.Equal(t, result.Stdout.String(), "")
    27  	assert.Equal(t, result.Stderr.String(), "")
    28  }
    29  
    30  func TestExecInvalid(t *testing.T) {
    31  	result, err := Exec("invalidexecutable", nil, time.Second, testLog)
    32  	assert.Error(t, err)
    33  	require.True(t, strings.HasPrefix(err.Error(), `exec: "invalidexecutable": executable file not found in `))
    34  	assert.Equal(t, result.Stdout.String(), "")
    35  	assert.Equal(t, result.Stderr.String(), "")
    36  }
    37  
    38  func TestExecEcho(t *testing.T) {
    39  	if runtime.GOOS == "windows" {
    40  		t.Skip("Unsupported on windows")
    41  	}
    42  	result, err := Exec("echo", []string{"arg1", "arg2"}, time.Second, testLog)
    43  	assert.NoError(t, err)
    44  	assert.Equal(t, result.Stdout.String(), "arg1 arg2\n")
    45  }
    46  
    47  func TestExecNil(t *testing.T) {
    48  	execCmd := func(name string, arg ...string) *exec.Cmd {
    49  		return nil
    50  	}
    51  	_, err := execWithFunc("echo", []string{"arg1", "arg2"}, nil, execCmd, time.Second, testLog)
    52  	require.Error(t, err)
    53  }
    54  
    55  func TestExecTimeout(t *testing.T) {
    56  	start := time.Now()
    57  	timeout := 10 * time.Millisecond
    58  	result, err := Exec("sleep", []string{"10"}, timeout, testLog)
    59  	elapsed := time.Since(start)
    60  	t.Logf("We elapsed %s", elapsed)
    61  	if elapsed < timeout {
    62  		t.Error("We didn't actually sleep more than a second")
    63  	}
    64  	assert.Equal(t, result.Stdout.String(), "")
    65  	assert.Equal(t, result.Stderr.String(), "")
    66  	require.EqualError(t, err, "Timed out")
    67  }
    68  
    69  func TestExecBadTimeout(t *testing.T) {
    70  	result, err := Exec("sleep", []string{"1"}, -time.Second, testLog)
    71  	assert.Equal(t, result.Stdout.String(), "")
    72  	assert.Equal(t, result.Stderr.String(), "")
    73  	assert.EqualError(t, err, "Invalid timeout: -1s")
    74  }
    75  
    76  type testObj struct {
    77  	StringVar string        `json:"stringVar"`
    78  	NumberVar int           `json:"numberVar"`
    79  	BoolVar   bool          `json:"boolVar"`
    80  	ObjectVar testNestedObj `json:"objectVar"`
    81  }
    82  
    83  type testNestedObj struct {
    84  	FloatVar float64 `json:"floatVar"`
    85  }
    86  
    87  const testJSON = `{
    88    "stringVar": "hi",
    89    "numberVar": 1,
    90    "boolVar": true,
    91    "objectVar": {
    92      "floatVar": 1.23
    93    }
    94  }`
    95  
    96  var testVal = testObj{
    97  	StringVar: "hi",
    98  	NumberVar: 1,
    99  	BoolVar:   true,
   100  	ObjectVar: testNestedObj{
   101  		FloatVar: 1.23,
   102  	},
   103  }
   104  
   105  func TestExecForJSON(t *testing.T) {
   106  	var testValOut testObj
   107  	err := ExecForJSON("echo", []string{testJSON}, &testValOut, time.Second, testLog)
   108  	assert.NoError(t, err)
   109  	t.Logf("Out: %#v", testValOut)
   110  	if !reflect.DeepEqual(testVal, testValOut) {
   111  		t.Errorf("Invalid object: %#v", testValOut)
   112  	}
   113  }
   114  
   115  func TestExecForJSONEmpty(t *testing.T) {
   116  	err := ExecForJSON("", nil, nil, time.Second, testLog)
   117  	require.Error(t, err)
   118  }
   119  
   120  func TestExecForJSONInvalidObject(t *testing.T) {
   121  	// Valid JSON, but not the right object
   122  	validJSON := `{"stringVar": true}`
   123  	var testValOut testObj
   124  	err := ExecForJSON("echo", []string{validJSON}, &testValOut, time.Second, testLog)
   125  	require.Error(t, err)
   126  	t.Logf("Error: %s", err)
   127  }
   128  
   129  // TestExecForJSONAddingInvalidInput tests valid JSON input with invalid input after.
   130  // We still succeed in this case since we got valid input to start.
   131  func TestExecForJSONAddingInvalidInput(t *testing.T) {
   132  	var testValOut testObj
   133  	err := ExecForJSON("echo", []string{testJSON + "bad input"}, &testValOut, time.Second, testLog)
   134  	assert.NoError(t, err)
   135  	t.Logf("Out: %#v", testValOut)
   136  	if !reflect.DeepEqual(testVal, testValOut) {
   137  		t.Errorf("Invalid object: %#v", testValOut)
   138  	}
   139  }
   140  
   141  func TestExecForJSONTimeout(t *testing.T) {
   142  	var testValOut testObj
   143  	err := ExecForJSON("sleep", []string{"10"}, &testValOut, 10*time.Millisecond, testLog)
   144  	if assert.Error(t, err) {
   145  		assert.Equal(t, err.Error(), "Timed out")
   146  	}
   147  }
   148  
   149  // TestExecTimeoutProcessKilled checks to make sure process is killed after timeout
   150  func TestExecTimeoutProcessKilled(t *testing.T) {
   151  	if runtime.GOOS == "windows" {
   152  		t.Skip("Unsupported on windows")
   153  	}
   154  	result, err := execWithFunc("sleep", []string{"10"}, nil, exec.Command, 10*time.Millisecond, testLog)
   155  	assert.Equal(t, result.Stdout.String(), "")
   156  	assert.Equal(t, result.Stderr.String(), "")
   157  	assert.Error(t, err)
   158  	require.NotNil(t, result.Process)
   159  	findProcess, _ := os.FindProcess(result.Process.Pid)
   160  	// This should error since killing a non-existant process should error
   161  	perr := findProcess.Kill()
   162  	assert.NotNil(t, perr, "Should have errored killing since killing non-existant process should error")
   163  }
   164  
   165  // TestExecNoExit runs a go binary called test from package go-updater/test,
   166  // that should be installed prior to running the tests.
   167  func TestExecNoExit(t *testing.T) {
   168  	path := filepath.Join(os.Getenv("GOPATH"), "bin", "test")
   169  	_, err := Exec(path, []string{"noexit"}, 10*time.Millisecond, testLog)
   170  	require.EqualError(t, err, "Timed out")
   171  }
   172  
   173  func TestExecOutput(t *testing.T) {
   174  	path := filepath.Join(os.Getenv("GOPATH"), "bin", "test")
   175  	result, err := execWithFunc(path, []string{"output"}, nil, exec.Command, time.Second, testLog)
   176  	assert.NoError(t, err)
   177  	assert.Equal(t, "stdout output\n", result.Stdout.String())
   178  	assert.Equal(t, "stderr output\n", result.Stderr.String())
   179  }
   180  
   181  func TestProgramArgsWith(t *testing.T) {
   182  	assert.Equal(t, []string(nil), Program{Args: nil}.ArgsWith(nil))
   183  	assert.Equal(t, []string(nil), Program{Args: []string{}}.ArgsWith(nil))
   184  	assert.Equal(t, []string{}, Program{Args: nil}.ArgsWith([]string{}))
   185  	assert.Equal(t, []string{}, Program{Args: []string{}}.ArgsWith([]string{}))
   186  	assert.Equal(t, []string{"1"}, Program{Args: []string{"1"}}.ArgsWith(nil))
   187  	assert.Equal(t, []string{"1", "2"}, Program{Args: []string{"1"}}.ArgsWith([]string{"2"}))
   188  	assert.Equal(t, []string{"2"}, Program{Args: []string{}}.ArgsWith([]string{"2"}))
   189  }