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