Hatena::Groupsubtech

NaN days

ブログを移転しています。最新の記事は motemen.hatenablog.com へどうぞ

2013-08-14

なぜかときどき落ちるテストをリカバリーする undead

| 19:26 | なぜかときどき落ちるテストをリカバリーする undead - NaN days を含むブックマーク はてなブックマーク - なぜかときどき落ちるテストをリカバリーする undead - NaN days

動的にポートを確保しているとか PhantomJS を使っているテストが、手元では大丈夫なのに Jenkinsなぜか(!)ときどき(!)落ちるという現象に際し、毎回ログを見て「ああこのエラーなら問題ないからやり直そう……」というのも馬鹿らしいので、標準(エラー)出力を監視しつつ Perl を実行し、特定の出力を吐いて異常終了していたらもう一度やり直すラッパーを書きました。

https://github.com/motemen/undead

使い方

.undeadrc.pl に以下のような内容を記述して、

{
    pattern => {
        stderr => [
            qr/^PhantomJS has crashed\./,
        ]
    },
    count => 20,
};

起動します。

% undead perl -le 'die "PhantomJS has crashed." if rand() < 0.9; print "alive!"'

指定された出力に引っかかって死んだ時に、指定した回数までコマンドの実行をやり直してくれます。

# [undead] err - PhantomJS has crashed. at -e line 1.
# [undead] Retry 'perl -le die "PhantomJS has crashed." if rand() < 0.9; print "alive!"' (19 remains)
# [undead] err - PhantomJS has crashed. at -e line 1.
# [undead] Retry 'perl -le die "PhantomJS has crashed." if rand() < 0.9; print "alive!"' (18 remains)
# [undead] err - PhantomJS has crashed. at -e line 1.
# [undead] Retry 'perl -le die "PhantomJS has crashed." if rand() < 0.9; print "alive!"' (17 remains)
# [undead] err - PhantomJS has crashed. at -e line 1.
# [undead] Retry 'perl -le die "PhantomJS has crashed." if rand() < 0.9; print "alive!"' (16 remains)
# [undead] err - PhantomJS has crashed. at -e line 1.
# [undead] Retry 'perl -le die "PhantomJS has crashed." if rand() < 0.9; print "alive!"' (15 remains)
# [undead] err - PhantomJS has crashed. at -e line 1.
# [undead] Retry 'perl -le die "PhantomJS has crashed." if rand() < 0.9; print "alive!"' (14 remains)
alive!

undeadperl

"undead command args" として起動する以外に、実行スクリプトの名前を "uneadprog" (例えば undeadperl)に変えれば、prog を起動するようになります。

% undeadperl -le '…'

prove で使うときは HARNESS_PERL を指定すると便利です。

% HARNESS_PERL=./script/undeadperl prove …

これで(だいぶ雑に)CI を安定化させられますね!

2013-02-19

テストが失敗したときに再試行できるようにするモジュールを書きました

| 13:22 |  テストが失敗したときに再試行できるようにするモジュールを書きました - NaN days を含むブックマーク はてなブックマーク -  テストが失敗したときに再試行できるようにするモジュールを書きました - NaN days

Selenium を使ったときなどテストの状態が外部に依存しているような場合、状態が確実に変化するのを待つために sleep などしてみているけれどやりたいのはそういうことじゃないんだ! という訳で、テストが失敗したら少しウェイトを入れて再試行するモジュールを書きました。

GitHub - motemen/perl5-Test-Retry: Retry a code block for several times until a test function succeeds


無意味な例ですが、

use Test::More;
use Test::Retry max => 3, delay => 0.1; # exports retry_test(&)
my $x = 0;

retry_test {
    is $x++, 2, '$x++ == 2';
};

retry_test のブロック内でのテストが失敗しそうな場合、デフォルトでは 0.5 秒待機して最大 5 回リトライします。テストが成功する場合にはそのまま次のへ実行を移し、最大試行数を越えた場合は失敗します。

上の例だと、以下のような出力になります。

# test '$x++ == 2' failing; retry (2 remaining)
#          got: '0'
#     expected: '2'
# test '$x++ == 2' failing; retry (1 remaining)
#          got: '1'
#     expected: '2'

ok 1 - $x++ == 2

また、いちいちブロックで囲んでらんないという人のために、既存のテスト関数を書き換えるような機能も提供しています。

use Test::Retry override => [ 'is' ];

# または以下; prototype ごと変わるので BEGIN で
BEGIN { Test::Retry->override('is') }

is { func_with_some_random_lag(), $expected };

テスト関数に渡される引数を返すブロックを渡すと、以下と同じ動作をします。

retry_test {
    is func_with_some_random_lag(), $expected;
};

たとえば Selenium::Remote::Driver を使ったテストだと

$driver->find_element(...)->click;

retry_test {
    is $driver->get_title, ...;
};

みたいな感じにして、ページ読み込みを待つ手間が省けます。

どうぞご利用ください。

2013-02-15

jQuery のメソッドチェーン呼び出しのコードを簡単に生成できる Perl モジュールを書きました

| 13:25 | jQuery のメソッドチェーン呼び出しのコードを簡単に生成できる Perl モジュールを書きました - NaN days を含むブックマーク はてなブックマーク - jQuery のメソッドチェーン呼び出しのコードを簡単に生成できる Perl モジュールを書きました - NaN days

PhantomJS 1.8 のリリースで まっとうな方法で Perl から PhantomJS の操作ができるようになった おかげで 去年の YAPC::Asia 2012 で発表した Wight はめでたくオワコン化しました……。だけれど Wight のオマケで作っていたモジュールは便利なので、別に分けてみました。

GitHub - motemen/String-jQuery: Easy generating of jQuery expressions by Perl codes in similar form

SYNOPSIS にある通りですが、このモジュールがエクスポートする jQuery() という関数を起点にして、JavaScriptjQuery を使うようにしてメソッドチェーンを繋ぎ、最後に文字列化すると JavaScript における jQueryAPI 呼び出しとして使える式が得られます。Perl のコード中に文字列で JS を埋め込むよりは読みやすいはず……です。

以下の例を見るのが分かりやすいと思います。.'length' あたりがポイントですね( ╹◡╹)

jQuery();                           # => 'jQuery()'
jQuery('a');                        # => 'jQuery("a")'
jQuery(\'document');                # => 'jQuery(document)'
jQuery('a')->text();                # => 'jQuery("a").text()'
jQuery('a')->text('xxx');           # => 'jQuery("a").text("xxx")'
jQuery('a')->click(sub { e => 'return false' });
                                    # => 'jQuery("a").click(function (e) { return false })'
jQuery('a').'length';               # => 'jQuery("a").length'
jQuery->ajax({ method => 'POST' }); # => 'jQuery.ajax({"method:"POST"})'

Selenium::Remote::Driver を使うにしても、ページ内要素の調査を行うのに jQuery は便利だと思います(このモジュールが jQuery の埋め込みを行うわけではありません)。

my $result = $driver->execute_javascript(
    'return ' . jQuery('#content a')->attr('href')
);

似たようなモジュールには HTML::JQuery というのがありますが、こちらは script 要素全体を出力するもののようです。

2012-10-05

carton bundle を高速化する

| 21:17 | carton bundle を高速化する - NaN days を含むブックマーク はてなブックマーク - carton bundle を高速化する - NaN days

2013-02-08: Carton 本体に bundle 時にデフォルトで carton.lock を参照するような変更 が入りました。以下の内容は古くなってると思います


carton を使ってるプロジェクトで tarball をリポジトリに同梱している場合、依存モジュールが増えるたびに carton bundle することになるのだけど、モジュールの数が増えてくるとこれがすこぶる遅くなってしまう。なんでかというと (cpanfile や Makefile.PL を元に) 依存関係をチェックしなおしているからで、その結果 tarball 追加するだけのつもりが carton.lock まで更新されてしまった、というような事態にもなったりする。

これらの挙動を自分好みというか気楽なふうにするため、--locked というオプションを実装してみました。

https://github.com/motemen/carton/tree/bundle-locked

carton bundle --locked とすると、(cpanfile ではなく) carton.lock の内容をもとに tarball をダウンロードするようになります。こうすることで

  • carton.lock が更新されない
  • carton.lock に記録されているモジュールは依存関係などのチェックがすんでいるので、単純にダウンロードだけを行える

というメリットがあります。

ちなみに [2012-10-24: 以降の内容は bundle-locked-download ブランチに移動しました] cpanminus を起動するときに (本家にはない) --download というオプションが指定されていて、ふつうのバージョンを使っている場合には無視されるのですが、本家にあった pull-req (https://github.com/miyagawa/cpanminus/pull/118) に1コミット追加して (https://github.com/motemen/cpanminus/tree/download) 最新の master にマージしたもの (https://github.com/motemen/cpanminus) を使うと便利なオプションで、--download を指定すると

  • tarball の展開をしない (もともとの pull-req の内容)
  • すでに同名の tarball が存在している場合、ダウンロードしない

という挙動になり、IO が劇的に減るため、2 回目以降の carton bundle が高速化できます (数秒で完了するようになります)。

2012-10-03

Test::Deep で JSON をパーズした結果と比較できる Test::Deep::JSON というのを書きました

| 13:56 | Test::Deep で JSON をパーズした結果と比較できる Test::Deep::JSON というのを書きました - NaN days を含むブックマーク はてなブックマーク - Test::Deep で JSON をパーズした結果と比較できる Test::Deep::JSON というのを書きました - NaN days

すぐに使いたくなったので、さきほど shipit いたしました。https://metacpan.org/release/MOTEMEN/Test-Deep-JSON-0.01/

https://github.com/motemen/Test-Deep-JSON

適当な hashref があって、その一部が JSON 形式文字列になっているようなとき (HTTP API のテスト時など) に code() でなんとかするのもあらかじめパーズしておくのも面倒なので、こんなときに Test::Deep で使える関数を提供するモジュールを書きました。

SYNOPSIS を見ていただければ使い方がわかると思います。

use Test::Deep;
use Test::Deep::JSON;

cmp_deeply {
    mode => 'hoge',
    payload => '{"a":1}',
}, {
    mode => ignore(),
    payload => json({ a => 1 }),
};

こんな風に Test::Deep の expected 部に使えるクラスを作る際には、以下のことを知っておくと便利そうです:

  • use Test::Deep::Cmp する
    • すると (/^Test::Deep::/ なパッケージの場合) @ISA に Test::Deep::Cmp が入ります
  • $self->descend($got) を実装する
  • diagnostics か diag_message も実装しておくとよし
  • expected の実体は $self->{val} に保持しておく
    • と diag 時のメッセージとかによしなに使われるっぽい

テストのテストには Test::Tester が便利そうですね。ひとつの関数呼び出しで複数テストが走ってびっくりするけど。

2012-09-30

YAPC::Asia 2012 に参加して発表してきました #yapcasia

| 14:28 |  YAPC::Asia 2012 に参加して発表してきました #yapcasia - NaN days を含むブックマーク はてなブックマーク -  YAPC::Asia 2012 に参加して発表してきました #yapcasia - NaN days

発表者としての初参加だった去年はスイーツエリア勢だったので、今回本トークとして採用されて嬉しかったです。タイムテーブルを見ると同じ会場に名前を見たことある人しかいなくて、かなりびびってましたが……。

きのうの夜に京都に帰ってきて、一通り終わって、じゃっかん自分の中では燃え尽きたというか、脱力した感じがあります。


内容は以前にも紹介した Wight という、Perl から PhantomJS を操作できてスクレイピングやテストに使えるよ、というモジュールの使い方と実装の紹介でした。説明とか、話とか、もっと上手くできればいいなと思うのですが、まあ、精進していきます。とりあえず発表も終わったので、PhantomJS や Poltergeist の新しいバージョンへの対応などをぼちぼち進めていきたいと思います。



いち参加者としては

  • xaicron さん
  • antipop さん
  • Perl 今昔
  • miyagawa さん
  • mizzy さんのクロージング

が特によかったです。こういう場所で同業者の事例をいろいろと見聞きできるのは、よいですね。mizzy さんの話は、ぼく自身 Web で使う Perl に触れたのは blosxom が最初だったので、懐しくて、そういえば kan さんの LT で出てきた wema にも、そういうことを感じました。それから、miyagawa さんのスライドにちょこっと取り上げていただいて、これは非常に感激しました……。

今回は前夜祭からの参加だったのですが、懇親会も含めて、去年の YAPC で会ったきりの人とかとも話せてよかったです。まだ話せなかった人もいて、できれば後夜祭で……と思っていたのだけれど、台風の影響で帰ってきてしまったのが残念です。


いろいろと発表を聞いていて思ったのは、やはり他の言語のカンファレンスにも参加しておかないと分からないこともあるよなーということで、また来年!とかじゃなく、何か機会を見つけたらイベントに参加できるように身も心も軽くしておきたいということでした。

2012-09-20

コード中から簡単に plackup できるモジュールを書いた

| 19:50 | コード中から簡単に plackup できるモジュールを書いた - NaN days を含むブックマーク はてなブックマーク - コード中から簡単に plackup できるモジュールを書いた - NaN days


[2013-10-15] shipit しました。P が大文字になっています。


https://github.com/motemen/AnyEvent-plackup

使い方は簡単で、

use AnyEvent::plackup;
my $server = plackup(app => \&psgi_app, port => $port);
say $server; # => 'http://0.0.0.0:8290' # 文字列化すると URL に

とするだけです。ポートを指定しないと適当に空いているポートが選ばれます。

で、これだけだとただの Twiggy のラッパーなのですが、これが便利なのは app を指定せずに起動することができる点です。

my $server = plackup();

こういうことができるわけです。この使い方をした場合、呼び出し側でよしなにリクエストを捌く必要があります。

my $req = $server->recv;
$req->respond([ 200, [], [ 'OK' ] ]);

$server->recv を呼ぶと、リクエストが来るまでそこで処理がストップします (AnyEvent のループが回りはじめます)。やってくるリクエストオブジェクトは Plack::Request に respond というメソッドが生えたもので、これに raw PSGI response を返してやることで、クライアントにレスポンスを返すことができます。

これが何に使えるのかというと、OAuth アプリを書いてる際など、コールバックをとりあえず受け取るためだけのサーバを立てたいときに便利です。

my $plackup = plackup(port => 4000);
   $plackup->host('localhost'); # 0.0.0.0 だとリダイレクト先として許されないので…
my $client = Net::OAuth2::Client->new(
    $client_id,
    $client_secret,
    site               => 'https://accounts.google.com',
    authorize_path     => '/o/oauth2/auth',
    access_token_path  => '/o/oauth2/token',
    scope              => 'https://www.googleapis.com/auth/drive.readonly',
    access_token_param => 'oauth_token',
)->web_server(
    redirect_uri => "$plackup/oauth2callback",
);

system 'open', $client->authorize_url; # ここでブラウザが開く

my $code = $plackup->recv->parameters->{code}; # ブラウザで承認してリダイレクトされるとここに到達
my $token = $client->get_access_token($code);

ブラウザを勝手に開いちゃえばあとはリダイレクトで何とかしてくれる、という感じです。PIN みたいなのを手で入れたりする必要がないので便利です。

2012-08-22

Kyoto.pm #2 で DBIx::Lite の紹介をしました #kyotopm

| 23:30 | Kyoto.pm #2 で DBIx::Lite の紹介をしました #kyotopm - NaN days を含むブックマーク はてなブックマーク - Kyoto.pm #2 で DBIx::Lite の紹介をしました #kyotopm - NaN days

Kyoto.pm Tech Talks 02を開催します! - kyotopm's blog

d:id:shiba_yu36 さんに「モテメンさん何か発表しませんか?」と打診されて「じゃあボクが集めたカッコイイ Perl のコードを大発表するよ!」と答えたのですが 1 週間前になっても 2 つ 3 つほどしかネタがなかったので諦めて最近使ってみている ORM の紹介をしました(自分が作ったわけではありません)。

DBIx::Lite はメソッドチェーンでクエリを組み立てるインターフェースのシンプルな OR マッパです。ロークラスやスキーマクラスの定義を要求せず、すぐに使いはじめられるのが特徴のようです。使いはじめるのは簡単ですが、いちど拡張しようとしはじめるといろいろと癖があって、少しはまるところがあるかもしれません。最近 ORM や DBI に思いを馳せることが多く、その一環での発表でした。