github.com/grailbio/bigslice@v0.0.0-20230519005545-30c4c12152ad/cmd/slicetrace/quartile_test.go (about)

     1  // Copyright 2020 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"sort"
     9  	"testing"
    10  	"time"
    11  
    12  	fuzz "github.com/google/gofuzz"
    13  )
    14  
    15  // TestComputeQuartiles verifies that quartiles are computed correctly for
    16  // select test cases.
    17  func TestComputeQuartiles(t *testing.T) {
    18  	for _, c := range []struct {
    19  		name       string
    20  		ds         []int64
    21  		q1, q2, q3 int64
    22  	}{
    23  		{
    24  			name: "OneElement",
    25  			ds:   []int64{0},
    26  			q1:   0,
    27  			q2:   0,
    28  			q3:   0,
    29  		},
    30  		{
    31  			name: "TwoElementAllSame",
    32  			ds:   []int64{0, 0},
    33  			q1:   0,
    34  			q2:   0,
    35  			q3:   0,
    36  		},
    37  		{
    38  			name: "TwoElement",
    39  			ds:   []int64{0, 100},
    40  			q1:   0,
    41  			q2:   50,
    42  			q3:   100,
    43  		},
    44  		{
    45  			name: "ThreeElementAllSame",
    46  			ds:   []int64{0, 0, 0},
    47  			q1:   0,
    48  			q2:   0,
    49  			q3:   0,
    50  		},
    51  		{
    52  			name: "ThreeElementLowSame",
    53  			ds:   []int64{0, 0, 200},
    54  			q1:   0,
    55  			q2:   0,
    56  			q3:   100,
    57  		},
    58  		{
    59  			name: "ThreeElementHighSame",
    60  			ds:   []int64{0, 200, 200},
    61  			q1:   100,
    62  			q2:   200,
    63  			q3:   200,
    64  		},
    65  		{
    66  			name: "ThreeElement",
    67  			ds:   []int64{0, 100, 200},
    68  			q1:   50,
    69  			q2:   100,
    70  			q3:   150,
    71  		},
    72  		{
    73  			name: "FourElementAllSame",
    74  			ds:   []int64{0, 0, 0, 0},
    75  			q1:   0,
    76  			q2:   0,
    77  			q3:   0,
    78  		},
    79  		{
    80  			name: "FourElementSomeSame",
    81  			ds:   []int64{0, 100, 100, 200},
    82  			q1:   50,
    83  			q2:   100,
    84  			q3:   150,
    85  		},
    86  		{
    87  			name: "FourElement",
    88  			ds:   []int64{0, 100, 200, 300},
    89  			q1:   50,
    90  			q2:   150,
    91  			q3:   250,
    92  		},
    93  		{
    94  			name: "FiveElementAllSame",
    95  			ds:   []int64{0, 0, 0, 0, 0},
    96  			q1:   0,
    97  			q2:   0,
    98  			q3:   0,
    99  		},
   100  		{
   101  			name: "FiveElementSomeSame",
   102  			ds:   []int64{0, 100, 100, 100, 200},
   103  			q1:   100,
   104  			q2:   100,
   105  			q3:   100,
   106  		},
   107  		{
   108  			name: "FiveElement",
   109  			ds:   []int64{0, 100, 200, 300, 400},
   110  			q1:   100,
   111  			q2:   200,
   112  			q3:   300,
   113  		},
   114  	} {
   115  		t.Run(c.name, func(t *testing.T) {
   116  			ds := make([]time.Duration, len(c.ds))
   117  			for i := range ds {
   118  				ds[i] = time.Duration(c.ds[i])
   119  			}
   120  			q1, q2, q3 := computeQuartiles(ds)
   121  			if got, want := q1, time.Duration(c.q1); got != want {
   122  				t.Errorf("got %v, want %v", got, want)
   123  			}
   124  			if got, want := q2, time.Duration(c.q2); got != want {
   125  				t.Errorf("got %v, want %v", got, want)
   126  			}
   127  			if got, want := q3, time.Duration(c.q3); got != want {
   128  				t.Errorf("got %v, want %v", got, want)
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  // TestComputeQuartilesFuzz verifies that the quartiles computed from fuzzed
   135  // values are within [min, max] and monotonically increasing.
   136  func TestComputeQuartilesFuzz(t *testing.T) {
   137  	const N = 10000
   138  	f := fuzz.New()
   139  	for i := 0; i < N; i++ {
   140  		var ds []time.Duration
   141  		f.Fuzz(&ds)
   142  		if len(ds) == 0 {
   143  			// computeQuartiles does not handle empty slices.
   144  			continue
   145  		}
   146  		sort.Slice(ds, func(i, j int) bool {
   147  			return ds[i] < ds[j]
   148  		})
   149  		var (
   150  			min = time.Duration(1<<63 - 1)
   151  			max = time.Duration(-1 << 63)
   152  		)
   153  		for _, d := range ds {
   154  			if d < min {
   155  				min = d
   156  			}
   157  			if max < d {
   158  				max = d
   159  			}
   160  		}
   161  		q1, q2, q3 := computeQuartiles(ds)
   162  		t.Logf("test durations: %v", ds)
   163  		if q1 < min {
   164  			t.Errorf("%v(q1) < %v(min)", q1, min)
   165  		}
   166  		if q2 < q1 {
   167  			t.Errorf("%v(q2) < %v(q1)", q2, q1)
   168  		}
   169  		if q3 < q2 {
   170  			t.Errorf("%v(q3) < %v(q2)", q3, q2)
   171  		}
   172  		if max < q3 {
   173  			t.Errorf("%v(max) < %v(q3)", max, q3)
   174  		}
   175  	}
   176  }