github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/sharding/label_test.go (about)

     1  // SPDX-License-Identifier: AGPL-3.0-only
     2  
     3  package sharding
     4  
     5  import (
     6  	"fmt"
     7  	"math/rand"
     8  	"testing"
     9  
    10  	"github.com/prometheus/prometheus/model/labels"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestParseShard(t *testing.T) {
    16  	tests := map[string]struct {
    17  		input        string
    18  		index, count uint64
    19  		err          bool
    20  	}{
    21  		"should return error on invalid format": {
    22  			input: "lsdjf",
    23  			err:   true,
    24  		},
    25  		"should return error on invalid index (not an integer)": {
    26  			input: "a_of_3",
    27  			err:   true,
    28  		},
    29  		"should return error on invalid index (not positive)": {
    30  			input: "-1_of_3",
    31  			err:   true,
    32  		},
    33  		"should return error on invalid count (not positive)": {
    34  			input: "-1_of_-3",
    35  			err:   true,
    36  		},
    37  		"should return error on invalid index (too large)": {
    38  			input: "4_of_3",
    39  			err:   true,
    40  		},
    41  		"should return error on invalid index (too small)": {
    42  			input: "0_of_3",
    43  			err:   true,
    44  		},
    45  		"should return error on invalid separator": {
    46  			input: "1_out_3",
    47  			err:   true,
    48  		},
    49  		"should succeed on valid first shard ID": {
    50  			input: "1_of_2",
    51  			index: 0, // 0-based
    52  			count: 2,
    53  		},
    54  		"should succeed on valid last shard selector": {
    55  			input: "2_of_2",
    56  			index: 1, // 0-based
    57  			count: 2,
    58  		},
    59  	}
    60  
    61  	for testName, testData := range tests {
    62  		t.Run(testName, func(t *testing.T) {
    63  			index, count, err := ParseShardIDLabelValue(testData.input)
    64  			if testData.err {
    65  				require.Error(t, err)
    66  			} else {
    67  				require.NoError(t, err)
    68  				require.Equal(t, testData.index, index)
    69  				require.Equal(t, testData.count, count)
    70  			}
    71  		})
    72  	}
    73  }
    74  
    75  func TestRemoveShardFromMatchers(t *testing.T) {
    76  	tests := map[string]struct {
    77  		input            []*labels.Matcher
    78  		expectedShard    *ShardSelector
    79  		expectedMatchers []*labels.Matcher
    80  		expectedError    error
    81  	}{
    82  		"should return no shard on empty label matchers": {},
    83  		"should return no shard on no shard label matcher": {
    84  			input: []*labels.Matcher{
    85  				labels.MustNewMatcher(labels.MatchEqual, labels.MetricName, "test"),
    86  				labels.MustNewMatcher(labels.MatchRegexp, "foo", "bar.*"),
    87  			},
    88  			expectedShard: nil,
    89  			expectedMatchers: []*labels.Matcher{
    90  				labels.MustNewMatcher(labels.MatchEqual, labels.MetricName, "test"),
    91  				labels.MustNewMatcher(labels.MatchRegexp, "foo", "bar.*"),
    92  			},
    93  		},
    94  		"should return matching shard and filter out its matcher": {
    95  			input: []*labels.Matcher{
    96  				labels.MustNewMatcher(labels.MatchEqual, labels.MetricName, "test"),
    97  				labels.MustNewMatcher(labels.MatchEqual, ShardLabel, ShardSelector{ShardIndex: 1, ShardCount: 8}.LabelValue()),
    98  				labels.MustNewMatcher(labels.MatchRegexp, "foo", "bar.*"),
    99  			},
   100  			expectedShard: &ShardSelector{
   101  				ShardIndex: 1,
   102  				ShardCount: 8,
   103  			},
   104  			expectedMatchers: []*labels.Matcher{
   105  				labels.MustNewMatcher(labels.MatchEqual, labels.MetricName, "test"),
   106  				labels.MustNewMatcher(labels.MatchRegexp, "foo", "bar.*"),
   107  			},
   108  		},
   109  	}
   110  
   111  	for testName, testData := range tests {
   112  		t.Run(testName, func(t *testing.T) {
   113  			actualShard, actualMatchers, actualError := RemoveShardFromMatchers(testData.input)
   114  			assert.Equal(t, testData.expectedShard, actualShard)
   115  			assert.Equal(t, testData.expectedError, actualError)
   116  
   117  			// Assert same matchers. We do some optimizations in mimir-prometheus which make
   118  			// the label matchers not comparable with reflect.DeepEqual() so we're going to
   119  			// compare their string representation.
   120  			require.Len(t, actualMatchers, len(testData.expectedMatchers))
   121  			for i := 0; i < len(testData.expectedMatchers); i++ {
   122  				assert.Equal(t, testData.expectedMatchers[i].String(), actualMatchers[i].String())
   123  			}
   124  		})
   125  	}
   126  }
   127  
   128  func TestShardFromMatchers(t *testing.T) {
   129  	testExpr := []struct {
   130  		input []*labels.Matcher
   131  		shard *ShardSelector
   132  		idx   int
   133  		err   bool
   134  	}{
   135  		{
   136  			input: []*labels.Matcher{
   137  				{},
   138  				{
   139  					Name: ShardLabel,
   140  					Type: labels.MatchEqual,
   141  					Value: ShardSelector{
   142  						ShardIndex: 10,
   143  						ShardCount: 16,
   144  					}.LabelValue(),
   145  				},
   146  				{},
   147  			},
   148  			shard: &ShardSelector{
   149  				ShardIndex: 10,
   150  				ShardCount: 16,
   151  			},
   152  			idx: 1,
   153  			err: false,
   154  		},
   155  		{
   156  			input: []*labels.Matcher{
   157  				{
   158  					Name:  ShardLabel,
   159  					Type:  labels.MatchEqual,
   160  					Value: "invalid-fmt",
   161  				},
   162  			},
   163  			shard: nil,
   164  			idx:   0,
   165  			err:   true,
   166  		},
   167  		{
   168  			input: []*labels.Matcher{},
   169  			shard: nil,
   170  			idx:   0,
   171  			err:   false,
   172  		},
   173  	}
   174  
   175  	for i, c := range testExpr {
   176  		t.Run(fmt.Sprint(i), func(t *testing.T) {
   177  			shard, idx, err := ShardFromMatchers(c.input)
   178  			if c.err {
   179  				require.NotNil(t, err)
   180  			} else {
   181  				require.Nil(t, err)
   182  				require.Equal(t, c.shard, shard)
   183  				require.Equal(t, c.idx, idx)
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  func TestFormatAndParseShardId(t *testing.T) {
   190  	r := rand.New(rand.NewSource(0))
   191  
   192  	const maxTests = 1000
   193  	const maxShardCount = 10000
   194  
   195  	for i := 0; i < maxTests; i++ {
   196  		count := 1 + r.Intn(maxShardCount)
   197  		id := r.Intn(count)
   198  
   199  		require.True(t, id < count)
   200  
   201  		out := FormatShardIDLabelValue(uint64(id), uint64(count))
   202  		nid, ncount, err := ParseShardIDLabelValue(out)
   203  
   204  		require.NoError(t, err)
   205  		require.Equal(t, uint64(id), nid)
   206  		require.Equal(t, uint64(count), ncount)
   207  	}
   208  }