github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/jsonnet/find_importers_test.go (about) 1 package jsonnet 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strings" 7 "testing" 8 9 "github.com/grafana/tanka/pkg/jsonnet/jpath" 10 "github.com/stretchr/testify/require" 11 ) 12 13 type findImportersTestCase struct { 14 name string 15 files []string 16 expectedImporters []string 17 expectedErr error 18 } 19 20 func (tc findImportersTestCase) run(t testing.TB) { 21 importers, err := FindImporterForFiles("testdata/findImporters", tc.files) 22 23 if tc.expectedErr != nil { 24 require.EqualError(t, err, tc.expectedErr.Error()) 25 } else { 26 require.NoError(t, err) 27 require.Equal(t, tc.expectedImporters, importers) 28 } 29 } 30 31 func findImportersTestCases(t testing.TB) []findImportersTestCase { 32 t.Helper() 33 34 return []findImportersTestCase{ 35 { 36 name: "no files", 37 files: []string{}, 38 expectedImporters: nil, 39 }, 40 { 41 name: "invalid file", 42 files: []string{"testdata/findImporters/does-not-exist.jsonnet"}, 43 expectedErr: fmt.Errorf("lstat %s: no such file or directory", absPath(t, "testdata/findImporters/does-not-exist.jsonnet")), 44 }, 45 { 46 name: "project with no imports", 47 files: []string{"testdata/findImporters/environments/no-imports/main.jsonnet"}, 48 expectedImporters: []string{absPath(t, "testdata/findImporters/environments/no-imports/main.jsonnet")}, // itself only 49 }, 50 { 51 name: "local import", 52 files: []string{"testdata/findImporters/environments/imports-locals-and-vendored/local-file1.libsonnet"}, 53 expectedImporters: []string{absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet")}, 54 }, 55 { 56 name: "local import with relative path", 57 files: []string{"testdata/findImporters/environments/imports-locals-and-vendored/local-file2.libsonnet"}, 58 expectedImporters: []string{absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet")}, 59 }, 60 { 61 name: "lib imported through chain", 62 files: []string{"testdata/findImporters/lib/lib1/main.libsonnet"}, 63 expectedImporters: []string{ 64 absPath(t, "testdata/findImporters/environments/imports-lib-and-vendored-through-chain/main.jsonnet"), 65 }, 66 }, 67 { 68 name: "vendored lib imported through chain + directly", 69 files: []string{"testdata/findImporters/vendor/vendored/main.libsonnet"}, 70 expectedImporters: []string{ 71 absPath(t, "testdata/findImporters/environments/imports-lib-and-vendored-through-chain/main.jsonnet"), 72 absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet"), 73 absPath(t, "testdata/findImporters/environments/imports-symlinked-vendor/main.jsonnet"), 74 }, 75 }, 76 { 77 name: "vendored lib found through symlink", // expect same result as previous test 78 files: []string{"testdata/findImporters/vendor/vendor-symlinked/main.libsonnet"}, 79 expectedImporters: []string{ 80 absPath(t, "testdata/findImporters/environments/imports-lib-and-vendored-through-chain/main.jsonnet"), 81 absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet"), 82 absPath(t, "testdata/findImporters/environments/imports-symlinked-vendor/main.jsonnet"), 83 }, 84 }, 85 { 86 name: "text file", 87 files: []string{"testdata/findImporters/vendor/vendored/text-file.txt"}, 88 expectedImporters: []string{ 89 absPath(t, "testdata/findImporters/environments/imports-lib-and-vendored-through-chain/main.jsonnet"), 90 absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet"), 91 absPath(t, "testdata/findImporters/environments/imports-symlinked-vendor/main.jsonnet"), 92 }, 93 }, 94 { 95 name: "relative imported environment", 96 files: []string{"testdata/findImporters/environments/relative-imported/main.jsonnet"}, 97 expectedImporters: []string{ 98 absPath(t, "testdata/findImporters/environments/relative-import/main.jsonnet"), 99 absPath(t, "testdata/findImporters/environments/relative-imported/main.jsonnet"), // itself, it's a main file 100 }, 101 }, 102 { 103 name: "relative imported environment with doubled '..'", 104 files: []string{"testdata/findImporters/environments/relative-imported2/main.jsonnet"}, 105 expectedImporters: []string{ 106 absPath(t, "testdata/findImporters/environments/relative-import/main.jsonnet"), 107 absPath(t, "testdata/findImporters/environments/relative-imported2/main.jsonnet"), // itself, it's a main file 108 }, 109 }, 110 { 111 name: "relative imported text file", 112 files: []string{"testdata/findImporters/other-files/test.txt"}, 113 expectedImporters: []string{ 114 absPath(t, "testdata/findImporters/environments/relative-import/main.jsonnet"), 115 }, 116 }, 117 { 118 name: "relative imported text file with doubled '..'", 119 files: []string{"testdata/findImporters/other-files/test2.txt"}, 120 expectedImporters: []string{ 121 absPath(t, "testdata/findImporters/environments/relative-import/main.jsonnet"), 122 }, 123 }, 124 { 125 name: "vendor override in env: override vendor used", 126 files: []string{ 127 "testdata/findImporters/environments/vendor-override-in-env/vendor/vendor-override-in-env/main.libsonnet", 128 }, 129 expectedImporters: []string{ 130 absPath(t, "testdata/findImporters/environments/vendor-override-in-env/main.jsonnet"), 131 }, 132 }, 133 { 134 name: "vendor override in env: global vendor unused", 135 files: []string{ 136 "testdata/findImporters/vendor/vendor-override-in-env/main.libsonnet", 137 }, 138 expectedImporters: nil, 139 }, 140 { 141 name: "imported file in lib relative to env", 142 files: []string{"testdata/findImporters/environments/lib-import-relative-to-env/file-to-import.libsonnet"}, 143 expectedImporters: []string{ 144 absPath(t, "testdata/findImporters/environments/lib-import-relative-to-env/folder1/folder2/main.jsonnet"), 145 }, 146 }, 147 { 148 name: "unused deleted file", 149 files: []string{ 150 "deleted:testdata/findImporters/vendor/deleted-vendor/main.libsonnet", 151 }, 152 expectedImporters: nil, 153 }, 154 { 155 name: "deleted local path that is still potentially imported", 156 files: []string{ 157 "deleted:testdata/findImporters/environments/using-deleted-stuff/my-import-dir/main.libsonnet", 158 }, 159 expectedImporters: []string{ 160 absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"), 161 }, 162 }, 163 { 164 name: "deleted lib that is still potentially imported", 165 files: []string{ 166 "deleted:testdata/findImporters/lib/my-import-dir/main.libsonnet", 167 }, 168 expectedImporters: []string{ 169 absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"), 170 }, 171 }, 172 { 173 name: "deleted vendor that is still potentially imported", 174 files: []string{ 175 "deleted:testdata/findImporters/vendor/my-import-dir/main.libsonnet", 176 }, 177 expectedImporters: []string{ 178 absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"), 179 }, 180 }, 181 { 182 name: "deleted lib that is still potentially imported, relative path from root", 183 files: []string{ 184 "deleted:lib/my-import-dir/main.libsonnet", 185 }, 186 expectedImporters: []string{ 187 absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"), 188 }, 189 }, 190 { 191 // All files in an environment are considered to be imported by the main file, so the same should apply for deleted files 192 name: "deleted dir in environment", 193 files: []string{ 194 "deleted:testdata/findImporters/environments/no-imports/deleted-dir/deleted-file.libsonnet", 195 }, 196 expectedImporters: []string{ 197 absPath(t, "testdata/findImporters/environments/no-imports/main.jsonnet"), 198 }, 199 }, 200 { 201 name: "imports through a main file are followed", 202 files: []string{ 203 "testdata/findImporters/environments/import-other-main-file/env2/file.libsonnet", 204 }, 205 expectedImporters: []string{ 206 absPath(t, "testdata/findImporters/environments/import-other-main-file/env1/main.jsonnet"), 207 absPath(t, "testdata/findImporters/environments/import-other-main-file/env2/main.jsonnet"), 208 }, 209 }, 210 } 211 } 212 213 func TestFindImportersForFiles(t *testing.T) { 214 // Sanity check 215 // Make sure the main files all eval correctly 216 // We want to make sure that the importers command works correctly, 217 // but there's no point in testing on invalid jsonnet files 218 files, err := FindFiles("testdata", nil) 219 require.NoError(t, err) 220 require.NotEmpty(t, files) 221 for _, file := range files { 222 // This project is known to be invalid (as the name suggests) 223 if strings.Contains(file, "using-deleted-stuff") { 224 continue 225 } 226 227 // Skip non-main files 228 if filepath.Base(file) != jpath.DefaultEntrypoint { 229 continue 230 } 231 _, err := EvaluateFile(jsonnetImpl, file, Opts{}) 232 require.NoError(t, err, "failed to eval %s", file) 233 } 234 235 for _, c := range findImportersTestCases(t) { 236 t.Run(c.name, func(t *testing.T) { 237 c.run(t) 238 }) 239 } 240 } 241 242 func BenchmarkFindImporters(b *testing.B) { 243 // Create a very large and complex project 244 tempDir, err := filepath.EvalSymlinks(b.TempDir()) 245 require.NoError(b, err) 246 generateTestProject(b, tempDir, 100, false) 247 248 // Run the benchmark 249 expectedImporters := []string{filepath.Join(tempDir, "main.jsonnet")} 250 b.ResetTimer() 251 for i := 0; i < b.N; i++ { 252 importersCache = make(map[string][]string) 253 jsonnetFilesCache = make(map[string]map[string]*cachedJsonnetFile) 254 symlinkCache = make(map[string]string) 255 importers, err := FindImporterForFiles(tempDir, []string{filepath.Join(tempDir, "file10.libsonnet")}) 256 257 require.NoError(b, err) 258 require.Equal(b, expectedImporters, importers) 259 } 260 } 261 262 func BenchmarkFindImporters_StaticCases(b *testing.B) { 263 for _, c := range findImportersTestCases(b) { 264 b.Run(c.name, func(b *testing.B) { 265 for i := 0; i < b.N; i++ { 266 c.run(b) 267 } 268 }) 269 } 270 } 271 272 func absPath(t testing.TB, path string) string { 273 t.Helper() 274 275 abs, err := filepath.Abs(path) 276 require.NoError(t, err) 277 return abs 278 }