test: Make contained tests easier to write

pull/34/head
David Drysdale 9 years ago
parent 5dc450a4b3
commit b644412a66
  1. 65
      test/ares-test-init.cc
  2. 35
      test/ares-test-ns.cc
  3. 4
      test/ares-test.cc
  4. 43
      test/ares-test.h

@ -297,42 +297,35 @@ TEST(Init, NoLibraryInit) {
// These tests rely on the ability of non-root users to create a chroot
// using Linux namespaces.
TEST(LibraryInit, ContainerChannelInit) {
TransientDir root("chroot");
TransientDir etc("chroot/etc");
TransientFile resolv("chroot/etc/resolv.conf",
"nameserver 1.2.3.4\n"
"search first.com second.com\n");
TransientFile hosts("chroot/etc/hosts",
"3.4.5.6 ahostname.com");
TransientFile nsswitch("chroot/etc/nsswitch.conf",
"hosts: files\n");
auto testfn = [] () {
ares_channel channel = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
std::vector<std::string> actual = GetNameServers(channel);
std::vector<std::string> expected = {"1.2.3.4"};
EXPECT_EQ(expected, actual);
struct ares_options opts;
int optmask = 0;
ares_save_options(channel, &opts, &optmask);
EXPECT_EQ(2, opts.ndomains);
EXPECT_EQ(std::string("first.com"), std::string(opts.domains[0]));
EXPECT_EQ(std::string("second.com"), std::string(opts.domains[1]));
ares_destroy_options(&opts);
HostResult result;
ares_gethostbyname(channel, "ahostname.com", AF_INET, HostCallback, &result);
ProcessWork(channel, NoExtraFDs, nullptr);
EXPECT_TRUE(result.done_);
std::stringstream ss;
ss << result.host_;
EXPECT_EQ("{'ahostname.com' aliases=[] addrs=[3.4.5.6]}", ss.str());
return HasFailure();
};
CONTAINER_RUN("chroot", "myhostname", "mydomainname.org", testfn);
NameContentList filelist = {
{"/etc/resolv.conf", "nameserver 1.2.3.4\n"
"search first.com second.com\n"},
{"/etc/hosts", "3.4.5.6 ahostname.com\n"},
{"/etc/nsswitch.conf", "hosts: files\n"}};
CONTAINED_TEST_F(LibraryTest, ContainerChannelInit,
"myhostname", "mydomainname.org", filelist) {
ares_channel channel = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
std::vector<std::string> actual = GetNameServers(channel);
std::vector<std::string> expected = {"1.2.3.4"};
EXPECT_EQ(expected, actual);
struct ares_options opts;
int optmask = 0;
ares_save_options(channel, &opts, &optmask);
EXPECT_EQ(2, opts.ndomains);
EXPECT_EQ(std::string("first.com"), std::string(opts.domains[0]));
EXPECT_EQ(std::string("second.com"), std::string(opts.domains[1]));
ares_destroy_options(&opts);
HostResult result;
ares_gethostbyname(channel, "ahostname.com", AF_INET, HostCallback, &result);
ProcessWork(channel, NoExtraFDs, nullptr);
EXPECT_TRUE(result.done_);
std::stringstream ss;
ss << result.host_;
EXPECT_EQ("{'ahostname.com' aliases=[] addrs=[3.4.5.6]}", ss.str());
return HasFailure();
}
#endif

@ -135,6 +135,41 @@ int RunInContainer(const std::string& dirname, const std::string& hostname,
return status;
}
ContainerFilesystem::ContainerFilesystem(NameContentList files) {
rootdir_ = TempNam(nullptr, "ares-chroot");
mkdir(rootdir_.c_str(), 0755);
dirs_.push_front(rootdir_);
for (const auto& nc : files) {
std::string fullpath = rootdir_ + nc.first;
int idx = fullpath.rfind('/');
std::string dir = fullpath.substr(0, idx);
EnsureDirExists(dir);
files_.push_back(std::unique_ptr<TransientFile>(
new TransientFile(fullpath, nc.second)));
}
}
ContainerFilesystem::~ContainerFilesystem() {
files_.clear();
for (const std::string& dir : dirs_) {
rmdir(dir.c_str());
}
}
void ContainerFilesystem::EnsureDirExists(const std::string& dir) {
if (std::find(dirs_.begin(), dirs_.end(), dir) != dirs_.end()) {
return;
}
size_t idx = dir.rfind('/');
if (idx != std::string::npos) {
std::string prevdir = dir.substr(0, idx);
EnsureDirExists(prevdir);
}
// Ensure this directory is in the list before its ancestors.
mkdir(dir.c_str(), 0755);
dirs_.push_front(dir);
}
} // namespace test
} // namespace ares

@ -651,8 +651,6 @@ TransientFile::~TransientFile() {
unlink(filename_.c_str());
}
namespace {
std::string TempNam(const char *dir, const char *prefix) {
char *p = tempnam(dir, prefix);
std::string result(p);
@ -660,8 +658,6 @@ std::string TempNam(const char *dir, const char *prefix) {
return result;
}
} // namespace
TempFile::TempFile(const std::string& contents)
: TransientFile(TempNam(nullptr, "ares"), contents) {

@ -15,8 +15,12 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_USER_NAMESPACE) && defined(HAVE_UTS_NAMESPACE)
#define HAVE_CONTAINER
#endif
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <set>
@ -287,6 +291,9 @@ class TransientDir {
std::string dirname_;
};
// C++ wrapper around tempnam()
std::string TempNam(const char *dir, const char *prefix);
// RAII class to temporarily create file of a given name and contents.
class TransientFile {
public:
@ -330,14 +337,40 @@ class EnvValue {
};
#endif
// Linux-specific functionality for running code in a container.
#if defined(HAVE_USER_NAMESPACE) && defined(HAVE_UTS_NAMESPACE)
#define HAVE_CONTAINER
// Linux-specific functionality for running code in a container, implemented
// in ares-test-ns.cc
#ifdef HAVE_CONTAINER
typedef std::function<int(void)> VoidToIntFn;
typedef std::vector<std::pair<std::string, std::string>> NameContentList;
int RunInContainer(const std::string& dirname, const std::string& hostname,
const std::string& domainname, VoidToIntFn fn);
#define CONTAINER_RUN(dir, host, domain, fn) \
EXPECT_EQ(0, RunInContainer(dir, host, domain, static_cast<VoidToIntFn>(fn)));
class ContainerFilesystem {
public:
explicit ContainerFilesystem(NameContentList files);
~ContainerFilesystem();
std::string root() const { return rootdir_; };
private:
void EnsureDirExists(const std::string& dir);
std::string rootdir_;
std::list<std::string> dirs_;
std::vector<std::unique_ptr<TransientFile>> files_;
};
#define ICLASS_NAME(casename, testname) Contained##casename##_##testname
#define CONTAINED_TEST_F(casename, testname, hostname, domainname, files) \
class ICLASS_NAME(casename, testname) : public casename { \
public: \
ICLASS_NAME(casename, testname)() {} \
static int InnerTestBody(); \
}; \
TEST_F(ICLASS_NAME(casename, testname), _) { \
ContainerFilesystem chroot(files); \
VoidToIntFn fn(ICLASS_NAME(casename, testname)::InnerTestBody); \
EXPECT_EQ(0, RunInContainer(chroot.root(), hostname, domainname, fn)); \
} \
int ICLASS_NAME(casename, testname)::InnerTestBody()
#endif
} // namespace test

Loading…
Cancel
Save