ini_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. package flags
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "reflect"
  8. "strings"
  9. "testing"
  10. )
  11. func TestWriteIni(t *testing.T) {
  12. oldEnv := EnvSnapshot()
  13. defer oldEnv.Restore()
  14. os.Setenv("ENV_DEFAULT", "env-def")
  15. var tests = []struct {
  16. args []string
  17. options IniOptions
  18. expected string
  19. }{
  20. {
  21. []string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
  22. IniDefault,
  23. `[Application Options]
  24. ; Show verbose debug information
  25. verbose = true
  26. verbose = true
  27. ; Test env-default1 value
  28. EnvDefault1 = env-def
  29. ; Test env-default2 value
  30. EnvDefault2 = env-def
  31. [Other Options]
  32. ; A map from string to int
  33. int-map = a:2
  34. int-map = b:3
  35. `,
  36. },
  37. {
  38. []string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
  39. IniDefault | IniIncludeDefaults,
  40. `[Application Options]
  41. ; Show verbose debug information
  42. verbose = true
  43. verbose = true
  44. ; A slice of pointers to string
  45. ; PtrSlice =
  46. EmptyDescription = false
  47. ; Test default value
  48. Default = "Some\nvalue"
  49. ; Test default array value
  50. DefaultArray = Some value
  51. DefaultArray = "Other\tvalue"
  52. ; Testdefault map value
  53. DefaultMap = another:value
  54. DefaultMap = some:value
  55. ; Test env-default1 value
  56. EnvDefault1 = env-def
  57. ; Test env-default2 value
  58. EnvDefault2 = env-def
  59. ; Option with named argument
  60. OptionWithArgName =
  61. ; Option with choices
  62. OptionWithChoices =
  63. ; Option only available in ini
  64. only-ini =
  65. [Other Options]
  66. ; A slice of strings
  67. StringSlice = some
  68. StringSlice = value
  69. ; A map from string to int
  70. int-map = a:2
  71. int-map = b:3
  72. [Subgroup]
  73. ; This is a subgroup option
  74. Opt =
  75. ; Not hidden inside group
  76. NotHiddenInsideGroup =
  77. [Subsubgroup]
  78. ; This is a subsubgroup option
  79. Opt =
  80. [command]
  81. ; Use for extra verbosity
  82. ; ExtraVerbose =
  83. `,
  84. },
  85. {
  86. []string{"filename", "0", "3.14", "command"},
  87. IniDefault | IniIncludeDefaults | IniCommentDefaults,
  88. `[Application Options]
  89. ; Show verbose debug information
  90. ; verbose =
  91. ; A slice of pointers to string
  92. ; PtrSlice =
  93. ; EmptyDescription = false
  94. ; Test default value
  95. ; Default = "Some\nvalue"
  96. ; Test default array value
  97. ; DefaultArray = Some value
  98. ; DefaultArray = "Other\tvalue"
  99. ; Testdefault map value
  100. ; DefaultMap = another:value
  101. ; DefaultMap = some:value
  102. ; Test env-default1 value
  103. EnvDefault1 = env-def
  104. ; Test env-default2 value
  105. EnvDefault2 = env-def
  106. ; Option with named argument
  107. ; OptionWithArgName =
  108. ; Option with choices
  109. ; OptionWithChoices =
  110. ; Option only available in ini
  111. ; only-ini =
  112. [Other Options]
  113. ; A slice of strings
  114. ; StringSlice = some
  115. ; StringSlice = value
  116. ; A map from string to int
  117. ; int-map = a:1
  118. [Subgroup]
  119. ; This is a subgroup option
  120. ; Opt =
  121. ; Not hidden inside group
  122. ; NotHiddenInsideGroup =
  123. [Subsubgroup]
  124. ; This is a subsubgroup option
  125. ; Opt =
  126. [command]
  127. ; Use for extra verbosity
  128. ; ExtraVerbose =
  129. `,
  130. },
  131. {
  132. []string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "3.14", "command"},
  133. IniDefault | IniIncludeDefaults | IniCommentDefaults,
  134. `[Application Options]
  135. ; Show verbose debug information
  136. ; verbose =
  137. ; A slice of pointers to string
  138. ; PtrSlice =
  139. ; EmptyDescription = false
  140. ; Test default value
  141. Default = New value
  142. ; Test default array value
  143. DefaultArray = New value
  144. ; Testdefault map value
  145. DefaultMap = new:value
  146. ; Test env-default1 value
  147. EnvDefault1 = env-def
  148. ; Test env-default2 value
  149. EnvDefault2 = env-def
  150. ; Option with named argument
  151. ; OptionWithArgName =
  152. ; Option with choices
  153. ; OptionWithChoices =
  154. ; Option only available in ini
  155. ; only-ini =
  156. [Other Options]
  157. ; A slice of strings
  158. ; StringSlice = some
  159. ; StringSlice = value
  160. ; A map from string to int
  161. ; int-map = a:1
  162. [Subgroup]
  163. ; This is a subgroup option
  164. ; Opt =
  165. ; Not hidden inside group
  166. ; NotHiddenInsideGroup =
  167. [Subsubgroup]
  168. ; This is a subsubgroup option
  169. ; Opt =
  170. [command]
  171. ; Use for extra verbosity
  172. ; ExtraVerbose =
  173. `,
  174. },
  175. }
  176. for _, test := range tests {
  177. var opts helpOptions
  178. p := NewNamedParser("TestIni", Default)
  179. p.AddGroup("Application Options", "The application options", &opts)
  180. _, err := p.ParseArgs(test.args)
  181. if err != nil {
  182. t.Fatalf("Unexpected error: %v", err)
  183. }
  184. inip := NewIniParser(p)
  185. var b bytes.Buffer
  186. inip.Write(&b, test.options)
  187. got := b.String()
  188. expected := test.expected
  189. msg := fmt.Sprintf("with arguments %+v and ini options %b", test.args, test.options)
  190. assertDiff(t, got, expected, msg)
  191. }
  192. }
  193. func TestReadIni_flagEquivalent(t *testing.T) {
  194. type options struct {
  195. Opt1 bool `long:"opt1"`
  196. Group1 struct {
  197. Opt2 bool `long:"opt2"`
  198. } `group:"group1"`
  199. Group2 struct {
  200. Opt3 bool `long:"opt3"`
  201. } `group:"group2" namespace:"ns1"`
  202. Cmd1 struct {
  203. Opt4 bool `long:"opt4"`
  204. Opt5 bool `long:"foo.opt5"`
  205. Group1 struct {
  206. Opt6 bool `long:"opt6"`
  207. Opt7 bool `long:"foo.opt7"`
  208. } `group:"group1"`
  209. Group2 struct {
  210. Opt8 bool `long:"opt8"`
  211. } `group:"group2" namespace:"ns1"`
  212. } `command:"cmd1"`
  213. }
  214. a := `
  215. opt1=true
  216. [group1]
  217. opt2=true
  218. [group2]
  219. ns1.opt3=true
  220. [cmd1]
  221. opt4=true
  222. foo.opt5=true
  223. [cmd1.group1]
  224. opt6=true
  225. foo.opt7=true
  226. [cmd1.group2]
  227. ns1.opt8=true
  228. `
  229. b := `
  230. opt1=true
  231. opt2=true
  232. ns1.opt3=true
  233. [cmd1]
  234. opt4=true
  235. foo.opt5=true
  236. opt6=true
  237. foo.opt7=true
  238. ns1.opt8=true
  239. `
  240. parse := func(readIni string) (opts options, writeIni string) {
  241. p := NewNamedParser("TestIni", Default)
  242. p.AddGroup("Application Options", "The application options", &opts)
  243. inip := NewIniParser(p)
  244. err := inip.Parse(strings.NewReader(readIni))
  245. if err != nil {
  246. t.Fatalf("Unexpected error: %s\n\nFile:\n%s", err, readIni)
  247. }
  248. var b bytes.Buffer
  249. inip.Write(&b, Default)
  250. return opts, b.String()
  251. }
  252. aOpt, aIni := parse(a)
  253. bOpt, bIni := parse(b)
  254. assertDiff(t, aIni, bIni, "")
  255. if !reflect.DeepEqual(aOpt, bOpt) {
  256. t.Errorf("not equal")
  257. }
  258. }
  259. func TestReadIni(t *testing.T) {
  260. var opts helpOptions
  261. p := NewNamedParser("TestIni", Default)
  262. p.AddGroup("Application Options", "The application options", &opts)
  263. inip := NewIniParser(p)
  264. inic := `
  265. ; Show verbose debug information
  266. verbose = true
  267. verbose = true
  268. DefaultMap = another:"value\n1"
  269. DefaultMap = some:value 2
  270. [Application Options]
  271. ; A slice of pointers to string
  272. ; PtrSlice =
  273. ; Test default value
  274. Default = "New\nvalue"
  275. ; Test env-default1 value
  276. EnvDefault1 = New value
  277. [Other Options]
  278. # A slice of strings
  279. StringSlice = "some\nvalue"
  280. StringSlice = another value
  281. ; A map from string to int
  282. int-map = a:2
  283. int-map = b:3
  284. `
  285. b := strings.NewReader(inic)
  286. err := inip.Parse(b)
  287. if err != nil {
  288. t.Fatalf("Unexpected error: %s", err)
  289. }
  290. assertBoolArray(t, opts.Verbose, []bool{true, true})
  291. if v := map[string]string{"another": "value\n1", "some": "value 2"}; !reflect.DeepEqual(opts.DefaultMap, v) {
  292. t.Fatalf("Expected %#v for DefaultMap but got %#v", v, opts.DefaultMap)
  293. }
  294. assertString(t, opts.Default, "New\nvalue")
  295. assertString(t, opts.EnvDefault1, "New value")
  296. assertStringArray(t, opts.Other.StringSlice, []string{"some\nvalue", "another value"})
  297. if v, ok := opts.Other.IntMap["a"]; !ok {
  298. t.Errorf("Expected \"a\" in Other.IntMap")
  299. } else if v != 2 {
  300. t.Errorf("Expected Other.IntMap[\"a\"] = 2, but got %v", v)
  301. }
  302. if v, ok := opts.Other.IntMap["b"]; !ok {
  303. t.Errorf("Expected \"b\" in Other.IntMap")
  304. } else if v != 3 {
  305. t.Errorf("Expected Other.IntMap[\"b\"] = 3, but got %v", v)
  306. }
  307. }
  308. func TestReadAndWriteIni(t *testing.T) {
  309. var tests = []struct {
  310. options IniOptions
  311. read string
  312. write string
  313. }{
  314. {
  315. IniIncludeComments,
  316. `[Application Options]
  317. ; Show verbose debug information
  318. verbose = true
  319. verbose = true
  320. ; Test default value
  321. Default = "quote me"
  322. ; Test default array value
  323. DefaultArray = 1
  324. DefaultArray = "2"
  325. DefaultArray = 3
  326. ; Testdefault map value
  327. ; DefaultMap =
  328. ; Test env-default1 value
  329. EnvDefault1 = env-def
  330. ; Test env-default2 value
  331. EnvDefault2 = env-def
  332. [Other Options]
  333. ; A slice of strings
  334. ; StringSlice =
  335. ; A map from string to int
  336. int-map = a:2
  337. int-map = b:"3"
  338. `,
  339. `[Application Options]
  340. ; Show verbose debug information
  341. verbose = true
  342. verbose = true
  343. ; Test default value
  344. Default = "quote me"
  345. ; Test default array value
  346. DefaultArray = 1
  347. DefaultArray = 2
  348. DefaultArray = 3
  349. ; Testdefault map value
  350. ; DefaultMap =
  351. ; Test env-default1 value
  352. EnvDefault1 = env-def
  353. ; Test env-default2 value
  354. EnvDefault2 = env-def
  355. [Other Options]
  356. ; A slice of strings
  357. ; StringSlice =
  358. ; A map from string to int
  359. int-map = a:2
  360. int-map = b:3
  361. `,
  362. },
  363. {
  364. IniIncludeComments,
  365. `[Application Options]
  366. ; Show verbose debug information
  367. verbose = true
  368. verbose = true
  369. ; Test default value
  370. Default = "quote me"
  371. ; Test default array value
  372. DefaultArray = "1"
  373. DefaultArray = "2"
  374. DefaultArray = "3"
  375. ; Testdefault map value
  376. ; DefaultMap =
  377. ; Test env-default1 value
  378. EnvDefault1 = env-def
  379. ; Test env-default2 value
  380. EnvDefault2 = env-def
  381. [Other Options]
  382. ; A slice of strings
  383. ; StringSlice =
  384. ; A map from string to int
  385. int-map = a:"2"
  386. int-map = b:"3"
  387. `,
  388. `[Application Options]
  389. ; Show verbose debug information
  390. verbose = true
  391. verbose = true
  392. ; Test default value
  393. Default = "quote me"
  394. ; Test default array value
  395. DefaultArray = "1"
  396. DefaultArray = "2"
  397. DefaultArray = "3"
  398. ; Testdefault map value
  399. ; DefaultMap =
  400. ; Test env-default1 value
  401. EnvDefault1 = env-def
  402. ; Test env-default2 value
  403. EnvDefault2 = env-def
  404. [Other Options]
  405. ; A slice of strings
  406. ; StringSlice =
  407. ; A map from string to int
  408. int-map = a:"2"
  409. int-map = b:"3"
  410. `,
  411. },
  412. }
  413. for _, test := range tests {
  414. var opts helpOptions
  415. p := NewNamedParser("TestIni", Default)
  416. p.AddGroup("Application Options", "The application options", &opts)
  417. inip := NewIniParser(p)
  418. read := strings.NewReader(test.read)
  419. err := inip.Parse(read)
  420. if err != nil {
  421. t.Fatalf("Unexpected error: %s", err)
  422. }
  423. var write bytes.Buffer
  424. inip.Write(&write, test.options)
  425. got := write.String()
  426. msg := fmt.Sprintf("with ini options %b", test.options)
  427. assertDiff(t, got, test.write, msg)
  428. }
  429. }
  430. func TestReadIniWrongQuoting(t *testing.T) {
  431. var tests = []struct {
  432. iniFile string
  433. lineNumber uint
  434. }{
  435. {
  436. iniFile: `Default = "New\nvalue`,
  437. lineNumber: 1,
  438. },
  439. {
  440. iniFile: `StringSlice = "New\nvalue`,
  441. lineNumber: 1,
  442. },
  443. {
  444. iniFile: `StringSlice = "New\nvalue"
  445. StringSlice = "Second\nvalue`,
  446. lineNumber: 2,
  447. },
  448. {
  449. iniFile: `DefaultMap = some:"value`,
  450. lineNumber: 1,
  451. },
  452. {
  453. iniFile: `DefaultMap = some:value
  454. DefaultMap = another:"value`,
  455. lineNumber: 2,
  456. },
  457. }
  458. for _, test := range tests {
  459. var opts helpOptions
  460. p := NewNamedParser("TestIni", Default)
  461. p.AddGroup("Application Options", "The application options", &opts)
  462. inip := NewIniParser(p)
  463. inic := test.iniFile
  464. b := strings.NewReader(inic)
  465. err := inip.Parse(b)
  466. if err == nil {
  467. t.Fatalf("Expect error")
  468. }
  469. iniError := err.(*IniError)
  470. if iniError.LineNumber != test.lineNumber {
  471. t.Fatalf("Expect error on line %d", test.lineNumber)
  472. }
  473. }
  474. }
  475. func TestIniCommands(t *testing.T) {
  476. var opts struct {
  477. Value string `short:"v" long:"value"`
  478. Add struct {
  479. Name int `short:"n" long:"name" ini-name:"AliasName"`
  480. Other struct {
  481. O string `short:"o" long:"other"`
  482. } `group:"Other Options"`
  483. } `command:"add"`
  484. }
  485. p := NewNamedParser("TestIni", Default)
  486. p.AddGroup("Application Options", "The application options", &opts)
  487. inip := NewIniParser(p)
  488. inic := `[Application Options]
  489. value = some value
  490. [add]
  491. AliasName = 5
  492. [add.Other Options]
  493. other = subgroup
  494. `
  495. b := strings.NewReader(inic)
  496. err := inip.Parse(b)
  497. if err != nil {
  498. t.Fatalf("Unexpected error: %s", err)
  499. }
  500. assertString(t, opts.Value, "some value")
  501. if opts.Add.Name != 5 {
  502. t.Errorf("Expected opts.Add.Name to be 5, but got %v", opts.Add.Name)
  503. }
  504. assertString(t, opts.Add.Other.O, "subgroup")
  505. // Test writing it back
  506. buf := &bytes.Buffer{}
  507. inip.Write(buf, IniDefault)
  508. assertDiff(t, buf.String(), inic, "ini contents")
  509. }
  510. func TestIniNoIni(t *testing.T) {
  511. var opts struct {
  512. NoValue string `short:"n" long:"novalue" no-ini:"yes"`
  513. Value string `short:"v" long:"value"`
  514. }
  515. p := NewNamedParser("TestIni", Default)
  516. p.AddGroup("Application Options", "The application options", &opts)
  517. inip := NewIniParser(p)
  518. // read INI
  519. inic := `[Application Options]
  520. novalue = some value
  521. value = some other value
  522. `
  523. b := strings.NewReader(inic)
  524. err := inip.Parse(b)
  525. if err == nil {
  526. t.Fatalf("Expected error")
  527. }
  528. iniError := err.(*IniError)
  529. if v := uint(2); iniError.LineNumber != v {
  530. t.Errorf("Expected opts.Add.Name to be %d, but got %d", v, iniError.LineNumber)
  531. }
  532. if v := "unknown option: novalue"; iniError.Message != v {
  533. t.Errorf("Expected opts.Add.Name to be %s, but got %s", v, iniError.Message)
  534. }
  535. // write INI
  536. opts.NoValue = "some value"
  537. opts.Value = "some other value"
  538. file, err := ioutil.TempFile("", "")
  539. if err != nil {
  540. t.Fatalf("Cannot create temporary file: %s", err)
  541. }
  542. defer os.Remove(file.Name())
  543. err = inip.WriteFile(file.Name(), IniIncludeDefaults)
  544. if err != nil {
  545. t.Fatalf("Could not write ini file: %s", err)
  546. }
  547. found, err := ioutil.ReadFile(file.Name())
  548. if err != nil {
  549. t.Fatalf("Could not read written ini file: %s", err)
  550. }
  551. expected := "[Application Options]\nValue = some other value\n\n"
  552. assertDiff(t, string(found), expected, "ini content")
  553. }
  554. func TestIniParse(t *testing.T) {
  555. file, err := ioutil.TempFile("", "")
  556. if err != nil {
  557. t.Fatalf("Cannot create temporary file: %s", err)
  558. }
  559. defer os.Remove(file.Name())
  560. _, err = file.WriteString("value = 123")
  561. if err != nil {
  562. t.Fatalf("Cannot write to temporary file: %s", err)
  563. }
  564. file.Close()
  565. var opts struct {
  566. Value int `long:"value"`
  567. }
  568. err = IniParse(file.Name(), &opts)
  569. if err != nil {
  570. t.Fatalf("Could not parse ini: %s", err)
  571. }
  572. if opts.Value != 123 {
  573. t.Fatalf("Expected Value to be \"123\" but was \"%d\"", opts.Value)
  574. }
  575. }
  576. func TestIniCliOverrides(t *testing.T) {
  577. file, err := ioutil.TempFile("", "")
  578. if err != nil {
  579. t.Fatalf("Cannot create temporary file: %s", err)
  580. }
  581. defer os.Remove(file.Name())
  582. _, err = file.WriteString("values = 123\n")
  583. _, err = file.WriteString("values = 456\n")
  584. if err != nil {
  585. t.Fatalf("Cannot write to temporary file: %s", err)
  586. }
  587. file.Close()
  588. var opts struct {
  589. Values []int `long:"values"`
  590. }
  591. p := NewParser(&opts, Default)
  592. err = NewIniParser(p).ParseFile(file.Name())
  593. if err != nil {
  594. t.Fatalf("Could not parse ini: %s", err)
  595. }
  596. _, err = p.ParseArgs([]string{"--values", "111", "--values", "222"})
  597. if err != nil {
  598. t.Fatalf("Failed to parse arguments: %s", err)
  599. }
  600. if len(opts.Values) != 2 {
  601. t.Fatalf("Expected Values to contain two elements, but got %d", len(opts.Values))
  602. }
  603. if opts.Values[0] != 111 {
  604. t.Fatalf("Expected Values[0] to be 111, but got '%d'", opts.Values[0])
  605. }
  606. if opts.Values[1] != 222 {
  607. t.Fatalf("Expected Values[1] to be 222, but got '%d'", opts.Values[1])
  608. }
  609. }
  610. func TestIniOverrides(t *testing.T) {
  611. file, err := ioutil.TempFile("", "")
  612. if err != nil {
  613. t.Fatalf("Cannot create temporary file: %s", err)
  614. }
  615. defer os.Remove(file.Name())
  616. _, err = file.WriteString("value-with-default = \"ini-value\"\n")
  617. _, err = file.WriteString("value-with-default-override-cli = \"ini-value\"\n")
  618. if err != nil {
  619. t.Fatalf("Cannot write to temporary file: %s", err)
  620. }
  621. file.Close()
  622. var opts struct {
  623. ValueWithDefault string `long:"value-with-default" default:"value"`
  624. ValueWithDefaultOverrideCli string `long:"value-with-default-override-cli" default:"value"`
  625. }
  626. p := NewParser(&opts, Default)
  627. err = NewIniParser(p).ParseFile(file.Name())
  628. if err != nil {
  629. t.Fatalf("Could not parse ini: %s", err)
  630. }
  631. _, err = p.ParseArgs([]string{"--value-with-default-override-cli", "cli-value"})
  632. if err != nil {
  633. t.Fatalf("Failed to parse arguments: %s", err)
  634. }
  635. assertString(t, opts.ValueWithDefault, "ini-value")
  636. assertString(t, opts.ValueWithDefaultOverrideCli, "cli-value")
  637. }
  638. func TestIniRequired(t *testing.T) {
  639. var opts struct {
  640. Required string `short:"r" required:"yes" description:"required"`
  641. Config func(s string) error `long:"config" default:"no-ini-file" no-ini:"true"`
  642. }
  643. p := NewParser(&opts, Default)
  644. opts.Config = func(s string) error {
  645. inip := NewIniParser(p)
  646. inip.ParseAsDefaults = true
  647. return inip.Parse(strings.NewReader("Required = ini-value\n"))
  648. }
  649. _, err := p.ParseArgs([]string{"-r", "cli-value"})
  650. if err != nil {
  651. t.Fatalf("Failed to parse arguments: %s", err)
  652. }
  653. assertString(t, opts.Required, "cli-value")
  654. }
  655. func TestWriteFile(t *testing.T) {
  656. file, err := ioutil.TempFile("", "")
  657. if err != nil {
  658. t.Fatalf("Cannot create temporary file: %s", err)
  659. }
  660. defer os.Remove(file.Name())
  661. var opts struct {
  662. Value int `long:"value"`
  663. }
  664. opts.Value = 123
  665. p := NewParser(&opts, Default)
  666. ini := NewIniParser(p)
  667. err = ini.WriteFile(file.Name(), IniIncludeDefaults)
  668. if err != nil {
  669. t.Fatalf("Could not write ini file: %s", err)
  670. }
  671. found, err := ioutil.ReadFile(file.Name())
  672. if err != nil {
  673. t.Fatalf("Could not read written ini file: %s", err)
  674. }
  675. expected := "[Application Options]\nValue = 123\n\n"
  676. assertDiff(t, string(found), expected, "ini content")
  677. }
  678. func TestOverwriteRequiredOptions(t *testing.T) {
  679. var tests = []struct {
  680. args []string
  681. expected []string
  682. }{
  683. {
  684. args: []string{"--value", "from CLI"},
  685. expected: []string{
  686. "from CLI",
  687. "from default",
  688. },
  689. },
  690. {
  691. args: []string{"--value", "from CLI", "--default", "from CLI"},
  692. expected: []string{
  693. "from CLI",
  694. "from CLI",
  695. },
  696. },
  697. {
  698. args: []string{"--config", "no file name"},
  699. expected: []string{
  700. "from INI",
  701. "from INI",
  702. },
  703. },
  704. {
  705. args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name"},
  706. expected: []string{
  707. "from INI",
  708. "from INI",
  709. },
  710. },
  711. {
  712. args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name", "--value", "from CLI after", "--default", "from CLI after"},
  713. expected: []string{
  714. "from CLI after",
  715. "from CLI after",
  716. },
  717. },
  718. }
  719. for _, test := range tests {
  720. var opts struct {
  721. Config func(s string) error `long:"config" no-ini:"true"`
  722. Value string `long:"value" required:"true"`
  723. Default string `long:"default" required:"true" default:"from default"`
  724. }
  725. p := NewParser(&opts, Default)
  726. opts.Config = func(s string) error {
  727. ini := NewIniParser(p)
  728. return ini.Parse(bytes.NewBufferString("value = from INI\ndefault = from INI"))
  729. }
  730. _, err := p.ParseArgs(test.args)
  731. if err != nil {
  732. t.Fatalf("Unexpected error %s with args %+v", err, test.args)
  733. }
  734. if opts.Value != test.expected[0] {
  735. t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected[0], opts.Value, test.args)
  736. }
  737. if opts.Default != test.expected[1] {
  738. t.Fatalf("Expected Default to be \"%s\" but was \"%s\" with args %+v", test.expected[1], opts.Default, test.args)
  739. }
  740. }
  741. }
  742. func TestIniOverwriteOptions(t *testing.T) {
  743. var tests = []struct {
  744. args []string
  745. expected string
  746. toggled bool
  747. }{
  748. {
  749. args: []string{},
  750. expected: "from default",
  751. },
  752. {
  753. args: []string{"--value", "from CLI"},
  754. expected: "from CLI",
  755. },
  756. {
  757. args: []string{"--config", "no file name"},
  758. expected: "from INI",
  759. toggled: true,
  760. },
  761. {
  762. args: []string{"--value", "from CLI before", "--config", "no file name"},
  763. expected: "from CLI before",
  764. toggled: true,
  765. },
  766. {
  767. args: []string{"--config", "no file name", "--value", "from CLI after"},
  768. expected: "from CLI after",
  769. toggled: true,
  770. },
  771. {
  772. args: []string{"--toggle"},
  773. toggled: true,
  774. expected: "from default",
  775. },
  776. }
  777. for _, test := range tests {
  778. var opts struct {
  779. Config string `long:"config" no-ini:"true"`
  780. Value string `long:"value" default:"from default"`
  781. Toggle bool `long:"toggle"`
  782. }
  783. p := NewParser(&opts, Default)
  784. _, err := p.ParseArgs(test.args)
  785. if err != nil {
  786. t.Fatalf("Unexpected error %s with args %+v", err, test.args)
  787. }
  788. if opts.Config != "" {
  789. inip := NewIniParser(p)
  790. inip.ParseAsDefaults = true
  791. err = inip.Parse(bytes.NewBufferString("value = from INI\ntoggle = true"))
  792. if err != nil {
  793. t.Fatalf("Unexpected error %s with args %+v", err, test.args)
  794. }
  795. }
  796. if opts.Value != test.expected {
  797. t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected, opts.Value, test.args)
  798. }
  799. if opts.Toggle != test.toggled {
  800. t.Fatalf("Expected Toggle to be \"%v\" but was \"%v\" with args %+v", test.toggled, opts.Toggle, test.args)
  801. }
  802. }
  803. }