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  }