github.com/google/yamlfmt@v0.12.2-0.20240514121411-7f77800e2681/path_collector_test.go (about) 1 // Copyright 2024 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package yamlfmt_test 16 17 import ( 18 "fmt" 19 "os" 20 "path/filepath" 21 "strings" 22 "testing" 23 24 "github.com/google/yamlfmt" 25 "github.com/google/yamlfmt/internal/collections" 26 "github.com/google/yamlfmt/internal/tempfile" 27 ) 28 29 func TestFilepathCollector(t *testing.T) { 30 testCaseTable{ 31 { 32 name: "finds direct paths", 33 files: []tempfile.Path{ 34 {FileName: "x.yaml"}, 35 {FileName: "y.yaml"}, 36 {FileName: "z.yml"}, 37 }, 38 includePatterns: testPatterns{ 39 {pattern: "x.yaml"}, 40 {pattern: "y.yaml"}, 41 {pattern: "z.yml"}, 42 }, 43 expectedFiles: collections.Set[string]{ 44 "x.yaml": {}, 45 "y.yaml": {}, 46 "z.yml": {}, 47 }, 48 }, 49 { 50 name: "finds all in directory one layer", 51 files: []tempfile.Path{ 52 {FileName: "a", IsDir: true}, 53 {FileName: "a/x.yaml"}, 54 {FileName: "a/y.yaml"}, 55 {FileName: "a/z.yml"}, 56 }, 57 includePatterns: testPatterns{ 58 {pattern: "a"}, 59 }, 60 extensions: []string{ 61 "yaml", 62 "yml", 63 }, 64 expectedFiles: collections.Set[string]{ 65 "a/x.yaml": {}, 66 "a/y.yaml": {}, 67 "a/z.yml": {}, 68 }, 69 }, 70 { 71 name: "finds direct path to subdirectory", 72 files: []tempfile.Path{ 73 {FileName: "a", IsDir: true}, 74 {FileName: "a/x.yaml"}, 75 {FileName: "a/y.yaml"}, 76 {FileName: "a/z.yml"}, 77 }, 78 includePatterns: testPatterns{ 79 {pattern: "a/x.yaml"}, 80 {pattern: "a/z.yml"}, 81 }, 82 expectedFiles: collections.Set[string]{ 83 "a/x.yaml": {}, 84 "a/z.yml": {}, 85 }, 86 }, 87 { 88 name: "finds all in layered directories", 89 files: []tempfile.Path{ 90 {FileName: "a", IsDir: true}, 91 {FileName: "a/b", IsDir: true}, 92 {FileName: "x.yml"}, 93 {FileName: "y.yml"}, 94 {FileName: "z.yaml"}, 95 {FileName: "a/x.yaml"}, 96 {FileName: "a/b/x.yaml"}, 97 {FileName: "a/b/y.yml"}, 98 }, 99 includePatterns: testPatterns{ 100 {pattern: ""}, // with the test this functionally means the whole temp dir 101 }, 102 extensions: []string{ 103 "yaml", 104 "yml", 105 }, 106 expectedFiles: collections.Set[string]{ 107 "x.yml": {}, 108 "y.yml": {}, 109 "z.yaml": {}, 110 "a/x.yaml": {}, 111 "a/b/x.yaml": {}, 112 "a/b/y.yml": {}, 113 }, 114 }, 115 { 116 name: "exclude files", 117 files: []tempfile.Path{ 118 {FileName: "a", IsDir: true}, 119 {FileName: "a/b", IsDir: true}, 120 {FileName: "x.yml"}, 121 {FileName: "y.yml"}, 122 {FileName: "z.yaml"}, 123 {FileName: "a/x.yaml"}, 124 {FileName: "a/b/x.yaml"}, 125 {FileName: "a/b/y.yml"}, 126 }, 127 includePatterns: testPatterns{ 128 {pattern: ""}, // with the test this functionally means the whole temp dir 129 }, 130 excludePatterns: testPatterns{ 131 {pattern: "x.yml"}, 132 {pattern: "a/x.yaml"}, 133 }, 134 extensions: []string{ 135 "yaml", 136 "yml", 137 }, 138 expectedFiles: collections.Set[string]{ 139 "y.yml": {}, 140 "z.yaml": {}, 141 "a/b/x.yaml": {}, 142 "a/b/y.yml": {}, 143 }, 144 }, 145 { 146 name: "exclude directory", 147 changeToTempDir: true, 148 files: []tempfile.Path{ 149 {FileName: "x.yml"}, 150 {FileName: "y.yml"}, 151 {FileName: "z.yaml"}, 152 153 {FileName: "a", IsDir: true}, 154 {FileName: "a/x.yaml"}, 155 156 {FileName: "a/b", IsDir: true}, 157 {FileName: "a/b/x.yaml"}, 158 {FileName: "a/b/y.yml"}, 159 }, 160 includePatterns: testPatterns{ 161 {pattern: ""}, // with the test this functionally means the whole temp dir 162 }, 163 excludePatterns: testPatterns{ 164 {pattern: "a/b"}, 165 }, 166 extensions: []string{ 167 "yaml", 168 "yml", 169 }, 170 expectedFiles: collections.Set[string]{ 171 "x.yml": {}, 172 "y.yml": {}, 173 "z.yaml": {}, 174 "a/x.yaml": {}, 175 }, 176 }, 177 { 178 name: "don't get files with wrong extension", 179 files: []tempfile.Path{ 180 {FileName: "x.yml"}, 181 {FileName: "y.yaml"}, 182 {FileName: "z.json"}, 183 }, 184 includePatterns: testPatterns{ 185 {pattern: ""}, // with the test this functionally means the whole temp dir 186 }, 187 extensions: []string{ 188 "yaml", 189 "yml", 190 }, 191 expectedFiles: collections.Set[string]{ 192 "x.yml": {}, 193 "y.yaml": {}, 194 }, 195 }, 196 }.runAll(t, useFilepathCollector) 197 } 198 199 func TestDoublestarCollectorBasic(t *testing.T) { 200 testCaseTable{ 201 { 202 name: "no excludes", 203 files: []tempfile.Path{ 204 {FileName: "x.yaml"}, 205 {FileName: "y.yaml"}, 206 {FileName: "z.yaml"}, 207 }, 208 includePatterns: testPatterns{ 209 {pattern: "**/*.yaml"}, 210 }, 211 expectedFiles: collections.Set[string]{ 212 "x.yaml": {}, 213 "y.yaml": {}, 214 "z.yaml": {}, 215 }, 216 }, 217 }.runAll(t, useDoublestarCollector) 218 } 219 220 func TestDoublestarCollectorExcludeDirectory(t *testing.T) { 221 testFiles := []tempfile.Path{ 222 {FileName: "x.yaml"}, 223 224 {FileName: "y", IsDir: true}, 225 {FileName: "y/y.yaml"}, 226 227 {FileName: "z", IsDir: true}, 228 {FileName: "z/z.yaml"}, 229 {FileName: "z/z1.yaml"}, 230 {FileName: "z/z2.yaml"}, 231 } 232 233 testCaseTable{ 234 { 235 name: "exclude_directory/start with doublestar", 236 files: testFiles, 237 includePatterns: testPatterns{ 238 {pattern: "**/*.yaml"}, 239 }, 240 excludePatterns: testPatterns{ 241 {pattern: "**/z/**/*.yaml", stayRelative: true}, 242 }, 243 expectedFiles: collections.Set[string]{ 244 "x.yaml": {}, 245 "y/y.yaml": {}, 246 }, 247 }, 248 { 249 name: "exclude_directory/relative include and exclude", 250 changeToTempDir: true, 251 files: testFiles, 252 includePatterns: testPatterns{ 253 {pattern: "**/*.yaml", stayRelative: true}, 254 }, 255 excludePatterns: testPatterns{ 256 {pattern: "z/**/*.yaml", stayRelative: true}, 257 }, 258 expectedFiles: collections.Set[string]{ 259 "x.yaml": {}, 260 "y/y.yaml": {}, 261 }, 262 }, 263 { 264 name: "exclude_directory/absolute include and exclude", 265 files: testFiles, 266 includePatterns: testPatterns{ 267 {pattern: "**/*.yaml"}, 268 }, 269 excludePatterns: testPatterns{ 270 {pattern: "z/**/*.yaml"}, 271 }, 272 expectedFiles: collections.Set[string]{ 273 "x.yaml": {}, 274 "y/y.yaml": {}, 275 }, 276 }, 277 { 278 name: "exclude_directory/absolute include relative exclude", 279 skip: true, 280 changeToTempDir: true, 281 files: testFiles, 282 includePatterns: testPatterns{ 283 {pattern: "**/*.yaml"}, 284 }, 285 excludePatterns: testPatterns{ 286 {pattern: "z/**/*.yaml", stayRelative: true}, 287 }, 288 expectedFiles: collections.Set[string]{ 289 "x.yaml": {}, 290 "y/y.yaml": {}, 291 }, 292 }, 293 { 294 name: "exclude_directory/relative include absolute exclude", 295 skip: true, 296 changeToTempDir: true, 297 files: testFiles, 298 includePatterns: testPatterns{ 299 {pattern: "**/*.yaml", stayRelative: true}, 300 }, 301 excludePatterns: testPatterns{ 302 {pattern: "z/**/*.yaml"}, 303 }, 304 expectedFiles: collections.Set[string]{ 305 "x.yaml": {}, 306 "y/y.yaml": {}, 307 }, 308 }, 309 }.runAll(t, useDoublestarCollector) 310 } 311 312 type testPatterns []struct { 313 pattern string 314 stayRelative bool 315 } 316 317 func (tps testPatterns) allPatterns(path string) []string { 318 result := make([]string, len(tps)) 319 for i := 0; i < len(tps); i++ { 320 if tps[i].stayRelative { 321 result[i] = tps[i].pattern 322 } else { 323 result[i] = fmt.Sprintf("%s/%s", path, tps[i].pattern) 324 } 325 } 326 return result 327 } 328 329 // In some test scenarios we want to ignore whether a pattern is marked stayRelative 330 // and always treat them as relative by formatting the base path on them. 331 func (tps testPatterns) allPatternsForceAbsolute(path string) []string { 332 result := make([]string, len(tps)) 333 for i := 0; i < len(tps); i++ { 334 result[i] = fmt.Sprintf("%s/%s", path, tps[i].pattern) 335 } 336 return result 337 } 338 339 type testCase struct { 340 name string 341 skip bool 342 changeToTempDir bool 343 files []tempfile.Path 344 includePatterns testPatterns 345 extensions []string 346 excludePatterns testPatterns 347 expectedFiles collections.Set[string] 348 } 349 350 func (tc testCase) run(t *testing.T, makeCollector makeCollectorFunc) { 351 testStartDir, err := os.Getwd() 352 if err != nil { 353 t.Fatalf("could not get working directory: %v", err) 354 } 355 t.Run(tc.name, func(t *testing.T) { 356 if tc.skip { 357 t.Skip() 358 } 359 tempPath := t.TempDir() 360 361 if tc.changeToTempDir { 362 os.Chdir(tempPath) 363 } 364 365 for _, file := range tc.files { 366 file.BasePath = tempPath 367 if err := file.Create(); err != nil { 368 t.Fatalf("Failed to create file") 369 } 370 } 371 372 collector := makeCollector(tc, tempPath) 373 paths, err := collector.CollectPaths() 374 if err != nil { 375 t.Fatalf("Test case failed: %v", err) 376 } 377 378 filesToFormat := collections.Set[string]{} 379 for _, path := range paths { 380 formatPath := path 381 if strings.HasPrefix(formatPath, "/") { 382 formatPath, err = filepath.Rel(tempPath, path) 383 if err != nil { 384 t.Fatalf("Path %s could not match to path %s", tempPath, path) 385 } 386 } 387 filesToFormat.Add(formatPath) 388 } 389 if !filesToFormat.Equals(tc.expectedFiles) { 390 t.Fatalf("Expected to receive paths %v\nbut got %v", tc.expectedFiles, filesToFormat) 391 } 392 }) 393 394 // Restore the starting directory if we changed in the test. 395 if tc.changeToTempDir { 396 os.Chdir(testStartDir) 397 } 398 } 399 400 type testCaseTable []testCase 401 402 func (tcs testCaseTable) runAll(t *testing.T, makeCollector makeCollectorFunc) { 403 for _, tc := range tcs { 404 tc.run(t, makeCollector) 405 } 406 } 407 408 type makeCollectorFunc func(tc testCase, path string) yamlfmt.PathCollector 409 410 func useFilepathCollector(tc testCase, path string) yamlfmt.PathCollector { 411 return &yamlfmt.FilepathCollector{ 412 Include: tc.includePatterns.allPatterns(path), 413 Exclude: tc.excludePatterns.allPatterns(path), 414 Extensions: tc.extensions, 415 } 416 } 417 418 func useDoublestarCollector(tc testCase, path string) yamlfmt.PathCollector { 419 var includePatterns []string 420 if tc.changeToTempDir { 421 includePatterns = tc.includePatterns.allPatterns(path) 422 } else { 423 // If we didn't change to temp dir, disallow relative paths so we don't pick up 424 // something confusing from the main working directory. 425 includePatterns = tc.includePatterns.allPatternsForceAbsolute(path) 426 } 427 return &yamlfmt.DoublestarCollector{ 428 Include: includePatterns, 429 Exclude: tc.excludePatterns.allPatterns(path), 430 } 431 }