feat: add field comments, local time zone decoding and in-value cancellation
Test / test (push) Successful in 1m32s
Test / test (push) Successful in 1m32s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user