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