diff --git a/lifecycle.go b/lifecycle.go index 6453dba..7591f65 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -87,6 +87,9 @@ func (app *Lifecycle) WithLogger(logger *slog.Logger) *Lifecycle { // Logger returns the logger for the lifecycle. func (app *Lifecycle) Logger() *slog.Logger { + if app == nil { + panic("lifecycle is nil, cannot get logger") + } if app.opts.Logger == nil { app.opts.Logger = slog.Default() } @@ -182,12 +185,16 @@ func (app *Lifecycle) require(logger *slog.Logger, unique bool, modules ...*Modu // Check if the module has already been set up if _, ok := app.setupTracker[mod.name]; ok { if unique { - return fmt.Errorf("module %s is already set up, cannot require it again", mod) + return fmt.Errorf("module %s is already set up, cannot require again", mod) } app.Logger().Warn("module already set up, ignoring", "module", mod) + + // Mark duplicate module as loaded mod.loaded = true + mod.lifecycle = app mod.logger = logger + continue } diff --git a/lifecycle_test.go b/lifecycle_test.go index 3c6a0c1..014be08 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -1,6 +1,7 @@ package app import ( + "context" "errors" "log/slog" "os" @@ -46,6 +47,27 @@ func TestNewLifecycle(t *testing.T) { }, "expected panic on duplicate module names") } +func TestLifecycleToFromContext(t *testing.T) { + assert := assert.New(t) + + // Create a Lifecycle instance + lc := NewLifecycle() + + // Add the Lifecycle to the context + ctx := LifecycleToContext(context.Background(), lc) + + // Retrieve the Lifecycle from the context + retrievedLC := LifecycleFromContext(ctx) + + assert.NotNil(retrievedLC, "expected Lifecycle to be retrieved from context") + assert.Equal(lc, retrievedLC, "expected retrieved Lifecycle to match original") + + // Test with nil Lifecycle + nilCtx := LifecycleToContext(context.Background(), nil) + retrievedNilLC := LifecycleFromContext(nilCtx) + assert.Nil(retrievedNilLC, "expected nil Lifecycle to return nil from context") +} + func TestLifecycle_WithOpts(t *testing.T) { assert := assert.New(t) @@ -65,6 +87,10 @@ func TestLifecycle_WithLogger(t *testing.T) { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) lc.WithLogger(logger) assert.Equal(logger, lc.Logger(), "expected Lifecycle to have the provided logger") + + assert.PanicsWithValue("logger cannot be nil", func() { + lc.WithLogger(nil) + }, "expected panic when setting nil logger") } func TestLifecycle_Logger(t *testing.T) { @@ -77,6 +103,12 @@ func TestLifecycle_Logger(t *testing.T) { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) lc.opts.Logger = logger assert.Equal(logger, lc.Logger()) + + // Test with nil Lifecycle + assert.PanicsWithValue("lifecycle is nil, cannot get logger", func() { + var nilLC *Lifecycle + nilLC.Logger() + }, "expected panic when calling Logger on nil Lifecycle") } func TestLifecycle_Setup(t *testing.T) { @@ -225,15 +257,17 @@ func TestLifecycle_Teardown(t *testing.T) { } func TestLifecycle_Require(t *testing.T) { + assert := assert.New(t) + lc := NewLifecycle() - mod := NewModule("module1", ModuleOpts{ - Setup: func(m *Module) error { return nil }, - }) - err := lc.Require(mod) - assert.NoError(t, err, "expected Require to succeed") - assert.Contains(t, lc.setupTracker, mod.name, "expected module to be marked as set up") - assert.Nil(t, mod.logger, "expected module logger to be nil") - assert.Equal(t, lc.Logger(), mod.Logger(), "expected module logger to match lifecycle logger") + + // Test that duplicate modules are allowed + mod1 := NewModule("module1", ModuleOpts{}) + mod2 := NewModule("module1", ModuleOpts{}) + err := lc.Require(mod1, mod2) + assert.NoError(err, "expected Require to succeed with duplicate modules") + assert.Len(lc.modules, 1, "expected only one instance of the module to be added") + assert.Equal(lc.Logger(), mod1.Logger(), "expected module logger to match lifecycle logger") } func TestLifecycle_RequireUnique(t *testing.T) { @@ -241,56 +275,77 @@ func TestLifecycle_RequireUnique(t *testing.T) { lc := NewLifecycle() mod := NewModule("module1", ModuleOpts{}) - err := lc.RequireUnique(mod) - assert.NoError(err, "expected RequireL to succeed") - assert.Nil(mod.logger, "expected module logger to be nil") - - // Requiring the same module again should fail - err = lc.RequireUnique(mod) + err := lc.RequireUnique(mod, mod) assert.Error(err, "expected RequireUnique to fail on duplicate module") } func TestLifecycle_RequireL(t *testing.T) { + assert := assert.New(t) + // Create a logger for the test cases logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) + lc := NewLifecycle() + + // Test that duplicate modules are allowed with RequireL + mod1 := NewModule("module1", ModuleOpts{}) + mod2 := NewModule("module1", ModuleOpts{}) + err := lc.RequireL(logger, mod1, mod2) + assert.NoError(err, "expected RequireL to succeed with duplicate modules") + assert.Len(lc.modules, 1, "expected only one instance of the module to be added") + assert.Equal(logger, mod1.Logger(), "expected module logger to match provided logger") + + // Test with nil logger + err = lc.RequireL(nil, mod1) + assert.Error(err, "expected RequireL to fail with nil logger") +} + +func TestLifecycle_RequireUniqueL(t *testing.T) { + assert := assert.New(t) + + // Create a logger for the test cases + logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) + + lc := NewLifecycle() + + // Test that duplicate modules are not allowed with RequireUniqueL + mod1 := NewModule("module1", ModuleOpts{}) + mod2 := NewModule("module1", ModuleOpts{}) + err := lc.RequireUniqueL(logger, mod1, mod2) + assert.Error(err, "expected RequireUniqueL to fail on duplicate module") + assert.Contains(err.Error(), "cannot require again", "expected error message to match") + + // Test with nil logger + err = lc.RequireUniqueL(nil, mod1) + assert.Error(err, "expected RequireUniqueL to fail with nil logger") +} + +func TestLifecycle_require(t *testing.T) { cases := []struct { - name string - modules []*Module - existingModules []*Module - expectedErr string + name string + logger *slog.Logger + unique bool + modules []*Module + expectedErr string + length int }{ { name: "no modules", expectedErr: "no modules to require", }, { - name: "nil module", - modules: []*Module{nil}, - expectedErr: "is nil", - }, - { - name: "basic module", + name: "nil module", modules: []*Module{ - NewModule("testModule", ModuleOpts{ - Setup: func(m *Module) error { - if m.name != "testModule" { - return errors.New("module name mismatch") - } - return nil - }, - }), + NewModule("", ModuleOpts{}), + nil, }, + expectedErr: "module 1 is nil", }, { - name: "several modules", + name: "basic modules", modules: []*Module{ - NewModule("module1", ModuleOpts{ - Setup: func(m *Module) error { return nil }, - }), - NewModule("module2", ModuleOpts{ - Setup: func(m *Module) error { return nil }, - }), + NewModule("module1", ModuleOpts{}), + NewModule("module2", ModuleOpts{}), }, }, { @@ -300,21 +355,7 @@ func TestLifecycle_RequireL(t *testing.T) { Setup: func(m *Module) error { return errors.New("setup failed") }, }), }, - expectedErr: "setup failed", - }, - { - name: "module with dependency", - modules: []*Module{ - NewModule("dependentModule", ModuleOpts{ - Setup: func(m *Module) error { return nil }, - Depends: []string{"dependencyModule"}, - }), - }, - existingModules: []*Module{ - NewModule("dependencyModule", ModuleOpts{ - Setup: func(m *Module) error { return nil }, - }), - }, + expectedErr: "error setting up required module", }, { name: "duplicate module names", @@ -322,6 +363,16 @@ func TestLifecycle_RequireL(t *testing.T) { NewModule("duplicateModule", ModuleOpts{}), NewModule("duplicateModule", ModuleOpts{}), }, + length: 1, // Expect only one instance to be added + }, + { + name: "duplicate module names with unique", + unique: true, + modules: []*Module{ + NewModule("duplicateModule", ModuleOpts{}), + NewModule("duplicateModule", ModuleOpts{}), + }, + expectedErr: "cannot require again", }, } @@ -329,75 +380,34 @@ func TestLifecycle_RequireL(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert := assert.New(t) - lc := NewLifecycle(tc.existingModules...) - - err := lc.RequireL(logger, tc.modules...) + lc := NewLifecycle() + err := lc.require(tc.logger, tc.unique, tc.modules...) if tc.expectedErr == "" { - assert.NoError(err, "expected RequireL to succeed") - for i, mod := range tc.modules { + assert.NoError(err, "expected require to succeed") + + expectedLength := tc.length + if expectedLength == 0 { + expectedLength = len(tc.modules) + } + assert.Equal(expectedLength, len(lc.modules), "expected all modules to be added to lifecycle") + + for _, mod := range tc.modules { if mod != nil { _, ok := lc.setupTracker[mod.name] - assert.Truef(ok, "expected module %d %s to be marked as set up", i, mod) - assert.Truef(mod.loaded, "expected module %d %s to be loaded", i, mod) - assert.Equalf(logger, mod.logger, "expected module %d %s logger to match", i, mod) + assert.Truef(ok, "expected module %s to be marked as set up", mod) + assert.Truef(mod.loaded, "expected module %s to be loaded", mod) + assert.Equalf(lc, mod.lifecycle, "expected module %s lifecycle to match", mod) + + if tc.logger != nil { + assert.Equalf(tc.logger, mod.Logger(), "expected module %s logger to match", mod) + } else { + assert.Equalf(lc.Logger(), mod.Logger(), "expected module %s logger to match lifecycle logger", mod) + } } } } else { - assert.Error(err, "expected RequireL to fail") - assert.Contains(err.Error(), tc.expectedErr, "expected error message to match") - } - }) - } -} - -func TestLifecycle_RequireUniqueL(t *testing.T) { - // Create a logger for the test cases - logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) - - cases := []struct { - name string - modules []*Module - existingModules []string - expectedErr string - }{ - { - name: "no existing modules", - modules: []*Module{ - NewModule("module1", ModuleOpts{}), - }, - }, - { - name: "module already set up", - modules: []*Module{ - NewModule("module1", ModuleOpts{}), - NewModule("module2", ModuleOpts{}), - }, - existingModules: []string{"module1"}, - expectedErr: "module [module1] is already set up, cannot require it again", - }, - { - name: "nil module", - modules: []*Module{nil}, - expectedErr: "is nil", - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - assert := assert.New(t) - lc := NewLifecycle() - - // Fake existing modules - for _, modName := range tc.existingModules { - lc.setupTracker[modName] = 0 // Mark as set up - } - - err := lc.RequireUniqueL(logger, tc.modules...) - if tc.expectedErr == "" { - assert.NoError(err, "expected RequireUniqueL to succeed") - } else { - assert.Error(err, "expected RequireUniqueL to fail") + assert.Error(err, "expected require to fail") assert.Contains(err.Error(), tc.expectedErr, "expected error message to match") } }) diff --git a/module.go b/module.go index 48859a6..53cd8f1 100644 --- a/module.go +++ b/module.go @@ -59,6 +59,9 @@ func (s *Module) Logger() *slog.Logger { if s.logger != nil { return s.logger } + if s.lifecycle == nil { + panic("module has no lifecycle, cannot get logger") + } return s.lifecycle.Logger() }