github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/helpers/general_test.go (about)

     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  }