Merge pull request #2802 from BrzVlad/feature-evacuation-opt2
[mono.git] / mcs / class / Mono.Options / Test / Mono.Options / OptionSetTest.cs
1 //
2 // OptionSetTest.cs
3 //
4 // Authors:
5 //  Jonathan Pryor <jpryor@novell.com>
6 //
7 // Copyright (C) 2008 Novell (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.ComponentModel;
33 using System.Globalization;
34 using System.IO;
35
36 #if NDESK_OPTIONS
37 using NDesk.Options;
38 #else
39 using Mono.Options;
40 #endif
41
42 using Cadenza.Collections.Tests;
43
44 using NUnit.Framework;
45
46 #if NDESK_OPTIONS
47 namespace Tests.NDesk.Options
48 #else
49 namespace MonoTests.Mono.Options
50 #endif
51 {
52         class FooConverter : TypeConverter {
53                 public override bool CanConvertFrom (ITypeDescriptorContext context, Type sourceType)
54                 {
55                         if (sourceType == typeof (string))
56                                 return true;
57                         return base.CanConvertFrom (context, sourceType);
58                 }
59
60                 public override object ConvertFrom (ITypeDescriptorContext context,
61                                 CultureInfo culture, object value)
62                 {
63                         string v = value as string;
64                         if (v != null) {
65                                 switch (v) {
66                                         case "A": return Foo.A;
67                                         case "B": return Foo.B;
68                                 }
69                         }
70
71                         return base.ConvertFrom (context, culture, value);
72                 }
73         }
74
75         [TypeConverter (typeof(FooConverter))]
76         class Foo {
77                 public static readonly Foo A = new Foo ("A");
78                 public static readonly Foo B = new Foo ("B");
79                 string s;
80                 Foo (string s) { this.s = s; }
81                 public override string ToString () {return s;}
82         }
83
84         class TestArgumentSource : ArgumentSource, IEnumerable {
85                 string[] names;
86                 string desc;
87
88                 public TestArgumentSource (string[] names, string desc)
89                 {
90                         this.names  = names;
91                         this.desc   = desc;
92                 }
93
94                 Dictionary<string, string[]> args = new Dictionary<string, string[]>();
95
96                 public void Add (string key, params string[] values)
97                 {
98                         args.Add (key, values);
99                 }
100
101                 public override string[] GetNames ()
102                 {
103                         return names;
104                 }
105
106                 public override string Description {
107                         get {return desc;}
108                 }
109
110                 public override bool GetArguments (string value, out IEnumerable<string> replacement)
111                 {
112                         replacement = null;
113
114                         string[] values;
115                         if (args.TryGetValue (value, out values)) {
116                                 replacement = values;
117                                 return true;
118                         }
119
120                         return false;
121                 }
122
123
124                 IEnumerator IEnumerable.GetEnumerator ()
125                 {
126                         return args.GetEnumerator ();
127                 }
128         }
129
130         [TestFixture]
131         public class OptionSetTest : ListContract<Option> {
132
133                 protected override ICollection<Option> CreateCollection (IEnumerable<Option> values)
134                 {
135                         OptionSet set = new OptionSet();
136                         foreach (Option value in values)
137                                 set.Add (value);
138                         return set;
139                 }
140
141                 protected override Option CreateValueA ()
142                 {
143                         return new CustomOption ("A", null, 0, null);
144                 }
145
146                 protected override Option CreateValueB ()
147                 {
148                         return new CustomOption ("B", null, 0, null);
149                 }
150
151                 protected override Option CreateValueC ()
152                 {
153                         return new CustomOption ("C", null, 0, null);
154                 }
155
156                 static IEnumerable<string> _ (params string[] a)
157                 {
158                         return a;
159                 }
160
161                 [Test]
162                 public void BundledValues ()
163                 {
164                         BundledValues (_("-DNAME", "-D", "NAME2", "-Debug", "-L/foo", "-L", "/bar", "-EDNAME3"));
165                         BundledValues (_("@s1", "-D", "@s2", "-L/foo", "@s4"));
166                 }
167
168                 public void BundledValues (IEnumerable<string> args)
169                 {
170                         var defines = new List<string> ();
171                         var libs    = new List<string> ();
172                         bool debug  = false;
173                         var p = new OptionSet () {
174                                 { "D|define=",  v => defines.Add (v) },
175                                 { "L|library:", v => libs.Add (v) },
176                                 { "Debug",      v => debug = v != null },
177                                 { "E",          v => { /* ignore */ } },
178                                 new TestArgumentSource (null, null) {
179                                         { "@s1", "-DNAME" },
180                                         { "@s2", "NAME2", "@s3" },
181                                         { "@s3", "-Debug" },
182                                         { "@s4", "-L", "/bar", "-EDNAME3" },
183                                 },
184                         };
185                         p.Parse (args);
186                         Assert.AreEqual (defines.Count, 3);
187                         Assert.AreEqual (defines [0], "NAME");
188                         Assert.AreEqual (defines [1], "NAME2");
189                         Assert.AreEqual (defines [2], "NAME3");
190                         Assert.AreEqual (debug, true);
191                         Assert.AreEqual (libs.Count, 2);
192                         Assert.AreEqual (libs [0], "/foo");
193                         Assert.AreEqual (libs [1], null);
194
195                         Utils.AssertException (typeof(OptionException), 
196                                         "Cannot use unregistered option 'V' in bundle '-EVALUENOTSUP'.",
197                                         p, v => { v.Parse (_("-EVALUENOTSUP")); });
198                 }
199
200                 [Test]
201                 public void RequiredValues ()
202                 {
203                         RequiredValues (_("a", "-a", "s", "-n=42", "n"));
204                         RequiredValues (_("@s1", "s", "@s2", "n"));
205                 }
206
207                 void RequiredValues (IEnumerable<string> args)
208                 {
209                         string a = null;
210                         int n = 0;
211                         OptionSet p = new OptionSet () {
212                                 { "a=", v => a = v },
213                                 { "n=", (int v) => n = v },
214                                 new TestArgumentSource (null, null) {
215                                         { "@s1", "a", "-a" },
216                                         { "@s2", "-n=42" },
217                                 },
218                         };
219                         List<string> extra = p.Parse (args);
220                         Assert.AreEqual (extra.Count, 2);
221                         Assert.AreEqual (extra [0], "a");
222                         Assert.AreEqual (extra [1], "n");
223                         Assert.AreEqual (a, "s");
224                         Assert.AreEqual (n, 42);
225
226                         extra = p.Parse (_("-a="));
227                         Assert.AreEqual (extra.Count, 0);
228                         Assert.AreEqual (a, "");
229                 }
230
231                 [Test]
232                 public void OptionalValues ()
233                 {
234                         string a = null;
235                         int? n = -1;
236                         Foo f = null;
237                         OptionSet p = new OptionSet () {
238                                 { "a:", v => a = v },
239                                 { "n:", (int? v) => n = v },
240                                 { "f:", (Foo v) => f = v },
241                         };
242                         p.Parse (_("-a=s"));
243                         Assert.AreEqual (a, "s");
244                         p.Parse (_("-a"));
245                         Assert.AreEqual (a, null);
246                         p.Parse (_("-a="));
247                         Assert.AreEqual (a, "");
248
249                         p.Parse (_("-f", "A"));
250                         Assert.AreEqual (f, null);
251                         p.Parse (_("-f"));
252                         Assert.AreEqual (f, null);
253                         p.Parse (_("-f=A"));
254                         Assert.AreEqual (f, Foo.A);
255                         f = null;
256                         p.Parse (_("-fA"));
257                         Assert.AreEqual (f, Foo.A);
258
259                         p.Parse (_("-n42"));
260                         Assert.AreEqual (n.Value, 42);
261                         p.Parse (_("-n", "42"));
262                         Assert.AreEqual (n.HasValue, false);
263                         p.Parse (_("-n=42"));
264                         Assert.AreEqual (n.Value, 42);
265                         p.Parse (_("-n"));
266                         Assert.AreEqual (n.HasValue, false);
267                         Utils.AssertException (typeof(OptionException),
268                                         "Could not convert string `' to type Int32 for option `-n'.",
269                                         p, v => { v.Parse (_("-n=")); });
270                 }
271
272                 [Test]
273                 public void EnumValues ()
274                 {
275                         DayOfWeek a = 0;
276                         OptionSet p = new OptionSet () {
277                                 { "a=", (DayOfWeek v) => a = v },
278                         };
279                         p.Parse (_ ("-a=Monday"));
280                         Assert.AreEqual (a, DayOfWeek.Monday);
281                         p.Parse (_ ("-a=tuesday"));
282                         Assert.AreEqual (a, DayOfWeek.Tuesday);
283                         p.Parse (_ ("-a=3"));
284                         Assert.AreEqual (a, DayOfWeek.Wednesday);
285                         p.Parse (_ ("-a=Monday,Tuesday"));
286                         Assert.AreEqual (a, DayOfWeek.Monday | DayOfWeek.Tuesday);
287                         Utils.AssertException (typeof (OptionException),
288                                         "Could not convert string `Noday' to type DayOfWeek for option `-a'.",
289                                         p, v => { v.Parse (_ ("-a=Noday")); });
290                 }
291
292                 [Test]
293                 public void BooleanValues ()
294                 {
295                         bool a = false;
296                         OptionSet p = new OptionSet () {
297                                 { "a", v => a = v != null },
298                         };
299                         p.Parse (_("-a"));
300                         Assert.AreEqual (a, true);
301                         p.Parse (_("-a+"));
302                         Assert.AreEqual (a, true);
303                         p.Parse (_("-a-"));
304                         Assert.AreEqual (a, false);
305                 }
306
307                 [Test]
308                 public void CombinationPlatter ()
309                 {
310                         CombinationPlatter (new string[]{"foo", "-v", "-a=42", "/b-",
311                                 "-a", "64", "bar", "--f", "B", "/h", "-?", "--help", "-v"});
312                         CombinationPlatter (_("@s1", "-a=42", "@s3", "-a", "64", "bar", "@s4"));
313                 }
314
315                 void CombinationPlatter (IEnumerable<string> args)
316                 {
317                         int a = -1, b = -1;
318                         string av = null, bv = null;
319                         Foo f = null;
320                         int help = 0;
321                         int verbose = 0;
322                         OptionSet p = new OptionSet () {
323                                 { "a=", v => { a = 1; av = v; } },
324                                 { "b", "desc", v => {b = 2; bv = v;} },
325                                 { "f=", (Foo v) => f = v },
326                                 { "v", v => { ++verbose; } },
327                                 { "h|?|help", (v) => { switch (v) {
328                                         case "h": help |= 0x1; break; 
329                                         case "?": help |= 0x2; break;
330                                         case "help": help |= 0x4; break;
331                                 } } },
332                                 new TestArgumentSource (null, null) {
333                                         { "@s1", "foo", "-v", "@s2" },
334                                         { "@s2" },
335                                         { "@s3", "/b-" },
336                                         { "@s4", "--f", "B", "/h", "-?", "--help", "-v" },
337                                 },
338                         };
339                         List<string> e = p.Parse (args);
340
341                         Assert.AreEqual (e.Count, 2);
342                         Assert.AreEqual (e[0], "foo");
343                         Assert.AreEqual (e[1], "bar");
344                         Assert.AreEqual (a, 1);
345                         Assert.AreEqual (av, "64");
346                         Assert.AreEqual (b, 2);
347                         Assert.AreEqual (bv, null);
348                         Assert.AreEqual (verbose, 2);
349                         Assert.AreEqual (help, 0x7);
350                         Assert.AreEqual (f, Foo.B);
351                 }
352
353                 [Test]
354                 public void Exceptions ()
355                 {
356                         string a = null;
357                         var p = new OptionSet () {
358                                 { "a=", v => a = v },
359                                 { "b",  v => { } },
360                                 { "c",  v => { } },
361                                 { "n=", (int v) => { } },
362                                 { "f=", (Foo v) => { } },
363                         };
364                         // missing argument
365                         Utils.AssertException (typeof(OptionException), 
366                                         "Missing required value for option '-a'.", 
367                                         p, v => { v.Parse (_("-a")); });
368                         // another named option while expecting one -- follow Getopt::Long
369                         Utils.AssertException (null, null,
370                                         p, v => { v.Parse (_("-a", "-a")); });
371                         Assert.AreEqual (a, "-a");
372                         // no exception when an unregistered named option follows.
373                         Utils.AssertException (null, null, 
374                                         p, v => { v.Parse (_("-a", "-b")); });
375                         Assert.AreEqual (a, "-b");
376                         Utils.AssertException (typeof(ArgumentNullException),
377                                         "Value cannot be null.\nParameter name: option",
378                                         p, v => { v.Add ((Option) null); });
379                         Utils.AssertException (typeof(ArgumentNullException),
380                                         "Value cannot be null.\nParameter name: header",
381                                         p, v => { v.Add ((string) null); });
382
383                         // bad type
384                         Utils.AssertException (typeof(OptionException),
385                                         "Could not convert string `value' to type Int32 for option `-n'.",
386                                         p, v => { v.Parse (_("-n", "value")); });
387                         Utils.AssertException (typeof(OptionException),
388                                         "Could not convert string `invalid' to type Foo for option `--f'.",
389                                         p, v => { v.Parse (_("--f", "invalid")); });
390
391                         // try to bundle with an option requiring a value
392                         Utils.AssertException (typeof(OptionException), 
393                                         "Cannot use unregistered option 'z' in bundle '-cz'.",
394                                         p, v => { v.Parse (_("-cz", "extra")); });
395
396                         Utils.AssertException (typeof(ArgumentNullException), 
397                                         "Value cannot be null.\nParameter name: action",
398                                         p, v => { v.Add ("foo", (Action<string>) null); });
399                         Utils.AssertException (typeof(ArgumentException), 
400                                         "Cannot provide maxValueCount of 2 for OptionValueType.None.\nParameter name: maxValueCount",
401                                         p, v => { v.Add ("foo", (k, val) => {/* ignore */}); });
402                 }
403
404                 [Test]
405                 public void WriteOptionDescriptions ()
406                 {
407                         var p = new OptionSet () {
408                                 "\n:Category 1:",
409                                 { "hidden",             "hidden option, invisible in help",     v => {}, true },
410                                 { "hidden2=",           "hidden option, invisible in help",     (k, v) => {}, true },
411                                 { "p|indicator-style=", "append / indicator to directories",    v => {} },
412                                 { "color:",             "controls color info",                  v => {} },
413                                 { "color2:",            "set {color}",                          v => {} },
414                                 { "rk=",                "required key/value option",            (k, v) => {} },
415                                 { "rk2=",               "required {{foo}} {0:key}/{1:value} option",    (k, v) => {} },
416                                 { "ok:",                "optional key/value option",            (k, v) => {} },
417                                 { "long-desc",
418                                         "This has a really\nlong, multi-line description that also\ntests\n" +
419                                                 "the-builtin-supercalifragilisticexpialidicious-break-on-hyphen.  " + 
420                                                 "Also, a list:\n" +
421                                                 "  item 1\n" +
422                                                 "  item 2",
423                                         v => {} },
424                                 { "long-desc2",
425                                         "IWantThisDescriptionToBreakInsideAWordGeneratingAutoWordHyphenation. ",
426                                         v => {} },
427                                 { "long-desc3",
428                                         "OnlyOnePeriod.AndNoWhitespaceShouldBeSupportedEvenWithLongDescriptions",
429                                         v => {} },
430                                 { "long-desc4",
431                                         "Lots of spaces in the middle 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 and more until the end.",
432                                         v => {} },
433                                 { "long-desc5",
434                                         "Lots of spaces in the middle - . - . - . - . - . - . - . - and more until the end.",
435                                         v => {} },
436                                 "",
437                                 "==This is a really long category name which will involve line wrapping, just because...==",
438                                 { "o|out=",
439                                         "The {DIRECTORY} to place the generated files and directories.\n\n" +
440                                         "If not specified, defaults to\n`dirname FILE`/cache/`basename FILE .tree`.",
441                                         v => {} },
442                                 "",
443                                 "Category 3:",
444                                 { "h|?|help",           "show help text",                       v => {} },
445                                 { "version",            "output version information and exit",  v => {} },
446                                 { "<>", v => {} },
447                                 new TestArgumentSource (new[]{"@s1", "@s2"}, "Read Response File for More Options"),
448                         };
449
450                         StringWriter expected = new StringWriter ();
451                         expected.WriteLine ("");
452                         expected.WriteLine (":Category 1:");
453                         expected.WriteLine ("  -p, --indicator-style=VALUE");
454                         expected.WriteLine ("                             append / indicator to directories");
455                         expected.WriteLine ("      --color[=VALUE]        controls color info");
456                         expected.WriteLine ("      --color2[=color]       set color");
457                         expected.WriteLine ("      --rk=VALUE1:VALUE2     required key/value option");
458                         expected.WriteLine ("      --rk2=key:value        required {foo} key/value option");
459                         expected.WriteLine ("      --ok[=VALUE1:VALUE2]   optional key/value option");
460                         expected.WriteLine ("      --long-desc            This has a really");
461                         expected.WriteLine ("                               long, multi-line description that also");
462                         expected.WriteLine ("                               tests");
463                         expected.WriteLine ("                               the-builtin-supercalifragilisticexpialidicious-");
464                         expected.WriteLine ("                               break-on-hyphen.  Also, a list:");
465                         expected.WriteLine ("                                 item 1");
466                         expected.WriteLine ("                                 item 2");
467                         expected.WriteLine ("      --long-desc2           IWantThisDescriptionToBreakInsideAWordGeneratingAu-");
468                         expected.WriteLine ("                               toWordHyphenation.");
469                         expected.WriteLine ("      --long-desc3           OnlyOnePeriod.");
470                         expected.WriteLine ("                               AndNoWhitespaceShouldBeSupportedEvenWithLongDesc-");
471                         expected.WriteLine ("                               riptions");
472                         expected.WriteLine ("      --long-desc4           Lots of spaces in the middle 1 2 3 4 5 6 7 8 9 0 1");
473                         expected.WriteLine ("                               2 3 4 5 and more until the end.");
474                         expected.WriteLine ("      --long-desc5           Lots of spaces in the middle - . - . - . - . - . -");
475                         expected.WriteLine ("                               . - . - and more until the end.");
476                         expected.WriteLine ("");
477                         expected.WriteLine ("==This is a really long category name which will involve line wrapping, just");
478                         expected.WriteLine ("because...==");
479                         expected.WriteLine ("  -o, --out=DIRECTORY        The DIRECTORY to place the generated files and");
480                         expected.WriteLine ("                               directories.");
481                         expected.WriteLine ("                               ");
482                         expected.WriteLine ("                               If not specified, defaults to");
483                         expected.WriteLine ("                               `dirname FILE`/cache/`basename FILE .tree`.");
484                         expected.WriteLine ("");
485                         expected.WriteLine ("Category 3:");
486                         expected.WriteLine ("  -h, -?, --help             show help text");
487                         expected.WriteLine ("      --version              output version information and exit");
488                         expected.WriteLine ("  @s1, @s2                   Read Response File for More Options");
489
490                         StringWriter actual = new StringWriter ();
491                         p.WriteOptionDescriptions (actual);
492
493                         Assert.AreEqual (expected.ToString (), actual.ToString ());
494                 }
495
496                 [Test]
497                 public void OptionBundling ()
498                 {
499                         OptionBundling (_ ("-abcf", "foo", "bar"));
500                         OptionBundling (_ ("@s1", "foo", "bar"));
501                 }
502
503                 void OptionBundling (IEnumerable<string> args)
504                 {
505                         string a, b, c, f;
506                         a = b = c = f = null;
507                         var p = new OptionSet () {
508                                 { "a", v => a = "a" },
509                                 { "b", v => b = "b" },
510                                 { "c", v => c = "c" },
511                                 { "f=", v => f = v },
512                                 new TestArgumentSource (null, null) {
513                                         { "@s1", "-abcf" },
514                                 },
515                         };
516                         List<string> extra = p.Parse (args);
517                         Assert.AreEqual (extra.Count, 1);
518                         Assert.AreEqual (extra [0], "bar");
519                         Assert.AreEqual (a, "a");
520                         Assert.AreEqual (b, "b");
521                         Assert.AreEqual (c, "c");
522                         Assert.AreEqual (f, "foo");
523                 }
524
525                 [Test]
526                 public void HaltProcessing ()
527                 {
528                         var p = new OptionSet () {
529                                 { "a", v => {} },
530                                 { "b", v => {} },
531                                 new TestArgumentSource (null, null) {
532                                         { "@s1", "-a", "-b" },
533                                 },
534                         };
535                         List<string> e = p.Parse (_ ("-a", "-b", "--", "-a", "-b"));
536                         Assert.AreEqual (e.Count, 2);
537                         Assert.AreEqual (e [0], "-a");
538                         Assert.AreEqual (e [1], "-b");
539
540                         e = p.Parse (_ ("@s1", "--", "@s1"));
541                         Assert.AreEqual (e.Count, 1);
542                         Assert.AreEqual (e [0], "@s1");
543                 }
544
545                 [Test]
546                 public void KeyValueOptions ()
547                 {
548                         var a = new Dictionary<string, string> ();
549                         var b = new Dictionary<int, char> ();
550                         var p = new OptionSet () {
551                                 { "a=", (k,v) => a.Add (k, v) },
552                                 { "b=", (int k, char v) => b.Add (k, v) },
553                                 { "c:", (k, v) => {if (k != null) a.Add (k, v);} },
554                                 { "d={=>}{-->}", (k, v) => a.Add (k, v) },
555                                 { "e={}", (k, v) => a.Add (k, v) },
556                                 { "f=+/", (k, v) => a.Add (k, v) },
557                                 new TestArgumentSource (null, null) {
558                                         { "@s1", "-a", "A" },
559                                         { "@s2", @"C:\tmp", "-a" },
560                                         { "@s3", "C=D", @"-a=E=F:\tmp" },
561                                         { "@s4", "-a:G:H", "-aI=J" },
562                                         { "@s5", "-b", "1" },
563                                         { "@s6", "a", "-b" },
564                                         { "@s7", "2", "b" },
565                                         { "@s8", "-dA=>B", "-d" },
566                                         { "@s9", "C-->D", "-d:E" },
567                                         { "@s10", "F", "-d" },
568                                         { "@s11", "G", "H" },
569                                         { "@s12", "-dJ-->K" }
570                                 },
571                         };
572                         p.Parse (_("-a", "A", @"C:\tmp", "-a", "C=D", @"-a=E=F:\tmp", "-a:G:H", "-aI=J", "-b", "1", "a", "-b", "2", "b"));
573                         Action assert = () => {
574                                 AssertDictionary (a, 
575                                                 "A", @"C:\tmp", 
576                                                 "C", "D", 
577                                                 "E", @"F:\tmp", 
578                                                 "G", "H", 
579                                                 "I", "J");
580                                 AssertDictionary (b,
581                                                 "1", "a",
582                                                 "2", "b");
583                         };
584                         assert ();
585                         a.Clear ();
586                         b.Clear ();
587
588                         p.Parse (_("@s1", "@s2", "@s3", "@s4", "@s5", "@s6", "@s7"));
589                         assert ();
590                         a.Clear ();
591                         b.Clear ();
592
593                         p.Parse (_("-c"));
594                         Assert.AreEqual (a.Count, 0);
595                         p.Parse (_("-c", "a"));
596                         Assert.AreEqual (a.Count, 0);
597                         p.Parse (_("-ca"));
598                         AssertDictionary (a, "a", null);
599                         a.Clear ();
600                         p.Parse (_("-ca=b"));
601                         AssertDictionary (a, "a", "b");
602
603                         a.Clear ();
604                         p.Parse (_("-dA=>B", "-d", "C-->D", "-d:E", "F", "-d", "G", "H", "-dJ-->K"));
605                         assert = () => {
606                                 AssertDictionary (a,
607                                                 "A", "B",
608                                                 "C", "D", 
609                                                 "E", "F",
610                                                 "G", "H",
611                                                 "J", "K");
612                         };
613                         assert ();
614                         a.Clear ();
615
616                         p.Parse (_("@s8", "@s9", "@s10", "@s11", "@s12"));
617                         assert ();
618                         a.Clear ();
619
620                         p.Parse (_("-eA=B", "-eC=D", "-eE", "F", "-e:G", "H"));
621                         AssertDictionary (a,
622                                         "A=B", "-eC=D",
623                                         "E", "F", 
624                                         "G", "H");
625
626                         a.Clear ();
627                         p.Parse (_("-f1/2", "-f=3/4", "-f:5+6", "-f7", "8", "-f9=10", "-f11=12"));
628                         AssertDictionary (a,
629                                         "1", "2",
630                                         "3", "4",
631                                         "5", "6", 
632                                         "7", "8", 
633                                         "9=10", "-f11=12");
634                 }
635
636                 static void AssertDictionary<TKey, TValue> (Dictionary<TKey, TValue> dict, params string[] set)
637                 {
638                         TypeConverter k = TypeDescriptor.GetConverter (typeof (TKey));
639                         TypeConverter v = TypeDescriptor.GetConverter (typeof (TValue));
640
641                         Assert.AreEqual (dict.Count, set.Length / 2);
642                         for (int i = 0; i < set.Length; i += 2) {
643                                 TKey key = (TKey) k.ConvertFromString (set [i]);
644                                 Assert.AreEqual (dict.ContainsKey (key), true);
645                                 if (set [i+1] == null)
646                                         Assert.AreEqual (dict [key], default (TValue));
647                                 else
648                                         Assert.AreEqual (dict [key], (TValue) v.ConvertFromString (set [i+1]));
649                         }
650                 }
651
652                 class CustomOption : Option {
653                         Action<OptionValueCollection> action;
654
655                         public CustomOption (string p, string d, int c, Action<OptionValueCollection> a)
656                                 : base (p, d, c)
657                         {
658                                 this.action = a;
659                         }
660
661                         protected override void OnParseComplete (OptionContext c)
662                         {
663                                 action (c.OptionValues);
664                         }
665                 }
666
667                 [Test]
668                 public void CustomKeyValue ()
669                 {
670                         var a = new Dictionary<string, string> ();
671                         var b = new Dictionary<string, string[]> ();
672                         var p = new OptionSet () {
673                                 new CustomOption ("a==:", null, 2, v => a.Add (v [0], v [1])),
674                                 new CustomOption ("b==:", null, 3, v => b.Add (v [0], new string[]{v [1], v [2]})),
675                         };
676                         p.Parse (_(@"-a=b=C:\tmp", "-a=d", @"C:\e", @"-a:f=C:\g", @"-a:h:C:\i", "-a", @"j=C:\k", "-a", @"l:C:\m"));
677                         Assert.AreEqual (a.Count, 6);
678                         Assert.AreEqual (a ["b"], @"C:\tmp");
679                         Assert.AreEqual (a ["d"], @"C:\e");
680                         Assert.AreEqual (a ["f"], @"C:\g");
681                         Assert.AreEqual (a ["h"], @"C:\i");
682                         Assert.AreEqual (a ["j"], @"C:\k");
683                         Assert.AreEqual (a ["l"], @"C:\m");
684
685                         Utils.AssertException (typeof(OptionException),
686                                         "Missing required value for option '-a'.",
687                                         p, v => {v.Parse (_("-a=b"));});
688
689                         p.Parse (_("-b", "a", "b", @"C:\tmp", @"-b:d:e:F:\tmp", @"-b=g=h:i:\tmp", @"-b:j=k:l:\tmp"));
690                         Assert.AreEqual (b.Count, 4);
691                         Assert.AreEqual (b ["a"][0], "b");
692                         Assert.AreEqual (b ["a"][1], @"C:\tmp");
693                         Assert.AreEqual (b ["d"][0], "e");
694                         Assert.AreEqual (b ["d"][1], @"F:\tmp");
695                         Assert.AreEqual (b ["g"][0], "h");
696                         Assert.AreEqual (b ["g"][1], @"i:\tmp");
697                         Assert.AreEqual (b ["j"][0], "k");
698                         Assert.AreEqual (b ["j"][1], @"l:\tmp");
699                 }
700
701                 [Test]
702                 public void Localization ()
703                 {
704                         var p = new OptionSet (f => "hello!") {
705                                 { "n=", (int v) => { } },
706                         };
707                         Utils.AssertException (typeof(OptionException), "hello!",
708                                         p, v => { v.Parse (_("-n=value")); });
709
710                         StringWriter expected = new StringWriter ();
711                         expected.WriteLine ("  -nhello!                   hello!");
712
713                         StringWriter actual = new StringWriter ();
714                         p.WriteOptionDescriptions (actual);
715
716                         Assert.AreEqual (actual.ToString (), expected.ToString ());
717                 }
718
719                 class CiOptionSet : OptionSet {
720                         protected override void InsertItem (int index, Option item)
721                         {
722                                 if (item.Prototype.ToLower () != item.Prototype)
723                                         throw new ArgumentException ("prototypes must be null!");
724                                 base.InsertItem (index, item);
725                         }
726
727                         protected override bool Parse (string option, OptionContext c)
728                         {
729                                 if (c.Option != null)
730                                         return base.Parse (option, c);
731                                 string f, n, s, v;
732                                 if (!GetOptionParts (option, out f, out n, out s, out v)) {
733                                         return base.Parse (option, c);
734                                 }
735                                 return base.Parse (f + n.ToLower () + (v != null && s != null ? s + v : ""), c);
736                         }
737
738                         public new Option GetOptionForName (string n)
739                         {
740                                 return base.GetOptionForName (n);
741                         }
742
743                         public void CheckOptionParts (string option, bool er, string ef, string en, string es, string ev)
744                         {
745                                 string f, n, s, v;
746                                 bool r = GetOptionParts (option, out f, out n, out s, out v);
747                                 Assert.AreEqual (r, er);
748                                 Assert.AreEqual (f, ef);
749                                 Assert.AreEqual (n, en);
750                                 Assert.AreEqual (s, es);
751                                 Assert.AreEqual (v, ev);
752                         }
753                 }
754
755                 [Test]
756                 public void DerivedType ()
757                 {
758                         bool help = false;
759                         var p = new CiOptionSet () {
760                                 { "h|help", v => help = v != null },
761                         };
762                         p.Parse (_("-H"));
763                         Assert.AreEqual (help, true);
764                         help = false;
765                         p.Parse (_("-HELP"));
766                         Assert.AreEqual (help, true);
767
768                         Assert.AreEqual (p.GetOptionForName ("h"), p [0]);
769                         Assert.AreEqual (p.GetOptionForName ("help"), p [0]);
770                         Assert.AreEqual (p.GetOptionForName ("invalid"), null);
771
772                         Utils.AssertException (typeof(ArgumentException), "prototypes must be null!",
773                                         p, v => { v.Add ("N|NUM=", (int n) => {}); });
774                         Utils.AssertException (typeof(ArgumentNullException),
775                                         "Value cannot be null.\nParameter name: option",
776                                         p, v => { v.GetOptionForName (null); });
777                 }
778
779                 [Test]
780                 public void OptionParts ()
781                 {
782                         var p = new CiOptionSet ();
783                         p.CheckOptionParts ("A",        false,  null, null, null, null);
784                         p.CheckOptionParts ("A=B",      false,  null, null, null, null);
785                         p.CheckOptionParts ("-A=B",     true,   "-",  "A",  "=",  "B");
786                         p.CheckOptionParts ("-A:B",     true,   "-",  "A",  ":",  "B");
787                         p.CheckOptionParts ("--A=B",    true,   "--", "A",  "=",  "B");
788                         p.CheckOptionParts ("--A:B",    true,   "--", "A",  ":",  "B");
789                         p.CheckOptionParts ("/A=B",     true,   "/",  "A",  "=",  "B");
790                         p.CheckOptionParts ("/A:B",     true,   "/",  "A",  ":",  "B");
791                         p.CheckOptionParts ("-A=B=C",   true,   "-",  "A",  "=",  "B=C");
792                         p.CheckOptionParts ("-A:B=C",   true,   "-",  "A",  ":",  "B=C");
793                         p.CheckOptionParts ("-A:B:C",   true,   "-",  "A",  ":",  "B:C");
794                         p.CheckOptionParts ("--A=B=C",  true,   "--", "A",  "=",  "B=C");
795                         p.CheckOptionParts ("--A:B=C",  true,   "--", "A",  ":",  "B=C");
796                         p.CheckOptionParts ("--A:B:C",  true,   "--", "A",  ":",  "B:C");
797                         p.CheckOptionParts ("/A=B=C",   true,   "/",  "A",  "=",  "B=C");
798                         p.CheckOptionParts ("/A:B=C",   true,   "/",  "A",  ":",  "B=C");
799                         p.CheckOptionParts ("/A:B:C",   true,   "/",  "A",  ":",  "B:C");
800                         p.CheckOptionParts ("-AB=C",    true,   "-",  "AB", "=",  "C");
801                         p.CheckOptionParts ("-AB:C",    true,   "-",  "AB", ":",  "C");
802                 }
803
804                 class ContextCheckerOption : Option {
805                         string eName, eValue;
806                         int index;
807
808                         public ContextCheckerOption (string p, string d, string eName, string eValue, int index)
809                                 : base (p, d)
810                         {
811                                 this.eName  = eName;
812                                 this.eValue = eValue;
813                                 this.index  = index;
814                         }
815
816                         protected override void OnParseComplete (OptionContext c)
817                         {
818                                 Assert.AreEqual (c.OptionValues.Count, 1);
819                                 Assert.AreEqual (c.OptionValues [0], eValue);
820                                 Assert.AreEqual (c.OptionName, eName);
821                                 Assert.AreEqual (c.OptionIndex, index);
822                                 Assert.AreEqual (c.Option, this);
823                                 Assert.AreEqual (c.Option.Description, base.Description);
824                         }
825                 }
826
827                 [Test]
828                 public void OptionContext ()
829                 {
830                         var p = new OptionSet () {
831                                 new ContextCheckerOption ("a=", "a desc", "/a",   "a-val", 1),
832                                 new ContextCheckerOption ("b",  "b desc", "--b+", "--b+",  2),
833                                 new ContextCheckerOption ("c=", "c desc", "--c",  "C",     3),
834                                 new ContextCheckerOption ("d",  "d desc", "/d-",  null,    4),
835                         };
836                         Assert.AreEqual (p.Count, 4);
837                         p.Parse (_("/a", "a-val", "--b+", "--c=C", "/d-"));
838                 }
839
840                 [Test]
841                 public void DefaultHandler ()
842                 {
843                         var extra = new List<string> ();
844                         var p = new OptionSet () {
845                                 { "<>", v => extra.Add (v) },
846                         };
847                         var e = p.Parse (_("-a", "b", "--c=D", "E"));
848                         Assert.AreEqual (e.Count, 0);
849                         Assert.AreEqual (extra.Count, 4);
850                         Assert.AreEqual (extra [0], "-a");
851                         Assert.AreEqual (extra [1], "b");
852                         Assert.AreEqual (extra [2], "--c=D");
853                         Assert.AreEqual (extra [3], "E");
854                 }
855
856                 [Test]
857                 public void MixedDefaultHandler ()
858                 {
859                         var tests = new List<string> ();
860                         var p = new OptionSet () {
861                                 { "t|<>=", v => tests.Add (v) },
862                         };
863                         var e = p.Parse (_("-tA", "-t:B", "-t=C", "D", "--E=F"));
864                         Assert.AreEqual (e.Count, 0);
865                         Assert.AreEqual (tests.Count, 5);
866                         Assert.AreEqual (tests [0], "A");
867                         Assert.AreEqual (tests [1], "B");
868                         Assert.AreEqual (tests [2], "C");
869                         Assert.AreEqual (tests [3], "D");
870                         Assert.AreEqual (tests [4], "--E=F");
871                 }
872
873                 [Test]
874                 public void DefaultHandlerRuns ()
875                 {
876                         var formats = new Dictionary<string, List<string>> ();
877                         string format = "foo";
878                         var p = new OptionSet () {
879                                 { "f|format=", v => format = v },
880                                 { "<>", 
881                                         v => {
882                                                 List<string> f;
883                                                 if (!formats.TryGetValue (format, out f)) {
884                                                         f = new List<string> ();
885                                                         formats.Add (format, f);
886                                                 }
887                                                 f.Add (v);
888                                 } },
889                         };
890                         var e = p.Parse (_("a", "b", "-fbar", "c", "d", "--format=baz", "e", "f"));
891                         Assert.AreEqual (e.Count, 0);
892                         Assert.AreEqual (formats.Count, 3);
893                         Assert.AreEqual (formats ["foo"].Count, 2);
894                         Assert.AreEqual (formats ["foo"][0], "a");
895                         Assert.AreEqual (formats ["foo"][1], "b");
896                         Assert.AreEqual (formats ["bar"].Count, 2);
897                         Assert.AreEqual (formats ["bar"][0], "c");
898                         Assert.AreEqual (formats ["bar"][1], "d");
899                         Assert.AreEqual (formats ["baz"].Count, 2);
900                         Assert.AreEqual (formats ["baz"][0], "e");
901                         Assert.AreEqual (formats ["baz"][1], "f");
902                 }
903
904                 [Test]
905                 public void ReportInvalidDuplication ()
906                 {
907                         int verbosity = 0;
908                         var p = new OptionSet () {
909                                 { "v", v => verbosity = v != null ? verbosity + 1 : verbosity },
910                         };
911                         try {
912                                 p.Parse (new []{"-v-v-v"});
913                                 Assert.Fail ("Should not be reached.");
914                         } catch (OptionException e) {
915                                 Assert.AreEqual (null, e.OptionName);
916                                 Assert.AreEqual ("Cannot use unregistered option '-' in bundle '-v-v-v'.", e.Message);
917                         }
918                 }
919         }
920 }
921