...

Source file src/go.uber.org/zap/zapio/writer_test.go

Documentation: go.uber.org/zap/zapio

     1  // Copyright (c) 2021 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package zapio
    22  
    23  import (
    24  	"io"
    25  	"testing"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	"go.uber.org/zap"
    30  	"go.uber.org/zap/zapcore"
    31  	"go.uber.org/zap/zaptest/observer"
    32  )
    33  
    34  func TestWriter(t *testing.T) {
    35  	t.Parallel()
    36  
    37  	tests := []struct {
    38  		desc   string
    39  		level  zapcore.Level // defaults to info
    40  		writes []string
    41  		want   []zapcore.Entry
    42  	}{
    43  		{
    44  			desc: "simple",
    45  			writes: []string{
    46  				"foo\n",
    47  				"bar\n",
    48  				"baz\n",
    49  			},
    50  			want: []zapcore.Entry{
    51  				{Level: zap.InfoLevel, Message: "foo"},
    52  				{Level: zap.InfoLevel, Message: "bar"},
    53  				{Level: zap.InfoLevel, Message: "baz"},
    54  			},
    55  		},
    56  		{
    57  			desc:  "level too low",
    58  			level: zap.DebugLevel,
    59  			writes: []string{
    60  				"foo\n",
    61  				"bar\n",
    62  			},
    63  			want: []zapcore.Entry{},
    64  		},
    65  		{
    66  			desc:  "multiple newlines in a message",
    67  			level: zap.WarnLevel,
    68  			writes: []string{
    69  				"foo\nbar\n",
    70  				"baz\n",
    71  				"qux\nquux\n",
    72  			},
    73  			want: []zapcore.Entry{
    74  				{Level: zap.WarnLevel, Message: "foo"},
    75  				{Level: zap.WarnLevel, Message: "bar"},
    76  				{Level: zap.WarnLevel, Message: "baz"},
    77  				{Level: zap.WarnLevel, Message: "qux"},
    78  				{Level: zap.WarnLevel, Message: "quux"},
    79  			},
    80  		},
    81  		{
    82  			desc:  "message split across multiple writes",
    83  			level: zap.ErrorLevel,
    84  			writes: []string{
    85  				"foo",
    86  				"bar\nbaz",
    87  				"qux",
    88  			},
    89  			want: []zapcore.Entry{
    90  				{Level: zap.ErrorLevel, Message: "foobar"},
    91  				{Level: zap.ErrorLevel, Message: "bazqux"},
    92  			},
    93  		},
    94  		{
    95  			desc: "blank lines in the middle",
    96  			writes: []string{
    97  				"foo\n\nbar\nbaz",
    98  			},
    99  			want: []zapcore.Entry{
   100  				{Level: zap.InfoLevel, Message: "foo"},
   101  				{Level: zap.InfoLevel, Message: ""},
   102  				{Level: zap.InfoLevel, Message: "bar"},
   103  				{Level: zap.InfoLevel, Message: "baz"},
   104  			},
   105  		},
   106  		{
   107  			desc: "blank line at the end",
   108  			writes: []string{
   109  				"foo\nbar\nbaz\n",
   110  			},
   111  			want: []zapcore.Entry{
   112  				{Level: zap.InfoLevel, Message: "foo"},
   113  				{Level: zap.InfoLevel, Message: "bar"},
   114  				{Level: zap.InfoLevel, Message: "baz"},
   115  			},
   116  		},
   117  		{
   118  			desc: "multiple blank line at the end",
   119  			writes: []string{
   120  				"foo\nbar\nbaz\n\n",
   121  			},
   122  			want: []zapcore.Entry{
   123  				{Level: zap.InfoLevel, Message: "foo"},
   124  				{Level: zap.InfoLevel, Message: "bar"},
   125  				{Level: zap.InfoLevel, Message: "baz"},
   126  				{Level: zap.InfoLevel, Message: ""},
   127  			},
   128  		},
   129  	}
   130  
   131  	for _, tt := range tests {
   132  		tt := tt // for t.Parallel
   133  		t.Run(tt.desc, func(t *testing.T) {
   134  			t.Parallel()
   135  
   136  			core, observed := observer.New(zap.InfoLevel)
   137  
   138  			w := Writer{
   139  				Log:   zap.New(core),
   140  				Level: tt.level,
   141  			}
   142  
   143  			for _, s := range tt.writes {
   144  				_, err := io.WriteString(&w, s)
   145  				require.NoError(t, err, "Writer.Write failed.")
   146  			}
   147  
   148  			assert.NoError(t, w.Close(), "Writer.Close failed.")
   149  
   150  			// Turn []observer.LoggedEntry => []zapcore.Entry
   151  			got := make([]zapcore.Entry, observed.Len())
   152  			for i, ent := range observed.AllUntimed() {
   153  				got[i] = ent.Entry
   154  			}
   155  			assert.Equal(t, tt.want, got, "Logged entries do not match.")
   156  		})
   157  	}
   158  }
   159  
   160  func TestWrite_Sync(t *testing.T) {
   161  	t.Parallel()
   162  
   163  	core, observed := observer.New(zap.InfoLevel)
   164  
   165  	w := Writer{
   166  		Log:   zap.New(core),
   167  		Level: zap.InfoLevel,
   168  	}
   169  
   170  	io.WriteString(&w, "foo")
   171  	io.WriteString(&w, "bar")
   172  
   173  	t.Run("no sync", func(t *testing.T) {
   174  		assert.Zero(t, observed.Len(), "Expected no logs yet")
   175  	})
   176  
   177  	t.Run("sync", func(t *testing.T) {
   178  		defer observed.TakeAll()
   179  
   180  		require.NoError(t, w.Sync(), "Sync must not fail")
   181  
   182  		assert.Equal(t, []observer.LoggedEntry{
   183  			{Entry: zapcore.Entry{Message: "foobar"}, Context: []zapcore.Field{}},
   184  		}, observed.AllUntimed(), "Log messages did not match")
   185  	})
   186  
   187  	t.Run("sync on empty", func(t *testing.T) {
   188  		require.NoError(t, w.Sync(), "Sync must not fail")
   189  		assert.Zero(t, observed.Len(), "Expected no logs yet")
   190  	})
   191  }
   192  
   193  func BenchmarkWriter(b *testing.B) {
   194  	tests := []struct {
   195  		name   string
   196  		writes [][]byte
   197  	}{
   198  		{
   199  			name: "single",
   200  			writes: [][]byte{
   201  				[]byte("foobar\n"),
   202  				[]byte("bazqux\n"),
   203  			},
   204  		},
   205  		{
   206  			name: "splits",
   207  			writes: [][]byte{
   208  				[]byte("foo"),
   209  				[]byte("bar\nbaz"),
   210  				[]byte("qux\n"),
   211  			},
   212  		},
   213  	}
   214  
   215  	writer := Writer{
   216  		Log:   zap.New(new(partiallyNopCore)),
   217  		Level: zapcore.DebugLevel,
   218  	}
   219  
   220  	for _, tt := range tests {
   221  		b.Run(tt.name, func(b *testing.B) {
   222  			b.ResetTimer()
   223  
   224  			for i := 0; i < b.N; i++ {
   225  				for _, bs := range tt.writes {
   226  					writer.Write(bs)
   227  				}
   228  			}
   229  		})
   230  	}
   231  }
   232  
   233  // partiallyNopCore behaves exactly like NopCore except it always returns true
   234  // for whether the provided level is enabled, and accepts all Check requests.
   235  //
   236  // This lets us measure the overhead of the writer without measuring the cost
   237  // of logging.
   238  type partiallyNopCore struct{}
   239  
   240  func (*partiallyNopCore) Enabled(zapcore.Level) bool { return true }
   241  
   242  func (c *partiallyNopCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
   243  	return ce.AddCore(ent, c)
   244  }
   245  
   246  func (c *partiallyNopCore) With([]zapcore.Field) zapcore.Core        { return c }
   247  func (*partiallyNopCore) Write(zapcore.Entry, []zapcore.Field) error { return nil }
   248  func (*partiallyNopCore) Sync() error                                { return nil }
   249  

View as plain text