storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/object-api-listobjects_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2015-2020 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/md5" 23 "encoding/hex" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "strconv" 28 "strings" 29 "testing" 30 ) 31 32 // Wrapper for calling ListObjects tests for both Erasure multiple disks and single node setup. 33 func TestListObjects(t *testing.T) { 34 ExecObjectLayerTest(t, testListObjects) 35 } 36 37 // Unit test for ListObjects in general. 38 func testListObjects(obj ObjectLayer, instanceType string, t1 TestErrHandler) { 39 t, _ := t1.(*testing.T) 40 testBuckets := []string{ 41 // This bucket is used for testing ListObject operations. 42 "test-bucket-list-object", 43 // This bucket will be tested with empty directories 44 "test-bucket-empty-dir", 45 // Will not store any objects in this bucket, 46 // Its to test ListObjects on an empty bucket. 47 "empty-bucket", 48 // Listing the case where the marker > last object. 49 "test-bucket-single-object", 50 // Listing uncommon delimiter. 51 "test-bucket-delimiter", 52 // Listing prefixes > maxKeys 53 "test-bucket-max-keys-prefixes", 54 } 55 for _, bucket := range testBuckets { 56 err := obj.MakeBucketWithLocation(context.Background(), bucket, BucketOptions{}) 57 if err != nil { 58 t.Fatalf("%s : %s", instanceType, err.Error()) 59 } 60 } 61 62 var err error 63 testObjects := []struct { 64 parentBucket string 65 name string 66 content string 67 meta map[string]string 68 }{ 69 {testBuckets[0], "Asia-maps.png", "asis-maps", map[string]string{"content-type": "image/png"}}, 70 {testBuckets[0], "Asia/India/India-summer-photos-1", "contentstring", nil}, 71 {testBuckets[0], "Asia/India/Karnataka/Bangalore/Koramangala/pics", "contentstring", nil}, 72 {testBuckets[0], "newPrefix0", "newPrefix0", nil}, 73 {testBuckets[0], "newPrefix1", "newPrefix1", nil}, 74 {testBuckets[0], "newzen/zen/recurse/again/again/again/pics", "recurse", nil}, 75 {testBuckets[0], "obj0", "obj0", nil}, 76 {testBuckets[0], "obj1", "obj1", nil}, 77 {testBuckets[0], "obj2", "obj2", nil}, 78 {testBuckets[1], "obj1", "obj1", nil}, 79 {testBuckets[1], "obj2", "obj2", nil}, 80 {testBuckets[1], "temporary/0/", "", nil}, 81 {testBuckets[3], "A/B", "contentstring", nil}, 82 {testBuckets[4], "file1/receipt.json", "content", nil}, 83 {testBuckets[4], "file1/guidSplunk-aaaa/file", "content", nil}, 84 {testBuckets[5], "dir/day_id=2017-10-10/issue", "content", nil}, 85 {testBuckets[5], "dir/day_id=2017-10-11/issue", "content", nil}, 86 } 87 for _, object := range testObjects { 88 md5Bytes := md5.Sum([]byte(object.content)) 89 _, err = obj.PutObject(context.Background(), object.parentBucket, object.name, mustGetPutObjReader(t, bytes.NewBufferString(object.content), 90 int64(len(object.content)), hex.EncodeToString(md5Bytes[:]), ""), ObjectOptions{UserDefined: object.meta}) 91 if err != nil { 92 t.Fatalf("%s : %s", instanceType, err.Error()) 93 } 94 95 } 96 97 // Formualting the result data set to be expected from ListObjects call inside the tests, 98 // This will be used in testCases and used for asserting the correctness of ListObjects output in the tests. 99 100 resultCases := []ListObjectsInfo{ 101 // ListObjectsResult-0. 102 // Testing for listing all objects in the bucket, (testCase 20,21,22). 103 { 104 IsTruncated: false, 105 Objects: []ObjectInfo{ 106 {Name: "Asia-maps.png"}, 107 {Name: "Asia/India/India-summer-photos-1"}, 108 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 109 {Name: "newPrefix0"}, 110 {Name: "newPrefix1"}, 111 {Name: "newzen/zen/recurse/again/again/again/pics"}, 112 {Name: "obj0"}, 113 {Name: "obj1"}, 114 {Name: "obj2"}, 115 }, 116 }, 117 // ListObjectsResult-1. 118 // Used for asserting the truncated case, (testCase 23). 119 { 120 IsTruncated: true, 121 Objects: []ObjectInfo{ 122 {Name: "Asia-maps.png"}, 123 {Name: "Asia/India/India-summer-photos-1"}, 124 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 125 {Name: "newPrefix0"}, 126 {Name: "newPrefix1"}, 127 }, 128 }, 129 // ListObjectsResult-2. 130 // (TestCase 24). 131 { 132 IsTruncated: true, 133 Objects: []ObjectInfo{ 134 {Name: "Asia-maps.png"}, 135 {Name: "Asia/India/India-summer-photos-1"}, 136 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 137 {Name: "newPrefix0"}, 138 }, 139 }, 140 // ListObjectsResult-3. 141 // (TestCase 25). 142 { 143 IsTruncated: true, 144 Objects: []ObjectInfo{ 145 {Name: "Asia-maps.png"}, 146 {Name: "Asia/India/India-summer-photos-1"}, 147 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 148 }, 149 }, 150 // ListObjectsResult-4. 151 // Again used for truncated case. 152 // (TestCase 26). 153 { 154 IsTruncated: true, 155 Objects: []ObjectInfo{ 156 {Name: "Asia-maps.png"}, 157 }, 158 }, 159 // ListObjectsResult-5. 160 // Used for Asserting prefixes. 161 // Used for test case with prefix "new", (testCase 27-29). 162 { 163 IsTruncated: false, 164 Objects: []ObjectInfo{ 165 {Name: "newPrefix0"}, 166 {Name: "newPrefix1"}, 167 {Name: "newzen/zen/recurse/again/again/again/pics"}, 168 }, 169 }, 170 // ListObjectsResult-6. 171 // Used for Asserting prefixes. 172 // Used for test case with prefix = "obj", (testCase 30). 173 { 174 IsTruncated: false, 175 Objects: []ObjectInfo{ 176 {Name: "obj0"}, 177 {Name: "obj1"}, 178 {Name: "obj2"}, 179 }, 180 }, 181 // ListObjectsResult-7. 182 // Used for Asserting prefixes and truncation. 183 // Used for test case with prefix = "new" and maxKeys = 1, (testCase 31). 184 { 185 IsTruncated: true, 186 Objects: []ObjectInfo{ 187 {Name: "newPrefix0"}, 188 }, 189 }, 190 // ListObjectsResult-8. 191 // Used for Asserting prefixes. 192 // Used for test case with prefix = "obj" and maxKeys = 2, (testCase 32). 193 { 194 IsTruncated: true, 195 Objects: []ObjectInfo{ 196 {Name: "obj0"}, 197 {Name: "obj1"}, 198 }, 199 }, 200 // ListObjectsResult-9. 201 // Used for asserting the case with marker, but without prefix. 202 //marker is set to "newPrefix0" in the testCase, (testCase 33). 203 { 204 IsTruncated: false, 205 Objects: []ObjectInfo{ 206 {Name: "newPrefix1"}, 207 {Name: "newzen/zen/recurse/again/again/again/pics"}, 208 {Name: "obj0"}, 209 {Name: "obj1"}, 210 {Name: "obj2"}, 211 }, 212 }, 213 // ListObjectsResult-10. 214 //marker is set to "newPrefix1" in the testCase, (testCase 34). 215 { 216 IsTruncated: false, 217 Objects: []ObjectInfo{ 218 {Name: "newzen/zen/recurse/again/again/again/pics"}, 219 {Name: "obj0"}, 220 {Name: "obj1"}, 221 {Name: "obj2"}, 222 }, 223 }, 224 // ListObjectsResult-11. 225 //marker is set to "obj0" in the testCase, (testCase 35). 226 { 227 IsTruncated: false, 228 Objects: []ObjectInfo{ 229 {Name: "obj1"}, 230 {Name: "obj2"}, 231 }, 232 }, 233 // ListObjectsResult-12. 234 // Marker is set to "obj1" in the testCase, (testCase 36). 235 { 236 IsTruncated: false, 237 Objects: []ObjectInfo{ 238 {Name: "obj2"}, 239 }, 240 }, 241 // ListObjectsResult-13. 242 // Marker is set to "man" in the testCase, (testCase37). 243 { 244 IsTruncated: false, 245 Objects: []ObjectInfo{ 246 {Name: "newPrefix0"}, 247 {Name: "newPrefix1"}, 248 {Name: "newzen/zen/recurse/again/again/again/pics"}, 249 {Name: "obj0"}, 250 {Name: "obj1"}, 251 {Name: "obj2"}, 252 }, 253 }, 254 // ListObjectsResult-14. 255 // Marker is set to "Abc" in the testCase, (testCase 39). 256 { 257 IsTruncated: false, 258 Objects: []ObjectInfo{ 259 {Name: "Asia-maps.png"}, 260 {Name: "Asia/India/India-summer-photos-1"}, 261 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 262 {Name: "newPrefix0"}, 263 {Name: "newPrefix1"}, 264 {Name: "newzen/zen/recurse/again/again/again/pics"}, 265 {Name: "obj0"}, 266 {Name: "obj1"}, 267 {Name: "obj2"}, 268 }, 269 }, 270 // ListObjectsResult-15. 271 // Marker is set to "Asia/India/India-summer-photos-1" in the testCase, (testCase 40). 272 { 273 IsTruncated: false, 274 Objects: []ObjectInfo{ 275 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 276 {Name: "newPrefix0"}, 277 {Name: "newPrefix1"}, 278 {Name: "newzen/zen/recurse/again/again/again/pics"}, 279 {Name: "obj0"}, 280 {Name: "obj1"}, 281 {Name: "obj2"}, 282 }, 283 }, 284 // ListObjectsResult-16. 285 // Marker is set to "Asia/India/Karnataka/Bangalore/Koramangala/pics" in the testCase, (testCase 41). 286 { 287 IsTruncated: false, 288 Objects: []ObjectInfo{ 289 {Name: "newPrefix0"}, 290 {Name: "newPrefix1"}, 291 {Name: "newzen/zen/recurse/again/again/again/pics"}, 292 {Name: "obj0"}, 293 {Name: "obj1"}, 294 {Name: "obj2"}, 295 }, 296 }, 297 // ListObjectsResult-17. 298 // Used for asserting the case with marker, without prefix but with truncation. 299 // Marker = "newPrefix0" & maxKeys = 3 in the testCase, (testCase42). 300 // Output truncated to 3 values. 301 { 302 IsTruncated: true, 303 Objects: []ObjectInfo{ 304 {Name: "newPrefix1"}, 305 {Name: "newzen/zen/recurse/again/again/again/pics"}, 306 {Name: "obj0"}, 307 }, 308 }, 309 // ListObjectsResult-18. 310 // Marker = "newPrefix1" & maxkeys = 1 in the testCase, (testCase43). 311 // Output truncated to 1 value. 312 { 313 IsTruncated: true, 314 Objects: []ObjectInfo{ 315 {Name: "newzen/zen/recurse/again/again/again/pics"}, 316 }, 317 }, 318 // ListObjectsResult-19. 319 // Marker = "obj0" & maxKeys = 1 in the testCase, (testCase44). 320 // Output truncated to 1 value. 321 { 322 IsTruncated: true, 323 Objects: []ObjectInfo{ 324 {Name: "obj1"}, 325 }, 326 }, 327 // ListObjectsResult-20. 328 // Marker = "obj0" & prefix = "obj" in the testCase, (testCase 45). 329 { 330 IsTruncated: false, 331 Objects: []ObjectInfo{ 332 {Name: "obj1"}, 333 {Name: "obj2"}, 334 }, 335 }, 336 // ListObjectsResult-21. 337 // Marker = "obj1" & prefix = "obj" in the testCase, (testCase 46). 338 { 339 IsTruncated: false, 340 Objects: []ObjectInfo{ 341 {Name: "obj2"}, 342 }, 343 }, 344 // ListObjectsResult-22. 345 // Marker = "newPrefix0" & prefix = "new" in the testCase,, (testCase 47). 346 { 347 IsTruncated: false, 348 Objects: []ObjectInfo{ 349 {Name: "newPrefix1"}, 350 {Name: "newzen/zen/recurse/again/again/again/pics"}, 351 }, 352 }, 353 // ListObjectsResult-23. 354 // Prefix is set to "Asia/India/" in the testCase, and delimiter is not set (testCase 55). 355 { 356 IsTruncated: false, 357 Objects: []ObjectInfo{ 358 {Name: "Asia/India/India-summer-photos-1"}, 359 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 360 }, 361 }, 362 363 // ListObjectsResult-24. 364 // Prefix is set to "Asia" in the testCase, and delimiter is not set (testCase 56). 365 { 366 IsTruncated: false, 367 Objects: []ObjectInfo{ 368 {Name: "Asia-maps.png"}, 369 {Name: "Asia/India/India-summer-photos-1"}, 370 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 371 }, 372 }, 373 374 // ListObjectsResult-25. 375 // Prefix is set to "Asia" in the testCase, and delimiter is set (testCase 57). 376 { 377 IsTruncated: false, 378 Objects: []ObjectInfo{ 379 {Name: "Asia-maps.png"}, 380 }, 381 Prefixes: []string{"Asia/"}, 382 }, 383 // ListObjectsResult-26. 384 // prefix = "new" and delimiter is set in the testCase.(testCase 58). 385 { 386 IsTruncated: false, 387 Objects: []ObjectInfo{ 388 {Name: "newPrefix0"}, 389 {Name: "newPrefix1"}, 390 }, 391 Prefixes: []string{"newzen/"}, 392 }, 393 // ListObjectsResult-27. 394 // Prefix is set to "Asia/India/" in the testCase, and delimiter is set to forward slash '/' (testCase 59). 395 { 396 IsTruncated: false, 397 Objects: []ObjectInfo{ 398 {Name: "Asia/India/India-summer-photos-1"}, 399 }, 400 Prefixes: []string{"Asia/India/Karnataka/"}, 401 }, 402 // ListObjectsResult-28. 403 // Marker is set to "Asia/India/India-summer-photos-1" and delimiter set in the testCase, (testCase 60). 404 { 405 IsTruncated: false, 406 Objects: []ObjectInfo{ 407 {Name: "newPrefix0"}, 408 {Name: "newPrefix1"}, 409 {Name: "obj0"}, 410 {Name: "obj1"}, 411 {Name: "obj2"}, 412 }, 413 Prefixes: []string{"newzen/"}, 414 }, 415 // ListObjectsResult-29. 416 // Marker is set to "Asia/India/Karnataka/Bangalore/Koramangala/pics" in the testCase and delimiter set, (testCase 61). 417 { 418 IsTruncated: false, 419 Objects: []ObjectInfo{ 420 {Name: "newPrefix0"}, 421 {Name: "newPrefix1"}, 422 {Name: "obj0"}, 423 {Name: "obj1"}, 424 {Name: "obj2"}, 425 }, 426 Prefixes: []string{"newzen/"}, 427 }, 428 // ListObjectsResult-30. 429 // Prefix and Delimiter is set to '/', (testCase 62). 430 { 431 IsTruncated: false, 432 Objects: []ObjectInfo{}, 433 }, 434 // ListObjectsResult-31 Empty directory, recursive listing 435 { 436 IsTruncated: false, 437 Objects: []ObjectInfo{ 438 {Name: "obj1"}, 439 {Name: "obj2"}, 440 {Name: "temporary/0/"}, 441 }, 442 }, 443 // ListObjectsResult-32 Empty directory, non recursive listing 444 { 445 IsTruncated: false, 446 Objects: []ObjectInfo{ 447 {Name: "obj1"}, 448 {Name: "obj2"}, 449 }, 450 Prefixes: []string{"temporary/"}, 451 }, 452 // ListObjectsResult-33 Listing empty directory only 453 { 454 IsTruncated: false, 455 Objects: []ObjectInfo{ 456 {Name: "temporary/0/"}, 457 }, 458 }, 459 // ListObjectsResult-34: 460 // * Listing with marker > last object should return empty 461 // * Listing an object with a trailing slash and '/' delimiter 462 { 463 IsTruncated: false, 464 Objects: []ObjectInfo{}, 465 }, 466 // ListObjectsResult-35 list with custom uncommon delimiter 467 { 468 IsTruncated: false, 469 Objects: []ObjectInfo{ 470 {Name: "file1/receipt.json"}, 471 }, 472 Prefixes: []string{"file1/guidSplunk"}, 473 }, 474 // ListObjectsResult-36 list with nextmarker prefix and maxKeys set to 1. 475 { 476 IsTruncated: true, 477 Prefixes: []string{"dir/day_id=2017-10-10/"}, 478 }, 479 } 480 481 testCases := []struct { 482 // Inputs to ListObjects. 483 bucketName string 484 prefix string 485 marker string 486 delimiter string 487 maxKeys int32 488 // Expected output of ListObjects. 489 result ListObjectsInfo 490 err error 491 // Flag indicating whether the test is expected to pass or not. 492 shouldPass bool 493 }{ 494 // Test cases with invalid bucket names ( Test number 1-4 ). 495 {".test", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: ".test"}, false}, 496 {"Test", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "Test"}, false}, 497 {"---", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "---"}, false}, 498 {"ad", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "ad"}, false}, 499 // Using an existing file for bucket name, but its not a directory (5). 500 {"simple-file.txt", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "simple-file.txt"}, false}, 501 // Valid bucket names, but they donot exist (6-8). 502 {"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false}, 503 {"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false}, 504 {"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false}, 505 // Testing for failure cases with both perfix and marker (11). 506 // The prefix and marker combination to be valid it should satisfy strings.HasPrefix(marker, prefix). 507 {"test-bucket-list-object", "asia", "europe-object", "", 0, ListObjectsInfo{}, fmt.Errorf("Invalid combination of marker '%s' and prefix '%s'", "europe-object", "asia"), false}, 508 // Setting a non-existing directory to be prefix (12-13). 509 {"empty-bucket", "europe/france/", "", "", 1, ListObjectsInfo{}, nil, true}, 510 {"empty-bucket", "africa/tunisia/", "", "", 1, ListObjectsInfo{}, nil, true}, 511 // Testing on empty bucket, that is, bucket without any objects in it (14). 512 {"empty-bucket", "", "", "", 0, ListObjectsInfo{}, nil, true}, 513 // Setting maxKeys to negative value (15-16). 514 {"empty-bucket", "", "", "", -1, ListObjectsInfo{}, nil, true}, 515 {"empty-bucket", "", "", "", 1, ListObjectsInfo{}, nil, true}, 516 // Setting maxKeys to a very large value (17). 517 {"empty-bucket", "", "", "", 111100000, ListObjectsInfo{}, nil, true}, 518 // Testing for all 10 objects in the bucket (18). 519 {"test-bucket-list-object", "", "", "", 10, resultCases[0], nil, true}, 520 //Testing for negative value of maxKey, this should set maxKeys to listObjectsLimit (19). 521 {"test-bucket-list-object", "", "", "", -1, resultCases[0], nil, true}, 522 // Testing for very large value of maxKey, this should set maxKeys to listObjectsLimit (20). 523 {"test-bucket-list-object", "", "", "", 1234567890, resultCases[0], nil, true}, 524 // Testing for trancated value (21-24). 525 {"test-bucket-list-object", "", "", "", 5, resultCases[1], nil, true}, 526 {"test-bucket-list-object", "", "", "", 4, resultCases[2], nil, true}, 527 {"test-bucket-list-object", "", "", "", 3, resultCases[3], nil, true}, 528 {"test-bucket-list-object", "", "", "", 1, resultCases[4], nil, true}, 529 // Testing with prefix (25-28). 530 {"test-bucket-list-object", "new", "", "", 3, resultCases[5], nil, true}, 531 {"test-bucket-list-object", "new", "", "", 4, resultCases[5], nil, true}, 532 {"test-bucket-list-object", "new", "", "", 5, resultCases[5], nil, true}, 533 {"test-bucket-list-object", "obj", "", "", 3, resultCases[6], nil, true}, 534 {"test-bucket-list-object", "/obj", "", "", 0, ListObjectsInfo{}, nil, true}, 535 // Testing with prefix and truncation (29-30). 536 {"test-bucket-list-object", "new", "", "", 1, resultCases[7], nil, true}, 537 {"test-bucket-list-object", "obj", "", "", 2, resultCases[8], nil, true}, 538 // Testing with marker, but without prefix and truncation (31-35). 539 {"test-bucket-list-object", "", "newPrefix0", "", 6, resultCases[9], nil, true}, 540 {"test-bucket-list-object", "", "newPrefix1", "", 5, resultCases[10], nil, true}, 541 {"test-bucket-list-object", "", "obj0", "", 4, resultCases[11], nil, true}, 542 {"test-bucket-list-object", "", "obj1", "", 2, resultCases[12], nil, true}, 543 {"test-bucket-list-object", "", "man", "", 11, resultCases[13], nil, true}, 544 // Marker being set to a value which is greater than and all object names when sorted (36). 545 // Expected to send an empty response in this case. 546 {"test-bucket-list-object", "", "zen", "", 10, ListObjectsInfo{}, nil, true}, 547 // Marker being set to a value which is lesser than and all object names when sorted (37). 548 // Expected to send all the objects in the bucket in this case. 549 {"test-bucket-list-object", "", "Abc", "", 10, resultCases[14], nil, true}, 550 // Marker is to a hierarhical value (38-39). 551 {"test-bucket-list-object", "", "Asia/India/India-summer-photos-1", "", 10, resultCases[15], nil, true}, 552 {"test-bucket-list-object", "", "Asia/India/Karnataka/Bangalore/Koramangala/pics", "", 10, resultCases[16], nil, true}, 553 // Testing with marker and truncation, but no prefix (40-42). 554 {"test-bucket-list-object", "", "newPrefix0", "", 3, resultCases[17], nil, true}, 555 {"test-bucket-list-object", "", "newPrefix1", "", 1, resultCases[18], nil, true}, 556 {"test-bucket-list-object", "", "obj0", "", 1, resultCases[19], nil, true}, 557 // Testing with both marker and prefix, but without truncation (43-45). 558 // The valid combination of marker and prefix should satisfy strings.HasPrefix(marker, prefix). 559 {"test-bucket-list-object", "obj", "obj0", "", 2, resultCases[20], nil, true}, 560 {"test-bucket-list-object", "obj", "obj1", "", 1, resultCases[21], nil, true}, 561 {"test-bucket-list-object", "new", "newPrefix0", "", 2, resultCases[22], nil, true}, 562 // Testing with maxKeys set to 0 (46-52). 563 // The parameters have to valid. 564 {"test-bucket-list-object", "", "obj1", "", 0, ListObjectsInfo{}, nil, true}, 565 {"test-bucket-list-object", "", "obj0", "", 0, ListObjectsInfo{}, nil, true}, 566 {"test-bucket-list-object", "new", "", "", 0, ListObjectsInfo{}, nil, true}, 567 {"test-bucket-list-object", "obj", "", "", 0, ListObjectsInfo{}, nil, true}, 568 {"test-bucket-list-object", "obj", "obj0", "", 0, ListObjectsInfo{}, nil, true}, 569 {"test-bucket-list-object", "obj", "obj1", "", 0, ListObjectsInfo{}, nil, true}, 570 {"test-bucket-list-object", "new", "newPrefix0", "", 0, ListObjectsInfo{}, nil, true}, 571 // Tests on hierarchical key names as prefix. 572 // Without delimteter the code should recurse into the prefix Dir. 573 // Tests with prefix, but without delimiter (53-54). 574 {"test-bucket-list-object", "Asia/India/", "", "", 10, resultCases[23], nil, true}, 575 {"test-bucket-list-object", "Asia", "", "", 10, resultCases[24], nil, true}, 576 // Tests with prefix and delimiter (55-57). 577 // With delimiter the code should not recurse into the sub-directories of prefix Dir. 578 {"test-bucket-list-object", "Asia", "", SlashSeparator, 10, resultCases[25], nil, true}, 579 {"test-bucket-list-object", "new", "", SlashSeparator, 10, resultCases[26], nil, true}, 580 {"test-bucket-list-object", "Asia/India/", "", SlashSeparator, 10, resultCases[27], nil, true}, 581 // Test with marker set as hierarhical value and with delimiter. (58-59) 582 {"test-bucket-list-object", "", "Asia/India/India-summer-photos-1", SlashSeparator, 10, resultCases[28], nil, true}, 583 {"test-bucket-list-object", "", "Asia/India/Karnataka/Bangalore/Koramangala/pics", SlashSeparator, 10, resultCases[29], nil, true}, 584 // Test with prefix and delimiter set to '/'. (60) 585 {"test-bucket-list-object", SlashSeparator, "", SlashSeparator, 10, resultCases[30], nil, true}, 586 // Test with invalid prefix (61) 587 {"test-bucket-list-object", "\\", "", SlashSeparator, 10, ListObjectsInfo{}, nil, true}, 588 // Test listing an empty directory in recursive mode (62) 589 {"test-bucket-empty-dir", "", "", "", 10, resultCases[31], nil, true}, 590 // Test listing an empty directory in a non recursive mode (63) 591 {"test-bucket-empty-dir", "", "", SlashSeparator, 10, resultCases[32], nil, true}, 592 // Test listing a directory which contains an empty directory (64) 593 {"test-bucket-empty-dir", "", "temporary/", "", 10, resultCases[33], nil, true}, 594 // Test listing with marker > last object such that response should be empty (65) 595 {"test-bucket-single-object", "", "A/C", "", 1000, resultCases[34], nil, true}, 596 // Test listing an object with a trailing slash and a slash delimiter (66) 597 {"test-bucket-list-object", "Asia-maps.png/", "", "/", 1000, resultCases[34], nil, true}, 598 // Test listing an object with uncommon delimiter 599 {testBuckets[4], "", "", "guidSplunk", 1000, resultCases[35], nil, true}, 600 // Test listing an object with uncommon delimiter and matching prefix 601 {testBuckets[4], "file1/", "", "guidSplunk", 1000, resultCases[35], nil, true}, 602 // Test listing at prefix with expected prefix markers 603 {testBuckets[5], "dir/", "", SlashSeparator, 1, resultCases[36], nil, true}, 604 } 605 606 for i, testCase := range testCases { 607 testCase := testCase 608 t.Run(fmt.Sprintf("%s-Test%d", instanceType, i+1), func(t *testing.T) { 609 t.Log("ListObjects, bucket:", testCase.bucketName, "prefix:", testCase.prefix, "marker:", testCase.marker, "delimiter:", testCase.delimiter, "maxkeys:", testCase.maxKeys) 610 result, err := obj.ListObjects(context.Background(), testCase.bucketName, 611 testCase.prefix, testCase.marker, testCase.delimiter, int(testCase.maxKeys)) 612 if err != nil && testCase.shouldPass { 613 t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error()) 614 } 615 if err == nil && !testCase.shouldPass { 616 t.Errorf("Test %d: %s: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, instanceType, testCase.err.Error()) 617 } 618 // Failed as expected, but does it fail for the expected reason. 619 if err != nil && !testCase.shouldPass { 620 if !strings.Contains(err.Error(), testCase.err.Error()) { 621 t.Errorf("Test %d: %s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, instanceType, testCase.err.Error(), err.Error()) 622 } 623 } 624 // Since there are cases for which ListObjects fails, this is 625 // necessary. Test passes as expected, but the output values 626 // are verified for correctness here. 627 if err == nil && testCase.shouldPass { 628 // The length of the expected ListObjectsResult.Objects 629 // should match in both expected result from test cases 630 // and in the output. On failure calling t.Fatalf, 631 // otherwise it may lead to index out of range error in 632 // assertion following this. 633 if len(testCase.result.Objects) != len(result.Objects) { 634 t.Logf("want: %v", objInfoNames(testCase.result.Objects)) 635 t.Logf("got: %v", objInfoNames(result.Objects)) 636 t.Errorf("Test %d: %s: Expected number of object in the result to be '%d', but found '%d' objects instead", i+1, instanceType, len(testCase.result.Objects), len(result.Objects)) 637 } 638 for j := 0; j < len(testCase.result.Objects); j++ { 639 if j >= len(result.Objects) { 640 t.Errorf("Test %d: %s: Expected object name to be \"%s\", but not nothing instead", i+1, instanceType, testCase.result.Objects[j].Name) 641 continue 642 } 643 if testCase.result.Objects[j].Name != result.Objects[j].Name { 644 t.Errorf("Test %d: %s: Expected object name to be \"%s\", but found \"%s\" instead", i+1, instanceType, testCase.result.Objects[j].Name, result.Objects[j].Name) 645 } 646 // FIXME: we should always check for ETag 647 if result.Objects[j].ETag == "" && !strings.HasSuffix(result.Objects[j].Name, SlashSeparator) { 648 t.Errorf("Test %d: %s: Expected ETag to be not empty, but found empty instead (%v)", i+1, instanceType, result.Objects[j].Name) 649 } 650 651 } 652 653 if len(testCase.result.Prefixes) != len(result.Prefixes) { 654 t.Logf("want: %v", testCase.result.Prefixes) 655 t.Logf("got: %v", result.Prefixes) 656 t.Errorf("Test %d: %s: Expected number of prefixes in the result to be '%d', but found '%d' prefixes instead", i+1, instanceType, len(testCase.result.Prefixes), len(result.Prefixes)) 657 } 658 for j := 0; j < len(testCase.result.Prefixes); j++ { 659 if j >= len(result.Prefixes) { 660 t.Errorf("Test %d: %s: Expected prefix name to be \"%s\", but found no result", i+1, instanceType, testCase.result.Prefixes[j]) 661 continue 662 } 663 if testCase.result.Prefixes[j] != result.Prefixes[j] { 664 t.Errorf("Test %d: %s: Expected prefix name to be \"%s\", but found \"%s\" instead", i+1, instanceType, testCase.result.Prefixes[j], result.Prefixes[j]) 665 } 666 } 667 668 if testCase.result.IsTruncated != result.IsTruncated { 669 // Allow an extra continuation token. 670 if !result.IsTruncated || len(result.Objects) == 0 { 671 t.Errorf("Test %d: %s: Expected IsTruncated flag to be %v, but instead found it to be %v", i+1, instanceType, testCase.result.IsTruncated, result.IsTruncated) 672 } 673 } 674 675 if testCase.result.IsTruncated && result.NextMarker == "" { 676 t.Errorf("Test %d: %s: Expected NextMarker to contain a string since listing is truncated, but instead found it to be empty", i+1, instanceType) 677 } 678 679 if !testCase.result.IsTruncated && result.NextMarker != "" { 680 if !result.IsTruncated || len(result.Objects) == 0 { 681 t.Errorf("Test %d: %s: Expected NextMarker to be empty since listing is not truncated, but instead found `%v`", i+1, instanceType, result.NextMarker) 682 } 683 } 684 685 } 686 }) 687 } 688 } 689 690 func objInfoNames(o []ObjectInfo) []string { 691 var res = make([]string, len(o)) 692 for i := range o { 693 res[i] = o[i].Name 694 } 695 return res 696 } 697 698 // Wrapper for calling ListObjectVersions tests for both Erasure multiple disks and single node setup. 699 func TestListObjectVersions(t *testing.T) { 700 ExecObjectLayerTest(t, testListObjectVersions) 701 } 702 703 // Unit test for ListObjectVersions 704 func testListObjectVersions(obj ObjectLayer, instanceType string, t1 TestErrHandler) { 705 t, _ := t1.(*testing.T) 706 testBuckets := []string{ 707 // This bucket is used for testing ListObject operations. 708 "test-bucket-list-object", 709 // This bucket will be tested with empty directories 710 "test-bucket-empty-dir", 711 // Will not store any objects in this bucket, 712 // Its to test ListObjects on an empty bucket. 713 "empty-bucket", 714 // Listing the case where the marker > last object. 715 "test-bucket-single-object", 716 // Listing uncommon delimiter. 717 "test-bucket-delimiter", 718 // Listing prefixes > maxKeys 719 "test-bucket-max-keys-prefixes", 720 } 721 for _, bucket := range testBuckets { 722 err := obj.MakeBucketWithLocation(context.Background(), bucket, BucketOptions{VersioningEnabled: true}) 723 if err != nil { 724 if _, ok := err.(NotImplemented); ok { 725 // Skip test for FS mode. 726 continue 727 } 728 t.Fatalf("%s : %s", instanceType, err.Error()) 729 } 730 } 731 732 var err error 733 testObjects := []struct { 734 parentBucket string 735 name string 736 content string 737 meta map[string]string 738 }{ 739 {testBuckets[0], "Asia-maps.png", "asis-maps", map[string]string{"content-type": "image/png"}}, 740 {testBuckets[0], "Asia/India/India-summer-photos-1", "contentstring", nil}, 741 {testBuckets[0], "Asia/India/Karnataka/Bangalore/Koramangala/pics", "contentstring", nil}, 742 {testBuckets[0], "newPrefix0", "newPrefix0", nil}, 743 {testBuckets[0], "newPrefix1", "newPrefix1", nil}, 744 {testBuckets[0], "newzen/zen/recurse/again/again/again/pics", "recurse", nil}, 745 {testBuckets[0], "obj0", "obj0", nil}, 746 {testBuckets[0], "obj1", "obj1", nil}, 747 {testBuckets[0], "obj2", "obj2", nil}, 748 {testBuckets[1], "obj1", "obj1", nil}, 749 {testBuckets[1], "obj2", "obj2", nil}, 750 {testBuckets[1], "temporary/0/", "", nil}, 751 {testBuckets[3], "A/B", "contentstring", nil}, 752 {testBuckets[4], "file1/receipt.json", "content", nil}, 753 {testBuckets[4], "file1/guidSplunk-aaaa/file", "content", nil}, 754 {testBuckets[5], "dir/day_id=2017-10-10/issue", "content", nil}, 755 {testBuckets[5], "dir/day_id=2017-10-11/issue", "content", nil}, 756 } 757 758 for _, object := range testObjects { 759 md5Bytes := md5.Sum([]byte(object.content)) 760 _, err = obj.PutObject(context.Background(), object.parentBucket, object.name, mustGetPutObjReader(t, bytes.NewBufferString(object.content), 761 int64(len(object.content)), hex.EncodeToString(md5Bytes[:]), ""), ObjectOptions{UserDefined: object.meta}) 762 if err != nil { 763 if _, ok := err.(BucketNotFound); ok { 764 // Skip test failure for FS mode. 765 continue 766 } 767 t.Fatalf("%s : %s", instanceType, err.Error()) 768 } 769 770 } 771 772 // Formualting the result data set to be expected from ListObjects call inside the tests, 773 // This will be used in testCases and used for asserting the correctness of ListObjects output in the tests. 774 775 resultCases := []ListObjectsInfo{ 776 // ListObjectsResult-0. 777 // Testing for listing all objects in the bucket, (testCase 20,21,22). 778 { 779 IsTruncated: false, 780 Objects: []ObjectInfo{ 781 {Name: "Asia-maps.png"}, 782 {Name: "Asia/India/India-summer-photos-1"}, 783 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 784 {Name: "newPrefix0"}, 785 {Name: "newPrefix1"}, 786 {Name: "newzen/zen/recurse/again/again/again/pics"}, 787 {Name: "obj0"}, 788 {Name: "obj1"}, 789 {Name: "obj2"}, 790 }, 791 }, 792 // ListObjectsResult-1. 793 // Used for asserting the truncated case, (testCase 23). 794 { 795 IsTruncated: true, 796 Objects: []ObjectInfo{ 797 {Name: "Asia-maps.png"}, 798 {Name: "Asia/India/India-summer-photos-1"}, 799 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 800 {Name: "newPrefix0"}, 801 {Name: "newPrefix1"}, 802 }, 803 }, 804 // ListObjectsResult-2. 805 // (TestCase 24). 806 { 807 IsTruncated: true, 808 Objects: []ObjectInfo{ 809 {Name: "Asia-maps.png"}, 810 {Name: "Asia/India/India-summer-photos-1"}, 811 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 812 {Name: "newPrefix0"}, 813 }, 814 }, 815 // ListObjectsResult-3. 816 // (TestCase 25). 817 { 818 IsTruncated: true, 819 Objects: []ObjectInfo{ 820 {Name: "Asia-maps.png"}, 821 {Name: "Asia/India/India-summer-photos-1"}, 822 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 823 }, 824 }, 825 // ListObjectsResult-4. 826 // Again used for truncated case. 827 // (TestCase 26). 828 { 829 IsTruncated: true, 830 Objects: []ObjectInfo{ 831 {Name: "Asia-maps.png"}, 832 }, 833 }, 834 // ListObjectsResult-5. 835 // Used for Asserting prefixes. 836 // Used for test case with prefix "new", (testCase 27-29). 837 { 838 IsTruncated: false, 839 Objects: []ObjectInfo{ 840 {Name: "newPrefix0"}, 841 {Name: "newPrefix1"}, 842 {Name: "newzen/zen/recurse/again/again/again/pics"}, 843 }, 844 }, 845 // ListObjectsResult-6. 846 // Used for Asserting prefixes. 847 // Used for test case with prefix = "obj", (testCase 30). 848 { 849 IsTruncated: false, 850 Objects: []ObjectInfo{ 851 {Name: "obj0"}, 852 {Name: "obj1"}, 853 {Name: "obj2"}, 854 }, 855 }, 856 // ListObjectsResult-7. 857 // Used for Asserting prefixes and truncation. 858 // Used for test case with prefix = "new" and maxKeys = 1, (testCase 31). 859 { 860 IsTruncated: true, 861 Objects: []ObjectInfo{ 862 {Name: "newPrefix0"}, 863 }, 864 }, 865 // ListObjectsResult-8. 866 // Used for Asserting prefixes. 867 // Used for test case with prefix = "obj" and maxKeys = 2, (testCase 32). 868 { 869 IsTruncated: true, 870 Objects: []ObjectInfo{ 871 {Name: "obj0"}, 872 {Name: "obj1"}, 873 }, 874 }, 875 // ListObjectsResult-9. 876 // Used for asserting the case with marker, but without prefix. 877 //marker is set to "newPrefix0" in the testCase, (testCase 33). 878 { 879 IsTruncated: false, 880 Objects: []ObjectInfo{ 881 {Name: "newPrefix1"}, 882 {Name: "newzen/zen/recurse/again/again/again/pics"}, 883 {Name: "obj0"}, 884 {Name: "obj1"}, 885 {Name: "obj2"}, 886 }, 887 }, 888 // ListObjectsResult-10. 889 //marker is set to "newPrefix1" in the testCase, (testCase 34). 890 { 891 IsTruncated: false, 892 Objects: []ObjectInfo{ 893 {Name: "newzen/zen/recurse/again/again/again/pics"}, 894 {Name: "obj0"}, 895 {Name: "obj1"}, 896 {Name: "obj2"}, 897 }, 898 }, 899 // ListObjectsResult-11. 900 //marker is set to "obj0" in the testCase, (testCase 35). 901 { 902 IsTruncated: false, 903 Objects: []ObjectInfo{ 904 {Name: "obj1"}, 905 {Name: "obj2"}, 906 }, 907 }, 908 // ListObjectsResult-12. 909 // Marker is set to "obj1" in the testCase, (testCase 36). 910 { 911 IsTruncated: false, 912 Objects: []ObjectInfo{ 913 {Name: "obj2"}, 914 }, 915 }, 916 // ListObjectsResult-13. 917 // Marker is set to "man" in the testCase, (testCase37). 918 { 919 IsTruncated: false, 920 Objects: []ObjectInfo{ 921 {Name: "newPrefix0"}, 922 {Name: "newPrefix1"}, 923 {Name: "newzen/zen/recurse/again/again/again/pics"}, 924 {Name: "obj0"}, 925 {Name: "obj1"}, 926 {Name: "obj2"}, 927 }, 928 }, 929 // ListObjectsResult-14. 930 // Marker is set to "Abc" in the testCase, (testCase 39). 931 { 932 IsTruncated: false, 933 Objects: []ObjectInfo{ 934 {Name: "Asia-maps.png"}, 935 {Name: "Asia/India/India-summer-photos-1"}, 936 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 937 {Name: "newPrefix0"}, 938 {Name: "newPrefix1"}, 939 {Name: "newzen/zen/recurse/again/again/again/pics"}, 940 {Name: "obj0"}, 941 {Name: "obj1"}, 942 {Name: "obj2"}, 943 }, 944 }, 945 // ListObjectsResult-15. 946 // Marker is set to "Asia/India/India-summer-photos-1" in the testCase, (testCase 40). 947 { 948 IsTruncated: false, 949 Objects: []ObjectInfo{ 950 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 951 {Name: "newPrefix0"}, 952 {Name: "newPrefix1"}, 953 {Name: "newzen/zen/recurse/again/again/again/pics"}, 954 {Name: "obj0"}, 955 {Name: "obj1"}, 956 {Name: "obj2"}, 957 }, 958 }, 959 // ListObjectsResult-16. 960 // Marker is set to "Asia/India/Karnataka/Bangalore/Koramangala/pics" in the testCase, (testCase 41). 961 { 962 IsTruncated: false, 963 Objects: []ObjectInfo{ 964 {Name: "newPrefix0"}, 965 {Name: "newPrefix1"}, 966 {Name: "newzen/zen/recurse/again/again/again/pics"}, 967 {Name: "obj0"}, 968 {Name: "obj1"}, 969 {Name: "obj2"}, 970 }, 971 }, 972 // ListObjectsResult-17. 973 // Used for asserting the case with marker, without prefix but with truncation. 974 // Marker = "newPrefix0" & maxKeys = 3 in the testCase, (testCase42). 975 // Output truncated to 3 values. 976 { 977 IsTruncated: true, 978 Objects: []ObjectInfo{ 979 {Name: "newPrefix1"}, 980 {Name: "newzen/zen/recurse/again/again/again/pics"}, 981 {Name: "obj0"}, 982 }, 983 }, 984 // ListObjectsResult-18. 985 // Marker = "newPrefix1" & maxkeys = 1 in the testCase, (testCase43). 986 // Output truncated to 1 value. 987 { 988 IsTruncated: true, 989 Objects: []ObjectInfo{ 990 {Name: "newzen/zen/recurse/again/again/again/pics"}, 991 }, 992 }, 993 // ListObjectsResult-19. 994 // Marker = "obj0" & maxKeys = 1 in the testCase, (testCase44). 995 // Output truncated to 1 value. 996 { 997 IsTruncated: true, 998 Objects: []ObjectInfo{ 999 {Name: "obj1"}, 1000 }, 1001 }, 1002 // ListObjectsResult-20. 1003 // Marker = "obj0" & prefix = "obj" in the testCase, (testCase 45). 1004 { 1005 IsTruncated: false, 1006 Objects: []ObjectInfo{ 1007 {Name: "obj1"}, 1008 {Name: "obj2"}, 1009 }, 1010 }, 1011 // ListObjectsResult-21. 1012 // Marker = "obj1" & prefix = "obj" in the testCase, (testCase 46). 1013 { 1014 IsTruncated: false, 1015 Objects: []ObjectInfo{ 1016 {Name: "obj2"}, 1017 }, 1018 }, 1019 // ListObjectsResult-22. 1020 // Marker = "newPrefix0" & prefix = "new" in the testCase,, (testCase 47). 1021 { 1022 IsTruncated: false, 1023 Objects: []ObjectInfo{ 1024 {Name: "newPrefix1"}, 1025 {Name: "newzen/zen/recurse/again/again/again/pics"}, 1026 }, 1027 }, 1028 // ListObjectsResult-23. 1029 // Prefix is set to "Asia/India/" in the testCase, and delimiter is not set (testCase 55). 1030 { 1031 IsTruncated: false, 1032 Objects: []ObjectInfo{ 1033 {Name: "Asia/India/India-summer-photos-1"}, 1034 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 1035 }, 1036 }, 1037 1038 // ListObjectsResult-24. 1039 // Prefix is set to "Asia" in the testCase, and delimiter is not set (testCase 56). 1040 { 1041 IsTruncated: false, 1042 Objects: []ObjectInfo{ 1043 {Name: "Asia-maps.png"}, 1044 {Name: "Asia/India/India-summer-photos-1"}, 1045 {Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"}, 1046 }, 1047 }, 1048 1049 // ListObjectsResult-25. 1050 // Prefix is set to "Asia" in the testCase, and delimiter is set (testCase 57). 1051 { 1052 IsTruncated: false, 1053 Objects: []ObjectInfo{ 1054 {Name: "Asia-maps.png"}, 1055 }, 1056 Prefixes: []string{"Asia/"}, 1057 }, 1058 // ListObjectsResult-26. 1059 // prefix = "new" and delimiter is set in the testCase.(testCase 58). 1060 { 1061 IsTruncated: false, 1062 Objects: []ObjectInfo{ 1063 {Name: "newPrefix0"}, 1064 {Name: "newPrefix1"}, 1065 }, 1066 Prefixes: []string{"newzen/"}, 1067 }, 1068 // ListObjectsResult-27. 1069 // Prefix is set to "Asia/India/" in the testCase, and delimiter is set to forward slash '/' (testCase 59). 1070 { 1071 IsTruncated: false, 1072 Objects: []ObjectInfo{ 1073 {Name: "Asia/India/India-summer-photos-1"}, 1074 }, 1075 Prefixes: []string{"Asia/India/Karnataka/"}, 1076 }, 1077 // ListObjectsResult-28. 1078 // Marker is set to "Asia/India/India-summer-photos-1" and delimiter set in the testCase, (testCase 60). 1079 { 1080 IsTruncated: false, 1081 Objects: []ObjectInfo{ 1082 {Name: "newPrefix0"}, 1083 {Name: "newPrefix1"}, 1084 {Name: "obj0"}, 1085 {Name: "obj1"}, 1086 {Name: "obj2"}, 1087 }, 1088 Prefixes: []string{"newzen/"}, 1089 }, 1090 // ListObjectsResult-29. 1091 // Marker is set to "Asia/India/Karnataka/Bangalore/Koramangala/pics" in the testCase and delimiter set, (testCase 61). 1092 { 1093 IsTruncated: false, 1094 Objects: []ObjectInfo{ 1095 {Name: "newPrefix0"}, 1096 {Name: "newPrefix1"}, 1097 {Name: "obj0"}, 1098 {Name: "obj1"}, 1099 {Name: "obj2"}, 1100 }, 1101 Prefixes: []string{"newzen/"}, 1102 }, 1103 // ListObjectsResult-30. 1104 // Prefix and Delimiter is set to '/', (testCase 62). 1105 { 1106 IsTruncated: false, 1107 Objects: []ObjectInfo{}, 1108 }, 1109 // ListObjectsResult-31 Empty directory, recursive listing 1110 { 1111 IsTruncated: false, 1112 Objects: []ObjectInfo{ 1113 {Name: "obj1"}, 1114 {Name: "obj2"}, 1115 {Name: "temporary/0/"}, 1116 }, 1117 }, 1118 // ListObjectsResult-32 Empty directory, non recursive listing 1119 { 1120 IsTruncated: false, 1121 Objects: []ObjectInfo{ 1122 {Name: "obj1"}, 1123 {Name: "obj2"}, 1124 }, 1125 Prefixes: []string{"temporary/"}, 1126 }, 1127 // ListObjectsResult-33 Listing empty directory only 1128 { 1129 IsTruncated: false, 1130 Objects: []ObjectInfo{ 1131 {Name: "temporary/0/"}, 1132 }, 1133 }, 1134 // ListObjectsResult-34: 1135 // * Listing with marker > last object should return empty 1136 // * Listing an object with a trailing slash and '/' delimiter 1137 { 1138 IsTruncated: false, 1139 Objects: []ObjectInfo{}, 1140 }, 1141 // ListObjectsResult-35 list with custom uncommon delimiter 1142 { 1143 IsTruncated: false, 1144 Objects: []ObjectInfo{ 1145 {Name: "file1/receipt.json"}, 1146 }, 1147 Prefixes: []string{"file1/guidSplunk"}, 1148 }, 1149 // ListObjectsResult-36 list with nextmarker prefix and maxKeys set to 1. 1150 { 1151 IsTruncated: true, 1152 Prefixes: []string{"dir/day_id=2017-10-10/"}, 1153 }, 1154 } 1155 1156 testCases := []struct { 1157 // Inputs to ListObjects. 1158 bucketName string 1159 prefix string 1160 marker string 1161 delimiter string 1162 maxKeys int32 1163 // Expected output of ListObjects. 1164 result ListObjectsInfo 1165 err error 1166 // Flag indicating whether the test is expected to pass or not. 1167 shouldPass bool 1168 }{ 1169 // Test cases with invalid bucket names ( Test number 1-4). 1170 {".test", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: ".test"}, false}, 1171 {"Test", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "Test"}, false}, 1172 {"---", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "---"}, false}, 1173 {"ad", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "ad"}, false}, 1174 // Using an existing file for bucket name, but its not a directory (5). 1175 {"simple-file.txt", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "simple-file.txt"}, false}, 1176 // Valid bucket names, but they donot exist (6-8). 1177 {"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false}, 1178 {"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false}, 1179 {"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false}, 1180 // Testing for failure cases with both perfix and marker (9). 1181 // The prefix and marker combination to be valid it should satisfy strings.HasPrefix(marker, prefix). 1182 {"test-bucket-list-object", "asia", "europe-object", "", 0, ListObjectsInfo{}, fmt.Errorf("Invalid combination of marker '%s' and prefix '%s'", "europe-object", "asia"), false}, 1183 // Setting a non-existing directory to be prefix (10-11). 1184 {"empty-bucket", "europe/france/", "", "", 1, ListObjectsInfo{}, nil, true}, 1185 {"empty-bucket", "africa/tunisia/", "", "", 1, ListObjectsInfo{}, nil, true}, 1186 // Testing on empty bucket, that is, bucket without any objects in it (12). 1187 {"empty-bucket", "", "", "", 0, ListObjectsInfo{}, nil, true}, 1188 // Setting maxKeys to negative value (13-14). 1189 {"empty-bucket", "", "", "", -1, ListObjectsInfo{}, nil, true}, 1190 {"empty-bucket", "", "", "", 1, ListObjectsInfo{}, nil, true}, 1191 // Setting maxKeys to a very large value (15). 1192 {"empty-bucket", "", "", "", 111100000, ListObjectsInfo{}, nil, true}, 1193 // Testing for all 10 objects in the bucket (16). 1194 {"test-bucket-list-object", "", "", "", 10, resultCases[0], nil, true}, 1195 //Testing for negative value of maxKey, this should set maxKeys to listObjectsLimit (17). 1196 {"test-bucket-list-object", "", "", "", -1, resultCases[0], nil, true}, 1197 // Testing for very large value of maxKey, this should set maxKeys to listObjectsLimit (18). 1198 {"test-bucket-list-object", "", "", "", 1234567890, resultCases[0], nil, true}, 1199 // Testing for trancated value (19-22). 1200 {"test-bucket-list-object", "", "", "", 5, resultCases[1], nil, true}, 1201 {"test-bucket-list-object", "", "", "", 4, resultCases[2], nil, true}, 1202 {"test-bucket-list-object", "", "", "", 3, resultCases[3], nil, true}, 1203 {"test-bucket-list-object", "", "", "", 1, resultCases[4], nil, true}, 1204 // Testing with prefix (23-26). 1205 {"test-bucket-list-object", "new", "", "", 3, resultCases[5], nil, true}, 1206 {"test-bucket-list-object", "new", "", "", 4, resultCases[5], nil, true}, 1207 {"test-bucket-list-object", "new", "", "", 5, resultCases[5], nil, true}, 1208 {"test-bucket-list-object", "obj", "", "", 3, resultCases[6], nil, true}, 1209 // Testing with prefix and truncation (27-28). 1210 {"test-bucket-list-object", "new", "", "", 1, resultCases[7], nil, true}, 1211 {"test-bucket-list-object", "obj", "", "", 2, resultCases[8], nil, true}, 1212 // Testing with marker, but without prefix and truncation (29-33). 1213 {"test-bucket-list-object", "", "newPrefix0", "", 6, resultCases[9], nil, true}, 1214 {"test-bucket-list-object", "", "newPrefix1", "", 5, resultCases[10], nil, true}, 1215 {"test-bucket-list-object", "", "obj0", "", 4, resultCases[11], nil, true}, 1216 {"test-bucket-list-object", "", "obj1", "", 2, resultCases[12], nil, true}, 1217 {"test-bucket-list-object", "", "man", "", 11, resultCases[13], nil, true}, 1218 // Marker being set to a value which is greater than and all object names when sorted (34). 1219 // Expected to send an empty response in this case. 1220 {"test-bucket-list-object", "", "zen", "", 10, ListObjectsInfo{}, nil, true}, 1221 // Marker being set to a value which is lesser than and all object names when sorted (35). 1222 // Expected to send all the objects in the bucket in this case. 1223 {"test-bucket-list-object", "", "Abc", "", 10, resultCases[14], nil, true}, 1224 // Marker is to a hierarhical value (36-37). 1225 {"test-bucket-list-object", "", "Asia/India/India-summer-photos-1", "", 10, resultCases[15], nil, true}, 1226 {"test-bucket-list-object", "", "Asia/India/Karnataka/Bangalore/Koramangala/pics", "", 10, resultCases[16], nil, true}, 1227 // Testing with marker and truncation, but no prefix (38-40). 1228 {"test-bucket-list-object", "", "newPrefix0", "", 3, resultCases[17], nil, true}, 1229 {"test-bucket-list-object", "", "newPrefix1", "", 1, resultCases[18], nil, true}, 1230 {"test-bucket-list-object", "", "obj0", "", 1, resultCases[19], nil, true}, 1231 // Testing with both marker and prefix, but without truncation (41-43). 1232 // The valid combination of marker and prefix should satisfy strings.HasPrefix(marker, prefix). 1233 {"test-bucket-list-object", "obj", "obj0", "", 2, resultCases[20], nil, true}, 1234 {"test-bucket-list-object", "obj", "obj1", "", 1, resultCases[21], nil, true}, 1235 {"test-bucket-list-object", "new", "newPrefix0", "", 2, resultCases[22], nil, true}, 1236 // Testing with maxKeys set to 0 (44-50). 1237 // The parameters have to valid. 1238 {"test-bucket-list-object", "", "obj1", "", 0, ListObjectsInfo{}, nil, true}, 1239 {"test-bucket-list-object", "", "obj0", "", 0, ListObjectsInfo{}, nil, true}, 1240 {"test-bucket-list-object", "new", "", "", 0, ListObjectsInfo{}, nil, true}, 1241 {"test-bucket-list-object", "obj", "", "", 0, ListObjectsInfo{}, nil, true}, 1242 {"test-bucket-list-object", "obj", "obj0", "", 0, ListObjectsInfo{}, nil, true}, 1243 {"test-bucket-list-object", "obj", "obj1", "", 0, ListObjectsInfo{}, nil, true}, 1244 {"test-bucket-list-object", "new", "newPrefix0", "", 0, ListObjectsInfo{}, nil, true}, 1245 // Tests on hierarchical key names as prefix. 1246 // Without delimteter the code should recurse into the prefix Dir. 1247 // Tests with prefix, but without delimiter (51-52). 1248 {"test-bucket-list-object", "Asia/India/", "", "", 10, resultCases[23], nil, true}, 1249 {"test-bucket-list-object", "Asia", "", "", 10, resultCases[24], nil, true}, 1250 // Tests with prefix and delimiter (53-55). 1251 // With delimiter the code should not recurse into the sub-directories of prefix Dir. 1252 {"test-bucket-list-object", "Asia", "", SlashSeparator, 10, resultCases[25], nil, true}, 1253 {"test-bucket-list-object", "new", "", SlashSeparator, 10, resultCases[26], nil, true}, 1254 {"test-bucket-list-object", "Asia/India/", "", SlashSeparator, 10, resultCases[27], nil, true}, 1255 // Test with marker set as hierarhical value and with delimiter. (56-57) 1256 {"test-bucket-list-object", "", "Asia/India/India-summer-photos-1", SlashSeparator, 10, resultCases[28], nil, true}, 1257 {"test-bucket-list-object", "", "Asia/India/Karnataka/Bangalore/Koramangala/pics", SlashSeparator, 10, resultCases[29], nil, true}, 1258 // Test with prefix and delimiter set to '/'. (58) 1259 {"test-bucket-list-object", SlashSeparator, "", SlashSeparator, 10, resultCases[30], nil, true}, 1260 // Test with invalid prefix (59) 1261 {"test-bucket-list-object", "\\", "", SlashSeparator, 10, ListObjectsInfo{}, nil, true}, 1262 // Test listing an empty directory in recursive mode (60) 1263 {"test-bucket-empty-dir", "", "", "", 10, resultCases[31], nil, true}, 1264 // Test listing an empty directory in a non recursive mode (61) 1265 {"test-bucket-empty-dir", "", "", SlashSeparator, 10, resultCases[32], nil, true}, 1266 // Test listing a directory which contains an empty directory (62) 1267 {"test-bucket-empty-dir", "", "temporary/", "", 10, resultCases[33], nil, true}, 1268 // Test listing with marker > last object such that response should be empty (63) 1269 {"test-bucket-single-object", "", "A/C", "", 1000, resultCases[34], nil, true}, 1270 // Test listing an object with a trailing slash and a slash delimiter (64) 1271 {"test-bucket-list-object", "Asia-maps.png/", "", "/", 1000, resultCases[34], nil, true}, 1272 // Test listing an object with uncommon delimiter 1273 {testBuckets[4], "", "", "guidSplunk", 1000, resultCases[35], nil, true}, 1274 // Test listing an object with uncommon delimiter and matching prefix 1275 {testBuckets[4], "file1/", "", "guidSplunk", 1000, resultCases[35], nil, true}, 1276 // Test listing at prefix with expected prefix markers 1277 {testBuckets[5], "dir/", "", SlashSeparator, 1, resultCases[36], nil, true}, 1278 } 1279 1280 for i, testCase := range testCases { 1281 testCase := testCase 1282 t.Run(fmt.Sprintf("%s-Test%d", instanceType, i+1), func(t *testing.T) { 1283 result, err := obj.ListObjectVersions(context.Background(), testCase.bucketName, 1284 testCase.prefix, testCase.marker, "", testCase.delimiter, int(testCase.maxKeys)) 1285 if _, ok := err.(NotImplemented); ok { 1286 // Not implemented should be skipped 1287 t.Skip() 1288 } 1289 if err != nil && testCase.shouldPass { 1290 t.Errorf("%s: Expected to pass, but failed with: <ERROR> %s", instanceType, err.Error()) 1291 } 1292 if err == nil && !testCase.shouldPass { 1293 t.Errorf("%s: Expected to fail with <ERROR> \"%s\", but passed instead", instanceType, testCase.err.Error()) 1294 } 1295 // Failed as expected, but does it fail for the expected reason. 1296 if err != nil && !testCase.shouldPass { 1297 if !strings.Contains(err.Error(), testCase.err.Error()) { 1298 t.Errorf("%s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", instanceType, testCase.err.Error(), err.Error()) 1299 } 1300 } 1301 // Since there are cases for which ListObjects fails, this is 1302 // necessary. Test passes as expected, but the output values 1303 // are verified for correctness here. 1304 if err == nil && testCase.shouldPass { 1305 // The length of the expected ListObjectsResult.Objects 1306 // should match in both expected result from test cases 1307 // and in the output. On failure calling t.Fatalf, 1308 // otherwise it may lead to index out of range error in 1309 // assertion following this. 1310 if len(testCase.result.Objects) != len(result.Objects) { 1311 t.Fatalf("%s: Expected number of object in the result to be '%d', but found '%d' objects instead", instanceType, len(testCase.result.Objects), len(result.Objects)) 1312 } 1313 for j := 0; j < len(testCase.result.Objects); j++ { 1314 if testCase.result.Objects[j].Name != result.Objects[j].Name { 1315 t.Errorf("%s: Expected object name to be \"%s\", but found \"%s\" instead", instanceType, testCase.result.Objects[j].Name, result.Objects[j].Name) 1316 } 1317 // FIXME: we should always check for ETag 1318 if result.Objects[j].ETag == "" && !strings.HasSuffix(result.Objects[j].Name, SlashSeparator) { 1319 t.Errorf("%s: Expected ETag to be not empty, but found empty instead (%v)", instanceType, result.Objects[j].Name) 1320 } 1321 1322 } 1323 1324 if len(testCase.result.Prefixes) != len(result.Prefixes) { 1325 t.Log(testCase, testCase.result.Prefixes, result.Prefixes) 1326 t.Fatalf("%s: Expected number of prefixes in the result to be '%d', but found '%d' prefixes instead", instanceType, len(testCase.result.Prefixes), len(result.Prefixes)) 1327 } 1328 for j := 0; j < len(testCase.result.Prefixes); j++ { 1329 if testCase.result.Prefixes[j] != result.Prefixes[j] { 1330 t.Errorf("%s: Expected prefix name to be \"%s\", but found \"%s\" instead", instanceType, testCase.result.Prefixes[j], result.Prefixes[j]) 1331 } 1332 } 1333 1334 if testCase.result.IsTruncated != result.IsTruncated { 1335 // Allow an extra continuation token. 1336 if !result.IsTruncated || len(result.Objects) == 0 { 1337 t.Errorf("%s: Expected IsTruncated flag to be %v, but instead found it to be %v", instanceType, testCase.result.IsTruncated, result.IsTruncated) 1338 } 1339 } 1340 1341 if testCase.result.IsTruncated && result.NextMarker == "" { 1342 t.Errorf("%s: Expected NextMarker to contain a string since listing is truncated, but instead found it to be empty", instanceType) 1343 } 1344 1345 if !testCase.result.IsTruncated && result.NextMarker != "" { 1346 if !result.IsTruncated || len(result.Objects) == 0 { 1347 t.Errorf("%s: Expected NextMarker to be empty since listing is not truncated, but instead found `%v`", instanceType, result.NextMarker) 1348 } 1349 } 1350 } 1351 }) 1352 } 1353 } 1354 1355 // Initialize FS backend for the benchmark. 1356 func initFSObjectsB(disk string, t *testing.B) (obj ObjectLayer) { 1357 var err error 1358 obj, err = NewFSObjectLayer(disk) 1359 if err != nil { 1360 t.Fatal("Unexpected err: ", err) 1361 } 1362 return obj 1363 } 1364 1365 // BenchmarkListObjects - Run ListObject Repeatedly and benchmark. 1366 func BenchmarkListObjects(b *testing.B) { 1367 // Make a temporary directory to use as the obj. 1368 directory, err := ioutil.TempDir(globalTestTmpDir, "minio-list-benchmark") 1369 if err != nil { 1370 b.Fatal(err) 1371 } 1372 defer os.RemoveAll(directory) 1373 1374 // Create the obj. 1375 obj := initFSObjectsB(directory, b) 1376 1377 bucket := "ls-benchmark-bucket" 1378 // Create a bucket. 1379 err = obj.MakeBucketWithLocation(context.Background(), bucket, BucketOptions{}) 1380 if err != nil { 1381 b.Fatal(err) 1382 } 1383 1384 // Insert objects to be listed and benchmarked later. 1385 for i := 0; i < 20000; i++ { 1386 key := "obj" + strconv.Itoa(i) 1387 _, err = obj.PutObject(context.Background(), bucket, key, mustGetPutObjReader(b, bytes.NewBufferString(key), int64(len(key)), "", ""), ObjectOptions{}) 1388 if err != nil { 1389 b.Fatal(err) 1390 } 1391 } 1392 1393 b.ResetTimer() 1394 1395 // List the buckets over and over and over. 1396 for i := 0; i < b.N; i++ { 1397 _, err = obj.ListObjects(context.Background(), bucket, "", "obj9000", "", -1) 1398 if err != nil { 1399 b.Fatal(err) 1400 } 1401 } 1402 }