mirror of
https://github.com/gin-gonic/gin.git
synced 2025-12-03 05:12:17 +08:00
test(gin): resolve race conditions in integration tests (#4453)
- Implement TestRebuild404Handlers to verify 404 handler chain rebuilding when global middleware is added via Use() - Add waitForServerReady helper with exponential backoff to replace unreliable time.Sleep() calls in integration tests - Fix race conditions in TestRunEmpty, TestRunEmptyWithEnv, and TestRunWithPort by using proper server readiness checks - All tests now pass consistently with -race flag This addresses the empty test function and eliminates flaky test failures caused by insufficient wait times for server startup. Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
parent
583db590ec
commit
f416d1e594
@ -70,9 +70,10 @@ func TestRunEmpty(t *testing.T) {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
assert.NoError(t, router.Run())
|
||||
}()
|
||||
// have to wait for the goroutine to start and run the server
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
// Wait for server to be ready with exponential backoff
|
||||
err := waitForServerReady("http://localhost:8080/example", 10)
|
||||
require.NoError(t, err, "server should start successfully")
|
||||
|
||||
require.Error(t, router.Run(":8080"))
|
||||
testRequest(t, "http://localhost:8080/example")
|
||||
@ -213,9 +214,10 @@ func TestRunEmptyWithEnv(t *testing.T) {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
assert.NoError(t, router.Run())
|
||||
}()
|
||||
// have to wait for the goroutine to start and run the server
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
// Wait for server to be ready with exponential backoff
|
||||
err := waitForServerReady("http://localhost:3123/example", 10)
|
||||
require.NoError(t, err, "server should start successfully")
|
||||
|
||||
require.Error(t, router.Run(":3123"))
|
||||
testRequest(t, "http://localhost:3123/example")
|
||||
@ -234,9 +236,10 @@ func TestRunWithPort(t *testing.T) {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
assert.NoError(t, router.Run(":5150"))
|
||||
}()
|
||||
// have to wait for the goroutine to start and run the server
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
// Wait for server to be ready with exponential backoff
|
||||
err := waitForServerReady("http://localhost:5150/example", 10)
|
||||
require.NoError(t, err, "server should start successfully")
|
||||
|
||||
require.Error(t, router.Run(":5150"))
|
||||
testRequest(t, "http://localhost:5150/example")
|
||||
|
||||
23
gin_test.go
23
gin_test.go
@ -545,6 +545,29 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRebuild404Handlers(t *testing.T) {
|
||||
var middleware0 HandlerFunc = func(c *Context) {}
|
||||
var middleware1 HandlerFunc = func(c *Context) {}
|
||||
|
||||
router := New()
|
||||
|
||||
// Initially, allNoRoute should be nil
|
||||
assert.Nil(t, router.allNoRoute)
|
||||
|
||||
// Set NoRoute handlers
|
||||
router.NoRoute(middleware0)
|
||||
assert.Len(t, router.allNoRoute, 1)
|
||||
assert.Len(t, router.noRoute, 1)
|
||||
compareFunc(t, router.allNoRoute[0], middleware0)
|
||||
|
||||
// Add Use middleware should trigger rebuild404Handlers
|
||||
router.Use(middleware1)
|
||||
assert.Len(t, router.allNoRoute, 2)
|
||||
assert.Len(t, router.Handlers, 1)
|
||||
assert.Len(t, router.noRoute, 1)
|
||||
|
||||
// Global middleware should come first
|
||||
compareFunc(t, router.allNoRoute[0], middleware1)
|
||||
compareFunc(t, router.allNoRoute[1], middleware0)
|
||||
}
|
||||
|
||||
func TestNoMethodWithGlobalHandlers(t *testing.T) {
|
||||
|
||||
@ -4,7 +4,11 @@
|
||||
|
||||
package gin
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CreateTestContext returns a fresh Engine and a Context associated with it.
|
||||
// This is useful for tests that need to set up a new Gin engine instance
|
||||
@ -29,3 +33,28 @@ func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {
|
||||
c.writermem.reset(w)
|
||||
return
|
||||
}
|
||||
|
||||
// waitForServerReady waits for a server to be ready by making HTTP requests
|
||||
// with exponential backoff. This is more reliable than time.Sleep() for testing.
|
||||
func waitForServerReady(url string, maxAttempts int) error {
|
||||
client := &http.Client{
|
||||
Timeout: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
resp, err := client.Get(url)
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exponential backoff: 10ms, 20ms, 40ms, 80ms, 160ms...
|
||||
backoff := time.Duration(10*(1<<uint(i))) * time.Millisecond
|
||||
if backoff > 500*time.Millisecond {
|
||||
backoff = 500 * time.Millisecond
|
||||
}
|
||||
time.Sleep(backoff)
|
||||
}
|
||||
|
||||
return fmt.Errorf("server at %s did not become ready after %d attempts", url, maxAttempts)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user