2

I would like to enable logging of Boost.Test output to both the stdout/sterr and to a logfile.

However, I would also like to have the stdout logging be in HumanReadableFormat and only the file output be in XML format.

Generating a report file seems to be addressed here. (official docs)

Choosing the format can be done in various ways, but it seems one is stuck with either HRF or XML?


Rationale:

When running tests on or Jenkins server, the xUnit plugin requires the log XML output (as opposed to the report XML output -- log_level vs. report_level).

However, the in-progress log output (in HRF) is highly valuable while the tests are running on the server, to detect hanging tests or to a quick manual check of where the tests currently are. So I want an in-progress HRF log for the tests and at the end I need an XML file containing the <TestLog>...</TestLog> output of Boost.Test (not the <TestResult> output).

Do note that we run both unit as well as integration tests via Boost.Test, so some of them are quite long-running.

Community
  • 1
  • 1
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 3
    Implemented in boost 1.62, see the [change log](http://live.boost.org/doc/libs/1_62_0/libs/test/doc/html/boost_test/change_log.html). – Raffi Oct 09 '16 at 22:28
  • @Raffi - cheers for the heads up. Now I only need to up from 1.44 to 1.62 :-D – Martin Ba Oct 10 '16 at 08:47
  • 1
    Before you upgrade, 1.62 has a bug that prevents setting the file for the log and report file. Better you wait a bit more for 1.63 (already fixed in development branches). – Raffi Oct 10 '16 at 13:02

1 Answers1

3

I've done it :-) After a lot of trial and error, I managed to find a decent way of having the actual output work being done by the Boost.Test framework itself and still get the XML output to a file.

The key is to understand what and how output can be customized in Boost.Test:

  • There are two log areas, Test-Log and Test-Result, only the Test-Log is useful, as the test result, even at highest detail level is just a summary
  • Boost.Test only supports one single output stream for its outputting, so replacing that one doesn't immediately help, although one can use boost::iostream and a tee_filterto write both to stdout and a file.
  • Above doesn't help, because Boost.Test also uses only one sinlge log formatter (unit_test_log_formatter) so even when you can split the output to two streams, you still have the problem that you only got one format.
  • However, the log formatter can be set via boost::unit_test::unit_test_log.set_formatter and this is exactly what I'm doing.

I provide a thin wrapper over both an HRF formatter as well as an XML formatter, where the HRF formatter just logs to the default stream and the XML formatter writes to a custom XML file.

It seems to work well, but use at your own risk, as this is pretty new and hasn't been running on all suites yet.

#pragma once

// Copyright (c) 2014
// This file is distributed under the 
// Boost Software License - Version 1.0 - August 17th, 2003
// (See http://www.boost.org/LICENSE_1_0.txt )

#include <boost/test/unit_test_log_formatter.hpp>
#include <boost/test/output/compiler_log_formatter.hpp>
#include <boost/test/output/xml_log_formatter.hpp>
#include <fstream>

namespace boost { 
namespace unit_test { 
namespace output {

//! Log formatter for Boost.Test that outputs the logging output *both*
//! to the standard HRF formatter (normal output stream ~ cout) *and*
//! also to the XML formatter, but the XML is written to a report file.
//!
//! Usage: 
//! // Call in init_unit_test_suite: (this will override the --log_format parameter)
//! boost::unit_test::unit_test_log.set_formatter(
//!   new boost::unit_test::output::dual_log_formatter(L"filename.xml")
//! );
//!
//! Note: Calling `boost::unit_test::unit_test_log.set_stream(...)` will change the stream for
//!       the HRF formatter used here.
//!
//! Note: Implemented in boost::unit_test::output for symmetry with existing formatter classes
//!
class dual_log_formatter : public unit_test_log_formatter {
public:
    // Formatter interface
    void log_start(std::ostream& os, counter_t test_cases_amount) override {
        hrf_logger.log_start(os, test_cases_amount);
        xml_logger.log_start(xml_file, test_cases_amount);
    }
    void log_finish(std::ostream& os) override {
        hrf_logger.log_finish(os);
        xml_logger.log_finish(xml_file);
    }
    void log_build_info(std::ostream& os) override {
        hrf_logger.log_build_info(os);
        xml_logger.log_build_info(xml_file);
    }

    void test_unit_start(std::ostream& os, test_unit const& tu) override {
        hrf_logger.test_unit_start(os, tu);
        xml_logger.test_unit_start(xml_file, tu);
    }

    void test_unit_finish(std::ostream& os, test_unit const& tu, unsigned long elapsed) override {
        hrf_logger.test_unit_finish(os, tu, elapsed);
        xml_logger.test_unit_finish(xml_file, tu, elapsed);
    }

    void test_unit_skipped(std::ostream& os, test_unit const& tu) override {
        hrf_logger.test_unit_skipped(os, tu);
        xml_logger.test_unit_skipped(xml_file, tu);
    }

    void log_exception(std::ostream& os, log_checkpoint_data const& checkpoint_data, execution_exception const& ex) override {
        hrf_logger.log_exception(os, checkpoint_data, ex);
        xml_logger.log_exception(xml_file, checkpoint_data, ex);
    }

    void log_entry_start(std::ostream& os, log_entry_data const& entry_data, log_entry_types let) override {
        hrf_logger.log_entry_start(os, entry_data, let);
        xml_logger.log_entry_start(xml_file, entry_data, let);
    }
    using unit_test_log_formatter::log_entry_value; // bring base class functions into overload set
    void log_entry_value(std::ostream& os, const_string value) override {
        hrf_logger.log_entry_value(os, value);
        xml_logger.log_entry_value(xml_file, value);
    }
    void log_entry_finish(std::ostream& os) override {
        hrf_logger.log_entry_finish(os);
        xml_logger.log_entry_finish(xml_file);
    }

    dual_log_formatter(const wchar_t* xmlFilename) { // Note: Use char* on non-MSVC compilers
        xml_file.open(xmlFilename);
    }

private:
    std::ofstream xml_file;
    compiler_log_formatter hrf_logger;
    xml_log_formatter xml_logger;
};

}
}
}
Martin Ba
  • 37,187
  • 33
  • 183
  • 337