1// Copyright (c) 2016 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 21package zap 22 23import ( 24 "encoding/hex" 25 "errors" 26 "io/ioutil" 27 "math/rand" 28 "net/url" 29 "os" 30 "path/filepath" 31 "strings" 32 "testing" 33 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 "go.uber.org/zap/zapcore" 37) 38 39func TestOpenNoPaths(t *testing.T) { 40 ws, cleanup, err := Open() 41 defer cleanup() 42 43 assert.NoError(t, err, "Expected opening no paths to succeed.") 44 assert.Equal( 45 t, 46 zapcore.AddSync(ioutil.Discard), 47 ws, 48 "Expected opening no paths to return a no-op WriteSyncer.", 49 ) 50} 51 52func TestOpen(t *testing.T) { 53 tempName := tempFileName("", "zap-open-test") 54 assert.False(t, fileExists(tempName)) 55 require.True(t, strings.HasPrefix(tempName, "/"), "Expected absolute temp file path.") 56 57 tests := []struct { 58 paths []string 59 errs []string 60 }{ 61 {[]string{"stdout"}, nil}, 62 {[]string{"stderr"}, nil}, 63 {[]string{tempName}, nil}, 64 {[]string{"file://" + tempName}, nil}, 65 {[]string{"file://localhost" + tempName}, nil}, 66 {[]string{"/foo/bar/baz"}, []string{"open /foo/bar/baz: no such file or directory"}}, 67 {[]string{"file://localhost/foo/bar/baz"}, []string{"open /foo/bar/baz: no such file or directory"}}, 68 { 69 paths: []string{"stdout", "/foo/bar/baz", tempName, "file:///baz/quux"}, 70 errs: []string{ 71 "open /foo/bar/baz: no such file or directory", 72 "open /baz/quux: no such file or directory", 73 }, 74 }, 75 {[]string{"file:///stderr"}, []string{"open /stderr: permission denied"}}, 76 {[]string{"file:///stdout"}, []string{"open /stdout: permission denied"}}, 77 {[]string{"file://host01.test.com" + tempName}, []string{"empty or use localhost"}}, 78 {[]string{"file://rms@localhost" + tempName}, []string{"user and password not allowed"}}, 79 {[]string{"file://localhost" + tempName + "#foo"}, []string{"fragments not allowed"}}, 80 {[]string{"file://localhost" + tempName + "?foo=bar"}, []string{"query parameters not allowed"}}, 81 {[]string{"file://localhost:8080" + tempName}, []string{"ports not allowed"}}, 82 } 83 84 for _, tt := range tests { 85 _, cleanup, err := Open(tt.paths...) 86 if err == nil { 87 defer cleanup() 88 } 89 90 if len(tt.errs) == 0 { 91 assert.NoError(t, err, "Unexpected error opening paths %v.", tt.paths) 92 } else { 93 msg := err.Error() 94 for _, expect := range tt.errs { 95 assert.Contains(t, msg, expect, "Unexpected error opening paths %v.", tt.paths) 96 } 97 } 98 } 99 100 assert.True(t, fileExists(tempName)) 101 os.Remove(tempName) 102} 103 104func TestOpenRelativePath(t *testing.T) { 105 const name = "test-relative-path.txt" 106 107 require.False(t, fileExists(name), "Test file already exists.") 108 s, cleanup, err := Open(name) 109 require.NoError(t, err, "Open failed.") 110 defer func() { 111 err := os.Remove(name) 112 if !t.Failed() { 113 // If the test has already failed, we probably didn't create this file. 114 require.NoError(t, err, "Deleting test file failed.") 115 } 116 }() 117 defer cleanup() 118 119 _, err = s.Write([]byte("test")) 120 assert.NoError(t, err, "Write failed.") 121 assert.True(t, fileExists(name), "Didn't create file for relative path.") 122} 123 124func TestOpenFails(t *testing.T) { 125 tests := []struct { 126 paths []string 127 }{ 128 {paths: []string{"./non-existent-dir/file"}}, // directory doesn't exist 129 {paths: []string{"stdout", "./non-existent-dir/file"}}, // directory doesn't exist 130 {paths: []string{"://foo.log"}}, // invalid URL, scheme can't begin with colon 131 {paths: []string{"mem://somewhere"}}, // scheme not registered 132 } 133 134 for _, tt := range tests { 135 _, cleanup, err := Open(tt.paths...) 136 require.Nil(t, cleanup, "Cleanup function should never be nil") 137 assert.Error(t, err, "Open with invalid URL should fail.") 138 } 139} 140 141type testWriter struct { 142 expected string 143 t testing.TB 144} 145 146func (w *testWriter) Write(actual []byte) (int, error) { 147 assert.Equal(w.t, []byte(w.expected), actual, "Unexpected write error.") 148 return len(actual), nil 149} 150 151func (w *testWriter) Sync() error { 152 return nil 153} 154 155func TestOpenWithErroringSinkFactory(t *testing.T) { 156 defer resetSinkRegistry() 157 158 msg := "expected factory error" 159 factory := func(_ *url.URL) (Sink, error) { 160 return nil, errors.New(msg) 161 } 162 163 assert.NoError(t, RegisterSink("test", factory), "Failed to register sink factory.") 164 _, _, err := Open("test://some/path") 165 assert.Contains(t, err.Error(), msg, "Unexpected error.") 166} 167 168func TestCombineWriteSyncers(t *testing.T) { 169 tw := &testWriter{"test", t} 170 w := CombineWriteSyncers(tw) 171 w.Write([]byte("test")) 172} 173 174func tempFileName(prefix, suffix string) string { 175 randBytes := make([]byte, 16) 176 rand.Read(randBytes) 177 return filepath.Join(os.TempDir(), prefix+hex.EncodeToString(randBytes)+suffix) 178} 179 180func fileExists(name string) bool { 181 if _, err := os.Stat(name); os.IsNotExist(err) { 182 return false 183 } 184 return true 185} 186