2012年9月30日日曜日

iutest v1.1.0 をリリースしました

ダウンロードはこちらから。
SourceForge もしくは github からソースコードの取得もできます。

v1.0 からの変更点
  • 追加機能
    • 例外の値を検査するアサーション
    • Pairwise
    • IUTEST_SKIP
    • Logger
    • ファイルパス操作補助クラス
  • 修正
    • 初期化関数が呼ばれていない場合に警告表示
    • CRT セキュリティ強化関数の失敗時ハンドラに対応
    • RTTI がない場合の型表示
    • IUTEST_*_NO_FATAL_FAILURE に空の statement を与えた場合の警告抑制
    • その他バグを修正

追加機能だけここで説明したいと思います。

例外の値を検査するアサーション
Google Test では、
*_THROW期待する例外が throw されることをテスト
*_ANY_THROWなんらかの例外が throw されることをテスト
*_NO_THROW例外が throw されないことをテスト
の3つのテスト方法があります。
今回、これに加えて throw された値の比較テストをできるようにしました。

static void ExceptionFunction(int i)
{
    switch( i )
    {
    case 1:
        throw 2;
        break;
    case 2:
        throw "error";
    case 3:
        throw ::std::string("error");
    default:
        break;
    }
}

IUTEST(AssertionTest, Exception)
{
    IUTEST_EXPECT_THROW_VALUE_EQ(ExceptionFunction(1), int, 2);
    IUTEST_EXPECT_THROW_VALUE_NE(ExceptionFunction(1), int, 0);

    IUTEST_ASSERT_THROW_VALUE_STREQ(ExceptionFunction(2), const char *, "error");
    IUTEST_ASSERT_THROW_VALUE_STREQ(ExceptionFunction(3), ::std::string, "error");
    IUTEST_ASSERT_THROW_VALUE_STRCASEEQ(ExceptionFunction(2), const char *, "Error");
}

対応しているのは、EQ/NE/STREQ/STRCASEEQ です。
本来ならばユーザーが例外をキャッチして値を検査するところを、簡単に記述できるようになりました。
こちらは gtest 実装を利用するモードでも使用できます。

Pairwise
複数のパラメータの組み合わせを生成する Combine に加えて、
オールペア法による組み合わせを生成する Pairwise を追加しました。
Pairwise を使用することで Combine よりも格段に少ないテスト回数で済むようになります。
オールペア法についての詳しい説明はここでは省きます。
(※ Pairwise は gtest 利用モードでは使用できません。)

IUTEST_SKIP
これまでは実行されるべきテストが実行されなかった場合に、
テスト結果を「成功」ではなく「スキップ」として出力するようにしていました。
(gtest はスキップ非対応。そのへんはGoogle Test を使ってみる - その6(Jenkins との連携、スキップ対応)で説明してます。)

今回、新たにユーザーがスキップできるように IUTEST_SKIP を追加しました。
実行環境に依存しているテストの実行/不実行の選択に使用することを想定しています。
すでにテストが失敗している場合はスキップになりません。
IUTEST(Foo, Bar)
{
    IUTEST_SKIP(); // この時点でテストをスキップして終了
    IUTEST_ASSERT_EQ(2, 3); // ここは実行されない
}

IUTEST(Foo, Baz)
{
    IUTEST_EXPECT_EQ(2, 3);
    IUTEST_SKIP(); // すでにテストが失敗しているのでスキップにはせず、終了
    IUTEST_EXPECT_EQ(2, 3); // ここは実行されない
}
(※ IUTEST_SKIP は gtest 利用モードではスキップせずに return するだけです。)

Logger
ログ出力のカスタマイズポイントとして用意しました。
Visual Studio Unit Test Framework 対応のために追加しましたが、他のケースでも利用できると思います。

ファイルパス操作補助クラス
カレントディレクトリの取得などをするクラスを追加しました。
gtest には同様なクラス(::testing::internal::FilePath)があります。

2012年9月26日水曜日

Persona Plugin があると「再起動せずにインストール」ができない

2013/05/13 追記
Version 2.3 (May 12, 2013) で修正されました。

以前、Persona Plugin について書きました。
が、どうやらこちらをインストールしていると、
プラグインを「再起動せずにインストール」ができないみたいです。

こちらがログ

hudson.util.IOException2: Failed to dynamically deploy this plugin
 at hudson.model.UpdateCenter$InstallationJob._run(UpdateCenter.java:1209)
 at hudson.model.UpdateCenter$DownloadJob.run(UpdateCenter.java:1019)
 at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
 at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
 at java.util.concurrent.FutureTask.run(Unknown Source)
 at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
 at java.lang.Thread.run(Unknown Source)
Caused by: hudson.util.IOException2: Failed to install selection-tasks-plugin plugin
 at hudson.PluginManager.dynamicLoad(PluginManager.java:397)
 at hudson.model.UpdateCenter$InstallationJob._run(UpdateCenter.java:1205)
 ... 7 more
Caused by: jenkins.ExtensionRefreshException: hudson.plugins.persona.random.RandomPersonaFinder@148410b doesn't support refresh
 at jenkins.model.Jenkins.refreshExtensions(Jenkins.java:2056)
 at hudson.PluginManager.dynamicLoad(PluginManager.java:390)
 ... 8 more

同様の報告がこちらにありますが、進展はないもよう。
[#JENKINS-12803] Installing plugin (without restarting) is broken with other non-refreshable extension.

Ruby-runtime Plugin でも同じ問題があったようですが、こちらは修正済み。
[#JENKINS-12091] Installing plugin (without restarting) is broken with ruby-runtime.


ダウンロードして再起動後にインストールは可能なので、
私個人としてはこの問題はあまり気にしていませんが。。。

jenkins をこれからドンドン使っていこう!って人(開発チーム)で、
気軽に jenkins の再起動ができないような場合は、
Persona は使わない方がよいのかもしれません。

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 で公開してます。