2013年4月29日月曜日

autest v1.0.0 をリリースしました

autest v1.0.0 をリリースしました。

autest は AutoIt スクリプトで Google Test のようなテストを記述できるようにするフレームワークです。

AutoIt を使用して GUI 操作の自動化をした時、実行環境やタイミングによって
予期せぬ失敗が起こることがあると思います。
autest は失敗した時に、ソースファイルと行数、期待値と実際の値を出力するので、
どこでどうして失敗したかが一目瞭然です。
[==========] Running 2 tests from 1 test cases.
[----------] 2 tests from Sample
[ RUN      ] Sample.Test
autest_test_test.au3:22 error: Value of 0
  Actual: 0
Expected: 1

[  FAILED  ] Sample.Test
[ RUN      ] Sample.Test2
autest_test_test.au3:26 error: Value of 0
  Actual: 0
Expected: 1

[  FAILED  ] Sample.Test2
[----------] 2 tests from Sample

これは特に Jenkins などで自動テストをする場合に便利です。
もちろん、テスト結果の xml 出力にも対応しています。

2013年4月22日月曜日

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

iutest v1.5.0 をリリースしました。

今回の変更点は以下のとおりです。
  • 日本語テスト名に対応
  • iutest_list_tests_with_where コマンドラインオプションを追加
  • MiniDump に対応(MSVC)
  • SetUpTestCase などのテスト実行中以外でも RecordProperty できるように変更
  • IUTEST_TYPED_TEST_CASE,IUTEST_TYPED_TEST_CASE_P に直接 ::iutest::Types を書けるように修正

また、IUTEST_PEEP 関係の仕様が変更になっていますので、ご注意ください。

あと、v1.5.0 で予定していた「テストの実行順序の制御」への対応は取り止めにしました。
実装は可能ですが、テストの実行順序を指定したいケースが思いつかなかったのと、
他のテストに依存したテストの書き方は良くないと判断して、取り止めにしました。

今後の予定
今回のリリースで iutest の開発をしばらく中断したいと思います。
まだ、Death テストやスレッドセーフへの対応など大きな対応項目がありますが、
Google Test がありますし、問題ないでしょう。

細かな修正などは気が向いた時にやると思います。

C 言語バージョンの iutest_c の開発は継続していきます。
次のバージョンで「値のパラメータ化テスト」に対応します。

2013年4月19日金曜日

[blogger] ラベル検索がうまく機能してない模様

自分の過去記事を見ていて気づいたのですが、ラベル一覧から「c++」を選択してもすべての記事が表示されません。
c++11」だと一件もヒットしません。

URL 的には 「http://srz-zumix.blogspot.jp/search/label/c%2B%2B」 のようになっていて、
多分 "+" にあたる "%2B" が無視されて検索されてる感じがします。(c++ で検索すると c がヒットしてるし)

サポートを見ても "+" が禁止とも書かれてませんし、前はちゃんと出ていた気がするんだけど…バグ?
うーん、どうだったか覚えてない…

ひとまず、様子見で…

2013年4月16日火曜日

[Jenkins] スレーブマシンの再起動をするジョブ(その2)

ブログズミ - [Jenkins] スレーブマシンの再起動をするジョブ で紹介したスクリプトをパワーアップさせました。

前回は、再起動したいスレーブでジョブを実行してマシンを再起動させていました。
今回は、再起動したいスレーブとは別のスレーブでジョブを実行し、
ターゲットスレーブのビルドが終わるのを待ってから、再起動させるようにしました。

import hudson.slaves.OfflineCause.SimpleOfflineCause
import hudson.util.RemotingDiagnostics

def jenkins = hudson.model.Hudson.instance

class OfflineMessage extends org.jvnet.localizer.Localizable {
  def message
  OfflineMessage() {
    super(null, null, [])
    def timestr = new Date().format("HH:mm dd/MM/yy z", TimeZone.getTimeZone("UTC"))
    this.message = "automated reboot at end of test at " + timestr
  }
  String toString() {
    this.message
  }
  String toString(java.util.Locale l) {
    toString()
  }
}

def cause = SimpleOfflineCause.create(new OfflineMessage())
def target_name = manager.build.buildVariables.get("REBOOT_SLAVE")

if ( target_name == null ) {
    manager.listener.logger.println("ERROR!! REBOOT_SLAVE is null.")
}

// 同一 PC 上のスレーブをオフライン
def slaves = jenkins.slaves
slaves.each {
    def com = it.toComputer()
    def name = com.getEnvironment().get("COMPUTERNAME","")
    if( name.compareToIgnoreCase(target_name) == 0 ) {
        if( com.isOnline() ) {
            // スレーブをオフラインにしてアイドル待ち
            com.setTemporarilyOffline(true, SimpleOfflineCause.create(new OfflineMessage()))
            // 実際にアイドル待ちするのは、全部オフラインにしてから
        }
    }
}

slaves.each {
    def com = it.toComputer()
    def name = com.getEnvironment().get("COMPUTERNAME","")
    def node_name = it.getNodeName()
    if( name.compareToIgnoreCase(target_name) == 0 ) {
        if( !com.isIdle() ) {
            manager.listener.logger.println("Wait ${node_name} Slave Task...")
            while( !com.isIdle() ) {
                Thread.sleep(3000)
            }
        }
    }
}

// reboot
def script = """

    if (Functions.isWindows()) {
      'cmd /c "shutdown /f /r /t 10 -m \\\\\\\\${target_name} /c \"Restarting after Jenkins test completed\""'.execute()
    }

"""

def computer = manager.build.getBuiltOn().toComputer()
def channel = computer.getChannel()
manager.listener.logger.println(script)
RemotingDiagnostics.executeGroovy( script, channel )

// 再起動待ち
Thread.sleep(5*60*1000)

// 再接続
slaves.each {
    def com = it.toComputer()
    def name = com.getEnvironment().get("COMPUTERNAME","")
    if( name.compareToIgnoreCase(target_name) == 0 ) {
        com.setTemporarilyOffline(false, com.getOfflineCause())
    }
}

こちらのコードは github にもアップしています。

2013年4月10日水曜日

[メモ] Jenkins の状態を調べる

Jenkins を使っていると色々とトラブルことがあります。
そんな時に覚えておきたい手法をメモっておきます。
(※あくまで個人用メモです。)

ログを見る
Jenkins の管理ページからシステムログを見ることができます。
http://yourjenkins:8080/log/all
こちらを見ることで例外が発生していないかなどを確認することができます。

GC ログを見る
Jenkins の起動オプションに以下のオプションを追加します。
-Xloggc:gc.log
すると、指定したファイルに GC のログが出力されます。
これを、gcviewer を使って表示するとこのようになります。


適切なメモリの設定については、あまり詳しくないので省きます。
(検索すればすぐにでてきます)
スレッドダンプ
Jenkins のスレッドダンプは、http://yourjenkins:8080/threadDumpの URL から見ることができます。

または、Jenkins のサーバーマシンにアクセスできるようであれば、
jstack を使って取得することもできます。

jstack pid
プロセスID は jenkins の java のプロセスID を入力します。
プロセスID は jps コマンドでも調べることができます。

Windows でサービスとして Jenkins を動かしている場合は、
PsTools の psexec を使用して jstack を実行してください。

psexec -s "%JAVA_HOME%\bin\jstack.exe" pid

取得したダンプの解析には、 というツールが良いらしい。
こんな感じで表示してくれます。


プロセスの監視
さらに詳しく状態を調べるには jconsole を使うのが良さげ。
コマンドプロンプトから jconsole を起動すると以下のような GUI が表示されるので、
Jenkins の java のプロセスを選択して接続します。

Windows サービスの場合は、psexec を使用して起動します。
psexec -s -i "%JAVA_HOME%\bin\jconsole.exe"

また、デフォルトの Jenkins の起動オプションでは接続できない(たぶん)ので、
起動オプションに以下のオプションを追加してください。(ポート番号は任意)
-Dcom.sun.management.jmxremote.port=8999 
-Dcom.sun.management.jmxremote.ssl=false 
-Dcom.sun.management.jmxremote.authenticate=false

jconsole が起動したら、リモートプロセスを選択し
localhost:8999
と入力し、接続ボタンを押してください。

接続するとこのようにメモリやスレッドなどの監視ができます。


検索する
最後に、上記方法で収集した情報をもとに同じような問題が報告されていないか(issues)、
ブログに書いている人はいないか、つぶやいている人はいないか、検索してみると良いでしょう。

2013年4月2日火曜日

特定範囲の乱数生成について

特定範囲の乱数を得るのに剰余を使うと偏るそうです。
乱数生成のアンチパターン - Faith and Brave - C++で遊ぼう
今まで特に気にせず剰余を使ってました…乱数の扱いって難しいんですよね…
というわけで、どれくらい偏るのか検証してみました。

コード(ideone)
#include <random>
#include <iostream>

const int N=6;
const int X=1000000;
int main(int, char**)
{
    int n1[N]={},n2[N]={};
    ::std::random_device rd;
    unsigned int s = rd();
    ::std::mt19937 g1(s),g2(s);
    for( int i=0; i < X; ++i )
    {
        n1[ g1()%N ]++;
        n2[ ::std::uniform_int_distribution<unsigned int>(0, N-1)(g2) ]++;
    }
    for( int i=0; i < N; ++i )
    {
        ::std::cout << n1[i] << ", " << n2[i] << ::std::endl;
    }
    return 0;
}

結果

偏ってる?よくわかりませんね。
少しコードを変えてみます。

コード2(ideone)
#include <random>
#include <iostream>
#include <algorithm>

const int N=6;
const int X=10000;
const int C=10000;

void test(::std::vector<int>& c, unsigned int seed, unsigned int (*f)(::std::mt19937&))
{
    ::std::mt19937 g(seed);
    int n[N] ={};
    for( int i=0; i < X; ++i )
    {
        n[ f(g) ]++;
    }
    int r[N] = {};
    for( int i=0; i < N; ++i ) r[i] = i;
    ::std::sort(r, r+N, [&](int a, int b){ return n[a] > n[b]; });
    for( int i=0; i < N; ++i )
    {
        c[r[i]] += N/2 - i;
    }
}

int main(int, char**)
{
    ::std::random_device rd;
    ::std::vector<int> c1(N), c2(N);
    for( int j=0; j < C; ++j )
    {
        unsigned int s = rd();
        test(c1, s, [](::std::mt19937& g){ return g()%N; });
        test(c2, s, [](::std::mt19937& g){ return ::std::uniform_int_distribution<unsigned int>(0, N-1)(g); });
    }
    for( int i=0; i < N; ++i )
    {
        ::std::cout << i << ": " << c1[i] << ", " << c2[i] << ::std::endl;
    }
    return 0;
}

結果


確かに、偏っている…ような気がします。(ホントは全然わかってない。)
ただ、この偏りは逆に利用できるのかもしれません。

例えば、テトリスを考えてみてください。
縦棒のミノを大きい数値に割り当てれば、なんとなくやりごたえが上がるかもしれません。

もしくは、
レアカードを大きい数値に割り当てれば、自然と引きにくくなるかもしれません。

…かも

まとめ
乱数生成器を作るのは(検索すればすぐに情報が出てきますし)、そんなに難しいことではないと思います。
ただ、乱数生成器が作れたからといって、乱数を正しく扱えるわけではありません。

というわけで、C++11 の distribution はホントありがたいです。