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






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 関数の引数には、デフォルトのデフォルト引数の値を指定します。
サンプリングされた値がない場合に、使用されます。


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

2012年9月12日水曜日

Visual Studio 2012: C++ Unit Test Framework で iutest を使う(値のパラメータ化テストに対応)

Visual Studio 11 : C++ Unit Test Framework で Google Test を使ってみた(実験) の続きです。

以前、できなかったことが少しだけできるようになりました。
ただし、今回は iutest のみ対応がほとんどです。(gtest 対応はいずれ…)

テストフィクスチャの使用
TEST マクロに加えて TEST_F も使用できるようになりました。
ただし、SetUpTestCase/TearDownTestCase に対応していません。


~~~ ここから iutest のみの対応 ~~~
ログ出力対応
iutest のログ出力を Visutal Studio の出力ウィンドウに出すようにしました。

値のパラメータ化テスト対応
値のパラメータ化テスト(IUTEST_P)に対応しました。
いくつか制限がありますが、とりあえずテストできるようにはなりました。

  1. SetUpTestCase/TearDownTestCase 非対応
  2. 1つの TEST_METHOD ですべてのパラメータテストを実行する

このテストが、
// Param Test Range
class TestP : public iutest::TestWithParam<int>
{
protected:
    static int a;
    static int b;
public:
    static void SetUpTestCase(void)
    {
        a = 0;
        b = 0;
    }
};
int TestP::a = 0;
int TestP::b = 0;

IUTEST_INSTANTIATE_TEST_CASE_P(TestPInstance, TestP, iutest::Range<int>(0, 10));

IUTEST_P(TestP, TestA)
{
    IUTEST_ASSERT_EQ(a, GetParam());
    ++a;
}

1つの TEST_METHOD にまとまって




実行されます。




今回はここまで。
IUTEST_P のように強引にやれば、型付けテストもできるんじゃないかなと思ってます。
まぁ、できたから何って感じですが・・・

2012年9月9日日曜日

Jenkins のJNLPスレーブをちょっと便利にする方法

Jenkins で JNLP スレーブを使用していると、たま~に接続が切れたり、
ジョブが実行される度に例外で必ず失敗したり、なんかおかしくなったり…

そんな時は、リモートでスレーブマシンにログインして、スレーブを再起動させるのですが、
このひと手間がメンドイ!ってことで、こんなバッチファイル書いて使ってます。

@echo off

if "x%~1" == "x" (
    @echo スレーブ名を指定してください。
    goto error
)

rem Config
SET SLAVE_NAME=%~1
SET SLAVE_OPTION=-Xmx512m
SET JENKINS_COMPUTER_URL=http://localhost:8080/computer/
SET JNLP_PATH=%JENKINS_COMPUTER_URL%%SLAVE_NAME%/slave-agent.jnlp

:loop
@echo N | start /WAIT /REALTIME "%SLAVE_NAME%" "java" %SLAVE_OPTION% -jar slave.jar -jnlpUrl %JNLP_PATH%
ping localhost > NUL
goto loop

:end
exit /b 0

:error
exit /b 1

第一引数にスレーブ名を指定します。
スレーブを起動して、なんらかの理由でそれが終了した場合、再起動させます。

仕組みとしてはものすごく単純ですが、
これでスレーブが切断されてしまった場合に、勝手に再接続してくれます。

また、Jenkins のスレーブのページから「切断」メニューを押すことで、
スレーブマシンに入ることなくスレーブの再起動ができるようになるオマケ付き!