github.com/grafana/pyroscope@v1.18.0/pkg/model/recording_rule_test.go (about)

     1  package model
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/require"
     7  
     8  	settingsv1 "github.com/grafana/pyroscope/api/gen/proto/go/settings/v1"
     9  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    10  )
    11  
    12  func Test_NewRecordingRule_GroupByValidation(t *testing.T) {
    13  	tests := []struct {
    14  		name    string
    15  		rule    *settingsv1.RecordingRule
    16  		wantErr string
    17  	}{
    18  		{
    19  			name: "valid group_by",
    20  			rule: &settingsv1.RecordingRule{
    21  				Id:         "test",
    22  				MetricName: "profiles_recorded_test",
    23  				Matchers:   []string{`{__profile_type__="cpu"}`},
    24  				GroupBy:    []string{"valid_label", "another_valid"},
    25  				ExternalLabels: []*typesv1.LabelPair{
    26  					{Name: "external_label", Value: "value"},
    27  				},
    28  			},
    29  			wantErr: "",
    30  		},
    31  		{
    32  			name: "invalid group_by with dot",
    33  			rule: &settingsv1.RecordingRule{
    34  				Id:         "test",
    35  				MetricName: "profiles_recorded_test",
    36  				Matchers:   []string{`{__profile_type__="cpu"}`},
    37  				GroupBy:    []string{"service.name"},
    38  				ExternalLabels: []*typesv1.LabelPair{
    39  					{Name: "external_label", Value: "value"},
    40  				},
    41  			},
    42  			wantErr: `group_by label "service.name" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
    43  		},
    44  		{
    45  			name: "invalid group_by with UTF-8",
    46  			rule: &settingsv1.RecordingRule{
    47  				Id:         "test",
    48  				MetricName: "profiles_recorded_test",
    49  				Matchers:   []string{`{__profile_type__="cpu"}`},
    50  				GroupBy:    []string{"世界"},
    51  				ExternalLabels: []*typesv1.LabelPair{
    52  					{Name: "external_label", Value: "value"},
    53  				},
    54  			},
    55  			wantErr: `group_by label "世界" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
    56  		},
    57  		{
    58  			name: "invalid group_by starts with number",
    59  			rule: &settingsv1.RecordingRule{
    60  				Id:         "test",
    61  				MetricName: "profiles_recorded_test",
    62  				Matchers:   []string{`{__profile_type__="cpu"}`},
    63  				GroupBy:    []string{"123invalid"},
    64  				ExternalLabels: []*typesv1.LabelPair{
    65  					{Name: "external_label", Value: "value"},
    66  				},
    67  			},
    68  			wantErr: `group_by label "123invalid" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
    69  		},
    70  		{
    71  			name: "invalid group_by with hyphen",
    72  			rule: &settingsv1.RecordingRule{
    73  				Id:         "test",
    74  				MetricName: "profiles_recorded_test",
    75  				Matchers:   []string{`{__profile_type__="cpu"}`},
    76  				GroupBy:    []string{"service-name"},
    77  				ExternalLabels: []*typesv1.LabelPair{
    78  					{Name: "external_label", Value: "value"},
    79  				},
    80  			},
    81  			wantErr: `group_by label "service-name" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
    82  		},
    83  	}
    84  
    85  	for _, tt := range tests {
    86  		t.Run(tt.name, func(t *testing.T) {
    87  			_, err := NewRecordingRule(tt.rule)
    88  			if tt.wantErr != "" {
    89  				require.Error(t, err)
    90  				require.EqualError(t, err, tt.wantErr)
    91  			} else {
    92  				require.NoError(t, err)
    93  			}
    94  		})
    95  	}
    96  }
    97  
    98  func Test_NewRecordingRule_ExternalLabelsValidation(t *testing.T) {
    99  	tests := []struct {
   100  		name    string
   101  		rule    *settingsv1.RecordingRule
   102  		wantErr string
   103  	}{
   104  		{
   105  			name: "valid external_labels",
   106  			rule: &settingsv1.RecordingRule{
   107  				Id:         "test",
   108  				MetricName: "profiles_recorded_test",
   109  				Matchers:   []string{`{__profile_type__="cpu"}`},
   110  				GroupBy:    []string{"valid_label"},
   111  				ExternalLabels: []*typesv1.LabelPair{
   112  					{Name: "valid_label", Value: "value"},
   113  					{Name: "another_valid", Value: "value"},
   114  				},
   115  			},
   116  			wantErr: "",
   117  		},
   118  		{
   119  			name: "invalid external_labels with dot",
   120  			rule: &settingsv1.RecordingRule{
   121  				Id:         "test",
   122  				MetricName: "profiles_recorded_test",
   123  				Matchers:   []string{`{__profile_type__="cpu"}`},
   124  				GroupBy:    []string{"valid_label"},
   125  				ExternalLabels: []*typesv1.LabelPair{
   126  					{Name: "service.name", Value: "foo"},
   127  				},
   128  			},
   129  			wantErr: `external_labels name "service.name" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   130  		},
   131  		{
   132  			name: "invalid external_labels with UTF-8",
   133  			rule: &settingsv1.RecordingRule{
   134  				Id:         "test",
   135  				MetricName: "profiles_recorded_test",
   136  				Matchers:   []string{`{__profile_type__="cpu"}`},
   137  				GroupBy:    []string{"valid_label"},
   138  				ExternalLabels: []*typesv1.LabelPair{
   139  					{Name: "世界", Value: "value"},
   140  				},
   141  			},
   142  			wantErr: `external_labels name "世界" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   143  		},
   144  		{
   145  			name: "invalid external_labels starts with number",
   146  			rule: &settingsv1.RecordingRule{
   147  				Id:         "test",
   148  				MetricName: "profiles_recorded_test",
   149  				Matchers:   []string{`{__profile_type__="cpu"}`},
   150  				GroupBy:    []string{"valid_label"},
   151  				ExternalLabels: []*typesv1.LabelPair{
   152  					{Name: "123invalid", Value: "value"},
   153  				},
   154  			},
   155  			wantErr: `external_labels name "123invalid" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   156  		},
   157  		{
   158  			name: "invalid external_labels with hyphen",
   159  			rule: &settingsv1.RecordingRule{
   160  				Id:         "test",
   161  				MetricName: "profiles_recorded_test",
   162  				Matchers:   []string{`{__profile_type__="cpu"}`},
   163  				GroupBy:    []string{"valid_label"},
   164  				ExternalLabels: []*typesv1.LabelPair{
   165  					{Name: "service-name", Value: "value"},
   166  				},
   167  			},
   168  			wantErr: `external_labels name "service-name" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   169  		},
   170  		{
   171  			name: "valid external_labels UTF-8 value is allowed",
   172  			rule: &settingsv1.RecordingRule{
   173  				Id:         "test",
   174  				MetricName: "profiles_recorded_test",
   175  				Matchers:   []string{`{__profile_type__="cpu"}`},
   176  				GroupBy:    []string{"valid_label"},
   177  				ExternalLabels: []*typesv1.LabelPair{
   178  					{Name: "valid_label", Value: "世界"},
   179  				},
   180  			},
   181  			wantErr: "",
   182  		},
   183  	}
   184  
   185  	for _, tt := range tests {
   186  		t.Run(tt.name, func(t *testing.T) {
   187  			_, err := NewRecordingRule(tt.rule)
   188  			if tt.wantErr != "" {
   189  				require.Error(t, err)
   190  				require.EqualError(t, err, tt.wantErr)
   191  			} else {
   192  				require.NoError(t, err)
   193  			}
   194  		})
   195  	}
   196  }
   197  
   198  func Test_NewRecordingRule_MetricNameValidation(t *testing.T) {
   199  	tests := []struct {
   200  		name    string
   201  		rule    *settingsv1.RecordingRule
   202  		wantErr string
   203  	}{
   204  		{
   205  			name: "valid metric_name",
   206  			rule: &settingsv1.RecordingRule{
   207  				Id:         "test",
   208  				MetricName: "profiles_recorded_test",
   209  				Matchers:   []string{`{__profile_type__="cpu"}`},
   210  				GroupBy:    []string{"valid_label"},
   211  				ExternalLabels: []*typesv1.LabelPair{
   212  					{Name: "external_label", Value: "value"},
   213  				},
   214  			},
   215  			wantErr: "",
   216  		},
   217  		{
   218  			name: "valid metric_name with underscores and numbers",
   219  			rule: &settingsv1.RecordingRule{
   220  				Id:         "test",
   221  				MetricName: "profiles_recorded_test_123",
   222  				Matchers:   []string{`{__profile_type__="cpu"}`},
   223  				GroupBy:    []string{"valid_label"},
   224  				ExternalLabels: []*typesv1.LabelPair{
   225  					{Name: "external_label", Value: "value"},
   226  				},
   227  			},
   228  			wantErr: "",
   229  		},
   230  		{
   231  			name: "invalid metric_name with dot",
   232  			rule: &settingsv1.RecordingRule{
   233  				Id:         "test",
   234  				MetricName: "profiles_recorded.test",
   235  				Matchers:   []string{`{__profile_type__="cpu"}`},
   236  				GroupBy:    []string{"valid_label"},
   237  				ExternalLabels: []*typesv1.LabelPair{
   238  					{Name: "external_label", Value: "value"},
   239  				},
   240  			},
   241  			wantErr: `invalid metric name: profiles_recorded.test`,
   242  		},
   243  		{
   244  			name: "invalid metric_name with UTF-8",
   245  			rule: &settingsv1.RecordingRule{
   246  				Id:         "test",
   247  				MetricName: "profiles_recorded_世界",
   248  				Matchers:   []string{`{__profile_type__="cpu"}`},
   249  				GroupBy:    []string{"valid_label"},
   250  				ExternalLabels: []*typesv1.LabelPair{
   251  					{Name: "external_label", Value: "value"},
   252  				},
   253  			},
   254  			wantErr: `invalid metric name: profiles_recorded_世界`,
   255  		},
   256  		{
   257  			name: "invalid metric_name with emoji",
   258  			rule: &settingsv1.RecordingRule{
   259  				Id:         "test",
   260  				MetricName: "profiles_recorded_test_😘",
   261  				Matchers:   []string{`{__profile_type__="cpu"}`},
   262  				GroupBy:    []string{"valid_label"},
   263  				ExternalLabels: []*typesv1.LabelPair{
   264  					{Name: "external_label", Value: "value"},
   265  				},
   266  			},
   267  			wantErr: `invalid metric name: profiles_recorded_test_😘`,
   268  		},
   269  		{
   270  			name: "invalid metric_name starts with number",
   271  			rule: &settingsv1.RecordingRule{
   272  				Id:         "test",
   273  				MetricName: "123_invalid",
   274  				Matchers:   []string{`{__profile_type__="cpu"}`},
   275  				GroupBy:    []string{"valid_label"},
   276  				ExternalLabels: []*typesv1.LabelPair{
   277  					{Name: "external_label", Value: "value"},
   278  				},
   279  			},
   280  			wantErr: `invalid metric name: 123_invalid`,
   281  		},
   282  		{
   283  			name: "invalid metric_name with hyphen",
   284  			rule: &settingsv1.RecordingRule{
   285  				Id:         "test",
   286  				MetricName: "profiles_recorded-test",
   287  				Matchers:   []string{`{__profile_type__="cpu"}`},
   288  				GroupBy:    []string{"valid_label"},
   289  				ExternalLabels: []*typesv1.LabelPair{
   290  					{Name: "external_label", Value: "value"},
   291  				},
   292  			},
   293  			wantErr: `invalid metric name: profiles_recorded-test`,
   294  		},
   295  		{
   296  			name: "invalid metric_name with exclamation marks",
   297  			rule: &settingsv1.RecordingRule{
   298  				Id:         "test",
   299  				MetricName: "profiles_recorded_test!!!",
   300  				Matchers:   []string{`{__profile_type__="cpu"}`},
   301  				GroupBy:    []string{"valid_label"},
   302  				ExternalLabels: []*typesv1.LabelPair{
   303  					{Name: "external_label", Value: "value"},
   304  				},
   305  			},
   306  			wantErr: `invalid metric name: profiles_recorded_test!!!`,
   307  		},
   308  	}
   309  
   310  	for _, tt := range tests {
   311  		t.Run(tt.name, func(t *testing.T) {
   312  			_, err := NewRecordingRule(tt.rule)
   313  			if tt.wantErr != "" {
   314  				require.Error(t, err)
   315  				require.EqualError(t, err, tt.wantErr)
   316  			} else {
   317  				require.NoError(t, err)
   318  			}
   319  		})
   320  	}
   321  }
   322  
   323  func Test_NewRecordingRule_MatchersValidation(t *testing.T) {
   324  	tests := []struct {
   325  		name    string
   326  		rule    *settingsv1.RecordingRule
   327  		wantErr string
   328  	}{
   329  		{
   330  			name: "valid matchers with standard characters",
   331  			rule: &settingsv1.RecordingRule{
   332  				Id:         "test",
   333  				MetricName: "profiles_recorded_test",
   334  				Matchers:   []string{`{__profile_type__="cpu"}`},
   335  				GroupBy:    []string{"valid_label"},
   336  				ExternalLabels: []*typesv1.LabelPair{
   337  					{Name: "external_label", Value: "value"},
   338  				},
   339  			},
   340  			wantErr: "",
   341  		},
   342  		{
   343  			name: "valid matchers with UTF-8 characters",
   344  			rule: &settingsv1.RecordingRule{
   345  				Id:         "test",
   346  				MetricName: "profiles_recorded_test",
   347  				Matchers:   []string{`{service_name="世界", __profile_type__="cpu"}`},
   348  				GroupBy:    []string{"valid_label"},
   349  				ExternalLabels: []*typesv1.LabelPair{
   350  					{Name: "external_label", Value: "value"},
   351  				},
   352  			},
   353  			wantErr: "",
   354  		},
   355  		{
   356  			name: "valid matchers with UTF-8 label names (quoted)",
   357  			rule: &settingsv1.RecordingRule{
   358  				Id:         "test",
   359  				MetricName: "profiles_recorded_test",
   360  				Matchers:   []string{`{"世界"="value", __profile_type__="cpu"}`},
   361  				GroupBy:    []string{"valid_label"},
   362  				ExternalLabels: []*typesv1.LabelPair{
   363  					{Name: "external_label", Value: "value"},
   364  				},
   365  			},
   366  			wantErr: "",
   367  		},
   368  		{
   369  			name: "valid matchers with emojis in label names and values",
   370  			rule: &settingsv1.RecordingRule{
   371  				Id:         "test",
   372  				MetricName: "profiles_recorded_test",
   373  				Matchers:   []string{`{"utf-8 matchers are fine 🔥"="we don't export them", __profile_type__="cpu"}`},
   374  				GroupBy:    []string{"valid_label"},
   375  				ExternalLabels: []*typesv1.LabelPair{
   376  					{Name: "external_label", Value: "value"},
   377  				},
   378  			},
   379  			wantErr: "",
   380  		},
   381  		{
   382  			name: "valid matchers with dots in label names",
   383  			rule: &settingsv1.RecordingRule{
   384  				Id:         "test",
   385  				MetricName: "profiles_recorded_test",
   386  				Matchers:   []string{`{"service.name"="my-service", __profile_type__="cpu"}`},
   387  				GroupBy:    []string{"valid_label"},
   388  				ExternalLabels: []*typesv1.LabelPair{
   389  					{Name: "external_label", Value: "value"},
   390  				},
   391  			},
   392  			wantErr: "",
   393  		},
   394  	}
   395  
   396  	for _, tt := range tests {
   397  		t.Run(tt.name, func(t *testing.T) {
   398  			_, err := NewRecordingRule(tt.rule)
   399  			if tt.wantErr != "" {
   400  				require.Error(t, err)
   401  				require.EqualError(t, err, tt.wantErr)
   402  			} else {
   403  				require.NoError(t, err)
   404  			}
   405  		})
   406  	}
   407  }