k8s.io/apiserver@v0.31.1/pkg/util/shufflesharding/shufflesharding_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 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 shufflesharding 18 19 import ( 20 "fmt" 21 "math" 22 "math/rand" 23 "reflect" 24 "sort" 25 "testing" 26 ) 27 28 func TestRequiredEntropyBits(t *testing.T) { 29 tests := []struct { 30 name string 31 deckSize int 32 handSize int 33 expectedBits int 34 }{ 35 { 36 "deckSize: 1024 handSize: 6", 37 1024, 38 6, 39 60, 40 }, 41 { 42 "deckSize: 512 handSize: 8", 43 512, 44 8, 45 72, 46 }, 47 } 48 49 for _, test := range tests { 50 bits := RequiredEntropyBits(test.deckSize, test.handSize) 51 if bits != test.expectedBits { 52 t.Errorf("test %s fails: expected %v but got %v", test.name, test.expectedBits, bits) 53 return 54 } 55 } 56 } 57 58 func TestNewDealer(t *testing.T) { 59 tests := []struct { 60 name string 61 deckSize int 62 handSize int 63 err error 64 }{ 65 { 66 "deckSize <= 0", 67 -100, 68 8, 69 fmt.Errorf("deckSize -100 or handSize 8 is not positive"), 70 }, 71 { 72 "handSize <= 0", 73 100, 74 0, 75 fmt.Errorf("deckSize 100 or handSize 0 is not positive"), 76 }, 77 { 78 "handSize is greater than deckSize", 79 100, 80 101, 81 fmt.Errorf("handSize 101 is greater than deckSize 100"), 82 }, 83 { 84 "deckSize is impractically large", 85 1 << 27, 86 2, 87 fmt.Errorf("deckSize 134217728 is impractically large"), 88 }, 89 { 90 "required entropy bits is greater than MaxHashBits", 91 512, 92 8, 93 fmt.Errorf("required entropy bits of deckSize 512 and handSize 8 is greater than 60"), 94 }, 95 { 96 "deckSize: 1024 handSize: 6", 97 1024, 98 6, 99 nil, 100 }, 101 } 102 for _, test := range tests { 103 t.Run(test.name, func(t *testing.T) { 104 _, err := NewDealer(test.deckSize, test.handSize) 105 if !reflect.DeepEqual(err, test.err) { 106 t.Errorf("test %s fails: expected %v but got %v", test.name, test.err, err) 107 return 108 } 109 }) 110 } 111 } 112 113 func TestCardDuplication(t *testing.T) { 114 tests := []struct { 115 name string 116 deckSize int 117 handSize int 118 }{ 119 { 120 "deckSize = handSize = 4", 121 4, 122 4, 123 }, 124 { 125 "deckSize = handSize = 8", 126 8, 127 8, 128 }, 129 { 130 "deckSize = handSize = 10", 131 10, 132 10, 133 }, 134 { 135 "deckSize = handSize = 12", 136 12, 137 12, 138 }, 139 { 140 "deckSize = 128, handSize = 8", 141 128, 142 8, 143 }, 144 { 145 "deckSize = 256, handSize = 7", 146 256, 147 7, 148 }, 149 { 150 "deckSize = 512, handSize = 6", 151 512, 152 6, 153 }, 154 } 155 for _, test := range tests { 156 hashValue := rand.Uint64() 157 t.Run(test.name, func(t *testing.T) { 158 dealer, err := NewDealer(test.deckSize, test.handSize) 159 if err != nil { 160 t.Errorf("fail to create Dealer: %v", err) 161 return 162 } 163 hand := make([]int, 0) 164 hand = dealer.DealIntoHand(hashValue, hand) 165 166 // check cards number 167 if len(hand) != int(test.handSize) { 168 t.Errorf("test case %s fails in cards number", test.name) 169 return 170 } 171 172 // check cards range and duplication 173 cardMap := make(map[int]struct{}) 174 for _, card := range hand { 175 if card < 0 || card >= int(test.deckSize) { 176 t.Errorf("test case %s fails in range check", test.name) 177 return 178 } 179 cardMap[card] = struct{}{} 180 } 181 if len(cardMap) != int(test.handSize) { 182 t.Errorf("test case %s fails in duplication check", test.name) 183 return 184 } 185 186 }) 187 } 188 } 189 190 // ff computes the falling factorial `n!/(n-m)!` and requires n to be 191 // positive and m to be in the range [0, n] and requires the answer to 192 // fit in an int 193 func ff(n, m int) int { 194 ans := 1 195 for f := n; f > n-m; f-- { 196 ans *= f 197 } 198 return ans 199 } 200 201 func TestUniformDistribution(t *testing.T) { 202 const spare = 64 - MaxHashBits 203 tests := []struct { 204 deckSize, handSize int 205 hashMax int 206 }{ 207 {64, 3, 1 << uint(math.Ceil(math.Log2(float64(ff(64, 3))))+spare)}, 208 {128, 3, ff(128, 3)}, 209 {50, 4, ff(50, 4)}, 210 } 211 for _, test := range tests { 212 dealer, err := NewDealer(test.deckSize, test.handSize) 213 if err != nil { 214 t.Errorf("fail to create Dealer: %v", err) 215 return 216 } 217 handCoordinateMap := make(map[int]int) // maps coded hand to count of times seen 218 219 fallingFactorial := ff(test.deckSize, test.handSize) 220 permutations := ff(test.handSize, test.handSize) 221 allCoordinateCount := fallingFactorial / permutations 222 nff := float64(test.hashMax) / float64(fallingFactorial) 223 minCount := permutations * int(math.Floor(nff)) 224 maxCount := permutations * int(math.Ceil(nff)) 225 aHand := make([]int, test.handSize) 226 for i := 0; i < test.hashMax; i++ { 227 aHand = dealer.DealIntoHand(uint64(i), aHand) 228 sort.IntSlice(aHand).Sort() 229 handCoordinate := 0 230 for _, card := range aHand { 231 handCoordinate = handCoordinate<<7 + card 232 } 233 handCoordinateMap[handCoordinate]++ 234 } 235 numHandsSeen := len(handCoordinateMap) 236 237 t.Logf("Deck size = %v, hand size = %v, number of possible hands = %d, number of hands seen = %d, number of deals = %d, expected count range = [%v, %v]", test.deckSize, test.handSize, allCoordinateCount, numHandsSeen, test.hashMax, minCount, maxCount) 238 239 // histogram maps (count of times a hand is seen) to (number of hands having that count) 240 histogram := make(map[int]int) 241 for _, count := range handCoordinateMap { 242 histogram[count] = histogram[count] + 1 243 } 244 245 var goodSum int 246 for count := minCount; count <= maxCount; count++ { 247 goodSum += histogram[count] 248 } 249 250 goodPct := 100 * float64(goodSum) / float64(numHandsSeen) 251 252 t.Logf("good percentage = %v, histogram = %v", goodPct, histogram) 253 if goodSum != numHandsSeen { 254 t.Errorf("Only %v percent of the hands got a central count", goodPct) 255 } 256 } 257 } 258 259 func TestDealer_DealIntoHand(t *testing.T) { 260 dealer, _ := NewDealer(6, 6) 261 262 tests := []struct { 263 name string 264 hand []int 265 expectedSize int 266 }{ 267 { 268 "nil slice", 269 nil, 270 6, 271 }, 272 { 273 "empty slice", 274 make([]int, 0), 275 6, 276 }, 277 { 278 "size: 6 cap: 6 slice", 279 make([]int, 6), 280 6, 281 }, 282 { 283 "size: 6 cap: 12 slice", 284 make([]int, 6, 12), 285 6, 286 }, 287 { 288 "size: 4 cap: 4 slice", 289 make([]int, 4), 290 6, 291 }, 292 { 293 "size: 4 cap: 12 slice", 294 make([]int, 4, 12), 295 6, 296 }, 297 { 298 "size: 10 cap: 10 slice", 299 make([]int, 10), 300 6, 301 }, 302 { 303 "size: 10 cap: 12 slice", 304 make([]int, 10, 12), 305 6, 306 }, 307 } 308 for _, test := range tests { 309 t.Run(test.name, func(t *testing.T) { 310 h := dealer.DealIntoHand(0, test.hand) 311 if len(h) != test.expectedSize { 312 t.Errorf("test %s fails: expetced size %d but got %d", test.name, test.expectedSize, len(h)) 313 return 314 } 315 }) 316 } 317 }