C++ 测试夹具 testharness leveldb
leveldb中的测试夹具
首先贴出leveldb中testharness相关的源码,之后再给出自己的理解。写这个主要是因为有了测试夹具能明显提高我们的工作效率,他能方便的给我们指出哪里是错误的。方便我们进行定位错误和修改。
这个是testharness.h
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.#ifndef STORAGE_LEVELDB_UTIL_TESTHARNESS_H_
#define STORAGE_LEVELDB_UTIL_TESTHARNESS_H_#include <stdio.h>
#include <stdlib.h>
#include <sstream>
#include "leveldb/env.h"
#include "leveldb/slice.h"
#include "util/random.h"namespace leveldb {
namespace test {// Run some of the tests registered by the TEST() macro. If the
// environment variable "LEVELDB_TESTS" is not set, runs all tests.
// Otherwise, runs only the tests whose name contains the value of
// "LEVELDB_TESTS" as a substring. E.g., suppose the tests are:
// TEST(Foo, Hello) { ... }
// TEST(Foo, World) { ... }
// LEVELDB_TESTS=Hello will run the first test
// LEVELDB_TESTS=o will run both tests
// LEVELDB_TESTS=Junk will run no tests
//
// Returns 0 if all tests pass.
// Dies or returns a non-zero value if some test fails.
extern int RunAllTests();// Return the directory to use for temporary storage.
extern std::string TmpDir();// Return a randomization seed for this run. Typically returns the
// same number on repeated invocations of this binary, but automated
// runs may be able to vary the seed.
extern int RandomSeed();// An instance of Tester is allocated to hold temporary state during
// the execution of an assertion.
class Tester {private:bool ok_;const char* fname_;int line_;std::stringstream ss_;public:Tester(const char* f, int l): ok_(true), fname_(f), line_(l) {}~Tester() {if (!ok_) {fprintf(stderr, "%s:%d:%s\n", fname_, line_, ss_.str().c_str());exit(1);}}Tester& Is(bool b, const char* msg) {if (!b) {ss_ << " Assertion failure " << msg;ok_ = false;}return *this;}Tester& IsOk(const Status& s) {if (!s.ok()) {ss_ << " " << s.ToString();ok_ = false;}return *this;}#define BINARY_OP(name,op) \template <class X, class Y> \Tester& name(const X& x, const Y& y) { \if (! (x op y)) { \ss_ << " failed: " << x << (" " #op " ") << y; \ok_ = false; \} \return *this; \}BINARY_OP(IsEq, ==)BINARY_OP(IsNe, !=)BINARY_OP(IsGe, >=)BINARY_OP(IsGt, >)BINARY_OP(IsLe, <=)BINARY_OP(IsLt, <)
#undef BINARY_OP// Attach the specified value to the error message if an error has occurredtemplate <class V>Tester& operator<<(const V& value) {if (!ok_) {ss_ << " " << value;}return *this;}
};#define ASSERT_TRUE(c) ::leveldb::test::Tester(__FILE__, __LINE__).Is((c), #c)
#define ASSERT_OK(s) ::leveldb::test::Tester(__FILE__, __LINE__).IsOk((s))
#define ASSERT_EQ(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsEq((a),(b))
#define ASSERT_NE(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsNe((a),(b))
#define ASSERT_GE(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsGe((a),(b))
#define ASSERT_GT(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsGt((a),(b))
#define ASSERT_LE(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsLe((a),(b))
#define ASSERT_LT(a,b) ::leveldb::test::Tester(__FILE__, __LINE__).IsLt((a),(b))#define TCONCAT(a,b) TCONCAT1(a,b)
#define TCONCAT1(a,b) a##b#define TEST(base,name) \
class TCONCAT(_Test_,name) : public base { \public: \void _Run(); \static void _RunIt() { \TCONCAT(_Test_,name) t; \t._Run(); \} \
}; \
bool TCONCAT(_Test_ignored_,name) = \::leveldb::test::RegisterTest(#base, #name, &TCONCAT(_Test_,name)::_RunIt); \
void TCONCAT(_Test_,name)::_Run()// Register the specified test. Typically not used directly, but
// invoked via the macro expansion of TEST.
extern bool RegisterTest(const char* base, const char* name, void (*func)());} // namespace test
} // namespace leveldb#endif // STORAGE_LEVELDB_UTIL_TESTHARNESS_H_
从上面的testharness.h中我们可以看出,由于这个是我摘自leveldb源码中的测试夹具,所以首先在leveldb命名空间中定义了一个新的命名空间test,这个命名空间中的各个成员仅仅是用于代码的测试工作。在test命名空间中,定义了一个tester类和一些独立的函数。这些独立的函数在下面的testharness.cc里进行了定义。主要完成的功能是对测试函数进行注册并运行,最后打印出统计信息。这里我们主要说一下tester类。
在tester类的实现中,我们可以看出在构造函数中,将测试所在的文件和行号保存在其成员变量中,然后还有一个stringstream用于保存一些打印信息,在最后析构对象的时候讲打印信息打到stderr上。里面还有个宏,
<p>#define BINARY_OP(name,op) \</p><p> template <class X, class Y> \</p><p> Tester& name(const X& x, const Y& y) { \</p><p> if (! (x op y)) { \</p><p> ss_ << " failed: " << x << (" "#op " ") << y; \</p><p> ok_ = false; \</p><p> } \</p><p> return *this; \</p><p> }</p><p> </p><p> BINARY_OP(IsEq, ==)</p><p> BINARY_OP(IsNe, !=)</p><p> BINARY_OP(IsGe, >=)</p><p> BINARY_OP(IsGt, >)</p><p> BINARY_OP(IsLe, <=)</p><p> BINARY_OP(IsLt, <)</p><p>#undef BINARY_OP</p>
BINARY_OP(IsEq, ==)经过预处理之后展开为template<class X, class Y> Tester& IsEq(const X& x, const Y& y) { if(! (x == y)) { ss_ << " failed: " << x << ("" "==" " ") << y; ok_ = false; } return *this; }
通过宏 BINARY_OP,定义了一个函数模板,这里的宏参数name用作函数模板的函数名,op用于两个函数参数的比较。这样就定义了六个函数模板。当BINARY_OP失败是将
失败信息保存在tester类中的stream中,用于以后的输出。宏里面的 #op 用于将op转换为其所对应的字符串。也就是 == !=这些运算符。
宏里面的#
#define ASSERT_TRUE(c)::leveldb::test::Tester(__FILE__, __LINE__).Is((c), #c)
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
printf(STR(vck)); // 输出字符串"vck"
printf("%d\n", CONS(2,3)); // 2e3 输出:2000
宏的巧用
#define ASSERT_EQ(a,b)::leveldb::test::Tester(__FILE__, __LINE__).IsEq((a),(b))
经过上面的分析知道IsEq成员函数是
template <class X, class Y> Tester& IsEq(const X& x, constY& y) { if (! (x == y)) { ss_ << " failed: " << x<< (" " "==" " ") << y; ok_ = false;} return *this; }
而上面这个宏就是把ASSERT_EQ(a,b)字符串替换为::leveldb::test::Tester(__FILE__, __LINE__).IsEq((a),(b))的字符串
如在arena_test.cc 文件中第59行定义的ASSERT_EQ(int(p[b]) & 0xff, i % 256);被扩展成
::leveldb::test::Tester("arena_test.cc",59).IsEq((int(p[b]) & 0xff),(i % 256));
::Tester("arena_test.cc",59).Is(c,"hahahaha");
::Tester("arena_test.cc", 59).Is(c,"hahaha");
::Tester("arena_test.cc", 59).Is(c,"haha");
这种调用方式仅仅是申请了一个无名的临时的类实例。有存储空间但是没有名字。多个重复的相同的无名变量有不同的内存空间。上面这三个是在不同的内存空间中,通过debug进行分析,先是执行初始化函数,然后才执行is函数。也就是初始化了三个匿名变量。
也就是调用tester中的IsEq成员函数。因为ASSERT_EQ(a,b)这个都是内嵌在别的成员函数中。
#define TCONCAT(a,b) TCONCAT1(a,b)
#define TCONCAT1(a,b) a##b//把a和b连接起来
#define TEST(base,name) \
class TCONCAT(_Test_,name) : public base { \public: \void _Run(); \static void _RunIt() { \TCONCAT(_Test_,name) t; \t._Run(); \} \
}; \
bool TCONCAT(_Test_ignored_,name) = \::leveldb::test::RegisterTest(#base, #name, &TCONCAT(_Test_,name)::_RunIt); \
void TCONCAT(_Test_,name)::_Run()
使用时的形式
TEST(ArenaTest, Empty) {
Arena arena;
}展开后的形式
class _Test_Empty : public ArenaTest {public: void _Run(); static void _RunIt() { _Test_Empty t; t._Run(); } }; bool_Test_ignored_Empty = ::leveldb::test::RegisterTest("ArenaTest","Empty", &_Test_Empty::_RunIt); void _Test_Empty::_Run() {
Arena arena;
}
首先定义一个新的类型,注意里面的静态成员方法
static void _RunIt() {
TCONCAT(_Test_,name) t;
t._Run();
}
之后定义了一个全局变量:全局变量的初始化会在main函数执行之前进行,而初始化函数对测试函数进行了注册。所以在main函数执行之前就已经注册好了要执行的测试函数。
bool TCONCAT(_Test_ignored_,name) =
::leveldb::test::RegisterTest(#base, #name,&TCONCAT(_Test_,name)::_RunIt);
注意这里是把这个新类型的静态方法进行了注册。静态成员函数的调用不需要有对象。
这样就可以发现这个宏也就是把一个函数形式的东西展开成一个类和成员函数的形式,并通过全局变量的形式对里面的静态成员函数进行了注册。
下面是testharness.c
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.#include "util/testharness.h"#include <string>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>namespace leveldb {
namespace test {namespace {
struct Test {const char* base;const char* name;void (*func)();
};
std::vector<Test>* tests;
}bool RegisterTest(const char* base, const char* name, void (*func)()) {if (tests == NULL) {tests = new std::vector<Test>;}Test t;t.base = base;t.name = name;t.func = func;tests->push_back(t);return true;
}int RunAllTests() {const char* matcher = getenv("LEVELDB_TESTS");int num = 0;if (tests != NULL) {for (size_t i = 0; i < tests->size(); i++) {const Test& t = (*tests)[i];if (matcher != NULL) {std::string name = t.base;name.push_back('.');name.append(t.name);if (strstr(name.c_str(), matcher) == NULL) {continue;}}fprintf(stderr, "==== Test %s.%s\n", t.base, t.name);(*t.func)();++num;}}fprintf(stderr, "==== PASSED %d tests\n", num);return 0;
}std::string TmpDir() {std::string dir;Status s = Env::Default()->GetTestDirectory(&dir);ASSERT_TRUE(s.ok()) << s.ToString();return dir;
}int RandomSeed() {const char* env = getenv("TEST_RANDOM_SEED");int result = (env != NULL ? atoi(env) : 301);if (result <= 0) {result = 301;}return result;
}} // namespace test
} // namespace leveldb
在testharness.cc中,首先在test命名空间中声明了一个匿名的命名空间,这个匿名的命名空间中声明的所有东西只能在这个文件中引用。这里我们需要注意一下这个匿名命名空间里的std::vector<Test>* tests;这个vector中保存了我们需要进行测试的函数。之后会统一调用这个vector中的所有函数。
首先是RegisterTest函数,这个函数把所有需要执行的测试函数和相关的信息添加进tests里面。
之后是RunAllTests函数,这个函数执行tests中所有的测试函数。如果我们设置了LEVELDB_TESTS环境变量,则只会运行与这个环境变量相关的测试函数。如果没有设置,则会运行所有注册过的测试函数。
匿名命名空间
当定义一个命名空间时,可以忽略这个命名空间的名称:
namespce {
char c;
int i;
double d;
}
编译器在内部会为这个命名空间生成一个唯一的名字,而且还会为这个匿名的命名空间生成一条using指令。所以上面的代码在效果上等同于:
namespace __UNIQUE_NAME_ {
char c;
int i;
double d;
}
using namespace __UNIQUE_NAME_;
在匿名命名空间中声明的名称也将被编译器转换,与编译器为这个匿名命名空间生成的唯一内部名称(即这里的__UNIQUE_NAME_)绑定在一起。还有一点很重要,就是这些名称具有internal链接属性,这和声明为static的全局名称的链接属性是相同的,即名称的作用域被限制在当前文件中,无法通过在另外的文件中使用extern声明来进行链接。如果不提倡使用全局static声明一个名称拥有internal链接属性,则匿名命名空间可以作为一种更好的达到相同效果的方法。
注意:命名空间都是具有external连接属性的,只是匿名的命名空间产生的__UNIQUE_NAME__在别的文件中无法得到,这个唯一的名字是不可见的.
C++ 新的标准中提倡使用匿名命名空间,而不推荐使用static,因为static用在不同的地方,涵义不同,容易造成混淆.另外,static不能修饰class
因为using namespace __UNIQUE_NAME_;声明
所以在此文件中这些匿名命名空间中的所有东西都是可见的。
使用时的形式
namespace leveldb {class ArenaTest { };TEST(ArenaTest, Simple) {std::vector<std::pair<size_t, char*> > allocated;Arena arena;const int N = 100000;size_t bytes = 0;Random rnd(301);for (int i = 0; i < N; i++) {size_t s;if (i % (N / 10) == 0) {s = i;} else {s = rnd.OneIn(4000) ? rnd.Uniform(6000) :(rnd.OneIn(10) ? rnd.Uniform(100) : rnd.Uniform(20));}if (s == 0) {// Our arena disallows size 0 allocations.s = 1;}char* r;if (rnd.OneIn(10)) {r = arena.AllocateAligned(s);} else {r = arena.Allocate(s);}for (size_t b = 0; b < s; b++) {// Fill the "i"th allocation with a known bit patternr[b] = i % 256;}bytes += s;allocated.push_back(std::make_pair(s, r));ASSERT_GE(arena.MemoryUsage(), bytes);if (i > N/10) {ASSERT_LE(arena.MemoryUsage(), bytes * 1.10);}}for (size_t i = 0; i < allocated.size(); i++) {size_t num_bytes = allocated[i].first;const char* p = allocated[i].second;for (size_t b = 0; b < num_bytes; b++) {// Check the "i"th allocation for the known bit patternASSERT_EQ(int(p[b]) & 0xff, i % 256);}}
}
};
预编译展开后
class _Test_Empty : public ArenaTest { public: void _Run(); static void _RunIt() { _Test_Empty t; t._Run(); } }; bool _Test_ignored_Empty = ::leveldb::test::RegisterTest("ArenaTest", "Empty", &_Test_Empty::_RunIt); void _Test_Empty::_Run(){std::vector<std::pair<size_t, char*> > allocated;Arena arena;const int N = 100000;size_t bytes = 0;Random rnd(301);for (int i = 0; i < N; i++) {size_t s;if (i % (N / 10) == 0) {s = i;} else {s = rnd.OneIn(4000) ? rnd.Uniform(6000) :(rnd.OneIn(10) ? rnd.Uniform(100) : rnd.Uniform(20));}if (s == 0) {// Our arena disallows size 0 allocations.s = 1;}char* r;if (rnd.OneIn(10)) {r = arena.AllocateAligned(s);} else {r = arena.Allocate(s);}for (size_t b = 0; b < s; b++) {// Fill the "i"th allocation with a known bit patternr[b] = i % 256;}bytes += s;allocated.push_back(std::make_pair(s, r));ASSERT_GE(arena.MemoryUsage(), bytes);if (i > N/10) {ASSERT_LE(arena.MemoryUsage(), bytes * 1.10);}}for (size_t i = 0; i < allocated.size(); i++) {size_t num_bytes = allocated[i].first;const char* p = allocated[i].second;for (size_t b = 0; b < num_bytes; b++) {// Check the "i"th allocation for the known bit patternASSERT_EQ(int(p[b]) & 0xff, i % 256);}}
}
注意里面的ASSERT_GE(arena.MemoryUsage(), bytes);等,这样的代码就会生成一个匿名对象,里面保存了文件名和行号,当出错的时候就会将出错信息保存在stringstream中,之后在析构这个匿名对象时就会打印到stderr中。
int main(int argc, char** argv) {return leveldb::test::RunAllTests();
}
这样就会调用多有已经注册的测试函数。
C++ 测试夹具 testharness leveldb相关推荐
- 从JoinBatchGroup 代码细节 来看Rocksdb的相比于leveldb的写入优势
文章目录 1. Rocksdb写入模型 2. LevelDB写入的优化点 3. Rocksdb 的优化 1. Busy Loop 2. Short Wait -- SOMETIMES busy Loo ...
- 数据集cifar10到Caffe支持的lmdb/leveldb转换的实现
在 http://blog.csdn.net/fengbingchun/article/details/53560637 对数据集cifar10进行过介绍,它是一个普通的物体识别数据集.为了使用Caf ...
- 【AI】caffe使用步骤(一):将标注数据生成lmdb或leveldb
1.简述 caffe使用工具 convert_imageset 将标注数据转换成lmdb或leveldb格式,convert_imageset 使用方法可以参考脚本examples/imagenet/ ...
- LevelDb系列之简介
说起LevelDb也许您不清楚,但是如果作为IT工程师,不知道下面两位大神级别的工程师,那您的领导估计会Hold不住了:Jeff Dean和Sanjay Ghemawat.这两位是Google公司重量 ...
- leveldb源码分析:数据查询
leveldb数据查询 查询的示例代码如下: string res; status = db->Get(ReadOptions(), "KeyNameExample", &a ...
- leveldb源码分析:数据插入续(跳表)
leveldb数据的插入-跳表 本文主要是接着上一篇文章,继续深入探索Write函数调用插入之后的流程. status = WriteBatchInternal::InsertInto(updates ...
- leveldb源码分析:数据插入与删除(Put与Delete)
leveldb数据的插入与获取 leveldb提供的数据的交互接口如下: // Set the database entry for "key" to "value&qu ...
- leveldb源码分析:Open启动流程
leveldb概述 Leveldb 是一个持久化的KV存储系统,主要将大部分数据存储在磁盘上,在存储数据的过程中,根据记录的key值有序存储,当然使用者也可以自定义Key大小比较函数,一个leveld ...
- windows下编译leveldb
前提条件,下载boost库并编译 boost库弄好后,就可以编译leveldb了 首先,下载leveldb-windows,这个github上有 一. 1文件->新建->从现有代码文件创建 ...
最新文章
- Firefox v5 正式版
- metabase 以链接或图片查看_Python下载微信公众号文章内的图片
- python PyQt5教程
- php对话框制作,js制作一个简单的对话框教程
- 8255编程c语言程序,51单片机8255驱动C程序
- 清华人工智能研究院院长张钹:深度学习的钥匙丢在黑暗角落
- php七牛云rtmp直播推流,GitHub - jangocheng/FlutterQiniucloudLivePlugin: Flutter 七牛云直播云 推流/播放 SDK集成...
- linux服务器系统_利用Zabbix监控系统监测Linux服务器系统时间是否准确完美实现...
- %lf 和 %f 有什么区别
- Java中Date和Calender类的使用方法
- 深度学习:词嵌入Embedding
- zend studio 导入已有项目
- 博客园添加背景音乐,给你的博文加点料
- oracle 官网下载
- 英语语法(简单句型篇)
- can和could的用法_情态动词can与could的用法
- 关于Mybatis中的条件查询。createCriteria example里面的条件
- Windows7旗舰版桌面整理
- 替换掉NO_OS逻辑和代码中的SPI部分
- 用于基于 CNT 的射频辐射热计开发研究的 CPX-VF 探针台