github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/utils/rand/rand_test.go (about) 1 package rand 2 3 import ( 4 "math" 5 mrand "math/rand" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 "github.com/onflow/crypto/random" 12 ) 13 14 func TestRandomIntegers(t *testing.T) { 15 t.Run("basic uniformity", func(t *testing.T) { 16 17 t.Run("Uint", func(t *testing.T) { 18 // make sure n is a power of 2 so that there is no bias in the last class 19 // n is a random power of 2 (from 2 to 2^10) 20 n := 1 << (1 + mrand.Intn(10)) 21 classWidth := (math.MaxUint / uint(n)) + 1 22 uintf := func() (uint64, error) { 23 r, err := Uint() 24 return uint64(r), err 25 } 26 random.BasicDistributionTest(t, uint64(n), uint64(classWidth), uintf) 27 }) 28 29 t.Run("Uint64", func(t *testing.T) { 30 // make sure n is a power of 2 so that there is no bias in the last class 31 // n is a random power of 2 (from 2 to 2^10) 32 n := 1 << (1 + mrand.Intn(10)) 33 classWidth := (math.MaxUint64 / uint64(n)) + 1 34 random.BasicDistributionTest(t, uint64(n), uint64(classWidth), Uint64) 35 }) 36 37 t.Run("Uint32", func(t *testing.T) { 38 // make sure n is a power of 2 so that there is no bias in the last class 39 // n is a random power of 2 (from 2 to 2^10) 40 n := 1 << (1 + mrand.Intn(10)) 41 classWidth := (math.MaxUint32 / uint32(n)) + 1 42 uintf := func() (uint64, error) { 43 r, err := Uint32() 44 return uint64(r), err 45 } 46 random.BasicDistributionTest(t, uint64(n), uint64(classWidth), uintf) 47 }) 48 49 t.Run("Uintn", func(t *testing.T) { 50 n := 10 + mrand.Intn(100) 51 uintf := func() (uint64, error) { 52 r, err := Uintn(uint(n)) 53 return uint64(r), err 54 } 55 // classWidth is 1 since `n` is small 56 random.BasicDistributionTest(t, uint64(n), uint64(1), uintf) 57 }) 58 59 t.Run("Uint64n", func(t *testing.T) { 60 n := 10 + mrand.Intn(100) 61 uintf := func() (uint64, error) { 62 return Uint64n(uint64(n)) 63 } 64 // classWidth is 1 since `n` is small 65 random.BasicDistributionTest(t, uint64(n), uint64(1), uintf) 66 }) 67 68 t.Run("Uint32n", func(t *testing.T) { 69 n := 10 + mrand.Intn(100) 70 uintf := func() (uint64, error) { 71 r, err := Uint32n(uint32(n)) 72 return uint64(r), err 73 } 74 // classWidth is 1 since `n` is small 75 random.BasicDistributionTest(t, uint64(n), uint64(1), uintf) 76 }) 77 }) 78 79 t.Run("zero n error", func(t *testing.T) { 80 t.Run("Uintn", func(t *testing.T) { 81 _, err := Uintn(uint(0)) 82 require.Error(t, err) 83 }) 84 t.Run("Uint64n", func(t *testing.T) { 85 _, err := Uint64n(uint64(0)) 86 require.Error(t, err) 87 }) 88 t.Run("Uint32n", func(t *testing.T) { 89 _, err := Uint32n(uint32(0)) 90 require.Error(t, err) 91 }) 92 }) 93 } 94 95 // Simple unit testing of Shuffle using a basic randomness test. 96 // It doesn't evaluate randomness of the output and doesn't perform advanced statistical tests. 97 func TestShuffle(t *testing.T) { 98 t.Run("basic randomness", func(t *testing.T) { 99 // test parameters 100 listSize := 100 101 sampleSize := 80000 102 // the distribution of a particular random element of the list, testElement 103 distribution := make([]float64, listSize) 104 testElement := mrand.Intn(listSize) 105 // Slice to shuffle 106 list := make([]int, listSize) 107 108 // shuffles the slice and counts the frequency of the test element 109 // in each position 110 shuffleAndCount := func(t *testing.T) { 111 err := Shuffle(uint(listSize), func(i, j uint) { 112 list[i], list[j] = list[j], list[i] 113 }) 114 require.NoError(t, err) 115 has := make(map[int]struct{}) 116 for j, e := range list { 117 // check for repetition 118 _, ok := has[e] 119 require.False(t, ok, "duplicated item") 120 has[e] = struct{}{} 121 // increment the frequency distribution in position `j` 122 if e == testElement { 123 distribution[j] += 1.0 124 } 125 } 126 } 127 128 t.Run("shuffle a random permutation", func(t *testing.T) { 129 // initialize the list 130 for i := 0; i < listSize; i++ { 131 list[i] = i 132 } 133 // shuffle and count multiple times 134 for k := 0; k < sampleSize; k++ { 135 shuffleAndCount(t) 136 } 137 // if the shuffle is uniform, the test element 138 // should end up uniformly in all positions of the slice 139 random.EvaluateDistributionUniformity(t, distribution) 140 }) 141 142 t.Run("shuffle a same permutation", func(t *testing.T) { 143 for k := 0; k < sampleSize; k++ { 144 for i := 0; i < listSize; i++ { 145 list[i] = i 146 } 147 // suffle the same permutation 148 shuffleAndCount(t) 149 } 150 // if the shuffle is uniform, the test element 151 // should end up uniformly in all positions of the slice 152 random.EvaluateDistributionUniformity(t, distribution) 153 }) 154 }) 155 156 t.Run("empty slice", func(t *testing.T) { 157 emptySlice := make([]float64, 0) 158 err := Shuffle(0, func(i, j uint) { 159 emptySlice[i], emptySlice[j] = emptySlice[j], emptySlice[i] 160 }) 161 require.NoError(t, err) 162 assert.True(t, len(emptySlice) == 0) 163 }) 164 } 165 166 func TestSamples(t *testing.T) { 167 t.Run("basic randmoness", func(t *testing.T) { 168 listSize := 100 169 samplesSize := 20 170 sampleSize := 100000 171 // tests the subset sampling randomness 172 samplingDistribution := make([]float64, listSize) 173 // tests the subset ordering randomness (using a particular element testElement) 174 orderingDistribution := make([]float64, samplesSize) 175 testElement := mrand.Intn(listSize) 176 // Slice to shuffle 177 list := make([]int, 0, listSize) 178 for i := 0; i < listSize; i++ { 179 list = append(list, i) 180 } 181 182 for i := 0; i < sampleSize; i++ { 183 err := Samples(uint(listSize), uint(samplesSize), func(i, j uint) { 184 list[i], list[j] = list[j], list[i] 185 }) 186 require.NoError(t, err) 187 has := make(map[int]struct{}) 188 for j, e := range list[:samplesSize] { 189 // check for repetition 190 _, ok := has[e] 191 require.False(t, ok, "duplicated item") 192 has[e] = struct{}{} 193 // fill the distribution 194 samplingDistribution[e] += 1.0 195 if e == testElement { 196 orderingDistribution[j] += 1.0 197 } 198 } 199 } 200 // if the sampling is uniform, all elements 201 // should end up being sampled an equivalent number of times 202 random.EvaluateDistributionUniformity(t, samplingDistribution) 203 // if the sampling is uniform, the test element 204 // should end up uniformly in all positions of the sample slice 205 random.EvaluateDistributionUniformity(t, orderingDistribution) 206 }) 207 208 t.Run("zero edge cases", func(t *testing.T) { 209 // Sampling from an empty set 210 emptySlice := make([]float64, 0) 211 err := Samples(0, 0, func(i, j uint) { 212 emptySlice[i], emptySlice[j] = emptySlice[j], emptySlice[i] 213 }) 214 require.NoError(t, err) 215 assert.True(t, len(emptySlice) == 0) 216 217 // drawing a sample of size zero from an non-empty list should leave the original list unmodified 218 constant := []float64{0, 1, 2, 3, 4, 5} 219 fullSlice := constant 220 err = Samples(uint(len(fullSlice)), 0, func(i, j uint) { // modifies fullSlice in-place 221 emptySlice[i], emptySlice[j] = emptySlice[j], emptySlice[i] 222 }) 223 require.NoError(t, err) 224 assert.Equal(t, constant, fullSlice) 225 }) 226 }