// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

#include "gtest/gtest.h"
#include "include/cephfs/libcephfs.h"
#include "include/stat.h"
#include "include/ceph_assert.h"
#include "include/object.h"
#include "include/stringify.h"
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <vector>
#include <algorithm>
#include <limits.h>
#include <dirent.h>

using namespace std;
class TestMount {
  ceph_mount_info* cmount = nullptr;
  char dir_path[64];

public:
  TestMount( const char* root_dir_name = "dir0") {
    ceph_create(&cmount, NULL);
    ceph_conf_read_file(cmount, NULL);
    ceph_conf_parse_env(cmount, NULL);
    ceph_assert(0 == ceph_mount(cmount, NULL));

    sprintf(dir_path, "/%s_%d", root_dir_name, getpid());
    ceph_assert(0 == ceph_mkdir(cmount, dir_path, 0777));
  }
  ~TestMount()
  {
    if (cmount) {
      ceph_assert(0 == purge_dir(""));
    }
    ceph_rmdir(cmount, dir_path);
    ceph_shutdown(cmount);
  }

  int conf_get(const char *option, char *buf, size_t len) {
    return ceph_conf_get(cmount, option, buf, len);
  }

  string make_file_path(const char* relpath) {
    char path[PATH_MAX];
    sprintf(path, "%s/%s", dir_path, relpath);
    return path;
  }

  string make_snap_name(const char* name) {
    char snap_name[64];
    if (name && *name) {
      sprintf(snap_name, "%s_%d", name, getpid());
    } else {
      // just simulate empty snapname
      snap_name[0] = 0;
    }
    return snap_name;
  }
  string make_snap_path(const char* sname, const char* subdir = nullptr) {
    char snap_path[PATH_MAX];
    string snap_name = subdir ?
      concat_path(make_snap_name(sname), subdir) :
      make_snap_name(sname);
    sprintf(snap_path, ".snap/%s", snap_name.c_str());
    return snap_path;
  }

  int mksnap(const char* name) {
    string snap_name = make_snap_name(name);
    return ceph_mksnap(cmount, dir_path, snap_name.c_str(),
      0755, nullptr, 0);
  }
  int rmsnap(const char* name) {
    string snap_name = make_snap_name(name);
    return ceph_rmsnap(cmount, dir_path, snap_name.c_str());
  }
  int get_snapid(const char* name, uint64_t* res)
  {
    ceph_assert(res);
    snap_info snap_info;

    char snap_path[PATH_MAX];
    string snap_name = make_snap_name(name);
    sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name.c_str());
    int r = ceph_get_snap_info(cmount, snap_path, &snap_info);
    if (r >= 0) {
      *res = snap_info.id;
      r = 0;
    }
    return r;
  }

  int write_full(const char* relpath, const string& data)
  {
    auto file_path = make_file_path(relpath);
    int fd = ceph_open(cmount, file_path.c_str(), O_WRONLY | O_CREAT, 0666);
    if (fd < 0) {
      return -EACCES;
    }
    int r = ceph_write(cmount, fd, data.c_str(), data.size(), 0);
    if (r >= 0) {
      ceph_truncate(cmount, file_path.c_str(), data.size());
      ceph_fsync(cmount, fd, 0);
    }
    ceph_close(cmount, fd);
    return r;
  }
  string concat_path(string_view path, string_view name) {
    string s(path);
    if (s.empty() || s.back() != '/') {
      s += '/';
    }
    s += name;
    return s;
  }
  int unlink(const char* relpath)
  {
    auto file_path = make_file_path(relpath);
    return ceph_unlink(cmount, file_path.c_str());
  }

  int test_open(const char* relpath)
  {
    auto subdir_path = make_file_path(relpath);
    struct ceph_dir_result* ls_dir;
    int r = ceph_opendir(cmount, subdir_path.c_str(), &ls_dir);
    if (r != 0) {
      return r;
    }
    ceph_assert(0 == ceph_closedir(cmount, ls_dir));
    return r;
  }

  int for_each_readdir(const char* relpath,
    std::function<bool(const dirent*, const struct ceph_statx*)> fn)
  {
    auto subdir_path = make_file_path(relpath);
    struct ceph_dir_result* ls_dir;
    int r = ceph_opendir(cmount, subdir_path.c_str(), &ls_dir);
    if (r != 0) {
      return r;
    }

    while (1) {
      struct dirent result;
      struct ceph_statx stx;

      r = ceph_readdirplus_r(
        cmount, ls_dir, &result, &stx, CEPH_STATX_BASIC_STATS,
        0,
        NULL);
      if (!r)
        break;
      if (r < 0) {
        std::cerr << "ceph_readdirplus_r failed, error: "
                  << r << std::endl;
        return r;
      }

      if (strcmp(result.d_name, ".") == 0 ||
          strcmp(result.d_name, "..") == 0) {
        continue;
      }
      if (!fn(&result, &stx)) {
        r = -EINTR;
        break;
      }
    }
    ceph_assert(0 == ceph_closedir(cmount, ls_dir));
    return r;
  }
  int readdir_and_compare(const char* relpath,
    const vector<string>& expected0)
  {
    vector<string> expected(expected0);
    auto end = expected.end();
    int r = for_each_readdir(relpath,
      [&](const dirent* dire, const struct ceph_statx* stx) {

        std::string name(dire->d_name);
        auto it = std::find(expected.begin(), end, name);
        if (it == end) {
          std::cerr << "readdir_and_compare error: unexpected name:"
                    << name << std::endl;
          return false;
        }
        expected.erase(it);
        return true;
      });
    if (r == 0 && !expected.empty()) {
      std::cerr << __func__ << " error: left entries:" << std::endl;
      for (auto& e : expected) {
        std::cerr << e << std::endl;
      }
      std::cerr << __func__ << " ************" << std::endl;
      r = -ENOTEMPTY;
    }
    return r;
  }
  int for_each_readdir_snapdiff(const char* relpath,
    const char* snap1,
    const char* snap2,
    std::function<bool(const dirent*, uint64_t)> fn)
  {
    auto s1 = make_snap_name(snap1);
    auto s2 = make_snap_name(snap2);
    ceph_snapdiff_info info;
    ceph_snapdiff_entry_t res_entry;
    int r = ceph_open_snapdiff(cmount,
                               dir_path,
                               relpath,
                               s1.c_str(),
                               s2.c_str(),
                               &info);
    if (r != 0) {
      std::cerr << " Failed to open snapdiff, ret:" << r << std::endl;
      return r;
    }
    while (0 < (r = ceph_readdir_snapdiff(&info,
                                          &res_entry))) {
      if (strcmp(res_entry.dir_entry.d_name, ".") == 0 ||
        strcmp(res_entry.dir_entry.d_name, "..") == 0) {
        continue;
      }
      if (!fn(&res_entry.dir_entry, res_entry.snapid)) {
        r = -EINTR;
        break;
      }
    }
    ceph_assert(0 == ceph_close_snapdiff(&info));
    if (r != 0) {
      std::cerr << " Failed to readdir snapdiff, ret:" << r
                << " " << relpath << ", " << snap1 << " vs. " << snap2
                << std::endl;
    }
    return r;
  }
  int readdir_snapdiff_and_compare(const char* relpath,
    const char* snap1,
    const char* snap2,
    const vector<pair<string, uint64_t>>& expected0)
  {
    vector<pair<string, uint64_t>> expected(expected0);
    auto end = expected.end();
    int r = for_each_readdir_snapdiff(relpath, snap1, snap2,
      [&](const dirent* dire, uint64_t snapid) {

        pair<string, uint64_t> p = std::make_pair(dire->d_name, snapid);
        auto it = std::find(expected.begin(), end, p);
        if (it == end) {
          std::cerr << "readdir_snapdiff_and_compare error: unexpected name:"
            << dire->d_name << "/" << snapid << std::endl;
          return false;
        }
        expected.erase(it);
        return true;
      });
    if (r == 0 && !expected.empty()) {
      std::cerr << __func__ << " error: left entries:" << std::endl;
      for (auto& e : expected) {
        std::cerr << e.first << "/" << e.second << std::endl;
      }
      std::cerr << __func__ << " ************" << std::endl;
      r = -ENOTEMPTY;
    }
    return r;
  }

  int mkdir(const char* relpath)
  {
    auto path = make_file_path(relpath);
    return ceph_mkdir(cmount, path.c_str(), 0777);
  }
  int rmdir(const char* relpath)
  {
    auto path = make_file_path(relpath);
    return ceph_rmdir(cmount, path.c_str());
  }
  int purge_dir(const char* relpath0, bool inclusive = true)
  {
    int r =
      for_each_readdir(relpath0,
        [&](const dirent* dire, const struct ceph_statx* stx) {
          string relpath = concat_path(relpath0, dire->d_name);
	  if (S_ISDIR(stx->stx_mode)) {
            purge_dir(relpath.c_str());
            rmdir(relpath.c_str());
          } else {
            unlink(relpath.c_str());
          }
          return true;
        });
    if (r != 0) {
      return r;
    }
    if (*relpath0 != 0) {
      r = rmdir(relpath0);
    }
    return r;
  }

  void remove_all() {
    purge_dir("/", false);
  }

  ceph_mount_info* get_cmount() {
    return cmount;
  }

  void verify_snap_diff(vector<pair<string, uint64_t>>& expected,
                        const char* relpath,
                        const char* snap1,
                        const char* snap2);
  void print_snap_diff(const char* relpath,
		       const char* snap1,
                       const char* snap2);

  void prepareSnapDiffLib1Cases();
  void prepareSnapDiffLib2Cases();
  void prepareSnapDiffLib3Cases();
  void prepareHugeSnapDiff(const std::string& name_prefix_start,
                           const std::string& name_prefix_bulk,
                           const std::string& name_prefix_end,
                           size_t file_count,
                           bool bulk_diff);
};

// Helper function to verify readdir_snapdiff returns expected results
void TestMount::verify_snap_diff(vector<pair<string, uint64_t>>& expected,
                                 const char* relpath,
                                 const char* snap1,
                                 const char* snap2)
{
  std::cout << "---------" << snap1 << " vs. " << snap2
            << " diff listing verification for /" << (relpath ? relpath : "")
            << std::endl;
  ASSERT_EQ(0,
    readdir_snapdiff_and_compare(relpath, snap1, snap2, expected));
};

// Helper function to print readdir_snapdiff results
void TestMount::print_snap_diff(const char* relpath,
				const char* snap1,
                                const char* snap2)
{
  std::cout << "---------" << snap1 << " vs. " << snap2
            << " diff listing for /" << (relpath ? relpath : "")
            << std::endl;
  ASSERT_EQ(0, for_each_readdir_snapdiff(relpath, snap1, snap2,
    [&](const dirent* dire, uint64_t snapid) {
      std::cout << dire->d_name << " snap " << snapid << std::endl;
      return true;
    }));
};


/* The following method creates some files/folders/snapshots layout,
   described in the sheet below.
   We're to test SnapDiff readdir API against that structure.

* where:
  - xN denotes file 'x' version N.
  - X denotes folder name
  - * denotes no/removed file/folder

#    snap1         snap2
# fileA1      | fileA2      |
# *           | fileB2      |
# fileC1      | *           |
# fileD1      | fileD1      |
# dirA        | dirA        |
# dirA/fileA1 | dirA/fileA2 |
# *           | dirB        |
# *           | dirB/fileb2 |
# dirC        | *           |
# dirC/filec1 | *           |
# dirD        | dirD        |
# dirD/fileD1 | dirD/fileD1 |
*/
void TestMount::prepareSnapDiffLib1Cases()
{
  //************ snap1 *************
  ASSERT_LE(0, write_full("fileA", "hello world"));
  ASSERT_LE(0, write_full("fileC", "hello world to be removed"));
  ASSERT_LE(0, write_full("fileD", "hello world unmodified"));
  ASSERT_EQ(0, mkdir("dirA"));
  ASSERT_LE(0, write_full("dirA/fileA", "file 'A/a' v1"));
  ASSERT_EQ(0, mkdir("dirC"));
  ASSERT_LE(0, write_full("dirC/filec", "file 'C/c' v1"));
  ASSERT_EQ(0, mkdir("dirD"));
  ASSERT_LE(0, write_full("dirD/filed", "file 'D/d' v1"));

  ASSERT_EQ(0, mksnap("snap1"));

  //************ snap2 *************
  ASSERT_LE(0, write_full("fileA", "hello world again in A"));
  ASSERT_LE(0, write_full("fileB", "hello world in B"));
  ASSERT_EQ(0, unlink("fileC"));

  ASSERT_LE(0, write_full("dirA/fileA", "file 'A/a' v2"));
  ASSERT_EQ(0, purge_dir("dirC"));
  ASSERT_EQ(0, mkdir("dirB"));
  ASSERT_LE(0, write_full("dirB/fileb", "file 'B/b' v2"));

  ASSERT_EQ(0, mksnap("snap2"));
}

/*
* Basic functionality testing for the SnapDiff readdir API
*/
TEST(LibCephFS, SnapDiffLib)
{
  TestMount test_mount;

  // Create simple directory tree with a couple of snapshots
  // to test against
  test_mount.prepareSnapDiffLib1Cases();

  uint64_t snapid1;
  uint64_t snapid2;

  // learn snapshot ids and do basic verification
  ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1));
  ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2));
  ASSERT_GT(snapid1, 0);
  ASSERT_GT(snapid2, 0);
  ASSERT_GT(snapid2, snapid1);
  std::cout << snapid1 << " vs. " << snapid2 << std::endl;

  //
  // Make sure root listing for snapshot snap1 is as expected
  //
  {
    std::cout << "---------snap1 listing verification---------" << std::endl;
    string snap_path = test_mount.make_snap_path("snap1");
    vector<string> expected;
    expected.push_back("fileA");
    expected.push_back("fileC");
    expected.push_back("fileD");
    expected.push_back("dirA");
    expected.push_back("dirC");
    expected.push_back("dirD");
    ASSERT_EQ(0,
      test_mount.readdir_and_compare(snap_path.c_str(), expected));
  }

  //
  // Make sure root listing for snapshot snap2 is as expected
  //
  {
    std::cout << "---------snap2 listing verification---------" << std::endl;
    string snap_path = test_mount.make_snap_path("snap2");
    vector<string> expected;
    expected.push_back("fileA");
    expected.push_back("fileB");
    expected.push_back("fileD");
    expected.push_back("dirA");
    expected.push_back("dirB");
    expected.push_back("dirD");
    ASSERT_EQ(0,
      test_mount.readdir_and_compare(snap_path.c_str(), expected));
  }

  //
  // Print snap1 vs. snap2 delta for the root
  //
  test_mount.print_snap_diff("", "snap1", "snap2");

  //
  // Make sure snap1 vs. snap2 delta for the root is as expected
  //
  {
    vector<pair<string, uint64_t>> expected;
    expected.emplace_back("fileA", snapid2);
    expected.emplace_back("fileB", snapid2);
    expected.emplace_back("fileC", snapid1);
    expected.emplace_back("dirA", snapid2);
    expected.emplace_back("dirB", snapid2);
    expected.emplace_back("dirC", snapid1);
    expected.emplace_back("dirD", snapid2);
    test_mount.verify_snap_diff(expected, "", "snap1", "snap2");
  }

  //
  // Make sure snap1 vs. snap2 delta for /dirA is as expected
  //
  {
    vector<pair<string, uint64_t>> expected;
    expected.emplace_back("fileA", snapid2);
    test_mount.verify_snap_diff(expected, "dirA", "snap1", "snap2");
  }

  //
  // Make sure snap1 vs. snap2 delta for /dirB is as expected
  //
  {
    vector<pair<string, uint64_t>> expected;
    expected.emplace_back("fileb", snapid2);
    test_mount.verify_snap_diff(expected, "dirB", "snap1", "snap2");
  }

  //
  // Make sure snap1 vs. snap2 delta for /dirC is as expected
  //
  {
    vector<pair<string, uint64_t>> expected;
    expected.emplace_back("filec", snapid1);
    test_mount.verify_snap_diff(expected, "dirC", "snap2", "snap1");
  }

  //
  // Make sure snap1 vs. snap2 delta for /dirD is as expected
  //
  {
    vector<pair<string, uint64_t>> expected;
    test_mount.verify_snap_diff(expected, "dirD", "snap1", "snap2");
  }

  // Make sure SnapDiff returns an error when provided with the same
  // snapshot name for both parties A and B.
  {
    string snap_path = test_mount.make_snap_path("snap2");
    string snap_other_path = snap_path;
    std::cout << "---------invalid snapdiff params, the same snaps---------" << std::endl;
    ASSERT_EQ(-EINVAL, test_mount.for_each_readdir_snapdiff(
      "",
      "snap2",
      "snap2",
      [&](const dirent* dire, uint64_t snapid) {
        return true;
      }));
  }
  // Make sure SnapDiff returns an error when provided with an empty
  // snapshot name for one of the parties
  {
    std::cout << "---------invalid snapdiff params, no snap_other ---------" << std::endl;
    string snap_path = test_mount.make_snap_path("snap2");
    string snap_other_path;
    ASSERT_EQ(-EINVAL, test_mount.for_each_readdir_snapdiff(
      "",
      "snap2",
      "",
      [&](const dirent* dire, uint64_t snapid) {
        return true;
      }));
  }

  std::cout << "------------- closing -------------" << std::endl;
  ASSERT_EQ(0, test_mount.purge_dir(""));
  ASSERT_EQ(0, test_mount.rmsnap("snap1"));
  ASSERT_EQ(0, test_mount.rmsnap("snap2"));
}

/* The following method creates some files/folders/snapshots layout,
   described in the sheet below.
   We're to test SnapDiff readdir API against that structure.

* where:
  - xN denotes file 'x' version N.
  - X denotes folder name
  - * denotes no/removed file/folder

#     snap1        snap2      snap3      head
# fileA1      | fileA2      | fileA2
# *           | fileB2      | fileB2
# fileC1      | *           | fileC3
# fileD1      | fileD1      | fileD3
# *           | *           | fileE3
# fileF1      | *           | *
# fileG1      | fileG2      | *
# dirA        | dirA        | *
# dirA/fileA1 | dirA/fileA2 | *
# *           | dirB        | *
# *           | dirB/fileb2 | *
# dirC        | *           | *
# dirC/filec1 | *           | *
# dirD        | dirD        | dirD
# dirD/filed1 | dirD/filed1 | dirD/filed1
*/
void TestMount::prepareSnapDiffLib2Cases()
{
  //************ snap1 *************
  ASSERT_LE(0, write_full("fileA", "hello world"));
  ASSERT_LE(0, write_full("fileC", "hello world to be removed temporarily"));
  ASSERT_LE(0, write_full("fileD", "hello world unmodified"));
  ASSERT_LE(0, write_full("fileF", "hello world to be removed completely"));
  ASSERT_LE(0, write_full("fileG", "hello world to be overwritten at snap2"));
  ASSERT_EQ(0, mkdir("dirA"));
  ASSERT_LE(0, write_full("dirA/fileA", "file 'A/a' v1"));
  ASSERT_EQ(0, mkdir("dirC"));
  ASSERT_LE(0, write_full("dirC/filec", "file 'C/c' v1"));
  ASSERT_EQ(0, mkdir("dirD"));
  ASSERT_LE(0, write_full("dirD/filed", "file 'D/d' v1"));

  ASSERT_EQ(0, mksnap("snap1"));

  //************ snap2 *************
  ASSERT_LE(0, write_full("fileA", "hello world again in A"));
  ASSERT_LE(0, write_full("fileB", "hello world in B"));
  ASSERT_LE(0, write_full("fileG", "hello world to be removed at snap3"));
  ASSERT_EQ(0, unlink("fileC"));
  ASSERT_EQ(0, unlink("fileF"));

  ASSERT_LE(0, write_full("dirA/fileA", "file 'A/a' v2"));
  ASSERT_EQ(0, mkdir("dirB"));
  ASSERT_LE(0, write_full("dirB/fileb", "file 'B/b' v2"));
  ASSERT_EQ(0, purge_dir("dirC"));

  ASSERT_EQ(0, mksnap("snap2"));

  //************ snap3 *************
  ASSERT_LE(0, write_full("fileC", "hello world in C recovered"));
  ASSERT_LE(0, write_full("fileD", "hello world in D now modified"));
  ASSERT_LE(0, write_full("fileE", "file 'E' created at snap3"));
  ASSERT_EQ(0, unlink("fileG"));
  ASSERT_EQ(0, purge_dir("dirA"));
  ASSERT_EQ(0, purge_dir("dirB"));
  ASSERT_EQ(0, mksnap("snap3"));
}

/* The following method creates a folder with tons of file
   updated between two snapshots
   We're to test SnapDiff readdir API against that structure.

* where:
  - xN denotes file 'x' version N.
  - X denotes folder name
  - * denotes no/removed file/folder

#    snap1         snap2
* aaaaA1     | aaaaA1    |
* aaaaB1     |    *      |
* *          | aaaaC2    |
* aaaaD1     | aaaaD2    |
# file<NNN>1 | file<NNN>2|
* fileZ1     | fileA1    |
* zzzzA1     | zzzzA1    |
* zzzzB1     |    *      |
* *          | zzzzC2    |
* zzzzD1     | zzzzD2    |
*/

void TestMount::prepareHugeSnapDiff(const std::string& name_prefix_start,
                                    const std::string& name_prefix_bulk,
                                    const std::string& name_prefix_end,
                                    size_t file_count,
                                    bool bulk_diff)
{
  //************ snap1 *************
  std::string startA = name_prefix_start + "A";
  std::string startB = name_prefix_start + "B";
  std::string startC = name_prefix_start + "C";
  std::string startD = name_prefix_start + "D";
  std::string endA = name_prefix_end + "A";
  std::string endB = name_prefix_end + "B";
  std::string endC = name_prefix_end + "C";
  std::string endD = name_prefix_end + "D";

  ASSERT_LE(0, write_full(startA.c_str(), "hello world"));
  ASSERT_LE(0, write_full(startB.c_str(), "hello world"));
  ASSERT_LE(0, write_full(startD.c_str(), "hello world"));
  for(size_t i = 0; i < file_count; i++) {
    auto s = name_prefix_bulk + stringify(i);
    ASSERT_LE(0, write_full(s.c_str(), "hello world"));
  }
  ASSERT_LE(0, write_full(endA.c_str(), "hello world"));
  ASSERT_LE(0, write_full(endB.c_str(), "hello world"));
  ASSERT_LE(0, write_full(endD.c_str(), "hello world"));

  ASSERT_EQ(0, mksnap("snap1"));

  ASSERT_LE(0, unlink(startB.c_str()));
  ASSERT_LE(0, write_full(startC.c_str(), "hello world2"));
  ASSERT_LE(0, write_full(startD.c_str(), "hello world2"));
  if (bulk_diff) {
    for(size_t i = 0; i < file_count; i++) {
      auto s = std::string(name_prefix_bulk) + stringify(i);
      ASSERT_LE(0, write_full(s.c_str(), "hello world2"));
    }
  }
  ASSERT_LE(0, unlink(endB.c_str()));
  ASSERT_LE(0, write_full(endC.c_str(), "hello world2"));
  ASSERT_LE(0, write_full(endD.c_str(), "hello world2"));
  ASSERT_EQ(0, mksnap("snap2"));
}

/*
* More versatile SnapDiff readdir API verification,
* includes 3 different snapshots and interleaving/repetitive calls to make sure
* the results aren't spoiled due to caching.
*/
TEST(LibCephFS, SnapDiffLib2)
{
  TestMount test_mount;

  test_mount.prepareSnapDiffLib2Cases();

  // Create simple directory tree with a couple of snapshots to test against
  uint64_t snapid1;
  uint64_t snapid2;
  uint64_t snapid3;
  ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1));
  ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2));
  ASSERT_EQ(0, test_mount.get_snapid("snap3", &snapid3));
  std::cout << snapid1 << " vs. " << snapid2 << " vs. " << snapid3 << std::endl;
  ASSERT_GT(snapid1, 0);
  ASSERT_GT(snapid2, 0);
  ASSERT_GT(snapid3, 0);
  ASSERT_GT(snapid2, snapid1);
  ASSERT_GT(snapid3, snapid2);

  // define a labda which verifies snap1/snap2/snap3 listings
  auto verify_snap_listing = [&]()
  {
    {
      string snap_path = test_mount.make_snap_path("snap1");

      std::cout << "---------snap1 listing verification---------" << std::endl;
      vector<string> expected;
      expected.push_back("fileA");
      expected.push_back("fileC");
      expected.push_back("fileD");
      expected.push_back("fileF");
      expected.push_back("fileG");
      expected.push_back("dirA");
      expected.push_back("dirC");
      expected.push_back("dirD");
      ASSERT_EQ(0,
        test_mount.readdir_and_compare(snap_path.c_str(), expected));
    }
    {
      std::cout << "---------snap2 listing verification---------" << std::endl;
      string snap_path = test_mount.make_snap_path("snap2");
      vector<string> expected;
      expected.push_back("fileA");
      expected.push_back("fileB");
      expected.push_back("fileD");
      expected.push_back("fileG");
      expected.push_back("dirA");
      expected.push_back("dirB");
      expected.push_back("dirD");
      ASSERT_EQ(0,
        test_mount.readdir_and_compare(snap_path.c_str(), expected));
    }
    {
      std::cout << "---------snap3 listing verification---------" << std::endl;
      string snap_path = test_mount.make_snap_path("snap3");
      vector<string> expected;
      expected.push_back("fileA");
      expected.push_back("fileB");
      expected.push_back("fileC");
      expected.push_back("fileD");
      expected.push_back("fileE");
      expected.push_back("dirD");
      ASSERT_EQ(0,
        test_mount.readdir_and_compare(snap_path.c_str(), expected));
    }
  };
  // Prepare expected delta for snap1 vs. snap2
  vector<pair<string, uint64_t>> snap1_2_diff_expected;
  snap1_2_diff_expected.emplace_back("fileA", snapid2);
  snap1_2_diff_expected.emplace_back("fileB", snapid2);
  snap1_2_diff_expected.emplace_back("fileC", snapid1);
  snap1_2_diff_expected.emplace_back("fileF", snapid1);
  snap1_2_diff_expected.emplace_back("fileG", snapid2);
  snap1_2_diff_expected.emplace_back("dirA", snapid2);
  snap1_2_diff_expected.emplace_back("dirB", snapid2);
  snap1_2_diff_expected.emplace_back("dirC", snapid1);
  snap1_2_diff_expected.emplace_back("dirD", snapid2);

  // Prepare expected delta for snap1 vs. snap3
  vector<pair<string, uint64_t>> snap1_3_diff_expected;
  snap1_3_diff_expected.emplace_back("fileA", snapid3);
  snap1_3_diff_expected.emplace_back("fileB", snapid3);
  snap1_3_diff_expected.emplace_back("fileC", snapid3);
  snap1_3_diff_expected.emplace_back("fileD", snapid3);
  snap1_3_diff_expected.emplace_back("fileE", snapid3);
  snap1_3_diff_expected.emplace_back("fileF", snapid1);
  snap1_3_diff_expected.emplace_back("fileG", snapid1);
  snap1_3_diff_expected.emplace_back("dirA", snapid1);
  snap1_3_diff_expected.emplace_back("dirC", snapid1);
  snap1_3_diff_expected.emplace_back("dirD", snapid3);

  // Prepare expected delta for snap2 vs. snap3
  vector<pair<string, uint64_t>> snap2_3_diff_expected;
  snap2_3_diff_expected.emplace_back("fileC", snapid3);
  snap2_3_diff_expected.emplace_back("fileD", snapid3);
  snap2_3_diff_expected.emplace_back("fileE", snapid3);
  snap2_3_diff_expected.emplace_back("fileG", snapid2);
  snap2_3_diff_expected.emplace_back("dirA", snapid2);
  snap2_3_diff_expected.emplace_back("dirB", snapid2);
  snap2_3_diff_expected.emplace_back("dirD", snapid3);

  // Check snapshot listings on a cold cache
  verify_snap_listing();

  // Check snapshot listings on a warm cache
  verify_snap_listing(); // served from cache

  // Print snap1 vs. snap2 delta against the root folder
  test_mount.print_snap_diff("", "snap1", "snap2");

  // Verify snap1 vs. snap2 delta for the root
  test_mount.verify_snap_diff(snap1_2_diff_expected, "", "snap1", "snap2");

  // Check snapshot listings on a warm cache once again
  // to make sure it wasn't spoiled by SnapDiff
  verify_snap_listing(); // served from cache

  // Verify snap2 vs. snap1 delta
  test_mount.verify_snap_diff(snap1_2_diff_expected, "", "snap2", "snap1");

  // Check snapshot listings on a warm cache once again
  // to make sure it wasn't spoiled by SnapDiff
  verify_snap_listing(); // served from cache

  // Verify snap1 vs. snap3 delta for the root
  test_mount.verify_snap_diff(snap1_3_diff_expected, "", "snap1", "snap3");

  // Verify snap2 vs. snap3 delta for the root
  test_mount.verify_snap_diff(snap2_3_diff_expected, "", "snap2", "snap3");

  // Check snapshot listings on a warm cache once again
  // to make sure it wasn't spoiled by SnapDiff
  verify_snap_listing(); // served from cache

  // Print snap1 vs. snap2 delta against /dirA folder
  test_mount.print_snap_diff("dirA", "snap1", "snap2");

  // Verify snap1 vs. snap2 delta for /dirA
  {
    vector<pair<string, uint64_t>> expected;
    expected.emplace_back("fileA", snapid2);
    test_mount.verify_snap_diff(expected, "dirA", "snap1", "snap2");
  }

  // Print snap1 vs. snap2 delta against /dirB folder
  test_mount.print_snap_diff("dirB", "snap1", "snap2");

  // Verify snap1 vs. snap2 delta for /dirB
  {
    vector<pair<string, uint64_t>> expected;
    expected.emplace_back("fileb", snapid2);
    test_mount.verify_snap_diff(expected, "dirB", "snap1", "snap2");
  }

  // Print snap1 vs. snap2 delta against /dirD folder
  test_mount.print_snap_diff("dirD", "snap1", "snap2");

  // Verify snap1 vs. snap2 delta for /dirD
  {
    vector<pair<string, uint64_t>> expected;
    test_mount.verify_snap_diff(expected, "dirD", "snap1", "snap2");
  }

  // Check snapshot listings on a warm cache once again
  // to make sure it wasn't spoiled by SnapDiff
  verify_snap_listing(); // served from cache

  // Verify snap1 vs. snap2 delta for the root once again
  test_mount.verify_snap_diff(snap1_2_diff_expected, "", "snap1", "snap2");

  // Verify snap2 vs. snap3 delta for the root once again
  test_mount.verify_snap_diff(snap2_3_diff_expected, "", "snap3", "snap2");

  // Verify snap1 vs. snap3 delta for the root once again
  test_mount.verify_snap_diff(snap1_3_diff_expected, "", "snap1", "snap3");

  std::cout << "------------- closing -------------" << std::endl;
  ASSERT_EQ(0, test_mount.purge_dir(""));
  ASSERT_EQ(0, test_mount.rmsnap("snap1"));
  ASSERT_EQ(0, test_mount.rmsnap("snap2"));
  ASSERT_EQ(0, test_mount.rmsnap("snap3"));
}

/* The following method creates some files/folders/snapshots layout,
   described in the sheet below.
   We're to test SnapDiff against that structure.

* where:
  - xN denotes file 'x' version N.
  - X denotes folder name
  - * denotes no/removed file/folder

#     snap1        snap2      snap3      head
# a1     |     a1     |    a3    |    a4
# b1     |     b2     |    b3    |    b3
# c1     |     *      |    *     |    *
# *      |     d2     |    d3    |    d3
# f1     |     f2     |    *     |    *
# ff1    |     ff1    |    *     |    *
# g1     |     *      |    g3    |    g3
# *      |     *      |    *     |    h4
# i1     |     i1     |    i1    |    i1
# S      |     S      |    S     |    S
# S/sa1  |     S/sa2  |    S/sa3 |    S/sa3
# *      |     *      |    *     |    S/sh4
# *      |     T      |    T     |    T
# *      |     T/td2  |    T/td3 |    T/td3
# C      |     *      |    *     |    *
# C/cc1  |     *      |    *     |    *
# C/C1   |     *      |    *     |    *
# C/C1/c1|     *      |    *     |    *
# G      |     *      |    G     |    G
# G/gg1  |     *      |    G/gg3 |    G/gg3
# *      |     k2     |    *     |    *
# *      |     l2     |    l2    |    *
# *      |     K      |    *     |    *
# *      |     K/kk2  |    *     |    *
# *      |     *      |    H     |    H
# *      |     *      |    H/hh3 |    H/hh3
# I      |     I      |    I     |    *
# I/ii1  |     I/ii2  |    I/ii3 |    *
# I/iii1 |     I/iii1 |    I/iii3|    *
# *      |     *      |   I/iiii3|    *
# *      |    I/J     |  I/J     |    *
# *      |   I/J/i2   |  I/J/i3  |    *
# *      |   I/J/j2   |  I/J/j2  |    *
# *      |   I/J/k2   |    *     |    *
# *      |     *      |  I/J/l3  |    *
# L      |     L      |    L     |    L
# L/ll1  |    L/ll1   |   L/ll3  |    L/ll3
# L/LL   |    L/LL    |  L/LL    |    L/LL
# *      |    L/LL/ll2|  L/LL/ll3|    L/LL/ll4
# *      |    L/LM    |    *     |    *
# *      |    L/LM/lm2|    *     |    *
# *      |    L/LN    |    L/LN  |    *
*/
void TestMount::prepareSnapDiffLib3Cases()
{
  //************ snap1 *************
  ASSERT_LE(0, write_full("a", "file 'a' v1"));
  ASSERT_LE(0, write_full("b", "file 'b' v1"));
  ASSERT_LE(0, write_full("c", "file 'c' v1"));
  ASSERT_LE(0, write_full("e", "file 'e' v1"));
  ASSERT_LE(0, write_full("~e", "file '~e' v1"));
  ASSERT_LE(0, write_full("f", "file 'f' v1"));
  ASSERT_LE(0, write_full("ff", "file 'ff' v1"));
  ASSERT_LE(0, write_full("g", "file 'g' v1"));
  ASSERT_LE(0, write_full("i", "file 'i' v1"));

  ASSERT_EQ(0, mkdir("S"));
  ASSERT_LE(0, write_full("S/sa", "file 'S/sa' v1"));

  ASSERT_EQ(0, mkdir("C"));
  ASSERT_LE(0, write_full("C/cc", "file 'C/cc' v1"));

  ASSERT_EQ(0, mkdir("C/CC"));
  ASSERT_LE(0, write_full("C/CC/c", "file 'C/CC/c' v1"));

  ASSERT_EQ(0, mkdir("G"));
  ASSERT_LE(0, write_full("G/gg", "file 'G/gg' v1"));

  ASSERT_EQ(0, mkdir("I"));
  ASSERT_LE(0, write_full("I/ii", "file 'I/ii' v1"));
  ASSERT_LE(0, write_full("I/iii", "file 'I/iii' v1"));

  ASSERT_EQ(0, mkdir("L"));
  ASSERT_LE(0, write_full("L/ll", "file 'L/ll' v1"));
  ASSERT_EQ(0, mkdir("L/LL"));

  ASSERT_EQ(0, mksnap("snap1"));
  //************ snap2 *************

  ASSERT_LE(0, write_full("b", "file 'b' v2"));
  ASSERT_EQ(0, unlink("c"));
  ASSERT_LE(0, write_full("d", "file 'd' v2"));
  ASSERT_LE(0, write_full("e", "file 'e' v2"));
  ASSERT_LE(0, write_full("~e", "file '~e' v2"));
  ASSERT_LE(0, write_full("f", "file 'f' v2"));
  ASSERT_EQ(0, unlink("g"));

  ASSERT_LE(0, write_full("S/sa", "file 'S/sa' v2"));

  ASSERT_EQ(0, mkdir("T"));
  ASSERT_LE(0, write_full("T/td", "file 'T/td' v2"));

  ASSERT_EQ(0, purge_dir("C"));
  ASSERT_EQ(0, purge_dir("G"));

  ASSERT_LE(0, write_full("k", "file 'k' v2"));
  ASSERT_LE(0, write_full("l", "file 'l' v2"));

  ASSERT_EQ(0, mkdir("K"));
  ASSERT_LE(0, write_full("K/kk", "file 'K/kk' v2"));

  ASSERT_LE(0, write_full("I/ii", "file 'I/ii' v2"));

  ASSERT_EQ(0, mkdir("I/J"));
  ASSERT_LE(0, write_full("I/J/i", "file 'I/J/i' v2"));
  ASSERT_LE(0, write_full("I/J/j", "file 'I/J/j' v2"));
  ASSERT_LE(0, write_full("I/J/k", "file 'I/J/k' v2"));

  ASSERT_LE(0, write_full("L/LL/ll", "file 'L/LL/ll' v2"));

  ASSERT_EQ(0, mkdir("L/LM"));
  ASSERT_LE(0, write_full("L/LM/lm", "file 'L/LM/lm' v2"));

  ASSERT_EQ(0, mkdir("L/LN"));

  ASSERT_EQ(0, mksnap("snap2"));
    //************ snap3 *************

  ASSERT_LE(0, write_full("a", "file 'a' v3"));
  ASSERT_LE(0, write_full("b", "file 'b' v3"));
  ASSERT_LE(0, write_full("d", "file 'd' v3"));
  ASSERT_EQ(0, unlink("e"));
  ASSERT_EQ(0, unlink("~e"));
  ASSERT_EQ(0, unlink("f"));
  ASSERT_EQ(0, unlink("ff"));
  ASSERT_LE(0, write_full("g", "file 'g' v3"));

  ASSERT_LE(0, write_full("S/sa", "file 'S/sa' v3"));

  ASSERT_LE(0, write_full("T/td", "file 'T/td' v3"));

  ASSERT_EQ(0, mkdir("G"));
  ASSERT_LE(0, write_full("G/gg", "file 'G/gg' v3"));

  ASSERT_EQ(0, unlink("k"));

  ASSERT_EQ(0, purge_dir("K"));

  ASSERT_EQ(0, mkdir("H"));
  ASSERT_LE(0, write_full("H/hh", "file 'H/hh' v3"));

  ASSERT_LE(0, write_full("I/ii", "file 'I/ii' v3"));
  ASSERT_LE(0, write_full("I/iii", "file 'I/iii' v3"));
  ASSERT_LE(0, write_full("I/iiii", "file 'I/iiii' v3"));

  ASSERT_LE(0, write_full("I/J/i", "file 'I/J/i' v3"));
  ASSERT_EQ(0, unlink("I/J/k"));
  ASSERT_LE(0, write_full("I/J/l", "file 'I/J/l' v3"));

  ASSERT_LE(0, write_full("L/ll", "file 'L/ll' v3"));

  ASSERT_LE(0, write_full("L/LL/ll", "file 'L/LL/ll' v3"));

  ASSERT_EQ(0, purge_dir("L/LM"));

  ASSERT_EQ(0, mksnap("snap3"));
  //************ head *************
  ASSERT_LE(0, write_full("a", "file 'a' head"));

  ASSERT_LE(0, write_full("h", "file 'h' head"));

  ASSERT_LE(0, write_full("S/sh", "file 'S/sh' head"));

  ASSERT_EQ(0, unlink("l"));

  ASSERT_EQ(0, purge_dir("I"));

  ASSERT_LE(0, write_full("L/LL/ll", "file 'L/LL/ll' head"));

  ASSERT_EQ(0, purge_dir("L/LN"));
}

//
// This case tests SnapDiff functionality for snap1/snap2 snapshot delta
// It operates against FS layout created by prepareSnapDiffCases() method,
// see relevant table before that function for FS state overview.
//
TEST(LibCephFS, SnapDiffCases1_2)
{
  TestMount test_mount;

  // Create directory tree evolving through a bunch of snapshots
  test_mount.prepareSnapDiffLib3Cases();

  uint64_t snapid1;
  uint64_t snapid2;
  ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1));
  ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2));
  std::cout << snapid1 << " vs. " << snapid2 << std::endl;
  ASSERT_GT(snapid1, 0);
  ASSERT_GT(snapid2, 0);
  ASSERT_GT(snapid2, snapid1);

  // Print snapshot delta (snap1 vs. snap2) results for root in a
  // human-readable form.
  test_mount.print_snap_diff("", "snap1", "snap2");

  {
    // Make sure the root delta is as expected
    // One should use columns snap1 and snap2 from
    // the table preceeding prepareSnapDiffCases() function
    // to learn which names to expect in the delta.
    //
    //  - file 'a' is unchanged hence not present in delta
    //  - file 'ff' is unchanged hence not present in delta
    //  - file 'i' is unchanged hence not present in delta
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("b", snapid2);  // file 'b' is updated in snap2
    expected.emplace_back("c", snapid1);  // file 'c' is removed in snap2
    expected.emplace_back("d", snapid2);  // file 'd' is created in snap2
    expected.emplace_back("e", snapid2);  // file 'e' is updated in snap2
    expected.emplace_back("~e", snapid2); // file '~e' is updated in snap2
    expected.emplace_back("f", snapid2);  // file 'f' is updated in snap2
    expected.emplace_back("g", snapid1);  // file 'g' is removed in snap2
    expected.emplace_back("S", snapid2);  // folder 'S' is present in snap2 hence reported
    expected.emplace_back("T", snapid2);  // folder 'T' is created in snap2
    expected.emplace_back("C", snapid1);  // folder 'C' is removed in snap2
    expected.emplace_back("G", snapid1);  // folder 'G' is removed in snap2
    expected.emplace_back("k", snapid2);  // file 'k' is created in snap2
    expected.emplace_back("l", snapid2);  // file 'l' is created in snap2
    expected.emplace_back("K", snapid2);  // folder 'K' is created in snap2
    expected.emplace_back("I", snapid2);  // folder 'I' is created in snap2
    expected.emplace_back("L", snapid2);  // folder 'L' is present in snap2 but got more
                                       // subfolders
    test_mount.verify_snap_diff(expected, "", "snap1", "snap2");
  }
  {

    //
    // Make sure snapshot delta for /S (existed at both snap1 and snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("sa", snapid2);
    test_mount.verify_snap_diff(expected, "S", "snap1", "snap2");
  }
  {
    //
    // Make sure snapshot delta for /T (created at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("td", snapid2);
    test_mount.verify_snap_diff(expected, "T", "snap1", "snap2");
  }
  {
    //
    // Make sure snapshot delta for /C (removed at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("cc", snapid1);
    expected.emplace_back("CC", snapid1);
    test_mount.verify_snap_diff(expected, "C", "snap2", "snap1");
  }
  {
    //
    // Make sure snapshot delta for /C/CC (removed at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("c", snapid1);
    test_mount.verify_snap_diff(expected, "C/CC", "snap2", "snap1");
  }
  {
    //
    // Make sure snapshot delta for /I (created at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("ii", snapid2);
    expected.emplace_back("J", snapid2);
    test_mount.verify_snap_diff(expected, "I", "snap1", "snap2");
  }
  {
    //
    // Make sure snapshot delta for /I/J (created at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("i", snapid2);
    expected.emplace_back("j", snapid2);
    expected.emplace_back("k", snapid2);
    test_mount.verify_snap_diff(expected, "I/J", "snap1", "snap2");
  }
  {
    //
    // Make sure snapshot delta for /L (extended at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("LL", snapid2);
    expected.emplace_back("LM", snapid2);
    expected.emplace_back("LN", snapid2);
    test_mount.verify_snap_diff(expected, "L", "snap1", "snap2");
  }
  {
    //
    // Make sure snapshot delta for /L/LL (updated at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("ll", snapid2);
    test_mount.verify_snap_diff(expected, "L/LL", "snap1", "snap2");
  }
  {
    //
    // Make sure snapshot delta for /L/LN (created empty at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    test_mount.verify_snap_diff(expected, "L/LN", "snap1", "snap2");
  }

  {
    // Make sure snapshot delta for /L/LM (created at snap2)
    // is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("lm", snapid2);
    test_mount.verify_snap_diff(expected, "L/LM", "snap1", "snap2");
  }
  std::cout << "-------------" << std::endl;

  test_mount.remove_all();
  test_mount.rmsnap("snap1");
  test_mount.rmsnap("snap2");
  test_mount.rmsnap("snap3");
}

//
// This case tests SnapDiff functionality for snap2/snap3 snapshot delta
// retrieved through .snap path-based query API.
// It operates against FS layout created by prepareSnapDiffCases() method,
// see relevant table before that function for FS state overview.
//
TEST(LibCephFS, SnapDiffCases2_3)
{
  TestMount test_mount;

  // Create directory tree evolving through a bunch of snapshots
  test_mount.prepareSnapDiffLib3Cases();

  uint64_t snapid2;
  uint64_t snapid3;
  ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2));
  ASSERT_EQ(0, test_mount.get_snapid("snap3", &snapid3));
  std::cout << snapid2 << " vs. " << snapid3 << std::endl;
  ASSERT_GT(snapid3, 0);
  ASSERT_GT(snapid3, 0);
  ASSERT_GT(snapid3, snapid2);

  // Print snapshot delta (snap2 vs. snap3) results for root in a
  // human-readable form.
  test_mount.print_snap_diff("", "snap2", "snap3");

  {
    // Make sure the root delta is as expected
    // One should use columns snap1 and snap2 from
    // the table preceeding prepareSnapDiffCases() function
    // to learn which names to expect in the delta.
    //
    //  - file 'c' is removed since snap1 hence not present in delta
    //  - file 'l' is unchanged hence not present in delta
    //  - file 'i' is unchanged hence not present in delta
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("a", snapid3);   // file 'a' is updated in snap3
    expected.emplace_back("b", snapid3);   // file 'b' is updated in snap3
    expected.emplace_back("d", snapid3);   // file 'd' is updated in snap3
    expected.emplace_back("~e", snapid2);  // file '~e' is removed in snap3
    expected.emplace_back("e", snapid2);   // file 'e' is removed in snap3
    expected.emplace_back("f", snapid2);   // file 'f' is removed in snap3
    expected.emplace_back("ff", snapid2);  // file 'ff' is removed in snap3
    expected.emplace_back("g", snapid3);   // file 'g' re-appeared in snap3
    expected.emplace_back("S", snapid3);   // folder 'S' is present in snap3 hence reported
    expected.emplace_back("T", snapid3);   // folder 'T' is present in snap3 hence reported
    expected.emplace_back("G", snapid3);   // folder 'G' re-appeared in snap3 hence reported
    expected.emplace_back("k", snapid2);   // file 'k' is removed in snap3
    expected.emplace_back("K", snapid2);   // folder 'K' is removed in snap3
    expected.emplace_back("H", snapid3);   // folder 'H' is created in snap3 hence reported
    expected.emplace_back("I", snapid3);   // folder 'I' is present in snap3 hence reported
    expected.emplace_back("L", snapid3);   // folder 'L' is present in snap3 hence reported
    test_mount.verify_snap_diff(expected, "", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /S (children updated) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("sa", snapid3);
    test_mount.verify_snap_diff(expected, "S", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /T (children updated) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("td", snapid3);
    test_mount.verify_snap_diff(expected, "T", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /G (re-appeared) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("gg", snapid3);
    test_mount.verify_snap_diff(expected, "G", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /K (removed) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("kk", snapid2);
    test_mount.verify_snap_diff(expected, "K", "snap3", "snap2");
  }
  {
    //
    // Make sure snapshot delta for /H (created) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("hh", snapid3);
    test_mount.verify_snap_diff(expected, "H", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /I (children updated) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("ii", snapid3);
    expected.emplace_back("iii", snapid3);
    expected.emplace_back("iiii", snapid3);
    expected.emplace_back("J", snapid3);
    test_mount.verify_snap_diff(expected, "I", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /I/J (children updated/removed) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("i", snapid3);
    expected.emplace_back("k", snapid2);
        expected.emplace_back("l", snapid3);
    test_mount.verify_snap_diff(expected, "I/J", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /L (children updated/removed) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("ll", snapid3);
    expected.emplace_back("LL", snapid3);
    expected.emplace_back("LM", snapid2);
    expected.emplace_back("LN", snapid3);
    test_mount.verify_snap_diff(expected, "L", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /L/LL (children updated) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("ll", snapid3);
    test_mount.verify_snap_diff(expected, "L/LL", "snap2", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /L/LM (removed) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("lm", snapid2);
    test_mount.verify_snap_diff(expected, "L/LM", "snap3", "snap2");
  }
  {
    //
    // Make sure snapshot delta for /L/LN (created empty) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    test_mount.verify_snap_diff(expected, "L/LN", "snap2", "snap3");
  }
  test_mount.remove_all();
  test_mount.rmsnap("snap1");
  test_mount.rmsnap("snap2");
  test_mount.rmsnap("snap3");
}

//
// This case tests SnapDiff functionality for snap1/snap3 snapshot delta
// retrieved through .snap path-based query API.
// It operates against FS layout created by prepareSnapDiffCases() method,
// see relevant table before that function for FS state overview.
//
TEST(LibCephFS, SnapDiffCases1_3)
{
  TestMount test_mount;

  // Create directory tree evolving through a bunch of snapshots
  test_mount.prepareSnapDiffLib3Cases();

  uint64_t snapid1;
  uint64_t snapid3;
  ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1));
  ASSERT_EQ(0, test_mount.get_snapid("snap3", &snapid3));
  std::cout << snapid1 << " vs. " << snapid3 << std::endl;
  ASSERT_GT(snapid3, 0);
  ASSERT_GT(snapid3, 0);
  ASSERT_GT(snapid3, snapid1);

  // Print snapshot delta (snap2 vs. snap3) results for root in a
  // human-readable form.
  test_mount.print_snap_diff("", "snap1", "snap3");

  {
    // Make sure the root delta is as expected
    // One should use columns snap1 and snap3 from
    // the table preceeding prepareSnapDiffCases() function
    // to learn which names to expect in the delta.
    //
    //  - file 'i' is unchanged hence not present in delta
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("a", snapid3);  // file 'a' is updated in snap3
    expected.emplace_back("b", snapid3);  // file 'b' is updated in snap3
    expected.emplace_back("c", snapid1); // file 'c' is removed in snap2
    expected.emplace_back("d", snapid3);  // file 'd' is updated in snap3
    expected.emplace_back("~e", snapid1); // file '~e' is removed in snap3
    expected.emplace_back("e", snapid1);  // file 'e' is removed in snap3
    expected.emplace_back("f", snapid1);  // file 'f' is removed in snap3
    expected.emplace_back("ff", snapid1); // file 'ff' is removed in snap3
    expected.emplace_back("g", snapid3);  // file 'g' removed in snap2 and
                                          // re-appeared in snap3
    expected.emplace_back("S", snapid3);  // folder 'S' is present in snap3 hence reported
    expected.emplace_back("T", snapid3);  // folder 'T' is present in snap3 hence reported
    expected.emplace_back("C", snapid1);  // folder 'C' is removed in snap2

    // folder 'G' is removed in snap2 and re-appeared in snap3
    // hence reporting it twice under different snapid
    expected.emplace_back("G", snapid1);
    expected.emplace_back("G", snapid3);

    expected.emplace_back("l", snapid3);   // file 'l' is created in snap2
    expected.emplace_back("H", snapid3);   // folder 'H' is created in snap3 hence reported
    expected.emplace_back("I", snapid3);   // folder 'I' is created in snap3 hence reported
    expected.emplace_back("L", snapid3);   // folder 'L' is created in snap3 hence reported
    test_mount.verify_snap_diff(expected, "", "snap3", "snap1");
  }
  {
    //
    // Make sure snapshot delta for /S (children updated) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("sa", snapid3);
    test_mount.verify_snap_diff(expected, "S", "snap3", "snap1");
  }
  {
    //
    // Make sure snapshot delta for /T (created and children updated) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("td", snapid3);
    test_mount.verify_snap_diff(expected, "T", "snap3", "snap1");
  }
  {
    //
    // Make sure snapshot delta for /C (removed) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("cc", snapid1);
    expected.emplace_back("CC", snapid1);
    test_mount.verify_snap_diff(expected, "C", "snap3", "snap1");
  }
  {
    //
    // Make sure snapshot delta for /C/CC (removed) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("c", snapid1);
    test_mount.verify_snap_diff(expected, "C/CC", "snap3", "snap1");
  }
  {
    //
    // Make sure snapshot delta for /G (removed) is as expected
    // For this case (G@snap1 and G@snap3 are different entries)
    // the order in which snapshot names are provided is crucial.
    // Making  G@snap1 vs. snap3 delta returns everything from G@snap1
    // but omits any entries from G/snap3 (since it's a different entry).
    // And making  G@snap3 vs. snap1 delta returns everything from G@snap3
    // but nothing from snap1,

    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("gg", snapid1);
    test_mount.verify_snap_diff(expected, "G", "snap1", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /G (re-created) is as expected
    // The snapshot names order is important, see above.
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("gg", snapid3);
    test_mount.verify_snap_diff(expected, "G", "snap3", "snap1");
  }
  {
    //
    // Make sure snapshot delta for /H (created) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("hh", snapid3);
    test_mount.verify_snap_diff(expected, "H", "snap1", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /I (chinldren updated) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("ii", snapid3);
    expected.emplace_back("iii", snapid3);
    expected.emplace_back("iiii", snapid3);
    expected.emplace_back("J", snapid3);
    test_mount.verify_snap_diff(expected, "I", "snap1", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /I/J (created at snap2) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("i", snapid3);
    expected.emplace_back("j", snapid3);
    expected.emplace_back("l", snapid3);
    test_mount.verify_snap_diff(expected, "I/J", "snap1", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /L is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("ll", snapid3);
    expected.emplace_back("LL", snapid3);
    expected.emplace_back("LN", snapid3);
    test_mount.verify_snap_diff(expected, "L", "snap1", "snap3");
  }
  {
    //
    // Make sure snapshot delta for /L/LL (children updated) is as expected
    //
    vector<std::pair<string, uint64_t>> expected;
    expected.emplace_back("ll", snapid3);
    test_mount.verify_snap_diff(expected, "L/LL", "snap1", "snap3");
  }
  {
    vector<std::pair<string, uint64_t>> expected;
    test_mount.verify_snap_diff(expected, "L/LN", "snap1", "snap3");
  }
  std::cout << "-------------" << std::endl;

  test_mount.remove_all();
  test_mount.rmsnap("snap1");
  test_mount.rmsnap("snap2");
  test_mount.rmsnap("snap3");
}

/*
* SnapDiff readdir API testing for huge dir
* when delta is minor.
*/
TEST(LibCephFS, HugeSnapDiffSmallDelta)
{
  TestMount test_mount;

  long int file_count = 10000;
  printf("Seeding %ld files...\n", file_count);

  // Create simple directory tree with a couple of snapshots
  // to test against.
  string name_prefix_start = "aaaa";
  string name_prefix_bulk = "file";
  string name_prefix_end = "zzzz";
  test_mount.prepareHugeSnapDiff(name_prefix_start,
                                 name_prefix_bulk,
                                 name_prefix_end,
                                 file_count,
                                 false);

  uint64_t snapid1;
  uint64_t snapid2;

  // learn snapshot ids and do basic verification
  ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1));
  ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2));
  ASSERT_GT(snapid1, 0);
  ASSERT_GT(snapid2, 0);
  ASSERT_GT(snapid2, snapid1);
  std::cout << snapid1 << " vs. " << snapid2 << std::endl;

  //
  // Make sure snap1 vs. snap2 delta for the root is as expected
  //
  {
    vector<pair<string, uint64_t>> expected;
    expected.emplace_back(name_prefix_start + "B", snapid1);
    expected.emplace_back(name_prefix_start + "C", snapid2);
    expected.emplace_back(name_prefix_start + "D", snapid2);

    expected.emplace_back(name_prefix_end + "B", snapid1);
    expected.emplace_back(name_prefix_end + "C", snapid2);
    expected.emplace_back(name_prefix_end + "D", snapid2);
    test_mount.verify_snap_diff(expected, "", "snap1", "snap2");
  }

  std::cout << "------------- closing -------------" << std::endl;
  ASSERT_EQ(0, test_mount.purge_dir(""));
  ASSERT_EQ(0, test_mount.rmsnap("snap1"));
  ASSERT_EQ(0, test_mount.rmsnap("snap2"));
}

/*
* SnapDiff readdir API testing for huge dir
* when delta is large
*/
TEST(LibCephFS, HugeSnapDiffLargeDelta)
{
  TestMount test_mount;

  // Calculate amount of files required to have multiple directory fragments
  // using relevant config parameters.
  // file_count = mds_bal_spli_size * mds_bal_fragment_fast_factor + 100
  char buf[256];
  int r = test_mount.conf_get("mds_bal_split_size", buf, sizeof(buf));
  ASSERT_TRUE(r >= 0);
  long int file_count = strtol(buf, nullptr, 10);
  r = test_mount.conf_get("mds_bal_fragment_fast_factor ", buf, sizeof(buf));
  ASSERT_TRUE(r >= 0);
  double factor = strtod(buf, nullptr);
  file_count *= factor;
  file_count += 100;
  printf("Seeding %ld files...\n", file_count);

  // Create simple directory tree with a couple of snapshots
  // to test against.

  string name_prefix_start = "aaaa";
  string name_prefix_bulk = "file";
  string name_prefix_end = "zzzz";

  test_mount.prepareHugeSnapDiff(name_prefix_start,
                                 name_prefix_bulk,
                                 name_prefix_end,
                                 file_count,
                                 true);
  uint64_t snapid1;
  uint64_t snapid2;

  // learn snapshot ids and do basic verification
  ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1));
  ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2));
  ASSERT_GT(snapid1, 0);
  ASSERT_GT(snapid2, 0);
  ASSERT_GT(snapid2, snapid1);
  std::cout << snapid1 << " vs. " << snapid2 << std::endl;

  //
  // Make sure snap1 vs. snap2 delta for the root is as expected
  //
  {
    vector<pair<string, uint64_t>> expected;
    expected.emplace_back(name_prefix_start + "B", snapid1);
    expected.emplace_back(name_prefix_start + "C", snapid2);
    expected.emplace_back(name_prefix_start + "D", snapid2);
    for (size_t i = 0; i < (size_t)file_count; i++) {
      expected.emplace_back(name_prefix_bulk + stringify(i), snapid2);
    }
    expected.emplace_back(name_prefix_end + "B", snapid1);
    expected.emplace_back(name_prefix_end + "C", snapid2);
    expected.emplace_back(name_prefix_end + "D", snapid2);
    test_mount.verify_snap_diff(expected, "", "snap1", "snap2");
  }

  std::cout << "------------- closing -------------" << std::endl;
  ASSERT_EQ(0, test_mount.purge_dir(""));
  ASSERT_EQ(0, test_mount.rmsnap("snap1"));
  ASSERT_EQ(0, test_mount.rmsnap("snap2"));
}
