parser.hpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. // (C) Copyright Gennadiy Rozental 2001.
  2. // Use, modification, and distribution are subject to the
  3. // Boost Software License, Version 1.0. (See accompanying file
  4. // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. // See http://www.boost.org/libs/test for the library home page.
  6. //
  7. //!@file
  8. //!@brief CLA parser
  9. // ***************************************************************************
  10. #ifndef BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
  11. #define BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
  12. // Boost.Test Runtime parameters
  13. #include <boost/test/utils/runtime/argument.hpp>
  14. #include <boost/test/utils/runtime/modifier.hpp>
  15. #include <boost/test/utils/runtime/parameter.hpp>
  16. #include <boost/test/utils/runtime/cla/argv_traverser.hpp>
  17. // Boost.Test
  18. #include <boost/test/utils/foreach.hpp>
  19. #include <boost/test/utils/algorithm.hpp>
  20. #include <boost/test/detail/throw_exception.hpp>
  21. #include <boost/test/detail/global_typedef.hpp>
  22. #include <boost/algorithm/cxx11/all_of.hpp> // !! ?? unnecessary after cxx11
  23. // STL
  24. // !! ?? #include <unordered_set>
  25. #include <set>
  26. #include <iostream>
  27. #include <boost/test/detail/suppress_warnings.hpp>
  28. namespace boost {
  29. namespace runtime {
  30. namespace cla {
  31. // ************************************************************************** //
  32. // ************** runtime::cla::parameter_trie ************** //
  33. // ************************************************************************** //
  34. namespace rt_cla_detail {
  35. struct parameter_trie;
  36. typedef shared_ptr<parameter_trie> parameter_trie_ptr;
  37. typedef std::map<char,parameter_trie_ptr> trie_per_char;
  38. typedef std::vector<boost::reference_wrapper<parameter_cla_id const> > param_cla_id_list;
  39. struct parameter_trie {
  40. parameter_trie() : m_has_final_candidate( false ) {}
  41. /// If subtrie corresponding to the char c exists returns it otherwise creates new
  42. parameter_trie_ptr make_subtrie( char c )
  43. {
  44. trie_per_char::const_iterator it = m_subtrie.find( c );
  45. if( it == m_subtrie.end() )
  46. it = m_subtrie.insert( std::make_pair( c, parameter_trie_ptr( new parameter_trie ) ) ).first;
  47. return it->second;
  48. }
  49. /// Creates series of sub-tries per characters in a string
  50. parameter_trie_ptr make_subtrie( cstring s )
  51. {
  52. parameter_trie_ptr res;
  53. BOOST_TEST_FOREACH( char, c, s )
  54. res = (res ? res->make_subtrie( c ) : make_subtrie( c ));
  55. return res;
  56. }
  57. /// Registers candidate parameter for this subtrie. If final, it needs to be unique
  58. void add_candidate_id( parameter_cla_id const& param_id, basic_param_ptr param_candidate, bool final )
  59. {
  60. BOOST_TEST_I_ASSRT( !m_has_final_candidate && (!final || m_id_candidates.empty()),
  61. conflicting_param() << "Parameter cla id " << param_id.m_tag << " conflicts with the "
  62. << "parameter cla id " << m_id_candidates.back().get().m_tag );
  63. m_has_final_candidate = final;
  64. m_id_candidates.push_back( ref(param_id) );
  65. if( m_id_candidates.size() == 1 )
  66. m_param_candidate = param_candidate;
  67. else
  68. m_param_candidate.reset();
  69. }
  70. /// Gets subtrie for specified char if present or nullptr otherwise
  71. parameter_trie_ptr get_subtrie( char c ) const
  72. {
  73. trie_per_char::const_iterator it = m_subtrie.find( c );
  74. return it != m_subtrie.end() ? it->second : parameter_trie_ptr();
  75. }
  76. // Data members
  77. trie_per_char m_subtrie;
  78. param_cla_id_list m_id_candidates;
  79. basic_param_ptr m_param_candidate;
  80. bool m_has_final_candidate;
  81. };
  82. // ************************************************************************** //
  83. // ************** runtime::cla::report_foreing_token ************** //
  84. // ************************************************************************** //
  85. static void
  86. report_foreing_token( cstring program_name, cstring token )
  87. {
  88. std::cerr << "Boost.Test WARNING: token \"" << token << "\" does not correspond to the Boost.Test argument \n"
  89. << " and should be placed after all Boost.Test arguments and the -- separator.\n"
  90. << " For example: " << program_name << " --random -- " << token << "\n";
  91. }
  92. } // namespace rt_cla_detail
  93. // ************************************************************************** //
  94. // ************** runtime::cla::parser ************** //
  95. // ************************************************************************** //
  96. class parser {
  97. public:
  98. /// Initializes a parser and builds internal trie representation used for
  99. /// parsing based on the supplied parameters
  100. #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS
  101. template<typename Modifiers=nfp::no_params_type>
  102. parser( parameters_store const& parameters, Modifiers const& m = nfp::no_params )
  103. #else
  104. template<typename Modifiers>
  105. parser( parameters_store const& parameters, Modifiers const& m )
  106. #endif
  107. {
  108. nfp::opt_assign( m_end_of_param_indicator, m, end_of_params );
  109. nfp::opt_assign( m_negation_prefix, m, negation_prefix );
  110. BOOST_TEST_I_ASSRT( algorithm::all_of( m_end_of_param_indicator.begin(),
  111. m_end_of_param_indicator.end(),
  112. parameter_cla_id::valid_prefix_char ),
  113. invalid_cla_id() << "End of parameters indicator can only consist of prefix characters." );
  114. BOOST_TEST_I_ASSRT( algorithm::all_of( m_negation_prefix.begin(),
  115. m_negation_prefix.end(),
  116. parameter_cla_id::valid_name_char ),
  117. invalid_cla_id() << "Negation prefix can only consist of prefix characters." );
  118. build_trie( parameters );
  119. }
  120. // input processing method
  121. int
  122. parse( int argc, char** argv, runtime::arguments_store& res )
  123. {
  124. // save program name for help message
  125. m_program_name = argv[0];
  126. cstring path_sep( "\\/" );
  127. cstring::iterator it = unit_test::utils::find_last_of( m_program_name.begin(), m_program_name.end(),
  128. path_sep.begin(), path_sep.end() );
  129. if( it != m_program_name.end() )
  130. m_program_name.trim_left( it + 1 );
  131. // Set up the traverser
  132. argv_traverser tr( argc, (char const**)argv );
  133. // Loop till we reach end of input
  134. while( !tr.eoi() ) {
  135. cstring curr_token = tr.current_token();
  136. cstring prefix;
  137. cstring name;
  138. cstring value_separator;
  139. bool negative_form = false;
  140. // Perform format validations and split the argument into prefix, name and separator
  141. // False return value indicates end of params indicator is met
  142. if( !validate_token_format( curr_token, prefix, name, value_separator, negative_form ) ) {
  143. // get rid of "end of params" token
  144. tr.next_token();
  145. break;
  146. }
  147. // Locate trie corresponding to found prefix and skip it in the input
  148. trie_ptr curr_trie = m_param_trie[prefix];
  149. if( !curr_trie ) {
  150. // format_error() << "Unrecognized parameter prefix in the argument " << tr.current_token()
  151. rt_cla_detail::report_foreing_token( m_program_name, curr_token );
  152. tr.save_token();
  153. continue;
  154. }
  155. curr_token.trim_left( prefix.size() );
  156. // Locate parameter based on a name and skip it in the input
  157. locate_result locate_res = locate_parameter( curr_trie, name, curr_token );
  158. parameter_cla_id const& found_id = locate_res.first;
  159. basic_param_ptr found_param = locate_res.second;
  160. if( negative_form ) {
  161. BOOST_TEST_I_ASSRT( found_id.m_negatable,
  162. format_error( found_param->p_name )
  163. << "Parameter tag " << found_id.m_tag << " is not negatable." );
  164. curr_token.trim_left( m_negation_prefix.size() );
  165. }
  166. curr_token.trim_left( name.size() );
  167. bool should_go_to_next = true;
  168. cstring value;
  169. // Skip validations if parameter has optional value and we are at the end of token
  170. if( !value_separator.is_empty() || !found_param->p_has_optional_value ) {
  171. // we are given a separator or there is no optional value
  172. // Validate and skip value separator in the input
  173. BOOST_TEST_I_ASSRT( found_id.m_value_separator == value_separator,
  174. format_error( found_param->p_name )
  175. << "Invalid separator for the parameter "
  176. << found_param->p_name
  177. << " in the argument " << tr.current_token() );
  178. curr_token.trim_left( value_separator.size() );
  179. // Deduce value source
  180. value = curr_token;
  181. if( value.is_empty() ) {
  182. tr.next_token();
  183. value = tr.current_token();
  184. }
  185. BOOST_TEST_I_ASSRT( !value.is_empty(),
  186. format_error( found_param->p_name )
  187. << "Missing an argument value for the parameter "
  188. << found_param->p_name
  189. << " in the argument " << tr.current_token() );
  190. }
  191. else if( (value_separator.is_empty() && found_id.m_value_separator.empty()) ) {
  192. // Deduce value source
  193. value = curr_token;
  194. if( value.is_empty() ) {
  195. tr.next_token(); // tokenization broke the value, we check the next one
  196. if(!found_param->p_has_optional_value) {
  197. // there is no separator and there is no optional value
  198. // we look for the value on the next token
  199. // example "-t XXXX" (no default)
  200. // and we commit this value as being the passed value
  201. value = tr.current_token();
  202. }
  203. else {
  204. // there is no separator and the value is optional
  205. // we check the next token
  206. // example "-c" (defaults to true)
  207. // and commit this as the value if this is not a token
  208. cstring value_check = tr.current_token();
  209. cstring prefix_test, name_test, value_separator_test;
  210. bool negative_form_test;
  211. if( validate_token_format( value_check, prefix_test, name_test, value_separator_test, negative_form_test )
  212. && m_param_trie[prefix_test]) {
  213. // this is a token, we consume what we have
  214. should_go_to_next = false;
  215. }
  216. else {
  217. // this is a value, we commit it
  218. value = value_check;
  219. }
  220. }
  221. }
  222. }
  223. // Validate against argument duplication
  224. BOOST_TEST_I_ASSRT( !res.has( found_param->p_name ) || found_param->p_repeatable,
  225. duplicate_arg( found_param->p_name )
  226. << "Duplicate argument value for the parameter "
  227. << found_param->p_name
  228. << " in the argument " << tr.current_token() );
  229. // Produce argument value
  230. found_param->produce_argument( value, negative_form, res );
  231. if(should_go_to_next) {
  232. tr.next_token();
  233. }
  234. }
  235. // generate the remainder and return it's size
  236. return tr.remainder();
  237. }
  238. // help/usage/version
  239. void
  240. version( std::ostream& ostr )
  241. {
  242. ostr << "Boost.Test module ";
  243. #if defined(BOOST_TEST_MODULE)
  244. // we do not want to refer to the master test suite there
  245. ostr << '\'' << BOOST_TEST_STRINGIZE( BOOST_TEST_MODULE ).trim( "\"" ) << "' ";
  246. #endif
  247. ostr << "in executable '" << m_program_name << "'\n";
  248. ostr << "Compiled from Boost version "
  249. << BOOST_VERSION/100000 << "."
  250. << BOOST_VERSION/100 % 1000 << "."
  251. << BOOST_VERSION % 100 ;
  252. ostr << " with ";
  253. #if defined(BOOST_TEST_INCLUDED)
  254. ostr << "header-only inclusion of";
  255. #elif defined(BOOST_TEST_DYN_LINK)
  256. ostr << "dynamic linking to";
  257. #else
  258. ostr << "static linking to";
  259. #endif
  260. ostr << " Boost.Test\n";
  261. ostr << "- Compiler: " << BOOST_COMPILER << '\n'
  262. << "- Platform: " << BOOST_PLATFORM << '\n'
  263. << "- STL : " << BOOST_STDLIB;
  264. ostr << std::endl;
  265. }
  266. void
  267. usage(std::ostream& ostr,
  268. cstring param_name = cstring(),
  269. bool use_color = true)
  270. {
  271. namespace utils = unit_test::utils;
  272. namespace ut_detail = unit_test::ut_detail;
  273. if( !param_name.is_empty() ) {
  274. basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second;
  275. param->usage( ostr, m_negation_prefix );
  276. }
  277. else {
  278. ostr << "\n The program '" << m_program_name << "' is a Boost.Test module containing unit tests.";
  279. {
  280. BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
  281. ostr << "\n\n Usage\n ";
  282. }
  283. {
  284. BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
  285. ostr << m_program_name << " [Boost.Test argument]... ";
  286. }
  287. if( !m_end_of_param_indicator.empty() ) {
  288. BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW );
  289. ostr << '[' << m_end_of_param_indicator << " [custom test module argument]...]";
  290. }
  291. }
  292. ostr << "\n\n Use\n ";
  293. {
  294. BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
  295. ostr << m_program_name << " --help";
  296. }
  297. ostr << "\n or ";
  298. {
  299. BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
  300. ostr << m_program_name << " --help=<parameter name>";
  301. }
  302. ostr << "\n for detailed help on Boost.Test parameters.\n";
  303. }
  304. void
  305. help(std::ostream& ostr,
  306. parameters_store const& parameters,
  307. cstring param_name,
  308. bool use_color = true)
  309. {
  310. namespace utils = unit_test::utils;
  311. namespace ut_detail = unit_test::ut_detail;
  312. if( !param_name.is_empty() ) {
  313. basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second;
  314. param->help( ostr, m_negation_prefix, use_color);
  315. return;
  316. }
  317. usage(ostr, cstring(), use_color);
  318. ostr << "\n\n";
  319. {
  320. BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
  321. ostr << " Command line flags:\n";
  322. }
  323. runtime::commandline_pretty_print(
  324. ostr,
  325. " ",
  326. "The command line flags of Boost.Test are listed below. "
  327. "All parameters are optional. You can specify parameter value either "
  328. "as a command line argument or as a value of its corresponding environment "
  329. "variable. If a flag is specified as a command line argument and an environment variable "
  330. "at the same time, the command line takes precedence. "
  331. "The command line argument "
  332. "support name guessing, and works with shorter names as long as those are not ambiguous."
  333. );
  334. if( !m_end_of_param_indicator.empty() ) {
  335. ostr << "\n\n All the arguments after the '";
  336. {
  337. BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW );
  338. ostr << m_end_of_param_indicator;
  339. }
  340. ostr << "' are ignored by Boost.Test.";
  341. }
  342. {
  343. BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
  344. ostr << "\n\n Environment variables:\n";
  345. }
  346. runtime::commandline_pretty_print(
  347. ostr,
  348. " ",
  349. "Every argument listed below may also be set by a corresponding environment"
  350. "variable. For an argument '--argument_x=<value>', the corresponding "
  351. "environment variable is 'BOOST_TEST_ARGUMENT_X=value"
  352. );
  353. ostr << "\n\n The following parameters are supported:\n";
  354. BOOST_TEST_FOREACH(
  355. parameters_store::storage_type::value_type const&,
  356. v,
  357. parameters.all() )
  358. {
  359. basic_param_ptr param = v.second;
  360. ostr << "\n";
  361. param->usage( ostr, m_negation_prefix, use_color);
  362. }
  363. }
  364. private:
  365. typedef rt_cla_detail::parameter_trie_ptr trie_ptr;
  366. typedef rt_cla_detail::trie_per_char trie_per_char;
  367. typedef std::map<cstring,trie_ptr> str_to_trie;
  368. void
  369. build_trie( parameters_store const& parameters )
  370. {
  371. // Iterate over all parameters
  372. BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) {
  373. basic_param_ptr param = v.second;
  374. // Register all parameter's ids in trie.
  375. BOOST_TEST_FOREACH( parameter_cla_id const&, id, param->cla_ids() ) {
  376. // This is the trie corresponding to the prefix.
  377. trie_ptr next_trie = m_param_trie[id.m_prefix];
  378. if( !next_trie )
  379. next_trie = m_param_trie[id.m_prefix] = trie_ptr( new rt_cla_detail::parameter_trie );
  380. // Build the trie, by following name's characters
  381. // and register this parameter as candidate on each level
  382. for( size_t index = 0; index < id.m_tag.size(); ++index ) {
  383. next_trie = next_trie->make_subtrie( id.m_tag[index] );
  384. next_trie->add_candidate_id( id, param, index == (id.m_tag.size() - 1) );
  385. }
  386. }
  387. }
  388. }
  389. bool
  390. validate_token_format( cstring token, cstring& prefix, cstring& name, cstring& separator, bool& negative_form )
  391. {
  392. // Match prefix
  393. cstring::iterator it = token.begin();
  394. while( it != token.end() && parameter_cla_id::valid_prefix_char( *it ) )
  395. ++it;
  396. prefix.assign( token.begin(), it );
  397. if( prefix.empty() )
  398. return true;
  399. // Match name
  400. while( it != token.end() && parameter_cla_id::valid_name_char( *it ) )
  401. ++it;
  402. name.assign( prefix.end(), it );
  403. if( name.empty() ) {
  404. if( prefix == m_end_of_param_indicator )
  405. return false;
  406. BOOST_TEST_I_THROW( format_error() << "Invalid format for an actual argument " << token );
  407. }
  408. // Match value separator
  409. while( it != token.end() && parameter_cla_id::valid_separator_char( *it ) )
  410. ++it;
  411. separator.assign( name.end(), it );
  412. // Match negation prefix
  413. negative_form = !m_negation_prefix.empty() && ( name.substr( 0, m_negation_prefix.size() ) == m_negation_prefix );
  414. if( negative_form )
  415. name.trim_left( m_negation_prefix.size() );
  416. return true;
  417. }
  418. // C++03: cannot have references as types
  419. typedef std::pair<parameter_cla_id, basic_param_ptr> locate_result;
  420. locate_result
  421. locate_parameter( trie_ptr curr_trie, cstring name, cstring token )
  422. {
  423. std::vector<trie_ptr> typo_candidates;
  424. std::vector<trie_ptr> next_typo_candidates;
  425. trie_ptr next_trie;
  426. BOOST_TEST_FOREACH( char, c, name ) {
  427. if( curr_trie ) {
  428. // locate next subtrie corresponding to the char
  429. next_trie = curr_trie->get_subtrie( c );
  430. if( next_trie )
  431. curr_trie = next_trie;
  432. else {
  433. // Initiate search for typo candicates. We will account for 'wrong char' typo
  434. // 'missing char' typo and 'extra char' typo
  435. BOOST_TEST_FOREACH( trie_per_char::value_type const&, typo_cand, curr_trie->m_subtrie ) {
  436. // 'wrong char' typo
  437. typo_candidates.push_back( typo_cand.second );
  438. // 'missing char' typo
  439. if( (next_trie = typo_cand.second->get_subtrie( c )) )
  440. typo_candidates.push_back( next_trie );
  441. }
  442. // 'extra char' typo
  443. typo_candidates.push_back( curr_trie );
  444. curr_trie.reset();
  445. }
  446. }
  447. else {
  448. // go over existing typo candidates and see if they are still viable
  449. BOOST_TEST_FOREACH( trie_ptr, typo_cand, typo_candidates ) {
  450. trie_ptr next_typo_cand = typo_cand->get_subtrie( c );
  451. if( next_typo_cand )
  452. next_typo_candidates.push_back( next_typo_cand );
  453. }
  454. next_typo_candidates.swap( typo_candidates );
  455. next_typo_candidates.clear();
  456. }
  457. }
  458. if( !curr_trie ) {
  459. std::vector<cstring> typo_candidate_names;
  460. std::set<parameter_cla_id const*> unique_typo_candidate; // !! ?? unordered_set
  461. typo_candidate_names.reserve( typo_candidates.size() );
  462. // !! ?? unique_typo_candidate.reserve( typo_candidates.size() );
  463. BOOST_TEST_FOREACH( trie_ptr, trie_cand, typo_candidates ) {
  464. // avoid ambiguos candidate trie
  465. if( trie_cand->m_id_candidates.size() > 1 )
  466. continue;
  467. BOOST_TEST_FOREACH( parameter_cla_id const&, param_cand, trie_cand->m_id_candidates ) {
  468. if( !unique_typo_candidate.insert( &param_cand ).second )
  469. continue;
  470. typo_candidate_names.push_back( param_cand.m_tag );
  471. }
  472. }
  473. #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
  474. BOOST_TEST_I_THROW( unrecognized_param( std::move(typo_candidate_names) )
  475. << "An unrecognized parameter in the argument "
  476. << token );
  477. #else
  478. BOOST_TEST_I_THROW( unrecognized_param( typo_candidate_names )
  479. << "An unrecognized parameter in the argument "
  480. << token );
  481. #endif
  482. }
  483. if( curr_trie->m_id_candidates.size() > 1 ) {
  484. std::vector<cstring> amb_names;
  485. BOOST_TEST_FOREACH( parameter_cla_id const&, param_id, curr_trie->m_id_candidates )
  486. amb_names.push_back( param_id.m_tag );
  487. #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
  488. BOOST_TEST_I_THROW( ambiguous_param( std::move( amb_names ) )
  489. << "An ambiguous parameter name in the argument " << token );
  490. #else
  491. BOOST_TEST_I_THROW( ambiguous_param( amb_names )
  492. << "An ambiguous parameter name in the argument " << token );
  493. #endif
  494. }
  495. return locate_result( curr_trie->m_id_candidates.back().get(), curr_trie->m_param_candidate );
  496. }
  497. // Data members
  498. cstring m_program_name;
  499. std::string m_end_of_param_indicator;
  500. std::string m_negation_prefix;
  501. str_to_trie m_param_trie;
  502. };
  503. } // namespace cla
  504. } // namespace runtime
  505. } // namespace boost
  506. #include <boost/test/detail/enable_warnings.hpp>
  507. #endif // BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP