]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/arrow/cpp/src/arrow/buffer_test.cc
import quincy 17.2.0
[ceph.git] / ceph / src / arrow / cpp / src / arrow / buffer_test.cc
diff --git a/ceph/src/arrow/cpp/src/arrow/buffer_test.cc b/ceph/src/arrow/cpp/src/arrow/buffer_test.cc
new file mode 100644 (file)
index 0000000..4295d4c
--- /dev/null
@@ -0,0 +1,926 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "arrow/buffer.h"
+#include "arrow/buffer_builder.h"
+#include "arrow/device.h"
+#include "arrow/io/interfaces.h"
+#include "arrow/memory_pool.h"
+#include "arrow/status.h"
+#include "arrow/testing/gtest_util.h"
+#include "arrow/util/checked_cast.h"
+
+namespace arrow {
+
+using internal::checked_cast;
+using internal::checked_pointer_cast;
+
+static const char kMyDeviceTypeName[] = "arrowtest::MyDevice";
+
+static const int kMyDeviceAllowCopy = 1;
+static const int kMyDeviceAllowView = 2;
+static const int kMyDeviceDisallowCopyView = 3;
+
+class MyDevice : public Device {
+ public:
+  explicit MyDevice(int value) : Device(), value_(value) {}
+
+  const char* type_name() const override { return kMyDeviceTypeName; }
+
+  std::string ToString() const override {
+    switch (value_) {
+      case kMyDeviceAllowCopy:
+        return "MyDevice[noview]";
+      case kMyDeviceAllowView:
+        return "MyDevice[nocopy]";
+      default:
+        return "MyDevice[nocopy][noview]";
+    }
+  }
+
+  bool Equals(const Device& other) const override {
+    if (other.type_name() != kMyDeviceTypeName) {
+      return false;
+    }
+    return checked_cast<const MyDevice&>(other).value_ == value_;
+  }
+
+  std::shared_ptr<MemoryManager> default_memory_manager() override;
+
+  int value() const { return value_; }
+
+  bool allow_copy() const { return value_ == kMyDeviceAllowCopy; }
+
+  bool allow_view() const { return value_ == kMyDeviceAllowView; }
+
+ protected:
+  int value_;
+};
+
+class MyMemoryManager : public MemoryManager {
+ public:
+  explicit MyMemoryManager(std::shared_ptr<Device> device) : MemoryManager(device) {}
+
+  bool allow_copy() const {
+    return checked_cast<const MyDevice&>(*device()).allow_copy();
+  }
+
+  bool allow_view() const {
+    return checked_cast<const MyDevice&>(*device()).allow_view();
+  }
+
+  Result<std::shared_ptr<io::RandomAccessFile>> GetBufferReader(
+      std::shared_ptr<Buffer> buf) override {
+    return Status::NotImplemented("");
+  }
+
+  Result<std::shared_ptr<Buffer>> AllocateBuffer(int64_t size) override {
+    return Status::NotImplemented("");
+  }
+
+  Result<std::shared_ptr<io::OutputStream>> GetBufferWriter(
+      std::shared_ptr<Buffer> buf) override {
+    return Status::NotImplemented("");
+  }
+
+ protected:
+  Result<std::shared_ptr<Buffer>> CopyBufferFrom(
+      const std::shared_ptr<Buffer>& buf,
+      const std::shared_ptr<MemoryManager>& from) override;
+  Result<std::shared_ptr<Buffer>> CopyBufferTo(
+      const std::shared_ptr<Buffer>& buf,
+      const std::shared_ptr<MemoryManager>& to) override;
+  Result<std::shared_ptr<Buffer>> ViewBufferFrom(
+      const std::shared_ptr<Buffer>& buf,
+      const std::shared_ptr<MemoryManager>& from) override;
+  Result<std::shared_ptr<Buffer>> ViewBufferTo(
+      const std::shared_ptr<Buffer>& buf,
+      const std::shared_ptr<MemoryManager>& to) override;
+};
+
+class MyBuffer : public Buffer {
+ public:
+  MyBuffer(std::shared_ptr<MemoryManager> mm, const std::shared_ptr<Buffer>& parent)
+      : Buffer(parent->data(), parent->size()) {
+    parent_ = parent;
+    SetMemoryManager(mm);
+  }
+};
+
+std::shared_ptr<MemoryManager> MyDevice::default_memory_manager() {
+  return std::make_shared<MyMemoryManager>(shared_from_this());
+}
+
+Result<std::shared_ptr<Buffer>> MyMemoryManager::CopyBufferFrom(
+    const std::shared_ptr<Buffer>& buf, const std::shared_ptr<MemoryManager>& from) {
+  if (!allow_copy()) {
+    return nullptr;
+  }
+  if (from->is_cpu()) {
+    // CPU to MyDevice:
+    // 1. CPU to CPU
+    ARROW_ASSIGN_OR_RAISE(auto dest,
+                          MemoryManager::CopyBuffer(buf, default_cpu_memory_manager()));
+    // 2. Wrap CPU buffer result
+    return std::make_shared<MyBuffer>(shared_from_this(), dest);
+  }
+  return nullptr;
+}
+
+Result<std::shared_ptr<Buffer>> MyMemoryManager::CopyBufferTo(
+    const std::shared_ptr<Buffer>& buf, const std::shared_ptr<MemoryManager>& to) {
+  if (!allow_copy()) {
+    return nullptr;
+  }
+  if (to->is_cpu() && buf->parent()) {
+    // MyDevice to CPU
+    return MemoryManager::CopyBuffer(buf->parent(), to);
+  }
+  return nullptr;
+}
+
+Result<std::shared_ptr<Buffer>> MyMemoryManager::ViewBufferFrom(
+    const std::shared_ptr<Buffer>& buf, const std::shared_ptr<MemoryManager>& from) {
+  if (!allow_view()) {
+    return nullptr;
+  }
+  if (from->is_cpu()) {
+    // CPU on MyDevice: wrap CPU buffer
+    return std::make_shared<MyBuffer>(shared_from_this(), buf);
+  }
+  return nullptr;
+}
+
+Result<std::shared_ptr<Buffer>> MyMemoryManager::ViewBufferTo(
+    const std::shared_ptr<Buffer>& buf, const std::shared_ptr<MemoryManager>& to) {
+  if (!allow_view()) {
+    return nullptr;
+  }
+  if (to->is_cpu() && buf->parent()) {
+    // MyDevice on CPU: unwrap buffer
+    return buf->parent();
+  }
+  return nullptr;
+}
+
+// Like AssertBufferEqual, but doesn't call Buffer::data()
+void AssertMyBufferEqual(const Buffer& buffer, util::string_view expected) {
+  ASSERT_EQ(util::string_view(buffer), expected);
+}
+
+void AssertIsCPUBuffer(const Buffer& buf) {
+  ASSERT_TRUE(buf.is_cpu());
+  ASSERT_EQ(*buf.device(), *CPUDevice::Instance());
+}
+
+class TestDevice : public ::testing::Test {
+ public:
+  void SetUp() {
+    cpu_device_ = CPUDevice::Instance();
+    my_copy_device_ = std::make_shared<MyDevice>(kMyDeviceAllowCopy);
+    my_view_device_ = std::make_shared<MyDevice>(kMyDeviceAllowView);
+    my_other_device_ = std::make_shared<MyDevice>(kMyDeviceDisallowCopyView);
+
+    cpu_mm_ = cpu_device_->default_memory_manager();
+    my_copy_mm_ = my_copy_device_->default_memory_manager();
+    my_view_mm_ = my_view_device_->default_memory_manager();
+    my_other_mm_ = my_other_device_->default_memory_manager();
+
+    cpu_src_ = Buffer::FromString("some data");
+    my_copy_src_ = std::make_shared<MyBuffer>(my_copy_mm_, cpu_src_);
+    my_view_src_ = std::make_shared<MyBuffer>(my_view_mm_, cpu_src_);
+    my_other_src_ = std::make_shared<MyBuffer>(my_other_mm_, cpu_src_);
+  }
+
+ protected:
+  std::shared_ptr<Device> cpu_device_, my_copy_device_, my_view_device_, my_other_device_;
+  std::shared_ptr<MemoryManager> cpu_mm_, my_copy_mm_, my_view_mm_, my_other_mm_;
+  std::shared_ptr<Buffer> cpu_src_, my_copy_src_, my_view_src_, my_other_src_;
+};
+
+TEST_F(TestDevice, Basics) {
+  ASSERT_TRUE(cpu_device_->is_cpu());
+
+  ASSERT_EQ(*cpu_device_, *cpu_device_);
+  ASSERT_EQ(*my_copy_device_, *my_copy_device_);
+  ASSERT_NE(*cpu_device_, *my_copy_device_);
+  ASSERT_NE(*my_copy_device_, *cpu_device_);
+
+  ASSERT_TRUE(cpu_mm_->is_cpu());
+  ASSERT_FALSE(my_copy_mm_->is_cpu());
+  ASSERT_FALSE(my_other_mm_->is_cpu());
+}
+
+TEST_F(TestDevice, Copy) {
+  // CPU-to-CPU
+  ASSERT_OK_AND_ASSIGN(auto buffer, MemoryManager::CopyBuffer(cpu_src_, cpu_mm_));
+  ASSERT_EQ(buffer->device(), cpu_device_);
+  ASSERT_TRUE(buffer->is_cpu());
+  ASSERT_NE(buffer->address(), cpu_src_->address());
+  ASSERT_NE(buffer->data(), nullptr);
+  AssertBufferEqual(*buffer, "some data");
+
+  // CPU-to-device
+  ASSERT_OK_AND_ASSIGN(buffer, MemoryManager::CopyBuffer(cpu_src_, my_copy_mm_));
+  ASSERT_EQ(buffer->device(), my_copy_device_);
+  ASSERT_FALSE(buffer->is_cpu());
+  ASSERT_NE(buffer->address(), cpu_src_->address());
+#ifdef NDEBUG
+  ASSERT_EQ(buffer->data(), nullptr);
+#endif
+  AssertMyBufferEqual(*buffer, "some data");
+
+  // Device-to-CPU
+  ASSERT_OK_AND_ASSIGN(buffer, MemoryManager::CopyBuffer(my_copy_src_, cpu_mm_));
+  ASSERT_EQ(buffer->device(), cpu_device_);
+  ASSERT_TRUE(buffer->is_cpu());
+  ASSERT_NE(buffer->address(), my_copy_src_->address());
+  ASSERT_NE(buffer->data(), nullptr);
+  AssertBufferEqual(*buffer, "some data");
+
+  // Device-to-device with an intermediate CPU copy
+  ASSERT_OK_AND_ASSIGN(buffer, MemoryManager::CopyBuffer(my_copy_src_, my_copy_mm_));
+  ASSERT_EQ(buffer->device(), my_copy_device_);
+  ASSERT_FALSE(buffer->is_cpu());
+  ASSERT_NE(buffer->address(), my_copy_src_->address());
+#ifdef NDEBUG
+  ASSERT_EQ(buffer->data(), nullptr);
+#endif
+  AssertMyBufferEqual(*buffer, "some data");
+
+  // Device-to-device with an intermediate view on CPU, then a copy from CPU to device
+  ASSERT_OK_AND_ASSIGN(buffer, MemoryManager::CopyBuffer(my_view_src_, my_copy_mm_));
+  ASSERT_EQ(buffer->device(), my_copy_device_);
+  ASSERT_FALSE(buffer->is_cpu());
+  ASSERT_NE(buffer->address(), my_copy_src_->address());
+#ifdef NDEBUG
+  ASSERT_EQ(buffer->data(), nullptr);
+#endif
+  AssertMyBufferEqual(*buffer, "some data");
+
+  ASSERT_RAISES(NotImplemented, MemoryManager::CopyBuffer(cpu_src_, my_other_mm_));
+  ASSERT_RAISES(NotImplemented, MemoryManager::CopyBuffer(my_other_src_, cpu_mm_));
+}
+
+TEST_F(TestDevice, View) {
+  // CPU-on-CPU
+  ASSERT_OK_AND_ASSIGN(auto buffer, MemoryManager::ViewBuffer(cpu_src_, cpu_mm_));
+  ASSERT_EQ(buffer->device(), cpu_device_);
+  ASSERT_TRUE(buffer->is_cpu());
+  ASSERT_EQ(buffer->address(), cpu_src_->address());
+  ASSERT_NE(buffer->data(), nullptr);
+  AssertBufferEqual(*buffer, "some data");
+
+  // CPU-on-device
+  ASSERT_OK_AND_ASSIGN(buffer, MemoryManager::ViewBuffer(cpu_src_, my_view_mm_));
+  ASSERT_EQ(buffer->device(), my_view_device_);
+  ASSERT_FALSE(buffer->is_cpu());
+  ASSERT_EQ(buffer->address(), cpu_src_->address());
+#ifdef NDEBUG
+  ASSERT_EQ(buffer->data(), nullptr);
+#endif
+  AssertMyBufferEqual(*buffer, "some data");
+
+  // Device-on-CPU
+  ASSERT_OK_AND_ASSIGN(buffer, MemoryManager::ViewBuffer(my_view_src_, cpu_mm_));
+  ASSERT_EQ(buffer->device(), cpu_device_);
+  ASSERT_TRUE(buffer->is_cpu());
+  ASSERT_EQ(buffer->address(), my_copy_src_->address());
+  ASSERT_NE(buffer->data(), nullptr);
+  AssertBufferEqual(*buffer, "some data");
+
+  ASSERT_RAISES(NotImplemented, MemoryManager::CopyBuffer(cpu_src_, my_other_mm_));
+  ASSERT_RAISES(NotImplemented, MemoryManager::CopyBuffer(my_other_src_, cpu_mm_));
+}
+
+TEST(TestAllocate, Basics) {
+  ASSERT_OK_AND_ASSIGN(auto new_buffer, AllocateBuffer(1024));
+  auto mm = new_buffer->memory_manager();
+  ASSERT_TRUE(mm->is_cpu());
+  ASSERT_EQ(mm.get(), default_cpu_memory_manager().get());
+  auto cpu_mm = checked_pointer_cast<CPUMemoryManager>(mm);
+  ASSERT_EQ(cpu_mm->pool(), default_memory_pool());
+
+  auto pool = std::make_shared<ProxyMemoryPool>(default_memory_pool());
+  ASSERT_OK_AND_ASSIGN(new_buffer, AllocateBuffer(1024, pool.get()));
+  mm = new_buffer->memory_manager();
+  ASSERT_TRUE(mm->is_cpu());
+  cpu_mm = checked_pointer_cast<CPUMemoryManager>(mm);
+  ASSERT_EQ(cpu_mm->pool(), pool.get());
+  new_buffer.reset();  // Destroy before pool
+}
+
+TEST(TestAllocate, Bitmap) {
+  ASSERT_OK_AND_ASSIGN(auto new_buffer, AllocateBitmap(100));
+  AssertIsCPUBuffer(*new_buffer);
+  EXPECT_GE(new_buffer->size(), 13);
+  EXPECT_EQ(new_buffer->capacity() % 8, 0);
+}
+
+TEST(TestAllocate, EmptyBitmap) {
+  ASSERT_OK_AND_ASSIGN(auto new_buffer, AllocateEmptyBitmap(100));
+  AssertIsCPUBuffer(*new_buffer);
+  EXPECT_EQ(new_buffer->size(), 13);
+  EXPECT_EQ(new_buffer->capacity() % 8, 0);
+  EXPECT_TRUE(std::all_of(new_buffer->data(), new_buffer->data() + new_buffer->capacity(),
+                          [](int8_t byte) { return byte == 0; }));
+}
+
+TEST(TestBuffer, FromStdString) {
+  std::string val = "hello, world";
+
+  Buffer buf(val);
+  AssertIsCPUBuffer(buf);
+  ASSERT_EQ(0, memcmp(buf.data(), val.c_str(), val.size()));
+  ASSERT_EQ(static_cast<int64_t>(val.size()), buf.size());
+}
+
+TEST(TestBuffer, FromStdStringWithMemory) {
+  std::string expected = "hello, world";
+  std::shared_ptr<Buffer> buf;
+
+  {
+    std::string temp = "hello, world";
+    buf = Buffer::FromString(temp);
+    AssertIsCPUBuffer(*buf);
+    ASSERT_EQ(0, memcmp(buf->data(), temp.c_str(), temp.size()));
+    ASSERT_EQ(static_cast<int64_t>(temp.size()), buf->size());
+  }
+
+  // Now temp goes out of scope and we check if created buffer
+  // is still valid to make sure it actually owns its space
+  ASSERT_EQ(0, memcmp(buf->data(), expected.c_str(), expected.size()));
+  ASSERT_EQ(static_cast<int64_t>(expected.size()), buf->size());
+}
+
+TEST(TestBuffer, EqualsWithSameContent) {
+  MemoryPool* pool = default_memory_pool();
+  const int32_t bufferSize = 128 * 1024;
+  uint8_t* rawBuffer1;
+  ASSERT_OK(pool->Allocate(bufferSize, &rawBuffer1));
+  memset(rawBuffer1, 12, bufferSize);
+  uint8_t* rawBuffer2;
+  ASSERT_OK(pool->Allocate(bufferSize, &rawBuffer2));
+  memset(rawBuffer2, 12, bufferSize);
+  uint8_t* rawBuffer3;
+  ASSERT_OK(pool->Allocate(bufferSize, &rawBuffer3));
+  memset(rawBuffer3, 3, bufferSize);
+
+  Buffer buffer1(rawBuffer1, bufferSize);
+  Buffer buffer2(rawBuffer2, bufferSize);
+  Buffer buffer3(rawBuffer3, bufferSize);
+  ASSERT_TRUE(buffer1.Equals(buffer2));
+  ASSERT_FALSE(buffer1.Equals(buffer3));
+
+  pool->Free(rawBuffer1, bufferSize);
+  pool->Free(rawBuffer2, bufferSize);
+  pool->Free(rawBuffer3, bufferSize);
+}
+
+TEST(TestBuffer, EqualsWithSameBuffer) {
+  MemoryPool* pool = default_memory_pool();
+  const int32_t bufferSize = 128 * 1024;
+  uint8_t* rawBuffer;
+  ASSERT_OK(pool->Allocate(bufferSize, &rawBuffer));
+  memset(rawBuffer, 111, bufferSize);
+
+  Buffer buffer1(rawBuffer, bufferSize);
+  Buffer buffer2(rawBuffer, bufferSize);
+  ASSERT_TRUE(buffer1.Equals(buffer2));
+
+  const int64_t nbytes = bufferSize / 2;
+  Buffer buffer3(rawBuffer, nbytes);
+  ASSERT_TRUE(buffer1.Equals(buffer3, nbytes));
+  ASSERT_FALSE(buffer1.Equals(buffer3, nbytes + 1));
+
+  pool->Free(rawBuffer, bufferSize);
+}
+
+TEST(TestBuffer, CopySlice) {
+  std::string data_str = "some data to copy";
+
+  auto data = reinterpret_cast<const uint8_t*>(data_str.c_str());
+
+  Buffer buf(data, data_str.size());
+
+  ASSERT_OK_AND_ASSIGN(auto out, buf.CopySlice(5, 4));
+  AssertIsCPUBuffer(*out);
+
+  Buffer expected(data + 5, 4);
+  ASSERT_TRUE(out->Equals(expected));
+  // assert the padding is zeroed
+  std::vector<uint8_t> zeros(out->capacity() - out->size());
+  ASSERT_EQ(0, memcmp(out->data() + out->size(), zeros.data(), zeros.size()));
+}
+
+TEST(TestBuffer, CopySliceEmpty) {
+  auto buf = std::make_shared<Buffer>("");
+  ASSERT_OK_AND_ASSIGN(auto out, buf->CopySlice(0, 0));
+  AssertBufferEqual(*out, "");
+
+  buf = std::make_shared<Buffer>("1234");
+  ASSERT_OK_AND_ASSIGN(out, buf->CopySlice(0, 0));
+  AssertBufferEqual(*out, "");
+  ASSERT_OK_AND_ASSIGN(out, buf->CopySlice(4, 0));
+  AssertBufferEqual(*out, "");
+}
+
+TEST(TestBuffer, ToHexString) {
+  const uint8_t data_array[] = "\a0hex string\xa9";
+  std::basic_string<uint8_t> data_str = data_array;
+
+  auto data = reinterpret_cast<const uint8_t*>(data_str.c_str());
+
+  Buffer buf(data, data_str.size());
+
+  ASSERT_EQ(buf.ToHexString(), std::string("073068657820737472696E67A9"));
+}
+
+TEST(TestBuffer, SliceBuffer) {
+  std::string data_str = "some data to slice";
+  auto data = reinterpret_cast<const uint8_t*>(data_str.c_str());
+
+  auto buf = std::make_shared<Buffer>(data, data_str.size());
+
+  std::shared_ptr<Buffer> out = SliceBuffer(buf, 5, 4);
+  AssertIsCPUBuffer(*out);
+  Buffer expected(data + 5, 4);
+  ASSERT_TRUE(out->Equals(expected));
+
+  ASSERT_EQ(2, buf.use_count());
+}
+
+TEST(TestBuffer, SliceBufferSafe) {
+  std::string data_str = "some data to slice";
+  auto data = reinterpret_cast<const uint8_t*>(data_str.c_str());
+
+  auto buf = std::make_shared<Buffer>(data, data_str.size());
+
+  ASSERT_OK_AND_ASSIGN(auto sliced, SliceBufferSafe(buf, 5, 4));
+  AssertBufferEqual(*sliced, "data");
+  ASSERT_OK_AND_ASSIGN(sliced, SliceBufferSafe(buf, 0, 4));
+  AssertBufferEqual(*sliced, "some");
+  ASSERT_OK_AND_ASSIGN(sliced, SliceBufferSafe(buf, 0, 0));
+  AssertBufferEqual(*sliced, "");
+  ASSERT_OK_AND_ASSIGN(sliced, SliceBufferSafe(buf, 4, 0));
+  AssertBufferEqual(*sliced, "");
+  ASSERT_OK_AND_ASSIGN(sliced, SliceBufferSafe(buf, buf->size(), 0));
+  AssertBufferEqual(*sliced, "");
+
+  ASSERT_RAISES(Invalid, SliceBufferSafe(buf, -1, 0));
+  ASSERT_RAISES(Invalid, SliceBufferSafe(buf, 0, -1));
+  ASSERT_RAISES(Invalid, SliceBufferSafe(buf, 0, buf->size() + 1));
+  ASSERT_RAISES(Invalid, SliceBufferSafe(buf, 2, buf->size() - 1));
+  ASSERT_RAISES(Invalid, SliceBufferSafe(buf, buf->size() + 1, 0));
+  ASSERT_RAISES(Invalid,
+                SliceBufferSafe(buf, 3, std::numeric_limits<int64_t>::max() - 2));
+
+  ASSERT_OK_AND_ASSIGN(sliced, SliceBufferSafe(buf, 0));
+  AssertBufferEqual(*sliced, "some data to slice");
+  ASSERT_OK_AND_ASSIGN(sliced, SliceBufferSafe(buf, 5));
+  AssertBufferEqual(*sliced, "data to slice");
+  ASSERT_OK_AND_ASSIGN(sliced, SliceBufferSafe(buf, buf->size()));
+  AssertBufferEqual(*sliced, "");
+
+  ASSERT_RAISES(Invalid, SliceBufferSafe(buf, -1));
+  ASSERT_RAISES(Invalid, SliceBufferSafe(buf, buf->size() + 1));
+}
+
+TEST(TestMutableBuffer, Wrap) {
+  std::vector<int32_t> values = {1, 2, 3};
+
+  auto buf = MutableBuffer::Wrap(values.data(), values.size());
+  AssertIsCPUBuffer(*buf);
+  reinterpret_cast<int32_t*>(buf->mutable_data())[1] = 4;
+
+  ASSERT_EQ(4, values[1]);
+}
+
+TEST(TestBuffer, FromStringRvalue) {
+  std::string expected = "input data";
+
+  std::shared_ptr<Buffer> buffer;
+  {
+    std::string data_str = "input data";
+    buffer = Buffer::FromString(std::move(data_str));
+    AssertIsCPUBuffer(*buffer);
+  }
+
+  ASSERT_FALSE(buffer->is_mutable());
+
+  ASSERT_EQ(0, memcmp(buffer->data(), expected.c_str(), expected.size()));
+  ASSERT_EQ(static_cast<int64_t>(expected.size()), buffer->size());
+}
+
+TEST(TestBuffer, SliceMutableBuffer) {
+  std::string data_str = "some data to slice";
+  auto data = reinterpret_cast<const uint8_t*>(data_str.c_str());
+
+  ASSERT_OK_AND_ASSIGN(std::shared_ptr<Buffer> buffer, AllocateBuffer(50));
+
+  memcpy(buffer->mutable_data(), data, data_str.size());
+
+  std::shared_ptr<Buffer> slice = SliceMutableBuffer(buffer, 5, 10);
+  AssertIsCPUBuffer(*slice);
+  ASSERT_TRUE(slice->is_mutable());
+  ASSERT_EQ(10, slice->size());
+
+  Buffer expected(data + 5, 10);
+  ASSERT_TRUE(slice->Equals(expected));
+}
+
+TEST(TestBuffer, GetReader) {
+  const std::string data_str = "some data to read";
+  auto data = reinterpret_cast<const uint8_t*>(data_str.c_str());
+
+  auto buf = std::make_shared<Buffer>(data, data_str.size());
+  ASSERT_OK_AND_ASSIGN(auto reader, Buffer::GetReader(buf));
+  ASSERT_OK_AND_EQ(static_cast<int64_t>(data_str.size()), reader->GetSize());
+  ASSERT_OK_AND_ASSIGN(auto read_buf, reader->ReadAt(5, 4));
+  AssertBufferEqual(*read_buf, "data");
+}
+
+TEST(TestBuffer, GetWriter) {
+  ASSERT_OK_AND_ASSIGN(std::shared_ptr<Buffer> buf, AllocateBuffer(9));
+  ASSERT_OK_AND_ASSIGN(auto writer, Buffer::GetWriter(buf));
+  ASSERT_OK(writer->Write(reinterpret_cast<const uint8_t*>("some data"), 9));
+  AssertBufferEqual(*buf, "some data");
+
+  // Non-mutable buffer
+  buf = std::make_shared<Buffer>(reinterpret_cast<const uint8_t*>("xxx"), 3);
+  ASSERT_RAISES(Invalid, Buffer::GetWriter(buf));
+}
+
+template <typename AllocateFunction>
+void TestZeroSizeAllocateBuffer(MemoryPool* pool, AllocateFunction&& allocate_func) {
+  auto allocated_bytes = pool->bytes_allocated();
+  {
+    std::shared_ptr<Buffer> buffer, buffer2;
+
+    ASSERT_OK(allocate_func(pool, 0, &buffer));
+    AssertIsCPUBuffer(*buffer);
+    ASSERT_EQ(buffer->size(), 0);
+    // Even 0-sized buffers should not have a null data pointer
+    auto data = buffer->data();
+    ASSERT_NE(data, nullptr);
+    ASSERT_EQ(buffer->mutable_data(), data);
+
+    // As an optimization, another 0-size buffer should share the same memory "area"
+    ASSERT_OK(allocate_func(pool, 0, &buffer2));
+    AssertIsCPUBuffer(*buffer2);
+    ASSERT_EQ(buffer2->size(), 0);
+    ASSERT_EQ(buffer2->data(), data);
+
+    ASSERT_GE(pool->bytes_allocated(), allocated_bytes);
+  }
+  ASSERT_EQ(pool->bytes_allocated(), allocated_bytes);
+}
+
+TEST(TestAllocateBuffer, ZeroSize) {
+  MemoryPool* pool = default_memory_pool();
+  auto allocate_func = [](MemoryPool* pool, int64_t size, std::shared_ptr<Buffer>* out) {
+    return AllocateBuffer(size, pool).Value(out);
+  };
+  TestZeroSizeAllocateBuffer(pool, allocate_func);
+}
+
+TEST(TestAllocateResizableBuffer, ZeroSize) {
+  MemoryPool* pool = default_memory_pool();
+  auto allocate_func = [](MemoryPool* pool, int64_t size, std::shared_ptr<Buffer>* out) {
+    ARROW_ASSIGN_OR_RAISE(auto resizable, AllocateResizableBuffer(size, pool));
+    *out = std::move(resizable);
+    return Status::OK();
+  };
+  TestZeroSizeAllocateBuffer(pool, allocate_func);
+}
+
+TEST(TestAllocateResizableBuffer, ZeroResize) {
+  MemoryPool* pool = default_memory_pool();
+  auto allocated_bytes = pool->bytes_allocated();
+  {
+    std::shared_ptr<ResizableBuffer> buffer;
+
+    ASSERT_OK_AND_ASSIGN(buffer, AllocateResizableBuffer(1000, pool));
+    ASSERT_EQ(buffer->size(), 1000);
+    ASSERT_NE(buffer->data(), nullptr);
+    ASSERT_EQ(buffer->mutable_data(), buffer->data());
+
+    ASSERT_GE(pool->bytes_allocated(), allocated_bytes + 1000);
+
+    ASSERT_OK(buffer->Resize(0));
+    ASSERT_NE(buffer->data(), nullptr);
+    ASSERT_EQ(buffer->mutable_data(), buffer->data());
+
+    ASSERT_GE(pool->bytes_allocated(), allocated_bytes);
+    ASSERT_LT(pool->bytes_allocated(), allocated_bytes + 1000);
+  }
+  ASSERT_EQ(pool->bytes_allocated(), allocated_bytes);
+}
+
+TEST(TestBufferBuilder, ResizeReserve) {
+  const std::string data = "some data";
+  auto data_ptr = data.c_str();
+
+  BufferBuilder builder;
+
+  ASSERT_OK(builder.Append(data_ptr, 9));
+  ASSERT_EQ(9, builder.length());
+
+  ASSERT_OK(builder.Resize(128));
+  ASSERT_EQ(128, builder.capacity());
+  ASSERT_EQ(9, builder.length());
+
+  // Do not shrink to fit
+  ASSERT_OK(builder.Resize(64, false));
+  ASSERT_EQ(128, builder.capacity());
+  ASSERT_EQ(9, builder.length());
+
+  // Shrink to fit
+  ASSERT_OK(builder.Resize(64));
+  ASSERT_EQ(64, builder.capacity());
+  ASSERT_EQ(9, builder.length());
+
+  // Reserve elements
+  ASSERT_OK(builder.Reserve(60));
+  ASSERT_EQ(128, builder.capacity());
+  ASSERT_EQ(9, builder.length());
+}
+
+TEST(TestBufferBuilder, Finish) {
+  const std::string data = "some data";
+  auto data_ptr = data.c_str();
+
+  for (const bool shrink_to_fit : {true, false}) {
+    ARROW_SCOPED_TRACE("shrink_to_fit = ", shrink_to_fit);
+    BufferBuilder builder;
+    ASSERT_OK(builder.Append(data_ptr, 9));
+    ASSERT_OK(builder.Append(data_ptr, 9));
+    ASSERT_EQ(18, builder.length());
+    ASSERT_EQ(64, builder.capacity());
+
+    ASSERT_OK_AND_ASSIGN(auto buf, builder.Finish(shrink_to_fit));
+    ASSERT_EQ(buf->size(), 18);
+    ASSERT_EQ(buf->capacity(), 64);
+  }
+  for (const bool shrink_to_fit : {true, false}) {
+    ARROW_SCOPED_TRACE("shrink_to_fit = ", shrink_to_fit);
+    BufferBuilder builder;
+    ASSERT_OK(builder.Reserve(1024));
+    builder.UnsafeAppend(data_ptr, 9);
+    builder.UnsafeAppend(data_ptr, 9);
+    ASSERT_EQ(18, builder.length());
+    ASSERT_EQ(builder.capacity(), 1024);
+
+    ASSERT_OK_AND_ASSIGN(auto buf, builder.Finish(shrink_to_fit));
+    ASSERT_EQ(buf->size(), 18);
+    ASSERT_EQ(buf->capacity(), shrink_to_fit ? 64 : 1024);
+  }
+}
+
+TEST(TestBufferBuilder, FinishEmpty) {
+  for (const bool shrink_to_fit : {true, false}) {
+    ARROW_SCOPED_TRACE("shrink_to_fit = ", shrink_to_fit);
+    BufferBuilder builder;
+    ASSERT_EQ(0, builder.length());
+    ASSERT_EQ(0, builder.capacity());
+
+    ASSERT_OK_AND_ASSIGN(auto buf, builder.Finish(shrink_to_fit));
+    ASSERT_EQ(buf->size(), 0);
+    ASSERT_EQ(buf->capacity(), 0);
+  }
+  for (const bool shrink_to_fit : {true, false}) {
+    ARROW_SCOPED_TRACE("shrink_to_fit = ", shrink_to_fit);
+    BufferBuilder builder;
+    ASSERT_OK(builder.Reserve(1024));
+    ASSERT_EQ(0, builder.length());
+    ASSERT_EQ(1024, builder.capacity());
+
+    ASSERT_OK_AND_ASSIGN(auto buf, builder.Finish(shrink_to_fit));
+    ASSERT_EQ(buf->size(), 0);
+    ASSERT_EQ(buf->capacity(), shrink_to_fit ? 0 : 1024);
+  }
+}
+
+template <typename T>
+class TypedTestBufferBuilder : public ::testing::Test {};
+
+using BufferBuilderElements = ::testing::Types<int16_t, uint32_t, double>;
+
+TYPED_TEST_SUITE(TypedTestBufferBuilder, BufferBuilderElements);
+
+TYPED_TEST(TypedTestBufferBuilder, BasicTypedBufferBuilderUsage) {
+  TypedBufferBuilder<TypeParam> builder;
+
+  ASSERT_OK(builder.Append(static_cast<TypeParam>(0)));
+  ASSERT_EQ(builder.length(), 1);
+  ASSERT_EQ(builder.capacity(), 64 / sizeof(TypeParam));
+
+  constexpr int nvalues = 4;
+  TypeParam values[nvalues];
+  for (int i = 0; i != nvalues; ++i) {
+    values[i] = static_cast<TypeParam>(i);
+  }
+  ASSERT_OK(builder.Append(values, nvalues));
+  ASSERT_EQ(builder.length(), nvalues + 1);
+
+  std::shared_ptr<Buffer> built;
+  ASSERT_OK(builder.Finish(&built));
+  AssertIsCPUBuffer(*built);
+
+  auto data = reinterpret_cast<const TypeParam*>(built->data());
+  ASSERT_EQ(data[0], static_cast<TypeParam>(0));
+  for (auto value : values) {
+    ++data;
+    ASSERT_EQ(*data, value);
+  }
+}
+
+TYPED_TEST(TypedTestBufferBuilder, AppendCopies) {
+  TypedBufferBuilder<TypeParam> builder;
+
+  ASSERT_OK(builder.Append(13, static_cast<TypeParam>(1)));
+  ASSERT_OK(builder.Append(17, static_cast<TypeParam>(0)));
+  ASSERT_EQ(builder.length(), 13 + 17);
+
+  std::shared_ptr<Buffer> built;
+  ASSERT_OK(builder.Finish(&built));
+
+  auto data = reinterpret_cast<const TypeParam*>(built->data());
+  for (int i = 0; i != 13 + 17; ++i, ++data) {
+    ASSERT_EQ(*data, static_cast<TypeParam>(i < 13)) << "index = " << i;
+  }
+}
+
+TEST(TestBoolBufferBuilder, Basics) {
+  TypedBufferBuilder<bool> builder;
+
+  ASSERT_OK(builder.Append(false));
+  ASSERT_EQ(builder.length(), 1);
+  ASSERT_EQ(builder.capacity(), 64 * 8);
+
+  constexpr int nvalues = 4;
+  uint8_t values[nvalues];
+  for (int i = 0; i != nvalues; ++i) {
+    values[i] = static_cast<uint8_t>(i);
+  }
+  ASSERT_OK(builder.Append(values, nvalues));
+  ASSERT_EQ(builder.length(), nvalues + 1);
+
+  ASSERT_EQ(builder.false_count(), 2);
+
+  std::shared_ptr<Buffer> built;
+  ASSERT_OK(builder.Finish(&built));
+  AssertIsCPUBuffer(*built);
+
+  ASSERT_EQ(BitUtil::GetBit(built->data(), 0), false);
+  for (int i = 0; i != nvalues; ++i) {
+    ASSERT_EQ(BitUtil::GetBit(built->data(), i + 1), static_cast<bool>(values[i]));
+  }
+
+  ASSERT_EQ(built->size(), BitUtil::BytesForBits(nvalues + 1));
+}
+
+TEST(TestBoolBufferBuilder, AppendCopies) {
+  TypedBufferBuilder<bool> builder;
+
+  ASSERT_OK(builder.Append(13, true));
+  ASSERT_OK(builder.Append(17, false));
+  ASSERT_EQ(builder.length(), 13 + 17);
+  ASSERT_EQ(builder.capacity(), 64 * 8);
+  ASSERT_EQ(builder.false_count(), 17);
+
+  std::shared_ptr<Buffer> built;
+  ASSERT_OK(builder.Finish(&built));
+  AssertIsCPUBuffer(*built);
+
+  for (int i = 0; i != 13 + 17; ++i) {
+    EXPECT_EQ(BitUtil::GetBit(built->data(), i), i < 13) << "index = " << i;
+  }
+
+  ASSERT_EQ(built->size(), BitUtil::BytesForBits(13 + 17));
+}
+
+TEST(TestBoolBufferBuilder, Reserve) {
+  TypedBufferBuilder<bool> builder;
+
+  ASSERT_OK(builder.Reserve(13 + 17));
+  builder.UnsafeAppend(13, true);
+  builder.UnsafeAppend(17, false);
+  ASSERT_EQ(builder.length(), 13 + 17);
+  ASSERT_EQ(builder.capacity(), 64 * 8);
+  ASSERT_EQ(builder.false_count(), 17);
+
+  ASSERT_OK_AND_ASSIGN(auto built, builder.Finish());
+  AssertIsCPUBuffer(*built);
+  ASSERT_EQ(built->size(), BitUtil::BytesForBits(13 + 17));
+}
+
+template <typename T>
+class TypedTestBuffer : public ::testing::Test {};
+
+using BufferPtrs =
+    ::testing::Types<std::shared_ptr<ResizableBuffer>, std::unique_ptr<ResizableBuffer>>;
+
+TYPED_TEST_SUITE(TypedTestBuffer, BufferPtrs);
+
+TYPED_TEST(TypedTestBuffer, IsMutableFlag) {
+  Buffer buf(nullptr, 0);
+
+  ASSERT_FALSE(buf.is_mutable());
+
+  MutableBuffer mbuf(nullptr, 0);
+  ASSERT_TRUE(mbuf.is_mutable());
+  AssertIsCPUBuffer(mbuf);
+
+  TypeParam pool_buf;
+  ASSERT_OK_AND_ASSIGN(pool_buf, AllocateResizableBuffer(0));
+  ASSERT_TRUE(pool_buf->is_mutable());
+  AssertIsCPUBuffer(*pool_buf);
+}
+
+TYPED_TEST(TypedTestBuffer, Resize) {
+  TypeParam buf;
+  ASSERT_OK_AND_ASSIGN(buf, AllocateResizableBuffer(0));
+  AssertIsCPUBuffer(*buf);
+
+  ASSERT_EQ(0, buf->size());
+  ASSERT_OK(buf->Resize(100));
+  ASSERT_EQ(100, buf->size());
+  ASSERT_OK(buf->Resize(200));
+  ASSERT_EQ(200, buf->size());
+
+  // Make it smaller, too
+  ASSERT_OK(buf->Resize(50, true));
+  ASSERT_EQ(50, buf->size());
+  // We have actually shrunken in size
+  // The spec requires that capacity is a multiple of 64
+  ASSERT_EQ(64, buf->capacity());
+
+  // Resize to a larger capacity again to test shrink_to_fit = false
+  ASSERT_OK(buf->Resize(100));
+  ASSERT_EQ(128, buf->capacity());
+  ASSERT_OK(buf->Resize(50, false));
+  ASSERT_EQ(128, buf->capacity());
+}
+
+TYPED_TEST(TypedTestBuffer, TypedResize) {
+  TypeParam buf;
+  ASSERT_OK_AND_ASSIGN(buf, AllocateResizableBuffer(0));
+
+  ASSERT_EQ(0, buf->size());
+  ASSERT_OK(buf->template TypedResize<double>(100));
+  ASSERT_EQ(800, buf->size());
+  ASSERT_OK(buf->template TypedResize<double>(200));
+  ASSERT_EQ(1600, buf->size());
+
+  ASSERT_OK(buf->template TypedResize<double>(50, true));
+  ASSERT_EQ(400, buf->size());
+  ASSERT_EQ(448, buf->capacity());
+
+  ASSERT_OK(buf->template TypedResize<double>(100));
+  ASSERT_EQ(832, buf->capacity());
+  ASSERT_OK(buf->template TypedResize<double>(50, false));
+  ASSERT_EQ(832, buf->capacity());
+}
+
+TYPED_TEST(TypedTestBuffer, ResizeOOM) {
+// This test doesn't play nice with AddressSanitizer
+#ifndef ADDRESS_SANITIZER
+  // realloc fails, even though there may be no explicit limit
+  TypeParam buf;
+  ASSERT_OK_AND_ASSIGN(buf, AllocateResizableBuffer(0));
+  ASSERT_OK(buf->Resize(100));
+  int64_t to_alloc = std::min<uint64_t>(std::numeric_limits<int64_t>::max(),
+                                        std::numeric_limits<size_t>::max());
+  // subtract 63 to prevent overflow after the size is aligned
+  to_alloc -= 63;
+  ASSERT_RAISES(OutOfMemory, buf->Resize(to_alloc));
+#endif
+}
+
+}  // namespace arrow