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") }) }) }