--- /dev/null
+// 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