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

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

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

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

func sameWords(a words, b words) bool {
22 23 24
	if len(a) != len(b) {
		return false
	}
25 26 27 28 29 30 31 32 33 34 35 36 37
	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 {
38 39 40 41 42 43 44 45
		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] {
46 47 48 49 50 51
			return false
		}
	}
	return true
}

52 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
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)
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

487 488 489 490 491 492
	for _, tc := range tcs {
		if tc.f != nil {
			if _, err := tc.f.Seek(0, os.SEEK_SET); err != nil {
				t.Fatal(err)
			}
		}
493

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

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

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

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

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

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

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)
		}
	}
}
572 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

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"),
645 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
			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)
			}
		}
	}
}