1package zli
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"io"
8	"io/ioutil"
9	"net/mail"
10	"os"
11	"reflect"
12	"runtime"
13	"strings"
14	"testing"
15)
16
17func TestFatal(t *testing.T) {
18	tests := []struct {
19		in   interface{}
20		args []interface{}
21		want string
22	}{
23		{"", nil, "zli.test: \n"},
24		{nil, nil, "zli.test: <nil>\n"},
25		{nil, nil, "zli.test: <nil>\n"},
26		{42, nil, "zli.test: 42\n"},
27
28		{"oh noes", nil, "zli.test: oh noes\n"},
29		{"oh noes: %d", []interface{}{42}, "zli.test: oh noes: 42\n"},
30		{"oh noes: %d %d", []interface{}{42, 666}, "zli.test: oh noes: 42 666\n"},
31		{[]byte("oh noes: %d %d"), []interface{}{42, 666}, "zli.test: oh noes: 42 666\n"},
32
33		{errors.New("oh noes"), nil, "zli.test: oh noes\n"},
34		{errors.New("oh noes"), []interface{}{"data", 666}, "zli.test: oh noes [data 666]\n"},
35
36		{mail.Address{Name: "asd", Address: "qwe"}, nil, "zli.test: {asd qwe}\n"},
37		{mail.Address{Name: "asd", Address: "qwe"}, []interface{}{"data", 666}, "zli.test: {asd qwe} [data 666]\n"},
38	}
39
40	for i, tt := range tests {
41		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
42			exit, _, out := Test(t)
43
44			func() {
45				defer exit.Recover()
46				Fatalf(tt.in, tt.args...)
47			}()
48
49			if *exit != 1 {
50				t.Errorf("wrong exit: %d", *exit)
51			}
52			got := out.String()
53			if got != tt.want {
54				t.Errorf("\ngot:  %q\nwant: %q", got, tt.want)
55			}
56		})
57	}
58}
59
60func TestF(t *testing.T) {
61	t.Run("nil", func(t *testing.T) {
62		_, _, out := Test(t)
63
64		var err error
65		F(err) // Will panic if exit is set.
66
67		if out.String() != "" {
68			t.Errorf("out has data: %q", out.String())
69		}
70	})
71
72	t.Run("nil", func(t *testing.T) {
73		exit, _, out := Test(t)
74
75		func() {
76			defer exit.Recover()
77			F(errors.New("oh noes"))
78		}()
79
80		if *exit != 1 {
81			t.Errorf("wrong exit: %d", *exit)
82		}
83		if out.String() != "zli.test: oh noes\n" {
84			t.Errorf("wrong out: %q", out.String())
85		}
86	})
87}
88
89func TestInputOrFile(t *testing.T) {
90	tests := []struct {
91		in            string
92		stdin         io.Reader
93		want, wantErr string
94	}{
95		{"/nonexistent", nil, "", "no such file or directory"},
96		{"zli_test.go", strings.NewReader("xx"), "package zli", ""},
97		{"-", strings.NewReader("xx yy\nzz"), "xx yy\nzz", ""},
98		{"", strings.NewReader("xx yy\nzz"), "xx yy\nzz", ""},
99	}
100
101	for _, tt := range tests {
102		t.Run(tt.in, func(t *testing.T) {
103			Stdin = tt.stdin
104			defer func() { Stdin = os.Stdin }()
105
106			fp, err := InputOrFile(tt.in, true)
107			if !errorContains(err, tt.wantErr) {
108				t.Errorf("wrong error\ngot:  %s\nwant: %s", err, tt.wantErr)
109			}
110
111			// No need to test the test if there's an error.
112			if err != nil {
113				return
114			}
115
116			if fp == nil {
117				t.Fatal("fp is nil")
118			}
119
120			got, err := ioutil.ReadAll(fp)
121			if err != nil {
122				t.Errorf("error reading fp: %s", err)
123			}
124
125			err = fp.Close()
126			if err != nil {
127				t.Errorf("error closing fp: %s", err)
128			}
129
130			g := string(got)
131			if len(g) > len(tt.want) {
132				g = g[:len(tt.want)]
133			}
134			if !strings.HasPrefix(g, tt.want) {
135				t.Errorf("wrong output\ngot:  %q\nwant: %q", g, tt.want)
136			}
137		})
138	}
139}
140
141func TestInputOrArgs(t *testing.T) {
142	tests := []struct {
143		in    []string
144		sep   string
145		stdin io.Reader
146		want  []string
147	}{
148		{[]string{"arg"}, "", nil, []string{"arg"}},
149		{nil, "", strings.NewReader(""), []string{}},
150		{nil, "\n ", strings.NewReader(""), []string{}},
151
152		{nil, "", strings.NewReader("a"), []string{"a"}},
153		{[]string{}, "", strings.NewReader("a"), []string{"a"}},
154		{[]string{}, "", strings.NewReader("a\nb c"), []string{"a\nb c"}},
155
156		{[]string{}, " ", strings.NewReader(" a b  c "), []string{"a", "b", "c"}},
157		{[]string{}, "\x00", strings.NewReader("aa\x00bb"), []string{"aa", "bb"}},
158	}
159
160	for _, tt := range tests {
161		t.Run(fmt.Sprintf("%s", tt.in), func(t *testing.T) {
162			Stdin = tt.stdin
163			defer func() { Stdin = os.Stdin }()
164
165			got, err := InputOrArgs(tt.in, tt.sep, true)
166			if err != nil {
167				t.Fatal(err)
168			}
169
170			if !reflect.DeepEqual(got, tt.want) {
171				t.Errorf("\ngot:  %#v\nwant: %#v", got, tt.want)
172			}
173		})
174	}
175}
176
177func TestPager(t *testing.T) {
178	set := func(term bool) (*bytes.Buffer, func()) {
179		buf := new(bytes.Buffer)
180		Stdout = buf
181		Stderr = buf
182
183		save := IsTerminal
184		if term {
185			IsTerminal = func(uintptr) bool { return true }
186		}
187
188		return buf, func() {
189			Stdout = os.Stdout
190			Stderr = os.Stderr
191			IsTerminal = save
192		}
193	}
194
195	t.Run("not a terminal", func(t *testing.T) {
196		buf, c := set(false)
197		defer c()
198
199		Pager(strings.NewReader("buffy"))
200		if buf.String() != "buffy" {
201			t.Error(buf.String())
202		}
203	})
204
205	t.Run("no PAGER", func(t *testing.T) {
206		buf, c := set(true)
207		defer c()
208
209		os.Unsetenv("PAGER")
210		Pager(strings.NewReader("buffy"))
211		if buf.String() != "buffy" {
212			t.Errorf("out: %q", buf.String())
213		}
214	})
215
216	t.Run("PAGER doesn't exist", func(t *testing.T) {
217		buf, c := set(true)
218		defer c()
219
220		os.Setenv("PAGER", "doesntexistasdad")
221		Pager(strings.NewReader("buffy"))
222
223		want := "zli.test: zli.Pager: running $PAGER: exec: \"doesntexistasdad\": executable file not found in $PATH\nbuffy"
224		if buf.String() != want {
225			t.Errorf("out: %q", buf.String())
226		}
227	})
228
229	t.Run("PAGER doesn't exist w/ args", func(t *testing.T) {
230		buf, c := set(true)
231		defer c()
232
233		os.Setenv("PAGER", "doesntexistasdad -r -f")
234		Pager(strings.NewReader("buffy"))
235
236		want := "zli.test: zli.Pager: running $PAGER: exec: \"doesntexistasdad\": executable file not found in $PATH\nbuffy"
237		if buf.String() != want {
238			t.Errorf("out: %q", buf.String())
239		}
240	})
241
242	t.Run("error", func(t *testing.T) {
243		buf, c := set(true)
244		defer c()
245
246		// zli.Pager: running $PAGER: exit status 1
247		os.Setenv("PAGER", "false")
248		Pager(strings.NewReader("buffy"))
249
250		want := "zli.test: zli.Pager: running $PAGER: exit status 1\n"
251		if buf.String() != want {
252			t.Errorf("out: %q", buf.String())
253		}
254	})
255
256	t.Run("run it", func(t *testing.T) {
257		if runtime.GOOS == "windows" || runtime.GOOS == "js" {
258			t.Skip("requires cat shell tool")
259		}
260
261		buf, c := set(true)
262		defer c()
263
264		os.Setenv("PAGER", "cat")
265		Pager(strings.NewReader("buffy"))
266
267		if buf.String() != "buffy" {
268			t.Errorf("out: %q", buf.String())
269		}
270	})
271
272	t.Run("pagestdout", func(t *testing.T) {
273		buf, c := set(true)
274		defer c()
275
276		func() {
277			defer PagerStdout()()
278			fmt.Fprintf(Stdout, "buffy")
279		}()
280
281		if buf.String() != "buffy" {
282			t.Errorf("out: %q", buf.String())
283		}
284
285	})
286}
287
288func errorContains(out error, want string) bool {
289	if out == nil {
290		return want == ""
291	}
292	if want == "" {
293		return false
294	}
295	return strings.Contains(out.Error(), want)
296}
297