collections_test.go (29395B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package collections
15
16 import (
17 "errors"
18 "fmt"
19 "html/template"
20 "math/rand"
21 "reflect"
22 "testing"
23 "time"
24
25 "github.com/gohugoio/hugo/common/maps"
26
27 qt "github.com/frankban/quicktest"
28 "github.com/gohugoio/hugo/common/loggers"
29 "github.com/gohugoio/hugo/config"
30 "github.com/gohugoio/hugo/deps"
31 "github.com/gohugoio/hugo/helpers"
32 "github.com/gohugoio/hugo/hugofs"
33 "github.com/gohugoio/hugo/langs"
34 "github.com/spf13/afero"
35 )
36
37 type tstNoStringer struct{}
38
39 func TestAfter(t *testing.T) {
40 t.Parallel()
41 c := qt.New(t)
42
43 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
44
45 for i, test := range []struct {
46 index any
47 seq any
48 expect any
49 }{
50 {int(2), []string{"a", "b", "c", "d"}, []string{"c", "d"}},
51 {int32(3), []string{"a", "b"}, []string{}},
52 {int64(2), []int{100, 200, 300}, []int{300}},
53 {100, []int{100, 200}, []int{}},
54 {"1", []int{100, 200, 300}, []int{200, 300}},
55 {0, []int{100, 200, 300, 400, 500}, []int{100, 200, 300, 400, 500}},
56 {0, []string{"a", "b", "c", "d", "e"}, []string{"a", "b", "c", "d", "e"}},
57 {int64(-1), []int{100, 200, 300}, false},
58 {"noint", []int{100, 200, 300}, false},
59 {2, []string{}, []string{}},
60 {1, nil, false},
61 {nil, []int{100}, false},
62 {1, t, false},
63 {1, (*string)(nil), false},
64 } {
65 errMsg := qt.Commentf("[%d] %v", i, test)
66
67 result, err := ns.After(test.index, test.seq)
68
69 if b, ok := test.expect.(bool); ok && !b {
70 c.Assert(err, qt.Not(qt.IsNil), errMsg)
71 continue
72 }
73
74 c.Assert(err, qt.IsNil, errMsg)
75 c.Assert(result, qt.DeepEquals, test.expect, errMsg)
76 }
77 }
78
79 type tstGrouper struct {
80 }
81
82 type tstGroupers []*tstGrouper
83
84 func (g tstGrouper) Group(key any, items any) (any, error) {
85 ilen := reflect.ValueOf(items).Len()
86 return fmt.Sprintf("%v(%d)", key, ilen), nil
87 }
88
89 type tstGrouper2 struct {
90 }
91
92 func (g *tstGrouper2) Group(key any, items any) (any, error) {
93 ilen := reflect.ValueOf(items).Len()
94 return fmt.Sprintf("%v(%d)", key, ilen), nil
95 }
96
97 func TestGroup(t *testing.T) {
98 t.Parallel()
99 c := qt.New(t)
100 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
101
102 for i, test := range []struct {
103 key any
104 items any
105 expect any
106 }{
107 {"a", []*tstGrouper{{}, {}}, "a(2)"},
108 {"b", tstGroupers{&tstGrouper{}, &tstGrouper{}}, "b(2)"},
109 {"a", []tstGrouper{{}, {}}, "a(2)"},
110 {"a", []*tstGrouper2{{}, {}}, "a(2)"},
111 {"b", []tstGrouper2{{}, {}}, "b(2)"},
112 {"a", []*tstGrouper{}, "a(0)"},
113 {"a", []string{"a", "b"}, false},
114 {"a", "asdf", false},
115 {"a", nil, false},
116 {nil, []*tstGrouper{{}, {}}, false},
117 } {
118 errMsg := qt.Commentf("[%d] %v", i, test)
119
120 result, err := ns.Group(test.key, test.items)
121
122 if b, ok := test.expect.(bool); ok && !b {
123 c.Assert(err, qt.Not(qt.IsNil), errMsg)
124 continue
125 }
126
127 c.Assert(err, qt.IsNil, errMsg)
128 c.Assert(result, qt.Equals, test.expect, errMsg)
129 }
130 }
131
132 func TestDelimit(t *testing.T) {
133 t.Parallel()
134 c := qt.New(t)
135
136 ns := New(&deps.Deps{
137 Language: langs.NewDefaultLanguage(config.New()),
138 })
139
140 for i, test := range []struct {
141 seq any
142 delimiter any
143 last any
144 expect template.HTML
145 }{
146 {[]string{"class1", "class2", "class3"}, " ", nil, "class1 class2 class3"},
147 {[]int{1, 2, 3, 4, 5}, ",", nil, "1,2,3,4,5"},
148 {[]int{1, 2, 3, 4, 5}, ", ", nil, "1, 2, 3, 4, 5"},
149 {[]string{"class1", "class2", "class3"}, " ", " and ", "class1 class2 and class3"},
150 {[]int{1, 2, 3, 4, 5}, ",", ",", "1,2,3,4,5"},
151 {[]int{1, 2, 3, 4, 5}, ", ", ", and ", "1, 2, 3, 4, and 5"},
152 // test maps with and without sorting required
153 {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", nil, "10--20--30--40--50"},
154 {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", nil, "30--20--10--40--50"},
155 {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", nil, "10--20--30--40--50"},
156 {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", nil, "30--20--10--40--50"},
157 {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", nil, "50--40--10--30--20"},
158 {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", nil, "10--20--30--40--50"},
159 {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", nil, "30--20--10--40--50"},
160 {map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, "--", nil, "30--20--10--40--50"},
161 // test maps with a last delimiter
162 {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", "--and--", "10--20--30--40--and--50"},
163 {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", "--and--", "30--20--10--40--and--50"},
164 {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", "--and--", "10--20--30--40--and--50"},
165 {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", "--and--", "30--20--10--40--and--50"},
166 {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", "--and--", "50--40--10--30--and--20"},
167 {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", "--and--", "10--20--30--40--and--50"},
168 {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", "--and--", "30--20--10--40--and--50"},
169 {map[float64]string{3.5: "10", 2.5: "20", 1.5: "30", 4.5: "40", 5.5: "50"}, "--", "--and--", "30--20--10--40--and--50"},
170 } {
171 errMsg := qt.Commentf("[%d] %v", i, test)
172
173 var result template.HTML
174 var err error
175
176 if test.last == nil {
177 result, err = ns.Delimit(test.seq, test.delimiter)
178 } else {
179 result, err = ns.Delimit(test.seq, test.delimiter, test.last)
180 }
181
182 c.Assert(err, qt.IsNil, errMsg)
183 c.Assert(result, qt.Equals, test.expect, errMsg)
184 }
185 }
186
187 func TestDictionary(t *testing.T) {
188 c := qt.New(t)
189
190 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
191
192 for i, test := range []struct {
193 values []any
194 expect any
195 }{
196 {[]any{"a", "b"}, map[string]any{"a": "b"}},
197 {[]any{[]string{"a", "b"}, "c"}, map[string]any{"a": map[string]any{"b": "c"}}},
198 {
199 []any{[]string{"a", "b"}, "c", []string{"a", "b2"}, "c2", "b", "c"},
200 map[string]any{"a": map[string]any{"b": "c", "b2": "c2"}, "b": "c"},
201 },
202 {[]any{"a", 12, "b", []int{4}}, map[string]any{"a": 12, "b": []int{4}}},
203 // errors
204 {[]any{5, "b"}, false},
205 {[]any{"a", "b", "c"}, false},
206 } {
207 i := i
208 test := test
209 c.Run(fmt.Sprint(i), func(c *qt.C) {
210 c.Parallel()
211 errMsg := qt.Commentf("[%d] %v", i, test.values)
212
213 result, err := ns.Dictionary(test.values...)
214
215 if b, ok := test.expect.(bool); ok && !b {
216 c.Assert(err, qt.Not(qt.IsNil), errMsg)
217 return
218 }
219
220 c.Assert(err, qt.IsNil, errMsg)
221 c.Assert(result, qt.DeepEquals, test.expect, qt.Commentf(fmt.Sprint(result)))
222 })
223 }
224 }
225
226 func TestReverse(t *testing.T) {
227 t.Parallel()
228 c := qt.New(t)
229 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
230
231 s := []string{"a", "b", "c"}
232 reversed, err := ns.Reverse(s)
233 c.Assert(err, qt.IsNil)
234 c.Assert(reversed, qt.DeepEquals, []string{"c", "b", "a"}, qt.Commentf(fmt.Sprint(reversed)))
235 c.Assert(s, qt.DeepEquals, []string{"a", "b", "c"})
236
237 reversed, err = ns.Reverse(nil)
238 c.Assert(err, qt.IsNil)
239 c.Assert(reversed, qt.IsNil)
240 _, err = ns.Reverse(43)
241 c.Assert(err, qt.Not(qt.IsNil))
242 }
243
244 func TestEchoParam(t *testing.T) {
245 t.Parallel()
246 c := qt.New(t)
247
248 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
249
250 for i, test := range []struct {
251 a any
252 key any
253 expect any
254 }{
255 {[]int{1, 2, 3}, 1, int64(2)},
256 {[]uint{1, 2, 3}, 1, uint64(2)},
257 {[]float64{1.1, 2.2, 3.3}, 1, float64(2.2)},
258 {[]string{"foo", "bar", "baz"}, 1, "bar"},
259 {[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}}, 1, ""},
260 {map[string]int{"foo": 1, "bar": 2, "baz": 3}, "bar", int64(2)},
261 {map[string]uint{"foo": 1, "bar": 2, "baz": 3}, "bar", uint64(2)},
262 {map[string]float64{"foo": 1.1, "bar": 2.2, "baz": 3.3}, "bar", float64(2.2)},
263 {map[string]string{"foo": "FOO", "bar": "BAR", "baz": "BAZ"}, "bar", "BAR"},
264 {map[string]TstX{"foo": {A: "a", B: "b"}, "bar": {A: "c", B: "d"}, "baz": {A: "e", B: "f"}}, "bar", ""},
265 {map[string]any{"foo": nil}, "foo", ""},
266 {(*[]string)(nil), "bar", ""},
267 } {
268 errMsg := qt.Commentf("[%d] %v", i, test)
269
270 result := ns.EchoParam(test.a, test.key)
271
272 c.Assert(result, qt.Equals, test.expect, errMsg)
273 }
274 }
275
276 func TestFirst(t *testing.T) {
277 t.Parallel()
278 c := qt.New(t)
279
280 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
281
282 for i, test := range []struct {
283 limit any
284 seq any
285 expect any
286 }{
287 {int(2), []string{"a", "b", "c"}, []string{"a", "b"}},
288 {int32(3), []string{"a", "b"}, []string{"a", "b"}},
289 {int64(2), []int{100, 200, 300}, []int{100, 200}},
290 {100, []int{100, 200}, []int{100, 200}},
291 {"1", []int{100, 200, 300}, []int{100}},
292 {0, []string{"h", "u", "g", "o"}, []string{}},
293 {int64(-1), []int{100, 200, 300}, false},
294 {"noint", []int{100, 200, 300}, false},
295 {1, nil, false},
296 {nil, []int{100}, false},
297 {1, t, false},
298 {1, (*string)(nil), false},
299 } {
300 errMsg := qt.Commentf("[%d] %v", i, test)
301
302 result, err := ns.First(test.limit, test.seq)
303
304 if b, ok := test.expect.(bool); ok && !b {
305 c.Assert(err, qt.Not(qt.IsNil), errMsg)
306 continue
307 }
308
309 c.Assert(err, qt.IsNil)
310 c.Assert(result, qt.DeepEquals, test.expect, errMsg)
311 }
312 }
313
314 func TestIn(t *testing.T) {
315 t.Parallel()
316 c := qt.New(t)
317
318 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
319
320 for i, test := range []struct {
321 l1 any
322 l2 any
323 expect bool
324 }{
325 {[]string{"a", "b", "c"}, "b", true},
326 {[]any{"a", "b", "c"}, "b", true},
327 {[]any{"a", "b", "c"}, "d", false},
328 {[]string{"a", "b", "c"}, "d", false},
329 {[]string{"a", "12", "c"}, 12, false},
330 {[]string{"a", "b", "c"}, nil, false},
331 {[]int{1, 2, 4}, 2, true},
332 {[]any{1, 2, 4}, 2, true},
333 {[]any{1, 2, 4}, nil, false},
334 {[]any{nil}, nil, false},
335 {[]int{1, 2, 4}, 3, false},
336 {[]float64{1.23, 2.45, 4.67}, 1.23, true},
337 {[]float64{1.234567, 2.45, 4.67}, 1.234568, false},
338 {[]float64{1, 2, 3}, 1, true},
339 {[]float32{1, 2, 3}, 1, true},
340 {"this substring should be found", "substring", true},
341 {"this substring should not be found", "subseastring", false},
342 {nil, "foo", false},
343 // Pointers
344 {pagesPtr{p1, p2, p3, p2}, p2, true},
345 {pagesPtr{p1, p2, p3, p2}, p4, false},
346 // Structs
347 {pagesVals{p3v, p2v, p3v, p2v}, p2v, true},
348 {pagesVals{p3v, p2v, p3v, p2v}, p4v, false},
349 // template.HTML
350 {template.HTML("this substring should be found"), "substring", true},
351 {template.HTML("this substring should not be found"), "subseastring", false},
352 // Uncomparable, use hashstructure
353 {[]string{"a", "b"}, []string{"a", "b"}, false},
354 {[][]string{{"a", "b"}}, []string{"a", "b"}, true},
355 } {
356
357 errMsg := qt.Commentf("[%d] %v", i, test)
358
359 result, err := ns.In(test.l1, test.l2)
360 c.Assert(err, qt.IsNil)
361 c.Assert(result, qt.Equals, test.expect, errMsg)
362 }
363 }
364
365 type testPage struct {
366 Title string
367 }
368
369 func (p testPage) String() string {
370 return "p-" + p.Title
371 }
372
373 type (
374 pagesPtr []*testPage
375 pagesVals []testPage
376 )
377
378 var (
379 p1 = &testPage{"A"}
380 p2 = &testPage{"B"}
381 p3 = &testPage{"C"}
382 p4 = &testPage{"D"}
383
384 p1v = testPage{"A"}
385 p2v = testPage{"B"}
386 p3v = testPage{"C"}
387 p4v = testPage{"D"}
388 )
389
390 func TestIntersect(t *testing.T) {
391 t.Parallel()
392 c := qt.New(t)
393
394 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
395
396 for i, test := range []struct {
397 l1, l2 any
398 expect any
399 }{
400 {[]string{"a", "b", "c", "c"}, []string{"a", "b", "b"}, []string{"a", "b"}},
401 {[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b"}},
402 {[]string{"a", "b", "c"}, []string{"d", "e"}, []string{}},
403 {[]string{}, []string{}, []string{}},
404 {[]string{"a", "b"}, nil, []any{}},
405 {nil, []string{"a", "b"}, []any{}},
406 {nil, nil, []any{}},
407 {[]string{"1", "2"}, []int{1, 2}, []string{}},
408 {[]int{1, 2}, []string{"1", "2"}, []int{}},
409 {[]int{1, 2, 4}, []int{2, 4}, []int{2, 4}},
410 {[]int{2, 4}, []int{1, 2, 4}, []int{2, 4}},
411 {[]int{1, 2, 4}, []int{3, 6}, []int{}},
412 {[]float64{2.2, 4.4}, []float64{1.1, 2.2, 4.4}, []float64{2.2, 4.4}},
413
414 // []interface{} ∩ []interface{}
415 {[]any{"a", "b", "c"}, []any{"a", "b", "b"}, []any{"a", "b"}},
416 {[]any{1, 2, 3}, []any{1, 2, 2}, []any{1, 2}},
417 {[]any{int8(1), int8(2), int8(3)}, []any{int8(1), int8(2), int8(2)}, []any{int8(1), int8(2)}},
418 {[]any{int16(1), int16(2), int16(3)}, []any{int16(1), int16(2), int16(2)}, []any{int16(1), int16(2)}},
419 {[]any{int32(1), int32(2), int32(3)}, []any{int32(1), int32(2), int32(2)}, []any{int32(1), int32(2)}},
420 {[]any{int64(1), int64(2), int64(3)}, []any{int64(1), int64(2), int64(2)}, []any{int64(1), int64(2)}},
421 {[]any{float32(1), float32(2), float32(3)}, []any{float32(1), float32(2), float32(2)}, []any{float32(1), float32(2)}},
422 {[]any{float64(1), float64(2), float64(3)}, []any{float64(1), float64(2), float64(2)}, []any{float64(1), float64(2)}},
423
424 // []interface{} ∩ []T
425 {[]any{"a", "b", "c"}, []string{"a", "b", "b"}, []any{"a", "b"}},
426 {[]any{1, 2, 3}, []int{1, 2, 2}, []any{1, 2}},
427 {[]any{int8(1), int8(2), int8(3)}, []int8{1, 2, 2}, []any{int8(1), int8(2)}},
428 {[]any{int16(1), int16(2), int16(3)}, []int16{1, 2, 2}, []any{int16(1), int16(2)}},
429 {[]any{int32(1), int32(2), int32(3)}, []int32{1, 2, 2}, []any{int32(1), int32(2)}},
430 {[]any{int64(1), int64(2), int64(3)}, []int64{1, 2, 2}, []any{int64(1), int64(2)}},
431 {[]any{uint(1), uint(2), uint(3)}, []uint{1, 2, 2}, []any{uint(1), uint(2)}},
432 {[]any{float32(1), float32(2), float32(3)}, []float32{1, 2, 2}, []any{float32(1), float32(2)}},
433 {[]any{float64(1), float64(2), float64(3)}, []float64{1, 2, 2}, []any{float64(1), float64(2)}},
434
435 // []T ∩ []interface{}
436 {[]string{"a", "b", "c"}, []any{"a", "b", "b"}, []string{"a", "b"}},
437 {[]int{1, 2, 3}, []any{1, 2, 2}, []int{1, 2}},
438 {[]int8{1, 2, 3}, []any{int8(1), int8(2), int8(2)}, []int8{1, 2}},
439 {[]int16{1, 2, 3}, []any{int16(1), int16(2), int16(2)}, []int16{1, 2}},
440 {[]int32{1, 2, 3}, []any{int32(1), int32(2), int32(2)}, []int32{1, 2}},
441 {[]int64{1, 2, 3}, []any{int64(1), int64(2), int64(2)}, []int64{1, 2}},
442 {[]float32{1, 2, 3}, []any{float32(1), float32(2), float32(2)}, []float32{1, 2}},
443 {[]float64{1, 2, 3}, []any{float64(1), float64(2), float64(2)}, []float64{1, 2}},
444
445 // Structs
446 {pagesPtr{p1, p4, p2, p3}, pagesPtr{p4, p2, p2}, pagesPtr{p4, p2}},
447 {pagesVals{p1v, p4v, p2v, p3v}, pagesVals{p1v, p3v, p3v}, pagesVals{p1v, p3v}},
448 {[]any{p1, p4, p2, p3}, []any{p4, p2, p2}, []any{p4, p2}},
449 {[]any{p1v, p4v, p2v, p3v}, []any{p1v, p3v, p3v}, []any{p1v, p3v}},
450 {pagesPtr{p1, p4, p2, p3}, pagesPtr{}, pagesPtr{}},
451 {pagesVals{}, pagesVals{p1v, p3v, p3v}, pagesVals{}},
452 {[]any{p1, p4, p2, p3}, []any{}, []any{}},
453 {[]any{}, []any{p1v, p3v, p3v}, []any{}},
454
455 // errors
456 {"not array or slice", []string{"a"}, false},
457 {[]string{"a"}, "not array or slice", false},
458
459 // uncomparable types - #3820
460 {[]map[int]int{{1: 1}, {2: 2}}, []map[int]int{{2: 2}, {3: 3}}, false},
461 {[][]int{{1, 1}, {1, 2}}, [][]int{{1, 2}, {1, 2}, {1, 3}}, false},
462 {[]int{1, 1}, [][]int{{1, 2}, {1, 2}, {1, 3}}, false},
463 } {
464
465 errMsg := qt.Commentf("[%d] %v", i, test)
466
467 result, err := ns.Intersect(test.l1, test.l2)
468
469 if b, ok := test.expect.(bool); ok && !b {
470 c.Assert(err, qt.Not(qt.IsNil), errMsg)
471 continue
472 }
473
474 c.Assert(err, qt.IsNil, errMsg)
475 if !reflect.DeepEqual(result, test.expect) {
476 t.Fatalf("[%d] Got\n%v expected\n%v", i, result, test.expect)
477 }
478 }
479 }
480
481 func TestIsSet(t *testing.T) {
482 t.Parallel()
483 c := qt.New(t)
484 ns := newTestNs()
485
486 for i, test := range []struct {
487 a any
488 key any
489 expect bool
490 isErr bool
491 }{
492 {[]any{1, 2, 3, 5}, 2, true, false},
493 {[]any{1, 2, 3, 5}, "2", true, false},
494 {[]any{1, 2, 3, 5}, 2.0, true, false},
495
496 {[]any{1, 2, 3, 5}, 22, false, false},
497
498 {map[string]any{"a": 1, "b": 2}, "b", true, false},
499 {map[string]any{"a": 1, "b": 2}, "bc", false, false},
500
501 {time.Now(), "Day", false, false},
502 {nil, "nil", false, false},
503 {[]any{1, 2, 3, 5}, TstX{}, false, true},
504 } {
505 errMsg := qt.Commentf("[%d] %v", i, test)
506
507 result, err := ns.IsSet(test.a, test.key)
508 if test.isErr {
509 continue
510 }
511
512 c.Assert(err, qt.IsNil, errMsg)
513 c.Assert(result, qt.Equals, test.expect, errMsg)
514 }
515 }
516
517 func TestLast(t *testing.T) {
518 t.Parallel()
519 c := qt.New(t)
520
521 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
522
523 for i, test := range []struct {
524 limit any
525 seq any
526 expect any
527 }{
528 {int(2), []string{"a", "b", "c"}, []string{"b", "c"}},
529 {int32(3), []string{"a", "b"}, []string{"a", "b"}},
530 {int64(2), []int{100, 200, 300}, []int{200, 300}},
531 {100, []int{100, 200}, []int{100, 200}},
532 {"1", []int{100, 200, 300}, []int{300}},
533 {"0", []int{100, 200, 300}, []int{}},
534 {"0", []string{"a", "b", "c"}, []string{}},
535 // errors
536 {int64(-1), []int{100, 200, 300}, false},
537 {"noint", []int{100, 200, 300}, false},
538 {1, nil, false},
539 {nil, []int{100}, false},
540 {1, t, false},
541 {1, (*string)(nil), false},
542 } {
543 errMsg := qt.Commentf("[%d] %v", i, test)
544
545 result, err := ns.Last(test.limit, test.seq)
546
547 if b, ok := test.expect.(bool); ok && !b {
548 c.Assert(err, qt.Not(qt.IsNil), errMsg)
549 continue
550 }
551
552 c.Assert(err, qt.IsNil, errMsg)
553 c.Assert(result, qt.DeepEquals, test.expect, errMsg)
554 }
555 }
556
557 func TestQuerify(t *testing.T) {
558 t.Parallel()
559 c := qt.New(t)
560 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
561
562 for i, test := range []struct {
563 params []any
564 expect any
565 }{
566 {[]any{"a", "b"}, "a=b"},
567 {[]any{"a", "b", "c", "d", "f", " &"}, `a=b&c=d&f=+%26`},
568 {[]any{[]string{"a", "b"}}, "a=b"},
569 {[]any{[]string{"a", "b", "c", "d", "f", " &"}}, `a=b&c=d&f=+%26`},
570 {[]any{[]any{"x", "y"}}, `x=y`},
571 {[]any{[]any{"x", 5}}, `x=5`},
572 // errors
573 {[]any{5, "b"}, false},
574 {[]any{"a", "b", "c"}, false},
575 {[]any{[]string{"a", "b", "c"}}, false},
576 {[]any{[]string{"a", "b"}, "c"}, false},
577 {[]any{[]any{"c", "d", "e"}}, false},
578 } {
579 errMsg := qt.Commentf("[%d] %v", i, test.params)
580
581 result, err := ns.Querify(test.params...)
582
583 if b, ok := test.expect.(bool); ok && !b {
584 c.Assert(err, qt.Not(qt.IsNil), errMsg)
585 continue
586 }
587
588 c.Assert(err, qt.IsNil, errMsg)
589 c.Assert(result, qt.Equals, test.expect, errMsg)
590 }
591 }
592
593 func BenchmarkQuerify(b *testing.B) {
594 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
595 params := []any{"a", "b", "c", "d", "f", " &"}
596
597 b.ResetTimer()
598 for i := 0; i < b.N; i++ {
599 _, err := ns.Querify(params...)
600 if err != nil {
601 b.Fatal(err)
602 }
603 }
604 }
605
606 func BenchmarkQuerifySlice(b *testing.B) {
607 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
608 params := []string{"a", "b", "c", "d", "f", " &"}
609
610 b.ResetTimer()
611 for i := 0; i < b.N; i++ {
612 _, err := ns.Querify(params)
613 if err != nil {
614 b.Fatal(err)
615 }
616 }
617 }
618
619 func TestSeq(t *testing.T) {
620 t.Parallel()
621 c := qt.New(t)
622 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
623
624 for i, test := range []struct {
625 args []any
626 expect any
627 }{
628 {[]any{-2, 5}, []int{-2, -1, 0, 1, 2, 3, 4, 5}},
629 {[]any{1, 2, 4}, []int{1, 3}},
630 {[]any{1}, []int{1}},
631 {[]any{3}, []int{1, 2, 3}},
632 {[]any{3.2}, []int{1, 2, 3}},
633 {[]any{0}, []int{}},
634 {[]any{-1}, []int{-1}},
635 {[]any{-3}, []int{-1, -2, -3}},
636 {[]any{3, -2}, []int{3, 2, 1, 0, -1, -2}},
637 {[]any{6, -2, 2}, []int{6, 4, 2}},
638 // errors
639 {[]any{1, 0, 2}, false},
640 {[]any{1, -1, 2}, false},
641 {[]any{2, 1, 1}, false},
642 {[]any{2, 1, 1, 1}, false},
643 {[]any{2001}, false},
644 {[]any{}, false},
645 {[]any{0, -1000000}, false},
646 {[]any{tstNoStringer{}}, false},
647 {nil, false},
648 } {
649 errMsg := qt.Commentf("[%d] %v", i, test)
650
651 result, err := ns.Seq(test.args...)
652
653 if b, ok := test.expect.(bool); ok && !b {
654 c.Assert(err, qt.Not(qt.IsNil), errMsg)
655 continue
656 }
657
658 c.Assert(err, qt.IsNil, errMsg)
659 c.Assert(result, qt.DeepEquals, test.expect, errMsg)
660 }
661 }
662
663 func TestShuffle(t *testing.T) {
664 t.Parallel()
665 c := qt.New(t)
666 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
667
668 for i, test := range []struct {
669 seq any
670 success bool
671 }{
672 {[]string{"a", "b", "c", "d"}, true},
673 {[]int{100, 200, 300}, true},
674 {[]int{100, 200, 300}, true},
675 {[]int{100, 200}, true},
676 {[]string{"a", "b"}, true},
677 {[]int{100, 200, 300}, true},
678 {[]int{100, 200, 300}, true},
679 {[]int{100}, true},
680 // errors
681 {nil, false},
682 {t, false},
683 {(*string)(nil), false},
684 } {
685 errMsg := qt.Commentf("[%d] %v", i, test)
686
687 result, err := ns.Shuffle(test.seq)
688
689 if !test.success {
690 c.Assert(err, qt.Not(qt.IsNil), errMsg)
691 continue
692 }
693
694 c.Assert(err, qt.IsNil, errMsg)
695
696 resultv := reflect.ValueOf(result)
697 seqv := reflect.ValueOf(test.seq)
698
699 c.Assert(seqv.Len(), qt.Equals, resultv.Len(), errMsg)
700 }
701 }
702
703 func TestShuffleRandomising(t *testing.T) {
704 t.Parallel()
705 c := qt.New(t)
706 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
707
708 // Note that this test can fail with false negative result if the shuffle
709 // of the sequence happens to be the same as the original sequence. However
710 // the probability of the event is 10^-158 which is negligible.
711 seqLen := 100
712 rand.Seed(time.Now().UTC().UnixNano())
713
714 for _, test := range []struct {
715 seq []int
716 }{
717 {rand.Perm(seqLen)},
718 } {
719 result, err := ns.Shuffle(test.seq)
720 resultv := reflect.ValueOf(result)
721
722 c.Assert(err, qt.IsNil)
723
724 allSame := true
725 for i, v := range test.seq {
726 allSame = allSame && (resultv.Index(i).Interface() == v)
727 }
728
729 c.Assert(allSame, qt.Equals, false)
730 }
731 }
732
733 // Also see tests in commons/collection.
734 func TestSlice(t *testing.T) {
735 t.Parallel()
736 c := qt.New(t)
737 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
738
739 for i, test := range []struct {
740 args []any
741 expected any
742 }{
743 {[]any{"a", "b"}, []string{"a", "b"}},
744 {[]any{}, []any{}},
745 {[]any{nil}, []any{nil}},
746 {[]any{5, "b"}, []any{5, "b"}},
747 {[]any{tstNoStringer{}}, []tstNoStringer{{}}},
748 } {
749 errMsg := qt.Commentf("[%d] %v", i, test.args)
750
751 result := ns.Slice(test.args...)
752
753 c.Assert(result, qt.DeepEquals, test.expected, errMsg)
754 }
755 }
756
757 func TestUnion(t *testing.T) {
758 t.Parallel()
759 c := qt.New(t)
760
761 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
762
763 for i, test := range []struct {
764 l1 any
765 l2 any
766 expect any
767 isErr bool
768 }{
769 {nil, nil, []any{}, false},
770 {nil, []string{"a", "b"}, []string{"a", "b"}, false},
771 {[]string{"a", "b"}, nil, []string{"a", "b"}, false},
772
773 // []A ∪ []B
774 {[]string{"1", "2"}, []int{3}, []string{}, false},
775 {[]int{1, 2}, []string{"1", "2"}, []int{}, false},
776
777 // []T ∪ []T
778 {[]string{"a", "b", "c", "c"}, []string{"a", "b", "b"}, []string{"a", "b", "c"}, false},
779 {[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b", "c"}, false},
780 {[]string{"a", "b", "c"}, []string{"d", "e"}, []string{"a", "b", "c", "d", "e"}, false},
781 {[]string{}, []string{}, []string{}, false},
782 {[]int{1, 2, 3}, []int{3, 4, 5}, []int{1, 2, 3, 4, 5}, false},
783 {[]int{1, 2, 3}, []int{1, 2, 3}, []int{1, 2, 3}, false},
784 {[]int{1, 2, 4}, []int{2, 4}, []int{1, 2, 4}, false},
785 {[]int{2, 4}, []int{1, 2, 4}, []int{2, 4, 1}, false},
786 {[]int{1, 2, 4}, []int{3, 6}, []int{1, 2, 4, 3, 6}, false},
787 {[]float64{2.2, 4.4}, []float64{1.1, 2.2, 4.4}, []float64{2.2, 4.4, 1.1}, false},
788 {[]any{"a", "b", "c", "c"}, []any{"a", "b", "b"}, []any{"a", "b", "c"}, false},
789
790 // []T ∪ []interface{}
791 {[]string{"1", "2"}, []any{"9"}, []string{"1", "2", "9"}, false},
792 {[]int{2, 4}, []any{1, 2, 4}, []int{2, 4, 1}, false},
793 {[]int8{2, 4}, []any{int8(1), int8(2), int8(4)}, []int8{2, 4, 1}, false},
794 {[]int8{2, 4}, []any{1, 2, 4}, []int8{2, 4, 1}, false},
795 {[]int16{2, 4}, []any{1, 2, 4}, []int16{2, 4, 1}, false},
796 {[]int32{2, 4}, []any{1, 2, 4}, []int32{2, 4, 1}, false},
797 {[]int64{2, 4}, []any{1, 2, 4}, []int64{2, 4, 1}, false},
798
799 {[]float64{2.2, 4.4}, []any{1.1, 2.2, 4.4}, []float64{2.2, 4.4, 1.1}, false},
800 {[]float32{2.2, 4.4}, []any{1.1, 2.2, 4.4}, []float32{2.2, 4.4, 1.1}, false},
801
802 // []interface{} ∪ []T
803 {[]any{"a", "b", "c", "c"}, []string{"a", "b", "d"}, []any{"a", "b", "c", "d"}, false},
804 {[]any{}, []string{}, []any{}, false},
805 {[]any{1, 2}, []int{2, 3}, []any{1, 2, 3}, false},
806 {[]any{1, 2}, []int8{2, 3}, []any{1, 2, 3}, false}, // 28
807 {[]any{uint(1), uint(2)}, []uint{2, 3}, []any{uint(1), uint(2), uint(3)}, false},
808 {[]any{1.1, 2.2}, []float64{2.2, 3.3}, []any{1.1, 2.2, 3.3}, false},
809
810 // Structs
811 {pagesPtr{p1, p4}, pagesPtr{p4, p2, p2}, pagesPtr{p1, p4, p2}, false},
812 {pagesVals{p1v}, pagesVals{p3v, p3v}, pagesVals{p1v, p3v}, false},
813 {[]any{p1, p4}, []any{p4, p2, p2}, []any{p1, p4, p2}, false},
814 {[]any{p1v}, []any{p3v, p3v}, []any{p1v, p3v}, false},
815 // #3686
816 {[]any{p1v}, []any{}, []any{p1v}, false},
817 {[]any{}, []any{p1v}, []any{p1v}, false},
818 {pagesPtr{p1}, pagesPtr{}, pagesPtr{p1}, false},
819 {pagesVals{p1v}, pagesVals{}, pagesVals{p1v}, false},
820 {pagesPtr{}, pagesPtr{p1}, pagesPtr{p1}, false},
821 {pagesVals{}, pagesVals{p1v}, pagesVals{p1v}, false},
822
823 // errors
824 {"not array or slice", []string{"a"}, false, true},
825 {[]string{"a"}, "not array or slice", false, true},
826
827 // uncomparable types - #3820
828 {[]map[string]int{{"K1": 1}}, []map[string]int{{"K2": 2}, {"K2": 2}}, false, true},
829 {[][]int{{1, 1}, {1, 2}}, [][]int{{2, 1}, {2, 2}}, false, true},
830 } {
831
832 errMsg := qt.Commentf("[%d] %v", i, test)
833
834 result, err := ns.Union(test.l1, test.l2)
835 if test.isErr {
836 c.Assert(err, qt.Not(qt.IsNil), errMsg)
837 continue
838 }
839
840 c.Assert(err, qt.IsNil, errMsg)
841 if !reflect.DeepEqual(result, test.expect) {
842 t.Fatalf("[%d] Got\n%v expected\n%v", i, result, test.expect)
843 }
844 }
845 }
846
847 func TestUniq(t *testing.T) {
848 t.Parallel()
849 c := qt.New(t)
850 ns := New(&deps.Deps{Language: langs.NewDefaultLanguage(config.New())})
851 for i, test := range []struct {
852 l any
853 expect any
854 isErr bool
855 }{
856 {[]string{"a", "b", "c"}, []string{"a", "b", "c"}, false},
857 {[]string{"a", "b", "c", "c"}, []string{"a", "b", "c"}, false},
858 {[]string{"a", "b", "b", "c"}, []string{"a", "b", "c"}, false},
859 {[]string{"a", "b", "c", "b"}, []string{"a", "b", "c"}, false},
860 {[]int{1, 2, 3}, []int{1, 2, 3}, false},
861 {[]int{1, 2, 3, 3}, []int{1, 2, 3}, false},
862 {[]int{1, 2, 2, 3}, []int{1, 2, 3}, false},
863 {[]int{1, 2, 3, 2}, []int{1, 2, 3}, false},
864 {[4]int{1, 2, 3, 2}, []int{1, 2, 3}, false},
865 {nil, make([]any, 0), false},
866 // Pointers
867 {pagesPtr{p1, p2, p3, p2}, pagesPtr{p1, p2, p3}, false},
868 {pagesPtr{}, pagesPtr{}, false},
869 // Structs
870 {pagesVals{p3v, p2v, p3v, p2v}, pagesVals{p3v, p2v}, false},
871
872 // not Comparable(), use hashstructure
873 {[]map[string]int{
874 {"K1": 1}, {"K2": 2}, {"K1": 1}, {"K2": 1},
875 }, []map[string]int{
876 {"K1": 1}, {"K2": 2}, {"K2": 1},
877 }, false},
878
879 // should fail
880 {1, 1, true},
881 {"foo", "fo", true},
882 } {
883 errMsg := qt.Commentf("[%d] %v", i, test)
884
885 result, err := ns.Uniq(test.l)
886 if test.isErr {
887 c.Assert(err, qt.Not(qt.IsNil), errMsg)
888 continue
889 }
890
891 c.Assert(err, qt.IsNil, errMsg)
892 c.Assert(result, qt.DeepEquals, test.expect, errMsg)
893 }
894 }
895
896 func (x *TstX) TstRp() string {
897 return "r" + x.A
898 }
899
900 func (x TstX) TstRv() string {
901 return "r" + x.B
902 }
903
904 func (x TstX) TstRv2() string {
905 return "r" + x.B
906 }
907
908 func (x TstX) unexportedMethod() string {
909 return x.unexported
910 }
911
912 func (x TstX) MethodWithArg(s string) string {
913 return s
914 }
915
916 func (x TstX) MethodReturnNothing() {}
917
918 func (x TstX) MethodReturnErrorOnly() error {
919 return errors.New("some error occurred")
920 }
921
922 func (x TstX) MethodReturnTwoValues() (string, string) {
923 return "foo", "bar"
924 }
925
926 func (x TstX) MethodReturnValueWithError() (string, error) {
927 return "", errors.New("some error occurred")
928 }
929
930 func (x TstX) String() string {
931 return fmt.Sprintf("A: %s, B: %s", x.A, x.B)
932 }
933
934 type TstX struct {
935 A, B string
936 unexported string
937 }
938
939 type TstParams struct {
940 params maps.Params
941 }
942
943 func (x TstParams) Params() maps.Params {
944 return x.params
945 }
946
947 type TstXIHolder struct {
948 XI TstXI
949 }
950
951 // Partially implemented by the TstX struct.
952 type TstXI interface {
953 TstRv2() string
954 }
955
956 func ToTstXIs(slice any) []TstXI {
957 s := reflect.ValueOf(slice)
958 if s.Kind() != reflect.Slice {
959 return nil
960 }
961 tis := make([]TstXI, s.Len())
962
963 for i := 0; i < s.Len(); i++ {
964 tsti, ok := s.Index(i).Interface().(TstXI)
965 if !ok {
966 return nil
967 }
968 tis[i] = tsti
969 }
970
971 return tis
972 }
973
974 func newDeps(cfg config.Provider) *deps.Deps {
975 l := langs.NewLanguage("en", cfg)
976 l.Set("i18nDir", "i18n")
977 cs, err := helpers.NewContentSpec(l, loggers.NewErrorLogger(), afero.NewMemMapFs(), nil)
978 if err != nil {
979 panic(err)
980 }
981 return &deps.Deps{
982 Language: l,
983 Cfg: cfg,
984 Fs: hugofs.NewMem(l),
985 ContentSpec: cs,
986 Log: loggers.NewErrorLogger(),
987 }
988 }
989
990 func newTestNs() *Namespace {
991 return New(newDeps(config.NewWithTestDefaults()))
992 }