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  }