2014年4月28日月曜日

[Jenkins][管理者向け] 起動時トリガージョブ

Jenkins の管理者向けに Jenkins が起動したらメール通知するジョブを作りました。
(マシンの不調で再起動することが稀にあるので、その検知をするために作った)

まず、Jenkins が起動した時に "何か" させるために、Startup Trigger Plugin をインストールします。
Startup Trigger Plugin はその名の通り、Jenkins の master もしくは slave が起動したことをビルドトリガーとして使えるようにするプラグインです。

あとは、適当なジョブを作成して Startup Trigger で master の起動をトリガーとして設定。

ビルド後の処理にメール通知を設定したら終わりです。
これで Jenkins が起動するとメールが送られてくるようになりました。


ついでなので、Jenkins のログをメールに含めるようにしてみました。
Groovy postbuild Plugin で以下のコードを実行してください。
def jenkins = hudson.model.Hudson.instance
def logs = jenkins.logRecords
if( logs != null )
{
    manager.listener.logger.println("log: ")
    logs.each {
        manager.listener.logger.println(new Date(it.getMillis()))
        manager.listener.logger.println(it.getMessage())
    }
}

logs の部分を以下のようにすれば、ユーザーが作成したログレコーダーからでもログを取得できます。
def logs = jenkins.getLog().getLogRecorder('HOGEHOGE').getLogRecords()

2014年4月21日月曜日

[Jenkins] Email-ext plugin の Pre-send Script を使う

Email-ext plugin の Pre-send Script を使う機会があったので、備忘録として書き残しておきます。
(Jenkins 1.556, Email-ext plugin 2.37.2.2)

Pre-send Script とは?
Pre-send Script はメール送信前にメッセージなどの編集ができる機能です。そのまんまですね。

Pre-send Script では以下のオブジェクトが扱えます。
msgMimeMessage オブジェクト
loggerログ書き込みオブジェクト
buildビルドオブジェクト
cancelboolean, 送信キャンセル

msg はメールの内容を扱う MimeMessage オブジェクトです。宛先の追加やメッセージの追加などができます。
logger はログ出力用のオブジェクトです。logger.println でビルドログに出力できます。
build はビルド情報を扱うオブジェクトです。結果や変更点などビルドに関わる情報をここから取得できます。
cancel に true をセットすると、その名の通りメールの通知がキャンセルされます。

設定方法
拡張e-mail通知の「Advanced Settings...」を開くと「Pre-send Script」の項目があるので、ここの groovy でスクリプトを書き込みます。


注意が必要なのは Pre-send の前に宛先が空だと Pre-send Script は実行されず "An attempt to send an e-mail to empty list of recipients, ignored." でメールが送信されませんので、宛先を編集する場合は気をつけてください。
Examples
上流ビルドのコミッタ-にメール通知する
上流ビルドのコミッタ-にメール通知する方法 - akira0622's blog

特定のログが含まれる場合にメール通知をキャンセル
def log = build.getLog()
pattern = /test/
if( log=~pattern )
{
  logger.println("キャンセルした")
  cancel |= true
}

ユーザーがビルドを実行した場合にメール通知をキャンセル
def cause = build.getCause(hudson.model.Cause.UserIdCause.class)
if( cause )
{
  logger.println("キャンセルした")
  cancel |= true
}

定期ビルドの場合に宛先を追加
import javax.mail.Message.RecipientType
def cause = build.getCause(hudson.triggers.TimerTrigger.TimerTriggerCause.class)
if( cause )
{
  msg.addRecipients(RecipientType.TO, "hogehoge@hoge.co.jp")
}

その他の使い方は公式の Recipe を参照してください。
https://wiki.jenkins-ci.org/display/JENKINS/Email-ext+Recipes

2014年4月14日月曜日

[Jenkins] アップグレードのスケジューリング

Jenkins の管理者の方々は Jenkins 自体のアップグレードやプラグインのアップグレードなど、メンテナンス業務があると思います。
それを少し楽にしたって話です。少しです、少し。

何をしたかというと、「Jenkins を SafeRestart させるジョブ」を作っただけです。
SafeRestart は実行(ビルド)中のジョブがなければ、Jenkins を再起動させる機能です。

どう楽になったか
アップグレードをする場合、Jenkins を止める必要があります。この作業はできるだけ開発メンバーが作業中でない方が良いです。そうなると、アップグレードは夜中とか休日とかなることが多いと思います。
また、大きいプロジェクトや1ビルドがなが~いテストを持つ Jenkins の場合、止めるのにも時間がかかったりします。(強制中断という手もありますが・・・)

それだと、管理者は大変ですよね?

今回、「Jenkins を SafeRestart させるジョブ」を作ったことで、「SafeRestart のスケジューリング」ができるようになり、Jenkins の再起動を予約できるようになりました。
つまり、金曜に Jenkins の更新をダウンロード&インストールしておき(再起動はしてないので、アップグレード自体は未完)、開発メンバーがいない土曜深夜に SafeRestart を仕掛けておけば、日曜日には再起動がかかって無事アップグレードが完了。
そして月曜日にはいつもどおり CI が回せるというわけです。

これで管理者である私は休日出勤しなくて済むようになりました。
SafeRestart させるジョブの作り方
いろいろ方法があると思います。
SafeRestart 自体は、Jenkins の URL + /safeRestart(http://~/safeRestart)を開くだけでもできますし、SafeRestart Plugin ってのもあります。

今回は、みんな大好き Groovy postbuild Plugin を使った方法を紹介します。

1. プロジェクトを作る
2. 「ビルド後の処理の追加」から「Groovy Postbuild」を追加
3. 以下のスクリプトを入力
def jenkins = hudson.model.Hudson.instance
jenkins.doSafeRestart()
4. ビルド・トリガに再起動させたい日時を設定する(ビルド中であることも考慮して早めに設定しておくといいかも)

設定は以上です。これで Jenkins の管理が楽になりますね。

※ Windows の場合、サービスで実行してないとダメなようです。(その他の OS は確認してません)

2014年4月7日月曜日

C++ Testing Framework の Catch を使ってみた



というわけで、試した。
チュートリアル
まずは、簡単なサンプルを書きました。
Catchgithub から clone してきたのを使用しました。Catch はヘッダーのみで使えるので、ライブラリのビルドは不要。パスを通して include するだけで OK です。
#define CATCH_CONFIG_MAIN // main の定義を catch に任せる
#include <catch.hpp>

int f()
{
    return 1;
}

TEST_CASE("Test", "[sample]")
{
    CHECK( f() == 2 );
    REQUIRE( f() <= 0 );
}

実行すると以下のように出力されます。
-------------------------------------------------------------------------------
Test
-------------------------------------------------------------------------------
main.cpp(9)
...............................................................................

main.cpp(11): FAILED:
  CHECK( f() == 2 )
with expansion:
  1 == 2

main.cpp(12): FAILED:
  REQUIRE( f() <= 0 )
with expansion:
  1 <= 0

===============================================================================
1 test case - failed (2 assertions - both failed)

REQUIRE マクロで式を渡しても、 f() の結果が出力されるのはイイですね!

ドキュメント
さて、ここからはもう少し踏み入って説明したいと思います。
と言っても、Catch のドキュメントを見ながらやったことを書き綴るだけですので、公式ドキュメント以上のことはあまり書いてないと思います。その点ご了承くださいm(__)m

アサーションレベル(フレーバー)
まずは、アサーションのレベル(フレーバー)の種類から。
LevelTest Case Fails?Aborts Execution?
REQUIREYesYes
CHECKYesNo
REQUIRE は失敗した場合に以降の処理を中断、 CHECK は継続します。
アサーションレベルについては、Boost.Test のそれと似ています。Boost.Test - Assertion Levels
Google Test で言うなら、REQUIRE=ASSERT, CHECK=EXPECT という感じです。

アサーション
続いて、アサーションの種類を紹介します。
(REQUIRE を列挙しますが、CHECK もあります。)

Macro概要sample
REQUIRE(expression)expression が真であることを検証REQUIRE( f() == 1 )
REQUIRE_FALSE(expression)expression が偽であることを検証REQUIRE_FALSE( false )
REQUIRE_THROWS(expression)expression が例外を投げることを検証REQUIRE_THROWS(throw 1)
REQUIRE_THROWS_AS(expression, type)expression が例外(type)を投げることを検証REQUIRE_THROWS_AS(throw 1, int)
REQUIRE_NOTHROW(expression)expression が例外を投げないを検証REQUIRE_NOTHROW(1)
REQUIRE_THAT(lhs, matcher call)lhs が matcher を満たすかを検証REQUIRE_THAT("hoge fuga piyo", StartsWith("hoge"))

必要な機能が全部揃ってる感じですね!すばらしい!
THAT で使える matcher はこちらです。

name概要
AllOfすべての matcher が真
AnyOfいづれかの matcher が真
Equals文字列の一致
Contains文字列の部分一致
StartsWith文字列の前方一致
EndsWith文字列の後方一致

テストケースの作成
テストの入り口となる部分の記述方法です。
Catch では、TEST_CASE と SECTION マクロを使って記述します。

TEST_CASE の例:
TEST_CASE("Test", "[sample]")
{
}

TEST_CASE マクロの第一引数はテストの名前です。第二引数にはタグを記述できます。タグについては後述します。TEST_CASE だけでもテストは書けますが、SECTION を使うことで setup/teardown を表現できます。

SECTION を使った例:
TEST_CASE("Test", "[sample]")
{
    int a=0;
    REQUIRE(a == 0); // 1
    puts("1");

    SECTION("A")
    {
        // 2
        puts("2");
        REQUIRE(a == 0);
        ++a;
        REQUIRE(a == 1);
    }
    SECTION("B")
    {
        // 3
        puts("3");
        REQUIRE(a == 0);
        ++a;

        SECTION("BB")
        {
            // 4
            puts("4");
            REQUIRE(a == 1);
        }
        // 5
        puts("5");
        REQUIRE(a == 1);
    }
    SECTION("C")
    {
        // 6
        puts("6");
        REQUIRE(a == 1);
    }
    // 7
    puts("7");
    puts("---------------");
}

1
7
---------------
1
2
7
---------------
1
3
5
7
---------------
1
3
4
5
7
---------------
1
6

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
catch_sample.exe is a Catch v1.0 b32 host application.
Run with -? for options

-------------------------------------------------------------------------------
Test
  C
-------------------------------------------------------------------------------
main.cpp(46)
...............................................................................

main.cpp(75): FAILED:
  REQUIRE( a == 1 )
with expansion:
  0 == 1

1
7
---------------
===============================================================================
1 test case - failed (12 assertions - 1 failed)
最後に 1->7 と実行されてるのはバグか?

setup/teardown が同じブロックの中に流れに沿って書けるのため、わかりやすいです。
また、SECTION はネスト可能で、上の場合 Test, Test/A, Test/B, Test/B/BB, Test/C が実行されます。
各セクションは以下の順で実行されます。
Test1 -> 7
Test/A1 -> 2 -> 7
Test/B1 -> 3 -> 5 -> 7
Test/B/BB1 -> 3 -> 4 -> 5 -> 7
Test/C1 -> 6 -> 7

BDD-style の記述方法もありますが、今回は説明を省きます。

タグ
タグは Google Test にはない概念です。
テストにはタグを付けることができます。
TEST_CASE("Test", "[sample]")
[sample] がタグで、タグは "[]" で囲むことになっています。
タグは1つのテストに複数付けることもできます。
TEST_CASE("Test", "[sample][system][io]")

タグを付けることによって、指定のタグがついたテストだけ実行などができ、テストの分類に役立ちます。
任意のテストのみを実行する方法は次のコマンドライン引数の項で説明します。

コマンドライン引数

オプション概要
[filter ...]テスト選択フィルター
--reporter-rconsole
xml
junit
リポート形式
--break-b-テストが失敗した際に DebugBreak を発生させます
--success-s-成功したテストも表示する
--abort-a-REQUIRE レベルの検証に失敗した場合に abort します
--abortx-x<n>REQUIRE レベルの検証に n回 失敗した場合に abort します
--list-tests-l-テストを列挙します
--list-tags-t-タグを列挙します
--list-reporters---reporter で使用可能なリポーターを列挙します
--out-o<filename>出力をファイルに書き出します
--name-n<name>テストに名前をつけます(デフォルトは実行ファイル名)
--nothrow-e-例外検証の失敗を無視します e.g. REQUIRE_THROWS
--warn-wNoAssertions警告を表示します
--durations-d<yes|no>テスト時間を出力するか指定します
--help-h
-?
-ヘルプ表示
太字=デフォルト

実行するテストの選択
テストのフィルタリングはオプション無しで指定します。

・ワイルドカードが使用可能
sample.exe Test*
・複数指定可能
sample.exe Test2 Test3
・タグ指定可能
sample.exe [sample]
・除外指定可能
exclude: または ~ を先頭に付けることで除外。
sample.exe exclude:Test2
sample.exe ~Test2
sample.exe ~[sample]
・複数タグ指定(AND)
[sample] かつ [test]
sample.exe [sample][test]
・複数タグ指定(OR)
[sample] または [test]
sample.exe [sample],[test]

Jenkins で集計
--reporter で junit を選択できるので Jenkins で集計可能です。
--reporter junit のみだと標準出力に出力されるので、ファイルに出力する場合はリダイレクトするか --out オプションを使用します。
sample.exe -r junit -o sample.xml

--warn
--warn オプションで指定できるのは現在 NoAssertions のみです。
これはセクション内に1つもアサーションが記述されていない場合に警告を出力します。

TEST_CASE("NoTest1", "[no]")
{
}

TEST_CASE("NoTest2", "[sample]")
{
    REQUIRE(true);
    SECTION("A", "[no]")
    {
    }
}

-------------------------------------------------------------------------------
NoTest1
-------------------------------------------------------------------------------
main.cpp(29)
...............................................................................


No assertions in test case 'NoTest1'

-------------------------------------------------------------------------------
NoTest2
  A
-------------------------------------------------------------------------------
main.cpp(33)
...............................................................................


No assertions in section 'A'

シャッフルテスト
Google Test や Boost.Test にあるシャッフルテストができないようです。
個人的にこれは欲しいので、ちょっと残念です。

まとめ
私は Google Test に慣れているので Catch ではできない機能がある点と、若干記述方法が気になるくらいで、
トータル的にCatch イイ感じです。


今回説明を省いた、BDD-style や test-fixture などについては、そのうちまたブログにまとめようと思います。

2014年4月1日火曜日

4月も始まり

今年も新入社員が入ってきました。毎年毎年新人が入ってきますが、出ていく人も毎年います。

今年も数名会社を去っていきます(ました)。私が入社してから10名以上去っていきましたが、今年はちょっと寂しさのようなものを感じました。
私も今の会社に入って四年。こう思うのも当然でしょうか。。。

何が言いたいのかというと、今の会社も長くなってきたのでそろそろ内も外も色々と目線を変えて見ていく必要があるのかな~と感じた今日このごろでした。
というわけで、社会人8年目はりきってスタートです!