Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,50 @@ func TestWriteModeEnableAuto_RoundTrip(t *testing.T) {

func boolPtr(b bool) *bool { return &b }

// TestApplyOverrides_PointerDoesNotAliasCaller pins that overriding a pointer
// field replaces the pointer rather than writing through the existing pointee.
// ResolveIncrementalIntent shallow-copies the caller's config, so the copy and
// the original share every pointer, and writing through one would silently
// change the other.
func TestApplyOverrides_PointerDoesNotAliasCaller(t *testing.T) {
current := Default()
current.Storage.StateCommit.WriteModeEnableAuto = boolPtr(true)

res, err := ResolveIncrementalIntent(ConfigIntent{
Overrides: map[string]string{
"storage.state_commit.write_mode_enable_auto": "false",
},
}, current)
if err != nil {
t.Fatalf("ResolveIncrementalIntent: %v", err)
}
if !res.Valid {
t.Fatalf("result not valid: %+v", res.Diagnostics)
}

got := current.Storage.StateCommit.WriteModeEnableAuto
if got == nil || !*got {
t.Errorf("the caller's config was mutated: got %v, want unchanged pointer to true", got)
}
}

// TestApplyOverrides_PointerRejectedValueLeavesFieldUnset pins that a rejected
// value does not land as a pointer to the zero value. For
// write_mode_enable_auto that zero is false, which renders a pin, and ResolveEnv
// only warns on a bad value, so the field must be left as it was.
func TestApplyOverrides_PointerRejectedValueLeavesFieldUnset(t *testing.T) {
cfg := Default()
err := ApplyOverrides(cfg, map[string]string{
"storage.state_commit.write_mode_enable_auto": "not-a-bool",
})
if err == nil {
t.Fatal("expected an error for an invalid bool")
}
if got := cfg.Storage.StateCommit.WriteModeEnableAuto; got != nil {
t.Errorf("rejected value left the field set to %v; a pin must never come from a parse failure", *got)
}
}

func TestApplyOverrides_Uint(t *testing.T) {
cfg := Default()
if err := ApplyOverrides(cfg, map[string]string{
Expand Down
20 changes: 14 additions & 6 deletions resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,22 @@ func setFieldByPath(cfg *SeiConfig, path string, value string) error {

func setReflectValue(v reflect.Value, s string) error {
// A pointer field is the tri-state form: nil renders as an absent key, so
// the binary's own default applies. Allocate before setting, otherwise an
// override on such a field reaches the type switch as a pointer and is
// rejected as unsupported.
// the binary's own default applies. Such a field reaches the type switch as
// a pointer and would be rejected as unsupported, so resolve it here.
//
// The value is parsed into a fresh pointee and the pointer is replaced only
// once parsing succeeds. Writing through an existing pointee instead would
// reach whatever else aliases it — ResolveIncrementalIntent shallow-copies
// the caller's config, so the copy and the original share every pointer —
// and would leave a rejected value behind as a pointer to the zero value,
// which for storage.state_commit.write_mode_enable_auto is false, a pin.
if v.Kind() == reflect.Ptr {
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
elem := reflect.New(v.Type().Elem())
if err := setReflectValue(elem.Elem(), s); err != nil {
return err
}
return setReflectValue(v.Elem(), s)
v.Set(elem)
return nil
}

if v.Type() == reflect.TypeFor[Duration]() {
Expand Down
Loading