Test::Time と日付系モジュールを同時に使う際の注意
cho45 さんが作られた Test::Time と日付系モジュール (DateTime や Time::Piece など) と同時に使用する場合には注意が必要です。
Test::Time の動作を補足的に解説しますと、このモジュールは sleep() と time() を上書きしています。
sleep() が呼ばれると、本来ならば引数として与えられた秒数だけスリープしますが、
Test::Time を use しているとスリープされません (つまり即座にsleep() は処理を終える)。
その代わりに、引数として sleep() に与えられていた秒数を time() によって得られる秒数に加えるという動作を行います。
...日本語で説明するのがなんだか難しいので、参考コードを掲示します。
こんな感じです。おわかり頂けるでしょうか。
で。
例えば以下の様なコードは期待通りに動作しません。
(今回の例では DateTime を用いていますが、適宜 Time::Piece に読み替えてもらっても差し支えありません)
2回目の`say $time;`では`sleep 60;` という記述によって 60 秒加算された時間が出力されそうな感じがしますが、そうはなりません。
どうすれば期待通りの動作を実現できるかと言うと、以下のように書き換えると良いです。
やったことは至って単純で、`use DateTime;` と `use Test::Time;` の呼び出し順を変更しただけです。
日付系モジュールよりも先に`use Test::Time;` を呼んでやることによって期待通りの動作を実現できます。
なぜこんなことが起こるのか
前述の通り、Test::Time は sleep() と time() を上書きしています。
Test::Time のソースコードを読むと分かるように、この上書き処理はCORE::GLOBAL::sleep と CORE::GLOBAL::time を上書きすることによって実現しています。
で、ここらへんの挙動はCORE - perldoc.perl.org を読むと分かるんですが、実際それを読むよりも以下に引用するtokuhirom さんの Post がわかりやすいので掲示します。
要するにPerl5はコンパイルする時点で、組み込み関数は 普通の関数呼出ではなく、直接該当の組み込み関数のルーチンにひもづけるんで、その部分をコンパイルする時点までの間に、「この関数をおきかえるよ」っていっておかないとだめって話です
— tokuhirom (@tokuhirom) 2013, 8月 19
具体的には該当部分のコードより上の方で BEGIN { } ブロックで置き換えておくか、use でおきかえくんモジュールをインクルードしておくか、どちらかしかない。
— tokuhirom (@tokuhirom) 2013, 8月 19
なので、日付系モジュールよりも先に Test::Time を読み込んでおかなければ、日付系モジュールが上書きされた time() を使うことが出来ないって話だったんですね。