Source file src/strings/builder_test.go

     1  // Copyright 2017 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 strings_test
     6  
     7  import (
     8  	"bytes"
     9  	"internal/asan"
    10  	. "strings"
    11  	"testing"
    12  	"unicode/utf8"
    13  )
    14  
    15  func check(t *testing.T, b *Builder, want string) {
    16  	t.Helper()
    17  	got := b.String()
    18  	if got != want {
    19  		t.Errorf("String: got %#q; want %#q", got, want)
    20  		return
    21  	}
    22  	if n := b.Len(); n != len(got) {
    23  		t.Errorf("Len: got %d; but len(String()) is %d", n, len(got))
    24  	}
    25  	if n := b.Cap(); n < len(got) {
    26  		t.Errorf("Cap: got %d; but len(String()) is %d", n, len(got))
    27  	}
    28  }
    29  
    30  func TestBuilder(t *testing.T) {
    31  	var b Builder
    32  	check(t, &b, "")
    33  	n, err := b.WriteString("hello")
    34  	if err != nil || n != 5 {
    35  		t.Errorf("WriteString: got %d,%s; want 5,nil", n, err)
    36  	}
    37  	check(t, &b, "hello")
    38  	if err = b.WriteByte(' '); err != nil {
    39  		t.Errorf("WriteByte: %s", err)
    40  	}
    41  	check(t, &b, "hello ")
    42  	n, err = b.WriteString("world")
    43  	if err != nil || n != 5 {
    44  		t.Errorf("WriteString: got %d,%s; want 5,nil", n, err)
    45  	}
    46  	check(t, &b, "hello world")
    47  }
    48  
    49  func TestBuilderString(t *testing.T) {
    50  	var b Builder
    51  	b.WriteString("alpha")
    52  	check(t, &b, "alpha")
    53  	s1 := b.String()
    54  	b.WriteString("beta")
    55  	check(t, &b, "alphabeta")
    56  	s2 := b.String()
    57  	b.WriteString("gamma")
    58  	check(t, &b, "alphabetagamma")
    59  	s3 := b.String()
    60  
    61  	// Check that subsequent operations didn't change the returned strings.
    62  	if want := "alpha"; s1 != want {
    63  		t.Errorf("first String result is now %q; want %q", s1, want)
    64  	}
    65  	if want := "alphabeta"; s2 != want {
    66  		t.Errorf("second String result is now %q; want %q", s2, want)
    67  	}
    68  	if want := "alphabetagamma"; s3 != want {
    69  		t.Errorf("third String result is now %q; want %q", s3, want)
    70  	}
    71  }
    72  
    73  func TestBuilderReset(t *testing.T) {
    74  	var b Builder
    75  	check(t, &b, "")
    76  	b.WriteString("aaa")
    77  	s := b.String()
    78  	check(t, &b, "aaa")
    79  	b.Reset()
    80  	check(t, &b, "")
    81  
    82  	// Ensure that writing after Reset doesn't alter
    83  	// previously returned strings.
    84  	b.WriteString("bbb")
    85  	check(t, &b, "bbb")
    86  	if want := "aaa"; s != want {
    87  		t.Errorf("previous String result changed after Reset: got %q; want %q", s, want)
    88  	}
    89  }
    90  
    91  func TestBuilderGrow(t *testing.T) {
    92  	for _, growLen := range []int{0, 100, 1000, 10000, 100000} {
    93  		if asan.Enabled {
    94  			t.Logf("skipping allocs check for growLen %d: extra allocs with -asan; see #70079", growLen)
    95  			continue
    96  		}
    97  		p := bytes.Repeat([]byte{'a'}, growLen)
    98  		allocs := testing.AllocsPerRun(100, func() {
    99  			var b Builder
   100  			b.Grow(growLen) // should be only alloc, when growLen > 0
   101  			if b.Cap() < growLen {
   102  				t.Fatalf("growLen=%d: Cap() is lower than growLen", growLen)
   103  			}
   104  			b.Write(p)
   105  			if b.String() != string(p) {
   106  				t.Fatalf("growLen=%d: bad data written after Grow", growLen)
   107  			}
   108  		})
   109  		wantAllocs := 1
   110  		if growLen == 0 {
   111  			wantAllocs = 0
   112  		}
   113  		if g, w := int(allocs), wantAllocs; g != w {
   114  			t.Errorf("growLen=%d: got %d allocs during Write; want %v", growLen, g, w)
   115  		}
   116  	}
   117  	// when growLen < 0, should panic
   118  	var a Builder
   119  	n := -1
   120  	defer func() {
   121  		if r := recover(); r == nil {
   122  			t.Errorf("a.Grow(%d) should panic()", n)
   123  		}
   124  	}()
   125  	a.Grow(n)
   126  }
   127  
   128  func TestBuilderWrite2(t *testing.T) {
   129  	const s0 = "hello 世界"
   130  	for _, tt := range []struct {
   131  		name string
   132  		fn   func(b *Builder) (int, error)
   133  		n    int
   134  		want string
   135  	}{
   136  		{
   137  			"Write",
   138  			func(b *Builder) (int, error) { return b.Write([]byte(s0)) },
   139  			len(s0),
   140  			s0,
   141  		},
   142  		{
   143  			"WriteRune",
   144  			func(b *Builder) (int, error) { return b.WriteRune('a') },
   145  			1,
   146  			"a",
   147  		},
   148  		{
   149  			"WriteRuneWide",
   150  			func(b *Builder) (int, error) { return b.WriteRune('世') },
   151  			3,
   152  			"世",
   153  		},
   154  		{
   155  			"WriteString",
   156  			func(b *Builder) (int, error) { return b.WriteString(s0) },
   157  			len(s0),
   158  			s0,
   159  		},
   160  	} {
   161  		t.Run(tt.name, func(t *testing.T) {
   162  			var b Builder
   163  			n, err := tt.fn(&b)
   164  			if err != nil {
   165  				t.Fatalf("first call: got %s", err)
   166  			}
   167  			if n != tt.n {
   168  				t.Errorf("first call: got n=%d; want %d", n, tt.n)
   169  			}
   170  			check(t, &b, tt.want)
   171  
   172  			n, err = tt.fn(&b)
   173  			if err != nil {
   174  				t.Fatalf("second call: got %s", err)
   175  			}
   176  			if n != tt.n {
   177  				t.Errorf("second call: got n=%d; want %d", n, tt.n)
   178  			}
   179  			check(t, &b, tt.want+tt.want)
   180  		})
   181  	}
   182  }
   183  
   184  func TestBuilderWriteByte(t *testing.T) {
   185  	var b Builder
   186  	if err := b.WriteByte('a'); err != nil {
   187  		t.Error(err)
   188  	}
   189  	if err := b.WriteByte(0); err != nil {
   190  		t.Error(err)
   191  	}
   192  	check(t, &b, "a\x00")
   193  }
   194  
   195  func TestBuilderAllocs(t *testing.T) {
   196  	if asan.Enabled {
   197  		t.Skip("test allocates more with -asan; see #70079")
   198  	}
   199  	// Issue 23382; verify that copyCheck doesn't force the
   200  	// Builder to escape and be heap allocated.
   201  	n := testing.AllocsPerRun(10000, func() {
   202  		var b Builder
   203  		b.Grow(5)
   204  		b.WriteString("abcde")
   205  		_ = b.String()
   206  	})
   207  	if n != 1 {
   208  		t.Errorf("Builder allocs = %v; want 1", n)
   209  	}
   210  }
   211  
   212  func TestBuilderCopyPanic(t *testing.T) {
   213  	tests := []struct {
   214  		name      string
   215  		fn        func()
   216  		wantPanic bool
   217  	}{
   218  		{
   219  			name:      "String",
   220  			wantPanic: false,
   221  			fn: func() {
   222  				var a Builder
   223  				a.WriteByte('x')
   224  				b := a
   225  				_ = b.String() // appease vet
   226  			},
   227  		},
   228  		{
   229  			name:      "Len",
   230  			wantPanic: false,
   231  			fn: func() {
   232  				var a Builder
   233  				a.WriteByte('x')
   234  				b := a
   235  				b.Len()
   236  			},
   237  		},
   238  		{
   239  			name:      "Cap",
   240  			wantPanic: false,
   241  			fn: func() {
   242  				var a Builder
   243  				a.WriteByte('x')
   244  				b := a
   245  				b.Cap()
   246  			},
   247  		},
   248  		{
   249  			name:      "Reset",
   250  			wantPanic: false,
   251  			fn: func() {
   252  				var a Builder
   253  				a.WriteByte('x')
   254  				b := a
   255  				b.Reset()
   256  				b.WriteByte('y')
   257  			},
   258  		},
   259  		{
   260  			name:      "Write",
   261  			wantPanic: true,
   262  			fn: func() {
   263  				var a Builder
   264  				a.Write([]byte("x"))
   265  				b := a
   266  				b.Write([]byte("y"))
   267  			},
   268  		},
   269  		{
   270  			name:      "WriteByte",
   271  			wantPanic: true,
   272  			fn: func() {
   273  				var a Builder
   274  				a.WriteByte('x')
   275  				b := a
   276  				b.WriteByte('y')
   277  			},
   278  		},
   279  		{
   280  			name:      "WriteString",
   281  			wantPanic: true,
   282  			fn: func() {
   283  				var a Builder
   284  				a.WriteString("x")
   285  				b := a
   286  				b.WriteString("y")
   287  			},
   288  		},
   289  		{
   290  			name:      "WriteRune",
   291  			wantPanic: true,
   292  			fn: func() {
   293  				var a Builder
   294  				a.WriteRune('x')
   295  				b := a
   296  				b.WriteRune('y')
   297  			},
   298  		},
   299  		{
   300  			name:      "Grow",
   301  			wantPanic: true,
   302  			fn: func() {
   303  				var a Builder
   304  				a.Grow(1)
   305  				b := a
   306  				b.Grow(2)
   307  			},
   308  		},
   309  	}
   310  	for _, tt := range tests {
   311  		didPanic := make(chan bool)
   312  		go func() {
   313  			defer func() { didPanic <- recover() != nil }()
   314  			tt.fn()
   315  		}()
   316  		if got := <-didPanic; got != tt.wantPanic {
   317  			t.Errorf("%s: panicked = %v; want %v", tt.name, got, tt.wantPanic)
   318  		}
   319  	}
   320  }
   321  
   322  func TestBuilderWriteInvalidRune(t *testing.T) {
   323  	// Invalid runes, including negative ones, should be written as
   324  	// utf8.RuneError.
   325  	for _, r := range []rune{-1, utf8.MaxRune + 1} {
   326  		var b Builder
   327  		b.WriteRune(r)
   328  		check(t, &b, "\uFFFD")
   329  	}
   330  }
   331  
   332  var someBytes = []byte("some bytes sdljlk jsklj3lkjlk djlkjw")
   333  
   334  var sinkS string
   335  
   336  func benchmarkBuilder(b *testing.B, f func(b *testing.B, numWrite int, grow bool)) {
   337  	b.Run("1Write_NoGrow", func(b *testing.B) {
   338  		b.ReportAllocs()
   339  		f(b, 1, false)
   340  	})
   341  	b.Run("3Write_NoGrow", func(b *testing.B) {
   342  		b.ReportAllocs()
   343  		f(b, 3, false)
   344  	})
   345  	b.Run("3Write_Grow", func(b *testing.B) {
   346  		b.ReportAllocs()
   347  		f(b, 3, true)
   348  	})
   349  }
   350  
   351  func BenchmarkBuildString_Builder(b *testing.B) {
   352  	benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
   353  		for i := 0; i < b.N; i++ {
   354  			var buf Builder
   355  			if grow {
   356  				buf.Grow(len(someBytes) * numWrite)
   357  			}
   358  			for i := 0; i < numWrite; i++ {
   359  				buf.Write(someBytes)
   360  			}
   361  			sinkS = buf.String()
   362  		}
   363  	})
   364  }
   365  
   366  func BenchmarkBuildString_WriteString(b *testing.B) {
   367  	someString := string(someBytes)
   368  	benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
   369  		for i := 0; i < b.N; i++ {
   370  			var buf Builder
   371  			if grow {
   372  				buf.Grow(len(someString) * numWrite)
   373  			}
   374  			for i := 0; i < numWrite; i++ {
   375  				buf.WriteString(someString)
   376  			}
   377  			sinkS = buf.String()
   378  		}
   379  	})
   380  }
   381  
   382  func BenchmarkBuildString_ByteBuffer(b *testing.B) {
   383  	benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
   384  		for i := 0; i < b.N; i++ {
   385  			var buf bytes.Buffer
   386  			if grow {
   387  				buf.Grow(len(someBytes) * numWrite)
   388  			}
   389  			for i := 0; i < numWrite; i++ {
   390  				buf.Write(someBytes)
   391  			}
   392  			sinkS = buf.String()
   393  		}
   394  	})
   395  }
   396  
   397  func TestBuilderGrowSizeclasses(t *testing.T) {
   398  	if asan.Enabled {
   399  		t.Skip("test allocates more with -asan; see #70079")
   400  	}
   401  	s := Repeat("a", 19)
   402  	allocs := testing.AllocsPerRun(100, func() {
   403  		var b Builder
   404  		b.Grow(18)
   405  		b.WriteString(s)
   406  		_ = b.String()
   407  	})
   408  	if allocs > 1 {
   409  		t.Fatalf("unexpected amount of allocations: %v, want: 1", allocs)
   410  	}
   411  }
   412  

View as plain text