tpl/collections: Add an integration test for collections.D

Also
* improve the handling if invalid input
* add a sanity check for number of elements asked for in D (1000000).
This commit is contained in:
Bjørn Erik Pedersen
2025-08-27 15:21:29 +02:00
parent 2912415955
commit 84b5123912
3 changed files with 55 additions and 5 deletions

View File

@@ -409,6 +409,11 @@ func (ns *Namespace) Reverse(l any) (any, error) {
return sliceCopy.Interface(), nil
}
// Sanity check for slices created by Seq and D.
const maxSeqSize = 1000000
var errSeqSizeExceedsLimit = errors.New("size of result exceeds limit")
// Seq creates a sequence of integers from args. It's named and used as GNU's seq.
//
// Examples:
@@ -462,14 +467,14 @@ func (ns *Namespace) Seq(args ...any) ([]int, error) {
}
// sanity check
if last < -100000 {
return nil, errors.New("size of result exceeds limit")
if last < -maxSeqSize {
return nil, errSeqSizeExceedsLimit
}
size := ((last - first) / inc) + 1
// sanity check
if size <= 0 || size > 2000 {
return nil, errors.New("size of result exceeds limit")
if size <= 0 || size > maxSeqSize {
return nil, errSeqSizeExceedsLimit
}
seq := make([]int, size)
@@ -536,6 +541,12 @@ type dKey struct {
// See https://getkerf.wordpress.com/2016/03/30/the-best-algorithm-no-one-knows-about/
func (ns *Namespace) D(seed, n, hi any) []int {
key := dKey{seed: cast.ToUint64(seed), n: cast.ToInt(n), hi: cast.ToInt(hi)}
if key.n <= 0 || key.hi <= 0 || key.n > key.hi {
return nil
}
if key.n > maxSeqSize {
panic(errSeqSizeExceedsLimit)
}
v, _ := ns.dCache.GetOrCreate(key, func() ([]int, error) {
prng := rand.New(rand.NewPCG(key.seed, 0))
result := make([]int, 0, key.n)

View File

@@ -298,3 +298,33 @@ All|{{ .Title }}|
b.AssertFileContent("public/index.html", "Len: 1|")
}
func TestD(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- layouts/home.html --
{{ $p := site.RegularPages }}
5 random pages: {{ range collections.D 42 5 ($p | len) }}{{ with (index $p .) }}{{ .RelPermalink }}|{{ end }}{{ end }}$
-- content/a.md --
-- content/b.md --
-- content/c.md --
-- content/d.md --
-- content/e.md --
-- content/f.md --
-- content/g.md --
-- content/h.md --
-- content/i.md --
-- content/j.md --
-- content/k.md --
-- content/l.md --
-- content/m.md --
-- content/n.md --
-- content/o.md --
-- content/p.md --
`
b := hugolib.Test(t, files)
b.AssertFileContentExact("public/index.html", "5 random pages: /b/|/g/|/j/|/k/|/l/|$")
}

View File

@@ -536,7 +536,7 @@ func TestSeq(t *testing.T) {
{[]any{1, -1, 2}, false},
{[]any{2, 1, 1}, false},
{[]any{2, 1, 1, 1}, false},
{[]any{2001}, false},
{[]any{-1000001}, false},
{[]any{}, false},
{[]any{0, -1000000}, false},
{[]any{tstNoStringer{}}, false},
@@ -795,6 +795,15 @@ func TestD(t *testing.T) {
c.Assert(ns.D(42, 5, 100), qt.DeepEquals, []int{24, 34, 66, 82, 96})
c.Assert(ns.D(31, 5, 100), qt.DeepEquals, []int{12, 37, 38, 69, 98})
c.Assert(ns.D(42, 9, 10), qt.DeepEquals, []int{0, 1, 2, 3, 4, 6, 7, 8, 9})
c.Assert(ns.D(42, 10, 10), qt.DeepEquals, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
c.Assert(ns.D(42, 11, 10), qt.IsNil) // n > hi
c.Assert(ns.D(42, -5, 100), qt.IsNil)
c.Assert(ns.D(42, 0, 100), qt.IsNil)
c.Assert(ns.D(42, 5, 0), qt.IsNil)
c.Assert(ns.D(42, 5, -10), qt.IsNil)
c.Assert(ns.D(42, 5, 3000000), qt.DeepEquals, []int{720363, 1041693, 2009179, 2489106, 2873969})
c.Assert(func() { ns.D(31, 2000000, 3000000) }, qt.PanicMatches, "size of result exceeds limit")
}
func BenchmarkD2(b *testing.B) {