last_getopt_ut.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. #include <library/cpp/getopt/last_getopt.h>
  2. #include <library/cpp/colorizer/colors.h>
  3. #include <library/cpp/testing/unittest/registar.h>
  4. #include <util/generic/array_size.h>
  5. #include <util/string/subst.h>
  6. #include <util/string/vector.h>
  7. #include <util/string/split.h>
  8. using namespace NLastGetopt;
  9. namespace {
  10. struct TOptsNoDefault: public TOpts {
  11. TOptsNoDefault(const TStringBuf& optstring = TStringBuf())
  12. : TOpts(optstring)
  13. {
  14. }
  15. };
  16. class TOptsParseResultTestWrapper: public TOptsParseResultException {
  17. TVector<const char*> Argv_;
  18. public:
  19. TOptsParseResultTestWrapper(const TOpts* opts, TVector<const char*> argv)
  20. : Argv_(argv)
  21. {
  22. Init(opts, (int)Argv_.size(), Argv_.data());
  23. }
  24. };
  25. using V = TVector<const char*>;
  26. }
  27. struct TOptsParserTester {
  28. TOptsNoDefault Opts_;
  29. TVector<const char*> Argv_;
  30. THolder<TOptsParser> Parser_;
  31. void Initialize() {
  32. if (!Parser_)
  33. Parser_.Reset(new TOptsParser(&Opts_, (int)Argv_.size(), Argv_.data()));
  34. }
  35. void Accept() {
  36. Initialize();
  37. UNIT_ASSERT(Parser_->Next());
  38. }
  39. void AcceptOption() {
  40. Accept();
  41. UNIT_ASSERT(!!Parser_->CurOpt());
  42. }
  43. void AcceptOption(char c) {
  44. AcceptOption();
  45. UNIT_ASSERT(Parser_->CurOpt()->CharIs(c));
  46. }
  47. void AcceptOption(const TString& optName) {
  48. AcceptOption();
  49. UNIT_ASSERT(Parser_->CurOpt()->NameIs(optName));
  50. }
  51. template <typename TOpt>
  52. void AcceptOptionWithValue(TOpt optName, const TString& value) {
  53. AcceptOption(optName);
  54. UNIT_ASSERT_VALUES_EQUAL_C(value, Parser_->CurValStr(), "; option " << optName);
  55. }
  56. template <typename TOpt>
  57. void AcceptOptionWithoutValue(TOpt optName) {
  58. AcceptOption(optName);
  59. UNIT_ASSERT_C(!Parser_->CurVal(), ": opt " << optName << " must have no param");
  60. }
  61. void AcceptFreeArgInOrder(const TString& expected) {
  62. Accept();
  63. UNIT_ASSERT(!Parser_->CurOpt());
  64. UNIT_ASSERT_VALUES_EQUAL(expected, Parser_->CurValStr());
  65. }
  66. size_t Pos_;
  67. void AcceptEndOfOptions() {
  68. Initialize();
  69. UNIT_ASSERT(!Parser_->Next());
  70. Pos_ = Parser_->Pos_;
  71. // pos must not be changed after last meaningful invocation of Next()
  72. UNIT_ASSERT(!Parser_->Next());
  73. UNIT_ASSERT_VALUES_EQUAL(Pos_, Parser_->Pos_);
  74. UNIT_ASSERT(!Parser_->Next());
  75. UNIT_ASSERT_VALUES_EQUAL(Pos_, Parser_->Pos_);
  76. }
  77. void AcceptError() {
  78. Initialize();
  79. try {
  80. Parser_->Next();
  81. UNIT_FAIL("expecting exception");
  82. } catch (const TUsageException&) {
  83. // expecting
  84. }
  85. }
  86. void AcceptUnexpectedOption() {
  87. Initialize();
  88. size_t pos = Parser_->Pos_;
  89. size_t sop = Parser_->Sop_;
  90. AcceptError();
  91. UNIT_ASSERT_VALUES_EQUAL(pos, Parser_->Pos_);
  92. UNIT_ASSERT_VALUES_EQUAL(sop, Parser_->Sop_);
  93. }
  94. void AcceptFreeArg(const TString& expected) {
  95. UNIT_ASSERT(Pos_ < Parser_->Argc_);
  96. UNIT_ASSERT_VALUES_EQUAL(expected, Parser_->Argv_[Pos_]);
  97. ++Pos_;
  98. }
  99. void AcceptEndOfFreeArgs() {
  100. UNIT_ASSERT_VALUES_EQUAL(Argv_.size(), Pos_);
  101. }
  102. };
  103. namespace {
  104. bool gSimpleFlag = false;
  105. void SimpleHander(void) {
  106. gSimpleFlag = true;
  107. }
  108. }
  109. Y_UNIT_TEST_SUITE(TLastGetoptTests) {
  110. Y_UNIT_TEST(TestEqual) {
  111. TOptsNoDefault opts;
  112. opts.AddLongOption("from");
  113. opts.AddLongOption("to");
  114. TOptsParseResultTestWrapper r(&opts, V({"copy", "--from=/", "--to=/etc"}));
  115. UNIT_ASSERT_VALUES_EQUAL("copy", r.GetProgramName());
  116. UNIT_ASSERT_VALUES_EQUAL("/", r.Get("from"));
  117. UNIT_ASSERT_VALUES_EQUAL("/etc", r.Get("to"));
  118. UNIT_ASSERT_VALUES_EQUAL("/etc", r.GetOrElse("to", "trash"));
  119. UNIT_ASSERT(r.Has("from"));
  120. UNIT_ASSERT(r.Has("to"));
  121. UNIT_ASSERT_EXCEPTION(r.Get("left"), TException);
  122. }
  123. Y_UNIT_TEST(TestCharOptions) {
  124. TOptsNoDefault opts;
  125. opts.AddCharOption('R', NO_ARGUMENT);
  126. opts.AddCharOption('l', NO_ARGUMENT);
  127. opts.AddCharOption('h', NO_ARGUMENT);
  128. TOptsParseResultTestWrapper r(&opts, V({"cp", "/etc", "-Rl", "/tmp/etc"}));
  129. UNIT_ASSERT(r.Has('R'));
  130. UNIT_ASSERT(r.Has('l'));
  131. UNIT_ASSERT(!r.Has('h'));
  132. UNIT_ASSERT_VALUES_EQUAL(2u, r.GetFreeArgs().size());
  133. UNIT_ASSERT_VALUES_EQUAL(2u, r.GetFreeArgCount());
  134. UNIT_ASSERT_VALUES_EQUAL("/etc", r.GetFreeArgs()[0]);
  135. UNIT_ASSERT_VALUES_EQUAL("/tmp/etc", r.GetFreeArgs()[1]);
  136. }
  137. Y_UNIT_TEST(TestFreeArgs) {
  138. TOptsNoDefault opts;
  139. opts.SetFreeArgsNum(1, 3);
  140. TOptsParseResultTestWrapper r11(&opts, V({"cp", "/etc"}));
  141. TOptsParseResultTestWrapper r12(&opts, V({"cp", "/etc", "/tmp/etc"}));
  142. TOptsParseResultTestWrapper r13(&opts, V({"cp", "/etc", "/tmp/etc", "verbose"}));
  143. UNIT_ASSERT_EXCEPTION(
  144. TOptsParseResultTestWrapper(&opts, V({"cp", "/etc", "/tmp/etc", "verbose", "nosymlink"})),
  145. yexception);
  146. UNIT_ASSERT_EXCEPTION(
  147. TOptsParseResultTestWrapper(&opts, V({"cp"})),
  148. yexception);
  149. opts.SetFreeArgsNum(2);
  150. TOptsParseResultTestWrapper r22(&opts, V({"cp", "/etc", "/var/tmp"}));
  151. }
  152. Y_UNIT_TEST(TestCharOptionsRequiredOptional) {
  153. TOptsNoDefault opts;
  154. opts.AddCharOption('d', REQUIRED_ARGUMENT);
  155. opts.AddCharOption('e', REQUIRED_ARGUMENT);
  156. opts.AddCharOption('x', REQUIRED_ARGUMENT);
  157. opts.AddCharOption('y', REQUIRED_ARGUMENT);
  158. opts.AddCharOption('l', NO_ARGUMENT);
  159. TOptsParseResultTestWrapper r(&opts, V({"cmd", "-ld11", "-e", "22", "-lllx33", "-y", "44"}));
  160. UNIT_ASSERT_VALUES_EQUAL("11", r.Get('d'));
  161. UNIT_ASSERT_VALUES_EQUAL("22", r.Get('e'));
  162. UNIT_ASSERT_VALUES_EQUAL("33", r.Get('x'));
  163. UNIT_ASSERT_VALUES_EQUAL("44", r.Get('y'));
  164. }
  165. Y_UNIT_TEST(TestReturnInOrder) {
  166. TOptsParserTester tester;
  167. tester.Opts_.AddLongOption('v', "value");
  168. tester.Opts_.ArgPermutation_ = RETURN_IN_ORDER;
  169. tester.Argv_.push_back("cmd");
  170. tester.Argv_.push_back("--value=11");
  171. tester.Argv_.push_back("xx");
  172. tester.Argv_.push_back("-v12");
  173. tester.Argv_.push_back("yy");
  174. tester.Argv_.push_back("--");
  175. tester.Argv_.push_back("-v13");
  176. tester.Argv_.push_back("--");
  177. tester.AcceptOptionWithValue("value", "11");
  178. tester.AcceptFreeArgInOrder("xx");
  179. tester.AcceptOptionWithValue('v', "12");
  180. tester.AcceptFreeArgInOrder("yy");
  181. tester.AcceptFreeArgInOrder("-v13");
  182. tester.AcceptFreeArgInOrder("--");
  183. tester.AcceptEndOfOptions();
  184. tester.AcceptEndOfFreeArgs();
  185. }
  186. Y_UNIT_TEST(TestRequireOrder) {
  187. TOptsParserTester tester;
  188. tester.Opts_.ArgPermutation_ = REQUIRE_ORDER;
  189. tester.Opts_.AddLongOption('v', "value");
  190. tester.Argv_.push_back("cmd");
  191. tester.Argv_.push_back("--value=11");
  192. tester.Argv_.push_back("xx");
  193. tester.Argv_.push_back("-v12");
  194. tester.Argv_.push_back("yy");
  195. tester.AcceptOptionWithValue("value", "11");
  196. tester.AcceptEndOfOptions();
  197. tester.AcceptFreeArg("xx");
  198. tester.AcceptFreeArg("-v12");
  199. tester.AcceptFreeArg("yy");
  200. tester.AcceptEndOfFreeArgs();
  201. }
  202. Y_UNIT_TEST(TestPlusForLongOption) {
  203. TOptsParserTester tester;
  204. tester.Opts_.AddLongOption('v', "value");
  205. tester.Opts_.AllowPlusForLong_ = true;
  206. tester.Argv_.push_back("cmd");
  207. tester.Argv_.push_back("+value=11");
  208. tester.Argv_.push_back("xx");
  209. tester.Argv_.push_back("-v12");
  210. tester.Argv_.push_back("yy");
  211. tester.AcceptOptionWithValue("value", "11");
  212. tester.AcceptOptionWithValue("value", "12");
  213. tester.AcceptEndOfOptions();
  214. tester.AcceptFreeArg("xx");
  215. tester.AcceptFreeArg("yy");
  216. tester.AcceptEndOfFreeArgs();
  217. }
  218. Y_UNIT_TEST(TestBug1) {
  219. TOptsParserTester tester;
  220. tester.Opts_.AddCharOptions("A:b:cd:");
  221. tester.Argv_.push_back("cmd");
  222. tester.Argv_.push_back("-A");
  223. tester.Argv_.push_back("aaaa");
  224. tester.Argv_.push_back("zz");
  225. tester.Argv_.push_back("-c");
  226. tester.Argv_.push_back("-d8");
  227. tester.Argv_.push_back("ww");
  228. tester.AcceptOptionWithValue('A', "aaaa");
  229. tester.AcceptOptionWithoutValue('c');
  230. tester.AcceptOptionWithValue('d', "8");
  231. tester.AcceptEndOfOptions();
  232. tester.AcceptFreeArg("zz");
  233. tester.AcceptFreeArg("ww");
  234. tester.AcceptEndOfFreeArgs();
  235. }
  236. Y_UNIT_TEST(TestPermuteComplex) {
  237. TOptsParserTester tester;
  238. tester.Opts_.AddCharOption('x').NoArgument();
  239. tester.Opts_.AddCharOption('y').RequiredArgument();
  240. tester.Opts_.AddCharOption('z').NoArgument();
  241. tester.Opts_.AddCharOption('w').RequiredArgument();
  242. tester.Opts_.ArgPermutation_ = PERMUTE;
  243. tester.Argv_.push_back("cmd");
  244. tester.Argv_.push_back("-x");
  245. tester.Argv_.push_back("-y");
  246. tester.Argv_.push_back("val");
  247. tester.Argv_.push_back("freearg1");
  248. tester.Argv_.push_back("-zw");
  249. tester.Argv_.push_back("val2");
  250. tester.Argv_.push_back("freearg2");
  251. tester.AcceptOptionWithoutValue('x');
  252. tester.AcceptOptionWithValue('y', "val");
  253. tester.AcceptOptionWithoutValue('z');
  254. tester.AcceptOptionWithValue('w', "val2");
  255. tester.AcceptEndOfOptions();
  256. tester.AcceptFreeArg("freearg1");
  257. tester.AcceptFreeArg("freearg2");
  258. tester.AcceptEndOfFreeArgs();
  259. }
  260. Y_UNIT_TEST(TestFinalDashDash) {
  261. TOptsParserTester tester;
  262. tester.Opts_.AddLongOption("size");
  263. tester.Argv_.push_back("cmd");
  264. tester.Argv_.push_back("--");
  265. tester.AcceptEndOfOptions();
  266. tester.AcceptEndOfFreeArgs();
  267. }
  268. Y_UNIT_TEST(TestDashDashAfterDashDash) {
  269. TOptsParserTester tester;
  270. tester.Opts_.AddLongOption("size");
  271. tester.Argv_.push_back("cmd");
  272. tester.Argv_.push_back("--");
  273. tester.Argv_.push_back("--");
  274. tester.Argv_.push_back("--");
  275. tester.AcceptEndOfOptions();
  276. tester.AcceptFreeArg("--");
  277. tester.AcceptFreeArg("--");
  278. tester.AcceptEndOfFreeArgs();
  279. }
  280. Y_UNIT_TEST(TestUnexpectedUnknownOption) {
  281. TOptsParserTester tester;
  282. tester.Argv_.push_back("cmd");
  283. tester.Argv_.push_back("-x");
  284. tester.AcceptUnexpectedOption();
  285. }
  286. Y_UNIT_TEST(TestDuplicatedOptionCrash) {
  287. // this test is broken, cause UNIT_ASSERT(false) always throws
  288. return;
  289. bool exception = false;
  290. try {
  291. TOpts opts;
  292. opts.AddLongOption('x', "one");
  293. opts.AddLongOption('x', "two");
  294. UNIT_ASSERT(false);
  295. } catch (...) {
  296. // we should go here, duplicating options are forbidden
  297. exception = true;
  298. }
  299. UNIT_ASSERT(exception);
  300. }
  301. Y_UNIT_TEST(TestPositionWhenNoArgs) {
  302. TOptsParserTester tester;
  303. tester.Argv_.push_back("cmd");
  304. tester.Opts_.AddCharOption('c');
  305. tester.AcceptEndOfOptions();
  306. UNIT_ASSERT_VALUES_EQUAL(1u, tester.Parser_->Pos_);
  307. }
  308. Y_UNIT_TEST(TestExpectedUnknownCharOption) {
  309. TOptsParserTester tester;
  310. tester.Argv_.push_back("cmd");
  311. tester.Argv_.push_back("-x");
  312. tester.Argv_.push_back("-y");
  313. tester.Argv_.push_back("val");
  314. tester.Argv_.push_back("freearg1");
  315. tester.Argv_.push_back("-zw");
  316. tester.Argv_.push_back("val2");
  317. tester.Argv_.push_back("freearg2");
  318. tester.Opts_.AllowUnknownCharOptions_ = true;
  319. tester.AcceptOptionWithoutValue('x');
  320. tester.AcceptOptionWithValue('y', "val");
  321. tester.AcceptOptionWithoutValue('z');
  322. tester.AcceptOptionWithValue('w', "val2");
  323. tester.AcceptEndOfOptions();
  324. tester.AcceptFreeArg("freearg1");
  325. tester.AcceptFreeArg("freearg2");
  326. tester.AcceptEndOfFreeArgs();
  327. }
  328. #if 0
  329. Y_UNIT_TEST(TestRequiredParams) {
  330. TOptsParserTester tester;
  331. tester.Argv_.push_back("cmd");
  332. tester.Argv_.push_back("--port=1231");
  333. tester.Argv_.push_back("asas");
  334. tester.Opts_.AddLongOption("port");
  335. tester.Opts_.AddLongOption("home").Required();
  336. tester.AcceptOptionWithValue("port", "1231");
  337. tester.AcceptError();
  338. }
  339. #endif
  340. Y_UNIT_TEST(TestEqParseOnly) {
  341. TOptsParserTester tester;
  342. tester.Argv_.push_back("cmd");
  343. tester.Argv_.push_back("--data=jjhh");
  344. tester.Argv_.push_back("-n");
  345. tester.Argv_.push_back("11");
  346. tester.Argv_.push_back("--optional-number-1=8");
  347. tester.Argv_.push_back("--optional-string-1=os1");
  348. tester.Argv_.push_back("--optional-number-2");
  349. tester.Argv_.push_back("10");
  350. tester.Argv_.push_back("--optional-string-2");
  351. tester.Argv_.push_back("freearg");
  352. tester.Opts_.AddLongOption('d', "data");
  353. tester.Opts_.AddLongOption('n', "number");
  354. tester.Opts_.AddLongOption("optional-string-0");
  355. tester.Opts_.AddLongOption("optional-number-0");
  356. tester.Opts_.AddLongOption("optional-string-1");
  357. tester.Opts_.AddLongOption("optional-number-1");
  358. tester.Opts_.AddLongOption("optional-string-2").OptionalArgument().DisableSpaceParse();
  359. tester.Opts_.AddLongOption("optional-number-2").OptionalArgument();
  360. tester.AcceptOptionWithValue("data", "jjhh");
  361. tester.AcceptOptionWithValue('n', "11");
  362. tester.AcceptOptionWithValue("optional-number-1", "8");
  363. tester.AcceptOptionWithValue("optional-string-1", "os1");
  364. tester.AcceptOptionWithValue("optional-number-2", "10");
  365. tester.AcceptOptionWithoutValue("optional-string-2");
  366. tester.AcceptEndOfOptions();
  367. tester.AcceptFreeArg("freearg");
  368. tester.AcceptEndOfFreeArgs();
  369. }
  370. Y_UNIT_TEST(TestStoreResult) {
  371. TOptsNoDefault opts;
  372. TString data;
  373. int number;
  374. TMaybe<TString> optionalString0, optionalString1;
  375. TMaybe<int> optionalNumber0, optionalNumber1;
  376. opts.AddLongOption('d', "data").StoreResult(&data);
  377. opts.AddLongOption('n', "number").StoreResult(&number);
  378. opts.AddLongOption("optional-string-0").StoreResult(&optionalString0);
  379. opts.AddLongOption("optional-number-0").StoreResult(&optionalNumber0);
  380. opts.AddLongOption("optional-string-1").StoreResult(&optionalString1);
  381. opts.AddLongOption("optional-number-1").StoreResult(&optionalNumber1);
  382. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--data=jjhh", "-n", "11", "--optional-number-1=8", "--optional-string-1=os1"}));
  383. UNIT_ASSERT_VALUES_EQUAL("jjhh", data);
  384. UNIT_ASSERT_VALUES_EQUAL(11, number);
  385. UNIT_ASSERT(!optionalString0.Defined());
  386. UNIT_ASSERT(!optionalNumber0.Defined());
  387. UNIT_ASSERT_VALUES_EQUAL(*optionalString1, "os1");
  388. UNIT_ASSERT_VALUES_EQUAL(*optionalNumber1, 8);
  389. }
  390. Y_UNIT_TEST(TestStoreValue) {
  391. int a = 0, b = 0;
  392. size_t c = 0;
  393. EHasArg e = NO_ARGUMENT;
  394. TOptsNoDefault opts;
  395. opts.AddLongOption('a', "alpha").NoArgument().StoreValue(&a, 42);
  396. opts.AddLongOption('b', "beta").NoArgument().StoreValue(&b, 24);
  397. opts.AddLongOption('e', "enum").NoArgument().StoreValue(&e, REQUIRED_ARGUMENT).StoreValue(&c, 12345);
  398. TOptsParseResultTestWrapper r(&opts, V({"cmd", "-a", "-e"}));
  399. UNIT_ASSERT_VALUES_EQUAL(42, a);
  400. UNIT_ASSERT_VALUES_EQUAL(0, b);
  401. UNIT_ASSERT(e == REQUIRED_ARGUMENT);
  402. UNIT_ASSERT_VALUES_EQUAL(12345u, c);
  403. }
  404. Y_UNIT_TEST(TestSetFlag) {
  405. bool a = false, b = true, c = false, d = true;
  406. TOptsNoDefault opts;
  407. opts.AddLongOption('a', "alpha").NoArgument().SetFlag(&a);
  408. opts.AddLongOption('b', "beta").NoArgument().SetFlag(&b);
  409. opts.AddCharOption('c').StoreTrue(&c);
  410. opts.AddCharOption('d').StoreTrue(&d);
  411. TOptsParseResultTestWrapper r(&opts, V({"cmd", "-a", "-c"}));
  412. UNIT_ASSERT(a);
  413. UNIT_ASSERT(!b);
  414. UNIT_ASSERT(c);
  415. UNIT_ASSERT(!d);
  416. }
  417. Y_UNIT_TEST(TestDefaultValue) {
  418. TOptsNoDefault opts;
  419. opts.AddLongOption("path").DefaultValue("/etc");
  420. int value = 42;
  421. opts.AddLongOption("value").StoreResult(&value).DefaultValue(32);
  422. TOptsParseResultTestWrapper r(&opts, V({"cmd", "dfdf"}));
  423. UNIT_ASSERT_VALUES_EQUAL("/etc", r.Get("path"));
  424. UNIT_ASSERT_VALUES_EQUAL(32, value);
  425. }
  426. Y_UNIT_TEST(TestSplitValue) {
  427. TOptsNoDefault opts;
  428. TVector<TString> vals;
  429. opts.AddLongOption('s', "split").SplitHandler(&vals, ',');
  430. TOptsParseResultTestWrapper r(&opts, V({"prog", "--split=a,b,c"}));
  431. UNIT_ASSERT_EQUAL(vals.size(), 3);
  432. UNIT_ASSERT_EQUAL(vals[0], "a");
  433. UNIT_ASSERT_EQUAL(vals[1], "b");
  434. UNIT_ASSERT_EQUAL(vals[2], "c");
  435. }
  436. Y_UNIT_TEST(TestRangeSplitValue) {
  437. TOptsNoDefault opts;
  438. TVector<ui32> vals;
  439. opts.AddLongOption('s', "split").RangeSplitHandler(&vals, ',', '-');
  440. TOptsParseResultTestWrapper r(&opts, V({"prog", "--split=1,8-10", "--split=12-14"}));
  441. UNIT_ASSERT_EQUAL(vals.size(), 7);
  442. UNIT_ASSERT_EQUAL(vals[0], 1);
  443. UNIT_ASSERT_EQUAL(vals[1], 8);
  444. UNIT_ASSERT_EQUAL(vals[2], 9);
  445. UNIT_ASSERT_EQUAL(vals[3], 10);
  446. UNIT_ASSERT_EQUAL(vals[4], 12);
  447. UNIT_ASSERT_EQUAL(vals[5], 13);
  448. UNIT_ASSERT_EQUAL(vals[6], 14);
  449. }
  450. Y_UNIT_TEST(TestParseArgs) {
  451. TOptsNoDefault o("AbCx:y:z::");
  452. UNIT_ASSERT_EQUAL(o.GetCharOption('A').HasArg_, NO_ARGUMENT);
  453. UNIT_ASSERT_EQUAL(o.GetCharOption('b').HasArg_, NO_ARGUMENT);
  454. UNIT_ASSERT_EQUAL(o.GetCharOption('C').HasArg_, NO_ARGUMENT);
  455. UNIT_ASSERT_EQUAL(o.GetCharOption('x').HasArg_, REQUIRED_ARGUMENT);
  456. UNIT_ASSERT_EQUAL(o.GetCharOption('y').HasArg_, REQUIRED_ARGUMENT);
  457. UNIT_ASSERT_EQUAL(o.GetCharOption('z').HasArg_, OPTIONAL_ARGUMENT);
  458. }
  459. Y_UNIT_TEST(TestRequiredOpts) {
  460. TOptsNoDefault opts;
  461. TOpt& opt_d = opts.AddCharOption('d');
  462. // test 'not required'
  463. // makes sure that the problem will only be in 'required'
  464. TOptsParseResultTestWrapper r1(&opts, V({"cmd"}));
  465. // test 'required'
  466. opt_d.Required();
  467. UNIT_ASSERT_EXCEPTION(
  468. TOptsParseResultTestWrapper(&opts, V({"cmd"})),
  469. TUsageException);
  470. TOptsParseResultTestWrapper r3(&opts, V({"cmd", "-d11"}));
  471. UNIT_ASSERT_VALUES_EQUAL("11", r3.Get('d'));
  472. }
  473. class HandlerStoreTrue {
  474. bool* Flag;
  475. public:
  476. HandlerStoreTrue(bool* flag)
  477. : Flag(flag)
  478. {
  479. }
  480. void operator()() {
  481. *Flag = true;
  482. }
  483. };
  484. Y_UNIT_TEST(TestHandlers) {
  485. {
  486. TOptsNoDefault opts;
  487. bool flag = false;
  488. opts.AddLongOption("flag").Handler0(HandlerStoreTrue(&flag)).NoArgument();
  489. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--flag"}));
  490. UNIT_ASSERT(flag);
  491. }
  492. {
  493. TOptsNoDefault opts;
  494. unsigned uval = 5;
  495. double fval = 0.0;
  496. opts.AddLongOption("flag1").RequiredArgument().StoreResult(&uval);
  497. opts.AddLongOption("flag2").RequiredArgument().StoreResultT<int>(&uval);
  498. opts.AddLongOption("flag3").RequiredArgument().StoreMappedResult(&fval, (double (*)(double))fabs);
  499. opts.AddLongOption("flag4").RequiredArgument().StoreMappedResult(&fval, (double (*)(double))sqrt);
  500. UNIT_ASSERT_EXCEPTION(
  501. TOptsParseResultTestWrapper(&opts, V({"cmd", "--flag3", "-2.0", "--flag1", "-1"})),
  502. yexception);
  503. UNIT_ASSERT_VALUES_EQUAL(uval, 5u);
  504. UNIT_ASSERT_VALUES_EQUAL(fval, 2.0);
  505. TOptsParseResultTestWrapper r1(&opts, V({"cmd", "--flag4", "9.0", "--flag2", "-1"}));
  506. UNIT_ASSERT_VALUES_EQUAL(uval, Max<unsigned>());
  507. UNIT_ASSERT_VALUES_EQUAL(fval, 3.0);
  508. }
  509. }
  510. Y_UNIT_TEST(TestTitleAndPrintUsage) {
  511. TOpts opts;
  512. const char* prog = "my_program";
  513. TString title = TString("Sample ") + TString(prog).Quote() + " application";
  514. opts.SetTitle(title);
  515. int argc = 2;
  516. const char* cmd[] = {prog};
  517. TOptsParser parser(&opts, argc, cmd);
  518. TStringStream out;
  519. parser.PrintUsage(out);
  520. // find title
  521. UNIT_ASSERT(out.Str().find(title) != TString::npos);
  522. // find usage
  523. UNIT_ASSERT(out.Str().find(" " + TString(prog) + " ") != TString::npos);
  524. }
  525. Y_UNIT_TEST(TestCustomCmdLineDescr) {
  526. TOpts opts;
  527. const char* prog = "my_program";
  528. TString customDescr = "<FILE|TABLE> USER [OPTIONS]";
  529. int argc = 2;
  530. const char* cmd[] = {prog};
  531. opts.SetCmdLineDescr(customDescr);
  532. TOptsParser parser(&opts, argc, cmd);
  533. TStringStream out;
  534. parser.PrintUsage(out);
  535. // find custom usage
  536. UNIT_ASSERT(out.Str().find(customDescr) != TString::npos);
  537. }
  538. Y_UNIT_TEST(TestColorPrint) {
  539. TOpts opts;
  540. const char* prog = "my_program";
  541. opts.AddLongOption("long_option").Required();
  542. opts.AddLongOption('o', "other");
  543. opts.AddCharOption('d').DefaultValue("42");
  544. opts.AddCharOption('s').DefaultValue("str_default");
  545. opts.SetFreeArgsNum(123, 456);
  546. opts.SetFreeArgTitle(0, "first_free_arg", "help");
  547. opts.SetFreeArgTitle(2, "second_free_arg");
  548. opts.AddSection("Section", "Section\n text");
  549. const char* cmd[] = {prog};
  550. TOptsParser parser(&opts, Y_ARRAY_SIZE(cmd), cmd);
  551. TStringStream out;
  552. NColorizer::TColors colors(true);
  553. parser.PrintUsage(out, colors);
  554. // find options and green color
  555. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "--long_option" << colors.OldColor()) != TString::npos);
  556. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "--other" << colors.OldColor()) != TString::npos);
  557. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "-o" << colors.OldColor()) != TString::npos);
  558. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "-d" << colors.OldColor()) != TString::npos);
  559. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "-s" << colors.OldColor()) != TString::npos);
  560. // find default values
  561. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.CyanColor() << "42" << colors.OldColor()) != TString::npos);
  562. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.CyanColor() << "\"str_default\"" << colors.OldColor()) != TString::npos);
  563. // find free args
  564. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "123" << colors.OldColor()) != TString::npos);
  565. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "456" << colors.OldColor()) != TString::npos);
  566. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "first_free_arg" << colors.OldColor()) != TString::npos);
  567. // free args without help not rendered even if they have custom title
  568. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "second_free_arg" << colors.OldColor()) == TString::npos);
  569. // find signatures
  570. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Usage" << colors.OldColor()) != TString::npos);
  571. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Required parameters" << colors.OldColor()) != TString::npos);
  572. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Optional parameters" << colors.OldColor()) != TString::npos);
  573. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Free args" << colors.OldColor()) != TString::npos);
  574. // find sections
  575. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Section" << colors.OldColor() << ":") != TString::npos);
  576. UNIT_ASSERT(out.Str().find(TStringBuilder() << " Section\n text") != TString::npos);
  577. // print without colors
  578. TStringStream out2;
  579. opts.PrintUsage(prog, out2);
  580. UNIT_ASSERT(out2.Str().find(colors.GreenColor()) == TString::npos);
  581. UNIT_ASSERT(out2.Str().find(colors.CyanColor()) == TString::npos);
  582. UNIT_ASSERT(out2.Str().find(colors.BoldColor()) == TString::npos);
  583. UNIT_ASSERT(out2.Str().find(colors.OldColor()) == TString::npos);
  584. }
  585. Y_UNIT_TEST(TestPadding) {
  586. const bool withColorsOpt[] = {false, true};
  587. for (bool withColors : withColorsOpt) {
  588. TOpts opts;
  589. const char* prog = "my_program";
  590. opts.AddLongOption("option", "description 1").Required(); // long option
  591. opts.AddLongOption('o', "other", "description 2"); // char and long option
  592. opts.AddCharOption('d', "description 3").RequiredArgument("DD"); // char option
  593. opts.AddCharOption('s', "description 4\ndescription 5\ndescription 6"); // multiline desc
  594. opts.AddLongOption('l', "very_very_very_loooong_ooooption", "description 7").RequiredArgument("LONG_ARGUMENT");
  595. const char* cmd[] = {prog};
  596. TOptsParser parser(&opts, Y_ARRAY_SIZE(cmd), cmd);
  597. TStringStream out;
  598. NColorizer::TColors colors(withColors);
  599. parser.PrintUsage(out, colors);
  600. TString printed = out.Str();
  601. if (withColors) {
  602. // remove not printable characters
  603. SubstGlobal(printed, TString(colors.BoldColor()), "");
  604. SubstGlobal(printed, TString(colors.GreenColor()), "");
  605. SubstGlobal(printed, TString(colors.CyanColor()), "");
  606. SubstGlobal(printed, TString(colors.OldColor()), "");
  607. }
  608. TVector<TString> lines;
  609. StringSplitter(printed).Split('\n').SkipEmpty().Collect(&lines);
  610. UNIT_ASSERT(!lines.empty());
  611. TVector<size_t> indents;
  612. for (const TString& line : lines) {
  613. const size_t indent = line.find("description ");
  614. if (indent != TString::npos)
  615. indents.push_back(indent);
  616. }
  617. UNIT_ASSERT_VALUES_EQUAL(indents.size(), 7);
  618. const size_t theOnlyIndent = indents[0];
  619. for (size_t indent : indents) {
  620. UNIT_ASSERT_VALUES_EQUAL_C(indent, theOnlyIndent, printed);
  621. }
  622. }
  623. }
  624. Y_UNIT_TEST(TestAppendTo) {
  625. TVector<int> ints;
  626. std::vector<std::string> strings;
  627. TOptsNoDefault opts;
  628. opts.AddLongOption("size").AppendTo(&ints);
  629. opts.AddLongOption("value").AppendTo(&strings);
  630. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--size=17", "--size=19", "--value=v1", "--value=v2"}));
  631. UNIT_ASSERT_VALUES_EQUAL(size_t(2), ints.size());
  632. UNIT_ASSERT_VALUES_EQUAL(17, ints.at(0));
  633. UNIT_ASSERT_VALUES_EQUAL(19, ints.at(1));
  634. UNIT_ASSERT_VALUES_EQUAL(size_t(2), strings.size());
  635. UNIT_ASSERT_VALUES_EQUAL("v1", strings.at(0));
  636. UNIT_ASSERT_VALUES_EQUAL("v2", strings.at(1));
  637. }
  638. Y_UNIT_TEST(TestEmplaceTo) {
  639. TVector<std::tuple<TString>> richPaths;
  640. TOptsNoDefault opts;
  641. opts.AddLongOption("path").EmplaceTo(&richPaths);
  642. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--path=<a=b>//cool", "--path=//nice"}));
  643. UNIT_ASSERT_VALUES_EQUAL(size_t(2), richPaths.size());
  644. UNIT_ASSERT_VALUES_EQUAL("<a=b>//cool", std::get<0>(richPaths.at(0)));
  645. UNIT_ASSERT_VALUES_EQUAL("//nice", std::get<0>(richPaths.at(1)));
  646. }
  647. Y_UNIT_TEST(TestKVHandler) {
  648. TStringBuilder keyvals;
  649. TOptsNoDefault opts;
  650. opts.AddLongOption("set").KVHandler([&keyvals](TString k, TString v) { keyvals << k << ":" << v << ","; });
  651. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--set", "x=1", "--set", "y=2", "--set=z=3"}));
  652. UNIT_ASSERT_VALUES_EQUAL(keyvals, "x:1,y:2,z:3,");
  653. }
  654. Y_UNIT_TEST(TestEasySetup) {
  655. TEasySetup opts;
  656. bool flag = false;
  657. opts('v', "version", "print version information")('a', "abstract", "some abstract param", true)('b', "buffer", "SIZE", "some param with argument")('c', "count", "SIZE", "some param with required argument")('t', "true", HandlerStoreTrue(&flag), "Some arg with handler")("global", SimpleHander, "Another arg with handler");
  658. {
  659. gSimpleFlag = false;
  660. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--abstract"}));
  661. UNIT_ASSERT(!flag);
  662. UNIT_ASSERT(!gSimpleFlag);
  663. }
  664. {
  665. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--abstract", "--global", "-t"}));
  666. UNIT_ASSERT(flag);
  667. UNIT_ASSERT(gSimpleFlag);
  668. }
  669. {
  670. UNIT_ASSERT_EXCEPTION(
  671. TOptsParseResultTestWrapper(&opts, V({"cmd", "--true"})),
  672. TUsageException);
  673. }
  674. {
  675. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--abstract", "--buffer=512"}));
  676. UNIT_ASSERT(r.Has('b'));
  677. UNIT_ASSERT_VALUES_EQUAL(r.Get('b', 0), "512");
  678. }
  679. }
  680. Y_UNIT_TEST(TestTOptsParseResultException) {
  681. // verify that TOptsParseResultException actually throws a TUsageException instead of exit()
  682. // not using wrapper here because it can hide bugs (see review #243810 and r2737774)
  683. TOptsNoDefault opts;
  684. opts.AddLongOption("required-opt").Required();
  685. const char* argv[] = {"cmd"};
  686. // Should throw TUsageException. Other exception types, no exceptions at all and exit(1) are failures
  687. UNIT_ASSERT_EXCEPTION(
  688. TOptsParseResultException(&opts, Y_ARRAY_SIZE(argv), argv),
  689. TUsageException);
  690. }
  691. Y_UNIT_TEST(TestFreeArgsStoreResult) {
  692. TOptsNoDefault opts;
  693. TString data;
  694. int number = 0;
  695. opts.AddFreeArgBinding("data", data);
  696. opts.AddFreeArgBinding("number", number);
  697. TOptsParseResultTestWrapper r(&opts, V({"cmd", "hello", "25"}));
  698. UNIT_ASSERT_VALUES_EQUAL("hello", data);
  699. UNIT_ASSERT_VALUES_EQUAL(25, number);
  700. UNIT_ASSERT_VALUES_EQUAL(2, r.GetFreeArgCount());
  701. }
  702. Y_UNIT_TEST(TestCheckUserTypos) {
  703. {
  704. TOptsNoDefault opts;
  705. opts.SetCheckUserTypos();
  706. opts.AddLongOption("from");
  707. opts.AddLongOption("to");
  708. UNIT_ASSERT_EXCEPTION(
  709. TOptsParseResultTestWrapper(&opts, V({"copy", "-from", "/home", "--to=/etc"})),
  710. TUsageException);
  711. UNIT_ASSERT_NO_EXCEPTION(
  712. TOptsParseResultTestWrapper(&opts, V({"copy", "--from", "from", "--to=/etc"})));
  713. }
  714. {
  715. TOptsNoDefault opts;
  716. opts.SetCheckUserTypos();
  717. opts.AddLongOption('f', "file", "");
  718. opts.AddLongOption('r', "read", "");
  719. opts.AddLongOption("fr");
  720. UNIT_ASSERT_NO_EXCEPTION(
  721. TOptsParseResultTestWrapper(&opts, V({"copy", "-fr"})));
  722. }
  723. }
  724. }