mirror of
https://frontier.innolan.net/rainlance/c-ares.git
synced 2025-10-05 08:19:29 +00:00
test: Make contained tests easier to write
This commit is contained in:
@ -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");
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
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
|
||||
|
Reference in New Issue
Block a user