mirror of
https://github.com/grafana/grafana.git
synced 2025-09-23 18:52:33 +08:00
SQLStore: Run tests as integration tests (#28265)
* sqlstore: Run tests as integration tests * Truncate database instead of re-creating it on each test * Fix test description See https://github.com/grafana/grafana/pull/12129 * Fix lint issues * Fix postgres dialect after review suggestion * Rename and document functions after review suggestion * Add periods * Fix auto-increment value for mysql dialect Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:

committed by
GitHub

parent
c07896063b
commit
4937f0daab
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -128,7 +130,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
|||||||
So(alert.DashboardSlug, ShouldEqual, "dashboard-with-alerts")
|
So(alert.DashboardSlug, ShouldEqual, "dashboard-with-alerts")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Viewer cannot read alerts", func() {
|
Convey("Viewer can read alerts", func() {
|
||||||
viewerUser := &models.SignedInUser{OrgRole: models.ROLE_VIEWER, OrgId: 1}
|
viewerUser := &models.SignedInUser{OrgRole: models.ROLE_VIEWER, OrgId: 1}
|
||||||
alertQuery := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, PanelId: 1, OrgId: 1, User: viewerUser}
|
alertQuery := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, PanelId: 1, OrgId: 1, User: viewerUser}
|
||||||
err2 := HandleAlertsQuery(&alertQuery)
|
err2 := HandleAlertsQuery(&alertQuery)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -45,6 +45,7 @@ type Dialect interface {
|
|||||||
PostInsertId(table string, sess *xorm.Session) error
|
PostInsertId(table string, sess *xorm.Session) error
|
||||||
|
|
||||||
CleanDB() error
|
CleanDB() error
|
||||||
|
TruncateDBTables() error
|
||||||
NoOpSql() string
|
NoOpSql() string
|
||||||
|
|
||||||
IsUniqueConstraintViolation(err error) bool
|
IsUniqueConstraintViolation(err error) bool
|
||||||
@ -270,3 +271,7 @@ func (db *BaseDialect) CleanDB() error {
|
|||||||
func (db *BaseDialect) NoOpSql() string {
|
func (db *BaseDialect) NoOpSql() string {
|
||||||
return "SELECT 0;"
|
return "SELECT 0;"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *BaseDialect) TruncateDBTables() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package migrator
|
package migrator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -137,6 +138,36 @@ func (db *Mysql) CleanDB() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TruncateDBTables truncates all the tables.
|
||||||
|
// A special case is the dashboard_acl table where we keep the default permissions.
|
||||||
|
func (db *Mysql) TruncateDBTables() error {
|
||||||
|
tables, err := db.engine.DBMetas()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sess := db.engine.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
switch table.Name {
|
||||||
|
case "dashboard_acl":
|
||||||
|
// keep default dashboard permissions
|
||||||
|
if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %v WHERE dashboard_id != -1 AND org_id != -1;", db.Quote(table.Name))); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to truncate table %q", table.Name)
|
||||||
|
}
|
||||||
|
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE %v AUTO_INCREMENT = 3;", db.Quote(table.Name))); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to reset table %q", table.Name)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err := sess.Exec(fmt.Sprintf("TRUNCATE TABLE %v;", db.Quote(table.Name))); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to truncate table %q", table.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *Mysql) isThisError(err error, errcode uint16) bool {
|
func (db *Mysql) isThisError(err error, errcode uint16) bool {
|
||||||
if driverErr, ok := err.(*mysql.MySQLError); ok {
|
if driverErr, ok := err.(*mysql.MySQLError); ok {
|
||||||
if driverErr.Number == errcode {
|
if driverErr.Number == errcode {
|
||||||
|
@ -140,6 +140,37 @@ func (db *Postgres) CleanDB() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TruncateDBTables truncates all the tables.
|
||||||
|
// A special case is the dashboard_acl table where we keep the default permissions.
|
||||||
|
func (db *Postgres) TruncateDBTables() error {
|
||||||
|
sess := db.engine.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
for _, table := range db.engine.Tables {
|
||||||
|
switch table.Name {
|
||||||
|
case "":
|
||||||
|
continue
|
||||||
|
case "dashboard_acl":
|
||||||
|
// keep default dashboard permissions
|
||||||
|
if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %v WHERE dashboard_id != -1 AND org_id != -1;", db.Quote(table.Name))); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to truncate table %q", table.Name)
|
||||||
|
}
|
||||||
|
if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE %v RESTART WITH 3;", db.Quote(fmt.Sprintf("%v_id_seq", table.Name)))); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to reset table %q", table.Name)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err := sess.Exec(fmt.Sprintf("TRUNCATE TABLE %v RESTART IDENTITY CASCADE;", db.Quote(table.Name))); err != nil {
|
||||||
|
if db.isUndefinedTable(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return errutil.Wrapf(err, "failed to truncate table %q", table.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *Postgres) isThisError(err error, errcode string) bool {
|
func (db *Postgres) isThisError(err error, errcode string) bool {
|
||||||
if driverErr, ok := err.(*pq.Error); ok {
|
if driverErr, ok := err.(*pq.Error); ok {
|
||||||
if string(driverErr.Code) == errcode {
|
if string(driverErr.Code) == errcode {
|
||||||
@ -157,6 +188,10 @@ func (db *Postgres) ErrorMessage(err error) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *Postgres) isUndefinedTable(err error) bool {
|
||||||
|
return db.isThisError(err, "42P01")
|
||||||
|
}
|
||||||
|
|
||||||
func (db *Postgres) IsUniqueConstraintViolation(err error) bool {
|
func (db *Postgres) IsUniqueConstraintViolation(err error) bool {
|
||||||
return db.isThisError(err, "23505")
|
return db.isThisError(err, "23505")
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package migrator
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/util/errutil"
|
||||||
"github.com/mattn/go-sqlite3"
|
"github.com/mattn/go-sqlite3"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
@ -85,6 +86,39 @@ func (db *Sqlite3) CleanDB() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TruncateDBTables deletes all data from all the tables and resets the sequences.
|
||||||
|
// A special case is the dashboard_acl table where we keep the default permissions.
|
||||||
|
func (db *Sqlite3) TruncateDBTables() error {
|
||||||
|
tables, err := db.engine.DBMetas()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := db.engine.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
switch table.Name {
|
||||||
|
case "dashboard_acl":
|
||||||
|
// keep default dashboard permissions
|
||||||
|
if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %q WHERE dashboard_id != -1 AND org_id != -1;", table.Name)); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to truncate table %q", table.Name)
|
||||||
|
}
|
||||||
|
if _, err := sess.Exec("UPDATE sqlite_sequence SET seq = 2 WHERE name = '%s';", table.Name); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to cleanup sqlite_sequence")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %s;", table.Name)); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to truncate table %q", table.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := sess.Exec("UPDATE sqlite_sequence SET seq = 0 WHERE name != 'dashboard_acl';"); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to cleanup sqlite_sequence")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *Sqlite3) isThisError(err error, errcode int) bool {
|
func (db *Sqlite3) isThisError(err error, errcode int) bool {
|
||||||
if driverErr, ok := err.(sqlite3.Error); ok {
|
if driverErr, ok := err.(sqlite3.Error); ok {
|
||||||
if int(driverErr.ExtendedCode) == errcode {
|
if int(driverErr.ExtendedCode) == errcode {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
// package search_test contains integration tests for search
|
// package search_test contains integration tests for search
|
||||||
package searchstore_test
|
package searchstore_test
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -340,72 +340,80 @@ type ITestDB interface {
|
|||||||
Logf(format string, args ...interface{})
|
Logf(format string, args ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var testSqlStore *SqlStore
|
||||||
|
|
||||||
// InitTestDB initializes the test DB.
|
// InitTestDB initializes the test DB.
|
||||||
func InitTestDB(t ITestDB) *SqlStore {
|
func InitTestDB(t ITestDB) *SqlStore {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
sqlstore := &SqlStore{}
|
if testSqlStore == nil {
|
||||||
sqlstore.Bus = bus.New()
|
testSqlStore = &SqlStore{}
|
||||||
sqlstore.CacheService = localcache.New(5*time.Minute, 10*time.Minute)
|
testSqlStore.Bus = bus.New()
|
||||||
sqlstore.skipEnsureDefaultOrgAndUser = true
|
testSqlStore.CacheService = localcache.New(5*time.Minute, 10*time.Minute)
|
||||||
|
testSqlStore.skipEnsureDefaultOrgAndUser = true
|
||||||
|
|
||||||
dbType := migrator.SQLITE
|
dbType := migrator.SQLITE
|
||||||
|
|
||||||
// environment variable present for test db?
|
// environment variable present for test db?
|
||||||
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
|
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
|
||||||
t.Logf("Using database type %q", db)
|
t.Logf("Using database type %q", db)
|
||||||
dbType = db
|
dbType = db
|
||||||
}
|
}
|
||||||
|
|
||||||
// set test db config
|
// set test db config
|
||||||
sqlstore.Cfg = setting.NewCfg()
|
testSqlStore.Cfg = setting.NewCfg()
|
||||||
sec, err := sqlstore.Cfg.Raw.NewSection("database")
|
sec, err := testSqlStore.Cfg.Raw.NewSection("database")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create section: %s", err)
|
t.Fatalf("Failed to create section: %s", err)
|
||||||
}
|
}
|
||||||
if _, err := sec.NewKey("type", dbType); err != nil {
|
if _, err := sec.NewKey("type", dbType); err != nil {
|
||||||
t.Fatalf("Failed to create key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch dbType {
|
|
||||||
case "mysql":
|
|
||||||
if _, err := sec.NewKey("connection_string", sqlutil.MySQLTestDB().ConnStr); err != nil {
|
|
||||||
t.Fatalf("Failed to create key: %s", err)
|
t.Fatalf("Failed to create key: %s", err)
|
||||||
}
|
}
|
||||||
case "postgres":
|
|
||||||
if _, err := sec.NewKey("connection_string", sqlutil.PostgresTestDB().ConnStr); err != nil {
|
switch dbType {
|
||||||
t.Fatalf("Failed to create key: %s", err)
|
case "mysql":
|
||||||
|
if _, err := sec.NewKey("connection_string", sqlutil.MySQLTestDB().ConnStr); err != nil {
|
||||||
|
t.Fatalf("Failed to create key: %s", err)
|
||||||
|
}
|
||||||
|
case "postgres":
|
||||||
|
if _, err := sec.NewKey("connection_string", sqlutil.PostgresTestDB().ConnStr); err != nil {
|
||||||
|
t.Fatalf("Failed to create key: %s", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err := sec.NewKey("connection_string", sqlutil.Sqlite3TestDB().ConnStr); err != nil {
|
||||||
|
t.Fatalf("Failed to create key: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
if _, err := sec.NewKey("connection_string", sqlutil.Sqlite3TestDB().ConnStr); err != nil {
|
// need to get engine to clean db before we init
|
||||||
t.Fatalf("Failed to create key: %s", err)
|
t.Logf("Creating database connection: %q", sec.Key("connection_string"))
|
||||||
|
engine, err := xorm.NewEngine(dbType, sec.Key("connection_string").String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to init test database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testSqlStore.Dialect = migrator.NewDialect(engine)
|
||||||
|
|
||||||
|
// temp global var until we get rid of global vars
|
||||||
|
dialect = testSqlStore.Dialect
|
||||||
|
|
||||||
|
t.Logf("Cleaning DB")
|
||||||
|
if err := dialect.CleanDB(); err != nil {
|
||||||
|
t.Fatalf("Failed to clean test db %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testSqlStore.Init(); err != nil {
|
||||||
|
t.Fatalf("Failed to init test database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testSqlStore.engine.DatabaseTZ = time.UTC
|
||||||
|
testSqlStore.engine.TZLocation = time.UTC
|
||||||
}
|
}
|
||||||
|
|
||||||
// need to get engine to clean db before we init
|
if err := dialect.TruncateDBTables(); err != nil {
|
||||||
t.Logf("Creating database connection: %q", sec.Key("connection_string"))
|
t.Fatalf("Failed to truncate test db %v", err)
|
||||||
engine, err := xorm.NewEngine(dbType, sec.Key("connection_string").String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to init test database: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlstore.Dialect = migrator.NewDialect(engine)
|
return testSqlStore
|
||||||
|
|
||||||
// temp global var until we get rid of global vars
|
|
||||||
dialect = sqlstore.Dialect
|
|
||||||
|
|
||||||
t.Logf("Cleaning DB")
|
|
||||||
if err := dialect.CleanDB(); err != nil {
|
|
||||||
t.Fatalf("Failed to clean test db %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sqlstore.Init(); err != nil {
|
|
||||||
t.Fatalf("Failed to init test database: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlstore.engine.DatabaseTZ = time.UTC
|
|
||||||
sqlstore.engine.TZLocation = time.UTC
|
|
||||||
|
|
||||||
return sqlstore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsTestDbMySql() bool {
|
func IsTestDbMySql() bool {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
Reference in New Issue
Block a user