github.com/splunk/qbec@v0.15.2/vm/internal/ds/exec/exec_test.go (about)

     1  /*
     2     Copyright 2021 Splunk Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package exec
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"os/exec"
    24  	"runtime"
    25  	"testing"
    26  
    27  	"github.com/splunk/qbec/vm/datasource"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestExecBasic(t *testing.T) {
    33  	exe, err := exec.LookPath("qbec-replay-exec")
    34  	if err != nil {
    35  		t.SkipNow()
    36  	}
    37  	a := assert.New(t)
    38  	pwd, err := os.Getwd()
    39  	require.NoError(t, err)
    40  	var tests = []struct {
    41  		inherit       bool
    42  		override      bool
    43  		expectedEnvKV string
    44  	}{
    45  		{true, true, "_akey_=baz"}, {false, true, "_akey_=baz"},
    46  		{true, false, "_akey_=aval"}, {false, false, "<none>"},
    47  	}
    48  	for _, test := range tests {
    49  		t.Run(fmt.Sprintf("inhert_%t_override_%t", test.inherit, test.override), func(t *testing.T) {
    50  			ds := New("replay", "var1")
    51  			os.Setenv("_akey_", "aval")
    52  			defer os.Unsetenv("_akey_")
    53  			env := ""
    54  			if test.override {
    55  				env = `"_akey_": "baz",`
    56  			}
    57  			err = ds.Init(func(name string) (string, error) {
    58  				if name != "var1" {
    59  					return "", fmt.Errorf("invalid call to config provider, want %q got %q", "var1", name)
    60  				}
    61  				return fmt.Sprintf(`
    62  {
    63  	"command": "qbec-replay-exec",
    64  	"args": [ "one", "two" ],
    65  	"env": {
    66  		%s
    67  		"foo": "bar"
    68  	},
    69  	"stdin": "input",
    70  	"inheritEnv": %t
    71  }
    72  `, env, test.inherit), nil
    73  			})
    74  			require.NoError(t, err)
    75  			defer ds.Close()
    76  			a.Equal("replay", ds.Name())
    77  			str, err := ds.Resolve("/foo/bar")
    78  			require.NoError(t, err)
    79  			var data struct {
    80  				DSName  string   `json:"dsName"`
    81  				Command string   `json:"command"`
    82  				Args    []string `json:"args"`
    83  				Dir     string   `json:"dir"`
    84  				Env     []string `json:"env"`
    85  				Input   string   `json:"stdin"`
    86  			}
    87  			err = json.Unmarshal([]byte(str), &data)
    88  			require.NoError(t, err)
    89  			a.Equal("replay", data.DSName)
    90  			a.Equal(exe, data.Command)
    91  			a.EqualValues([]string{"one", "two"}, data.Args)
    92  			a.Equal(pwd, data.Dir)
    93  			a.Contains(data.Env, "foo=bar")
    94  			if test.expectedEnvKV != "<none>" {
    95  				a.Contains(data.Env, test.expectedEnvKV)
    96  			} else {
    97  				a.NotContains(data.Env, "_akey_=")
    98  			}
    99  			a.Contains(data.Env, "__DS_NAME__=replay")
   100  			a.Contains(data.Env, "__DS_PATH__=/foo/bar")
   101  			a.Equal("input", data.Input)
   102  		})
   103  	}
   104  
   105  }
   106  
   107  func TestExecRelativeFilePath(t *testing.T) {
   108  	if runtime.GOOS == "windows" {
   109  		t.Skip("not running exec bit tests on windows")
   110  	}
   111  	ds := New("replay", "var1")
   112  	err := ds.Init(func(name string) (string, error) {
   113  		c := Config{Command: "testdata/exec-bit-set.sh"}
   114  		b, _ := json.Marshal(c)
   115  		return string(b), nil
   116  	})
   117  	require.NoError(t, err)
   118  	defer ds.Close()
   119  	s, err := ds.Resolve("/")
   120  	require.NoError(t, err)
   121  	assert.Equal(t, "{}\n", s)
   122  }
   123  
   124  func TestExecNegative(t *testing.T) {
   125  	if _, err := exec.LookPath("qbec-replay-exec"); err != nil {
   126  		t.SkipNow()
   127  	}
   128  	tests := []struct {
   129  		name         string
   130  		config       Config
   131  		path         string
   132  		cp           datasource.ConfigProvider
   133  		skipWindows  bool
   134  		initAsserter func(t *testing.T, err error)
   135  		asserter     func(t *testing.T, resolved string, err error)
   136  	}{
   137  		{
   138  			name:   "bad-exe",
   139  			config: Config{Command: "non-existent"},
   140  			initAsserter: func(t *testing.T, err error) {
   141  				require.Error(t, err)
   142  				assert.Contains(t, err.Error(), `init data source replay: invalid command 'non-existent'`)
   143  			},
   144  		},
   145  		{
   146  			name:        "bad-exec-bit",
   147  			config:      Config{Command: "./testdata/exec-bit-not-set.sh"},
   148  			skipWindows: true,
   149  			initAsserter: func(t *testing.T, err error) {
   150  				require.Error(t, err)
   151  				assert.Contains(t, err.Error(), `init data source replay: invalid command './testdata/exec-bit-not-set.sh'`)
   152  			},
   153  		},
   154  		{
   155  			name:   "exe-err",
   156  			path:   "/fail",
   157  			config: Config{Command: "qbec-replay-exec"},
   158  			asserter: func(t *testing.T, resolved string, err error) {
   159  				require.Error(t, err)
   160  				assert.Contains(t, err.Error(), `exit status 1`)
   161  			},
   162  		},
   163  		{
   164  			name:   "exe-slow",
   165  			path:   "/slow",
   166  			config: Config{Command: "qbec-replay-exec", Timeout: "500ms"},
   167  			asserter: func(t *testing.T, resolved string, err error) {
   168  				require.Error(t, err)
   169  				if runtime.GOOS == "windows" {
   170  					assert.Contains(t, err.Error(), `exit status 1`)
   171  				} else {
   172  					assert.Contains(t, err.Error(), `signal: killed`)
   173  				}
   174  			},
   175  		},
   176  		{
   177  			name:   "no-command",
   178  			config: Config{},
   179  			initAsserter: func(t *testing.T, err error) {
   180  				require.Error(t, err)
   181  				assert.Contains(t, err.Error(), `command not specified`)
   182  			},
   183  		},
   184  		{
   185  			name:   "bad-timeout",
   186  			config: Config{Command: "qbec-exec-replay", Timeout: "abc"},
   187  			initAsserter: func(t *testing.T, err error) {
   188  				require.Error(t, err)
   189  				assert.Contains(t, err.Error(), `init data source replay: invalid timeout 'abc'`)
   190  			},
   191  		},
   192  		{
   193  			name:   "cp-not-found",
   194  			config: Config{Command: "qbec-exec-replay"},
   195  			cp:     func(name string) (string, error) { return "", fmt.Errorf("NOT FOUND") },
   196  			initAsserter: func(t *testing.T, err error) {
   197  				require.Error(t, err)
   198  				assert.Contains(t, err.Error(), `init data source replay: NOT FOUND`)
   199  			},
   200  		},
   201  		{
   202  			name:   "cp-bad-json",
   203  			config: Config{Command: "qbec-exec-replay"},
   204  			cp:     func(name string) (string, error) { return "{", nil },
   205  			initAsserter: func(t *testing.T, err error) {
   206  				require.Error(t, err)
   207  				assert.Contains(t, err.Error(), `init data source replay: unexpected end of JSON input`)
   208  			},
   209  		},
   210  	}
   211  	for _, test := range tests {
   212  		t.Run(test.name, func(t *testing.T) {
   213  			if test.skipWindows && runtime.GOOS == "windows" {
   214  				t.SkipNow()
   215  			}
   216  			ds := New("replay", "c")
   217  			cp := func(name string) (string, error) {
   218  				b, _ := json.Marshal(test.config)
   219  				return string(b), nil
   220  			}
   221  			if test.cp != nil {
   222  				cp = test.cp
   223  			}
   224  			err := ds.Init(cp)
   225  			if test.initAsserter == nil {
   226  				require.NoError(t, err)
   227  			} else {
   228  				test.initAsserter(t, err)
   229  			}
   230  			if err != nil {
   231  				return
   232  			}
   233  			defer ds.Close()
   234  			p := test.path
   235  			if p == "" {
   236  				p = "/"
   237  			}
   238  			ret, err := ds.Resolve(p)
   239  			if test.asserter != nil {
   240  				test.asserter(t, ret, err)
   241  			}
   242  		})
   243  	}
   244  }