github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/xact/xs/utils_test.go (about) 1 // Package xs_test - basic list-concatenation unit tests. 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package xs_test 6 7 import ( 8 "testing" 9 10 "github.com/NVIDIA/aistore/cmn" 11 "github.com/NVIDIA/aistore/tools/tassert" 12 "github.com/NVIDIA/aistore/tools/trand" 13 ) 14 15 func TestConcatObjLists(t *testing.T) { 16 if testing.Short() { 17 t.Skipf("skipping %s in short mode", t.Name()) 18 } 19 tests := []struct { 20 name string 21 objCounts []int 22 maxSize int 23 token bool 24 }{ 25 // * `st` stands for "single target" 26 {name: "st/all", objCounts: []int{10}, maxSize: 0, token: false}, 27 {name: "st/half", objCounts: []int{10}, maxSize: 5, token: true}, 28 {name: "st/all_with_marker", objCounts: []int{10}, maxSize: 10, token: true}, 29 {name: "st/more_than_all", objCounts: []int{10}, maxSize: 11, token: false}, 30 31 // * `mt` stands for "multiple targets" 32 // * `one` stands for "one target has objects" 33 {name: "mt/one/all", objCounts: []int{0, 0, 10}, maxSize: 0, token: false}, 34 {name: "mt/one/half", objCounts: []int{0, 0, 10}, maxSize: 5, token: true}, 35 {name: "mt/one/all_with_marker", objCounts: []int{0, 0, 10}, maxSize: 10, token: true}, 36 {name: "mt/one/more_than_all", objCounts: []int{0, 0, 10}, maxSize: 11, token: false}, 37 38 // * `mt` stands for "multiple targets" 39 // * `more` stands for "more than one target has objects" 40 {name: "mt/more/all", objCounts: []int{5, 1, 4, 10}, maxSize: 0, token: false}, 41 {name: "mt/more/half", objCounts: []int{5, 1, 4, 10}, maxSize: 10, token: true}, 42 {name: "mt/more/all_with_marker", objCounts: []int{5, 1, 4, 10}, maxSize: 20, token: true}, 43 {name: "mt/more/more_than_all", objCounts: []int{5, 1, 4, 10}, maxSize: 21, token: false}, 44 } 45 46 for _, test := range tests { 47 t.Run(test.name, func(t *testing.T) { 48 var ( 49 lists = make([]*cmn.LsoRes, 0, len(test.objCounts)) 50 expectedObjCnt = 0 51 ) 52 for _, objCount := range test.objCounts { 53 list := &cmn.LsoRes{} 54 for range objCount { 55 list.Entries = append(list.Entries, &cmn.LsoEnt{ 56 Name: trand.String(5), 57 }) 58 } 59 lists = append(lists, list) 60 expectedObjCnt += len(list.Entries) 61 } 62 expectedObjCnt = min(expectedObjCnt, test.maxSize) 63 64 objs := concatLso(lists, test.maxSize) 65 tassert.Errorf( 66 t, test.maxSize == 0 || len(objs.Entries) == expectedObjCnt, 67 "number of objects (%d) is different from expected (%d)", len(objs.Entries), expectedObjCnt, 68 ) 69 tassert.Errorf( 70 t, (objs.ContinuationToken != "") == test.token, 71 "continuation token expected to be set=%t", test.token, 72 ) 73 }) 74 } 75 } 76 77 // concatLso takes a slice of object lists and concatenates them: all lists 78 // are appended to the first one. 79 // If maxSize is greater than 0, the resulting list is sorted and truncated. Zero 80 // or negative maxSize means returning all objects. 81 func concatLso(lists []*cmn.LsoRes, maxSize int) (objs *cmn.LsoRes) { 82 if len(lists) == 0 { 83 return &cmn.LsoRes{} 84 } 85 86 objs = &cmn.LsoRes{} 87 objs.Entries = make(cmn.LsoEntries, 0) 88 89 for _, l := range lists { 90 objs.Flags |= l.Flags 91 objs.Entries = append(objs.Entries, l.Entries...) 92 } 93 94 if len(objs.Entries) == 0 { 95 return objs 96 } 97 98 // For corner case: we have objects with replicas on page threshold 99 // we have to sort taking status into account. Otherwise wrong 100 // one(Status=moved) may get into the response 101 cmn.SortLso(objs.Entries) 102 103 // Remove duplicates 104 objs.Entries = cmn.DedupLso(objs.Entries, maxSize) 105 l := len(objs.Entries) 106 if maxSize > 0 && l >= maxSize { 107 objs.ContinuationToken = objs.Entries[l-1].Name 108 } 109 return 110 }