fix: correct lifecycle setup and teardown semantics
Guard against double setup when autoload runs dependencies early, tear down in reverse setup order, roll back on partial failure, and detect circular Depends chains. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"gitea.auvem.com/go-toolkit/app"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetupTeardownIntegration(t *testing.T) {
|
||||
var order []string
|
||||
modA := app.NewModule("a", app.ModuleOpts{
|
||||
Setup: func(m *app.Module) error { order = append(order, "setup:a"); return nil },
|
||||
Teardown: func(m *app.Module) error { order = append(order, "teardown:a"); return nil },
|
||||
})
|
||||
modB := app.NewModule("b", app.ModuleOpts{
|
||||
Setup: func(m *app.Module) error { order = append(order, "setup:b"); return nil },
|
||||
Teardown: func(m *app.Module) error { order = append(order, "teardown:b"); return nil },
|
||||
Depends: []string{"a"},
|
||||
})
|
||||
|
||||
lc := app.NewLifecycle(modB, modA)
|
||||
assert.NoError(t, lc.Setup())
|
||||
assert.Equal(t, []string{"setup:a", "setup:b"}, order)
|
||||
|
||||
order = nil
|
||||
assert.NoError(t, lc.Teardown())
|
||||
assert.Equal(t, []string{"teardown:b", "teardown:a"}, order)
|
||||
}
|
||||
|
||||
func TestSetupNoDoubleSetupWithAutoload(t *testing.T) {
|
||||
var count int
|
||||
modA := app.NewModule("a", app.ModuleOpts{
|
||||
Setup: func(m *app.Module) error { count++; return nil },
|
||||
})
|
||||
modB := app.NewModule("b", app.ModuleOpts{
|
||||
Setup: func(m *app.Module) error { count++; return nil },
|
||||
Depends: []string{"a"},
|
||||
})
|
||||
|
||||
lc := app.NewLifecycle(modB, modA)
|
||||
assert.NoError(t, lc.Setup())
|
||||
assert.Equal(t, 2, count)
|
||||
}
|
||||
|
||||
func TestSetupPartialFailureRollback(t *testing.T) {
|
||||
var tornDown bool
|
||||
modA := app.NewModule("a", app.ModuleOpts{
|
||||
Setup: func(m *app.Module) error { return nil },
|
||||
Teardown: func(m *app.Module) error { tornDown = true; return nil },
|
||||
})
|
||||
modB := app.NewModule("b", app.ModuleOpts{
|
||||
Setup: func(m *app.Module) error { return fmt.Errorf("fail b") },
|
||||
})
|
||||
|
||||
lc := app.NewLifecycle(modA, modB)
|
||||
err := lc.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.True(t, tornDown)
|
||||
assert.False(t, modA.Loaded())
|
||||
}
|
||||
|
||||
func TestSetupCircularDependency(t *testing.T) {
|
||||
modA := app.NewModule("a", app.ModuleOpts{Depends: []string{"b"}})
|
||||
modB := app.NewModule("b", app.ModuleOpts{Depends: []string{"a"}})
|
||||
lc := app.NewLifecycle(modA, modB)
|
||||
err := lc.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "circular dependency")
|
||||
}
|
||||
|
||||
func TestGetModule(t *testing.T) {
|
||||
mod := app.NewModule("db", app.ModuleOpts{})
|
||||
lc := app.NewLifecycle(mod)
|
||||
got, err := lc.GetModule("db")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, mod, got)
|
||||
_, err = lc.GetModule("missing")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user