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)
6 // See http://www.boost.org/libs/test for the library home page.
10 // ***************************************************************************
12 #ifndef BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
13 #define BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
15 // Boost.Test Runtime parameters
16 #include <boost/test/utils/runtime/argument.hpp>
17 #include <boost/test/utils/runtime/modifier.hpp>
18 #include <boost/test/utils/runtime/parameter.hpp>
20 #include <boost/test/utils/runtime/cla/argv_traverser.hpp>
23 #include <boost/test/utils/foreach.hpp>
24 #include <boost/test/utils/algorithm.hpp>
25 #include <boost/test/detail/throw_exception.hpp>
26 #include <boost/test/detail/global_typedef.hpp>
28 #include <boost/algorithm/cxx11/all_of.hpp> // !! ?? unnecessary after cxx11
31 // !! ?? #include <unordered_set>
35 #include <boost/test/detail/suppress_warnings.hpp>
41 // ************************************************************************** //
42 // ************** runtime::cla::parameter_trie ************** //
43 // ************************************************************************** //
45 namespace rt_cla_detail {
47 struct parameter_trie;
48 typedef shared_ptr<parameter_trie> parameter_trie_ptr;
49 typedef std::map<char,parameter_trie_ptr> trie_per_char;
50 typedef std::vector<boost::reference_wrapper<parameter_cla_id const> > param_cla_id_list;
52 struct parameter_trie {
53 parameter_trie() : m_has_final_candidate( false ) {}
55 /// If subtrie corresponding to the char c exists returns it otherwise creates new
56 parameter_trie_ptr make_subtrie( char c )
58 trie_per_char::const_iterator it = m_subtrie.find( c );
60 if( it == m_subtrie.end() )
61 it = m_subtrie.insert( std::make_pair( c, parameter_trie_ptr( new parameter_trie ) ) ).first;
66 /// Creates series of sub-tries per characters in a string
67 parameter_trie_ptr make_subtrie( cstring s )
69 parameter_trie_ptr res;
71 BOOST_TEST_FOREACH( char, c, s )
72 res = (res ? res->make_subtrie( c ) : make_subtrie( c ));
77 /// Registers candidate parameter for this subtrie. If final, it needs to be unique
78 void add_candidate_id( parameter_cla_id const& param_id, basic_param_ptr param_candidate, bool final )
80 BOOST_TEST_I_ASSRT( !m_has_final_candidate && (!final || m_id_candidates.empty()),
81 conflicting_param() << "Parameter cla id " << param_id.m_tag << " conflicts with the "
82 << "parameter cla id " << m_id_candidates.back().get().m_tag );
84 m_has_final_candidate = final;
85 m_id_candidates.push_back( ref(param_id) );
87 if( m_id_candidates.size() == 1 )
88 m_param_candidate = param_candidate;
90 m_param_candidate.reset();
93 /// Gets subtrie for specified char if present or nullptr otherwise
94 parameter_trie_ptr get_subtrie( char c ) const
96 trie_per_char::const_iterator it = m_subtrie.find( c );
98 return it != m_subtrie.end() ? it->second : parameter_trie_ptr();
102 trie_per_char m_subtrie;
103 param_cla_id_list m_id_candidates;
104 basic_param_ptr m_param_candidate;
105 bool m_has_final_candidate;
108 // ************************************************************************** //
109 // ************** runtime::cla::report_foreing_token ************** //
110 // ************************************************************************** //
113 report_foreing_token( cstring program_name, cstring token )
115 std::cerr << "Boost.Test WARNING: token \"" << token << "\" does not correspond to the Boost.Test argument \n"
116 << " and should be placed after all Boost.Test arguments and the -- separator.\n"
117 << " For example: " << program_name << " --random -- " << token << "\n";
120 } // namespace rt_cla_detail
122 // ************************************************************************** //
123 // ************** runtime::cla::parser ************** //
124 // ************************************************************************** //
128 /// Initializes a parser and builds internal trie representation used for
129 /// parsing based on the supplied parameters
130 #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS
131 template<typename Modifiers=nfp::no_params_type>
132 parser( parameters_store const& parameters, Modifiers const& m = nfp::no_params )
134 template<typename Modifiers>
135 parser( parameters_store const& parameters, Modifiers const& m )
138 nfp::opt_assign( m_end_of_param_indicator, m, end_of_params );
139 nfp::opt_assign( m_negation_prefix, m, negation_prefix );
141 BOOST_TEST_I_ASSRT( algorithm::all_of( m_end_of_param_indicator.begin(),
142 m_end_of_param_indicator.end(),
143 parameter_cla_id::valid_prefix_char ),
144 invalid_cla_id() << "End of parameters indicator can only consist of prefix characters." );
146 BOOST_TEST_I_ASSRT( algorithm::all_of( m_negation_prefix.begin(),
147 m_negation_prefix.end(),
148 parameter_cla_id::valid_name_char ),
149 invalid_cla_id() << "Negation prefix can only consist of prefix characters." );
151 build_trie( parameters );
154 // input processing method
156 parse( int argc, char** argv, runtime::arguments_store& res )
158 // save program name for help message
159 m_program_name = argv[0];
160 cstring path_sep( "\\/" );
162 cstring::iterator it = unit_test::utils::find_last_of( m_program_name.begin(), m_program_name.end(),
163 path_sep.begin(), path_sep.end() );
164 if( it != m_program_name.end() )
165 m_program_name.trim_left( it + 1 );
167 // Set up the traverser
168 argv_traverser tr( argc, (char const**)argv );
170 // Loop till we reach end of input
172 cstring curr_token = tr.current_token();
176 cstring value_separator;
177 bool negative_form = false;
179 // Perform format validations and split the argument into prefix, name and separator
180 // False return value indicates end of params indicator is met
181 if( !validate_token_format( curr_token, prefix, name, value_separator, negative_form ) ) {
182 // get rid of "end of params" token
187 // Locate trie corresponding to found prefix and skip it in the input
188 trie_ptr curr_trie = m_param_trie[prefix];
191 // format_error() << "Unrecognized parameter prefix in the argument " << tr.current_token()
192 rt_cla_detail::report_foreing_token( m_program_name, curr_token );
197 curr_token.trim_left( prefix.size() );
199 // Locate parameter based on a name and skip it in the input
200 locate_result locate_res = locate_parameter( curr_trie, name, curr_token );
201 parameter_cla_id const& found_id = locate_res.first;
202 basic_param_ptr found_param = locate_res.second;
204 if( negative_form ) {
205 BOOST_TEST_I_ASSRT( found_id.m_negatable,
206 format_error( found_param->p_name )
207 << "Parameter tag " << found_id.m_tag << " is not negatable." );
209 curr_token.trim_left( m_negation_prefix.size() );
212 curr_token.trim_left( name.size() );
216 // Skip validations if parameter has optional value and we are at the end of token
217 if( !value_separator.is_empty() || !found_param->p_has_optional_value ) {
218 // Validate and skip value separator in the input
219 BOOST_TEST_I_ASSRT( found_id.m_value_separator == value_separator,
220 format_error( found_param->p_name )
221 << "Invalid separator for the parameter "
222 << found_param->p_name
223 << " in the argument " << tr.current_token() );
225 curr_token.trim_left( value_separator.size() );
227 // Deduce value source
229 if( value.is_empty() ) {
231 value = tr.current_token();
234 BOOST_TEST_I_ASSRT( !value.is_empty(),
235 format_error( found_param->p_name )
236 << "Missing an argument value for the parameter "
237 << found_param->p_name
238 << " in the argument " << tr.current_token() );
241 // Validate against argument duplication
242 BOOST_TEST_I_ASSRT( !res.has( found_param->p_name ) || found_param->p_repeatable,
243 duplicate_arg( found_param->p_name )
244 << "Duplicate argument value for the parameter "
245 << found_param->p_name
246 << " in the argument " << tr.current_token() );
248 // Produce argument value
249 found_param->produce_argument( value, negative_form, res );
254 // generate the remainder and return it's size
255 return tr.remainder();
258 // help/usage/version
260 version( std::ostream& ostr )
262 ostr << "Boost.Test module ";
264 #if defined(BOOST_TEST_MODULE)
265 // we do not want to refer to the master test suite there
266 ostr << '\'' << BOOST_TEST_STRINGIZE( BOOST_TEST_MODULE ).trim( "\"" ) << "' ";
269 ostr << "in executable '" << m_program_name << "'\n";
270 ostr << "Compiled from Boost version "
271 << BOOST_VERSION/100000 << "."
272 << BOOST_VERSION/100 % 1000 << "."
273 << BOOST_VERSION % 100 ;
275 #if defined(BOOST_TEST_INCLUDED)
276 ostr << "single header inclusion of";
277 #elif defined(BOOST_TEST_DYN_LINK)
278 ostr << "dynamic linking to";
280 ostr << "static linking to";
282 ostr << " Boost.Test\n";
283 ostr << "- Compiler: " << BOOST_COMPILER << '\n'
284 << "- Platform: " << BOOST_PLATFORM << '\n'
285 << "- STL : " << BOOST_STDLIB;
290 usage( std::ostream& ostr, cstring param_name = cstring() )
292 if( !param_name.is_empty() ) {
293 basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second;
294 param->usage( ostr, m_negation_prefix );
297 ostr << "Usage: " << m_program_name << " [Boost.Test argument]... ";
298 if( !m_end_of_param_indicator.empty() )
299 ostr << m_end_of_param_indicator << " [custom test module argument]...";
303 ostr << "\nFor detailed help on Boost.Test parameters use:\n"
304 << " " << m_program_name << " --help\n"
306 << " " << m_program_name << " --help=<parameter name>\n";
310 help( std::ostream& ostr, parameters_store const& parameters, cstring param_name )
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 );
318 ostr << "Usage: " << m_program_name << " [Boost.Test argument]... ";
319 if( !m_end_of_param_indicator.empty() )
320 ostr << m_end_of_param_indicator << " [custom test module argument]...";
322 ostr << "\n\nBoost.Test arguments correspond to parameters listed below. "
323 "All parameters are optional. You can use specify parameter value either "
324 "as a command line argument or as a value of corresponding environment "
325 "variable. In case if argument for the same parameter is specified in both "
326 "places, command line is taking precedence. Command line argument format "
327 "supports parameter name guessing, so you can use any unambiguous "
328 "prefix to identify a parameter.";
329 if( !m_end_of_param_indicator.empty() )
330 ostr << " All the arguments after the " << m_end_of_param_indicator << " are ignored by the Boost.Test.";
332 ostr << "\n\nBoost.Test supports following parameters:\n";
334 BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) {
335 basic_param_ptr param = v.second;
337 param->usage( ostr, m_negation_prefix );
340 ostr << "\nUse --help=<parameter name> to display detailed help for specific parameter.\n";
344 typedef rt_cla_detail::parameter_trie_ptr trie_ptr;
345 typedef rt_cla_detail::trie_per_char trie_per_char;
346 typedef std::map<cstring,trie_ptr> str_to_trie;
349 build_trie( parameters_store const& parameters )
351 // Iterate over all parameters
352 BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) {
353 basic_param_ptr param = v.second;
355 // Register all parameter's ids in trie.
356 BOOST_TEST_FOREACH( parameter_cla_id const&, id, param->cla_ids() ) {
357 // This is the trie corresponding to the prefix.
358 trie_ptr next_trie = m_param_trie[id.m_prefix];
360 next_trie = m_param_trie[id.m_prefix] = trie_ptr( new rt_cla_detail::parameter_trie );
362 // Build the trie, by following name's characters
363 // and register this parameter as candidate on each level
364 for( size_t index = 0; index < id.m_tag.size(); ++index ) {
365 next_trie = next_trie->make_subtrie( id.m_tag[index] );
367 next_trie->add_candidate_id( id, param, index == (id.m_tag.size() - 1) );
374 validate_token_format( cstring token, cstring& prefix, cstring& name, cstring& separator, bool& negative_form )
377 cstring::iterator it = token.begin();
378 while( it != token.end() && parameter_cla_id::valid_prefix_char( *it ) )
381 prefix.assign( token.begin(), it );
387 while( it != token.end() && parameter_cla_id::valid_name_char( *it ) )
390 name.assign( prefix.end(), it );
393 if( prefix == m_end_of_param_indicator )
396 BOOST_TEST_I_THROW( format_error() << "Invalid format for an actual argument " << token );
399 // Match value separator
400 while( it != token.end() && parameter_cla_id::valid_separator_char( *it ) )
403 separator.assign( name.end(), it );
405 // Match negation prefix
406 negative_form = !m_negation_prefix.empty() && ( name.substr( 0, m_negation_prefix.size() ) == m_negation_prefix );
408 name.trim_left( m_negation_prefix.size() );
413 // C++03: cannot have references as types
414 typedef std::pair<parameter_cla_id, basic_param_ptr> locate_result;
417 locate_parameter( trie_ptr curr_trie, cstring name, cstring token )
419 std::vector<trie_ptr> typo_candidates;
420 std::vector<trie_ptr> next_typo_candidates;
423 BOOST_TEST_FOREACH( char, c, name ) {
425 // locate next subtrie corresponding to the char
426 next_trie = curr_trie->get_subtrie( c );
429 curr_trie = next_trie;
431 // Initiate search for typo candicates. We will account for 'wrong char' typo
432 // 'missing char' typo and 'extra char' typo
433 BOOST_TEST_FOREACH( trie_per_char::value_type const&, typo_cand, curr_trie->m_subtrie ) {
435 typo_candidates.push_back( typo_cand.second );
437 // 'missing char' typo
438 if( (next_trie = typo_cand.second->get_subtrie( c )) )
439 typo_candidates.push_back( next_trie );
443 typo_candidates.push_back( curr_trie );
449 // go over existing typo candidates and see if they are still viable
450 BOOST_TEST_FOREACH( trie_ptr, typo_cand, typo_candidates ) {
451 trie_ptr next_typo_cand = typo_cand->get_subtrie( c );
454 next_typo_candidates.push_back( next_typo_cand );
457 next_typo_candidates.swap( typo_candidates );
458 next_typo_candidates.clear();
463 std::vector<cstring> typo_candidate_names;
464 std::set<parameter_cla_id const*> unique_typo_candidate; // !! ?? unordered_set
465 typo_candidate_names.reserve( typo_candidates.size() );
466 // !! ?? unique_typo_candidate.reserve( typo_candidates.size() );
468 BOOST_TEST_FOREACH( trie_ptr, trie_cand, typo_candidates ) {
469 // avoid ambiguos candidate trie
470 if( trie_cand->m_id_candidates.size() > 1 )
473 BOOST_TEST_FOREACH( parameter_cla_id const&, param_cand, trie_cand->m_id_candidates ) {
474 if( !unique_typo_candidate.insert( ¶m_cand ).second )
477 typo_candidate_names.push_back( param_cand.m_tag );
481 #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
482 BOOST_TEST_I_THROW( unrecognized_param( std::move(typo_candidate_names) )
483 << "An unrecognized parameter in the argument "
486 BOOST_TEST_I_THROW( unrecognized_param( typo_candidate_names )
487 << "An unrecognized parameter in the argument "
492 if( curr_trie->m_id_candidates.size() > 1 ) {
493 std::vector<cstring> amb_names;
494 BOOST_TEST_FOREACH( parameter_cla_id const&, param_id, curr_trie->m_id_candidates )
495 amb_names.push_back( param_id.m_tag );
497 #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
498 BOOST_TEST_I_THROW( ambiguous_param( std::move( amb_names ) )
499 << "An ambiguous parameter name in the argument " << token );
501 BOOST_TEST_I_THROW( ambiguous_param( amb_names )
502 << "An ambiguous parameter name in the argument " << token );
506 return locate_result( curr_trie->m_id_candidates.back().get(), curr_trie->m_param_candidate );
510 cstring m_program_name;
511 std::string m_end_of_param_indicator;
512 std::string m_negation_prefix;
513 str_to_trie m_param_trie;
517 } // namespace runtime
520 #include <boost/test/detail/enable_warnings.hpp>
522 #endif // BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP