github.com/splunk/qbec@v0.15.2/vm/internal/importers/glob_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 importers 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "testing" 26 27 "github.com/google/go-jsonnet" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 ) 31 32 func makeVM() (*jsonnet.VM, *GlobImporter) { 33 vm := jsonnet.MakeVM() 34 g1 := NewGlobImporter("import") 35 g2 := NewGlobImporter("importstr") 36 vm.Importer( 37 NewCompositeImporter( 38 g1, 39 g2, 40 NewFileImporter(&jsonnet.FileImporter{}), 41 ), 42 ) 43 return vm, g1 44 } 45 46 type outputData map[string]interface{} 47 48 func generateFile(t *testing.T, virtFile string, code string) (outFile string) { 49 file := virtFile + ".generated" 50 err := ioutil.WriteFile(file, []byte(code), 0644) 51 require.NoError(t, err) 52 return file 53 } 54 55 func evaluateVirtual(t *testing.T, vm *jsonnet.VM, virtFile string, code string) outputData { 56 file := generateFile(t, virtFile, code) 57 defer os.Remove(file) 58 jsonStr, err := vm.EvaluateFile(file) 59 require.NoError(t, err) 60 t.Logf("input from '%s'\n%s\noutput:%s\n", virtFile, code, jsonStr) 61 var data outputData 62 err = json.Unmarshal([]byte(jsonStr), &data) 63 require.NoError(t, err) 64 return data 65 } 66 67 func evaluateVirtualErr(t *testing.T, virtFile string, code string) error { 68 file := generateFile(t, virtFile, code) 69 defer os.Remove(file) 70 vm, _ := makeVM() 71 _, err := vm.EvaluateFile(file) 72 require.Error(t, err) 73 return err 74 } 75 76 func TestGlobSimple(t *testing.T) { 77 vm, _ := makeVM() 78 data := evaluateVirtual(t, vm, "testdata/caller.jsonnet", `import 'glob-import:example1/*.json'`) 79 for _, k := range []string{"a", "b", "z"} { 80 relFile := fmt.Sprintf("example1/%s.json", k) 81 val, ok := data[relFile] 82 require.True(t, ok) 83 mVal, ok := val.(map[string]interface{}) 84 require.True(t, ok) 85 _, ok = mVal[k] 86 assert.True(t, ok) 87 } 88 } 89 90 func TestGlobDoublestar(t *testing.T) { 91 vm, _ := makeVM() 92 data := evaluateVirtual(t, vm, "testdata/caller.jsonnet", `import 'glob-import:example2/**/*.json'`) 93 expectedJSON := ` 94 { 95 "example2/inc1/a.json": { 96 "a": "a" 97 }, 98 "example2/inc1/subdir/a.json": { 99 "a": "inner a" 100 }, 101 "example2/inc2/a.json": { 102 "a": "long form a" 103 } 104 } 105 ` 106 var expected interface{} 107 err := json.Unmarshal([]byte(expectedJSON), &expected) 108 require.Nil(t, err) 109 assert.EqualValues(t, expected, data) 110 } 111 112 func TestGlobOutsideRoot(t *testing.T) { 113 wd, err := os.Getwd() 114 require.NoError(t, err) 115 dir := filepath.FromSlash("testdata/example1/caller2") 116 err = os.Chdir(dir) 117 require.NoError(t, err) 118 defer func() { 119 err := os.Chdir(wd) 120 require.NoError(t, err) 121 }() 122 vm, _ := makeVM() 123 data := evaluateVirtual(t, vm, "caller.jsonnet", `import 'glob-import:../*json'`) 124 _, ok := data["../a.json"] 125 require.True(t, ok) 126 } 127 128 func TestDuplicateFileName(t *testing.T) { 129 vm, _ := makeVM() 130 data := evaluateVirtual(t, vm, "testdata/example2/caller.jsonnet", `import 'glob-import:inc?/*.json'`) 131 _, firstOk := data["inc1/a.json"] 132 require.True(t, firstOk) 133 _, secondOk := data["inc2/a.json"] 134 require.True(t, secondOk) 135 } 136 137 func TestGlobNoMatch(t *testing.T) { 138 vm, _ := makeVM() 139 data := evaluateVirtual(t, vm, "testdata/example1/caller/no-match.jsonnet", `import 'glob-import:*.json'`) 140 require.Equal(t, 0, len(data)) 141 } 142 143 func TestGlobImportStr(t *testing.T) { 144 vm, _ := makeVM() 145 file := generateFile(t, "testdata/example1/caller/synthesized.jsonnet", `importstr 'glob-import:../*.json'`) 146 defer os.Remove(file) 147 data, err := vm.EvaluateFile(file) 148 require.NoError(t, err) 149 var str string 150 err = json.Unmarshal([]byte(data), &str) 151 require.NoError(t, err) 152 assert.Equal(t, `{ 153 '../a.json': import '../a.json', 154 '../b.json': import '../b.json', 155 '../z.json': import '../z.json', 156 }`, str) 157 } 158 159 func TestGlobImportStrVerb(t *testing.T) { 160 vm, _ := makeVM() 161 data := evaluateVirtual(t, vm, "testdata/example1/caller/synthesized.jsonnet", `import 'glob-importstr:../*.json'`) 162 for _, k := range []string{"a", "b", "z"} { 163 val, ok := data[fmt.Sprintf("../%s.json", k)] 164 require.True(t, ok) 165 mVal, ok := val.(string) 166 require.True(t, ok) 167 assert.Contains(t, mVal, fmt.Sprintf("%q", k)) 168 } 169 } 170 171 func TestGlobInternalCaching(t *testing.T) { 172 a := assert.New(t) 173 vm, gi := makeVM() 174 _ = evaluateVirtual(t, vm, "testdata/example1/caller/synthesized.jsonnet", `import 'glob-import:../*.json'`) 175 a.Equal(1, len(gi.cache)) 176 _ = evaluateVirtual(t, vm, "testdata/example1/caller/synthesized2.jsonnet", `import 'glob-import:../*.json'`) 177 a.Equal(1, len(gi.cache)) 178 _ = evaluateVirtual(t, vm, "testdata/example1/caller2/synthesized.jsonnet", `import 'glob-import:../*.json'`) 179 a.Equal(1, len(gi.cache)) 180 _ = evaluateVirtual(t, vm, "testdata/example1/caller/inner/synthesized.jsonnet", `import 'glob-import:../../*.json'`) 181 a.Equal(2, len(gi.cache)) 182 _ = evaluateVirtual(t, vm, "testdata/example1/caller/inner/synthesized2.jsonnet", `import 'glob-import:../../[a,b].json'`) 183 a.Equal(3, len(gi.cache)) 184 } 185 186 func TestGlobNegativeCases(t *testing.T) { 187 checkMsg := func(m string) func(t *testing.T, err error) { 188 return func(t *testing.T, err error) { 189 assert.Contains(t, err.Error(), m) 190 } 191 } 192 tests := []struct { 193 name string 194 expr string 195 asserter func(t *testing.T, err error) 196 }{ 197 { 198 name: "bad path", 199 expr: `import 'glob-import:/bag-of-files/*.json'`, 200 asserter: checkMsg(`RUNTIME ERROR: invalid glob pattern '/bag-of-files/*.json', cannot be absolute`), 201 }, 202 { 203 name: "bad pattern", 204 expr: `import 'glob-import:../[.json'`, 205 asserter: func(t *testing.T, err error) { 206 assert.Contains(t, err.Error(), `RUNTIME ERROR: unable to expand glob`) 207 assert.Contains(t, err.Error(), `[.json", syntax error in pattern`) 208 }, 209 }, 210 } 211 file := "testdata/example1/caller/synthesized.jsonnet" 212 for _, test := range tests { 213 t.Run(test.name, func(t *testing.T) { 214 test.asserter(t, evaluateVirtualErr(t, file, test.expr)) 215 }) 216 } 217 } 218 219 func TestGlobInit(t *testing.T) { 220 require.Panics(t, func() { _ = NewGlobImporter("foobar") }) 221 }