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 }