Perl には Test::TCP というのがあって、テスト中、空いてるポートで何かしらのサーバーを起動して使うということができます。Ruby においては Glint というライブラリがあって、同じことができます。
node.js の場合、node-test-tcp というのがあって、node の net.Server で動くサーバに関しては簡単に同じことができます。が、memcached とか外部プロセスを起動させようとするとちょっと困るのと、done() を呼ばないと終了しないので、何かいい方法はないかなと思ったので書いてみました。
test-tcp だとカブるので glint のほうの名前を仮りています。
つかいかた
glint(
function (port) {
// ここは外部プロセスで実行される
// ただし文字列化して関数が渡されるので外のスコープの変数は使えない。
// node.js の exec, execFile はいわゆる exec ではないので1つプロセスが余分にできる。我慢するしかない。
console.log('starting memcached with port: ' + port);
require('child_process').execFile('memcached', ['-p', port]);
},
function (error, server) {
if (error) throw error;
// server は起動が確認済み
// server.port でポート番号がとれる。
// server.kill() でこのプロセスだけ殺せる
console.log(server);
var s = net.connect(server.port, function () {
s.write("version\r\n");
});
s.on('data', function (data) {
console.log(data.toString('UTF-8'));
s.end();
});
// この関数を抜けても exit されるまで server は (明示的にkillしない限り) kill されない
}
);
しくみ
他のモジュールとやってることは一緒なのですが、node.js だと面倒な点がいくつかあります
- fork / exec はいわゆる *nix の fork/exec ではない
- オブジェクトファイナライザ的な仕組みがない
- 特定のオブジェクトが破棄されるタイミングで処理を行うことができない
node の fork / exec (execFile) は *nix の fork / exec とは全く違うので、細かいプロセスの制御ができません。特に exec 単体相当はないので、現在のプロセス自体を置き換えるということができません。そんなわけで1つ別プロセスを経由している関係で、無駄に1プロセスを消費しています。
オブジェクトのファイナライザがないので、node-glint では process.on('exit') で起動したサーバを終了しています。ただし、普通に外部プロセスを起動すると、そのプロセスの終了を待つ挙動になり、exit されません。が、spawn() のオプションに detach: true を指定した上で、unref() を呼んであげることで、外部プロセスの終了に関わらず起動元の node プロセスを exit させることができます。
まとめ
一応形にはなりましたが、細かい挙動の検証がめんどうなので npm にあげてません。特に現在の実装はシグナルまわりのハンドリングがいい加減です。node マスターの皆様におかれましては、よりよい方法をご教示頂ければと思う所存です。