2012年9月20日木曜日

Google Test の出力XMLに日本語を出力する

Google Test で日本語を出力したり、日本語テスト名(VC)を使うと XML に文字化けした状態で出力されてしまいます。
#ifdef _MSC_VER
TEST(Japanese, テスト)
{
    FAIL() << "テスト";
}
#endif

出力されたXML
<testsuite name="Japanese" tests="1" failures="1" disabled="0" errors="0" time="0.001">
    <testcase name="eXg" status="run" time="0" classname="Japanese">
      <failure message="Failed" type=""><![CDATA[samples.cpp:16
Failed]]></failure>
    </testcase>
  </testsuite>
テスト名「テスト」が「eXg」になってしまっています。(先行バイトが抜けてる)
FAIL() で出力した「テスト」メッセージも無き者にされてしまっています。

今回は日本語出力ができるようにソースコードを変更します。
改変

文字コード変換
まずは、MBS を UTF-8 に変換します。(XML が UTF-8 で出力しているので)
幸い、ワイド文字列から UTF-8 への変換関数はあるのでそちらを利用します。

gtest.cc
--- src.orig/gtest.cc 2011-04-15 12:49:12.000000000 +0900
+++ src/gtest.cc 2012-09-18 21:50:15.185407700 +0900
@@ -1525,6 +1525,27 @@
   return StringStreamToString(&stream);
 }
 
+String MultiByteStringToUtf8(const char* str, int num_chars) {
+  if (num_chars == -1)
+    num_chars = static_cast<int>(strlen(str));
+
+  ::std::stringstream stream;
+  for (int i = 0; i < num_chars; ++i) {
+    wchar_t wc=0;
+    int len = mbtowc(&wc, str+i, MB_CUR_MAX);
+    if( len > 1 )
+    {
+      stream << WideStringToUtf8(&wc, 1);
+      i += len-1;
+    }
+    else
+    {
+      stream << str[i];
+    }
+  }
+  return StringStreamToString(&stream);
+}
+ 
 // Converts a wide C string to a String using the UTF-8 encoding.
 // NULL will be converted to "(null)".
 String String::ShowWideCString(const wchar_t * wide_c_str) {
gtest-internal-inl.h
--- src.orig/gtest-internal-inl.h 2011-04-15 12:49:12.000000000 +0900
+++ src/gtest-internal-inl.h 2012-09-18 21:48:43.489163000 +0900
@@ -221,6 +221,7 @@
 // (i.e. outside of Unicode range U+0 to U+10FFFF) it will be output
 // as '(Invalid Unicode 0xXXXXXXXX)'.
 GTEST_API_ char* CodePointToUtf8(UInt32 code_point, char* str);
+GTEST_API_ String MultiByteStringToUtf8(const char* str, int num_chars);
 
 // Converts a wide string to a narrow string in UTF-8 encoding.
 // The wide string is assumed to have the following encoding:
ヘッダに宣言を追加しておきます。


XML の処理を修正
XML に出力するか判断する前に UTF-8 に変換しておきます。
@@ -3124,6 +3145,9 @@
 String XmlUnitTestResultPrinter::EscapeXml(const char* str, bool is_attribute) {
   Message m;
 
+  String tmp = MultiByteStringToUtf8(str, -1);
+  str = tmp.c_str();
+
   if (str != NULL) {
     for (const char* src = str; *src; ++src) {
       switch (*src) {
@@ -3255,7 +3279,8 @@
               << "\" type=\"\">";
       const string location = internal::FormatCompilerIndependentFileLocation(
           part.file_name(), part.line_number());
-      const string message = location + "\n" + part.message();
+      const string message_raw = location + "\n" + part.message();
+      const string message = MultiByteStringToUtf8(message_raw.c_str(), -1);
       OutputXmlCDataSection(stream,
                             RemoveInvalidXmlCharacters(message).c_str());
       *stream << "</failure>\n";

先行バイトを許可する
最後に先行バイトを許可します。
(厳密にやるなら本当に先行バイトかどうか、後続バイトも調べるべきかも)
@@ -3022,7 +3043,7 @@
 
   // May c appear in a well-formed XML document?
   static bool IsValidXmlCharacter(char c) {
-    return IsNormalizableWhitespace(c) || c >= 0x20;
+    return IsNormalizableWhitespace(c) || c >= 0x20 || c < 0x00;
   }
 
   // Returns an XML-escaped copy of the input string str.  If

テスト側の修正
テストを実行する前に、setlocale( LC_ALL, "" ); をしてください。
Visual Studio だと、起動時に setlocale( LC_ALL, "C" ); を実行するので、
mbtowc が期待どおりに動作しません。

以上で、日本語も XML に出力できるようになるはずです。

ソース/パッチ

github で公開してます。






2012年9月17日月曜日

[Cppcheck] ソースにエラー無視指定コメントを書く

Cppcheck を使ってみた
や、
Cppcheck + Visual Studio
で、書いてなかったのですが(忘れてた)、
自分でもちょこちょこ忘れて検索しているので、備忘録としてブログに書いておきます。

inline-suppr
以下のようなコメントをエラー報告のある行の前に記述します。
// cppcheck-suppress <error ID>
(error ID は syntaxError や uninitMemberVar などです。)

そして、cppcheck のコマンドライン引数に "--inline-suppr" を追加します。
以上で、コメントで書いた箇所の警告が無視されるようになります。


2012年9月13日木曜日

引数の値を集計してデフォルト引数に反映させる(動的デフォルト引数)

[C++]デフォルト引数の値を動的に変える

動的デフォルト引数で何か面白いことできないかな、と思って作ってみました。

github
ideone

#include <iostream>
#include "arggregate.hpp"

// 集計カテゴリの定義(現状は行ごとにカテゴリ化される。)
typedef ::arggregate::ArggregateCategory(test)  Arggregate1;
typedef ::arggregate::ArggregateCategory(test)  Arggregate2;

typedef ::arggregate::Min<int, ::arggregate::Max<int> > MinMax;

void f1(Arggregate1::Decl< int, ::arggregate::Min<int> >::Type x=Arggregate1::Value< int, ::arggregate::Min<int> >(1000) )
{
    if( x.is_default() ) ::std::cout << x << ::std::endl;
}

void f2(Arggregate2::Decl< int, MinMax >::Type x1=Arggregate2::Value< int, ::arggregate::Min<int> >(-1)
    , Arggregate2::Decl< int, MinMax >::Type x2=Arggregate2::Value< int, ::arggregate::Max<int> >(1000) )
{
    ::std::cout << x1 << ":" << x2 << ::std::endl;
}

int main(void)
{
    {
        ::std::cout << "----- f1 ----- " << ::std::endl;

        f1();   // 集計情報 0 のため 通常デフォルト引数が渡される
        for( int i=10; i > 0; --i )
        {
            f1(i);  // 通常使用
        }
        f1();   // 集計した結果の最小値がデフォルト引数となる
    }
    
    {
        ::std::cout << "----- f2 ----- " << ::std::endl;
        
        f2();   // 集計情報 0 のため 通常デフォルト引数が渡される
        for( int i=10; i > 0; --i )
        {
            f2(i);  // 第一引数のみ指定、第二引数にも集計結果が反映される
        }
        f2();   // 集計した結果の最小、最大値がデフォルト引数となる
    }

    return 0;
}




カテゴリ
typedef ::arggregate::ArggregateCategory(test)  Arggregate1;
カテゴリの定義をします。こちらのカテゴリごとに情報が集計されます。
現時点では、__LINE__ ごとにカテゴリが分けられます。
(※とりあえずの実装です。将来的に変更する予定です。)

引数の型
Arggregate1::Decl< int, ::arggregate::Min<int> >::Type
集計用の引数の型です。

Decl template の1つ目に本来の型を指定します。
2つ目に集計を行うクラスを指定します。
上の例では、Min と Min, Max を組み合わせた MinMax を使用しています。
(※現時点では、Min,Max のみです。今後増やす予定)

デフォルト引数の値
Arggregate1::Value< int, ::arggregate::Min<int> >(1000)
デフォルト引数の値を指定します。

2つ目の template に指定するのは単一の集計クラスです。(Min と Max を同時指定はできない)
Min の場合、今までに渡された値の最小値をデフォルト引数として使用します。

Value 関数の引数には、デフォルトのデフォルト引数の値を指定します。
サンプリングされた値がない場合に、使用されます。


最後に
なんとなく使えそうな気もするが、どういうときに使えばいいかサッパリ。