From 02b7e26f6b5dcdc49797cc44c26a255a69f3aff3 Mon Sep 17 00:00:00 2001 From: Cheese Date: Wed, 8 Feb 2023 16:29:09 +0800 Subject: [PATCH] feat: add tidb integration test cases (#6014) * feat: support tidb integration test * feat: update the mysql driver version to test --- .github/workflows/tests.yml | 33 +++++++ tests/associations_belongs_to_test.go | 1 + tests/associations_many2many_test.go | 2 + tests/associations_test.go | 4 + tests/docker-compose.yml | 5 + tests/go.mod | 4 +- tests/helper_test.go | 11 +++ tests/migrate_test.go | 132 ++++++++++++++++++++++++++ tests/sql_builder_test.go | 2 +- tests/tests_all.sh | 2 +- tests/tests_test.go | 7 ++ 11 files changed, 199 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e9a1e63..cfe8e56f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -167,3 +167,36 @@ jobs: - name: Tests run: GITHUB_ACTION=true GORM_DIALECT=sqlserver GORM_DSN="sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm" ./tests/tests_all.sh + + tidb: + strategy: + matrix: + dbversion: [ 'v6.5.0' ] + go: [ '1.19', '1.18' ] + platform: [ ubuntu-latest ] + runs-on: ${{ matrix.platform }} + + steps: + - name: Setup TiDB + uses: Icemap/tidb-action@main + with: + port: 9940 + version: ${{matrix.dbversion}} + + - name: Set up Go 1.x + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go }} + + - name: Check out code into the Go module directory + uses: actions/checkout@v3 + + + - name: go mod package cache + uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('tests/go.mod') }} + + - name: Tests + run: GITHUB_ACTION=true GORM_DIALECT=tidb GORM_DSN="root:@tcp(localhost:9940)/test?charset=utf8&parseTime=True&loc=Local" ./tests/tests_all.sh diff --git a/tests/associations_belongs_to_test.go b/tests/associations_belongs_to_test.go index a1f014d9..99e8aa79 100644 --- a/tests/associations_belongs_to_test.go +++ b/tests/associations_belongs_to_test.go @@ -138,6 +138,7 @@ func TestBelongsToAssociation(t *testing.T) { unexistCompanyID := company.ID + 9999999 user = User{Name: "invalid-user-with-invalid-belongs-to-foreign-key", CompanyID: &unexistCompanyID} if err := DB.Create(&user).Error; err == nil { + tidbSkip(t, "not support the foreign key feature") t.Errorf("should have gotten foreign key violation error") } } diff --git a/tests/associations_many2many_test.go b/tests/associations_many2many_test.go index 7b45befb..4ba31f90 100644 --- a/tests/associations_many2many_test.go +++ b/tests/associations_many2many_test.go @@ -95,6 +95,8 @@ func TestMany2ManyAssociation(t *testing.T) { } func TestMany2ManyOmitAssociations(t *testing.T) { + tidbSkip(t, "not support the foreign key feature") + user := *GetUser("many2many_omit_associations", Config{Languages: 2}) if err := DB.Omit("Languages.*").Create(&user).Error; err == nil { diff --git a/tests/associations_test.go b/tests/associations_test.go index 4c9076da..4e8862e5 100644 --- a/tests/associations_test.go +++ b/tests/associations_test.go @@ -71,6 +71,8 @@ func TestAssociationNotNullClear(t *testing.T) { } func TestForeignKeyConstraints(t *testing.T) { + tidbSkip(t, "not support the foreign key feature") + type Profile struct { ID uint Name string @@ -126,6 +128,8 @@ func TestForeignKeyConstraints(t *testing.T) { } func TestForeignKeyConstraintsBelongsTo(t *testing.T) { + tidbSkip(t, "not support the foreign key feature") + type Profile struct { ID uint Name string diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 9ab4ddb6..0e5673fb 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -29,3 +29,8 @@ services: - MSSQL_DB=gorm - MSSQL_USER=gorm - MSSQL_PASSWORD=LoremIpsum86 + tidb: + image: 'pingcap/tidb:v6.5.0' + ports: + - 9940:4000 + command: /tidb-server -store unistore -path "" -lease 0s > tidb.log 2>&1 & diff --git a/tests/go.mod b/tests/go.mod index 251aabb3..69d6cf87 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -8,11 +8,11 @@ require ( github.com/lib/pq v1.10.7 github.com/mattn/go-sqlite3 v1.14.16 // indirect golang.org/x/crypto v0.5.0 // indirect - gorm.io/driver/mysql v1.4.5 + gorm.io/driver/mysql v1.4.6 gorm.io/driver/postgres v1.4.6 gorm.io/driver/sqlite v1.4.4 gorm.io/driver/sqlserver v1.4.2 - gorm.io/gorm v1.24.3 + gorm.io/gorm v1.24.5 ) replace gorm.io/gorm => ../ diff --git a/tests/helper_test.go b/tests/helper_test.go index d1af0739..d40fa5ce 100644 --- a/tests/helper_test.go +++ b/tests/helper_test.go @@ -1,6 +1,7 @@ package tests_test import ( + "os" "sort" "strconv" "strings" @@ -235,3 +236,13 @@ func CheckUser(t *testing.T, user User, expect User) { } }) } + +func tidbSkip(t *testing.T, reason string) { + if isTiDB() { + t.Skipf("This test case skipped, because of TiDB '%s'", reason) + } +} + +func isTiDB() bool { + return os.Getenv("GORM_DIALECT") == "tidb" +} diff --git a/tests/migrate_test.go b/tests/migrate_test.go index fcd0b5bd..489da976 100644 --- a/tests/migrate_test.go +++ b/tests/migrate_test.go @@ -374,7 +374,137 @@ func TestMigrateIndexes(t *testing.T) { } } +func TestTiDBMigrateColumns(t *testing.T) { + if !isTiDB() { + t.Skip() + } + + // TiDB can't change column constraint and has auto_random feature + type ColumnStruct struct { + ID int `gorm:"primarykey;default:auto_random()"` + Name string + Age int `gorm:"default:18;comment:my age"` + Code string `gorm:"unique;comment:my code;"` + Code2 string + Code3 string `gorm:"unique"` + } + + DB.Migrator().DropTable(&ColumnStruct{}) + + if err := DB.AutoMigrate(&ColumnStruct{}); err != nil { + t.Errorf("Failed to migrate, got %v", err) + } + + type ColumnStruct2 struct { + ID int `gorm:"primarykey;default:auto_random()"` + Name string `gorm:"size:100"` + Code string `gorm:"unique;comment:my code2;default:hello"` + Code2 string `gorm:"comment:my code2;default:hello"` + } + + if err := DB.Table("column_structs").Migrator().AlterColumn(&ColumnStruct{}, "Name"); err != nil { + t.Fatalf("no error should happened when alter column, but got %v", err) + } + + if err := DB.Table("column_structs").AutoMigrate(&ColumnStruct2{}); err != nil { + t.Fatalf("no error should happened when auto migrate column, but got %v", err) + } + + if columnTypes, err := DB.Migrator().ColumnTypes(&ColumnStruct{}); err != nil { + t.Fatalf("no error should returns for ColumnTypes") + } else { + stmt := &gorm.Statement{DB: DB} + stmt.Parse(&ColumnStruct2{}) + + for _, columnType := range columnTypes { + switch columnType.Name() { + case "id": + if v, ok := columnType.PrimaryKey(); !ok || !v { + t.Fatalf("column id primary key should be correct, name: %v, column: %#v", columnType.Name(), columnType) + } + case "name": + dataType := DB.Dialector.DataTypeOf(stmt.Schema.LookUpField(columnType.Name())) + if !strings.Contains(strings.ToUpper(dataType), strings.ToUpper(columnType.DatabaseTypeName())) { + t.Fatalf("column name type should be correct, name: %v, length: %v, expects: %v, column: %#v", columnType.Name(), columnType.DatabaseTypeName(), dataType, columnType) + } + if length, ok := columnType.Length(); !ok || length != 100 { + t.Fatalf("column name length should be correct, name: %v, length: %v, expects: %v, column: %#v", columnType.Name(), length, 100, columnType) + } + case "age": + if v, ok := columnType.DefaultValue(); !ok || v != "18" { + t.Fatalf("column age default value should be correct, name: %v, column: %#v", columnType.Name(), columnType) + } + if v, ok := columnType.Comment(); !ok || v != "my age" { + t.Fatalf("column age comment should be correct, name: %v, column: %#v", columnType.Name(), columnType) + } + case "code": + if v, ok := columnType.Unique(); !ok || !v { + t.Fatalf("column code unique should be correct, name: %v, column: %#v", columnType.Name(), columnType) + } + if v, ok := columnType.DefaultValue(); !ok || v != "hello" { + t.Fatalf("column code default value should be correct, name: %v, column: %#v, default value: %v", columnType.Name(), columnType, v) + } + if v, ok := columnType.Comment(); !ok || v != "my code2" { + t.Fatalf("column code comment should be correct, name: %v, column: %#v", columnType.Name(), columnType) + } + case "code2": + // Code2 string `gorm:"comment:my code2;default:hello"` + if v, ok := columnType.DefaultValue(); !ok || v != "hello" { + t.Fatalf("column code default value should be correct, name: %v, column: %#v, default value: %v", columnType.Name(), columnType, v) + } + if v, ok := columnType.Comment(); !ok || v != "my code2" { + t.Fatalf("column code comment should be correct, name: %v, column: %#v", columnType.Name(), columnType) + } + } + } + } + + type NewColumnStruct struct { + gorm.Model + Name string + NewName string + } + + if err := DB.Table("column_structs").Migrator().AddColumn(&NewColumnStruct{}, "NewName"); err != nil { + t.Fatalf("Failed to add column, got %v", err) + } + + if !DB.Table("column_structs").Migrator().HasColumn(&NewColumnStruct{}, "NewName") { + t.Fatalf("Failed to find added column") + } + + if err := DB.Table("column_structs").Migrator().DropColumn(&NewColumnStruct{}, "NewName"); err != nil { + t.Fatalf("Failed to add column, got %v", err) + } + + if DB.Table("column_structs").Migrator().HasColumn(&NewColumnStruct{}, "NewName") { + t.Fatalf("Found deleted column") + } + + if err := DB.Table("column_structs").Migrator().AddColumn(&NewColumnStruct{}, "NewName"); err != nil { + t.Fatalf("Failed to add column, got %v", err) + } + + if err := DB.Table("column_structs").Migrator().RenameColumn(&NewColumnStruct{}, "NewName", "new_new_name"); err != nil { + t.Fatalf("Failed to add column, got %v", err) + } + + if !DB.Table("column_structs").Migrator().HasColumn(&NewColumnStruct{}, "new_new_name") { + t.Fatalf("Failed to found renamed column") + } + + if err := DB.Table("column_structs").Migrator().DropColumn(&NewColumnStruct{}, "new_new_name"); err != nil { + t.Fatalf("Failed to add column, got %v", err) + } + + if DB.Table("column_structs").Migrator().HasColumn(&NewColumnStruct{}, "new_new_name") { + t.Fatalf("Found deleted column") + } +} + func TestMigrateColumns(t *testing.T) { + tidbSkip(t, "use another test case") + sqlite := DB.Dialector.Name() == "sqlite" sqlserver := DB.Dialector.Name() == "sqlserver" @@ -853,6 +983,8 @@ func TestUniqueColumn(t *testing.T) { AssertEqual(t, "", value) AssertEqual(t, false, ok) + tidbSkip(t, "can't change column constraint") + // null -> empty string err = DB.Table("unique_tests").AutoMigrate(&UniqueTest3{}) if err != nil { diff --git a/tests/sql_builder_test.go b/tests/sql_builder_test.go index 0fbd6118..022e0495 100644 --- a/tests/sql_builder_test.go +++ b/tests/sql_builder_test.go @@ -29,7 +29,7 @@ func TestRow(t *testing.T) { } table := "gorm.users" - if DB.Dialector.Name() != "mysql" { + if DB.Dialector.Name() != "mysql" || isTiDB() { table = "users" // other databases doesn't support select with `database.table` } diff --git a/tests/tests_all.sh b/tests/tests_all.sh index 5b9bae97..ee9e7675 100755 --- a/tests/tests_all.sh +++ b/tests/tests_all.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -dialects=("sqlite" "mysql" "postgres" "sqlserver") +dialects=("sqlite" "mysql" "postgres" "sqlserver" "tidb") if [[ $(pwd) == *"gorm/tests"* ]]; then cd .. diff --git a/tests/tests_test.go b/tests/tests_test.go index dcba3cbf..90eb847f 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -21,6 +21,7 @@ var ( mysqlDSN = "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local" postgresDSN = "user=gorm password=gorm dbname=gorm host=localhost port=9920 sslmode=disable TimeZone=Asia/Shanghai" sqlserverDSN = "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm" + tidbDSN = "root:@tcp(localhost:9940)/test?charset=utf8&parseTime=True&loc=Local" ) func init() { @@ -80,6 +81,12 @@ func OpenTestConnection() (db *gorm.DB, err error) { dbDSN = sqlserverDSN } db, err = gorm.Open(sqlserver.Open(dbDSN), &gorm.Config{}) + case "tidb": + log.Println("testing tidb...") + if dbDSN == "" { + dbDSN = tidbDSN + } + db, err = gorm.Open(mysql.Open(dbDSN), &gorm.Config{}) default: log.Println("testing sqlite3...") db, err = gorm.Open(sqlite.Open(filepath.Join(os.TempDir(), "gorm.db")), &gorm.Config{})