github.com/GuanceCloud/cliutils@v1.1.21/lineproto/lineproto_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 lineproto
     7  
     8  import (
     9  	"fmt"
    10  	"math"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/GuanceCloud/cliutils"
    15  	"github.com/GuanceCloud/cliutils/testutil"
    16  	"github.com/influxdata/influxdb1-client/models"
    17  	influxdb "github.com/influxdata/influxdb1-client/v2"
    18  )
    19  
    20  func parseLineProto(t *testing.T, data []byte, precision string) (models.Points, error) {
    21  	t.Helper()
    22  
    23  	if len(data) == 0 {
    24  		return nil, fmt.Errorf("empty data")
    25  	}
    26  
    27  	return models.ParsePointsWithPrecision(data, time.Now().UTC(), precision)
    28  }
    29  
    30  func TestAdjustTags(t *testing.T) {
    31  	cases := []struct {
    32  		tags map[string]string
    33  	}{}
    34  
    35  	_ = cases
    36  }
    37  
    38  func TestAdjustKV(t *testing.T) {
    39  	cases := []struct {
    40  		name, x, y string
    41  	}{
    42  		{
    43  			name: "x with trailling backslash",
    44  			x:    "x\\",
    45  			y:    "x",
    46  		},
    47  
    48  		{
    49  			name: "x with line break",
    50  			x: `
    51  x
    52  def`,
    53  			y: " x def",
    54  		},
    55  	}
    56  
    57  	for _, tc := range cases {
    58  		t.Run(tc.name, func(t *testing.T) {
    59  			testutil.Equals(t, tc.y, adjustKV(tc.x))
    60  		})
    61  	}
    62  }
    63  
    64  func TestMakeLineProtoPointWithWarnings(t *testing.T) {
    65  	cases := []struct {
    66  		tname     string // test name
    67  		name      string
    68  		tags      map[string]string
    69  		fields    map[string]interface{}
    70  		ts        time.Time
    71  		opt       *Option
    72  		expect    string
    73  		warnTypes []string
    74  		fail      bool
    75  	}{
    76  		{
    77  			tname: `64k-field-value-length`,
    78  			name:  "some",
    79  			fields: map[string]interface{}{
    80  				"key": func() string {
    81  					const str = "1234567890"
    82  					var out string
    83  					for {
    84  						out += str
    85  						if len(out) > 64*1024 {
    86  							break
    87  						}
    88  					}
    89  					return out
    90  				}(),
    91  			},
    92  			opt: func() *Option {
    93  				opt := NewDefaultOption()
    94  				opt.MaxFieldValueLen = 0
    95  				opt.Time = time.Unix(0, 123)
    96  				return opt
    97  			}(),
    98  
    99  			expect: fmt.Sprintf(`some key="%s" 123`, func() string {
   100  				const str = "1234567890"
   101  				var out string
   102  				for {
   103  					out += str
   104  					if len(out) > 64*1024 {
   105  						break
   106  					}
   107  				}
   108  				return out
   109  			}()),
   110  		},
   111  
   112  		{
   113  			tname:  `max-field-value-length`,
   114  			name:   "some",
   115  			fields: map[string]interface{}{"key": "too-long-field-value-123"},
   116  			opt: func() *Option {
   117  				opt := NewDefaultOption()
   118  				opt.MaxFieldValueLen = 2
   119  				opt.Time = time.Unix(0, 123)
   120  				return opt
   121  			}(),
   122  			expect:    "some key=\"to\" 123",
   123  			warnTypes: []string{WarnMaxFieldValueLen},
   124  		},
   125  
   126  		{
   127  			tname:  `max-field-key-length`,
   128  			name:   "some",
   129  			fields: map[string]interface{}{"too-long-field-key": "123"},
   130  			opt: func() *Option {
   131  				opt := NewDefaultOption()
   132  				opt.MaxFieldKeyLen = 2
   133  				opt.Time = time.Unix(0, 123)
   134  				return opt
   135  			}(),
   136  			expect:    "some to=\"123\" 123",
   137  			warnTypes: []string{WarnMaxFieldKeyLen},
   138  		},
   139  
   140  		{
   141  			tname:  `max-tag-value-length`,
   142  			name:   "some",
   143  			fields: map[string]interface{}{"f1": 1},
   144  			tags:   map[string]string{"key": "too-long-tag-value-123"},
   145  			opt: func() *Option {
   146  				opt := NewDefaultOption()
   147  				opt.MaxTagValueLen = 2
   148  				opt.Time = time.Unix(0, 123)
   149  				return opt
   150  			}(),
   151  			warnTypes: []string{WarnMaxTagValueLen},
   152  			expect:    "some,key=to f1=1i 123",
   153  		},
   154  		{
   155  			tname:  `disable-string-field`,
   156  			name:   "some",
   157  			fields: map[string]interface{}{"f1": 1, "f2": "this is a string"},
   158  			tags:   map[string]string{"key": "string"},
   159  			opt: func() *Option {
   160  				opt := NewDefaultOption()
   161  				opt.DisableStringField = true
   162  				opt.Time = time.Unix(0, 123)
   163  				return opt
   164  			}(),
   165  			warnTypes: []string{WarnInvalidFieldValueType},
   166  			expect:    "some,key=string f1=1i 123",
   167  		},
   168  
   169  		{
   170  			tname:  `max tag key length`,
   171  			name:   "some",
   172  			fields: map[string]interface{}{"f1": 1},
   173  			tags:   map[string]string{"too-long-tag-key": "123"},
   174  			opt: func() *Option {
   175  				opt := NewDefaultOption()
   176  				opt.MaxTagKeyLen = 2
   177  				opt.Time = time.Unix(0, 123)
   178  				return opt
   179  			}(),
   180  			warnTypes: []string{WarnMaxTagKeyLen},
   181  			expect:    "some,to=123 f1=1i 123",
   182  		},
   183  
   184  		{
   185  			tname:  `empty measurement name`,
   186  			name:   "", // empty
   187  			fields: map[string]interface{}{"f.1": 1, "f2": uint64(32)},
   188  			tags:   map[string]string{"t.1": "abc", "t2": "32"},
   189  
   190  			opt: func() *Option {
   191  				opt := NewDefaultOption()
   192  				opt.Time = time.Unix(0, 123)
   193  				opt.EnablePointInKey = true
   194  				return opt
   195  			}(),
   196  
   197  			fail: true,
   198  		},
   199  
   200  		{
   201  			tname:  `enable point in metric point`,
   202  			name:   "abc",
   203  			fields: map[string]interface{}{"f.1": 1, "f2": uint64(32)},
   204  			tags:   map[string]string{"t.1": "abc", "t2": "32"},
   205  
   206  			opt: func() *Option {
   207  				opt := NewDefaultOption()
   208  				opt.Time = time.Unix(0, 123)
   209  				opt.EnablePointInKey = true
   210  				return opt
   211  			}(),
   212  
   213  			expect: "abc,t.1=abc,t2=32 f.1=1i,f2=32i 123",
   214  		},
   215  
   216  		{
   217  			tname:  `enable point in metric point`,
   218  			name:   "abc",
   219  			fields: map[string]interface{}{"f.1": 1, "f2": uint64(32)},
   220  			tags:   map[string]string{"t1": "abc", "t2": "32"},
   221  
   222  			opt: func() *Option {
   223  				opt := NewDefaultOption()
   224  				opt.Time = time.Unix(0, 123)
   225  				opt.EnablePointInKey = true
   226  				return opt
   227  			}(),
   228  			expect: "abc,t1=abc,t2=32 f.1=1i,f2=32i 123",
   229  		},
   230  
   231  		{
   232  			tname:  `with disabled field keys`,
   233  			name:   "abc",
   234  			fields: map[string]interface{}{"f1": 1, "f2": uint64(32)},
   235  			tags:   map[string]string{"t1": "abc", "t2": "32"},
   236  
   237  			opt: func() *Option {
   238  				opt := NewDefaultOption()
   239  				opt.DisabledFieldKeys = []string{"f1"}
   240  				return opt
   241  			}(),
   242  
   243  			fail: true,
   244  		},
   245  
   246  		{
   247  			tname:  `with disabled tag keys`,
   248  			name:   "abc",
   249  			fields: map[string]interface{}{"f1": 1, "f2": uint64(32)},
   250  			tags:   map[string]string{"t1": "abc", "t2": "32"},
   251  
   252  			opt: func() *Option {
   253  				opt := NewDefaultOption()
   254  				opt.DisabledTagKeys = []string{"t2"}
   255  				return opt
   256  			}(),
   257  
   258  			fail: true,
   259  		},
   260  
   261  		{
   262  			tname:  `int exceed int64-max under non-strict mode`,
   263  			name:   "abc",
   264  			fields: map[string]interface{}{"f1": 1, "f2": uint64(32)},
   265  			expect: "abc f1=1i,f2=32i 123",
   266  			opt: func() *Option {
   267  				opt := NewDefaultOption()
   268  				opt.Time = time.Unix(0, 123)
   269  				return opt
   270  			}(),
   271  
   272  			fail: false,
   273  		},
   274  
   275  		{
   276  			tname:     `int exceed int64-max under non-strict mode`,
   277  			name:      "abc",
   278  			fields:    map[string]interface{}{"f1": 1, "f2": uint64(math.MaxInt64) + 1},
   279  			expect:    "abc f1=1i 123", // f2 dropped
   280  			warnTypes: []string{WarnMaxFieldValueInt},
   281  			opt: func() *Option {
   282  				opt := NewDefaultOption()
   283  				opt.Time = time.Unix(0, 123)
   284  				opt.Strict = false
   285  				return opt
   286  			}(),
   287  
   288  			fail: false,
   289  		},
   290  
   291  		{
   292  			tname:  `int exceed int64-max under strict mode`,
   293  			name:   "abc",
   294  			fields: map[string]interface{}{"f1": 1, "f2": uint64(math.MaxInt64) + 1},
   295  
   296  			opt: func() *Option {
   297  				opt := NewDefaultOption()
   298  				opt.Time = time.Unix(0, 123)
   299  				return opt
   300  			}(),
   301  
   302  			fail: true,
   303  		},
   304  
   305  		{
   306  			tname:  `extra tags and field exceed max tags`,
   307  			name:   "abc",
   308  			fields: map[string]interface{}{"f1": 1, "f2": "3"},
   309  			tags:   map[string]string{"t1": "def", "t2": "abc"},
   310  
   311  			opt: func() *Option {
   312  				opt := NewDefaultOption()
   313  				opt.Time = time.Unix(0, 123)
   314  				opt.MaxTags = 2
   315  				opt.MaxFields = 1
   316  				opt.ExtraTags = map[string]string{
   317  					"etag1": "1",
   318  					"etag2": "2",
   319  				}
   320  				return opt
   321  			}(),
   322  			warnTypes: []string{WarnMaxTags, WarnMaxFields},
   323  			expect:    "abc,etag1=1,etag2=2 f1=1i 123", // f2 dropped,
   324  		},
   325  
   326  		{
   327  			tname:     `extra tags exceed max tags`,
   328  			name:      "abc",
   329  			fields:    map[string]interface{}{"f1": 1},
   330  			tags:      map[string]string{"t1": "def", "t2": "abc"},
   331  			warnTypes: []string{WarnMaxTags},
   332  			opt: func() *Option {
   333  				opt := NewDefaultOption()
   334  				opt.Time = time.Unix(0, 123)
   335  				opt.MaxTags = 2
   336  				opt.ExtraTags = map[string]string{
   337  					"etag1": "1",
   338  					"etag2": "2",
   339  				}
   340  				opt.Time = time.Unix(0, 123)
   341  				return opt
   342  			}(),
   343  			expect: "abc,etag1=1,etag2=2 f1=1i 123",
   344  		},
   345  
   346  		{
   347  			tname:  `extra tags not exceed max tags`,
   348  			name:   "abc",
   349  			fields: map[string]interface{}{"f1": 1},
   350  			tags:   map[string]string{"t1": "def", "t2": "abc"},
   351  			expect: "abc,etag1=1,etag2=2,t1=def,t2=abc f1=1i 123",
   352  
   353  			opt: func() *Option {
   354  				opt := NewDefaultOption()
   355  				opt.Time = time.Unix(0, 123)
   356  				opt.MaxTags = 4
   357  				opt.ExtraTags = map[string]string{
   358  					"etag1": "1",
   359  					"etag2": "2",
   360  				}
   361  				return opt
   362  			}(),
   363  
   364  			fail: false,
   365  		},
   366  
   367  		{
   368  			tname:  `only extra tags`,
   369  			name:   "abc",
   370  			fields: map[string]interface{}{"f1": 1},
   371  			expect: "abc,etag1=1,etag2=2 f1=1i 123",
   372  
   373  			opt: func() *Option {
   374  				opt := NewDefaultOption()
   375  				opt.Time = time.Unix(0, 123)
   376  				opt.MaxTags = 4
   377  				opt.ExtraTags = map[string]string{
   378  					"etag1": "1",
   379  					"etag2": "2",
   380  				}
   381  				return opt
   382  			}(),
   383  
   384  			fail: false,
   385  		},
   386  
   387  		{
   388  			tname:  `exceed max tags`,
   389  			name:   "abc",
   390  			fields: map[string]interface{}{"f1": 1, "f2": nil},
   391  			tags:   map[string]string{"t1": "def", "t2": "abc"},
   392  			opt: func() *Option {
   393  				opt := NewDefaultOption()
   394  				opt.Time = time.Unix(0, 123)
   395  				opt.MaxTags = 1
   396  				return opt
   397  			}(),
   398  			warnTypes: []string{WarnMaxTags},
   399  			fail:      true,
   400  		},
   401  
   402  		{
   403  			tname:  `exceed max field`,
   404  			name:   "abc",
   405  			fields: map[string]interface{}{"f1": 1, "f2": 2},
   406  			tags:   map[string]string{"t1": "def"},
   407  			opt: func() *Option {
   408  				opt := NewDefaultOption()
   409  				opt.Time = time.Unix(0, 123)
   410  				opt.MaxFields = 1
   411  				opt.Time = time.Unix(0, 123)
   412  				return opt
   413  			}(),
   414  			warnTypes: []string{WarnMaxFields},
   415  			expect:    "abc,t1=def f1=1i 123",
   416  		},
   417  
   418  		{
   419  			tname:  `field key with "."`,
   420  			name:   "abc",
   421  			fields: map[string]interface{}{"f1.a": 1},
   422  			tags:   map[string]string{"t1.a": "def"},
   423  			opt:    NewDefaultOption(),
   424  			fail:   true,
   425  		},
   426  
   427  		{
   428  			tname:  `field key with "."`,
   429  			name:   "abc",
   430  			fields: map[string]interface{}{"f1.a": 1},
   431  			tags:   map[string]string{"t1": "def"},
   432  			opt:    NewDefaultOption(),
   433  			fail:   true,
   434  		},
   435  
   436  		{
   437  			tname:  `tag key with "."`,
   438  			name:   "abc",
   439  			fields: map[string]interface{}{"f1": 1, "f2": nil},
   440  			tags:   map[string]string{"t1.a": "def"},
   441  			opt:    NewDefaultOption(),
   442  			fail:   true,
   443  		},
   444  
   445  		{
   446  			tname:  `nil field, not allowed`,
   447  			name:   "abc",
   448  			fields: map[string]interface{}{"f1": 1, "f2": nil},
   449  			tags:   map[string]string{"t1": "def"},
   450  			opt: func() *Option {
   451  				opt := NewDefaultOption()
   452  				opt.Time = time.Unix(0, 123)
   453  				return opt
   454  			}(),
   455  			fail: true,
   456  		},
   457  
   458  		{
   459  			tname:  `same key in field and tag`,
   460  			name:   "abc",
   461  			fields: map[string]interface{}{"f1": 1, "f2": 2},
   462  			tags:   map[string]string{"f1": "def"},
   463  			opt: func() *Option {
   464  				opt := NewDefaultOption()
   465  				opt.Time = time.Unix(0, 123)
   466  				return opt
   467  			}(),
   468  			warnTypes: []string{WarnSameTagFieldKey},
   469  			expect:    "abc,f1=def f2=2i 123",
   470  		},
   471  
   472  		{
   473  			tname:  `no tag`,
   474  			name:   "abc",
   475  			fields: map[string]interface{}{"f1": 1},
   476  			tags:   nil,
   477  			opt: func() *Option {
   478  				opt := NewDefaultOption()
   479  				opt.Time = time.Unix(0, 123)
   480  				return opt
   481  			}(),
   482  			expect: "abc f1=1i 123",
   483  		},
   484  
   485  		{
   486  			tname:  `no filed`,
   487  			name:   "abc",
   488  			fields: nil,
   489  			tags:   map[string]string{"f1": "def"},
   490  			opt:    NewDefaultOption(),
   491  			fail:   true,
   492  		},
   493  
   494  		{
   495  			tname: `field-val with '\n'`,
   496  			name:  "abc",
   497  			fields: map[string]interface{}{"f1": `abc
   498  123`},
   499  			opt: func() *Option {
   500  				opt := NewDefaultOption()
   501  				opt.Time = time.Unix(0, 123)
   502  				opt.Strict = false
   503  				return opt
   504  			}(),
   505  			expect: `abc f1="abc
   506  123" 123`,
   507  			fail: false,
   508  		},
   509  
   510  		{
   511  			tname: `tag-k/v with '\n' under non-strict`,
   512  			name:  "abc",
   513  			tags: map[string]string{
   514  				"tag1": `abc
   515  123`,
   516  				`tag
   517  2`: `def
   518  456\`,
   519  			},
   520  			fields: map[string]interface{}{"f1": 123},
   521  			opt: func() *Option {
   522  				opt := NewDefaultOption()
   523  				opt.Time = time.Unix(0, 123)
   524  				opt.Strict = false
   525  				return opt
   526  			}(),
   527  			expect: "abc,tag\\ 2=def\\ 456,tag1=abc\\ 123 f1=123i 123",
   528  			fail:   false,
   529  		},
   530  
   531  		{
   532  			tname: `tag-k/v with '\n' under strict`,
   533  			name:  "abc",
   534  			tags: map[string]string{
   535  				"tag1": `abc
   536  123`,
   537  				`tag
   538  2`: `def
   539  456\`,
   540  			},
   541  			fields: map[string]interface{}{"f1": 123},
   542  			opt: func() *Option {
   543  				opt := NewDefaultOption()
   544  				opt.Time = time.Unix(0, 123)
   545  				return opt
   546  			}(),
   547  			fail: true,
   548  		},
   549  
   550  		{
   551  			tname:  `ok case`,
   552  			name:   "abc",
   553  			tags:   nil,
   554  			fields: map[string]interface{}{"f1": 123},
   555  			opt: func() *Option {
   556  				opt := NewDefaultOption()
   557  				opt.Time = time.Unix(0, 123)
   558  				return opt
   559  			}(),
   560  			expect: "abc f1=123i 123",
   561  			fail:   false,
   562  		},
   563  
   564  		{
   565  			tname:  `tag key with backslash`,
   566  			name:   "abc",
   567  			tags:   map[string]string{"tag1": "val1", `tag2\`: `val2\`},
   568  			fields: map[string]interface{}{"f1": 123},
   569  			opt: func() *Option {
   570  				opt := NewDefaultOption()
   571  				opt.Time = time.Unix(0, 123)
   572  				return opt
   573  			}(),
   574  			fail: true,
   575  		},
   576  
   577  		{
   578  			tname:  `auto fix tag-key, tag-value under non-strict mode`,
   579  			name:   "abc",
   580  			tags:   map[string]string{"tag1": "val1", `tag2\`: `val2\`},
   581  			fields: map[string]interface{}{"f1": 123},
   582  
   583  			opt: func() *Option {
   584  				opt := NewDefaultOption()
   585  				opt.Time = time.Unix(0, 123)
   586  				opt.Strict = false
   587  				return opt
   588  			}(),
   589  			expect: "abc,tag1=val1,tag2=val2 f1=123i 123",
   590  			fail:   false,
   591  		},
   592  
   593  		{
   594  			tname:  `under strict: error`,
   595  			name:   "abc",
   596  			tags:   map[string]string{"tag1": "val1", `tag2\`: `val2\`},
   597  			fields: map[string]interface{}{"f1": 123},
   598  
   599  			opt: func() *Option {
   600  				opt := NewDefaultOption()
   601  				opt.Time = time.Unix(0, 123)
   602  				return opt
   603  			}(),
   604  
   605  			expect: "abc,tag1=val1,tag2=val2 f1=123i 123",
   606  			fail:   true,
   607  		},
   608  
   609  		{
   610  			tname:  `under strict: field is nil`,
   611  			name:   "abc",
   612  			tags:   map[string]string{"tag1": "val1", `tag2`: `val2`},
   613  			fields: map[string]interface{}{"f1": 123, "f2": nil},
   614  
   615  			opt: func() *Option {
   616  				opt := NewDefaultOption()
   617  				opt.Time = time.Unix(0, 123)
   618  				return opt
   619  			}(),
   620  
   621  			expect: "abc,tag1=val1,tag2=val2 f1=123i 123",
   622  			fail:   true,
   623  		},
   624  
   625  		{
   626  			tname:  `under strict: field is map`,
   627  			name:   "abc",
   628  			tags:   map[string]string{"tag1": "val1", `tag2`: `val2`},
   629  			fields: map[string]interface{}{"f1": 123, "f2": map[string]interface{}{"a": "b"}},
   630  
   631  			opt: func() *Option {
   632  				opt := NewDefaultOption()
   633  				opt.Time = time.Unix(0, 123)
   634  				return opt
   635  			}(),
   636  
   637  			expect: "abc,tag1=val1,tag2=val2 f1=123i 123",
   638  			fail:   true,
   639  		},
   640  
   641  		{
   642  			tname:  `under strict: field is object`,
   643  			name:   "abc",
   644  			tags:   map[string]string{"tag1": "val1", `tag2`: `val2`},
   645  			fields: map[string]interface{}{"f1": 123, "f2": struct{ a string }{a: "abc"}},
   646  
   647  			opt: func() *Option {
   648  				opt := NewDefaultOption()
   649  				opt.Time = time.Unix(0, 123)
   650  				return opt
   651  			}(),
   652  
   653  			expect: "abc,tag1=val1,tag2=val2 f1=123i 123",
   654  			fail:   true,
   655  		},
   656  
   657  		{
   658  			tname:  `under non-strict, ignore nil field`,
   659  			name:   "abc",
   660  			tags:   map[string]string{"tag1": "val1", `tag2\`: `val2\`},
   661  			fields: map[string]interface{}{"f1": 123, "f2": nil},
   662  
   663  			opt: func() *Option {
   664  				opt := NewDefaultOption()
   665  				opt.Time = time.Unix(0, 123)
   666  				opt.Strict = false
   667  				return opt
   668  			}(),
   669  
   670  			expect: "abc,tag1=val1,tag2=val2 f1=123i 123",
   671  			fail:   false,
   672  		},
   673  
   674  		{
   675  			tname:  `under strict, utf8 characters in metric-name`,
   676  			name:   "abc≈≈≈≈øøππ†®",
   677  			tags:   map[string]string{"tag1": "val1", `tag2`: `val2`},
   678  			fields: map[string]interface{}{"f1": 123},
   679  
   680  			opt: func() *Option {
   681  				opt := NewDefaultOption()
   682  				opt.Time = time.Unix(0, 123)
   683  				return opt
   684  			}(),
   685  
   686  			expect: "abc≈≈≈≈øøππ†®,tag1=val1,tag2=val2 f1=123i 123",
   687  			fail:   false,
   688  		},
   689  
   690  		{
   691  			tname:  `under strict, utf8 characters in metric-name, fields, tags`,
   692  			name:   "abc≈≈≈≈øøππ†®",
   693  			tags:   map[string]string{"tag1": "val1", `tag2`: `val2`, "tag3": `ºª•¶§∞¢£`},
   694  			fields: map[string]interface{}{"f1": 123, "f2": "¡™£¢∞§¶•ªº"},
   695  			opt: func() *Option {
   696  				opt := NewDefaultOption()
   697  				opt.Time = time.Unix(0, 123)
   698  				return opt
   699  			}(),
   700  
   701  			expect: `abc≈≈≈≈øøππ†®,tag1=val1,tag2=val2,tag3=ºª•¶§∞¢£ f1=123i,f2="¡™£¢∞§¶•ªº" 123`,
   702  			fail:   false,
   703  		},
   704  
   705  		{
   706  			tname: `missing field`,
   707  			name:  "abc≈≈≈≈øøππ†®",
   708  			tags:  map[string]string{"tag1": "val1", `tag2`: `val2`, "tag3": `ºª•¶§∞¢£`},
   709  
   710  			opt: func() *Option {
   711  				opt := NewDefaultOption()
   712  				opt.Time = time.Unix(0, 123)
   713  				return opt
   714  			}(),
   715  
   716  			expect: `abc≈≈≈≈øøππ†®,tag1=val1,tag2=val2,tag3=ºª•¶§∞¢£ f1=123i,f2="¡™£¢∞§¶•ªº" 123`,
   717  			fail:   true,
   718  		},
   719  
   720  		{
   721  			tname: `new line in field`,
   722  			name:  "abc",
   723  			tags:  map[string]string{"tag1": "val1"},
   724  			fields: map[string]interface{}{
   725  				"f1": `aaa
   726  	bbb
   727  			ccc`,
   728  			},
   729  			opt: func() *Option {
   730  				opt := NewDefaultOption()
   731  				opt.Time = time.Unix(0, 123)
   732  				return opt
   733  			}(),
   734  
   735  			expect: `abc,tag1=val1 f1="aaa
   736  	bbb
   737  			ccc" 123`,
   738  		},
   739  	}
   740  
   741  	for i, tc := range cases {
   742  		t.Run(tc.tname, func(t *testing.T) {
   743  			pt, warnings, err := MakeLineProtoPointWithWarnings(tc.name, tc.tags, tc.fields, tc.opt)
   744  
   745  			if len(tc.warnTypes) == 0 {
   746  				testutil.Equals(t, 0, len(warnings))
   747  			}
   748  			for _, warnType := range tc.warnTypes {
   749  				isFound := false
   750  				for _, w := range warnings {
   751  					if w.WarningType == warnType {
   752  						isFound = true
   753  						break
   754  					}
   755  				}
   756  				if isFound {
   757  					continue
   758  				} else {
   759  					t.Fail()
   760  					t.Logf("[%d]: expected warning type %s, but not found", i, warnType)
   761  				}
   762  			}
   763  			if tc.fail {
   764  				testutil.NotOk(t, err, "")
   765  				t.Logf("[%d] expect error: %s", i, err)
   766  			} else {
   767  				testutil.Ok(t, err)
   768  				x := pt.String()
   769  				testutil.Equals(t, tc.expect, x)
   770  				_, err := parseLineProto(t, []byte(x), "n")
   771  				testutil.Equals(t, err, nil)
   772  				fmt.Printf("\n[%d]%s\n", i, x)
   773  			}
   774  		})
   775  	}
   776  }
   777  
   778  func TestParsePoint(t *testing.T) {
   779  	newPoint := func(m string,
   780  		tags map[string]string,
   781  		fields map[string]interface{},
   782  		ts ...time.Time,
   783  	) *influxdb.Point {
   784  		pt, err := influxdb.NewPoint(m, tags, fields, ts...)
   785  		if err != nil {
   786  			t.Fatal(err) // should never been here
   787  		}
   788  
   789  		return pt
   790  	}
   791  
   792  	__32mbString := cliutils.CreateRandomString(32 * 1024 * 1024)
   793  	__65kbString := cliutils.CreateRandomString(65 * 1024)
   794  
   795  	cases := []struct {
   796  		name   string
   797  		data   []byte
   798  		opt    *Option
   799  		expect []*influxdb.Point
   800  		fail   bool
   801  	}{
   802  		{
   803  			name: `32mb-field`,
   804  			data: []byte(fmt.Sprintf(`abc f1="%s" 123`, __32mbString)),
   805  			opt: func() *Option {
   806  				opt := NewDefaultOption()
   807  				opt.MaxFieldValueLen = 32 * 1024 * 1024
   808  				opt.Time = time.Unix(0, 123)
   809  				return opt
   810  			}(),
   811  
   812  			expect: []*influxdb.Point{
   813  				newPoint("abc",
   814  					nil,
   815  					map[string]interface{}{"f1": __32mbString},
   816  					time.Unix(0, 123)),
   817  			},
   818  		},
   819  		{
   820  			name: `65k-field`,
   821  			data: []byte(fmt.Sprintf(`abc f1="%s" 123`, __65kbString)),
   822  			opt: func() *Option {
   823  				opt := NewDefaultOption()
   824  				opt.MaxFieldValueLen = 0
   825  				opt.Time = time.Unix(0, 123)
   826  				return opt
   827  			}(),
   828  
   829  			expect: []*influxdb.Point{
   830  				newPoint("abc",
   831  					nil,
   832  					map[string]interface{}{"f1": __65kbString},
   833  					time.Unix(0, 123)),
   834  			},
   835  		},
   836  
   837  		{
   838  			name: `with disabled field`,
   839  			data: []byte(`abc,t1=1,t2=2 f1=1i,f2=2,f3="abc" 123`),
   840  			opt:  &Option{DisabledFieldKeys: []string{"f1"}},
   841  			fail: true,
   842  		},
   843  
   844  		{
   845  			name: `with disabled tags`,
   846  			data: []byte(`abc,t1=1,t2=2 f1=1i,f2=2,f3="abc" 123`),
   847  			opt:  &Option{DisabledTagKeys: []string{"t1"}},
   848  			fail: true,
   849  		},
   850  
   851  		{
   852  			name: `exceed max tags`,
   853  			data: []byte(`abc,t1=1,t2=2 f1=1i,f2=2,f3="abc" 123`),
   854  			opt:  &Option{Time: time.Unix(0, 123), MaxTags: 1},
   855  			fail: true,
   856  		},
   857  
   858  		{
   859  			name: `exceed max fields`,
   860  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
   861  			opt:  &Option{Time: time.Unix(0, 123), MaxFields: 2},
   862  			fail: true,
   863  		},
   864  
   865  		{
   866  			name: `tag key with .`,
   867  			data: []byte(`abc,tag.1=xxx f1=1i,f2=2,f3="abc" 123`),
   868  			opt:  &Option{Time: time.Unix(0, 123)},
   869  			fail: true,
   870  		},
   871  
   872  		{
   873  			name: `field key with .`,
   874  			data: []byte(`abc f.1=1i,f2=2,f3="abc" 123`),
   875  			opt:  &Option{Time: time.Unix(0, 123)},
   876  			fail: true,
   877  		},
   878  
   879  		{
   880  			name: `with comments`,
   881  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123
   882  # some comments
   883  abc f1=1i,f2=2,f3="abc" 456
   884  							# other comments with leading spaces
   885  abc f1=1i,f2=2,f3="abc" 789
   886  
   887  			`),
   888  			opt: &Option{Time: time.Unix(0, 123)},
   889  			expect: []*influxdb.Point{
   890  				newPoint("abc",
   891  					nil,
   892  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
   893  					time.Unix(0, 123)),
   894  
   895  				newPoint("abc",
   896  					nil,
   897  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
   898  					time.Unix(0, 456)),
   899  
   900  				newPoint("abc",
   901  					nil,
   902  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
   903  					time.Unix(0, 789)),
   904  			},
   905  		},
   906  
   907  		{
   908  			name: `same key in field and tag, dup tag comes from ExtraTags`,
   909  			data: []byte(`abc b="abc",a=1i 123`),
   910  			opt:  &Option{Time: time.Unix(0, 123), ExtraTags: map[string]string{"a": "456"}}, // dup tag from Option
   911  			fail: true,
   912  		},
   913  
   914  		{
   915  			name: `same key in tags and fields`,
   916  			data: []byte(`abc,b=abc a=1i,b="abc" 123`),
   917  			opt:  &Option{Time: time.Unix(0, 123)},
   918  			fail: true,
   919  		},
   920  
   921  		{
   922  			name: `same key in fields`,
   923  			data: []byte(`abc,b=abc a=1i,c="abc",c=f 123`),
   924  			opt:  &Option{Time: time.Unix(0, 123)},
   925  			fail: true,
   926  		},
   927  
   928  		{
   929  			name: `same key in tag`,
   930  			data: []byte(`abc,b=abc,b=xyz a=1i 123`),
   931  			fail: true,
   932  		},
   933  
   934  		{
   935  			name: `empty data`,
   936  			data: nil,
   937  			fail: true,
   938  		},
   939  
   940  		{
   941  			name: "normal case",
   942  			data: []byte(`abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 123`),
   943  			opt:  &Option{Time: time.Unix(0, 123)},
   944  			expect: []*influxdb.Point{
   945  				newPoint("abc",
   946  					map[string]string{"tag1": "1", "tag2": "2"},
   947  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
   948  					time.Unix(0, 123)),
   949  			},
   950  		},
   951  
   952  		{
   953  			name: `no tags`,
   954  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
   955  			opt:  &Option{Time: time.Unix(0, 123)},
   956  			expect: []*influxdb.Point{
   957  				newPoint("abc",
   958  					nil,
   959  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
   960  					time.Unix(0, 123)),
   961  			},
   962  		},
   963  
   964  		{
   965  			name: `multiple empty lines in body`,
   966  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123
   967  
   968  abc f1=1i,f2=2,f3="abc" 456
   969  
   970  abc f1=1i,f2=2,f3="abc" 789
   971  
   972  			`),
   973  			opt: &Option{Time: time.Unix(0, 123)},
   974  			expect: []*influxdb.Point{
   975  				newPoint("abc",
   976  					nil,
   977  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
   978  					time.Unix(0, 123)),
   979  
   980  				newPoint("abc",
   981  					nil,
   982  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
   983  					time.Unix(0, 456)),
   984  
   985  				newPoint("abc",
   986  					nil,
   987  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
   988  					time.Unix(0, 789)),
   989  			},
   990  		},
   991  
   992  		{
   993  			name: `no fields`,
   994  			fail: true,
   995  			data: []byte(`abc,tag1=1,tag2=2 123123`),
   996  		},
   997  
   998  		{
   999  			name: `parse with extra tags`,
  1000  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
  1001  			opt: &Option{
  1002  				Time:      time.Unix(0, 123),
  1003  				ExtraTags: map[string]string{"tag1": "1", "tag2": "2"},
  1004  			},
  1005  			expect: []*influxdb.Point{
  1006  				newPoint("abc",
  1007  					map[string]string{"tag1": "1", "tag2": "2"},
  1008  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
  1009  					time.Unix(0, 123)),
  1010  			},
  1011  		},
  1012  
  1013  		{
  1014  			name: `extra tag key with '\' suffix`,
  1015  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
  1016  			opt: &Option{
  1017  				Time:      time.Unix(0, 123),
  1018  				ExtraTags: map[string]string{`tag1\`: `1`, "tag2": `2`},
  1019  			},
  1020  			expect: []*influxdb.Point{
  1021  				newPoint("abc",
  1022  					map[string]string{`tag1\`: "1", "tag2": `2`},
  1023  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
  1024  					time.Unix(0, 123)),
  1025  			},
  1026  		},
  1027  
  1028  		{
  1029  			name: `extra tag val with '\' suffix`,
  1030  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
  1031  			opt: &Option{
  1032  				Time:      time.Unix(0, 123),
  1033  				ExtraTags: map[string]string{`tag1`: `1,`, "tag2": `2\`, "tag3": `3`},
  1034  			},
  1035  			expect: []*influxdb.Point{
  1036  				newPoint("abc",
  1037  					map[string]string{`tag1`: "1,", "tag2": `2\`, "tag3": `3`},
  1038  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
  1039  					time.Unix(0, 123)),
  1040  			},
  1041  		},
  1042  
  1043  		{
  1044  			name: `extra tag kv with '\'`,
  1045  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
  1046  			opt: &Option{
  1047  				Time:      time.Unix(0, 123),
  1048  				ExtraTags: map[string]string{`tag\1`: `1`, "tag2": `2\34`},
  1049  			},
  1050  			expect: []*influxdb.Point{
  1051  				newPoint("abc",
  1052  					map[string]string{`tag\1`: "1", "tag2": `2\34`},
  1053  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
  1054  					time.Unix(0, 123)),
  1055  			},
  1056  		},
  1057  
  1058  		{
  1059  			name: `tag kv with '\': missing tag value`,
  1060  			fail: true,
  1061  			data: []byte(`abc,tag1\=1,tag2=2\ f1=1i 123123`),
  1062  		},
  1063  
  1064  		{
  1065  			name: `parse with callback: no point`,
  1066  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
  1067  			opt: &Option{
  1068  				Time: time.Unix(0, 123),
  1069  				Callback: func(p models.Point) (models.Point, error) {
  1070  					return nil, nil
  1071  				},
  1072  			},
  1073  			fail: true,
  1074  		},
  1075  
  1076  		{
  1077  			name: `parse with callback failed`,
  1078  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
  1079  			opt: &Option{
  1080  				Time: time.Unix(0, 123),
  1081  				Callback: func(p models.Point) (models.Point, error) {
  1082  					return nil, fmt.Errorf("callback failed")
  1083  				},
  1084  			},
  1085  			fail: true,
  1086  		},
  1087  
  1088  		{
  1089  			name: `parse with callback`,
  1090  			data: []byte(`abc f1=1i,f2=2,f3="abc" 123`),
  1091  			opt: &Option{
  1092  				Time: time.Unix(0, 123),
  1093  				Callback: func(p models.Point) (models.Point, error) {
  1094  					if string(p.Name()) == "abc" {
  1095  						t.Logf("haha, we get measurement `abc'")
  1096  					}
  1097  					p.AddTag("callback-added-tag", "callback-added-tag-value")
  1098  					return p, nil
  1099  				},
  1100  			},
  1101  			expect: []*influxdb.Point{
  1102  				newPoint("abc",
  1103  					map[string]string{"callback-added-tag": "callback-added-tag-value"},
  1104  					map[string]interface{}{"f1": 1, "f2": 2.0, "f3": "abc"},
  1105  					time.Unix(0, 123)),
  1106  			},
  1107  		},
  1108  	}
  1109  
  1110  	for _, tc := range cases {
  1111  		t.Run(tc.name, func(t *testing.T) {
  1112  			pts, err := ParsePoints(tc.data, tc.opt)
  1113  			if tc.fail {
  1114  				testutil.NotOk(t, err, "")
  1115  				t.Logf("expect error: %s", err)
  1116  			} else {
  1117  				testutil.Ok(t, err)
  1118  
  1119  				for idx, pt := range pts {
  1120  					if len(tc.expect) > 0 {
  1121  						_ = idx
  1122  
  1123  						got := pt.String()
  1124  
  1125  						pts, err := parseLineProto(t, []byte(got), "n")
  1126  						if err != nil {
  1127  							t.Logf("parseLineProto failed")
  1128  							continue
  1129  						}
  1130  
  1131  						for _, pt := range pts {
  1132  							fields, err := pt.Fields()
  1133  							testutil.Ok(t, err)
  1134  
  1135  							for k, v := range fields {
  1136  								switch x := v.(type) {
  1137  								case string:
  1138  									t.Logf("%s: %s", k, cliutils.StringTrim(x, 32))
  1139  								default:
  1140  									t.Logf("%s: %v", k, x)
  1141  								}
  1142  							}
  1143  						}
  1144  					}
  1145  				}
  1146  			}
  1147  		})
  1148  	}
  1149  }
  1150  
  1151  func TestParseLineProto(t *testing.T) {
  1152  	__32mbString := cliutils.CreateRandomString(32 * 1024 * 1024)
  1153  	__65kbString := cliutils.CreateRandomString(65 * 1024)
  1154  
  1155  	cases := []struct {
  1156  		data  []byte
  1157  		prec  string
  1158  		fail  bool
  1159  		name  string
  1160  		check func(pts models.Points) error
  1161  	}{
  1162  		{
  1163  			name: `nil data`,
  1164  			data: nil,
  1165  			prec: "n",
  1166  			fail: true,
  1167  		},
  1168  
  1169  		{
  1170  			name: `no data`,
  1171  			data: []byte(""),
  1172  			prec: "n",
  1173  			fail: true,
  1174  		},
  1175  
  1176  		{
  1177  			name: `with multiple empty lines`,
  1178  			data: []byte(`abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc"
  1179  		abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc"
  1180  		abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc"
  1181  
  1182  		`),
  1183  			prec: "n",
  1184  		},
  1185  
  1186  		{
  1187  			name: `missing field`,
  1188  			data: []byte(`abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc"
  1189  		abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc"
  1190  		abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc"
  1191  		abc
  1192  		`),
  1193  			prec: "n",
  1194  			fail: true,
  1195  		},
  1196  
  1197  		{
  1198  			name: `missing tag`,
  1199  			data: []byte(`abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc"
  1200  			abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc"
  1201  			abc,tag1=1,tag2=2 f1=1i,f2=2,f3="abc" 123456789
  1202  			abc f1=1i,f2=2,f3="abc"
  1203  			`),
  1204  			prec: "n",
  1205  		},
  1206  
  1207  		{
  1208  			name: `65kb-field-key`,
  1209  			data: []byte(fmt.Sprintf(`abc,tag1=1,tag2=2 "%s"="hello" 123`, func() string {
  1210  				return __65kbString
  1211  			}())),
  1212  
  1213  			fail: true,
  1214  			prec: "n",
  1215  		},
  1216  
  1217  		{
  1218  			name: `65kb-tag-key`,
  1219  			data: []byte(fmt.Sprintf(`abc,tag1=1,%s=2 f1="hello" 123`, func() string {
  1220  				return __65kbString
  1221  			}())),
  1222  
  1223  			fail: true,
  1224  			prec: "n",
  1225  		},
  1226  
  1227  		{
  1228  			name: `65kb-measurement-name`,
  1229  			data: []byte(fmt.Sprintf(`%s,tag1=1,t2=2 f1="hello" 123`, func() string {
  1230  				return __65kbString
  1231  			}())),
  1232  
  1233  			fail: true,
  1234  			prec: "n",
  1235  		},
  1236  
  1237  		{
  1238  			name: `32mb-field`,
  1239  			data: []byte(fmt.Sprintf(`abc,tag1=1,tag2=2 f3="%s" 123`, func() string {
  1240  				return __32mbString
  1241  			}())),
  1242  
  1243  			check: func(pts models.Points) error {
  1244  				if len(pts) != 1 {
  1245  					return fmt.Errorf("expect only 1 point, got %d", len(pts))
  1246  				}
  1247  
  1248  				fields, err := pts[0].Fields()
  1249  				if err != nil {
  1250  					return err
  1251  				}
  1252  
  1253  				if v, ok := fields["f3"]; !ok {
  1254  					return fmt.Errorf("field f3 missing")
  1255  				} else if v != __32mbString {
  1256  					return fmt.Errorf("field f3 not expected")
  1257  				}
  1258  				return nil
  1259  			},
  1260  
  1261  			prec: "n",
  1262  		},
  1263  	}
  1264  
  1265  	for _, tc := range cases {
  1266  		t.Run(tc.name, func(t *testing.T) {
  1267  			pts, err := parseLineProto(t, tc.data, tc.prec)
  1268  
  1269  			if tc.fail {
  1270  				testutil.NotOk(t, err, "")
  1271  				t.Logf("expect error: %s", cliutils.LeftStringTrim(err.Error(), 64))
  1272  			} else {
  1273  				testutil.Ok(t, err)
  1274  			}
  1275  
  1276  			if tc.check != nil {
  1277  				testutil.Ok(t, tc.check(pts))
  1278  			}
  1279  		})
  1280  	}
  1281  }