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