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