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