查看源代码 cover (tools v4.1.1)
Erlang 的覆盖率分析工具
模块 cover
提供了一组用于 Erlang 程序覆盖率分析的函数,用于统计程序运行时每条可执行代码行被执行的次数。可执行行是指函数、case
、receive
或 try
语句中的子句体中的代码行。子句头中的行、空行和仅包含注释的行不是可执行行。
覆盖率分析可用于验证测试用例是否覆盖了被测代码中的所有相关行。它在查找代码瓶颈时也很有帮助。
在进行任何分析之前,需要先对相关的模块进行覆盖率编译。这意味着在将模块编译成二进制文件并加载之前,会向模块添加一些额外的信息。模块的源文件不受影响,并且不会创建 .beam
文件。如果运行时系统本身支持覆盖率,Cover 将自动使用该功能来降低覆盖率编译代码的执行开销。
更改
Erlang/OTP 27 中添加了原生覆盖率支持。
每次调用覆盖率编译模块中的函数时,有关该调用的信息都会被添加到 Cover 的内部数据库中。覆盖率分析是通过检查 Cover 数据库的内容来执行的。输出 Answer
由两个参数确定:Level
和 Analysis
。
Level = module
Answer = {Module,Value}
,其中Module
是模块名称。Level = function
Answer = [{Function,Value}]
,模块中每个函数一个元组。一个函数由它的模块名称M
、函数名称F
和元数A
指定为一个元组{M,F,A}
。Level = clause
Answer = [{Clause,Value}]
,模块中每个子句一个元组。一个子句由它的模块名称M
、函数名称F
、元数A
和在函数定义中的位置C
指定为一个元组{M,F,A,C}
。Level = line
Answer = [{Line,Value}]
,模块中每个可执行行一个元组。一行由它的模块名称M
和源代码文件中的行号N
指定为一个元组{M,N}
。Analysis = coverage
Value = {Cov,NotCov}
,其中Cov
是模块、函数、子句或行中至少被执行一次的可执行行的数量,NotCov
是未被执行的可执行行的数量。Analysis = calls
Value = Calls
,它是模块、函数或子句被调用的次数。对于行级分析,Calls
是该行被执行的次数。
分布式
Cover 可用于分布式 Erlang 系统。系统中的一个节点必须被选为主节点,并且所有 Cover 命令都必须从该节点执行。如果在远程节点上调用接口函数,则会返回错误原因 not_main_node
。
使用 cover:start/1
和 cover:stop/1
添加或删除节点。相同的覆盖率编译代码将加载到每个节点上,并且分析将收集并汇总来自所有节点的覆盖率数据结果。
要仅从远程节点收集数据而不停止这些节点上的 cover
,请使用 cover:flush/1
。
如果到远程节点的连接中断,主节点会将其标记为丢失。如果该节点恢复,它将再次被添加。如果远程节点在断开连接期间处于活动状态,则此期间之前和期间的覆盖率数据将包含在分析中。
总结
函数
分析由 Arg
指定的一个或多个模块。
分析由 Arg1
和 Arg2
指定的一个或多个模块。
通过检查内部数据库的内容,按照 Analysis
和 Level
的指定,对一个或多个覆盖率编译的模块执行分析。
如果 Arg
是一个 analyse_option()
选项列表,则此调用等价于 analyse_to_file('_', Arg)
。
输出给定模块的源代码副本,并为每个可执行行注释执行计数。
此函数的工作方式与 analyse_to_file/2
相同,但它是异步的而不是同步的。
基于包含抽象代码(选项 debug_info
)的 .beam
文件,对一个或多个模块进行覆盖率编译。
以与 compile_beam/1
相同的方式,对目录 Dir
中的所有 .beam
文件进行覆盖率编译。
以与 compile_module/1,2
相同的方式,编译目录 Dir
中的所有模块(.erl
文件)以进行 Cover 分析,并返回 Result
列表。
对一个或多个模块进行覆盖率编译。
将 Module
的当前覆盖率数据导出到文件 ExportFile
。
从远程节点上的 Cover 数据库获取数据,并将其存储在主节点上。
从使用 export/1,2
创建的文件 ExportFile
导入覆盖率数据。
返回所有导入文件的列表。
返回所有存在导入数据的模块的列表。
如果模块 Module
已覆盖率编译,则返回 {file, File}
,否则返回 false
。
仅支持在本地节点上运行 Cover。
返回当前已覆盖率编译的所有模块的列表。
重置所有节点上的 Cover 数据库中所有已覆盖率编译模块的所有覆盖率数据。
重置所有节点上的 Cover 数据库中已覆盖率编译模块 Module
的所有覆盖率数据。
启动拥有 Cover 内部数据库的 Cover 服务器。此函数由模块中的其他函数自动调用。
在每个给定节点上启动 Cover 服务器,并加载所有已覆盖率编译的模块。
停止 Cover 服务器并卸载所有已覆盖率编译的代码。
停止给定节点上的 Cover 服务器并卸载所有已覆盖率编译的代码。
返回作为覆盖率分析一部分的所有节点的列表。
类型
-type analyse_answer() :: {ok, OutFile :: file:filename()} | {error, analyse_rsn()}.
-type analyse_fail() :: [{not_cover_compiled, module()}].
-type analyse_file_fail() :: [analyse_rsn()].
-type analyse_file_ok() :: [OutFile :: file:filename()].
-type analyse_item() :: (Line :: {M :: module(), N :: non_neg_integer()}) | (Clause :: {M :: module(), F :: atom(), A :: arity(), C :: non_neg_integer()}) | (Function :: {M :: module(), F :: atom(), A :: arity()}).
-type analyse_ok() :: [{Module :: module(), Value :: analyse_value()}] | [{Item :: analyse_item(), Value :: analyse_value()}].
-type analyse_option() :: html | {outfile, OutFile :: file:filename()} | {outdir, OutDir :: file:filename()}.
-type analyse_rsn() :: {not_cover_compiled, Module :: module()} | {file, File :: file:filename(), Reason :: term()} | {no_source_code_found, Module :: module()}.
-type analyse_value() :: {Cov :: non_neg_integer(), NotCov :: non_neg_integer()} | (Calls :: non_neg_integer()).
-type analysis() :: coverage | calls.
-type beam_mod_file() :: (Module :: module()) | (BeamFile :: file:filename()).
-type beam_mod_files() :: beam_mod_file() | [beam_mod_file()].
-type compile_beam_result() :: {ok, module()} | {error, BeamFile :: file:filename()} | {error, Reason :: compile_beam_rsn()}.
-type compile_beam_rsn() :: non_existing | {no_abstract_code, BeamFile :: file:filename()} | {encrypted_abstract_code, BeamFile :: file:filename()} | {already_cover_compiled, no_beam_found, module()} | {{missing_backend, module()}, BeamFile :: file:filename()} | {no_file_attribute, BeamFile :: file:filename()} | not_main_node.
-type compile_result() :: {ok, Module :: module()} | {error, file:filename()} | {error, not_main_node}.
-type export_reason() :: {not_cover_compiled, Module :: module()} | {cant_open_file, ExportFile :: file:filename(), FileReason :: term()} | not_main_node.
-type file_error() :: eacces | enoent.
-type level() :: line | clause | function | module.
-type mod_file() :: (Module :: module()) | (File :: file:filename()).
-type one_result() :: {ok, {Module :: module(), Value :: analyse_value()}} | {ok, [{Item :: analyse_item(), Value :: analyse_value()}]} | {error, {not_cover_compiled, module()}}.
-type option() :: {i, Dir :: file:filename()} | {d, Macro :: atom()} | {d, Macro :: atom(), Value :: term()} | export_all.
函数
-spec analyse() -> {result, analyse_ok(), analyse_fail()} | {error, not_main_node}.
-spec analyse(Analysis) -> {result, analyse_ok(), analyse_fail()} | {error, not_main_node} when Analysis :: analysis(); (Level) -> {result, analyse_ok(), analyse_fail()} | {error, not_main_node} when Level :: level(); (Modules) -> OneResult | {result, analyse_ok(), analyse_fail()} | {error, not_main_node} when Modules :: modules(), OneResult :: one_result().
分析由 Arg
指定的一个或多个模块。
如果 Arg
是 analysis()
中的一个值,则此调用等效于 analyse('_', Arg, function)
。
如果 Arg
是 level()
中的一个值,则此调用等效于 analyse('_', coverage, Arg)
。
否则,Arg
被假定为模块名,此调用等效于 analyse(Arg, coverage, function)
。
注意
要分析名称与
analysis()
或level()
中的值重叠的模块,模块名称必须在列表中。 例如,要分析名为calls
的模块cover:analyse([calls]).
-spec analyse(Analysis, Level) -> {result, analyse_ok(), analyse_fail()} | {error, not_main_node} when Analysis :: analysis(), Level :: level(); (Modules, Analysis) -> OneResult | {result, analyse_ok(), analyse_fail()} | {error, not_main_node} when Analysis :: analysis(), Modules :: modules(), OneResult :: one_result(); (Modules, Level) -> OneResult | {result, analyse_ok(), analyse_fail()} | {error, not_main_node} when Level :: level(), Modules :: modules(), OneResult :: one_result().
分析由 Arg1
和 Arg2
指定的一个或多个模块。
如果 Arg1
是 analysis()
中的一个值,且 Arg2
是 level()
中的一个值,则此调用等效于 analyse('_', Arg1, Arg2)
。
如果 Arg2
是 analysis()
中的一个值,则假定 Arg1
是一个模块,并且此调用等效于 analyse(Arg1, Arg2, function)
。
如果 Arg2
是 level()
中的一个值,则假定 Arg1
是一个模块,并且此调用等效于 analyse(Arg1, coverage, Arg2)
。
注意
要分析名称与
analysis()
中的值重叠的模块,模块名称需要放在列表中。例如,要分析名为calls
的模块cover:analyse([calls], function).
-spec analyse(Modules, Analysis, Level) -> OneResult | {result, analyse_ok(), analyse_fail()} | {error, not_main_node} when Analysis :: analysis(), Level :: level(), Modules :: modules(), OneResult :: one_result().
通过检查内部数据库的内容,按照 Analysis
和 Level
的指定,对一个或多个覆盖率编译的模块执行分析。
如果 Modules
是一个原子(单个模块),则返回 OneResult
,否则返回 {result, Ok, Fail}
。
如果 Modules
是原子 '_'
,则分析 cover 数据表中具有数据的所有模块。 请注意,这包括 cover 编译的模块和导入的模块。
如果给定的模块未进行 cover 编译,则由错误原因 {not_cover_compiled, Module}
指示。
-spec analyse_to_file() -> {result, analyse_file_ok(), analyse_file_fail()} | {error, not_main_node}.
-spec analyse_to_file(Modules) -> Answer | {result, analyse_file_ok(), analyse_file_fail()} | {error, not_main_node} when Modules :: modules(), Answer :: analyse_answer(); (Options) -> {result, analyse_file_ok(), analyse_file_fail()} | {error, not_main_node} when Options :: [analyse_option()].
如果 Arg
是一个 analyse_option()
选项列表,则此调用等价于 analyse_to_file('_', Arg)
。
否则,Arg
被假定为模块,此调用等效于 analyse_to_file(Arg, [])
。
注意
要分析名称为
html
的模块(与analyse_option()
中的一个选项重叠),则必须使用cover:analyse_to_file/2
cover:analyse_to_file([html], []).
-spec analyse_to_file(Modules, Options) -> Answer | {result, analyse_file_ok(), analyse_file_fail()} | {error, not_main_node} when Modules :: modules(), Options :: [analyse_option()], Answer :: analyse_answer().
输出给定模块的源代码副本,并为每个可执行行注释执行计数。
输出文件 OutFile
默认为 Module.COVER.out
,如果使用 html
选项,则默认为 Module.COVER.html
。
如果 Modules
是一个原子(一个模块),则返回 Answer
,否则返回一个列表 {result, Ok, Fail}
。
如果 Modules
是 '_',则分析 Cover 数据表中具有数据的所有模块。 请注意,这包括 cover 编译的模块和导入的模块。
如果模块未进行 cover 编译,则由错误原因 {not_cover_compiled, Module}
指示。
如果无法使用 file:open/2
打开源文件和/或输出文件,则该函数返回 {error, {file, File, Reason}}
,其中 File
是文件名,Reason
是错误原因。
如果模块是从 .beam
文件进行 cover 编译的,即使用 compile_beam/1
或 compile_beam_directory/0,1
,则假定可以在以下位置之一找到源代码
- 与
.beam
文件相同的目录 - 相对于具有
.beam
文件的目录的../src
Module:module_info(compile)
中的源路径,在这种情况下,将检查两个路径- 首先,通过连接
../src
和编译路径的尾部(低于尾部的src
组件)来构造的路径 - 编译路径本身
- 首先,通过连接
如果找不到源代码,则由错误原因 {no_source_code_found, Module}
指示。
-spec async_analyse_to_file(Module, OutFile) -> pid() when Module :: module(), OutFile :: file:filename(); (Module, Options) -> pid() when Module :: module(), Options :: [Option], Option :: html.
-spec async_analyse_to_file(Module, OutFile, Options) -> pid() when Module :: module(), OutFile :: file:filename(), Options :: [Option], Option :: html.
此函数的工作方式与 analyse_to_file/2
相同,但它是异步的而不是同步的。
创建时,生成的进程将与调用方链接。 如果在进行 cover 分析时发生 analyse_rsn()
类型的错误,该进程将崩溃,并使用与 analyse_to_file
返回的相同的错误原因。
-spec compile(ModFiles) -> Result | [Result] when ModFiles :: mod_files(), Result :: compile_result().
-spec compile(ModFiles, Options) -> Result | [Result] when ModFiles :: mod_files(), Options :: [option()], Result :: compile_result().
-spec compile_beam(ModFiles) -> Result | [Result] when ModFiles :: beam_mod_files(), Result :: compile_beam_result().
基于包含抽象代码(选项 debug_info
)的 .beam
文件,对一个或多个模块进行覆盖率编译。
从 .beam
文件进行 Cover 编译比从源代码进行编译更快,也更方便,因为无需为包含路径或宏提供选项。 但是,现有的 .beam
文件必须使用选项 debug_info
进行编译,以便它们包含抽象代码。
如果缺少抽象代码,则返回错误原因 {no_abstract_code, BeamFile}
。 如果抽象代码已加密,并且没有可用于解密的密钥,则返回错误原因 {encrypted_abstract_code, BeamFile}
。
如果仅将模块名称(即,不是 .beam
文件的完整名称)提供给此函数,则通过调用 code:which(Module)
来查找 .beam
文件。 如果找不到 .beam
文件,则返回错误原因 non_existing
。 如果该模块已使用 compile_beam/1
进行 cover 编译,则 .beam
文件将从首次编译时的相同位置选取。 如果该模块已使用 compile_module/2
进行 cover 编译,则无法找到正确的 .beam
文件,因此返回错误原因 {already_cover_compiled, no_beam_found, Module}
。
如果无法在节点上加载编译的代码,则返回 {error, BeamFile}
。
如果将 ModFiles
列表作为输入提供,则将返回 Result
列表。 返回列表的顺序是未定义的。
-spec compile_beam_directory() -> [Result] | {error, Reason} when Reason :: file_error(), Result :: compile_beam_result().
-spec compile_beam_directory(Dir) -> [Result] | {error, Reason} when Dir :: file:filename(), Reason :: file_error(), Result :: compile_beam_result().
以与 compile_beam/1
相同的方式,对目录 Dir
中的所有 .beam
文件进行覆盖率编译。
如果成功,此函数将返回 compile_beam_result()
列表。 否则,如果目录不可读,则返回 {error, eacces}
,如果该目录不存在,则返回 {error, enoent}
。
-spec compile_directory() -> [Result] | {error, Reason} when Reason :: file_error(), Result :: compile_result().
-spec compile_directory(Dir) -> [Result] | {error, Reason} when Dir :: file:filename(), Reason :: file_error(), Result :: compile_result().
-spec compile_directory(Dir, Options) -> [Result] | {error, Reason} when Dir :: file:filename(), Options :: [option()], Reason :: file_error(), Result :: compile_result().
以与 compile_module/1,2
相同的方式,编译目录 Dir
中的所有模块(.erl
文件)以进行 Cover 分析,并返回 Result
列表。
如果目录不可读,此函数将返回 {error, eacces}
,如果该目录不存在,则返回 {error, enoent}
。
-spec compile_module(ModFiles) -> Result | [Result] when ModFiles :: mod_files(), Result :: compile_result().
-spec compile_module(ModFiles, Options) -> Result | [Result] when ModFiles :: mod_files(), Options :: [option()], Result :: compile_result().
对一个或多个模块进行覆盖率编译。
模块由其模块名称 Module
或其文件名 File
给出。
可以省略 .erl
扩展名。如果模块不在当前目录中,则必须指定其完整路径。
Options
是编译器选项的列表。只有定义包含文件目录和宏的选项会被传递给 compile:file/2
;其他所有选项都会被忽略。
如果模块成功进行覆盖编译,该函数会返回 {ok, Module}
。否则,该函数会返回 {error, File}
。错误和警告会在发生时打印出来。
如果将 ModFiles
列表作为输入,则会返回一个 Result
列表。列表中返回结果的顺序是未定义的。
请注意,内部数据库在编译期间初始化,这意味着该模块之前收集的任何覆盖率数据都将丢失。
-spec export(File) -> ok | {error, Reason} when File :: file:filename(), Reason :: export_reason().
等价于 export(File, '_')
。
-spec export(File, Module) -> ok | {error, Reason} when File :: file:filename(), Module :: module(), Reason :: export_reason().
将 Module
的当前覆盖率数据导出到文件 ExportFile
。
建议使用 .coverdata
扩展名来命名 ExportFile
。
如果 Module
为 '_',则会导出所有已进行覆盖编译或较早导入的模块的数据。
如果要合并来自不同系统的覆盖率数据,此函数会很有用。
另请参见 import/1
。
从远程节点上的 Cover 数据库获取数据,并将其存储在主节点上。
-spec import(ExportFile) -> ok | {error, Reason} when ExportFile :: file:filename(), Reason :: {cant_open_file, ExportFile, FileReason :: term()} | not_main_node.
从使用 export/1,2
创建的文件 ExportFile
导入覆盖率数据。
在此调用之后执行的任何分析都将包括导入的数据。
请注意,在编译模块时,所有现有的覆盖率数据都会被删除,包括导入的数据。如果导入数据时模块已经编译,则导入的数据会添加到现有的覆盖率数据中。
可以将来自多个导出文件的覆盖率数据导入到一个系统中。然后,在分析时会累加覆盖率数据。
除非首先重置或编译模块,否则不能从同一个文件导入同一个模块的覆盖率数据两次。该检查基于文件名,因此您可以通过重命名导出文件来轻松欺骗系统。
-spec imported() -> [file:filename()] | {error, not_main_node}.
返回所有导入文件的列表。
-spec imported_modules() -> [module()] | {error, not_main_node}.
返回所有存在导入数据的模块的列表。
-spec is_compiled(Module) -> {file, File :: file:filename()} | false | {error, not_main_node} when Module :: module().
如果模块 Module
已覆盖率编译,则返回 {file, File}
,否则返回 false
。
File
是 compile_module/1,2
使用的 .erl
文件,或者 compile_beam/1
使用的 .beam
文件。
-spec local_only() -> ok | {error, too_late}.
仅支持在本地节点上运行 Cover。
必须在编译任何模块或添加任何节点之前调用此函数。在此模式下运行时,模块将以更有效的方式进行覆盖编译,但生成的代码只能在编译它们的同一节点上工作。
-spec modules() -> [module()] | {error, not_main_node}.
返回当前已覆盖率编译的所有模块的列表。
-spec reset() -> ok | {error, not_main_node}.
重置所有节点上的 Cover 数据库中所有已覆盖率编译模块的所有覆盖率数据。
-spec reset(Module) -> ok | {error, not_main_node} | {error, {not_cover_compiled, Module}} when Module :: module().
重置所有节点上的 Cover 数据库中已覆盖率编译模块 Module
的所有覆盖率数据。
如果 Module
未进行覆盖编译,则该函数会返回 {error, {not_cover_compiled, Module}}
。
启动拥有 Cover 内部数据库的 Cover 服务器。此函数由模块中的其他函数自动调用。
-spec start(Nodes) -> {ok, StartedNodes} | {error, not_main_node} | {error, local_only} when Nodes :: node() | [node()], StartedNodes :: [node()].
在每个给定节点上启动 Cover 服务器,并加载所有已覆盖率编译的模块。
如果已调用 cover:local_only/0
,则此调用将失败。
-spec stop() -> ok | {error, not_main_node}.
停止 Cover 服务器并卸载所有已覆盖率编译的代码。
停止给定节点上的 Cover 服务器并卸载所有已覆盖率编译的代码。
将从远程节点上的 Cover 数据库中提取数据并存储在主节点上。
-spec which_nodes() -> [node()].
返回作为覆盖率分析一部分的所有节点的列表。
请注意,不包括当前节点,因为它始终是分析的一部分。