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
6// See http://www.boost.org/libs/test for the library home page.
7//
8//!@file
9//!@brief CLA parser
10// ***************************************************************************
11
12#ifndef BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
13#define BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
14
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>
19
20#include <boost/test/utils/runtime/cla/argv_traverser.hpp>
21
22// Boost.Test
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>
27
28#include <boost/algorithm/cxx11/all_of.hpp> // !! ?? unnecessary after cxx11
29
30// STL
31// !! ?? #include <unordered_set>
32#include <set>
33#include <iostream>
34
35#include <boost/test/detail/suppress_warnings.hpp>
36
37namespace boost {
38namespace runtime {
39namespace cla {
40
41// ************************************************************************** //
42// ************** runtime::cla::parameter_trie ************** //
43// ************************************************************************** //
44
45namespace rt_cla_detail {
46
47struct parameter_trie;
48typedef shared_ptr<parameter_trie> parameter_trie_ptr;
49typedef std::map<char,parameter_trie_ptr> trie_per_char;
50typedef std::vector<boost::reference_wrapper<parameter_cla_id const> > param_cla_id_list;
51
52struct parameter_trie {
53 parameter_trie() : m_has_final_candidate( false ) {}
54
55 /// If subtrie corresponding to the char c exists returns it otherwise creates new
56 parameter_trie_ptr make_subtrie( char c )
57 {
58 trie_per_char::const_iterator it = m_subtrie.find( x: c );
59
60 if( it == m_subtrie.end() )
61 it = m_subtrie.insert( x: std::make_pair( x&: c, y: parameter_trie_ptr( new parameter_trie ) ) ).first;
62
63 return it->second;
64 }
65
66 /// Creates series of sub-tries per characters in a string
67 parameter_trie_ptr make_subtrie( cstring s )
68 {
69 parameter_trie_ptr res;
70
71 BOOST_TEST_FOREACH( char, c, s )
72 res = (res ? res->make_subtrie( c ) : make_subtrie( c ));
73
74 return res;
75 }
76
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 )
79 {
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 );
83
84 m_has_final_candidate = final;
85 m_id_candidates.push_back( x: ref(t: param_id) );
86
87 if( m_id_candidates.size() == 1 )
88 m_param_candidate = param_candidate;
89 else
90 m_param_candidate.reset();
91 }
92
93 /// Gets subtrie for specified char if present or nullptr otherwise
94 parameter_trie_ptr get_subtrie( char c ) const
95 {
96 trie_per_char::const_iterator it = m_subtrie.find( x: c );
97
98 return it != m_subtrie.end() ? it->second : parameter_trie_ptr();
99 }
100
101 // Data members
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;
106};
107
108// ************************************************************************** //
109// ************** runtime::cla::report_foreing_token ************** //
110// ************************************************************************** //
111
112static void
113report_foreing_token( cstring program_name, cstring token )
114{
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";
118}
119
120} // namespace rt_cla_detail
121
122// ************************************************************************** //
123// ************** runtime::cla::parser ************** //
124// ************************************************************************** //
125
126class parser {
127public:
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 )
133#else
134 template<typename Modifiers>
135 parser( parameters_store const& parameters, Modifiers const& m )
136#endif
137 {
138 nfp::opt_assign( m_end_of_param_indicator, m, end_of_params );
139 nfp::opt_assign( m_negation_prefix, m, negation_prefix );
140
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." );
145
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." );
150
151 build_trie( parameters );
152 }
153
154 // input processing method
155 int
156 parse( int argc, char** argv, runtime::arguments_store& res )
157 {
158 // save program name for help message
159 m_program_name = argv[0];
160 cstring path_sep( "\\/" );
161
162 cstring::iterator it = unit_test::utils::find_last_of( first1: m_program_name.begin(), last1: m_program_name.end(),
163 first2: path_sep.begin(), last2: path_sep.end() );
164 if( it != m_program_name.end() )
165 m_program_name.trim_left( it: it + 1 );
166
167 // Set up the traverser
168 argv_traverser tr( argc, (char const**)argv );
169
170 // Loop till we reach end of input
171 while( !tr.eoi() ) {
172 cstring curr_token = tr.current_token();
173
174 cstring prefix;
175 cstring name;
176 cstring value_separator;
177 bool negative_form = false;
178
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( token: curr_token, prefix, name, separator&: value_separator, negative_form ) ) {
182 // get rid of "end of params" token
183 tr.next_token();
184 break;
185 }
186
187 // Locate trie corresponding to found prefix and skip it in the input
188 trie_ptr curr_trie = m_param_trie[prefix];
189
190 if( !curr_trie ) {
191 // format_error() << "Unrecognized parameter prefix in the argument " << tr.current_token()
192 rt_cla_detail::report_foreing_token( program_name: m_program_name, token: curr_token );
193 tr.save_token();
194 continue;
195 }
196
197 curr_token.trim_left( trim_size: prefix.size() );
198
199 // Locate parameter based on a name and skip it in the input
200 locate_result locate_res = locate_parameter( curr_trie, name, token: curr_token );
201 parameter_cla_id const& found_id = locate_res.first;
202 basic_param_ptr found_param = locate_res.second;
203
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." );
208
209 curr_token.trim_left( trim_size: m_negation_prefix.size() );
210 }
211
212 curr_token.trim_left( trim_size: name.size() );
213
214 bool should_go_to_next = true;
215 cstring value;
216
217
218 // Skip validations if parameter has optional value and we are at the end of token
219 if( !value_separator.is_empty() || !found_param->p_has_optional_value ) {
220
221 // we are given a separator or there is no optional value
222
223 // Validate and skip value separator in the input
224 BOOST_TEST_I_ASSRT( found_id.m_value_separator == value_separator,
225 format_error( found_param->p_name )
226 << "Invalid separator for the parameter "
227 << found_param->p_name
228 << " in the argument " << tr.current_token() );
229
230 curr_token.trim_left( trim_size: value_separator.size() );
231
232 // Deduce value source
233 value = curr_token;
234 if( value.is_empty() ) {
235 tr.next_token();
236 value = tr.current_token();
237 }
238
239 BOOST_TEST_I_ASSRT( !value.is_empty(),
240 format_error( found_param->p_name )
241 << "Missing an argument value for the parameter "
242 << found_param->p_name
243 << " in the argument " << tr.current_token() );
244 }
245 else if( (value_separator.is_empty() && found_id.m_value_separator.empty()) ) {
246 // Deduce value source
247 value = curr_token;
248 if( value.is_empty() ) {
249 tr.next_token(); // tokenization broke the value, we check the next one
250
251 if(!found_param->p_has_optional_value) {
252 // there is no separator and there is no optional value
253 // we look for the value on the next token
254 // example "-t XXXX" (no default)
255 // and we commit this value as being the passed value
256 value = tr.current_token();
257 }
258 else {
259 // there is no separator and the value is optional
260 // we check the next token
261 // example "-c" (defaults to true)
262 // and commit this as the value if this is not a token
263 cstring value_check = tr.current_token();
264
265 cstring prefix_test, name_test, value_separator_test;
266 bool negative_form_test;
267 if( validate_token_format( token: value_check, prefix&: prefix_test, name&: name_test, separator&: value_separator_test, negative_form&: negative_form_test )
268 && m_param_trie[prefix_test]) {
269 // this is a token, we consume what we have
270 should_go_to_next = false;
271 }
272 else {
273 // this is a value, we commit it
274 value = value_check;
275 }
276 }
277 }
278 }
279
280 // Validate against argument duplication
281 BOOST_TEST_I_ASSRT( !res.has( found_param->p_name ) || found_param->p_repeatable,
282 duplicate_arg( found_param->p_name )
283 << "Duplicate argument value for the parameter "
284 << found_param->p_name
285 << " in the argument " << tr.current_token() );
286
287 // Produce argument value
288 found_param->produce_argument( token: value, negative_form, store&: res );
289
290 if(should_go_to_next) {
291 tr.next_token();
292 }
293 }
294
295 // generate the remainder and return it's size
296 return tr.remainder();
297 }
298
299 // help/usage/version
300 void
301 version( std::ostream& ostr )
302 {
303 ostr << "Boost.Test module ";
304
305#if defined(BOOST_TEST_MODULE)
306 // we do not want to refer to the master test suite there
307 ostr << '\'' << BOOST_TEST_STRINGIZE( BOOST_TEST_MODULE ).trim( "\"" ) << "' ";
308#endif
309
310 ostr << "in executable '" << m_program_name << "'\n";
311 ostr << "Compiled from Boost version "
312 << BOOST_VERSION/100000 << "."
313 << BOOST_VERSION/100 % 1000 << "."
314 << BOOST_VERSION % 100 ;
315 ostr << " with ";
316#if defined(BOOST_TEST_INCLUDED)
317 ostr << "header-only inclusion of";
318#elif defined(BOOST_TEST_DYN_LINK)
319 ostr << "dynamic linking to";
320#else
321 ostr << "static linking to";
322#endif
323 ostr << " Boost.Test\n";
324 ostr << "- Compiler: " << BOOST_COMPILER << '\n'
325 << "- Platform: " << BOOST_PLATFORM << '\n'
326 << "- STL : " << BOOST_STDLIB;
327 ostr << std::endl;
328 }
329
330 void
331 usage(std::ostream& ostr,
332 cstring param_name = cstring(),
333 bool use_color = true)
334 {
335 namespace utils = unit_test::utils;
336 namespace ut_detail = unit_test::ut_detail;
337
338 if( !param_name.is_empty() ) {
339 basic_param_ptr param = locate_parameter( curr_trie: m_param_trie[help_prefix], name: param_name, token: "" ).second;
340 param->usage( ostr, negation_prefix_: m_negation_prefix );
341 }
342 else {
343 ostr << "\n The program '" << m_program_name << "' is a Boost.Test module containing unit tests.";
344
345 {
346 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
347 ostr << "\n\n Usage\n ";
348 }
349
350 {
351 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
352 ostr << m_program_name << " [Boost.Test argument]... ";
353 }
354 if( !m_end_of_param_indicator.empty() ) {
355 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW );
356 ostr << '[' << m_end_of_param_indicator << " [custom test module argument]...]";
357 }
358 }
359
360 ostr << "\n\n Use\n ";
361 {
362
363 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
364 ostr << m_program_name << " --help";
365 }
366 ostr << "\n or ";
367 {
368 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN );
369 ostr << m_program_name << " --help=<parameter name>";
370 }
371 ostr << "\n for detailed help on Boost.Test parameters.\n";
372 }
373
374 void
375 help(std::ostream& ostr,
376 parameters_store const& parameters,
377 cstring param_name,
378 bool use_color = true)
379 {
380 namespace utils = unit_test::utils;
381 namespace ut_detail = unit_test::ut_detail;
382
383 if( !param_name.is_empty() ) {
384 basic_param_ptr param = locate_parameter( curr_trie: m_param_trie[help_prefix], name: param_name, token: "" ).second;
385 param->help( ostr, negation_prefix_: m_negation_prefix, use_color);
386 return;
387 }
388
389 usage(ostr, param_name: cstring(), use_color);
390
391 ostr << "\n\n";
392 {
393 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
394 ostr << " Command line flags:\n";
395 }
396 runtime::commandline_pretty_print(
397 ostr,
398 prefix: " ",
399 to_print: "The command line flags of Boost.Test are listed below. "
400 "All parameters are optional. You can specify parameter value either "
401 "as a command line argument or as a value of its corresponding environment "
402 "variable. If a flag is specified as a command line argument and an environment variable "
403 "at the same time, the command line takes precedence. "
404 "The command line argument "
405 "support name guessing, and works with shorter names as long as those are not ambiguous."
406 );
407
408 if( !m_end_of_param_indicator.empty() ) {
409 ostr << "\n\n All the arguments after the '";
410 {
411 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW );
412 ostr << m_end_of_param_indicator;
413 }
414 ostr << "' are ignored by Boost.Test.";
415 }
416
417
418 {
419 BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL );
420 ostr << "\n\n Environment variables:\n";
421 }
422 runtime::commandline_pretty_print(
423 ostr,
424 prefix: " ",
425 to_print: "Every argument listed below may also be set by a corresponding environment"
426 "variable. For an argument '--argument_x=<value>', the corresponding "
427 "environment variable is 'BOOST_TEST_ARGUMENT_X=value"
428 );
429
430
431
432 ostr << "\n\n The following parameters are supported:\n";
433
434 BOOST_TEST_FOREACH(
435 parameters_store::storage_type::value_type const&,
436 v,
437 parameters.all() )
438 {
439 basic_param_ptr param = v.second;
440 ostr << "\n";
441 param->usage( ostr, negation_prefix_: m_negation_prefix, use_color);
442 }
443
444 }
445
446private:
447 typedef rt_cla_detail::parameter_trie_ptr trie_ptr;
448 typedef rt_cla_detail::trie_per_char trie_per_char;
449 typedef std::map<cstring,trie_ptr> str_to_trie;
450
451 void
452 build_trie( parameters_store const& parameters )
453 {
454 // Iterate over all parameters
455 BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) {
456 basic_param_ptr param = v.second;
457
458 // Register all parameter's ids in trie.
459 BOOST_TEST_FOREACH( parameter_cla_id const&, id, param->cla_ids() ) {
460 // This is the trie corresponding to the prefix.
461 trie_ptr next_trie = m_param_trie[id.m_prefix];
462 if( !next_trie )
463 next_trie = m_param_trie[id.m_prefix] = trie_ptr( new rt_cla_detail::parameter_trie );
464
465 // Build the trie, by following name's characters
466 // and register this parameter as candidate on each level
467 for( size_t index = 0; index < id.m_tag.size(); ++index ) {
468 next_trie = next_trie->make_subtrie( c: id.m_tag[index] );
469
470 next_trie->add_candidate_id( param_id: id, param_candidate: param, final: index == (id.m_tag.size() - 1) );
471 }
472 }
473 }
474 }
475
476 bool
477 validate_token_format( cstring token, cstring& prefix, cstring& name, cstring& separator, bool& negative_form )
478 {
479 // Match prefix
480 cstring::iterator it = token.begin();
481 while( it != token.end() && parameter_cla_id::valid_prefix_char( c: *it ) )
482 ++it;
483
484 prefix.assign( f: token.begin(), l: it );
485
486 if( prefix.empty() )
487 return true;
488
489 // Match name
490 while( it != token.end() && parameter_cla_id::valid_name_char( c: *it ) )
491 ++it;
492
493 name.assign( f: prefix.end(), l: it );
494
495 if( name.empty() ) {
496 if( prefix == m_end_of_param_indicator )
497 return false;
498
499 BOOST_TEST_I_THROW( format_error() << "Invalid format for an actual argument " << token );
500 }
501
502 // Match value separator
503 while( it != token.end() && parameter_cla_id::valid_separator_char( c: *it ) )
504 ++it;
505
506 separator.assign( f: name.end(), l: it );
507
508 // Match negation prefix
509 negative_form = !m_negation_prefix.empty() && ( name.substr( beg_index: 0, end_index: m_negation_prefix.size() ) == m_negation_prefix );
510 if( negative_form )
511 name.trim_left( trim_size: m_negation_prefix.size() );
512
513 return true;
514 }
515
516 // C++03: cannot have references as types
517 typedef std::pair<parameter_cla_id, basic_param_ptr> locate_result;
518
519 locate_result
520 locate_parameter( trie_ptr curr_trie, cstring name, cstring token )
521 {
522 std::vector<trie_ptr> typo_candidates;
523 std::vector<trie_ptr> next_typo_candidates;
524 trie_ptr next_trie;
525
526 BOOST_TEST_FOREACH( char, c, name ) {
527 if( curr_trie ) {
528 // locate next subtrie corresponding to the char
529 next_trie = curr_trie->get_subtrie( c );
530
531 if( next_trie )
532 curr_trie = next_trie;
533 else {
534 // Initiate search for typo candicates. We will account for 'wrong char' typo
535 // 'missing char' typo and 'extra char' typo
536 BOOST_TEST_FOREACH( trie_per_char::value_type const&, typo_cand, curr_trie->m_subtrie ) {
537 // 'wrong char' typo
538 typo_candidates.push_back( x: typo_cand.second );
539
540 // 'missing char' typo
541 if( (next_trie = typo_cand.second->get_subtrie( c )) )
542 typo_candidates.push_back( x: next_trie );
543 }
544
545 // 'extra char' typo
546 typo_candidates.push_back( x: curr_trie );
547
548 curr_trie.reset();
549 }
550 }
551 else {
552 // go over existing typo candidates and see if they are still viable
553 BOOST_TEST_FOREACH( trie_ptr, typo_cand, typo_candidates ) {
554 trie_ptr next_typo_cand = typo_cand->get_subtrie( c );
555
556 if( next_typo_cand )
557 next_typo_candidates.push_back( x: next_typo_cand );
558 }
559
560 next_typo_candidates.swap( x&: typo_candidates );
561 next_typo_candidates.clear();
562 }
563 }
564
565 if( !curr_trie ) {
566 std::vector<cstring> typo_candidate_names;
567 std::set<parameter_cla_id const*> unique_typo_candidate; // !! ?? unordered_set
568 typo_candidate_names.reserve( n: typo_candidates.size() );
569// !! ?? unique_typo_candidate.reserve( typo_candidates.size() );
570
571 BOOST_TEST_FOREACH( trie_ptr, trie_cand, typo_candidates ) {
572 // avoid ambiguos candidate trie
573 if( trie_cand->m_id_candidates.size() > 1 )
574 continue;
575
576 BOOST_TEST_FOREACH( parameter_cla_id const&, param_cand, trie_cand->m_id_candidates ) {
577 if( !unique_typo_candidate.insert( x: &param_cand ).second )
578 continue;
579
580 typo_candidate_names.push_back( x: param_cand.m_tag );
581 }
582 }
583
584#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
585 BOOST_TEST_I_THROW( unrecognized_param( std::move(typo_candidate_names) )
586 << "An unrecognized parameter in the argument "
587 << token );
588#else
589 BOOST_TEST_I_THROW( unrecognized_param( typo_candidate_names )
590 << "An unrecognized parameter in the argument "
591 << token );
592#endif
593 }
594
595 if( curr_trie->m_id_candidates.size() > 1 ) {
596 std::vector<cstring> amb_names;
597 BOOST_TEST_FOREACH( parameter_cla_id const&, param_id, curr_trie->m_id_candidates )
598 amb_names.push_back( x: param_id.m_tag );
599
600#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
601 BOOST_TEST_I_THROW( ambiguous_param( std::move( amb_names ) )
602 << "An ambiguous parameter name in the argument " << token );
603#else
604 BOOST_TEST_I_THROW( ambiguous_param( amb_names )
605 << "An ambiguous parameter name in the argument " << token );
606#endif
607 }
608
609 return locate_result( curr_trie->m_id_candidates.back().get(), curr_trie->m_param_candidate );
610 }
611
612 // Data members
613 cstring m_program_name;
614 std::string m_end_of_param_indicator;
615 std::string m_negation_prefix;
616 str_to_trie m_param_trie;
617};
618
619} // namespace cla
620} // namespace runtime
621} // namespace boost
622
623#include <boost/test/detail/enable_warnings.hpp>
624
625#endif // BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
626

source code of include/boost/test/utils/runtime/cla/parser.hpp