Ctrl K
ComunidadGuías…

internal/autogen/settingsmerge_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package autogen

import (
	"reflect"
	"testing"
)

// TestSettingsPatch_AllFieldsArePointers is the guard behind MergeSettingsPatch.
// A value-typed field would look "set" at its zero value, so the merge would
// happily overwrite a stored setting with 0/""/false on every save from a
// section that doesn't model it. Adding such a field must fail here, loudly,
// rather than in a user's config a week later.
func TestSettingsPatch_AllFieldsArePointers(t *testing.T) {
	rt := reflect.TypeOf(SettingsPatch{})
	for i := 0; i < rt.NumField(); i++ {
		f := rt.Field(i)
		if f.Type.Kind() != reflect.Ptr {
			t.Errorf("SettingsPatch.%s is %s, must be a pointer (see MergeSettingsPatch)", f.Name, f.Type)
		}
	}
}

// TestMergeSettingsPatch_CarriesForwardUntouchedFields is the regression the
// merge exists for: the dashboard saves in sections, so a patch carrying only
// the memory form's fields must leave the guard/advanced fields alone.
func TestMergeSettingsPatch_CarriesForwardUntouchedFields(t *testing.T) {
	f := func(v float64) *float64 { return &v }
	i := func(v int) *int { return &v }
	b := func(v bool) *bool { return &v }

	prev := SettingsPatch{
		TargetVramGB:     f(20),
		OomGuardEvict:    b(false),
		OomGuardGraceSec: i(90),
		ComputeBufFactor: f(1.3),
		DryDefault:       b(true),
		TtlSec:           i(300),
	}
	// What the memory form PUTs: its own four fields, nothing else.
	next := SettingsPatch{
		TargetVramGB:   f(22),
		VramOverheadGB: f(1.5),
		MaxRamGB:       f(32),
		AutoVram:       b(false),
	}

	got := MergeSettingsPatch(prev, next)

	if got.TargetVramGB == nil || *got.TargetVramGB != 22 {
		t.Errorf("TargetVramGB: the new value must win, got %v", got.TargetVramGB)
	}
	if got.OomGuardEvict == nil || *got.OomGuardEvict != false {
		t.Errorf("OomGuardEvict was wiped by a memory-form save: %v", got.OomGuardEvict)
	}
	if got.OomGuardGraceSec == nil || *got.OomGuardGraceSec != 90 {
		t.Errorf("OomGuardGraceSec was wiped by a memory-form save: %v", got.OomGuardGraceSec)
	}
	if got.ComputeBufFactor == nil || *got.ComputeBufFactor != 1.3 {
		t.Errorf("ComputeBufFactor was wiped by a memory-form save: %v", got.ComputeBufFactor)
	}
	// The two fields that used to need bespoke carry-forward lines.
	if got.DryDefault == nil || *got.DryDefault != true {
		t.Errorf("DryDefault was wiped: %v", got.DryDefault)
	}
	if got.TtlSec == nil || *got.TtlSec != 300 {
		t.Errorf("TtlSec was wiped: %v", got.TtlSec)
	}
}

// TestMergeSettingsPatch_ClearedFieldStaysCleared: nil in prev and nil in next
// must stay nil, i.e. the merge never invents a value. That is what makes a
// per-section "restore defaults" (which nils the section's fields) work.
func TestMergeSettingsPatch_ClearedFieldStaysCleared(t *testing.T) {
	got := MergeSettingsPatch(SettingsPatch{}, SettingsPatch{})
	rv := reflect.ValueOf(got)
	for i := 0; i < rv.NumField(); i++ {
		if !rv.Field(i).IsNil() {
			t.Errorf("field %s became non-nil from two empty patches", rv.Type().Field(i).Name)
		}
	}
}

// TestSettingsPatch_ApplyCoversEveryField keeps (*SettingsPatch).apply in step
// with the struct: every field must actually reach Settings. apply is written
// explicitly (Settings is flat values, not pointers), so a new patch field can
// be added and silently never applied — the setting would save to disk, survive
// a reload, and do nothing.
func TestSettingsPatch_ApplyCoversEveryField(t *testing.T) {
	// Fill every pointer field with a non-zero value, apply, and assert Settings
	// changed for each one in turn.
	rt := reflect.TypeOf(SettingsPatch{})
	for i := 0; i < rt.NumField(); i++ {
		f := rt.Field(i)
		p := SettingsPatch{}
		pv := reflect.ValueOf(&p).Elem().Field(i)
		elem := reflect.New(f.Type.Elem())
		switch elem.Elem().Kind() {
		case reflect.Float64:
			elem.Elem().SetFloat(3.5)
		case reflect.Int:
			elem.Elem().SetInt(4242)
		case reflect.Bool:
			elem.Elem().SetBool(true)
		case reflect.String:
			elem.Elem().SetString("q8_0")
		case reflect.Slice:
			elem.Elem().Set(reflect.ValueOf([]int{4096}))
		case reflect.Map:
			m := reflect.MakeMap(elem.Elem().Type())
			m.SetMapIndex(reflect.ValueOf("image"), reflect.ValueOf(`D:/loras`))
			elem.Elem().Set(m)
		default:
			t.Fatalf("field %s has unhandled kind %s — extend this test", f.Name, elem.Elem().Kind())
		}
		pv.Set(elem)

		var s Settings
		p.apply(&s)
		if reflect.DeepEqual(s, Settings{}) {
			t.Errorf("(*SettingsPatch).apply ignores field %s — it would save but never take effect", f.Name)
		}
	}
}