From daccf443db53cb4be609325934fab6f94d483e8e Mon Sep 17 00:00:00 2001 From: Saumil Diwaker Date: Thu, 21 Aug 2025 14:08:22 +0530 Subject: [PATCH 1/6] tests: enhance connection test coverage --- tests/connection_test.go | 329 +++++++++++++++++++++++++++++++++++++++ tests/passed-tests.txt | 10 ++ 2 files changed, 339 insertions(+) diff --git a/tests/connection_test.go b/tests/connection_test.go index 7e2cd7b..39a78df 100644 --- a/tests/connection_test.go +++ b/tests/connection_test.go @@ -39,8 +39,11 @@ package tests import ( + "context" "fmt" + "sync" "testing" + "time" "gorm.io/gorm" ) @@ -65,3 +68,329 @@ func TestWithSingleConnection(t *testing.T) { t.Errorf("WithSingleConnection() method should get correct value, expect: %v, got %v", expectedString, actualString) } } + +func TestTransactionCommitRollback(t *testing.T) { + // Create test table + type TestTxTable struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"size:100;column:name"` + } + + err := DB.AutoMigrate(&TestTxTable{}) + if err != nil { + t.Fatalf("Failed to migrate test table: %v", err) + } + defer DB.Migrator().DropTable(&TestTxTable{}) + + // Test commit + t.Run("Commit", func(t *testing.T) { + tx := DB.Begin() + if tx.Error != nil { + t.Fatalf("Failed to begin transaction: %v", tx.Error) + } + + record := TestTxTable{Name: "test_commit"} + if err := tx.Create(&record).Error; err != nil { + t.Errorf("Failed to create record in transaction: %v", err) + } + + if err := tx.Commit().Error; err != nil { + t.Errorf("Failed to commit transaction: %v", err) + } + + // Verify record exists using quoted column name + var count int64 + if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_commit").Count(&count).Error; err != nil { + t.Errorf("Failed to count records: %v", err) + } + if count != 1 { + t.Errorf("Expected 1 record after commit, got %d", count) + } + }) + + // Test rollback + t.Run("Rollback", func(t *testing.T) { + tx := DB.Begin() + if tx.Error != nil { + t.Fatalf("Failed to begin transaction: %v", tx.Error) + } + + record := TestTxTable{Name: "test_rollback"} + if err := tx.Create(&record).Error; err != nil { + t.Errorf("Failed to create record in transaction: %v", err) + } + + if err := tx.Rollback().Error; err != nil { + t.Errorf("Failed to rollback transaction: %v", err) + } + + // Verify record doesn't exist using quoted column name + var count int64 + if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_rollback").Count(&count).Error; err != nil { + t.Errorf("Failed to count records: %v", err) + } + if count != 0 { + t.Errorf("Expected 0 records after rollback, got %d", count) + } + }) +} + +func TestConnectionAfterError(t *testing.T) { + // Execute an invalid query to cause an error + err := DB.Exec("SELECT invalid_column FROM dual").Error + if err == nil { + t.Error("Expected error for invalid query, but got nil") + } + + // Verify connection still works after error + var result string + err = DB.Raw("SELECT 'connection_works' FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Connection should work after error, but got: %v", err) + } + if result != "connection_works" { + t.Errorf("Expected 'connection_works', got '%s'", result) + } +} + +func TestConcurrentConnections(t *testing.T) { + const numGoroutines = 10 + const operationsPerGoroutine = 5 + + var wg sync.WaitGroup + errors := make(chan error, numGoroutines*operationsPerGoroutine) + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(goroutineID int) { + defer wg.Done() + for j := 0; j < operationsPerGoroutine; j++ { + var result string + query := fmt.Sprintf("SELECT 'goroutine_%d_op_%d' FROM dual", goroutineID, j) + if err := DB.Raw(query).Scan(&result).Error; err != nil { + errors <- fmt.Errorf("goroutine %d operation %d failed: %v", goroutineID, j, err) + return + } + expected := fmt.Sprintf("goroutine_%d_op_%d", goroutineID, j) + if result != expected { + errors <- fmt.Errorf("goroutine %d operation %d: expected '%s', got '%s'", goroutineID, j, expected, result) + return + } + } + }(i) + } + + wg.Wait() + close(errors) + + for err := range errors { + t.Error(err) + } +} + +func TestContextTimeout(t *testing.T) { + // Test with very short timeout that should trigger + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + // This should timeout for most operations + err := DB.WithContext(ctx).Raw("SELECT 1 FROM dual").Error + if err == nil { + t.Log("Operation completed before timeout (this is possible on fast systems)") + } + + // Test with reasonable timeout that should succeed + ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel2() + + var result int + err = DB.WithContext(ctx2).Raw("SELECT 42 FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Operation with reasonable timeout failed: %v", err) + } + if result != 42 { + t.Errorf("Expected 42, got %d", result) + } +} + +func TestLargeResultSet(t *testing.T) { + var results []struct { + RowNum int `gorm:"column:ROW_NUM"` + Value string `gorm:"column:VALUE"` + } + + query := ` + SELECT LEVEL as row_num, 'row_' || LEVEL as value + FROM dual + CONNECT BY LEVEL <= 1000 + ` + + err := DB.Raw(query).Scan(&results).Error + if err != nil { + t.Errorf("Failed to execute large result set query: %v", err) + return + } + + if len(results) != 1000 { + t.Errorf("Expected 1000 rows, got %d", len(results)) + return + } + + // Verify first and last rows + if results[0].RowNum != 1 || results[0].Value != "row_1" { + t.Errorf("First row incorrect: %+v", results[0]) + } + if results[999].RowNum != 1000 || results[999].Value != "row_1000" { + t.Errorf("Last row incorrect: %+v", results[999]) + } +} + +func TestSessionInfo(t *testing.T) { + // Test USER function first (should always work) + var username string + err := DB.Raw("SELECT USER FROM dual").Scan(&username).Error + if err != nil { + t.Errorf("Failed to get username: %v", err) + return + } + + if username == "" { + t.Skip("USER function returned empty - unusual Oracle configuration") + } + + // Test SYS_CONTEXT functions + var sessionInfo struct { + InstanceName string `gorm:"column:instance_name"` + DatabaseName string `gorm:"column:database_name"` + } + + query := ` + SELECT + SYS_CONTEXT('USERENV', 'INSTANCE_NAME') as instance_name, + SYS_CONTEXT('USERENV', 'DB_NAME') as database_name + FROM dual + ` + + err = DB.Raw(query).Scan(&sessionInfo).Error + if err != nil { + t.Errorf("Failed to get session context info: %v", err) + return + } + + // Log what we found + t.Logf("Session Info - User: %s", username) + if sessionInfo.InstanceName != "" { + t.Logf("Instance: %s", sessionInfo.InstanceName) + } + if sessionInfo.DatabaseName != "" { + t.Logf("Database: %s", sessionInfo.DatabaseName) + } + + // Only require username - instance/database names might not be available in all environments + if sessionInfo.InstanceName == "" || sessionInfo.DatabaseName == "" { + t.Skip("SYS_CONTEXT functions unavailable - likely permissions or configuration issue") + } +} + +func TestConnectionPing(t *testing.T) { + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get sql.DB: %v", err) + } + + err = sqlDB.Ping() + if err != nil { + t.Errorf("Database ping failed: %v", err) + } + + // Test ping with context + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err = sqlDB.PingContext(ctx) + if err != nil { + t.Errorf("Database ping with context failed: %v", err) + } +} + +func TestIntervalDataTypes(t *testing.T) { + // Test Year to Month interval + t.Run("YearToMonth", func(t *testing.T) { + var result string + err := DB.Raw("SELECT INTERVAL '2-3' YEAR TO MONTH FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Year to Month interval query failed: %v", err) + } + t.Logf("Year to Month interval result: %s", result) + }) + + // Test Day to Second interval + t.Run("DayToSecond", func(t *testing.T) { + var result string + err := DB.Raw("SELECT INTERVAL '4 5:12:10.222' DAY TO SECOND FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Day to Second interval query failed: %v", err) + } + t.Logf("Day to Second interval result: %s", result) + }) +} + +func TestConnectionPoolStats(t *testing.T) { + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get sql.DB: %v", err) + } + + stats := sqlDB.Stats() + t.Logf("Connection Pool Stats:") + t.Logf(" Open Connections: %d", stats.OpenConnections) + t.Logf(" In Use: %d", stats.InUse) + t.Logf(" Idle: %d", stats.Idle) + t.Logf(" Wait Count: %d", stats.WaitCount) + t.Logf(" Wait Duration: %v", stats.WaitDuration) + t.Logf(" Max Idle Closed: %d", stats.MaxIdleClosed) + t.Logf(" Max Idle Time Closed: %d", stats.MaxIdleTimeClosed) + t.Logf(" Max Lifetime Closed: %d", stats.MaxLifetimeClosed) + + // Basic sanity checks + if stats.OpenConnections < 0 { + t.Error("Open connections should not be negative") + } + if stats.InUse < 0 { + t.Error("In use connections should not be negative") + } + if stats.Idle < 0 { + t.Error("Idle connections should not be negative") + } +} + +func TestDatabaseVersionInfo(t *testing.T) { + var versionInfo struct { + Version string `gorm:"column:version"` + Banner string `gorm:"column:banner"` + } + + query := ` + SELECT + (SELECT VERSION FROM V$INSTANCE) as version, + (SELECT BANNER FROM V$VERSION WHERE ROWNUM = 1) as banner + FROM dual + ` + + err := DB.Raw(query).Scan(&versionInfo).Error + if err == nil && versionInfo.Version != "" && versionInfo.Banner != "" { + t.Logf("Database Version: %s", versionInfo.Version) + t.Logf("Database Banner: %s", versionInfo.Banner) + return + } + + // Fallback to PRODUCT_COMPONENT_VERSION + var simpleVersion string + err = DB.Raw("SELECT VERSION FROM PRODUCT_COMPONENT_VERSION WHERE PRODUCT LIKE 'Oracle%' AND ROWNUM = 1").Scan(&simpleVersion).Error + if err == nil && simpleVersion != "" { + t.Logf("Database Version: %s", simpleVersion) + return + } + + t.Skip("Could not retrieve database version info - insufficient privileges to access system views") +} diff --git a/tests/passed-tests.txt b/tests/passed-tests.txt index 08e05ee..7998e7b 100644 --- a/tests/passed-tests.txt +++ b/tests/passed-tests.txt @@ -38,6 +38,16 @@ TestPluginCallbacks TestCallbacksGet TestCallbacksRemove TestWithSingleConnection +TestTransactionCommitRollback +TestConnectionAfterError +TestConcurrentConnections +TestContextTimeout +TestLargeResultSet +TestSessionInfo +TestConnectionPing +TestIntervalDataTypes +TestConnectionPoolStats +TestDatabaseVersionInfo TestCountWithGroup TestCount TestCountOnEmptyTable From b2d720bdd2ebe4ebdf6a4fd37edcc55783a3e73b Mon Sep 17 00:00:00 2001 From: Saumil Diwaker Date: Thu, 21 Aug 2025 15:13:44 +0530 Subject: [PATCH 2/6] add connection pool tests --- tests/connection_pool_test.go | 498 ++++++++++++++++++++++++++++++++++ tests/passed-tests.txt | 7 + 2 files changed, 505 insertions(+) diff --git a/tests/connection_pool_test.go b/tests/connection_pool_test.go index df12198..f9940db 100644 --- a/tests/connection_pool_test.go +++ b/tests/connection_pool_test.go @@ -1,3 +1,41 @@ +/* +** Copyright (c) 2025 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + package tests import ( @@ -7,6 +45,8 @@ import ( "sync" "testing" "time" + + "gorm.io/gorm" ) type TestProduct struct { @@ -285,3 +325,461 @@ func setupConnectionPoolTestTables(t *testing.T) { t.Log("Test tables created successfully") } + +func TestPoolConfiguration(t *testing.T) { + setupConnectionPoolTestTables(t) + + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + t.Run("ValidPoolSettings", func(t *testing.T) { + // Test valid pool configuration + sqlDB.SetMaxOpenConns(10) + sqlDB.SetMaxIdleConns(5) + sqlDB.SetConnMaxLifetime(30 * time.Minute) + sqlDB.SetConnMaxIdleTime(5 * time.Minute) + + // Verify settings took effect + stats := sqlDB.Stats() + if stats.MaxOpenConnections != 10 { + t.Errorf("Expected MaxOpenConnections: 10, got: %d", stats.MaxOpenConnections) + } + + // Test basic database operation works with pool + var result string + err := DB.Raw("SELECT 'pool_test' FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Basic query failed with pool configuration: %v", err) + } + if result != "pool_test" { + t.Errorf("Expected 'pool_test', got '%s'", result) + } + }) + + t.Run("InvalidPoolSettings", func(t *testing.T) { + // Test that invalid settings are handled gracefully + // Note: Go's sql.DB doesn't validate these at set time, but we can test edge cases + + // Test with zero values + sqlDB.SetMaxOpenConns(0) // 0 means unlimited + sqlDB.SetMaxIdleConns(0) // 0 means use default + + // Verify database still works + var count int64 + err := DB.Model(&TestProduct{}).Count(&count).Error + if err != nil { + t.Errorf("Database operation failed with zero pool settings: %v", err) + } + + // Reset to reasonable values + sqlDB.SetMaxOpenConns(10) + sqlDB.SetMaxIdleConns(5) + }) +} + +// TestBasicPoolOperations tests fundamental pool operations +func TestBasicPoolOperations(t *testing.T) { + setupConnectionPoolTestTables(t) + + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + // Configure pool for testing + sqlDB.SetMaxOpenConns(5) + sqlDB.SetMaxIdleConns(2) + sqlDB.SetConnMaxLifetime(10 * time.Minute) + + t.Run("BasicCRUDOperations", func(t *testing.T) { + // Test CREATE + product := &TestProduct{ + Code: "POOL_CRUD_001", + Name: "Pool Test Product", + Price: 100, + } + err := DB.Create(product).Error + if err != nil { + t.Errorf("CREATE operation failed: %v", err) + } + if product.ID == 0 { + t.Error("Product ID should be set after creation") + } + + // Test READ + var foundProduct TestProduct + err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&foundProduct).Error + if err != nil { + t.Errorf("READ operation failed: %v", err) + } + if foundProduct.Name != "Pool Test Product" { + t.Errorf("Expected 'Pool Test Product', got '%s'", foundProduct.Name) + } + + // Test UPDATE + err = DB.Model(&foundProduct).Update("\"PRICE\"", 150).Error + if err != nil { + t.Errorf("UPDATE operation failed: %v", err) + } + + // Verify update + var updatedProduct TestProduct + err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&updatedProduct).Error + if err != nil { + t.Errorf("Failed to verify update: %v", err) + } + if updatedProduct.Price != 150 { + t.Errorf("Expected price 150, got %d", updatedProduct.Price) + } + + // Test DELETE + err = DB.Delete(&updatedProduct).Error + if err != nil { + t.Errorf("DELETE operation failed: %v", err) + } + + // Verify deletion + var deletedProduct TestProduct + err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&deletedProduct).Error + if err != gorm.ErrRecordNotFound { + t.Errorf("Expected record not found, got: %v", err) + } + }) + + t.Run("PoolStatistics", func(t *testing.T) { + initialStats := sqlDB.Stats() + t.Logf("Initial Pool Stats - Open: %d, InUse: %d, Idle: %d", + initialStats.OpenConnections, initialStats.InUse, initialStats.Idle) + + // Perform some operations to exercise the pool + for i := 0; i < 3; i++ { + var result string + err := DB.Raw("SELECT ? FROM dual", fmt.Sprintf("test_%d", i)).Scan(&result).Error + if err != nil { + t.Errorf("Query %d failed: %v", i, err) + } + } + + finalStats := sqlDB.Stats() + t.Logf("Final Pool Stats - Open: %d, InUse: %d, Idle: %d", + finalStats.OpenConnections, finalStats.InUse, finalStats.Idle) + + // Basic sanity checks + if finalStats.OpenConnections < 0 { + t.Error("OpenConnections should not be negative") + } + if finalStats.InUse < 0 { + t.Error("InUse connections should not be negative") + } + }) +} + +// TestPoolExhaustion tests behavior when pool is exhausted +func TestPoolExhaustion(t *testing.T) { + setupConnectionPoolTestTables(t) + + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + // Configure a very small pool for testing exhaustion + sqlDB.SetMaxOpenConns(2) + sqlDB.SetMaxIdleConns(1) + sqlDB.SetConnMaxIdleTime(1 * time.Second) + + t.Run("PoolExhaustionBehavior", func(t *testing.T) { + var wg sync.WaitGroup + const numGoroutines = 5 // More than maxOpenConns + errors := make(chan error, numGoroutines) + + t.Logf("Starting %d goroutines with pool size 2", numGoroutines) + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + // Use context with timeout to prevent infinite waiting + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // Start a transaction to hold the connection longer + tx := DB.WithContext(ctx).Begin() + if tx.Error != nil { + errors <- fmt.Errorf("goroutine %d: failed to begin transaction: %v", id, tx.Error) + return + } + + // Do some work + var count int64 + err := tx.Model(&TestProduct{}).Count(&count).Error + if err != nil { + tx.Rollback() + errors <- fmt.Errorf("goroutine %d: count failed: %v", id, err) + return + } + + // Hold the connection for a bit + time.Sleep(1 * time.Second) + + // Commit and release + err = tx.Commit().Error + if err != nil { + errors <- fmt.Errorf("goroutine %d: commit failed: %v", id, err) + return + } + + t.Logf("Goroutine %d completed successfully", id) + }(i) + } + + wg.Wait() + close(errors) + + // Check if all operations completed (some might timeout, which is expected) + errorCount := 0 + for err := range errors { + t.Logf("Error: %v", err) + errorCount++ + } + + // In a properly configured pool, some operations might timeout but shouldn't panic + if errorCount == numGoroutines { + t.Error("All operations failed - pool might not be working correctly") + } + + // Check final pool stats + finalStats := sqlDB.Stats() + t.Logf("Pool exhaustion stats - WaitCount: %d, WaitDuration: %v", + finalStats.WaitCount, finalStats.WaitDuration) + }) +} + +// TestConcurrentDatabaseOperations tests concurrent GORM operations +func TestConcurrentDatabaseOperations(t *testing.T) { + setupConnectionPoolTestTables(t) + + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + sqlDB.SetMaxOpenConns(10) + sqlDB.SetMaxIdleConns(5) + + t.Run("ConcurrentCRUDMix", func(t *testing.T) { + var wg sync.WaitGroup + const numWorkers = 8 + const opsPerWorker = 10 + + results := make(chan string, numWorkers*opsPerWorker) + + for worker := 0; worker < numWorkers; worker++ { + wg.Add(1) + go func(workerID int) { + defer wg.Done() + + for op := 0; op < opsPerWorker; op++ { + switch op % 4 { + case 0: // CREATE + product := &TestProduct{ + Code: fmt.Sprintf("WORKER_%d_OP_%d", workerID, op), + Name: fmt.Sprintf("Worker %d Product %d", workerID, op), + Price: uint(100 + workerID + op), + } + if err := DB.Create(product).Error; err != nil { + results <- fmt.Sprintf("Worker %d CREATE failed: %v", workerID, err) + } else { + results <- fmt.Sprintf("Worker %d CREATE success", workerID) + } + + case 1: // READ + var count int64 + if err := DB.Model(&TestProduct{}).Count(&count).Error; err != nil { + results <- fmt.Sprintf("Worker %d READ failed: %v", workerID, err) + } else { + results <- fmt.Sprintf("Worker %d READ success: %d products", workerID, count) + } + + case 2: // UPDATE + affected := DB.Model(&TestProduct{}). + Where("\"CODE\" LIKE ?", fmt.Sprintf("WORKER_%d_%%", workerID)). + Update("\"PRICE\"", uint(200+workerID+op)).RowsAffected + results <- fmt.Sprintf("Worker %d UPDATE: %d rows affected", workerID, affected) + + case 3: // RAW QUERY + var result string + if err := DB.Raw("SELECT ? || '_' || ? FROM dual", + fmt.Sprintf("worker_%d", workerID), + fmt.Sprintf("op_%d", op)).Scan(&result).Error; err != nil { + results <- fmt.Sprintf("Worker %d RAW failed: %v", workerID, err) + } else { + results <- fmt.Sprintf("Worker %d RAW success: %s", workerID, result) + } + } + + // Small delay to simulate real work + time.Sleep(10 * time.Millisecond) + } + }(worker) + } + + wg.Wait() + close(results) + + // Collect and analyze results + successCount := 0 + errorCount := 0 + for result := range results { + if strings.Contains(result, "failed") { + t.Logf("Error: %s", result) + errorCount++ + } else { + successCount++ + } + } + + t.Logf("Concurrent operations completed - Success: %d, Errors: %d", successCount, errorCount) + + // We expect most operations to succeed + totalOps := numWorkers * opsPerWorker + if successCount < totalOps/2 { + t.Errorf("Too many failures: %d errors out of %d operations", errorCount, totalOps) + } + }) +} + +// TestPoolConnectionRecovery tests pool behavior after connection errors +func TestPoolConnectionRecovery(t *testing.T) { + setupConnectionPoolTestTables(t) + + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + sqlDB.SetMaxOpenConns(5) + sqlDB.SetMaxIdleConns(2) + + t.Run("RecoveryAfterInvalidQuery", func(t *testing.T) { + // First, verify pool is working + var result string + err := DB.Raw("SELECT 'before_error' FROM dual").Scan(&result).Error + if err != nil { + t.Fatalf("Initial query failed: %v", err) + } + + // Execute an invalid query that should cause an error but not break the pool + err = DB.Raw("SELECT * FROM non_existent_table_12345").Scan(&result).Error + if err == nil { + t.Error("Expected error for invalid query") + } + + // Verify pool still works after the error + err = DB.Raw("SELECT 'after_error' FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Pool should work after error, but got: %v", err) + } + if result != "after_error" { + t.Errorf("Expected 'after_error', got '%s'", result) + } + + // Test that GORM operations still work + var count int64 + err = DB.Model(&TestProduct{}).Count(&count).Error + if err != nil { + t.Errorf("GORM operations should work after error: %v", err) + } + }) + + t.Run("ContextCancellationHandling", func(t *testing.T) { + // Test context cancellation doesn't break the pool + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + // This should timeout/cancel + var result string + err := DB.WithContext(ctx).Raw("SELECT 'timeout_test' FROM dual").Scan(&result).Error + // Error is expected (timeout or cancellation) + + // Verify pool still works after context cancellation + err = DB.Raw("SELECT 'post_cancel' FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Pool should work after context cancellation: %v", err) + } + if result != "post_cancel" { + t.Errorf("Expected 'post_cancel', got '%s'", result) + } + }) +} + +// TestPoolLifecycleManagement tests proper pool lifecycle and cleanup +func TestPoolLifecycleManagement(t *testing.T) { + setupConnectionPoolTestTables(t) + + t.Run("ConnectionPingAndHealth", func(t *testing.T) { + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + // Test basic ping + err = sqlDB.Ping() + if err != nil { + t.Errorf("Database ping failed: %v", err) + } + + // Test ping with context + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err = sqlDB.PingContext(ctx) + if err != nil { + t.Errorf("Database ping with context failed: %v", err) + } + }) + + t.Run("PoolStatisticsOverTime", func(t *testing.T) { + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + // Configure pool with short idle timeout for testing + sqlDB.SetMaxOpenConns(5) + sqlDB.SetMaxIdleConns(2) + sqlDB.SetConnMaxIdleTime(2 * time.Second) + + // Create some connections + for i := 0; i < 3; i++ { + var result string + err := DB.Raw("SELECT ? FROM dual", fmt.Sprintf("conn_test_%d", i)).Scan(&result).Error + if err != nil { + t.Errorf("Query %d failed: %v", i, err) + } + } + + initialStats := sqlDB.Stats() + t.Logf("Initial stats - Open: %d, InUse: %d, Idle: %d", + initialStats.OpenConnections, initialStats.InUse, initialStats.Idle) + + // Wait for idle timeout + time.Sleep(3 * time.Second) + + finalStats := sqlDB.Stats() + t.Logf("Final stats - Open: %d, InUse: %d, Idle: %d", + finalStats.OpenConnections, finalStats.InUse, finalStats.Idle) + t.Logf("Lifetime stats - MaxIdleTimeClosed: %d, MaxLifetimeClosed: %d", + finalStats.MaxIdleTimeClosed, finalStats.MaxLifetimeClosed) + + // Verify pool is still functional + var result string + err = DB.Raw("SELECT 'final_test' FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Pool should still work after idle timeout: %v", err) + } + }) +} diff --git a/tests/passed-tests.txt b/tests/passed-tests.txt index 7998e7b..e911a06 100644 --- a/tests/passed-tests.txt +++ b/tests/passed-tests.txt @@ -48,6 +48,13 @@ TestConnectionPing TestIntervalDataTypes TestConnectionPoolStats TestDatabaseVersionInfo +TestConnectionPooling +TestPoolConfiguration +TestBasicPoolOperations +TestPoolExhaustion +TestConcurrentDatabaseOperations +TestPoolConnectionRecovery +TestPoolLifecycleManagement TestCountWithGroup TestCount TestCountOnEmptyTable From f2d382f2c0a161642166c34191bdf3cbc122e2e5 Mon Sep 17 00:00:00 2001 From: Saumil Diwaker Date: Thu, 21 Aug 2025 21:30:41 +0530 Subject: [PATCH 3/6] standardize to direct test functions, remove t.Run subtests --- tests/connection_pool_test.go | 978 ++++++++++++++-------------------- tests/connection_test.go | 191 +++---- tests/passed-tests.txt | 30 +- 3 files changed, 478 insertions(+), 721 deletions(-) diff --git a/tests/connection_pool_test.go b/tests/connection_pool_test.go index f9940db..fd62ca2 100644 --- a/tests/connection_pool_test.go +++ b/tests/connection_pool_test.go @@ -63,8 +63,7 @@ type TestCategory struct { Name string `gorm:"column:NAME;size:100"` } -func TestConnectionPooling(t *testing.T) { - t.Skip() +func TestPoolValidSettings(t *testing.T) { setupConnectionPoolTestTables(t) sqlDB, err := DB.DB() @@ -72,261 +71,30 @@ func TestConnectionPooling(t *testing.T) { t.Fatalf("Failed to get underlying DB: %v", err) } - t.Log("Setting up connection pool configuration...") + // Test valid pool configuration sqlDB.SetMaxOpenConns(10) sqlDB.SetMaxIdleConns(5) sqlDB.SetConnMaxLifetime(30 * time.Minute) sqlDB.SetConnMaxIdleTime(5 * time.Minute) - t.Log("Connection pool configured") - - // Test 1: Check initial pool stats - t.Run("InitialPoolStatistics", func(t *testing.T) { - stats := sqlDB.Stats() - t.Logf("Max Open Connections: %d", stats.MaxOpenConnections) - t.Logf("Open Connections: %d", stats.OpenConnections) - t.Logf("In Use: %d", stats.InUse) - t.Logf("Idle: %d", stats.Idle) - - if stats.MaxOpenConnections != 10 { - t.Errorf("Expected max open connections: 10, got: %d", stats.MaxOpenConnections) - } - }) - - // Test 2: Concurrent database operations - t.Run("ConcurrentOperations", func(t *testing.T) { - var wg sync.WaitGroup - numGoroutines := 15 - - results := make(chan string, numGoroutines) - - startTime := time.Now() - - for i := 0; i < numGoroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - - switch id % 3 { - case 0: - // SELECT operation - var count int64 - err := DB.Model(&TestProduct{}).Count(&count).Error - if err != nil { - results <- fmt.Sprintf("Goroutine %d SELECT failed: %v", id, err) - } else { - results <- fmt.Sprintf("Goroutine %d SELECT success: %d products", id, count) - } - - case 1: - // INSERT operation - product := &TestProduct{ - Code: fmt.Sprintf("POOL_%d_%d", id, time.Now().UnixNano()), - Name: fmt.Sprintf("Pool Test Product %d", id), - Price: uint(100 + id), - } - err := DB.Create(product).Error - if err != nil { - results <- fmt.Sprintf("Goroutine %d INSERT failed: %v", id, err) - } else { - results <- fmt.Sprintf("Goroutine %d INSERT success: ID %d", id, product.ID) - } - - case 2: - // Long-running query (simulate connection hold) - var products []TestProduct - err := DB.Raw("SELECT * FROM test_products WHERE ROWNUM <= 10").Scan(&products).Error - time.Sleep(100 * time.Millisecond) - if err != nil { - results <- fmt.Sprintf("Goroutine %d LONG-QUERY failed: %v", id, err) - } else { - results <- fmt.Sprintf("Goroutine %d LONG-QUERY success: %d products", id, len(products)) - } - } - }(i) - } - - // Monitor pool stats during concurrent operations - go func() { - for i := 0; i < 5; i++ { - time.Sleep(200 * time.Millisecond) - stats := sqlDB.Stats() - t.Logf("[Monitor] Open: %d, InUse: %d, Idle: %d, WaitCount: %d", - stats.OpenConnections, stats.InUse, stats.Idle, stats.WaitCount) - } - }() - - wg.Wait() - close(results) - - duration := time.Since(startTime) - t.Logf("Concurrent operations completed in %v", duration) - - // Collect and display results - successCount := 0 - errorCount := 0 - for result := range results { - if strings.Contains(result, "success") { - successCount++ - } else { - errorCount++ - } - t.Logf("%s", result) - } - - t.Logf("Summary: %d successful, %d failed operations", successCount, errorCount) - - if errorCount > 0 { - t.Errorf("Expected no errors, but got %d failed operations", errorCount) - } - }) - - // Test 3: Final pool statistics - t.Run("FinalPoolStatistics", func(t *testing.T) { - finalStats := sqlDB.Stats() - t.Logf("Max Open Connections: %d", finalStats.MaxOpenConnections) - t.Logf("Open Connections: %d", finalStats.OpenConnections) - t.Logf("In Use: %d", finalStats.InUse) - t.Logf("Idle: %d", finalStats.Idle) - t.Logf("Wait Count: %d", finalStats.WaitCount) - t.Logf("Wait Duration: %v", finalStats.WaitDuration) - t.Logf("Max Idle Closed: %d", finalStats.MaxIdleClosed) - t.Logf("Max Idle Time Closed: %d", finalStats.MaxIdleTimeClosed) - t.Logf("Max Lifetime Closed: %d", finalStats.MaxLifetimeClosed) - }) - - // Test 4: Connection timeout test - t.Run("ConnectionTimeout", func(t *testing.T) { - var timeoutWg sync.WaitGroup - numTimeoutTests := 20 - - for i := 0; i < numTimeoutTests; i++ { - timeoutWg.Add(1) - go func(id int) { - defer timeoutWg.Done() - - // Create a context with timeout - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - var count int64 - err := DB.WithContext(ctx).Model(&TestProduct{}).Count(&count).Error - if err != nil { - t.Logf("Timeout test %d failed: %v", id, err) - } else { - t.Logf("Timeout test %d success", id) - } - }(i) - } - - timeoutWg.Wait() - t.Log("Connection timeout tests completed") - }) - - // Test 5: Connection cleanup verification - t.Run("ConnectionCleanup", func(t *testing.T) { - t.Log("Waiting for idle connection cleanup...") - time.Sleep(2 * time.Second) - - cleanupStats := sqlDB.Stats() - t.Logf("After cleanup - Open: %d, InUse: %d, Idle: %d", - cleanupStats.OpenConnections, cleanupStats.InUse, cleanupStats.Idle) - }) - - // Test 6: Connection health check - t.Run("ConnectionHealth", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - err := sqlDB.PingContext(ctx) - if err != nil { - t.Errorf("Connection health check failed: %v", err) - } else { - t.Log("Connection health check passed") - } - }) - t.Run("PoolExhaustion", func(t *testing.T) { - numConnections := 15 // More than maxOpenConns (10) - var wg sync.WaitGroup - - t.Logf("Starting %d long-running connections (max pool size: 10)", numConnections) - - // Start operations that hold connections for a longer period - for i := 0; i < numConnections; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - - t.Logf("Goroutine %d: Starting long transaction", id) - tx := DB.Begin() - if tx.Error != nil { - t.Logf("Goroutine %d: Failed to begin transaction: %v", id, tx.Error) - return - } - - // Perform some work to actually use the connection - var count int64 - tx.Model(&TestProduct{}).Count(&count) - - // Hold connection for a significant time - time.Sleep(2 * time.Second) - - tx.Rollback() - t.Logf("Goroutine %d: Transaction completed", id) - }(i) - } - - // Give goroutines time to start and acquire connections - time.Sleep(500 * time.Millisecond) - - // Check pool stats during the exhaustion period - stats := sqlDB.Stats() - t.Logf("Pool stats during exhaustion:") - t.Logf(" Max Open: %d", stats.MaxOpenConnections) - t.Logf(" Open: %d", stats.OpenConnections) - t.Logf(" InUse: %d", stats.InUse) - t.Logf(" Idle: %d", stats.Idle) - t.Logf(" WaitCount: %d", stats.WaitCount) - t.Logf(" WaitDuration: %v", stats.WaitDuration) - - // Wait a bit more to ensure some connections are waiting - time.Sleep(500 * time.Millisecond) - - // Check again for wait statistics - finalStats := sqlDB.Stats() - t.Logf("Final pool stats:") - t.Logf(" WaitCount: %d", finalStats.WaitCount) - t.Logf(" WaitDuration: %v", finalStats.WaitDuration) - - // Either we should have some waits, OR all connections should be in use - if finalStats.WaitCount == 0 { - t.Errorf("Expected either some waits (WaitCount > 0) or high connection usage (InUse >= 8), got WaitCount=%d, InUse=%d", - finalStats.WaitCount, finalStats.InUse) - } - - // Wait for all goroutines to complete - wg.Wait() - - t.Log("Pool exhaustion test completed") - }) -} - -func setupConnectionPoolTestTables(t *testing.T) { - t.Log("Setting up test tables using GORM migrator") - - // Drop existing tables - DB.Migrator().DropTable(&TestProduct{}, &TestCategory{}) + // Verify settings took effect + stats := sqlDB.Stats() + if stats.MaxOpenConnections != 10 { + t.Errorf("Expected MaxOpenConnections: 10, got: %d", stats.MaxOpenConnections) + } - // Create tables using GORM - err := DB.AutoMigrate(&TestCategory{}, &TestProduct{}) + // Test basic database operation works with pool + var result string + err = DB.Raw("SELECT 'pool_test' FROM dual").Scan(&result).Error if err != nil { - t.Fatalf("Failed to migrate tables: %v", err) + t.Errorf("Basic query failed with pool configuration: %v", err) + } + if result != "pool_test" { + t.Errorf("Expected 'pool_test', got '%s'", result) } - - t.Log("Test tables created successfully") } -func TestPoolConfiguration(t *testing.T) { +func TestPoolInvalidSettings(t *testing.T) { setupConnectionPoolTestTables(t) sqlDB, err := DB.DB() @@ -334,53 +102,23 @@ func TestPoolConfiguration(t *testing.T) { t.Fatalf("Failed to get underlying DB: %v", err) } - t.Run("ValidPoolSettings", func(t *testing.T) { - // Test valid pool configuration - sqlDB.SetMaxOpenConns(10) - sqlDB.SetMaxIdleConns(5) - sqlDB.SetConnMaxLifetime(30 * time.Minute) - sqlDB.SetConnMaxIdleTime(5 * time.Minute) + // Test with zero values + sqlDB.SetMaxOpenConns(0) // 0 means unlimited + sqlDB.SetMaxIdleConns(0) // 0 means use default - // Verify settings took effect - stats := sqlDB.Stats() - if stats.MaxOpenConnections != 10 { - t.Errorf("Expected MaxOpenConnections: 10, got: %d", stats.MaxOpenConnections) - } - - // Test basic database operation works with pool - var result string - err := DB.Raw("SELECT 'pool_test' FROM dual").Scan(&result).Error - if err != nil { - t.Errorf("Basic query failed with pool configuration: %v", err) - } - if result != "pool_test" { - t.Errorf("Expected 'pool_test', got '%s'", result) - } - }) - - t.Run("InvalidPoolSettings", func(t *testing.T) { - // Test that invalid settings are handled gracefully - // Note: Go's sql.DB doesn't validate these at set time, but we can test edge cases - - // Test with zero values - sqlDB.SetMaxOpenConns(0) // 0 means unlimited - sqlDB.SetMaxIdleConns(0) // 0 means use default - - // Verify database still works - var count int64 - err := DB.Model(&TestProduct{}).Count(&count).Error - if err != nil { - t.Errorf("Database operation failed with zero pool settings: %v", err) - } + // Verify database still works + var count int64 + err = DB.Model(&TestProduct{}).Count(&count).Error + if err != nil { + t.Errorf("Database operation failed with zero pool settings: %v", err) + } - // Reset to reasonable values - sqlDB.SetMaxOpenConns(10) - sqlDB.SetMaxIdleConns(5) - }) + // Reset to reasonable values + sqlDB.SetMaxOpenConns(10) + sqlDB.SetMaxIdleConns(5) } -// TestBasicPoolOperations tests fundamental pool operations -func TestBasicPoolOperations(t *testing.T) { +func TestBasicCRUDOperations(t *testing.T) { setupConnectionPoolTestTables(t) sqlDB, err := DB.DB() @@ -393,91 +131,95 @@ func TestBasicPoolOperations(t *testing.T) { sqlDB.SetMaxIdleConns(2) sqlDB.SetConnMaxLifetime(10 * time.Minute) - t.Run("BasicCRUDOperations", func(t *testing.T) { - // Test CREATE - product := &TestProduct{ - Code: "POOL_CRUD_001", - Name: "Pool Test Product", - Price: 100, - } - err := DB.Create(product).Error - if err != nil { - t.Errorf("CREATE operation failed: %v", err) - } - if product.ID == 0 { - t.Error("Product ID should be set after creation") - } + // Test CREATE + product := &TestProduct{ + Code: "POOL_CRUD_001", + Name: "Pool Test Product", + Price: 100, + } + err = DB.Create(product).Error + if err != nil { + t.Errorf("CREATE operation failed: %v", err) + } + if product.ID == 0 { + t.Error("Product ID should be set after creation") + } - // Test READ - var foundProduct TestProduct - err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&foundProduct).Error - if err != nil { - t.Errorf("READ operation failed: %v", err) - } - if foundProduct.Name != "Pool Test Product" { - t.Errorf("Expected 'Pool Test Product', got '%s'", foundProduct.Name) - } + // Test READ + var foundProduct TestProduct + err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&foundProduct).Error + if err != nil { + t.Errorf("READ operation failed: %v", err) + } + if foundProduct.Name != "Pool Test Product" { + t.Errorf("Expected 'Pool Test Product', got '%s'", foundProduct.Name) + } - // Test UPDATE - err = DB.Model(&foundProduct).Update("\"PRICE\"", 150).Error - if err != nil { - t.Errorf("UPDATE operation failed: %v", err) - } + // Test UPDATE + err = DB.Model(&foundProduct).Update("\"PRICE\"", 150).Error + if err != nil { + t.Errorf("UPDATE operation failed: %v", err) + } - // Verify update - var updatedProduct TestProduct - err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&updatedProduct).Error - if err != nil { - t.Errorf("Failed to verify update: %v", err) - } - if updatedProduct.Price != 150 { - t.Errorf("Expected price 150, got %d", updatedProduct.Price) - } + // Verify update + var updatedProduct TestProduct + err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&updatedProduct).Error + if err != nil { + t.Errorf("Failed to verify update: %v", err) + } + if updatedProduct.Price != 150 { + t.Errorf("Expected price 150, got %d", updatedProduct.Price) + } - // Test DELETE - err = DB.Delete(&updatedProduct).Error - if err != nil { - t.Errorf("DELETE operation failed: %v", err) - } + // Test DELETE + err = DB.Delete(&updatedProduct).Error + if err != nil { + t.Errorf("DELETE operation failed: %v", err) + } - // Verify deletion - var deletedProduct TestProduct - err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&deletedProduct).Error - if err != gorm.ErrRecordNotFound { - t.Errorf("Expected record not found, got: %v", err) - } - }) + // Verify deletion + var deletedProduct TestProduct + err = DB.Where("\"CODE\" = ?", "POOL_CRUD_001").First(&deletedProduct).Error + if err != gorm.ErrRecordNotFound { + t.Errorf("Expected record not found, got: %v", err) + } +} - t.Run("PoolStatistics", func(t *testing.T) { - initialStats := sqlDB.Stats() - t.Logf("Initial Pool Stats - Open: %d, InUse: %d, Idle: %d", - initialStats.OpenConnections, initialStats.InUse, initialStats.Idle) +func TestPoolStatistics(t *testing.T) { + setupConnectionPoolTestTables(t) - // Perform some operations to exercise the pool - for i := 0; i < 3; i++ { - var result string - err := DB.Raw("SELECT ? FROM dual", fmt.Sprintf("test_%d", i)).Scan(&result).Error - if err != nil { - t.Errorf("Query %d failed: %v", i, err) - } - } + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } - finalStats := sqlDB.Stats() - t.Logf("Final Pool Stats - Open: %d, InUse: %d, Idle: %d", - finalStats.OpenConnections, finalStats.InUse, finalStats.Idle) + initialStats := sqlDB.Stats() + t.Logf("Initial Pool Stats - Open: %d, InUse: %d, Idle: %d", + initialStats.OpenConnections, initialStats.InUse, initialStats.Idle) - // Basic sanity checks - if finalStats.OpenConnections < 0 { - t.Error("OpenConnections should not be negative") - } - if finalStats.InUse < 0 { - t.Error("InUse connections should not be negative") + // Perform some operations to exercise the pool + for i := 0; i < 3; i++ { + var result string + err := DB.Raw("SELECT ? FROM dual", fmt.Sprintf("test_%d", i)).Scan(&result).Error + if err != nil { + t.Errorf("Query %d failed: %v", i, err) } - }) + } + + finalStats := sqlDB.Stats() + t.Logf("Final Pool Stats - Open: %d, InUse: %d, Idle: %d", + finalStats.OpenConnections, finalStats.InUse, finalStats.Idle) + + // Basic sanity checks + if finalStats.OpenConnections < 0 { + t.Error("OpenConnections should not be negative") + } + if finalStats.InUse < 0 { + t.Error("InUse connections should not be negative") + } } -// TestPoolExhaustion tests behavior when pool is exhausted -func TestPoolExhaustion(t *testing.T) { +func TestPoolExhaustionBehavior(t *testing.T) { setupConnectionPoolTestTables(t) sqlDB, err := DB.DB() @@ -490,76 +232,73 @@ func TestPoolExhaustion(t *testing.T) { sqlDB.SetMaxIdleConns(1) sqlDB.SetConnMaxIdleTime(1 * time.Second) - t.Run("PoolExhaustionBehavior", func(t *testing.T) { - var wg sync.WaitGroup - const numGoroutines = 5 // More than maxOpenConns - errors := make(chan error, numGoroutines) + var wg sync.WaitGroup + const numGoroutines = 5 // More than maxOpenConns + errors := make(chan error, numGoroutines) - t.Logf("Starting %d goroutines with pool size 2", numGoroutines) + t.Logf("Starting %d goroutines with pool size 2", numGoroutines) - for i := 0; i < numGoroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() - // Use context with timeout to prevent infinite waiting - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() + // Use context with timeout to prevent infinite waiting + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() - // Start a transaction to hold the connection longer - tx := DB.WithContext(ctx).Begin() - if tx.Error != nil { - errors <- fmt.Errorf("goroutine %d: failed to begin transaction: %v", id, tx.Error) - return - } + // Start a transaction to hold the connection longer + tx := DB.WithContext(ctx).Begin() + if tx.Error != nil { + errors <- fmt.Errorf("goroutine %d: failed to begin transaction: %v", id, tx.Error) + return + } - // Do some work - var count int64 - err := tx.Model(&TestProduct{}).Count(&count).Error - if err != nil { - tx.Rollback() - errors <- fmt.Errorf("goroutine %d: count failed: %v", id, err) - return - } + // Do some work + var count int64 + err := tx.Model(&TestProduct{}).Count(&count).Error + if err != nil { + tx.Rollback() + errors <- fmt.Errorf("goroutine %d: count failed: %v", id, err) + return + } - // Hold the connection for a bit - time.Sleep(1 * time.Second) + // Hold the connection for a bit + time.Sleep(1 * time.Second) - // Commit and release - err = tx.Commit().Error - if err != nil { - errors <- fmt.Errorf("goroutine %d: commit failed: %v", id, err) - return - } + // Commit and release + err = tx.Commit().Error + if err != nil { + errors <- fmt.Errorf("goroutine %d: commit failed: %v", id, err) + return + } - t.Logf("Goroutine %d completed successfully", id) - }(i) - } + t.Logf("Goroutine %d completed successfully", id) + }(i) + } - wg.Wait() - close(errors) + wg.Wait() + close(errors) - // Check if all operations completed (some might timeout, which is expected) - errorCount := 0 - for err := range errors { - t.Logf("Error: %v", err) - errorCount++ - } + // Check if all operations completed (some might timeout, which is expected) + errorCount := 0 + for err := range errors { + t.Logf("Error: %v", err) + errorCount++ + } - // In a properly configured pool, some operations might timeout but shouldn't panic - if errorCount == numGoroutines { - t.Error("All operations failed - pool might not be working correctly") - } + // In a properly configured pool, some operations might timeout but shouldn't panic + if errorCount == numGoroutines { + t.Error("All operations failed - pool might not be working correctly") + } - // Check final pool stats - finalStats := sqlDB.Stats() - t.Logf("Pool exhaustion stats - WaitCount: %d, WaitDuration: %v", - finalStats.WaitCount, finalStats.WaitDuration) - }) + // Check final pool stats + finalStats := sqlDB.Stats() + t.Logf("Pool exhaustion stats - WaitCount: %d, WaitDuration: %v", + finalStats.WaitCount, finalStats.WaitDuration) } -// TestConcurrentDatabaseOperations tests concurrent GORM operations -func TestConcurrentDatabaseOperations(t *testing.T) { +func TestConcurrentCRUDMix(t *testing.T) { setupConnectionPoolTestTables(t) sqlDB, err := DB.DB() @@ -570,90 +309,87 @@ func TestConcurrentDatabaseOperations(t *testing.T) { sqlDB.SetMaxOpenConns(10) sqlDB.SetMaxIdleConns(5) - t.Run("ConcurrentCRUDMix", func(t *testing.T) { - var wg sync.WaitGroup - const numWorkers = 8 - const opsPerWorker = 10 - - results := make(chan string, numWorkers*opsPerWorker) - - for worker := 0; worker < numWorkers; worker++ { - wg.Add(1) - go func(workerID int) { - defer wg.Done() - - for op := 0; op < opsPerWorker; op++ { - switch op % 4 { - case 0: // CREATE - product := &TestProduct{ - Code: fmt.Sprintf("WORKER_%d_OP_%d", workerID, op), - Name: fmt.Sprintf("Worker %d Product %d", workerID, op), - Price: uint(100 + workerID + op), - } - if err := DB.Create(product).Error; err != nil { - results <- fmt.Sprintf("Worker %d CREATE failed: %v", workerID, err) - } else { - results <- fmt.Sprintf("Worker %d CREATE success", workerID) - } - - case 1: // READ - var count int64 - if err := DB.Model(&TestProduct{}).Count(&count).Error; err != nil { - results <- fmt.Sprintf("Worker %d READ failed: %v", workerID, err) - } else { - results <- fmt.Sprintf("Worker %d READ success: %d products", workerID, count) - } - - case 2: // UPDATE - affected := DB.Model(&TestProduct{}). - Where("\"CODE\" LIKE ?", fmt.Sprintf("WORKER_%d_%%", workerID)). - Update("\"PRICE\"", uint(200+workerID+op)).RowsAffected - results <- fmt.Sprintf("Worker %d UPDATE: %d rows affected", workerID, affected) - - case 3: // RAW QUERY - var result string - if err := DB.Raw("SELECT ? || '_' || ? FROM dual", - fmt.Sprintf("worker_%d", workerID), - fmt.Sprintf("op_%d", op)).Scan(&result).Error; err != nil { - results <- fmt.Sprintf("Worker %d RAW failed: %v", workerID, err) - } else { - results <- fmt.Sprintf("Worker %d RAW success: %s", workerID, result) - } + var wg sync.WaitGroup + const numWorkers = 8 + const opsPerWorker = 10 + + results := make(chan string, numWorkers*opsPerWorker) + + for worker := 0; worker < numWorkers; worker++ { + wg.Add(1) + go func(workerID int) { + defer wg.Done() + + for op := 0; op < opsPerWorker; op++ { + switch op % 4 { + case 0: // CREATE + product := &TestProduct{ + Code: fmt.Sprintf("WORKER_%d_OP_%d", workerID, op), + Name: fmt.Sprintf("Worker %d Product %d", workerID, op), + Price: uint(100 + workerID + op), + } + if err := DB.Create(product).Error; err != nil { + results <- fmt.Sprintf("Worker %d CREATE failed: %v", workerID, err) + } else { + results <- fmt.Sprintf("Worker %d CREATE success", workerID) } - // Small delay to simulate real work - time.Sleep(10 * time.Millisecond) + case 1: // READ + var count int64 + if err := DB.Model(&TestProduct{}).Count(&count).Error; err != nil { + results <- fmt.Sprintf("Worker %d READ failed: %v", workerID, err) + } else { + results <- fmt.Sprintf("Worker %d READ success: %d products", workerID, count) + } + + case 2: // UPDATE + affected := DB.Model(&TestProduct{}). + Where("\"CODE\" LIKE ?", fmt.Sprintf("WORKER_%d_%%", workerID)). + Update("\"PRICE\"", uint(200+workerID+op)).RowsAffected + results <- fmt.Sprintf("Worker %d UPDATE: %d rows affected", workerID, affected) + + case 3: // RAW QUERY + var result string + if err := DB.Raw("SELECT ? || '_' || ? FROM dual", + fmt.Sprintf("worker_%d", workerID), + fmt.Sprintf("op_%d", op)).Scan(&result).Error; err != nil { + results <- fmt.Sprintf("Worker %d RAW failed: %v", workerID, err) + } else { + results <- fmt.Sprintf("Worker %d RAW success: %s", workerID, result) + } } - }(worker) - } - wg.Wait() - close(results) - - // Collect and analyze results - successCount := 0 - errorCount := 0 - for result := range results { - if strings.Contains(result, "failed") { - t.Logf("Error: %s", result) - errorCount++ - } else { - successCount++ + // Small delay to simulate real work + time.Sleep(10 * time.Millisecond) } - } + }(worker) + } - t.Logf("Concurrent operations completed - Success: %d, Errors: %d", successCount, errorCount) + wg.Wait() + close(results) - // We expect most operations to succeed - totalOps := numWorkers * opsPerWorker - if successCount < totalOps/2 { - t.Errorf("Too many failures: %d errors out of %d operations", errorCount, totalOps) + // Collect and analyze results + successCount := 0 + errorCount := 0 + for result := range results { + if strings.Contains(result, "failed") { + t.Logf("Error: %s", result) + errorCount++ + } else { + successCount++ } - }) + } + + t.Logf("Concurrent operations completed - Success: %d, Errors: %d", successCount, errorCount) + + // We expect most operations to succeed + totalOps := numWorkers * opsPerWorker + if successCount < totalOps/2 { + t.Errorf("Too many failures: %d errors out of %d operations", errorCount, totalOps) + } } -// TestPoolConnectionRecovery tests pool behavior after connection errors -func TestPoolConnectionRecovery(t *testing.T) { +func TestRecoveryAfterInvalidQuery(t *testing.T) { setupConnectionPoolTestTables(t) sqlDB, err := DB.DB() @@ -664,122 +400,192 @@ func TestPoolConnectionRecovery(t *testing.T) { sqlDB.SetMaxOpenConns(5) sqlDB.SetMaxIdleConns(2) - t.Run("RecoveryAfterInvalidQuery", func(t *testing.T) { - // First, verify pool is working - var result string - err := DB.Raw("SELECT 'before_error' FROM dual").Scan(&result).Error - if err != nil { - t.Fatalf("Initial query failed: %v", err) - } + // First, verify pool is working + var result string + err = DB.Raw("SELECT 'before_error' FROM dual").Scan(&result).Error + if err != nil { + t.Fatalf("Initial query failed: %v", err) + } - // Execute an invalid query that should cause an error but not break the pool - err = DB.Raw("SELECT * FROM non_existent_table_12345").Scan(&result).Error - if err == nil { - t.Error("Expected error for invalid query") - } + // Execute an invalid query that should cause an error but not break the pool + err = DB.Raw("SELECT * FROM non_existent_table_12345").Scan(&result).Error + if err == nil { + t.Error("Expected error for invalid query") + } - // Verify pool still works after the error - err = DB.Raw("SELECT 'after_error' FROM dual").Scan(&result).Error - if err != nil { - t.Errorf("Pool should work after error, but got: %v", err) - } - if result != "after_error" { - t.Errorf("Expected 'after_error', got '%s'", result) - } + // Verify pool still works after the error + err = DB.Raw("SELECT 'after_error' FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Pool should work after error, but got: %v", err) + } + if result != "after_error" { + t.Errorf("Expected 'after_error', got '%s'", result) + } - // Test that GORM operations still work - var count int64 - err = DB.Model(&TestProduct{}).Count(&count).Error - if err != nil { - t.Errorf("GORM operations should work after error: %v", err) - } - }) + // Test that GORM operations still work + var count int64 + err = DB.Model(&TestProduct{}).Count(&count).Error + if err != nil { + t.Errorf("GORM operations should work after error: %v", err) + } +} - t.Run("ContextCancellationHandling", func(t *testing.T) { - // Test context cancellation doesn't break the pool - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() +func TestContextCancellationHandling(t *testing.T) { + setupConnectionPoolTestTables(t) - // This should timeout/cancel - var result string - err := DB.WithContext(ctx).Raw("SELECT 'timeout_test' FROM dual").Scan(&result).Error - // Error is expected (timeout or cancellation) + // Test context cancellation doesn't break the pool + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() - // Verify pool still works after context cancellation - err = DB.Raw("SELECT 'post_cancel' FROM dual").Scan(&result).Error - if err != nil { - t.Errorf("Pool should work after context cancellation: %v", err) - } - if result != "post_cancel" { - t.Errorf("Expected 'post_cancel', got '%s'", result) - } - }) + // This should timeout/cancel + var result string + err := DB.WithContext(ctx).Raw("SELECT 'timeout_test' FROM dual").Scan(&result).Error + // Error is expected (timeout or cancellation) + + // Verify pool still works after context cancellation + err = DB.Raw("SELECT 'post_cancel' FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Pool should work after context cancellation: %v", err) + } + if result != "post_cancel" { + t.Errorf("Expected 'post_cancel', got '%s'", result) + } +} + +func TestConnectionPingAndHealth(t *testing.T) { + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + // Test basic ping + err = sqlDB.Ping() + if err != nil { + t.Errorf("Database ping failed: %v", err) + } + + // Test ping with context + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err = sqlDB.PingContext(ctx) + if err != nil { + t.Errorf("Database ping with context failed: %v", err) + } } -// TestPoolLifecycleManagement tests proper pool lifecycle and cleanup -func TestPoolLifecycleManagement(t *testing.T) { +func TestPoolStatisticsOverTime(t *testing.T) { setupConnectionPoolTestTables(t) - t.Run("ConnectionPingAndHealth", func(t *testing.T) { - sqlDB, err := DB.DB() - if err != nil { - t.Fatalf("Failed to get underlying DB: %v", err) - } + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get underlying DB: %v", err) + } + + // Configure pool with short idle timeout for testing + sqlDB.SetMaxOpenConns(5) + sqlDB.SetMaxIdleConns(2) + sqlDB.SetConnMaxIdleTime(2 * time.Second) - // Test basic ping - err = sqlDB.Ping() + // Create some connections + for i := 0; i < 3; i++ { + var result string + err := DB.Raw("SELECT ? FROM dual", fmt.Sprintf("conn_test_%d", i)).Scan(&result).Error if err != nil { - t.Errorf("Database ping failed: %v", err) + t.Errorf("Query %d failed: %v", i, err) } + } - // Test ping with context - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() + initialStats := sqlDB.Stats() + t.Logf("Initial stats - Open: %d, InUse: %d, Idle: %d", + initialStats.OpenConnections, initialStats.InUse, initialStats.Idle) - err = sqlDB.PingContext(ctx) - if err != nil { - t.Errorf("Database ping with context failed: %v", err) - } - }) + // Wait for idle timeout + time.Sleep(3 * time.Second) - t.Run("PoolStatisticsOverTime", func(t *testing.T) { - sqlDB, err := DB.DB() - if err != nil { - t.Fatalf("Failed to get underlying DB: %v", err) - } + finalStats := sqlDB.Stats() + t.Logf("Final stats - Open: %d, InUse: %d, Idle: %d", + finalStats.OpenConnections, finalStats.InUse, finalStats.Idle) + t.Logf("Lifetime stats - MaxIdleTimeClosed: %d, MaxLifetimeClosed: %d", + finalStats.MaxIdleTimeClosed, finalStats.MaxLifetimeClosed) - // Configure pool with short idle timeout for testing - sqlDB.SetMaxOpenConns(5) - sqlDB.SetMaxIdleConns(2) - sqlDB.SetConnMaxIdleTime(2 * time.Second) + // Verify pool is still functional + var result string + err = DB.Raw("SELECT 'final_test' FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Pool should still work after idle timeout: %v", err) + } +} - // Create some connections - for i := 0; i < 3; i++ { - var result string - err := DB.Raw("SELECT ? FROM dual", fmt.Sprintf("conn_test_%d", i)).Scan(&result).Error - if err != nil { - t.Errorf("Query %d failed: %v", i, err) - } - } +func setupConnectionPoolTestTables(t *testing.T) { + t.Log("Setting up test tables using GORM migrator") + + // Drop existing tables + DB.Migrator().DropTable(&TestProduct{}, &TestCategory{}) + + // Create tables using GORM + err := DB.AutoMigrate(&TestCategory{}, &TestProduct{}) + if err != nil { + t.Fatalf("Failed to migrate tables: %v", err) + } - initialStats := sqlDB.Stats() - t.Logf("Initial stats - Open: %d, InUse: %d, Idle: %d", - initialStats.OpenConnections, initialStats.InUse, initialStats.Idle) + t.Log("Test tables created successfully") +} - // Wait for idle timeout - time.Sleep(3 * time.Second) +func TestConnectionPoolStats(t *testing.T) { + setupConnectionPoolTestTables(t) - finalStats := sqlDB.Stats() - t.Logf("Final stats - Open: %d, InUse: %d, Idle: %d", - finalStats.OpenConnections, finalStats.InUse, finalStats.Idle) - t.Logf("Lifetime stats - MaxIdleTimeClosed: %d, MaxLifetimeClosed: %d", - finalStats.MaxIdleTimeClosed, finalStats.MaxLifetimeClosed) + sqlDB, err := DB.DB() + if err != nil { + t.Fatalf("Failed to get sql.DB: %v", err) + } - // Verify pool is still functional + // Configure pool for testing + sqlDB.SetMaxOpenConns(8) + sqlDB.SetMaxIdleConns(3) + sqlDB.SetConnMaxLifetime(10 * time.Minute) + sqlDB.SetConnMaxIdleTime(2 * time.Second) + + // Get initial stats + initialStats := sqlDB.Stats() + t.Logf("Initial Connection Pool Stats:") + t.Logf(" Max Open Connections: %d", initialStats.MaxOpenConnections) + t.Logf(" Open Connections: %d", initialStats.OpenConnections) + t.Logf(" In Use: %d", initialStats.InUse) + t.Logf(" Idle: %d", initialStats.Idle) + + // Perform some database operations to exercise the pool + for i := 0; i < 5; i++ { var result string - err = DB.Raw("SELECT 'final_test' FROM dual").Scan(&result).Error + err := DB.Raw("SELECT ? FROM dual", fmt.Sprintf("pool_stat_test_%d", i)).Scan(&result).Error if err != nil { - t.Errorf("Pool should still work after idle timeout: %v", err) + t.Errorf("Query %d failed: %v", i, err) } - }) + } + + // Get stats after operations + afterStats := sqlDB.Stats() + t.Logf("Connection Pool Stats After Operations:") + t.Logf(" Open Connections: %d", afterStats.OpenConnections) + t.Logf(" In Use: %d", afterStats.InUse) + t.Logf(" Idle: %d", afterStats.Idle) + t.Logf(" Wait Count: %d", afterStats.WaitCount) + t.Logf(" Wait Duration: %v", afterStats.WaitDuration) + t.Logf(" Max Idle Closed: %d", afterStats.MaxIdleClosed) + t.Logf(" Max Idle Time Closed: %d", afterStats.MaxIdleTimeClosed) + t.Logf(" Max Lifetime Closed: %d", afterStats.MaxLifetimeClosed) + + // Basic sanity checks + if afterStats.MaxOpenConnections != 8 { + t.Errorf("Expected MaxOpenConnections: 8, got: %d", afterStats.MaxOpenConnections) + } + if afterStats.OpenConnections < 0 { + t.Error("Open connections should not be negative") + } + if afterStats.InUse < 0 { + t.Error("In use connections should not be negative") + } + if afterStats.Idle < 0 { + t.Error("Idle connections should not be negative") + } } diff --git a/tests/connection_test.go b/tests/connection_test.go index d839d4d..d29c1e6 100644 --- a/tests/connection_test.go +++ b/tests/connection_test.go @@ -71,7 +71,7 @@ func TestWithSingleConnection(t *testing.T) { } } -func TestTransactionCommitRollback(t *testing.T) { +func TestTransactionCommit(t *testing.T) { // Create test table type TestTxTable struct { ID uint `gorm:"primaryKey"` @@ -84,61 +84,69 @@ func TestTransactionCommitRollback(t *testing.T) { } defer DB.Migrator().DropTable(&TestTxTable{}) - // Test commit - t.Run("Commit", func(t *testing.T) { - tx := DB.Begin() - if tx.Error != nil { - t.Fatalf("Failed to begin transaction: %v", tx.Error) - } + tx := DB.Begin() + if tx.Error != nil { + t.Fatalf("Failed to begin transaction: %v", tx.Error) + } - record := TestTxTable{Name: "test_commit"} - if err := tx.Create(&record).Error; err != nil { - t.Errorf("Failed to create record in transaction: %v", err) - } + record := TestTxTable{Name: "test_commit"} + if err := tx.Create(&record).Error; err != nil { + t.Errorf("Failed to create record in transaction: %v", err) + } - if err := tx.Commit().Error; err != nil { - t.Errorf("Failed to commit transaction: %v", err) - } + if err := tx.Commit().Error; err != nil { + t.Errorf("Failed to commit transaction: %v", err) + } - // Verify record exists using quoted column name - var count int64 - if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_commit").Count(&count).Error; err != nil { - t.Errorf("Failed to count records: %v", err) - } - if count != 1 { - t.Errorf("Expected 1 record after commit, got %d", count) - } - }) + // Verify record exists using quoted column name + var count int64 + if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_commit").Count(&count).Error; err != nil { + t.Errorf("Failed to count records: %v", err) + } + if count != 1 { + t.Errorf("Expected 1 record after commit, got %d", count) + } +} - // Test rollback - t.Run("Rollback", func(t *testing.T) { - tx := DB.Begin() - if tx.Error != nil { - t.Fatalf("Failed to begin transaction: %v", tx.Error) - } +func TestTransactionRollback(t *testing.T) { + // Create test table + type TestTxTable struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"size:100;column:name"` + } - record := TestTxTable{Name: "test_rollback"} - if err := tx.Create(&record).Error; err != nil { - t.Errorf("Failed to create record in transaction: %v", err) - } + err := DB.AutoMigrate(&TestTxTable{}) + if err != nil { + t.Fatalf("Failed to migrate test table: %v", err) + } + defer DB.Migrator().DropTable(&TestTxTable{}) - if err := tx.Rollback().Error; err != nil { - t.Errorf("Failed to rollback transaction: %v", err) - } + tx := DB.Begin() + if tx.Error != nil { + t.Fatalf("Failed to begin transaction: %v", tx.Error) + } - // Verify record doesn't exist using quoted column name - var count int64 - if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_rollback").Count(&count).Error; err != nil { - t.Errorf("Failed to count records: %v", err) - } - if count != 0 { - t.Errorf("Expected 0 records after rollback, got %d", count) - } - }) + record := TestTxTable{Name: "test_rollback"} + if err := tx.Create(&record).Error; err != nil { + t.Errorf("Failed to create record in transaction: %v", err) + } + + if err := tx.Rollback().Error; err != nil { + t.Errorf("Failed to rollback transaction: %v", err) + } + + // Verify record doesn't exist using quoted column name + var count int64 + if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_rollback").Count(&count).Error; err != nil { + t.Errorf("Failed to count records: %v", err) + } + if count != 0 { + t.Errorf("Expected 0 records after rollback, got %d", count) + } } func TestConnectionAfterError(t *testing.T) { - // Execute an invalid query to cause an error + // Execute an invalid query err := DB.Exec("SELECT invalid_column FROM dual").Error if err == nil { t.Error("Expected error for invalid query, but got nil") @@ -306,7 +314,7 @@ func TestLargeResultSet(t *testing.T) { } func TestSessionInfo(t *testing.T) { - // Test USER function first (should always work) + // Test USER function first var username string err := DB.Raw("SELECT USER FROM dual").Scan(&username).Error if err != nil { @@ -337,7 +345,6 @@ func TestSessionInfo(t *testing.T) { return } - // Log what we found t.Logf("Session Info - User: %s", username) if sessionInfo.InstanceName != "" { t.Logf("Instance: %s", sessionInfo.InstanceName) @@ -346,7 +353,7 @@ func TestSessionInfo(t *testing.T) { t.Logf("Database: %s", sessionInfo.DatabaseName) } - // Only require username - instance/database names might not be available in all environments + // Only require username if sessionInfo.InstanceName == "" || sessionInfo.DatabaseName == "" { t.Skip("SYS_CONTEXT functions unavailable - likely permissions or configuration issue") } @@ -373,84 +380,20 @@ func TestConnectionPing(t *testing.T) { } } -func TestIntervalDataTypes(t *testing.T) { - // Test Year to Month interval - t.Run("YearToMonth", func(t *testing.T) { - var result string - err := DB.Raw("SELECT INTERVAL '2-3' YEAR TO MONTH FROM dual").Scan(&result).Error - if err != nil { - t.Errorf("Year to Month interval query failed: %v", err) - } - t.Logf("Year to Month interval result: %s", result) - }) - - // Test Day to Second interval - t.Run("DayToSecond", func(t *testing.T) { - var result string - err := DB.Raw("SELECT INTERVAL '4 5:12:10.222' DAY TO SECOND FROM dual").Scan(&result).Error - if err != nil { - t.Errorf("Day to Second interval query failed: %v", err) - } - t.Logf("Day to Second interval result: %s", result) - }) -} - -func TestConnectionPoolStats(t *testing.T) { - sqlDB, err := DB.DB() +func TestIntervalYearToMonth(t *testing.T) { + var result string + err := DB.Raw("SELECT INTERVAL '2-3' YEAR TO MONTH FROM dual").Scan(&result).Error if err != nil { - t.Fatalf("Failed to get sql.DB: %v", err) - } - - stats := sqlDB.Stats() - t.Logf("Connection Pool Stats:") - t.Logf(" Open Connections: %d", stats.OpenConnections) - t.Logf(" In Use: %d", stats.InUse) - t.Logf(" Idle: %d", stats.Idle) - t.Logf(" Wait Count: %d", stats.WaitCount) - t.Logf(" Wait Duration: %v", stats.WaitDuration) - t.Logf(" Max Idle Closed: %d", stats.MaxIdleClosed) - t.Logf(" Max Idle Time Closed: %d", stats.MaxIdleTimeClosed) - t.Logf(" Max Lifetime Closed: %d", stats.MaxLifetimeClosed) - - // Basic sanity checks - if stats.OpenConnections < 0 { - t.Error("Open connections should not be negative") - } - if stats.InUse < 0 { - t.Error("In use connections should not be negative") - } - if stats.Idle < 0 { - t.Error("Idle connections should not be negative") + t.Errorf("Year to Month interval query failed: %v", err) } + t.Logf("Year to Month interval result: %s", result) } -func TestDatabaseVersionInfo(t *testing.T) { - var versionInfo struct { - Version string `gorm:"column:version"` - Banner string `gorm:"column:banner"` - } - - query := ` - SELECT - (SELECT VERSION FROM V$INSTANCE) as version, - (SELECT BANNER FROM V$VERSION WHERE ROWNUM = 1) as banner - FROM dual - ` - - err := DB.Raw(query).Scan(&versionInfo).Error - if err == nil && versionInfo.Version != "" && versionInfo.Banner != "" { - t.Logf("Database Version: %s", versionInfo.Version) - t.Logf("Database Banner: %s", versionInfo.Banner) - return - } - - // Fallback to PRODUCT_COMPONENT_VERSION - var simpleVersion string - err = DB.Raw("SELECT VERSION FROM PRODUCT_COMPONENT_VERSION WHERE PRODUCT LIKE 'Oracle%' AND ROWNUM = 1").Scan(&simpleVersion).Error - if err == nil && simpleVersion != "" { - t.Logf("Database Version: %s", simpleVersion) - return +func TestIntervalDayToSecond(t *testing.T) { + var result string + err := DB.Raw("SELECT INTERVAL '4 5:12:10.222' DAY TO SECOND FROM dual").Scan(&result).Error + if err != nil { + t.Errorf("Day to Second interval query failed: %v", err) } - - t.Skip("Could not retrieve database version info - insufficient privileges to access system views") + t.Logf("Day to Second interval result: %s", result) } diff --git a/tests/passed-tests.txt b/tests/passed-tests.txt index ad16a5a..69ded3b 100644 --- a/tests/passed-tests.txt +++ b/tests/passed-tests.txt @@ -38,24 +38,32 @@ TestPluginCallbacks TestCallbacksGet TestCallbacksRemove TestWithSingleConnection -TestTransactionCommitRollback +TestTransactionCommit +TestTransactionRollback TestConnectionAfterError +TestConnectionWithInvalidQuery +TestMultipleSequentialConnections +TestConnectionAfterDBClose +TestConnectionHandlesPanic TestConcurrentConnections TestContextTimeout TestLargeResultSet TestSessionInfo TestConnectionPing -TestIntervalDataTypes +TestIntervalYearToMonth +TestIntervalDayToSecond TestConnectionPoolStats -TestDatabaseVersionInfo -TestConnectionPooling -TestPoolConfiguration -TestBasicPoolOperations -TestPoolExhaustion -TestConcurrentDatabaseOperations -TestPoolConnectionRecovery -TestPoolLifecycleManagement -TestConnectionWithInvalidQuery +TestPoolValidSettings +TestPoolInvalidSettings +TestBasicCRUDOperations +TestPoolStatistics +TestPoolExhaustionBehavior +TestConcurrentCRUDMix +TestRecoveryAfterInvalidQuery +TestContextCancellationHandling +TestConnectionPingAndHealth +TestPoolStatisticsOverTime +setupConnectionPoolTestTables TestNestedConnection TestMultipleSequentialConnections TestConnectionAfterDBClose From 9c1208b89776cd13dd48f756d3cedbe3ae4f0238 Mon Sep 17 00:00:00 2001 From: Saumil Diwaker Date: Thu, 21 Aug 2025 22:03:19 +0530 Subject: [PATCH 4/6] Moved tests --- tests/connection_test.go | 106 -------------------------------------- tests/passed-tests.txt | 1 - tests/query_test.go | 32 ++++++++++++ tests/transaction_test.go | 74 ++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 107 deletions(-) diff --git a/tests/connection_test.go b/tests/connection_test.go index d29c1e6..f3a02c9 100644 --- a/tests/connection_test.go +++ b/tests/connection_test.go @@ -71,80 +71,6 @@ func TestWithSingleConnection(t *testing.T) { } } -func TestTransactionCommit(t *testing.T) { - // Create test table - type TestTxTable struct { - ID uint `gorm:"primaryKey"` - Name string `gorm:"size:100;column:name"` - } - - err := DB.AutoMigrate(&TestTxTable{}) - if err != nil { - t.Fatalf("Failed to migrate test table: %v", err) - } - defer DB.Migrator().DropTable(&TestTxTable{}) - - tx := DB.Begin() - if tx.Error != nil { - t.Fatalf("Failed to begin transaction: %v", tx.Error) - } - - record := TestTxTable{Name: "test_commit"} - if err := tx.Create(&record).Error; err != nil { - t.Errorf("Failed to create record in transaction: %v", err) - } - - if err := tx.Commit().Error; err != nil { - t.Errorf("Failed to commit transaction: %v", err) - } - - // Verify record exists using quoted column name - var count int64 - if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_commit").Count(&count).Error; err != nil { - t.Errorf("Failed to count records: %v", err) - } - if count != 1 { - t.Errorf("Expected 1 record after commit, got %d", count) - } -} - -func TestTransactionRollback(t *testing.T) { - // Create test table - type TestTxTable struct { - ID uint `gorm:"primaryKey"` - Name string `gorm:"size:100;column:name"` - } - - err := DB.AutoMigrate(&TestTxTable{}) - if err != nil { - t.Fatalf("Failed to migrate test table: %v", err) - } - defer DB.Migrator().DropTable(&TestTxTable{}) - - tx := DB.Begin() - if tx.Error != nil { - t.Fatalf("Failed to begin transaction: %v", tx.Error) - } - - record := TestTxTable{Name: "test_rollback"} - if err := tx.Create(&record).Error; err != nil { - t.Errorf("Failed to create record in transaction: %v", err) - } - - if err := tx.Rollback().Error; err != nil { - t.Errorf("Failed to rollback transaction: %v", err) - } - - // Verify record doesn't exist using quoted column name - var count int64 - if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_rollback").Count(&count).Error; err != nil { - t.Errorf("Failed to count records: %v", err) - } - if count != 0 { - t.Errorf("Expected 0 records after rollback, got %d", count) - } -} - func TestConnectionAfterError(t *testing.T) { // Execute an invalid query err := DB.Exec("SELECT invalid_column FROM dual").Error @@ -281,38 +207,6 @@ func TestContextTimeout(t *testing.T) { } } -func TestLargeResultSet(t *testing.T) { - var results []struct { - RowNum int `gorm:"column:ROW_NUM"` - Value string `gorm:"column:VALUE"` - } - - query := ` - SELECT LEVEL as row_num, 'row_' || LEVEL as value - FROM dual - CONNECT BY LEVEL <= 1000 - ` - - err := DB.Raw(query).Scan(&results).Error - if err != nil { - t.Errorf("Failed to execute large result set query: %v", err) - return - } - - if len(results) != 1000 { - t.Errorf("Expected 1000 rows, got %d", len(results)) - return - } - - // Verify first and last rows - if results[0].RowNum != 1 || results[0].Value != "row_1" { - t.Errorf("First row incorrect: %+v", results[0]) - } - if results[999].RowNum != 1000 || results[999].Value != "row_1000" { - t.Errorf("Last row incorrect: %+v", results[999]) - } -} - func TestSessionInfo(t *testing.T) { // Test USER function first var username string diff --git a/tests/passed-tests.txt b/tests/passed-tests.txt index 69ded3b..90af8c9 100644 --- a/tests/passed-tests.txt +++ b/tests/passed-tests.txt @@ -63,7 +63,6 @@ TestRecoveryAfterInvalidQuery TestContextCancellationHandling TestConnectionPingAndHealth TestPoolStatisticsOverTime -setupConnectionPoolTestTables TestNestedConnection TestMultipleSequentialConnections TestConnectionAfterDBClose diff --git a/tests/query_test.go b/tests/query_test.go index 032f949..26807d9 100644 --- a/tests/query_test.go +++ b/tests/query_test.go @@ -1664,3 +1664,35 @@ func TestNumberPrecision(t *testing.T) { t.Errorf("Big number mismatch: expected %d, got %d", record.BigNumber, result.BigNumber) } } + +func TestLargeResultSet(t *testing.T) { + var results []struct { + RowNum int `gorm:"column:ROW_NUM"` + Value string `gorm:"column:VALUE"` + } + + query := ` + SELECT LEVEL as row_num, 'row_' || LEVEL as value + FROM dual + CONNECT BY LEVEL <= 1000 + ` + + err := DB.Raw(query).Scan(&results).Error + if err != nil { + t.Errorf("Failed to execute large result set query: %v", err) + return + } + + if len(results) != 1000 { + t.Errorf("Expected 1000 rows, got %d", len(results)) + return + } + + // Verify first and last rows + if results[0].RowNum != 1 || results[0].Value != "row_1" { + t.Errorf("First row incorrect: %+v", results[0]) + } + if results[999].RowNum != 1000 || results[999].Value != "row_1000" { + t.Errorf("Last row incorrect: %+v", results[999]) + } +} diff --git a/tests/transaction_test.go b/tests/transaction_test.go index afab390..0ae2ba6 100644 --- a/tests/transaction_test.go +++ b/tests/transaction_test.go @@ -689,3 +689,77 @@ func TestTransactionWithRawSQL(t *testing.T) { t.Errorf("Expected age %d, got %d", user.Age+1, result.Age) } } + +func TestTransactionCommit(t *testing.T) { + // Create test table + type TestTxTable struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"size:100;column:name"` + } + + err := DB.AutoMigrate(&TestTxTable{}) + if err != nil { + t.Fatalf("Failed to migrate test table: %v", err) + } + defer DB.Migrator().DropTable(&TestTxTable{}) + + tx := DB.Begin() + if tx.Error != nil { + t.Fatalf("Failed to begin transaction: %v", tx.Error) + } + + record := TestTxTable{Name: "test_commit"} + if err := tx.Create(&record).Error; err != nil { + t.Errorf("Failed to create record in transaction: %v", err) + } + + if err := tx.Commit().Error; err != nil { + t.Errorf("Failed to commit transaction: %v", err) + } + + // Verify record exists using quoted column name + var count int64 + if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_commit").Count(&count).Error; err != nil { + t.Errorf("Failed to count records: %v", err) + } + if count != 1 { + t.Errorf("Expected 1 record after commit, got %d", count) + } +} + +func TestTransactionRollback(t *testing.T) { + // Create test table + type TestTxTable struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"size:100;column:name"` + } + + err := DB.AutoMigrate(&TestTxTable{}) + if err != nil { + t.Fatalf("Failed to migrate test table: %v", err) + } + defer DB.Migrator().DropTable(&TestTxTable{}) + + tx := DB.Begin() + if tx.Error != nil { + t.Fatalf("Failed to begin transaction: %v", tx.Error) + } + + record := TestTxTable{Name: "test_rollback"} + if err := tx.Create(&record).Error; err != nil { + t.Errorf("Failed to create record in transaction: %v", err) + } + + if err := tx.Rollback().Error; err != nil { + t.Errorf("Failed to rollback transaction: %v", err) + } + + // Verify record doesn't exist using quoted column name + var count int64 + if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_rollback").Count(&count).Error; err != nil { + t.Errorf("Failed to count records: %v", err) + } + if count != 0 { + t.Errorf("Expected 0 records after rollback, got %d", count) + } +} From 25e863495a8c32bd29150aea004e36dd44695cad Mon Sep 17 00:00:00 2001 From: Saumil Diwaker Date: Thu, 21 Aug 2025 23:11:40 +0530 Subject: [PATCH 5/6] Removed test functions and will move to separate files later. --- tests/connection_test.go | 18 ---------- tests/passed-tests.txt | 4 --- tests/transaction_test.go | 74 --------------------------------------- 3 files changed, 96 deletions(-) diff --git a/tests/connection_test.go b/tests/connection_test.go index f3a02c9..65bd3e7 100644 --- a/tests/connection_test.go +++ b/tests/connection_test.go @@ -273,21 +273,3 @@ func TestConnectionPing(t *testing.T) { t.Errorf("Database ping with context failed: %v", err) } } - -func TestIntervalYearToMonth(t *testing.T) { - var result string - err := DB.Raw("SELECT INTERVAL '2-3' YEAR TO MONTH FROM dual").Scan(&result).Error - if err != nil { - t.Errorf("Year to Month interval query failed: %v", err) - } - t.Logf("Year to Month interval result: %s", result) -} - -func TestIntervalDayToSecond(t *testing.T) { - var result string - err := DB.Raw("SELECT INTERVAL '4 5:12:10.222' DAY TO SECOND FROM dual").Scan(&result).Error - if err != nil { - t.Errorf("Day to Second interval query failed: %v", err) - } - t.Logf("Day to Second interval result: %s", result) -} diff --git a/tests/passed-tests.txt b/tests/passed-tests.txt index 90af8c9..f6cbde8 100644 --- a/tests/passed-tests.txt +++ b/tests/passed-tests.txt @@ -38,8 +38,6 @@ TestPluginCallbacks TestCallbacksGet TestCallbacksRemove TestWithSingleConnection -TestTransactionCommit -TestTransactionRollback TestConnectionAfterError TestConnectionWithInvalidQuery TestMultipleSequentialConnections @@ -50,8 +48,6 @@ TestContextTimeout TestLargeResultSet TestSessionInfo TestConnectionPing -TestIntervalYearToMonth -TestIntervalDayToSecond TestConnectionPoolStats TestPoolValidSettings TestPoolInvalidSettings diff --git a/tests/transaction_test.go b/tests/transaction_test.go index 0ae2ba6..afab390 100644 --- a/tests/transaction_test.go +++ b/tests/transaction_test.go @@ -689,77 +689,3 @@ func TestTransactionWithRawSQL(t *testing.T) { t.Errorf("Expected age %d, got %d", user.Age+1, result.Age) } } - -func TestTransactionCommit(t *testing.T) { - // Create test table - type TestTxTable struct { - ID uint `gorm:"primaryKey"` - Name string `gorm:"size:100;column:name"` - } - - err := DB.AutoMigrate(&TestTxTable{}) - if err != nil { - t.Fatalf("Failed to migrate test table: %v", err) - } - defer DB.Migrator().DropTable(&TestTxTable{}) - - tx := DB.Begin() - if tx.Error != nil { - t.Fatalf("Failed to begin transaction: %v", tx.Error) - } - - record := TestTxTable{Name: "test_commit"} - if err := tx.Create(&record).Error; err != nil { - t.Errorf("Failed to create record in transaction: %v", err) - } - - if err := tx.Commit().Error; err != nil { - t.Errorf("Failed to commit transaction: %v", err) - } - - // Verify record exists using quoted column name - var count int64 - if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_commit").Count(&count).Error; err != nil { - t.Errorf("Failed to count records: %v", err) - } - if count != 1 { - t.Errorf("Expected 1 record after commit, got %d", count) - } -} - -func TestTransactionRollback(t *testing.T) { - // Create test table - type TestTxTable struct { - ID uint `gorm:"primaryKey"` - Name string `gorm:"size:100;column:name"` - } - - err := DB.AutoMigrate(&TestTxTable{}) - if err != nil { - t.Fatalf("Failed to migrate test table: %v", err) - } - defer DB.Migrator().DropTable(&TestTxTable{}) - - tx := DB.Begin() - if tx.Error != nil { - t.Fatalf("Failed to begin transaction: %v", tx.Error) - } - - record := TestTxTable{Name: "test_rollback"} - if err := tx.Create(&record).Error; err != nil { - t.Errorf("Failed to create record in transaction: %v", err) - } - - if err := tx.Rollback().Error; err != nil { - t.Errorf("Failed to rollback transaction: %v", err) - } - - // Verify record doesn't exist using quoted column name - var count int64 - if err := DB.Model(&TestTxTable{}).Where("\"name\" = ?", "test_rollback").Count(&count).Error; err != nil { - t.Errorf("Failed to count records: %v", err) - } - if count != 0 { - t.Errorf("Expected 0 records after rollback, got %d", count) - } -} From 3dfe0f6e335870660677c98c73513a9268f8e6e9 Mon Sep 17 00:00:00 2001 From: Saumil Diwaker Date: Fri, 22 Aug 2025 10:52:56 +0530 Subject: [PATCH 6/6] Removed passed-tests.txt --- tests/passed-tests.txt | 431 ----------------------------------------- 1 file changed, 431 deletions(-) delete mode 100644 tests/passed-tests.txt diff --git a/tests/passed-tests.txt b/tests/passed-tests.txt deleted file mode 100644 index f6cbde8..0000000 --- a/tests/passed-tests.txt +++ /dev/null @@ -1,431 +0,0 @@ -TestBelongsToAssociation -TestBelongsToAssociationForSlice -TestBelongsToDefaultValue -TestBelongsToAssociationUnscoped -TestHasManyAssociation -TestSingleTableHasManyAssociation -TestHasManyAssociationForSlice -TestSingleTableHasManyAssociationForSlice -TestPolymorphicHasManyAssociation -TestPolymorphicHasManyAssociationForSlice -TestHasManyAssociationUnscoped -TestHasManyAssociationReplaceWithNonValidValue -TestHasOneAssociation -TestHasOneAssociationWithSelect -TestHasOneAssociationForSlice -TestPolymorphicHasOneAssociation -TestPolymorphicHasOneAssociationForSlice -TestHasOneAssociationReplaceWithNonValidValue -TestMany2ManyAssociation -TestMany2ManyOmitAssociations -TestMany2ManyAssociationForSlice -TestSingleTableMany2ManyAssociation -TestSingleTableMany2ManyAssociationForSlice -TestDuplicateMany2ManyAssociation -TestConcurrentMany2ManyAssociation -TestMany2ManyDuplicateBelongsToAssociation -TestInvalidAssociation -TestAssociationNotNullClear -#TestForeignKeyConstraints -#TestForeignKeyConstraintsBelongsTo -#TestFullSaveAssociations -TestSaveBelongsCircularReference -TestSaveHasManyCircularReference -TestAssociationError -TestAssociationEmptyQueryClause -TestCallbacks -TestPluginCallbacks -TestCallbacksGet -TestCallbacksRemove -TestWithSingleConnection -TestConnectionAfterError -TestConnectionWithInvalidQuery -TestMultipleSequentialConnections -TestConnectionAfterDBClose -TestConnectionHandlesPanic -TestConcurrentConnections -TestContextTimeout -TestLargeResultSet -TestSessionInfo -TestConnectionPing -TestConnectionPoolStats -TestPoolValidSettings -TestPoolInvalidSettings -TestBasicCRUDOperations -TestPoolStatistics -TestPoolExhaustionBehavior -TestConcurrentCRUDMix -TestRecoveryAfterInvalidQuery -TestContextCancellationHandling -TestConnectionPingAndHealth -TestPoolStatisticsOverTime -TestNestedConnection -TestMultipleSequentialConnections -TestConnectionAfterDBClose -TestConnectionHandlesPanic -TestConcurrentConnections -TestCountWithGroup -TestCount -TestCountOnEmptyTable -TestCountWithHaving -TestCountWithSoftDelete -TestCreate -TestCreateInBatches -TestCreateInBatchesWithDefaultSize -TestCreateFromMap -#TestCreateWithAssociations -TestBulkCreateWithAssociations -TestBulkCreatePtrDataWithAssociations -#TestPolymorphicHasOne -TestCreateEmptyStruct -TestCreateEmptySlice -TestCreateInvalidSlice -TestCreateWithExistingTimestamp -TestCreateWithNowFuncOverride -TestCreateWithNoGORMPrimaryKey -TestSelectWithCreate -TestOmitWithCreate -TestFirstOrCreateNotExistsTable -TestFirstOrCreateWithPrimaryKey -TestCreateFromSubQuery -TestCreateNilPointer -TestFirstOrCreateRowsAffected -TestCreateWithAutoIncrementCompositeKey -TestCreateOnConflictWithDefaultNull -TestCreateFromMapWithoutPK -TestCreateFromMapWithTable -TestCustomizeColumn -TestCustomColumnAndIgnoredFieldClash -TestCustomizeField -TestDefaultValue -TestDelete -TestDeleteWithTable -TestInlineCondDelete -TestBlockGlobalDelete -TestDeleteWithAssociations -TestDeleteAssociationsWithUnscoped -TestDeleteSliceWithAssociations -TestSoftDeleteReturning -TestDeleteReturning -TestDeleteByPrimaryKeyOnly -TestHardDeleteAfterSoftDelete -TestDeleteWithLimitAndOrder -TestRawSQLDeleteWithLimit -TestRawSQLDelete -TestDeleteCustomTableName -TestDeleteWithSQLExecution -TestDeleteWithCompositePrimaryKey -TestDeleteOmitAssociations -TestDeleteWithSelectField -TestUnscopedBatchDelete -TestDeleteByWhereClause -TestDeleteWithOnDeleteCascade -TestUnscopedDeleteByIDs -TestDistinct -TestEmbeddedStruct -TestEmbeddedPointerTypeStruct -TestEmbeddedScanValuer -TestEmbeddedRelations -TestEmbeddedTagSetting -TestZeroValueEmbeddedStruct -TestUpdateEmbeddedFields -TestDialectorWithErrorTranslatorSupport -TestSupportedDialectorWithErrDuplicatedKey -TestSupportedDialectorWithErrForeignKeyViolated -#TestGenericsCreate -TestGenericsCreateInBatches -#TestGenericsExecAndUpdate -TestGenericsRow -TestGenericsDelete -TestGenericsFindInBatches -TestGenericsScopes -#TestGenericsJoins -TestGenericsNestedJoins -#TestGenericsPreloads -#TestGenericsNestedPreloads -TestGenericsDistinct -TestGenericsGroupHaving -TestGenericsSubQuery -TestGenericsUpsert -TestGenericsWithResult -TestGenericsReuse -TestGenericsWithTransaction -TestGenericsToSQL -TestOpen -TestReturningWithNullToZeroValues -TestGroupBy -TestRunCallbacks -TestCallbacksWithErrors -#TestUseDBInHooks -TestSetColumn -TestHooksForSlice -TestFailedToSaveAssociationShouldRollback -TestUpdateCallbacks -TestPropagateUnscoped -#TestOverrideJoinTable -TestJoins -TestJoinsForSlice -TestJoinConds -TestJoinOn -TestJoinsWithSelect -TestJoinWithOmit -TestJoinCount -TestJoinWithSoftDeleted -TestInnerJoins -TestJoinWithSameColumnName -TestJoinArgsWithDB -TestNestedJoins -TestJoinsPreload_Issue7013 -TestJoinsPreload_Issue7013_RelationEmpty -TestJoinsPreload_Issue7013_NoEntries -#TestExceptionsWithInvalidSql -TestSetAndGet -TestMigrate -TestAutoMigrateInt8PG -TestAutoMigrateSelfReferential -#TestAutoMigrateNullable -TestSmartMigrateColumn -TestMigrateWithColumnComment -TestMigrateWithUniqueIndex -TestMigrateTable -TestMigrateIndexes -TestMigrateColumnOrder -#TestMigrateColumns -#TestMigrateConstraint -TestMigrateIndexesWithDynamicTableName -TestMigrateSerialColumn -TestMigrateWithSpecialName -TestMigrateAutoIncrement -TestPrimarykeyID -TestCurrentTimestamp -TestUniqueColumn -TestDifferentTypeWithoutDeclaredLength -TestMigrateArrayTypeModel -TestMigrateDonotAlterColumn -TestMigrateSameEmbeddedFieldName -#TestMigrateWithDefaultValue -TestMigrateMySQLWithCustomizedTypes -TestMigrateIgnoreRelations -TestMigrateView -TestMigrateExistingBoolColumnPG -#TestMigrateWithUniqueIndexAndUnique -#TestManyToManyWithMultiPrimaryKeys -#TestManyToManyWithCustomizedForeignKeys -#TestManyToManyWithCustomizedForeignKeys2 -#TestCompositePrimaryKeysAssociations -TestNamedArg -TestNamedArgMultipleSameParamRefs -TestNamedArgNullValues -TestNamedArgMixedNamedAndMapParams -TestNamedArgUnusedParameter -TestNamedArgCaseSensitivity -TestNamedArgInClause -TestNamedArgReservedWordParam -TestNamedPolymorphic -TestNonStdPrimaryKeyAndDefaultValues -TestNestedPreload1 -TestNestedPreload2 -TestNestedPreload3 -TestNestedPreload4 -TestNestedPreload5 -TestNestedPreload6 -TestNestedPreload7 -TestNestedPreload8 -TestNestedPreload9 -TestNestedPreload10 -TestNestedPreload11 -TestNestedPreload12 -TestManyToManyPreloadWithMultiPrimaryKeys -TestManyToManyPreloadForNestedPointer -TestNestedManyToManyPreload -TestNestedManyToManyPreload2 -TestNestedManyToManyPreload3 -TestNestedManyToManyPreload3ForStruct -TestNestedManyToManyPreload4 -TestManyToManyPreloadForPointer -TestNilPointerSlice -TestNilPointerSlice2 -TestPrefixedPreloadDuplication -TestPreloadManyToManyCallbacks -TestPreloadWithAssociations -TestNestedPreload -TestNestedPreloadForSlice -#TestPreloadWithConds -TestNestedPreloadWithConds -TestPreloadEmptyData -TestPreloadGoroutine -TestPreloadWithDiffModel -TestNestedPreloadWithUnscoped -TestNestedPreloadWithNestedJoin -TestMergeNestedPreloadWithNestedJoin -TestNestedPreloadWithPointerJoin -TestEmbedPreload -TestPreparedStmt -TestPreparedStmtFromTransaction -TestPreparedStmtLruFromTransaction -TestPreparedStmtDeadlock -TestPreparedStmtInTransaction -TestPreparedStmtClose -TestPreparedStmtConcurrentClose -#TestFind -TestQueryWithAssociation -TestFindInBatches -TestFindInBatchesWithOffsetLimit -TestFindInBatchesWithError -TestFillSmallerStruct -TestFillSmallerStructWithAllFields -TestNot -TestNotWithAllFields -TestOr -TestOrWithAllFields -#TestPluck -TestSelect -TestOmit -TestOmitWithAllFields -TestMapColumns -TestPluckWithSelect -#TestSelectWithVariables -TestSelectWithArrayInput -TestCustomizedTypePrimaryKey -TestStringPrimaryKeyForNumericValueStartingWithZero -TestSearchWithEmptyChain -TestOrder -TestOrderWithAllFields -TestLimit -TestOffset -TestSearchWithMap -TestSearchWithStruct -TestSubQuery -TestSubQueryWithRaw -TestSubQueryWithHaving -TestScanNullValue -TestQueryWithTableAndConditions -TestQueryWithTableAndConditionsAndAllFields -TestQueryScannerWithSingleColumn -TestQueryResetNullValue -TestQueryError -TestQueryScanToArray -TestRownum -TestNullHandling -TestDualTable -TestStringConcatenation -TestRegexpFunctions -TestBindVariables -TestNumberPrecision -TestScan -TestScanRows -TestScanRowsNullValuesScanToFieldDefault -TestScanToEmbedded -#TestScannerValuer -#TestScannerValuerWithFirstOrCreate -#TestInvalidValuer -TestGORMValuer -TestScopes -TestComplexScopes -TestSerializer -TestSerializerZeroValue -TestSerializerAssignFirstOrCreate -TestSoftDelete -TestDeletedAtUnMarshal -TestDeletedAtOneOr -TestSoftDeleteZeroValue -TestSoftDeleteWithWhereClause -TestSoftDeleteTimeFilter -TestSoftDeleteIdempotent -TestSoftDeleteWithClause -TestSoftDeleteWithCompositeKey -TestSoftDeleteWithPreload -TestSoftDeletedRecordReinsert -TestRow -TestRows -TestRaw -TestRowsWithGroup -TestQueryRaw -TestDryRun -TestExplainSQL -TestGroupConditions -TestCombineStringConditions -TestFromWithJoins -TestToSQL -TestTable -TestTableWithAllFields -TestTableWithNamer -TestTransaction -TestCancelTransaction -TestTransactionWithBlock -TestTransactionRaiseErrorOnRollbackAfterCommit -TestTransactionWithSavePoint -TestNestedTransactionWithBlock -#TestDeeplyNestedTransactionWithBlockAndWrappedCallback -TestDisabledNestedTransaction -TestTransactionOnClosedConn -TestTransactionWithHooks -TestTransactionWithDefaultTimeout -TestComplexNestedTransactions -TestTransactionWithRawSQL -TestUpdateBelongsTo -#TestUpdateHasManyAssociations -TestUpdateHasOne -TestUpdateMany2ManyAssociations -TestUpdate -TestUpdates -TestUpdateColumn -TestBlockGlobalUpdate -TestSelectWithUpdate -TestSelectWithUpdateWithMap -TestWithUpdateWithInvalidMap -TestOmitWithUpdate -TestOmitWithUpdateWithMap -TestSelectWithUpdateColumn -TestOmitWithUpdateColumn -TestUpdateColumnsSkipsAssociations -TestUpdatesWithBlankValues -TestUpdatesTableWithIgnoredValues -TestUpdateFromSubQuery -TestIdempotentSave -TestSave -TestSaveWithPrimaryValue -TestUpdateReturning -TestUpdateWithDiffSchema -TestSaveWithHooks -TestUpdateFrom -TestUpdateWithMultipleWhere -TestUpdatePrimaryKeyField -TestUpdatesWithZeroValue -TestUpdateStringFieldToNull -TestUpdateColumnsWithNulls -TestUpdatesWithStructPointer -TestUpdateCustomDataType -TestBatchUpdateSlice -#TestMixedSaveBatch -#TestDistinctComputedColumn -TestDistinctWithVaryingCase -#TestDistinctWithAggregation -TestUpsert -TestUpsertSlice -TestUpsertWithSave -TestFindOrInitialize -TestUpdateWithMissWhere -TestUpsertCompositePK -TestUpsertIgnoreColumn -TestUpsertReturning -TestUpsertNullValues -TestUpsertSliceMixed -TestUpsertWithExpressions -TestUpsertPrimaryKeyNotUpdated -TestUpsertWithNullUnique -TestUpsertLargeBatch -TestUpsertFromSubquery -TestFindOrCreate -BenchmarkCreate -BenchmarkFind -BenchmarkScan -BenchmarkScanSlice -BenchmarkScanSlicePointer -BenchmarkUpdate -BenchmarkDelete -TestRawQueryInjection -TestWhereClauseInjection -TestUpdateInjection -TestFirstOrCreateInjection -TestUserInsertScenarios