228 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
| package cratedb
 | |
| 
 | |
| import (
 | |
| 	"database/sql"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/influxdata/telegraf"
 | |
| 	"github.com/influxdata/telegraf/internal"
 | |
| 	"github.com/influxdata/telegraf/metric"
 | |
| 	"github.com/influxdata/telegraf/testutil"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func TestConnectAndWrite(t *testing.T) {
 | |
| 	if testing.Short() {
 | |
| 		t.Skip("Skipping integration test in short mode")
 | |
| 	}
 | |
| 
 | |
| 	if os.Getenv("CIRCLE_PROJECT_REPONAME") != "" {
 | |
| 		t.Skip("Skipping test on CircleCI due to docker failures")
 | |
| 	}
 | |
| 
 | |
| 	url := testURL()
 | |
| 	table := "test"
 | |
| 
 | |
| 	// dropSQL drops our table before each test. This simplifies changing the
 | |
| 	// schema during development :).
 | |
| 	dropSQL := "DROP TABLE IF EXISTS " + escapeString(table, `"`)
 | |
| 	db, err := sql.Open("pgx", url)
 | |
| 	require.NoError(t, err)
 | |
| 	_, err = db.Exec(dropSQL)
 | |
| 	require.NoError(t, err)
 | |
| 	defer db.Close()
 | |
| 
 | |
| 	c := &CrateDB{
 | |
| 		URL:         url,
 | |
| 		Table:       table,
 | |
| 		Timeout:     internal.Duration{Duration: time.Second * 5},
 | |
| 		TableCreate: true,
 | |
| 	}
 | |
| 
 | |
| 	metrics := testutil.MockMetrics()
 | |
| 	require.NoError(t, c.Connect())
 | |
| 	require.NoError(t, c.Write(metrics))
 | |
| 
 | |
| 	// The code below verifies that the metrics were written. We have to select
 | |
| 	// the rows using their primary keys in order to take advantage of
 | |
| 	// read-after-write consistency in CrateDB.
 | |
| 	for _, m := range metrics {
 | |
| 		hashIDVal, err := escapeValue(hashID(m))
 | |
| 		require.NoError(t, err)
 | |
| 		timestamp, err := escapeValue(m.Time())
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		var id int64
 | |
| 		row := db.QueryRow(
 | |
| 			"SELECT hash_id FROM " + escapeString(table, `"`) + " " +
 | |
| 				"WHERE hash_id = " + hashIDVal + " " +
 | |
| 				"AND timestamp = " + timestamp,
 | |
| 		)
 | |
| 		require.NoError(t, row.Scan(&id))
 | |
| 		// We could check the whole row, but this is meant to be more of a smoke
 | |
| 		// test, so just checking the HashID seems fine.
 | |
| 		require.Equal(t, id, hashID(m))
 | |
| 	}
 | |
| 
 | |
| 	require.NoError(t, c.Close())
 | |
| }
 | |
| 
 | |
| func Test_insertSQL(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		Metrics []telegraf.Metric
 | |
| 		Want    string
 | |
| 	}{
 | |
| 		{
 | |
| 			Metrics: testutil.MockMetrics(),
 | |
| 			Want: strings.TrimSpace(`
 | |
| INSERT INTO my_table ("hash_id", "timestamp", "name", "tags", "fields")
 | |
| VALUES
 | |
| (-4023501406646044814, '2009-11-10T23:00:00+0000', 'test1', {"tag1" = 'value1'}, {"value" = 1});
 | |
| `),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		if got, err := insertSQL("my_table", test.Metrics); err != nil {
 | |
| 			t.Error(err)
 | |
| 		} else if got != test.Want {
 | |
| 			t.Errorf("got:\n%s\n\nwant:\n%s", got, test.Want)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func Test_escapeValue(t *testing.T) {
 | |
| 	if testing.Short() {
 | |
| 		t.Skip("Skipping integration test in short mode")
 | |
| 	}
 | |
| 
 | |
| 	if os.Getenv("CIRCLE_PROJECT_REPONAME") != "" {
 | |
| 		t.Skip("Skipping test on CircleCI due to docker failures")
 | |
| 	}
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		Val  interface{}
 | |
| 		Want string
 | |
| 	}{
 | |
| 		// string
 | |
| 		{`foo`, `'foo'`},
 | |
| 		{`foo'bar 'yeah`, `'foo''bar ''yeah'`},
 | |
| 		// int types
 | |
| 		{int64(123), `123`},
 | |
| 		{uint64(123), `123`},
 | |
| 		{uint64(MaxInt64) + 1, `9223372036854775807`},
 | |
| 		{true, `true`},
 | |
| 		{false, `false`},
 | |
| 		// float types
 | |
| 		{float64(123.456), `123.456`},
 | |
| 		// time.Time
 | |
| 		{time.Date(2017, 8, 7, 16, 44, 52, 123*1000*1000, time.FixedZone("Dreamland", 5400)), `'2017-08-07T16:44:52.123+0130'`},
 | |
| 		// map[string]string
 | |
| 		{map[string]string{}, `{}`},
 | |
| 		{map[string]string(nil), `{}`},
 | |
| 		{map[string]string{"foo": "bar"}, `{"foo" = 'bar'}`},
 | |
| 		{map[string]string{"foo": "bar", "one": "more"}, `{"foo" = 'bar', "one" = 'more'}`},
 | |
| 		// map[string]interface{}
 | |
| 		{map[string]interface{}{}, `{}`},
 | |
| 		{map[string]interface{}(nil), `{}`},
 | |
| 		{map[string]interface{}{"foo": "bar"}, `{"foo" = 'bar'}`},
 | |
| 		{map[string]interface{}{"foo": "bar", "one": "more"}, `{"foo" = 'bar', "one" = 'more'}`},
 | |
| 		{map[string]interface{}{"foo": map[string]interface{}{"one": "more"}}, `{"foo" = {"one" = 'more'}}`},
 | |
| 		{map[string]interface{}{`fo"o`: `b'ar`, `ab'c`: `xy"z`, `on"""e`: `mo'''re`}, `{"ab'c" = 'xy"z', "fo""o" = 'b''ar', "on""""""e" = 'mo''''''re'}`},
 | |
| 	}
 | |
| 
 | |
| 	url := testURL()
 | |
| 	db, err := sql.Open("pgx", url)
 | |
| 	require.NoError(t, err)
 | |
| 	defer db.Close()
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		got, err := escapeValue(test.Val)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("val: %#v: %s", test.Val, err)
 | |
| 		} else if got != test.Want {
 | |
| 			t.Errorf("got:\n%s\n\nwant:\n%s", got, test.Want)
 | |
| 		}
 | |
| 
 | |
| 		// This is a smoke test that will blow up if our escaping causing a SQL
 | |
| 		// syntax error, which may allow for an attack.
 | |
| 		var reply interface{}
 | |
| 		row := db.QueryRow("SELECT " + got)
 | |
| 		require.NoError(t, row.Scan(&reply))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func Test_hashID(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		Name   string
 | |
| 		Tags   map[string]string
 | |
| 		Fields map[string]interface{}
 | |
| 		Want   int64
 | |
| 	}{
 | |
| 		{
 | |
| 			Name:   "metric1",
 | |
| 			Tags:   map[string]string{"tag1": "val1", "tag2": "val2"},
 | |
| 			Fields: map[string]interface{}{"field1": "val1", "field2": "val2"},
 | |
| 			Want:   8973971082006474188,
 | |
| 		},
 | |
| 
 | |
| 		// This metric has a different tag order (in a perhaps non-ideal attempt to
 | |
| 		// trigger different pseudo-random map iteration)) and fields (none)
 | |
| 		// compared to the previous metric, but should still get the same hash.
 | |
| 		{
 | |
| 			Name:   "metric1",
 | |
| 			Tags:   map[string]string{"tag2": "val2", "tag1": "val1"},
 | |
| 			Fields: map[string]interface{}{"field3": "val3"},
 | |
| 			Want:   8973971082006474188,
 | |
| 		},
 | |
| 
 | |
| 		// Different metric name -> different hash
 | |
| 		{
 | |
| 			Name:   "metric2",
 | |
| 			Tags:   map[string]string{"tag1": "val1", "tag2": "val2"},
 | |
| 			Fields: map[string]interface{}{"field1": "val1", "field2": "val2"},
 | |
| 			Want:   306487682448261783,
 | |
| 		},
 | |
| 
 | |
| 		// Different tag val -> different hash
 | |
| 		{
 | |
| 			Name:   "metric1",
 | |
| 			Tags:   map[string]string{"tag1": "new-val", "tag2": "val2"},
 | |
| 			Fields: map[string]interface{}{"field1": "val1", "field2": "val2"},
 | |
| 			Want:   1938713695181062970,
 | |
| 		},
 | |
| 
 | |
| 		// Different tag key -> different hash
 | |
| 		{
 | |
| 			Name:   "metric1",
 | |
| 			Tags:   map[string]string{"new-key": "val1", "tag2": "val2"},
 | |
| 			Fields: map[string]interface{}{"field1": "val1", "field2": "val2"},
 | |
| 			Want:   7678889081527706328,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for i, test := range tests {
 | |
| 		m, err := metric.New(
 | |
| 			test.Name,
 | |
| 			test.Tags,
 | |
| 			test.Fields,
 | |
| 			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
 | |
| 		)
 | |
| 		require.NoError(t, err)
 | |
| 		if got := hashID(m); got != test.Want {
 | |
| 			t.Errorf("test #%d: got=%d want=%d", i, got, test.Want)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testURL() string {
 | |
| 	url := os.Getenv("CRATE_URL")
 | |
| 	if url == "" {
 | |
| 		return "postgres://" + testutil.GetLocalHost() + ":6543/test?sslmode=disable"
 | |
| 	}
 | |
| 	return url
 | |
| }
 |