diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 67bb09e..e76163f 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -650,6 +650,15 @@ still run through the active protocol. Keep tests' intended boundaries; do not delete meaningful composition cases when changing fixture setup. Focused check: `go test ./internal/engine -run 'Import|Destination|SourceVersion|LoadGrouped|Underlay'`. +Status: **complete** (F01). Prior completed commits: `2736ddf`, `deb9aa5`, +`ae3408a`, `ea27f17`, `08a8c25`, `877c497`, `8aba43d`, `7bf2f0c`, `0f9ecf1`, +`119b42c`, `247a12b`, `4fcff5f`, `12d350a`, `08f2a8f`, `826062a`, `e4a5ecb`, +`3122e2f`, `6f6130f`, and `4fd3bee`. Validation passed: +`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -run 'Import|Destination|SourceVersion|LoadGrouped|Underlay' -count=1`, +`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -count=1`, +`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./... -count=1`, +and `git diff --check`. + ## Stage 21 — Isolate Parsed-Input Directive And Version Processing Finding: first part of **F02**; requires Stage 20. diff --git a/internal/engine/acquisition.go b/internal/engine/acquisition.go index ef738f5..64364b3 100644 --- a/internal/engine/acquisition.go +++ b/internal/engine/acquisition.go @@ -23,7 +23,6 @@ type inputAcquisitionRequest struct { role sourceVersionRole directiveContext rootDirectiveContext protectionRule *VersionRule - deferAssembly bool ordinaryOverlay string } diff --git a/internal/engine/destination_accounting_test.go b/internal/engine/destination_accounting_test.go index e25954d..b5a8d23 100644 --- a/internal/engine/destination_accounting_test.go +++ b/internal/engine/destination_accounting_test.go @@ -34,17 +34,17 @@ func TestDestinationPlacementCountsAgainstSharedNodeBudget(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() accounting := &importAccounting{nodes: limits.nodes - perInput + test.delta} - composition, _, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{ + acquired, diagnostics := testAcquireUnderlayGroup(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) + if len(diagnostics) != 1 || diagnostics[0].Code != CodeSourceLimitExceeded { + t.Fatalf("over-budget placement = %#v, %#v", acquired, diagnostics) } return } - if composition == nil || len(diagnostics) != 0 || composition.inputNodes != test.wantCharge { - t.Fatalf("bounded placement = %#v, %#v", composition, diagnostics) + if acquired == nil || len(diagnostics) != 0 || acquired.inputNodes != test.wantCharge { + t.Fatalf("bounded placement = %#v, %#v", acquired, diagnostics) } }) } @@ -62,9 +62,9 @@ func TestMultipleMountedInputsSharePlacementCharges(t *testing.T) { perInput := countInputNodes(&document.Node{Kind: document.Mapping}) + destinationWrapperNodeCount(Keys("collection", "one")) charge := len(inputs) * perInput accounting := &importAccounting{nodes: limits.nodes - charge} - composition, _, _, diagnostics := testAcquireUnderlayGroup(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) + 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) } } @@ -107,22 +107,22 @@ id: parent } { t.Run(test.name, func(t *testing.T) { accounting := &importAccounting{nodes: limits.nodes - wantNodes + test.delta} - composition, sources, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{ + 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 composition != nil || len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded { - t.Fatalf("under-budget nested item glob = %#v, %#v", composition, diagnostics) + if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded { + t.Fatalf("under-budget nested item glob = %#v, %#v", acquired, diagnostics) } return } - if composition == nil || len(diagnostics) != 0 || len(sources) != 3 { - t.Fatalf("bounded nested item glob = %#v, sources %#v, diagnostics %#v", composition, sources, diagnostics) + 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 composition.inputNodes != wantNodes || accounting.nodes != limits.nodes+test.delta { - t.Fatalf("nested item node charge = %d/%d, want %d/%d", composition.inputNodes, accounting.nodes, wantNodes, limits.nodes+test.delta) + 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) } }) } @@ -160,7 +160,7 @@ func TestImportedEmptyBodiesChargeEachDestinationWrapper(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() accounting := &importAccounting{nodes: limits.nodes - rawNodes - placementNodes + test.delta} - composition, sources, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{ + composition, sources, _, diagnostics := testComposeUnderlayGroup(loadGroup{inputs: []loadInput{{ source: mainSource, imports: true, destination: destination, }}}, 1, nil, accounting) if test.wantError { @@ -217,22 +217,26 @@ func TestImportedEmptyBodiesChargeFullRelativeDestinations(t *testing.T) { currentLimits.nodes = wantNodes + test.delta accounting := &importAccounting{} request := testImportRequest(File(mainPath), nil, main, entryDestination, nil, nil) - composed, sources, diagnostics := testAcquireImportWithAccounting(request, currentLimits, accounting) + acquired, sources, diagnostics := testAcquireImportWithAccounting(request, currentLimits, accounting) if test.wantError { - if composed != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { - t.Fatalf("below-bound relative placement = %#v, %#v", composed, diagnostics) + 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 || composed == nil || len(sources) != 2 { - t.Fatalf("bounded relative placement = %#v, %#v, %#v", composed, sources, diagnostics) + if len(diagnostics) != 0 || acquired == nil || len(sources) != 2 { + t.Fatalf("bounded relative placement = %#v, %#v, %#v", acquired, sources, diagnostics) } - if composed.inputNodes != wantNodes || accounting.nodes != wantNodes { - t.Fatalf("relative placement node charge = %d/%d, want %d", composed.inputNodes, accounting.nodes, wantNodes) + 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) } @@ -279,24 +283,28 @@ func TestGlobMatchesChargeTheirSharedRelativeDestination(t *testing.T) { currentLimits.nodes = wantNodes + test.delta accounting := &importAccounting{} request := testImportRequest(File(mainPath), nil, main, entryDestination, nil, nil) - composed, sources, diagnostics := testAcquireImportWithAccounting(request, currentLimits, accounting) + acquired, sources, diagnostics := testAcquireImportWithAccounting(request, currentLimits, accounting) if test.wantError { - if composed != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { - t.Fatalf("below-bound glob placement = %#v, %#v", composed, diagnostics) + 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 || composed == nil || len(sources) != len(fragmentPaths)+1 { - t.Fatalf("bounded glob placement = %#v, %#v, %#v", composed, sources, diagnostics) + if len(diagnostics) != 0 || acquired == nil || len(sources) != len(fragmentPaths)+1 { + t.Fatalf("bounded glob placement = %#v, %#v, %#v", acquired, sources, diagnostics) } - if composed.inputNodes != wantNodes || accounting.nodes != wantNodes { - t.Fatalf("glob relative placement node charge = %d/%d, want %d", composed.inputNodes, accounting.nodes, wantNodes) + 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) } @@ -365,13 +373,17 @@ func TestAbsentOptionalImportsDoNotChargeUnusedDeepDestinations(t *testing.T) { limits := defaultImportLimits() accounting := &importAccounting{} request := testImportRequest(File(mainPath), nil, main, destination, nil, nil) - composed, sources, diagnostics := testAcquireImportWithAccounting(request, limits, accounting) - if len(diagnostics) != 0 || composed == nil || len(sources) != 1 { - t.Fatalf("absent optional import = %#v, %#v, %#v", composed, sources, diagnostics) + 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 composed.inputNodes != wantNodes || accounting.nodes != wantNodes { - t.Fatalf("absent optional node charge = %d/%d, want %d", composed.inputNodes, accounting.nodes, wantNodes) + 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) @@ -384,12 +396,12 @@ func TestNoSourceAndRootPlacementAddNoWrapperCharges(t *testing.T) { t.Parallel() accounting := &importAccounting{} - composition, sources, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{ + acquired, diagnostics := testAcquireUnderlayGroup(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) + 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) } } diff --git a/internal/engine/import_acquisition.go b/internal/engine/import_acquisition.go index 8b4c9c5..8cf40c4 100644 --- a/internal/engine/import_acquisition.go +++ b/internal/engine/import_acquisition.go @@ -75,7 +75,6 @@ func acquireImportedWithAccess(request importAcquisitionRequest, policy *acquisi destination := request.input.input.destination limits := policy.importLimits accounting := loadState.accounting - versionState := loadState.versionState processing := request.processing basePath, mainPath := resolved.basePath, resolved.mainPath if tier.visited == nil { @@ -213,25 +212,13 @@ func acquireImportedWithAccess(request importAcquisitionRequest, policy *acquisi }}, } } - if request.input.deferAssembly { - if diagnostic := reservePendingItemPlacements(&acquisition, limits, accounting); diagnostic != nil { - return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic} - } - return &composedDocument{ - acquisition: []acquisitionScope{cloneAcquisitionScope(acquisition)}, - importGraph: state.importGraph.clone(), inputBytes: accounting.bytes, inputNodes: accounting.nodes, - }, cloneSourceInfos(state.sources), nil + if diagnostic := reservePendingItemPlacements(&acquisition, limits, accounting); diagnostic != nil { + return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic} } - acquired := &composedDocument{ + return &composedDocument{ acquisition: []acquisitionScope{cloneAcquisitionScope(acquisition)}, importGraph: state.importGraph.clone(), inputBytes: accounting.bytes, inputNodes: accounting.nodes, - } - input := assembledInput{ - parsed: main, composition: acquired, acquisition: cloneAcquisitionScopes(acquired.acquisition), - sources: state.sources, versionState: versionState, accounting: accounting, - } - composed, diagnostics := composeAcquiredInputWithPolicies(input, collectionPolicyScope{}, policy.sequencePolicy) - return composed, cloneSourceInfos(state.sources), diagnostics + }, cloneSourceInfos(state.sources), nil } func (s *importTraversal) checkInitialLimits() *Diagnostic { diff --git a/internal/engine/imports_test.go b/internal/engine/imports_test.go index 69710bd..fdedc07 100644 --- a/internal/engine/imports_test.go +++ b/internal/engine/imports_test.go @@ -113,17 +113,22 @@ tasks: [{id: z-parent}] aIndependentPath := writeImportFile(t, directory, "a-independent.yml", "tasks: [{id: a-independent}]\n") bDependencyPath := writeImportFile(t, directory, "b-dependency.yml", "tasks: [{id: b-dependency}]\n") + sequencePolicy := mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("tasks")}, identityFields: []string{"id"}}}) input, diagnostics := testAcquireInput(inputAcquisitionRequest{ input: loadInput{source: File(mainPath), imports: true}, role: sourceVersionMain, directiveContext: rootDirectiveContextImportsOnly, }, acquisitionPolicy{ - sequencePolicy: mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("tasks")}, identityFields: []string{"id"}}}), + sequencePolicy: sequencePolicy, importLimits: defaultImportLimits(), }, nil) if len(diagnostics) != 0 { t.Fatalf("assemble imports: %#v", diagnostics) } - tasks, ok := documentAtPath(input.document, Keys("tasks")) + composition, diagnostics := compositionForInputWithPolicy(input, sequencePolicy) + if len(diagnostics) != 0 { + t.Fatalf("compose acquired imports: %#v", diagnostics) + } + tasks, ok := documentAtPath(composition.root, Keys("tasks")) if !ok || tasks.Kind != document.Sequence { t.Fatalf("tasks document = %#v, %v", tasks, ok) } @@ -627,7 +632,7 @@ id: item accounting := &importAccounting{} input, diagnostics := testAcquireInput(inputAcquisitionRequest{ input: loadInput{source: File(mainPath), imports: true, filesystem: &FilesystemContext{Root: directory}}, - role: sourceVersionMain, directiveContext: rootDirectiveContextImportsOnly, deferAssembly: true, + role: sourceVersionMain, directiveContext: rootDirectiveContextImportsOnly, }, acquisitionPolicy{importLimits: defaultImportLimits()}, accounting) if len(diagnostics) != 0 { t.Fatalf("acquire input: %#v", diagnostics) @@ -683,6 +688,7 @@ func TestItemImportCanJoinAnAuthoredListUnderRegisteredComposition(t *testing.T) `) writeImportFile(t, directory, "item.yml", "configkit: {imports: [{path: helper.yml}]}\nname: imported\n") writeImportFile(t, directory, "helper.yml", "id: helper-id\n") + sequencePolicy := mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}) input, diagnostics := testAcquireInput(inputAcquisitionRequest{ input: loadInput{source: File(mainPath), imports: true}, role: sourceVersionMain, directiveContext: rootDirectiveContextImportsOnly, @@ -690,8 +696,12 @@ func TestItemImportCanJoinAnAuthoredListUnderRegisteredComposition(t *testing.T) if len(diagnostics) != 0 { t.Fatalf("assemble item import: %#v", diagnostics) } + imported, diagnostics := compositionForInputWithPolicy(input, sequencePolicy) + if len(diagnostics) != 0 { + t.Fatalf("compose acquired item import: %#v", diagnostics) + } authored := parseDocumentForDecode(t, "items: [{id: authored-id, name: authored}]\n") - composed, diagnostics := composeDocumentsWithPolicy(mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}), authored, input.document) + composed, diagnostics := composeDocumentsWithPolicy(sequencePolicy, authored, imported.root) if len(diagnostics) != 0 { t.Fatalf("compose authored and imported lists: %#v", diagnostics) } @@ -998,10 +1008,10 @@ func TestItemAcquisitionRetainsInclusionGraphAndScopeOwnership(t *testing.T) { break } } - if item == nil || item.identity != graph.nodes[1].key || item.scopeOwner != rootScope.owner || item.nested == nil { + if item == nil || item.identity != graph.nodes[1].key || item.scopeOwner != rootScope.owner || item.pendingItem == nil { t.Fatalf("outer item contribution = %#v", item) } - itemScope := item.nested.acquisition[0] + itemScope := item.pendingItem if itemScope.owner != item.identity || len(itemScope.contributions) != 2 { t.Fatalf("nested item scope = %#v", itemScope) } @@ -1190,8 +1200,12 @@ func TestItemHelperScopeScheduleInterleavesUnequalBranches(t *testing.T) { if len(diagnostics) != 0 { t.Fatalf("assemble item scope: %#v", diagnostics) } + composition, diagnostics := compositionForInputWithPolicy(input, sequencePolicy{}) + if len(diagnostics) != 0 { + t.Fatalf("compose acquired item scope: %#v", diagnostics) + } var nested *composedDocument - for _, contribution := range input.composition.acquisition[0].contributions { + for _, contribution := range composition.acquisition[0].contributions { if contribution.role == importRoleItem { nested = contribution.nested break @@ -1398,15 +1412,15 @@ func TestImportDepthBudgetCountsSelectedEdges(t *testing.T) { limits.depth = 1 main := parseImportMain(t, mainPath) request := testImportRequest(File(mainPath), nil, main, Path{}, nil, nil) - composed, sources, diagnostics := testAcquireImportWithLimits(request, limits) + acquired, sources, diagnostics := testAcquireImportWithLimits(request, limits) if test.code != "" { if len(diagnostics) != 1 || diagnostics[0].Code != test.code { t.Fatalf("diagnostics = %#v, want %s", diagnostics, test.code) } return } - if len(diagnostics) != 0 || composed == nil || len(sources) != 2 { - t.Fatalf("depth-boundary composition: sources=%#v diagnostics=%#v", sources, diagnostics) + if len(diagnostics) != 0 || acquired == nil || len(acquired.acquisition) == 0 || len(sources) != 2 { + t.Fatalf("depth-boundary acquisition: sources=%#v diagnostics=%#v", sources, diagnostics) } }) } @@ -1429,9 +1443,9 @@ func TestImportTraversalAcceptsExactPrivateBudgets(t *testing.T) { nodes: countInputNodes(main.document) + countInputNodes(imported.document), } request := testImportRequest(File(mainPath), nil, main, Path{}, nil, nil) - composed, sources, diagnostics := testAcquireImportWithLimits(request, limits) - if len(diagnostics) != 0 || composed == nil || len(sources) != 2 { - t.Fatalf("exact-limit composition = (%#v, %#v, %#v)", composed, sources, diagnostics) + acquired, sources, diagnostics := testAcquireImportWithLimits(request, limits) + if len(diagnostics) != 0 || acquired == nil || len(acquired.acquisition) == 0 || len(sources) != 2 { + t.Fatalf("exact-limit acquisition = (%#v, %#v, %#v)", acquired, sources, diagnostics) } } @@ -1516,6 +1530,14 @@ func testAcquireImport(request importAcquisitionRequest, policy acquisitionPolic return composition, sources, diagnostics } +func testComposeAcquiredImport(request importAcquisitionRequest, policy acquisitionPolicy, state *acquisitionState, acquired *composedDocument, sources []SourceInfo) (*composedDocument, []Diagnostic) { + input := assembledInput{ + parsed: request.main, composition: acquired, acquisition: cloneAcquisitionScopes(acquired.acquisition), + sources: sources, versionState: state.versionState, accounting: state.accounting, + } + return composeAcquiredInputWithPolicies(input, collectionPolicyScope{}, policy.sequencePolicy) +} + func testAcquireImportWithLimits(request importAcquisitionRequest, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) { return testAcquireImport(request, acquisitionPolicy{importLimits: limits}, &acquisitionState{accounting: &importAccounting{}}, &tierAcquisitionState{}) } diff --git a/internal/engine/load.go b/internal/engine/load.go index 2307ec4..2b4cf43 100644 --- a/internal/engine/load.go +++ b/internal/engine/load.go @@ -212,7 +212,7 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo } input, diagnostics := acquireInput(inputAcquisitionRequest{ input: request.main, role: sourceVersionMain, directiveContext: directiveContext, - protectionRule: request.protectionVersion(), deferAssembly: true, + protectionRule: request.protectionVersion(), ordinaryOverlay: ordinaryOverlay, }, policy, state, &tierAcquisitionState{visited: make(map[resourceIdentity]importRoute)}) request.main.source = input.source diff --git a/internal/engine/load_request.go b/internal/engine/load_request.go index 9bccfdb..9adac05 100644 --- a/internal/engine/load_request.go +++ b/internal/engine/load_request.go @@ -246,7 +246,7 @@ func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, s for inputIndex, contribution := range group.inputs { acquired, diagnostics := acquireInput(inputAcquisitionRequest{ input: contribution, role: sourceVersionContribution, - directiveContext: rootDirectiveContextLayered, protectionRule: rule, deferAssembly: true, + directiveContext: rootDirectiveContextLayered, protectionRule: rule, }, policy, state, tier) for _, source := range acquired.sources { source.Layer = LayerUnderlay diff --git a/internal/engine/load_request_test.go b/internal/engine/load_request_test.go index a0f87f3..f55424a 100644 --- a/internal/engine/load_request_test.go +++ b/internal/engine/load_request_test.go @@ -457,21 +457,26 @@ func TestGroupedLoadStrictConflictsRepetitionAndBudgets(t *testing.T) { accounting := &importAccounting{sources: maxImportSources - 1} group := loadGroup{inputs: []loadInput{{source: Mapping("first", map[string]any{})}, {source: Mapping("second", map[string]any{})}}} - _, _, _, diagnostics := testAcquireUnderlayGroup(group, 1, nil, accounting) + _, diagnostics := testAcquireUnderlayGroup(group, 1, nil, accounting) if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded { t.Fatalf("shared source budget = %#v", diagnostics) } accounting = &importAccounting{bytes: maxTotalBytes - 2} group.inputs = []loadInput{{source: Mapping("first", map[string]any{"port": "one"})}} - _, _, _, diagnostics = testAcquireUnderlayGroup(group, 1, nil, accounting) + _, diagnostics = testAcquireUnderlayGroup(group, 1, nil, accounting) if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded { t.Fatalf("shared byte budget = %#v", diagnostics) } } -func testAcquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) { +func testAcquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*acquiredUnderlayGroup, []Diagnostic) { state := &acquisitionState{accounting: accounting} acquired, diagnostics := acquireUnderlayGroupScopes(group, index, rule, state, acquisitionPolicy{importLimits: defaultImportLimits()}) + return acquired, diagnostics +} + +func testComposeUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) { + acquired, diagnostics := testAcquireUnderlayGroup(group, index, rule, accounting) if len(diagnostics) != 0 { return nil, acquired.sources, acquired.contexts, diagnostics } @@ -520,13 +525,13 @@ func TestGroupedAccountingChargesMixedOccurrencesOnce(t *testing.T) { "child-two.yml": &fstest.MapFile{Data: []byte(child)}, }) accounting := &importAccounting{} - first, sources, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("one.yml"), imports: true}}}, 1, nil, accounting) - if len(diagnostics) != 0 || first == nil || len(sources) != 2 || accounting.sources != 2 || accounting.bytes != int64(len(one)+len(child)) { - t.Fatalf("first acquisition = %#v, %#v, %#v, accounting %#v", first, sources, diagnostics, accounting) + first, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("one.yml"), imports: true}}}, 1, nil, accounting) + if len(diagnostics) != 0 || first == nil || len(first.sources) != 2 || accounting.sources != 2 || accounting.bytes != int64(len(one)+len(child)) { + t.Fatalf("first acquisition = %#v, %#v, accounting %#v", first, diagnostics, accounting) } - second, sources, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("two.yml"), imports: true}}}, 2, nil, accounting) - if len(diagnostics) != 0 || second == nil || len(sources) != 2 || sources[0].ID != "source-3" || sources[1].ID != "source-4" || accounting.sources != 4 || accounting.bytes != int64(len(one)+len(two)+2*len(child)) { - t.Fatalf("second acquisition = %#v, %#v, %#v, accounting %#v", second, sources, diagnostics, accounting) + second, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("two.yml"), imports: true}}}, 2, nil, accounting) + if len(diagnostics) != 0 || second == nil || len(second.sources) != 2 || second.sources[0].ID != "source-3" || second.sources[1].ID != "source-4" || accounting.sources != 4 || accounting.bytes != int64(len(one)+len(two)+2*len(child)) { + t.Fatalf("second acquisition = %#v, %#v, accounting %#v", second, diagnostics, accounting) } var wantNodes int for _, text := range []string{one, child, two, child} { @@ -551,7 +556,7 @@ func TestGroupedGlobEntryBudgetIsShared(t *testing.T) { "parts/b.yml": &fstest.MapFile{Data: []byte("right: two\n")}, }) accounting := &importAccounting{entries: maxGlobEntries - 1} - _, _, _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("main.yml"), imports: true}}}, 1, nil, accounting) + _, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("main.yml"), imports: true}}}, 1, nil, accounting) if len(diagnostics) == 0 || diagnostics[0].Code != CodeImportLimitExceeded || accounting.entries <= maxGlobEntries { t.Fatalf("shared glob budget = %#v, accounting %#v", diagnostics, accounting) } diff --git a/internal/engine/source_version_test.go b/internal/engine/source_version_test.go index e489303..db0509c 100644 --- a/internal/engine/source_version_test.go +++ b/internal/engine/source_version_test.go @@ -1533,9 +1533,13 @@ func TestSourceVersionImportAccountingIncludesConsumedHeaderNodes(t *testing.T) policy := acquisitionPolicy{importLimits: limits} owners := &acquisitionState{accounting: accounting, versionState: state} tier := &tierAcquisitionState{} - composed, sources, foundDiagnostics := testAcquireImport(request, policy, owners, tier) - if len(foundDiagnostics) != 0 || composed == nil || len(sources) != 2 || accounting.nodes != wantNodes || accounting.bytes != wantBytes { - t.Fatalf("exact raw byte/node accounting = %#v, %d/%d, want %d/%d, %#v", composed, accounting.bytes, accounting.nodes, wantBytes, wantNodes, foundDiagnostics) + acquired, sources, foundDiagnostics := testAcquireImport(request, policy, owners, tier) + if len(foundDiagnostics) != 0 || acquired == nil || len(sources) != 2 || accounting.nodes != wantNodes || accounting.bytes != wantBytes { + t.Fatalf("exact raw byte/node accounting = %#v, %d/%d, want %d/%d, %#v", acquired, accounting.bytes, accounting.nodes, wantBytes, wantNodes, foundDiagnostics) + } + composed, foundDiagnostics := testComposeAcquiredImport(request, policy, owners, acquired, sources) + if len(foundDiagnostics) != 0 || composed == nil { + t.Fatalf("compose acquired input = %#v, %#v", composed, foundDiagnostics) } if len(state.evidence) != 1 || state.evidence[0].sourceID != "source-2" || state.evidence[0].version.Text != "accepted" { t.Fatalf("acquired child evidence = %#v", state.evidence) @@ -1551,9 +1555,9 @@ func TestSourceVersionImportAccountingIncludesConsumedHeaderNodes(t *testing.T) policy.importLimits = below owners = &acquisitionState{accounting: accounting, versionState: &sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}} tier = &tierAcquisitionState{} - composed, _, foundDiagnostics = testAcquireImport(request, policy, owners, tier) - if composed != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded { - t.Fatalf("raw header nodes were not charged: %#v, %#v", composed, foundDiagnostics) + acquired, _, foundDiagnostics = testAcquireImport(request, policy, owners, tier) + if acquired != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded { + t.Fatalf("raw header nodes were not charged: %#v, %#v", acquired, foundDiagnostics) } below = defaultImportLimits() @@ -1563,9 +1567,9 @@ func TestSourceVersionImportAccountingIncludesConsumedHeaderNodes(t *testing.T) policy.importLimits = below owners = &acquisitionState{accounting: accounting, versionState: &sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}} tier = &tierAcquisitionState{} - composed, _, foundDiagnostics = testAcquireImport(request, policy, owners, tier) - if composed != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded { - t.Fatalf("raw header bytes were not charged: %#v, %#v", composed, foundDiagnostics) + acquired, _, foundDiagnostics = testAcquireImport(request, policy, owners, tier) + if acquired != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded { + t.Fatalf("raw header bytes were not charged: %#v, %#v", acquired, foundDiagnostics) } }