From 0613d06644b3a4c1c67db112b5b93cc359152521 Mon Sep 17 00:00:00 2001 From: Qalipso Date: Tue, 28 Jul 2026 12:56:03 -0300 Subject: [PATCH] test(memstore): add tests for Store operations and Entry conversions core/memstore was at 11.0% statement coverage. This covers the Store CRUD surface (Set/Get/GetEntry/GetEntryAt/Remove/Reset/Len/Visit/ GetOrSet), immutability semantics, the Entry type conversions and their error paths, ErrEntryNotFound matching, and the sized integer and float getters including out-of-range rejection. Coverage goes from 11.0% to 59.2%. No non-test code is changed. --- core/memstore/entry_numeric_test.go | 220 ++++++++++++++++++++++ core/memstore/entry_test.go | 254 ++++++++++++++++++++++++++ core/memstore/store_test.go | 272 ++++++++++++++++++++++++++++ 3 files changed, 746 insertions(+) create mode 100644 core/memstore/entry_numeric_test.go create mode 100644 core/memstore/entry_test.go create mode 100644 core/memstore/store_test.go diff --git a/core/memstore/entry_numeric_test.go b/core/memstore/entry_numeric_test.go new file mode 100644 index 000000000..4a7a0733b --- /dev/null +++ b/core/memstore/entry_numeric_test.go @@ -0,0 +1,220 @@ +// white-box testing +package memstore + +import ( + "testing" +) + +func entry(value any) Entry { + return Entry{Key: "key", ValueRaw: value} +} + +func TestEntrySignedDefaults(t *testing.T) { + // in-range values round-trip through every signed getter, from both + // native integer types and their decimal string form + t.Run("int8", func(t *testing.T) { + for _, v := range []any{int8(8), int(8), int16(8), int32(8), int64(8), uint(8), "8"} { + got, err := entry(v).Int8Default(-1) + if err != nil { + t.Fatalf("%T(%v): unexpected error %v", v, v, err) + } + if got != 8 { + t.Fatalf("%T(%v): expected 8 but got %d", v, v, got) + } + } + }) + + t.Run("int16", func(t *testing.T) { + for _, v := range []any{int16(300), int(300), int32(300), int64(300), "300"} { + got, err := entry(v).Int16Default(-1) + if err != nil { + t.Fatalf("%T(%v): unexpected error %v", v, v, err) + } + if got != 300 { + t.Fatalf("%T(%v): expected 300 but got %d", v, v, got) + } + } + }) + + t.Run("int32", func(t *testing.T) { + got, err := entry(int32(70000)).Int32Default(-1) + if err != nil || got != 70000 { + t.Fatalf("expected 70000 but got %d (err=%v)", got, err) + } + }) + + t.Run("int64", func(t *testing.T) { + got, err := entry(int64(5_000_000_000)).Int64Default(-1) + if err != nil || got != 5_000_000_000 { + t.Fatalf("expected 5000000000 but got %d (err=%v)", got, err) + } + }) +} + +func TestEntrySignedDefaultsErrors(t *testing.T) { + // nil, unparsable strings, strings that do not fit the target width, + // and unsupported types all report an error and yield the default + t.Run("int8", func(t *testing.T) { + for _, v := range []any{nil, "abc", "200", true} { + got, err := entry(v).Int8Default(-1) + if err == nil { + t.Fatalf("%#v: expected an error", v) + } + if got != -1 { + t.Fatalf("%#v: expected the default but got %d", v, got) + } + } + }) + + t.Run("int16", func(t *testing.T) { + for _, v := range []any{nil, "abc", "40000", true} { + if _, err := entry(v).Int16Default(-1); err == nil { + t.Fatalf("%#v: expected an error", v) + } + } + }) +} + +func TestEntryUnsignedDefaults(t *testing.T) { + t.Run("uint", func(t *testing.T) { + for _, v := range []any{uint(8), uint8(8), uint16(8), uint32(8), uint64(8), "8"} { + got, err := entry(v).UintDefault(0) + if err != nil { + t.Fatalf("%T(%v): unexpected error %v", v, v, err) + } + if got != 8 { + t.Fatalf("%T(%v): expected 8 but got %d", v, v, got) + } + } + }) + + t.Run("uint8", func(t *testing.T) { + got, err := entry(uint8(255)).Uint8Default(0) + if err != nil || got != 255 { + t.Fatalf("expected 255 but got %d (err=%v)", got, err) + } + got, err = entry("255").Uint8Default(0) + if err != nil || got != 255 { + t.Fatalf("expected 255 from a string but got %d (err=%v)", got, err) + } + }) + + t.Run("uint64", func(t *testing.T) { + got, err := entry(uint64(5_000_000_000)).Uint64Default(0) + if err != nil || got != 5_000_000_000 { + t.Fatalf("expected 5000000000 but got %d (err=%v)", got, err) + } + }) +} + +// Unlike the signed getters, the unsigned ones reject values that do not fit +// the target width instead of truncating them. +func TestEntryUnsignedDefaultsRejectOutOfRange(t *testing.T) { + tests := []struct { + name string + value any + }{ + {"uint16 above uint8 max", uint16(300)}, + {"uint32 above uint8 max", uint32(300)}, + {"uint64 above uint8 max", uint64(300)}, + {"uint above uint8 max", uint(300)}, + {"int above uint8 max", int(300)}, + {"negative int", int(-1)}, + {"string above uint8 max", "256"}, + {"negative string", "-1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := entry(tt.value).Uint8Default(7) + if err == nil { + t.Fatalf("expected an out of range value to report an error") + } + if got != 7 { + t.Fatalf("expected the default 7 but got %d", got) + } + }) + } +} + +func TestEntryFloat64Default(t *testing.T) { + got, err := entry(3.5).Float64Default(-1) + if err != nil || got != 3.5 { + t.Fatalf("expected 3.5 but got %v (err=%v)", got, err) + } + + got, err = entry("3.5").Float64Default(-1) + if err != nil || got != 3.5 { + t.Fatalf("expected 3.5 from a string but got %v (err=%v)", got, err) + } + + got, err = entry(int(3)).Float64Default(-1) + if err != nil || got != 3 { + t.Fatalf("expected 3 from an int but got %v (err=%v)", got, err) + } + + if _, err = entry(nil).Float64Default(-1); err == nil { + t.Fatalf("expected an error for a nil value") + } + if _, err = entry("abc").Float64Default(-1); err == nil { + t.Fatalf("expected an error for an unparsable string") + } +} + +// the Store level getters delegate to the Entry ones and fall back to the +// default when the key is absent +func TestStoreSizedGetters(t *testing.T) { + var p Store + p.Set("i8", int8(8)) + p.Set("i16", int16(16)) + p.Set("i32", int32(32)) + p.Set("i64", int64(64)) + p.Set("u", uint(1)) + p.Set("u8", uint8(8)) + p.Set("u16", uint16(16)) + p.Set("u32", uint32(32)) + p.Set("u64", uint64(64)) + p.Set("f", 1.5) + + if v := p.GetInt8Default("i8", -1); v != 8 { + t.Fatalf("expected 8 but got %d", v) + } + if v := p.GetInt16Default("i16", -1); v != 16 { + t.Fatalf("expected 16 but got %d", v) + } + if v := p.GetInt32Default("i32", -1); v != 32 { + t.Fatalf("expected 32 but got %d", v) + } + if v := p.GetInt64Default("i64", -1); v != 64 { + t.Fatalf("expected 64 but got %d", v) + } + if v := p.GetUintDefault("u", 0); v != 1 { + t.Fatalf("expected 1 but got %d", v) + } + if v := p.GetUint8Default("u8", 0); v != 8 { + t.Fatalf("expected 8 but got %d", v) + } + if v := p.GetUint16Default("u16", 0); v != 16 { + t.Fatalf("expected 16 but got %d", v) + } + if v := p.GetUint32Default("u32", 0); v != 32 { + t.Fatalf("expected 32 but got %d", v) + } + if v := p.GetUint64Default("u64", 0); v != 64 { + t.Fatalf("expected 64 but got %d", v) + } + if v := p.GetFloat64Default("f", -1); v != 1.5 { + t.Fatalf("expected 1.5 but got %v", v) + } + + // every getter falls back to its default for a missing key + if v := p.GetInt8Default("missing", -1); v != -1 { + t.Fatalf("expected the default but got %d", v) + } + if v := p.GetUint64Default("missing", 9); v != 9 { + t.Fatalf("expected the default but got %d", v) + } + if v := p.GetFloat64Default("missing", -1); v != -1 { + t.Fatalf("expected the default but got %v", v) + } +} diff --git a/core/memstore/entry_test.go b/core/memstore/entry_test.go new file mode 100644 index 000000000..426a69c1a --- /dev/null +++ b/core/memstore/entry_test.go @@ -0,0 +1,254 @@ +// white-box testing +package memstore + +import ( + "errors" + "reflect" + "testing" + "time" +) + +func TestEntryStringDefault(t *testing.T) { + tests := []struct { + name string + value any + def string + expected string + }{ + {"string", "value", "def", "value"}, + {"empty string is returned as is", "", "def", ""}, + {"nil falls back to the default", nil, "def", "def"}, + {"int is formatted", 42, "def", "42"}, + {"bool is formatted", true, "def", "true"}, + {"float is formatted", 3.5, "def", "3.5"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := Entry{Key: "key", ValueRaw: tt.value} + if got := e.StringDefault(tt.def); got != tt.expected { + t.Fatalf("expected %q but got %q", tt.expected, got) + } + }) + } +} + +func TestEntryString(t *testing.T) { + if got := (Entry{ValueRaw: "value"}).String(); got != "value" { + t.Fatalf("expected %q but got %q", "value", got) + } + // String defaults to the empty string + if got := (Entry{ValueRaw: nil}).String(); got != "" { + t.Fatalf("expected an empty string but got %q", got) + } +} + +func TestEntryStringTrim(t *testing.T) { + if got := (Entry{ValueRaw: " value "}).StringTrim(); got != "value" { + t.Fatalf("expected %q but got %q", "value", got) + } + if got := (Entry{ValueRaw: "\tvalue\n"}).StringTrim(); got != "value" { + t.Fatalf("expected %q but got %q", "value", got) + } + if got := (Entry{ValueRaw: nil}).StringTrim(); got != "" { + t.Fatalf("expected an empty string but got %q", got) + } +} + +func TestEntryIntDefault(t *testing.T) { + tests := []struct { + name string + value any + expected int + wantErr bool + }{ + {"int", 42, 42, false}, + {"int8", int8(8), 8, false}, + {"int16", int16(16), 16, false}, + {"int32", int32(32), 32, false}, + {"int64", int64(64), 64, false}, + {"uint", uint(1), 1, false}, + {"uint8", uint8(8), 8, false}, + {"uint16", uint16(16), 16, false}, + {"uint32", uint32(32), 32, false}, + {"uint64", uint64(64), 64, false}, + {"numeric string", "7", 7, false}, + {"negative numeric string", "-7", -7, false}, + {"unparsable string", "abc", -1, true}, + {"nil", nil, -1, true}, + {"unsupported type", true, -1, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := Entry{Key: "key", ValueRaw: tt.value} + got, err := e.IntDefault(-1) + if tt.wantErr && err == nil { + t.Fatalf("expected an error but got none") + } + if !tt.wantErr && err != nil { + t.Fatalf("expected no error but got %v", err) + } + if got != tt.expected { + t.Fatalf("expected %d but got %d", tt.expected, got) + } + }) + } +} + +func TestEntryBoolDefault(t *testing.T) { + tests := []struct { + name string + value any + expected bool + wantErr bool + }{ + {"bool true", true, true, false}, + {"bool false", false, false, false}, + {"string true", "true", true, false}, + {"string 1", "1", true, false}, + {"string false", "false", false, false}, + {"string 0", "0", false, false}, + {"int 1 is true", 1, true, false}, + {"int 0 is false", 0, false, false}, + {"any other int is false", 5, false, false}, + {"unparsable string", "maybe", false, true}, + {"nil", nil, false, true}, + {"unsupported type", 1.5, false, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := Entry{Key: "key", ValueRaw: tt.value} + got, err := e.BoolDefault(false) + if tt.wantErr && err == nil { + t.Fatalf("expected an error but got none") + } + if !tt.wantErr && err != nil { + t.Fatalf("expected no error but got %v", err) + } + if got != tt.expected { + t.Fatalf("expected %v but got %v", tt.expected, got) + } + }) + } +} + +func TestEntryTimeDefault(t *testing.T) { + now := time.Now() + def := time.Time{} + + got, err := (Entry{Key: "key", ValueRaw: now}).TimeDefault(def) + if err != nil { + t.Fatalf("expected no error but got %v", err) + } + if !got.Equal(now) { + t.Fatalf("expected the stored time but got %v", got) + } + + // a nil value reports not found + if _, err = (Entry{Key: "key", ValueRaw: nil}).TimeDefault(def); err == nil { + t.Fatalf("expected a not found error for a nil value") + } + + // a value of the wrong type falls back to the default without an error + got, err = (Entry{Key: "key", ValueRaw: "not a time"}).TimeDefault(def) + if err != nil { + t.Fatalf("expected no error for a mistyped value but got %v", err) + } + if !got.Equal(def) { + t.Fatalf("expected the default time but got %v", got) + } +} + +func TestEntryWeekdayDefault(t *testing.T) { + got, err := (Entry{Key: "key", ValueRaw: time.Friday}).WeekdayDefault(time.Monday) + if err != nil { + t.Fatalf("expected no error but got %v", err) + } + if got != time.Friday { + t.Fatalf("expected %v but got %v", time.Friday, got) + } + + if _, err = (Entry{Key: "key", ValueRaw: nil}).WeekdayDefault(time.Monday); err == nil { + t.Fatalf("expected a not found error for a nil value") + } + + // a value of the wrong type falls back to the default without an error + got, err = (Entry{Key: "key", ValueRaw: "not a weekday"}).WeekdayDefault(time.Monday) + if err != nil { + t.Fatalf("expected no error for a mistyped value but got %v", err) + } + if got != time.Monday { + t.Fatalf("expected the default weekday but got %v", got) + } +} + +func TestErrEntryNotFoundError(t *testing.T) { + err := &ErrEntryNotFound{Key: "key", Kind: reflect.Int, Type: intType} + + expected := "not found: key as int (int)" + if err.Error() != expected { + t.Fatalf("expected %q but got %q", expected, err.Error()) + } +} + +func TestErrEntryNotFoundAs(t *testing.T) { + err := &ErrEntryNotFound{Key: "key", Kind: reflect.Int, Type: intType} + + // an empty target matches any key and kind + if !err.As(&ErrEntryNotFound{}) { + t.Fatalf("expected an empty target to match") + } + if !err.As(&ErrEntryNotFound{Key: "key"}) { + t.Fatalf("expected a matching key to match") + } + if !err.As(&ErrEntryNotFound{Key: "key", Kind: reflect.Int}) { + t.Fatalf("expected a matching key and kind to match") + } + + if err.As(&ErrEntryNotFound{Key: "other"}) { + t.Fatalf("expected a different key not to match") + } + if err.As(&ErrEntryNotFound{Key: "key", Kind: reflect.String}) { + t.Fatalf("expected a different kind not to match") + } + // a target of another type never matches + if err.As(&struct{}{}) { + t.Fatalf("expected an unrelated target type not to match") + } +} + +func TestErrEntryNotFoundErrorsAs(t *testing.T) { + _, err := (Entry{Key: "key", ValueRaw: nil}).IntDefault(-1) + if err == nil { + t.Fatalf("expected a not found error") + } + + var target *ErrEntryNotFound + if !errors.As(err, &target) { + t.Fatalf("expected the error to unwrap to *ErrEntryNotFound") + } + if target.Key != "key" { + t.Fatalf("expected the error to carry the key %q but got %q", "key", target.Key) + } + if target.Kind != reflect.Int { + t.Fatalf("expected the error to carry the int kind but got %v", target.Kind) + } +} + +func TestEntryGetByKindOrNil(t *testing.T) { + e := Entry{Key: "key", ValueRaw: "value"} + if got := e.GetByKindOrNil(reflect.String); got != "value" { + t.Fatalf("expected %q but got %v", "value", got) + } + + // a string entry cannot be read as an int + if got := (Entry{Key: "key", ValueRaw: "abc"}).GetByKindOrNil(reflect.Int); got != nil { + t.Fatalf("expected nil but got %v", got) + } + + if got := (Entry{Key: "key", ValueRaw: 42}).GetByKindOrNil(reflect.Int); got != 42 { + t.Fatalf("expected 42 but got %v", got) + } +} diff --git a/core/memstore/store_test.go b/core/memstore/store_test.go new file mode 100644 index 000000000..b5b5b23fa --- /dev/null +++ b/core/memstore/store_test.go @@ -0,0 +1,272 @@ +// white-box testing +package memstore + +import ( + "testing" +) + +func TestStoreSetAndGet(t *testing.T) { + var p Store + + entry, inserted := p.Set("key", "value") + if !inserted { + t.Fatalf("expected the first Set of a key to report an insert") + } + if entry.Key != "key" { + t.Fatalf("expected entry key to be %q but got %q", "key", entry.Key) + } + if got := p.Get("key"); got != "value" { + t.Fatalf("expected %q but got %v", "value", got) + } + + // setting an existing key updates it and reports that it was not inserted + _, inserted = p.Set("key", "other") + if inserted { + t.Fatalf("expected an update of an existing key to report no insert") + } + if got := p.Get("key"); got != "other" { + t.Fatalf("expected %q but got %v", "other", got) + } + if p.Len() != 1 { + t.Fatalf("expected the store to still hold one entry but it holds %d", p.Len()) + } +} + +func TestStoreGetMissing(t *testing.T) { + var p Store + + if got := p.Get("missing"); got != nil { + t.Fatalf("expected nil for a missing key but got %v", got) + } + if got := p.GetDefault("missing", "def"); got != "def" { + t.Fatalf("expected the default value but got %v", got) + } + if _, ok := p.GetEntry("missing"); ok { + t.Fatalf("expected GetEntry to report a missing key") + } + if p.Exists("missing") { + t.Fatalf("expected Exists to report false for a missing key") + } +} + +// a key stored with a nil value is treated as absent by the value getters +func TestStoreNilValueFallsBackToDefault(t *testing.T) { + var p Store + p.Set("key", nil) + + if !p.Exists("key") { + t.Fatalf("expected the key to exist even though its value is nil") + } + if got := p.GetDefault("key", "def"); got != "def" { + t.Fatalf("expected the default value but got %v", got) + } + if got := p.Get("key"); got != nil { + t.Fatalf("expected nil but got %v", got) + } +} + +func TestStoreGetEntryAt(t *testing.T) { + var p Store + p.Set("first", 1) + p.Set("second", 2) + + entry, ok := p.GetEntryAt(0) + if !ok || entry.Key != "first" { + t.Fatalf("expected the entry at index 0 to be %q but got %q (ok=%v)", "first", entry.Key, ok) + } + + entry, ok = p.GetEntryAt(1) + if !ok || entry.Key != "second" { + t.Fatalf("expected the entry at index 1 to be %q but got %q (ok=%v)", "second", entry.Key, ok) + } + + if _, ok = p.GetEntryAt(2); ok { + t.Fatalf("expected an out of range index to report not found") + } +} + +func TestStoreRemove(t *testing.T) { + var p Store + p.Set("a", 1) + p.Set("b", 2) + p.Set("c", 3) + + if !p.Remove("b") { + t.Fatalf("expected Remove to report that an entry was removed") + } + if p.Len() != 2 { + t.Fatalf("expected two remaining entries but got %d", p.Len()) + } + if p.Exists("b") { + t.Fatalf("expected the removed key to be gone") + } + // the surrounding entries survive the removal + if p.Get("a") != 1 || p.Get("c") != 3 { + t.Fatalf("expected the other entries to be untouched") + } + + if p.Remove("missing") { + t.Fatalf("expected Remove of a missing key to report false") + } +} + +func TestStoreReset(t *testing.T) { + var p Store + p.Set("a", 1) + p.Set("b", 2) + + p.Reset() + + if p.Len() != 0 { + t.Fatalf("expected an empty store after Reset but it holds %d entries", p.Len()) + } + if p.Exists("a") { + t.Fatalf("expected entries to be gone after Reset") + } +} + +func TestStoreVisit(t *testing.T) { + var p Store + p.Set("a", 1) + p.Set("b", 2) + + visited := make(map[string]any) + p.Visit(func(key string, value any) { + visited[key] = value + }) + + if len(visited) != 2 { + t.Fatalf("expected to visit two entries but visited %d", len(visited)) + } + if visited["a"] != 1 || visited["b"] != 2 { + t.Fatalf("expected the visitor to receive every key and value, got %v", visited) + } +} + +func TestStoreGetOrSet(t *testing.T) { + var p Store + + calls := 0 + setFunc := func() any { + calls++ + return "computed" + } + + if got := p.GetOrSet("key", setFunc); got != "computed" { + t.Fatalf("expected the computed value but got %v", got) + } + if calls != 1 { + t.Fatalf("expected the set function to be called once but it was called %d times", calls) + } + + // the value is now stored, so the function must not be called again + if got := p.GetOrSet("key", setFunc); got != "computed" { + t.Fatalf("expected the stored value but got %v", got) + } + if calls != 1 { + t.Fatalf("expected the set function not to be called again, call count is %d", calls) + } +} + +func TestStoreImmutable(t *testing.T) { + var p Store + + p.SetImmutable("key", "original") + if got := p.Get("key"); got != "original" { + t.Fatalf("expected %q but got %v", "original", got) + } + + // a plain Set must not overwrite an immutable entry + p.Set("key", "changed") + if got := p.Get("key"); got != "original" { + t.Fatalf("expected an immutable entry to reject Set, but it became %v", got) + } + + // SetImmutable may still update it + p.SetImmutable("key", "updated") + if got := p.Get("key"); got != "updated" { + t.Fatalf("expected SetImmutable to update an immutable entry, got %v", got) + } +} + +func TestStoreGetStringDefault(t *testing.T) { + var p Store + p.Set("str", "value") + p.Set("num", 42) + + if got := p.GetStringDefault("str", "def"); got != "value" { + t.Fatalf("expected %q but got %q", "value", got) + } + // non-string values are formatted + if got := p.GetStringDefault("num", "def"); got != "42" { + t.Fatalf("expected %q but got %q", "42", got) + } + if got := p.GetStringDefault("missing", "def"); got != "def" { + t.Fatalf("expected the default but got %q", got) + } +} + +func TestStoreGetIntDefault(t *testing.T) { + var p Store + p.Set("num", 42) + p.Set("str", "7") + p.Set("bad", "not a number") + + if got := p.GetIntDefault("num", -1); got != 42 { + t.Fatalf("expected 42 but got %d", got) + } + // numeric strings are parsed + if got := p.GetIntDefault("str", -1); got != 7 { + t.Fatalf("expected 7 but got %d", got) + } + if got := p.GetIntDefault("bad", -1); got != -1 { + t.Fatalf("expected the default for an unparsable value but got %d", got) + } + if got := p.GetIntDefault("missing", -1); got != -1 { + t.Fatalf("expected the default for a missing key but got %d", got) + } +} + +func TestStoreGetBoolDefault(t *testing.T) { + var p Store + p.Set("yes", true) + p.Set("str", "true") + p.Set("one", 1) + p.Set("zero", 0) + + if !p.GetBoolDefault("yes", false) { + t.Fatalf("expected true for a stored bool") + } + if !p.GetBoolDefault("str", false) { + t.Fatalf("expected true for the string %q", "true") + } + if !p.GetBoolDefault("one", false) { + t.Fatalf("expected int 1 to be true") + } + if p.GetBoolDefault("zero", true) { + t.Fatalf("expected int 0 to be false") + } + if !p.GetBoolDefault("missing", true) { + t.Fatalf("expected the default for a missing key") + } +} + +func TestStoreLen(t *testing.T) { + var p Store + + if p.Len() != 0 { + t.Fatalf("expected an empty store to have length 0") + } + + p.Set("a", 1) + p.Set("b", 2) + if p.Len() != 2 { + t.Fatalf("expected length 2 but got %d", p.Len()) + } + + // updating an existing key does not grow the store + p.Set("a", 3) + if p.Len() != 2 { + t.Fatalf("expected length to stay 2 after an update but got %d", p.Len()) + } +}