1package e2e_test
2
3import (
4	"bytes"
5	"encoding/json"
6	"fmt"
7	"io"
8	"regexp"
9	"strings"
10	"testing"
11
12	"github.com/fatih/color"
13	"github.com/google/go-cmp/cmp"
14	"github.com/ktr0731/evans/app"
15	"github.com/ktr0731/evans/cui"
16	"github.com/ktr0731/evans/meta"
17	"github.com/ktr0731/evans/mode"
18	"github.com/ktr0731/evans/usecase"
19	"github.com/pkg/errors"
20)
21
22var replacer = regexp.MustCompile(`access-control-expose-headers: .*\n`)
23
24func TestE2E_CLI(t *testing.T) {
25	commonFlags := []string{"--verbose"}
26
27	cases := map[string]struct {
28		// Common flags all sub-commands can have.
29		commonFlags string
30		cmd         string
31		// Space separated arguments text.
32		args string
33
34		// The server enables TLS.
35		tls bool
36
37		// The server enables gRPC reflection.
38		reflection bool
39
40		// The server uses gRPC-Web protocol.
41		web bool
42
43		// Register a service that has no package.
44		registerEmptyPackageService bool
45
46		// beforeTest set up a testcase specific environment.
47		// If beforeTest is nil, it is ignored.
48		// beforeTest may return a function named afterTest that cleans up
49		// the environment. If afterTest is nil, it is ignored.
50		beforeTest func(t *testing.T) (afterTest func(t *testing.T))
51
52		// assertTest checks whether the output is expected.
53		// If nil, it will be ignored.
54		assertTest func(t *testing.T, output string)
55
56		// The output we expected. It is ignored if expectedCode isn't 0.
57		expectedOut string
58		// assertWithGolden asserts the output with the golden file.
59		assertWithGolden bool
60
61		// The exit code we expected.
62		expectedCode int
63
64		// Each output is formatted by flatten() for remove break lines.
65		// But, if it is prefer to show as it is, you can it by specifying
66		// unflatten to true.
67		unflatten bool
68
69		deprecatedUsage bool
70	}{
71		"print usage text to the Writer (common flag)": {
72			commonFlags:      "--help",
73			assertWithGolden: true,
74			unflatten:        true,
75		},
76		"print usage text to the Writer": {
77			args:             "--help",
78			assertWithGolden: true,
79			unflatten:        true,
80		},
81		"print version text to the Writer (common flag)": {
82			commonFlags: "--version",
83			expectedOut: fmt.Sprintf("evans %s\n", meta.Version),
84			unflatten:   true,
85		},
86		"print version text to the Writer": {
87			args:        "--version",
88			expectedOut: fmt.Sprintf("evans %s\n", meta.Version),
89			unflatten:   true,
90		},
91		"cannot specify both of --cli and --repl": {
92			args:         "--cli --repl",
93			expectedCode: 1,
94		},
95		"cannot specify both of --tls and --web": {
96			args:         "--web --tls testdata/test.proto",
97			expectedCode: 1,
98		},
99		"cannot launch without proto files and reflection": {
100			args:         "",
101			expectedCode: 1,
102		},
103
104		// call command
105
106		"print call command usage": {
107			commonFlags:      "",
108			cmd:              "call",
109			args:             "-h",
110			assertWithGolden: true,
111		},
112		"cannot launch CLI mode because proto files didn't be passed": {
113			cmd:          "call",
114			args:         "--file testdata/unary_call.in api.Example.Unary",
115			expectedCode: 1,
116		},
117		"cannot launch CLI mode because package name is invalid value": {
118			commonFlags:  "--proto testdata/test.proto",
119			cmd:          "call",
120			args:         "--file testdata/unary_call.in foo.Example.Unary",
121			expectedCode: 1,
122		},
123		"cannot launch CLI mode because service name is invalid value": {
124			commonFlags:  "--proto testdata/test.proto",
125			cmd:          "call",
126			args:         "--file testdata/unary_call.in api.Foo.Unary",
127			expectedCode: 1,
128		},
129		"cannot launch CLI mode because method name is missing": {
130			commonFlags:  "--proto testdata/test.proto",
131			cmd:          "call",
132			args:         "--file testdata/unary_call.in api.Example",
133			expectedCode: 1,
134		},
135		"cannot launch CLI mode because method name is invalid value": {
136			commonFlags:  "--proto testdata/test.proto",
137			cmd:          "call",
138			args:         "--file testdata/unary_call.in api.Example.Foo",
139			expectedCode: 1,
140		},
141		"cannot launch CLI mode because the path of --file is invalid path": {
142			commonFlags:  "--proto testdata/test.proto",
143			cmd:          "call",
144			args:         "--file foo api.Example.Unary",
145			expectedCode: 1,
146		},
147		"cannot launch CLI mode because the path of --file is invalid input": {
148			commonFlags:  "--proto testdata/test.proto",
149			cmd:          "call",
150			args:         "--file testdata/invalid.in api.Example.Unary",
151			expectedCode: 1,
152		},
153		"cannot launch CLI mode because --header didn't have value": {
154			commonFlags:  "--header foo --proto testdata/test.proto",
155			cmd:          "call",
156			args:         "--file testdata/unary_call.in api.Example.Unary",
157			expectedCode: 1,
158		},
159		"call unary RPC with an input file by CLI mode (backward-compatibility)": {
160			commonFlags:     "--package api --service Example --proto testdata/test.proto",
161			cmd:             "call",
162			args:            "--file testdata/unary_call.in Unary",
163			deprecatedUsage: true,
164			expectedOut:     `{ "message": "hello, oumae" }`,
165		},
166		"call unary RPC with an input file by CLI mode": {
167			commonFlags: "--proto testdata/test.proto",
168			cmd:         "call",
169			args:        "--file testdata/unary_call.in api.Example.Unary",
170			expectedOut: `{ "message": "hello, oumae" }`,
171		},
172		"call fully-qualified unary RPC with an input file by CLI mode": {
173			commonFlags: "--proto testdata/test.proto",
174			cmd:         "call",
175			args:        "--file testdata/unary_call.in api.Example.Unary",
176			expectedOut: `{ "message": "hello, oumae" }`,
177		},
178		"call unary RPC with --call flag (backward-compatibility)": {
179			commonFlags:     "--package api --service Example --proto testdata/test.proto",
180			cmd:             "",
181			args:            "--file testdata/unary_call.in --call Unary",
182			deprecatedUsage: true,
183			expectedOut:     `{ "message": "hello, oumae" }`,
184		},
185		"call unary RPC without package name because the size of packages is 1 (backward-compatibility)": {
186			commonFlags:     "--service Example --proto testdata/test.proto",
187			cmd:             "call",
188			args:            "--file testdata/unary_call.in Unary",
189			deprecatedUsage: true,
190			expectedOut:     `{ "message": "hello, oumae" }`,
191		},
192		"call unary RPC without package and service name because the size of packages and services are 1": {
193			commonFlags: "--proto testdata/test.proto",
194			cmd:         "call",
195			args:        "--file testdata/unary_call.in Unary",
196			expectedOut: `{ "message": "hello, oumae" }`,
197		},
198		"call unary RPC with an input reader by CLI mode": {
199			commonFlags: "--proto testdata/test.proto",
200			cmd:         "call",
201			args:        "api.Example.Unary",
202			beforeTest: func(t *testing.T) func(*testing.T) {
203				old := mode.DefaultCLIReader
204				mode.DefaultCLIReader = strings.NewReader(`{"name": "oumae"}`)
205				return func(t *testing.T) {
206					mode.DefaultCLIReader = old
207				}
208			},
209			expectedOut: `{ "message": "hello, oumae" }`,
210		},
211		"call client streaming RPC by CLI mode": {
212			commonFlags: "--proto testdata/test.proto",
213			cmd:         "call",
214			args:        "--file testdata/client_streaming.in api.Example.ClientStreaming",
215			expectedOut: `{ "message": "you sent requests 4 times (oumae, kousaka, kawashima, kato)." }`,
216		},
217		"call server streaming RPC by CLI mode": {
218			commonFlags: "--proto testdata/test.proto",
219			cmd:         "call",
220			args:        "--file testdata/server_streaming.in api.Example.ServerStreaming",
221			expectedOut: `{ "message": "hello oumae, I greet 1 times." } { "message": "hello oumae, I greet 2 times." } { "message": "hello oumae, I greet 3 times." }`,
222		},
223		"call bidi streaming RPC by CLI mode": {
224			commonFlags: "--proto testdata/test.proto",
225			cmd:         "call",
226			args:        "--file testdata/bidi_streaming.in api.Example.BidiStreaming",
227			assertTest: func(t *testing.T, output string) {
228				dec := json.NewDecoder(strings.NewReader(output))
229				for {
230					var iface interface{}
231					err := dec.Decode(&iface)
232					if errors.Is(err, io.EOF) {
233						return
234					}
235					if err != nil {
236						t.Errorf("expected no errors, but got '%s'", err)
237					}
238				}
239			},
240		},
241		"call unary RPC with an input file and custom headers by CLI mode": {
242			commonFlags: "--header ogiso=setsuna --header touma=kazusa,youko --header sound=of=destiny --proto testdata/test.proto",
243			cmd:         "call",
244			args:        "--file testdata/unary_header.in api.Example.UnaryHeader",
245			assertTest: func(t *testing.T, output string) {
246				expectedStrings := []string{
247					"key = ogiso",
248					"val = setsuna",
249					"key = touma",
250					"val = kazusa, youko",
251					"key = sound",
252					"val = of=destiny",
253				}
254				for _, s := range expectedStrings {
255					if !strings.Contains(output, s) {
256						t.Errorf("expected to contain '%s', but missing in '%s'", s, output)
257					}
258				}
259			},
260		},
261
262		// call command with reflection
263
264		"cannot launch CLI mode with reflection because method name is missing": {
265			commonFlags:  "--reflection --proto testdata/test.proto",
266			cmd:          "call",
267			args:         "--file testdata/unary_call.in api.Example",
268			reflection:   true,
269			expectedCode: 1,
270		},
271		"cannot launch CLI mode with reflection because server didn't enable reflection": {
272			commonFlags:  "--reflection",
273			cmd:          "call",
274			args:         "--file testdata/unary_call.in api.Example.Unary",
275			reflection:   false,
276			expectedCode: 1,
277		},
278		"call unary RPC by CLI mode with reflection with an input file (backward-compatibility)": {
279			commonFlags:     "--reflection --package api --service Example",
280			cmd:             "call",
281			args:            "--file testdata/unary_call.in Unary",
282			reflection:      true,
283			deprecatedUsage: true,
284			expectedOut:     `{ "message": "hello, oumae" }`,
285		},
286		"call unary RPC by CLI mode with reflection with an input file": {
287			commonFlags: "--reflection",
288			cmd:         "call",
289			args:        "--file testdata/unary_call.in api.Example.Unary",
290			reflection:  true,
291			expectedOut: `{ "message": "hello, oumae" }`,
292		},
293
294		// call command with TLS
295
296		"cannot launch CLI mode with TLS because the server didn't enable TLS": {
297			commonFlags:  "--tls --proto testdata/test.proto",
298			cmd:          "call",
299			args:         "--file testdata/unary_call.in api.Example.Unary",
300			tls:          false,
301			expectedCode: 1,
302		},
303		"cannot launch CLI mode with TLS because the client didn't enable TLS": {
304			commonFlags:  "--proto testdata/test.proto",
305			cmd:          "call",
306			args:         "--file testdata/unary_call.in api.Example.Unary",
307			tls:          true,
308			expectedCode: 1,
309		},
310		"cannot launch CLI mode with TLS because cannot validate certs for 127.0.0.1 (default value)": {
311			commonFlags:  "--tls --proto testdata/test.proto",
312			cmd:          "call",
313			args:         "--file testdata/unary_call.in api.Example.Unary",
314			tls:          true,
315			expectedCode: 1,
316		},
317		"cannot launch CLI mode with TLS because signed authority is unknown": {
318			commonFlags:  "--tls --host localhost --proto testdata/test.proto",
319			cmd:          "call",
320			args:         "--file testdata/unary_call.in api.Example.Unary",
321			tls:          true,
322			expectedCode: 1,
323		},
324		"call unary RPC with TLS by CLI mode": {
325			commonFlags: "--tls --host localhost --cacert testdata/rootCA.pem --proto testdata/test.proto",
326			cmd:         "call",
327			args:        "--file testdata/unary_call.in api.Example.Unary",
328			tls:         true,
329			expectedOut: `{ "message": "hello, oumae" }`,
330		},
331		"cannot launch CLI mode with TLS and reflection by CLI mode because server didn't enable TLS": {
332			commonFlags:  "--tls -r --host localhost --cacert testdata/rootCA.pem",
333			cmd:          "call",
334			args:         "--file testdata/unary_call.in api.Example.Unary",
335			tls:          false,
336			reflection:   true,
337			expectedCode: 1,
338		},
339		"call unary RPC with TLS and reflection by CLI mode": {
340			commonFlags: "--tls -r --host localhost --cacert testdata/rootCA.pem",
341			cmd:         "call",
342			args:        "--file testdata/unary_call.in api.Example.Unary",
343			tls:         true,
344			reflection:  true,
345			expectedOut: `{ "message": "hello, oumae" }`,
346		},
347		"call unary RPC with TLS and --servername by CLI mode": {
348			commonFlags: "--tls --servername localhost --cacert testdata/rootCA.pem --proto testdata/test.proto",
349			cmd:         "call",
350			args:        "--file testdata/unary_call.in api.Example.Unary",
351			tls:         true,
352			expectedOut: `{ "message": "hello, oumae" }`,
353		},
354		"cannot launch CLI mode with mutual TLS auth because --certkey is missing": {
355			commonFlags:  "--tls --host localhost --cacert testdata/rootCA.pem --cert testdata/localhost.pem --proto testdata/test.proto",
356			cmd:          "call",
357			args:         "--file testdata/unary_call.in api.Example.Unary",
358			tls:          true,
359			expectedCode: 1,
360		},
361		"cannot launch CLI mode with mutual TLS auth because --cert is missing": {
362			commonFlags:  "--tls --host localhost --cacert testdata/rootCA.pem --certkey testdata/localhost-key.pem --proto testdata/test.proto",
363			cmd:          "call",
364			args:         "--file testdata/unary_call.in api.Example.Unary",
365			tls:          true,
366			expectedCode: 1,
367		},
368		"call unary RPC with mutual TLS auth by CLI mode": {
369			commonFlags: "--tls --host localhost --cacert testdata/rootCA.pem --cert testdata/localhost.pem --certkey testdata/localhost-key.pem --proto testdata/test.proto",
370			cmd:         "call",
371			args:        "--file testdata/unary_call.in api.Example.Unary",
372			tls:         true,
373			expectedOut: `{ "message": "hello, oumae" }`,
374		},
375
376		// call command with gRPC-Web
377
378		"cannot send a request to gRPC-Web server because the server didn't enable gRPC-Web": {
379			commonFlags:  "--web --proto testdata/test.proto",
380			cmd:          "call",
381			args:         "--file testdata/unary_call.in api.Example.Unary",
382			web:          false,
383			expectedCode: 1,
384		},
385		"call unary RPC with an input file by CLI mode against to gRPC-Web server": {
386			commonFlags: "--web --proto testdata/test.proto",
387			cmd:         "call",
388			args:        "--file testdata/unary_call.in api.Example.Unary",
389			web:         true,
390			expectedOut: `{ "message": "hello, oumae" }`,
391		},
392		"call unary RPC with an input file by CLI mode and reflection against to gRPC-Web server": {
393			commonFlags: "--web -r",
394			cmd:         "call",
395			args:        "--file testdata/unary_call.in api.Example.Unary",
396			web:         true,
397			reflection:  true,
398			expectedOut: `{ "message": "hello, oumae" }`,
399		},
400		"call client streaming RPC by CLI mode against to gRPC-Web server": {
401			commonFlags: "--web --proto testdata/test.proto",
402			cmd:         "call",
403			args:        "--file testdata/client_streaming.in api.Example.ClientStreaming",
404			web:         true,
405			expectedOut: `{ "message": "you sent requests 4 times (oumae, kousaka, kawashima, kato)." }`,
406		},
407		"call server streaming RPC by CLI mode against to gRPC-Web server": {
408			commonFlags: "--web --proto testdata/test.proto",
409			cmd:         "call",
410			args:        "--file testdata/server_streaming.in api.Example.ServerStreaming",
411			web:         true,
412			expectedOut: `{ "message": "hello oumae, I greet 1 times." } { "message": "hello oumae, I greet 2 times." } { "message": "hello oumae, I greet 3 times." }`,
413		},
414		"call bidi streaming RPC by CLI mode against to gRPC-Web server": {
415			commonFlags: "--web --proto testdata/test.proto",
416			cmd:         "call",
417			args:        "--file testdata/bidi_streaming.in api.Example.BidiStreaming",
418			web:         true,
419			assertTest: func(t *testing.T, output string) {
420				dec := json.NewDecoder(strings.NewReader(output))
421				for {
422					var iface interface{}
423					err := dec.Decode(&iface)
424					if errors.Is(err, io.EOF) {
425						return
426					}
427					if err != nil {
428						t.Errorf("expected no errors, but got '%s'", err)
429					}
430				}
431			},
432		},
433		"call unary RPC with --enrich flag": {
434			commonFlags:      "-r",
435			cmd:              "call",
436			args:             "--file testdata/unary_call.in --enrich api.Example.UnaryHeaderTrailer",
437			reflection:       true,
438			unflatten:        true,
439			assertWithGolden: true,
440		},
441		"call unary RPC with --enrich flag and JSON format": {
442			commonFlags:      "-r",
443			cmd:              "call",
444			args:             "--file testdata/unary_call.in --enrich --output json api.Example.UnaryHeaderTrailer",
445			reflection:       true,
446			unflatten:        true,
447			assertWithGolden: true,
448		},
449		"call failure unary RPC with --enrich flag": {
450			commonFlags:      "-r",
451			cmd:              "call",
452			args:             "--file testdata/unary_call.in --enrich api.Example.UnaryHeaderTrailerFailure",
453			reflection:       true,
454			unflatten:        true,
455			assertWithGolden: true,
456			expectedCode:     1,
457		},
458		"call failure unary RPC with --enrich and JSON format": {
459			commonFlags:      "-r",
460			cmd:              "call",
461			args:             "--file testdata/unary_call.in --enrich --output json api.Example.UnaryHeaderTrailerFailure",
462			reflection:       true,
463			unflatten:        true,
464			assertWithGolden: true,
465			expectedCode:     1,
466		},
467		"call unary RPC with --enrich flag against to gRPC-Web server": {
468			commonFlags:      "--web -r",
469			cmd:              "call",
470			args:             "--file testdata/unary_call.in --enrich api.Example.UnaryHeaderTrailer",
471			web:              true,
472			reflection:       true,
473			unflatten:        true,
474			assertWithGolden: true,
475		},
476		"call failure unary RPC with --enrich flag against to gRPC-Web server": {
477			commonFlags:      "--web -r",
478			cmd:              "call",
479			args:             "--file testdata/unary_call.in --enrich api.Example.UnaryHeaderTrailerFailure",
480			web:              true,
481			reflection:       true,
482			unflatten:        true,
483			assertWithGolden: true,
484			expectedCode:     1,
485		},
486
487		// list command
488		"print list command usage": {
489			commonFlags:      "",
490			cmd:              "list",
491			args:             "-h",
492			assertWithGolden: true,
493		},
494		"list services without args": {
495			commonFlags: "--proto testdata/test.proto",
496			cmd:         "list",
497			args:        "",
498			expectedOut: `api.Example`,
499		},
500		"list services without args and two protos": {
501			commonFlags: "--proto testdata/test.proto,testdata/empty_package.proto",
502			cmd:         "list",
503			args:        "",
504			expectedOut: `EmptyPackageService api.Example`,
505		},
506		"list services with name format": {
507			commonFlags: "--proto testdata/test.proto",
508			cmd:         "list",
509			args:        "-o name",
510			expectedOut: `api.Example`,
511		},
512		"list services with JSON format": {
513			commonFlags: "--proto testdata/test.proto",
514			cmd:         "list",
515			args:        "-o json",
516			expectedOut: `{ "services": [ { "name": "api.Example" } ] }`,
517		},
518		"list methods with name format": {
519			commonFlags:      "--proto testdata/test.proto",
520			cmd:              "list",
521			args:             "-o name api.Example",
522			assertWithGolden: true,
523		},
524		"list methods with JSON format": {
525			commonFlags:      "--proto testdata/test.proto",
526			cmd:              "list",
527			args:             "-o json api.Example",
528			assertWithGolden: true,
529		},
530		"list methods that have empty name": {
531			commonFlags: "--proto testdata/empty_package.proto",
532			cmd:         "list",
533			args:        "EmptyPackageService",
534			expectedOut: `EmptyPackageService.Unary`,
535		},
536		"list a method with name format": {
537			commonFlags: "--proto testdata/test.proto,testdata/empty_package.proto",
538			cmd:         "list",
539			args:        "-o name api.Example.Unary",
540			expectedOut: `api.Example.Unary`,
541		},
542		"list a method with JSON format": {
543			commonFlags: "--proto testdata/test.proto",
544			cmd:         "list",
545			args:        "-o json api.Example.Unary",
546			expectedOut: `{ "name": "Unary", "fully_qualified_name": "api.Example.Unary", "request_type": "api.SimpleRequest", "response_type": "api.SimpleResponse" }`,
547		},
548		"cannot list because of invalid package name": {
549			commonFlags:  "--proto testdata/test.proto",
550			cmd:          "list",
551			args:         "Foo",
552			expectedCode: 1,
553		},
554		"cannot list because of invalid service name": {
555			commonFlags:  "--proto testdata/test.proto",
556			cmd:          "list",
557			args:         "api.Foo",
558			expectedCode: 1,
559		},
560		"cannot list because of invalid method name": {
561			commonFlags:  "--proto testdata/test.proto",
562			cmd:          "list",
563			args:         "api.Example.UnaryFoo",
564			expectedCode: 1,
565		},
566
567		// desc command
568
569		"print desc command usage": {
570			commonFlags:      "",
571			cmd:              "desc",
572			args:             "-h",
573			assertWithGolden: true,
574		},
575		"describe all service descriptors": {
576			commonFlags:      "--proto testdata/test.proto,testdata/empty_package.proto",
577			cmd:              "desc",
578			args:             "",
579			assertWithGolden: true,
580		},
581		"describe a service descriptor": {
582			commonFlags:      "--proto testdata/test.proto,testdata/empty_package.proto",
583			cmd:              "desc",
584			args:             "api.Example",
585			assertWithGolden: true,
586		},
587		"describe a method descriptor": {
588			commonFlags:      "--proto testdata/test.proto,testdata/empty_package.proto",
589			cmd:              "desc",
590			args:             "api.Example.Unary",
591			assertWithGolden: true,
592		},
593		"describe a message descriptor": {
594			commonFlags:      "--proto testdata/test.proto,testdata/empty_package.proto",
595			cmd:              "desc",
596			args:             "api.SimpleRequest",
597			assertWithGolden: true,
598		},
599		"invalid symbol": {
600			commonFlags:  "--proto testdata/test.proto,testdata/empty_package.proto",
601			cmd:          "desc",
602			args:         "api.Foo",
603			expectedCode: 1,
604		},
605	}
606	for name, c := range cases {
607		c := c
608		t.Run(name, func(t *testing.T) {
609			defer usecase.Clear()
610
611			stopServer, port := startServer(t, c.tls, c.reflection, c.web, c.registerEmptyPackageService)
612			defer stopServer()
613
614			outBuf, eoutBuf := new(bytes.Buffer), new(bytes.Buffer)
615			cui := cui.New(cui.Writer(outBuf), cui.ErrWriter(eoutBuf))
616
617			args := commonFlags
618			args = append([]string{"--port", port}, args...)
619			if c.commonFlags != "" {
620				args = append(args, strings.Split(c.commonFlags, " ")...)
621			}
622			args = append(args, "cli")
623			if c.cmd != "" {
624				args = append(args, c.cmd)
625			}
626			if c.args != "" {
627				args = append(args, strings.Split(c.args, " ")...)
628			}
629
630			if c.beforeTest != nil {
631				afterTest := c.beforeTest(t)
632				if afterTest != nil {
633					defer afterTest(t)
634				}
635			}
636
637			a := app.New(cui)
638			code := a.Run(args)
639			if code != c.expectedCode {
640				t.Errorf("unexpected code returned: expected = %d, actual = %d", c.expectedCode, code)
641			}
642
643			actual := outBuf.String()
644			if !c.unflatten {
645				actual = flatten(actual)
646			}
647
648			if c.expectedCode == 0 {
649				if c.expectedOut != "" && actual != c.expectedOut {
650					t.Errorf("unexpected output:\n%s", cmp.Diff(c.expectedOut, actual))
651				}
652				eout := eoutBuf.String()
653				if c.deprecatedUsage {
654					// Trim "deprecated" message.
655					eout = strings.Replace(eout, color.YellowString("evans: deprecated usage, please use sub-commands. see `evans -h` for more details.")+"\n", "", -1)
656				}
657				if eout != "" {
658					t.Errorf("expected code is 0, but got an error message: '%s'", eoutBuf.String())
659				}
660			}
661			if c.assertTest != nil {
662				c.assertTest(t, actual)
663			}
664			if c.assertWithGolden {
665				s := outBuf.String()
666				s = replacer.ReplaceAllString(s, "")
667				compareWithGolden(t, s)
668			}
669		})
670	}
671}
672