parse_test.go 20.2 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
func TestOptionParsing(t *testing.T) {
Jan Winkelmann's avatar
Jan Winkelmann committed
81
	cmd := &cmds.Command{
Steven Allen's avatar
Steven Allen committed
82 83
		Options: []cmds.Option{
			cmds.StringOption("string", "s", "a string"),
84
			cmds.StringOption("flag", "alias", "multiple long"),
Steven Allen's avatar
Steven Allen committed
85
			cmds.BoolOption("bool", "b", "a bool"),
86
			cmds.StringsOption("strings", "r", "strings array"),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
87
		},
Jan Winkelmann's avatar
Jan Winkelmann committed
88
		Subcommands: map[string]*cmds.Command{
89 90
			"test": &cmds.Command{},
			"defaults": &cmds.Command{
Steven Allen's avatar
Steven Allen committed
91 92
				Options: []cmds.Option{
					cmds.StringOption("opt", "o", "an option").WithDefault("def"),
93 94
				},
			},
Matt Bell's avatar
Matt Bell committed
95
		},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
96
	}
97

Etienne Laurin's avatar
Etienne Laurin committed
98
	testHelper := func(args string, expectedOpts kvs, expectedWords words, expectErr bool) {
99 100 101 102 103
		req := &cmds.Request{}
		err := parse(req, strings.Split(args, " "), cmd)
		if err == nil {
			err = req.FillDefaults()
		}
Etienne Laurin's avatar
Etienne Laurin committed
104 105 106 107 108 109
		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)
110
		} else if !sameWords(req.Arguments, expectedWords) || !sameKVs(kvs(req.Options), expectedOpts) {
Etienne Laurin's avatar
Etienne Laurin committed
111
			t.Errorf("Command line '%v':\n  parsed as  %v %v\n  instead of %v %v",
112
				args, req.Options, req.Arguments, expectedOpts, expectedWords)
Etienne Laurin's avatar
Etienne Laurin committed
113
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
114
	}
Etienne Laurin's avatar
Etienne Laurin committed
115 116 117

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

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

124
	test("test -", kvs{}, words{"-"})
Etienne Laurin's avatar
Etienne Laurin committed
125 126 127
	testFail("-b -b")
	test("test beep boop", kvs{}, words{"beep", "boop"})
	testFail("-s")
128 129 130 131 132 133 134
	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"})
135
	test("--bool test foo", kvs{"bool": true}, words{"foo"})
Etienne Laurin's avatar
Etienne Laurin committed
136 137 138 139 140
	testFail("--bool=foo")
	testFail("--string")
	test("--string foo", kvs{"string": "foo"}, words{})
	test("--string=foo", kvs{"string": "foo"}, words{})
	test("-- -b", kvs{}, words{"-b"})
141 142 143 144 145
	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"})
146 147
	test("--bool=true test foo", kvs{"bool": true}, words{"foo"})
	test("--bool=false test foo", kvs{"bool": false}, words{"foo"})
148 149 150 151 152 153 154 155
	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{})
156
	test("--strings a --strings b", kvs{"strings": []string{"a", "b"}}, words{})
157
	testFail("foo test")
158 159
	test("defaults", kvs{"opt": "def"}, words{})
	test("defaults -o foo", kvs{"opt": "foo"}, words{})
160

161 162 163 164 165
	test("--flag=foo", kvs{"flag": "foo"}, words{})
	test("--alias=foo", kvs{"flag": "foo"}, words{})
	testFail("--flag=bar --alias=foo")
	testFail("--alias=bar --flag=foo")

166 167 168 169 170
	testFail("--bad-flag")
	testFail("--bad-flag=")
	testFail("--bad-flag=xyz")
	testFail("-z")
	testFail("-zz--- --")
171
}
172 173

func TestArgumentParsing(t *testing.T) {
Jan Winkelmann's avatar
Jan Winkelmann committed
174 175
	rootCmd := &cmds.Command{
		Subcommands: map[string]*cmds.Command{
rht's avatar
rht committed
176 177
			"noarg": {},
			"onearg": {
Steven Allen's avatar
Steven Allen committed
178 179
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
180 181
				},
			},
rht's avatar
rht committed
182
			"twoargs": {
Steven Allen's avatar
Steven Allen committed
183 184 185
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", true, false, "another arg"),
186 187
				},
			},
rht's avatar
rht committed
188
			"variadic": {
Steven Allen's avatar
Steven Allen committed
189 190
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, true, "some arg"),
191 192
				},
			},
rht's avatar
rht committed
193
			"optional": {
Steven Allen's avatar
Steven Allen committed
194 195
				Arguments: []cmds.Argument{
					cmds.StringArg("b", false, true, "another arg"),
196 197
				},
			},
198
			"optionalsecond": {
Steven Allen's avatar
Steven Allen committed
199 200 201
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", false, false, "another arg"),
202 203
				},
			},
rht's avatar
rht committed
204
			"reversedoptional": {
Steven Allen's avatar
Steven Allen committed
205 206 207
				Arguments: []cmds.Argument{
					cmds.StringArg("a", false, false, "some arg"),
					cmds.StringArg("b", true, false, "another arg"),
208 209 210 211 212
				},
			},
		},
	}

213
	test := func(cmd words, f *os.File, res words) {
214
		if f != nil {
Hector Sanjuan's avatar
Hector Sanjuan committed
215
			if _, err := f.Seek(0, io.SeekStart); err != nil {
216 217 218
				t.Fatal(err)
			}
		}
219 220
		ctx := context.Background()
		req, err := Parse(ctx, cmd, f, rootCmd)
221
		if err != nil {
222
			t.Errorf("Command '%v' should have passed parsing: %v", cmd, err)
223
		}
224 225
		if !sameWords(req.Arguments, res) {
			t.Errorf("Arguments parsed from '%v' are '%v' instead of '%v'", cmd, req.Arguments, res)
226
		}
227
	}
228

Jeromy's avatar
Jeromy committed
229
	testFail := func(cmd words, fi *os.File, msg string) {
230
		_, err := Parse(context.Background(), cmd, nil, rootCmd)
231 232 233
		if err == nil {
			t.Errorf("Should have failed: %v", msg)
		}
234 235
	}

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

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

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

246 247
	test([]string{"variadic", "value!"}, nil, []string{"value!"})
	test([]string{"variadic", "value1", "value2", "value3"}, nil, []string{"value1", "value2", "value3"})
Jeromy's avatar
Jeromy committed
248
	testFail([]string{"variadic"}, nil, "didn't provide any args, 1 required")
249

250 251
	test([]string{"optional", "value!"}, nil, []string{"value!"})
	test([]string{"optional"}, nil, []string{})
252 253 254 255
	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
256 257
	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")
258 259 260

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

Jeromy's avatar
Jeromy committed
262 263
	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")
264

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
}

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
284 285
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, true, "some arg").EnableStdin(),
286 287 288
				},
			},
			"stdinenabled2args": &cmds.Command{
Steven Allen's avatar
Steven Allen committed
289 290 291
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", true, true, "another arg").EnableStdin(),
292 293 294
				},
			},
			"stdinenablednotvariadic": &cmds.Command{
Steven Allen's avatar
Steven Allen committed
295 296
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg").EnableStdin(),
297 298 299
				},
			},
			"stdinenablednotvariadic2args": &cmds.Command{
Steven Allen's avatar
Steven Allen committed
300 301 302
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", true, false, "another arg").EnableStdin(),
303 304 305
				},
			},
			"optionalsecond": {
Steven Allen's avatar
Steven Allen committed
306 307 308
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", false, false, "another arg"),
309 310
				},
			},
311
			"optionalstdin": {
Steven Allen's avatar
Steven Allen committed
312 313 314
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", false, false, "another arg").EnableStdin(),
315 316 317
				},
			},
			"optionalvariadicstdin": {
Steven Allen's avatar
Steven Allen committed
318 319 320
				Arguments: []cmds.Argument{
					cmds.StringArg("a", true, false, "some arg"),
					cmds.StringArg("b", false, true, "another arg").EnableStdin(),
321 322
				},
			},
323 324 325
		},
	}

326 327 328
	// Use a temp file to simulate stdin
	fileToSimulateStdin := func(t *testing.T, content string) *os.File {
		fstdin, err := ioutil.TempFile("", "")
329 330 331
		if err != nil {
			t.Fatal(err)
		}
332 333 334
		defer os.Remove(fstdin.Name())

		if _, err := io.WriteString(fstdin, content); err != nil {
335 336
			t.Fatal(err)
		}
337
		return fstdin
338
	}
339

340 341 342 343 344
	fstdin1 := fileToSimulateStdin(t, "stdin1")
	fstdin12 := fileToSimulateStdin(t, "stdin1\nstdin2")
	fstdin123 := fileToSimulateStdin(t, "stdin1\nstdin2\nstdin3")

	var tcs = []struct {
345 346 347 348 349
		cmd              words
		f                *os.File
		posArgs, varArgs words
		parseErr         error
		bodyArgs         bool
350 351 352 353
	}{
		{
			cmd: words{"stdinenabled", "value1", "value2"}, f: nil,
			posArgs: words{"value1", "value2"}, varArgs: nil,
354
			parseErr: nil, bodyArgs: false,
355 356 357
		},
		{
			cmd: words{"stdinenabled"}, f: fstdin1,
358 359
			posArgs: words{"stdin1"}, varArgs: words{},
			parseErr: nil, bodyArgs: true,
360 361 362 363
		},
		{
			cmd: words{"stdinenabled", "value1"}, f: fstdin1,
			posArgs: words{"value1"}, varArgs: words{},
364
			parseErr: nil, bodyArgs: false,
365 366 367 368
		},
		{
			cmd: words{"stdinenabled", "value1", "value2"}, f: fstdin1,
			posArgs: words{"value1", "value2"}, varArgs: words{},
369
			parseErr: nil, bodyArgs: false,
370 371 372
		},
		{
			cmd: words{"stdinenabled"}, f: fstdin12,
373 374
			posArgs: words{"stdin1"}, varArgs: words{"stdin2"},
			parseErr: nil, bodyArgs: true,
375 376 377
		},
		{
			cmd: words{"stdinenabled"}, f: fstdin123,
378 379
			posArgs: words{"stdin1"}, varArgs: words{"stdin2", "stdin3"},
			parseErr: nil, bodyArgs: true,
380 381 382 383
		},
		{
			cmd: words{"stdinenabled2args", "value1", "value2"}, f: nil,
			posArgs: words{"value1", "value2"}, varArgs: words{},
384
			parseErr: nil, bodyArgs: false,
385 386 387
		},
		{
			cmd: words{"stdinenabled2args", "value1"}, f: fstdin1,
388
			posArgs: words{"value1", "stdin1"}, varArgs: words{},
389
			parseErr: nil, bodyArgs: true,
390 391 392 393
		},
		{
			cmd: words{"stdinenabled2args", "value1", "value2"}, f: fstdin1,
			posArgs: words{"value1", "value2"}, varArgs: words{},
394
			parseErr: nil, bodyArgs: false,
395 396 397 398
		},
		{
			cmd: words{"stdinenabled2args", "value1", "value2", "value3"}, f: fstdin1,
			posArgs: words{"value1", "value2", "value3"}, varArgs: words{},
399
			parseErr: nil, bodyArgs: false,
400 401 402
		},
		{
			cmd: words{"stdinenabled2args", "value1"}, f: fstdin12,
403
			posArgs: words{"value1", "stdin1"}, varArgs: words{"stdin2"},
404
			parseErr: nil, bodyArgs: true,
405 406 407 408
		},
		{
			cmd: words{"stdinenablednotvariadic", "value1"}, f: nil,
			posArgs: words{"value1"}, varArgs: words{},
409
			parseErr: nil, bodyArgs: false,
410 411 412
		},
		{
			cmd: words{"stdinenablednotvariadic"}, f: fstdin1,
413 414
			posArgs: words{"stdin1"}, varArgs: words{},
			parseErr: nil, bodyArgs: true,
415 416 417 418
		},
		{
			cmd: words{"stdinenablednotvariadic", "value1"}, f: fstdin1,
			posArgs: words{"value1"}, varArgs: words{"value1"},
419
			parseErr: nil, bodyArgs: false,
420 421 422 423
		},
		{
			cmd: words{"stdinenablednotvariadic2args", "value1", "value2"}, f: nil,
			posArgs: words{"value1", "value2"}, varArgs: words{},
424
			parseErr: nil, bodyArgs: false,
425 426 427
		},
		{
			cmd: words{"stdinenablednotvariadic2args", "value1"}, f: fstdin1,
428
			posArgs: words{"value1", "stdin1"}, varArgs: words{},
429
			parseErr: nil, bodyArgs: true,
430 431 432 433
		},
		{
			cmd: words{"stdinenablednotvariadic2args", "value1", "value2"}, f: fstdin1,
			posArgs: words{"value1", "value2"}, varArgs: words{},
434
			parseErr: nil, bodyArgs: false,
435 436 437 438
		},
		{
			cmd: words{"stdinenablednotvariadic2args"}, f: fstdin1,
			posArgs: words{}, varArgs: words{},
439
			parseErr: fmt.Errorf(`argument %q is required`, "a"), bodyArgs: true,
440 441 442 443
		},
		{
			cmd: words{"stdinenablednotvariadic2args", "value1"}, f: nil,
			posArgs: words{"value1"}, varArgs: words{},
444
			parseErr: fmt.Errorf(`argument %q is required`, "b"), bodyArgs: true,
445 446 447 448
		},
		{
			cmd: words{"noarg"}, f: fstdin1,
			posArgs: words{}, varArgs: words{},
449
			parseErr: nil, bodyArgs: false,
450 451 452 453
		},
		{
			cmd: words{"optionalsecond", "value1", "value2"}, f: fstdin1,
			posArgs: words{"value1", "value2"}, varArgs: words{},
454
			parseErr: nil, bodyArgs: false,
455
		},
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
		{
			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,
		},
486
	}
487

488 489
	for _, tc := range tcs {
		if tc.f != nil {
Hector Sanjuan's avatar
Hector Sanjuan committed
490
			if _, err := tc.f.Seek(0, io.SeekStart); err != nil {
491 492 493
				t.Fatal(err)
			}
		}
494

495
		req, err := Parse(context.Background(), tc.cmd, tc.f, rootCmd)
496 497 498
		if err == nil {
			err = req.Command.CheckArguments(req)
		}
499 500 501 502 503 504
		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
		}
505

506 507 508
		if !sameWords(req.Arguments, tc.posArgs) {
			t.Errorf("Arguments parsed from %v are %v instead of %v", tc.cmd, req.Arguments, tc.posArgs)
		}
509

510 511 512 513 514
		s := req.BodyArgs()
		if !tc.bodyArgs {
			if s != nil {
				t.Fatalf("expected no BodyArgs for cmd %q", tc.cmd)
			}
515 516 517
			continue
		}
		if s == nil {
518
			t.Fatalf("expected BodyArgs for cmd %q", tc.cmd)
519
		}
520

521
		var bodyArgs words
522 523 524 525 526
		for s.Scan() {
			bodyArgs = append(bodyArgs, s.Argument())
		}
		if err := s.Err(); err != nil {
			t.Fatal(err)
527
		}
528

529 530 531 532
		if !sameWords(bodyArgs, tc.varArgs) {
			t.Errorf("BodyArgs parsed from %v are %v instead of %v", tc.cmd, bodyArgs, tc.varArgs)
		}
	}
533
}
jmank88's avatar
jmank88 committed
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554

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
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572

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)
		}
	}
}
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 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 636 637 638 639 640 641 642 643 644 645

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"),
646 647
		},
		{
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
			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)
			}
		}
	}
}