last_getopt_ut.cpp 29 KB

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