github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/internal/fileresolver/directory_test.go (about) 1 //go:build !windows 2 // +build !windows 3 4 package fileresolver 5 6 import ( 7 "context" 8 "io" 9 "io/fs" 10 "os" 11 "path/filepath" 12 "sort" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/google/go-cmp/cmp" 18 "github.com/scylladb/go-set/strset" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "go.uber.org/goleak" 22 23 stereoscopeFile "github.com/anchore/stereoscope/pkg/file" 24 "github.com/anchore/syft/syft/file" 25 ) 26 27 func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) { 28 // / 29 // somewhere/ 30 // outside.txt 31 // root-link -> ./ 32 // path/ 33 // to/ 34 // abs-inside.txt -> /path/to/the/file.txt # absolute link to somewhere inside of the root 35 // rel-inside.txt -> ./the/file.txt # relative link to somewhere inside of the root 36 // the/ 37 // file.txt 38 // abs-outside.txt -> /somewhere/outside.txt # absolute link to outside of the root 39 // rel-outside -> ../../../somewhere/outside.txt # relative link to outside of the root 40 // 41 42 testDir, err := os.Getwd() 43 require.NoError(t, err) 44 relative := filepath.Join("test-fixtures", "req-resp") 45 absolute := filepath.Join(testDir, relative) 46 47 absInsidePath := filepath.Join(absolute, "path", "to", "abs-inside.txt") 48 absOutsidePath := filepath.Join(absolute, "path", "to", "the", "abs-outside.txt") 49 50 relativeViaLink := filepath.Join(relative, "root-link") 51 absoluteViaLink := filepath.Join(absolute, "root-link") 52 53 relativeViaDoubleLink := filepath.Join(relative, "root-link", "root-link") 54 absoluteViaDoubleLink := filepath.Join(absolute, "root-link", "root-link") 55 56 cleanup := func() { 57 _ = os.Remove(absInsidePath) 58 _ = os.Remove(absOutsidePath) 59 } 60 61 // ensure the absolute symlinks are cleaned up from any previous runs 62 cleanup() 63 64 require.NoError(t, os.Symlink(filepath.Join(absolute, "path", "to", "the", "file.txt"), absInsidePath)) 65 require.NoError(t, os.Symlink(filepath.Join(absolute, "somewhere", "outside.txt"), absOutsidePath)) 66 67 t.Cleanup(cleanup) 68 69 cases := []struct { 70 name string 71 cwd string 72 root string 73 base string 74 input string 75 expectedRealPath string 76 expectedAccessPath string // note: if empty it will be assumed to match the expectedRealPath 77 }{ 78 { 79 name: "relative root, relative request, direct", 80 root: relative, 81 input: "path/to/the/file.txt", 82 expectedRealPath: "path/to/the/file.txt", 83 }, 84 { 85 name: "abs root, relative request, direct", 86 root: absolute, 87 input: "path/to/the/file.txt", 88 expectedRealPath: "path/to/the/file.txt", 89 }, 90 { 91 name: "relative root, abs request, direct", 92 root: relative, 93 input: "/path/to/the/file.txt", 94 expectedRealPath: "path/to/the/file.txt", 95 }, 96 { 97 name: "abs root, abs request, direct", 98 root: absolute, 99 input: "/path/to/the/file.txt", 100 expectedRealPath: "path/to/the/file.txt", 101 }, 102 // cwd within root... 103 { 104 name: "relative root, relative request, direct, cwd within root", 105 cwd: filepath.Join(relative, "path/to"), 106 root: "../../", 107 input: "path/to/the/file.txt", 108 expectedRealPath: "path/to/the/file.txt", 109 }, 110 { 111 name: "abs root, relative request, direct, cwd within root", 112 cwd: filepath.Join(relative, "path/to"), 113 root: absolute, 114 input: "path/to/the/file.txt", 115 expectedRealPath: "path/to/the/file.txt", 116 }, 117 { 118 name: "relative root, abs request, direct, cwd within root", 119 cwd: filepath.Join(relative, "path/to"), 120 root: "../../", 121 input: "/path/to/the/file.txt", 122 expectedRealPath: "path/to/the/file.txt", 123 }, 124 { 125 name: "abs root, abs request, direct, cwd within root", 126 cwd: filepath.Join(relative, "path/to"), 127 128 root: absolute, 129 input: "/path/to/the/file.txt", 130 expectedRealPath: "path/to/the/file.txt", 131 }, 132 // cwd within symlink root... 133 { 134 name: "relative root, relative request, direct, cwd within symlink root", 135 cwd: relativeViaLink, 136 root: "./", 137 input: "path/to/the/file.txt", 138 // note: why not expect "path/to/the/file.txt" here? 139 // this is because we don't know that the path used to access this path (which is a link within 140 // the root) resides within the root. Without this information it appears as if this file resides 141 // outside the root. 142 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 143 //expectedRealPath: "path/to/the/file.txt", 144 expectedAccessPath: "path/to/the/file.txt", 145 }, 146 { 147 name: "abs root, relative request, direct, cwd within symlink root", 148 cwd: relativeViaLink, 149 root: absoluteViaLink, 150 input: "path/to/the/file.txt", 151 expectedRealPath: "path/to/the/file.txt", 152 }, 153 { 154 name: "relative root, abs request, direct, cwd within symlink root", 155 cwd: relativeViaLink, 156 root: "./", 157 input: "/path/to/the/file.txt", 158 // note: why not expect "path/to/the/file.txt" here? 159 // this is because we don't know that the path used to access this path (which is a link within 160 // the root) resides within the root. Without this information it appears as if this file resides 161 // outside the root. 162 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 163 //expectedRealPath: "path/to/the/file.txt", 164 expectedAccessPath: "path/to/the/file.txt", 165 }, 166 { 167 name: "abs root, abs request, direct, cwd within symlink root", 168 cwd: relativeViaLink, 169 root: absoluteViaLink, 170 input: "/path/to/the/file.txt", 171 expectedRealPath: "path/to/the/file.txt", 172 }, 173 // cwd within symlink root, request nested within... 174 { 175 name: "relative root, relative nested request, direct, cwd within symlink root", 176 cwd: relativeViaLink, 177 root: "./path", 178 input: "to/the/file.txt", 179 // note: why not expect "to/the/file.txt" here? 180 // this is because we don't know that the path used to access this path (which is a link within 181 // the root) resides within the root. Without this information it appears as if this file resides 182 // outside the root. 183 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 184 //expectedRealPath: "to/the/file.txt", 185 expectedAccessPath: "to/the/file.txt", 186 }, 187 { 188 name: "abs root, relative nested request, direct, cwd within symlink root", 189 cwd: relativeViaLink, 190 root: filepath.Join(absoluteViaLink, "path"), 191 input: "to/the/file.txt", 192 expectedRealPath: "to/the/file.txt", 193 }, 194 { 195 name: "relative root, abs nested request, direct, cwd within symlink root", 196 cwd: relativeViaLink, 197 root: "./path", 198 input: "/to/the/file.txt", 199 // note: why not expect "to/the/file.txt" here? 200 // this is because we don't know that the path used to access this path (which is a link within 201 // the root) resides within the root. Without this information it appears as if this file resides 202 // outside the root. 203 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 204 //expectedRealPath: "to/the/file.txt", 205 expectedAccessPath: "to/the/file.txt", 206 }, 207 { 208 name: "abs root, abs nested request, direct, cwd within symlink root", 209 cwd: relativeViaLink, 210 root: filepath.Join(absoluteViaLink, "path"), 211 input: "/to/the/file.txt", 212 expectedRealPath: "to/the/file.txt", 213 }, 214 // cwd within DOUBLE symlink root... 215 { 216 name: "relative root, relative request, direct, cwd within (double) symlink root", 217 cwd: relativeViaDoubleLink, 218 root: "./", 219 input: "path/to/the/file.txt", 220 // note: why not expect "path/to/the/file.txt" here? 221 // this is because we don't know that the path used to access this path (which is a link within 222 // the root) resides within the root. Without this information it appears as if this file resides 223 // outside the root. 224 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 225 //expectedRealPath: "path/to/the/file.txt", 226 expectedAccessPath: "path/to/the/file.txt", 227 }, 228 { 229 name: "abs root, relative request, direct, cwd within (double) symlink root", 230 cwd: relativeViaDoubleLink, 231 root: absoluteViaDoubleLink, 232 input: "path/to/the/file.txt", 233 expectedRealPath: "path/to/the/file.txt", 234 }, 235 { 236 name: "relative root, abs request, direct, cwd within (double) symlink root", 237 cwd: relativeViaDoubleLink, 238 root: "./", 239 input: "/path/to/the/file.txt", 240 // note: why not expect "path/to/the/file.txt" here? 241 // this is because we don't know that the path used to access this path (which is a link within 242 // the root) resides within the root. Without this information it appears as if this file resides 243 // outside the root. 244 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 245 //expectedRealPath: "path/to/the/file.txt", 246 expectedAccessPath: "path/to/the/file.txt", 247 }, 248 { 249 name: "abs root, abs request, direct, cwd within (double) symlink root", 250 cwd: relativeViaDoubleLink, 251 root: absoluteViaDoubleLink, 252 input: "/path/to/the/file.txt", 253 expectedRealPath: "path/to/the/file.txt", 254 }, 255 // cwd within DOUBLE symlink root, request nested within... 256 { 257 name: "relative root, relative nested request, direct, cwd within (double) symlink root", 258 cwd: relativeViaDoubleLink, 259 root: "./path", 260 input: "to/the/file.txt", 261 // note: why not expect "path/to/the/file.txt" here? 262 // this is because we don't know that the path used to access this path (which is a link within 263 // the root) resides within the root. Without this information it appears as if this file resides 264 // outside the root. 265 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 266 //expectedRealPath: "to/the/file.txt", 267 expectedAccessPath: "to/the/file.txt", 268 }, 269 { 270 name: "abs root, relative nested request, direct, cwd within (double) symlink root", 271 cwd: relativeViaDoubleLink, 272 root: filepath.Join(absoluteViaDoubleLink, "path"), 273 input: "to/the/file.txt", 274 expectedRealPath: "to/the/file.txt", 275 }, 276 { 277 name: "relative root, abs nested request, direct, cwd within (double) symlink root", 278 cwd: relativeViaDoubleLink, 279 root: "./path", 280 input: "/to/the/file.txt", 281 // note: why not expect "path/to/the/file.txt" here? 282 // this is because we don't know that the path used to access this path (which is a link within 283 // the root) resides within the root. Without this information it appears as if this file resides 284 // outside the root. 285 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 286 //expectedRealPath: "to/the/file.txt", 287 expectedAccessPath: "to/the/file.txt", 288 }, 289 { 290 name: "abs root, abs nested request, direct, cwd within (double) symlink root", 291 cwd: relativeViaDoubleLink, 292 root: filepath.Join(absoluteViaDoubleLink, "path"), 293 input: "/to/the/file.txt", 294 expectedRealPath: "to/the/file.txt", 295 }, 296 // cwd within DOUBLE symlink root, request nested DEEP within... 297 { 298 name: "relative root, relative nested request, direct, cwd deep within (double) symlink root", 299 cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), 300 root: "../", 301 input: "to/the/file.txt", 302 // note: why not expect "path/to/the/file.txt" here? 303 // this is because we don't know that the path used to access this path (which is a link within 304 // the root) resides within the root. Without this information it appears as if this file resides 305 // outside the root. 306 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 307 //expectedRealPath: "to/the/file.txt", 308 expectedAccessPath: "to/the/file.txt", 309 }, 310 { 311 name: "abs root, relative nested request, direct, cwd deep within (double) symlink root", 312 cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), 313 root: filepath.Join(absoluteViaDoubleLink, "path"), 314 input: "to/the/file.txt", 315 expectedRealPath: "to/the/file.txt", 316 }, 317 { 318 name: "relative root, abs nested request, direct, cwd deep within (double) symlink root", 319 cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), 320 root: "../", 321 input: "/to/the/file.txt", 322 // note: why not expect "path/to/the/file.txt" here? 323 // this is because we don't know that the path used to access this path (which is a link within 324 // the root) resides within the root. Without this information it appears as if this file resides 325 // outside the root. 326 expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), 327 //expectedRealPath: "to/the/file.txt", 328 expectedAccessPath: "to/the/file.txt", 329 }, 330 { 331 name: "abs root, abs nested request, direct, cwd deep within (double) symlink root", 332 cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), 333 root: filepath.Join(absoluteViaDoubleLink, "path"), 334 input: "/to/the/file.txt", 335 expectedRealPath: "to/the/file.txt", 336 }, 337 // link to outside of root cases... 338 { 339 name: "relative root, relative request, abs indirect (outside of root)", 340 root: filepath.Join(relative, "path"), 341 input: "to/the/abs-outside.txt", 342 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 343 expectedAccessPath: "to/the/abs-outside.txt", 344 }, 345 { 346 name: "abs root, relative request, abs indirect (outside of root)", 347 root: filepath.Join(absolute, "path"), 348 input: "to/the/abs-outside.txt", 349 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 350 expectedAccessPath: "to/the/abs-outside.txt", 351 }, 352 { 353 name: "relative root, abs request, abs indirect (outside of root)", 354 root: filepath.Join(relative, "path"), 355 input: "/to/the/abs-outside.txt", 356 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 357 expectedAccessPath: "to/the/abs-outside.txt", 358 }, 359 { 360 name: "abs root, abs request, abs indirect (outside of root)", 361 root: filepath.Join(absolute, "path"), 362 input: "/to/the/abs-outside.txt", 363 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 364 expectedAccessPath: "to/the/abs-outside.txt", 365 }, 366 { 367 name: "relative root, relative request, relative indirect (outside of root)", 368 root: filepath.Join(relative, "path"), 369 input: "to/the/rel-outside.txt", 370 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 371 expectedAccessPath: "to/the/rel-outside.txt", 372 }, 373 { 374 name: "abs root, relative request, relative indirect (outside of root)", 375 root: filepath.Join(absolute, "path"), 376 input: "to/the/rel-outside.txt", 377 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 378 expectedAccessPath: "to/the/rel-outside.txt", 379 }, 380 { 381 name: "relative root, abs request, relative indirect (outside of root)", 382 root: filepath.Join(relative, "path"), 383 input: "/to/the/rel-outside.txt", 384 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 385 expectedAccessPath: "to/the/rel-outside.txt", 386 }, 387 { 388 name: "abs root, abs request, relative indirect (outside of root)", 389 root: filepath.Join(absolute, "path"), 390 input: "/to/the/rel-outside.txt", 391 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 392 expectedAccessPath: "to/the/rel-outside.txt", 393 }, 394 // link to outside of root cases... cwd within symlink root 395 { 396 name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root", 397 cwd: relativeViaLink, 398 root: "path", 399 input: "to/the/abs-outside.txt", 400 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 401 expectedAccessPath: "to/the/abs-outside.txt", 402 }, 403 { 404 name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root", 405 cwd: relativeViaLink, 406 root: filepath.Join(absolute, "path"), 407 input: "to/the/abs-outside.txt", 408 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 409 expectedAccessPath: "to/the/abs-outside.txt", 410 }, 411 { 412 name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root", 413 cwd: relativeViaLink, 414 root: "path", 415 input: "/to/the/abs-outside.txt", 416 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 417 expectedAccessPath: "to/the/abs-outside.txt", 418 }, 419 { 420 name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root", 421 cwd: relativeViaLink, 422 root: filepath.Join(absolute, "path"), 423 input: "/to/the/abs-outside.txt", 424 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 425 expectedAccessPath: "to/the/abs-outside.txt", 426 }, 427 { 428 name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root", 429 cwd: relativeViaLink, 430 root: "path", 431 input: "to/the/rel-outside.txt", 432 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 433 expectedAccessPath: "to/the/rel-outside.txt", 434 }, 435 { 436 name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root", 437 cwd: relativeViaLink, 438 root: filepath.Join(absolute, "path"), 439 input: "to/the/rel-outside.txt", 440 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 441 expectedAccessPath: "to/the/rel-outside.txt", 442 }, 443 { 444 name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root", 445 cwd: relativeViaLink, 446 root: "path", 447 input: "/to/the/rel-outside.txt", 448 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 449 expectedAccessPath: "to/the/rel-outside.txt", 450 }, 451 { 452 name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root", 453 cwd: relativeViaLink, 454 root: filepath.Join(absolute, "path"), 455 input: "/to/the/rel-outside.txt", 456 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 457 expectedAccessPath: "to/the/rel-outside.txt", 458 }, 459 { 460 name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", 461 cwd: relativeViaDoubleLink, 462 root: "path", 463 input: "to/the/rel-outside.txt", 464 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 465 expectedAccessPath: "to/the/rel-outside.txt", 466 }, 467 { 468 name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", 469 cwd: relativeViaDoubleLink, 470 root: filepath.Join(absolute, "path"), 471 input: "to/the/rel-outside.txt", 472 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 473 expectedAccessPath: "to/the/rel-outside.txt", 474 }, 475 { 476 name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", 477 cwd: relativeViaDoubleLink, 478 root: "path", 479 input: "/to/the/rel-outside.txt", 480 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 481 expectedAccessPath: "to/the/rel-outside.txt", 482 }, 483 { 484 name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", 485 cwd: relativeViaDoubleLink, 486 root: filepath.Join(absolute, "path"), 487 input: "/to/the/rel-outside.txt", 488 expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), 489 expectedAccessPath: "to/the/rel-outside.txt", 490 }, 491 } 492 for _, c := range cases { 493 t.Run(c.name, func(t *testing.T) { 494 if c.expectedAccessPath == "" { 495 c.expectedAccessPath = c.expectedRealPath 496 } 497 498 // we need to mimic a shell, otherwise we won't get a path within a symlink 499 targetPath := filepath.Join(testDir, c.cwd) 500 t.Setenv("PWD", filepath.Clean(targetPath)) 501 502 require.NoError(t, err) 503 require.NoError(t, os.Chdir(targetPath)) 504 t.Cleanup(func() { 505 require.NoError(t, os.Chdir(testDir)) 506 }) 507 508 resolver, err := NewFromDirectory(c.root, c.base) 509 require.NoError(t, err) 510 require.NotNil(t, resolver) 511 512 refs, err := resolver.FilesByPath(c.input) 513 require.NoError(t, err) 514 if c.expectedRealPath == "" { 515 require.Empty(t, refs) 516 return 517 } 518 require.Len(t, refs, 1) 519 assert.Equal(t, c.expectedRealPath, refs[0].RealPath, "real path different") 520 assert.Equal(t, c.expectedAccessPath, refs[0].AccessPath, "virtual path different") 521 }) 522 } 523 } 524 525 func TestDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) { 526 cases := []struct { 527 name string 528 relativeRoot string 529 input string 530 expected []string 531 }{ 532 { 533 name: "should find a file from an absolute input", 534 relativeRoot: "./test-fixtures/", 535 input: "/image-symlinks/file-1.txt", 536 expected: []string{ 537 "image-symlinks/file-1.txt", 538 }, 539 }, 540 { 541 name: "should find a file from a relative path", 542 relativeRoot: "./test-fixtures/", 543 input: "image-symlinks/file-1.txt", 544 expected: []string{ 545 "image-symlinks/file-1.txt", 546 }, 547 }, 548 { 549 name: "should find a file from a relative path (root above cwd)", 550 // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great 551 relativeRoot: "../", 552 input: "fileresolver/directory.go", 553 expected: []string{ 554 "fileresolver/directory.go", 555 }, 556 }, 557 } 558 for _, c := range cases { 559 t.Run(c.name, func(t *testing.T) { 560 resolver, err := NewFromDirectory(c.relativeRoot, "") 561 assert.NoError(t, err) 562 563 refs, err := resolver.FilesByPath(c.input) 564 require.NoError(t, err) 565 assert.Len(t, refs, len(c.expected)) 566 s := strset.New() 567 for _, actual := range refs { 568 s.Add(actual.RealPath) 569 } 570 assert.ElementsMatch(t, c.expected, s.List()) 571 }) 572 } 573 } 574 575 func TestDirectoryResolver_FilesByPath_absoluteRoot(t *testing.T) { 576 cases := []struct { 577 name string 578 relativeRoot string 579 input string 580 expected []string 581 }{ 582 { 583 name: "should find a file from an absolute input", 584 relativeRoot: "./test-fixtures/", 585 input: "/image-symlinks/file-1.txt", 586 expected: []string{ 587 "image-symlinks/file-1.txt", 588 }, 589 }, 590 { 591 name: "should find a file from a relative path", 592 relativeRoot: "./test-fixtures/", 593 input: "image-symlinks/file-1.txt", 594 expected: []string{ 595 "image-symlinks/file-1.txt", 596 }, 597 }, 598 { 599 name: "should find a file from a relative path (root above cwd)", 600 // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great 601 relativeRoot: "../", 602 input: "fileresolver/directory.go", 603 expected: []string{ 604 "fileresolver/directory.go", 605 }, 606 }, 607 } 608 for _, c := range cases { 609 t.Run(c.name, func(t *testing.T) { 610 // note: this test is all about asserting correct functionality when the given analysis path 611 // is an absolute path 612 absRoot, err := filepath.Abs(c.relativeRoot) 613 require.NoError(t, err) 614 615 resolver, err := NewFromDirectory(absRoot, "") 616 assert.NoError(t, err) 617 618 refs, err := resolver.FilesByPath(c.input) 619 require.NoError(t, err) 620 assert.Len(t, refs, len(c.expected)) 621 s := strset.New() 622 for _, actual := range refs { 623 s.Add(actual.RealPath) 624 } 625 assert.ElementsMatch(t, c.expected, s.List()) 626 }) 627 } 628 } 629 630 func TestDirectoryResolver_FilesByPath(t *testing.T) { 631 cases := []struct { 632 name string 633 root string 634 input string 635 expected string 636 refCount int 637 forcePositiveHasPath bool 638 }{ 639 { 640 name: "finds a file (relative)", 641 root: "./test-fixtures/", 642 input: "image-symlinks/file-1.txt", 643 expected: "image-symlinks/file-1.txt", 644 refCount: 1, 645 }, 646 { 647 name: "finds a file with relative indirection", 648 root: "./test-fixtures/../test-fixtures", 649 input: "image-symlinks/file-1.txt", 650 expected: "image-symlinks/file-1.txt", 651 refCount: 1, 652 }, 653 { 654 name: "managed non-existing files (relative)", 655 root: "./test-fixtures/", 656 input: "test-fixtures/image-symlinks/bogus.txt", 657 refCount: 0, 658 }, 659 { 660 name: "finds a file (absolute)", 661 root: "./test-fixtures/", 662 input: "/image-symlinks/file-1.txt", 663 expected: "image-symlinks/file-1.txt", 664 refCount: 1, 665 }, 666 { 667 name: "directories ignored", 668 root: "./test-fixtures/", 669 input: "/image-symlinks", 670 refCount: 0, 671 forcePositiveHasPath: true, 672 }, 673 } 674 for _, c := range cases { 675 t.Run(c.name, func(t *testing.T) { 676 resolver, err := NewFromDirectory(c.root, "") 677 assert.NoError(t, err) 678 679 hasPath := resolver.HasPath(c.input) 680 if !c.forcePositiveHasPath { 681 if c.refCount != 0 && !hasPath { 682 t.Errorf("expected HasPath() to indicate existence, but did not") 683 } else if c.refCount == 0 && hasPath { 684 t.Errorf("expected HasPath() to NOT indicate existence, but does") 685 } 686 } else if !hasPath { 687 t.Errorf("expected HasPath() to indicate existence, but did not (force path)") 688 } 689 690 refs, err := resolver.FilesByPath(c.input) 691 require.NoError(t, err) 692 assert.Len(t, refs, c.refCount) 693 for _, actual := range refs { 694 assert.Equal(t, c.expected, actual.RealPath) 695 } 696 }) 697 } 698 } 699 700 func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) { 701 cases := []struct { 702 name string 703 input []string 704 refCount int 705 }{ 706 { 707 name: "finds multiple files", 708 input: []string{"image-symlinks/file-1.txt", "image-symlinks/file-2.txt"}, 709 refCount: 2, 710 }, 711 { 712 name: "skips non-existing files", 713 input: []string{"image-symlinks/bogus.txt", "image-symlinks/file-1.txt"}, 714 refCount: 1, 715 }, 716 { 717 name: "does not return anything for non-existing directories", 718 input: []string{"non-existing/bogus.txt", "non-existing/file-1.txt"}, 719 refCount: 0, 720 }, 721 } 722 for _, c := range cases { 723 t.Run(c.name, func(t *testing.T) { 724 resolver, err := NewFromDirectory("./test-fixtures", "") 725 assert.NoError(t, err) 726 refs, err := resolver.FilesByPath(c.input...) 727 assert.NoError(t, err) 728 729 if len(refs) != c.refCount { 730 t.Errorf("unexpected number of refs: %d != %d", len(refs), c.refCount) 731 } 732 }) 733 } 734 } 735 736 func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) { 737 resolver, err := NewFromDirectory("./test-fixtures", "") 738 assert.NoError(t, err) 739 refs, err := resolver.FilesByGlob("**/image-symlinks/file*") 740 assert.NoError(t, err) 741 742 assert.Len(t, refs, 2) 743 } 744 745 func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) { 746 resolver, err := NewFromDirectory("./test-fixtures/image-symlinks", "") 747 assert.NoError(t, err) 748 refs, err := resolver.FilesByGlob("**/*.txt") 749 assert.NoError(t, err) 750 assert.Len(t, refs, 6) 751 } 752 753 func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) { 754 resolver, err := NewFromDirectory("./test-fixtures", "") 755 assert.NoError(t, err) 756 refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt") 757 assert.NoError(t, err) 758 759 assert.Len(t, refs, 1) 760 assert.Equal(t, "image-symlinks/file-1.txt", refs[0].RealPath) 761 } 762 763 func TestDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) { 764 765 tests := []struct { 766 name string 767 fixture string 768 }{ 769 { 770 name: "one degree", 771 fixture: "link_to_new_readme", 772 }, 773 { 774 name: "two degrees", 775 fixture: "link_to_link_to_new_readme", 776 }, 777 } 778 779 for _, test := range tests { 780 t.Run(test.name, func(t *testing.T) { 781 resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "") 782 assert.NoError(t, err) 783 784 refs, err := resolver.FilesByPath(test.fixture) 785 require.NoError(t, err) 786 assert.Len(t, refs, 1) 787 788 reader, err := resolver.FileContentsByLocation(refs[0]) 789 require.NoError(t, err) 790 791 actual, err := io.ReadAll(reader) 792 require.NoError(t, err) 793 794 expected, err := os.ReadFile("test-fixtures/symlinks-simple/readme") 795 require.NoError(t, err) 796 797 assert.Equal(t, string(expected), string(actual)) 798 }) 799 } 800 } 801 802 func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) { 803 // let's make certain that "dev/place" is not ignored, since it is not "/dev/place" 804 resolver, err := NewFromDirectory("test-fixtures/system_paths/target", "") 805 assert.NoError(t, err) 806 807 // all paths should be found (non filtering matches a path) 808 locations, err := resolver.FilesByGlob("**/place") 809 assert.NoError(t, err) 810 // 4: within target/ 811 // 1: target/link --> relative path to "place" // NOTE: this is filtered out since it not unique relative to outside_root/link_target/place 812 // 1: outside_root/link_target/place 813 assert.Len(t, locations, 5) 814 815 // ensure that symlink indexing outside of root worked 816 testLocation := "test-fixtures/system_paths/outside_root/link_target/place" 817 ok := false 818 for _, location := range locations { 819 if strings.HasSuffix(location.RealPath, testLocation) { 820 ok = true 821 } 822 } 823 824 if !ok { 825 t.Fatalf("could not find test location=%q", testLocation) 826 } 827 } 828 829 var _ fs.FileInfo = (*testFileInfo)(nil) 830 831 type testFileInfo struct { 832 mode os.FileMode 833 } 834 835 func (t testFileInfo) Name() string { 836 panic("implement me") 837 } 838 839 func (t testFileInfo) Size() int64 { 840 panic("implement me") 841 } 842 843 func (t testFileInfo) Mode() fs.FileMode { 844 return t.mode 845 } 846 847 func (t testFileInfo) ModTime() time.Time { 848 panic("implement me") 849 } 850 851 func (t testFileInfo) IsDir() bool { 852 panic("implement me") 853 } 854 855 func (t testFileInfo) Sys() interface{} { 856 panic("implement me") 857 } 858 859 func Test_isUnallowableFileType(t *testing.T) { 860 tests := []struct { 861 name string 862 info os.FileInfo 863 expected error 864 }{ 865 { 866 name: "regular file", 867 info: testFileInfo{ 868 mode: 0, 869 }, 870 }, 871 { 872 name: "dir", 873 info: testFileInfo{ 874 mode: os.ModeDir, 875 }, 876 }, 877 { 878 name: "symlink", 879 info: testFileInfo{ 880 mode: os.ModeSymlink, 881 }, 882 }, 883 { 884 name: "socket", 885 info: testFileInfo{ 886 mode: os.ModeSocket, 887 }, 888 expected: ErrSkipPath, 889 }, 890 { 891 name: "named pipe", 892 info: testFileInfo{ 893 mode: os.ModeNamedPipe, 894 }, 895 expected: ErrSkipPath, 896 }, 897 { 898 name: "char device", 899 info: testFileInfo{ 900 mode: os.ModeCharDevice, 901 }, 902 expected: ErrSkipPath, 903 }, 904 { 905 name: "block device", 906 info: testFileInfo{ 907 mode: os.ModeDevice, 908 }, 909 expected: ErrSkipPath, 910 }, 911 { 912 name: "irregular", 913 info: testFileInfo{ 914 mode: os.ModeIrregular, 915 }, 916 expected: ErrSkipPath, 917 }, 918 } 919 for _, test := range tests { 920 t.Run(test.name, func(t *testing.T) { 921 assert.Equal(t, test.expected, disallowByFileType("", "dont/care", test.info, nil)) 922 }) 923 } 924 } 925 926 func Test_directoryResolver_FilesByMIMEType(t *testing.T) { 927 tests := []struct { 928 fixturePath string 929 mimeType string 930 expectedPaths *strset.Set 931 }{ 932 { 933 fixturePath: "./test-fixtures/image-simple", 934 mimeType: "text/plain", 935 expectedPaths: strset.New("file-1.txt", "file-2.txt", "target/really/nested/file-3.txt", "Dockerfile"), 936 }, 937 } 938 for _, test := range tests { 939 t.Run(test.fixturePath, func(t *testing.T) { 940 resolver, err := NewFromDirectory(test.fixturePath, "") 941 assert.NoError(t, err) 942 locations, err := resolver.FilesByMIMEType(test.mimeType) 943 assert.NoError(t, err) 944 assert.Equal(t, test.expectedPaths.Size(), len(locations)) 945 for _, l := range locations { 946 assert.True(t, test.expectedPaths.Has(l.RealPath), "does not have path %q", l.RealPath) 947 } 948 }) 949 } 950 } 951 952 func Test_IndexingNestedSymLinks(t *testing.T) { 953 resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "") 954 require.NoError(t, err) 955 956 // check that we can get the real path 957 locations, err := resolver.FilesByPath("./readme") 958 require.NoError(t, err) 959 assert.Len(t, locations, 1) 960 961 // check that we can access the same file via 1 symlink 962 locations, err = resolver.FilesByPath("./link_to_new_readme") 963 require.NoError(t, err) 964 require.Len(t, locations, 1) 965 assert.Equal(t, "readme", locations[0].RealPath) 966 assert.Equal(t, "link_to_new_readme", locations[0].AccessPath) 967 968 // check that we can access the same file via 2 symlinks 969 locations, err = resolver.FilesByPath("./link_to_link_to_new_readme") 970 require.NoError(t, err) 971 require.Len(t, locations, 1) 972 assert.Equal(t, "readme", locations[0].RealPath) 973 assert.Equal(t, "link_to_link_to_new_readme", locations[0].AccessPath) 974 975 // check that we can access the same file via 2 symlinks 976 locations, err = resolver.FilesByGlob("**/link_*") 977 require.NoError(t, err) 978 require.Len(t, locations, 1) // you would think this is 2, however, they point to the same file, and glob only returns unique files 979 980 // returned locations can be in any order 981 expectedAccessPaths := []string{ 982 "link_to_link_to_new_readme", 983 //"link_to_new_readme", // we filter out this one because the first symlink resolves to the same file 984 } 985 986 expectedRealPaths := []string{ 987 "readme", 988 } 989 990 actualRealPaths := strset.New() 991 actualAccessPaths := strset.New() 992 for _, a := range locations { 993 actualAccessPaths.Add(a.AccessPath) 994 actualRealPaths.Add(a.RealPath) 995 } 996 997 assert.ElementsMatch(t, expectedAccessPaths, actualAccessPaths.List()) 998 assert.ElementsMatch(t, expectedRealPaths, actualRealPaths.List()) 999 } 1000 1001 func Test_IndexingNestedSymLinks_ignoredIndexes(t *testing.T) { 1002 filterFn := func(_, path string, _ os.FileInfo, _ error) error { 1003 if strings.HasSuffix(path, string(filepath.Separator)+"readme") { 1004 return ErrSkipPath 1005 } 1006 return nil 1007 } 1008 1009 resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "", filterFn) 1010 require.NoError(t, err) 1011 1012 // the path to the real file is PRUNED from the index, so we should NOT expect a location returned 1013 locations, err := resolver.FilesByPath("./readme") 1014 require.NoError(t, err) 1015 assert.Empty(t, locations) 1016 1017 // check that we cannot access the file even via symlink 1018 locations, err = resolver.FilesByPath("./link_to_new_readme") 1019 require.NoError(t, err) 1020 assert.Empty(t, locations) 1021 1022 // check that we still cannot access the same file via 2 symlinks 1023 locations, err = resolver.FilesByPath("./link_to_link_to_new_readme") 1024 require.NoError(t, err) 1025 assert.Empty(t, locations) 1026 } 1027 1028 func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) { 1029 resolver, err := NewFromDirectory("./test-fixtures/symlinks-multiple-roots/root", "") 1030 require.NoError(t, err) 1031 1032 // check that we can get the real path 1033 locations, err := resolver.FilesByPath("./readme") 1034 require.NoError(t, err) 1035 assert.Len(t, locations, 1) 1036 1037 // check that we can access the same file via 2 symlinks (link_to_link_to_readme -> link_to_readme -> readme) 1038 locations, err = resolver.FilesByPath("./link_to_link_to_readme") 1039 require.NoError(t, err) 1040 assert.Len(t, locations, 1) 1041 1042 // something looks wrong here 1043 t.Failed() 1044 } 1045 1046 func Test_RootViaSymlink(t *testing.T) { 1047 resolver, err := NewFromDirectory("./test-fixtures/symlinked-root/nested/link-root", "") 1048 require.NoError(t, err) 1049 1050 locations, err := resolver.FilesByPath("./file1.txt") 1051 require.NoError(t, err) 1052 assert.Len(t, locations, 1) 1053 1054 locations, err = resolver.FilesByPath("./nested/file2.txt") 1055 require.NoError(t, err) 1056 assert.Len(t, locations, 1) 1057 1058 locations, err = resolver.FilesByPath("./nested/linked-file1.txt") 1059 require.NoError(t, err) 1060 assert.Len(t, locations, 1) 1061 } 1062 1063 func Test_directoryResolver_FileContentsByLocation(t *testing.T) { 1064 cwd, err := os.Getwd() 1065 require.NoError(t, err) 1066 1067 r, err := NewFromDirectory(".", "") 1068 require.NoError(t, err) 1069 1070 exists, existingPath, err := r.tree.File(stereoscopeFile.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt"))) 1071 require.True(t, exists) 1072 require.NoError(t, err) 1073 require.True(t, existingPath.HasReference()) 1074 1075 tests := []struct { 1076 name string 1077 location file.Location 1078 expects string 1079 err bool 1080 }{ 1081 { 1082 name: "use file reference for content requests", 1083 location: file.NewLocationFromDirectory("some/place", *existingPath.Reference), 1084 expects: "this file has contents", 1085 }, 1086 { 1087 name: "error on empty file reference", 1088 location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), 1089 err: true, 1090 }, 1091 } 1092 for _, test := range tests { 1093 t.Run(test.name, func(t *testing.T) { 1094 1095 actual, err := r.FileContentsByLocation(test.location) 1096 if test.err { 1097 require.Error(t, err) 1098 return 1099 } 1100 1101 require.NoError(t, err) 1102 if test.expects != "" { 1103 b, err := io.ReadAll(actual) 1104 require.NoError(t, err) 1105 assert.Equal(t, test.expects, string(b)) 1106 } 1107 }) 1108 } 1109 } 1110 1111 func Test_SymlinkLoopWithGlobsShouldResolve(t *testing.T) { 1112 test := func(t *testing.T) { 1113 resolver, err := NewFromDirectory("./test-fixtures/symlinks-loop", "") 1114 require.NoError(t, err) 1115 1116 locations, err := resolver.FilesByGlob("**/file.target") 1117 require.NoError(t, err) 1118 1119 require.Len(t, locations, 1) 1120 assert.Equal(t, "devices/loop0/file.target", locations[0].RealPath) 1121 } 1122 1123 testWithTimeout(t, 5*time.Second, test) 1124 } 1125 1126 func TestDirectoryResolver_FilesByPath_baseRoot(t *testing.T) { 1127 cases := []struct { 1128 name string 1129 root string 1130 input string 1131 expected []string 1132 }{ 1133 { 1134 name: "should find the base file", 1135 root: "./test-fixtures/symlinks-base/", 1136 input: "./base", 1137 expected: []string{ 1138 "/base", 1139 }, 1140 }, 1141 { 1142 name: "should follow a link with a pivoted root", 1143 root: "./test-fixtures/symlinks-base/", 1144 input: "./foo", 1145 expected: []string{ 1146 "/base", 1147 }, 1148 }, 1149 { 1150 name: "should follow a relative link with extra parents", 1151 root: "./test-fixtures/symlinks-base/", 1152 input: "./bar", 1153 expected: []string{ 1154 "/base", 1155 }, 1156 }, 1157 { 1158 name: "should follow an absolute link with extra parents", 1159 root: "./test-fixtures/symlinks-base/", 1160 input: "./baz", 1161 expected: []string{ 1162 "/base", 1163 }, 1164 }, 1165 { 1166 name: "should follow an absolute link with extra parents", 1167 root: "./test-fixtures/symlinks-base/", 1168 input: "./sub/link", 1169 expected: []string{ 1170 "/sub/item", 1171 }, 1172 }, 1173 { 1174 name: "should follow chained pivoted link", 1175 root: "./test-fixtures/symlinks-base/", 1176 input: "./chain", 1177 expected: []string{ 1178 "/base", 1179 }, 1180 }, 1181 } 1182 for _, c := range cases { 1183 t.Run(c.name, func(t *testing.T) { 1184 resolver, err := NewFromDirectory(c.root, c.root) 1185 assert.NoError(t, err) 1186 1187 refs, err := resolver.FilesByPath(c.input) 1188 require.NoError(t, err) 1189 assert.Len(t, refs, len(c.expected)) 1190 s := strset.New() 1191 for _, actual := range refs { 1192 s.Add(actual.RealPath) 1193 } 1194 assert.ElementsMatch(t, c.expected, s.List()) 1195 }) 1196 } 1197 1198 } 1199 1200 func Test_directoryResolver_resolvesLinks(t *testing.T) { 1201 tests := []struct { 1202 name string 1203 runner func(file.Resolver) []file.Location 1204 expected []file.Location 1205 }{ 1206 { 1207 name: "by mimetype", 1208 runner: func(resolver file.Resolver) []file.Location { 1209 // links should not show up when searching mimetype 1210 actualLocations, err := resolver.FilesByMIMEType("text/plain") 1211 assert.NoError(t, err) 1212 return actualLocations 1213 }, 1214 expected: []file.Location{ 1215 file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" 1216 file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" 1217 file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" 1218 file.NewLocation("parent/file-4.txt"), // note: missing virtual path "file-4.txt" 1219 }, 1220 }, 1221 { 1222 name: "by glob to links", 1223 runner: func(resolver file.Resolver) []file.Location { 1224 // links are searched, but resolve to the real files 1225 // for that reason we need to place **/ in front (which is not the same for other resolvers) 1226 actualLocations, err := resolver.FilesByGlob("**/*ink-*") 1227 assert.NoError(t, err) 1228 return actualLocations 1229 }, 1230 expected: []file.Location{ 1231 file.NewVirtualLocation("file-1.txt", "link-1"), 1232 file.NewVirtualLocation("file-2.txt", "link-2"), 1233 // we already have this real file path via another link, so only one is returned 1234 //file.NewVirtualLocation("file-2.txt", "link-indirect"), 1235 file.NewVirtualLocation("file-3.txt", "link-within"), 1236 }, 1237 }, 1238 { 1239 name: "by basename", 1240 runner: func(resolver file.Resolver) []file.Location { 1241 // links are searched, but resolve to the real files 1242 actualLocations, err := resolver.FilesByGlob("**/file-2.txt") 1243 assert.NoError(t, err) 1244 return actualLocations 1245 }, 1246 expected: []file.Location{ 1247 // this has two copies in the base image, which overwrites the same location 1248 file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt", 1249 }, 1250 }, 1251 { 1252 name: "by basename glob", 1253 runner: func(resolver file.Resolver) []file.Location { 1254 // links are searched, but resolve to the real files 1255 actualLocations, err := resolver.FilesByGlob("**/file-?.txt") 1256 assert.NoError(t, err) 1257 return actualLocations 1258 }, 1259 expected: []file.Location{ 1260 file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" 1261 file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" 1262 file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" 1263 file.NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt" 1264 }, 1265 }, 1266 { 1267 name: "by basename glob to links", 1268 runner: func(resolver file.Resolver) []file.Location { 1269 actualLocations, err := resolver.FilesByGlob("**/link-*") 1270 assert.NoError(t, err) 1271 return actualLocations 1272 }, 1273 expected: []file.Location{ 1274 file.NewVirtualLocation("file-1.txt", "link-1"), 1275 file.NewVirtualLocation("file-2.txt", "link-2"), 1276 1277 // we already have this real file path via another link, so only one is returned 1278 //file.NewVirtualLocation("file-2.txt", "link-indirect"), 1279 1280 file.NewVirtualLocation("file-3.txt", "link-within"), 1281 }, 1282 }, 1283 { 1284 name: "by extension", 1285 runner: func(resolver file.Resolver) []file.Location { 1286 // links are searched, but resolve to the real files 1287 actualLocations, err := resolver.FilesByGlob("**/*.txt") 1288 assert.NoError(t, err) 1289 return actualLocations 1290 }, 1291 expected: []file.Location{ 1292 file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" 1293 file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" 1294 file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" 1295 file.NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt" 1296 }, 1297 }, 1298 { 1299 name: "by path to degree 1 link", 1300 runner: func(resolver file.Resolver) []file.Location { 1301 // links resolve to the final file 1302 actualLocations, err := resolver.FilesByPath("/link-2") 1303 assert.NoError(t, err) 1304 return actualLocations 1305 }, 1306 expected: []file.Location{ 1307 // we have multiple copies across layers 1308 file.NewVirtualLocation("file-2.txt", "link-2"), 1309 }, 1310 }, 1311 { 1312 name: "by path to degree 2 link", 1313 runner: func(resolver file.Resolver) []file.Location { 1314 // multiple links resolves to the final file 1315 actualLocations, err := resolver.FilesByPath("/link-indirect") 1316 assert.NoError(t, err) 1317 return actualLocations 1318 }, 1319 expected: []file.Location{ 1320 // we have multiple copies across layers 1321 file.NewVirtualLocation("file-2.txt", "link-indirect"), 1322 }, 1323 }, 1324 } 1325 1326 for _, test := range tests { 1327 t.Run(test.name, func(t *testing.T) { 1328 resolver, err := NewFromDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture", "") 1329 require.NoError(t, err) 1330 assert.NoError(t, err) 1331 1332 actual := test.runner(resolver) 1333 1334 compareLocations(t, test.expected, actual) 1335 }) 1336 } 1337 } 1338 1339 func TestDirectoryResolver_DoNotAddVirtualPathsToTree(t *testing.T) { 1340 resolver, err := NewFromDirectory("./test-fixtures/symlinks-prune-indexing", "") 1341 require.NoError(t, err) 1342 1343 var allRealPaths []stereoscopeFile.Path 1344 for l := range resolver.AllLocations(context.Background()) { 1345 allRealPaths = append(allRealPaths, stereoscopeFile.Path(l.RealPath)) 1346 } 1347 pathSet := stereoscopeFile.NewPathSet(allRealPaths...) 1348 1349 assert.False(t, 1350 pathSet.Contains("before-path/file.txt"), 1351 "symlink destinations should only be indexed at their real path, not through their virtual (symlinked) path", 1352 ) 1353 1354 assert.False(t, 1355 pathSet.Contains("a-path/file.txt"), 1356 "symlink destinations should only be indexed at their real path, not through their virtual (symlinked) path", 1357 ) 1358 1359 } 1360 1361 func TestDirectoryResolver_FilesContents_errorOnDirRequest(t *testing.T) { 1362 defer goleak.VerifyNone(t) 1363 resolver, err := NewFromDirectory("./test-fixtures/system_paths", "") 1364 assert.NoError(t, err) 1365 1366 var dirLoc *file.Location 1367 ctx, cancel := context.WithCancel(context.Background()) 1368 defer cancel() 1369 for loc := range resolver.AllLocations(ctx) { 1370 entry, err := resolver.index.Get(loc.Reference()) 1371 require.NoError(t, err) 1372 if entry.Metadata.IsDir() { 1373 dirLoc = &loc 1374 break 1375 } 1376 } 1377 1378 require.NotNil(t, dirLoc) 1379 1380 reader, err := resolver.FileContentsByLocation(*dirLoc) 1381 require.Error(t, err) 1382 require.Nil(t, reader) 1383 } 1384 1385 func TestDirectoryResolver_AllLocations(t *testing.T) { 1386 resolver, err := NewFromDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture", "") 1387 assert.NoError(t, err) 1388 1389 paths := strset.New() 1390 for loc := range resolver.AllLocations(context.Background()) { 1391 if strings.HasPrefix(loc.RealPath, "/") { 1392 // ignore outside the fixture root for now 1393 continue 1394 } 1395 paths.Add(loc.RealPath) 1396 } 1397 expected := []string{ 1398 "file-1.txt", 1399 "file-2.txt", 1400 "file-3.txt", 1401 "link-1", 1402 "link-2", 1403 "link-dead", 1404 "link-indirect", 1405 "link-within", 1406 "parent", 1407 "parent-link", 1408 "parent/file-4.txt", 1409 } 1410 1411 pathsList := paths.List() 1412 sort.Strings(pathsList) 1413 1414 assert.ElementsMatchf(t, expected, pathsList, "expected all paths to be indexed, but found different paths: \n%s", cmp.Diff(expected, paths.List())) 1415 } 1416 1417 func TestAllLocationsDoesNotLeakGoRoutine(t *testing.T) { 1418 defer goleak.VerifyNone(t) 1419 resolver, err := NewFromDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture", "") 1420 require.NoError(t, err) 1421 ctx, cancel := context.WithCancel(context.Background()) 1422 for range resolver.AllLocations(ctx) { 1423 break 1424 } 1425 cancel() 1426 }