coverモジュールとsmerlモジュールを併用するためのパッチ

Erlang
デフォルトでは、coverモジュールとsmerlモジュールの併用が不可能(おそらく)なので、その問題を解消するためのパッチを作成。

使用したバージョンはErlangがR14B01、erlwebが0.7.1。

問題点

末尾のパッチが対象としている問題:

  • cover:compile関数を使ってカバレッジ測定用にコンパイルしたモジュールに対して、smerlを用いた関数の追加等の操作ができない。

普通にErlangを使っている分には、あまり関係ないかもしれない。
今回は、Erlang製のO/Rマッパー(?)であるerlydbを使用しているソースに対してカバレッジ計測を行うという要件があり、erlydbが内部的にsmerlを利用していたために上記問題が発生した。


以下、修正の概要:

  • coverモジュール:
    • cover:compiled関数でカバレッジ測定用にコンパイルされたモジュールのbeamデータを取得するためのインターフェースを追加
      • smerlはローカルからbeamファイル(or erlファイル)を読み込む代りに、coverサーバからbeamデータを取得するようにする
    • カバレッジ測定用コードを挿入したモジュールをコンパイルする際に、コンパイルオプションとしてdebug_infoを渡すように変更
      • smerlに必要な情報をbeamデータから読み込むためには、デバッグ情報付きでコンパイルされている必要があるため
      • beamにデバッグ情報が付与されていないと、smerlはモジュールのソースファイル(もちろんこれにはカバレッジ用のコードは挿入されていない)を直接使用してしまう
  • smerlモジュール:
    • カバレッジ測定用にコンパイルされたモジュールに対応する処理を追加
      • 具体的には、モジュールがカバレッジ用にコンパイルされている場合、code:which関数はcover_compiledアトムを返すので、その対応の追加
        • beamデータ取得の際にcode:which関数がcover_compiledアトムを返したら、coverサーバに問い合わせるようにする
        • smerlが修正してコンパイルしたモジュールに対するcode:which関数呼び出しがcover_comiledアトムを返すようにタグを設定する

この修正がベストかどうかは分からないけど、応急処置の一つとしてメモしておく。

パッチ

パッチ適用方法。

# cover.erl
$ cd ${解凍ディレクトリ}/otp_src_R14B01/lib/tools/src/
$ patch cover.erl < cover.patch
# smerl.erl
$ cd ${解凍ディレクトリ}/erlyweb-0.7.1/src/smerl/
$ patch smerl.erl < smerl.patch


cover.patch:

*** cover.erl	2010-12-17 01:41:28.972951213 +0900
--- cover.erl.patched	2010-12-17 01:03:41.042983354 +0900
***************
*** 65,70 ****
--- 65,71 ----
  	 modules/0, imported/0, imported_modules/0, which_nodes/0, is_compiled/1,
  	 reset/1, reset/0,
  	 stop/0, stop/1]).
+ -export([get_compiled_beam/1]).
  -export([remote_start/1]).
  %-export([bump/5]).
  -export([transform/4]). % for test purposes
***************
*** 150,155 ****
--- 151,163 ----
  start(Nodes) ->
      call({start_nodes,remove_myself(Nodes,[])}).
  
+ %% get_compiled_beam(Module) -> Result
+ %%   Module = atom()
+ %%   Result = {ok,binary()} | {error, Reason}
+ %%     Reason = term()
+ get_compiled_beam(Module) ->
+     call({get_compiled_beam, Module}).
+ 
  %% compile(ModFile) ->
  %% compile(ModFile, Options) ->
  %% compile_module(ModFile) -> Result
***************
*** 548,554 ****
  				 compiled = Compiled},
  	    reply(From, {ok,StartedNodes}),
  	    main_process_loop(State1);
! 
  	{From, {compile, File, Options}} ->
  	    case do_compile(File, Options) of
  		{ok, Module} ->
--- 556,569 ----
  				 compiled = Compiled},
  	    reply(From, {ok,StartedNodes}),
  	    main_process_loop(State1);
!         {From, {get_compiled_beam, Module}} ->
!             case ets:lookup(?BINARY_TABLE,Module) of 
!                 [{Module,Beam}] ->
!                     reply(From, {ok, Beam});
!                 _ ->
!                     reply(From, {error, not_cover_compiled})
!             end,
!             main_process_loop(State);
  	{From, {compile, File, Options}} ->
  	    case do_compile(File, Options) of
  		{ok, Module} ->
***************
*** 1253,1259 ****
  	    %% Compile and load the result
  	    %% It's necessary to check the result of loading since it may
  	    %% fail, for example if Module resides in a sticky directory
! 	    {ok, Module, Binary} = compile:forms(Forms, []),
  	    case code:load_binary(Module, ?TAG, Binary) of
  		{module, Module} ->
  		    
--- 1268,1274 ----
  	    %% Compile and load the result
  	    %% It's necessary to check the result of loading since it may
  	    %% fail, for example if Module resides in a sticky directory
! 	    {ok, Module, Binary} = compile:forms(Forms, [debug_info]), 
  	    case code:load_binary(Module, ?TAG, Binary) of
  		{module, Module} ->

smerl.patch:

*** smerl.erl	2010-12-17 01:42:04.302955178 +0900
--- smerl.erl.patched	2010-12-17 01:42:20.942951890 +0900
***************
*** 166,171 ****
--- 166,179 ----
  			_Other ->
  			    {error, {invalid_module, ModuleName}}
  		    end;
+                 cover_compiled ->
+                     {ok, BeamBinary} = cover:get_compiled_beam(ModuleName),
+ 		    case get_forms(ModuleName, BeamBinary) of
+ 			{ok, Forms} ->
+ 			    mod_for_forms(Forms);
+ 			_Other ->
+ 			    {error, {invalid_module, ModuleName}}
+ 		    end;
  		_Err ->
  		    {error, {invalid_module, ModuleName}}
  	    end
***************
*** 594,603 ****
  		end,
  	    case Res of
  		ok ->
  		    code:purge(Module),
! 		    case code:load_binary(
! 			   Module,
! 			   atom_to_list(Module) ++ ".erl", Bin) of
  			{module, _Module} ->
  			    ok;
  			Err ->
--- 602,613 ----
  		end,
  	    case Res of
  		ok ->
+                     Tag = case code:which(Module) of
+                               cover_compiled -> cover_compiled;
+                               _ -> atom_to_list(Module) ++ ".erl"
+                           end,
  		    code:purge(Module),
! 		    case code:load_binary(Module, Tag, Bin) of
  			{module, _Module} ->
  			    ok;
  			Err ->