parse_test.go 29.1 KB
Newer Older
1 2 3
package cli

import (
4 5
	"context"
	"fmt"
6 7
	"io"
	"io/ioutil"
jmank88's avatar
jmank88 committed
8
	"net/url"
9
	"os"
10
	"path"
rht's avatar
rht committed
11 12
	"strings"
	"testing"
Matt Bell's avatar
Matt Bell committed
13

Hector Sanjuan's avatar
Hector Sanjuan committed
14 15
	files "github.com/ipfs/go-ipfs-files"

16
	cmds "github.com/ipfs/go-ipfs-cmds"
17 18
)

19 20 21 22
type kvs map[string]interface{}
type words []string

func sameWords(a words, b words) bool {
23 24 25
	if len(a) != len(b) {
		return false
	}
26 27 28 29 30 31 32 33 34 35 36 37 38
	for i, w := range a {
		if w != b[i] {
			return false
		}
	}
	return true
}

func sameKVs(a kvs, b kvs) bool {
	if len(a) != len(b) {
		return false
	}
	for k, v := range a {
39 40 41 42 43 44 45 46
		if ks, ok := v.([]string); ok {
			bks, _ := b[k].([]string)
			for i := 0; i < len(ks); i++ {
				if ks[i] != bks[i] {
					return false
				}
			}
		} else if v != b[k] {
47 48 49 50 51 52
			return false
		}
	}
	return true
}

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
func TestSameWords(t *testing.T) {
	a := []string{"v1", "v2"}
	b := []string{"v1", "v2", "v3"}
	c := []string{"v2", "v3"}
	d := []string{"v2"}
	e := []string{"v2", "v3"}
	f := []string{"v2", "v1"}

	test := func(a words, b words, v bool) {
		if sameWords(a, b) != v {
			t.Errorf("sameWords('%v', '%v') != %v", a, b, v)
		}
	}

	test(a, b, false)
	test(a, a, true)
	test(a, c, false)
	test(b, c, false)
	test(c, d, false)
	test(c, e, true)
	test(b, e, false)
	test(a, b, false)
	test(a, f, false)
	test(e, f, false)
	test(f, f, true)
}

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
func testOptionHelper(t *testing.T, cmd *cmds.Command, args string, expectedOpts kvs, expectedWords words, expectErr bool) {
	req := &cmds.Request{}
	err := parse(req, strings.Split(args, " "), cmd)
	if err == nil {
		err = req.FillDefaults()
	}
	if expectErr {
		if err == nil {
			t.Errorf("Command line '%v' parsing should have failed", args)
		}
	} else if err != nil {
		t.Errorf("Command line '%v' failed to parse: %v", args, err)
	} else if !sameWords(req.Arguments, expectedWords) || !sameKVs(kvs(req.Options), expectedOpts) {
		t.Errorf("Command line '%v':\n  parsed as  %v %v\n  instead of %v %v",
			args, req.Options, req.Arguments, expectedOpts, expectedWords)
	}
}

98
func TestOptionParsing(t *testing.T) {
Jan Winkelmann's avatar
Jan Winkelmann committed
99
	cmd := &cmds.Command{
Steven Allen's avatar
Steven Allen committed
100 101
		Options: []cmds.Option{
			cmds.StringOption("string", "s", "a string"),
102
			cmds.StringOption("flag", "alias", "multiple long"),
Steven Allen's avatar
Steven Allen committed
103
			cmds.BoolOption("bool", "b", "a bool"),
104
			cmds.StringsOption("strings", "r", "strings array"),
105
			cmds.DelimitedStringsOption(",", "delimstrings", "d", "comma delimited string array"),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
106
		},
Jan Winkelmann's avatar
Jan Winkelmann committed
107
		Subcommands: map[string]*cmds.Command{
108 109
			"test": &cmds.Command{},
			"defaults": &cmds.Command{
Steven Allen's avatar
Steven Allen committed
110 111
				Options: []cmds.Option{
					cmds.StringOption("opt", "o", "an option").WithDefault("def"),
112 113
				},
			},
Matt Bell's avatar
Matt Bell committed
114
		},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
115
	}
116

Etienne Laurin's avatar
Etienne Laurin committed
117
	testFail := func(args string) {
118
		testOptionHelper(t, cmd, args, kvs{}, words{}, true)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
119
	}
Etienne Laurin's avatar
Etienne Laurin committed
120 121

	test := func(args string, expectedOpts kvs, expectedWords words) {
122
		testOptionHelper(t, cmd, args, expectedOpts, expectedWords, false)
Matt Bell's avatar
Matt Bell committed
123
	}
Etienne Laurin's avatar
Etienne Laurin committed
124

125
	test("test -", kvs{}, words{"-"})
Etienne Laurin's avatar
Etienne Laurin committed
126 127 128
	testFail("-b -b")
	test("test beep boop", kvs{}, words{"beep", "boop"})
	testFail("-s")
129 130 131 132 133 134 135
	test("-s foo", kvs{"string": "foo"}, words{})
	test("-sfoo", kvs{"string": "foo"}, words{})
	test("-s=foo", kvs{"string": "foo"}, words{})
	test("-b", kvs{"bool": true}, words{})
	test("-bs foo", kvs{"bool": true, "string": "foo"}, words{})
	test("-sb", kvs{"string": "b"}, words{})
	test("-b test foo", kvs{"bool": true}, words{"foo"})
136
	test("--bool test foo", kvs{"bool": true}, words{"foo"})
Etienne Laurin's avatar
Etienne Laurin committed
137 138 139 140 141
	testFail("--bool=foo")
	testFail("--string")
	test("--string foo", kvs{"string": "foo"}, words{})
	test("--string=foo", kvs{"string": "foo"}, words{})
	test("-- -b", kvs{}, words{"-b"})
142 143 144 145 146
	test("test foo -b", kvs{"bool": true}, words{"foo"})
	test("-b=false", kvs{"bool": false}, words{})
	test("-b=true", kvs{"bool": true}, words{})
	test("-b=false test foo", kvs{"bool": false}, words{"foo"})
	test("-b=true test foo", kvs{"bool": true}, words{"foo"})
147 148
	test("--bool=true test foo", kvs{"bool": true}, words{"foo"})
	test("--bool=false test foo", kvs{"bool": false}, words{"foo"})
149 150 151 152 153 154 155 156
	test("-b test true", kvs{"bool": true}, words{"true"})
	test("-b test false", kvs{"bool": true}, words{"false"})
	test("-b=FaLsE test foo", kvs{"bool": false}, words{"foo"})
	test("-b=TrUe test foo", kvs{"bool": true}, words{"foo"})
	test("-b test true", kvs{"bool": true}, words{"true"})
	test("-b test false", kvs{"bool": true}, words{"false"})
	test("-b --string foo test bar", kvs{"bool": true, "string": "foo"}, words{"bar"})
	test("-b=false --string bar", kvs{"bool": false, "string": "bar"}, words{})
157
	test("--strings a --strings b", kvs{"strings": []string{"a", "b"}}, words{})
158 159 160 161 162 163 164

	test("--delimstrings a,b", kvs{"delimstrings": []string{"a", "b"}}, words{})
	test("--delimstrings=a,b", kvs{"delimstrings": []string{"a", "b"}}, words{})
	test("-d a,b", kvs{"delimstrings": []string{"a", "b"}}, words{})
	test("-d=a,b", kvs{"delimstrings": []string{"a", "b"}}, words{})
	test("-d=a,b -d c --delimstrings d", kvs{"delimstrings": []string{"a", "b", "c", "d"}}, words{})

165
	testFail("foo test")
166 167
	test("defaults", kvs{"opt": "def"}, words{})
	test("defaults -o foo", kvs{"opt": "foo"}, words{})
168

169 170 171 172 173
	test("--flag=foo", kvs{"flag": "foo"}, words{})
	test("--alias=foo", kvs{"flag": "foo"}, words{})
	testFail("--flag=bar --alias=foo")
	testFail("--alias=bar --flag=foo")

174 175 176 177 178
	testFail("--bad-flag")
	testFail("--bad-flag=")
	testFail("--bad-flag=xyz")
	testFail("-z")
	testFail("-zz--- --")
179
}
180

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
func TestDefaultOptionParsing(t *testing.T) {
	testPanic := func(f func()) {
		fnFinished := false
		defer func() {
			if r := recover(); fnFinished == true {
				panic(r)
			}
		}()
		f()
		fnFinished = true
		t.Error("expected panic")
	}

	testPanic(func() { cmds.StringOption("string", "s", "a string").WithDefault(0) })
	testPanic(func() { cmds.StringOption("string", "s", "a string").WithDefault(false) })
	testPanic(func() { cmds.StringOption("string", "s", "a string").WithDefault(nil) })
	testPanic(func() { cmds.StringOption("string", "s", "a string").WithDefault([]string{"foo"}) })
	testPanic(func() { cmds.StringsOption("strings", "a", "a string array").WithDefault(0) })
	testPanic(func() { cmds.StringsOption("strings", "a", "a string array").WithDefault(false) })
	testPanic(func() { cmds.StringsOption("strings", "a", "a string array").WithDefault(nil) })
	testPanic(func() { cmds.StringsOption("strings", "a", "a string array").WithDefault("foo") })
	testPanic(func() { cmds.StringsOption("strings", "a", "a string array").WithDefault([]bool{false}) })
	testPanic(func() { cmds.DelimitedStringsOption(",", "dstrings", "d", "delimited string array").WithDefault(0) })
	testPanic(func() { cmds.DelimitedStringsOption(",", "dstrs", "d", "delimited string array").WithDefault(false) })
	testPanic(func() { cmds.DelimitedStringsOption(",", "dstrings", "d", "delimited string array").WithDefault(nil) })
	testPanic(func() { cmds.DelimitedStringsOption(",", "dstrs", "d", "delimited string array").WithDefault("foo") })
	testPanic(func() { cmds.DelimitedStringsOption(",", "dstrs", "d", "delimited string array").WithDefault([]int{0}) })

	testPanic(func() { cmds.BoolOption("bool", "b", "a bool").WithDefault(0) })
	testPanic(func() { cmds.BoolOption("bool", "b", "a bool").WithDefault(1) })
	testPanic(func() { cmds.BoolOption("bool", "b", "a bool").WithDefault(nil) })
	testPanic(func() { cmds.BoolOption("bool", "b", "a bool").WithDefault([]bool{false}) })
	testPanic(func() { cmds.BoolOption("bool", "b", "a bool").WithDefault([]string{"foo"}) })

	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault(int(0)) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault(int32(0)) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault(int64(0)) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault(uint64(0)) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault(uint32(0)) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault(float32(0)) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault(float64(0)) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault(nil) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault([]uint{0}) })
	testPanic(func() { cmds.UintOption("uint", "u", "a uint").WithDefault([]string{"foo"}) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(int(0)) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(int32(0)) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(int64(0)) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(uint(0)) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(uint32(0)) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(float32(0)) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(float64(0)) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(nil) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault([]uint64{0}) })
	testPanic(func() { cmds.Uint64Option("uint64", "v", "a uint64").WithDefault([]string{"foo"}) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault(int32(0)) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault(int64(0)) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault(uint(0)) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault(uint32(0)) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault(uint64(0)) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault(float32(0)) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault(float64(0)) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault(nil) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault([]int{0}) })
	testPanic(func() { cmds.IntOption("int", "i", "an int").WithDefault([]string{"foo"}) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault(int(0)) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault(int32(0)) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault(uint(0)) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault(uint32(0)) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault(uint64(0)) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault(float32(0)) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault(float64(0)) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault(nil) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault([]int64{0}) })
	testPanic(func() { cmds.Int64Option("int64", "j", "an int64").WithDefault([]string{"foo"}) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault(int(0)) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault(int32(0)) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault(int64(0)) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault(uint(0)) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault(uint32(0)) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault(uint64(0)) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault(float32(0)) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault(nil) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault([]int{0}) })
	testPanic(func() { cmds.FloatOption("float", "f", "a float64").WithDefault([]string{"foo"}) })

	cmd := &cmds.Command{
		Subcommands: map[string]*cmds.Command{
			"defaults": &cmds.Command{
				Options: []cmds.Option{
					cmds.StringOption("string", "s", "a string").WithDefault("foo"),
					cmds.StringsOption("strings1", "a", "a string array").WithDefault([]string{"foo"}),
					cmds.StringsOption("strings2", "b", "a string array").WithDefault([]string{"foo", "bar"}),
					cmds.DelimitedStringsOption(",", "dstrings1", "c", "a delimited string array").WithDefault([]string{"foo"}),
					cmds.DelimitedStringsOption(",", "dstrings2", "d", "a delimited string array").WithDefault([]string{"foo", "bar"}),

					cmds.BoolOption("boolT", "t", "a bool").WithDefault(true),
					cmds.BoolOption("boolF", "a bool").WithDefault(false),

					cmds.UintOption("uint", "u", "a uint").WithDefault(uint(1)),
					cmds.Uint64Option("uint64", "v", "a uint64").WithDefault(uint64(1)),
					cmds.IntOption("int", "i", "an int").WithDefault(int(1)),
					cmds.Int64Option("int64", "j", "an int64").WithDefault(int64(1)),
					cmds.FloatOption("float", "f", "a float64").WithDefault(float64(1)),
				},
			},
		},
	}

	test := func(args string, expectedOpts kvs, expectedWords words) {
		testOptionHelper(t, cmd, args, expectedOpts, expectedWords, false)
	}

	test("defaults", kvs{
		"string":    "foo",
		"strings1":  []string{"foo"},
		"strings2":  []string{"foo", "bar"},
		"dstrings1": []string{"foo"},
		"dstrings2": []string{"foo", "bar"},
		"boolT":     true,
		"boolF":     false,
		"uint":      uint(1),
		"uint64":    uint64(1),
		"int":       int(1),
		"int64":     int64(1),
		"float":     float64(1),
	}, words{})
	test("defaults --string baz --strings1=baz -b baz -b=foo -c=foo -d=foo,baz,bing -d=zip,zap -d=zorp -t=false --boolF -u=0 -v=10 -i=-5 -j=10 -f=-3.14", kvs{
		"string":    "baz",
		"strings1":  []string{"baz"},
		"strings2":  []string{"baz", "foo"},
		"dstrings1": []string{"foo"},
		"dstrings2": []string{"foo", "baz", "bing", "zip", "zap", "zorp"},
		"boolT":     false,
		"boolF":     true,
		"uint":      uint(0),
		"uint64":    uint64(10),
		"int":       int(-5),
		"int64":     int64(10),
		"float":     float64(-3.14),
	}, words{})
}

323
func TestArgumentParsing(t *testing.T) {
Jan Winkelmann's avatar
Jan Winkelmann committed
324 325
	rootCmd := &cmds.Command{
		Subcommands: map[string]*cmds.Command{
rht's avatar
rht committed
326 327
			"noarg": {},
			"onearg": {
Steven Allen's avatar
Steven Allen committed
328 329
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
330 331
				},
			},
rht's avatar
rht committed
332
			"twoargs": {
Steven Allen's avatar
Steven Allen committed
333 334 335
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", true, false, "another arg"),
336 337
				},
			},
rht's avatar
rht committed
338
			"variadic": {
Steven Allen's avatar
Steven Allen committed
339 340
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, true, "some arg"),
341 342
				},
			},
rht's avatar
rht committed
343
			"optional": {
Steven Allen's avatar
Steven Allen committed
344 345
				Arguments: []cmds.Argument{
					cmds.StringArg("b", false, true, "another arg"),
346 347
				},
			},
348
			"optionalsecond": {
Steven Allen's avatar
Steven Allen committed
349 350 351
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", false, false, "another arg"),
352 353
				},
			},
rht's avatar
rht committed
354
			"reversedoptional": {
Steven Allen's avatar
Steven Allen committed
355 356 357
				Arguments: []cmds.Argument{
					cmds.StringArg("a", false, false, "some arg"),
					cmds.StringArg("b", true, false, "another arg"),
358 359 360 361 362
				},
			},
		},
	}

363
	test := func(cmd words, f *os.File, res words) {
364
		if f != nil {
Hector Sanjuan's avatar
Hector Sanjuan committed
365
			if _, err := f.Seek(0, io.SeekStart); err != nil {
366 367 368
				t.Fatal(err)
			}
		}
369 370
		ctx := context.Background()
		req, err := Parse(ctx, cmd, f, rootCmd)
371
		if err != nil {
372
			t.Errorf("Command '%v' should have passed parsing: %v", cmd, err)
373
		}
374 375
		if !sameWords(req.Arguments, res) {
			t.Errorf("Arguments parsed from '%v' are '%v' instead of '%v'", cmd, req.Arguments, res)
376
		}
377
	}
378

Jeromy's avatar
Jeromy committed
379
	testFail := func(cmd words, fi *os.File, msg string) {
380
		_, err := Parse(context.Background(), cmd, nil, rootCmd)
381 382 383
		if err == nil {
			t.Errorf("Should have failed: %v", msg)
		}
384 385
	}

386
	test([]string{"noarg"}, nil, []string{})
Jeromy's avatar
Jeromy committed
387
	testFail([]string{"noarg", "value!"}, nil, "provided an arg, but command didn't define any")
388

389
	test([]string{"onearg", "value!"}, nil, []string{"value!"})
Jeromy's avatar
Jeromy committed
390
	testFail([]string{"onearg"}, nil, "didn't provide any args, arg is required")
391

392
	test([]string{"twoargs", "value1", "value2"}, nil, []string{"value1", "value2"})
Jeromy's avatar
Jeromy committed
393 394
	testFail([]string{"twoargs", "value!"}, nil, "only provided 1 arg, needs 2")
	testFail([]string{"twoargs"}, nil, "didn't provide any args, 2 required")
395

396 397
	test([]string{"variadic", "value!"}, nil, []string{"value!"})
	test([]string{"variadic", "value1", "value2", "value3"}, nil, []string{"value1", "value2", "value3"})
Jeromy's avatar
Jeromy committed
398
	testFail([]string{"variadic"}, nil, "didn't provide any args, 1 required")
399

400 401
	test([]string{"optional", "value!"}, nil, []string{"value!"})
	test([]string{"optional"}, nil, []string{})
402 403 404 405
	test([]string{"optional", "value1", "value2"}, nil, []string{"value1", "value2"})

	test([]string{"optionalsecond", "value!"}, nil, []string{"value!"})
	test([]string{"optionalsecond", "value1", "value2"}, nil, []string{"value1", "value2"})
Jeromy's avatar
Jeromy committed
406 407
	testFail([]string{"optionalsecond"}, nil, "didn't provide any args, 1 required")
	testFail([]string{"optionalsecond", "value1", "value2", "value3"}, nil, "provided too many args, takes 2 maximum")
408 409 410

	test([]string{"reversedoptional", "value1", "value2"}, nil, []string{"value1", "value2"})
	test([]string{"reversedoptional", "value!"}, nil, []string{"value!"})
411

Jeromy's avatar
Jeromy committed
412 413
	testFail([]string{"reversedoptional"}, nil, "didn't provide any args, 1 required")
	testFail([]string{"reversedoptional", "value1", "value2", "value3"}, nil, "provided too many args, only takes 1")
414

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
}

func errEq(err1, err2 error) bool {
	if err1 == nil && err2 == nil {
		return true
	}

	if err1 == nil || err2 == nil {
		return false
	}

	return err1.Error() == err2.Error()
}

func TestBodyArgs(t *testing.T) {
	rootCmd := &cmds.Command{
		Subcommands: map[string]*cmds.Command{
			"noarg": {},
			"stdinenabled": {
Steven Allen's avatar
Steven Allen committed
434 435
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, true, "some arg").EnableStdin(),
436 437 438
				},
			},
			"stdinenabled2args": &cmds.Command{
Steven Allen's avatar
Steven Allen committed
439 440 441
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", true, true, "another arg").EnableStdin(),
442 443 444
				},
			},
			"stdinenablednotvariadic": &cmds.Command{
Steven Allen's avatar
Steven Allen committed
445 446
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg").EnableStdin(),
447 448 449
				},
			},
			"stdinenablednotvariadic2args": &cmds.Command{
Steven Allen's avatar
Steven Allen committed
450 451 452
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", true, false, "another arg").EnableStdin(),
453 454 455
				},
			},
			"optionalsecond": {
Steven Allen's avatar
Steven Allen committed
456 457 458
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", false, false, "another arg"),
459 460
				},
			},
461
			"optionalstdin": {
Steven Allen's avatar
Steven Allen committed
462 463 464
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", false, false, "another arg").EnableStdin(),
465 466 467
				},
			},
			"optionalvariadicstdin": {
Steven Allen's avatar
Steven Allen committed
468 469 470
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", false, true, "another arg").EnableStdin(),
471 472
				},
			},
473 474 475
		},
	}

476 477 478
	// Use a temp file to simulate stdin
	fileToSimulateStdin := func(t *testing.T, content string) *os.File {
		fstdin, err := ioutil.TempFile("", "")
479 480 481
		if err != nil {
			t.Fatal(err)
		}
482 483 484
		defer os.Remove(fstdin.Name())

		if _, err := io.WriteString(fstdin, content); err != nil {
485 486
			t.Fatal(err)
		}
487
		return fstdin
488
	}
489

490 491 492 493 494
	fstdin1 := fileToSimulateStdin(t, "stdin1")
	fstdin12 := fileToSimulateStdin(t, "stdin1\nstdin2")
	fstdin123 := fileToSimulateStdin(t, "stdin1\nstdin2\nstdin3")

	var tcs = []struct {
495 496 497 498 499
		cmd              words
		f                *os.File
		posArgs, varArgs words
		parseErr         error
		bodyArgs         bool
500 501 502 503
	}{
		{
			cmd: words{"stdinenabled", "value1", "value2"}, f: nil,
			posArgs: words{"value1", "value2"}, varArgs: nil,
504
			parseErr: nil, bodyArgs: false,
505 506 507
		},
		{
			cmd: words{"stdinenabled"}, f: fstdin1,
508 509
			posArgs: words{"stdin1"}, varArgs: words{},
			parseErr: nil, bodyArgs: true,
510 511 512 513
		},
		{
			cmd: words{"stdinenabled", "value1"}, f: fstdin1,
			posArgs: words{"value1"}, varArgs: words{},
514
			parseErr: nil, bodyArgs: false,
515 516 517 518
		},
		{
			cmd: words{"stdinenabled", "value1", "value2"}, f: fstdin1,
			posArgs: words{"value1", "value2"}, varArgs: words{},
519
			parseErr: nil, bodyArgs: false,
520 521 522
		},
		{
			cmd: words{"stdinenabled"}, f: fstdin12,
523 524
			posArgs: words{"stdin1"}, varArgs: words{"stdin2"},
			parseErr: nil, bodyArgs: true,
525 526 527
		},
		{
			cmd: words{"stdinenabled"}, f: fstdin123,
528 529
			posArgs: words{"stdin1"}, varArgs: words{"stdin2", "stdin3"},
			parseErr: nil, bodyArgs: true,
530 531 532 533
		},
		{
			cmd: words{"stdinenabled2args", "value1", "value2"}, f: nil,
			posArgs: words{"value1", "value2"}, varArgs: words{},
534
			parseErr: nil, bodyArgs: false,
535 536 537
		},
		{
			cmd: words{"stdinenabled2args", "value1"}, f: fstdin1,
538
			posArgs: words{"value1", "stdin1"}, varArgs: words{},
539
			parseErr: nil, bodyArgs: true,
540 541 542 543
		},
		{
			cmd: words{"stdinenabled2args", "value1", "value2"}, f: fstdin1,
			posArgs: words{"value1", "value2"}, varArgs: words{},
544
			parseErr: nil, bodyArgs: false,
545 546 547 548
		},
		{
			cmd: words{"stdinenabled2args", "value1", "value2", "value3"}, f: fstdin1,
			posArgs: words{"value1", "value2", "value3"}, varArgs: words{},
549
			parseErr: nil, bodyArgs: false,
550 551 552
		},
		{
			cmd: words{"stdinenabled2args", "value1"}, f: fstdin12,
553
			posArgs: words{"value1", "stdin1"}, varArgs: words{"stdin2"},
554
			parseErr: nil, bodyArgs: true,
555 556 557 558
		},
		{
			cmd: words{"stdinenablednotvariadic", "value1"}, f: nil,
			posArgs: words{"value1"}, varArgs: words{},
559
			parseErr: nil, bodyArgs: false,
560 561 562
		},
		{
			cmd: words{"stdinenablednotvariadic"}, f: fstdin1,
563 564
			posArgs: words{"stdin1"}, varArgs: words{},
			parseErr: nil, bodyArgs: true,
565 566 567 568
		},
		{
			cmd: words{"stdinenablednotvariadic", "value1"}, f: fstdin1,
			posArgs: words{"value1"}, varArgs: words{"value1"},
569
			parseErr: nil, bodyArgs: false,
570 571 572 573
		},
		{
			cmd: words{"stdinenablednotvariadic2args", "value1", "value2"}, f: nil,
			posArgs: words{"value1", "value2"}, varArgs: words{},
574
			parseErr: nil, bodyArgs: false,
575 576 577
		},
		{
			cmd: words{"stdinenablednotvariadic2args", "value1"}, f: fstdin1,
578
			posArgs: words{"value1", "stdin1"}, varArgs: words{},
579
			parseErr: nil, bodyArgs: true,
580 581 582 583
		},
		{
			cmd: words{"stdinenablednotvariadic2args", "value1", "value2"}, f: fstdin1,
			posArgs: words{"value1", "value2"}, varArgs: words{},
584
			parseErr: nil, bodyArgs: false,
585 586 587 588
		},
		{
			cmd: words{"stdinenablednotvariadic2args"}, f: fstdin1,
			posArgs: words{}, varArgs: words{},
589
			parseErr: fmt.Errorf(`argument %q is required`, "a"), bodyArgs: true,
590 591 592 593
		},
		{
			cmd: words{"stdinenablednotvariadic2args", "value1"}, f: nil,
			posArgs: words{"value1"}, varArgs: words{},
594
			parseErr: fmt.Errorf(`argument %q is required`, "b"), bodyArgs: true,
595 596 597 598
		},
		{
			cmd: words{"noarg"}, f: fstdin1,
			posArgs: words{}, varArgs: words{},
599
			parseErr: nil, bodyArgs: false,
600 601 602 603
		},
		{
			cmd: words{"optionalsecond", "value1", "value2"}, f: fstdin1,
			posArgs: words{"value1", "value2"}, varArgs: words{},
604
			parseErr: nil, bodyArgs: false,
605
		},
606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
		{
			cmd: words{"optionalstdin", "value1"}, f: fstdin1,
			posArgs: words{"value1"}, varArgs: words{"stdin1"},
			parseErr: nil, bodyArgs: true,
		},
		{
			cmd: words{"optionalstdin", "value1"}, f: nil,
			posArgs: words{"value1"}, varArgs: words{},
			parseErr: nil, bodyArgs: false,
		},
		{
			cmd: words{"optionalstdin"}, f: fstdin1,
			posArgs: words{"value1"}, varArgs: words{},
			parseErr: fmt.Errorf(`argument %q is required`, "a"), bodyArgs: false,
		},
		{
			cmd: words{"optionalvariadicstdin", "value1"}, f: nil,
			posArgs: words{"value1"}, varArgs: words{},
			parseErr: nil, bodyArgs: false,
		},
		{
			cmd: words{"optionalvariadicstdin", "value1"}, f: fstdin1,
			posArgs: words{"value1"}, varArgs: words{"stdin1"},
			parseErr: nil, bodyArgs: true,
		},
		{
			cmd: words{"optionalvariadicstdin", "value1"}, f: fstdin12,
			posArgs: words{"value1"}, varArgs: words{"stdin1", "stdin2"},
			parseErr: nil, bodyArgs: true,
		},
636
	}
637

638 639
	for _, tc := range tcs {
		if tc.f != nil {
Hector Sanjuan's avatar
Hector Sanjuan committed
640
			if _, err := tc.f.Seek(0, io.SeekStart); err != nil {
641 642 643
				t.Fatal(err)
			}
		}
644

645
		req, err := Parse(context.Background(), tc.cmd, tc.f, rootCmd)
646 647 648
		if err == nil {
			err = req.Command.CheckArguments(req)
		}
649 650 651 652 653 654
		if !errEq(err, tc.parseErr) {
			t.Fatalf("parsing request for cmd %q: expected error %q, got %q", tc.cmd, tc.parseErr, err)
		}
		if err != nil {
			continue
		}
655

656 657 658
		if !sameWords(req.Arguments, tc.posArgs) {
			t.Errorf("Arguments parsed from %v are %v instead of %v", tc.cmd, req.Arguments, tc.posArgs)
		}
659

660 661 662 663 664
		s := req.BodyArgs()
		if !tc.bodyArgs {
			if s != nil {
				t.Fatalf("expected no BodyArgs for cmd %q", tc.cmd)
			}
665 666 667
			continue
		}
		if s == nil {
668
			t.Fatalf("expected BodyArgs for cmd %q", tc.cmd)
669
		}
670

671
		var bodyArgs words
672 673 674 675 676
		for s.Scan() {
			bodyArgs = append(bodyArgs, s.Argument())
		}
		if err := s.Err(); err != nil {
			t.Fatal(err)
677
		}
678

679 680 681 682
		if !sameWords(bodyArgs, tc.varArgs) {
			t.Errorf("BodyArgs parsed from %v are %v instead of %v", tc.cmd, bodyArgs, tc.varArgs)
		}
	}
683
}
jmank88's avatar
jmank88 committed
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704

func Test_isURL(t *testing.T) {
	for _, u := range []string{
		"http://www.example.com",
		"https://www.example.com",
	} {
		if isURL(u) == nil {
			t.Errorf("expected url: %s", u)
		}
	}

	for _, u := range []string{
		"adir/afile",
		"http:/ /afile",
		"http:/a/file",
	} {
		if isURL(u) != nil {
			t.Errorf("expected non-url: %s", u)
		}
	}
}
jmank88's avatar
jmank88 committed
705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722

func Test_urlBase(t *testing.T) {
	for _, test := range []struct{ url, base string }{
		{"http://host", "host"},
		{"http://host/test", "test"},
		{"http://host/test?param=val", "test"},
		{"http://host/test?param=val&param2=val", "test"},
	} {
		u, err := url.Parse(test.url)
		if err != nil {
			t.Errorf("failed to parse %q: %v", test.url, err)
			continue
		}
		if got := urlBase(u); got != test.base {
			t.Errorf("expected %q but got %q", test.base, got)
		}
	}
}
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795

func TestFileArgs(t *testing.T) {
	rootCmd := &cmds.Command{
		Subcommands: map[string]*cmds.Command{
			"fileOp": {
				Arguments: []cmds.Argument{
					cmds.FileArg("path", true, true, "The path to the file to be operated upon.").EnableRecursive().EnableStdin(),
				},
				Options: []cmds.Option{
					cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
					cmds.OptionHidden,
					cmds.OptionIgnoreRules,
					cmds.OptionIgnore,
				},
			},
		},
	}
	mkTempFile := func(t *testing.T, dir, pattern, content string) *os.File {
		pat := "test_tmpFile_"
		if pattern != "" {
			pat = pattern
		}
		tmpFile, err := ioutil.TempFile(dir, pat)
		if err != nil {
			t.Fatal(err)
		}

		if _, err := io.WriteString(tmpFile, content); err != nil {
			t.Fatal(err)
		}
		return tmpFile
	}
	tmpDir1, err := ioutil.TempDir("", "parsetest_fileargs_tmpdir_")
	if err != nil {
		t.Fatal(err)
	}
	tmpDir2, err := ioutil.TempDir("", "parsetest_utildir_")
	if err != nil {
		t.Fatal(err)
	}
	tmpFile1 := mkTempFile(t, "", "", "test1")
	tmpFile2 := mkTempFile(t, tmpDir1, "", "toBeIgnored")
	tmpFile3 := mkTempFile(t, tmpDir1, "", "test3")
	ignoreFile := mkTempFile(t, tmpDir2, "", path.Base(tmpFile2.Name()))
	tmpHiddenFile := mkTempFile(t, tmpDir1, ".test_hidden_file_*", "test")
	defer func() {
		for _, f := range []string{
			tmpDir1,
			tmpFile1.Name(),
			tmpFile2.Name(),
			tmpHiddenFile.Name(),
			tmpFile3.Name(),
			ignoreFile.Name(),
			tmpDir2,
		} {
			os.Remove(f)
		}
	}()
	var testCases = []struct {
		cmd      words
		f        *os.File
		args     words
		parseErr error
	}{
		{
			cmd:      words{"fileOp"},
			args:     nil,
			parseErr: fmt.Errorf("argument %q is required", "path"),
		},
		{
			cmd: words{"fileOp", "--ignore", path.Base(tmpFile2.Name()), tmpDir1, tmpFile1.Name()}, f: nil,
			args:     words{tmpDir1, tmpFile1.Name(), tmpFile3.Name()},
			parseErr: fmt.Errorf(notRecursiveFmtStr, tmpDir1, "r"),
796 797
		},
		{
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
			cmd: words{"fileOp", tmpFile1.Name(), "--ignore", path.Base(tmpFile2.Name()), "--ignore"}, f: nil,
			args:     words{tmpDir1, tmpFile1.Name(), tmpFile3.Name()},
			parseErr: fmt.Errorf("missing argument for option %q", "ignore"),
		},
		{
			cmd: words{"fileOp", "-r", "--ignore", path.Base(tmpFile2.Name()), tmpDir1, tmpFile1.Name()}, f: nil,
			args:     words{tmpDir1, tmpFile1.Name(), tmpFile3.Name()},
			parseErr: nil,
		},
		{
			cmd: words{"fileOp", "--hidden", "-r", "--ignore", path.Base(tmpFile2.Name()), tmpDir1, tmpFile1.Name()}, f: nil,
			args:     words{tmpDir1, tmpFile1.Name(), tmpFile3.Name(), tmpHiddenFile.Name()},
			parseErr: nil,
		},
		{
			cmd: words{"fileOp", "-r", "--ignore", path.Base(tmpFile2.Name()), tmpDir1, tmpFile1.Name(), "--ignore", "anotherRule"}, f: nil,
			args:     words{tmpDir1, tmpFile1.Name(), tmpFile3.Name()},
			parseErr: nil,
		},
		{
			cmd: words{"fileOp", "-r", "--ignore-rules-path", ignoreFile.Name(), tmpDir1, tmpFile1.Name()}, f: nil,
			args:     words{tmpDir1, tmpFile1.Name(), tmpFile3.Name()},
			parseErr: nil,
		},
	}

	for _, tc := range testCases {
		req, err := Parse(context.Background(), tc.cmd, tc.f, rootCmd)
		if err == nil {
			err = req.Command.CheckArguments(req)
		}
		if !errEq(err, tc.parseErr) {
			t.Fatalf("parsing request for cmd %q: expected error %q, got %q", tc.cmd, tc.parseErr, err)
		}
		if err != nil {
			continue
		}

		if len(tc.args) == 0 {
			continue
		}
		expectedFileMap := make(map[string]bool)
		for _, arg := range tc.args {
			expectedFileMap[path.Base(arg)] = false
		}
		it := req.Files.Entries()
		for it.Next() {
			name := it.Name()
			if _, ok := expectedFileMap[name]; ok {
				expectedFileMap[name] = true
			} else {
				t.Errorf("found unexpected file %q in request %v", name, req)
			}
			file := it.Node()
			files.Walk(file, func(fpath string, nd files.Node) error {
				if fpath != "" {
					if _, ok := expectedFileMap[fpath]; ok {
						expectedFileMap[fpath] = true
					} else {
						t.Errorf("found unexpected file %q in request file arguments", fpath)
					}
				}
				return nil
			})
		}
		for p, found := range expectedFileMap {
			if !found {
				t.Errorf("failed to find expected path %q in req %v", p, req)
			}
		}
	}
}