Close callback contexts when callbacks panic
This commit is contained in:
73
internal/engine/callback_panic_test.go
Normal file
73
internal/engine/callback_panic_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func capturePanic(call func()) (recovered any) {
|
||||
defer func() { recovered = recover() }()
|
||||
call()
|
||||
return nil
|
||||
}
|
||||
|
||||
func requirePanicValue(t *testing.T, want any, call func()) {
|
||||
t.Helper()
|
||||
if got := capturePanic(call); got != want {
|
||||
t.Fatalf("panic = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallbackContextsCloseWhenCallbacksPanic(t *testing.T) {
|
||||
type config struct {
|
||||
Value string `yaml:"value"`
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
t.Run("resolution", func(t *testing.T) {
|
||||
sentinel := errors.New("resolution panic sentinel")
|
||||
var retained *Resolution
|
||||
got := capturePanic(func() {
|
||||
_, _ = testLoad[config](NoSource(), Options[config]{Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
|
||||
retained = resolution
|
||||
panic(sentinel)
|
||||
}}})
|
||||
})
|
||||
if got != sentinel {
|
||||
t.Fatalf("callback panic = %#v, want original sentinel", got)
|
||||
}
|
||||
requirePanicValue(t, closedCallbackContextMessage, func() { retained.IsExplicit(Keys("value")) })
|
||||
})
|
||||
|
||||
t.Run("validation", func(t *testing.T) {
|
||||
sentinel := errors.New("validation panic sentinel")
|
||||
var retained *Validation
|
||||
got := capturePanic(func() {
|
||||
_, _ = testLoad[config](NoSource(), Options[config]{Definition: Definition[config]{Validate: func(_ *config, validation *Validation) {
|
||||
retained = validation
|
||||
panic(sentinel)
|
||||
}}})
|
||||
})
|
||||
if got != sentinel {
|
||||
t.Fatalf("callback panic = %#v, want original sentinel", got)
|
||||
}
|
||||
requirePanicValue(t, closedCallbackContextMessage, func() { retained.Error(Keys("value"), "too late") })
|
||||
})
|
||||
|
||||
t.Run("version", func(t *testing.T) {
|
||||
sentinel := errors.New("version panic sentinel")
|
||||
var retained *Validation
|
||||
got := capturePanic(func() {
|
||||
_, _ = testLoad[config](Bytes("version.yml", []byte("version: v1\n")), Options[config]{Definition: Definition[config]{
|
||||
Version: &VersionRule{Path: Keys("version"), Check: func(_ Version, validation *Validation) error {
|
||||
retained = validation
|
||||
panic(sentinel)
|
||||
}},
|
||||
}})
|
||||
})
|
||||
if got != sentinel {
|
||||
t.Fatalf("callback panic = %#v, want original sentinel", got)
|
||||
}
|
||||
requirePanicValue(t, closedCallbackContextMessage, func() { retained.Warning(Keys("version"), "too late") })
|
||||
})
|
||||
}
|
||||
@@ -287,8 +287,11 @@ func runResolution[T any](configuration *T, resolver func(*T, *Resolution) error
|
||||
records: make(map[string]*resolutionRecord),
|
||||
}
|
||||
context := &Resolution{state: state}
|
||||
err := resolver(configuration, context)
|
||||
state.active = false
|
||||
var err error
|
||||
func() {
|
||||
defer func() { state.active = false }()
|
||||
err = resolver(configuration, context)
|
||||
}()
|
||||
if err != nil && !hasErrors(state.diagnostics) {
|
||||
state.addDiagnostic(CodeResolveHookFailed, Path{}, "application resolution failed")
|
||||
}
|
||||
|
||||
@@ -102,8 +102,10 @@ func runValidation[T any](configuration *T, validator func(*T, *Validation), sch
|
||||
invalidPath: CodeValidateInvalidPath, schema: schema, document: input,
|
||||
}
|
||||
context := &Validation{state: state}
|
||||
validator(configuration, context)
|
||||
state.active = false
|
||||
func() {
|
||||
defer func() { state.active = false }()
|
||||
validator(configuration, context)
|
||||
}()
|
||||
after, issue := projectConfiguration(schema, reflect.ValueOf(*configuration), defaultProjectionLimits())
|
||||
if issue != nil {
|
||||
state.diagnostics = append(state.diagnostics, validationMutationDiagnostic(issue.path))
|
||||
@@ -253,8 +255,11 @@ func reportVersion(rule *VersionRule, version Version, root *document.Node) []Di
|
||||
invalidPath: CodeVersionError, document: root,
|
||||
}
|
||||
validation := &Validation{state: state}
|
||||
err := rule.Check(version, validation)
|
||||
state.active = false
|
||||
var err error
|
||||
func() {
|
||||
defer func() { state.active = false }()
|
||||
err = rule.Check(version, validation)
|
||||
}()
|
||||
if err != nil && !hasErrors(state.diagnostics) {
|
||||
location, _ := nearestDocumentLocation(root, rule.Path)
|
||||
state.diagnostics = append(state.diagnostics, Diagnostic{
|
||||
|
||||
Reference in New Issue
Block a user