...

Source file src/runtime/string_test.go

Documentation: runtime

     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 TestConcatTempString(t *testing.T) {
   227  	s := "bytes"
   228  	b := []byte(s)
   229  	n := testing.AllocsPerRun(1000, func() {
   230  		if "prefix "+string(b)+" suffix" != "prefix bytes suffix" {
   231  			t.Fatalf("strings are not equal: '%v' and '%v'", "prefix "+string(b)+" suffix", "prefix bytes suffix")
   232  		}
   233  	})
   234  	if n != 0 {
   235  		t.Fatalf("want 0 allocs, got %v", n)
   236  	}
   237  }
   238  
   239  func TestCompareTempString(t *testing.T) {
   240  	s := strings.Repeat("x", sizeNoStack)
   241  	b := []byte(s)
   242  	n := testing.AllocsPerRun(1000, func() {
   243  		if string(b) != s {
   244  			t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
   245  		}
   246  		if string(b) < s {
   247  			t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
   248  		}
   249  		if string(b) > s {
   250  			t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
   251  		}
   252  		if string(b) == s {
   253  		} else {
   254  			t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
   255  		}
   256  		if string(b) <= s {
   257  		} else {
   258  			t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
   259  		}
   260  		if string(b) >= s {
   261  		} else {
   262  			t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
   263  		}
   264  	})
   265  	if n != 0 {
   266  		t.Fatalf("want 0 allocs, got %v", n)
   267  	}
   268  }
   269  
   270  func TestStringIndexHaystack(t *testing.T) {
   271  	// See issue 25864.
   272  	haystack := []byte("hello")
   273  	needle := "ll"
   274  	n := testing.AllocsPerRun(1000, func() {
   275  		if strings.Index(string(haystack), needle) != 2 {
   276  			t.Fatalf("needle not found")
   277  		}
   278  	})
   279  	if n != 0 {
   280  		t.Fatalf("want 0 allocs, got %v", n)
   281  	}
   282  }
   283  
   284  func TestStringIndexNeedle(t *testing.T) {
   285  	// See issue 25864.
   286  	haystack := "hello"
   287  	needle := []byte("ll")
   288  	n := testing.AllocsPerRun(1000, func() {
   289  		if strings.Index(haystack, string(needle)) != 2 {
   290  			t.Fatalf("needle not found")
   291  		}
   292  	})
   293  	if n != 0 {
   294  		t.Fatalf("want 0 allocs, got %v", n)
   295  	}
   296  }
   297  
   298  func TestStringOnStack(t *testing.T) {
   299  	s := ""
   300  	for i := 0; i < 3; i++ {
   301  		s = "a" + s + "b" + s + "c"
   302  	}
   303  
   304  	if want := "aaabcbabccbaabcbabccc"; s != want {
   305  		t.Fatalf("want: '%v', got '%v'", want, s)
   306  	}
   307  }
   308  
   309  func TestIntString(t *testing.T) {
   310  	// Non-escaping result of intstring.
   311  	s := ""
   312  	for i := rune(0); i < 4; i++ {
   313  		s += string(i+'0') + string(i+'0'+1)
   314  	}
   315  	if want := "01122334"; s != want {
   316  		t.Fatalf("want '%v', got '%v'", want, s)
   317  	}
   318  
   319  	// Escaping result of intstring.
   320  	var a [4]string
   321  	for i := rune(0); i < 4; i++ {
   322  		a[i] = string(i + '0')
   323  	}
   324  	s = a[0] + a[1] + a[2] + a[3]
   325  	if want := "0123"; s != want {
   326  		t.Fatalf("want '%v', got '%v'", want, s)
   327  	}
   328  }
   329  
   330  func TestIntStringAllocs(t *testing.T) {
   331  	unknown := '0'
   332  	n := testing.AllocsPerRun(1000, func() {
   333  		s1 := string(unknown)
   334  		s2 := string(unknown + 1)
   335  		if s1 == s2 {
   336  			t.Fatalf("bad")
   337  		}
   338  	})
   339  	if n != 0 {
   340  		t.Fatalf("want 0 allocs, got %v", n)
   341  	}
   342  }
   343  
   344  func TestRangeStringCast(t *testing.T) {
   345  	s := strings.Repeat("x", sizeNoStack)
   346  	n := testing.AllocsPerRun(1000, func() {
   347  		for i, c := range []byte(s) {
   348  			if c != s[i] {
   349  				t.Fatalf("want '%c' at pos %v, got '%c'", s[i], i, c)
   350  			}
   351  		}
   352  	})
   353  	if n != 0 {
   354  		t.Fatalf("want 0 allocs, got %v", n)
   355  	}
   356  }
   357  
   358  func isZeroed(b []byte) bool {
   359  	for _, x := range b {
   360  		if x != 0 {
   361  			return false
   362  		}
   363  	}
   364  	return true
   365  }
   366  
   367  func isZeroedR(r []rune) bool {
   368  	for _, x := range r {
   369  		if x != 0 {
   370  			return false
   371  		}
   372  	}
   373  	return true
   374  }
   375  
   376  func TestString2Slice(t *testing.T) {
   377  	// Make sure we don't return slices that expose
   378  	// an unzeroed section of stack-allocated temp buf
   379  	// between len and cap. See issue 14232.
   380  	s := "foož"
   381  	b := ([]byte)(s)
   382  	if !isZeroed(b[len(b):cap(b)]) {
   383  		t.Errorf("extra bytes not zeroed")
   384  	}
   385  	r := ([]rune)(s)
   386  	if !isZeroedR(r[len(r):cap(r)]) {
   387  		t.Errorf("extra runes not zeroed")
   388  	}
   389  }
   390  
   391  const intSize = 32 << (^uint(0) >> 63)
   392  
   393  type atoi64Test struct {
   394  	in  string
   395  	out int64
   396  	ok  bool
   397  }
   398  
   399  var atoi64tests = []atoi64Test{
   400  	{"", 0, false},
   401  	{"0", 0, true},
   402  	{"-0", 0, true},
   403  	{"1", 1, true},
   404  	{"-1", -1, true},
   405  	{"12345", 12345, true},
   406  	{"-12345", -12345, true},
   407  	{"012345", 12345, true},
   408  	{"-012345", -12345, true},
   409  	{"12345x", 0, false},
   410  	{"-12345x", 0, false},
   411  	{"98765432100", 98765432100, true},
   412  	{"-98765432100", -98765432100, true},
   413  	{"20496382327982653440", 0, false},
   414  	{"-20496382327982653440", 0, false},
   415  	{"9223372036854775807", 1<<63 - 1, true},
   416  	{"-9223372036854775807", -(1<<63 - 1), true},
   417  	{"9223372036854775808", 0, false},
   418  	{"-9223372036854775808", -1 << 63, true},
   419  	{"9223372036854775809", 0, false},
   420  	{"-9223372036854775809", 0, false},
   421  }
   422  
   423  func TestAtoi(t *testing.T) {
   424  	switch intSize {
   425  	case 32:
   426  		for i := range atoi32tests {
   427  			test := &atoi32tests[i]
   428  			out, ok := runtime.Atoi(test.in)
   429  			if test.out != int32(out) || test.ok != ok {
   430  				t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)",
   431  					test.in, out, ok, test.out, test.ok)
   432  			}
   433  		}
   434  	case 64:
   435  		for i := range atoi64tests {
   436  			test := &atoi64tests[i]
   437  			out, ok := runtime.Atoi(test.in)
   438  			if test.out != int64(out) || test.ok != ok {
   439  				t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)",
   440  					test.in, out, ok, test.out, test.ok)
   441  			}
   442  		}
   443  	}
   444  }
   445  
   446  type atoi32Test struct {
   447  	in  string
   448  	out int32
   449  	ok  bool
   450  }
   451  
   452  var atoi32tests = []atoi32Test{
   453  	{"", 0, false},
   454  	{"0", 0, true},
   455  	{"-0", 0, true},
   456  	{"1", 1, true},
   457  	{"-1", -1, true},
   458  	{"12345", 12345, true},
   459  	{"-12345", -12345, true},
   460  	{"012345", 12345, true},
   461  	{"-012345", -12345, true},
   462  	{"12345x", 0, false},
   463  	{"-12345x", 0, false},
   464  	{"987654321", 987654321, true},
   465  	{"-987654321", -987654321, true},
   466  	{"2147483647", 1<<31 - 1, true},
   467  	{"-2147483647", -(1<<31 - 1), true},
   468  	{"2147483648", 0, false},
   469  	{"-2147483648", -1 << 31, true},
   470  	{"2147483649", 0, false},
   471  	{"-2147483649", 0, false},
   472  }
   473  
   474  func TestAtoi32(t *testing.T) {
   475  	for i := range atoi32tests {
   476  		test := &atoi32tests[i]
   477  		out, ok := runtime.Atoi32(test.in)
   478  		if test.out != out || test.ok != ok {
   479  			t.Errorf("atoi32(%q) = (%v, %v) want (%v, %v)",
   480  				test.in, out, ok, test.out, test.ok)
   481  		}
   482  	}
   483  }
   484  
   485  func TestParseByteCount(t *testing.T) {
   486  	for _, test := range []struct {
   487  		in  string
   488  		out int64
   489  		ok  bool
   490  	}{
   491  		// Good numeric inputs.
   492  		{"1", 1, true},
   493  		{"12345", 12345, true},
   494  		{"012345", 12345, true},
   495  		{"98765432100", 98765432100, true},
   496  		{"9223372036854775807", 1<<63 - 1, true},
   497  
   498  		// Good trivial suffix inputs.
   499  		{"1B", 1, true},
   500  		{"12345B", 12345, true},
   501  		{"012345B", 12345, true},
   502  		{"98765432100B", 98765432100, true},
   503  		{"9223372036854775807B", 1<<63 - 1, true},
   504  
   505  		// Good binary suffix inputs.
   506  		{"1KiB", 1 << 10, true},
   507  		{"05KiB", 5 << 10, true},
   508  		{"1MiB", 1 << 20, true},
   509  		{"10MiB", 10 << 20, true},
   510  		{"1GiB", 1 << 30, true},
   511  		{"100GiB", 100 << 30, true},
   512  		{"1TiB", 1 << 40, true},
   513  		{"99TiB", 99 << 40, true},
   514  
   515  		// Good zero inputs.
   516  		//
   517  		// -0 is an edge case, but no harm in supporting it.
   518  		{"-0", 0, true},
   519  		{"0", 0, true},
   520  		{"0B", 0, true},
   521  		{"0KiB", 0, true},
   522  		{"0MiB", 0, true},
   523  		{"0GiB", 0, true},
   524  		{"0TiB", 0, true},
   525  
   526  		// Bad inputs.
   527  		{"", 0, false},
   528  		{"-1", 0, false},
   529  		{"a12345", 0, false},
   530  		{"a12345B", 0, false},
   531  		{"12345x", 0, false},
   532  		{"0x12345", 0, false},
   533  
   534  		// Bad numeric inputs.
   535  		{"9223372036854775808", 0, false},
   536  		{"9223372036854775809", 0, false},
   537  		{"18446744073709551615", 0, false},
   538  		{"20496382327982653440", 0, false},
   539  		{"18446744073709551616", 0, false},
   540  		{"18446744073709551617", 0, false},
   541  		{"9999999999999999999999", 0, false},
   542  
   543  		// Bad trivial suffix inputs.
   544  		{"9223372036854775808B", 0, false},
   545  		{"9223372036854775809B", 0, false},
   546  		{"18446744073709551615B", 0, false},
   547  		{"20496382327982653440B", 0, false},
   548  		{"18446744073709551616B", 0, false},
   549  		{"18446744073709551617B", 0, false},
   550  		{"9999999999999999999999B", 0, false},
   551  
   552  		// Bad binary suffix inputs.
   553  		{"1Ki", 0, false},
   554  		{"05Ki", 0, false},
   555  		{"10Mi", 0, false},
   556  		{"100Gi", 0, false},
   557  		{"99Ti", 0, false},
   558  		{"22iB", 0, false},
   559  		{"B", 0, false},
   560  		{"iB", 0, false},
   561  		{"KiB", 0, false},
   562  		{"MiB", 0, false},
   563  		{"GiB", 0, false},
   564  		{"TiB", 0, false},
   565  		{"-120KiB", 0, false},
   566  		{"-891MiB", 0, false},
   567  		{"-704GiB", 0, false},
   568  		{"-42TiB", 0, false},
   569  		{"99999999999999999999KiB", 0, false},
   570  		{"99999999999999999MiB", 0, false},
   571  		{"99999999999999GiB", 0, false},
   572  		{"99999999999TiB", 0, false},
   573  		{"555EiB", 0, false},
   574  
   575  		// Mistaken SI suffix inputs.
   576  		{"0KB", 0, false},
   577  		{"0MB", 0, false},
   578  		{"0GB", 0, false},
   579  		{"0TB", 0, false},
   580  		{"1KB", 0, false},
   581  		{"05KB", 0, false},
   582  		{"1MB", 0, false},
   583  		{"10MB", 0, false},
   584  		{"1GB", 0, false},
   585  		{"100GB", 0, false},
   586  		{"1TB", 0, false},
   587  		{"99TB", 0, false},
   588  		{"1K", 0, false},
   589  		{"05K", 0, false},
   590  		{"10M", 0, false},
   591  		{"100G", 0, false},
   592  		{"99T", 0, false},
   593  		{"99999999999999999999KB", 0, false},
   594  		{"99999999999999999MB", 0, false},
   595  		{"99999999999999GB", 0, false},
   596  		{"99999999999TB", 0, false},
   597  		{"99999999999TiB", 0, false},
   598  		{"555EB", 0, false},
   599  	} {
   600  		out, ok := runtime.ParseByteCount(test.in)
   601  		if test.out != out || test.ok != ok {
   602  			t.Errorf("parseByteCount(%q) = (%v, %v) want (%v, %v)",
   603  				test.in, out, ok, test.out, test.ok)
   604  		}
   605  	}
   606  }
   607  

View as plain text