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