1/*
2 * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17package spew_test
18
19import (
20	"bytes"
21	"fmt"
22	"io/ioutil"
23	"os"
24	"testing"
25
26	"github.com/davecgh/go-spew/spew"
27)
28
29// spewFunc is used to identify which public function of the spew package or
30// ConfigState a test applies to.
31type spewFunc int
32
33const (
34	fCSFdump spewFunc = iota
35	fCSFprint
36	fCSFprintf
37	fCSFprintln
38	fCSPrint
39	fCSPrintln
40	fCSSdump
41	fCSSprint
42	fCSSprintf
43	fCSSprintln
44	fCSErrorf
45	fCSNewFormatter
46	fErrorf
47	fFprint
48	fFprintln
49	fPrint
50	fPrintln
51	fSdump
52	fSprint
53	fSprintf
54	fSprintln
55)
56
57// Map of spewFunc values to names for pretty printing.
58var spewFuncStrings = map[spewFunc]string{
59	fCSFdump:        "ConfigState.Fdump",
60	fCSFprint:       "ConfigState.Fprint",
61	fCSFprintf:      "ConfigState.Fprintf",
62	fCSFprintln:     "ConfigState.Fprintln",
63	fCSSdump:        "ConfigState.Sdump",
64	fCSPrint:        "ConfigState.Print",
65	fCSPrintln:      "ConfigState.Println",
66	fCSSprint:       "ConfigState.Sprint",
67	fCSSprintf:      "ConfigState.Sprintf",
68	fCSSprintln:     "ConfigState.Sprintln",
69	fCSErrorf:       "ConfigState.Errorf",
70	fCSNewFormatter: "ConfigState.NewFormatter",
71	fErrorf:         "spew.Errorf",
72	fFprint:         "spew.Fprint",
73	fFprintln:       "spew.Fprintln",
74	fPrint:          "spew.Print",
75	fPrintln:        "spew.Println",
76	fSdump:          "spew.Sdump",
77	fSprint:         "spew.Sprint",
78	fSprintf:        "spew.Sprintf",
79	fSprintln:       "spew.Sprintln",
80}
81
82func (f spewFunc) String() string {
83	if s, ok := spewFuncStrings[f]; ok {
84		return s
85	}
86	return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
87}
88
89// spewTest is used to describe a test to be performed against the public
90// functions of the spew package or ConfigState.
91type spewTest struct {
92	cs     *spew.ConfigState
93	f      spewFunc
94	format string
95	in     interface{}
96	want   string
97}
98
99// spewTests houses the tests to be performed against the public functions of
100// the spew package and ConfigState.
101//
102// These tests are only intended to ensure the public functions are exercised
103// and are intentionally not exhaustive of types.  The exhaustive type
104// tests are handled in the dump and format tests.
105var spewTests []spewTest
106
107// redirStdout is a helper function to return the standard output from f as a
108// byte slice.
109func redirStdout(f func()) ([]byte, error) {
110	tempFile, err := ioutil.TempFile("", "ss-test")
111	if err != nil {
112		return nil, err
113	}
114	fileName := tempFile.Name()
115	defer os.Remove(fileName) // Ignore error
116
117	origStdout := os.Stdout
118	os.Stdout = tempFile
119	f()
120	os.Stdout = origStdout
121	tempFile.Close()
122
123	return ioutil.ReadFile(fileName)
124}
125
126func initSpewTests() {
127	// Config states with various settings.
128	scsDefault := spew.NewDefaultConfig()
129	scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
130	scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
131	scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
132	scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
133	scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
134	scsNoCap := &spew.ConfigState{DisableCapacities: true}
135
136	// Variables for tests on types which implement Stringer interface with and
137	// without a pointer receiver.
138	ts := stringer("test")
139	tps := pstringer("test")
140
141	type ptrTester struct {
142		s *struct{}
143	}
144	tptr := &ptrTester{s: &struct{}{}}
145
146	// depthTester is used to test max depth handling for structs, array, slices
147	// and maps.
148	type depthTester struct {
149		ic    indirCir1
150		arr   [1]string
151		slice []string
152		m     map[string]int
153	}
154	dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
155		map[string]int{"one": 1}}
156
157	// Variable for tests on types which implement error interface.
158	te := customError(10)
159
160	spewTests = []spewTest{
161		{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
162		{scsDefault, fCSFprint, "", int16(32767), "32767"},
163		{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
164		{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
165		{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
166		{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
167		{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
168		{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
169		{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
170		{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
171		{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
172		{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
173		{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
174		{scsDefault, fFprint, "", float32(3.14), "3.14"},
175		{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
176		{scsDefault, fPrint, "", true, "true"},
177		{scsDefault, fPrintln, "", false, "false\n"},
178		{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
179		{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
180		{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
181		{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
182		{scsNoMethods, fCSFprint, "", ts, "test"},
183		{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
184		{scsNoMethods, fCSFprint, "", tps, "test"},
185		{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
186		{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
187		{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
188		{scsNoPmethods, fCSFprint, "", tps, "test"},
189		{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
190		{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
191		{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
192			" ic: (spew_test.indirCir1) {\n  <max depth reached>\n },\n" +
193			" arr: ([1]string) (len=1 cap=1) {\n  <max depth reached>\n },\n" +
194			" slice: ([]string) (len=1 cap=1) {\n  <max depth reached>\n },\n" +
195			" m: (map[string]int) (len=1) {\n  <max depth reached>\n }\n}\n"},
196		{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
197		{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
198			"(len=4) (stringer test) \"test\"\n"},
199		{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
200		{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
201			"(error: 10) 10\n"},
202		{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
203		{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
204		{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
205		{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
206	}
207}
208
209// TestSpew executes all of the tests described by spewTests.
210func TestSpew(t *testing.T) {
211	initSpewTests()
212
213	t.Logf("Running %d tests", len(spewTests))
214	for i, test := range spewTests {
215		buf := new(bytes.Buffer)
216		switch test.f {
217		case fCSFdump:
218			test.cs.Fdump(buf, test.in)
219
220		case fCSFprint:
221			test.cs.Fprint(buf, test.in)
222
223		case fCSFprintf:
224			test.cs.Fprintf(buf, test.format, test.in)
225
226		case fCSFprintln:
227			test.cs.Fprintln(buf, test.in)
228
229		case fCSPrint:
230			b, err := redirStdout(func() { test.cs.Print(test.in) })
231			if err != nil {
232				t.Errorf("%v #%d %v", test.f, i, err)
233				continue
234			}
235			buf.Write(b)
236
237		case fCSPrintln:
238			b, err := redirStdout(func() { test.cs.Println(test.in) })
239			if err != nil {
240				t.Errorf("%v #%d %v", test.f, i, err)
241				continue
242			}
243			buf.Write(b)
244
245		case fCSSdump:
246			str := test.cs.Sdump(test.in)
247			buf.WriteString(str)
248
249		case fCSSprint:
250			str := test.cs.Sprint(test.in)
251			buf.WriteString(str)
252
253		case fCSSprintf:
254			str := test.cs.Sprintf(test.format, test.in)
255			buf.WriteString(str)
256
257		case fCSSprintln:
258			str := test.cs.Sprintln(test.in)
259			buf.WriteString(str)
260
261		case fCSErrorf:
262			err := test.cs.Errorf(test.format, test.in)
263			buf.WriteString(err.Error())
264
265		case fCSNewFormatter:
266			fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
267
268		case fErrorf:
269			err := spew.Errorf(test.format, test.in)
270			buf.WriteString(err.Error())
271
272		case fFprint:
273			spew.Fprint(buf, test.in)
274
275		case fFprintln:
276			spew.Fprintln(buf, test.in)
277
278		case fPrint:
279			b, err := redirStdout(func() { spew.Print(test.in) })
280			if err != nil {
281				t.Errorf("%v #%d %v", test.f, i, err)
282				continue
283			}
284			buf.Write(b)
285
286		case fPrintln:
287			b, err := redirStdout(func() { spew.Println(test.in) })
288			if err != nil {
289				t.Errorf("%v #%d %v", test.f, i, err)
290				continue
291			}
292			buf.Write(b)
293
294		case fSdump:
295			str := spew.Sdump(test.in)
296			buf.WriteString(str)
297
298		case fSprint:
299			str := spew.Sprint(test.in)
300			buf.WriteString(str)
301
302		case fSprintf:
303			str := spew.Sprintf(test.format, test.in)
304			buf.WriteString(str)
305
306		case fSprintln:
307			str := spew.Sprintln(test.in)
308			buf.WriteString(str)
309
310		default:
311			t.Errorf("%v #%d unrecognized function", test.f, i)
312			continue
313		}
314		s := buf.String()
315		if test.want != s {
316			t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
317			continue
318		}
319	}
320}
321