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

View as plain text