github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/test/compare/comparison.go (about)

     1  // Copyright (c) 2018 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 compare provides comparison functions for testing.
    22  package compare
    23  
    24  import (
    25  	"bytes"
    26  	"fmt"
    27  	"math"
    28  	"sort"
    29  	"testing"
    30  
    31  	"github.com/m3db/m3/src/query/block"
    32  	"github.com/m3db/m3/src/query/models"
    33  
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  )
    37  
    38  // EqualsWithNans helps compare float slices which have NaNs in them
    39  func EqualsWithNans(t *testing.T, expected interface{}, actual interface{}) {
    40  	EqualsWithNansWithDelta(t, expected, actual, 0)
    41  }
    42  
    43  // EqualsWithNansWithDelta helps compare float slices which have NaNs in them
    44  // allowing a delta for float comparisons.
    45  func EqualsWithNansWithDelta(t *testing.T,
    46  	expected interface{}, actual interface{}, delta float64) {
    47  	debugMsg := fmt.Sprintf("expected: %v, actual: %v", expected, actual)
    48  	switch v := expected.(type) {
    49  	case [][]float64:
    50  		actualV, ok := actual.([][]float64)
    51  		require.True(t, ok, "actual should be of type [][]float64, found: %T", actual)
    52  		require.Equal(t, len(v), len(actualV),
    53  			fmt.Sprintf("expected length: %v, actual length: %v\nfor expected: %v, actual: %v",
    54  				len(v), len(actualV), expected, actual))
    55  		for i, vals := range v {
    56  			debugMsg = fmt.Sprintf("on index %d, expected: %v, actual: %v", i, vals, actualV[i])
    57  			equalsWithNans(t, vals, actualV[i], delta, debugMsg)
    58  		}
    59  
    60  	case []float64:
    61  		actualV, ok := actual.([]float64)
    62  		require.True(t, ok, "actual should be of type []float64, found: %T", actual)
    63  		require.Equal(t, len(v), len(actualV),
    64  			fmt.Sprintf("expected length: %v, actual length: %v\nfor expected: %v, actual: %v",
    65  				len(v), len(actualV), expected, actual))
    66  
    67  		equalsWithNans(t, v, actualV, delta, debugMsg)
    68  
    69  	case float64:
    70  		actualV, ok := actual.(float64)
    71  		require.True(t, ok, "actual should be of type float64, found: %T", actual)
    72  		equalsWithDelta(t, v, actualV, delta, debugMsg)
    73  
    74  	default:
    75  		require.Fail(t, "unknown type: %T", v)
    76  	}
    77  }
    78  
    79  func equalsWithNans(t *testing.T, expected []float64,
    80  	actual []float64, delta float64, debugMsg string) {
    81  	require.Equal(t, len(expected), len(actual))
    82  	for i, v := range expected {
    83  		if math.IsNaN(v) {
    84  			require.True(t, math.IsNaN(actual[i]), debugMsg)
    85  		} else {
    86  			equalsWithDelta(t, v, actual[i], delta, debugMsg)
    87  		}
    88  	}
    89  }
    90  
    91  func equalsWithDelta(t *testing.T, expected, actual, delta float64, debugMsg string) {
    92  	if math.IsNaN(expected) {
    93  		require.True(t, math.IsNaN(actual), debugMsg)
    94  		return
    95  	}
    96  	if math.IsInf(expected, 0) {
    97  		require.Equal(t, expected, actual, debugMsg)
    98  		return
    99  	}
   100  	if delta == 0 {
   101  		require.Equal(t, expected, actual, debugMsg)
   102  	} else {
   103  		diff := math.Abs(expected - actual)
   104  		require.True(t, delta > diff, debugMsg)
   105  	}
   106  }
   107  
   108  type match struct {
   109  	indices    []int
   110  	seriesTags []models.Tag
   111  	name       []byte
   112  	values     []float64
   113  }
   114  
   115  type matches []match
   116  
   117  func (m matches) Len() int      { return len(m) }
   118  func (m matches) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
   119  func (m matches) Less(i, j int) bool {
   120  	return bytes.Compare(
   121  		models.NewTags(0, nil).AddTags(m[i].seriesTags).ID(),
   122  		models.NewTags(0, nil).AddTags(m[j].seriesTags).ID(),
   123  	) == -1
   124  }
   125  
   126  // CompareListsInOrder compares series meta / index pairs (order sensitive)
   127  func CompareListsInOrder(t *testing.T, meta, exMeta []block.SeriesMeta, index, exIndex [][]int) {
   128  	require.Equal(t, len(exIndex), len(exMeta))
   129  
   130  	assert.Equal(t, exMeta, meta)
   131  	assert.Equal(t, exIndex, index)
   132  }
   133  
   134  // CompareValuesInOrder compares series meta / value pairs (order sensitive)
   135  func CompareValuesInOrder(t *testing.T, meta, exMeta []block.SeriesMeta, vals, exVals [][]float64) {
   136  	require.Equal(t, len(exVals), len(exMeta))
   137  	require.Equal(t, len(exVals), len(vals), "Vals is", meta, "ExVals is", exMeta)
   138  
   139  	assert.Equal(t, exMeta, meta)
   140  
   141  	for i := range exVals {
   142  		EqualsWithNansWithDelta(t, exVals[i], vals[i], 0.00001)
   143  	}
   144  }
   145  
   146  // CompareValues compares series meta / value pairs (order insensitive)
   147  func CompareValues(t *testing.T, meta, exMeta []block.SeriesMeta, vals, exVals [][]float64) {
   148  	require.Equal(t, len(exVals), len(exMeta))
   149  	require.Equal(t, len(exMeta), len(meta), "Meta is", meta, "ExMeta is", exMeta)
   150  	require.Equal(t, len(exVals), len(vals), "Vals is", meta, "ExVals is", exMeta)
   151  
   152  	ex := make(matches, len(meta))
   153  	actual := make(matches, len(meta))
   154  	// build matchers
   155  	for i := range meta {
   156  		ex[i] = match{[]int{}, exMeta[i].Tags.Tags, exMeta[i].Name, exVals[i]}
   157  		actual[i] = match{[]int{}, meta[i].Tags.Tags, exMeta[i].Name, vals[i]}
   158  	}
   159  
   160  	sort.Sort(ex)
   161  	sort.Sort(actual)
   162  	for i := range ex {
   163  		assert.Equal(t, ex[i].name, actual[i].name)
   164  		assert.Equal(t, ex[i].seriesTags, actual[i].seriesTags)
   165  		EqualsWithNansWithDelta(t, ex[i].values, actual[i].values, 0.00001)
   166  	}
   167  }