File indexing completed on 2025-01-18 09:52:42
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #ifndef BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
0013 #define BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
0014
0015
0016 #include <boost/test/utils/runtime/argument.hpp>
0017 #include <boost/test/utils/runtime/modifier.hpp>
0018 #include <boost/test/utils/runtime/parameter.hpp>
0019
0020 #include <boost/test/utils/runtime/cla/argv_traverser.hpp>
0021
0022
0023 #include <boost/test/utils/foreach.hpp>
0024 #include <boost/test/utils/algorithm.hpp>
0025 #include <boost/test/detail/throw_exception.hpp>
0026 #include <boost/test/detail/global_typedef.hpp>
0027
0028 #include <boost/algorithm/cxx11/all_of.hpp> // !! ?? unnecessary after cxx11
0029
0030
0031
0032 #include <set>
0033 #include <iostream>
0034
0035 #include <boost/test/detail/suppress_warnings.hpp>
0036
0037 namespace boost {
0038 namespace runtime {
0039 namespace cla {
0040
0041
0042
0043
0044
0045 namespace rt_cla_detail {
0046
0047 struct parameter_trie;
0048 typedef shared_ptr<parameter_trie> parameter_trie_ptr;
0049 typedef std::map<char,parameter_trie_ptr> trie_per_char;
0050 typedef std::vector<boost::reference_wrapper<parameter_cla_id const> > param_cla_id_list;
0051
0052 struct parameter_trie {
0053 parameter_trie() : m_has_final_candidate( false ) {}
0054
0055
0056 parameter_trie_ptr make_subtrie( char c )
0057 {
0058 trie_per_char::const_iterator it = m_subtrie.find( c );
0059
0060 if( it == m_subtrie.end() )
0061 it = m_subtrie.insert( std::make_pair( c, parameter_trie_ptr( new parameter_trie ) ) ).first;
0062
0063 return it->second;
0064 }
0065
0066
0067 parameter_trie_ptr make_subtrie( cstring s )
0068 {
0069 parameter_trie_ptr res;
0070
0071 BOOST_TEST_FOREACH( char, c, s )
0072 res = (res ? res->make_subtrie( c ) : make_subtrie( c ));
0073
0074 return res;
0075 }
0076
0077
0078 void add_candidate_id( parameter_cla_id const& param_id, basic_param_ptr param_candidate, bool final )
0079 {
0080 BOOST_TEST_I_ASSRT( !m_has_final_candidate && (!final || m_id_candidates.empty()),
0081 conflicting_param() << "Parameter cla id " << param_id.m_tag << " conflicts with the "
0082 << "parameter cla id " << m_id_candidates.back().get().m_tag );
0083
0084 m_has_final_candidate = final;
0085 m_id_candidates.push_back( ref(param_id) );
0086
0087 if( m_id_candidates.size() == 1 )
0088 m_param_candidate = param_candidate;
0089 else
0090 m_param_candidate.reset();
0091 }
0092
0093
0094 parameter_trie_ptr get_subtrie( char c ) const
0095 {
0096 trie_per_char::const_iterator it = m_subtrie.find( c );
0097
0098 return it != m_subtrie.end() ? it->second : parameter_trie_ptr();
0099 }
0100
0101
0102 trie_per_char m_subtrie;
0103 param_cla_id_list m_id_candidates;
0104 basic_param_ptr m_param_candidate;
0105 bool m_has_final_candidate;
0106 };
0107
0108
0109
0110
0111
0112 static void
0113 report_foreing_token( cstring program_name, cstring token )
0114 {
0115 std::cerr << "Boost.Test WARNING: token \"" << token << "\" does not correspond to the Boost.Test argument \n"
0116 << " and should be placed after all Boost.Test arguments and the -- separator.\n"
0117 << " For example: " << program_name << " --random -- " << token << "\n";
0118 }
0119
0120 }
0121
0122
0123
0124
0125
0126 class parser {
0127 public:
0128
0129
0130 #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS
0131 template<typename Modifiers=nfp::no_params_type>
0132 parser( parameters_store const& parameters, Modifiers const& m = nfp::no_params )
0133 #else
0134 template<typename Modifiers>
0135 parser( parameters_store const& parameters, Modifiers const& m )
0136 #endif
0137 {
0138 nfp::opt_assign( m_end_of_param_indicator, m, end_of_params );
0139 nfp::opt_assign( m_negation_prefix, m, negation_prefix );
0140
0141 BOOST_TEST_I_ASSRT( algorithm::all_of( m_end_of_param_indicator.begin(),
0142 m_end_of_param_indicator.end(),
0143 parameter_cla_id::valid_prefix_char ),
0144 invalid_cla_id() << "End of parameters indicator can only consist of prefix characters." );
0145
0146 BOOST_TEST_I_ASSRT( algorithm::all_of( m_negation_prefix.begin(),
0147 m_negation_prefix.end(),
0148 parameter_cla_id::valid_name_char ),
0149 invalid_cla_id() << "Negation prefix can only consist of prefix characters." );
0150
0151 build_trie( parameters );
0152 }
0153
0154
0155 int
0156 parse( int argc, char** argv, runtime::arguments_store& res )
0157 {
0158
0159 m_program_name = argv[0];
0160 cstring path_sep( "\\/" );
0161
0162 cstring::iterator it = unit_test::utils::find_last_of( m_program_name.begin(), m_program_name.end(),
0163 path_sep.begin(), path_sep.end() );
0164 if( it != m_program_name.end() )
0165 m_program_name.trim_left( it + 1 );
0166
0167
0168 argv_traverser tr( argc, (char const**)argv );
0169
0170
0171 while( !tr.eoi() ) {
0172 cstring curr_token = tr.current_token();
0173
0174 cstring prefix;
0175 cstring name;
0176 cstring value_separator;
0177 bool negative_form = false;
0178
0179
0180
0181 if( !validate_token_format( curr_token, prefix, name, value_separator, negative_form ) ) {
0182
0183 tr.next_token();
0184 break;
0185 }
0186
0187
0188 trie_ptr curr_trie = m_param_trie[prefix];
0189
0190 if( !curr_trie ) {
0191
0192 rt_cla_detail::report_foreing_token( m_program_name, curr_token );
0193 tr.save_token();
0194 continue;
0195 }
0196
0197 curr_token.trim_left( prefix.size() );
0198
0199
0200 locate_result locate_res = locate_parameter( curr_trie, name, curr_token );
0201 parameter_cla_id const& found_id = locate_res.first;
0202 basic_param_ptr found_param = locate_res.second;
0203
0204 if( negative_form ) {
0205 BOOST_TEST_I_ASSRT( found_id.m_negatable,
0206 format_error( found_param->p_name )
0207 << "Parameter tag " << found_id.m_tag << " is not negatable." );
0208
0209 curr_token.trim_left( m_negation_prefix.size() );
0210 }
0211
0212 curr_token.trim_left( name.size() );
0213
0214 bool should_go_to_next = true;
0215 cstring value;
0216
0217
0218
0219 if( !value_separator.is_empty() || !found_param->p_has_optional_value ) {
0220
0221
0222
0223
0224 BOOST_TEST_I_ASSRT( found_id.m_value_separator == value_separator,
0225 format_error( found_param->p_name )
0226 << "Invalid separator for the parameter "
0227 << found_param->p_name
0228 << " in the argument " << tr.current_token() );
0229
0230 curr_token.trim_left( value_separator.size() );
0231
0232
0233 value = curr_token;
0234 if( value.is_empty() ) {
0235 tr.next_token();
0236 value = tr.current_token();
0237 }
0238
0239 BOOST_TEST_I_ASSRT( !value.is_empty(),
0240 format_error( found_param->p_name )
0241 << "Missing an argument value for the parameter "
0242 << found_param->p_name
0243 << " in the argument " << tr.current_token() );
0244 }
0245 else if( (value_separator.is_empty() && found_id.m_value_separator.empty()) ) {
0246
0247 value = curr_token;
0248 if( value.is_empty() ) {
0249 tr.next_token();
0250
0251 if(!found_param->p_has_optional_value) {
0252
0253
0254
0255
0256 value = tr.current_token();
0257 }
0258 else {
0259
0260
0261
0262
0263 cstring value_check = tr.current_token();
0264
0265 cstring prefix_test, name_test, value_separator_test;
0266 bool negative_form_test;
0267 if( validate_token_format( value_check, prefix_test, name_test, value_separator_test, negative_form_test )
0268 && m_param_trie[prefix_test]) {
0269
0270 should_go_to_next = false;
0271 }
0272 else {
0273
0274 value = value_check;
0275 }
0276 }
0277 }
0278 }
0279
0280
0281 BOOST_TEST_I_ASSRT( !res.has( found_param->p_name ) || found_param->p_repeatable,
0282 duplicate_arg( found_param->p_name )
0283 << "Duplicate argument value for the parameter "
0284 << found_param->p_name
0285 << " in the argument " << tr.current_token() );
0286
0287
0288 found_param->produce_argument( value, negative_form, res );
0289
0290 if(should_go_to_next) {
0291 tr.next_token();
0292 }
0293 }
0294
0295
0296 return tr.remainder();
0297 }
0298
0299
0300 void
0301 version( std::ostream& ostr )
0302 {
0303 ostr << "Boost.Test module ";
0304
0305 #if defined(BOOST_TEST_MODULE)
0306
0307 ostr << '\'' << BOOST_TEST_STRINGIZE( BOOST_TEST_MODULE ).trim( "\"" ) << "' ";
0308 #endif
0309
0310 ostr << "in executable '" << m_program_name << "'\n";
0311 ostr << "Compiled from Boost version "
0312 << BOOST_VERSION/100000 << "."
0313 << BOOST_VERSION/100 % 1000 << "."
0314 << BOOST_VERSION % 100 ;
0315 ostr << " with ";
0316 #if defined(BOOST_TEST_INCLUDED)
0317 ostr << "header-only inclusion of";
0318 #elif defined(BOOST_TEST_DYN_LINK)
0319 ostr << "dynamic linking to";
0320 #else
0321 ostr << "static linking to";
0322 #endif
0323 ostr << " Boost.Test\n";
0324 ostr << "- Compiler: " << BOOST_COMPILER << '\n'
0325 << "- Platform: " << BOOST_PLATFORM << '\n'
0326 << "- STL : " << BOOST_STDLIB;
0327 ostr << std::endl;
0328 }
0329
0330 void
0331 usage(std::ostream& ostr,
0332 cstring param_name = cstring(),
0333 bool use_color = true)
0334 {
0335 namespace utils = unit_test::utils;
0336 namespace ut_detail = unit_test::ut_detail;
0337
0338 if( !param_name.is_empty() ) {
0339 basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second;
0340 param->usage( ostr, m_negation_prefix );
0341 }
0342 else {
0343 ostr << "\n The program '" << m_program_name << "' is a Boost.Test module containing unit tests.";
0344
0345 {
0346 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
0347 ostr << "\n\n Usage\n ";
0348 }
0349
0350 {
0351 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
0352 ostr << m_program_name << " [Boost.Test argument]... ";
0353 }
0354 if( !m_end_of_param_indicator.empty() ) {
0355 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW );
0356 ostr << '[' << m_end_of_param_indicator << " [custom test module argument]...]";
0357 }
0358 }
0359
0360 ostr << "\n\n Use\n ";
0361 {
0362
0363 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
0364 ostr << m_program_name << " --help";
0365 }
0366 ostr << "\n or ";
0367 {
0368 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
0369 ostr << m_program_name << " --help=<parameter name>";
0370 }
0371 ostr << "\n for detailed help on Boost.Test parameters.\n";
0372 }
0373
0374 void
0375 help(std::ostream& ostr,
0376 parameters_store const& parameters,
0377 cstring param_name,
0378 bool use_color = true)
0379 {
0380 namespace utils = unit_test::utils;
0381 namespace ut_detail = unit_test::ut_detail;
0382
0383 if( !param_name.is_empty() ) {
0384 basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second;
0385 param->help( ostr, m_negation_prefix, use_color);
0386 return;
0387 }
0388
0389 usage(ostr, cstring(), use_color);
0390
0391 ostr << "\n\n";
0392 {
0393 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
0394 ostr << " Command line flags:\n";
0395 }
0396 runtime::commandline_pretty_print(
0397 ostr,
0398 " ",
0399 "The command line flags of Boost.Test are listed below. "
0400 "All parameters are optional. You can specify parameter value either "
0401 "as a command line argument or as a value of its corresponding environment "
0402 "variable. If a flag is specified as a command line argument and an environment variable "
0403 "at the same time, the command line takes precedence. "
0404 "The command line argument "
0405 "support name guessing, and works with shorter names as long as those are not ambiguous."
0406 );
0407
0408 if( !m_end_of_param_indicator.empty() ) {
0409 ostr << "\n\n All the arguments after the '";
0410 {
0411 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW );
0412 ostr << m_end_of_param_indicator;
0413 }
0414 ostr << "' are ignored by Boost.Test.";
0415 }
0416
0417
0418 {
0419 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
0420 ostr << "\n\n Environment variables:\n";
0421 }
0422 runtime::commandline_pretty_print(
0423 ostr,
0424 " ",
0425 "Every argument listed below may also be set by a corresponding environment"
0426 "variable. For an argument '--argument_x=<value>', the corresponding "
0427 "environment variable is 'BOOST_TEST_ARGUMENT_X=value"
0428 );
0429
0430
0431
0432 ostr << "\n\n The following parameters are supported:\n";
0433
0434 BOOST_TEST_FOREACH(
0435 parameters_store::storage_type::value_type const&,
0436 v,
0437 parameters.all() )
0438 {
0439 basic_param_ptr param = v.second;
0440 ostr << "\n";
0441 param->usage( ostr, m_negation_prefix, use_color);
0442 }
0443
0444 }
0445
0446 private:
0447 typedef rt_cla_detail::parameter_trie_ptr trie_ptr;
0448 typedef rt_cla_detail::trie_per_char trie_per_char;
0449 typedef std::map<cstring,trie_ptr> str_to_trie;
0450
0451 void
0452 build_trie( parameters_store const& parameters )
0453 {
0454
0455 BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) {
0456 basic_param_ptr param = v.second;
0457
0458
0459 BOOST_TEST_FOREACH( parameter_cla_id const&, id, param->cla_ids() ) {
0460
0461 trie_ptr next_trie = m_param_trie[id.m_prefix];
0462 if( !next_trie )
0463 next_trie = m_param_trie[id.m_prefix] = trie_ptr( new rt_cla_detail::parameter_trie );
0464
0465
0466
0467 for( size_t index = 0; index < id.m_tag.size(); ++index ) {
0468 next_trie = next_trie->make_subtrie( id.m_tag[index] );
0469
0470 next_trie->add_candidate_id( id, param, index == (id.m_tag.size() - 1) );
0471 }
0472 }
0473 }
0474 }
0475
0476 bool
0477 validate_token_format( cstring token, cstring& prefix, cstring& name, cstring& separator, bool& negative_form )
0478 {
0479
0480 cstring::iterator it = token.begin();
0481 while( it != token.end() && parameter_cla_id::valid_prefix_char( *it ) )
0482 ++it;
0483
0484 prefix.assign( token.begin(), it );
0485
0486 if( prefix.empty() )
0487 return true;
0488
0489
0490 while( it != token.end() && parameter_cla_id::valid_name_char( *it ) )
0491 ++it;
0492
0493 name.assign( prefix.end(), it );
0494
0495 if( name.empty() ) {
0496 if( prefix == m_end_of_param_indicator )
0497 return false;
0498
0499 BOOST_TEST_I_THROW( format_error() << "Invalid format for an actual argument " << token );
0500 }
0501
0502
0503 while( it != token.end() && parameter_cla_id::valid_separator_char( *it ) )
0504 ++it;
0505
0506 separator.assign( name.end(), it );
0507
0508
0509 negative_form = !m_negation_prefix.empty() && ( name.substr( 0, m_negation_prefix.size() ) == m_negation_prefix );
0510 if( negative_form )
0511 name.trim_left( m_negation_prefix.size() );
0512
0513 return true;
0514 }
0515
0516
0517 typedef std::pair<parameter_cla_id, basic_param_ptr> locate_result;
0518
0519 locate_result
0520 locate_parameter( trie_ptr curr_trie, cstring name, cstring token )
0521 {
0522 std::vector<trie_ptr> typo_candidates;
0523 std::vector<trie_ptr> next_typo_candidates;
0524 trie_ptr next_trie;
0525
0526 BOOST_TEST_FOREACH( char, c, name ) {
0527 if( curr_trie ) {
0528
0529 next_trie = curr_trie->get_subtrie( c );
0530
0531 if( next_trie )
0532 curr_trie = next_trie;
0533 else {
0534
0535
0536 BOOST_TEST_FOREACH( trie_per_char::value_type const&, typo_cand, curr_trie->m_subtrie ) {
0537
0538 typo_candidates.push_back( typo_cand.second );
0539
0540
0541 if( (next_trie = typo_cand.second->get_subtrie( c )) )
0542 typo_candidates.push_back( next_trie );
0543 }
0544
0545
0546 typo_candidates.push_back( curr_trie );
0547
0548 curr_trie.reset();
0549 }
0550 }
0551 else {
0552
0553 BOOST_TEST_FOREACH( trie_ptr, typo_cand, typo_candidates ) {
0554 trie_ptr next_typo_cand = typo_cand->get_subtrie( c );
0555
0556 if( next_typo_cand )
0557 next_typo_candidates.push_back( next_typo_cand );
0558 }
0559
0560 next_typo_candidates.swap( typo_candidates );
0561 next_typo_candidates.clear();
0562 }
0563 }
0564
0565 if( !curr_trie ) {
0566 std::vector<cstring> typo_candidate_names;
0567 std::set<parameter_cla_id const*> unique_typo_candidate;
0568 typo_candidate_names.reserve( typo_candidates.size() );
0569
0570
0571 BOOST_TEST_FOREACH( trie_ptr, trie_cand, typo_candidates ) {
0572
0573 if( trie_cand->m_id_candidates.size() > 1 )
0574 continue;
0575
0576 BOOST_TEST_FOREACH( parameter_cla_id const&, param_cand, trie_cand->m_id_candidates ) {
0577 if( !unique_typo_candidate.insert( ¶m_cand ).second )
0578 continue;
0579
0580 typo_candidate_names.push_back( param_cand.m_tag );
0581 }
0582 }
0583
0584 #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
0585 BOOST_TEST_I_THROW( unrecognized_param( std::move(typo_candidate_names) )
0586 << "An unrecognized parameter in the argument "
0587 << token );
0588 #else
0589 BOOST_TEST_I_THROW( unrecognized_param( typo_candidate_names )
0590 << "An unrecognized parameter in the argument "
0591 << token );
0592 #endif
0593 }
0594
0595 if( curr_trie->m_id_candidates.size() > 1 ) {
0596 std::vector<cstring> amb_names;
0597 BOOST_TEST_FOREACH( parameter_cla_id const&, param_id, curr_trie->m_id_candidates )
0598 amb_names.push_back( param_id.m_tag );
0599
0600 #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
0601 BOOST_TEST_I_THROW( ambiguous_param( std::move( amb_names ) )
0602 << "An ambiguous parameter name in the argument " << token );
0603 #else
0604 BOOST_TEST_I_THROW( ambiguous_param( amb_names )
0605 << "An ambiguous parameter name in the argument " << token );
0606 #endif
0607 }
0608
0609 return locate_result( curr_trie->m_id_candidates.back().get(), curr_trie->m_param_candidate );
0610 }
0611
0612
0613 cstring m_program_name;
0614 std::string m_end_of_param_indicator;
0615 std::string m_negation_prefix;
0616 str_to_trie m_param_trie;
0617 };
0618
0619 }
0620 }
0621 }
0622
0623 #include <boost/test/detail/enable_warnings.hpp>
0624
0625 #endif