diff --git a/docs/roadmap/audit.md b/docs/roadmap/audit.md index a143e39..e2a2c45 100644 --- a/docs/roadmap/audit.md +++ b/docs/roadmap/audit.md @@ -738,10 +738,10 @@ Type: test cleanup. Priority: low. Source: testing 6; related deadcode 3. compile-time interfaces, public construction, and distinct lifecycle checks. A literal assignment equality assertion on a new `Result.Config` adds no protection beyond compilation and ordinary Go assignment. -- Replace `TestWrappedPathsRetainEngineSegmentIntent` in - [facade_internal_test.go](../../facade_internal_test.go), which compares private - representations, with public contextual mapping/index behavior under F09. - Keep engine segment-intent coverage. +- Replace the former `TestWrappedPathsRetainEngineSegmentIntent` internal + representation check with public contextual mapping/index behavior under F09. + Keep engine segment-intent coverage; public source-form behavior is consolidated + in [facade_equivalence_test.go](../../facade_equivalence_test.go). - Move `TestWrappedSourcesPreserveEngineOwnership` from direct `engine.Load` through `source.toEngine()` to public `Load` and root error assertions. Consolidate reader cases with [facade_equivalence_test.go](../../facade_equivalence_test.go) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index cd264c1..230b58a 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -519,6 +519,13 @@ Findings: **F04**, remaining **F12**; requires Stage 15. Owners: root `records.go`, `records_test.go`, `facade_internal_test.go`, `facade_compatibility_test.go`, `facade_candidate_test.go`, `facade_equivalence_test.go`. +Status: **complete** (F04; remaining F12). Prior completed commits: `2736ddf`, +`deb9aa5`, `ae3408a`, `ea27f17`, `08a8c25`, `877c497`, `8aba43d`, `7bf2f0c`, +`0f9ecf1`, `119b42c`, `247a12b`, `4fcff5f`, `12d350a`, `08f2a8f`, `826062a`. +Validation passed: `env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test .`, +`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./examples/consumers`, +and `git diff --check`. + Remove the complete private clone family named in audit F04 after reconfirming that only the tests enter it. Preserve engine clones, active `Path.clone`, explicit root conversions, and nil/empty conversion checks. Remove the ineffective diff --git a/facade_compatibility_test.go b/facade_compatibility_test.go index b199531..ab77f2c 100644 --- a/facade_compatibility_test.go +++ b/facade_compatibility_test.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "reflect" "testing" configkit "gitea.maximumdirect.net/eric/ConfigKit" @@ -27,23 +26,6 @@ type facadeCompatibilityConfig struct { func TestFacadeCompatibility(t *testing.T) { t.Parallel() - const packagePath = "gitea.maximumdirect.net/eric/ConfigKit" - for _, candidate := range []struct { - name string - type_ reflect.Type - }{ - {"Path", reflect.TypeFor[configkit.Path]()}, - {"Source", reflect.TypeFor[configkit.Source]()}, - {"Secret", reflect.TypeFor[configkit.Secret]()}, - {"Definition", reflect.TypeFor[configkit.Definition[facadeCompatibilityConfig]]()}, - {"Options", reflect.TypeFor[configkit.Options[facadeCompatibilityConfig]]()}, - {"Result", reflect.TypeFor[configkit.Result[facadeCompatibilityConfig]]()}, - } { - if candidate.type_.PkgPath() != packagePath { - t.Errorf("%s belongs to %q, want %q", candidate.name, candidate.type_.PkgPath(), packagePath) - } - } - constructed := configkit.Options[facadeCompatibilityConfig]{ Definition: configkit.Definition[facadeCompatibilityConfig]{}, Filesystem: &configkit.FilesystemContext{BaseDir: "configs", Root: "configs"}, @@ -56,11 +38,6 @@ func TestFacadeCompatibility(t *testing.T) { if got := constructed.Overrides[0].Path.String(); got != "/value" { t.Fatalf("constructed override path = %q, want /value", got) } - publicResult := configkit.Result[facadeCompatibilityConfig]{Config: facadeCompatibilityConfig{Value: "constructed"}} - if publicResult.Config.Value != "constructed" { - t.Fatal("constructed result lost its public configuration") - } - result, err := configkit.Load(configkit.Bytes("facade.yml", []byte("value: input\n")), configkit.Options[facadeCompatibilityConfig]{ Definition: configkit.Definition[facadeCompatibilityConfig]{ Resolve: func(configuration *facadeCompatibilityConfig, resolution *configkit.Resolution) error { diff --git a/facade_equivalence_test.go b/facade_equivalence_test.go index 7c83578..1aea88e 100644 --- a/facade_equivalence_test.go +++ b/facade_equivalence_test.go @@ -23,6 +23,9 @@ type equivalentConfig struct { func TestEquivalentSourceFormsPreserveInputSemantics(t *testing.T) { const yaml = "version: 1\nname: retained\nenabled: false\ncount: 0\nitems: []\noptional: null\n" + bytesData := []byte(yaml) + bytesSource := configkit.Bytes("memory", bytesData) + bytesData[0] = 'X' hostPath := filepath.Join(t.TempDir(), "main.yml") if err := os.WriteFile(hostPath, []byte(yaml), 0o600); err != nil { t.Fatal(err) @@ -37,7 +40,7 @@ func TestEquivalentSourceFormsPreserveInputSemantics(t *testing.T) { }{ {"host", configkit.File(hostPath), nil, configkit.SourceKindFile}, {"native", namespace.File("main.yml"), nil, configkit.SourceKindFilesystem}, - {"bytes", configkit.Bytes("memory", []byte(yaml)), context, configkit.SourceKindBytes}, + {"bytes", bytesSource, context, configkit.SourceKindBytes}, {"reader", configkit.Reader("stream", strings.NewReader(yaml)), context, configkit.SourceKindReader}, {"mapping", configkit.Mapping("programmatic", map[string]any{ "version": uint64(1), "name": "retained", "enabled": false, "count": 0, @@ -112,6 +115,15 @@ func TestRepeatedImportsAcrossTiersAndReaderConsumption(t *testing.T) { if got := layeredErrorCode(t, err); got != configkit.CodeSourceReaderUsed { t.Fatalf("reader re-consumption = %s", got) } + copiedReader := configkit.Reader("copied-single-use", strings.NewReader("name: retained\n")) + readerCopy := copiedReader + if _, err := configkit.Load[config](readerCopy, configkit.Options[config]{}); err != nil { + t.Fatal(err) + } + _, err = configkit.Load[config](copiedReader, configkit.Options[config]{}) + if got := layeredErrorCode(t, err); got != configkit.CodeSourceReaderUsed { + t.Fatalf("copied reader re-consumption = %s", got) + } } func TestReplacedSecretRemainsAbsentFromSafeOutput(t *testing.T) { diff --git a/facade_internal_test.go b/facade_internal_test.go deleted file mode 100644 index aaf3a6f..0000000 --- a/facade_internal_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package configkit - -import ( - "errors" - "strings" - "testing" - - "gitea.maximumdirect.net/eric/ConfigKit/internal/engine" -) - -func TestWrappedSourcesPreserveEngineOwnership(t *testing.T) { - t.Parallel() - - type config struct { - Value string `yaml:"value"` - } - data := []byte("value: retained\n") - source := Bytes("snapshot.yml", data) - data[7] = 'X' - result, err := engine.Load[config](source.toEngine(), engine.SecretBinding{}) - if err != nil || result.Config.Value != "retained" { - t.Fatalf("snapshotted source = %#v, %v", result, err) - } - - readerSource := Reader("reader.yml", strings.NewReader("value: once\n")) - first := readerSource - if _, err := engine.Load[config](first.toEngine(), engine.SecretBinding{}); err != nil { - t.Fatal(err) - } - _, err = engine.Load[config](readerSource.toEngine(), engine.SecretBinding{}) - var loadError *engine.LoadError - if !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != engine.CodeSourceReaderUsed { - t.Fatalf("copied reader source error = %#v", err) - } -} diff --git a/records.go b/records.go index faaa056..eadbca0 100644 --- a/records.go +++ b/records.go @@ -313,118 +313,3 @@ type Change struct { Reasons []string Dependencies []Path } - -func cloneLocation(location *Location) *Location { - if location == nil { - return nil - } - cloned := *location - return &cloned -} - -func cloneLocations(locations []Location) []Location { - if locations == nil { - return nil - } - return append([]Location(nil), locations...) -} - -func cloneSourceInfo(source SourceInfo) SourceInfo { - source.Destination = source.Destination.clone() - source.ImportedFrom = cloneLocation(source.ImportedFrom) - source.VersionCheck = cloneSourceVersionCheck(source.VersionCheck) - source.ImportSelections = cloneImportSelections(source.ImportSelections) - return source -} - -func cloneImportSelections(selections []ImportSelection) []ImportSelection { - if selections == nil { - return nil - } - cloned := make([]ImportSelection, len(selections)) - for index, selection := range selections { - selection.Path = selection.Path.clone() - cloned[index] = selection - } - return cloned -} - -func cloneSourceVersionCheck(value *SourceVersionCheck) *SourceVersionCheck { - if value == nil { - return nil - } - cloned := *value - cloned.Path = value.Path.clone() - cloned.Location = cloneLocation(value.Location) - return &cloned -} - -func cloneSourceInfos(sources []SourceInfo) []SourceInfo { - if sources == nil { - return nil - } - cloned := make([]SourceInfo, len(sources)) - for i, source := range sources { - cloned[i] = cloneSourceInfo(source) - } - return cloned -} - -func cloneOrigin(origin Origin) Origin { - origin.Locations = cloneLocations(origin.Locations) - if origin.Inheritance != nil { - steps := make([]InheritanceStep, len(origin.Inheritance)) - for index, step := range origin.Inheritance { - steps[index] = InheritanceStep{ - From: step.From.clone(), To: step.To.clone(), Reference: step.Reference.clone(), - Locations: cloneLocations(step.Locations), - } - } - origin.Inheritance = steps - } - return origin -} - -func cloneFieldInfo(info FieldInfo) FieldInfo { - info.Origin = cloneOrigin(info.Origin) - return info -} - -func cloneChange(change Change) Change { - change.Path = change.Path.clone() - change.Origin = cloneOrigin(change.Origin) - if change.Previous != nil { - previous := make([]Origin, len(change.Previous)) - for i, origin := range change.Previous { - previous[i] = cloneOrigin(origin) - } - change.Previous = previous - } - change.Reasons = append([]string(nil), change.Reasons...) - if change.Dependencies != nil { - dependencies := make([]Path, len(change.Dependencies)) - for i, path := range change.Dependencies { - dependencies[i] = path.clone() - } - change.Dependencies = dependencies - } - return change -} - -func cloneDiagnostic(diagnostic Diagnostic) Diagnostic { - diagnostic.Path = diagnostic.Path.clone() - diagnostic.Primary = cloneLocation(diagnostic.Primary) - diagnostic.Related = cloneLocations(diagnostic.Related) - return diagnostic -} - -func cloneDiagnostics(diagnostics []Diagnostic) []Diagnostic { - if diagnostics == nil { - return nil - } - cloned := make([]Diagnostic, len(diagnostics)) - for i, diagnostic := range diagnostics { - cloned[i] = cloneDiagnostic(diagnostic) - } - return cloned -} diff --git a/records_test.go b/records_test.go index 9fc676e..abb5c6b 100644 --- a/records_test.go +++ b/records_test.go @@ -27,7 +27,7 @@ func TestOverlayPublicValues(t *testing.T) { } } -func TestSourceInfoDestinationConversionsAndCopies(t *testing.T) { +func TestSourceInfoDestinationConversion(t *testing.T) { destination := Keys("collections", "a/b", "0") path := writeRecordConfig(t, "value: main\n") options := engine.OptionsWithPlacement(engine.Options[map[string]any]{}, engine.Keys("collections", "a/b", "0"), uint8(PlacementModeSequenceItem)) @@ -36,14 +36,9 @@ func TestSourceInfoDestinationConversionsAndCopies(t *testing.T) { if converted.Destination.String() != destination.String() || converted.PlacementMode != PlacementModeSequenceItem || converted.DestinationUnresolved { t.Fatalf("public source evidence = %#v", converted) } - copy := cloneSourceInfo(converted) - copy.Destination = Path{} - if converted.Destination.String() != destination.String() { - t.Fatalf("source record destination changed through clone: %q", converted.Destination.String()) - } } -func TestSourceVersionCheckConversionsAndCopies(t *testing.T) { +func TestSourceVersionCheckConversion(t *testing.T) { path := Keys("version") file := writeRecordConfig(t, "version: '123'\nvalue: main\n") options := engine.Options[recordConfig]{ @@ -67,18 +62,9 @@ func TestSourceVersionCheckConversionsAndCopies(t *testing.T) { converted.VersionCheck.Location == nil || converted.VersionCheck.Location.SourceID != internal.ID || converted.VersionCheck.Location.Line != 1 { t.Fatalf("public source version check = %#v", converted.VersionCheck) } - copy := cloneSourceInfo(converted) - copy.VersionCheck.Path = Keys("changed") - copy.VersionCheck.Version.Text = "changed" - copy.VersionCheck.Location.Line = 99 - copy.VersionCheck.Redacted = true - if converted.VersionCheck.Path.String() != path.String() || converted.VersionCheck.Version.Text != "123" || - converted.VersionCheck.Location.Line != 1 || converted.VersionCheck.Redacted { - t.Fatalf("source version check changed through a detached copy: %#v", converted.VersionCheck) - } } -func TestImportSelectionConversionsAndCopies(t *testing.T) { +func TestImportSelectionConversion(t *testing.T) { file := writeRecordConfig(t, "configkit:\n imports:\n - glob: absent/*.yml\nvalue: main\n") internal := engineSourceInfoFromLoad(t, file, "source-1", engine.Options[recordConfig]{Imports: true}) converted := fromEngineSourceInfo(internal) @@ -87,12 +73,6 @@ func TestImportSelectionConversionsAndCopies(t *testing.T) { converted.ImportSelections[0].Selector != ImportSelectorGlob || converted.ImportSelections[0].Outcome != ImportSelectionNone { t.Fatalf("converted selection evidence = %#v", converted.ImportSelections) } - copy := cloneSourceInfo(converted) - copy.ImportSelections[0].Path = Keys("copy") - copy.ImportSelections[0].Selector = ImportSelectorPath - if converted.ImportSelections[0].Path.String() != "/configkit/imports/0" || converted.ImportSelections[0].Selector != ImportSelectorGlob { - t.Fatalf("source selection evidence changed through copy: %#v", converted.ImportSelections) - } if converted.ImportSelections[0].Outcome != ImportSelectionNone { t.Fatalf("source selection outcome = %q", converted.ImportSelections[0].Outcome) } @@ -102,10 +82,6 @@ func TestImportSelectionConversionsAndCopies(t *testing.T) { if fromEngineImportSelections([]engine.ImportSelectionForFacade{}) == nil { t.Fatal("converting an empty engine selection slice returned nil") } - empty := cloneSourceInfo(SourceInfo{ImportSelections: make([]ImportSelection, 0)}) - if empty.ImportSelections == nil { - t.Fatal("cloning collapsed a non-nil empty selection slice") - } } func TestOverrideConversionsPreserveSliceShapeAndCopyRecords(t *testing.T) {