feat: add field comments, local time zone decoding and in-value cancellation
Test / test (push) Successful in 1m32s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-22 00:44:23 +02:00
parent 71bd82a7a5
commit d18935ebc2
8 changed files with 307 additions and 11 deletions
+87
View File
@@ -11,6 +11,7 @@ import (
"net"
"slices"
"strings"
"sync/atomic"
"testing"
"time"
)
@@ -1487,3 +1488,89 @@ func TestPathString(t *testing.T) {
t.Errorf("String() of an empty path = %q, want the empty string", got)
}
}
func TestLocalTimeLocation(t *testing.T) {
zone := time.FixedZone("CET", 3600)
t.Run("without the option a local kind fills only its wrapper", func(t *testing.T) {
var cfg struct {
When time.Time `toml:"when"`
}
err := Unmarshal([]byte("when = 1979-05-27T07:32:00\n"), &cfg)
if err == nil || !strings.Contains(err.Error(), "LocalTimeLocation") {
t.Errorf("err = %v, want the option hint", err)
}
})
t.Run("with the option the value lands in the zone", func(t *testing.T) {
var cfg struct {
When time.Time `toml:"when"`
Date LocalDate `toml:"date"`
Wall LocalDateTime `toml:"wall"`
}
dec := NewDecoder().LocalTimeLocation(zone)
in := []byte("when = 1979-05-27T07:32:00\ndate = 1979-05-27\nwall = 1979-05-27T07:32:00\n")
if err := dec.Decode(in, &cfg); err != nil {
t.Fatal(err)
}
if got := cfg.When.Format("15:04:05 MST"); got != "07:32:00 CET" {
t.Errorf("when = %s, want 07:32:00 CET", got)
}
if cfg.Date != (LocalDate{time.Date(1979, 5, 27, 0, 0, 0, 0, time.UTC)}) {
t.Errorf("date = %v", cfg.Date)
}
})
t.Run("the wrapper still takes the value with the option on", func(t *testing.T) {
var cfg struct {
Wall LocalDateTime `toml:"wall"`
}
dec := NewDecoder().LocalTimeLocation(zone)
if err := dec.Decode([]byte("wall = 1979-05-27T07:32:00\n"), &cfg); err != nil {
t.Fatal(err)
}
if cfg.Wall.Hour() != 7 {
t.Errorf("wall = %v", cfg.Wall)
}
})
}
// errAfterN is a context that reports cancelled once its Err has been read
// more than n times, which drives the in-value cancellation checks: the
// parser reads Err a fixed number of times per statement, so a huge array
// fails only where the checks inside the value run.
type errAfterN struct {
context.Context
n int
how atomic.Int32
}
func (c *errAfterN) Err() error {
if c.how.Add(1) > int32(c.n) {
return context.Canceled
}
return nil
}
func TestCancelInsideValue(t *testing.T) {
// Two top-level checks happen before the value (the entry check and the
// statement loop's first); the array checks follow inside the value, so
// the third read is the first that can fail today. The document only
// parses to the end when the checks inside the value are missing, which
// is the defect this test pins.
var b strings.Builder
b.WriteString("a = [")
for i := range 4000 {
if i > 0 {
b.WriteByte(',')
}
b.WriteString("1")
}
b.WriteString("]\n")
ctx := &errAfterN{Context: context.Background(), n: 2}
var tree map[string]any
err := NewDecoder().DecodeContext(ctx, []byte(b.String()), &tree)
if err == nil {
t.Fatal("a cancelled context did not stop the parse inside the value")
}
if !errors.Is(err, context.Canceled) {
t.Errorf("err = %v, want context.Canceled", err)
}
}