Unified Storage: Testing For Fix Create, Update and Delete wrt Resource Versions (#88568)

Add testing harness
This commit is contained in:
Diego Augusto Molina
2024-06-05 15:18:33 -03:00
committed by GitHub
parent 6fcd7d9e03
commit 5fc580b401
66 changed files with 4416 additions and 322 deletions
@@ -1,49 +1,92 @@
package dbimpl
import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetEnginePostgresFromConfig(t *testing.T) {
cfg := setting.NewCfg()
s, err := cfg.Raw.NewSection("entity_api")
require.NoError(t, err)
s.Key("db_type").SetValue("mysql")
s.Key("db_host").SetValue("localhost")
s.Key("db_name").SetValue("grafana")
s.Key("db_user").SetValue("user")
s.Key("db_password").SetValue("password")
getter := &sectionGetter{
DynamicSection: cfg.SectionWithEnvOverrides("entity_api"),
}
engine, err := getEnginePostgres(getter, nil)
assert.NotNil(t, engine)
assert.NoError(t, err)
assert.True(t, strings.Contains(engine.DataSourceName(), "dbname=grafana"))
}
func TestGetEngineMySQLFromConfig(t *testing.T) {
cfg := setting.NewCfg()
s, err := cfg.Raw.NewSection("entity_api")
require.NoError(t, err)
s.Key("db_type").SetValue("mysql")
s.Key("db_host").SetValue("localhost")
s.Key("db_name").SetValue("grafana")
s.Key("db_user").SetValue("user")
s.Key("db_password").SetValue("password")
t.Parallel()
getter := &sectionGetter{
DynamicSection: cfg.SectionWithEnvOverrides("entity_api"),
}
engine, err := getEngineMySQL(getter, nil)
t.Run("happy path", func(t *testing.T) {
t.Parallel()
assert.NotNil(t, engine)
assert.NoError(t, err)
getter := newTestSectionGetter(map[string]string{
"db_type": "mysql",
"db_host": "/var/run/mysql.socket",
"db_name": "grafana",
"db_user": "user",
"db_password": "password",
})
engine, err := getEngineMySQL(getter, nil)
assert.NotNil(t, engine)
assert.NoError(t, err)
})
t.Run("invalid string", func(t *testing.T) {
t.Parallel()
getter := newTestSectionGetter(map[string]string{
"db_type": "mysql",
"db_host": "/var/run/mysql.socket",
"db_name": string(invalidUTF8ByteSequence),
"db_user": "user",
"db_password": "password",
})
engine, err := getEngineMySQL(getter, nil)
assert.Nil(t, engine)
assert.Error(t, err)
assert.ErrorIs(t, err, ErrInvalidUTF8Sequence)
})
}
func TestGetEnginePostgresFromConfig(t *testing.T) {
t.Parallel()
t.Run("happy path", func(t *testing.T) {
t.Parallel()
getter := newTestSectionGetter(map[string]string{
"db_type": "mysql",
"db_host": "localhost",
"db_name": "grafana",
"db_user": "user",
"db_password": "password",
})
engine, err := getEnginePostgres(getter, nil)
assert.NotNil(t, engine)
assert.NoError(t, err)
})
t.Run("invalid string", func(t *testing.T) {
t.Parallel()
getter := newTestSectionGetter(map[string]string{
"db_type": "mysql",
"db_host": string(invalidUTF8ByteSequence),
"db_name": "grafana",
"db_user": "user",
"db_password": "password",
})
engine, err := getEnginePostgres(getter, nil)
assert.Nil(t, engine)
assert.Error(t, err)
assert.ErrorIs(t, err, ErrInvalidUTF8Sequence)
})
t.Run("invalid hostport", func(t *testing.T) {
t.Parallel()
getter := newTestSectionGetter(map[string]string{
"db_type": "mysql",
"db_host": "1:1:1",
"db_name": "grafana",
"db_user": "user",
"db_password": "password",
})
engine, err := getEnginePostgres(getter, nil)
assert.Nil(t, engine)
assert.Error(t, err)
})
}
@@ -2,6 +2,7 @@ package dbimpl
import (
"context"
"errors"
"testing"
"time"
@@ -29,6 +30,44 @@ func newCtx(t *testing.T) context.Context {
return ctx
}
var errTest = errors.New("because of reasons")
const driverName = "sqlmock"
func TestDB_BeginTx(t *testing.T) {
t.Parallel()
t.Run("happy path", func(t *testing.T) {
t.Parallel()
sqldb, mock, err := sqlmock.New()
require.NoError(t, err)
db := NewDB(sqldb, driverName)
require.Equal(t, driverName, db.DriverName())
mock.ExpectBegin()
tx, err := db.BeginTx(newCtx(t), nil)
require.NoError(t, err)
require.NotNil(t, tx)
})
t.Run("fail begin", func(t *testing.T) {
t.Parallel()
sqldb, mock, err := sqlmock.New()
require.NoError(t, err)
db := NewDB(sqldb, "sqlmock")
mock.ExpectBegin().WillReturnError(errTest)
tx, err := db.BeginTx(newCtx(t), nil)
require.Nil(t, tx)
require.Error(t, err)
require.ErrorIs(t, err, errTest)
})
}
func TestDB_WithTx(t *testing.T) {
t.Parallel()
@@ -51,4 +90,65 @@ func TestDB_WithTx(t *testing.T) {
require.NoError(t, err)
})
t.Run("fail begin", func(t *testing.T) {
t.Parallel()
sqldb, mock, err := sqlmock.New()
require.NoError(t, err)
db := NewDB(sqldb, "sqlmock")
mock.ExpectBegin().WillReturnError(errTest)
err = db.WithTx(newCtx(t), nil, newTxFunc(nil))
require.Error(t, err)
require.ErrorIs(t, err, errTest)
})
t.Run("fail tx", func(t *testing.T) {
t.Parallel()
sqldb, mock, err := sqlmock.New()
require.NoError(t, err)
db := NewDB(sqldb, "sqlmock")
mock.ExpectBegin()
mock.ExpectRollback()
err = db.WithTx(newCtx(t), nil, newTxFunc(errTest))
require.Error(t, err)
require.ErrorIs(t, err, errTest)
})
t.Run("fail tx; fail rollback", func(t *testing.T) {
t.Parallel()
sqldb, mock, err := sqlmock.New()
require.NoError(t, err)
db := NewDB(sqldb, "sqlmock")
errTest2 := errors.New("yet another err")
mock.ExpectBegin()
mock.ExpectRollback().WillReturnError(errTest)
err = db.WithTx(newCtx(t), nil, newTxFunc(errTest2))
require.Error(t, err)
require.ErrorIs(t, err, errTest)
require.ErrorIs(t, err, errTest2)
})
t.Run("fail commit", func(t *testing.T) {
t.Parallel()
sqldb, mock, err := sqlmock.New()
require.NoError(t, err)
db := NewDB(sqldb, "sqlmock")
mock.ExpectBegin()
mock.ExpectCommit().WillReturnError(errTest)
err = db.WithTx(newCtx(t), nil, newTxFunc(nil))
require.Error(t, err)
require.ErrorIs(t, err, errTest)
})
}
@@ -44,15 +44,18 @@ type EntityDB struct {
}
func (db *EntityDB) Init() error {
_, err := db.GetEngine()
return err
}
func (db *EntityDB) GetEngine() (*xorm.Engine, error) {
db.once.Do(func() {
db.onceErr = db.init()
})
return db.onceErr
}
func (db *EntityDB) GetEngine() (*xorm.Engine, error) {
if err := db.Init(); err != nil {
return nil, err
}
return db.engine, db.onceErr
}
+16 -4
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net"
"sort"
"strings"
"unicode/utf8"
@@ -25,9 +26,6 @@ func (g *sectionGetter) Err() error {
}
func (g *sectionGetter) String(key string) string {
if g.err != nil {
return ""
}
v := g.DynamicSection.Key(key).MustString("")
if !utf8.ValidString(v) {
g.err = fmt.Errorf("value for key %q: %w", key, ErrInvalidUTF8Sequence)
@@ -43,7 +41,10 @@ func (g *sectionGetter) String(key string) string {
func MakeDSN(m map[string]string) (string, error) {
b := new(strings.Builder)
for k, v := range m {
ks := keys(m)
sort.Strings(ks) // provide deterministic behaviour
for _, k := range ks {
v := m[k]
if !utf8.ValidString(v) {
return "", fmt.Errorf("value for DSN key %q: %w", k,
ErrInvalidUTF8Sequence)
@@ -63,6 +64,14 @@ func MakeDSN(m map[string]string) (string, error) {
return b.String(), nil
}
func keys(m map[string]string) []string {
ret := make([]string, 0, len(m))
for k := range m {
ret = append(ret, k)
}
return ret
}
func writeDSNValue(b *strings.Builder, v string) {
numq := strings.Count(v, `'`)
numb := strings.Count(v, `\`)
@@ -83,6 +92,9 @@ func writeDSNValue(b *strings.Builder, v string) {
_ = b.WriteByte('\'')
}
// splitHostPortDefault is similar to net.SplitHostPort, but will also accept a
// specification with no port and apply the default port instead. It also
// applies the given defaults if the results are empty strings.
func splitHostPortDefault(hostport, defaultHost, defaultPort string) (string, string, error) {
host, port, err := net.SplitHostPort(hostport)
if err != nil {
@@ -0,0 +1,108 @@
package dbimpl
import (
"fmt"
"testing"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
var invalidUTF8ByteSequence = []byte{0xff, 0xfe, 0xfd}
func setSectionKeyValues(section *setting.DynamicSection, m map[string]string) {
for k, v := range m {
section.Key(k).SetValue(v)
}
}
func newTestSectionGetter(m map[string]string) *sectionGetter {
section := setting.NewCfg().SectionWithEnvOverrides("entity_api")
setSectionKeyValues(section, m)
return &sectionGetter{
DynamicSection: section,
}
}
func TestSectionGetter(t *testing.T) {
t.Parallel()
var (
key = "the key"
val = string(invalidUTF8ByteSequence)
)
g := newTestSectionGetter(map[string]string{
key: val,
})
v := g.String("whatever")
require.Empty(t, v)
require.NoError(t, g.Err())
v = g.String(key)
require.Empty(t, v)
require.Error(t, g.Err())
require.ErrorIs(t, g.Err(), ErrInvalidUTF8Sequence)
}
func TestMakeDSN(t *testing.T) {
t.Parallel()
s, err := MakeDSN(map[string]string{
"db_name": string(invalidUTF8ByteSequence),
})
require.Empty(t, s)
require.Error(t, err)
require.ErrorIs(t, err, ErrInvalidUTF8Sequence)
s, err = MakeDSN(map[string]string{
"skip": "",
"user": `shou'ld esc\ape`,
"pass": "noescape",
})
require.NoError(t, err)
require.Equal(t, `pass=noescape user='shou\'ld esc\\ape'`, s)
}
func TestSplitHostPort(t *testing.T) {
t.Parallel()
testCases := []struct {
hostport string
defaultHost string
defaultPort string
fails bool
host string
port string
}{
{hostport: "192.168.0.140:456", defaultHost: "", defaultPort: "", host: "192.168.0.140", port: "456"},
{hostport: "192.168.0.140", defaultHost: "", defaultPort: "123", host: "192.168.0.140", port: "123"},
{hostport: "[::1]:456", defaultHost: "", defaultPort: "", host: "::1", port: "456"},
{hostport: "[::1]", defaultHost: "", defaultPort: "123", host: "::1", port: "123"},
{hostport: ":456", defaultHost: "1.2.3.4", defaultPort: "", host: "1.2.3.4", port: "456"},
{hostport: "xyz.rds.amazonaws.com", defaultHost: "", defaultPort: "123", host: "xyz.rds.amazonaws.com", port: "123"},
{hostport: "xyz.rds.amazonaws.com:123", defaultHost: "", defaultPort: "", host: "xyz.rds.amazonaws.com", port: "123"},
{hostport: "", defaultHost: "localhost", defaultPort: "1433", host: "localhost", port: "1433"},
{hostport: "1:1:1", fails: true},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("test index #%d", i), func(t *testing.T) {
t.Parallel()
host, port, err := splitHostPortDefault(tc.hostport, tc.defaultHost, tc.defaultPort)
if tc.fails {
require.Error(t, err)
require.Empty(t, host)
require.Empty(t, port)
} else {
require.NoError(t, err)
require.Equal(t, tc.host, host)
require.Equal(t, tc.port, port)
}
})
}
}