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