github.com/pgavlin/text@v0.0.0-20240419000839-8438d0a47805/builder_test.go (about)

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