...

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

Documentation: go.uber.org/zap

     1  // Copyright (c) 2016-2022 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 zap
    22  
    23  import (
    24  	"errors"
    25  	"io"
    26  	"io/fs"
    27  	"net/url"
    28  	"os"
    29  	"path/filepath"
    30  	"testing"
    31  
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  	"go.uber.org/multierr"
    35  	"go.uber.org/zap/zapcore"
    36  )
    37  
    38  func TestOpenNoPaths(t *testing.T) {
    39  	ws, cleanup, err := Open()
    40  	defer cleanup()
    41  
    42  	assert.NoError(t, err, "Expected opening no paths to succeed.")
    43  	assert.Equal(
    44  		t,
    45  		zapcore.AddSync(io.Discard),
    46  		ws,
    47  		"Expected opening no paths to return a no-op WriteSyncer.",
    48  	)
    49  }
    50  
    51  func TestOpen(t *testing.T) {
    52  	tempName := filepath.Join(t.TempDir(), "test.log")
    53  	assert.False(t, fileExists(tempName))
    54  	require.True(t, filepath.IsAbs(tempName), "Expected absolute temp file path.")
    55  
    56  	tests := []struct {
    57  		msg   string
    58  		paths []string
    59  	}{
    60  		{
    61  			msg:   "stdout",
    62  			paths: []string{"stdout"},
    63  		},
    64  		{
    65  			msg:   "stderr",
    66  			paths: []string{"stderr"},
    67  		},
    68  		{
    69  			msg:   "temp file path only",
    70  			paths: []string{tempName},
    71  		},
    72  		{
    73  			msg:   "temp file file scheme",
    74  			paths: []string{"file://" + tempName},
    75  		},
    76  		{
    77  			msg:   "temp file with file scheme and host localhost",
    78  			paths: []string{"file://localhost" + tempName},
    79  		},
    80  	}
    81  
    82  	for _, tt := range tests {
    83  		t.Run(tt.msg, func(t *testing.T) {
    84  			_, cleanup, err := Open(tt.paths...)
    85  			if err == nil {
    86  				defer cleanup()
    87  			}
    88  
    89  			assert.NoError(t, err, "Unexpected error opening paths %v.", tt.paths)
    90  		})
    91  	}
    92  
    93  	assert.True(t, fileExists(tempName))
    94  }
    95  
    96  func TestOpenPathsNotFound(t *testing.T) {
    97  	tempName := filepath.Join(t.TempDir(), "test.log")
    98  
    99  	tests := []struct {
   100  		msg               string
   101  		paths             []string
   102  		wantNotFoundPaths []string
   103  	}{
   104  		{
   105  			msg:               "missing path",
   106  			paths:             []string{"/foo/bar/baz"},
   107  			wantNotFoundPaths: []string{"/foo/bar/baz"},
   108  		},
   109  		{
   110  			msg:               "missing file scheme url with host localhost",
   111  			paths:             []string{"file://localhost/foo/bar/baz"},
   112  			wantNotFoundPaths: []string{"/foo/bar/baz"},
   113  		},
   114  		{
   115  			msg:   "multiple paths",
   116  			paths: []string{"stdout", "/foo/bar/baz", tempName, "file:///baz/quux"},
   117  			wantNotFoundPaths: []string{
   118  				"/foo/bar/baz",
   119  				"/baz/quux",
   120  			},
   121  		},
   122  	}
   123  
   124  	for _, tt := range tests {
   125  		t.Run(tt.msg, func(t *testing.T) {
   126  			_, cleanup, err := Open(tt.paths...)
   127  			if !assert.Error(t, err, "Open must fail.") {
   128  				cleanup()
   129  				return
   130  			}
   131  
   132  			errs := multierr.Errors(err)
   133  			require.Len(t, errs, len(tt.wantNotFoundPaths))
   134  			for i, err := range errs {
   135  				assert.ErrorIs(t, err, fs.ErrNotExist)
   136  				assert.ErrorContains(t, err, tt.wantNotFoundPaths[i], "missing path in error")
   137  			}
   138  		})
   139  	}
   140  }
   141  
   142  func TestOpenRelativePath(t *testing.T) {
   143  	const name = "test-relative-path.txt"
   144  
   145  	require.False(t, fileExists(name), "Test file already exists.")
   146  	s, cleanup, err := Open(name)
   147  	require.NoError(t, err, "Open failed.")
   148  	defer func() {
   149  		err := os.Remove(name)
   150  		if !t.Failed() {
   151  			// If the test has already failed, we probably didn't create this file.
   152  			require.NoError(t, err, "Deleting test file failed.")
   153  		}
   154  	}()
   155  	defer cleanup()
   156  
   157  	_, err = s.Write([]byte("test"))
   158  	assert.NoError(t, err, "Write failed.")
   159  	assert.True(t, fileExists(name), "Didn't create file for relative path.")
   160  }
   161  
   162  func TestOpenFails(t *testing.T) {
   163  	tests := []struct {
   164  		paths []string
   165  	}{
   166  		{paths: []string{"./non-existent-dir/file"}},           // directory doesn't exist
   167  		{paths: []string{"stdout", "./non-existent-dir/file"}}, // directory doesn't exist
   168  		{paths: []string{"://foo.log"}},                        // invalid URL, scheme can't begin with colon
   169  		{paths: []string{"mem://somewhere"}},                   // scheme not registered
   170  	}
   171  
   172  	for _, tt := range tests {
   173  		_, cleanup, err := Open(tt.paths...)
   174  		require.Nil(t, cleanup, "Cleanup function should never be nil")
   175  		assert.Error(t, err, "Open with invalid URL should fail.")
   176  	}
   177  }
   178  
   179  func TestOpenOtherErrors(t *testing.T) {
   180  	tempName := filepath.Join(t.TempDir(), "test.log")
   181  
   182  	tests := []struct {
   183  		msg     string
   184  		paths   []string
   185  		wantErr string
   186  	}{
   187  		{
   188  			msg:     "file with unexpected host",
   189  			paths:   []string{"file://host01.test.com" + tempName},
   190  			wantErr: "empty or use localhost",
   191  		},
   192  		{
   193  			msg:     "file with user on localhost",
   194  			paths:   []string{"file://rms@localhost" + tempName},
   195  			wantErr: "user and password not allowed",
   196  		},
   197  		{
   198  			msg:     "file url with fragment",
   199  			paths:   []string{"file://localhost" + tempName + "#foo"},
   200  			wantErr: "fragments not allowed",
   201  		},
   202  		{
   203  			msg:     "file url with query",
   204  			paths:   []string{"file://localhost" + tempName + "?foo=bar"},
   205  			wantErr: "query parameters not allowed",
   206  		},
   207  		{
   208  			msg:     "file with port",
   209  			paths:   []string{"file://localhost:8080" + tempName},
   210  			wantErr: "ports not allowed",
   211  		},
   212  	}
   213  
   214  	for _, tt := range tests {
   215  		t.Run(tt.msg, func(t *testing.T) {
   216  			_, cleanup, err := Open(tt.paths...)
   217  			if !assert.Error(t, err, "Open must fail.") {
   218  				cleanup()
   219  				return
   220  			}
   221  
   222  			assert.ErrorContains(t, err, tt.wantErr, "Unexpected error opening paths %v.", tt.paths)
   223  		})
   224  	}
   225  }
   226  
   227  type testWriter struct {
   228  	expected string
   229  	t        testing.TB
   230  }
   231  
   232  func (w *testWriter) Write(actual []byte) (int, error) {
   233  	assert.Equal(w.t, []byte(w.expected), actual, "Unexpected write error.")
   234  	return len(actual), nil
   235  }
   236  
   237  func (w *testWriter) Sync() error {
   238  	return nil
   239  }
   240  
   241  func TestOpenWithErroringSinkFactory(t *testing.T) {
   242  	stubSinkRegistry(t)
   243  
   244  	msg := "expected factory error"
   245  	factory := func(_ *url.URL) (Sink, error) {
   246  		return nil, errors.New(msg)
   247  	}
   248  
   249  	assert.NoError(t, RegisterSink("test", factory), "Failed to register sink factory.")
   250  	_, _, err := Open("test://some/path")
   251  	assert.ErrorContains(t, err, msg)
   252  }
   253  
   254  func TestCombineWriteSyncers(t *testing.T) {
   255  	tw := &testWriter{"test", t}
   256  	w := CombineWriteSyncers(tw)
   257  	_, err := w.Write([]byte("test"))
   258  	assert.NoError(t, err, "Unexpected write error.")
   259  }
   260  
   261  func fileExists(name string) bool {
   262  	if _, err := os.Stat(name); os.IsNotExist(err) {
   263  		return false
   264  	}
   265  	return true
   266  }
   267  

View as plain text