last_getopt_ut.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  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(TestEqParseOnlyRequiredArgument) {
  371. TOptsNoDefault opts;
  372. opts.AddLongOption("eq-only").RequiredArgument().DisableSpaceParse();
  373. TOptsParseResultTestWrapper res(&opts, V({"cmd", "--eq-only=value"}));
  374. UNIT_ASSERT_EQUAL(res.Get("eq-only"), "value"sv);
  375. UNIT_ASSERT_EXCEPTION(
  376. TOptsParseResultTestWrapper(&opts, V({"cmd", "--eq-only", "value"})),
  377. TUsageException);
  378. }
  379. Y_UNIT_TEST(TestStoreResult) {
  380. TOptsNoDefault opts;
  381. TString data;
  382. int number;
  383. TMaybe<TString> optionalString0, optionalString1;
  384. TMaybe<int> optionalNumber0, optionalNumber1;
  385. opts.AddLongOption('d', "data").StoreResult(&data);
  386. opts.AddLongOption('n', "number").StoreResult(&number);
  387. opts.AddLongOption("optional-string-0").StoreResult(&optionalString0);
  388. opts.AddLongOption("optional-number-0").StoreResult(&optionalNumber0);
  389. opts.AddLongOption("optional-string-1").StoreResult(&optionalString1);
  390. opts.AddLongOption("optional-number-1").StoreResult(&optionalNumber1);
  391. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--data=jjhh", "-n", "11", "--optional-number-1=8", "--optional-string-1=os1"}));
  392. UNIT_ASSERT_VALUES_EQUAL("jjhh", data);
  393. UNIT_ASSERT_VALUES_EQUAL(11, number);
  394. UNIT_ASSERT(!optionalString0.Defined());
  395. UNIT_ASSERT(!optionalNumber0.Defined());
  396. UNIT_ASSERT_VALUES_EQUAL(*optionalString1, "os1");
  397. UNIT_ASSERT_VALUES_EQUAL(*optionalNumber1, 8);
  398. }
  399. Y_UNIT_TEST(TestStoreValue) {
  400. int a = 0, b = 0;
  401. size_t c = 0;
  402. EHasArg e = NO_ARGUMENT;
  403. TOptsNoDefault opts;
  404. opts.AddLongOption('a', "alpha").NoArgument().StoreValue(&a, 42);
  405. opts.AddLongOption('b', "beta").NoArgument().StoreValue(&b, 24);
  406. opts.AddLongOption('e', "enum").NoArgument().StoreValue(&e, REQUIRED_ARGUMENT).StoreValue(&c, 12345);
  407. TOptsParseResultTestWrapper r(&opts, V({"cmd", "-a", "-e"}));
  408. UNIT_ASSERT_VALUES_EQUAL(42, a);
  409. UNIT_ASSERT_VALUES_EQUAL(0, b);
  410. UNIT_ASSERT(e == REQUIRED_ARGUMENT);
  411. UNIT_ASSERT_VALUES_EQUAL(12345u, c);
  412. }
  413. Y_UNIT_TEST(TestSetFlag) {
  414. bool a = false, b = true, c = false, d = true;
  415. TOptsNoDefault opts;
  416. opts.AddLongOption('a', "alpha").NoArgument().SetFlag(&a);
  417. opts.AddLongOption('b', "beta").NoArgument().SetFlag(&b);
  418. opts.AddCharOption('c').StoreTrue(&c);
  419. opts.AddCharOption('d').StoreTrue(&d);
  420. TOptsParseResultTestWrapper r(&opts, V({"cmd", "-a", "-c"}));
  421. UNIT_ASSERT(a);
  422. UNIT_ASSERT(!b);
  423. UNIT_ASSERT(c);
  424. UNIT_ASSERT(!d);
  425. }
  426. Y_UNIT_TEST(TestDefaultValue) {
  427. TOptsNoDefault opts;
  428. opts.AddLongOption("path").DefaultValue("/etc");
  429. int value = 42;
  430. opts.AddLongOption("value").StoreResult(&value).DefaultValue(32);
  431. TOptsParseResultTestWrapper r(&opts, V({"cmd", "dfdf"}));
  432. UNIT_ASSERT_VALUES_EQUAL("/etc", r.Get("path"));
  433. UNIT_ASSERT_VALUES_EQUAL(32, value);
  434. }
  435. Y_UNIT_TEST(TestSplitValue) {
  436. TOptsNoDefault opts;
  437. TVector<TString> vals;
  438. opts.AddLongOption('s', "split").SplitHandler(&vals, ',');
  439. TOptsParseResultTestWrapper r(&opts, V({"prog", "--split=a,b,c"}));
  440. UNIT_ASSERT_EQUAL(vals.size(), 3);
  441. UNIT_ASSERT_EQUAL(vals[0], "a");
  442. UNIT_ASSERT_EQUAL(vals[1], "b");
  443. UNIT_ASSERT_EQUAL(vals[2], "c");
  444. }
  445. Y_UNIT_TEST(TestRangeSplitValue) {
  446. TOptsNoDefault opts;
  447. TVector<ui32> vals;
  448. opts.AddLongOption('s', "split").RangeSplitHandler(&vals, ',', '-');
  449. TOptsParseResultTestWrapper r(&opts, V({"prog", "--split=1,8-10", "--split=12-14"}));
  450. UNIT_ASSERT_EQUAL(vals.size(), 7);
  451. UNIT_ASSERT_EQUAL(vals[0], 1);
  452. UNIT_ASSERT_EQUAL(vals[1], 8);
  453. UNIT_ASSERT_EQUAL(vals[2], 9);
  454. UNIT_ASSERT_EQUAL(vals[3], 10);
  455. UNIT_ASSERT_EQUAL(vals[4], 12);
  456. UNIT_ASSERT_EQUAL(vals[5], 13);
  457. UNIT_ASSERT_EQUAL(vals[6], 14);
  458. }
  459. Y_UNIT_TEST(TestParseArgs) {
  460. TOptsNoDefault o("AbCx:y:z::");
  461. UNIT_ASSERT_EQUAL(o.GetCharOption('A').HasArg_, NO_ARGUMENT);
  462. UNIT_ASSERT_EQUAL(o.GetCharOption('b').HasArg_, NO_ARGUMENT);
  463. UNIT_ASSERT_EQUAL(o.GetCharOption('C').HasArg_, NO_ARGUMENT);
  464. UNIT_ASSERT_EQUAL(o.GetCharOption('x').HasArg_, REQUIRED_ARGUMENT);
  465. UNIT_ASSERT_EQUAL(o.GetCharOption('y').HasArg_, REQUIRED_ARGUMENT);
  466. UNIT_ASSERT_EQUAL(o.GetCharOption('z').HasArg_, OPTIONAL_ARGUMENT);
  467. }
  468. Y_UNIT_TEST(TestRequiredOpts) {
  469. TOptsNoDefault opts;
  470. TOpt& opt_d = opts.AddCharOption('d');
  471. // test 'not required'
  472. // makes sure that the problem will only be in 'required'
  473. TOptsParseResultTestWrapper r1(&opts, V({"cmd"}));
  474. // test 'required'
  475. opt_d.Required();
  476. UNIT_ASSERT_EXCEPTION(
  477. TOptsParseResultTestWrapper(&opts, V({"cmd"})),
  478. TUsageException);
  479. TOptsParseResultTestWrapper r3(&opts, V({"cmd", "-d11"}));
  480. UNIT_ASSERT_VALUES_EQUAL("11", r3.Get('d'));
  481. }
  482. class HandlerStoreTrue {
  483. bool* Flag;
  484. public:
  485. HandlerStoreTrue(bool* flag)
  486. : Flag(flag)
  487. {
  488. }
  489. void operator()() {
  490. *Flag = true;
  491. }
  492. };
  493. Y_UNIT_TEST(TestHandlers) {
  494. {
  495. TOptsNoDefault opts;
  496. bool flag = false;
  497. opts.AddLongOption("flag").Handler0(HandlerStoreTrue(&flag)).NoArgument();
  498. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--flag"}));
  499. UNIT_ASSERT(flag);
  500. }
  501. {
  502. TOptsNoDefault opts;
  503. unsigned uval = 5;
  504. double fval = 0.0;
  505. opts.AddLongOption("flag1").RequiredArgument().StoreResult(&uval);
  506. opts.AddLongOption("flag2").RequiredArgument().StoreResultT<int>(&uval);
  507. opts.AddLongOption("flag3").RequiredArgument().StoreMappedResult(&fval, (double (*)(double))fabs);
  508. opts.AddLongOption("flag4").RequiredArgument().StoreMappedResult(&fval, (double (*)(double))sqrt);
  509. UNIT_ASSERT_EXCEPTION(
  510. TOptsParseResultTestWrapper(&opts, V({"cmd", "--flag3", "-2.0", "--flag1", "-1"})),
  511. yexception);
  512. UNIT_ASSERT_VALUES_EQUAL(uval, 5u);
  513. UNIT_ASSERT_VALUES_EQUAL(fval, 2.0);
  514. TOptsParseResultTestWrapper r1(&opts, V({"cmd", "--flag4", "9.0", "--flag2", "-1"}));
  515. UNIT_ASSERT_VALUES_EQUAL(uval, Max<unsigned>());
  516. UNIT_ASSERT_VALUES_EQUAL(fval, 3.0);
  517. }
  518. }
  519. Y_UNIT_TEST(TestTitleAndPrintUsage) {
  520. TOpts opts;
  521. const char* prog = "my_program";
  522. TString title = TString("Sample ") + TString(prog).Quote() + " application";
  523. opts.SetTitle(title);
  524. int argc = 2;
  525. const char* cmd[] = {prog};
  526. TOptsParser parser(&opts, argc, cmd);
  527. TStringStream out;
  528. parser.PrintUsage(out);
  529. // find title
  530. UNIT_ASSERT(out.Str().find(title) != TString::npos);
  531. // find usage
  532. UNIT_ASSERT(out.Str().find(" " + TString(prog) + " ") != TString::npos);
  533. }
  534. Y_UNIT_TEST(TestCustomCmdLineDescr) {
  535. TOpts opts;
  536. const char* prog = "my_program";
  537. TString customDescr = "<FILE|TABLE> USER [OPTIONS]";
  538. int argc = 2;
  539. const char* cmd[] = {prog};
  540. opts.SetCmdLineDescr(customDescr);
  541. TOptsParser parser(&opts, argc, cmd);
  542. TStringStream out;
  543. parser.PrintUsage(out);
  544. // find custom usage
  545. UNIT_ASSERT(out.Str().find(customDescr) != TString::npos);
  546. }
  547. Y_UNIT_TEST(TestColorPrint) {
  548. TOpts opts;
  549. const char* prog = "my_program";
  550. opts.AddLongOption("long_option").Required();
  551. opts.AddLongOption('o', "other");
  552. opts.AddCharOption('d').DefaultValue("42");
  553. opts.AddCharOption('s').DefaultValue("str_default");
  554. opts.SetFreeArgsNum(123, 456);
  555. opts.SetFreeArgTitle(0, "first_free_arg", "help");
  556. opts.SetFreeArgTitle(2, "second_free_arg");
  557. opts.AddSection("Section", "Section\n text");
  558. const char* cmd[] = {prog};
  559. TOptsParser parser(&opts, Y_ARRAY_SIZE(cmd), cmd);
  560. TStringStream out;
  561. NColorizer::TColors colors(true);
  562. parser.PrintUsage(out, colors);
  563. // find options and green color
  564. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "--long_option" << colors.OldColor()) != TString::npos);
  565. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "--other" << colors.OldColor()) != TString::npos);
  566. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "-o" << colors.OldColor()) != TString::npos);
  567. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "-d" << colors.OldColor()) != TString::npos);
  568. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "-s" << colors.OldColor()) != TString::npos);
  569. // find default values
  570. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.CyanColor() << "42" << colors.OldColor()) != TString::npos);
  571. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.CyanColor() << "\"str_default\"" << colors.OldColor()) != TString::npos);
  572. // find free args
  573. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "123" << colors.OldColor()) != TString::npos);
  574. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "456" << colors.OldColor()) != TString::npos);
  575. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "first_free_arg" << colors.OldColor()) != TString::npos);
  576. // free args without help not rendered even if they have custom title
  577. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.GreenColor() << "second_free_arg" << colors.OldColor()) == TString::npos);
  578. // find signatures
  579. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Usage" << colors.OldColor()) != TString::npos);
  580. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Required parameters" << colors.OldColor()) != TString::npos);
  581. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Optional parameters" << colors.OldColor()) != TString::npos);
  582. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Free args" << colors.OldColor()) != TString::npos);
  583. // find sections
  584. UNIT_ASSERT(out.Str().find(TStringBuilder() << colors.BoldColor() << "Section" << colors.OldColor() << ":") != TString::npos);
  585. UNIT_ASSERT(out.Str().find(TStringBuilder() << " Section\n text") != TString::npos);
  586. // print without colors
  587. TStringStream out2;
  588. opts.PrintUsage(prog, out2);
  589. UNIT_ASSERT(out2.Str().find(colors.GreenColor()) == TString::npos);
  590. UNIT_ASSERT(out2.Str().find(colors.CyanColor()) == TString::npos);
  591. UNIT_ASSERT(out2.Str().find(colors.BoldColor()) == TString::npos);
  592. UNIT_ASSERT(out2.Str().find(colors.OldColor()) == TString::npos);
  593. }
  594. Y_UNIT_TEST(TestPadding) {
  595. const bool withColorsOpt[] = {false, true};
  596. for (bool withColors : withColorsOpt) {
  597. TOpts opts;
  598. const char* prog = "my_program";
  599. opts.AddLongOption("option", "description 1").Required(); // long option
  600. opts.AddLongOption('o', "other", "description 2"); // char and long option
  601. opts.AddCharOption('d', "description 3").RequiredArgument("DD"); // char option
  602. opts.AddCharOption('s', "description 4\ndescription 5\ndescription 6"); // multiline desc
  603. opts.AddLongOption('l', "very_very_very_loooong_ooooption", "description 7").RequiredArgument("LONG_ARGUMENT");
  604. const char* cmd[] = {prog};
  605. TOptsParser parser(&opts, Y_ARRAY_SIZE(cmd), cmd);
  606. TStringStream out;
  607. NColorizer::TColors colors(withColors);
  608. parser.PrintUsage(out, colors);
  609. TString printed = out.Str();
  610. if (withColors) {
  611. // remove not printable characters
  612. SubstGlobal(printed, TString(colors.BoldColor()), "");
  613. SubstGlobal(printed, TString(colors.GreenColor()), "");
  614. SubstGlobal(printed, TString(colors.CyanColor()), "");
  615. SubstGlobal(printed, TString(colors.OldColor()), "");
  616. }
  617. TVector<TString> lines;
  618. StringSplitter(printed).Split('\n').SkipEmpty().Collect(&lines);
  619. UNIT_ASSERT(!lines.empty());
  620. TVector<size_t> indents;
  621. for (const TString& line : lines) {
  622. const size_t indent = line.find("description ");
  623. if (indent != TString::npos)
  624. indents.push_back(indent);
  625. }
  626. UNIT_ASSERT_VALUES_EQUAL(indents.size(), 7);
  627. const size_t theOnlyIndent = indents[0];
  628. for (size_t indent : indents) {
  629. UNIT_ASSERT_VALUES_EQUAL_C(indent, theOnlyIndent, printed);
  630. }
  631. }
  632. }
  633. Y_UNIT_TEST(TestAppendTo) {
  634. TVector<int> ints;
  635. std::vector<std::string> strings;
  636. TOptsNoDefault opts;
  637. opts.AddLongOption("size").AppendTo(&ints);
  638. opts.AddLongOption("value").AppendTo(&strings);
  639. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--size=17", "--size=19", "--value=v1", "--value=v2"}));
  640. UNIT_ASSERT_VALUES_EQUAL(size_t(2), ints.size());
  641. UNIT_ASSERT_VALUES_EQUAL(17, ints.at(0));
  642. UNIT_ASSERT_VALUES_EQUAL(19, ints.at(1));
  643. UNIT_ASSERT_VALUES_EQUAL(size_t(2), strings.size());
  644. UNIT_ASSERT_VALUES_EQUAL("v1", strings.at(0));
  645. UNIT_ASSERT_VALUES_EQUAL("v2", strings.at(1));
  646. }
  647. Y_UNIT_TEST(TestEmplaceTo) {
  648. TVector<std::tuple<TString>> richPaths;
  649. TOptsNoDefault opts;
  650. opts.AddLongOption("path").EmplaceTo(&richPaths);
  651. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--path=<a=b>//cool", "--path=//nice"}));
  652. UNIT_ASSERT_VALUES_EQUAL(size_t(2), richPaths.size());
  653. UNIT_ASSERT_VALUES_EQUAL("<a=b>//cool", std::get<0>(richPaths.at(0)));
  654. UNIT_ASSERT_VALUES_EQUAL("//nice", std::get<0>(richPaths.at(1)));
  655. }
  656. Y_UNIT_TEST(TestKVHandler) {
  657. TStringBuilder keyvals;
  658. TOptsNoDefault opts;
  659. opts.AddLongOption("set").KVHandler([&keyvals](TString k, TString v) { keyvals << k << ":" << v << ","; });
  660. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--set", "x=1", "--set", "y=2", "--set=z=3"}));
  661. UNIT_ASSERT_VALUES_EQUAL(keyvals, "x:1,y:2,z:3,");
  662. }
  663. Y_UNIT_TEST(TestEasySetup) {
  664. TEasySetup opts;
  665. bool flag = false;
  666. 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");
  667. {
  668. gSimpleFlag = false;
  669. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--abstract"}));
  670. UNIT_ASSERT(!flag);
  671. UNIT_ASSERT(!gSimpleFlag);
  672. }
  673. {
  674. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--abstract", "--global", "-t"}));
  675. UNIT_ASSERT(flag);
  676. UNIT_ASSERT(gSimpleFlag);
  677. }
  678. {
  679. UNIT_ASSERT_EXCEPTION(
  680. TOptsParseResultTestWrapper(&opts, V({"cmd", "--true"})),
  681. TUsageException);
  682. }
  683. {
  684. TOptsParseResultTestWrapper r(&opts, V({"cmd", "--abstract", "--buffer=512"}));
  685. UNIT_ASSERT(r.Has('b'));
  686. UNIT_ASSERT_VALUES_EQUAL(r.Get('b', 0), "512");
  687. }
  688. }
  689. Y_UNIT_TEST(TestTOptsParseResultException) {
  690. // verify that TOptsParseResultException actually throws a TUsageException instead of exit()
  691. // not using wrapper here because it can hide bugs (see review #243810 and r2737774)
  692. TOptsNoDefault opts;
  693. opts.AddLongOption("required-opt").Required();
  694. const char* argv[] = {"cmd"};
  695. // Should throw TUsageException. Other exception types, no exceptions at all and exit(1) are failures
  696. UNIT_ASSERT_EXCEPTION(
  697. TOptsParseResultException(&opts, Y_ARRAY_SIZE(argv), argv),
  698. TUsageException);
  699. }
  700. Y_UNIT_TEST(TestFreeArgsStoreResult) {
  701. TOptsNoDefault opts;
  702. TString data;
  703. int number = 0;
  704. opts.AddFreeArgBinding("data", data);
  705. opts.AddFreeArgBinding("number", number);
  706. TOptsParseResultTestWrapper r(&opts, V({"cmd", "hello", "25"}));
  707. UNIT_ASSERT_VALUES_EQUAL("hello", data);
  708. UNIT_ASSERT_VALUES_EQUAL(25, number);
  709. UNIT_ASSERT_VALUES_EQUAL(2, r.GetFreeArgCount());
  710. }
  711. Y_UNIT_TEST(TestCheckUserTypos) {
  712. {
  713. TOptsNoDefault opts;
  714. opts.SetCheckUserTypos();
  715. opts.AddLongOption("from");
  716. opts.AddLongOption("to");
  717. UNIT_ASSERT_EXCEPTION(
  718. TOptsParseResultTestWrapper(&opts, V({"copy", "-from", "/home", "--to=/etc"})),
  719. TUsageException);
  720. UNIT_ASSERT_NO_EXCEPTION(
  721. TOptsParseResultTestWrapper(&opts, V({"copy", "--from", "from", "--to=/etc"})));
  722. }
  723. {
  724. TOptsNoDefault opts;
  725. opts.SetCheckUserTypos();
  726. opts.AddLongOption('f', "file", "");
  727. opts.AddLongOption('r', "read", "");
  728. opts.AddLongOption("fr");
  729. UNIT_ASSERT_NO_EXCEPTION(
  730. TOptsParseResultTestWrapper(&opts, V({"copy", "-fr"})));
  731. }
  732. }
  733. }