github.com/influxdata/influxdb/v2@v2.7.6/influxql/query/functions_test.go (about) 1 package query_test 2 3 import ( 4 "crypto/sha1" 5 "fmt" 6 "math" 7 "math/rand" 8 "runtime" 9 "strconv" 10 "testing" 11 "time" 12 13 "github.com/davecgh/go-spew/spew" 14 "github.com/influxdata/influxdb/v2/influxql/query" 15 "github.com/influxdata/influxdb/v2/pkg/deep" 16 "github.com/influxdata/influxql" 17 tassert "github.com/stretchr/testify/assert" 18 trequire "github.com/stretchr/testify/require" 19 ) 20 21 func almostEqual(got, exp float64) bool { 22 return math.Abs(got-exp) < 1e-5 && !math.IsNaN(got) 23 } 24 25 func TestHoltWinters_AusTourists(t *testing.T) { 26 if runtime.GOARCH != "amd64" { 27 t.Skip("Expected HoltWinters outputs only valid when GOARCH = amd64") 28 } 29 30 hw := query.NewFloatHoltWintersReducer(10, 4, false, 1) 31 // Dataset from http://www.inside-r.org/packages/cran/fpp/docs/austourists 32 austourists := []query.FloatPoint{ 33 {Time: 1, Value: 30.052513}, 34 {Time: 2, Value: 19.148496}, 35 {Time: 3, Value: 25.317692}, 36 {Time: 4, Value: 27.591437}, 37 {Time: 5, Value: 32.076456}, 38 {Time: 6, Value: 23.487961}, 39 {Time: 7, Value: 28.47594}, 40 {Time: 8, Value: 35.123753}, 41 {Time: 9, Value: 36.838485}, 42 {Time: 10, Value: 25.007017}, 43 {Time: 11, Value: 30.72223}, 44 {Time: 12, Value: 28.693759}, 45 {Time: 13, Value: 36.640986}, 46 {Time: 14, Value: 23.824609}, 47 {Time: 15, Value: 29.311683}, 48 {Time: 16, Value: 31.770309}, 49 {Time: 17, Value: 35.177877}, 50 {Time: 18, Value: 19.775244}, 51 {Time: 19, Value: 29.60175}, 52 {Time: 20, Value: 34.538842}, 53 {Time: 21, Value: 41.273599}, 54 {Time: 22, Value: 26.655862}, 55 {Time: 23, Value: 28.279859}, 56 {Time: 24, Value: 35.191153}, 57 {Time: 25, Value: 41.727458}, 58 {Time: 26, Value: 24.04185}, 59 {Time: 27, Value: 32.328103}, 60 {Time: 28, Value: 37.328708}, 61 {Time: 29, Value: 46.213153}, 62 {Time: 30, Value: 29.346326}, 63 {Time: 31, Value: 36.48291}, 64 {Time: 32, Value: 42.977719}, 65 {Time: 33, Value: 48.901525}, 66 {Time: 34, Value: 31.180221}, 67 {Time: 35, Value: 37.717881}, 68 {Time: 36, Value: 40.420211}, 69 {Time: 37, Value: 51.206863}, 70 {Time: 38, Value: 31.887228}, 71 {Time: 39, Value: 40.978263}, 72 {Time: 40, Value: 43.772491}, 73 {Time: 41, Value: 55.558567}, 74 {Time: 42, Value: 33.850915}, 75 {Time: 43, Value: 42.076383}, 76 {Time: 44, Value: 45.642292}, 77 {Time: 45, Value: 59.76678}, 78 {Time: 46, Value: 35.191877}, 79 {Time: 47, Value: 44.319737}, 80 {Time: 48, Value: 47.913736}, 81 } 82 83 for _, p := range austourists { 84 hw.AggregateFloat(&p) 85 } 86 points := hw.Emit() 87 88 forecasted := []query.FloatPoint{ 89 {Time: 49, Value: 51.85064132137853}, 90 {Time: 50, Value: 43.26055282315273}, 91 {Time: 51, Value: 41.827258044814464}, 92 {Time: 52, Value: 54.3990354591749}, 93 {Time: 53, Value: 54.62334472770803}, 94 {Time: 54, Value: 45.57155693625209}, 95 {Time: 55, Value: 44.06051240252263}, 96 {Time: 56, Value: 57.30029870759433}, 97 {Time: 57, Value: 57.53591513519172}, 98 {Time: 58, Value: 47.999008139396096}, 99 } 100 101 if exp, got := len(forecasted), len(points); exp != got { 102 t.Fatalf("unexpected number of points emitted: got %d exp %d", got, exp) 103 } 104 105 for i := range forecasted { 106 if exp, got := forecasted[i].Time, points[i].Time; got != exp { 107 t.Errorf("unexpected time on points[%d] got %v exp %v", i, got, exp) 108 } 109 if exp, got := forecasted[i].Value, points[i].Value; !almostEqual(got, exp) { 110 t.Errorf("unexpected value on points[%d] got %v exp %v", i, got, exp) 111 } 112 } 113 } 114 115 func TestHoltWinters_AusTourists_Missing(t *testing.T) { 116 if runtime.GOARCH != "amd64" { 117 t.Skip("Expected HoltWinters outputs only valid when GOARCH = amd64") 118 } 119 120 hw := query.NewFloatHoltWintersReducer(10, 4, false, 1) 121 // Dataset from http://www.inside-r.org/packages/cran/fpp/docs/austourists 122 austourists := []query.FloatPoint{ 123 {Time: 1, Value: 30.052513}, 124 {Time: 3, Value: 25.317692}, 125 {Time: 4, Value: 27.591437}, 126 {Time: 5, Value: 32.076456}, 127 {Time: 6, Value: 23.487961}, 128 {Time: 7, Value: 28.47594}, 129 {Time: 9, Value: 36.838485}, 130 {Time: 10, Value: 25.007017}, 131 {Time: 11, Value: 30.72223}, 132 {Time: 12, Value: 28.693759}, 133 {Time: 13, Value: 36.640986}, 134 {Time: 14, Value: 23.824609}, 135 {Time: 15, Value: 29.311683}, 136 {Time: 16, Value: 31.770309}, 137 {Time: 17, Value: 35.177877}, 138 {Time: 19, Value: 29.60175}, 139 {Time: 20, Value: 34.538842}, 140 {Time: 21, Value: 41.273599}, 141 {Time: 22, Value: 26.655862}, 142 {Time: 23, Value: 28.279859}, 143 {Time: 24, Value: 35.191153}, 144 {Time: 25, Value: 41.727458}, 145 {Time: 26, Value: 24.04185}, 146 {Time: 27, Value: 32.328103}, 147 {Time: 28, Value: 37.328708}, 148 {Time: 30, Value: 29.346326}, 149 {Time: 31, Value: 36.48291}, 150 {Time: 32, Value: 42.977719}, 151 {Time: 34, Value: 31.180221}, 152 {Time: 35, Value: 37.717881}, 153 {Time: 36, Value: 40.420211}, 154 {Time: 37, Value: 51.206863}, 155 {Time: 38, Value: 31.887228}, 156 {Time: 41, Value: 55.558567}, 157 {Time: 42, Value: 33.850915}, 158 {Time: 43, Value: 42.076383}, 159 {Time: 44, Value: 45.642292}, 160 {Time: 45, Value: 59.76678}, 161 {Time: 46, Value: 35.191877}, 162 {Time: 47, Value: 44.319737}, 163 {Time: 48, Value: 47.913736}, 164 } 165 166 for _, p := range austourists { 167 hw.AggregateFloat(&p) 168 } 169 points := hw.Emit() 170 171 forecasted := []query.FloatPoint{ 172 {Time: 49, Value: 54.84533610387743}, 173 {Time: 50, Value: 41.19329421863249}, 174 {Time: 51, Value: 45.71673175112451}, 175 {Time: 52, Value: 56.05759298805955}, 176 {Time: 53, Value: 59.32337460282217}, 177 {Time: 54, Value: 44.75280096850461}, 178 {Time: 55, Value: 49.98865098113751}, 179 {Time: 56, Value: 61.86084934967605}, 180 {Time: 57, Value: 65.95805633454883}, 181 {Time: 58, Value: 50.1502170480547}, 182 } 183 184 if exp, got := len(forecasted), len(points); exp != got { 185 t.Fatalf("unexpected number of points emitted: got %d exp %d", got, exp) 186 } 187 188 for i := range forecasted { 189 if exp, got := forecasted[i].Time, points[i].Time; got != exp { 190 t.Errorf("unexpected time on points[%d] got %v exp %v", i, got, exp) 191 } 192 if exp, got := forecasted[i].Value, points[i].Value; !almostEqual(got, exp) { 193 t.Errorf("unexpected value on points[%d] got %v exp %v", i, got, exp) 194 } 195 } 196 } 197 198 func TestHoltWinters_USPopulation(t *testing.T) { 199 if runtime.GOARCH != "amd64" { 200 t.Skip("Expected HoltWinters outputs only valid when GOARCH = amd64") 201 } 202 203 series := []query.FloatPoint{ 204 {Time: 1, Value: 3.93}, 205 {Time: 2, Value: 5.31}, 206 {Time: 3, Value: 7.24}, 207 {Time: 4, Value: 9.64}, 208 {Time: 5, Value: 12.90}, 209 {Time: 6, Value: 17.10}, 210 {Time: 7, Value: 23.20}, 211 {Time: 8, Value: 31.40}, 212 {Time: 9, Value: 39.80}, 213 {Time: 10, Value: 50.20}, 214 {Time: 11, Value: 62.90}, 215 {Time: 12, Value: 76.00}, 216 {Time: 13, Value: 92.00}, 217 {Time: 14, Value: 105.70}, 218 {Time: 15, Value: 122.80}, 219 {Time: 16, Value: 131.70}, 220 {Time: 17, Value: 151.30}, 221 {Time: 18, Value: 179.30}, 222 {Time: 19, Value: 203.20}, 223 } 224 hw := query.NewFloatHoltWintersReducer(10, 0, true, 1) 225 for _, p := range series { 226 hw.AggregateFloat(&p) 227 } 228 points := hw.Emit() 229 230 forecasted := []query.FloatPoint{ 231 {Time: 1, Value: 3.93}, 232 {Time: 2, Value: 4.957405463559748}, 233 {Time: 3, Value: 7.012210102535647}, 234 {Time: 4, Value: 10.099589257439924}, 235 {Time: 5, Value: 14.229926188104242}, 236 {Time: 6, Value: 19.418878968703797}, 237 {Time: 7, Value: 25.68749172281409}, 238 {Time: 8, Value: 33.062351305731305}, 239 {Time: 9, Value: 41.575791076125206}, 240 {Time: 10, Value: 51.26614395589263}, 241 {Time: 11, Value: 62.178047564264595}, 242 {Time: 12, Value: 74.36280483872488}, 243 {Time: 13, Value: 87.87880423073163}, 244 {Time: 14, Value: 102.79200429905801}, 245 {Time: 15, Value: 119.17648832929542}, 246 {Time: 16, Value: 137.11509549747296}, 247 {Time: 17, Value: 156.70013608313175}, 248 {Time: 18, Value: 178.03419933863566}, 249 {Time: 19, Value: 201.23106385518594}, 250 {Time: 20, Value: 226.4167216525905}, 251 {Time: 21, Value: 253.73052878285205}, 252 {Time: 22, Value: 283.32649700397553}, 253 {Time: 23, Value: 315.37474308085984}, 254 {Time: 24, Value: 350.06311454009256}, 255 {Time: 25, Value: 387.59901328556873}, 256 {Time: 26, Value: 428.21144141893404}, 257 {Time: 27, Value: 472.1532969569147}, 258 {Time: 28, Value: 519.7039509590035}, 259 {Time: 29, Value: 571.1721419458248}, 260 } 261 262 if exp, got := len(forecasted), len(points); exp != got { 263 t.Fatalf("unexpected number of points emitted: got %d exp %d", got, exp) 264 } 265 for i := range forecasted { 266 if exp, got := forecasted[i].Time, points[i].Time; got != exp { 267 t.Errorf("unexpected time on points[%d] got %v exp %v", i, got, exp) 268 } 269 if exp, got := forecasted[i].Value, points[i].Value; !almostEqual(got, exp) { 270 t.Errorf("unexpected value on points[%d] got %v exp %v", i, got, exp) 271 } 272 } 273 } 274 275 func TestHoltWinters_USPopulation_Missing(t *testing.T) { 276 if runtime.GOARCH != "amd64" { 277 t.Skip("Expected HoltWinters outputs only valid when GOARCH = amd64") 278 } 279 280 series := []query.FloatPoint{ 281 {Time: 1, Value: 3.93}, 282 {Time: 2, Value: 5.31}, 283 {Time: 3, Value: 7.24}, 284 {Time: 4, Value: 9.64}, 285 {Time: 5, Value: 12.90}, 286 {Time: 6, Value: 17.10}, 287 {Time: 7, Value: 23.20}, 288 {Time: 8, Value: 31.40}, 289 {Time: 10, Value: 50.20}, 290 {Time: 11, Value: 62.90}, 291 {Time: 12, Value: 76.00}, 292 {Time: 13, Value: 92.00}, 293 {Time: 15, Value: 122.80}, 294 {Time: 16, Value: 131.70}, 295 {Time: 17, Value: 151.30}, 296 {Time: 19, Value: 203.20}, 297 } 298 hw := query.NewFloatHoltWintersReducer(10, 0, true, 1) 299 for _, p := range series { 300 hw.AggregateFloat(&p) 301 } 302 points := hw.Emit() 303 304 forecasted := []query.FloatPoint{ 305 {Time: 1, Value: 3.93}, 306 {Time: 2, Value: 4.8931364428135105}, 307 {Time: 3, Value: 6.962653629047061}, 308 {Time: 4, Value: 10.056207765903274}, 309 {Time: 5, Value: 14.18435088129532}, 310 {Time: 6, Value: 19.362939306110846}, 311 {Time: 7, Value: 25.613247940326584}, 312 {Time: 8, Value: 32.96213087008264}, 313 {Time: 9, Value: 41.442230043017204}, 314 {Time: 10, Value: 51.09223428526052}, 315 {Time: 11, Value: 61.95719155158485}, 316 {Time: 12, Value: 74.08887794968567}, 317 {Time: 13, Value: 87.54622778052787}, 318 {Time: 14, Value: 102.39582960014131}, 319 {Time: 15, Value: 118.7124941463221}, 320 {Time: 16, Value: 136.57990089987464}, 321 {Time: 17, Value: 156.09133107941278}, 322 {Time: 18, Value: 177.35049601833734}, 323 {Time: 19, Value: 200.472471161683}, 324 {Time: 20, Value: 225.58474737097785}, 325 {Time: 21, Value: 252.82841286206823}, 326 {Time: 22, Value: 282.35948095261017}, 327 {Time: 23, Value: 314.3503808953992}, 328 {Time: 24, Value: 348.99163145856954}, 329 {Time: 25, Value: 386.49371962730555}, 330 {Time: 26, Value: 427.08920989407727}, 331 {Time: 27, Value: 471.0351131332573}, 332 {Time: 28, Value: 518.615548088049}, 333 {Time: 29, Value: 570.1447331101863}, 334 } 335 336 if exp, got := len(forecasted), len(points); exp != got { 337 t.Fatalf("unexpected number of points emitted: got %d exp %d", got, exp) 338 } 339 for i := range forecasted { 340 if exp, got := forecasted[i].Time, points[i].Time; got != exp { 341 t.Errorf("unexpected time on points[%d] got %v exp %v", i, got, exp) 342 } 343 if exp, got := forecasted[i].Value, points[i].Value; !almostEqual(got, exp) { 344 t.Errorf("unexpected value on points[%d] got %v exp %v", i, got, exp) 345 } 346 } 347 } 348 func TestHoltWinters_RoundTime(t *testing.T) { 349 if runtime.GOARCH != "amd64" { 350 t.Skip("Expected HoltWinters outputs only valid when GOARCH = amd64") 351 } 352 353 maxTime := time.Unix(0, influxql.MaxTime).Round(time.Second).UnixNano() 354 data := []query.FloatPoint{ 355 {Time: maxTime - int64(5*time.Second), Value: 1}, 356 {Time: maxTime - int64(4*time.Second+103*time.Millisecond), Value: 10}, 357 {Time: maxTime - int64(3*time.Second+223*time.Millisecond), Value: 2}, 358 {Time: maxTime - int64(2*time.Second+481*time.Millisecond), Value: 11}, 359 } 360 hw := query.NewFloatHoltWintersReducer(2, 2, true, time.Second) 361 for _, p := range data { 362 hw.AggregateFloat(&p) 363 } 364 points := hw.Emit() 365 366 forecasted := []query.FloatPoint{ 367 {Time: maxTime - int64(5*time.Second), Value: 1}, 368 {Time: maxTime - int64(4*time.Second), Value: 10.006729104838234}, 369 {Time: maxTime - int64(3*time.Second), Value: 1.998341814469269}, 370 {Time: maxTime - int64(2*time.Second), Value: 10.997858830631172}, 371 {Time: maxTime - int64(1*time.Second), Value: 4.085860238030013}, 372 {Time: maxTime - int64(0*time.Second), Value: 11.35713604403339}, 373 } 374 375 if exp, got := len(forecasted), len(points); exp != got { 376 t.Fatalf("unexpected number of points emitted: got %d exp %d", got, exp) 377 } 378 for i := range forecasted { 379 if exp, got := forecasted[i].Time, points[i].Time; got != exp { 380 t.Errorf("unexpected time on points[%d] got %v exp %v", i, got, exp) 381 } 382 if exp, got := forecasted[i].Value, points[i].Value; !almostEqual(got, exp) { 383 t.Errorf("unexpected value on points[%d] got %v exp %v", i, got, exp) 384 } 385 } 386 } 387 388 func TestHoltWinters_MaxTime(t *testing.T) { 389 if runtime.GOARCH != "amd64" { 390 t.Skip("Expected HoltWinters outputs only valid when GOARCH = amd64") 391 } 392 393 data := []query.FloatPoint{ 394 {Time: influxql.MaxTime - 1, Value: 1}, 395 {Time: influxql.MaxTime, Value: 2}, 396 } 397 hw := query.NewFloatHoltWintersReducer(1, 0, true, 1) 398 for _, p := range data { 399 hw.AggregateFloat(&p) 400 } 401 points := hw.Emit() 402 403 forecasted := []query.FloatPoint{ 404 {Time: influxql.MaxTime - 1, Value: 1}, 405 {Time: influxql.MaxTime, Value: 2.001516944066403}, 406 {Time: influxql.MaxTime + 1, Value: 2.5365248972488343}, 407 } 408 409 if exp, got := len(forecasted), len(points); exp != got { 410 t.Fatalf("unexpected number of points emitted: got %d exp %d", got, exp) 411 } 412 for i := range forecasted { 413 if exp, got := forecasted[i].Time, points[i].Time; got != exp { 414 t.Errorf("unexpected time on points[%d] got %v exp %v", i, got, exp) 415 } 416 if exp, got := forecasted[i].Value, points[i].Value; !almostEqual(got, exp) { 417 t.Errorf("unexpected value on points[%d] got %v exp %v", i, got, exp) 418 } 419 } 420 } 421 422 // TestSample_AllSamplesSeen attempts to verify that it is possible 423 // to get every subsample in a reasonable number of iterations. 424 // 425 // The idea here is that 30 iterations should be enough to hit every possible 426 // sequence at least once. 427 func TestSample_AllSamplesSeen(t *testing.T) { 428 ps := []query.FloatPoint{ 429 {Time: 1, Value: 1}, 430 {Time: 2, Value: 2}, 431 {Time: 3, Value: 3}, 432 } 433 434 // List of all the possible subsamples 435 samples := [][]query.FloatPoint{ 436 { 437 {Time: 1, Value: 1}, 438 {Time: 2, Value: 2}, 439 }, 440 { 441 {Time: 1, Value: 1}, 442 {Time: 3, Value: 3}, 443 }, 444 { 445 {Time: 2, Value: 2}, 446 {Time: 3, Value: 3}, 447 }, 448 } 449 450 // 30 iterations should be sufficient to guarantee that 451 // we hit every possible subsample. 452 for i := 0; i < 30; i++ { 453 s := query.NewFloatSampleReducer(2) 454 for _, p := range ps { 455 s.AggregateFloat(&p) 456 } 457 458 points := s.Emit() 459 460 for i, sample := range samples { 461 // if we find a sample that it matches, remove it from 462 // this list of possible samples 463 if deep.Equal(sample, points) { 464 samples = append(samples[:i], samples[i+1:]...) 465 break 466 } 467 } 468 469 // if samples is empty we've seen every sample, so we're done 470 if len(samples) == 0 { 471 return 472 } 473 474 // The FloatSampleReducer is seeded with time.Now().UnixNano(), and without this sleep, 475 // this test will fail on machines where UnixNano doesn't return full resolution. 476 // Specifically, some Windows machines will only return timestamps accurate to 100ns. 477 // While iterating through this test without an explicit sleep, 478 // we would only see one or two unique seeds across all the calls to NewFloatSampleReducer. 479 time.Sleep(time.Millisecond) 480 } 481 482 // If we missed a sample, report the error 483 if len(samples) != 0 { 484 t.Fatalf("expected all samples to be seen; unseen samples: %#v", samples) 485 } 486 } 487 488 func TestSample_SampleSizeLessThanNumPoints(t *testing.T) { 489 s := query.NewFloatSampleReducer(2) 490 491 ps := []query.FloatPoint{ 492 {Time: 1, Value: 1}, 493 {Time: 2, Value: 2}, 494 {Time: 3, Value: 3}, 495 } 496 497 for _, p := range ps { 498 s.AggregateFloat(&p) 499 } 500 501 points := s.Emit() 502 503 if exp, got := 2, len(points); exp != got { 504 t.Fatalf("unexpected number of points emitted: got %d exp %d", got, exp) 505 } 506 } 507 508 func TestSample_SampleSizeGreaterThanNumPoints(t *testing.T) { 509 s := query.NewFloatSampleReducer(4) 510 511 ps := []query.FloatPoint{ 512 {Time: 1, Value: 1}, 513 {Time: 2, Value: 2}, 514 {Time: 3, Value: 3}, 515 } 516 517 for _, p := range ps { 518 s.AggregateFloat(&p) 519 } 520 521 points := s.Emit() 522 523 if exp, got := len(ps), len(points); exp != got { 524 t.Fatalf("unexpected number of points emitted: got %d exp %d", got, exp) 525 } 526 527 if !deep.Equal(ps, points) { 528 t.Fatalf("unexpected points: %s", spew.Sdump(points)) 529 } 530 } 531 532 func TestHll_SumAndMergeHll(t *testing.T) { 533 assert := tassert.New(t) 534 require := trequire.New(t) 535 536 // Make 3000 random strings 537 r := rand.New(rand.NewSource(42)) 538 input := make([]*query.StringPoint, 0, 3000) 539 for i := 0; i < 3000; i++ { 540 input = append(input, &query.StringPoint{Value: strconv.FormatUint(r.Uint64(), 10)}) 541 } 542 543 // Insert overlapping sections of the same points array to different reducers 544 s1 := query.NewStringSumHllReducer() 545 for _, p := range input[:2000] { 546 s1.AggregateString(p) 547 } 548 point1 := s1.Emit() 549 s2 := query.NewStringSumHllReducer() 550 for _, p := range input[1000:] { 551 s2.AggregateString(p) 552 } 553 point2 := s2.Emit() 554 // Demonstration of the input: repeatably seeded pseudorandom 555 // stringified integers (so we are testing the counting of unique strings, 556 // not unique integers). 557 require.Equal("17190211103962133664", input[2999].Value) 558 559 checkStringFingerprint := func(prefix string, length int, hash string, check string) { 560 assert.Equal(length, len(check)) 561 assert.Equal(prefix, check[:len(prefix)]) 562 h := sha1.New() 563 h.Write([]byte(check)) 564 assert.Equal(hash, fmt.Sprintf("%x", h.Sum(nil))) 565 } 566 567 require.Equal(len(point1), 1) 568 require.Equal(len(point2), 1) 569 checkStringFingerprint("HLL_AhABAAAAAAAAB9BIDQAJAAAUUaKsA4K/AtARkuMBsJwEyp8O", 570 6964, "c59fa799fe8e78ab5347de385bf2a7c5b8085882", point1[0].Value) 571 checkStringFingerprint("HLL_AhABAAAAAAAAB9Db0QAHAAAUaP6aAaSRAoK/Ap70B/xSysEE", 572 6996, "5f1696dfb455baab7fdb56ffd2197d27b09d6dcf", point2[0].Value) 573 574 m := query.NewStringMergeHllReducer() 575 m.AggregateString(&point1[0]) 576 m.AggregateString(&point2[0]) 577 merged := m.Emit() 578 require.Equal(1, len(merged)) 579 checkStringFingerprint("HLL_AhAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAA", 580 87396, "e5320860aa322efe9af268e171df916d2186c75f", merged[0].Value) 581 582 m.AggregateString(&query.StringPoint{ 583 Time: query.ZeroTime, 584 Value: "some random string", 585 }) 586 mergedError := m.Emit() 587 // mid-level errors are: 588 require.Equal(1, len(mergedError)) 589 assert.Equal("HLLERROR bad prefix for hll.Plus", mergedError[0].Value) 590 591 c := query.NewCountHllReducer() 592 c.AggregateString(&merged[0]) 593 counted := c.Emit() 594 require.Equal(1, len(counted)) 595 // Counted 4000 points, 3000 distinct points, answer is 2994 ≈ 3000 596 assert.Equal(uint64(2994), counted[0].Value) 597 598 c.AggregateString(&query.StringPoint{ 599 Time: query.ZeroTime, 600 Value: "HLLERROR bad prefix for hll.Plus", 601 }) 602 counted = c.Emit() 603 require.Equal(1, len(counted)) 604 // When we hit marshal/unmarshal errors 605 assert.Equal(uint64(0), counted[0].Value) 606 }