1//  (C) Copyright 2016 Raffi Enficiaud.
2//  Distributed under the Boost Software License, Version 1.0.
3//  (See accompanying file LICENSE_1_0.txt or copy at
4//  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 Contains the implementatoin of the Junit log formatter (OF_JUNIT)
10// ***************************************************************************
11
12#ifndef BOOST_TEST_JUNIT_LOG_FORMATTER_IPP__
13#define BOOST_TEST_JUNIT_LOG_FORMATTER_IPP__
14
15// Boost.Test
16#include <boost/test/output/junit_log_formatter.hpp>
17#include <boost/test/execution_monitor.hpp>
18#include <boost/test/framework.hpp>
19#include <boost/test/tree/test_unit.hpp>
20#include <boost/test/utils/basic_cstring/io.hpp>
21#include <boost/test/utils/xml_printer.hpp>
22#include <boost/test/utils/string_cast.hpp>
23#include <boost/test/framework.hpp>
24
25#include <boost/test/tree/visitor.hpp>
26#include <boost/test/tree/test_case_counter.hpp>
27#include <boost/test/tree/traverse.hpp>
28#include <boost/test/results_collector.hpp>
29
30#include <boost/test/utils/algorithm.hpp>
31#include <boost/test/utils/string_cast.hpp>
32
33//#include <boost/test/results_reporter.hpp>
34
35
36// Boost
37#include <boost/version.hpp>
38
39// STL
40#include <iostream>
41#include <fstream>
42#include <set>
43
44#include <boost/test/detail/suppress_warnings.hpp>
45
46
47//____________________________________________________________________________//
48
49namespace boost {
50namespace unit_test {
51namespace output {
52
53
54struct s_replace_chars {
55  template <class T>
56  void operator()(T& to_replace)
57  {
58    if(to_replace == '/')
59      to_replace = '.';
60    else if(to_replace == ' ')
61      to_replace = '_';
62  }
63};
64
65inline std::string tu_name_normalize(std::string full_name)
66{
67  // maybe directly using normalize_test_case_name instead?
68  std::for_each(full_name.begin(), full_name.end(), s_replace_chars());
69  return full_name;
70}
71
72inline std::string tu_name_remove_newlines(std::string full_name)
73{
74  full_name.erase(std::remove(full_name.begin(), full_name.end(), '\n'), full_name.end());
75  return full_name;
76}
77
78const_string file_basename(const_string filename) {
79
80    const_string path_sep( "\\/" );
81    const_string::iterator it = unit_test::utils::find_last_of( filename.begin(), filename.end(),
82                                                                path_sep.begin(), path_sep.end() );
83    if( it != filename.end() )
84        filename.trim_left( it + 1 );
85
86    return filename;
87
88}
89
90// ************************************************************************** //
91// **************               junit_log_formatter              ************** //
92// ************************************************************************** //
93
94void
95junit_log_formatter::log_start( std::ostream& /*ostr*/, counter_t /*test_cases_amount*/)
96{
97    map_tests.clear();
98    list_path_to_root.clear();
99    runner_log_entry.clear();
100}
101
102//____________________________________________________________________________//
103
104class junit_result_helper : public test_tree_visitor {
105private:
106    typedef junit_impl::junit_log_helper::assertion_entry assertion_entry;
107    typedef std::vector< assertion_entry >::const_iterator vect_assertion_entry_citerator;
108    typedef std::list<std::string>::const_iterator list_str_citerator;
109
110public:
111    explicit junit_result_helper(
112        std::ostream& stream,
113        test_unit const& ts,
114        junit_log_formatter::map_trace_t const& mt,
115        junit_impl::junit_log_helper const& runner_log_,
116        bool display_build_info )
117    : m_stream(stream)
118    , m_ts( ts )
119    , m_map_test( mt )
120    , runner_log( runner_log_ )
121    , m_id( 0 )
122    , m_display_build_info(display_build_info)
123    { }
124
125    void add_log_entry(assertion_entry const& log) const
126    {
127        std::string entry_type;
128        if( log.log_entry == assertion_entry::log_entry_failure ) {
129            entry_type = "failure";
130        }
131        else if( log.log_entry == assertion_entry::log_entry_error ) {
132            entry_type = "error";
133        }
134        else {
135            return;
136        }
137
138        m_stream
139            << "<" << entry_type
140            << " message" << utils::attr_value() << log.logentry_message
141            << " type" << utils::attr_value() << log.logentry_type
142            << ">";
143
144        if(!log.output.empty()) {
145            m_stream << utils::cdata() << "\n" + log.output;
146        }
147
148        m_stream << "</" << entry_type << ">";
149    }
150
151    struct conditional_cdata_helper {
152        std::ostream &ostr;
153        std::string const field;
154        bool empty;
155
156        conditional_cdata_helper(std::ostream &ostr_, std::string field_)
157        : ostr(ostr_)
158        , field(field_)
159        , empty(true)
160        {}
161
162        ~conditional_cdata_helper() {
163            if(!empty) {
164                ostr << BOOST_TEST_L( "]]>" ) << "</" << field << '>' << std::endl;
165            }
166        }
167
168        void operator()(const std::string& s) {
169            bool current_empty = s.empty();
170            if(empty) {
171                if(!current_empty) {
172                    empty = false;
173                    ostr << '<' << field << '>' << BOOST_TEST_L( "<![CDATA[" );
174                }
175            }
176            if(!current_empty) {
177                ostr << s;
178            }
179        }
180    };
181
182    std::list<std::string> build_skipping_chain(test_unit const & tu) const
183    {
184        // we enter here because we know that the tu has been skipped.
185        // either junit has not seen this tu, or it is indicated as disabled
186        assert(m_map_test.count(tu.p_id) == 0 || results_collector.results( tu.p_id ).p_skipped);
187
188        std::list<std::string> out;
189
190        test_unit_id id(tu.p_id);
191        while( id != m_ts.p_id && id != INV_TEST_UNIT_ID) {
192            test_unit const& tu_hierarchy = boost::unit_test::framework::get( id, TUT_ANY );
193            out.push_back("- disabled test unit: '" + tu_name_remove_newlines(tu_hierarchy.full_name()) + "'\n");
194            if(m_map_test.count(id) > 0)
195            {
196                // junit has seen the reason: this is enough for constructing the chain
197                break;
198            }
199            id = tu_hierarchy.p_parent_id;
200        }
201        junit_log_formatter::map_trace_t::const_iterator it_element_stack(m_map_test.find(id));
202        if( it_element_stack != m_map_test.end() )
203        {
204            out.push_back("- reason: '" + it_element_stack->second.skipping_reason + "'");
205            out.push_front("Test case disabled because of the following chain of decision:\n");
206        }
207
208        return out;
209    }
210
211    std::string get_class_name(test_unit const & tu_class) const {
212        std::string classname;
213        test_unit_id id(tu_class.p_parent_id);
214        while( id != m_ts.p_id && id != INV_TEST_UNIT_ID ) {
215            test_unit const& tu = boost::unit_test::framework::get( id, TUT_ANY );
216            classname = tu_name_normalize(tu.p_name) + "." + classname;
217            id = tu.p_parent_id;
218        }
219
220        // removes the trailing dot
221        if(!classname.empty() && *classname.rbegin() == '.') {
222            classname.erase(classname.size()-1);
223        }
224
225        return classname;
226    }
227
228    void    write_testcase_header(test_unit const & tu,
229                                  test_results const *tr,
230                                  int nb_assertions) const
231    {
232        std::string name;
233        std::string classname;
234
235        if(tu.p_id == m_ts.p_id ) {
236            name = "boost_test";
237        }
238        else {
239            classname = get_class_name(tu);
240            name = tu_name_normalize(tu.p_name);
241        }
242
243        if( tu.p_type == TUT_SUITE ) {
244            name += "-setup-teardown";
245        }
246
247        m_stream << "<testcase assertions" << utils::attr_value() << nb_assertions;
248        if(!classname.empty())
249            m_stream << " classname" << utils::attr_value() << classname;
250
251        // test case name and time taken
252        m_stream
253            << " name"      << utils::attr_value() << name
254            << " time"      << utils::attr_value() << double(tr->p_duration_microseconds) * 1E-6
255            << ">" << std::endl;
256    }
257
258    void    write_testcase_system_out(junit_impl::junit_log_helper const &detailed_log,
259                                      test_unit const * tu,
260                                      bool skipped) const
261    {
262        // system-out + all info/messages, the object skips the empty entries
263        conditional_cdata_helper system_out_helper(m_stream, "system-out");
264
265        // indicate why the test has been skipped first
266        if( skipped ) {
267            std::list<std::string> skipping_decision_chain = build_skipping_chain(*tu);
268            for(list_str_citerator it(skipping_decision_chain.begin()), ite(skipping_decision_chain.end());
269                it != ite;
270                ++it)
271            {
272              system_out_helper(*it);
273            }
274        }
275
276        // stdout
277        for(list_str_citerator it(detailed_log.system_out.begin()), ite(detailed_log.system_out.end());
278            it != ite;
279            ++it)
280        {
281          system_out_helper(*it);
282        }
283
284        // warning/info message last
285        for(vect_assertion_entry_citerator it(detailed_log.assertion_entries.begin());
286            it != detailed_log.assertion_entries.end();
287            ++it)
288        {
289            if(it->log_entry != assertion_entry::log_entry_info)
290                continue;
291            system_out_helper(it->output);
292        }
293    }
294
295    void    write_testcase_system_err(junit_impl::junit_log_helper const &detailed_log,
296                                      test_unit const * tu,
297                                      test_results const *tr) const
298    {
299        // system-err output + test case informations
300        bool has_failed = (tr != 0) ? !tr->p_skipped && !tr->passed() : false;
301        if(!detailed_log.system_err.empty() || has_failed)
302        {
303            std::ostringstream o;
304            if(has_failed) {
305                o << "Failures detected in:" << std::endl;
306            }
307            else {
308                o << "ERROR STREAM:" << std::endl;
309            }
310
311            if(tu->p_type == TUT_SUITE) {
312                if( tu->p_id == m_ts.p_id ) {
313                    o << " boost.test global setup/teardown" << std::endl;
314                } else {
315                    o << "- test suite: " << tu_name_remove_newlines(tu->full_name()) << std::endl;
316                }
317            }
318            else {
319              o << "- test case: " << tu_name_remove_newlines(tu->full_name());
320              if(!tu->p_description.value.empty())
321                  o << " '" << tu->p_description << "'";
322
323              o << std::endl
324                  << "- file: " << file_basename(tu->p_file_name) << std::endl
325                  << "- line: " << tu->p_line_num << std::endl
326                  ;
327            }
328
329            if(!detailed_log.system_err.empty())
330                o << std::endl << "STDERR BEGIN: ------------" << std::endl;
331
332            for(list_str_citerator it(detailed_log.system_err.begin()), ite(detailed_log.system_err.end());
333                it != ite;
334                ++it)
335            {
336              o << *it;
337            }
338
339            if(!detailed_log.system_err.empty())
340                o << std::endl << "STDERR END    ------------" << std::endl;
341
342            conditional_cdata_helper system_err_helper(m_stream, "system-err");
343            system_err_helper(o.str());
344        }
345    }
346
347    int     get_nb_assertions(junit_impl::junit_log_helper const &detailed_log,
348                              test_unit const & tu,
349                              test_results const *tr) const {
350        int nb_assertions(-1);
351        if( tu.p_type == TUT_SUITE ) {
352            nb_assertions = 0;
353            for(vect_assertion_entry_citerator it(detailed_log.assertion_entries.begin());
354                it != detailed_log.assertion_entries.end();
355                ++it)
356            {
357                if(it->log_entry != assertion_entry::log_entry_info)
358                    nb_assertions++;
359            }
360        }
361        else {
362            nb_assertions = tr->p_assertions_passed + tr->p_assertions_failed;
363        }
364
365        return nb_assertions;
366    }
367
368    void    output_detailed_logs(junit_impl::junit_log_helper const &detailed_log,
369                                 test_unit const & tu,
370                                 bool skipped,
371                                 test_results const *tr) const
372    {
373        int nb_assertions = get_nb_assertions(detailed_log, tu, tr);
374        if(!nb_assertions && tu.p_type == TUT_SUITE)
375            return;
376
377        write_testcase_header(tu, tr, nb_assertions);
378
379        if( skipped ) {
380            m_stream << "<skipped/>" << std::endl;
381        }
382        else {
383
384          for(vect_assertion_entry_citerator it(detailed_log.assertion_entries.begin());
385              it != detailed_log.assertion_entries.end();
386              ++it)
387          {
388              add_log_entry(*it);
389          }
390        }
391
392        write_testcase_system_out(detailed_log, &tu, skipped);
393        write_testcase_system_err(detailed_log, &tu, tr);
394        m_stream << "</testcase>" << std::endl;
395    }
396
397    void    visit( test_case const& tc )
398    {
399
400        test_results const& tr = results_collector.results( tc.p_id );
401        junit_log_formatter::map_trace_t::const_iterator it_find = m_map_test.find(tc.p_id);
402        if(it_find == m_map_test.end())
403        {
404            // test has been skipped and not seen by the logger
405            output_detailed_logs(junit_impl::junit_log_helper(), tc, true, &tr);
406        }
407        else {
408            output_detailed_logs(it_find->second, tc, tr.p_skipped, &tr);
409        }
410    }
411
412    bool    test_suite_start( test_suite const& ts )
413    {
414        test_results const& tr = results_collector.results( ts.p_id );
415
416        // unique test suite, without s, nesting not supported in CI
417        if( m_ts.p_id == ts.p_id ) {
418            m_stream << "<testsuite";
419
420            m_stream
421              // << "disabled=\"" << tr.p_test_cases_skipped << "\" "
422              << " tests"     << utils::attr_value() << tr.p_test_cases_passed
423              << " skipped"   << utils::attr_value() << tr.p_test_cases_skipped
424              << " errors"    << utils::attr_value() << tr.p_test_cases_aborted
425              << " failures"  << utils::attr_value() << tr.p_test_cases_failed
426              << " id"        << utils::attr_value() << m_id++
427              << " name"      << utils::attr_value() << tu_name_normalize(ts.p_name)
428              << " time"      << utils::attr_value() << (tr.p_duration_microseconds * 1E-6)
429              << ">" << std::endl;
430
431            if(m_display_build_info)
432            {
433                m_stream  << "<properties>" << std::endl;
434                m_stream  << "<property name=\"platform\" value" << utils::attr_value() << BOOST_PLATFORM << std::endl;
435                m_stream  << "<property name=\"compiler\" value" << utils::attr_value() << BOOST_COMPILER << std::endl;
436                m_stream  << "<property name=\"stl\" value" << utils::attr_value() << BOOST_STDLIB << std::endl;
437
438                std::ostringstream o;
439                o << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100;
440                m_stream  << "<property name=\"boost\" value" << utils::attr_value() << o.str() << std::endl;
441                m_stream  << "</properties>" << std::endl;
442            }
443        }
444
445        if( !tr.p_skipped ) {
446            // if we land here, then this is a chance that we are logging the fixture setup/teardown of a test-suite.
447            // the setup/teardown logging of a test-case is part of the test case.
448            // we do not care about the test-suite that were skipped (really??)
449            junit_log_formatter::map_trace_t::const_iterator it_find = m_map_test.find(ts.p_id);
450            if(it_find != m_map_test.end()) {
451                output_detailed_logs(it_find->second, ts, false, &tr);
452            }
453        }
454
455        return true; // indicates that the children should also be parsed
456    }
457
458    virtual void    test_suite_finish( test_suite const& ts )
459    {
460        if( m_ts.p_id == ts.p_id ) {
461            write_testcase_system_out(runner_log, 0, false);
462            write_testcase_system_err(runner_log, 0, 0);
463
464            m_stream << "</testsuite>";
465            return;
466        }
467    }
468
469private:
470    // Data members
471    std::ostream& m_stream;
472    test_unit const& m_ts;
473    junit_log_formatter::map_trace_t const& m_map_test;
474    junit_impl::junit_log_helper const& runner_log;
475    size_t m_id;
476    bool m_display_build_info;
477};
478
479
480
481void
482junit_log_formatter::log_finish( std::ostream& ostr )
483{
484    ostr << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
485
486    // getting the root test suite
487    if(!map_tests.empty()) {
488        test_unit* root = &boost::unit_test::framework::get( map_tests.begin()->first, TUT_ANY );
489
490        // looking for the root of the SUBtree (we stay in the subtree)
491        while(root->p_parent_id != INV_TEST_UNIT_ID && map_tests.count(root->p_parent_id) > 0) {
492            root = &boost::unit_test::framework::get( root->p_parent_id, TUT_ANY );
493        }
494        junit_result_helper ch( ostr, *root, map_tests, this->runner_log_entry, m_display_build_info );
495        traverse_test_tree( root->p_id, ch, true ); // last is to ignore disabled suite special handling
496    }
497    else {
498        ostr << "<testsuites errors=\"1\">";
499        ostr << "<testsuite errors=\"1\" name=\"boost-test-framework\">";
500        ostr << "<testcase assertions=\"1\" name=\"test-setup\">";
501        ostr << "<system-out>Incorrect setup: no test case executed</system-out>";
502        ostr << "</testcase></testsuite></testsuites>";
503    }
504    return;
505}
506
507//____________________________________________________________________________//
508
509void
510junit_log_formatter::log_build_info( std::ostream& /*ostr*/ )
511{
512    m_display_build_info = true;
513}
514
515//____________________________________________________________________________//
516
517void
518junit_log_formatter::test_unit_start( std::ostream& /*ostr*/, test_unit const& tu )
519{
520    list_path_to_root.push_back( tu.p_id );
521    map_tests.insert(std::make_pair(tu.p_id, junit_impl::junit_log_helper())); // current_test_case_id not working here
522}
523
524
525
526//____________________________________________________________________________//
527
528void
529junit_log_formatter::test_unit_finish( std::ostream& /*ostr*/, test_unit const& tu, unsigned long /*elapsed*/ )
530{
531    // the time is already stored in the result_reporter
532    assert( tu.p_id == list_path_to_root.back() );
533    list_path_to_root.pop_back();
534}
535
536void
537junit_log_formatter::test_unit_aborted( std::ostream& /*ostr*/, test_unit const& tu )
538{
539    assert( tu.p_id == list_path_to_root.back() );
540    //list_path_to_root.pop_back();
541}
542
543//____________________________________________________________________________//
544
545void
546junit_log_formatter::test_unit_skipped( std::ostream& /*ostr*/, test_unit const& tu, const_string reason )
547{
548    // if a test unit is skipped, then the start of this TU has not been called yet.
549    // we cannot use get_current_log_entry here, but the TU id should appear in the map.
550    // The "skip" boolean is given by the boost.test framework
551    junit_impl::junit_log_helper& v = map_tests[tu.p_id]; // not sure if we can use get_current_log_entry()
552    v.skipping_reason.assign(reason.begin(), reason.end());
553}
554
555//____________________________________________________________________________//
556
557void
558junit_log_formatter::log_exception_start( std::ostream& /*ostr*/, log_checkpoint_data const& checkpoint_data, execution_exception const& ex )
559{
560    std::ostringstream o;
561    execution_exception::location const& loc = ex.where();
562
563    m_is_last_assertion_or_error = false;
564
565    junit_impl::junit_log_helper& last_entry = get_current_log_entry();
566
567    junit_impl::junit_log_helper::assertion_entry entry;
568
569    entry.logentry_message = "unexpected exception";
570    entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_error;
571
572    switch(ex.code())
573    {
574    case execution_exception::cpp_exception_error:
575        entry.logentry_type = "uncaught exception";
576        break;
577    case execution_exception::timeout_error:
578        entry.logentry_type = "execution timeout";
579        break;
580    case execution_exception::user_error:
581        entry.logentry_type = "user, assert() or CRT error";
582        break;
583    case execution_exception::user_fatal_error:
584        // Looks like never used
585        entry.logentry_type = "user fatal error";
586        break;
587    case execution_exception::system_error:
588        entry.logentry_type = "system error";
589        break;
590    case execution_exception::system_fatal_error:
591        entry.logentry_type = "system fatal error";
592        break;
593    default:
594        entry.logentry_type = "no error"; // not sure how to handle this one
595        break;
596    }
597
598    o << "UNCAUGHT EXCEPTION:" << std::endl;
599    if( !loc.m_function.is_empty() )
600        o << "- function: \""   << loc.m_function << "\"" << std::endl;
601
602    o << "- file: " << file_basename(loc.m_file_name) << std::endl
603      << "- line: " << loc.m_line_num << std::endl
604      << std::endl;
605
606    o << "\nEXCEPTION STACK TRACE: --------------\n" << ex.what()
607      << "\n-------------------------------------";
608
609    if( !checkpoint_data.m_file_name.is_empty() ) {
610        o << std::endl << std::endl
611          << "Last checkpoint:" << std::endl
612          << "- message: \"" << checkpoint_data.m_message << "\"" << std::endl
613          << "- file: " << file_basename(checkpoint_data.m_file_name) << std::endl
614          << "- line: " << checkpoint_data.m_line_num << std::endl
615        ;
616    }
617
618    entry.output = o.str();
619
620    last_entry.assertion_entries.push_back(entry);
621}
622
623//____________________________________________________________________________//
624
625void
626junit_log_formatter::log_exception_finish( std::ostream& /*ostr*/ )
627{
628    // sealing the last entry
629    assert(!get_current_log_entry().assertion_entries.back().sealed);
630    get_current_log_entry().assertion_entries.back().sealed = true;
631}
632
633//____________________________________________________________________________//
634
635void
636junit_log_formatter::log_entry_start( std::ostream& /*ostr*/, log_entry_data const& entry_data, log_entry_types let )
637{
638    junit_impl::junit_log_helper& last_entry = get_current_log_entry();
639    last_entry.skipping = false;
640    m_is_last_assertion_or_error = true;
641    switch(let)
642    {
643      case unit_test_log_formatter::BOOST_UTL_ET_INFO:
644      {
645        if(m_log_level_internal > log_successful_tests) {
646          last_entry.skipping = true;
647          break;
648        }
649        // no break on purpose
650      }
651      case unit_test_log_formatter::BOOST_UTL_ET_MESSAGE:
652      {
653        if(m_log_level_internal > log_messages) {
654          last_entry.skipping = true;
655          break;
656        }
657        // no break on purpose
658      }
659      case unit_test_log_formatter::BOOST_UTL_ET_WARNING:
660      {
661        if(m_log_level_internal > log_warnings) {
662          last_entry.skipping = true;
663          break;
664        }
665        std::ostringstream o;
666        junit_impl::junit_log_helper::assertion_entry entry;
667
668        entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_info;
669        entry.logentry_message = "info";
670        entry.logentry_type = "message";
671
672        o << (let == unit_test_log_formatter::BOOST_UTL_ET_WARNING ?
673              "WARNING:" : (let == unit_test_log_formatter::BOOST_UTL_ET_MESSAGE ?
674                            "MESSAGE:" : "INFO:"))
675             << std::endl
676          << "- file   : " << file_basename(entry_data.m_file_name) << std::endl
677          << "- line   : " << entry_data.m_line_num << std::endl
678          << "- message: "; // no CR
679
680        entry.output += o.str();
681        last_entry.assertion_entries.push_back(entry);
682        break;
683      }
684      default:
685      case unit_test_log_formatter::BOOST_UTL_ET_ERROR:
686      case unit_test_log_formatter::BOOST_UTL_ET_FATAL_ERROR:
687      {
688        std::ostringstream o;
689        junit_impl::junit_log_helper::assertion_entry entry;
690        entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_failure;
691        entry.logentry_message = "failure";
692        entry.logentry_type = (let == unit_test_log_formatter::BOOST_UTL_ET_ERROR ? "assertion error" : "fatal error");
693
694        o << "ASSERTION FAILURE:" << std::endl
695          << "- file   : " << file_basename(entry_data.m_file_name) << std::endl
696          << "- line   : " << entry_data.m_line_num << std::endl
697          << "- message: " ; // no CR
698
699        entry.output += o.str();
700        last_entry.assertion_entries.push_back(entry);
701        break;
702      }
703    }
704}
705
706//____________________________________________________________________________//
707
708void
709junit_log_formatter::log_entry_value( std::ostream& /*ostr*/, const_string value )
710{
711    junit_impl::junit_log_helper& last_entry = get_current_log_entry();
712    if(last_entry.skipping)
713        return;
714
715    assert(last_entry.assertion_entries.empty() || !last_entry.assertion_entries.back().sealed);
716
717    if(!last_entry.assertion_entries.empty())
718    {
719        junit_impl::junit_log_helper::assertion_entry& log_entry = last_entry.assertion_entries.back();
720        log_entry.output += value;
721    }
722    else
723    {
724        // this may be a message coming from another observer
725        // the prefix is set in the log_entry_start
726        last_entry.system_out.push_back(std::string(value.begin(), value.end()));
727    }
728}
729
730//____________________________________________________________________________//
731
732void
733junit_log_formatter::log_entry_finish( std::ostream& /*ostr*/ )
734{
735    junit_impl::junit_log_helper& last_entry = get_current_log_entry();
736    if(!last_entry.skipping)
737    {
738        assert(last_entry.assertion_entries.empty() || !last_entry.assertion_entries.back().sealed);
739
740        if(!last_entry.assertion_entries.empty()) {
741            junit_impl::junit_log_helper::assertion_entry& log_entry = last_entry.assertion_entries.back();
742            log_entry.output += "\n\n"; // quote end, CR
743            log_entry.sealed = true;
744        }
745        else {
746            last_entry.system_out.push_back("\n\n"); // quote end, CR
747        }
748    }
749
750    last_entry.skipping = false;
751}
752
753//____________________________________________________________________________//
754
755void
756junit_log_formatter::entry_context_start( std::ostream& /*ostr*/, log_level )
757{
758    junit_impl::junit_log_helper& last_entry = get_current_log_entry();
759    if(last_entry.skipping)
760        return;
761
762    std::vector< junit_impl::junit_log_helper::assertion_entry > &v_failure_or_error = last_entry.assertion_entries;
763    assert(!v_failure_or_error.back().sealed);
764
765    junit_impl::junit_log_helper::assertion_entry& last_log_entry = v_failure_or_error.back();
766    if(m_is_last_assertion_or_error)
767    {
768        last_log_entry.output += "\n- context:\n";
769    }
770    else
771    {
772        last_log_entry.output += "\n\nCONTEXT:\n";
773    }
774}
775
776//____________________________________________________________________________//
777
778void
779junit_log_formatter::entry_context_finish( std::ostream& /*ostr*/, log_level )
780{
781    // no op, may be removed
782    junit_impl::junit_log_helper& last_entry = get_current_log_entry();
783    if(last_entry.skipping)
784        return;
785    assert(!get_current_log_entry().assertion_entries.back().sealed);
786}
787
788//____________________________________________________________________________//
789
790void
791junit_log_formatter::log_entry_context( std::ostream& /*ostr*/, log_level , const_string context_descr )
792{
793    junit_impl::junit_log_helper& last_entry = get_current_log_entry();
794    if(last_entry.skipping)
795        return;
796
797    assert(!last_entry.assertion_entries.back().sealed);
798    junit_impl::junit_log_helper::assertion_entry& last_log_entry = get_current_log_entry().assertion_entries.back();
799
800    last_log_entry.output +=
801        (m_is_last_assertion_or_error ? "  - '": "- '") + std::string(context_descr.begin(), context_descr.end()) + "'\n"; // quote end
802}
803
804//____________________________________________________________________________//
805
806
807std::string
808junit_log_formatter::get_default_stream_description() const {
809    std::string name = framework::master_test_suite().p_name.value;
810
811    static const std::string to_replace[] =  { " ", "\"", "/", "\\", ":"};
812    static const std::string replacement[] = { "_", "_" , "_", "_" , "_"};
813
814    name = unit_test::utils::replace_all_occurrences_of(
815        name,
816        to_replace, to_replace + sizeof(to_replace)/sizeof(to_replace[0]),
817        replacement, replacement + sizeof(replacement)/sizeof(replacement[0]));
818
819    std::ifstream check_init((name + ".xml").c_str());
820    if(!check_init)
821        return name + ".xml";
822
823    int index = 0;
824    for(; index < 100; index++) {
825      std::string candidate = name + "_" + utils::string_cast(index) + ".xml";
826      std::ifstream file(candidate.c_str());
827      if(!file)
828          return candidate;
829    }
830
831    return name + ".xml";
832}
833
834} // namespace output
835} // namespace unit_test
836} // namespace boost
837
838#include <boost/test/detail/enable_warnings.hpp>
839
840#endif // BOOST_TEST_junit_log_formatter_IPP_020105GER
841