Eclipse PDT + MakeGood でも SSH 経由で PHPUnit を実行する

PhpStorm 8 が正式にリリースされましたね。

PHP Remote Interpreters というものが実装されたので PHPUnitSSH 経由で実行するのがとても簡単になりました。

が、少し触ってみたところちょっと微妙なことろもありました。下記の記事にも記載されていますが Test scope に Directory や Class や Method を指定するとローカルとリモートのパスの違いでエラーになります。

この記事ではシンボリックリンクでどうにかする方法が紹介されていますが、ローカルが Windows でリモートが Linux な環境で開発している私のようなキチガイにそれは難しいです。なんせ D:\path\to\hoge みたいなパスですから。

わりと Ctrl+Shift+F10 でクラスやメソッド指定でテストを実行することがあるので、この機能が使えないと困ります。なので少し前に書いた下記の方法をしばらくは使っていこうと思ってます。

なお、上記の記事に埋め込んでいる Gist のコードは少し古いです。コードは こっち に移しました。使い方とかは多分変わっていませんが、まぁ自分しか使わない俺得なものなので細かい説明はなくてもいいでしょう。

↓追記 2014/09/19

なわけないですね、キチンと動作します。

ローカルとリモートのパスのマッピングのための Deployment の設定が必要なのですが、私は cifs でローカル~リモートを共有しているので Deployment は設定していませんでした。 Deployment でマッピングを設定するとうまく動作しました。ただ cifs で共有している状態で本当に PhpStorm でデプロイしてしまうと、コピー元とコピー先が同じファイルであるがゆえにファイルの中身が消し飛んでしまったりするのでできれば設定したくなかったです。

↑追記 2014/09/19

.

.

.

それはそれとして、自社内では Eclipse PDT の方が主流っぽいので Eclipse PDT + MakeGood でも同じようなことをやってみました。

私自身もう Eclipse PDT は使っていないので本当に誰得ですが、せっかくなので記事にしました。

Eclipse PDT + MakeGood での設定

phpunit と stagehand-testrunner を composer でインストールします。

stagehand-testrunner は MakeGood から phpunit を実行するときのランチャーのようなもので MakeGood をインストールすると一緒に含まれていますが、リモートにも必要なので composer でインストールします。

忘れがちなのが stagehand-testrunner を composer でインストールした後は vendor/bin/testrunner compile する必要があります。

次に MakeGood をそれっぽく設定します。

まずは プリロードスクリプト に後述する remote-makegood.php を指定します。また、PHPUnit タブの XML設定ファイルphpunit.xml が指定できるようになっていますが、ここでは指定しないでください。phpunit.xml は後述の remote-makegood.local.php で指定します。その他は普通に設定してください。

次に remote-makegood.php と同じディレクトリに remote-makegood.local.php を作成します。

<?php
// SSH のログインユーザー
$remote_user = 'ore';

// リモートホスト名
$remote_host = 'ore-no-server';

// リモートのパス
$remote_dir = '/home/ore/work';

// ローカルのパス
$local_dir = dirname(__DIR__);

// プリロードスクリプトのパス
$preload_script = 'vendor/autoload.php';

// phpunit.xml のパス
$phpunit_config = 'phpunit.xml';

$remote_user$remote_host$remote_dir$local_dir は見ての通りのものです。

$preload_script は MakeGood の設定でプリロードスクリプトに指定するはずだったものです。プリロードスクリプトには remote-makegood.php を指定する必要があるので、本来指定するはずだったものをここで指定します。普通は vendor/autoload.php のような PHPUnit をオートロードするためのものが指定されると思います(後述の通りこの方法なら vendor/autoload.php をプリロードする必要はありませんが)。

$phpunit_config には phpunit.xml のパスを指定します。私の実装がクソいので 諸事情により MakeGood の設定で phpunit.xml を指定するとうまく動作しなくなります。その代わりに remote-makegood.local.phpphpunit.xml を指定してください。なお、キチンと実装すれば MakeGood の設定で指定した phpunit.xml をそのまま活かすこともできるはずです。

なお、$preload_script$phpunit_config も、$local_dir からの相対パスで指定します。

正しく設定されていれば MakeGood でテストを実行したときに ssh 経由でテストが実行されて結果が IDE 上に表示されます。

なお、デバッグ実行には対応していません。これは実装が手抜きだからであって、頑張れば不可能ではないと思います。

remote-makegood.php のざっくり解説

PhpStorm でやったときと比べるとかなり手抜きな実装になっています。私が常用するものではないので。。。

前述の通り MakeGood は phpunit を直接実行するのではなく stagehand-testrunner というランチャーを利用して phpunit を実行します。ちなみに次のような $argv が渡されます(設定とか実行時の状況によって引数は増えたり減ったりします)。

Array
(
    [0] => D:\app\eclipse-php-luna-R-win32\plugins\com.piece_framework.makegood.stagehandtestrunner_3.1.1.v201409021510\resources\php\bin\testrunner.php
    [1] => --no-ansi
    [2] => phpunit
    [3] => -p
    [4] => D:/ore/devel/phpunit-via-ssh-on-ide/tests/remote-makegood.php
    [5] => --log-junit=C:\Users\ore\AppData\Local\Temp\com.piece_framework.makegood.launch\MakeGood1410358831955.xml
    [6] => --log-junit-realtime
    [7] => -R
    [8] => --test-file-pattern=Test(?:Case)?\.php$
    [9] => D:/ore/devel/phpunit-via-ssh-on-ide/tests
)

テストコードのパス $argv[9] はローカルのパスからリモートのパスに書き換えるだけです。

プリロード $argv[4]vendor/autoload.php とかに書き換えます。stagehand-testrunner を composer からインストールしていれば vendor/autoload.php は勝手に読まれるので、実はプリロードで vendor/autoload.php を読む必要はありません。

$argv[5]--log-junit は少し厄介です。

MakeGood はこのファイルを監視していて、このファイルに出力された内容を元に IDE にテスト結果を表示しているようです。

PhpStorm で SSH 経由で PHPUnit を実行させたときはリモートの標準出力(正確には PHP の出力、いわゆる php://output)をローカルの標準出力に書き込むだけで良かったのですが、MakeGood の場合はリモートの標準出力と標準エラーと --log-junit の合計で3つのストリームをどうにかする必要があります。

ssh 経由なので標準出力と標準エラーで2本のストリームが使えます。なので、リモートの標準出力と標準エラーはローカルの標準出力へ、--log-junit はローカルの標準エラーを経由して本来の出力先にリダイレクトすることにします。

また、--log-junitIDEスタックトレースなどをローカルのパスで表示するためにパスの書き換えも行います。

まず、リモートの標準出力と標準エラーをローカルの標準出力に流す方法ですが、proc_open$desc で次のように指定しました。

$desc = array(
    0 => array('file', '/dev/null', 'r'),
    1 => array('file', 'php://stdout', 'w'),
    2 => array('file', 'php://stdout', 'w'),
);

$proc = proc_open($cmd, $desc, $pipes);

--log-junit への書き込みを標準エラーに流す方法は、簡単な方法が思いつかなかったので FIFO(名前付きパイプ)を使いました。

$tmp = tempnam(sys_get_temp_dir(), "makegood-junit-");
unlink($tmp);
posix_mkfifo($tmp, 0600);

try {
     :

    $fifo = fopen($tmp, 'r');
    $stderr = fopen('php://stderr', 'w');

    while (strlen($data = fread($fifo, 1024)) !== 0) {
        $data = str_replace($remote_dir, $local_dir, $data);
        fwrite($stderr, $data);
    }

     :
} finally {
    unlink($tmp);
}

remote-makegood.php

remote-makegood.php の全文は次のとおりです。