github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/pipeline/type_test.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 pipeline
    22  
    23  import (
    24  	"encoding/json"
    25  	"testing"
    26  
    27  	"github.com/m3db/m3/src/metrics/aggregation"
    28  	"github.com/m3db/m3/src/metrics/generated/proto/pipelinepb"
    29  	"github.com/m3db/m3/src/metrics/generated/proto/transformationpb"
    30  	"github.com/m3db/m3/src/metrics/transformation"
    31  	"github.com/m3db/m3/src/metrics/x/bytes"
    32  	"github.com/m3db/m3/src/x/test/testmarshal"
    33  
    34  	"github.com/stretchr/testify/require"
    35  	"gopkg.in/yaml.v2"
    36  )
    37  
    38  var (
    39  	testTransformationOp = TransformationOp{
    40  		Type: transformation.PerSecond,
    41  	}
    42  	testTransformationOpProto = pipelinepb.TransformationOp{
    43  		Type: transformationpb.TransformationType_PERSECOND,
    44  	}
    45  	testBadTransformationOpProto = pipelinepb.TransformationOp{
    46  		Type: transformationpb.TransformationType_UNKNOWN,
    47  	}
    48  )
    49  
    50  func TestAggregationOpEqual(t *testing.T) {
    51  	inputs := []struct {
    52  		a1       AggregationOp
    53  		a2       AggregationOp
    54  		expected bool
    55  	}{
    56  		{
    57  			a1:       AggregationOp{aggregation.Count},
    58  			a2:       AggregationOp{aggregation.Count},
    59  			expected: true,
    60  		},
    61  		{
    62  			a1:       AggregationOp{aggregation.Count},
    63  			a2:       AggregationOp{aggregation.Sum},
    64  			expected: false,
    65  		},
    66  	}
    67  
    68  	for _, input := range inputs {
    69  		require.Equal(t, input.expected, input.a1.Equal(input.a2))
    70  		require.Equal(t, input.expected, input.a2.Equal(input.a1))
    71  	}
    72  }
    73  
    74  func TestAggregationOpMarshalling(t *testing.T) {
    75  	examples := []AggregationOp{{aggregation.Count}}
    76  
    77  	t.Run("roundtrips", func(t *testing.T) {
    78  		testmarshal.TestMarshalersRoundtrip(t, examples, []testmarshal.Marshaler{testmarshal.JSONMarshaler, testmarshal.YAMLMarshaler, testmarshal.TextMarshaler})
    79  	})
    80  
    81  	t.Run("marshals", func(t *testing.T) {
    82  		cases := []struct {
    83  			Example AggregationOp
    84  			YAML    string
    85  			JSON    string
    86  			Text    string
    87  		}{{
    88  			Example: AggregationOp{aggregation.Count},
    89  
    90  			Text: "Count",
    91  			JSON: `"Count"`,
    92  			YAML: "Count\n",
    93  		}}
    94  
    95  		t.Run("text", func(t *testing.T) {
    96  			for _, tc := range cases {
    97  				testmarshal.Require(t, testmarshal.AssertUnmarshals(t, testmarshal.TextMarshaler, tc.Example, []byte(tc.Text)))
    98  				testmarshal.Require(t, testmarshal.AssertMarshals(t, testmarshal.TextMarshaler, tc.Example, []byte(tc.Text)))
    99  			}
   100  		})
   101  
   102  		t.Run("json", func(t *testing.T) {
   103  			for _, tc := range cases {
   104  				testmarshal.Require(t, testmarshal.AssertUnmarshals(t, testmarshal.JSONMarshaler, tc.Example, []byte(tc.JSON)))
   105  				testmarshal.Require(t, testmarshal.AssertMarshals(t, testmarshal.JSONMarshaler, tc.Example, []byte(tc.JSON)))
   106  			}
   107  		})
   108  
   109  		t.Run("yaml", func(t *testing.T) {
   110  			for _, tc := range cases {
   111  				testmarshal.Require(t, testmarshal.AssertMarshals(t, testmarshal.YAMLMarshaler, tc.Example, []byte(tc.YAML)))
   112  			}
   113  		})
   114  	})
   115  }
   116  
   117  func TestTransformationOpEqual(t *testing.T) {
   118  	inputs := []struct {
   119  		a1       TransformationOp
   120  		a2       TransformationOp
   121  		expected bool
   122  	}{
   123  		{
   124  			a1:       TransformationOp{transformation.Absolute},
   125  			a2:       TransformationOp{transformation.Absolute},
   126  			expected: true,
   127  		},
   128  		{
   129  			a1:       TransformationOp{transformation.Absolute},
   130  			a2:       TransformationOp{transformation.PerSecond},
   131  			expected: false,
   132  		},
   133  	}
   134  
   135  	for _, input := range inputs {
   136  		require.Equal(t, input.expected, input.a1.Equal(input.a2))
   137  		require.Equal(t, input.expected, input.a2.Equal(input.a1))
   138  	}
   139  }
   140  
   141  func TestTransformationOpClone(t *testing.T) {
   142  	source := TransformationOp{transformation.Absolute}
   143  	clone := source.Clone()
   144  	require.Equal(t, source, clone)
   145  	clone.Type = transformation.PerSecond
   146  	require.Equal(t, transformation.Absolute, source.Type)
   147  }
   148  
   149  func TestPipelineString(t *testing.T) {
   150  	inputs := []struct {
   151  		p        Pipeline
   152  		expected string
   153  	}{
   154  		{
   155  			p: Pipeline{
   156  				operations: []OpUnion{
   157  					{
   158  						Type:        AggregationOpType,
   159  						Aggregation: AggregationOp{Type: aggregation.Last},
   160  					},
   161  					{
   162  						Type:           TransformationOpType,
   163  						Transformation: TransformationOp{Type: transformation.PerSecond},
   164  					},
   165  					{
   166  						Type: RollupOpType,
   167  						Rollup: RollupOp{
   168  							newName:       b("foo"),
   169  							Tags:          [][]byte{b("tag1"), b("tag2")},
   170  							AggregationID: aggregation.MustCompressTypes(aggregation.Sum),
   171  						},
   172  					},
   173  				},
   174  			},
   175  			expected: "{operations: [{aggregation: Last}, {transformation: PerSecond}, " +
   176  				"{rollup: {name: foo, type: 0, tags: [tag1, tag2], aggregation: Sum}}]}",
   177  		},
   178  		{
   179  			p: Pipeline{
   180  				operations: []OpUnion{
   181  					{
   182  						Type: OpType(10),
   183  					},
   184  				},
   185  			},
   186  			expected: "{operations: [{unknown op type: OpType(10)}]}",
   187  		},
   188  	}
   189  
   190  	for _, input := range inputs {
   191  		require.Equal(t, input.expected, input.p.String())
   192  	}
   193  }
   194  
   195  func TestTransformationOpToProto(t *testing.T) {
   196  	var pb pipelinepb.TransformationOp
   197  	require.NoError(t, testTransformationOp.ToProto(&pb))
   198  	require.Equal(t, testTransformationOpProto, pb)
   199  }
   200  
   201  func TestTransformationOpFromProto(t *testing.T) {
   202  	var res TransformationOp
   203  	require.NoError(t, res.FromProto(testTransformationOpProto))
   204  	require.Equal(t, testTransformationOp, res)
   205  }
   206  
   207  func TestTransformationOpFromProtoBadProto(t *testing.T) {
   208  	var res TransformationOp
   209  	require.Error(t, res.FromProto(testBadTransformationOpProto))
   210  }
   211  
   212  func TestTransformationOpRoundTrip(t *testing.T) {
   213  	var (
   214  		pb  pipelinepb.TransformationOp
   215  		res TransformationOp
   216  	)
   217  	require.NoError(t, testTransformationOp.ToProto(&pb))
   218  	require.NoError(t, res.FromProto(pb))
   219  	require.Equal(t, testTransformationOp, res)
   220  }
   221  
   222  func TestRollupOpEqual(t *testing.T) {
   223  	inputs := []struct {
   224  		a1       RollupOp
   225  		a2       RollupOp
   226  		expected bool
   227  	}{
   228  		{
   229  			a1:       RollupOp{Type: GroupByRollupType},
   230  			a2:       RollupOp{Type: GroupByRollupType},
   231  			expected: true,
   232  		},
   233  		{
   234  			a1:       RollupOp{Type: ExcludeByRollupType},
   235  			a2:       RollupOp{Type: ExcludeByRollupType},
   236  			expected: true,
   237  		},
   238  		{
   239  			a1:       RollupOp{Type: GroupByRollupType},
   240  			a2:       RollupOp{Type: ExcludeByRollupType},
   241  			expected: false,
   242  		},
   243  	}
   244  
   245  	for _, input := range inputs {
   246  		require.Equal(t, input.expected, input.a1.Equal(input.a2))
   247  		require.Equal(t, input.expected, input.a2.Equal(input.a1))
   248  	}
   249  }
   250  
   251  func TestRollupOpSameTransform(t *testing.T) {
   252  	rollupOp := RollupOp{
   253  		newName: b("foo"),
   254  		Tags:    bs("bar1", "bar2"),
   255  	}
   256  	inputs := []struct {
   257  		op     RollupOp
   258  		result bool
   259  	}{
   260  		{
   261  			op:     RollupOp{newName: b("foo"), Tags: bs("bar1", "bar2")},
   262  			result: true,
   263  		},
   264  		{
   265  			op:     RollupOp{newName: b("foo"), Tags: bs("bar2", "bar1")},
   266  			result: true,
   267  		},
   268  		{
   269  			op:     RollupOp{newName: b("foo"), Tags: bs("bar1")},
   270  			result: false,
   271  		},
   272  		{
   273  			op:     RollupOp{newName: b("foo"), Tags: bs("bar1", "bar2", "bar3")},
   274  			result: false,
   275  		},
   276  		{
   277  			op:     RollupOp{newName: b("foo"), Tags: bs("bar1", "bar3")},
   278  			result: false,
   279  		},
   280  		{
   281  			op:     RollupOp{newName: b("baz"), Tags: bs("bar1", "bar2")},
   282  			result: false,
   283  		},
   284  		{
   285  			op:     RollupOp{newName: b("baz"), Tags: bs("bar2", "bar1")},
   286  			result: false,
   287  		},
   288  	}
   289  	for _, input := range inputs {
   290  		require.Equal(t, input.result, rollupOp.SameTransform(input.op))
   291  	}
   292  }
   293  
   294  func TestOpUnionMarshalJSON(t *testing.T) {
   295  	inputs := []struct {
   296  		op       OpUnion
   297  		expected string
   298  	}{
   299  		{
   300  			op: OpUnion{
   301  				Type:        AggregationOpType,
   302  				Aggregation: AggregationOp{Type: aggregation.Sum},
   303  			},
   304  			expected: `{"aggregation":"Sum"}`,
   305  		},
   306  		{
   307  			op: OpUnion{
   308  				Type:           TransformationOpType,
   309  				Transformation: TransformationOp{Type: transformation.PerSecond},
   310  			},
   311  			expected: `{"transformation":"PerSecond"}`,
   312  		},
   313  		{
   314  			op: OpUnion{
   315  				Type: RollupOpType,
   316  				Rollup: RollupOp{
   317  					Type:          ExcludeByRollupType,
   318  					newName:       b("testRollup"),
   319  					Tags:          bs("tag1", "tag2"),
   320  					AggregationID: aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   321  				},
   322  			},
   323  			expected: `{"rollup":{"type":1,"newName":"testRollup","tags":["tag1","tag2"],"aggregation":["Min","Max"]}}`,
   324  		},
   325  		{
   326  			op: OpUnion{
   327  				Type: RollupOpType,
   328  				Rollup: RollupOp{
   329  					newName:       b("testRollup"),
   330  					Tags:          bs("tag1", "tag2"),
   331  					AggregationID: aggregation.DefaultID,
   332  				},
   333  			},
   334  			expected: `{"rollup":{"type":0,"newName":"testRollup","tags":["tag1","tag2"],"aggregation":null}}`,
   335  		},
   336  	}
   337  
   338  	for _, input := range inputs {
   339  		b, err := json.Marshal(input.op)
   340  		require.NoError(t, err)
   341  		require.Equal(t, input.expected, string(b))
   342  	}
   343  }
   344  
   345  func TestOpUnionMarshalJSONError(t *testing.T) {
   346  	op := OpUnion{}
   347  	_, err := json.Marshal(op)
   348  	require.Error(t, err)
   349  }
   350  
   351  func TestOpUnionMarshalRoundtrip(t *testing.T) {
   352  	ops := []OpUnion{
   353  		{
   354  			Type:        AggregationOpType,
   355  			Aggregation: AggregationOp{Type: aggregation.Sum},
   356  		},
   357  		{
   358  			Type:           TransformationOpType,
   359  			Transformation: TransformationOp{Type: transformation.PerSecond},
   360  		},
   361  		{
   362  			Type: RollupOpType,
   363  			Rollup: RollupOp{
   364  				newName:       b("testRollup"),
   365  				Tags:          bs("tag1", "tag2"),
   366  				AggregationID: aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   367  			},
   368  		},
   369  		{
   370  			Type: RollupOpType,
   371  			Rollup: RollupOp{
   372  				newName:       b("testRollup"),
   373  				Tags:          bs("tag1", "tag2"),
   374  				AggregationID: aggregation.DefaultID,
   375  			},
   376  		},
   377  	}
   378  
   379  	testmarshal.TestMarshalersRoundtrip(t, ops, []testmarshal.Marshaler{testmarshal.JSONMarshaler, testmarshal.YAMLMarshaler})
   380  }
   381  
   382  func TestPipelineMarshalJSON(t *testing.T) {
   383  	p := NewPipeline([]OpUnion{
   384  		{
   385  			Type:        AggregationOpType,
   386  			Aggregation: AggregationOp{Type: aggregation.Sum},
   387  		},
   388  		{
   389  			Type:           TransformationOpType,
   390  			Transformation: TransformationOp{Type: transformation.PerSecond},
   391  		},
   392  		{
   393  			Type: RollupOpType,
   394  			Rollup: RollupOp{
   395  				newName:       b("testRollup"),
   396  				Tags:          bs("tag1", "tag2"),
   397  				AggregationID: aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   398  			},
   399  		},
   400  		{
   401  			Type: RollupOpType,
   402  			Rollup: RollupOp{
   403  				newName:       b("testRollup"),
   404  				Tags:          bs("tag1", "tag2"),
   405  				AggregationID: aggregation.DefaultID,
   406  			},
   407  		},
   408  	})
   409  	b, err := json.Marshal(p)
   410  	require.NoError(t, err)
   411  
   412  	expected := `[{"aggregation":"Sum"},` +
   413  		`{"transformation":"PerSecond"},` +
   414  		`{"rollup":{"type":0,"newName":"testRollup","tags":["tag1","tag2"],"aggregation":["Min","Max"]}},` +
   415  		`{"rollup":{"type":0,"newName":"testRollup","tags":["tag1","tag2"],"aggregation":null}}]`
   416  	require.Equal(t, expected, string(b))
   417  }
   418  
   419  func TestPipelineMarshalRoundtrip(t *testing.T) {
   420  	p := NewPipeline([]OpUnion{
   421  		{
   422  			Type:        AggregationOpType,
   423  			Aggregation: AggregationOp{Type: aggregation.Sum},
   424  		},
   425  		{
   426  			Type:           TransformationOpType,
   427  			Transformation: TransformationOp{Type: transformation.PerSecond},
   428  		},
   429  		{
   430  			Type: RollupOpType,
   431  			Rollup: RollupOp{
   432  				newName:       b("testRollup"),
   433  				Tags:          bs("tag1", "tag2"),
   434  				AggregationID: aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   435  			},
   436  		},
   437  		{
   438  			Type: RollupOpType,
   439  			Rollup: RollupOp{
   440  				newName:       b("testRollup"),
   441  				Tags:          bs("tag1", "tag2"),
   442  				AggregationID: aggregation.DefaultID,
   443  			},
   444  		},
   445  	})
   446  
   447  	testmarshal.TestMarshalersRoundtrip(t, []Pipeline{p}, []testmarshal.Marshaler{testmarshal.YAMLMarshaler, testmarshal.JSONMarshaler})
   448  }
   449  
   450  func TestPipelineUnmarshalYAML(t *testing.T) {
   451  	input := `
   452  - aggregation: Sum
   453  - transformation: PerSecond
   454  - rollup:
   455      newName: testRollup
   456      tags:
   457        - tag1
   458        - tag2
   459      aggregation:
   460        - Min
   461        - Max
   462  - rollup:
   463      newName: testRollup2
   464      tags:
   465        - tag3
   466        - tag4
   467  `
   468  
   469  	var pipeline Pipeline
   470  	require.NoError(t, yaml.Unmarshal([]byte(input), &pipeline))
   471  
   472  	expected := NewPipeline([]OpUnion{
   473  		{
   474  			Type:        AggregationOpType,
   475  			Aggregation: AggregationOp{Type: aggregation.Sum},
   476  		},
   477  		{
   478  			Type:           TransformationOpType,
   479  			Transformation: TransformationOp{Type: transformation.PerSecond},
   480  		},
   481  		{
   482  			Type: RollupOpType,
   483  			Rollup: RollupOp{
   484  				newName:       b("testRollup"),
   485  				Tags:          bs("tag1", "tag2"),
   486  				AggregationID: aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   487  			},
   488  		},
   489  		{
   490  			Type: RollupOpType,
   491  			Rollup: RollupOp{
   492  				newName:       b("testRollup2"),
   493  				Tags:          bs("tag3", "tag4"),
   494  				AggregationID: aggregation.DefaultID,
   495  			},
   496  		},
   497  	})
   498  	require.Equal(t, expected, pipeline)
   499  }
   500  
   501  func b(v string) []byte       { return []byte(v) }
   502  func bs(v ...string) [][]byte { return bytes.ArraysFromStringArray(v) }