parser_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. package flags
  2. import (
  3. "fmt"
  4. "os"
  5. "reflect"
  6. "runtime"
  7. "strconv"
  8. "strings"
  9. "testing"
  10. "time"
  11. )
  12. type defaultOptions struct {
  13. Int int `long:"i"`
  14. IntDefault int `long:"id" default:"1"`
  15. Float64 float64 `long:"f"`
  16. Float64Default float64 `long:"fd" default:"-3.14"`
  17. NumericFlag bool `short:"3"`
  18. String string `long:"str"`
  19. StringDefault string `long:"strd" default:"abc"`
  20. StringNotUnquoted string `long:"strnot" unquote:"false"`
  21. Time time.Duration `long:"t"`
  22. TimeDefault time.Duration `long:"td" default:"1m"`
  23. Map map[string]int `long:"m"`
  24. MapDefault map[string]int `long:"md" default:"a:1"`
  25. Slice []int `long:"s"`
  26. SliceDefault []int `long:"sd" default:"1" default:"2"`
  27. }
  28. func TestDefaults(t *testing.T) {
  29. var tests = []struct {
  30. msg string
  31. args []string
  32. expected defaultOptions
  33. }{
  34. {
  35. msg: "no arguments, expecting default values",
  36. args: []string{},
  37. expected: defaultOptions{
  38. Int: 0,
  39. IntDefault: 1,
  40. Float64: 0.0,
  41. Float64Default: -3.14,
  42. NumericFlag: false,
  43. String: "",
  44. StringDefault: "abc",
  45. Time: 0,
  46. TimeDefault: time.Minute,
  47. Map: map[string]int{},
  48. MapDefault: map[string]int{"a": 1},
  49. Slice: []int{},
  50. SliceDefault: []int{1, 2},
  51. },
  52. },
  53. {
  54. msg: "non-zero value arguments, expecting overwritten arguments",
  55. args: []string{"--i=3", "--id=3", "--f=-2.71", "--fd=2.71", "-3", "--str=def", "--strd=def", "--t=3ms", "--td=3ms", "--m=c:3", "--md=c:3", "--s=3", "--sd=3"},
  56. expected: defaultOptions{
  57. Int: 3,
  58. IntDefault: 3,
  59. Float64: -2.71,
  60. Float64Default: 2.71,
  61. NumericFlag: true,
  62. String: "def",
  63. StringDefault: "def",
  64. Time: 3 * time.Millisecond,
  65. TimeDefault: 3 * time.Millisecond,
  66. Map: map[string]int{"c": 3},
  67. MapDefault: map[string]int{"c": 3},
  68. Slice: []int{3},
  69. SliceDefault: []int{3},
  70. },
  71. },
  72. {
  73. msg: "zero value arguments, expecting overwritten arguments",
  74. args: []string{"--i=0", "--id=0", "--f=0", "--fd=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"},
  75. expected: defaultOptions{
  76. Int: 0,
  77. IntDefault: 0,
  78. Float64: 0,
  79. Float64Default: 0,
  80. String: "",
  81. StringDefault: "",
  82. Time: 0,
  83. TimeDefault: 0,
  84. Map: map[string]int{"": 0},
  85. MapDefault: map[string]int{"": 0},
  86. Slice: []int{0},
  87. SliceDefault: []int{0},
  88. },
  89. },
  90. }
  91. for _, test := range tests {
  92. var opts defaultOptions
  93. _, err := ParseArgs(&opts, test.args)
  94. if err != nil {
  95. t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
  96. }
  97. if opts.Slice == nil {
  98. opts.Slice = []int{}
  99. }
  100. if !reflect.DeepEqual(opts, test.expected) {
  101. t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
  102. }
  103. }
  104. }
  105. func TestNoDefaultsForBools(t *testing.T) {
  106. var opts struct {
  107. DefaultBool bool `short:"d" default:"true"`
  108. }
  109. if runtime.GOOS == "windows" {
  110. assertParseFail(t, ErrInvalidTag, "boolean flag `/d' may not have default values, they always default to `false' and can only be turned on", &opts)
  111. } else {
  112. assertParseFail(t, ErrInvalidTag, "boolean flag `-d' may not have default values, they always default to `false' and can only be turned on", &opts)
  113. }
  114. }
  115. func TestUnquoting(t *testing.T) {
  116. var tests = []struct {
  117. arg string
  118. err error
  119. value string
  120. }{
  121. {
  122. arg: "\"abc",
  123. err: strconv.ErrSyntax,
  124. value: "",
  125. },
  126. {
  127. arg: "\"\"abc\"",
  128. err: strconv.ErrSyntax,
  129. value: "",
  130. },
  131. {
  132. arg: "\"abc\"",
  133. err: nil,
  134. value: "abc",
  135. },
  136. {
  137. arg: "\"\\\"abc\\\"\"",
  138. err: nil,
  139. value: "\"abc\"",
  140. },
  141. {
  142. arg: "\"\\\"abc\"",
  143. err: nil,
  144. value: "\"abc",
  145. },
  146. }
  147. for _, test := range tests {
  148. var opts defaultOptions
  149. for _, delimiter := range []bool{false, true} {
  150. p := NewParser(&opts, None)
  151. var err error
  152. if delimiter {
  153. _, err = p.ParseArgs([]string{"--str=" + test.arg, "--strnot=" + test.arg})
  154. } else {
  155. _, err = p.ParseArgs([]string{"--str", test.arg, "--strnot", test.arg})
  156. }
  157. if test.err == nil {
  158. if err != nil {
  159. t.Fatalf("Expected no error but got: %v", err)
  160. }
  161. if test.value != opts.String {
  162. t.Fatalf("Expected String to be %q but got %q", test.value, opts.String)
  163. }
  164. if q := strconv.Quote(test.value); q != opts.StringNotUnquoted {
  165. t.Fatalf("Expected StringDefault to be %q but got %q", q, opts.StringNotUnquoted)
  166. }
  167. } else {
  168. if err == nil {
  169. t.Fatalf("Expected error")
  170. } else if e, ok := err.(*Error); ok {
  171. if strings.HasPrefix(e.Message, test.err.Error()) {
  172. t.Fatalf("Expected error message to end with %q but got %v", test.err.Error(), e.Message)
  173. }
  174. }
  175. }
  176. }
  177. }
  178. }
  179. // EnvRestorer keeps a copy of a set of env variables and can restore the env from them
  180. type EnvRestorer struct {
  181. env map[string]string
  182. }
  183. func (r *EnvRestorer) Restore() {
  184. os.Clearenv()
  185. for k, v := range r.env {
  186. os.Setenv(k, v)
  187. }
  188. }
  189. // EnvSnapshot returns a snapshot of the currently set env variables
  190. func EnvSnapshot() *EnvRestorer {
  191. r := EnvRestorer{make(map[string]string)}
  192. for _, kv := range os.Environ() {
  193. parts := strings.SplitN(kv, "=", 2)
  194. if len(parts) != 2 {
  195. panic("got a weird env variable: " + kv)
  196. }
  197. r.env[parts[0]] = parts[1]
  198. }
  199. return &r
  200. }
  201. type envDefaultOptions struct {
  202. Int int `long:"i" default:"1" env:"TEST_I"`
  203. Time time.Duration `long:"t" default:"1m" env:"TEST_T"`
  204. Map map[string]int `long:"m" default:"a:1" env:"TEST_M" env-delim:";"`
  205. Slice []int `long:"s" default:"1" default:"2" env:"TEST_S" env-delim:","`
  206. }
  207. func TestEnvDefaults(t *testing.T) {
  208. var tests = []struct {
  209. msg string
  210. args []string
  211. expected envDefaultOptions
  212. env map[string]string
  213. }{
  214. {
  215. msg: "no arguments, no env, expecting default values",
  216. args: []string{},
  217. expected: envDefaultOptions{
  218. Int: 1,
  219. Time: time.Minute,
  220. Map: map[string]int{"a": 1},
  221. Slice: []int{1, 2},
  222. },
  223. },
  224. {
  225. msg: "no arguments, env defaults, expecting env default values",
  226. args: []string{},
  227. expected: envDefaultOptions{
  228. Int: 2,
  229. Time: 2 * time.Minute,
  230. Map: map[string]int{"a": 2, "b": 3},
  231. Slice: []int{4, 5, 6},
  232. },
  233. env: map[string]string{
  234. "TEST_I": "2",
  235. "TEST_T": "2m",
  236. "TEST_M": "a:2;b:3",
  237. "TEST_S": "4,5,6",
  238. },
  239. },
  240. {
  241. msg: "non-zero value arguments, expecting overwritten arguments",
  242. args: []string{"--i=3", "--t=3ms", "--m=c:3", "--s=3"},
  243. expected: envDefaultOptions{
  244. Int: 3,
  245. Time: 3 * time.Millisecond,
  246. Map: map[string]int{"c": 3},
  247. Slice: []int{3},
  248. },
  249. env: map[string]string{
  250. "TEST_I": "2",
  251. "TEST_T": "2m",
  252. "TEST_M": "a:2;b:3",
  253. "TEST_S": "4,5,6",
  254. },
  255. },
  256. {
  257. msg: "zero value arguments, expecting overwritten arguments",
  258. args: []string{"--i=0", "--t=0ms", "--m=:0", "--s=0"},
  259. expected: envDefaultOptions{
  260. Int: 0,
  261. Time: 0,
  262. Map: map[string]int{"": 0},
  263. Slice: []int{0},
  264. },
  265. env: map[string]string{
  266. "TEST_I": "2",
  267. "TEST_T": "2m",
  268. "TEST_M": "a:2;b:3",
  269. "TEST_S": "4,5,6",
  270. },
  271. },
  272. }
  273. oldEnv := EnvSnapshot()
  274. defer oldEnv.Restore()
  275. for _, test := range tests {
  276. var opts envDefaultOptions
  277. oldEnv.Restore()
  278. for envKey, envValue := range test.env {
  279. os.Setenv(envKey, envValue)
  280. }
  281. _, err := ParseArgs(&opts, test.args)
  282. if err != nil {
  283. t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
  284. }
  285. if opts.Slice == nil {
  286. opts.Slice = []int{}
  287. }
  288. if !reflect.DeepEqual(opts, test.expected) {
  289. t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
  290. }
  291. }
  292. }
  293. func TestOptionAsArgument(t *testing.T) {
  294. var tests = []struct {
  295. args []string
  296. expectError bool
  297. errType ErrorType
  298. errMsg string
  299. rest []string
  300. }{
  301. {
  302. // short option must not be accepted as argument
  303. args: []string{"--string-slice", "foobar", "--string-slice", "-o"},
  304. expectError: true,
  305. errType: ErrExpectedArgument,
  306. errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-o'",
  307. },
  308. {
  309. // long option must not be accepted as argument
  310. args: []string{"--string-slice", "foobar", "--string-slice", "--other-option"},
  311. expectError: true,
  312. errType: ErrExpectedArgument,
  313. errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `--other-option'",
  314. },
  315. {
  316. // long option must not be accepted as argument
  317. args: []string{"--string-slice", "--"},
  318. expectError: true,
  319. errType: ErrExpectedArgument,
  320. errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got double dash `--'",
  321. },
  322. {
  323. // quoted and appended option should be accepted as argument (even if it looks like an option)
  324. args: []string{"--string-slice", "foobar", "--string-slice=\"--other-option\""},
  325. },
  326. {
  327. // Accept any single character arguments including '-'
  328. args: []string{"--string-slice", "-"},
  329. },
  330. {
  331. // Do not accept arguments which start with '-' even if the next character is a digit
  332. args: []string{"--string-slice", "-3.14"},
  333. expectError: true,
  334. errType: ErrExpectedArgument,
  335. errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-3.14'",
  336. },
  337. {
  338. // Do not accept arguments which start with '-' if the next character is not a digit
  339. args: []string{"--string-slice", "-character"},
  340. expectError: true,
  341. errType: ErrExpectedArgument,
  342. errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-character'",
  343. },
  344. {
  345. args: []string{"-o", "-", "-"},
  346. rest: []string{"-", "-"},
  347. },
  348. {
  349. // Accept arguments which start with '-' if the next character is a digit, for number options only
  350. args: []string{"--int-slice", "-3"},
  351. },
  352. {
  353. // Accept arguments which start with '-' if the next character is a digit, for number options only
  354. args: []string{"--int16", "-3"},
  355. },
  356. {
  357. // Accept arguments which start with '-' if the next character is a digit, for number options only
  358. args: []string{"--float32", "-3.2"},
  359. },
  360. {
  361. // Accept arguments which start with '-' if the next character is a digit, for number options only
  362. args: []string{"--float32ptr", "-3.2"},
  363. },
  364. }
  365. var opts struct {
  366. StringSlice []string `long:"string-slice"`
  367. IntSlice []int `long:"int-slice"`
  368. Int16 int16 `long:"int16"`
  369. Float32 float32 `long:"float32"`
  370. Float32Ptr *float32 `long:"float32ptr"`
  371. OtherOption bool `long:"other-option" short:"o"`
  372. }
  373. for _, test := range tests {
  374. if test.expectError {
  375. assertParseFail(t, test.errType, test.errMsg, &opts, test.args...)
  376. } else {
  377. args := assertParseSuccess(t, &opts, test.args...)
  378. assertStringArray(t, args, test.rest)
  379. }
  380. }
  381. }
  382. func TestUnknownFlagHandler(t *testing.T) {
  383. var opts struct {
  384. Flag1 string `long:"flag1"`
  385. Flag2 string `long:"flag2"`
  386. }
  387. p := NewParser(&opts, None)
  388. var unknownFlag1 string
  389. var unknownFlag2 bool
  390. var unknownFlag3 string
  391. // Set up a callback to intercept unknown options during parsing
  392. p.UnknownOptionHandler = func(option string, arg SplitArgument, args []string) ([]string, error) {
  393. if option == "unknownFlag1" {
  394. if argValue, ok := arg.Value(); ok {
  395. unknownFlag1 = argValue
  396. return args, nil
  397. }
  398. // consume a value from remaining args list
  399. unknownFlag1 = args[0]
  400. return args[1:], nil
  401. } else if option == "unknownFlag2" {
  402. // treat this one as a bool switch, don't consume any args
  403. unknownFlag2 = true
  404. return args, nil
  405. } else if option == "unknownFlag3" {
  406. if argValue, ok := arg.Value(); ok {
  407. unknownFlag3 = argValue
  408. return args, nil
  409. }
  410. // consume a value from remaining args list
  411. unknownFlag3 = args[0]
  412. return args[1:], nil
  413. }
  414. return args, fmt.Errorf("Unknown flag: %v", option)
  415. }
  416. // Parse args containing some unknown flags, verify that
  417. // our callback can handle all of them
  418. _, err := p.ParseArgs([]string{"--flag1=stuff", "--unknownFlag1", "blah", "--unknownFlag2", "--unknownFlag3=baz", "--flag2=foo"})
  419. if err != nil {
  420. assertErrorf(t, "Parser returned unexpected error %v", err)
  421. }
  422. assertString(t, opts.Flag1, "stuff")
  423. assertString(t, opts.Flag2, "foo")
  424. assertString(t, unknownFlag1, "blah")
  425. assertString(t, unknownFlag3, "baz")
  426. if !unknownFlag2 {
  427. assertErrorf(t, "Flag should have been set by unknown handler, but had value: %v", unknownFlag2)
  428. }
  429. // Parse args with unknown flags that callback doesn't handle, verify it returns error
  430. _, err = p.ParseArgs([]string{"--flag1=stuff", "--unknownFlagX", "blah", "--flag2=foo"})
  431. if err == nil {
  432. assertErrorf(t, "Parser should have returned error, but returned nil")
  433. }
  434. }
  435. func TestChoices(t *testing.T) {
  436. var opts struct {
  437. Choice string `long:"choose" choice:"v1" choice:"v2"`
  438. }
  439. assertParseFail(t, ErrInvalidChoice, "Invalid value `invalid' for option `"+defaultLongOptDelimiter+"choose'. Allowed values are: v1 or v2", &opts, "--choose", "invalid")
  440. assertParseSuccess(t, &opts, "--choose", "v2")
  441. assertString(t, opts.Choice, "v2")
  442. }
  443. func TestEmbedded(t *testing.T) {
  444. type embedded struct {
  445. V bool `short:"v"`
  446. }
  447. var opts struct {
  448. embedded
  449. }
  450. assertParseSuccess(t, &opts, "-v")
  451. if !opts.V {
  452. t.Errorf("Expected V to be true")
  453. }
  454. }
  455. type command struct {
  456. }
  457. func (c *command) Execute(args []string) error {
  458. return nil
  459. }
  460. func TestCommandHandlerNoCommand(t *testing.T) {
  461. var opts = struct {
  462. Value bool `short:"v"`
  463. }{}
  464. parser := NewParser(&opts, Default&^PrintErrors)
  465. var executedCommand Commander
  466. var executedArgs []string
  467. executed := false
  468. parser.CommandHandler = func(command Commander, args []string) error {
  469. executed = true
  470. executedCommand = command
  471. executedArgs = args
  472. return nil
  473. }
  474. _, err := parser.ParseArgs([]string{"arg1", "arg2"})
  475. if err != nil {
  476. t.Fatalf("Unexpected parse error: %s", err)
  477. }
  478. if !executed {
  479. t.Errorf("Expected command handler to be executed")
  480. }
  481. if executedCommand != nil {
  482. t.Errorf("Did not exect an executed command")
  483. }
  484. assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
  485. }
  486. func TestCommandHandler(t *testing.T) {
  487. var opts = struct {
  488. Value bool `short:"v"`
  489. Command command `command:"cmd"`
  490. }{}
  491. parser := NewParser(&opts, Default&^PrintErrors)
  492. var executedCommand Commander
  493. var executedArgs []string
  494. executed := false
  495. parser.CommandHandler = func(command Commander, args []string) error {
  496. executed = true
  497. executedCommand = command
  498. executedArgs = args
  499. return nil
  500. }
  501. _, err := parser.ParseArgs([]string{"cmd", "arg1", "arg2"})
  502. if err != nil {
  503. t.Fatalf("Unexpected parse error: %s", err)
  504. }
  505. if !executed {
  506. t.Errorf("Expected command handler to be executed")
  507. }
  508. if executedCommand == nil {
  509. t.Errorf("Expected command handler to be executed")
  510. }
  511. assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
  512. }