]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
test/librbd: refactor DiffIterateTest.DiffIterateDeterministic{,PP}
authorIlya Dryomov <idryomov@gmail.com>
Fri, 21 Jun 2024 13:53:05 +0000 (15:53 +0200)
committerIlya Dryomov <idryomov@gmail.com>
Fri, 12 Jul 2024 21:02:53 +0000 (23:02 +0200)
In preparation for extending coverage, extract test logic into a couple
of parametrized helpers.

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

src/test/librbd/test_librbd.cc

index a309ce841d920a6de67b79abfca907ac8820fd40..8917570863308e23b23869f19bcf65dd613105cf 100644 (file)
@@ -5953,6 +5953,28 @@ TEST_F(TestLibRBD, FlushAioPP)
   ioctx.close();
 }
 
+struct diff_extent {
+  diff_extent(uint64_t _offset, uint64_t _length, bool _exists,
+              uint64_t object_size) :
+    offset(_offset), length(_length), exists(_exists)
+  {
+    if (object_size != 0) {
+      offset -= offset % object_size;
+      length = object_size;
+    }
+  }
+  uint64_t offset;
+  uint64_t length;
+  bool exists;
+  bool operator==(const diff_extent& o) const {
+    return offset == o.offset && length == o.length && exists == o.exists;
+  }
+};
+
+ostream& operator<<(ostream & o, const diff_extent& e) {
+  return o << '(' << e.offset << '~' << e.length << ' '
+           << (e.exists ? "true" : "false") << ')';
+}
 
 int iterate_cb(uint64_t off, size_t len, int exists, void *arg)
 {
@@ -5962,6 +5984,13 @@ int iterate_cb(uint64_t off, size_t len, int exists, void *arg)
   return 0;
 }
 
+int vector_iterate_cb(uint64_t off, size_t len, int exists, void *arg)
+{
+  auto diff = static_cast<std::vector<diff_extent>*>(arg);
+  diff->push_back(diff_extent(off, len, exists, 0));
+  return 0;
+}
+
 static int iterate_error_cb(uint64_t off, size_t len, int exists, void *arg)
 {
   return -EINVAL;
@@ -6036,426 +6065,432 @@ template <typename T>
 class DiffIterateTest : public TestLibRBD {
 public:
   static const uint8_t whole_object = T::whole_object;
-};
 
-template <bool _whole_object>
-class DiffIterateParams {
-public:
-  static const uint8_t whole_object = _whole_object;
-};
+  void test_deterministic(uint64_t object_off, uint64_t len) {
+    rados_ioctx_t ioctx;
+    ASSERT_EQ(0, rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx));
 
-typedef ::testing::Types<DiffIterateParams<false>,
-                         DiffIterateParams<true> > DiffIterateTypes;
-TYPED_TEST_SUITE(DiffIterateTest, DiffIterateTypes);
+    rbd_image_t image;
+    int order = 22;
+    std::string name = this->get_temp_image_name();
+    ASSERT_EQ(0, create_image(ioctx, name.c_str(), 20 << 20, &order));
+    ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+    test_deterministic(image, object_off, len, 1);
 
-TYPED_TEST(DiffIterateTest, DiffIterate)
-{
-  librados::IoCtx ioctx;
-  ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx));
+    ASSERT_EQ(0, rbd_close(image));
+    rados_ioctx_destroy(ioctx);
+  }
+
+  void test_deterministic_pp(uint64_t object_off, uint64_t len) {
+    librados::IoCtx ioctx;
+    ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx));
 
-  {
     librbd::RBD rbd;
     librbd::Image image;
-    int order = 0;
+    int order = 22;
     std::string name = this->get_temp_image_name();
-    uint64_t size = 20 << 20;
-
-    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order));
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), 20 << 20, &order));
     ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL));
+    test_deterministic_pp(image, object_off, len, 1);
+  }
 
-    bool skip_discard = this->is_skip_partial_discard_enabled(image);
+private:
+  void test_deterministic(rbd_image_t image, uint64_t object_off,
+                          uint64_t len, uint64_t block_size) {
+    uint64_t off1 = 0;
+    uint64_t off2 = 4 << 20;
+    uint64_t size = 20 << 20;
+    uint64_t extent_len = round_up_to(object_off + len, block_size);
+
+    rbd_image_info_t info;
+    ASSERT_EQ(0, rbd_stat(image, &info, sizeof(info)));
+    ASSERT_EQ(size, info.size);
+    ASSERT_EQ(5, info.num_objs);
+    ASSERT_EQ(4 << 20, info.obj_size);
+    ASSERT_EQ(22, info.order);
 
     uint64_t object_size = 0;
-    if (this->whole_object) {
-      object_size = 1 << order;
+    if (whole_object) {
+      object_size = 1 << info.order;
     }
 
-    interval_set<uint64_t> exists;
-    interval_set<uint64_t> one, two;
-    scribble(image, 10, 102400, skip_discard, &exists, &one);
-    cout << " wrote " << one << std::endl;
-    ASSERT_EQ(0, image.snap_create("one"));
-    scribble(image, 10, 102400, skip_discard, &exists, &two);
+    std::vector<diff_extent> extents;
+    ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-    two = round_diff_interval(two, object_size);
-    cout << " wrote " << two << std::endl;
+    ASSERT_EQ(-ENOENT, rbd_diff_iterate2(image, "snap1", 0, size, true,
+                                         whole_object, vector_iterate_cb,
+                                         &extents));
 
-    interval_set<uint64_t> diff;
-    ASSERT_EQ(0, image.diff_iterate2("one", 0, size, true, this->whole_object,
-                                     iterate_cb, (void *)&diff));
-    cout << " diff was " << diff << std::endl;
-    if (!two.subset_of(diff)) {
-      interval_set<uint64_t> i;
-      i.intersection_of(two, diff);
-      interval_set<uint64_t> l = two;
-      l.subtract(i);
-      cout << " ... two - (two*diff) = " << l << std::endl;
-    }
-    ASSERT_TRUE(two.subset_of(diff));
-  }
-  ioctx.close();
-}
+    ASSERT_EQ(0, rbd_snap_create(image, "snap1"));
 
-struct diff_extent {
-  diff_extent(uint64_t _offset, uint64_t _length, bool _exists,
-              uint64_t object_size) :
-    offset(_offset), length(_length), exists(_exists)
-  {
-    if (object_size != 0) {
-      offset -= offset % object_size;
-      length = object_size;
-    }
-  }
-  uint64_t offset;
-  uint64_t length;
-  bool exists;
-  bool operator==(const diff_extent& o) const {
-    return offset == o.offset && length == o.length && exists == o.exists;
-  }
-};
+    std::string buf(len, '1');
+    ASSERT_EQ(len, rbd_write(image, off1 + object_off, len, buf.data()));
+    ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-ostream& operator<<(ostream & o, const diff_extent& e) {
-  return o << '(' << e.offset << '~' << e.length << ' ' << (e.exists ? "true" : "false") << ')';
-}
+    ASSERT_EQ(0, rbd_snap_create(image, "snap2"));
 
-int vector_iterate_cb(uint64_t off, size_t len, int exists, void *arg)
-{
-  //cout << "iterate_cb " << off << "~" << len << std::endl;
-  vector<diff_extent> *diff = static_cast<vector<diff_extent> *>(arg);
-  diff->push_back(diff_extent(off, len, exists, 0));
-  return 0;
-}
+    ASSERT_EQ(len, rbd_write(image, off2 + object_off, len, buf.data()));
+    ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-TYPED_TEST(DiffIterateTest, DiffIterateDeterministic)
-{
-  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+    ASSERT_EQ(0, rbd_snap_create(image, "snap3"));
 
-  rados_ioctx_t ioctx;
-  ASSERT_EQ(0, rados_ioctx_create(this->_cluster, this->m_pool_name.c_str(),
-                                  &ioctx));
+    // 1. beginning of time -> HEAD
+    ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-  rbd_image_t image;
-  int order = 22;
-  std::string name = this->get_temp_image_name();
-  uint64_t size = 20 << 20;
+    // 2. snap1 -> HEAD
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-  ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order));
-  ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+    // 3. snap2 -> HEAD
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  uint64_t object_size = 0;
-  if (this->whole_object) {
-    object_size = 1 << order;
-  }
+    // 4. snap3 -> HEAD
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap3", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  std::vector<diff_extent> extents;
-  ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    ASSERT_PASSED(validate_object_map, image);
+    ASSERT_EQ(0, rbd_snap_set(image, "snap3"));
 
-  ASSERT_EQ(-ENOENT, rbd_diff_iterate2(image, "snap1", 0, size, true,
-                                       this->whole_object, vector_iterate_cb,
-                                       &extents));
+    // 5. beginning of time -> snap3
+    ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-  ASSERT_EQ(0, rbd_snap_create(image, "snap1"));
+    // 6. snap1 -> snap3
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-  std::string buf(256, '1');
-  ASSERT_EQ(256, rbd_write(image, 0, 256, buf.data()));
-  ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  extents.clear();
+    // 7. snap2 -> snap3
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  ASSERT_EQ(0, rbd_snap_create(image, "snap2"));
+    // 8. snap3 -> snap3
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap3", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  ASSERT_EQ(256, rbd_write(image, 1 << order, 256, buf.data()));
-  ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    ASSERT_PASSED(validate_object_map, image);
+    ASSERT_EQ(0, rbd_snap_set(image, "snap2"));
 
-  ASSERT_EQ(0, rbd_snap_create(image, "snap3"));
+    // 9. beginning of time -> snap2
+    ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  // 1. beginning of time -> HEAD
-  ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    // 10. snap1 -> snap2
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  // 2. snap1 -> HEAD
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    // 11. snap2 -> snap2
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  // 3. snap2 -> HEAD
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]);
-  extents.clear();
+    // 12. snap3 -> snap2
+    ASSERT_EQ(-EINVAL, rbd_diff_iterate2(image, "snap3", 0, size, true,
+                                         whole_object, vector_iterate_cb,
+                                         &extents));
 
-  // 4. snap3 -> HEAD
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap3", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    ASSERT_PASSED(validate_object_map, image);
+    ASSERT_EQ(0, rbd_snap_set(image, "snap1"));
 
-  ASSERT_PASSED(this->validate_object_map, image);
-  ASSERT_EQ(0, rbd_snap_set(image, "snap3"));
+    // 13. beginning of time -> snap1
+    ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  // 5. beginning of time -> snap3
-  ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    // 14. snap1 -> snap1
+    ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, whole_object,
+                                   vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  // 6. snap1 -> snap3
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    // 15. snap2 -> snap1
+    ASSERT_EQ(-EINVAL, rbd_diff_iterate2(image, "snap2", 0, size, true,
+                                         whole_object, vector_iterate_cb,
+                                         &extents));
 
-  // 7. snap2 -> snap3
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]);
-  extents.clear();
+    // 16. snap3 -> snap1
+    ASSERT_EQ(-EINVAL, rbd_diff_iterate2(image, "snap3", 0, size, true,
+                                         whole_object, vector_iterate_cb,
+                                         &extents));
 
-  // 8. snap3 -> snap3
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap3", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    ASSERT_PASSED(validate_object_map, image);
+  }
 
-  ASSERT_PASSED(this->validate_object_map, image);
-  ASSERT_EQ(0, rbd_snap_set(image, "snap2"));
+  void test_deterministic_pp(librbd::Image& image, uint64_t object_off,
+                             uint64_t len, uint64_t block_size) {
+    uint64_t off1 = 0 << 20;
+    uint64_t off2 = 4 << 20;
+    uint64_t size = 20 << 20;
+    uint64_t extent_len = round_up_to(object_off + len, block_size);
 
-  // 9. beginning of time -> snap2
-  ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  extents.clear();
+    librbd::image_info_t info;
+    ASSERT_EQ(0, image.stat(info, sizeof(info)));
+    ASSERT_EQ(size, info.size);
+    ASSERT_EQ(5, info.num_objs);
+    ASSERT_EQ(4 << 20, info.obj_size);
+    ASSERT_EQ(22, info.order);
 
-  // 10. snap1 -> snap2
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  extents.clear();
+    uint64_t object_size = 0;
+    if (whole_object) {
+      object_size = 1 << info.order;
+    }
 
-  // 11. snap2 -> snap2
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    std::vector<diff_extent> extents;
+    ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  // 12. snap3 -> snap2
-  ASSERT_EQ(-EINVAL, rbd_diff_iterate2(image, "snap3", 0, size, true,
-                                       this->whole_object, vector_iterate_cb,
-                                       &extents));
+    ASSERT_EQ(-ENOENT, image.diff_iterate2("snap1", 0, size, true,
+                                           whole_object, vector_iterate_cb,
+                                           &extents));
 
-  ASSERT_PASSED(this->validate_object_map, image);
-  ASSERT_EQ(0, rbd_snap_set(image, "snap1"));
+    ASSERT_EQ(0, image.snap_create("snap1"));
 
-  // 13. beginning of time -> snap1
-  ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    ceph::bufferlist bl;
+    bl.append(std::string(len, '1'));
+    ASSERT_EQ(len, image.write(off1 + object_off, len, bl));
+    ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  // 14. snap1 -> snap1
-  ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, this->whole_object,
-                                 vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    ASSERT_EQ(0, image.snap_create("snap2"));
 
-  // 15. snap2 -> snap1
-  ASSERT_EQ(-EINVAL, rbd_diff_iterate2(image, "snap2", 0, size, true,
-                                       this->whole_object, vector_iterate_cb,
-                                       &extents));
+    ASSERT_EQ(len, image.write(off2 + object_off, len, bl));
+    ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-  // 16. snap3 -> snap1
-  ASSERT_EQ(-EINVAL, rbd_diff_iterate2(image, "snap3", 0, size, true,
-                                       this->whole_object, vector_iterate_cb,
-                                       &extents));
+    ASSERT_EQ(0, image.snap_create("snap3"));
 
-  ASSERT_PASSED(this->validate_object_map, image);
+    // 1. beginning of time -> HEAD
+    ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-  ASSERT_EQ(0, rbd_close(image));
-  rados_ioctx_destroy(ioctx);
-}
+    // 2. snap1 -> HEAD
+    ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-TYPED_TEST(DiffIterateTest, DiffIterateDeterministicPP)
-{
-  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+    // 3. snap2 -> HEAD
+    ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  librados::IoCtx ioctx;
-  ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx));
+    // 4. snap3 -> HEAD
+    ASSERT_EQ(0, image.diff_iterate2("snap3", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  librbd::RBD rbd;
-  librbd::Image image;
-  int order = 22;
-  std::string name = this->get_temp_image_name();
-  uint64_t size = 20 << 20;
+    ASSERT_PASSED(validate_object_map, image);
+    ASSERT_EQ(0, image.snap_set("snap3"));
 
-  ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order));
-  ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL));
+    // 5. beginning of time -> snap3
+    ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-  uint64_t object_size = 0;
-  if (this->whole_object) {
-    object_size = 1 << order;
-  }
+    // 6. snap1 -> snap3
+    ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(2u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[1]);
+    extents.clear();
 
-  std::vector<diff_extent> extents;
-  ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    // 7. snap2 -> snap3
+    ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off2, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  ASSERT_EQ(-ENOENT, image.diff_iterate2("snap1", 0, size, true,
-                                         this->whole_object, vector_iterate_cb,
-                                         &extents));
+    // 8. snap3 -> snap3
+    ASSERT_EQ(0, image.diff_iterate2("snap3", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  ASSERT_EQ(0, image.snap_create("snap1"));
+    ASSERT_PASSED(validate_object_map, image);
+    ASSERT_EQ(0, image.snap_set("snap2"));
 
-  ceph::bufferlist bl;
-  bl.append(std::string(256, '1'));
-  ASSERT_EQ(256, image.write(0, 256, bl));
-  ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  extents.clear();
+    // 9. beginning of time -> snap2
+    ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  ASSERT_EQ(0, image.snap_create("snap2"));
+    // 10. snap1 -> snap2
+    ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(1u, extents.size());
+    ASSERT_EQ(diff_extent(off1, extent_len, true, object_size), extents[0]);
+    extents.clear();
 
-  ASSERT_EQ(256, image.write(1 << order, 256, bl));
-  ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    // 11. snap2 -> snap2
+    ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  ASSERT_EQ(0, image.snap_create("snap3"));
+    // 12. snap3 -> snap2
+    ASSERT_EQ(-EINVAL, image.diff_iterate2("snap3", 0, size, true,
+                                           whole_object, vector_iterate_cb,
+                                           &extents));
 
-  // 1. beginning of time -> HEAD
-  ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    ASSERT_PASSED(validate_object_map, image);
+    ASSERT_EQ(0, image.snap_set("snap1"));
 
-  // 2. snap1 -> HEAD
-  ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    // 13. beginning of time -> snap1
+    ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  // 3. snap2 -> HEAD
-  ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]);
-  extents.clear();
+    // 14. snap1 -> snap1
+    ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, whole_object,
+                                     vector_iterate_cb, &extents));
+    ASSERT_EQ(0u, extents.size());
 
-  // 4. snap3 -> HEAD
-  ASSERT_EQ(0, image.diff_iterate2("snap3", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    // 15. snap2 -> snap1
+    ASSERT_EQ(-EINVAL, image.diff_iterate2("snap2", 0, size, true,
+                                           whole_object, vector_iterate_cb,
+                                           &extents));
 
-  ASSERT_PASSED(this->validate_object_map, image);
-  ASSERT_EQ(0, image.snap_set("snap3"));
+    // 16. snap3 -> snap1
+    ASSERT_EQ(-EINVAL, image.diff_iterate2("snap3", 0, size, true,
+                                           whole_object, vector_iterate_cb,
+                                           &extents));
 
-  // 5. beginning of time -> snap3
-  ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+    ASSERT_PASSED(validate_object_map, image);
+  }
+};
 
-  // 6. snap1 -> snap3
-  ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(2u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]);
-  extents.clear();
+template <bool _whole_object>
+class DiffIterateParams {
+public:
+  static const uint8_t whole_object = _whole_object;
+};
 
-  // 7. snap2 -> snap3
-  ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]);
-  extents.clear();
+typedef ::testing::Types<DiffIterateParams<false>,
+                         DiffIterateParams<true> > DiffIterateTypes;
+TYPED_TEST_SUITE(DiffIterateTest, DiffIterateTypes);
 
-  // 8. snap3 -> snap3
-  ASSERT_EQ(0, image.diff_iterate2("snap3", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+TYPED_TEST(DiffIterateTest, DiffIterate)
+{
+  librados::IoCtx ioctx;
+  ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx));
 
-  ASSERT_PASSED(this->validate_object_map, image);
-  ASSERT_EQ(0, image.snap_set("snap2"));
+  {
+    librbd::RBD rbd;
+    librbd::Image image;
+    int order = 0;
+    std::string name = this->get_temp_image_name();
+    uint64_t size = 20 << 20;
 
-  // 9. beginning of time -> snap2
-  ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  extents.clear();
+    ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order));
+    ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL));
 
-  // 10. snap1 -> snap2
-  ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(1u, extents.size());
-  ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]);
-  extents.clear();
+    bool skip_discard = this->is_skip_partial_discard_enabled(image);
 
-  // 11. snap2 -> snap2
-  ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    uint64_t object_size = 0;
+    if (this->whole_object) {
+      object_size = 1 << order;
+    }
 
-  // 12. snap3 -> snap2
-  ASSERT_EQ(-EINVAL, image.diff_iterate2("snap3", 0, size, true,
-                                         this->whole_object, vector_iterate_cb,
-                                         &extents));
+    interval_set<uint64_t> exists;
+    interval_set<uint64_t> one, two;
+    scribble(image, 10, 102400, skip_discard, &exists, &one);
+    cout << " wrote " << one << std::endl;
+    ASSERT_EQ(0, image.snap_create("one"));
+    scribble(image, 10, 102400, skip_discard, &exists, &two);
 
-  ASSERT_PASSED(this->validate_object_map, image);
-  ASSERT_EQ(0, image.snap_set("snap1"));
+    two = round_diff_interval(two, object_size);
+    cout << " wrote " << two << std::endl;
 
-  // 13. beginning of time -> snap1
-  ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+    interval_set<uint64_t> diff;
+    ASSERT_EQ(0, image.diff_iterate2("one", 0, size, true, this->whole_object,
+                                     iterate_cb, (void *)&diff));
+    cout << " diff was " << diff << std::endl;
+    if (!two.subset_of(diff)) {
+      interval_set<uint64_t> i;
+      i.intersection_of(two, diff);
+      interval_set<uint64_t> l = two;
+      l.subtract(i);
+      cout << " ... two - (two*diff) = " << l << std::endl;
+    }
+    ASSERT_TRUE(two.subset_of(diff));
+  }
+  ioctx.close();
+}
 
-  // 14. snap1 -> snap1
-  ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object,
-                                   vector_iterate_cb, &extents));
-  ASSERT_EQ(0u, extents.size());
+TYPED_TEST(DiffIterateTest, DiffIterateDeterministic)
+{
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
 
-  // 15. snap2 -> snap1
-  ASSERT_EQ(-EINVAL, image.diff_iterate2("snap2", 0, size, true,
-                                         this->whole_object, vector_iterate_cb,
-                                         &extents));
+  this->test_deterministic(0, 256);
+}
 
-  // 16. snap3 -> snap1
-  ASSERT_EQ(-EINVAL, image.diff_iterate2("snap3", 0, size, true,
-                                         this->whole_object, vector_iterate_cb,
-                                         &extents));
+TYPED_TEST(DiffIterateTest, DiffIterateDeterministicPP)
+{
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
 
-  ASSERT_PASSED(this->validate_object_map, image);
+  this->test_deterministic_pp(0, 256);
 }
 
 TYPED_TEST(DiffIterateTest, DiffIterateDiscard)