| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed 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 <memory> |
| #include <utility> |
| |
| #include "androidfw/CursorWindow.h" |
| |
| #include "TestHelpers.h" |
| |
| // Verify that the memory in use is a multiple of 4 bytes |
| #define ASSERT_ALIGNED(w) \ |
| ASSERT_EQ(((w)->sizeInUse() & 3), 0); \ |
| ASSERT_EQ(((w)->freeSpace() & 3), 0); \ |
| ASSERT_EQ(((w)->sizeOfSlots() & 3), 0) |
| |
| #define CREATE_WINDOW_1K \ |
| CursorWindow* w; \ |
| CursorWindow::create(String8("test"), 1 << 10, &w); \ |
| ASSERT_ALIGNED(w); |
| |
| #define CREATE_WINDOW_1K_3X3 \ |
| CursorWindow* w; \ |
| CursorWindow::create(String8("test"), 1 << 10, &w); \ |
| ASSERT_EQ(w->setNumColumns(3), OK); \ |
| ASSERT_EQ(w->allocRow(), OK); \ |
| ASSERT_EQ(w->allocRow(), OK); \ |
| ASSERT_EQ(w->allocRow(), OK); \ |
| ASSERT_ALIGNED(w); |
| |
| #define CREATE_WINDOW_2M \ |
| CursorWindow* w; \ |
| CursorWindow::create(String8("test"), 1 << 21, &w); \ |
| ASSERT_ALIGNED(w); |
| |
| static constexpr const size_t kHalfInlineSize = 8192; |
| static constexpr const size_t kGiantSize = 1048576; |
| |
| namespace android { |
| |
| TEST(CursorWindowTest, Empty) { |
| CREATE_WINDOW_1K; |
| |
| ASSERT_EQ(w->getNumRows(), 0); |
| ASSERT_EQ(w->getNumColumns(), 0); |
| ASSERT_EQ(w->size(), 1 << 10); |
| ASSERT_EQ(w->freeSpace(), 1 << 10); |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, SetNumColumns) { |
| CREATE_WINDOW_1K; |
| |
| // Once we've locked in columns, we can't adjust |
| ASSERT_EQ(w->getNumColumns(), 0); |
| ASSERT_EQ(w->setNumColumns(4), OK); |
| ASSERT_NE(w->setNumColumns(5), OK); |
| ASSERT_NE(w->setNumColumns(3), OK); |
| ASSERT_EQ(w->getNumColumns(), 4); |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, SetNumColumnsAfterRow) { |
| CREATE_WINDOW_1K; |
| |
| // Once we've locked in a row, we can't adjust columns |
| ASSERT_EQ(w->getNumColumns(), 0); |
| ASSERT_EQ(w->allocRow(), OK); |
| ASSERT_NE(w->setNumColumns(4), OK); |
| ASSERT_EQ(w->getNumColumns(), 0); |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, AllocRow) { |
| CREATE_WINDOW_1K; |
| |
| ASSERT_EQ(w->setNumColumns(4), OK); |
| |
| // Rolling forward means we have less free space |
| ASSERT_EQ(w->getNumRows(), 0); |
| auto before = w->freeSpace(); |
| ASSERT_EQ(w->allocRow(), OK); |
| ASSERT_LT(w->freeSpace(), before); |
| ASSERT_EQ(w->getNumRows(), 1); |
| ASSERT_ALIGNED(w); |
| |
| // Verify we can unwind |
| ASSERT_EQ(w->freeLastRow(), OK); |
| ASSERT_EQ(w->freeSpace(), before); |
| ASSERT_EQ(w->getNumRows(), 0); |
| ASSERT_ALIGNED(w); |
| |
| // Can't unwind when no rows left |
| ASSERT_NE(w->freeLastRow(), OK); |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, AllocRowBounds) { |
| CREATE_WINDOW_1K; |
| |
| // 60 columns is 960 bytes, which means only a single row can fit |
| ASSERT_EQ(w->setNumColumns(60), OK); |
| ASSERT_EQ(w->allocRow(), OK); |
| ASSERT_NE(w->allocRow(), OK); |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, StoreNull) { |
| CREATE_WINDOW_1K_3X3; |
| |
| ASSERT_EQ(w->putNull(1, 1), OK); |
| ASSERT_EQ(w->putNull(0, 0), OK); |
| |
| { |
| auto field = w->getFieldSlot(1, 1); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL); |
| } |
| { |
| auto field = w->getFieldSlot(0, 0); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL); |
| } |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, StoreLong) { |
| CREATE_WINDOW_1K_3X3; |
| |
| ASSERT_EQ(w->putLong(1, 1, 0xf00d), OK); |
| ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); |
| |
| { |
| auto field = w->getFieldSlot(1, 1); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); |
| ASSERT_EQ(w->getFieldSlotValueLong(field), 0xf00d); |
| } |
| { |
| auto field = w->getFieldSlot(0, 0); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); |
| ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); |
| } |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, StoreString) { |
| CREATE_WINDOW_1K_3X3; |
| |
| ASSERT_EQ(w->putString(1, 1, "food", 5), OK); |
| ASSERT_EQ(w->putString(0, 0, "cafe", 5), OK); |
| |
| size_t size; |
| { |
| auto field = w->getFieldSlot(1, 1); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_STRING); |
| auto actual = w->getFieldSlotValueString(field, &size); |
| ASSERT_EQ(std::string(actual), "food"); |
| } |
| { |
| auto field = w->getFieldSlot(0, 0); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_STRING); |
| auto actual = w->getFieldSlotValueString(field, &size); |
| ASSERT_EQ(std::string(actual), "cafe"); |
| } |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, StoreBounds) { |
| CREATE_WINDOW_1K_3X3; |
| |
| // Can't work with values beyond bounds |
| ASSERT_NE(w->putLong(0, 3, 0xcafe), OK); |
| ASSERT_NE(w->putLong(3, 0, 0xcafe), OK); |
| ASSERT_NE(w->putLong(3, 3, 0xcafe), OK); |
| ASSERT_EQ(w->getFieldSlot(0, 3), nullptr); |
| ASSERT_EQ(w->getFieldSlot(3, 0), nullptr); |
| ASSERT_EQ(w->getFieldSlot(3, 3), nullptr); |
| |
| // Can't work with invalid indexes |
| ASSERT_NE(w->putLong(-1, 0, 0xcafe), OK); |
| ASSERT_NE(w->putLong(0, -1, 0xcafe), OK); |
| ASSERT_NE(w->putLong(-1, -1, 0xcafe), OK); |
| ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr); |
| ASSERT_EQ(w->getFieldSlot(0, -1), nullptr); |
| ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr); |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, Inflate) { |
| CREATE_WINDOW_2M; |
| |
| auto before = w->size(); |
| ASSERT_EQ(w->setNumColumns(4), OK); |
| ASSERT_EQ(w->allocRow(), OK); |
| |
| // Scratch buffer that will fit before inflation |
| char buf[kHalfInlineSize]; |
| |
| // Store simple value |
| ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); |
| |
| // Store first object that fits inside |
| memset(buf, 42, kHalfInlineSize); |
| ASSERT_EQ(w->putBlob(0, 1, buf, kHalfInlineSize), OK); |
| ASSERT_EQ(w->size(), before); |
| |
| // Store second simple value |
| ASSERT_EQ(w->putLong(0, 2, 0xface), OK); |
| |
| // Store second object that requires inflation |
| memset(buf, 84, kHalfInlineSize); |
| ASSERT_EQ(w->putBlob(0, 3, buf, kHalfInlineSize), OK); |
| ASSERT_GT(w->size(), before); |
| |
| // Verify data is intact |
| { |
| auto field = w->getFieldSlot(0, 0); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); |
| ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); |
| } |
| { |
| auto field = w->getFieldSlot(0, 1); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); |
| size_t actualSize; |
| auto actual = w->getFieldSlotValueBlob(field, &actualSize); |
| ASSERT_EQ(actualSize, kHalfInlineSize); |
| memset(buf, 42, kHalfInlineSize); |
| ASSERT_NE(actual, buf); |
| ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0); |
| } |
| { |
| auto field = w->getFieldSlot(0, 2); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); |
| ASSERT_EQ(w->getFieldSlotValueLong(field), 0xface); |
| } |
| { |
| auto field = w->getFieldSlot(0, 3); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); |
| size_t actualSize; |
| auto actual = w->getFieldSlotValueBlob(field, &actualSize); |
| ASSERT_EQ(actualSize, kHalfInlineSize); |
| memset(buf, 84, kHalfInlineSize); |
| ASSERT_NE(actual, buf); |
| ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0); |
| } |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, ParcelEmpty) { |
| CREATE_WINDOW_2M; |
| |
| Parcel p; |
| w->writeToParcel(&p); |
| p.setDataPosition(0); |
| w = nullptr; |
| |
| ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK); |
| ASSERT_EQ(w->getNumRows(), 0); |
| ASSERT_EQ(w->getNumColumns(), 0); |
| ASSERT_EQ(w->size(), 0); |
| ASSERT_EQ(w->freeSpace(), 0); |
| ASSERT_ALIGNED(w); |
| |
| // We can't mutate the window after parceling |
| ASSERT_NE(w->setNumColumns(4), OK); |
| ASSERT_NE(w->allocRow(), OK); |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, ParcelSmall) { |
| CREATE_WINDOW_2M; |
| |
| auto before = w->size(); |
| ASSERT_EQ(w->setNumColumns(4), OK); |
| ASSERT_EQ(w->allocRow(), OK); |
| |
| // Scratch buffer that will fit before inflation |
| char buf[kHalfInlineSize]; |
| |
| // Store simple value |
| ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); |
| |
| // Store first object that fits inside |
| memset(buf, 42, kHalfInlineSize); |
| ASSERT_EQ(w->putBlob(0, 1, buf, kHalfInlineSize), OK); |
| ASSERT_EQ(w->size(), before); |
| |
| // Store second object with zero length |
| ASSERT_EQ(w->putBlob(0, 2, buf, 0), OK); |
| ASSERT_EQ(w->size(), before); |
| |
| // Force through a parcel |
| Parcel p; |
| w->writeToParcel(&p); |
| p.setDataPosition(0); |
| w = nullptr; |
| |
| ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK); |
| ASSERT_EQ(w->getNumRows(), 1); |
| ASSERT_EQ(w->getNumColumns(), 4); |
| |
| // Verify data is intact |
| { |
| auto field = w->getFieldSlot(0, 0); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); |
| ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); |
| } |
| { |
| auto field = w->getFieldSlot(0, 1); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); |
| size_t actualSize; |
| auto actual = w->getFieldSlotValueBlob(field, &actualSize); |
| ASSERT_EQ(actualSize, kHalfInlineSize); |
| memset(buf, 42, kHalfInlineSize); |
| ASSERT_NE(actual, buf); |
| ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0); |
| } |
| { |
| auto field = w->getFieldSlot(0, 2); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); |
| size_t actualSize; |
| auto actual = w->getFieldSlotValueBlob(field, &actualSize); |
| ASSERT_EQ(actualSize, 0); |
| ASSERT_NE(actual, nullptr); |
| } |
| ASSERT_ALIGNED(w); |
| } |
| |
| TEST(CursorWindowTest, ParcelLarge) { |
| CREATE_WINDOW_2M; |
| |
| ASSERT_EQ(w->setNumColumns(4), OK); |
| ASSERT_EQ(w->allocRow(), OK); |
| |
| // Store simple value |
| ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); |
| |
| // Store object that forces inflation |
| std::unique_ptr<char> bufPtr(new char[kGiantSize]); |
| void* buf = bufPtr.get(); |
| memset(buf, 42, kGiantSize); |
| ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK); |
| |
| // Store second object with zero length |
| ASSERT_EQ(w->putBlob(0, 2, buf, 0), OK); |
| |
| // Force through a parcel |
| Parcel p; |
| w->writeToParcel(&p); |
| p.setDataPosition(0); |
| w = nullptr; |
| |
| ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK); |
| ASSERT_EQ(w->getNumRows(), 1); |
| ASSERT_EQ(w->getNumColumns(), 4); |
| |
| // Verify data is intact |
| { |
| auto field = w->getFieldSlot(0, 0); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); |
| ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); |
| } |
| { |
| auto field = w->getFieldSlot(0, 1); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); |
| size_t actualSize; |
| auto actual = w->getFieldSlotValueBlob(field, &actualSize); |
| ASSERT_EQ(actualSize, kGiantSize); |
| memset(buf, 42, kGiantSize); |
| ASSERT_EQ(memcmp(buf, actual, kGiantSize), 0); |
| } |
| { |
| auto field = w->getFieldSlot(0, 2); |
| ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); |
| size_t actualSize; |
| auto actual = w->getFieldSlotValueBlob(field, &actualSize); |
| ASSERT_EQ(actualSize, 0); |
| ASSERT_NE(actual, nullptr); |
| } |
| ASSERT_ALIGNED(w); |
| } |
| |
| } // android |