github.com/grailbio/base@v0.0.11/iofmt/linewriter_test.go (about)

     1  // Copyright 2021 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package iofmt_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"math/rand"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/grailbio/base/iofmt"
    15  	"github.com/grailbio/testutil/assert"
    16  )
    17  
    18  // saveWriter saves the calls made to Write for later comparison.
    19  type saveWriter struct {
    20  	writes [][]byte
    21  }
    22  
    23  func (w *saveWriter) Write(p []byte) (int, error) {
    24  	pCopy := make([]byte, len(p))
    25  	copy(pCopy, p)
    26  	w.writes = append(w.writes, pCopy)
    27  	return len(p), nil
    28  }
    29  
    30  // TestLineWriter verifies that a LineWriter calls Write on its underlying
    31  // writer with complete lines.
    32  func TestLineWriter(t *testing.T) {
    33  	for _, c := range []struct {
    34  		name      string
    35  		makeLines func() []string
    36  	}{
    37  		{
    38  			name:      "Empty",
    39  			makeLines: func() []string { return []string{} },
    40  		},
    41  		{
    42  			name: "Numbered",
    43  			makeLines: func() []string {
    44  				const Nlines = 1000
    45  				lines := make([]string, Nlines)
    46  				for i := range lines {
    47  					lines[i] = fmt.Sprintf("line %04d", i)
    48  				}
    49  				return lines
    50  			},
    51  		},
    52  		{
    53  			name: "SomeEmpty",
    54  			makeLines: func() []string {
    55  				const Nlines = 1000
    56  				lines := make([]string, Nlines)
    57  				for i := range lines {
    58  					if rand.Intn(2) == 0 {
    59  						continue
    60  					}
    61  					lines[i] = fmt.Sprintf("line %04d", i)
    62  				}
    63  				return lines
    64  			},
    65  		},
    66  		{
    67  			name: "SomeLong",
    68  			makeLines: func() []string {
    69  				const Nlines = 1000
    70  				lines := make([]string, Nlines)
    71  				for i := range lines {
    72  					var b strings.Builder
    73  					fmt.Fprintf(&b, "line %04d:", i)
    74  					for j := 0; j < rand.Intn(100); j++ {
    75  						b.WriteString(" lorem ipsum")
    76  					}
    77  				}
    78  				return lines
    79  			},
    80  		},
    81  	} {
    82  		t.Run(c.name, func(t *testing.T) {
    83  			lines := c.makeLines()
    84  			// bs is a concatenation of all the lines. We write this to a
    85  			// LineWriter in random segments.
    86  			var bs []byte
    87  			for _, line := range lines {
    88  				bs = append(bs, []byte(fmt.Sprintf("%s\n", line))...)
    89  			}
    90  			s := &saveWriter{}
    91  			w := iofmt.LineWriter(s)
    92  			defer func() {
    93  				assert.Nil(t, w.Close())
    94  			}()
    95  			for len(bs) > 0 {
    96  				// Write in random segments.
    97  				n := rand.Intn(20)
    98  				if len(bs) < n {
    99  					n = len(bs)
   100  				}
   101  				m, err := w.Write(bs[:n])
   102  				assert.Nil(t, err)
   103  				assert.EQ(t, m, n)
   104  				bs = bs[n:]
   105  			}
   106  			want := make([][]byte, len(lines))
   107  			for i, line := range lines {
   108  				want[i] = []byte(fmt.Sprintf("%s\n", line))
   109  			}
   110  			assert.EQ(t, s.writes, want)
   111  		})
   112  	}
   113  }
   114  
   115  // TestLineWriterClose verifies that (*LineWriter).Close writes any remaining
   116  // partial line to the underlying writer.
   117  func TestLineWriterClose(t *testing.T) {
   118  	for _, c := range []struct {
   119  		name string
   120  		bs   []byte
   121  	}{
   122  		{
   123  			name: "Empty",
   124  			bs:   []byte{},
   125  		},
   126  		{
   127  			name: "PartialOnly",
   128  			bs:   []byte("no terminal newline"),
   129  		},
   130  		{
   131  			name: "Partial",
   132  			bs:   []byte("line 0\nline 1\nline 2\nno terminal newline"),
   133  		},
   134  	} {
   135  		t.Run(c.name, func(t *testing.T) {
   136  			var b bytes.Buffer
   137  			w := iofmt.LineWriter(&b)
   138  			_, err := w.Write(c.bs)
   139  			assert.Nil(t, err)
   140  			assert.Nil(t, w.Close())
   141  			assert.EQ(t, b.Bytes(), c.bs)
   142  		})
   143  	}
   144  }