BuildContext can be used to set up a caddy context from a config, but not start any listeners or active components: The returned context has the configured apps provisioned, but otherwise is inert.
This is EXPERIMENTAL: Minimally it's missing documentation and the example for how this can be used to run unit tests.
For context: What I'm trying to do is write unit tests, where I take a (partial) configuration for caddy and then build a context from that. The context is then used to provision modules that need testing, possibly with some weird configuration etc. The problem with the current caddy codebase is that there's no good way to do get to such a context, as on the one hand it has some important private fields (apps), and on the other hand all functions making a context either throw it away again (Validate), or also Start the modules and potentially listeners etc.
By splitting the private run to have the function to build such a context public I can side-step that. For an example here's parts of a test for checking that we correctly implement caddyevents.Handler using this BuildContext function:
package caddy
import (
// Stuff
)
func init() {
caddy.RegisterModule(testModule{})
}
// A module that allows us to get a proper caddy context with a non-nil/non-empty `ancestry`
type testModule struct {
ctx caddy.Context
}
func (t *testModule) Provision(ctx caddyimpl.Context) error {
// Sorry, but not sorry.
t.ctx = ctx
return nil
}
func (testModule) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "test_module",
New: func() caddy.Module {
return new(testModule)
},
}
}
var _ caddy.Provisioner = (*testModule)(nil)
type testEvent struct {
name string
params map[string]any
}
// testRecorder implements an internal interface that allows us to override
// the APM tooling and instead do something else.
type testRecorder struct {
seenEvents []testEvent
}
func (t *testRecorder) RecordCustomEvent(name string, params map[string]any) {
t.seenEvents = append(t.seenEvents, testEvent{name, params})
}
var _ recorder = (*testRecorder)(nil)
func TestHandle(t *testing.T) {
tests := []struct {
name string
eventData map[string]any
expectedRecordedParams map[string]any
}{
// A test fixture that we can use to simulate this particular event.
{
name: "Records cached_managed_cert event shape",
eventData: map[string]any{
"sans": []string{"san1", "san2"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Make a caddy context with the needed applications in them.
caddyCtx, _ := caddy.BuildContext(&caddyimpl.Config{
AppsRaw: caddyimpl.ModuleMap{
"events": json.RawMessage{},
"our_own_apm": json.RawMessage{},
},
}, false)
m, err := caddyCtx.App("events")
assert.NoError(t, err)
events := m.(*caddyevents.App)
m, err = caddyCtx.App("our_own_apm")
assert.NoError(t, err)
monitoring := m.(*Monitoring)
// Sneak in a different recorder, so we can watch the events coming in.
testRecorder := &testRecorder{}
monitoring.recorder = testRecorder
// Simulate emitting an event from inside a module
m, err = caddyCtx.LoadModuleByID("test_module", json.RawMessage{})
assert.NoError(t, err)
tm := m.(*testModule)
// events.Emit() doesn't handle a nil ancestry correctly: In https://github.com/caddyserver/caddy/blob/101d3e740783581110340a68f0b0cbe5f1ab6dbb/modules/caddyevents/app.go#L228
// `e.origin` can be nil. Work around that by capturing properly bound context.
// Ugly, the alternative would be more code to expose a Emit on tm ...
event := events.Emit(tm.ctx, "test", tt.eventData)
assert.NoError(t, event.Aborted)
// Check that the world is good
if len(testRecorder.seenEvents) != 1 {
t.Fatalf("no event captured")
}
assert.Equal(t, 1, len(testRecorder.seenEvents))
assert.Equal(t, "CaddyEvent", testRecorder.seenEvents[0].name)
for k, v := range tt.expectedRecordedParams {
assert.Equal(t, v, testRecorder.seenEvents[0].params[k])
}
})
}
}
BuildContext
can be used to set up a caddy context from a config, but not start any listeners or active components: The returned context has the configured apps provisioned, but otherwise is inert.This is EXPERIMENTAL: Minimally it's missing documentation and the example for how this can be used to run unit tests.
For context: What I'm trying to do is write unit tests, where I take a (partial) configuration for caddy and then build a context from that. The context is then used to provision modules that need testing, possibly with some weird configuration etc. The problem with the current caddy codebase is that there's no good way to do get to such a context, as on the one hand it has some important private fields (
apps
), and on the other hand all functions making a context either throw it away again (Validate
), or alsoStart
the modules and potentially listeners etc.By splitting the private
run
to have the function to build such a context public I can side-step that. For an example here's parts of a test for checking that we correctly implementcaddyevents.Handler
using thisBuildContext
function: