github.com/GuanceCloud/cliutils@v1.1.21/point/check_test.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package point
     7  
     8  import (
     9  	"fmt"
    10  	"math"
    11  	"sort"
    12  	"testing"
    13  	T "testing"
    14  
    15  	"github.com/GuanceCloud/cliutils"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func TestCheckMeasurement(t *testing.T) {
    21  	cases := []struct {
    22  		name,
    23  		measurement,
    24  		expect string
    25  		opts []Option
    26  	}{
    27  		{
    28  			name:        "n-len",
    29  			measurement: "abc-def",
    30  			opts: []Option{
    31  				WithMaxMeasurementLen(3),
    32  			},
    33  			expect: "abc",
    34  		},
    35  
    36  		{
    37  			name:        "no-limit",
    38  			measurement: "abc-def",
    39  			expect:      "abc-def",
    40  		},
    41  
    42  		{
    43  			name:        "empty-measurement",
    44  			measurement: "",
    45  			expect:      DefaultMeasurementName,
    46  		},
    47  
    48  		{
    49  			name:        "empty-measurement-trim",
    50  			measurement: "",
    51  			opts: []Option{
    52  				WithMaxMeasurementLen(3),
    53  			},
    54  			expect: DefaultMeasurementName[:3],
    55  		},
    56  
    57  		{
    58  			name:        "test-utf8-measurement",
    59  			measurement: "δΈ­ζ–‡πŸ‘",
    60  			expect:      "δΈ­ζ–‡πŸ‘",
    61  		},
    62  
    63  		{
    64  			name:        "test-utf8-measurement-trim",
    65  			measurement: "δΈ­ζ–‡πŸ‘",
    66  			opts: []Option{
    67  				WithMaxMeasurementLen(3),
    68  			},
    69  			expect: string([]byte("δΈ­ζ–‡πŸ‘")[:3]),
    70  		},
    71  	}
    72  
    73  	for _, tc := range cases {
    74  		t.Run(tc.name, func(t *T.T) {
    75  			cfg := GetCfg()
    76  			defer PutCfg(cfg)
    77  			for _, opt := range tc.opts {
    78  				opt(cfg)
    79  			}
    80  
    81  			c := checker{cfg: cfg}
    82  			m := c.checkMeasurement(tc.measurement)
    83  			assert.Equal(t, tc.expect, m)
    84  		})
    85  	}
    86  }
    87  
    88  func TestCheckPoints(t *T.T) {
    89  	t.Run("string", func(t *T.T) {
    90  		var kvs KVs
    91  		kvs = kvs.Add("f1", 1.23, false, false)
    92  		kvs = kvs.Add("str", "hello", false, false)
    93  		kvs = kvs.Add("u64", uint64(math.MaxUint64), false, false)
    94  
    95  		pt := NewPointV2("m1", kvs, WithPrecheck(false))
    96  		pts := CheckPoints([]*Point{pt}, WithStrField(false))
    97  		assert.Len(t, pts, 1)
    98  		assert.Nil(t, pts[0].Get("str"))
    99  		assert.Equal(t, 1.23, pts[0].Get("f1"))
   100  	})
   101  
   102  	t.Run("u64", func(t *T.T) {
   103  		var kvs KVs
   104  		kvs = kvs.Add("f1", 1.23, false, false)
   105  		kvs = kvs.Add("str", "hello", false, false)
   106  		kvs = kvs.Add("u64", uint64(math.MaxUint64), false, false)
   107  
   108  		pt := NewPointV2("m1", kvs, WithPrecheck(false))
   109  		pts := CheckPoints([]*Point{pt}, WithU64Field(false))
   110  		assert.Len(t, pts, 1)
   111  		assert.Nil(t, pts[0].Get("u64"))
   112  		assert.Equal(t, "hello", pts[0].Get("str"))
   113  	})
   114  
   115  	t.Run("dot-in-key", func(t *T.T) {
   116  		var kvs KVs
   117  		kvs = kvs.Add("f.1", 1.23, false, false)
   118  		kvs = kvs.Add("u64", uint64(math.MaxUint64), false, false)
   119  
   120  		pt := NewPointV2("m1", kvs, WithPrecheck(false))
   121  
   122  		pts := CheckPoints([]*Point{pt}, WithDotInKey(false))
   123  		assert.Len(t, pts, 1)
   124  		assert.Equal(t, uint64(math.MaxUint64), pts[0].Get("u64"))
   125  		assert.Equal(t, 1.23, pts[0].Get("f_1"))
   126  		assert.Len(t, pts[0].Warns(), 1)
   127  
   128  		t.Logf("point: %s", pts[0].Pretty())
   129  	})
   130  }
   131  
   132  func TestCheckTags(t *T.T) {
   133  	cases := []struct {
   134  		name   string
   135  		t      map[string]string
   136  		expect KVs
   137  		warns  int
   138  		opts   []Option
   139  	}{
   140  		{
   141  			name: "disable-tag",
   142  			t: map[string]string{
   143  				"t1": "123456",
   144  				"t2": "23456",
   145  			},
   146  			opts: []Option{
   147  				WithDisabledKeys(NewTagKey(`t1`, "")),
   148  			},
   149  			warns: 1,
   150  			expect: NewTags(
   151  				map[string]string{
   152  					"t2": "23456",
   153  				}),
   154  		},
   155  
   156  		// { TODO
   157  		//	name: `exceed-tag-kv-compose`,
   158  		//	t: map[string]string{
   159  		//		"t1": "12345",
   160  		//		"t2": "abcde",
   161  		//	},
   162  		//	opts: []Option{
   163  		//		WithMaxKVComposeLen(10),
   164  		//		WithTime(time.Unix(0, 123)),
   165  		//	},
   166  
   167  		//	warns: 1,
   168  		//	expect: NewTags(map[string]string{
   169  		//		"t1": "12345",
   170  		//	}),
   171  		// },
   172  
   173  		{
   174  			name: `tag-kv-compose-limit-0`,
   175  			t: map[string]string{
   176  				"t1": "12345",
   177  				"t2": "abcde",
   178  			},
   179  			opts: []Option{
   180  				WithMaxMeasurementLen(0), // do nothing
   181  			},
   182  
   183  			expect: NewTags(map[string]string{
   184  				"t1": "12345",
   185  				"t2": "abcde",
   186  			}),
   187  		},
   188  	}
   189  
   190  	for _, tc := range cases {
   191  		t.Run(tc.name, func(t *T.T) {
   192  			cfg := GetCfg()
   193  			defer PutCfg(cfg)
   194  
   195  			for _, opt := range tc.opts {
   196  				opt(cfg)
   197  			}
   198  
   199  			c := checker{cfg: cfg}
   200  			kvs := c.checkKVs(NewTags(tc.t))
   201  
   202  			assert.Equal(t, tc.warns, len(c.warns), "got warns: %v, kvs: %s", c.warns, kvs.Pretty())
   203  
   204  			eopt := eqopt{}
   205  			if tc.expect != nil {
   206  				eq, r := eopt.kvsEq(tc.expect, kvs)
   207  				assert.True(t, eq, "reason: %s", r)
   208  			}
   209  		})
   210  	}
   211  
   212  	t.Run("key-updated-but-conflict", func(t *T.T) {
   213  		///////////////////////
   214  		// dot in tag key
   215  		var kvs KVs
   216  		kvs = kvs.AddV2("f.1", "some string", false, WithKVTagSet(true))
   217  		kvs = kvs.AddV2("f_1", 1.23, false)
   218  
   219  		pt := NewPointV2("m", kvs, WithDotInKey(false))
   220  
   221  		assert.Lenf(t, pt.pt.Fields, 1, "pt: %s", pt.Pretty())
   222  		// drop tag
   223  		assert.Len(t, pt.pt.Fields, 1)
   224  		assert.Equal(t, 1.23, pt.Get(`f_1`).(float64))
   225  		t.Logf("pt: %s", pt.Pretty())
   226  
   227  		///////////////////////
   228  		// too long tag key
   229  		kvs = kvs[:0]
   230  		kvs = kvs.AddV2("f111", "some string", false, WithKVTagSet(true))
   231  		kvs = kvs.AddV2("f1", 1.23, false)
   232  		pt = NewPointV2("m", kvs, WithMaxTagKeyLen(2))
   233  
   234  		assert.Len(t, pt.pt.Fields, 1)
   235  		// drop tag
   236  		assert.Equal(t, 1.23, pt.Get(`f1`).(float64))
   237  		t.Logf("pt: %s", pt.Pretty())
   238  
   239  		///////////////////////
   240  		// too long field key
   241  		kvs = kvs[:0]
   242  		kvs = kvs.AddV2("f1", 1.23, false)
   243  		kvs = kvs.AddV2("f111", "some string", false)
   244  		pt = NewPointV2("m", kvs, WithMaxFieldKeyLen(2))
   245  
   246  		assert.Len(t, pt.pt.Fields, 1)
   247  		// drop field
   248  		assert.Equal(t, 1.23, pt.Get(`f1`).(float64))
   249  		t.Logf("pt: %s", pt.Pretty())
   250  
   251  		///////////////////////
   252  		// conflict on updated-key
   253  		kvs = kvs[:0]
   254  		kvs = kvs.AddV2("f.1", 1.23, false)            // f.1 => f_1
   255  		kvs = kvs.AddV2("f_111", "some string", false) // f_111 => f_1: conflict
   256  		pt = NewPointV2("m", kvs, WithMaxFieldKeyLen(3), WithDotInKey(false))
   257  
   258  		assert.Len(t, pt.pt.Fields, 1)
   259  		// drop field
   260  		assert.Equal(t, 1.23, pt.Get(`f_1`).(float64))
   261  		t.Logf("pt: %s", pt.Pretty())
   262  	})
   263  }
   264  
   265  func TestCheckFields(t *T.T) {
   266  	cases := []struct {
   267  		name   string
   268  		f      map[string]interface{}
   269  		expect map[string]interface{}
   270  		warns  int
   271  		opts   []Option
   272  	}{
   273  		{
   274  			name: "exceed-max-field-len",
   275  			f: map[string]interface{}{
   276  				"f1": "123456",
   277  			},
   278  			opts:  []Option{WithMaxFieldValLen(1)},
   279  			warns: 1,
   280  			expect: map[string]interface{}{
   281  				"f1": "1",
   282  			},
   283  		},
   284  
   285  		{
   286  			name: "exceed-max-field-count",
   287  			f: map[string]interface{}{
   288  				"f1": "aaaaaa1",
   289  				"f2": "aaaaaa2",
   290  				"f3": "aaaaaa3",
   291  				"f4": "aaaaaa4",
   292  				"f5": "aaaaaa5",
   293  				"f6": "aaaaaa6",
   294  				"f7": "aaaaaa7",
   295  				"f8": "aaaaaa8",
   296  				"f9": "aaaaaa9",
   297  				"f0": "aaaaaa0",
   298  			},
   299  			opts:  []Option{WithMaxFields(1), WithKeySorted(true)},
   300  			warns: 1,
   301  			expect: map[string]interface{}{
   302  				"f0": "aaaaaa0",
   303  			},
   304  		},
   305  
   306  		{
   307  			name: "exceed-max-field-key-len",
   308  			f: map[string]interface{}{
   309  				"a1": "123456",
   310  				"b":  "abc123",
   311  			},
   312  			opts:  []Option{WithMaxFieldKeyLen(1)},
   313  			warns: 1,
   314  			expect: map[string]interface{}{
   315  				"a": "123456", // key truncated
   316  				"b": "abc123",
   317  			},
   318  		},
   319  
   320  		{
   321  			name: "drop-metric-string-field",
   322  			f: map[string]interface{}{
   323  				"a": 123456,
   324  				"b": "abc123", // dropped
   325  			},
   326  			opts:  []Option{WithStrField(false)},
   327  			warns: 1,
   328  			expect: map[string]interface{}{
   329  				"a": int64(123456),
   330  			},
   331  		},
   332  
   333  		{
   334  			name: "invalid-field-type",
   335  			f: map[string]interface{}{
   336  				"b": struct{}{},
   337  			},
   338  			warns: 1,
   339  		},
   340  
   341  		{
   342  			name: "nil-field",
   343  			f: map[string]interface{}{
   344  				"a": nil, // set value to nil
   345  				"b": 123,
   346  				"c": struct{}{}, // ignored
   347  			},
   348  			warns: 2,
   349  			expect: map[string]interface{}{
   350  				"b": int64(123),
   351  				"a": nil,
   352  				"c": nil,
   353  			},
   354  		},
   355  
   356  		{
   357  			name: "exceed-max-int64-under-influxdb1.x",
   358  			f: map[string]interface{}{
   359  				"b": uint64(math.MaxInt64) + 1, // exceed max-int64
   360  			},
   361  			opts:  DefaultMetricOptionsForInflux1X(),
   362  			warns: 1,
   363  		},
   364  
   365  		{
   366  			name: "exceed-max-int64",
   367  			f: map[string]interface{}{
   368  				"a": uint64(math.MaxInt64) + 1, // exceed max-int64, drop the key under non-strict mode
   369  				"b": "abc",
   370  			},
   371  
   372  			expect: map[string]interface{}{
   373  				"a": uint64(math.MaxInt64) + 1,
   374  				"b": "abc",
   375  			},
   376  		},
   377  
   378  		{
   379  			name: "small-uint64",
   380  			f: map[string]interface{}{
   381  				"a": uint64(12345),
   382  			},
   383  			expect: map[string]interface{}{
   384  				"a": uint64(12345),
   385  			},
   386  		},
   387  
   388  		{
   389  			name:   "no-field",
   390  			expect: nil,
   391  			warns:  0,
   392  		},
   393  
   394  		{
   395  			name: "dot-in-key",
   396  			f: map[string]interface{}{
   397  				"a.b": 12345,
   398  				"c":   "12345",
   399  			},
   400  			opts:  []Option{WithDotInKey(false)},
   401  			warns: 1,
   402  			expect: map[string]interface{}{
   403  				"a_b": int64(12345),
   404  				"c":   "12345",
   405  			},
   406  		},
   407  
   408  		{
   409  			name: "disabled-field",
   410  			f: map[string]interface{}{
   411  				"a": 12345,
   412  				"b": "12345",
   413  			},
   414  			warns: 1,
   415  			opts:  []Option{WithDisabledKeys(NewKey("a", I))},
   416  			expect: map[string]interface{}{
   417  				"b": "12345",
   418  			},
   419  		},
   420  
   421  		{
   422  			name: "valid-fields",
   423  			f: map[string]interface{}{
   424  				"small-uint64": uint64(12345),
   425  				"int8":         int8(1),
   426  				"int":          int(1),
   427  				"int16":        int16(12345),
   428  				"int32":        int32(1234567),
   429  				"int64":        int64(123456789),
   430  				"uint8":        uint8(1),
   431  				"uint":         uint(1),
   432  				"uint16":       uint16(12345),
   433  				"uint32":       uint32(1234567),
   434  				"uint64":       uint64(12345678),
   435  				"float32":      float32(1.234),
   436  				"float64":      float64(1.234),
   437  				"str":          "abc",
   438  			},
   439  
   440  			expect: map[string]interface{}{
   441  				"small-uint64": uint64(12345),
   442  				"int8":         int64(1),
   443  				"int":          int64(1),
   444  				"int16":        int64(12345),
   445  				"int32":        int64(1234567),
   446  				"int64":        int64(123456789),
   447  				"uint":         uint64(1),
   448  				"uint8":        uint64(1),
   449  				"uint16":       uint64(12345),
   450  				"uint32":       uint64(1234567),
   451  				"uint64":       uint64(12345678),
   452  				"float32":      float32(1.234),
   453  				"float64":      float64(1.234),
   454  				"str":          "abc",
   455  			},
   456  		},
   457  	}
   458  
   459  	for _, tc := range cases {
   460  		t.Run(tc.name, func(t *T.T) {
   461  			cfg := GetCfg()
   462  			defer PutCfg(cfg)
   463  
   464  			for _, opt := range tc.opts {
   465  				opt(cfg)
   466  			}
   467  
   468  			t.Logf("cfg: %+#v", cfg)
   469  
   470  			c := checker{cfg: cfg}
   471  
   472  			kvs := NewKVs(tc.f)
   473  			expect := NewKVs(tc.expect)
   474  
   475  			if cfg.keySorted {
   476  				sort.Sort(kvs)
   477  				sort.Sort(expect)
   478  			}
   479  
   480  			kvs = c.checkKVs(kvs)
   481  			require.Equal(t, tc.warns, len(c.warns), "got pt %s", kvs.Pretty())
   482  
   483  			eopt := eqopt{}
   484  			if tc.expect != nil {
   485  				eq, _ := eopt.kvsEq(expect, kvs)
   486  				assert.True(t, eq, "expect:\n%s\ngot:\n%s", expect.Pretty(), kvs.Pretty())
   487  			}
   488  		})
   489  	}
   490  }
   491  
   492  func TestAdjustKV(t *T.T) {
   493  	cases := []struct {
   494  		name, x, y string
   495  	}{
   496  		{
   497  			name: "x-with-trailling-backslash",
   498  			x:    "x\\",
   499  			y:    "x",
   500  		},
   501  
   502  		{
   503  			name: "x-with-line-break",
   504  			x: `
   505  x
   506  def`,
   507  			y: " x def",
   508  		},
   509  	}
   510  
   511  	for _, tc := range cases {
   512  		t.Run(tc.name, func(t *T.T) {
   513  			assert.Equal(t, tc.y, adjustKV(tc.x))
   514  		})
   515  	}
   516  }
   517  
   518  func TestRequiredKV(t *T.T) {
   519  	t.Run(`add`, func(t *T.T) {
   520  		pt := NewPointV2(`abc`, NewKVs(map[string]any{"f1": 123}),
   521  			WithRequiredKeys(NewKey(`rk`, I, 1024)))
   522  		assert.Equal(t, int64(1024), pt.Get(`rk`))
   523  	})
   524  }
   525  
   526  func BenchmarkCheck(b *T.B) {
   527  	__shortKey := cliutils.CreateRandomString(10)
   528  	__shortVal := cliutils.CreateRandomString(128)
   529  
   530  	cases := []struct {
   531  		name string
   532  		m    string
   533  		t    map[string]string
   534  		f    map[string]interface{}
   535  		opts []Option
   536  	}{
   537  		{
   538  			name: "3-tags-4-field",
   539  			m:    "not-set",
   540  			t: map[string]string{
   541  				__shortKey: __shortVal,
   542  			},
   543  			f: map[string]interface{}{
   544  				"f1": 123,
   545  				"f2": 123.0,
   546  				"f3": __shortVal,
   547  				"f4": false,
   548  			},
   549  		},
   550  
   551  		{
   552  			name: "3-tags-4-field-on-string-metric",
   553  			m:    "not-set",
   554  			t: map[string]string{
   555  				__shortKey: __shortVal,
   556  			},
   557  			f: map[string]interface{}{
   558  				"f1": 123,
   559  				"f2": 123.0,
   560  				"f3": __shortVal,
   561  				"f4": false,
   562  			},
   563  			opts: DefaultMetricOptions(),
   564  		},
   565  
   566  		{
   567  			name: "3-tags-4-field-on-disabled-tag-and-field",
   568  			m:    "not-set",
   569  			t: map[string]string{
   570  				__shortKey: __shortVal,
   571  				"source":   "should-be-dropped",
   572  			},
   573  			f: map[string]interface{}{
   574  				"f1":     123,
   575  				"f2":     123.0,
   576  				"f3":     __shortVal,
   577  				"f4":     false,
   578  				"source": "should-be-dropped",
   579  			},
   580  			opts: DefaultLoggingOptions(),
   581  		},
   582  
   583  		{
   584  			name: "100-tags-300-field-on-warnning-tags-fields",
   585  			m:    "not-set",
   586  			t: func() map[string]string {
   587  				x := map[string]string{}
   588  				for i := 0; i < 100; i++ {
   589  					switch i % 3 {
   590  					case 0: // normal
   591  						x[fmt.Sprintf("%s-%d", __shortKey, i)] = cliutils.CreateRandomString(32)
   592  					case 1: // key contains `\n'
   593  						x[fmt.Sprintf("%s-\n%d", __shortKey, i)] = cliutils.CreateRandomString(32)
   594  					case 2: // key suffix with `\'
   595  						x[fmt.Sprintf("%s-%d\\", __shortKey, i)] = cliutils.CreateRandomString(32)
   596  					}
   597  				}
   598  				return x
   599  			}(),
   600  			f: func() map[string]interface{} {
   601  				x := map[string]interface{}{}
   602  				for i := 0; i < 100; i++ {
   603  					switch i % 3 {
   604  					case 0: // exceed max int64
   605  						x[fmt.Sprintf("%s-%d", __shortKey, i)] = uint64(math.MaxInt64) + 1
   606  					case 1: // exceed max field value length
   607  						x[fmt.Sprintf("%s-%d", __shortKey, i)] = cliutils.CreateRandomString(1024 + 1)
   608  					case 2: // nil
   609  						x[fmt.Sprintf("%s-%d", __shortKey, i)] = nil
   610  					}
   611  				}
   612  				return x
   613  			}(),
   614  			opts: []Option{
   615  				WithMaxFieldValLen(1024),
   616  				WithMaxFields(299), // < 300
   617  			},
   618  		},
   619  	}
   620  
   621  	for _, tc := range cases {
   622  		pt, err := NewPoint(tc.m, tc.t, tc.f, tc.opts...)
   623  		assert.NoError(b, err)
   624  
   625  		cfg := GetCfg()
   626  		defer PutCfg(cfg)
   627  
   628  		for _, opt := range tc.opts {
   629  			opt(cfg)
   630  		}
   631  		c := checker{cfg: cfg}
   632  
   633  		b.ResetTimer()
   634  		b.Run(tc.name, func(b *T.B) {
   635  			for i := 0; i < b.N; i++ {
   636  				c.check(pt)
   637  			}
   638  		})
   639  	}
   640  }
   641  
   642  func BenchmarkCheckPoints(b *T.B) {
   643  	b.Run("check-rand-pts", func(b *T.B) {
   644  		r := NewRander()
   645  		pts := r.Rand(1000)
   646  
   647  		b.ResetTimer()
   648  		for i := 0; i < b.N; i++ {
   649  			CheckPoints(pts)
   650  		}
   651  	})
   652  
   653  	b.Run("check-pts-without-str-field", func(b *T.B) {
   654  		r := NewRander()
   655  		pts := r.Rand(1000)
   656  
   657  		b.ResetTimer()
   658  		for i := 0; i < b.N; i++ {
   659  			CheckPoints(pts, WithStrField(false))
   660  		}
   661  	})
   662  
   663  	b.Run("check-pts-without-u64-field", func(b *T.B) {
   664  		r := NewRander()
   665  		pts := r.Rand(1000)
   666  
   667  		b.ResetTimer()
   668  		for i := 0; i < b.N; i++ {
   669  			CheckPoints(pts, WithU64Field(false))
   670  		}
   671  	})
   672  }