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 }