Source file src/runtime/string_test.go

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package runtime_test
     6  
     7  import (
     8  	"runtime"
     9  	"strconv"
    10  	"strings"
    11  	"testing"
    12  	"unicode/utf8"
    13  )
    14  
    15  // Strings and slices that don't escape and fit into tmpBuf are stack allocated,
    16  // which defeats using AllocsPerRun to test other optimizations.
    17  const sizeNoStack = 100
    18  
    19  func BenchmarkCompareStringEqual(b *testing.B) {
    20  	bytes := []byte("Hello Gophers!")
    21  	s1, s2 := string(bytes), string(bytes)
    22  	for i := 0; i < b.N; i++ {
    23  		if s1 != s2 {
    24  			b.Fatal("s1 != s2")
    25  		}
    26  	}
    27  }
    28  
    29  func BenchmarkCompareStringIdentical(b *testing.B) {
    30  	s1 := "Hello Gophers!"
    31  	s2 := s1
    32  	for i := 0; i < b.N; i++ {
    33  		if s1 != s2 {
    34  			b.Fatal("s1 != s2")
    35  		}
    36  	}
    37  }
    38  
    39  func BenchmarkCompareStringSameLength(b *testing.B) {
    40  	s1 := "Hello Gophers!"
    41  	s2 := "Hello, Gophers"
    42  	for i := 0; i < b.N; i++ {
    43  		if s1 == s2 {
    44  			b.Fatal("s1 == s2")
    45  		}
    46  	}
    47  }
    48  
    49  func BenchmarkCompareStringDifferentLength(b *testing.B) {
    50  	s1 := "Hello Gophers!"
    51  	s2 := "Hello, Gophers!"
    52  	for i := 0; i < b.N; i++ {
    53  		if s1 == s2 {
    54  			b.Fatal("s1 == s2")
    55  		}
    56  	}
    57  }
    58  
    59  func BenchmarkCompareStringBigUnaligned(b *testing.B) {
    60  	bytes := make([]byte, 0, 1<<20)
    61  	for len(bytes) < 1<<20 {
    62  		bytes = append(bytes, "Hello Gophers!"...)
    63  	}
    64  	s1, s2 := string(bytes), "hello"+string(bytes)
    65  	for i := 0; i < b.N; i++ {
    66  		if s1 != s2[len("hello"):] {
    67  			b.Fatal("s1 != s2")
    68  		}
    69  	}
    70  	b.SetBytes(int64(len(s1)))
    71  }
    72  
    73  func BenchmarkCompareStringBig(b *testing.B) {
    74  	bytes := make([]byte, 0, 1<<20)
    75  	for len(bytes) < 1<<20 {
    76  		bytes = append(bytes, "Hello Gophers!"...)
    77  	}
    78  	s1, s2 := string(bytes), string(bytes)
    79  	for i := 0; i < b.N; i++ {
    80  		if s1 != s2 {
    81  			b.Fatal("s1 != s2")
    82  		}
    83  	}
    84  	b.SetBytes(int64(len(s1)))
    85  }
    86  
    87  func BenchmarkConcatStringAndBytes(b *testing.B) {
    88  	s1 := []byte("Gophers!")
    89  	for i := 0; i < b.N; i++ {
    90  		_ = "Hello " + string(s1)
    91  	}
    92  }
    93  
    94  var escapeString string
    95  
    96  func BenchmarkSliceByteToString(b *testing.B) {
    97  	buf := []byte{'!'}
    98  	for n := 0; n < 8; n++ {
    99  		b.Run(strconv.Itoa(len(buf)), func(b *testing.B) {
   100  			for i := 0; i < b.N; i++ {
   101  				escapeString = string(buf)
   102  			}
   103  		})
   104  		buf = append(buf, buf...)
   105  	}
   106  }
   107  
   108  var stringdata = []struct{ name, data string }{
   109  	{"ASCII", "01234567890"},
   110  	{"Japanese", "日本語日本語日本語"},
   111  	{"MixedLength", "$Ѐࠀက퀀𐀀\U00040000\U0010FFFF"},
   112  }
   113  
   114  var sinkInt int
   115  
   116  func BenchmarkRuneCount(b *testing.B) {
   117  	// Each sub-benchmark counts the runes in a string in a different way.
   118  	b.Run("lenruneslice", func(b *testing.B) {
   119  		for _, sd := range stringdata {
   120  			b.Run(sd.name, func(b *testing.B) {
   121  				for i := 0; i < b.N; i++ {
   122  					sinkInt += len([]rune(sd.data))
   123  				}
   124  			})
   125  		}
   126  	})
   127  	b.Run("rangeloop", func(b *testing.B) {
   128  		for _, sd := range stringdata {
   129  			b.Run(sd.name, func(b *testing.B) {
   130  				for i := 0; i < b.N; i++ {
   131  					n := 0
   132  					for range sd.data {
   133  						n++
   134  					}
   135  					sinkInt += n
   136  				}
   137  			})
   138  		}
   139  	})
   140  	b.Run("utf8.RuneCountInString", func(b *testing.B) {
   141  		for _, sd := range stringdata {
   142  			b.Run(sd.name, func(b *testing.B) {
   143  				for i := 0; i < b.N; i++ {
   144  					sinkInt += utf8.RuneCountInString(sd.data)
   145  				}
   146  			})
   147  		}
   148  	})
   149  }
   150  
   151  func BenchmarkRuneIterate(b *testing.B) {
   152  	b.Run("range", func(b *testing.B) {
   153  		for _, sd := range stringdata {
   154  			b.Run(sd.name, func(b *testing.B) {
   155  				for i := 0; i < b.N; i++ {
   156  					for range sd.data {
   157  					}
   158  				}
   159  			})
   160  		}
   161  	})
   162  	b.Run("range1", func(b *testing.B) {
   163  		for _, sd := range stringdata {
   164  			b.Run(sd.name, func(b *testing.B) {
   165  				for i := 0; i < b.N; i++ {
   166  					for range sd.data {
   167  					}
   168  				}
   169  			})
   170  		}
   171  	})
   172  	b.Run("range2", func(b *testing.B) {
   173  		for _, sd := range stringdata {
   174  			b.Run(sd.name, func(b *testing.B) {
   175  				for i := 0; i < b.N; i++ {
   176  					for range sd.data {
   177  					}
   178  				}
   179  			})
   180  		}
   181  	})
   182  }
   183  
   184  func BenchmarkArrayEqual(b *testing.B) {
   185  	a1 := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
   186  	a2 := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
   187  	b.ResetTimer()
   188  	for i := 0; i < b.N; i++ {
   189  		if a1 != a2 {
   190  			b.Fatal("not equal")
   191  		}
   192  	}
   193  }
   194  
   195  func TestStringW(t *testing.T) {
   196  	strings := []string{
   197  		"hello",
   198  		"a\u5566\u7788b",
   199  	}
   200  
   201  	for _, s := range strings {
   202  		var b []uint16
   203  		for _, c := range s {
   204  			b = append(b, uint16(c))
   205  			if c != rune(uint16(c)) {
   206  				t.Errorf("bad test: stringW can't handle >16 bit runes")
   207  			}
   208  		}
   209  		b = append(b, 0)
   210  		r := runtime.GostringW(b)
   211  		if r != s {
   212  			t.Errorf("gostringW(%v) = %s, want %s", b, r, s)
   213  		}
   214  	}
   215  }
   216  
   217  func TestLargeStringConcat(t *testing.T) {
   218  	output := runTestProg(t, "testprog", "stringconcat")
   219  	want := "panic: " + strings.Repeat("0", 1<<10) + strings.Repeat("1", 1<<10) +
   220  		strings.Repeat("2", 1<<10) + strings.Repeat("3", 1<<10)
   221  	if !strings.HasPrefix(output, want) {
   222  		t.Fatalf("output does not start with %q:\n%s", want, output)
   223  	}
   224  }
   225  
   226  func TestCompareTempString(t *testing.T) {
   227  	s := strings.Repeat("x", sizeNoStack)
   228  	b := []byte(s)
   229  	n := testing.AllocsPerRun(1000, func() {
   230  		if string(b) != s {
   231  			t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
   232  		}
   233  		if string(b) == s {
   234  		} else {
   235  			t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
   236  		}
   237  	})
   238  	if n != 0 {
   239  		t.Fatalf("want 0 allocs, got %v", n)
   240  	}
   241  }
   242  
   243  func TestStringIndexHaystack(t *testing.T) {
   244  	// See issue 25864.
   245  	haystack := []byte("hello")
   246  	needle := "ll"
   247  	n := testing.AllocsPerRun(1000, func() {
   248  		if strings.Index(string(haystack), needle) != 2 {
   249  			t.Fatalf("needle not found")
   250  		}
   251  	})
   252  	if n != 0 {
   253  		t.Fatalf("want 0 allocs, got %v", n)
   254  	}
   255  }
   256  
   257  func TestStringIndexNeedle(t *testing.T) {
   258  	// See issue 25864.
   259  	haystack := "hello"
   260  	needle := []byte("ll")
   261  	n := testing.AllocsPerRun(1000, func() {
   262  		if strings.Index(haystack, string(needle)) != 2 {
   263  			t.Fatalf("needle not found")
   264  		}
   265  	})
   266  	if n != 0 {
   267  		t.Fatalf("want 0 allocs, got %v", n)
   268  	}
   269  }
   270  
   271  func TestStringOnStack(t *testing.T) {
   272  	s := ""
   273  	for i := 0; i < 3; i++ {
   274  		s = "a" + s + "b" + s + "c"
   275  	}
   276  
   277  	if want := "aaabcbabccbaabcbabccc"; s != want {
   278  		t.Fatalf("want: '%v', got '%v'", want, s)
   279  	}
   280  }
   281  
   282  func TestIntString(t *testing.T) {
   283  	// Non-escaping result of intstring.
   284  	s := ""
   285  	for i := rune(0); i < 4; i++ {
   286  		s += string(i+'0') + string(i+'0'+1)
   287  	}
   288  	if want := "01122334"; s != want {
   289  		t.Fatalf("want '%v', got '%v'", want, s)
   290  	}
   291  
   292  	// Escaping result of intstring.
   293  	var a [4]string
   294  	for i := rune(0); i < 4; i++ {
   295  		a[i] = string(i + '0')
   296  	}
   297  	s = a[0] + a[1] + a[2] + a[3]
   298  	if want := "0123"; s != want {
   299  		t.Fatalf("want '%v', got '%v'", want, s)
   300  	}
   301  }
   302  
   303  func TestIntStringAllocs(t *testing.T) {
   304  	unknown := '0'
   305  	n := testing.AllocsPerRun(1000, func() {
   306  		s1 := string(unknown)
   307  		s2 := string(unknown + 1)
   308  		if s1 == s2 {
   309  			t.Fatalf("bad")
   310  		}
   311  	})
   312  	if n != 0 {
   313  		t.Fatalf("want 0 allocs, got %v", n)
   314  	}
   315  }
   316  
   317  func TestRangeStringCast(t *testing.T) {
   318  	s := strings.Repeat("x", sizeNoStack)
   319  	n := testing.AllocsPerRun(1000, func() {
   320  		for i, c := range []byte(s) {
   321  			if c != s[i] {
   322  				t.Fatalf("want '%c' at pos %v, got '%c'", s[i], i, c)
   323  			}
   324  		}
   325  	})
   326  	if n != 0 {
   327  		t.Fatalf("want 0 allocs, got %v", n)
   328  	}
   329  }
   330  
   331  func isZeroed(b []byte) bool {
   332  	for _, x := range b {
   333  		if x != 0 {
   334  			return false
   335  		}
   336  	}
   337  	return true
   338  }
   339  
   340  func isZeroedR(r []rune) bool {
   341  	for _, x := range r {
   342  		if x != 0 {
   343  			return false
   344  		}
   345  	}
   346  	return true
   347  }
   348  
   349  func TestString2Slice(t *testing.T) {
   350  	// Make sure we don't return slices that expose
   351  	// an unzeroed section of stack-allocated temp buf
   352  	// between len and cap. See issue 14232.
   353  	s := "foož"
   354  	b := ([]byte)(s)
   355  	if !isZeroed(b[len(b):cap(b)]) {
   356  		t.Errorf("extra bytes not zeroed")
   357  	}
   358  	r := ([]rune)(s)
   359  	if !isZeroedR(r[len(r):cap(r)]) {
   360  		t.Errorf("extra runes not zeroed")
   361  	}
   362  }
   363  
   364  const intSize = 32 << (^uint(0) >> 63)
   365  
   366  type atoi64Test struct {
   367  	in  string
   368  	out int64
   369  	ok  bool
   370  }
   371  
   372  var atoi64tests = []atoi64Test{
   373  	{"", 0, false},
   374  	{"0", 0, true},
   375  	{"-0", 0, true},
   376  	{"1", 1, true},
   377  	{"-1", -1, true},
   378  	{"12345", 12345, true},
   379  	{"-12345", -12345, true},
   380  	{"012345", 12345, true},
   381  	{"-012345", -12345, true},
   382  	{"12345x", 0, false},
   383  	{"-12345x", 0, false},
   384  	{"98765432100", 98765432100, true},
   385  	{"-98765432100", -98765432100, true},
   386  	{"20496382327982653440", 0, false},
   387  	{"-20496382327982653440", 0, false},
   388  	{"9223372036854775807", 1<<63 - 1, true},
   389  	{"-9223372036854775807", -(1<<63 - 1), true},
   390  	{"9223372036854775808", 0, false},
   391  	{"-9223372036854775808", -1 << 63, true},
   392  	{"9223372036854775809", 0, false},
   393  	{"-9223372036854775809", 0, false},
   394  }
   395  
   396  func TestAtoi(t *testing.T) {
   397  	switch intSize {
   398  	case 32:
   399  		for i := range atoi32tests {
   400  			test := &atoi32tests[i]
   401  			out, ok := runtime.Atoi(test.in)
   402  			if test.out != int32(out) || test.ok != ok {
   403  				t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)",
   404  					test.in, out, ok, test.out, test.ok)
   405  			}
   406  		}
   407  	case 64:
   408  		for i := range atoi64tests {
   409  			test := &atoi64tests[i]
   410  			out, ok := runtime.Atoi(test.in)
   411  			if test.out != int64(out) || test.ok != ok {
   412  				t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)",
   413  					test.in, out, ok, test.out, test.ok)
   414  			}
   415  		}
   416  	}
   417  }
   418  
   419  type atoi32Test struct {
   420  	in  string
   421  	out int32
   422  	ok  bool
   423  }
   424  
   425  var atoi32tests = []atoi32Test{
   426  	{"", 0, false},
   427  	{"0", 0, true},
   428  	{"-0", 0, true},
   429  	{"1", 1, true},
   430  	{"-1", -1, true},
   431  	{"12345", 12345, true},
   432  	{"-12345", -12345, true},
   433  	{"012345", 12345, true},
   434  	{"-012345", -12345, true},
   435  	{"12345x", 0, false},
   436  	{"-12345x", 0, false},
   437  	{"987654321", 987654321, true},
   438  	{"-987654321", -987654321, true},
   439  	{"2147483647", 1<<31 - 1, true},
   440  	{"-2147483647", -(1<<31 - 1), true},
   441  	{"2147483648", 0, false},
   442  	{"-2147483648", -1 << 31, true},
   443  	{"2147483649", 0, false},
   444  	{"-2147483649", 0, false},
   445  }
   446  
   447  func TestAtoi32(t *testing.T) {
   448  	for i := range atoi32tests {
   449  		test := &atoi32tests[i]
   450  		out, ok := runtime.Atoi32(test.in)
   451  		if test.out != out || test.ok != ok {
   452  			t.Errorf("atoi32(%q) = (%v, %v) want (%v, %v)",
   453  				test.in, out, ok, test.out, test.ok)
   454  		}
   455  	}
   456  }
   457  

View as plain text