Delayed/lazy dataset construction

The dataset generators need to access runtime variables, which is
possible only after the test framework enters its setup.

The purpose of those changes is to instanciate the dataset and populate
the test tree during the initialization phase and not during the static
instanciation of the different test case:

- new delayed dataset type that is used for holding the parameters of a dataset
  and its type. This dataset will be instanciated on demand (lazy construct)
- the test tree is now able to hold a generator until the init phase of the test
  module. Once the init reached, the lazy datasets are instanciated and the test
  tree populated with new tests.
- operations like zip do not require the size earlier than needed
This commit is contained in:
Raffi Enficiaud 2018-01-02 23:35:55 +01:00
parent 977c48fc6c
commit dea96e77bc
16 changed files with 372 additions and 69 deletions

View File

@ -77,7 +77,7 @@ invoke_action( Action const& action, T&& args, std::true_type /* is_tuple */ )
template<typename DataSet, typename Action>
inline typename std::enable_if<monomorphic::is_dataset<DataSet>::value,void>::type
for_each_sample( DataSet && samples,
for_each_sample( DataSet const & samples,
Action const& act,
data::size_t number_of_samples = BOOST_TEST_DS_INFINITE_SIZE )
{

View File

@ -1,19 +0,0 @@
// (C) Copyright Gennadiy Rozental 2001.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
// See http://www.boost.org/libs/test for the library home page.
//
//!@file
//!@brief specific generators
// ***************************************************************************
#ifndef BOOST_TEST_DATA_MONOMORPHIC_GENERATORS_HPP_112011GER
#define BOOST_TEST_DATA_MONOMORPHIC_GENERATORS_HPP_112011GER
// Boost.Test
#include <boost/test/data/monomorphic/generators/xrange.hpp>
#endif // BOOST_TEST_DATA_MONOMORPHIC_GENERATORS_HPP_112011GER

View File

@ -22,6 +22,7 @@
#include <boost/test/data/monomorphic/join.hpp>
#include <boost/test/data/monomorphic/singleton.hpp>
#include <boost/test/data/monomorphic/zip.hpp>
#include <boost/test/data/monomorphic/delayed.hpp>
#endif // BOOST_TEST_DATA_MONOMORPHIC_HPP_102211GER

View File

@ -0,0 +1,114 @@
// (C) Copyright Raffi Enficiaud 2018.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
// See http://www.boost.org/libs/test for the library home page.
//
/// @file
/// Defines a lazy/delayed dataset store
// ***************************************************************************
#ifndef BOOST_TEST_DATA_MONOMORPHIC_DELAYED_HPP_062018GER
#define BOOST_TEST_DATA_MONOMORPHIC_DELAYED_HPP_062018GER
// Boost.Test
#include <boost/test/data/config.hpp>
#include <boost/test/data/monomorphic/fwd.hpp>
#include <boost/test/data/index_sequence.hpp>
#include <boost/core/ref.hpp>
#include <algorithm>
#include <boost/test/detail/suppress_warnings.hpp>
//____________________________________________________________________________//
#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && \
!defined(BOOST_NO_CXX11_HDR_TUPLE)
namespace boost {
namespace unit_test {
namespace data {
namespace monomorphic {
// ************************************************************************** //
// ************** delayed_dataset ************** //
// ************************************************************************** //
/// Delayed dataset
///
/// This dataset holds another dataset that is instanciated on demand. It is
/// constructed with the @c data::make_delayed<dataset_t>(arg1,....) instead of the
/// @c data::make.
template <class dataset_t, class ...Args>
class delayed_dataset
{
public:
enum { arity = dataset_t::arity };
using iterator = decltype(std::declval<dataset_t>().begin());
delayed_dataset(Args... args)
: m_args(std::make_tuple(std::forward<Args>(args)...))
{}
boost::unit_test::data::size_t size() const {
return this->get().size();
}
// iterator
iterator begin() const {
return this->get().begin();
}
private:
dataset_t& get() const {
if(!m_dataset) {
m_dataset = create(boost::unit_test::data::index_sequence_for<Args...>());
}
return *m_dataset;
}
template<std::size_t... I>
std::unique_ptr<dataset_t>
create(boost::unit_test::data::index_sequence<I...>) const
{
return std::unique_ptr<dataset_t>{new dataset_t(std::get<I>(m_args)...)};
}
std::tuple<typename std::decay<Args>::type...> m_args;
mutable std::unique_ptr<dataset_t> m_dataset;
};
//____________________________________________________________________________//
//! A lazy/delayed dataset is a dataset.
template <class dataset_t, class ...Args>
struct is_dataset< delayed_dataset<dataset_t, Args...> > : boost::mpl::true_ {};
//____________________________________________________________________________//
} // namespace monomorphic
template<class dataset_t, class ...Args>
inline typename std::enable_if<
monomorphic::is_dataset< dataset_t >::value,
monomorphic::delayed_dataset<dataset_t, Args...>
>::type
make_delayed(Args... args)
{
return monomorphic::delayed_dataset<dataset_t, Args...>( std::forward<Args>(args)... );
}
} // namespace data
} // namespace unit_test
} // namespace boost
#endif
#include <boost/test/detail/enable_warnings.hpp>
#endif // BOOST_TEST_DATA_MONOMORPHIC_DELAYED_HPP_062018GER

View File

@ -43,9 +43,6 @@ namespace monomorphic {
#if !defined(BOOST_TEST_DOXYGEN_DOC__)
template<typename T, typename Specific>
class dataset;
template<typename T>
class singleton;
@ -58,6 +55,12 @@ class array;
template<typename T>
class init_list;
#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && \
!defined(BOOST_NO_CXX11_HDR_TUPLE)
template<class dataset_t, class ...Args>
class delayed_dataset;
#endif
#endif
// ************************************************************************** //
@ -158,6 +161,19 @@ template<typename T>
inline monomorphic::init_list<T>
make( std::initializer_list<T>&& );
//____________________________________________________________________________//
#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && \
!defined(BOOST_NO_CXX11_HDR_TUPLE)
template<class dataset_t, class ...Args>
inline typename std::enable_if<
monomorphic::is_dataset< dataset_t >::value,
monomorphic::delayed_dataset<dataset_t, Args...>
>::type
make_delayed(Args... args);
#endif
//____________________________________________________________________________//
namespace result_of {

View File

@ -75,28 +75,47 @@ public:
//! Constructor
//!
//! The datasets are moved and not copied.
zip( DataSet1&& ds1, DataSet2&& ds2, data::size_t size )
zip( DataSet1&& ds1, DataSet2&& ds2/*, data::size_t size*/ )
: m_ds1( std::forward<DataSet1>( ds1 ) )
, m_ds2( std::forward<DataSet2>( ds2 ) )
, m_size( size )
//, m_size( size )
{}
//! Move constructor
zip( zip&& j )
: m_ds1( std::forward<DataSet1>( j.m_ds1 ) )
, m_ds2( std::forward<DataSet2>( j.m_ds2 ) )
, m_size( j.m_size )
//, m_size( j.m_size )
{}
// dataset interface
data::size_t size() const { return m_size; }
data::size_t size() const { return zip_size(); }
iterator begin() const { return iterator( m_ds1.begin(), m_ds2.begin() ); }
private:
// Data members
DataSet1 m_ds1;
DataSet2 m_ds2;
data::size_t m_size;
//data::size_t m_size;
//! Handles the sise of the resulting zipped dataset.
data::size_t zip_size() const
{
data::size_t ds1_size = m_ds1.size();
data::size_t ds2_size = m_ds2.size();
if( ds1_size == ds2_size )
return ds1_size;
if( ds1_size == 1 || ds1_size.is_inf() )
return ds2_size;
if( ds2_size == 1 || ds2_size.is_inf() )
return ds1_size;
BOOST_TEST_DS_ERROR( "Can't zip datasets of different sizes" );
}
};
//____________________________________________________________________________//
@ -107,32 +126,6 @@ struct is_dataset<zip<DataSet1,DataSet2>> : mpl::true_ {};
//____________________________________________________________________________//
namespace ds_detail {
//! Handles the sise of the resulting zipped dataset.
template<typename DataSet1, typename DataSet2>
inline data::size_t
zip_size( DataSet1&& ds1, DataSet2&& ds2 )
{
data::size_t ds1_size = ds1.size();
data::size_t ds2_size = ds2.size();
if( ds1_size == ds2_size )
return ds1_size;
if( ds1_size == 1 || ds1_size.is_inf() )
return ds2_size;
if( ds2_size == 1 || ds2_size.is_inf() )
return ds1_size;
BOOST_TEST_DS_ERROR( "Can't zip datasets of different sizes" );
}
} // namespace ds_detail
//____________________________________________________________________________//
namespace result_of {
//! Result type of the zip operator.
@ -153,8 +146,8 @@ inline typename boost::lazy_enable_if_c<is_dataset<DataSet1>::value && is_datase
operator^( DataSet1&& ds1, DataSet2&& ds2 )
{
return zip<DataSet1,DataSet2>( std::forward<DataSet1>( ds1 ),
std::forward<DataSet2>( ds2 ),
ds_detail::zip_size( ds1, ds2 ) );
std::forward<DataSet2>( ds2 )/*,
ds_detail::zip_size( ds1, ds2 )*/ );
}
//____________________________________________________________________________//

View File

@ -126,15 +126,17 @@ public:
// Constructor
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet&& ds )
: m_tc_name( ut_detail::normalize_test_case_name( tc_name ) )
: m_dataset( std::forward<DataSet>( ds ) )
, m_generated( false )
, m_tc_name( ut_detail::normalize_test_case_name( tc_name ) )
, m_tc_file( tc_file )
, m_tc_line( tc_line )
, m_tc_index( 0 )
{
data::for_each_sample( std::forward<DataSet>( ds ), *this );
}
{}
test_case_gen( test_case_gen&& gen )
: m_tc_name( gen.m_tc_name )
: m_dataset( std::move( gen.m_dataset ) )
, m_generated( gen.m_generated )
, m_tc_name( gen.m_tc_name )
, m_tc_file( gen.m_tc_file )
, m_tc_line( gen.m_tc_line )
, m_tc_index( gen.m_tc_index )
@ -142,17 +144,23 @@ public:
{}
#else
test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet const& ds )
: m_tc_name( ut_detail::normalize_test_case_name( tc_name ) )
: m_dataset( ds )
, m_generated( false )
, m_tc_name( ut_detail::normalize_test_case_name( tc_name ) )
, m_tc_file( tc_file )
, m_tc_line( tc_line )
, m_tc_index( 0 )
{
data::for_each_sample( ds, *this );
}
{}
#endif
public:
virtual test_unit* next() const
{
if(!m_generated) {
data::for_each_sample( m_dataset, *this );
m_generated = true;
}
if( m_test_cases.empty() )
return 0;
@ -162,6 +170,7 @@ public:
return res;
}
#if !defined(BOOST_TEST_DATASET_VARIADIC)
// see BOOST_TEST_DATASET_MAX_ARITY to increase the default supported arity
// there is also a limit on boost::bind
@ -195,6 +204,8 @@ private:
}
// Data members
DataSet m_dataset;
mutable bool m_generated;
std::string m_tc_name;
const_string m_tc_file;
std::size_t m_tc_line;
@ -206,10 +217,10 @@ private:
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
template<typename TestCase,typename DataSet>
test_case_gen<TestCase,DataSet>
boost::shared_ptr<test_unit_generator> //test_case_gen<TestCase,DataSet>
make_test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet&& ds )
{
return test_case_gen<TestCase,DataSet>( tc_name, tc_file, tc_line, std::forward<DataSet>(ds) );
return boost::shared_ptr<test_unit_generator>(new test_case_gen<TestCase,DataSet>( tc_name, tc_file, tc_line, std::forward<DataSet>(ds) ));
}
#else
template<typename TestCase,typename DataSet>

View File

@ -64,6 +64,14 @@ collector::reset()
//____________________________________________________________________________//
std::vector<base_ptr>
collector::get_lazy_decorators() const
{
return m_tu_decorators;
}
//____________________________________________________________________________//
// ************************************************************************** //
// ************** decorator::base ************** //
// ************************************************************************** //

View File

@ -1181,6 +1181,13 @@ finalize_setup_phase( test_unit_id master_tu_id )
class apply_decorators : public test_tree_visitor {
private:
// test_tree_visitor interface
virtual bool test_suite_start( test_suite const& ts)
{
const_cast<test_suite&>(ts).generate();
return test_tree_visitor::test_suite_start(ts);
}
virtual bool visit( test_unit const& tu )
{
BOOST_TEST_FOREACH( decorator::base_ptr, d, tu.p_decorators.get() )

View File

@ -281,10 +281,48 @@ test_suite::add( test_unit_generator const& gen, decorator::collector& decorator
decorators.store_in( *tu );
add( tu, 0 );
}
decorators.reset();
}
void
test_suite::add( boost::shared_ptr<test_unit_generator> gen_ptr, decorator::collector& decorators )
{
std::pair<boost::shared_ptr<test_unit_generator>, std::vector<decorator::base_ptr> > toto(gen_ptr, decorators.get_lazy_decorators() );
m_generators.push_back(toto);
//m_generators.push_back(
// std::make_pair<boost::shared_ptr<test_unit_generator>, std::vector<decorator::base_ptr> >(gen_ptr, decorators.get_lazy_decorators() ) );
decorators.reset();
}
void
test_suite::generate( )
{
typedef std::pair<boost::shared_ptr<test_unit_generator>, std::vector<decorator::base_ptr> > element_t;
for(std::vector<element_t>::iterator it(m_generators.begin()), ite(m_generators.end());
it < ite;
++it)
{
test_unit* tu;
while((tu = it->first->next()) != 0) {
tu->p_decorators.value.insert( tu->p_decorators.value.end(), it->second.begin(), it->second.end() );
//it->second.store_in( *tu );
add( tu, 0 );
}
}
m_generators.clear();
#if 0
test_unit* tu;
while((tu = gen.next()) != 0) {
decorators.store_in( *tu );
add( tu, 0 );
}
#endif
}
//____________________________________________________________________________//
void
@ -455,6 +493,14 @@ auto_test_unit_registrar::auto_test_unit_registrar( test_unit_generator const& t
framework::current_auto_test_suite().add( tc_gen, decorators );
}
//____________________________________________________________________________//
auto_test_unit_registrar::auto_test_unit_registrar( boost::shared_ptr<test_unit_generator> tc_gen, decorator::collector& decorators )
{
framework::current_auto_test_suite().add( tc_gen, decorators );
}
//____________________________________________________________________________//
auto_test_unit_registrar::auto_test_unit_registrar( int )

View File

@ -263,6 +263,11 @@ unit_test_main( init_unit_test_func init_func, int argc, char* argv[] )
result_code = boost::exit_exception_failure;
}
BOOST_TEST_I_CATCH( std::logic_error, ex ) {
results_reporter::get_stream() << "Test setup error: " << ex.what() << std::endl;
result_code = boost::exit_exception_failure;
}
BOOST_TEST_I_CATCHALL() {
results_reporter::get_stream() << "Boost.Test framework internal error: unknown reason" << std::endl;

View File

@ -40,6 +40,7 @@ struct BOOST_TEST_DECL auto_test_unit_registrar {
auto_test_unit_registrar( test_case* tc, decorator::collector& decorators, counter_t exp_fail = 0 );
explicit auto_test_unit_registrar( const_string ts_name, const_string ts_file, std::size_t ts_line, decorator::collector& decorators );
explicit auto_test_unit_registrar( test_unit_generator const& tc_gen, decorator::collector& decorators );
explicit auto_test_unit_registrar( boost::shared_ptr<test_unit_generator> tc_gen, decorator::collector& decorators );
explicit auto_test_unit_registrar( int );
};

View File

@ -59,6 +59,8 @@ public:
void store_in( test_unit& tu );
void reset();
std::vector<base_ptr> get_lazy_decorators() const;
private:
BOOST_TEST_SINGLETON_CONS( collector )

View File

@ -177,9 +177,15 @@ public:
/// @overload
void add( test_unit_generator const& gen, decorator::collector& decorators );
/// @overload
void add( boost::shared_ptr<test_unit_generator> gen_ptr, decorator::collector& decorators );
//! Removes a test from the test suite.
void remove( test_unit_id id );
//! Generates all the delayed test_units from the generators
void generate( );
// access methods
@ -200,6 +206,8 @@ protected:
test_unit_id_list m_children;
children_per_rank m_ranked_children; ///< maps child sibling rank to list of children with that rank
std::vector< std::pair<boost::shared_ptr<test_unit_generator>, std::vector<decorator::base_ptr> > > m_generators; /// lazy evaluation
};
// ************************************************************************** //

View File

@ -186,6 +186,8 @@ test-suite "test-organization-ts"
[ boost.test-self-test run : test-organization-ts : test-tree-management-test ]
[ boost.test-self-test run : test-organization-ts : test-tree-several-suite-decl ]
[ boost.test-self-test run : test-organization-ts : test_unit-report-clashing-names ]
# cannot pass paramters from the command line, another test developed below
# [ boost.test-self-test run : test-organization-ts : dataset-master-test-suite-accessible-test : : --param1=1 --param2=2 : : : : $(requirements_datasets) ]
;
#_________________________________________________________________________________________________#
@ -322,6 +324,9 @@ exe smoke-ts-included-2 : smoke-ts/basic-smoke-test2.cpp ;
exe check-streams-on-exit : framework-ts/check-streams-on-exit.cpp ;
exe dataset-master-test-suite-accessible-test : test-organization-ts/dataset-master-test-suite-accessible-test.cpp
: $(requirements_datasets) ;
alias "smoke-ts"
:
[ boost.test-smoke-ts-logger bt-st-txml : XML : log ]
@ -336,6 +341,7 @@ alias "smoke-ts"
[ run smoke-ts-included : --version : : $(requirements_datasets) : cla-check-print-version ]
[ run check-streams-on-exit : --list_content --report_sink=stdout : : $(requirements_datasets) : cla-check-list-content-with-report-sink ]
[ run check-streams-on-exit : --list_content --report_sink=test.txt : : $(requirements_datasets) : cla-check-list-content-with-report-sink-2 ]
[ run dataset-master-test-suite-accessible-test : -- --param1=1 --param2=2 : : $(requirements_datasets) : dataset-lazy-generator ]
# : $(requirements_datasets)
;

View File

@ -0,0 +1,104 @@
// (C) Copyright Raffi Enficiaud 2018
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
// See http://www.boost.org/libs/test for the library home page.
//
/// @file
/// @brief tests the access to the master test suite from the datasets, trac 12953
// ***************************************************************************
#define BOOST_TEST_MODULE "Access master test suite arguments from datasets"
#include <boost/test/included/unit_test.hpp>
#include <boost/test/data/test_case.hpp>
#include <iostream>
class dataset_loader
{
public:
dataset_loader();
dataset_loader(std::initializer_list<std::string> init_list)
: m_input_extension(init_list)
{}
enum { arity = 1 };
struct iterator {
iterator(std::vector<std::string>::const_iterator const& v_iterator)
: m_iterator(v_iterator)
{}
std::string operator*() const { return *m_iterator; }
void operator++()
{
++m_iterator;
}
private:
std::vector<std::string>::const_iterator m_iterator;
};
boost::unit_test::data::size_t size() const {
return m_input_extension.size();
}
// iterator
iterator begin() const { return iterator(m_input_extension.begin()); }
private:
std::vector<std::string> m_input_extension;
};
dataset_loader::dataset_loader()
{
BOOST_TEST_INFO("dataset_loader");
int argc = boost::unit_test::framework::master_test_suite().argc;
char** argv = boost::unit_test::framework::master_test_suite().argv;
std::cout << "ArgCount = " << argc << '\n';
for(unsigned i = 1; i != argc; i++) { // not taking into account the name of the program
std::cout << "ArgValue = " << argv[i] << '\n';
m_input_extension.push_back(argv[i]);
}
}
//------------------------------------------------------------------------------
namespace boost { namespace unit_test { namespace data {
namespace monomorphic {
template <>
struct is_dataset<dataset_loader> : boost::mpl::true_ {};
}
}}}
BOOST_AUTO_TEST_SUITE( concrete_testsuite )
// parameters passed on the command line
char const* expected[] = {"--param1=1", "--param2=2"};
BOOST_DATA_TEST_CASE(cool_test,
boost::unit_test::data::make_delayed<dataset_loader>( ) ^ boost::unit_test::data::make(expected),
input, expect)
{
// ...
BOOST_TEST(input == expect);
}
BOOST_DATA_TEST_CASE(cool_test2,
boost::unit_test::data::make_delayed<dataset_loader>( ) ^ dataset_loader({"--param1=1", "--param2=2"}),
input, expect)
{
// ...
BOOST_TEST(input == expect);
}
BOOST_AUTO_TEST_SUITE_END()