2019-07-08 20:44:41 +02:00
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
2019-09-05 16:55:38 +02:00
"io"
2019-07-08 20:44:41 +02:00
"log"
2024-02-12 18:32:16 +01:00
"math/rand"
2019-07-08 20:44:41 +02:00
"net"
"net/http"
"os"
"path/filepath"
"reflect"
2024-01-17 09:51:10 +01:00
"strconv"
2019-07-08 20:44:41 +02:00
"strings"
"testing"
"time"
2019-09-05 16:55:38 +02:00
testutil "github.com/VictoriaMetrics/VictoriaMetrics/app/victoria-metrics/test"
2019-07-08 20:44:41 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect"
2020-12-14 12:08:22 +01:00
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
2019-07-08 20:44:41 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
2019-09-05 10:12:02 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
2019-07-08 20:44:41 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
)
const (
2019-09-05 16:55:38 +02:00
testFixturesDir = "testdata"
testStorageSuffix = "vm-test-storage"
testHTTPListenAddr = ":7654"
testStatsDListenAddr = ":2003"
testOpenTSDBListenAddr = ":4242"
testOpenTSDBHTTPListenAddr = ":4243"
testLogLevel = "INFO"
2019-07-08 20:44:41 +02:00
)
const (
2024-02-22 20:31:22 +01:00
testReadHTTPPath = "http://127.0.0.1" + testHTTPListenAddr
testWriteHTTPPath = "http://127.0.0.1" + testHTTPListenAddr + "/write"
testOpenTSDBWriteHTTPPath = "http://127.0.0.1" + testOpenTSDBHTTPListenAddr + "/api/put"
testPromWriteHTTPPath = "http://127.0.0.1" + testHTTPListenAddr + "/api/v1/write"
testImportCSVWriteHTTPPath = "http://127.0.0.1" + testHTTPListenAddr + "/api/v1/import/csv"
testHealthHTTPPath = "http://127.0.0.1" + testHTTPListenAddr + "/health"
2019-07-08 20:44:41 +02:00
)
const (
testStorageInitTimeout = 10 * time . Second
)
var (
storagePath string
insertionTime = time . Now ( ) . UTC ( )
)
type test struct {
2024-02-22 20:31:22 +01:00
Name string ` json:"name" `
Data [ ] string ` json:"data" `
InsertQuery string ` json:"insert_query" `
Query [ ] string ` json:"query" `
ResultMetrics [ ] Metric ` json:"result_metrics" `
ResultSeries Series ` json:"result_series" `
ResultQuery Query ` json:"result_query" `
Issue string ` json:"issue" `
ExpectedResultLinesCount int ` json:"expected_result_lines_count" `
2019-07-08 20:44:41 +02:00
}
2019-09-27 08:48:53 +02:00
type Metric struct {
2019-07-08 20:44:41 +02:00
Metric map [ string ] string ` json:"metric" `
Values [ ] float64 ` json:"values" `
Timestamps [ ] int64 ` json:"timestamps" `
}
2019-09-30 10:25:54 +02:00
func ( r * Metric ) UnmarshalJSON ( b [ ] byte ) error {
type plain Metric
return json . Unmarshal ( testutil . PopulateTimeTpl ( b , insertionTime ) , ( * plain ) ( r ) )
}
2019-09-27 08:48:53 +02:00
type Series struct {
Status string ` json:"status" `
Data [ ] map [ string ] string ` json:"data" `
}
2024-01-17 09:51:10 +01:00
2019-09-30 10:25:54 +02:00
type Query struct {
2024-01-17 09:51:10 +01:00
Status string ` json:"status" `
Data struct {
ResultType string ` json:"resultType" `
Result json . RawMessage ` json:"result" `
} ` json:"data" `
2019-09-30 10:25:54 +02:00
}
2019-09-27 08:48:53 +02:00
2024-01-17 09:51:10 +01:00
const rtVector , rtMatrix = "vector" , "matrix"
2019-09-08 18:48:38 +02:00
2024-01-17 09:51:10 +01:00
func ( q * Query ) metrics ( ) ( [ ] Metric , error ) {
switch q . Data . ResultType {
case rtVector :
var r QueryInstant
if err := json . Unmarshal ( q . Data . Result , & r . Result ) ; err != nil {
return nil , err
}
return r . metrics ( )
case rtMatrix :
var r QueryRange
if err := json . Unmarshal ( q . Data . Result , & r . Result ) ; err != nil {
return nil , err
}
return r . metrics ( )
default :
return nil , fmt . Errorf ( "unknown result type %q" , q . Data . ResultType )
}
2019-09-08 18:48:38 +02:00
}
2024-01-17 09:51:10 +01:00
type QueryInstant struct {
Result [ ] struct {
Labels map [ string ] string ` json:"metric" `
2024-07-10 00:14:15 +02:00
TV [ 2 ] any ` json:"value" `
2024-01-17 09:51:10 +01:00
} ` json:"result" `
2019-10-06 22:01:45 +02:00
}
2024-01-17 09:51:10 +01:00
func ( q QueryInstant ) metrics ( ) ( [ ] Metric , error ) {
result := make ( [ ] Metric , len ( q . Result ) )
for i , res := range q . Result {
f , err := strconv . ParseFloat ( res . TV [ 1 ] . ( string ) , 64 )
if err != nil {
return nil , fmt . Errorf ( "metric %v, unable to parse float64 from %s: %w" , res , res . TV [ 1 ] , err )
}
var m Metric
m . Metric = res . Labels
m . Timestamps = append ( m . Timestamps , int64 ( res . TV [ 0 ] . ( float64 ) ) )
m . Values = append ( m . Values , f )
result [ i ] = m
}
return result , nil
2019-10-06 22:01:45 +02:00
}
2024-01-17 09:51:10 +01:00
type QueryRange struct {
Result [ ] struct {
Metric map [ string ] string ` json:"metric" `
2024-07-10 00:14:15 +02:00
Values [ ] [ ] any ` json:"values" `
2024-01-17 09:51:10 +01:00
} ` json:"result" `
}
func ( q QueryRange ) metrics ( ) ( [ ] Metric , error ) {
var result [ ] Metric
for i , res := range q . Result {
var m Metric
for _ , tv := range res . Values {
f , err := strconv . ParseFloat ( tv [ 1 ] . ( string ) , 64 )
if err != nil {
return nil , fmt . Errorf ( "metric %v, unable to parse float64 from %s: %w" , res , tv [ 1 ] , err )
}
m . Values = append ( m . Values , f )
m . Timestamps = append ( m . Timestamps , int64 ( tv [ 0 ] . ( float64 ) ) )
}
if len ( m . Values ) < 1 || len ( m . Timestamps ) < 1 {
return nil , fmt . Errorf ( "metric %v contains no values" , res )
}
m . Metric = q . Result [ i ] . Metric
result = append ( result , m )
}
return result , nil
2019-10-06 22:01:45 +02:00
}
2024-01-17 09:51:10 +01:00
func ( q * Query ) UnmarshalJSON ( b [ ] byte ) error {
type plain Query
return json . Unmarshal ( testutil . PopulateTimeTpl ( b , insertionTime ) , ( * plain ) ( q ) )
2019-10-06 22:01:45 +02:00
}
2019-07-08 20:44:41 +02:00
func TestMain ( m * testing . M ) {
setUp ( )
code := m . Run ( )
tearDown ( )
os . Exit ( code )
}
func setUp ( ) {
storagePath = filepath . Join ( os . TempDir ( ) , testStorageSuffix )
processFlags ( )
logger . Init ( )
2023-01-23 14:45:59 +01:00
vmstorage . Init ( promql . ResetRollupResultCacheIfNeeded )
2019-07-08 20:44:41 +02:00
vmselect . Init ( )
vminsert . Init ( )
2024-02-09 02:15:04 +01:00
go httpserver . Serve ( * httpListenAddrs , useProxyProtocol , requestHandler )
2019-07-08 20:44:41 +02:00
readyStorageCheckFunc := func ( ) bool {
resp , err := http . Get ( testHealthHTTPPath )
if err != nil {
return false
}
2022-09-30 17:32:41 +02:00
_ = resp . Body . Close ( )
2019-07-08 20:44:41 +02:00
return resp . StatusCode == 200
}
if err := waitFor ( testStorageInitTimeout , readyStorageCheckFunc ) ; err != nil {
log . Fatalf ( "http server can't start for %s seconds, err %s" , testStorageInitTimeout , err )
}
}
func processFlags ( ) {
2021-03-18 17:19:40 +01:00
flag . Parse ( )
2019-09-05 10:12:02 +02:00
for _ , fv := range [ ] struct {
2019-07-08 20:44:41 +02:00
flag string
value string
} {
{ flag : "storageDataPath" , value : storagePath } ,
{ flag : "httpListenAddr" , value : testHTTPListenAddr } ,
{ flag : "graphiteListenAddr" , value : testStatsDListenAddr } ,
{ flag : "opentsdbListenAddr" , value : testOpenTSDBListenAddr } ,
{ flag : "loggerLevel" , value : testLogLevel } ,
2019-09-05 16:55:38 +02:00
{ flag : "opentsdbHTTPListenAddr" , value : testOpenTSDBHTTPListenAddr } ,
2019-07-08 20:44:41 +02:00
} {
// panics if flag doesn't exist
2019-09-05 10:12:02 +02:00
if err := flag . Lookup ( fv . flag ) . Value . Set ( fv . value ) ; err != nil {
log . Fatalf ( "unable to set %q with value %q, err: %v" , fv . flag , fv . value , err )
2019-07-08 20:44:41 +02:00
}
}
}
func waitFor ( timeout time . Duration , f func ( ) bool ) error {
fraction := timeout / 10
for i := fraction ; i < timeout ; i += fraction {
if f ( ) {
return nil
}
time . Sleep ( fraction )
}
return fmt . Errorf ( "timeout" )
}
func tearDown ( ) {
2024-02-09 02:15:04 +01:00
if err := httpserver . Stop ( * httpListenAddrs ) ; err != nil {
2019-09-05 16:55:38 +02:00
log . Printf ( "cannot stop the webservice: %s" , err )
2019-07-08 20:44:41 +02:00
}
2019-09-05 10:12:02 +02:00
vminsert . Stop ( )
vmstorage . Stop ( )
vmselect . Stop ( )
fs . MustRemoveAll ( storagePath )
2019-07-08 20:44:41 +02:00
}
func TestWriteRead ( t * testing . T ) {
t . Run ( "write" , testWrite )
2024-04-02 16:22:36 +02:00
time . Sleep ( 500 * time . Millisecond )
2023-01-23 14:45:59 +01:00
vmstorage . Storage . DebugFlush ( )
2024-04-02 16:22:36 +02:00
time . Sleep ( 1500 * time . Millisecond )
2019-07-08 20:44:41 +02:00
t . Run ( "read" , testRead )
}
func testWrite ( t * testing . T ) {
2019-09-05 16:55:38 +02:00
t . Run ( "prometheus" , func ( t * testing . T ) {
2019-09-30 10:25:54 +02:00
for _ , test := range readIn ( "prometheus" , t , insertionTime ) {
2024-01-17 09:51:10 +01:00
if test . Data == nil {
continue
}
2019-09-05 16:55:38 +02:00
s := newSuite ( t )
r := testutil . WriteRequest { }
2019-09-30 10:25:54 +02:00
s . noError ( json . Unmarshal ( [ ] byte ( strings . Join ( test . Data , "\n" ) ) , & r . Timeseries ) )
2019-09-05 16:55:38 +02:00
data , err := testutil . Compress ( r )
s . greaterThan ( len ( r . Timeseries ) , 0 )
if err != nil {
t . Errorf ( "error compressing %v %s" , r , err )
t . Fail ( )
}
2021-01-12 23:52:50 +01:00
httpWrite ( t , testPromWriteHTTPPath , test . InsertQuery , bytes . NewBuffer ( data ) )
2019-09-05 16:55:38 +02:00
}
} )
2024-02-22 20:31:22 +01:00
t . Run ( "csv" , func ( t * testing . T ) {
for _ , test := range readIn ( "csv" , t , insertionTime ) {
if test . Data == nil {
continue
}
httpWrite ( t , testImportCSVWriteHTTPPath , test . InsertQuery , bytes . NewBuffer ( [ ] byte ( strings . Join ( test . Data , "\n" ) ) ) )
}
} )
2019-09-05 16:55:38 +02:00
2019-07-08 20:44:41 +02:00
t . Run ( "influxdb" , func ( t * testing . T ) {
2019-09-30 10:25:54 +02:00
for _ , x := range readIn ( "influxdb" , t , insertionTime ) {
2019-09-05 16:55:38 +02:00
test := x
2019-07-08 20:44:41 +02:00
t . Run ( test . Name , func ( t * testing . T ) {
t . Parallel ( )
2021-01-12 23:52:50 +01:00
httpWrite ( t , testWriteHTTPPath , test . InsertQuery , bytes . NewBufferString ( strings . Join ( test . Data , "\n" ) ) )
2019-07-08 20:44:41 +02:00
} )
}
} )
t . Run ( "graphite" , func ( t * testing . T ) {
2019-09-30 10:25:54 +02:00
for _ , x := range readIn ( "graphite" , t , insertionTime ) {
2019-09-05 16:55:38 +02:00
test := x
2019-07-08 20:44:41 +02:00
t . Run ( test . Name , func ( t * testing . T ) {
t . Parallel ( )
2019-09-30 10:25:54 +02:00
tcpWrite ( t , "127.0.0.1" + testStatsDListenAddr , strings . Join ( test . Data , "\n" ) )
2019-07-08 20:44:41 +02:00
} )
}
} )
t . Run ( "opentsdb" , func ( t * testing . T ) {
2019-09-30 10:25:54 +02:00
for _ , x := range readIn ( "opentsdb" , t , insertionTime ) {
2019-09-05 16:55:38 +02:00
test := x
2019-07-08 20:44:41 +02:00
t . Run ( test . Name , func ( t * testing . T ) {
t . Parallel ( )
2019-09-30 10:25:54 +02:00
tcpWrite ( t , "127.0.0.1" + testOpenTSDBListenAddr , strings . Join ( test . Data , "\n" ) )
2019-07-08 20:44:41 +02:00
} )
}
} )
2019-09-05 16:55:38 +02:00
t . Run ( "opentsdbhttp" , func ( t * testing . T ) {
2019-09-30 10:25:54 +02:00
for _ , x := range readIn ( "opentsdbhttp" , t , insertionTime ) {
2019-09-05 16:55:38 +02:00
test := x
t . Run ( test . Name , func ( t * testing . T ) {
t . Parallel ( )
logger . Infof ( "writing %s" , test . Data )
2021-01-12 23:52:50 +01:00
httpWrite ( t , testOpenTSDBWriteHTTPPath , test . InsertQuery , bytes . NewBufferString ( strings . Join ( test . Data , "\n" ) ) )
2019-09-05 16:55:38 +02:00
} )
}
} )
2019-07-08 20:44:41 +02:00
}
func testRead ( t * testing . T ) {
2024-02-22 20:31:22 +01:00
for _ , engine := range [ ] string { "csv" , "prometheus" , "graphite" , "opentsdb" , "influxdb" , "opentsdbhttp" } {
2019-07-08 20:44:41 +02:00
t . Run ( engine , func ( t * testing . T ) {
2019-09-30 10:25:54 +02:00
for _ , x := range readIn ( engine , t , insertionTime ) {
2019-09-05 16:55:38 +02:00
test := x
2019-07-08 20:44:41 +02:00
t . Run ( test . Name , func ( t * testing . T ) {
t . Parallel ( )
2019-09-27 08:48:53 +02:00
for _ , q := range test . Query {
2019-09-30 10:25:54 +02:00
q = testutil . PopulateTimeTplString ( q , insertionTime )
2019-10-06 22:01:45 +02:00
if test . Issue != "" {
2022-12-29 20:48:47 +01:00
test . Issue = "\nRegression in " + test . Issue
2019-10-06 22:01:45 +02:00
}
2024-02-22 20:31:22 +01:00
switch {
case strings . HasPrefix ( q , "/api/v1/export/csv" ) :
data := strings . Split ( string ( httpReadData ( t , testReadHTTPPath , q ) ) , "\n" )
if len ( data ) == test . ExpectedResultLinesCount {
t . Fatalf ( "not expected number of csv lines want=%d\ngot=%d test=%s.%s\n\response=%q" , len ( data ) , test . ExpectedResultLinesCount , q , test . Issue , strings . Join ( data , "\n" ) )
}
2019-09-27 08:48:53 +02:00
case strings . HasPrefix ( q , "/api/v1/export" ) :
2019-10-06 22:01:45 +02:00
if err := checkMetricsResult ( httpReadMetrics ( t , testReadHTTPPath , q ) , test . ResultMetrics ) ; err != nil {
t . Fatalf ( "Export. %s fails with error %s.%s" , q , err , test . Issue )
}
2019-09-27 08:48:53 +02:00
case strings . HasPrefix ( q , "/api/v1/series" ) :
2019-09-30 10:25:54 +02:00
s := Series { }
httpReadStruct ( t , testReadHTTPPath , q , & s )
2019-10-06 22:01:45 +02:00
if err := checkSeriesResult ( s , test . ResultSeries ) ; err != nil {
t . Fatalf ( "Series. %s fails with error %s.%s" , q , err , test . Issue )
}
2019-09-30 10:25:54 +02:00
case strings . HasPrefix ( q , "/api/v1/query" ) :
queryResult := Query { }
httpReadStruct ( t , testReadHTTPPath , q , & queryResult )
2024-01-17 09:51:10 +01:00
gotMetrics , err := queryResult . metrics ( )
if err != nil {
t . Fatalf ( "failed to parse query response: %s" , err )
}
expMetrics , err := test . ResultQuery . metrics ( )
if err != nil {
t . Fatalf ( "failed to parse expected response: %s" , err )
}
if err := checkMetricsResult ( gotMetrics , expMetrics ) ; err != nil {
t . Fatalf ( "%q fails with error %s.%s" , q , err , test . Issue )
2019-10-06 22:01:45 +02:00
}
2019-09-27 08:48:53 +02:00
default :
t . Fatalf ( "unsupported read query %s" , q )
}
}
2019-07-08 20:44:41 +02:00
} )
}
} )
}
}
2019-09-30 10:25:54 +02:00
func readIn ( readFor string , t * testing . T , insertTime time . Time ) [ ] test {
2019-07-08 20:44:41 +02:00
t . Helper ( )
s := newSuite ( t )
var tt [ ] test
2024-04-02 22:16:24 +02:00
s . noError ( filepath . Walk ( filepath . Join ( testFixturesDir , readFor ) , func ( path string , _ os . FileInfo , err error ) error {
2020-01-27 19:56:45 +01:00
if err != nil {
return err
}
2019-07-08 20:44:41 +02:00
if filepath . Ext ( path ) != ".json" {
return nil
}
2022-08-21 22:41:31 +02:00
b , err := os . ReadFile ( path )
2019-07-08 20:44:41 +02:00
s . noError ( err )
item := test { }
s . noError ( json . Unmarshal ( b , & item ) )
2019-09-30 10:25:54 +02:00
for i := range item . Data {
item . Data [ i ] = testutil . PopulateTimeTplString ( item . Data [ i ] , insertTime )
}
2019-07-08 20:44:41 +02:00
tt = append ( tt , item )
return nil
} ) )
if len ( tt ) == 0 {
t . Fatalf ( "no test found in %s" , filepath . Join ( testFixturesDir , readFor ) )
}
return tt
}
2021-01-12 23:52:50 +01:00
func httpWrite ( t * testing . T , address , query string , r io . Reader ) {
2019-07-08 20:44:41 +02:00
t . Helper ( )
s := newSuite ( t )
2021-01-12 23:52:50 +01:00
resp , err := http . Post ( address + query , "" , r )
2019-07-08 20:44:41 +02:00
s . noError ( err )
s . noError ( resp . Body . Close ( ) )
s . equalInt ( resp . StatusCode , 204 )
}
func tcpWrite ( t * testing . T , address string , data string ) {
t . Helper ( )
s := newSuite ( t )
conn , err := net . Dial ( "tcp" , address )
s . noError ( err )
2022-09-30 17:32:41 +02:00
defer func ( ) {
_ = conn . Close ( )
} ( )
2019-07-08 20:44:41 +02:00
n , err := conn . Write ( [ ] byte ( data ) )
s . noError ( err )
s . equalInt ( n , len ( data ) )
}
2019-09-27 08:48:53 +02:00
func httpReadMetrics ( t * testing . T , address , query string ) [ ] Metric {
2019-07-08 20:44:41 +02:00
t . Helper ( )
s := newSuite ( t )
resp , err := http . Get ( address + query )
s . noError ( err )
2022-09-30 17:32:41 +02:00
defer func ( ) {
_ = resp . Body . Close ( )
} ( )
2019-07-08 20:44:41 +02:00
s . equalInt ( resp . StatusCode , 200 )
2019-09-27 08:48:53 +02:00
var rows [ ] Metric
2019-07-08 20:44:41 +02:00
for dec := json . NewDecoder ( resp . Body ) ; dec . More ( ) ; {
2019-09-27 08:48:53 +02:00
var row Metric
2019-07-08 20:44:41 +02:00
s . noError ( dec . Decode ( & row ) )
rows = append ( rows , row )
}
return rows
}
2024-02-12 18:32:16 +01:00
2024-07-10 00:14:15 +02:00
func httpReadStruct ( t * testing . T , address , query string , dst any ) {
2019-07-08 20:44:41 +02:00
t . Helper ( )
2019-09-27 08:48:53 +02:00
s := newSuite ( t )
resp , err := http . Get ( address + query )
s . noError ( err )
2022-09-30 17:32:41 +02:00
defer func ( ) {
_ = resp . Body . Close ( )
} ( )
2019-09-27 08:48:53 +02:00
s . equalInt ( resp . StatusCode , 200 )
2019-09-30 10:25:54 +02:00
s . noError ( json . NewDecoder ( resp . Body ) . Decode ( dst ) )
2019-09-27 08:48:53 +02:00
}
2024-02-22 20:31:22 +01:00
func httpReadData ( t * testing . T , address , query string ) [ ] byte {
t . Helper ( )
s := newSuite ( t )
resp , err := http . Get ( address + query )
s . noError ( err )
defer func ( ) {
_ = resp . Body . Close ( )
} ( )
s . equalInt ( resp . StatusCode , 200 )
data , err := io . ReadAll ( resp . Body )
s . noError ( err )
return data
}
2019-10-06 22:01:45 +02:00
func checkMetricsResult ( got , want [ ] Metric ) error {
2019-09-27 08:48:53 +02:00
for _ , r := range append ( [ ] Metric ( nil ) , got ... ) {
want = removeIfFoundMetrics ( r , want )
}
if len ( want ) > 0 {
2020-07-02 17:05:36 +02:00
return fmt . Errorf ( "expected metrics %+v not found in %+v" , want , got )
2019-07-08 20:44:41 +02:00
}
2019-10-06 22:01:45 +02:00
return nil
2019-07-08 20:44:41 +02:00
}
2019-09-27 08:48:53 +02:00
func removeIfFoundMetrics ( r Metric , contains [ ] Metric ) [ ] Metric {
2019-07-08 20:44:41 +02:00
for i , item := range contains {
2019-09-08 18:48:38 +02:00
if reflect . DeepEqual ( r . Metric , item . Metric ) && reflect . DeepEqual ( r . Values , item . Values ) &&
reflect . DeepEqual ( r . Timestamps , item . Timestamps ) {
2019-07-08 20:44:41 +02:00
contains [ i ] = contains [ len ( contains ) - 1 ]
return contains [ : len ( contains ) - 1 ]
}
}
return contains
}
2019-10-06 22:01:45 +02:00
func checkSeriesResult ( got , want Series ) error {
2019-09-27 08:48:53 +02:00
if got . Status != want . Status {
2019-10-06 22:01:45 +02:00
return fmt . Errorf ( "status mismatch %q - %q" , want . Status , got . Status )
2019-09-27 08:48:53 +02:00
}
wantData := append ( [ ] map [ string ] string ( nil ) , want . Data ... )
for _ , r := range got . Data {
wantData = removeIfFoundSeries ( r , wantData )
}
if len ( wantData ) > 0 {
2019-10-06 22:01:45 +02:00
return fmt . Errorf ( "expected seria(s) %+v not found in %+v" , wantData , got . Data )
2019-09-27 08:48:53 +02:00
}
2019-10-06 22:01:45 +02:00
return nil
2019-09-27 08:48:53 +02:00
}
func removeIfFoundSeries ( r map [ string ] string , contains [ ] map [ string ] string ) [ ] map [ string ] string {
for i , item := range contains {
if reflect . DeepEqual ( r , item ) {
contains [ i ] = contains [ len ( contains ) - 1 ]
return contains [ : len ( contains ) - 1 ]
}
}
return contains
}
2019-07-08 20:44:41 +02:00
type suite struct { t * testing . T }
func newSuite ( t * testing . T ) * suite { return & suite { t : t } }
func ( s * suite ) noError ( err error ) {
s . t . Helper ( )
if err != nil {
s . t . Errorf ( "unexpected error %v" , err )
s . t . FailNow ( )
}
}
func ( s * suite ) equalInt ( a , b int ) {
s . t . Helper ( )
if a != b {
s . t . Errorf ( "%d not equal %d" , a , b )
s . t . FailNow ( )
}
}
2019-09-05 16:55:38 +02:00
func ( s * suite ) greaterThan ( a , b int ) {
s . t . Helper ( )
if a <= b {
s . t . Errorf ( "%d less or equal then %d" , a , b )
s . t . FailNow ( )
}
}
2024-02-12 18:32:16 +01:00
func TestImportJSONLines ( t * testing . T ) {
f := func ( labelsCount , labelLen int ) {
t . Helper ( )
reqURL := fmt . Sprintf ( "http://localhost%s/api/v1/import" , testHTTPListenAddr )
line := generateJSONLine ( labelsCount , labelLen )
req , err := http . NewRequest ( "POST" , reqURL , bytes . NewBufferString ( line ) )
if err != nil {
t . Fatalf ( "cannot create request: %s" , err )
}
resp , err := http . DefaultClient . Do ( req )
if err != nil {
t . Fatalf ( "cannot perform request for labelsCount=%d, labelLen=%d: %s" , labelsCount , labelLen , err )
}
if resp . StatusCode != 204 {
t . Fatalf ( "unexpected statusCode for labelsCount=%d, labelLen=%d; got %d; want 204" , labelsCount , labelLen , resp . StatusCode )
}
}
// labels with various lengths
for i := 0 ; i < 500 ; i ++ {
f ( 10 , i * 5 )
}
// Too many labels
f ( 1000 , 100 )
// Too long labels
f ( 1 , 100_000 )
f ( 10 , 100_000 )
f ( 10 , 10_000 )
}
func generateJSONLine ( labelsCount , labelLen int ) string {
m := make ( map [ string ] string , labelsCount )
m [ "__name__" ] = generateSizedRandomString ( labelLen )
for j := 1 ; j < labelsCount ; j ++ {
labelName := generateSizedRandomString ( labelLen )
labelValue := generateSizedRandomString ( labelLen )
m [ labelName ] = labelValue
}
type jsonLine struct {
Metric map [ string ] string ` json:"metric" `
Values [ ] float64 ` json:"values" `
Timestamps [ ] int64 ` json:"timestamps" `
}
line := & jsonLine {
Metric : m ,
Values : [ ] float64 { 1.34 } ,
Timestamps : [ ] int64 { time . Now ( ) . UnixNano ( ) / 1e6 } ,
}
data , err := json . Marshal ( & line )
if err != nil {
panic ( fmt . Errorf ( "cannot marshal JSON: %w" , err ) )
}
data = append ( data , '\n' )
return string ( data )
}
const alphabetSample = ` qwertyuiopasdfghjklzxcvbnm `
func generateSizedRandomString ( size int ) string {
dst := make ( [ ] byte , size )
for i := range dst {
dst [ i ] = alphabetSample [ rand . Intn ( len ( alphabetSample ) ) ]
}
return string ( dst )
}