writer_test.go 6.9 KB

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