2010年1月25日

proveコマンドの出力内容を確認する

Test::Harnessの続き。proveコマンドの出力パターン。

パターン1:テスト成功

#!/usr/bin/perl

print "1..2\n";
print "ok 1\n";
print "ok 2\n";

結果は以下の通り。テストは無事成功です。

$ prove test.t
test.t .. ok
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.02 usr +  0.00 sys =  0.02 CPU)
Result: PASS

パターン2:テストは成功、ただしテストコードは正常終了しない

#!/usr/bin/perl

print "1..2\n";
print "ok 1\n";
print "ok 2\n";
die;

結果は以下の通り。テストは無事成功しましたがテストコードが正常終了以外のexit code(この場合は255)を返したので、テスト全体としては失敗。wait statusと呼ばれる子プロセスが親プロセス(この場合はprove)へ返す値が正常終了を示す値ではなかったので、テスト失敗です(wstatの0xff00の前半がtest.tが返したexit codeを示す)。

$ prove test.t
test.t .. 1/2 Died at test.t line 8.
test.t .. Dubious, test returned 255 (wstat 65280, 0xff00)
All 2 subtests passed 

Test Summary Report
-------------------
test.t (Wstat: 65280 Tests: 2 Failed: 0)
  Non-zero exit status: 255
Files=1, Tests=2,  0 wallclock secs ( 0.02 usr +  0.01 sys =  0.03 CPU)
Result: FAIL

パターン3:テストは失敗、ただしテストコードとしては正常終了

#!/usr/bin/perl

print "1..2\n";
print "ok 1\n";
print "not ok 2\n";

テスト項目が2件有って、1件失敗した事、失敗したテスト項目番号が2番である事が分かります。

$ prove test.t
test.t .. Failed 1/2 subtests 

Test Summary Report
-------------------
test.t (Wstat: 0 Tests: 2 Failed: 1)
  Failed test:  2
Files=1, Tests=2,  0 wallclock secs ( 0.02 usr +  0.01 sys =  0.03 CPU)
Result: FAIL

意外といくら検索しても、wstatの値をどの様に読み解けばいいのか分かりませんでした。延々とUnixのシステムコールのwait関数のマニュアルを読む羽目になりました。みんなどうしているのでしょう?

あと、最後にテストの実行時間が出てきますが、これもどの様に解釈すべきか書かれていないように思えますね。みんな見ないのかな?普通単体テストで性能試験しないからいらない?

まずはテストコードが「Dubious, test returned 255」を出力した時は、モジュールか、テストコードにコンパイルエラーが有るか、どこかにevalでトラップされない素のdieが有るか疑いまいしょう。

2010年1月24日

TAP - Test Anything Protcol

Perlのproveコマンドではテストコードが出力した結果を集約してレポートを作成してくれます。

この時、proveコマンド(正確にはそのバックエンドで動いている各種Test::Harnessモジュール群)と、各テストコードの結果を結びつけるプロトコルがTAP(Test Anything Protocol)です。

プロトコルと言っても、正式な規格書は無く、「Test::Harness」モジュールの動作自体が規格である、という面が強いようです。ただし、内容はそんなに複雑ではなく、最初にテスト項目数を宣言し、それに続いてテスト項目毎に「ok」又は「not ok」を出力するだけです。

実際の例です。以下のコードをエディタに打ち込んで、「Sample.t」の名前で保存します。

#!/usr/bin/perl

print "1..2\n";
print "ok 1\n";
print "not ok 2\n";

次に作ったファイルをproveコマンドへ引き渡します。

$ prove Sample.t 
Sample.t .. Failed 1/2 subtests 

Test Summary Report
-------------------
Sample.t (Wstat: 0 Tests: 2 Failed: 1)
  Failed test:  2
Files=1, Tests=2,  0 wallclock secs ( 0.02 usr +  0.01 sys =  0.03 CPU)
Result: FAIL

proveコマンドが、テストコード「Sample.t」のテスト結果をサマリーしてくれています。

標準出力に出力されたTAPに関係する出力を拾って、集計してくれているのです。実際のテストでは当然テスト結果によって、okとnot okを出力し分ける事で、個々のテスト結果を出力します。

TAPを真面目に調べようとすると、けっこうハマり始めたので、次回以降Test::Harnessの研究を続けてみます。

2010年1月22日

MacPortsでemacs23.1をインストールする

Snow LeopardになってMacPortsでのemacsインストールが失敗する様になってしまったんですが、ようやく解消されました。以下のコマンドでインストールする事が出来ます。

$ sudo port install emacs


入門 GNU Emacs 第3版
半田 剣一
487311277X

2010年1月21日

MacPortsでのPerl v5.10のインストール方法が変わった

MacPortsでPerl5.10をインストールする方法が若干変わっているようです。

  • 今まで
  • Portsには「perl5」、「perl5.10」の2種類が有り、perl5ではv5.8.9が、perl5.10ではv5.10.1がインストールされます。

    Portsは「perl5」に依存している物がたくさんあり、勝手にPerl v5.8.9がインストールされていました。

  • これから
  • 「perl5」にvariantsとして、「perl5_10」が用意されるようになりました。これでPerl v5.10.1をインストールする事が出来る様になります。またコマンド名に余計な5.10というバージョンを示す文字列も追加されません。

    これからMacPortsでPerl5.10.1を使いたい場合は以下のコマンドがお勧めです。

    $ sudo port install perl5 +perl5_10

ちなみに「perl5.10」のPortsも残っています。こちらもコマンド名の後ろに5.10という文字列を付けなくなりましたが、やはり依存関係から「perl5 +perl5_10」の方がお勧めです。

2010年1月20日

Perlにおける$VERSIONをもう一度考える

前回、$VERSIONに関するエントリを書いたのだけど、既にPerl v5.10ではv-string形式自体が時代遅れとの事。もう一度考えてみる。そんな記事です。

Perlではバージョンを表記する上で、三つの表記が有ります。

  • v-stiring形式
  • Perl v5.6で導入されたのに、Perl v5.8.1の段階で将来は無くなると宣言された記法。

    2個以上のドットで数値を連結した形式。頭にvを付ける(ドットが二つ以上の時はvは省略可。)

    例:v5.10.1

    「Perlベストプラクティス」では使うなと書かれている(古いPerlとの互換性から)。

  • 小数点形式
  • 普通の浮動小数点で表す形式。

    例:4.00501

    マイナーバージョン、リビジョンは、小数点以下を3桁単位に区切って表す慣習。

    メジャーバージョン:4

    マイナーバージョン:5

    リビジョン:1

  • version object形式
  • use version; $VERSION = qv('v4.5.1');

    単なる文字列や、浮動小数点ではなく、オブジェクトとしてバージョンを保持する。

    Module::Build等が古い場合は、上記の通り1行にuse versionに続けてバージョン番号を指定する。

通常は浮動小数点形式で十分の様です。CPANでアルファバージョンはアンダースコアに続けて2桁の数字を付ける慣習だそうです。その様な時には以下の様に記述するのがベストプラクティスだそうです。

our $VERSION = '4.005001_01';

$VERSION = eval($VERSION);

一旦文字列として定義して、数字として代入し直します。

2010年1月18日

Perlの$VERSION変数

Perlでは、パッケージ毎に$VERSIONというグローバル変数を設定する事が出来ます。例えばHoge.pmというモジュールが有った場合、以下の様な記述を行います。

package Hoge;

our $VERSION = v1.0.0;

これにより、Hoge.pmモジュールのバージョンナンバーを$Hoge::VERSIONで取得する事が出来ます。この"v"で始まる2個以上のドットで区切られた形式をv-string形式といい、Perlでは主にバージョン番号を表すために使用されます。

Perl内部ではドットで区切られた数字をそれぞれ一つの"文字コード"として管理しています。そのため、例えば"print v101.111.111;"は"foo"と表示されます(ASCIIコードで、101は"f"、111は"o"を表します)。

以前のPerlではバージョンナンバーを表すために単なる小数点の数値が使われていました。例えば"our $VERSION = 1.10"という風にです。しかし、単なる小数点ではバージョン1.9の次をバージョン1.10にする事が出来ません(数値として比較すると、当然1.9より1.10の方が小さいからです)。

そこでv-string形式がPerl5.6より導入されました。これに伴い、Perl自体の内部バージョン表記も5.6以降でv-sting形式に変更されています(以前は小数点以下3桁毎をマイナーバージョン、リビジョンと解釈していたそうです)。

Perl5.4 → 5.004
Perl5.5 → 5.005
Perl5.6 → v5.6.0
Perl5.8.9 → v5.8.9
Perl5.10.1 → v5.10.1

さて、実際のコードにおける$VERSIONの使い方ですが、使用するモジュールのバージョンチェックに使用します。以下、二つのコードが有ったとします(Hoge.pmと、Foo.plです)。

# Hoge.pm                                                                       
package Hoge;

our $VERSION = v1.0.0;

sub func {

}

1;

#!/usr/bin/perl                                                                 
# Foo.pl
use Hoge v1.0.1;

Hoge::func()

Foo.plを実行すると、以下のコンパイル時エラーメッセージが出ます。

Hoge version v1.0.1 required--this is only version v1.0.0 at Foo.pl line 3.
BEGIN failed--compilation aborted at Foo.pl line 3.

Foo.pl側でHoge.pmのv1.0.1以上を期待したのに、実際にはv1.0.0のHoge.pmしか存在しないので必要なバージョンが存在しない、という事でエラーになっています。これはモジュールの期待する機能が特定バージョン以上にしか備わっていない時に有効な指定となります。

useにモジュール名をバージョン番号付きで指定すると、コンパイル時にパッケージのVERSIONメソッド(Perlによって自動的に生成されるパッケージのバージョンチェック用メソッド)が自動的に実行される事で実現されています。VERSIONメソッドは、パッケージの$VERSIONが引数で指定されたバージョンと同じか、それより大きい事を確認するメソッドです。

更に"use v-string;"と記述すると実行するPerl自体のバージョンチェックを行う事が出来ます。

#!/usr/bin/perl                                                                 
# Foo.pl
use v5.10.1;

Hoge::func()

このスクリプトはPerl5.10.1以上でしか実行できない、という事になります。


更にv5.10より導入されたversion objectという機能が有りますが、それはまた次回。

追記:と、ここまで調べたのにv-string形式が、あまり有効でなかった的な記述をPerlのリリースノートで見つけたので(version objectに代わるとも思えませんが...)、改めて書き直してみます。

2010年1月16日

Exporterがやっている事

PerlにはExporterというモジュールが有って、関数ベースのモジュールを作る時には必須の存在です(逆にオブジェクトベースのモジュールでは使いません)。

Exporterの基本的な使い方は、以下の様になります。

/usr/local/bin/script.pl

#!/usr/local/bin/perl

use strict;
use warnings;

use Some::Module;

my $value = func();

print "$value\n";
/usr/local/lib/perl5/site_perl/5.10.1/Some/Module.pm
package Some::Module;

use strict;
use warnings;

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(func);

sub func {
  return localtime;
}

1;

前回の記事と違って、「Some::Module::func()」ではなく、「func()」と、パッケージ名を付けないで関数を呼び出す事が可能になっています。これがExporterにより実現されている事です。

モジュール側で上記の例の様にExporterを使う事で、モジュールの呼び出し側のパッケージに、指定されたシンボルがインポートされます。

つまり、呼び出し元のスクリプトでも、func()と記述するとSome::Module::func()と記述した事と同じになるのです。これはuse Some::ModuleのタイミングでExporterが持つimport関数が呼び出され、呼び出し元のパッケージへシンボルが導入される事で実現されています。図にするとこの様なイメージになります。

exporter.png

2010年1月12日

Perl関係の投稿を整理し始めました

今までバラバラに書いてたブログの記事を整理し始めました。色々と書き直しています。今後とも宜しくお願いします。

Perl To The People

2010年1月10日

Mac OS X Snow LeopardでPerlのバージョンを切り替える

Mac OS X Snow Leopardには最初からPerlがインストールされていますが、実はv5.10.0だけでなくv5.8.9もインストールされています。

通常、/usr/bin/perlはv5.10.0にリンクされていますが、以下のコマンドで/usr/bin/perlをv5.8.9に切り替える事が出来ます。

$ defaults write com.apple.versioner.perl Version 5.8.9

また、v5.10.0自体も32bit版と、64bit版の二種類がインストールされていて、デフォルトでは64bit版が有効になっています。これを32bit版を有効にするためには、以下のコマンドを入力します。

$ defaults write com.apple.versioner.perl Prefer-32-Bit -bool yes

なお、v5.8.9は32bit版しか有りません。

このあたりの情報はappleのサポート情報に書かれています。

2010年1月 9日

Perlにおけるモジュールとパッケージの関係

Perlで分かりづらい概念の一つに「モジュール」と「パッケージ」の関係が挙げられます。ようやく最近理解できてきた様な気がするので、以下にそのメモを。

例として、以下の様なスクリプト本体とモジュールが有ったとします。

/usr/local/bin/script.pl

#!/usr/local/bin/perl

use strict;
use warnings;

require Some::Module;

my $value = Some::Module::func();

print "$value\n";
/usr/local/lib/perl5/site_perl/5.10.1/Some/Module.pm
package Some::Module;

use strict;
use warnings;

sub func {
  return localtime;
}

1;

script.plの中で「require Some::Module;」と記述する事で、「/usr/local/lib/perl5/site_perl/5.10.1/Some/Module.pm」というモジュールが"実行"されます。このモジュールの中には、"func"というサブルーチンの定義だけが書かれているので、この段階では特にサブルーチンの中のコードが"実行"される事は有りません。定義されるだけです。

「/usr/local/lib/perl5/site_perl/5.10.1/Some/Module.pm」の中には「package Some::Module;」という記述が有る事で、「Some::Module」というパッケージ名が定義されます(名前空間と考えると分かりやすいですね)。このパッケージ名が定義された事で、「script.pl」の中からSome::Moduleの中に有るfuncという関数が呼び出す事が出来る様になるのです。

そう、重要なのはrequireにより呼び出された時に、モジュールは"実行される"という事です。モジュールの中のコードが実行される事でパッケージ名が"初めて"定義されるのです。つまり、requireするまではSome::Moduleというパッケージ名は存在しないのです。

パッケージ名が定義されていないのに、なぜ「require Some::Module;」と書けるのか?ここが最も理解しづらいポイントでした。

実は、ここで記述している"Some::Module"はパッケージ名を記述しているのではなく、単にモジュールファイルのパス名を記述しているだけなのです。だから、「require Some::Module;」と書けるのです。

Perlではrequireで指定されている「Some::Module」をライブラリの検索対象のパス配下に有る(この例では「/usr/local/lib/perl5/site_perl/5.10.1/」)、「Some/Module.pm」というファイルを探して実行しろ、と解釈しているのです。

module_path

Perlでは、モジュールを配置するパス名+ファイル名と、パッケージ名を一致させる習慣になっています。そうしないと、そのパッケージ名がどのモジュールで定義された物なのか分かりづらいですからね。

逆に言うと、requireとpakageのそれぞれにまったく同じ文字列(この例ではSome::Module)が書かれているのに、それらは全然違う事を意図しているので、凄く混乱し易いのです(requireの後ろはパス名、packageの後ろはパッケージ名)。図に整理するとこんな感じになります。

module_package.png

パッケージの階層と呼び出す時のPATHがきちんと整合している事を確認しましょう。

修正2010/2/26

誤り例が誤りという情けない指摘をいただきました。ひとまずさっくり書き直したのですが、Perl To The Peopleへ移す時にもう少しちゃんと考え直します。

2010年1月 8日

Perlのdoとuseとrequire

doとuseとrequireの使い分けがさっぱり分からなかったので整理した。

・do

実行契機:プログラム実行時に呼び出される。

複数回呼び出し:複数回記述すると、複数回実行される(関数定義が有ると、二重定義の警告が出る)。

モジュール:モジュールの関数シンボル(名前)をインポートしない。

主な用途:Perl5では使い道があまり無い。


・require

実行契機:プログラム実行時に呼び出される。

複数回呼び出し:複数回記述しても、一回しか実行されない(関数が二重定義されない)。

モジュール:モジュールの関数シンボル(名前)をインポートしない。

主な用途:実行時に読み込みたいモジュールが変化する時


・use

実行契機:コンパイル時に呼び出される。

複数回呼び出し:複数回記述しても、一回しか実行されない(関数が二重定義されない)。

モジュール:モジュールの関数シンボル(名前)をインポートする。

主な用途:モジュールの読み込み。


というわけで、通常はuseを使えば良さそうです。

2010年1月 6日

emacsのcperl-modeを設定する

最近のemacsにはPerlのスクリプトを書くのに便利なperl-modeとcperl-modeの二つのモードが用意されています。ただ、未だにperl-modeがデフォルトなのですが、cperl-modeの方が便利です。

cperl-modeを有効にするには、.emacsに以下の1行を追加すればokです。

(defalias 'perl-mode 'cperl-mode)

ただ、これではテストコードを示す拡張子が「.t」のファイルが自動的にperlのスクリプトとして認識されないので、以下の行も追加しています。

(add-to-list 'auto-mode-alist '("\\.\\([tT]\\)\\'" . cperl-mode))

2010年1月 4日

MacPortsのコマンド覚え書き

MacPortsで自分が使うコマンドの覚え書き。

  1. MacPortsのインストーラのダウンロード先
  2. インストーラはMac OS Xのバージョン毎にバイナリが分かれている。ダウンロードサイトは、ここ

  3. 最新のパッケージ一覧を取得
  4. $ sudo port selfupdate
    

  5. パッケージのインストール
  6. $ sudo port install パッケージ名
    

    variantsが有る場合はパッケージ名の後ろに"+variants name"。

  7. パッケージのアップグレード
  8. $ sudo port upgrade outdated
    

    個人の環境だと全部アップデートしちゃうので、これでOK。

  9. パッケージの検索
  10. $ port search パッケージ名
    

    ちなみにPerlのモジュールは"p5-" + "モジュールの名前"という形式で統一されている。

  11. variantsの検索
  12. $ port variants パッケージ名
    

    MacPortsでは、パッケージのインストール条件を変えるvariantsが指定できる。どんなvariantsが指定できるかはこのコマンドで調べる。

ちなみに同じPerl本体でもパッケージによってずいぶん指定できるvariantsは異なります。

$ port variants perl5
perl5 has no variants

$ port variants perl5.10
perl5.10 has the variants:
   darwin: Platform variant, selected automatically
   shared: Build shared perl library
   threads: Build with thread support

$ port variants perl5.8
perl5.8 has the variants:
   db: Build with Berkeley Data Base support
   gdbm: Build with gdbm support
   shared: Build shared perl library
   threads: Build with thread support

sharedとthreadsさえ指定してれば良さそうですが。

2010年1月 3日

Perl Cookbookを買った

新宿の紀伊國屋書店が洋書20%オフセールという事で、前から欲しかったPerl Cookbookの原著を購入。さすがに2003年の出版なので、そろそろアップデート版が欲しいところだけど、まだ現役。

Perl Cookbook
0596003137