この記事は……
Boost Advent Calendar 2011の一環として書かれた。ボレロ村上さんから引き継いだ、10日目である。
Advent Calendarとは何か? については、例えば師走を楽しもう。技術系アドベントカレンダーの魅力とは - @ITを参照の事。
参加者を見回すと、STAR WARSに出てくるシスの暗黒卿ばりに黒のローブを引きずったC++闇の軍団ばっかりで非常に恐ろしい。しかし、話を振られてしまったら、参加するしかないじゃない! あなたも、私も……!
なお、当記事中で言及するBoostのバージョンは、記事執筆時点での最新バージョンであるVersion 1.48.0を前提としている。
BOOST_CHECK_EQUAL_COLLECTIONS()
マクロ
Boost.Testにおいて、コンテナに格納されている値を検証するにはBOOST_CHECK_EQUAL_COLLECTIONS()
マクロを利用する。他のマクロと同様、BOOST_WARN_EQUAL_COLLECTIONS()
, BOOST_REQUIRE_EQUAL_COLLECTIONS()
という、異なるlevelのマクロも存在する。この記事では便宜上、これら3つをひっくるめてBOOST_CHECK_EQUAL_COLLECTIONSと呼ぶ事にする。
詳細な使用法は、The UTF testing tools referenceのBOOST_<level>_EQUAL_COLLECTION
項を参照。
BOOST_TEST_DONT_PRINT_LOG_VALUE()
マクロ
BOOST_TEST_DONT_PRINT_LOG_VALUE()
マクロは、BOOST_CHECK_EQUAL()
等で検証した値をログに出力しない事を指定する物だ。BOOST_CHECK_EQUAL()
等で検証するオブジェクトは出力ストリームへのoperator<<
をサポートしている必要が有る。が、全ての型がそれをサポートしているという事はあり得ない。例えばstd::pair
がそれに当たる。そういった型をこのマクロで指定すると、テスト結果のログに、その型のオブジェクトの値は出力されなくなる。
詳細はTest log outputを参照。
BOOST_TEST_DONT_PRINT_LOG_VALUE()
対応BOOST_CHECK_EQUAL_COLLECTIONS
さて、BOOST_TEST_DONT_PRINT_LOG_VALUE()
マクロだが、困った事にBOOST_CHECK_EQUAL_COLLECTIONS()
マクロでのログ出力においては適応されない。これは困る。
という訳で、BOOST_TEST_DONT_PRINT_LOG_VALUE()
に対応したBOOST_CHECK_EQUAL_COLLECTIONS()
を作ってみる。
BOOST_CHECK_EQUAL_COLLECTIONS()
の実装を覗く
まず、BOOST_CHECK_EQUAL_COLLECTIONS()
がどのように値を出力しているのかを調べる。boost/test/test_tools.hppの272行目にBOOST_CHECK_EQUAL_COLLECTIONS()
の定義が在る。BOOST_EQUAL_COLLECTIONS_IMPL()
を呼び出すだけの簡単な物だ。
#define BOOST_CHECK_EQUAL_COLLECTIONS( L_begin, L_end, R_begin, R_end ) \
BOOST_EQUAL_COLLECTIONS_IMPL( L_begin, L_end, R_begin, R_end, CHECK )
次に、同ファイル262行目。BOOST_EQUAL_COLLECTIONS_IMPL()
の定義。BOOST_CHECK_EQUAL_COLLECTIONS()
の引数として渡した各iterator、L_begin, L_end, R_begin, R_endの4つは、boost::test_tools::tt_detail::equal_coll_impl()
に引数として渡されているのが分かる。関数の引数として使われているだけなので、これ以降の部分には注意しなくて良い。
#define BOOST_EQUAL_COLLECTIONS_IMPL( L_begin, L_end, R_begin, R_end, TL ) \
BOOST_TEST_TOOL_IMPL( check_impl, ::boost::test_tools::tt_detail::equal_coll_impl( \
(L_begin), (L_end), (R_begin), (R_end) ), "", TL, CHECK_EQUAL_COLL ), \
4, \
BOOST_STRINGIZE( L_begin ), BOOST_STRINGIZE( L_end ), \
BOOST_STRINGIZE( R_begin ), BOOST_STRINGIZE( R_end ) ) \
/**/
そして、equal_coll_impl()
。631行。値の出力は、単純にiteratorをdereferenceしているだけだと言う事が分かる。
template <typename Left, typename Right>
inline predicate_result
equal_coll_impl( Left left_begin, Left left_end, Right right_begin, Right right_end )
{
predicate_result res( true );
std::size_t pos = 0;
for( ; left_begin != left_end && right_begin != right_end; ++left_begin, ++right_begin, ++pos ) {
if( *left_begin != *right_begin ) {
res = false;
res.message() << "\nMismatch in a position " << pos << ": " << *left_begin << " != " << *right_begin;
}
}
// ((後は関係ないので略))
}
成る程、これじゃBOOST_TEST_DONT_PRINT_LOG_VALUE()
が何をやろうが意味がない。
BOOST_TEST_DONT_PRINT_LOG_VALUE()
の実装を覗く
では、BOOST_TEST_DONT_PRINT_LOG_VALUE()
はどうなっているのだろうか? このマクロが定義されているboost/test/test_tools.hppの340行目を見てみる。
#define BOOST_TEST_DONT_PRINT_LOG_VALUE( the_type ) \
namespace boost { namespace test_tools { \
template<> \
struct print_log_value<the_type > { \
void operator()( std::ostream&, the_type const& ) {} \
}; \
}} \
/**/
boost::test_tools::print_log_value
テンプレートクラスを、BOOST_TEST_DONT_PRINT_LOG_VALUE()
の引数で指定された型で特殊化しているだけだ。この特殊化するprint_log_value
は、std::ostream
の参照と、特殊化に用いた型のconst
参照を取る、何もしないoperator()
をメンバに持つ。BOOST_TEST_DONT_PRINT_LOG_VALUE()
の提供する機能から考えると、BOOST_CHECK_EQUAL()
等、同マクロの影響を受ける物は、内部でprint_log_value
を使って値を出力しているであろう事が容易に想像出来る。
BOOST_TEST_DONT_PRINT_LOG_VALUE()
の影響を受けるBOOST_CHECK_EQUAL_COLLECTIONSを実装する
という事は、boost::test_tools::tt_detail::equal_coll_impl()
内部での値の出力に、なんとかしてboost::test_tools::print_log_value::operator()
を適用出来ればいい、という事になる。Boost.Testの実装を元に、それを実現させた物が以下のコードとなる。
Boostのそれと衝突しないよう、aslib名前空間、及び"ASLIB_"というprefixを採用している。
#include <boost/test/test_tools.hpp>
#define ASLIB_BOOST_WARN_EQUAL_COLLECTIONS( L_begin, L_end, R_begin, R_end ) \
ASLIB_BOOST_EQUAL_COLLECTIONS_IMPL( L_begin, L_end, R_begin, R_end, WARN )
#define ASLIB_BOOST_CHECK_EQUAL_COLLECTIONS( L_begin, L_end, R_begin, R_end ) \
ASLIB_BOOST_EQUAL_COLLECTIONS_IMPL( L_begin, L_end, R_begin, R_end, CHECK )
#define ASLIB_BOOST_REQUIRE_EQUAL_COLLECTIONS( L_begin, L_end, R_begin, R_end ) \
ASLIB_BOOST_EQUAL_COLLECTIONS_IMPL( L_begin, L_end, R_begin, R_end, REQUIRE )
#define ASLIB_BOOST_EQUAL_COLLECTIONS_IMPL( L_begin, L_end, R_begin, R_end, TL ) \
BOOST_TEST_TOOL_IMPL( check_impl, aslib::boost::test_tools::tt_detail::equal_coll_impl( \
(L_begin), (L_end), (R_begin), (R_end) ), "", TL, CHECK_EQUAL_COLL ), \
4, \
BOOST_STRINGIZE( L_begin ), BOOST_STRINGIZE( L_end ), \
BOOST_STRINGIZE( R_begin ), BOOST_STRINGIZE( R_end ) ) \
/**/
namespace aslib { namespace boost { namespace test_tools { namespace tt_detail {
template <typename Left, typename Right>
inline ::boost::test_tools::predicate_result
equal_coll_impl( Left left_begin, Left left_end, Right right_begin, Right right_end )
{
using namespace ::boost::test_tools;
predicate_result res( true );
std::size_t pos = 0;
print_log_value<std::remove_cv<std::remove_reference<decltype( *left_begin)>::type>::type> lPrintLogValue;
print_log_value<std::remove_cv<std::remove_reference<decltype(*right_begin)>::type>::type> rPrintLogValue;
for( ; left_begin != left_end && right_begin != right_end; ++left_begin, ++right_begin, ++pos ) {
if( *left_begin != *right_begin ) {
std::ostringstream buf;
res = false;
res.message() << "\nMismatch in a position " << pos << ": ";
lPrintLogValue(buf, *left_begin);
res.message() << buf << " != ";
rPrintLogValue(buf, *right_begin);
res.message() << buf;
}
}
if( left_begin != left_end ) {
std::size_t r_size = pos;
while( left_begin != left_end ) {
++pos;
++left_begin;
}
res = false;
res.message() << "\nCollections size mismatch: " << pos << " != " << r_size;
}
if( right_begin != right_end ) {
std::size_t l_size = pos;
while( right_begin != right_end ) {
++pos;
++right_begin;
}
res = false;
res.message() << "\nCollections size mismatch: " << l_size << " != " << pos;
}
return res;
}
}}}}
以下に、上で作成したASLIB_BOOST_CHECK_EQUAL_COLLECTIONS()
、及びlevel違いの2つのマクロの動作を検証するBoost.Testコードを示す。INSERT_BOOST_ORIGINAL_MACRO_CHECKER
を#define
する事で、オリジナルのBOOST_CHECK_EQUAL_COLLECTIONS()
を用いたテストケースを追加出来る。その場合、std::pair<int, int>
を右辺値に取るoperator<<
が無い事に起因するコンパイルエラーが発生する事を確認出来る筈だ。
typedef std::vector<std::pair<int, int>> PVec;
namespace {
struct TestFixture {
TestFixture() {
for(int i = 1; i <= 5; ++i) expected.push_back(std::make_pair(i, i * 10));
actual = expected;
actual[1] = std::make_pair(actual[1].first, actual[1].second * 11);
}
PVec expected, actual;
};
}
BOOST_TEST_DONT_PRINT_LOG_VALUE(PVec::value_type);
BOOST_FIXTURE_TEST_SUITE(Test, TestFixture)
BOOST_AUTO_TEST_CASE(testWarn) {
boost::unit_test::unit_test_log.set_threshold_level(boost::unit_test::log_warnings);
ASLIB_BOOST_WARN_EQUAL_COLLECTIONS(std::begin(actual), std::end(actual), std::begin(expected), std::end(expected));
}
BOOST_AUTO_TEST_CASE(testCheck) {
ASLIB_BOOST_CHECK_EQUAL_COLLECTIONS(std::begin(actual), std::end(actual), std::begin(expected), std::end(expected));
}
BOOST_AUTO_TEST_CASE(testReq) {
ASLIB_BOOST_REQUIRE_EQUAL_COLLECTIONS(std::begin(actual), std::end(actual), std::begin(expected), std::end(expected));
}
#ifdef INSERT_BOOST_ORIGINAL_MACRO_CHECKER
BOOST_AUTO_TEST_CASE(testOrigin) {
BOOST_CHECK_EQUAL_COLLECTIONS(std::begin(actual), std::end(actual), std::begin(expected), std::end(expected));
}
#endif
BOOST_AUTO_TEST_SUITE_END()
実装には、見ての通りC++11のdecltype
を利用している。恐らくこれが、BOOST_CHECK_EQUAL_COLLECTIONSにBOOST_TEST_DONT_PRINT_LOG_VALUE()
の影響を与える事が出来ない理由だろう。decltype
を使わないと、iteratorをdereferenceした結果の型が分からず、boost::test_tools::print_log_value
に必要な、適切な型が決定出来ない。std::iterator_trais::reference
を使えば、C++03規格上でもiteratorをderefereceした結果の型を求める事が出来た。詳細は当記事のコメント欄を参照。iorateさん有り難う御座います。
今後、各ベンダのC++コンパイラのC++11準拠度が上がっていけば、オリジナルのBOOST_CHECK_EQUAL_COLLECTIONSもBOOST_TEST_DONT_PRINT_LOG_VALUE()
の影響を受けるよう修正されるのではないだろうか。
そんな訳で
Boost Advent Calendar 2011の10日目であるこの記事ではBOOST_TEST_DONT_PRINT_LOG_VALUE()
を適用出来るBOOST_CHECK_EQUAL_COLLECTIONSの実装を示した。
では、ここら辺で筆を置き、11日目担当であるiorateさんにバトンタッチしたい。
コード中の
> print_log_value::type>::type> lPrintLogValue;
> print_log_value::type>::type> rPrintLogValue;
を、
print_log_value::reference>::type> lPrintLogValue;
print_log_value::reference>::type> rPrintLogValue;
と変更して試してみると、確かに通りますね……iterator_traisの存在を完璧に忘れていた……orz
しかもremove_cvが要らなくなって若干見通しが良くなったという……orz
しかしそうなると、なんでBOOST_CHECK_EQUAL_COLLECTIONSはBOOST_TEST_DONT_PRINT_LOG_VALUE()の影響を受けない作りになってるんでしょうね……?
>remove_cvが要らなくなって
いることはないと思います
>いることはないと思います
あれ、ホントだ。
VC++2010でエラーが出たから付けた記憶が有るけど気の所為だったのかな……?
template
inline predicate_result
equal_coll_impl( Left left_begin, Left left_end, Right right_begin, Right right_end )
↓
template
inline predicate_result
equal_coll_impl( Left left_begin, Left left_end, Right right_begin, Right right_end )
「Boostのそれと衝突しないよう、aslib名前空間、及び"ASLIB_"というprefixを採用している。」のところのコードで「#include」と書いてあってインクルードするファイル名がありませんが、これは意図したものでしょうか。
vectorのテンプレート引数がなくなっているようです。
typedef std::vector> PVec;
↓
typedef std::vector > PVec;
このblogをホスティングしてるSeesaa blogが広告挿入の為に使用しているJavaScriptの不具合が原因でした。
問題となる広告挿入機能をオフにしたので、現在はマトモなコードとして見られる筈……