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:
2026-06-29 17:58:00 -07:00
parent b7adafed67
commit 4cd218f658
4 changed files with 287 additions and 204 deletions
+22 -9
View File
@@ -246,10 +246,16 @@ func TestLifecycle_Teardown(t *testing.T) {
lc := NewLifecycle(tc.modules...)
// Fake setup for all modules
for _, mod := range tc.modules {
mod.loaded = true // Mark as loaded
lc.setupTracker[mod.name] = 0 // Mark as set up
if len(tc.modules) > 0 {
setupBefore := len(lc.setupOrder)
for i, mod := range tc.modules {
lc.setupOrder = append(lc.setupOrder, mod)
lc.setupTracker[mod.name] = i
mod.loaded = true
mod.lifecycle = lc
}
lc.setupCount = len(lc.setupOrder)
_ = setupBefore
}
err := lc.Teardown()
@@ -283,6 +289,8 @@ func TestLifecycle_Teardown(t *testing.T) {
// Fake setup for the module
lc.modules[0].loaded = true
lc.setupTracker[lc.modules[0].name] = 0
lc.setupOrder = []*Module{lc.modules[0]}
lc.setupCount = 1
err := lc.Teardown()
assert.NoError(err, "expected first Teardown to succeed")
@@ -418,7 +426,7 @@ func TestLifecycle_require(t *testing.T) {
assert := assert.New(t)
lc := NewLifecycle()
err := lc.require(tc.logger, tc.unique, tc.modules...)
err := lc.require(RequireOpts{Logger: tc.logger, Unique: tc.unique}, tc.modules...)
if tc.expectedErr == "" {
assert.NoError(err, "expected require to succeed")
@@ -550,7 +558,7 @@ func TestLifecycle_setupSingle(t *testing.T) {
lc = NewLifecycle(tc.modules...)
}
err := lc.setupSingle(l, tc.targetModule)
err := lc.setupSingle(l, tc.targetModule, nil)
if tc.expectedErr == "" {
assert.NoError(err, "expected no error from setupSingle")
@@ -616,12 +624,17 @@ func TestLifecycle_teardownSingle(t *testing.T) {
lc := NewLifecycle()
// Fake setup for all modules
var setupCount int
for _, mod := range tc.modules {
lc.setupTracker[mod] = setupCount
for _, modName := range tc.modules {
lc.setupTracker[modName] = setupCount
for _, m := range lc.modules {
if m.name == modName {
lc.setupOrder = append(lc.setupOrder, m)
}
}
setupCount++
}
lc.setupCount = setupCount
err := lc.teardownSingle(tc.targetModule)