github.com/m3db/m3@v1.5.0/src/query/graphite/ts/sortable_series_test.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package ts 22 23 import ( 24 "math" 25 "math/rand" 26 "sort" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/query/graphite/context" 31 xtest "github.com/m3db/m3/src/query/graphite/testing" 32 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 ) 36 37 type testSeries struct { 38 name string 39 data []float64 40 } 41 42 type testSortData struct { 43 inputs []testSeries 44 output []testSeries 45 } 46 47 func newTestSeriesValues(ctx context.Context, millisPerStep int, values []float64) Values { 48 tsv := NewValues(ctx, millisPerStep, len(values)) 49 50 for i, n := range values { 51 tsv.SetValueAt(i, n) 52 } 53 54 return tsv 55 } 56 57 func newTestSeriesList(ctx context.Context, start time.Time, inputs []testSeries, step int) []*Series { 58 seriesList := make([]*Series, 0, len(inputs)) 59 60 for _, in := range inputs { 61 series := NewSeries(ctx, in.name, start, newTestSeriesValues(ctx, step, in.data)) 62 seriesList = append(seriesList, series) 63 } 64 65 return seriesList 66 } 67 68 func validateOutputs(t *testing.T, step int, start time.Time, expected []testSeries, actual SeriesList) { 69 require.Equal(t, len(expected), len(actual.Values)) 70 71 for i := range expected { 72 a, e := actual.Values[i], expected[i].data 73 74 require.Equal(t, len(e), a.Len()) 75 76 for step := 0; step < a.Len(); step++ { 77 v := a.ValueAt(step) 78 xtest.Equalish(t, e[step], v, "invalid value for %d", step) 79 } 80 81 assert.Equal(t, expected[i].name, a.Name()) 82 assert.Equal(t, step, a.MillisPerStep()) 83 assert.Equal(t, start, a.StartTime()) 84 } 85 } 86 87 func testSortImpl(ctx context.Context, t *testing.T, tests []testSortData, sr SeriesReducer, dir Direction) { 88 var ( 89 startTime = time.Now() 90 step = 100 91 ) 92 93 for _, test := range tests { 94 series := newTestSeriesList(ctx, startTime, test.inputs, step) 95 96 output, err := SortSeries(NewSeriesListWithSeries(series...), sr, dir) 97 98 require.NoError(t, err) 99 validateOutputs(t, step, startTime, test.output, output) 100 } 101 } 102 103 func TestSortSeries(t *testing.T) { 104 ctx := context.New() 105 defer func() { _ = ctx.Close() }() 106 107 testInput := []testSeries{ 108 {"foo", []float64{0, 601, 3, 4}}, 109 {"nan", []float64{math.NaN(), math.NaN(), math.NaN()}}, 110 {"bar", []float64{500, -8}}, 111 {"baz", []float64{600, -600, 3}}, 112 {"qux", []float64{100, 50000, 888, -1, -2}}, 113 } 114 115 testSortImpl(ctx, t, []testSortData{ 116 {testInput, []testSeries{testInput[4], testInput[2], testInput[0], testInput[3], testInput[1]}}, 117 }, SeriesReducerAvg.Reducer(), Descending) 118 119 testSortImpl(ctx, t, []testSortData{ 120 {testInput, []testSeries{testInput[0], testInput[3], testInput[4], testInput[2], testInput[1]}}, 121 }, SeriesReducerLast.Reducer(), Descending) 122 123 testSortImpl(ctx, t, []testSortData{ 124 {testInput, []testSeries{testInput[4], testInput[0], testInput[3], testInput[2], testInput[1]}}, 125 }, SeriesReducerMax.Reducer(), Descending) 126 127 testSortImpl(ctx, t, []testSortData{ 128 {testInput, []testSeries{testInput[4], testInput[3], testInput[2], testInput[0], testInput[1]}}, 129 }, SeriesReducerStdDev.Reducer(), Descending) 130 131 testSortImpl(ctx, t, []testSortData{ 132 {testInput, []testSeries{testInput[1], testInput[3], testInput[0], testInput[2], testInput[4]}}, 133 }, SeriesReducerAvg.Reducer(), Ascending) 134 135 } 136 137 func TestSortSeriesStable(t *testing.T) { 138 ctx := context.New() 139 defer func() { _ = ctx.Close() }() 140 141 constValues := newTestSeriesValues(ctx, 1000, []float64{1, 2, 3, 4}) 142 series := []*Series{ 143 NewSeries(ctx, "foo", time.Now(), constValues), 144 NewSeries(ctx, "bar", time.Now(), constValues), 145 NewSeries(ctx, "baz", time.Now(), constValues), 146 NewSeries(ctx, "qux", time.Now(), constValues), 147 } 148 149 // Check that if input order is random that the same equal "lowest" 150 // series is chosen deterministically each time. 151 var lastOrder []string 152 for i := 0; i < 100; i++ { 153 rand.Shuffle(len(series), func(i, j int) { 154 series[i], series[j] = series[j], series[i] 155 }) 156 157 result, err := SortSeries(SeriesList{ 158 Values: series, 159 SortApplied: false, 160 }, SeriesReducerMin.Reducer(), Descending) 161 require.NoError(t, err) 162 163 order := make([]string, 0, len(result.Values)) 164 for _, series := range result.Values { 165 order = append(order, series.Name()) 166 } 167 168 expectedOrder := lastOrder 169 lastOrder = order 170 if expectedOrder == nil { 171 continue 172 } 173 174 require.Equal(t, expectedOrder, order) 175 } 176 } 177 178 func TestSortSeriesByNameAndNaturalNumbers(t *testing.T) { 179 ctx := context.New() 180 defer func() { _ = ctx.Close() }() 181 182 constValues := newTestSeriesValues(ctx, 1000, []float64{1, 2, 3, 4}) 183 series := []*Series{ 184 NewSeries(ctx, "server1", time.Now(), constValues), 185 NewSeries(ctx, "server11", time.Now(), constValues), 186 NewSeries(ctx, "server12", time.Now(), constValues), 187 NewSeries(ctx, "server2", time.Now(), constValues), 188 } 189 190 sort.Sort(SeriesByNameAndNaturalNumbers(series)) 191 192 actual := make([]string, 0, len(series)) 193 for _, s := range series { 194 actual = append(actual, s.Name()) 195 } 196 197 require.Equal(t, []string{ 198 "server1", 199 "server2", 200 "server11", 201 "server12", 202 }, actual) 203 }