package engine import ( "os" "path/filepath" "testing" "testing/fstest" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) func TestDestinationPlacementCountsAgainstSharedNodeBudget(t *testing.T) { t.Parallel() limits := defaultImportLimits() destination := Keys("collections", "entry") empty := Mapping("empty", map[string]any{}) parsed, diagnostics := parseSource(empty, "fixture", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatal(diagnostics) } perInput := countInputNodes(parsed.document) + destinationWrapperNodeCount(destination) for _, test := range []struct { name string delta int wantError bool wantCharge int }{ {name: "below aggregate bound", delta: -1, wantCharge: perInput}, {name: "at aggregate bound", delta: 0, wantCharge: perInput}, {name: "above aggregate bound", delta: 1, wantError: true}, } { t.Run(test.name, func(t *testing.T) { t.Parallel() accounting := &importAccounting{nodes: limits.nodes - perInput + test.delta} acquired, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{ source: empty, destination: destination, }}}, 1, nil, accounting) if test.wantError { if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceLimitExceeded { t.Fatalf("over-budget placement = %#v, %#v", acquired, diagnostics) } return } if acquired == nil || len(diagnostics) != 0 || acquired.inputNodes != test.wantCharge { t.Fatalf("bounded placement = %#v, %#v", acquired, diagnostics) } }) } } func TestMultipleMountedInputsSharePlacementCharges(t *testing.T) { t.Parallel() limits := defaultImportLimits() inputs := []loadInput{ {source: Mapping("one", map[string]any{}), destination: Keys("collection", "one")}, {source: Mapping("two", map[string]any{}), destination: Keys("collection", "two")}, {source: Mapping("three", map[string]any{}), destination: Keys("collection", "three")}, } perInput := countInputNodes(&document.Node{Kind: document.Mapping}) + destinationWrapperNodeCount(Keys("collection", "one")) charge := len(inputs) * perInput accounting := &importAccounting{nodes: limits.nodes - charge} acquired, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: inputs}, 1, nil, accounting) if len(diagnostics) != 0 || acquired == nil || accounting.nodes != limits.nodes || acquired.inputNodes != charge { t.Fatalf("shared placement accounting = %#v, %#v, accounting %#v; want charge %d", acquired, diagnostics, accounting, charge) } } func TestNestedItemGlobsShareUnderlayNodeBudget(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - glob: children/*.yml destination: [children] placement: sequence-item id: parent `) fragmentPath := writeImportFile(t, directory, "children/a.yml", "id: child-a\n") parseNodes := func(path string) int { t.Helper() parsed, diagnostics := parseSource(File(path), "fixture", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatalf("parse %s: %#v", path, diagnostics) } return countInputNodes(parsed.document) } destination := Keys("groups") childDestination := appendDestination(destination, Keys("children")) otherDestination := Keys("other") wantNodes := parseNodes(mainPath) + parseNodes(fragmentPath) + countInputNodes(&document.Node{Kind: document.Mapping}) + destinationWrapperNodeCountForMode(destination, PlacementModeSequenceItem) + destinationWrapperNodeCountForMode(childDestination, PlacementModeSequenceItem) + destinationWrapperNodeCountForMode(otherDestination, PlacementModeSequenceItem) limits := defaultImportLimits() for _, test := range []struct { name string delta int wantError bool }{ {name: "at aggregate bound"}, {name: "one node below aggregate bound", delta: 1, wantError: true}, } { t.Run(test.name, func(t *testing.T) { accounting := &importAccounting{nodes: limits.nodes - wantNodes + test.delta} acquired, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{ source: File(mainPath), imports: true, destination: destination, placementMode: PlacementModeSequenceItem, }, { source: Mapping("other", map[string]any{"id": "other"}), destination: otherDestination, placementMode: PlacementModeSequenceItem, }}}, 1, nil, accounting) if test.wantError { if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded { t.Fatalf("under-budget nested item glob = %#v, %#v", acquired, diagnostics) } return } if acquired == nil || len(diagnostics) != 0 || len(acquired.sources) != 3 { t.Fatalf("bounded nested item glob = %#v, sources %#v, diagnostics %#v", acquired, acquired.sources, diagnostics) } if acquired.inputNodes != wantNodes || accounting.nodes != limits.nodes+test.delta { t.Fatalf("nested item node charge = %d/%d, want %d/%d", acquired.inputNodes, accounting.nodes, wantNodes, limits.nodes+test.delta) } }) } } func TestImportedEmptyBodiesChargeEachDestinationWrapper(t *testing.T) { t.Parallel() filesystem := NewFilesystem("bundle", fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: fragment.yml}]}\n")}, "fragment.yml": &fstest.MapFile{Data: []byte("{}\n")}, }) mainSource := filesystem.File("main.yml") mainParsed, diagnostics := parseSource(mainSource, "fixture", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatal(diagnostics) } fragmentParsed, diagnostics := parseSource(Bytes("fragment", []byte("{}\n")), "fixture", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatal(diagnostics) } rawNodes := countInputNodes(mainParsed.document) + countInputNodes(fragmentParsed.document) destination := Keys("collection", "empty") placementNodes := 2 * destinationWrapperNodeCount(destination) limits := defaultImportLimits() for _, test := range []struct { name string delta int wantError bool }{ {name: "below aggregate bound", delta: -1}, {name: "at aggregate bound", delta: 0}, {name: "above aggregate bound", delta: 1, wantError: true}, } { t.Run(test.name, func(t *testing.T) { t.Parallel() accounting := &importAccounting{nodes: limits.nodes - rawNodes - placementNodes + test.delta} composition, sources, _, diagnostics := testComposeUnderlayGroup(loadGroup{inputs: []loadInput{{ source: mainSource, imports: true, destination: destination, }}}, 1, nil, accounting) if test.wantError { if composition != nil || len(diagnostics) == 0 || diagnostics[0].Code != CodeImportLimitExceeded { t.Fatalf("over-budget imports = %#v, %#v", composition, diagnostics) } return } if len(diagnostics) != 0 || composition == nil || len(sources) != 2 { t.Fatalf("bounded imports = %#v, sources %#v, diagnostics %#v", composition, sources, diagnostics) } if composition.inputNodes != rawNodes+placementNodes || accounting.nodes != limits.nodes+test.delta { t.Fatalf("import node charge = %d, accounting %d; want %d and %d", composition.inputNodes, accounting.nodes, rawNodes+placementNodes, limits.nodes+test.delta) } if node, present := documentAtPath(composition.root, Keys("collection", "empty")); !present || node.Kind != document.Mapping || len(node.Mapping) != 0 { t.Fatalf("placed empty import body = %#v, present %v", node, present) } }) } } func TestImportedEmptyBodiesChargeFullRelativeDestinations(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - path: empty.yml destination: [groups, empty] `) fragmentPath := writeImportFile(t, directory, "empty.yml", "{}\n") main := parseImportMain(t, mainPath) fragment, diagnostics := parseSource(File(fragmentPath), "source-2", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatalf("empty fragment diagnostics = %#v", diagnostics) } entryDestination := Keys("collections", "entry") importDestination := appendDestination(entryDestination, Keys("groups", "empty")) wantNodes := countInputNodes(main.document) + countInputNodes(fragment.document) + destinationWrapperNodeCount(entryDestination) + destinationWrapperNodeCount(importDestination) limits := defaultImportLimits() for _, test := range []struct { name string delta int wantError bool }{ {name: "below aggregate bound", delta: -1, wantError: true}, {name: "at aggregate bound"}, {name: "above aggregate bound", delta: 1}, } { t.Run(test.name, func(t *testing.T) { currentLimits := limits currentLimits.nodes = wantNodes + test.delta accounting := &importAccounting{} request := testImportRequest(File(mainPath), nil, main, entryDestination, nil, nil) acquired, sources, diagnostics := testAcquireImportWithAccounting(request, currentLimits, accounting) if test.wantError { if acquired != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { t.Fatalf("below-bound relative placement = %#v, %#v", acquired, diagnostics) } return } if len(diagnostics) != 0 || acquired == nil || len(sources) != 2 { t.Fatalf("bounded relative placement = %#v, %#v, %#v", acquired, sources, diagnostics) } if acquired.inputNodes != wantNodes || accounting.nodes != wantNodes { t.Fatalf("relative placement node charge = %d/%d, want %d", acquired.inputNodes, accounting.nodes, wantNodes) } if sources[1].Destination.String() != importDestination.String() { t.Fatalf("empty import destination = %q, want %q", sources[1].Destination.String(), importDestination.String()) } composed, diagnostics := testComposeAcquiredImport(request, acquisitionPolicy{importLimits: currentLimits}, &acquisitionState{accounting: accounting}, acquired, sources) if len(diagnostics) != 0 { t.Fatalf("compose relative placement: %#v", diagnostics) } if node, present := documentAtPath(composed.root, importDestination); !present || node.Kind != document.Mapping || len(node.Mapping) != 0 { t.Fatalf("empty relative import body = %#v, present %v", node, present) } }) } } func TestGlobMatchesChargeTheirSharedRelativeDestination(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: - glob: fragments/*.yml destination: [groups, empty] `) fragmentPaths := []string{ writeImportFile(t, directory, "fragments/a.yml", "{}\n"), writeImportFile(t, directory, "fragments/b.yml", "{}\n"), writeImportFile(t, directory, "fragments/c.yml", "{}\n"), } main := parseImportMain(t, mainPath) fragment, diagnostics := parseSource(File(fragmentPaths[0]), "source-fragment", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatalf("glob fragment diagnostics = %#v", diagnostics) } entryDestination := Keys("collections", "entry") importDestination := appendDestination(entryDestination, Keys("groups", "empty")) wantNodes := countInputNodes(main.document) + len(fragmentPaths)*countInputNodes(fragment.document) + destinationWrapperNodeCount(entryDestination) + len(fragmentPaths)*destinationWrapperNodeCount(importDestination) limits := defaultImportLimits() for _, test := range []struct { name string delta int wantError bool }{ {name: "below aggregate bound", delta: -1, wantError: true}, {name: "at aggregate bound"}, {name: "above aggregate bound", delta: 1}, } { t.Run(test.name, func(t *testing.T) { currentLimits := limits currentLimits.nodes = wantNodes + test.delta accounting := &importAccounting{} request := testImportRequest(File(mainPath), nil, main, entryDestination, nil, nil) acquired, sources, diagnostics := testAcquireImportWithAccounting(request, currentLimits, accounting) if test.wantError { if acquired != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { t.Fatalf("below-bound glob placement = %#v, %#v", acquired, diagnostics) } return } if len(diagnostics) != 0 || acquired == nil || len(sources) != len(fragmentPaths)+1 { t.Fatalf("bounded glob placement = %#v, %#v, %#v", acquired, sources, diagnostics) } if acquired.inputNodes != wantNodes || accounting.nodes != wantNodes { t.Fatalf("glob relative placement node charge = %d/%d, want %d", acquired.inputNodes, accounting.nodes, wantNodes) } for index, source := range sources[1:] { if source.Destination.String() != importDestination.String() { t.Errorf("glob source %d destination = %q, want %q", index, source.Destination.String(), importDestination.String()) } } composed, diagnostics := testComposeAcquiredImport(request, acquisitionPolicy{importLimits: currentLimits}, &acquisitionState{accounting: accounting}, acquired, sources) if len(diagnostics) != 0 { t.Fatalf("compose glob placement: %#v", diagnostics) } if node, present := documentAtPath(composed.root, importDestination); !present || node.Kind != document.Mapping || len(node.Mapping) != 0 { t.Fatalf("empty glob contributions = %#v, present %v", node, present) } }) } } func TestRelativeImportDestinationUsesAccumulatedDocumentDepth(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: fragment.yml, destination: [extra]}]}\n") writeImportFile(t, directory, "fragment.yml", "value: nested\n") destination := Path{segments: make([]pathSegment, maxDocumentDepth)} for index := range destination.segments { destination.segments[index] = pathSegment{kind: pathSegmentKey, key: "entry"} } _, err := LoadLayered( Input{Source: File(mainPath), Imports: true, Destination: destination}, testSecretBinding(), LayeredOptions[map[string]any]{}, ) loadError, ok := err.(*LoadError) if !ok || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != CodeImportLimitExceeded { t.Fatalf("accumulated depth result = %v", err) } sources := loadError.Sources() if len(sources) != 2 || len(sources[0].Destination.segments) != maxDocumentDepth || len(sources[1].Destination.segments) != maxDocumentDepth+1 { t.Fatalf("depth failure source destinations = %#v", sources) } } func TestAbsentOptionalImportsDoNotChargeUnusedDeepDestinations(t *testing.T) { t.Parallel() destination := Path{segments: make([]pathSegment, maxDocumentDepth)} for index := range destination.segments { destination.segments[index] = pathSegment{kind: pathSegmentKey, key: "entry"} } for _, test := range []struct { name string body string setup func(*testing.T, string) }{ { name: "optional missing path", body: "configkit: {imports: [{path: missing.yml, destination: [unused]}]}\n", }, { name: "optional unmatched glob", body: "configkit: {imports: [{glob: empty/*.yml, destination: [unused]}]}\n", setup: func(t *testing.T, directory string) { if err := os.Mkdir(filepath.Join(directory, "empty"), 0o755); err != nil { t.Fatal(err) } }, }, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() if test.setup != nil { test.setup(t, directory) } mainPath := writeImportFile(t, directory, "main.yml", test.body) main := parseImportMain(t, mainPath) limits := defaultImportLimits() accounting := &importAccounting{} request := testImportRequest(File(mainPath), nil, main, destination, nil, nil) acquired, sources, diagnostics := testAcquireImportWithAccounting(request, limits, accounting) if len(diagnostics) != 0 || acquired == nil || len(sources) != 1 { t.Fatalf("absent optional import = %#v, %#v, %#v", acquired, sources, diagnostics) } wantNodes := countInputNodes(main.document) + destinationWrapperNodeCount(destination) if acquired.inputNodes != wantNodes || accounting.nodes != wantNodes { t.Fatalf("absent optional node charge = %d/%d, want %d", acquired.inputNodes, accounting.nodes, wantNodes) } composed, diagnostics := testComposeAcquiredImport(request, acquisitionPolicy{importLimits: limits}, &acquisitionState{accounting: accounting}, acquired, sources) if len(diagnostics) != 0 { t.Fatalf("compose absent optional import: %#v", diagnostics) } if node, present := documentAtPath(composed.root, destination); !present || node.Kind != document.Mapping || len(node.Mapping) != 0 { t.Fatalf("empty entry body = %#v, present %v", node, present) } }) } } func TestNoSourceAndRootPlacementAddNoWrapperCharges(t *testing.T) { t.Parallel() accounting := &importAccounting{} acquired, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{ {source: NoSource(), destination: Keys("absent")}, {source: Mapping("root", map[string]any{})}, }}, 1, nil, accounting) if len(diagnostics) != 0 || acquired == nil || len(acquired.sources) != 1 || accounting.nodes != 1 || acquired.inputNodes != 1 { t.Fatalf("root and absent placement = %#v, diagnostics %#v, accounting %#v", acquired, diagnostics, accounting) } } func TestSelectedOverlayAndCLIUsePlacementNodeCharges(t *testing.T) { t.Parallel() limits := defaultImportLimits() baseNodes := countInputNodes(&document.Node{Kind: document.Mapping}) + destinationWrapperNodeCount(Keys("mounted")) t.Run("selected overlay", func(t *testing.T) { t.Parallel() filesystem := NewFilesystem("bundle", fstest.MapFS{ "overlays/test.yml": &fstest.MapFile{Data: []byte("{}\n")}, }) request := loadRequest[map[string]any]{ underlays: []loadGroup{{inputs: []loadInput{{source: Mapping("empty", map[string]any{}), destination: Keys("mounted")}}}}, overlay: "test", overlayFilesystem: &FilesystemContext{Root: ".", BaseDir: ".", FS: &filesystem}, } accounting := &importAccounting{nodes: limits.nodes - baseNodes} _, diagnostics := testAssembleLayeredInput(request, assembledInput{}, accounting) if len(diagnostics) != 1 || diagnostics[0].Code != CodeOverlayLimitExceeded { t.Fatalf("overlay diagnostics = %#v", diagnostics) } }) t.Run("CLI override", func(t *testing.T) { t.Parallel() override := Override{Path: Keys("mounted", "value"), Value: "x", Flag: "--value"} builder := valueBuilder{sourceID: "fixture", active: make(map[valueVisit]bool)} if _, issue := builder.build(override.Value, override.Path, len(override.Path.segments)); issue != nil { t.Fatal(issue) } overrideNodes := builder.nodes + destinationWrapperNodeCount(override.Path) request := loadRequest[map[string]any]{ underlays: []loadGroup{{inputs: []loadInput{{source: Mapping("empty", map[string]any{}), destination: Keys("mounted")}}}}, overrides: []Override{override}, } accounting := &importAccounting{nodes: limits.nodes - baseNodes - overrideNodes + 1} input, diagnostics := testAssembleLayeredInput(request, assembledInput{}, accounting) if len(diagnostics) != 0 { t.Fatalf("assembly diagnostics = %#v", diagnostics) } _, diagnostics = preparePostStructuralOverrides(overridePreparationInput{ root: input.document, inputBytes: accounting.bytes, inputNodes: accounting.nodes, }, input.sources, request.overrides, nil, limits) if len(diagnostics) != 1 || diagnostics[0].Code != CodeOverrideLimitExceeded { t.Fatalf("override diagnostics = %#v", diagnostics) } }) } func testAssembleLayeredInput[T any](request loadRequest[T], input assembledInput, accounting *importAccounting) (assembledInput, []Diagnostic) { if accounting == nil { accounting = &importAccounting{} } policy := acquisitionPolicy{ importDefaults: request.importDefaults, sequencePolicy: request.sequencePolicy, importLimits: defaultImportLimits(), } return assembleLayeredInput(request, input, policy, &acquisitionState{accounting: accounting, versionState: input.versionState}) }