おぎろぐはてブロ

なんだかんだエンジニアになって10年以上

phptのEXPECTREGEXは /s 修飾子指定

phptは、PHPの関数やスクリプトをテストするためのツールで、PHP本体や拡張モジュール、PEARライブラリなどいろんなところで利用されています。bobchinの日記さんで、phptドキュメントの日本語訳が公開されています。
設定パラメータ、実行コード、期待する結果を書いたテストケース(拡張子phpt)を作って、実行させると、パラメータをセットして、コードを実行、実行結果を期待する値と比較し、可否を判断します。そんだけ。シンプルです。たとえば、こんな感じ。

--TEST--
strtr() function
--FILE--
<?php
$trans = array("hello"=>"hi", "hi"=>"hello", "a"=>"A", "world"=>"planet");
var_dump(strtr("# hi all, I said hello world! #", $trans));
?>
--EXPECT--
string(32) "# hello All, I sAid hi planet! #"

自分の場合は、PHP Extensionのテストに利用しています。PHP4 / 5.1 / 5.2 用にビルドして、それぞれの環境で正しく動作することを検証するなんてことをしていて、このように同じ事を続けるにはすごく便利です。

正規表現でのマッチ

上の例の場合はよいのですが、実行の都度結果が変わるようなコードもあります。その場合は、文字列一致の EXPECT では対応できないため、正規表現でのマッチ EXPECTREGEX が利用できます。(EXPECTFという、%dとかscanf的な指定でマッチさせることができるブロックもありますが、これも内部的に正規表現に置換してます。)
正規表現マッチの例として、テストの例としてはよくない感じもしますが、以下のようなもの。

--TEST--
date format test
--FILE--
<?php
// string(31) "Sat, 07 Jul 2007 16:19:06 +0900"
var_dump(date("r"));
?>
--EXPECTREGEX--
string\(31\) "\w{3}, \d\d \w{3} \d{4} \d\d:\d\d:\d\d \+\d{4}"

EXPECTREGEXのpreg_matchは /s オプションで処理される

さらに出力が複数にわたる場合は、

--FILE--
<?php
echo "1st line: ", rand(), "\n";
echo "2nd line: ", rand(), "\n";
?>
--EXPECTREGEX--
1st line: \d+
2nd line: \d+

と、そのまんま改行すればよいわけです。上のケースだと、\w+ が英数字にマッチします。じゃ、英数字以外も含め、行末まで何かしら文字が入っていればいいと、\w でなく . を利用して

--FILE--
<?php
echo "1st line: ", rand(), "\n";
echo "2nd line: ", rand(), "\n";
?>
--EXPECTREGEX--
1st line: .+
2nd line: .+

と書くと、これはうまくいくのですが、実はまずくて、

--EXPECTREGEX--
1st line: .+

と、2行目を削ってしまってもテストをPASSします。.+が、改行も含めマッチするため、行末を超えてマッチしてしまうのです。

この理由はドキュメントには書かれていないのですが、コードを読んでみると、正規表現によるマッチが、

if (preg_match("/^$wanted_re\$/s", $output)) {

s (PCRE_DOTALL)修飾子付きで実行されていることが分かります。

下手にマッチするのでまずい

改行にはマッチしないと思って正規表現を書いていっても、テストがPASSしてしまうため気づきにくいです。これにより、不正のはずなのにPASSしてしまうパターンが出てきたりしますので注意する必要があります。
行末までにマッチさせるなら、非貪欲(ungreedy)にすれば改行まででマッチが終了するので .+? と都度切り替えるか、もしくはパターン中オプション設定で (?-s)と、PCRE_DOTALLを無効にするなどの方法があると思います。

まぁ、何でもマッチするdotを使うなって感じもしますが、自分がこれを使っていたのには理由があって、ExtensionからwrapしているCのライブラリから、パラメータが不正のときに標準エラーにNOTICEが吐かれ、phptではこれもスクリプトの出力として掴むのです。(phptの中で 2> /dev/null とリダイレクトしちゃえばいいんでしょうが) で、PHP側のテストで、ライブラリが吐くNOTICEをテストするというのもなんか変な感じなので、NOTICEが出る行を、.が改行以外にマッチすると思って、 .+ と書いていたと。
この際に、 .+ の数が増えすぎると、正規表現エンジンでうまく判定ができなくなり、マッチに失敗しまうのですが、それで、なんかおかしいと気づいたというのが経緯です。cURLみたく、ライブラリ側にはsilentモードを用意すべきだなと思いました。