Unistore Chore: Add OTEL testing harness (#94835)

* add testing harness

* fix mockery and linters

* fix data race in tests

* fix data race in tests

* reduce cardinality of data
This commit is contained in:
Diego Augusto Molina 2024-10-17 08:41:06 -03:00 committed by GitHub
parent 8b9bb2acf6
commit cf08f6762d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 757 additions and 42 deletions

View File

@ -357,7 +357,7 @@ func (b *backend) ListIterator(ctx context.Context, req *resource.ListRequest, c
}
type listIter struct {
rows *sql.Rows
rows db.Rows
offset int64
listRV int64

View File

@ -28,6 +28,30 @@ func (d sqlDB) DriverName() string {
return d.driverName
}
func (d sqlDB) BeginTx(ctx context.Context, opts *sql.TxOptions) (db.Tx, error) {
return d.DB.BeginTx(ctx, opts)
func (d sqlDB) QueryContext(ctx context.Context, query string, args ...any) (db.Rows, error) {
return d.DB.QueryContext(ctx, query, args...)
}
func (d sqlDB) QueryRowContext(ctx context.Context, query string, args ...any) db.Row {
return d.DB.QueryRowContext(ctx, query, args...)
}
func (d sqlDB) BeginTx(ctx context.Context, opts *sql.TxOptions) (db.Tx, error) {
tx, err := d.DB.BeginTx(ctx, opts)
if err != nil {
return nil, err
}
return sqlTx{tx}, err
}
type sqlTx struct {
*sql.Tx
}
func (d sqlTx) QueryContext(ctx context.Context, query string, args ...any) (db.Rows, error) {
return d.Tx.QueryContext(ctx, query, args...)
}
func (d sqlTx) QueryRowContext(ctx context.Context, query string, args ...any) db.Row {
return d.Tx.QueryRowContext(ctx, query, args...)
}

View File

@ -11,6 +11,7 @@ import (
func TestDB_BeginTx(t *testing.T) {
t.Parallel()
registerTestSQLDrivers()
ctx := testutil.NewDefaultTestContext(t)
sqlDB, err := sql.Open(driverWithIsolationLevelName, "")

View File

@ -60,6 +60,7 @@ type resourceDBProvider struct {
cfg *setting.Cfg
log log.Logger
migrateFunc func(context.Context, *xorm.Engine, *setting.Cfg) error
tracer trace.Tracer
registerMetrics bool
logQueries bool
}
@ -77,6 +78,7 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer trace.
log: log.New("entity-db"),
logQueries: getter.Bool("log_queries"),
migrateFunc: migrations.MigrateResourceStore,
tracer: tracer,
}
dbType := getter.String("type")
@ -145,5 +147,7 @@ func (p *resourceDBProvider) init(ctx context.Context) (db.DB, error) {
}
}
return NewDB(p.engine.DB().DB, p.engine.Dialect().DriverName()), nil
d := NewDB(p.engine.DB().DB, p.engine.Dialect().DriverName())
return d, nil
}

View File

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"net/http"
"sync"
"testing"
"github.com/google/uuid"
@ -111,8 +112,18 @@ func newTestInfraDB(t *testing.T, m cfgMap) infraDB.DB {
return sqlstoreDB
}
// globalUnprotectedMutableState controls access to global mutable state found
// in the `setting` package that is not appropriately protected. This would
// ideally be a part of some struct instead of being global, be protected if it
// needs to change, and be unmutable once it no longer needs to change. Example:
// `setting.AppUrl`. Nothing can run in parallel because of this.
// TODO: fix that.
var globalUnprotectedMutableState sync.Mutex
func newCfgFromIniMap(t *testing.T, m cfgMap) *setting.Cfg {
t.Helper()
globalUnprotectedMutableState.Lock()
defer globalUnprotectedMutableState.Unlock()
cfg, err := setting.NewCfgFromINIFile(newTestINIFile(t, m))
require.NoError(t, err)
return cfg

View File

@ -245,7 +245,7 @@ func (_c *DB_PingContext_Call) RunAndReturn(run func(context.Context) error) *DB
}
// QueryContext provides a mock function with given fields: ctx, query, args
func (_m *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
func (_m *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (db.Rows, error) {
var _ca []interface{}
_ca = append(_ca, ctx, query)
_ca = append(_ca, args...)
@ -255,16 +255,16 @@ func (_m *DB) QueryContext(ctx context.Context, query string, args ...interface{
panic("no return value specified for QueryContext")
}
var r0 *sql.Rows
var r0 db.Rows
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (*sql.Rows, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (db.Rows, error)); ok {
return rf(ctx, query, args...)
}
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Rows); ok {
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) db.Rows); ok {
r0 = rf(ctx, query, args...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*sql.Rows)
r0 = ret.Get(0).(db.Rows)
}
}
@ -304,18 +304,18 @@ func (_c *DB_QueryContext_Call) Run(run func(ctx context.Context, query string,
return _c
}
func (_c *DB_QueryContext_Call) Return(_a0 *sql.Rows, _a1 error) *DB_QueryContext_Call {
func (_c *DB_QueryContext_Call) Return(_a0 db.Rows, _a1 error) *DB_QueryContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *DB_QueryContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (*sql.Rows, error)) *DB_QueryContext_Call {
func (_c *DB_QueryContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (db.Rows, error)) *DB_QueryContext_Call {
_c.Call.Return(run)
return _c
}
// QueryRowContext provides a mock function with given fields: ctx, query, args
func (_m *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
func (_m *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) db.Row {
var _ca []interface{}
_ca = append(_ca, ctx, query)
_ca = append(_ca, args...)
@ -325,12 +325,12 @@ func (_m *DB) QueryRowContext(ctx context.Context, query string, args ...interfa
panic("no return value specified for QueryRowContext")
}
var r0 *sql.Row
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Row); ok {
var r0 db.Row
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) db.Row); ok {
r0 = rf(ctx, query, args...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*sql.Row)
r0 = ret.Get(0).(db.Row)
}
}
@ -364,12 +364,12 @@ func (_c *DB_QueryRowContext_Call) Run(run func(ctx context.Context, query strin
return _c
}
func (_c *DB_QueryRowContext_Call) Return(_a0 *sql.Row) *DB_QueryRowContext_Call {
func (_c *DB_QueryRowContext_Call) Return(_a0 db.Row) *DB_QueryRowContext_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *DB_QueryRowContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) *sql.Row) *DB_QueryRowContext_Call {
func (_c *DB_QueryRowContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) db.Row) *DB_QueryRowContext_Call {
_c.Call.Return(run)
return _c
}

View File

@ -0,0 +1,132 @@
// Code generated by mockery v2.43.1. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// Row is an autogenerated mock type for the Row type
type Row struct {
mock.Mock
}
type Row_Expecter struct {
mock *mock.Mock
}
func (_m *Row) EXPECT() *Row_Expecter {
return &Row_Expecter{mock: &_m.Mock}
}
// Err provides a mock function with given fields:
func (_m *Row) Err() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Err")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Row_Err_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Err'
type Row_Err_Call struct {
*mock.Call
}
// Err is a helper method to define mock.On call
func (_e *Row_Expecter) Err() *Row_Err_Call {
return &Row_Err_Call{Call: _e.mock.On("Err")}
}
func (_c *Row_Err_Call) Run(run func()) *Row_Err_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Row_Err_Call) Return(_a0 error) *Row_Err_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Row_Err_Call) RunAndReturn(run func() error) *Row_Err_Call {
_c.Call.Return(run)
return _c
}
// Scan provides a mock function with given fields: dest
func (_m *Row) Scan(dest ...interface{}) error {
var _ca []interface{}
_ca = append(_ca, dest...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for Scan")
}
var r0 error
if rf, ok := ret.Get(0).(func(...interface{}) error); ok {
r0 = rf(dest...)
} else {
r0 = ret.Error(0)
}
return r0
}
// Row_Scan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Scan'
type Row_Scan_Call struct {
*mock.Call
}
// Scan is a helper method to define mock.On call
// - dest ...interface{}
func (_e *Row_Expecter) Scan(dest ...interface{}) *Row_Scan_Call {
return &Row_Scan_Call{Call: _e.mock.On("Scan",
append([]interface{}{}, dest...)...)}
}
func (_c *Row_Scan_Call) Run(run func(dest ...interface{})) *Row_Scan_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]interface{}, len(args)-0)
for i, a := range args[0:] {
if a != nil {
variadicArgs[i] = a.(interface{})
}
}
run(variadicArgs...)
})
return _c
}
func (_c *Row_Scan_Call) Return(_a0 error) *Row_Scan_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Row_Scan_Call) RunAndReturn(run func(...interface{}) error) *Row_Scan_Call {
_c.Call.Return(run)
return _c
}
// NewRow creates a new instance of Row. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRow(t interface {
mock.TestingT
Cleanup(func())
}) *Row {
mock := &Row{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,267 @@
// Code generated by mockery v2.43.1. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// Rows is an autogenerated mock type for the Rows type
type Rows struct {
mock.Mock
}
type Rows_Expecter struct {
mock *mock.Mock
}
func (_m *Rows) EXPECT() *Rows_Expecter {
return &Rows_Expecter{mock: &_m.Mock}
}
// Close provides a mock function with given fields:
func (_m *Rows) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Rows_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
type Rows_Close_Call struct {
*mock.Call
}
// Close is a helper method to define mock.On call
func (_e *Rows_Expecter) Close() *Rows_Close_Call {
return &Rows_Close_Call{Call: _e.mock.On("Close")}
}
func (_c *Rows_Close_Call) Run(run func()) *Rows_Close_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Rows_Close_Call) Return(_a0 error) *Rows_Close_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Rows_Close_Call) RunAndReturn(run func() error) *Rows_Close_Call {
_c.Call.Return(run)
return _c
}
// Err provides a mock function with given fields:
func (_m *Rows) Err() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Err")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Rows_Err_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Err'
type Rows_Err_Call struct {
*mock.Call
}
// Err is a helper method to define mock.On call
func (_e *Rows_Expecter) Err() *Rows_Err_Call {
return &Rows_Err_Call{Call: _e.mock.On("Err")}
}
func (_c *Rows_Err_Call) Run(run func()) *Rows_Err_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Rows_Err_Call) Return(_a0 error) *Rows_Err_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Rows_Err_Call) RunAndReturn(run func() error) *Rows_Err_Call {
_c.Call.Return(run)
return _c
}
// Next provides a mock function with given fields:
func (_m *Rows) Next() bool {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Next")
}
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Rows_Next_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Next'
type Rows_Next_Call struct {
*mock.Call
}
// Next is a helper method to define mock.On call
func (_e *Rows_Expecter) Next() *Rows_Next_Call {
return &Rows_Next_Call{Call: _e.mock.On("Next")}
}
func (_c *Rows_Next_Call) Run(run func()) *Rows_Next_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Rows_Next_Call) Return(_a0 bool) *Rows_Next_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Rows_Next_Call) RunAndReturn(run func() bool) *Rows_Next_Call {
_c.Call.Return(run)
return _c
}
// NextResultSet provides a mock function with given fields:
func (_m *Rows) NextResultSet() bool {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for NextResultSet")
}
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Rows_NextResultSet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NextResultSet'
type Rows_NextResultSet_Call struct {
*mock.Call
}
// NextResultSet is a helper method to define mock.On call
func (_e *Rows_Expecter) NextResultSet() *Rows_NextResultSet_Call {
return &Rows_NextResultSet_Call{Call: _e.mock.On("NextResultSet")}
}
func (_c *Rows_NextResultSet_Call) Run(run func()) *Rows_NextResultSet_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Rows_NextResultSet_Call) Return(_a0 bool) *Rows_NextResultSet_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Rows_NextResultSet_Call) RunAndReturn(run func() bool) *Rows_NextResultSet_Call {
_c.Call.Return(run)
return _c
}
// Scan provides a mock function with given fields: dest
func (_m *Rows) Scan(dest ...interface{}) error {
var _ca []interface{}
_ca = append(_ca, dest...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for Scan")
}
var r0 error
if rf, ok := ret.Get(0).(func(...interface{}) error); ok {
r0 = rf(dest...)
} else {
r0 = ret.Error(0)
}
return r0
}
// Rows_Scan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Scan'
type Rows_Scan_Call struct {
*mock.Call
}
// Scan is a helper method to define mock.On call
// - dest ...interface{}
func (_e *Rows_Expecter) Scan(dest ...interface{}) *Rows_Scan_Call {
return &Rows_Scan_Call{Call: _e.mock.On("Scan",
append([]interface{}{}, dest...)...)}
}
func (_c *Rows_Scan_Call) Run(run func(dest ...interface{})) *Rows_Scan_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]interface{}, len(args)-0)
for i, a := range args[0:] {
if a != nil {
variadicArgs[i] = a.(interface{})
}
}
run(variadicArgs...)
})
return _c
}
func (_c *Rows_Scan_Call) Return(_a0 error) *Rows_Scan_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Rows_Scan_Call) RunAndReturn(run func(...interface{}) error) *Rows_Scan_Call {
_c.Call.Return(run)
return _c
}
// NewRows creates a new instance of Rows. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRows(t interface {
mock.TestingT
Cleanup(func())
}) *Rows {
mock := &Rows{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -5,6 +5,7 @@ package mocks
import (
context "context"
db "github.com/grafana/grafana/pkg/storage/unified/sql/db"
mock "github.com/stretchr/testify/mock"
sql "database/sql"
@ -139,7 +140,7 @@ func (_c *Tx_ExecContext_Call) RunAndReturn(run func(context.Context, string, ..
}
// QueryContext provides a mock function with given fields: ctx, query, args
func (_m *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
func (_m *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (db.Rows, error) {
var _ca []interface{}
_ca = append(_ca, ctx, query)
_ca = append(_ca, args...)
@ -149,16 +150,16 @@ func (_m *Tx) QueryContext(ctx context.Context, query string, args ...interface{
panic("no return value specified for QueryContext")
}
var r0 *sql.Rows
var r0 db.Rows
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (*sql.Rows, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (db.Rows, error)); ok {
return rf(ctx, query, args...)
}
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Rows); ok {
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) db.Rows); ok {
r0 = rf(ctx, query, args...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*sql.Rows)
r0 = ret.Get(0).(db.Rows)
}
}
@ -198,18 +199,18 @@ func (_c *Tx_QueryContext_Call) Run(run func(ctx context.Context, query string,
return _c
}
func (_c *Tx_QueryContext_Call) Return(_a0 *sql.Rows, _a1 error) *Tx_QueryContext_Call {
func (_c *Tx_QueryContext_Call) Return(_a0 db.Rows, _a1 error) *Tx_QueryContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Tx_QueryContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (*sql.Rows, error)) *Tx_QueryContext_Call {
func (_c *Tx_QueryContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (db.Rows, error)) *Tx_QueryContext_Call {
_c.Call.Return(run)
return _c
}
// QueryRowContext provides a mock function with given fields: ctx, query, args
func (_m *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
func (_m *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) db.Row {
var _ca []interface{}
_ca = append(_ca, ctx, query)
_ca = append(_ca, args...)
@ -219,12 +220,12 @@ func (_m *Tx) QueryRowContext(ctx context.Context, query string, args ...interfa
panic("no return value specified for QueryRowContext")
}
var r0 *sql.Row
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Row); ok {
var r0 db.Row
if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) db.Row); ok {
r0 = rf(ctx, query, args...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*sql.Row)
r0 = ret.Get(0).(db.Row)
}
}
@ -258,12 +259,12 @@ func (_c *Tx_QueryRowContext_Call) Run(run func(ctx context.Context, query strin
return _c
}
func (_c *Tx_QueryRowContext_Call) Return(_a0 *sql.Row) *Tx_QueryRowContext_Call {
func (_c *Tx_QueryRowContext_Call) Return(_a0 db.Row) *Tx_QueryRowContext_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Tx_QueryRowContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) *sql.Row) *Tx_QueryRowContext_Call {
func (_c *Tx_QueryRowContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) db.Row) *Tx_QueryRowContext_Call {
_c.Call.Return(run)
return _c
}

View File

@ -0,0 +1,142 @@
// Code generated by mockery v2.43.1. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
// Result is an autogenerated mock type for the result type
type Result struct {
mock.Mock
}
type Result_Expecter struct {
mock *mock.Mock
}
func (_m *Result) EXPECT() *Result_Expecter {
return &Result_Expecter{mock: &_m.Mock}
}
// LastInsertId provides a mock function with given fields:
func (_m *Result) LastInsertId() (int64, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for LastInsertId")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func() (int64, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() int64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Result_LastInsertId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LastInsertId'
type Result_LastInsertId_Call struct {
*mock.Call
}
// LastInsertId is a helper method to define mock.On call
func (_e *Result_Expecter) LastInsertId() *Result_LastInsertId_Call {
return &Result_LastInsertId_Call{Call: _e.mock.On("LastInsertId")}
}
func (_c *Result_LastInsertId_Call) Run(run func()) *Result_LastInsertId_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Result_LastInsertId_Call) Return(_a0 int64, _a1 error) *Result_LastInsertId_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Result_LastInsertId_Call) RunAndReturn(run func() (int64, error)) *Result_LastInsertId_Call {
_c.Call.Return(run)
return _c
}
// RowsAffected provides a mock function with given fields:
func (_m *Result) RowsAffected() (int64, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for RowsAffected")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func() (int64, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() int64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Result_RowsAffected_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RowsAffected'
type Result_RowsAffected_Call struct {
*mock.Call
}
// RowsAffected is a helper method to define mock.On call
func (_e *Result_Expecter) RowsAffected() *Result_RowsAffected_Call {
return &Result_RowsAffected_Call{Call: _e.mock.On("RowsAffected")}
}
func (_c *Result_RowsAffected_Call) Run(run func()) *Result_RowsAffected_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Result_RowsAffected_Call) Return(_a0 int64, _a1 error) *Result_RowsAffected_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Result_RowsAffected_Call) RunAndReturn(run func() (int64, error)) *Result_RowsAffected_Call {
_c.Call.Return(run)
return _c
}
// NewResult creates a new instance of Result. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewResult(t interface {
mock.TestingT
Cleanup(func())
}) *Result {
mock := &Result{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -0,0 +1,56 @@
package mocks
import (
"reflect"
"testing"
mock "github.com/stretchr/testify/mock"
)
func NewRowWithValues(t *testing.T, values ...any) *Row {
row := NewRow(t)
row.EXPECT().Err().Return(nil).Once() // should check error
ExpectRowValues(t, row, values...)
return row
}
func ExpectRowValues(t *testing.T, row *Row, values ...any) *Row_Scan_Call {
return row.EXPECT().Scan(mock.Anything).RunAndReturn(func(dsts ...any) error {
scan(t, dsts, values)
return nil
})
}
func scan(t *testing.T, dsts, srcs []any) {
t.Helper()
if len(dsts) > len(srcs) {
t.Fatalf("%d destination values, but only %d source values", len(dsts),
len(srcs))
return
}
for i := range dsts {
src := reflect.ValueOf(srcs[i])
if !src.IsValid() {
t.Fatalf("%d-eth value to be returned by the mocked db is"+
" invalid: %#v", i, srcs[i])
return
}
dst := reflect.ValueOf(dsts[i])
if !dst.IsValid() || dst.Kind() != reflect.Pointer {
t.Fatalf("%d-eth argument passed to Scan is invalid or not"+
" a pointer: %#v", i, dsts[i])
return
}
dst = dst.Elem() // get element to which the pointer points to
if !dst.CanSet() || !src.Type().AssignableTo(dst.Type()) {
t.Fatalf("%d-eth destination cannot be set, orcannot be assigned "+
"the value; type of destination: %T; value that would be "+
"assigned: %#v", i, dsts[i], srcs[i])
return
}
dst.Set(src)
}
}

View File

@ -8,6 +8,9 @@ import (
//go:generate mockery --with-expecter --name DB
//go:generate mockery --with-expecter --name Tx
//go:generate mockery --with-expecter --name Row
//go:generate mockery --with-expecter --name Rows
//go:generate mockery --with-expecter --exported --name result
const (
DriverPostgres = "postgres"
@ -59,9 +62,34 @@ type Tx interface {
// ContextExecer is a set of database operation methods that take
// context.Context.
type ContextExecer interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
ExecContext(ctx context.Context, query string, args ...any) (Result, error)
QueryContext(ctx context.Context, query string, args ...any) (Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) Row
}
// Row is the set of methods from *sql.Row that we use.
type Row interface {
Err() error
Scan(dest ...any) error
}
// Rows is the set of methods from *sql.Rows that we use.
type Rows interface {
Close() error
Err() error
Next() bool
NextResultSet() bool
Scan(dest ...any) error
}
// Result is the standard sql.Result interface, for convenience.
type Result = sql.Result
// result is needed for mockery, since it doesn't support type aliases.
//
//nolint:unused
type result interface {
Result
}
// WithTxFunc is an adapter to be able to provide the DB.WithTx method as an

View File

@ -14,6 +14,17 @@ import (
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
//nolint:unused
const (
otelAttrBaseKey = "dbutil_"
otelAttrTemplateNameKey = otelAttrBaseKey + "template"
otelAttrDialectKey = otelAttrBaseKey + "dialect"
)
func withOtelAttrs(ctx context.Context, tmplName, dialectName string) context.Context {
return ctx // TODO: in next PR
}
// SQLError is an error returned by the database, which includes additionally
// debugging information about what was sent to the database.
type SQLError struct {
@ -79,7 +90,7 @@ func Debug(err error) error {
// Exec uses `req` as input for a non-data returning query generated with
// `tmpl`, and executed in `x`.
func Exec(ctx context.Context, x db.ContextExecer, tmpl *template.Template, req sqltemplate.SQLTemplate) (sql.Result, error) {
func Exec(ctx context.Context, x db.ContextExecer, tmpl *template.Template, req sqltemplate.SQLTemplate) (db.Result, error) {
if err := req.Validate(); err != nil {
return nil, fmt.Errorf("Exec: invalid request for template %q: %w",
tmpl.Name(), err)
@ -91,13 +102,15 @@ func Exec(ctx context.Context, x db.ContextExecer, tmpl *template.Template, req
}
query := sqltemplate.FormatSQL(rawQuery)
res, err := x.ExecContext(ctx, query, req.GetArgs()...)
args := req.GetArgs()
ctx = withOtelAttrs(ctx, tmpl.Name(), req.DialectName())
res, err := x.ExecContext(ctx, query, args...)
if err != nil {
return nil, SQLError{
Err: err,
CallType: "Exec",
TemplateName: tmpl.Name(),
arguments: req.GetArgs(),
arguments: args,
Query: query,
RawQuery: rawQuery,
}
@ -108,7 +121,7 @@ func Exec(ctx context.Context, x db.ContextExecer, tmpl *template.Template, req
// Query uses `req` as input for a single-statement, set-returning query
// generated with `tmpl`, and executed in `x`.
func QueryRows(ctx context.Context, x db.ContextExecer, tmpl *template.Template, req sqltemplate.SQLTemplate) (*sql.Rows, error) {
func QueryRows(ctx context.Context, x db.ContextExecer, tmpl *template.Template, req sqltemplate.SQLTemplate) (db.Rows, error) {
if err := req.Validate(); err != nil {
return nil, fmt.Errorf("Query: invalid request for template %q: %w",
tmpl.Name(), err)
@ -120,13 +133,15 @@ func QueryRows(ctx context.Context, x db.ContextExecer, tmpl *template.Template,
}
query := sqltemplate.FormatSQL(rawQuery)
rows, err := x.QueryContext(ctx, query, req.GetArgs()...)
args := req.GetArgs()
ctx = withOtelAttrs(ctx, tmpl.Name(), req.DialectName())
rows, err := x.QueryContext(ctx, query, args...)
if err != nil {
return nil, SQLError{
Err: err,
CallType: "Query",
TemplateName: tmpl.Name(),
arguments: req.GetArgs(),
arguments: args,
ScanDest: req.GetScanDest(),
Query: query,
RawQuery: rawQuery,
@ -186,13 +201,13 @@ func QueryRow[T any](ctx context.Context, x db.ContextExecer, tmpl *template.Tem
}
}
// DiscardRows discards all the ResultSets in the given *sql.Rows and returns
// DiscardRows discards all the ResultSets in the given db.Rows and returns
// the final rows error and the number of times NextResultSet was called. This
// is useful to check for errors in queries with multiple SQL statements where
// there is no interesting output, since some drivers may omit an error returned
// by a SQL statement found in a statement that is not the first one. Note that
// not all drivers support multi-statement calls, though.
func DiscardRows(rows *sql.Rows) (int, error) {
func DiscardRows(rows db.Rows) (int, error) {
discardedResultSets := 1
for ; rows.NextResultSet(); discardedResultSets++ {
}
@ -203,7 +218,7 @@ type scanner interface {
Scan(dest ...any) error
}
// scanRow is used on *sql.Row and *sql.Rows, and is factored out here not to
// scanRow is used on db.Row and db.Rows, and is factored out here not to
// improving code reuse, but rather for ease of testing.
func scanRow[T any](sc scanner, req sqltemplate.WithResults[T]) (zero T, err error) {
if err = sc.Scan(req.GetScanDest()...); err != nil {

View File

@ -87,6 +87,8 @@ func TestQuery(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil)
req.EXPECT().GetScanDest().Return(nil).Maybe()
@ -108,6 +110,8 @@ func TestQuery(t *testing.T) {
rows := newReturnsRow(rdb.SQLMock, req)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rows.Add(1, nil)
@ -131,6 +135,8 @@ func TestQuery(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(errTest).Once()
// execute and assert
@ -152,6 +158,8 @@ func TestQuery(t *testing.T) {
req.EXPECT().Validate().Return(nil).Once()
// execute and assert
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
res, err := Query(ctx, rdb.DB, invalidTestTmpl, req)
require.Zero(t, res)
require.Error(t, err)
@ -167,6 +175,8 @@ func TestQuery(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil)
req.EXPECT().GetScanDest().Return(nil).Maybe()
@ -189,6 +199,8 @@ func TestQuery(t *testing.T) {
rows := newReturnsRow(rdb.SQLMock, req)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rows.Add(0, errTest)
@ -211,6 +223,8 @@ func TestQuery(t *testing.T) {
rows := newReturnsRow(rdb.SQLMock, req)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rows.Rows.AddRow() // we don't expect GetScanDest or Results here
@ -235,6 +249,8 @@ func TestQuery(t *testing.T) {
rows2 := newReturnsRow(rdb.SQLMock, req)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rows1.Add(1, nil)
@ -262,6 +278,8 @@ func TestQueryRow(t *testing.T) {
rows := newReturnsRow(rdb.SQLMock, req)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rows.Add(1, nil)
@ -282,6 +300,8 @@ func TestQueryRow(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rdb.SQLMock.ExpectQuery("").WillReturnRows(rdb.SQLMock.NewRows(nil))
@ -302,6 +322,8 @@ func TestQueryRow(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil)
req.EXPECT().GetScanDest().Return(nil).Maybe()
@ -324,6 +346,8 @@ func TestQueryRow(t *testing.T) {
rows := newReturnsRow(rdb.SQLMock, req)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rows.Add(1, nil)
@ -348,6 +372,8 @@ func TestQueryRow(t *testing.T) {
rows2 := newReturnsRow(rdb.SQLMock, req)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rows1.Add(1, nil)
@ -445,6 +471,8 @@ func TestExec(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil).Once()
rdb.SQLMock.ExpectExec("").WillReturnResult(sqlmock.NewResult(0, 0))
@ -464,6 +492,8 @@ func TestExec(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(errTest).Once()
// execute and assert
@ -482,6 +512,8 @@ func TestExec(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
// execute and assert
@ -500,6 +532,8 @@ func TestExec(t *testing.T) {
rdb := test.NewDBProviderNopSQL(t)
// setup expectations
req.EXPECT().DialectName().Return("test").Maybe()
req.EXPECT().GetColNames().Return(nil).Maybe()
req.EXPECT().Validate().Return(nil).Once()
req.EXPECT().GetArgs().Return(nil)
rdb.SQLMock.ExpectExec("").WillReturnError(errTest)