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  }