github.com/Laisky/zap@v1.27.0/writer_test.go (about) 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/Laisky/zap/zapcore" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 "go.uber.org/multierr" 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 }