Close owned import roots during panic unwinding
This commit is contained in:
108
internal/engine/import_root_panic_test.go
Normal file
108
internal/engine/import_root_panic_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
func TestImportedAcquisitionClosesOwnedHostRootOnEveryExit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
main string
|
||||
child string
|
||||
version bool
|
||||
wantError bool
|
||||
wantPanic bool
|
||||
}{
|
||||
{name: "success", main: "configkit: {imports: [{path: child.yml}]}\n", child: "value: child\n"},
|
||||
{name: "error", main: "configkit: {imports: [{path: missing.yml, required: true}]}\n", wantError: true},
|
||||
{name: "version panic", main: "configkit: {imports: [{path: child.yml}]}\n", child: "version: v1\n", version: true, wantPanic: true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
rootPath := t.TempDir()
|
||||
mainPath := filepath.Join(rootPath, "main.yml")
|
||||
if err := os.WriteFile(mainPath, []byte(test.main), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.child != "" {
|
||||
if err := os.WriteFile(filepath.Join(rootPath, "child.yml"), []byte(test.child), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
source := File(mainPath)
|
||||
main, diagnostics := parseSource(source, "source-1", defaultParseLimits())
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("main parse diagnostics = %#v", diagnostics)
|
||||
}
|
||||
access, err := openHostAccess(rootPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ownedRoot := access.root
|
||||
sentinel := errors.New("imported version checker panic sentinel")
|
||||
var rule *VersionRule
|
||||
var versionState *sourceVersionState
|
||||
if test.version {
|
||||
rule = &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { panic(sentinel) }}
|
||||
versionState = &sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}
|
||||
}
|
||||
request := testImportRequest(source, nil, main, Path{}, rule, nil)
|
||||
policy := &acquisitionPolicy{importLimits: defaultImportLimits()}
|
||||
state := &acquisitionState{accounting: &importAccounting{}, versionState: versionState}
|
||||
tier := &tierAcquisitionState{}
|
||||
resolved := resolvedFilesystemContext{rootPath: rootPath, basePath: rootPath, mainPath: mainPath}
|
||||
var resultDiagnostics []Diagnostic
|
||||
var recovered any
|
||||
func() {
|
||||
defer func() { recovered = recover() }()
|
||||
_, _, resultDiagnostics = acquireImportedWithAccess(request, policy, state, tier, access, resolved)
|
||||
}()
|
||||
if test.wantPanic {
|
||||
if recovered != sentinel {
|
||||
t.Fatalf("recovered panic = %#v, want original sentinel", recovered)
|
||||
}
|
||||
} else if recovered != nil {
|
||||
t.Fatalf("unexpected panic = %#v", recovered)
|
||||
}
|
||||
if test.wantError != (len(resultDiagnostics) != 0) {
|
||||
t.Fatalf("diagnostics = %#v, want error=%v", resultDiagnostics, test.wantError)
|
||||
}
|
||||
if _, err := ownedRoot.Stat("."); err == nil {
|
||||
t.Fatal("owned host root remained usable after acquisition returned")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedAcquisitionClosesChildFileBeforeVersionPanic(t *testing.T) {
|
||||
filesystem := &observedFilesystem{files: fstest.MapFS{
|
||||
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: child.yml}]}\n")},
|
||||
"child.yml": &fstest.MapFile{Data: []byte("version: v1\n")},
|
||||
}}
|
||||
namespace := newNativeNamespace("bundle", filesystem)
|
||||
source := filesystemFile(namespace, "main.yml")
|
||||
main, diagnostics := parseSource(source, "source-1", defaultParseLimits())
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("main parse diagnostics = %#v", diagnostics)
|
||||
}
|
||||
sentinel := errors.New("imported version checker panic sentinel")
|
||||
rule := &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { panic(sentinel) }}
|
||||
versionState := &sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}
|
||||
request := testImportRequest(source, nil, main, Path{}, rule, nil)
|
||||
access := newNativeAccess(filesystem, ".", namespace.token)
|
||||
var recovered any
|
||||
func() {
|
||||
defer func() { recovered = recover() }()
|
||||
_, _, _ = acquireImportedWithAccess(request, &acquisitionPolicy{importLimits: defaultImportLimits()},
|
||||
&acquisitionState{accounting: &importAccounting{}, versionState: versionState}, &tierAcquisitionState{}, access,
|
||||
resolvedFilesystemContext{native: namespace, rootPath: ".", basePath: ".", mainPath: source.path})
|
||||
}()
|
||||
if recovered != sentinel {
|
||||
t.Fatalf("recovered panic = %#v, want original sentinel", recovered)
|
||||
}
|
||||
filesystem.assertClosed(t)
|
||||
}
|
||||
Reference in New Issue
Block a user