177 lines
7.3 KiB
Go
177 lines
7.3 KiB
Go
package engine
|
|
|
|
import (
|
|
"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}
|
|
composition, _, _, diagnostics := acquireUnderlayGroup(loadGroup{inputs: []loadInput{{
|
|
source: empty, destination: destination,
|
|
}}}, 1, nil, accounting)
|
|
if test.wantError {
|
|
if composition != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceLimitExceeded {
|
|
t.Fatalf("over-budget placement = %#v, %#v", composition, diagnostics)
|
|
}
|
|
return
|
|
}
|
|
if composition == nil || len(diagnostics) != 0 || composition.inputNodes != test.wantCharge {
|
|
t.Fatalf("bounded placement = %#v, %#v", composition, 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}
|
|
composition, _, _, diagnostics := acquireUnderlayGroup(loadGroup{inputs: inputs}, 1, nil, accounting)
|
|
if len(diagnostics) != 0 || composition == nil || accounting.nodes != limits.nodes || composition.inputNodes != charge {
|
|
t.Fatalf("shared placement accounting = %#v, %#v, accounting %#v; want charge %d", composition, diagnostics, accounting, charge)
|
|
}
|
|
}
|
|
|
|
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 := acquireUnderlayGroup(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 TestNoSourceAndRootPlacementAddNoWrapperCharges(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
accounting := &importAccounting{}
|
|
composition, sources, _, diagnostics := acquireUnderlayGroup(loadGroup{inputs: []loadInput{
|
|
{source: NoSource(), destination: Keys("absent")},
|
|
{source: Mapping("root", map[string]any{})},
|
|
}}, 1, nil, accounting)
|
|
if len(diagnostics) != 0 || composition == nil || len(sources) != 1 || accounting.nodes != 1 || composition.inputNodes != 1 {
|
|
t.Fatalf("root and absent placement = %#v, sources %#v, diagnostics %#v, accounting %#v", composition, sources, 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 := assembleLayeredInput(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}
|
|
_, diagnostics := assembleLayeredInput(request, assembledInput{}, accounting)
|
|
if len(diagnostics) != 1 || diagnostics[0].Code != CodeOverrideLimitExceeded {
|
|
t.Fatalf("override diagnostics = %#v", diagnostics)
|
|
}
|
|
})
|
|
}
|