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 occurred
template <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 pattern
r[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 pattern
ASSERT_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 pattern
r[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 pattern
ASSERT_EQ(int(p[b]) & 0xff, i % 256);
}
}
}
注意里面的ASSERT_GE(arena.MemoryUsage(), bytes);等,这样的代码就会生成一个匿名对象,里面保存了文件名和行号,当出错的时候就会将出错信息保存在stringstream中,之后在析构这个匿名对象时就会打印到stderr中。
int main(int argc, char** argv) {
return leveldb::test::RunAllTests();
}
这样就会调用多有已经注册的测试函数。