1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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"}},
167 {paths: []string{"stdout", "./non-existent-dir/file"}},
168 {paths: []string{"://foo.log"}},
169 {paths: []string{"mem://somewhere"}},
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