代码覆盖率信息是软件开发的一个重要技术指标。从0.10.2.14版本开始,MYCDT支持收集C++ WASM合约的代码覆盖率信息,您只需要在编译合约时增加参数--coverage
来指示编译器进行代码插桩即可,然后正常部署运行合约,代码覆盖率信息会通过交易的Log返回,对应的topic
为coverage
,信息存放于data
字段。
注意:此种模式下编译出来的合约字节码文件,不能用于生成环境。
接下来,我们仍旧以hello world
合约为例来演示如何收集合约代码覆盖率。
编译合约
合约源代码如下。
#include <mychainlib/contract.h>
class Hello : public mychain::Contract {
public:
INTERFACE void hi() {
mychain::print("Hi");
}
};
INTERFACE_EXPORT(Hello, (hi))
编译时使用参数--coverage
。
$ my++ hello.cc -o hello.wasm --coverage
编译完成后,除了合约字节码 hello.wasm
外,在当前目录下还会产生一个名为 hello.gcno
的文件。这个文件在生成覆盖率报告时将会用到,请不要删除。
运行合约
合约在链上执行时,会把动态生成的覆盖率信息保存到log
中,topic
为coverage
,因此您需要在SDK侧获取topic
为coverage
的log
信息,每条log
的内容通过LEB128解码可以得到两个字段:覆盖率文件路径和覆盖率信息,请根据文件路径信息来保存覆盖率信息。覆盖率信息文件以.gcda
结尾,对应于上面一步MYCDT生成的.gcno
文件。
不同SDK收集覆盖率的具体实现不同,以C++SDK为例。
bool CallHi(const Identity& from) {
// 调用合约
auto params = std::make_shared<WASMParameter>();
params->SetFunctionSelector("hi");
auto req = std::make_shared<CallContractRequest>(from, contract_id, VMType::WASM, params, 0);
auto res = client_ptr->GetContractService()->CallContract(req);
if (res->GetReturnCode() != ErrorCode::SUCCESS || res->tx_receipt_.result_ != 0) {
LOG_ERROR(env->logger_, "call contract failed, error code:%s, receipt result:%s",
StringForErrorCode(res->GetReturnCode()).c_str(),
StringForErrorCode(res->tx_receipt_.result_).c_str());
return false;
}
// 保存覆盖率信息
std::string topic = "636f766572616765"; // hex from "coverage"
for (auto& log : res->tx_receipt_.logs_) {
if (!log.MatchTopic(topic))
continue;
WASMOutput output;
output.SetOutput(log.log_data_);
auto path = output.GetString();
auto data = output.GetString();
std::ofstream fs(path, std::ios::binary);
if (!fs.is_open())
LOG_WARN(env->logger_, "failed to save file: %s", path.c_str());
fs.write(data.data(), data.size());
}
return true;
}
生成覆盖率报告
使用MYCDT提供的llvm-gcov
工具处理编译时生成的hello.gcno
文件和运行时得到的hello.gcda
文件,得到直观的覆盖率报告。
$ llvm-gcov hello.gcda
上述命令会隐式的寻找hello.gcno
文件。最终会在当前目录生成一个名为hello.cc.gcov
文件,用文本编辑器打开该文件可以看到代码覆盖率信息,如下所示。
-: 0:Source:hello.cc
-: 0:Graph:hello.gcno
-: 0:Data:hello.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <mychainlib/contract.h>
-: 2:
1: 3:class Hello : public mychain::Contract {
-: 4:public:
1: 5: INTERFACE void hi() {
1: 6: mychain::print("Hi");
1: 7: }
-: 8:};
-: 9:
2: 10:INTERFACE_EXPORT(Hello, (hi))
生成网页版覆盖率报告
如果要生成网页形式的覆盖率报告,需要使用1.13版本以上的第三方工具lcov
,您可以根据以下安装方式完成此工具的安装,MYCDT中暂不单独提供。
在Mac OS上安装lcov方式如下:
先从https://brew.sh/ 安装homebrew工具,然后终端控制台下执行
brew install lcov
进行安装。在 CentOS 8上安装lcov方式如下(安装1.14版本的lcov):
yum install -y lcov
在 Ubuntu 20.04上安装lcov方式如下(安装1.14版本的lcov):
apt install -y lcov
安装完成lcov后可以执行lcov -v检查安装的lcov版本大于1.13版本。
报告生成命令如下:
$ lcov -c -d . -o coverage.info --gcov-tool llvm-gcov
$ genhtml coverage.info -o coverage-html
上述命令执行完成后会生成一个coverage-html
目录,用浏览器打开该目录下的index.html
文件即可看到网页版的覆盖率报告。