Improve test coverage

This commit is contained in:
Marcus Holland-Moritz 2021-03-16 13:06:09 +01:00
parent 44b135f548
commit 6ef5361fc5
2 changed files with 331 additions and 24 deletions

View File

@ -19,8 +19,11 @@
* along with dwarfs. If not, see <https://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <map>
#include <set>
#include <sstream>
#include <vector>
#include <gtest/gtest.h>
@ -64,7 +67,9 @@ class dir_reader_mock : public dir_reader {
namespace {
struct simplestat {
::ino_t st_ino;
::mode_t st_mode;
::nlink_t st_nlink;
::uid_t st_uid;
::gid_t st_gid;
::off_t st_size;
@ -75,19 +80,24 @@ struct simplestat {
};
std::map<std::string, simplestat> statmap{
{"", {S_IFDIR | 0777, 1000, 100, 0, 0, 1, 2, 3}},
{"/test.pl", {S_IFREG | 0644, 1000, 100, 0, 0, 1001, 1002, 1003}},
{"/somelink", {S_IFLNK | 0777, 1000, 100, 16, 0, 2001, 2002, 2003}},
{"/somedir", {S_IFDIR | 0777, 1000, 100, 0, 0, 3001, 3002, 3003}},
{"/foo.pl", {S_IFREG | 0600, 1337, 0, 23456, 0, 4001, 4002, 4003}},
{"/ipsum.txt", {S_IFREG | 0644, 1000, 100, 2000000, 0, 5001, 5002, 5003}},
{"", {1, S_IFDIR | 0777, 1, 1000, 100, 0, 0, 1, 2, 3}},
{"/test.pl", {3, S_IFREG | 0644, 2, 1000, 100, 0, 0, 1001, 1002, 1003}},
{"/somelink", {4, S_IFLNK | 0777, 1, 1000, 100, 16, 0, 2001, 2002, 2003}},
{"/somedir", {5, S_IFDIR | 0777, 1, 1000, 100, 0, 0, 3001, 3002, 3003}},
{"/foo.pl", {6, S_IFREG | 0600, 2, 1337, 0, 23456, 0, 4001, 4002, 4003}},
{"/bar.pl", {6, S_IFREG | 0600, 2, 1337, 0, 23456, 0, 4001, 4002, 4003}},
{"/baz.pl", {16, S_IFREG | 0600, 2, 1337, 0, 23456, 0, 8001, 8002, 8003}},
{"/ipsum.txt",
{7, S_IFREG | 0644, 1, 1000, 100, 2000000, 0, 5001, 5002, 5003}},
{"/somedir/ipsum.py",
{S_IFREG | 0644, 1000, 100, 10000, 0, 6001, 6002, 6003}},
{"/somedir/bad", {S_IFLNK | 0777, 1000, 100, 6, 0, 7001, 7002, 7003}},
{"/somedir/pipe", {S_IFIFO | 0644, 1000, 100, 0, 0, 8001, 8002, 8003}},
{"/somedir/null", {S_IFCHR | 0666, 0, 0, 0, 259, 9001, 9002, 9003}},
{9, S_IFREG | 0644, 1, 1000, 100, 10000, 0, 6001, 6002, 6003}},
{"/somedir/bad",
{10, S_IFLNK | 0777, 1, 1000, 100, 6, 0, 7001, 7002, 7003}},
{"/somedir/pipe",
{12, S_IFIFO | 0644, 1, 1000, 100, 0, 0, 8001, 8002, 8003}},
{"/somedir/null", {13, S_IFCHR | 0666, 1, 0, 0, 0, 259, 9001, 9002, 9003}},
{"/somedir/zero",
{S_IFCHR | 0666, 0, 0, 0, 261, 4000010001, 4000020002, 4000030003}},
{14, S_IFCHR | 0666, 1, 0, 0, 0, 261, 4000010001, 4000020002, 4000030003}},
};
} // namespace
@ -96,7 +106,8 @@ class os_access_mock : public os_access {
std::shared_ptr<dir_reader> opendir(const std::string& path) const override {
if (path.empty()) {
std::vector<std::string> files{
".", "..", "test.pl", "somelink", "somedir", "foo.pl", "ipsum.txt",
".", "..", "test.pl", "somelink", "somedir",
"foo.pl", "bar.pl", "baz.pl", "ipsum.txt",
};
return std::make_shared<dir_reader_mock>(std::move(files));
@ -113,8 +124,10 @@ class os_access_mock : public os_access {
void lstat(const std::string& path, struct ::stat* st) const override {
const simplestat& sst = statmap[path];
::memset(st, 0, sizeof(*st));
std::memset(st, 0, sizeof(*st));
st->st_ino = sst.st_ino;
st->st_mode = sst.st_mode;
st->st_nlink = sst.st_nlink;
st->st_uid = sst.st_uid;
st->st_gid = sst.st_gid;
st->st_size = sst.st_size;
@ -122,7 +135,6 @@ class os_access_mock : public os_access {
st->st_mtime = sst.mtime;
st->st_ctime = sst.ctime;
st->st_rdev = sst.st_rdev;
st->st_nlink = 1;
}
std::string readlink(const std::string& path, size_t size) const override {
@ -179,7 +191,8 @@ namespace {
void basic_end_to_end_test(std::string const& compressor,
unsigned block_size_bits, file_order_mode file_order,
bool with_devices, bool with_specials, bool set_uid,
bool set_gid, bool set_time, bool keep_all_times) {
bool set_gid, bool set_time, bool keep_all_times,
bool enable_nlink) {
block_manager::config cfg;
scanner_options options;
@ -228,6 +241,7 @@ void basic_end_to_end_test(std::string const& compressor,
filesystem_options opts;
opts.block_cache.max_bytes = 1 << 20;
opts.metadata.enable_nlink = enable_nlink;
filesystem_v2 fs(lgr, mm, opts);
@ -331,6 +345,150 @@ void basic_end_to_end_test(std::string const& compressor,
} else {
EXPECT_FALSE(entry);
}
entry = fs.find("/");
ASSERT_TRUE(entry);
auto dir = fs.opendir(*entry);
ASSERT_TRUE(dir);
EXPECT_EQ(9, fs.dirsize(*dir));
entry = fs.find("/somedir");
ASSERT_TRUE(entry);
dir = fs.opendir(*entry);
ASSERT_TRUE(dir);
EXPECT_EQ(4 + 2 * with_devices + with_specials, fs.dirsize(*dir));
std::vector<std::string> names;
for (size_t i = 0; i < fs.dirsize(*dir); ++i) {
auto r = fs.readdir(*dir, i);
ASSERT_TRUE(r);
auto [view, name] = *r;
names.emplace_back(name);
}
std::vector<std::string> expected{
".",
"..",
"bad",
"ipsum.py",
};
if (with_devices) {
expected.emplace_back("null");
}
if (with_specials) {
expected.emplace_back("pipe");
}
if (with_devices) {
expected.emplace_back("zero");
}
EXPECT_EQ(expected, names);
entry = fs.find("/foo.pl");
ASSERT_TRUE(entry);
auto e2 = fs.find("/bar.pl");
ASSERT_TRUE(e2);
EXPECT_EQ(entry->inode(), e2->inode());
struct ::stat st1, st2;
ASSERT_EQ(0, fs.getattr(*entry, &st1));
ASSERT_EQ(0, fs.getattr(*e2, &st2));
EXPECT_EQ(st1.st_ino, st2.st_ino);
if (enable_nlink) {
EXPECT_EQ(3, st1.st_nlink); // TODO: this should be 2
EXPECT_EQ(3, st2.st_nlink); // TODO: this should be 2
}
entry = fs.find("/");
ASSERT_TRUE(entry);
EXPECT_EQ(0, entry->inode());
e2 = fs.find(0);
ASSERT_TRUE(e2);
EXPECT_EQ(e2->inode(), 0);
e2 = fs.find(0, "baz.pl");
ASSERT_TRUE(e2);
EXPECT_GT(e2->inode(), 0);
entry = fs.find(e2->inode());
ASSERT_TRUE(entry);
ASSERT_EQ(0, fs.getattr(*entry, &st1));
EXPECT_EQ(23456, st1.st_size);
e2 = fs.find(0, "somedir");
ASSERT_TRUE(e2);
ASSERT_EQ(0, fs.getattr(*e2, &st2));
entry = fs.find(st2.st_ino, "ipsum.py");
ASSERT_TRUE(entry);
ASSERT_EQ(0, fs.getattr(*entry, &st1));
EXPECT_EQ(10000, st1.st_size);
EXPECT_EQ(0, fs.access(*entry, R_OK, 1000, 100));
entry = fs.find(0, "baz.pl");
ASSERT_TRUE(entry);
EXPECT_EQ(set_uid ? EACCES : 0, fs.access(*entry, R_OK, 1337, 0));
using mptype = void (filesystem_v2::*)(
std::function<void(entry_view, directory_view)> const&) const;
for (auto mp : {static_cast<mptype>(&filesystem_v2::walk),
static_cast<mptype>(&filesystem_v2::walk_inode_order)}) {
std::map<std::string, struct ::stat> entries;
std::vector<int> inodes;
(fs.*mp)([&](entry_view e, directory_view d) {
struct ::stat stbuf;
ASSERT_EQ(0, fs.getattr(e, &stbuf));
inodes.push_back(stbuf.st_ino);
std::string path;
if (e.inode() > 0) {
if (auto dp = d.path(); !dp.empty()) {
path += "/" + dp;
}
path += "/" + std::string(e.name());
}
EXPECT_TRUE(entries.emplace(path, stbuf).second);
});
EXPECT_EQ(entries.size(), dwarfs::test::statmap.size() + 2 * with_devices +
with_specials - 3);
for (auto const& [p, st] : entries) {
auto const& stref = dwarfs::test::statmap.at(p);
EXPECT_EQ(stref.st_mode, st.st_mode) << p;
EXPECT_EQ(set_uid ? 0 : stref.st_uid, st.st_uid) << p;
EXPECT_EQ(set_gid ? 0 : stref.st_gid, st.st_gid) << p;
if (!S_ISDIR(st.st_mode)) {
EXPECT_EQ(stref.st_size, st.st_size) << p;
}
}
if (mp == static_cast<mptype>(&filesystem_v2::walk_inode_order)) {
EXPECT_TRUE(std::is_sorted(inodes.begin(), inodes.end()));
}
}
std::ostringstream dumpss;
fs.dump(dumpss, 9);
EXPECT_GT(dumpss.str().size(), 1000) << dumpss.str();
auto dyn = fs.metadata_as_dynamic();
EXPECT_TRUE(dyn.isObject());
auto json = fs.serialize_metadata_as_json(true);
EXPECT_GT(json.size(), 1000) << json;
json = fs.serialize_metadata_as_json(false);
EXPECT_GT(json.size(), 1000) << json;
}
std::vector<std::string> const compressions{"null",
@ -352,7 +510,8 @@ class compression_test
std::tuple<std::string, unsigned, file_order_mode>> {};
class scanner_test : public testing::TestWithParam<
std::tuple<bool, bool, bool, bool, bool, bool>> {};
std::tuple<bool, bool, bool, bool, bool, bool, bool>> {
};
TEST_P(compression_test, end_to_end) {
auto [compressor, block_size_bits, file_order] = GetParam();
@ -363,16 +522,16 @@ TEST_P(compression_test, end_to_end) {
}
basic_end_to_end_test(compressor, block_size_bits, file_order, true, true,
false, false, false, false);
false, false, false, false, false);
}
TEST_P(scanner_test, end_to_end) {
auto [with_devices, with_specials, set_uid, set_gid, set_time,
keep_all_times] = GetParam();
auto [with_devices, with_specials, set_uid, set_gid, set_time, keep_all_times,
enable_nlink] = GetParam();
basic_end_to_end_test(compressions[0], 15, file_order_mode::NONE,
with_devices, with_specials, set_uid, set_gid, set_time,
keep_all_times);
keep_all_times, enable_nlink);
}
INSTANTIATE_TEST_SUITE_P(
@ -386,5 +545,5 @@ INSTANTIATE_TEST_SUITE_P(
INSTANTIATE_TEST_SUITE_P(
dwarfs, scanner_test,
::testing::Combine(::testing::Bool(), ::testing::Bool(), ::testing::Bool(),
::testing::Bool(), ::testing::Bool(),
::testing::Bool(), ::testing::Bool(), ::testing::Bool(),
::testing::Bool()));

View File

@ -21,6 +21,11 @@
#include <gtest/gtest.h>
// TODO: this test should be autogenerated somehow...
#include <algorithm>
#include <cstring>
#include <map>
#include <sstream>
#include <string>
#include <tuple>
@ -163,13 +168,25 @@ std::vector<std::string> versions{
"0.3.0",
};
std::string format_sh = R"(#!/bin/bash
find test/ src/ include/ -type f -name '*.[ch]*' | xargs -d $'\n' clang-format -i
)";
struct ::stat make_stat(::mode_t mode, ::off_t size) {
struct ::stat st;
std::memset(&st, 0, sizeof(st));
st.st_mode = mode;
st.st_size = size;
return st;
}
} // namespace
using namespace dwarfs;
class compat : public testing::TestWithParam<std::string> {};
class compat_metadata : public testing::TestWithParam<std::string> {};
TEST_P(compat, backwards_compatibility) {
TEST_P(compat_metadata, backwards_compat) {
std::ostringstream oss;
stream_logger lgr(oss);
auto filename =
@ -180,7 +197,138 @@ TEST_P(compat, backwards_compatibility) {
EXPECT_EQ(ref, meta);
}
INSTANTIATE_TEST_SUITE_P(dwarfs, compat, ::testing::ValuesIn(versions));
INSTANTIATE_TEST_SUITE_P(dwarfs, compat_metadata,
::testing::ValuesIn(versions));
class compat_filesystem
: public testing::TestWithParam<std::tuple<std::string, bool>> {};
TEST_P(compat_filesystem, backwards_compat) {
auto [version, enable_nlink] = GetParam();
std::ostringstream oss;
stream_logger lgr(oss);
auto filename = std::string(TEST_DATA_DIR "/compat-v") + version + ".dwarfs";
filesystem_options opts;
opts.metadata.enable_nlink = enable_nlink;
filesystem_v2 fs(lgr, std::make_shared<mmap>(filename), opts);
auto json = fs.serialize_metadata_as_json(true);
EXPECT_GT(json.size(), 1000) << json;
std::ostringstream dumpss;
fs.dump(dumpss, 9);
EXPECT_GT(dumpss.str().size(), 1000) << dumpss.str();
auto entry = fs.find("/format.sh");
struct ::stat st;
ASSERT_TRUE(entry);
EXPECT_EQ(0, fs.getattr(*entry, &st));
EXPECT_EQ(94, st.st_size);
EXPECT_EQ(S_IFREG | 0755, st.st_mode);
EXPECT_EQ(1000, st.st_uid);
EXPECT_EQ(100, st.st_gid);
EXPECT_EQ(1606161908 + 1007022, st.st_atime);
EXPECT_EQ(1606161908 + 94137, st.st_mtime);
EXPECT_EQ(1606161908 + 94137, st.st_ctime);
EXPECT_EQ(0, fs.access(*entry, R_OK, 1000, 0));
auto inode = fs.open(*entry);
EXPECT_GE(inode, 0);
std::vector<char> buf(st.st_size);
auto rv = fs.read(inode, &buf[0], st.st_size, 0);
EXPECT_EQ(rv, st.st_size);
EXPECT_EQ(format_sh, std::string(buf.begin(), buf.end()));
entry = fs.find("/foo/bad");
ASSERT_TRUE(entry);
std::string link;
EXPECT_EQ(fs.readlink(*entry, &link), 0);
EXPECT_EQ(link, "../foo");
entry = fs.find(0, "foo");
ASSERT_TRUE(entry);
auto dir = fs.opendir(*entry);
ASSERT_TRUE(dir);
EXPECT_EQ(5, fs.dirsize(*dir));
std::vector<std::string> names;
for (size_t i = 0; i < fs.dirsize(*dir); ++i) {
auto r = fs.readdir(*dir, i);
ASSERT_TRUE(r);
auto [view, name] = *r;
names.emplace_back(name);
}
std::vector<std::string> expected{
".", "..", "bad", "bar", "bla.sh",
};
EXPECT_EQ(expected, names);
std::map<std::string, struct ::stat> ref_entries{
{"", make_stat(S_IFDIR | 0755, 8)},
{"/bench.sh", make_stat(S_IFREG | 0644, 1517)},
{"/dev", make_stat(S_IFDIR | 0755, 0)},
{"/empty", make_stat(S_IFDIR | 0755, 1)},
{"/empty/alsoempty", make_stat(S_IFDIR | 0755, 0)},
{"/foo", make_stat(S_IFDIR | 0755, 3)},
{"/foo/bad", make_stat(S_IFLNK | 0777, 6)},
{"/foo/bar", make_stat(S_IFREG | 0644, 0)},
{"/foo/bla.sh", make_stat(S_IFREG | 0644, 1517)},
{"/foobar", make_stat(S_IFLNK | 0777, 7)},
{"/format.sh", make_stat(S_IFREG | 0755, 94)},
{"/perl-exec.sh", make_stat(S_IFREG | 0644, 87)},
{"/test.py", make_stat(S_IFREG | 0644, 1012)},
};
using mptype = void (filesystem_v2::*)(
std::function<void(entry_view, directory_view)> const&) const;
for (auto mp : {static_cast<mptype>(&filesystem_v2::walk),
static_cast<mptype>(&filesystem_v2::walk_inode_order)}) {
std::map<std::string, struct ::stat> entries;
std::vector<int> inodes;
(fs.*mp)([&](entry_view e, directory_view d) {
struct ::stat stbuf;
ASSERT_EQ(0, fs.getattr(e, &stbuf));
inodes.push_back(stbuf.st_ino);
std::string path;
if (e.inode() > 0) {
if (auto dp = d.path(); !dp.empty()) {
path += "/" + dp;
}
path += "/" + std::string(e.name());
}
EXPECT_TRUE(entries.emplace(path, stbuf).second);
});
EXPECT_EQ(entries.size(), ref_entries.size());
for (auto const& [p, st] : entries) {
auto const& stref = ref_entries.at(p);
EXPECT_EQ(stref.st_mode, st.st_mode) << p;
EXPECT_EQ(1000, st.st_uid) << p;
EXPECT_EQ(100, st.st_gid) << p;
EXPECT_EQ(stref.st_size, st.st_size) << p;
}
if (mp == static_cast<mptype>(&filesystem_v2::walk_inode_order)) {
EXPECT_TRUE(std::is_sorted(inodes.begin(), inodes.end()));
}
}
}
INSTANTIATE_TEST_SUITE_P(dwarfs, compat_filesystem,
::testing::Combine(::testing::ValuesIn(versions),
::testing::Bool()));
class rewrite
: public testing::TestWithParam<std::tuple<std::string, bool, bool>> {};