]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
test/librbd: redo TestMockObjectMapDiffRequest.*Delta tests
authorIlya Dryomov <idryomov@gmail.com>
Sun, 3 Dec 2023 15:39:39 +0000 (16:39 +0100)
committerIlya Dryomov <idryomov@gmail.com>
Tue, 26 Dec 2023 20:51:47 +0000 (21:51 +0100)
Existing *Delta tests cover:

- beginning of time -> HEAD, through intermediate snap
- snap -> snap, directly
- snap -> HEAD, directly

But coverage is too weak: none of the weird OBJECT_PENDING cases and
only a single diff-iterate vs deep-copy case is tested, for example.

Coverage is missing completely for:

- beginning of time -> HEAD, directly
- beginning of time -> snap, directly
- beginning of time -> snap, through intermediate snap
- snap -> snap, through intermediate snap
- snap -> HEAD, through intermediate snap

This adds the following tests:

- FromBeginningToHead
- FromBeginningToHeadIntermediateSnap (expands FullDelta)
- FromBeginningToSnap
- FromBeginningToSnapIntermediateSnap
- FromSnapToSnap (expands IntermediateDelta)
- FromSnapToSnapIntermediateSnap
- FromSnapToHead (expands EndDelta)
- FromSnapToHeadIntermediateSnap

Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
(cherry picked from commit 7aff35c987b9f0e9f0d4198d032737da0ce12b36)

src/test/librbd/object_map/test_mock_DiffRequest.cc

index 440040e9a835aff4acb65e1cb8ff6e80fa565576..aefebf4a05bba96b6ebb285294fc2491d0ecffa8 100644 (file)
@@ -32,6 +32,122 @@ using ::testing::WithArg;
 namespace librbd {
 namespace object_map {
 
+static constexpr uint8_t from_beginning_table[][2] = {
+  //        to                expected
+  { OBJECT_NONEXISTENT,   DIFF_STATE_HOLE },
+  { OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED }
+};
+
+static constexpr uint8_t from_beginning_intermediate_table[][4] = {
+  //   intermediate               to             diff-iterate expected       deep-copy expected
+  { OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   DIFF_STATE_HOLE,          DIFF_STATE_HOLE },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_NONEXISTENT,   DIFF_STATE_HOLE,          DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_NONEXISTENT,   DIFF_STATE_HOLE,          DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   DIFF_STATE_HOLE,          DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED }
+};
+
+static constexpr uint8_t from_snap_table[][3] = {
+  //       from                   to                expected
+  { OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   DIFF_STATE_HOLE },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA },
+  { OBJECT_PENDING,       OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA }
+};
+
+static constexpr uint8_t from_snap_intermediate_table[][5] = {
+  //       from              intermediate               to             diff-iterate expected       deep-copy expected
+  { OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   DIFF_STATE_HOLE,          DIFF_STATE_HOLE },
+  { OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS,        OBJECT_NONEXISTENT,   DIFF_STATE_HOLE,          DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS,        OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS,        OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_PENDING,       OBJECT_NONEXISTENT,   DIFF_STATE_HOLE,          DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_PENDING,       OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_PENDING,       OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   DIFF_STATE_HOLE,          DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_NONEXISTENT,   OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_NONEXISTENT,   OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS,        OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS,        OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS,        OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_PENDING,       OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_PENDING,       OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_PENDING,       OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA,          DIFF_STATE_DATA },
+  { OBJECT_PENDING,       OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_PENDING,       OBJECT_NONEXISTENT,   OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_NONEXISTENT,   OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS,        OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS,        OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS,        OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_PENDING,       OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_PENDING,       OBJECT_PENDING,       OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_PENDING,       OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  OBJECT_NONEXISTENT,   DIFF_STATE_HOLE_UPDATED,  DIFF_STATE_HOLE_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS,        DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  OBJECT_PENDING,       DIFF_STATE_DATA_UPDATED,  DIFF_STATE_DATA_UPDATED },
+  { OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  OBJECT_EXISTS_CLEAN,  DIFF_STATE_DATA,          DIFF_STATE_DATA }
+};
+
 class TestMockObjectMapDiffRequest : public TestMockFixture,
                                      public ::testing::WithParamInterface<bool> {
 public:
@@ -146,44 +262,108 @@ TEST_P(TestMockObjectMapDiffRequest, FastDiffInvalid) {
   ASSERT_EQ(-EINVAL, ctx.wait());
 }
 
-TEST_P(TestMockObjectMapDiffRequest, FullDelta) {
+TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnap) {
   REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
 
-  uint32_t object_count = 5;
+  uint32_t object_count = std::size(from_beginning_table);
   m_image_ctx->size = object_count * (1 << m_image_ctx->order);
 
   MockTestImageCtx mock_image_ctx(*m_image_ctx);
   mock_image_ctx.snap_info = {
     {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
-          {}, {}, {}}},
-    {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
           {}, {}, {}}}
   };
 
+  BitVector<2> object_map_1;
+  object_map_1.resize(object_count);
+  BitVector<2> expected_diff_state;
+  expected_diff_state.resize(object_count);
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_1[i] = from_beginning_table[i][0];
+    expected_diff_state[i] = from_beginning_table[i][1];
+  }
+
   InSequence seq;
 
   expect_get_flags(mock_image_ctx, 1U, 0, 0);
+  expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+  C_SaferCond ctx;
+  auto req = new MockDiffRequest(&mock_image_ctx, 0, 1, is_diff_iterate(),
+                                 &m_object_diff_state, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnap) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  uint32_t object_count = std::size(from_beginning_intermediate_table);
+  m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+  MockTestImageCtx mock_image_ctx(*m_image_ctx);
+  mock_image_ctx.snap_info = {
+    {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+          {}, {}, {}}},
+    {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+          {}, {}, {}}}
+  };
 
   BitVector<2> object_map_1;
   object_map_1.resize(object_count);
-  object_map_1[1] = OBJECT_EXISTS_CLEAN;
+  BitVector<2> object_map_2;
+  object_map_2.resize(object_count);
+  BitVector<2> expected_diff_state;
+  expected_diff_state.resize(object_count);
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_1[i] = from_beginning_intermediate_table[i][0];
+    object_map_2[i] = from_beginning_intermediate_table[i][1];
+    if (is_diff_iterate()) {
+      expected_diff_state[i] = from_beginning_intermediate_table[i][2];
+    } else {
+      expected_diff_state[i] = from_beginning_intermediate_table[i][3];
+    }
+  }
+
+  InSequence seq;
+
+  expect_get_flags(mock_image_ctx, 1U, 0, 0);
   expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
 
   expect_get_flags(mock_image_ctx, 2U, 0, 0);
-
-  BitVector<2> object_map_2;
-  object_map_2.resize(object_count);
-  object_map_2[1] = OBJECT_EXISTS_CLEAN;
-  object_map_2[2] = OBJECT_EXISTS;
-  object_map_2[3] = OBJECT_EXISTS;
   expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
 
-  expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+  C_SaferCond ctx;
+  auto req = new MockDiffRequest(&mock_image_ctx, 0, 2, is_diff_iterate(),
+                                 &m_object_diff_state, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHead) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  uint32_t object_count = std::size(from_beginning_table);
+  m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+  MockTestImageCtx mock_image_ctx(*m_image_ctx);
 
   BitVector<2> object_map_head;
   object_map_head.resize(object_count);
-  object_map_head[1] = OBJECT_EXISTS_CLEAN;
-  object_map_head[2] = OBJECT_EXISTS_CLEAN;
+  BitVector<2> expected_diff_state;
+  expected_diff_state.resize(object_count);
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_head[i] = from_beginning_table[i][0];
+    expected_diff_state[i] = from_beginning_table[i][1];
+  }
+
+  InSequence seq;
+
+  expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
   expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
 
   C_SaferCond ctx;
@@ -193,18 +373,59 @@ TEST_P(TestMockObjectMapDiffRequest, FullDelta) {
   req->send();
   ASSERT_EQ(0, ctx.wait());
 
+  ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnap) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  uint32_t object_count = std::size(from_beginning_intermediate_table);
+  m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+  MockTestImageCtx mock_image_ctx(*m_image_ctx);
+  mock_image_ctx.snap_info = {
+    {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+          {}, {}, {}}}
+  };
+
+  BitVector<2> object_map_1;
+  object_map_1.resize(object_count);
+  BitVector<2> object_map_head;
+  object_map_head.resize(object_count);
   BitVector<2> expected_diff_state;
   expected_diff_state.resize(object_count);
-  expected_diff_state[1] = DIFF_STATE_DATA_UPDATED;
-  expected_diff_state[2] = DIFF_STATE_DATA_UPDATED;
-  expected_diff_state[3] = DIFF_STATE_HOLE;
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_1[i] = from_beginning_intermediate_table[i][0];
+    object_map_head[i] = from_beginning_intermediate_table[i][1];
+    if (is_diff_iterate()) {
+      expected_diff_state[i] = from_beginning_intermediate_table[i][2];
+    } else {
+      expected_diff_state[i] = from_beginning_intermediate_table[i][3];
+    }
+  }
+
+  InSequence seq;
+
+  expect_get_flags(mock_image_ctx, 1U, 0, 0);
+  expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+  expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+  expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+  C_SaferCond ctx;
+  auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP,
+                                 is_diff_iterate(), &m_object_diff_state,
+                                 &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+
   ASSERT_EQ(expected_diff_state, m_object_diff_state);
 }
 
-TEST_P(TestMockObjectMapDiffRequest, IntermediateDelta) {
+TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnap) {
   REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
 
-  uint32_t object_count = 5;
+  uint32_t object_count = std::size(from_snap_table);
   m_image_ctx->size = object_count * (1 << m_image_ctx->order);
 
   MockTestImageCtx mock_image_ctx(*m_image_ctx);
@@ -215,23 +436,24 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateDelta) {
           {}, {}, {}}}
   };
 
+  BitVector<2> object_map_1;
+  object_map_1.resize(object_count);
+  BitVector<2> object_map_2;
+  object_map_2.resize(object_count);
+  BitVector<2> expected_diff_state;
+  expected_diff_state.resize(object_count);
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_1[i] = from_snap_table[i][0];
+    object_map_2[i] = from_snap_table[i][1];
+    expected_diff_state[i] = from_snap_table[i][2];
+  }
+
   InSequence seq;
 
   expect_get_flags(mock_image_ctx, 1U, 0, 0);
-
-  BitVector<2> object_map_1;
-  object_map_1.resize(object_count);
-  object_map_1[1] = OBJECT_EXISTS;
-  object_map_1[2] = OBJECT_EXISTS_CLEAN;
   expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
 
   expect_get_flags(mock_image_ctx, 2U, 0, 0);
-
-  BitVector<2> object_map_2;
-  object_map_2.resize(object_count);
-  object_map_2[1] = OBJECT_EXISTS_CLEAN;
-  object_map_2[2] = OBJECT_EXISTS;
-  object_map_2[3] = OBJECT_EXISTS;
   expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
 
   C_SaferCond ctx;
@@ -240,18 +462,13 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateDelta) {
   req->send();
   ASSERT_EQ(0, ctx.wait());
 
-  BitVector<2> expected_diff_state;
-  expected_diff_state.resize(object_count);
-  expected_diff_state[1] = DIFF_STATE_DATA;
-  expected_diff_state[2] = DIFF_STATE_DATA_UPDATED;
-  expected_diff_state[3] = DIFF_STATE_DATA_UPDATED;
   ASSERT_EQ(expected_diff_state, m_object_diff_state);
 }
 
-TEST_P(TestMockObjectMapDiffRequest, EndDelta) {
+TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapIntermediateSnap) {
   REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
 
-  uint32_t object_count = 5;
+  uint32_t object_count = std::size(from_snap_intermediate_table);
   m_image_ctx->size = object_count * (1 << m_image_ctx->order);
 
   MockTestImageCtx mock_image_ctx(*m_image_ctx);
@@ -259,40 +476,143 @@ TEST_P(TestMockObjectMapDiffRequest, EndDelta) {
     {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
           {}, {}, {}}},
     {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+          {}, {}, {}}},
+    {3U, {"snap3", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
           {}, {}, {}}}
   };
 
+  BitVector<2> object_map_1;
+  object_map_1.resize(object_count);
+  BitVector<2> object_map_2;
+  object_map_2.resize(object_count);
+  BitVector<2> object_map_3;
+  object_map_3.resize(object_count);
+  BitVector<2> expected_diff_state;
+  expected_diff_state.resize(object_count);
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_1[i] = from_snap_intermediate_table[i][0];
+    object_map_2[i] = from_snap_intermediate_table[i][1];
+    object_map_3[i] = from_snap_intermediate_table[i][2];
+    if (is_diff_iterate()) {
+      expected_diff_state[i] = from_snap_intermediate_table[i][3];
+    } else {
+      expected_diff_state[i] = from_snap_intermediate_table[i][4];
+    }
+  }
+
   InSequence seq;
 
-  expect_get_flags(mock_image_ctx, 2U, 0, 0);
+  expect_get_flags(mock_image_ctx, 1U, 0, 0);
+  expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
 
-  BitVector<2> object_map_2;
-  object_map_2.resize(object_count);
-  object_map_2[1] = OBJECT_EXISTS_CLEAN;
-  object_map_2[2] = OBJECT_EXISTS;
-  object_map_2[3] = OBJECT_EXISTS;
+  expect_get_flags(mock_image_ctx, 2U, 0, 0);
   expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
 
-  expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+  expect_get_flags(mock_image_ctx, 3U, 0, 0);
+  expect_load_map(mock_image_ctx, 3U, object_map_3, 0);
 
+  C_SaferCond ctx;
+  auto req = new MockDiffRequest(&mock_image_ctx, 1, 3, is_diff_iterate(),
+                                 &m_object_diff_state, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+
+  ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_P(TestMockObjectMapDiffRequest, FromSnapToHead) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  uint32_t object_count = std::size(from_snap_table);
+  m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+  MockTestImageCtx mock_image_ctx(*m_image_ctx);
+  mock_image_ctx.snap_info = {
+    {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+          {}, {}, {}}}
+  };
+
+  BitVector<2> object_map_1;
+  object_map_1.resize(object_count);
   BitVector<2> object_map_head;
   object_map_head.resize(object_count);
-  object_map_head[1] = OBJECT_EXISTS_CLEAN;
-  object_map_head[2] = OBJECT_EXISTS_CLEAN;
+  BitVector<2> expected_diff_state;
+  expected_diff_state.resize(object_count);
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_1[i] = from_snap_table[i][0];
+    object_map_head[i] = from_snap_table[i][1];
+    expected_diff_state[i] = from_snap_table[i][2];
+  }
+
+  InSequence seq;
+
+  expect_get_flags(mock_image_ctx, 1U, 0, 0);
+  expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+  expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
   expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
 
   C_SaferCond ctx;
-  auto req = new MockDiffRequest(&mock_image_ctx, 2, CEPH_NOSNAP,
+  auto req = new MockDiffRequest(&mock_image_ctx, 1, CEPH_NOSNAP,
                                  is_diff_iterate(), &m_object_diff_state,
                                  &ctx);
   req->send();
   ASSERT_EQ(0, ctx.wait());
 
+  ASSERT_EQ(expected_diff_state, m_object_diff_state);
+}
+
+TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadIntermediateSnap) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  uint32_t object_count = std::size(from_snap_intermediate_table);
+  m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+  MockTestImageCtx mock_image_ctx(*m_image_ctx);
+  mock_image_ctx.snap_info = {
+    {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+          {}, {}, {}}},
+    {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {},
+          {}, {}, {}}}
+  };
+
+  BitVector<2> object_map_1;
+  object_map_1.resize(object_count);
+  BitVector<2> object_map_2;
+  object_map_2.resize(object_count);
+  BitVector<2> object_map_head;
+  object_map_head.resize(object_count);
   BitVector<2> expected_diff_state;
   expected_diff_state.resize(object_count);
-  expected_diff_state[1] = DIFF_STATE_DATA;
-  expected_diff_state[2] = DIFF_STATE_DATA;
-  expected_diff_state[3] = DIFF_STATE_HOLE_UPDATED;
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_1[i] = from_snap_intermediate_table[i][0];
+    object_map_2[i] = from_snap_intermediate_table[i][1];
+    object_map_head[i] = from_snap_intermediate_table[i][2];
+    if (is_diff_iterate()) {
+      expected_diff_state[i] = from_snap_intermediate_table[i][3];
+    } else {
+      expected_diff_state[i] = from_snap_intermediate_table[i][4];
+    }
+  }
+
+  InSequence seq;
+
+  expect_get_flags(mock_image_ctx, 1U, 0, 0);
+  expect_load_map(mock_image_ctx, 1U, object_map_1, 0);
+
+  expect_get_flags(mock_image_ctx, 2U, 0, 0);
+  expect_load_map(mock_image_ctx, 2U, object_map_2, 0);
+
+  expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+  expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+
+  C_SaferCond ctx;
+  auto req = new MockDiffRequest(&mock_image_ctx, 1, CEPH_NOSNAP,
+                                 is_diff_iterate(), &m_object_diff_state,
+                                 &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+
   ASSERT_EQ(expected_diff_state, m_object_diff_state);
 }