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  }
   113  
   114  func TestBuilderWrite2(t *testing.T) {
   115  	const s0 = "hello 世界"
   116  	for _, tt := range []struct {
   117  		name string
   118  		fn   func(b *Builder) (int, error)
   119  		n    int
   120  		want string
   121  	}{
   122  		{
   123  			"Write",
   124  			func(b *Builder) (int, error) { return b.Write([]byte(s0)) },
   125  			len(s0),
   126  			s0,
   127  		},
   128  		{
   129  			"WriteRune",
   130  			func(b *Builder) (int, error) { return b.WriteRune('a') },
   131  			1,
   132  			"a",
   133  		},
   134  		{
   135  			"WriteRuneWide",
   136  			func(b *Builder) (int, error) { return b.WriteRune('世') },
   137  			3,
   138  			"世",
   139  		},
   140  		{
   141  			"WriteString",
   142  			func(b *Builder) (int, error) { return b.WriteString(s0) },
   143  			len(s0),
   144  			s0,
   145  		},
   146  	} {
   147  		t.Run(tt.name, func(t *testing.T) {
   148  			var b Builder
   149  			n, err := tt.fn(&b)
   150  			if err != nil {
   151  				t.Fatalf("first call: got %s", err)
   152  			}
   153  			if n != tt.n {
   154  				t.Errorf("first call: got n=%d; want %d", n, tt.n)
   155  			}
   156  			check(t, &b, tt.want)
   157  
   158  			n, err = tt.fn(&b)
   159  			if err != nil {
   160  				t.Fatalf("second call: got %s", err)
   161  			}
   162  			if n != tt.n {
   163  				t.Errorf("second call: got n=%d; want %d", n, tt.n)
   164  			}
   165  			check(t, &b, tt.want+tt.want)
   166  		})
   167  	}
   168  }
   169  
   170  func TestBuilderWriteByte(t *testing.T) {
   171  	var b Builder
   172  	if err := b.WriteByte('a'); err != nil {
   173  		t.Error(err)
   174  	}
   175  	if err := b.WriteByte(0); err != nil {
   176  		t.Error(err)
   177  	}
   178  	check(t, &b, "a\x00")
   179  }
   180  
   181  func TestBuilderAllocs(t *testing.T) {
   182  	// Issue 23382; verify that copyCheck doesn't force the
   183  	// Builder to escape and be heap allocated.
   184  	n := testing.AllocsPerRun(10000, func() {
   185  		var b Builder
   186  		b.Grow(5)
   187  		b.WriteString("abcde")
   188  		_ = b.String()
   189  	})
   190  	if n != 1 {
   191  		t.Errorf("Builder allocs = %v; want 1", n)
   192  	}
   193  }
   194  
   195  func TestBuilderCopyPanic(t *testing.T) {
   196  	tests := []struct {
   197  		name      string
   198  		fn        func()
   199  		wantPanic bool
   200  	}{
   201  		{
   202  			name:      "String",
   203  			wantPanic: false,
   204  			fn: func() {
   205  				var a Builder
   206  				a.WriteByte('x')
   207  				b := a
   208  				_ = b.String() // appease vet
   209  			},
   210  		},
   211  		{
   212  			name:      "Len",
   213  			wantPanic: false,
   214  			fn: func() {
   215  				var a Builder
   216  				a.WriteByte('x')
   217  				b := a
   218  				b.Len()
   219  			},
   220  		},
   221  		{
   222  			name:      "Cap",
   223  			wantPanic: false,
   224  			fn: func() {
   225  				var a Builder
   226  				a.WriteByte('x')
   227  				b := a
   228  				b.Cap()
   229  			},
   230  		},
   231  		{
   232  			name:      "Reset",
   233  			wantPanic: false,
   234  			fn: func() {
   235  				var a Builder
   236  				a.WriteByte('x')
   237  				b := a
   238  				b.Reset()
   239  				b.WriteByte('y')
   240  			},
   241  		},
   242  		{
   243  			name:      "Write",
   244  			wantPanic: true,
   245  			fn: func() {
   246  				var a Builder
   247  				a.Write([]byte("x"))
   248  				b := a
   249  				b.Write([]byte("y"))
   250  			},
   251  		},
   252  		{
   253  			name:      "WriteByte",
   254  			wantPanic: true,
   255  			fn: func() {
   256  				var a Builder
   257  				a.WriteByte('x')
   258  				b := a
   259  				b.WriteByte('y')
   260  			},
   261  		},
   262  		{
   263  			name:      "WriteString",
   264  			wantPanic: true,
   265  			fn: func() {
   266  				var a Builder
   267  				a.WriteString("x")
   268  				b := a
   269  				b.WriteString("y")
   270  			},
   271  		},
   272  		{
   273  			name:      "WriteRune",
   274  			wantPanic: true,
   275  			fn: func() {
   276  				var a Builder
   277  				a.WriteRune('x')
   278  				b := a
   279  				b.WriteRune('y')
   280  			},
   281  		},
   282  		{
   283  			name:      "Grow",
   284  			wantPanic: true,
   285  			fn: func() {
   286  				var a Builder
   287  				a.Grow(1)
   288  				b := a
   289  				b.Grow(2)
   290  			},
   291  		},
   292  	}
   293  	for _, tt := range tests {
   294  		didPanic := make(chan bool)
   295  		go func() {
   296  			defer func() { didPanic <- recover() != nil }()
   297  			tt.fn()
   298  		}()
   299  		if got := <-didPanic; got != tt.wantPanic {
   300  			t.Errorf("%s: panicked = %v; want %v", tt.name, got, tt.wantPanic)
   301  		}
   302  	}
   303  }
   304  
   305  func TestBuilderWriteInvalidRune(t *testing.T) {
   306  	// Invalid runes, including negative ones, should be written as
   307  	// utf8.RuneError.
   308  	for _, r := range []rune{-1, utf8.MaxRune + 1} {
   309  		var b Builder
   310  		b.WriteRune(r)
   311  		check(t, &b, "\uFFFD")
   312  	}
   313  }
   314  
   315  var someBytes = []byte("some bytes sdljlk jsklj3lkjlk djlkjw")
   316  
   317  var sinkS string
   318  
   319  func benchmarkBuilder(b *testing.B, f func(b *testing.B, numWrite int, grow bool)) {
   320  	b.Run("1Write_NoGrow", func(b *testing.B) {
   321  		b.ReportAllocs()
   322  		f(b, 1, false)
   323  	})
   324  	b.Run("3Write_NoGrow", func(b *testing.B) {
   325  		b.ReportAllocs()
   326  		f(b, 3, false)
   327  	})
   328  	b.Run("3Write_Grow", func(b *testing.B) {
   329  		b.ReportAllocs()
   330  		f(b, 3, true)
   331  	})
   332  }
   333  
   334  func BenchmarkBuildString_Builder(b *testing.B) {
   335  	benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
   336  		for i := 0; i < b.N; i++ {
   337  			var buf Builder
   338  			if grow {
   339  				buf.Grow(len(someBytes) * numWrite)
   340  			}
   341  			for i := 0; i < numWrite; i++ {
   342  				buf.Write(someBytes)
   343  			}
   344  			sinkS = buf.String()
   345  		}
   346  	})
   347  }
   348  
   349  func BenchmarkBuildString_ByteBuffer(b *testing.B) {
   350  	benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
   351  		for i := 0; i < b.N; i++ {
   352  			var buf bytes.Buffer
   353  			if grow {
   354  				buf.Grow(len(someBytes) * numWrite)
   355  			}
   356  			for i := 0; i < numWrite; i++ {
   357  				buf.Write(someBytes)
   358  			}
   359  			sinkS = buf.String()
   360  		}
   361  	})
   362  }
   363  

View as plain text