| 
									
										
										
										
											2021-04-19 03:41:13 +08:00
										 |  |  | // Copyright (c) 2015-2021 MinIO, Inc.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This file is part of MinIO Object Storage stack
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-19 07:23:42 +08:00
										 |  |  | package cmd | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | // Fixed volume name that could be used across tests
 | 
					
						
							|  |  |  | const volume = "testvolume" | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | // Test for filterMatchingPrefix.
 | 
					
						
							|  |  |  | func TestFilterMatchingPrefix(t *testing.T) { | 
					
						
							|  |  |  | 	entries := []string{"a", "aab", "ab", "abbbb", "zzz"} | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		prefixEntry string | 
					
						
							|  |  |  | 		result      []string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			// Empty prefix should match all entries.
 | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 			[]string{"a", "aab", "ab", "abbbb", "zzz"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"a", | 
					
						
							|  |  |  | 			[]string{"a", "aab", "ab", "abbbb"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"aa", | 
					
						
							|  |  |  | 			[]string{"aab"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			// Does not match any of the entries.
 | 
					
						
							|  |  |  | 			"c", | 
					
						
							|  |  |  | 			[]string{}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							|  |  |  | 		expected := testCase.result | 
					
						
							|  |  |  | 		got := filterMatchingPrefix(entries, testCase.prefixEntry) | 
					
						
							|  |  |  | 		if !reflect.DeepEqual(expected, got) { | 
					
						
							|  |  |  | 			t.Errorf("Test %d : expected %v, got %v", i+1, expected, got) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | // Helper function that creates a volume and files in it.
 | 
					
						
							|  |  |  | func createNamespace(disk StorageAPI, volume string, files []string) error { | 
					
						
							|  |  |  | 	// Make a volume.
 | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 	err := disk.MakeVol(context.Background(), volume) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	// Create files.
 | 
					
						
							|  |  |  | 	for _, file := range files { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		err = disk.AppendFile(context.Background(), volume, file, []byte{}) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | // Returns function "listDir" of the type listDirFunc.
 | 
					
						
							|  |  |  | // disks - used for doing disk.ListDir()
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | func listDirFactory(ctx context.Context, disk StorageAPI, isLeaf IsLeafFunc) ListDirFunc { | 
					
						
							|  |  |  | 	return func(volume, dirPath, dirEntry string) (emptyDir bool, entries []string, delayIsLeaf bool) { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		entries, err := disk.ListDir(ctx, volume, dirPath, -1) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 			return false, nil, false | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if len(entries) == 0 { | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 			return true, nil, false | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		entries, delayIsLeaf = filterListEntries(volume, dirPath, entries, dirEntry, isLeaf) | 
					
						
							|  |  |  | 		return false, entries, delayIsLeaf | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | // Test if tree walker returns entries matching prefix alone are received
 | 
					
						
							|  |  |  | // when a non empty prefix is supplied.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | func testTreeWalkPrefix(t *testing.T, listDir ListDirFunc, isLeaf IsLeafFunc, isLeafDir IsLeafDirFunc) { | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	// Start the tree walk go-routine.
 | 
					
						
							|  |  |  | 	prefix := "d/" | 
					
						
							|  |  |  | 	endWalkCh := make(chan struct{}) | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	twResultCh := startTreeWalk(context.Background(), volume, prefix, "", true, listDir, isLeaf, isLeafDir, endWalkCh) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Check if all entries received on the channel match the prefix.
 | 
					
						
							|  |  |  | 	for res := range twResultCh { | 
					
						
							| 
									
										
										
										
											2019-12-06 15:16:06 +08:00
										 |  |  | 		if !HasPrefix(res.entry, prefix) { | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 			t.Errorf("Entry %s doesn't match prefix %s", res.entry, prefix) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Test if entries received on tree walk's channel appear after the supplied marker.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | func testTreeWalkMarker(t *testing.T, listDir ListDirFunc, isLeaf IsLeafFunc, isLeafDir IsLeafDirFunc) { | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	// Start the tree walk go-routine.
 | 
					
						
							|  |  |  | 	prefix := "" | 
					
						
							|  |  |  | 	endWalkCh := make(chan struct{}) | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	twResultCh := startTreeWalk(context.Background(), volume, prefix, "d/g", true, listDir, isLeaf, isLeafDir, endWalkCh) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Check if only 3 entries, namely d/g/h, i/j/k, lmn are received on the channel.
 | 
					
						
							|  |  |  | 	expectedCount := 3 | 
					
						
							|  |  |  | 	actualCount := 0 | 
					
						
							|  |  |  | 	for range twResultCh { | 
					
						
							|  |  |  | 		actualCount++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if expectedCount != actualCount { | 
					
						
							|  |  |  | 		t.Errorf("Expected %d entries, actual no. of entries were %d", expectedCount, actualCount) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | // Test tree-walk.
 | 
					
						
							|  |  |  | func TestTreeWalk(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	fsDir := t.TempDir() | 
					
						
							| 
									
										
										
										
											2022-01-25 03:28:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	endpoints := mustGetNewEndpoints(fsDir) | 
					
						
							| 
									
										
										
										
											2016-10-27 18:30:52 +08:00
										 |  |  | 	disk, err := newStorageAPI(endpoints[0]) | 
					
						
							| 
									
										
										
										
											2016-10-19 03:49:24 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Unable to create StorageAPI: %s", err) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	files := []string{ | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 		"d/e", | 
					
						
							|  |  |  | 		"d/f", | 
					
						
							|  |  |  | 		"d/g/h", | 
					
						
							|  |  |  | 		"i/j/k", | 
					
						
							|  |  |  | 		"lmn", | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	err = createNamespace(disk, volume, files) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	isLeaf := func(bucket, leafPath string) bool { | 
					
						
							|  |  |  | 		return !strings.HasSuffix(leafPath, slashSeparator) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	isLeafDir := func(bucket, leafPath string) bool { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		entries, _ := disk.ListDir(context.Background(), bucket, leafPath, 1) | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		return len(entries) == 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listDir := listDirFactory(context.Background(), disk, isLeaf) | 
					
						
							| 
									
										
										
										
											2018-05-09 10:08:21 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	// Simple test for prefix based walk.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	testTreeWalkPrefix(t, listDir, isLeaf, isLeafDir) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	// Simple test when marker is set.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	testTreeWalkMarker(t, listDir, isLeaf, isLeafDir) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | // Test if tree walk go-routine exits cleanly if tree walk is aborted because of timeout.
 | 
					
						
							|  |  |  | func TestTreeWalkTimeout(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	fsDir := t.TempDir() | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	endpoints := mustGetNewEndpoints(fsDir) | 
					
						
							| 
									
										
										
										
											2016-10-27 18:30:52 +08:00
										 |  |  | 	disk, err := newStorageAPI(endpoints[0]) | 
					
						
							| 
									
										
										
										
											2016-10-19 03:49:24 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Unable to create StorageAPI: %s", err) | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	var myfiles []string | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	// Create maxObjectsList+1 number of entries.
 | 
					
						
							|  |  |  | 	for i := 0; i < maxObjectList+1; i++ { | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 		myfiles = append(myfiles, fmt.Sprintf("file.%d", i)) | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	err = createNamespace(disk, volume, myfiles) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	isLeaf := func(bucket, leafPath string) bool { | 
					
						
							|  |  |  | 		return !strings.HasSuffix(leafPath, slashSeparator) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	isLeafDir := func(bucket, leafPath string) bool { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		entries, _ := disk.ListDir(context.Background(), bucket, leafPath, 1) | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		return len(entries) == 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listDir := listDirFactory(context.Background(), disk, isLeaf) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	// TreeWalk pool with 2 seconds timeout for tree-walk go routines.
 | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | 	pool := NewTreeWalkPool(2 * time.Second) | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	endWalkCh := make(chan struct{}) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	prefix := "" | 
					
						
							|  |  |  | 	marker := "" | 
					
						
							|  |  |  | 	recursive := true | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	resultCh := startTreeWalk(context.Background(), volume, prefix, marker, recursive, listDir, isLeaf, isLeafDir, endWalkCh) | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	params := listParams{ | 
					
						
							|  |  |  | 		bucket:    volume, | 
					
						
							|  |  |  | 		recursive: recursive, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Add Treewalk to the pool.
 | 
					
						
							|  |  |  | 	pool.Set(params, resultCh, endWalkCh) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Wait for the Treewalk to timeout.
 | 
					
						
							|  |  |  | 	<-time.After(3 * time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Read maxObjectList number of entries from the channel.
 | 
					
						
							|  |  |  | 	// maxObjectsList number of entries would have been filled into the resultCh
 | 
					
						
							|  |  |  | 	// buffered channel. After the timeout resultCh would get closed and hence the
 | 
					
						
							|  |  |  | 	// maxObjectsList+1 entry would not be sent in the channel.
 | 
					
						
							|  |  |  | 	i := 0 | 
					
						
							|  |  |  | 	for range resultCh { | 
					
						
							|  |  |  | 		i++ | 
					
						
							|  |  |  | 		if i == maxObjectList { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-04 16:49:27 +08:00
										 |  |  | 	// The last entry will not be received as the Treewalk goroutine would have exited.
 | 
					
						
							|  |  |  | 	_, ok := <-resultCh | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		t.Error("Tree-walk go routine has not exited after timeout.") | 
					
						
							| 
									
										
										
										
											2016-06-29 13:32:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | // TestRecursiveWalk - tests if treeWalk returns entries correctly with and
 | 
					
						
							|  |  |  | // without recursively traversing prefixes.
 | 
					
						
							|  |  |  | func TestRecursiveTreeWalk(t *testing.T) { | 
					
						
							|  |  |  | 	// Create a backend directories fsDir1.
 | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	fsDir1 := t.TempDir() | 
					
						
							| 
									
										
										
										
											2016-10-19 03:49:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	endpoints := mustGetNewEndpoints(fsDir1) | 
					
						
							| 
									
										
										
										
											2016-10-27 18:30:52 +08:00
										 |  |  | 	disk1, err := newStorageAPI(endpoints[0]) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-10-19 03:49:24 +08:00
										 |  |  | 		t.Fatalf("Unable to create StorageAPI: %s", err) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	isLeaf := func(bucket, leafPath string) bool { | 
					
						
							|  |  |  | 		return !strings.HasSuffix(leafPath, slashSeparator) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	isLeafDir := func(bucket, leafPath string) bool { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		entries, _ := disk1.ListDir(context.Background(), bucket, leafPath, 1) | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		return len(entries) == 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | 	// Create listDir function.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	listDir := listDirFactory(context.Background(), disk1, isLeaf) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create the namespace.
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	files := []string{ | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 		"d/e", | 
					
						
							|  |  |  | 		"d/f", | 
					
						
							|  |  |  | 		"d/g/h", | 
					
						
							|  |  |  | 		"i/j/k", | 
					
						
							|  |  |  | 		"lmn", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	err = createNamespace(disk1, volume, files) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	endWalkCh := make(chan struct{}) | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		prefix    string | 
					
						
							|  |  |  | 		marker    string | 
					
						
							|  |  |  | 		recursive bool | 
					
						
							|  |  |  | 		expected  map[string]struct{} | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		// with no prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"", "", false, map[string]struct{}{ | 
					
						
							|  |  |  | 			"d/":  {}, | 
					
						
							|  |  |  | 			"i/":  {}, | 
					
						
							|  |  |  | 			"lmn": {}, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 		// with no prefix, no marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"", "", true, map[string]struct{}{ | 
					
						
							|  |  |  | 			"d/f":   {}, | 
					
						
							|  |  |  | 			"d/g/h": {}, | 
					
						
							|  |  |  | 			"d/e":   {}, | 
					
						
							|  |  |  | 			"i/j/k": {}, | 
					
						
							|  |  |  | 			"lmn":   {}, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 		// with no prefix, marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"", "d/e", false, map[string]struct{}{ | 
					
						
							|  |  |  | 			"d/f":  {}, | 
					
						
							|  |  |  | 			"d/g/": {}, | 
					
						
							|  |  |  | 			"i/":   {}, | 
					
						
							|  |  |  | 			"lmn":  {}, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 		// with no prefix, marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"", "d/e", true, map[string]struct{}{ | 
					
						
							|  |  |  | 			"d/f":   {}, | 
					
						
							|  |  |  | 			"d/g/h": {}, | 
					
						
							|  |  |  | 			"i/j/k": {}, | 
					
						
							|  |  |  | 			"lmn":   {}, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 		// with prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "", false, map[string]struct{}{ | 
					
						
							|  |  |  | 			"d/e":  {}, | 
					
						
							|  |  |  | 			"d/f":  {}, | 
					
						
							|  |  |  | 			"d/g/": {}, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 		// with prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "", true, map[string]struct{}{ | 
					
						
							|  |  |  | 			"d/e":   {}, | 
					
						
							|  |  |  | 			"d/f":   {}, | 
					
						
							|  |  |  | 			"d/g/h": {}, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 		// with prefix, marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "d/e", false, map[string]struct{}{ | 
					
						
							|  |  |  | 			"d/f":  {}, | 
					
						
							|  |  |  | 			"d/g/": {}, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 		// with prefix, marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "d/e", true, map[string]struct{}{ | 
					
						
							|  |  |  | 			"d/f":   {}, | 
					
						
							|  |  |  | 			"d/g/h": {}, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i, testCase := range testCases { | 
					
						
							| 
									
										
										
										
											2019-05-02 13:06:57 +08:00
										 |  |  | 		testCase := testCase | 
					
						
							|  |  |  | 		t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { | 
					
						
							|  |  |  | 			for entry := range startTreeWalk(context.Background(), volume, | 
					
						
							|  |  |  | 				testCase.prefix, testCase.marker, testCase.recursive, | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 				listDir, isLeaf, isLeafDir, endWalkCh) { | 
					
						
							| 
									
										
										
										
											2019-05-02 13:06:57 +08:00
										 |  |  | 				if _, found := testCase.expected[entry.entry]; !found { | 
					
						
							|  |  |  | 					t.Errorf("Expected %s, but couldn't find", entry.entry) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-05-02 13:06:57 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestSortedness(t *testing.T) { | 
					
						
							|  |  |  | 	// Create a backend directories fsDir1.
 | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	fsDir1 := t.TempDir() | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	endpoints := mustGetNewEndpoints(fsDir1) | 
					
						
							| 
									
										
										
										
											2016-10-27 18:30:52 +08:00
										 |  |  | 	disk1, err := newStorageAPI(endpoints[0]) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-10-27 18:30:52 +08:00
										 |  |  | 		t.Fatalf("Unable to create StorageAPI: %s", err) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	isLeaf := func(bucket, leafPath string) bool { | 
					
						
							|  |  |  | 		return !strings.HasSuffix(leafPath, slashSeparator) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	isLeafDir := func(bucket, leafPath string) bool { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		entries, _ := disk1.ListDir(context.Background(), bucket, leafPath, 1) | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		return len(entries) == 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | 	// Create listDir function.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	listDir := listDirFactory(context.Background(), disk1, isLeaf) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create the namespace.
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	files := []string{ | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 		"d/e", | 
					
						
							|  |  |  | 		"d/f", | 
					
						
							|  |  |  | 		"d/g/h", | 
					
						
							|  |  |  | 		"i/j/k", | 
					
						
							|  |  |  | 		"lmn", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	err = createNamespace(disk1, volume, files) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	endWalkCh := make(chan struct{}) | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		prefix    string | 
					
						
							|  |  |  | 		marker    string | 
					
						
							|  |  |  | 		recursive bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		// with no prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"", "", false}, | 
					
						
							|  |  |  | 		// with no prefix, no marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"", "", true}, | 
					
						
							|  |  |  | 		// with no prefix, marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"", "d/e", false}, | 
					
						
							|  |  |  | 		// with no prefix, marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"", "d/e", true}, | 
					
						
							|  |  |  | 		// with prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "", false}, | 
					
						
							|  |  |  | 		// with prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "", true}, | 
					
						
							|  |  |  | 		// with prefix, marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "d/e", false}, | 
					
						
							|  |  |  | 		// with prefix, marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "d/e", true}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i, test := range testCases { | 
					
						
							|  |  |  | 		var actualEntries []string | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 		for entry := range startTreeWalk(context.Background(), volume, | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 			test.prefix, test.marker, test.recursive, | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 			listDir, isLeaf, isLeafDir, endWalkCh) { | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 			actualEntries = append(actualEntries, entry.entry) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !sort.IsSorted(sort.StringSlice(actualEntries)) { | 
					
						
							|  |  |  | 			t.Error(i+1, "Expected entries to be sort, but it wasn't") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTreeWalkIsEnd(t *testing.T) { | 
					
						
							|  |  |  | 	// Create a backend directories fsDir1.
 | 
					
						
							| 
									
										
										
										
											2022-07-26 03:37:26 +08:00
										 |  |  | 	fsDir1 := t.TempDir() | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	endpoints := mustGetNewEndpoints(fsDir1) | 
					
						
							| 
									
										
										
										
											2016-10-27 18:30:52 +08:00
										 |  |  | 	disk1, err := newStorageAPI(endpoints[0]) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-10-27 18:30:52 +08:00
										 |  |  | 		t.Fatalf("Unable to create StorageAPI: %s", err) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	isLeaf := func(bucket, leafPath string) bool { | 
					
						
							|  |  |  | 		return !strings.HasSuffix(leafPath, slashSeparator) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	isLeafDir := func(bucket, leafPath string) bool { | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 		entries, _ := disk1.ListDir(context.Background(), bucket, leafPath, 1) | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 		return len(entries) == 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-18 06:16:52 +08:00
										 |  |  | 	// Create listDir function.
 | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 	listDir := listDirFactory(context.Background(), disk1, isLeaf) | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create the namespace.
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	files := []string{ | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 		"d/e", | 
					
						
							|  |  |  | 		"d/f", | 
					
						
							|  |  |  | 		"d/g/h", | 
					
						
							|  |  |  | 		"i/j/k", | 
					
						
							|  |  |  | 		"lmn", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	err = createNamespace(disk1, volume, files) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	endWalkCh := make(chan struct{}) | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		prefix        string | 
					
						
							|  |  |  | 		marker        string | 
					
						
							|  |  |  | 		recursive     bool | 
					
						
							|  |  |  | 		expectedEntry string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		// with no prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"", "", false, "lmn"}, | 
					
						
							|  |  |  | 		// with no prefix, no marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"", "", true, "lmn"}, | 
					
						
							|  |  |  | 		// with no prefix, marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"", "d/e", false, "lmn"}, | 
					
						
							|  |  |  | 		// with no prefix, marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"", "d/e", true, "lmn"}, | 
					
						
							|  |  |  | 		// with prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "", false, "d/g/"}, | 
					
						
							|  |  |  | 		// with prefix, no marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "", true, "d/g/h"}, | 
					
						
							|  |  |  | 		// with prefix, marker and no recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "d/e", false, "d/g/"}, | 
					
						
							|  |  |  | 		// with prefix, marker and recursive traversal
 | 
					
						
							|  |  |  | 		{"d/", "d/e", true, "d/g/h"}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i, test := range testCases { | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | 		var entry TreeWalkResult | 
					
						
							| 
									
										
										
										
											2019-05-02 13:06:57 +08:00
										 |  |  | 		for entry = range startTreeWalk(context.Background(), volume, test.prefix, | 
					
						
							| 
									
										
										
										
											2020-08-26 03:26:48 +08:00
										 |  |  | 			test.marker, test.recursive, listDir, isLeaf, isLeafDir, endWalkCh) { | 
					
						
							| 
									
										
										
										
											2016-07-15 09:37:43 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if entry.entry != test.expectedEntry { | 
					
						
							|  |  |  | 			t.Errorf("Test %d: Expected entry %s, but received %s with the EOF marker", i, test.expectedEntry, entry.entry) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !entry.end { | 
					
						
							|  |  |  | 			t.Errorf("Test %d: Last entry %s, doesn't have EOF marker set", i, entry.entry) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |