general_test.go (12837B)
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 helpers
15
16 import (
17 "fmt"
18 "reflect"
19 "strings"
20 "testing"
21 "time"
22
23 "github.com/gohugoio/hugo/common/loggers"
24 "github.com/gohugoio/hugo/config"
25
26 qt "github.com/frankban/quicktest"
27 "github.com/spf13/afero"
28 )
29
30 func TestResolveMarkup(t *testing.T) {
31 c := qt.New(t)
32 cfg := config.NewWithTestDefaults()
33 spec, err := NewContentSpec(cfg, loggers.NewErrorLogger(), afero.NewMemMapFs(), nil)
34 c.Assert(err, qt.IsNil)
35
36 for i, this := range []struct {
37 in string
38 expect string
39 }{
40 {"md", "markdown"},
41 {"markdown", "markdown"},
42 {"mdown", "markdown"},
43 {"asciidocext", "asciidocext"},
44 {"adoc", "asciidocext"},
45 {"ad", "asciidocext"},
46 {"rst", "rst"},
47 {"pandoc", "pandoc"},
48 {"pdc", "pandoc"},
49 {"html", "html"},
50 {"htm", "html"},
51 {"org", "org"},
52 {"excel", ""},
53 } {
54 result := spec.ResolveMarkup(this.in)
55 if result != this.expect {
56 t.Errorf("[%d] got %s but expected %s", i, result, this.expect)
57 }
58 }
59 }
60
61 func TestDistinctLoggerDoesNotLockOnWarningPanic(t *testing.T) {
62 // Testing to make sure logger mutex doesn't lock if warnings cause panics.
63 // func Warnf() of DistinctLogger is defined in general.go
64 l := NewDistinctLogger(loggers.NewWarningLogger())
65
66 // Set PanicOnWarning to true to reproduce issue 9380
67 // Ensure global variable loggers.PanicOnWarning is reset to old value after test
68 if loggers.PanicOnWarning == false {
69 loggers.PanicOnWarning = true
70 defer func() {
71 loggers.PanicOnWarning = false
72 }()
73 }
74
75 // Establish timeout in case a lock occurs:
76 timeIsUp := make(chan bool)
77 timeOutSeconds := 1
78 go func() {
79 time.Sleep(time.Second * time.Duration(timeOutSeconds))
80 timeIsUp <- true
81 }()
82
83 // Attempt to run multiple logging threads in parallel
84 counterC := make(chan int)
85 goroutines := 5
86
87 for i := 0; i < goroutines; i++ {
88 go func() {
89 defer func() {
90 // Intentional panic successfully recovered - notify counter channel
91 recover()
92 counterC <- 1
93 }()
94
95 l.Warnf("Placeholder template message: %v", "In this test, logging a warning causes a panic.")
96 }()
97 }
98
99 // All goroutines should complete before timeout
100 var counter int
101 for {
102 select {
103 case <-counterC:
104 counter++
105 if counter == goroutines {
106 return
107 }
108 case <-timeIsUp:
109 t.Errorf("Unable to log warnings with --panicOnWarning within alloted time of: %v seconds. Investigate possible mutex locking on panic in distinct warning logger.", timeOutSeconds)
110 return
111 }
112 }
113 }
114
115 func TestFirstUpper(t *testing.T) {
116 for i, this := range []struct {
117 in string
118 expect string
119 }{
120 {"foo", "Foo"},
121 {"foo bar", "Foo bar"},
122 {"Foo Bar", "Foo Bar"},
123 {"", ""},
124 {"å", "Å"},
125 } {
126 result := FirstUpper(this.in)
127 if result != this.expect {
128 t.Errorf("[%d] got %s but expected %s", i, result, this.expect)
129 }
130 }
131 }
132
133 func TestHasStringsPrefix(t *testing.T) {
134 for i, this := range []struct {
135 s []string
136 prefix []string
137 expect bool
138 }{
139 {[]string{"a"}, []string{"a"}, true},
140 {[]string{}, []string{}, true},
141 {[]string{"a", "b", "c"}, []string{"a", "b"}, true},
142 {[]string{"d", "a", "b", "c"}, []string{"a", "b"}, false},
143 {[]string{"abra", "ca", "dabra"}, []string{"abra", "ca"}, true},
144 {[]string{"abra", "ca"}, []string{"abra", "ca", "dabra"}, false},
145 } {
146 result := HasStringsPrefix(this.s, this.prefix)
147 if result != this.expect {
148 t.Fatalf("[%d] got %t but expected %t", i, result, this.expect)
149 }
150 }
151 }
152
153 func TestHasStringsSuffix(t *testing.T) {
154 for i, this := range []struct {
155 s []string
156 suffix []string
157 expect bool
158 }{
159 {[]string{"a"}, []string{"a"}, true},
160 {[]string{}, []string{}, true},
161 {[]string{"a", "b", "c"}, []string{"b", "c"}, true},
162 {[]string{"abra", "ca", "dabra"}, []string{"abra", "ca"}, false},
163 {[]string{"abra", "ca", "dabra"}, []string{"ca", "dabra"}, true},
164 } {
165 result := HasStringsSuffix(this.s, this.suffix)
166 if result != this.expect {
167 t.Fatalf("[%d] got %t but expected %t", i, result, this.expect)
168 }
169 }
170 }
171
172 var containsTestText = (`На берегу пустынных волн
173 Стоял он, дум великих полн,
174 И вдаль глядел. Пред ним широко
175 Река неслася; бедный чёлн
176 По ней стремился одиноко.
177 По мшистым, топким берегам
178 Чернели избы здесь и там,
179 Приют убогого чухонца;
180 И лес, неведомый лучам
181 В тумане спрятанного солнца,
182 Кругом шумел.
183
184 Τη γλώσσα μου έδωσαν ελληνική
185 το σπίτι φτωχικό στις αμμουδιές του Ομήρου.
186 Μονάχη έγνοια η γλώσσα μου στις αμμουδιές του Ομήρου.
187
188 από το Άξιον Εστί
189 του Οδυσσέα Ελύτη
190
191 Sîne klâwen durh die wolken sint geslagen,
192 er stîget ûf mit grôzer kraft,
193 ich sih in grâwen tägelîch als er wil tagen,
194 den tac, der im geselleschaft
195 erwenden wil, dem werden man,
196 den ich mit sorgen în verliez.
197 ich bringe in hinnen, ob ich kan.
198 sîn vil manegiu tugent michz leisten hiez.
199 `)
200
201 var containsBenchTestData = []struct {
202 v1 string
203 v2 []byte
204 expect bool
205 }{
206 {"abc", []byte("a"), true},
207 {"abc", []byte("b"), true},
208 {"abcdefg", []byte("efg"), true},
209 {"abc", []byte("d"), false},
210 {containsTestText, []byte("стремился"), true},
211 {containsTestText, []byte(containsTestText[10:80]), true},
212 {containsTestText, []byte(containsTestText[100:111]), true},
213 {containsTestText, []byte(containsTestText[len(containsTestText)-100 : len(containsTestText)-10]), true},
214 {containsTestText, []byte(containsTestText[len(containsTestText)-20:]), true},
215 {containsTestText, []byte("notfound"), false},
216 }
217
218 // some corner cases
219 var containsAdditionalTestData = []struct {
220 v1 string
221 v2 []byte
222 expect bool
223 }{
224 {"", nil, false},
225 {"", []byte("a"), false},
226 {"a", []byte(""), false},
227 {"", []byte(""), false},
228 }
229
230 func TestSliceToLower(t *testing.T) {
231 t.Parallel()
232 tests := []struct {
233 value []string
234 expected []string
235 }{
236 {[]string{"a", "b", "c"}, []string{"a", "b", "c"}},
237 {[]string{"a", "B", "c"}, []string{"a", "b", "c"}},
238 {[]string{"A", "B", "C"}, []string{"a", "b", "c"}},
239 }
240
241 for _, test := range tests {
242 res := SliceToLower(test.value)
243 for i, val := range res {
244 if val != test.expected[i] {
245 t.Errorf("Case mismatch. Expected %s, got %s", test.expected[i], res[i])
246 }
247 }
248 }
249 }
250
251 func TestReaderContains(t *testing.T) {
252 c := qt.New(t)
253 for i, this := range append(containsBenchTestData, containsAdditionalTestData...) {
254 result := ReaderContains(strings.NewReader(this.v1), this.v2)
255 if result != this.expect {
256 t.Errorf("[%d] got %t but expected %t", i, result, this.expect)
257 }
258 }
259
260 c.Assert(ReaderContains(nil, []byte("a")), qt.Equals, false)
261 c.Assert(ReaderContains(nil, nil), qt.Equals, false)
262 }
263
264 func TestGetTitleFunc(t *testing.T) {
265 title := "somewhere over the rainbow"
266 c := qt.New(t)
267
268 c.Assert(GetTitleFunc("go")(title), qt.Equals, "Somewhere Over The Rainbow")
269 c.Assert(GetTitleFunc("chicago")(title), qt.Equals, "Somewhere over the Rainbow")
270 c.Assert(GetTitleFunc("Chicago")(title), qt.Equals, "Somewhere over the Rainbow")
271 c.Assert(GetTitleFunc("ap")(title), qt.Equals, "Somewhere Over the Rainbow")
272 c.Assert(GetTitleFunc("ap")(title), qt.Equals, "Somewhere Over the Rainbow")
273 c.Assert(GetTitleFunc("")(title), qt.Equals, "Somewhere Over the Rainbow")
274 c.Assert(GetTitleFunc("unknown")(title), qt.Equals, "Somewhere Over the Rainbow")
275 }
276
277 func BenchmarkReaderContains(b *testing.B) {
278 b.ResetTimer()
279 for i := 0; i < b.N; i++ {
280 for i, this := range containsBenchTestData {
281 result := ReaderContains(strings.NewReader(this.v1), this.v2)
282 if result != this.expect {
283 b.Errorf("[%d] got %t but expected %t", i, result, this.expect)
284 }
285 }
286 }
287 }
288
289 func TestUniqueStrings(t *testing.T) {
290 in := []string{"a", "b", "a", "b", "c", "", "a", "", "d"}
291 output := UniqueStrings(in)
292 expected := []string{"a", "b", "c", "", "d"}
293 if !reflect.DeepEqual(output, expected) {
294 t.Errorf("Expected %#v, got %#v\n", expected, output)
295 }
296 }
297
298 func TestUniqueStringsReuse(t *testing.T) {
299 in := []string{"a", "b", "a", "b", "c", "", "a", "", "d"}
300 output := UniqueStringsReuse(in)
301 expected := []string{"a", "b", "c", "", "d"}
302 if !reflect.DeepEqual(output, expected) {
303 t.Errorf("Expected %#v, got %#v\n", expected, output)
304 }
305 }
306
307 func TestUniqueStringsSorted(t *testing.T) {
308 c := qt.New(t)
309 in := []string{"a", "a", "b", "c", "b", "", "a", "", "d"}
310 output := UniqueStringsSorted(in)
311 expected := []string{"", "a", "b", "c", "d"}
312 c.Assert(output, qt.DeepEquals, expected)
313 c.Assert(UniqueStringsSorted(nil), qt.IsNil)
314 }
315
316 func TestFindAvailablePort(t *testing.T) {
317 c := qt.New(t)
318 addr, err := FindAvailablePort()
319 c.Assert(err, qt.IsNil)
320 c.Assert(addr, qt.Not(qt.IsNil))
321 c.Assert(addr.Port > 0, qt.Equals, true)
322 }
323
324 func TestFastMD5FromFile(t *testing.T) {
325 fs := afero.NewMemMapFs()
326
327 if err := afero.WriteFile(fs, "small.txt", []byte("abc"), 0777); err != nil {
328 t.Fatal(err)
329 }
330
331 if err := afero.WriteFile(fs, "small2.txt", []byte("abd"), 0777); err != nil {
332 t.Fatal(err)
333 }
334
335 if err := afero.WriteFile(fs, "bigger.txt", []byte(strings.Repeat("a bc d e", 100)), 0777); err != nil {
336 t.Fatal(err)
337 }
338
339 if err := afero.WriteFile(fs, "bigger2.txt", []byte(strings.Repeat("c d e f g", 100)), 0777); err != nil {
340 t.Fatal(err)
341 }
342
343 c := qt.New(t)
344
345 sf1, err := fs.Open("small.txt")
346 c.Assert(err, qt.IsNil)
347 sf2, err := fs.Open("small2.txt")
348 c.Assert(err, qt.IsNil)
349
350 bf1, err := fs.Open("bigger.txt")
351 c.Assert(err, qt.IsNil)
352 bf2, err := fs.Open("bigger2.txt")
353 c.Assert(err, qt.IsNil)
354
355 defer sf1.Close()
356 defer sf2.Close()
357 defer bf1.Close()
358 defer bf2.Close()
359
360 m1, err := MD5FromFileFast(sf1)
361 c.Assert(err, qt.IsNil)
362 c.Assert(m1, qt.Equals, "e9c8989b64b71a88b4efb66ad05eea96")
363
364 m2, err := MD5FromFileFast(sf2)
365 c.Assert(err, qt.IsNil)
366 c.Assert(m2, qt.Not(qt.Equals), m1)
367
368 m3, err := MD5FromFileFast(bf1)
369 c.Assert(err, qt.IsNil)
370 c.Assert(m3, qt.Not(qt.Equals), m2)
371
372 m4, err := MD5FromFileFast(bf2)
373 c.Assert(err, qt.IsNil)
374 c.Assert(m4, qt.Not(qt.Equals), m3)
375
376 m5, err := MD5FromReader(bf2)
377 c.Assert(err, qt.IsNil)
378 c.Assert(m5, qt.Not(qt.Equals), m4)
379 }
380
381 func BenchmarkMD5FromFileFast(b *testing.B) {
382 fs := afero.NewMemMapFs()
383
384 for _, full := range []bool{false, true} {
385 b.Run(fmt.Sprintf("full=%t", full), func(b *testing.B) {
386 for i := 0; i < b.N; i++ {
387 b.StopTimer()
388 if err := afero.WriteFile(fs, "file.txt", []byte(strings.Repeat("1234567890", 2000)), 0777); err != nil {
389 b.Fatal(err)
390 }
391 f, err := fs.Open("file.txt")
392 if err != nil {
393 b.Fatal(err)
394 }
395 b.StartTimer()
396 if full {
397 if _, err := MD5FromReader(f); err != nil {
398 b.Fatal(err)
399 }
400 } else {
401 if _, err := MD5FromFileFast(f); err != nil {
402 b.Fatal(err)
403 }
404 }
405 f.Close()
406 }
407 })
408 }
409 }
410
411 func BenchmarkUniqueStrings(b *testing.B) {
412 input := []string{"a", "b", "d", "e", "d", "h", "a", "i"}
413
414 b.Run("Safe", func(b *testing.B) {
415 for i := 0; i < b.N; i++ {
416 result := UniqueStrings(input)
417 if len(result) != 6 {
418 b.Fatal(fmt.Sprintf("invalid count: %d", len(result)))
419 }
420 }
421 })
422
423 b.Run("Reuse slice", func(b *testing.B) {
424 b.StopTimer()
425 inputs := make([][]string, b.N)
426 for i := 0; i < b.N; i++ {
427 inputc := make([]string, len(input))
428 copy(inputc, input)
429 inputs[i] = inputc
430 }
431 b.StartTimer()
432 for i := 0; i < b.N; i++ {
433 inputc := inputs[i]
434
435 result := UniqueStringsReuse(inputc)
436 if len(result) != 6 {
437 b.Fatal(fmt.Sprintf("invalid count: %d", len(result)))
438 }
439 }
440 })
441
442 b.Run("Reuse slice sorted", func(b *testing.B) {
443 b.StopTimer()
444 inputs := make([][]string, b.N)
445 for i := 0; i < b.N; i++ {
446 inputc := make([]string, len(input))
447 copy(inputc, input)
448 inputs[i] = inputc
449 }
450 b.StartTimer()
451 for i := 0; i < b.N; i++ {
452 inputc := inputs[i]
453
454 result := UniqueStringsSorted(inputc)
455 if len(result) != 6 {
456 b.Fatal(fmt.Sprintf("invalid count: %d", len(result)))
457 }
458 }
459 })
460 }
461
462 func TestHashString(t *testing.T) {
463 c := qt.New(t)
464
465 c.Assert(HashString("a", "b"), qt.Equals, "2712570657419664240")
466 c.Assert(HashString("ab"), qt.Equals, "590647783936702392")
467 }