1package flags
2
3import (
4	"bytes"
5	"io"
6	"os"
7	"path"
8	"path/filepath"
9	"reflect"
10	"runtime"
11	"strings"
12	"testing"
13)
14
15type TestComplete struct {
16}
17
18func (t *TestComplete) Complete(match string) []Completion {
19	options := []string{
20		"hello world",
21		"hello universe",
22		"hello multiverse",
23	}
24
25	ret := make([]Completion, 0, len(options))
26
27	for _, o := range options {
28		if strings.HasPrefix(o, match) {
29			ret = append(ret, Completion{
30				Item: o,
31			})
32		}
33	}
34
35	return ret
36}
37
38var completionTestOptions struct {
39	Verbose  bool `short:"v" long:"verbose" description:"Verbose messages"`
40	Debug    bool `short:"d" long:"debug" description:"Enable debug"`
41	Info     bool `short:"i" description:"Display info"`
42	Version  bool `long:"version" description:"Show version"`
43	Required bool `long:"required" required:"true" description:"This is required"`
44	Hidden   bool `long:"hidden" hidden:"true" description:"This is hidden"`
45
46	AddCommand struct {
47		Positional struct {
48			Filename Filename
49		} `positional-args:"yes"`
50	} `command:"add" description:"add an item"`
51
52	AddMultiCommand struct {
53		Positional struct {
54			Filename []Filename
55		} `positional-args:"yes"`
56		Extra []Filename `short:"f"`
57	} `command:"add-multi" description:"add multiple items"`
58
59	AddMultiCommandFlag struct {
60		Files []Filename `short:"f"`
61	} `command:"add-multi-flag" description:"add multiple items via flags"`
62
63	RemoveCommand struct {
64		Other bool     `short:"o"`
65		File  Filename `short:"f" long:"filename"`
66	} `command:"rm" description:"remove an item"`
67
68	RenameCommand struct {
69		Completed TestComplete `short:"c" long:"completed"`
70	} `command:"rename" description:"rename an item"`
71}
72
73type completionTest struct {
74	Args             []string
75	Completed        []string
76	ShowDescriptions bool
77}
78
79var completionTests []completionTest
80
81func init() {
82	_, sourcefile, _, _ := runtime.Caller(0)
83	completionTestSourcedir := filepath.Join(filepath.SplitList(path.Dir(sourcefile))...)
84
85	completionTestFilename := []string{filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion_test.go")}
86
87	completionTests = []completionTest{
88		{
89			// Short names
90			[]string{"-"},
91			[]string{"--debug", "--required", "--verbose", "--version", "-i"},
92			false,
93		},
94
95		{
96			// Short names full
97			[]string{"-i"},
98			[]string{"-i"},
99			false,
100		},
101
102		{
103			// Short names concatenated
104			[]string{"-dv"},
105			[]string{"-dv"},
106			false,
107		},
108
109		{
110			// Long names
111			[]string{"--"},
112			[]string{"--debug", "--required", "--verbose", "--version"},
113			false,
114		},
115
116		{
117			// Long names with descriptions
118			[]string{"--"},
119			[]string{
120				"--debug     # Enable debug",
121				"--required  # This is required",
122				"--verbose   # Verbose messages",
123				"--version   # Show version",
124			},
125			true,
126		},
127
128		{
129			// Long names partial
130			[]string{"--ver"},
131			[]string{"--verbose", "--version"},
132			false,
133		},
134
135		{
136			// Commands
137			[]string{""},
138			[]string{"add", "add-multi", "add-multi-flag", "rename", "rm"},
139			false,
140		},
141
142		{
143			// Commands with descriptions
144			[]string{""},
145			[]string{
146				"add             # add an item",
147				"add-multi       # add multiple items",
148				"add-multi-flag  # add multiple items via flags",
149				"rename          # rename an item",
150				"rm              # remove an item",
151			},
152			true,
153		},
154
155		{
156			// Commands partial
157			[]string{"r"},
158			[]string{"rename", "rm"},
159			false,
160		},
161
162		{
163			// Positional filename
164			[]string{"add", filepath.Join(completionTestSourcedir, "completion")},
165			completionTestFilename,
166			false,
167		},
168
169		{
170			// Multiple positional filename (1 arg)
171			[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion")},
172			completionTestFilename,
173			false,
174		},
175		{
176			// Multiple positional filename (2 args)
177			[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
178			completionTestFilename,
179			false,
180		},
181		{
182			// Multiple positional filename (3 args)
183			[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
184			completionTestFilename,
185			false,
186		},
187
188		{
189			// Flag filename
190			[]string{"rm", "-f", path.Join(completionTestSourcedir, "completion")},
191			completionTestFilename,
192			false,
193		},
194
195		{
196			// Flag short concat last filename
197			[]string{"rm", "-of", path.Join(completionTestSourcedir, "completion")},
198			completionTestFilename,
199			false,
200		},
201
202		{
203			// Flag concat filename
204			[]string{"rm", "-f" + path.Join(completionTestSourcedir, "completion")},
205			[]string{"-f" + completionTestFilename[0], "-f" + completionTestFilename[1]},
206			false,
207		},
208
209		{
210			// Flag equal concat filename
211			[]string{"rm", "-f=" + path.Join(completionTestSourcedir, "completion")},
212			[]string{"-f=" + completionTestFilename[0], "-f=" + completionTestFilename[1]},
213			false,
214		},
215
216		{
217			// Flag concat long filename
218			[]string{"rm", "--filename=" + path.Join(completionTestSourcedir, "completion")},
219			[]string{"--filename=" + completionTestFilename[0], "--filename=" + completionTestFilename[1]},
220			false,
221		},
222
223		{
224			// Flag long filename
225			[]string{"rm", "--filename", path.Join(completionTestSourcedir, "completion")},
226			completionTestFilename,
227			false,
228		},
229
230		{
231			// Custom completed
232			[]string{"rename", "-c", "hello un"},
233			[]string{"hello universe"},
234			false,
235		},
236		{
237			// Multiple flag filename
238			[]string{"add-multi-flag", "-f", filepath.Join(completionTestSourcedir, "completion")},
239			completionTestFilename,
240			false,
241		},
242	}
243}
244
245func TestCompletion(t *testing.T) {
246	p := NewParser(&completionTestOptions, Default)
247	c := &completion{parser: p}
248
249	for _, test := range completionTests {
250		if test.ShowDescriptions {
251			continue
252		}
253
254		ret := c.complete(test.Args)
255		items := make([]string, len(ret))
256
257		for i, v := range ret {
258			items[i] = v.Item
259		}
260
261		if !reflect.DeepEqual(items, test.Completed) {
262			t.Errorf("Args: %#v, %#v\n  Expected: %#v\n  Got:     %#v", test.Args, test.ShowDescriptions, test.Completed, items)
263		}
264	}
265}
266
267func TestParserCompletion(t *testing.T) {
268	for _, test := range completionTests {
269		if test.ShowDescriptions {
270			os.Setenv("GO_FLAGS_COMPLETION", "verbose")
271		} else {
272			os.Setenv("GO_FLAGS_COMPLETION", "1")
273		}
274
275		tmp := os.Stdout
276
277		r, w, _ := os.Pipe()
278		os.Stdout = w
279
280		out := make(chan string)
281
282		go func() {
283			var buf bytes.Buffer
284
285			io.Copy(&buf, r)
286
287			out <- buf.String()
288		}()
289
290		p := NewParser(&completionTestOptions, None)
291
292		p.CompletionHandler = func(items []Completion) {
293			comp := &completion{parser: p}
294			comp.print(items, test.ShowDescriptions)
295		}
296
297		_, err := p.ParseArgs(test.Args)
298
299		w.Close()
300
301		os.Stdout = tmp
302
303		if err != nil {
304			t.Fatalf("Unexpected error: %s", err)
305		}
306
307		got := strings.Split(strings.Trim(<-out, "\n"), "\n")
308
309		if !reflect.DeepEqual(got, test.Completed) {
310			t.Errorf("Expected: %#v\nGot: %#v", test.Completed, got)
311		}
312	}
313
314	os.Setenv("GO_FLAGS_COMPLETION", "")
315}
316