context helpers, rework logging to support ongoing inheritance

Leaves module logger property as purely an override, pulling logger
information directly from the lifecycle instead.
This commit is contained in:
Elijah Duffy
2025-06-04 13:36:40 -07:00
parent 6f9e3731ac
commit c3c8a2cafc
5 changed files with 188 additions and 115 deletions

View File

@@ -3,6 +3,7 @@ package app
import (
"errors"
"log/slog"
"os"
"testing"
"github.com/stretchr/testify/assert"
@@ -11,20 +12,20 @@ import (
func TestNewLifecycle(t *testing.T) {
assert := assert.New(t)
// Create a blank Lifecycle instance
lc := NewLifecycle()
assert.NotNil(lc, "expected Lifecycle instance to be created")
assert.Equal(slog.Default(), lc.logger, "expected default logger to be set")
}
func TestNewLifecycleL(t *testing.T) {
assert := assert.New(t)
assert.Equal(slog.Default(), lc.Logger(), "expected default logger to be available")
// Create a blank Lifecycle instance with a custom logger
logger := slog.Default()
lc := NewLifecycleL(logger)
handler := slog.NewTextHandler(os.Stderr, nil)
logger := slog.New(handler)
lc = NewLifecycle().WithOpts(LifecycleOpts{
Logger: logger,
})
assert.NotNil(lc)
assert.Equal(logger, lc.logger, "expected logger to be set")
assert.Equal(logger, lc.Logger(), "expected logger to match")
assert.Len(lc.modules, 0, "expected modules to be empty")
assert.Len(lc.setupTracker, 0, "expected setup tracker to be empty")
assert.Len(lc.teardownTracker, 0, "expected teardown tracker to be empty")
@@ -33,15 +34,15 @@ func TestNewLifecycleL(t *testing.T) {
mod1 := NewModule("module1", ModuleOpts{})
mod2 := NewModule("module2", ModuleOpts{})
lcWithModules := NewLifecycleL(logger, mod1, mod2)
lcWithModules := NewLifecycle(mod1, mod2)
assert.NotNil(lcWithModules, "expected Lifecycle with modules to be created")
assert.Contains(lcWithModules.modules, mod1, "expected module1 to be included")
assert.Contains(lcWithModules.modules, mod2, "expected module2 to be included")
assert.Len(lcWithModules.modules, 2, "expected two modules to be present")
// Test for duplicate module names
assert.PanicsWithValue("duplicate module name: module1", func() {
NewLifecycleL(logger, mod1, mod1)
assert.PanicsWithValue("duplicate module: [module1]", func() {
NewLifecycle(mod1, mod1)
}, "expected panic on duplicate module names")
}
@@ -57,17 +58,25 @@ func TestLifecycle_WithOpts(t *testing.T) {
assert.Equal(opts, lc.opts, "expected Lifecycle to have the provided options")
}
func TestLifecycle_WithLogger(t *testing.T) {
assert := assert.New(t)
lc := NewLifecycle()
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
lc.WithLogger(logger)
assert.Equal(logger, lc.Logger(), "expected Lifecycle to have the provided logger")
}
func TestLifecycle_Logger(t *testing.T) {
assert := assert.New(t)
lc := NewLifecycle()
assert.Equal(slog.Default(), lc.Logger(), "expected Logger to return a logger")
// Test panic when logger is not initialized
lc.logger = nil
assert.PanicsWithValue("lifecycle logger is not initialized", func() {
lc.Logger()
}, "expected Logger to panic when logger is not set")
// Test that custom logger is used
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
lc.opts.Logger = logger
assert.Equal(logger, lc.Logger())
}
func TestLifecycle_Setup(t *testing.T) {
@@ -190,6 +199,7 @@ func TestLifecycle_Teardown(t *testing.T) {
// 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
}
@@ -200,7 +210,8 @@ func TestLifecycle_Teardown(t *testing.T) {
for _, mod := range tc.modules {
if mod.teardown != nil {
_, ok := lc.teardownTracker[mod.name]
assert.True(ok, "expected module to be marked as torn down")
assert.Truef(ok, "expected module %s to be marked as torn down", mod)
assert.Falsef(mod.loaded, "expected module %s to be unloaded after teardown", mod)
}
}
} else {
@@ -218,22 +229,33 @@ func TestLifecycle_Require(t *testing.T) {
mod := NewModule("module1", ModuleOpts{
Setup: func(m *Module) error { return nil },
})
lc.Require(mod)
assert.Equal(t, lc.logger, mod.logger, "expected module logger to match lifecycle logger")
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")
}
func TestLifecycle_RequireUnique(t *testing.T) {
assert := assert.New(t)
lc := NewLifecycle()
mod := NewModule("module1", ModuleOpts{})
err := lc.RequireUnique(mod)
assert.NoError(t, err, "expected RequireL to succeed")
assert.Equal(t, lc.logger, mod.logger, "expected module logger to match lifecycle logger")
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)
assert.Error(err, "expected RequireUnique to fail on duplicate module")
}
func TestLifecycle_RequireL(t *testing.T) {
// Create a logger for the test cases
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
cases := []struct {
name string
logger *slog.Logger
modules []*Module
existingModules []*Module
expectedErr string
@@ -245,7 +267,7 @@ func TestLifecycle_RequireL(t *testing.T) {
{
name: "nil module",
modules: []*Module{nil},
expectedErr: "module is nil",
expectedErr: "is nil",
},
{
name: "basic module",
@@ -309,21 +331,16 @@ func TestLifecycle_RequireL(t *testing.T) {
lc := NewLifecycle(tc.existingModules...)
l := tc.logger
if l == nil {
l = lc.logger
}
err := lc.RequireL(tc.logger, tc.modules...)
err := lc.RequireL(logger, tc.modules...)
if tc.expectedErr == "" {
assert.NoError(err, "expected RequireL to succeed")
for _, mod := range tc.modules {
for i, mod := range tc.modules {
if mod != nil {
_, ok := lc.setupTracker[mod.name]
assert.True(ok, "expected module to be marked as set up")
assert.True(mod.loaded, "expected module to be loaded")
assert.Equal(l, mod.logger, "expected module logger to match")
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)
}
}
} else {
@@ -335,6 +352,9 @@ func TestLifecycle_RequireL(t *testing.T) {
}
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
@@ -354,12 +374,12 @@ func TestLifecycle_RequireUniqueL(t *testing.T) {
NewModule("module2", ModuleOpts{}),
},
existingModules: []string{"module1"},
expectedErr: "module 'module1' is already set up, cannot require it again",
expectedErr: "module [module1] is already set up, cannot require it again",
},
{
name: "nil module",
modules: []*Module{nil},
expectedErr: "module is nil",
expectedErr: "is nil",
},
}
@@ -373,7 +393,7 @@ func TestLifecycle_RequireUniqueL(t *testing.T) {
lc.setupTracker[modName] = 0 // Mark as set up
}
err := lc.RequireUniqueL(lc.logger, tc.modules...)
err := lc.RequireUniqueL(logger, tc.modules...)
if tc.expectedErr == "" {
assert.NoError(err, "expected RequireUniqueL to succeed")
} else {
@@ -385,6 +405,9 @@ func TestLifecycle_RequireUniqueL(t *testing.T) {
}
func TestLifecycle_setupSingle(t *testing.T) {
// Create a logger for the test cases
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
cases := []struct {
name string
lc *Lifecycle
@@ -447,7 +470,7 @@ func TestLifecycle_setupSingle(t *testing.T) {
Setup: func(m *Module) error { return errors.New("dependency setup failed") },
}),
},
expectedErr: "error initializing 'errorModule'",
expectedErr: "dependency setup failed",
},
{
name: "dependency without autoload",
@@ -472,7 +495,7 @@ func TestLifecycle_setupSingle(t *testing.T) {
l := tc.logger
if l == nil {
l = slog.Default()
l = logger
}
lc := tc.lc
@@ -480,20 +503,19 @@ func TestLifecycle_setupSingle(t *testing.T) {
lc = NewLifecycle(tc.modules...)
}
err := lc.setupSingle(tc.targetModule, l)
if tc.targetModule != nil {
assert.Equal(l, tc.targetModule.logger, "expected module logger to match")
}
err := lc.setupSingle(l, tc.targetModule)
if tc.expectedErr == "" {
assert.NoError(err, "expected no error from setupSingle")
} else {
assert.Contains(err.Error(), tc.expectedErr, "expected error message to match")
}
if tc.targetModule != nil {
assert.Equal(l, tc.targetModule.logger, "expected module logger to match")
}
})
}
}
func TestLifecycle_teardownSingle(t *testing.T) {