blob: 72655ae38e62e16871be3a78749f848b7bee431d [file]
// Copyright (C) 2024 Google LLC
//
// 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 <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <random>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "icing/document-builder.h"
#include "icing/file/filesystem.h"
#include "icing/icing-search-engine.h"
#include "icing/jni/jni-cache.h"
#include "icing/legacy/index/icing-filesystem.h"
#include "icing/portable/equals-proto.h"
#include "icing/proto/storage.pb.h"
#include "icing/schema-builder.h"
#include "icing/testing/common-matchers.h"
#include "icing/testing/fake-clock.h"
#include "icing/testing/jni-test-helpers.h"
#include "icing/testing/tmp-directory.h"
#include "icing/util/clock.h"
#include "icing/util/sha256.h"
namespace icing {
namespace lib {
static constexpr int64_t kBlobInfoTTLMs = 7 * 24 * 60 * 60 * 1000; // 1 Week
namespace {
using ::icing::lib::portable_equals_proto::EqualsProto;
using ::testing::IsEmpty;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
// For mocking purpose, we allow tests to provide a custom Filesystem.
class TestIcingSearchEngine : public IcingSearchEngine {
public:
TestIcingSearchEngine(const IcingSearchEngineOptions& options,
std::unique_ptr<const Filesystem> filesystem,
std::unique_ptr<const IcingFilesystem> icing_filesystem,
std::unique_ptr<Clock> clock,
std::unique_ptr<JniCache> jni_cache)
: IcingSearchEngine(options, std::move(filesystem),
std::move(icing_filesystem), std::move(clock),
std::move(jni_cache)) {}
};
std::string GetTestBaseDir() { return GetTestTempDir() + "/icing"; }
std::string GetTestBlobDir() { return GetTestTempDir() + "/icing/blob_dir"; }
std::string GetTestBlobFileDir() { return GetTestBlobDir() + "/blob_files"; }
// This test is meant to cover all tests relating to IcingSearchEngine::Delete*.
class IcingSearchEngineBlobTest : public testing::Test {
protected:
void SetUp() override {
filesystem_.DeleteDirectoryRecursively(GetTestBaseDir().c_str());
filesystem_.CreateDirectoryRecursively(GetTestBaseDir().c_str());
}
void TearDown() override {
filesystem_.DeleteDirectoryRecursively(GetTestBaseDir().c_str());
}
const Filesystem* filesystem() const { return &filesystem_; }
private:
Filesystem filesystem_;
};
// Non-zero value so we don't override it to be the current time
constexpr int64_t kDefaultCreationTimestampMs = 1575492852000;
IcingSearchEngineOptions GetDefaultIcingOptions() {
IcingSearchEngineOptions icing_options;
icing_options.set_base_dir(GetTestBaseDir());
icing_options.set_enable_blob_store(true);
icing_options.set_orphan_blob_time_to_live_ms(kBlobInfoTTLMs);
return icing_options;
}
std::vector<unsigned char> GenerateRandomBytes(size_t length) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<unsigned char> distribution(0, 255);
std::vector<unsigned char> random_bytes(length);
for (size_t i = 0; i < length; ++i) {
random_bytes[i] = distribution(gen);
}
return random_bytes;
}
std::array<uint8_t, 32> CalculateDigest(
const std::vector<unsigned char>& data) {
Sha256 sha256;
sha256.Update(data.data(), data.size());
std::array<uint8_t, 32> hash = std::move(sha256).Finalize();
return hash;
}
SchemaProto CreateBlobSchema() {
return SchemaBuilder()
.AddType(SchemaTypeConfigBuilder()
.SetType("BlobType")
.AddProperty(PropertyConfigBuilder()
.SetName("blob")
.SetDataType(TYPE_BLOB_HANDLE)
.SetCardinality(CARDINALITY_REQUIRED)))
.Build();
}
DocumentProto CreateBlobDocument(std::string name_space, std::string uri,
PropertyProto::BlobHandleProto blob_handle) {
return DocumentBuilder()
.SetKey(std::move(name_space), std::move(uri))
.SetSchema("BlobType")
.AddBlobHandleProperty("blob", blob_handle)
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
}
TEST_F(IcingSearchEngineBlobTest, InvalidBlobHandle) {
PropertyProto::BlobHandleProto blob_handle;
blob_handle.set_digest("invalid");
blob_handle.set_namespace_("namespaceA");
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
BlobProto write_blob_proto = icing.OpenWriteBlob(blob_handle);
EXPECT_THAT(write_blob_proto.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
BlobProto commit_blob_proto = icing.CommitBlob(blob_handle);
EXPECT_THAT(commit_blob_proto.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
BlobProto read_blob_proto = icing.OpenReadBlob(blob_handle);
EXPECT_THAT(read_blob_proto.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineBlobTest, BlobStoreDisabled) {
IcingSearchEngineOptions icing_options;
icing_options.set_base_dir(GetTestBaseDir());
icing_options.set_enable_blob_store(false);
IcingSearchEngine icing(icing_options, GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest((void*)digest.data(), digest.size());
blob_handle.set_namespace_("namespaceA");
BlobProto write_blob_proto = icing.OpenWriteBlob(blob_handle);
EXPECT_THAT(write_blob_proto.status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
BlobProto commit_blob_proto = icing.CommitBlob(blob_handle);
EXPECT_THAT(commit_blob_proto.status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
BlobProto read_blob_proto = icing.OpenReadBlob(blob_handle);
EXPECT_THAT(read_blob_proto.status(),
ProtoStatusIs(StatusProto::FAILED_PRECONDITION));
}
TEST_F(IcingSearchEngineBlobTest, WriteAndReadBlob) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest((void*)digest.data(), digest.size());
blob_handle.set_namespace_("namespaceA");
BlobProto write_blob_proto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(write_blob_proto.status(), ProtoIsOk());
{
ScopedFd write_fd(write_blob_proto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
BlobProto commit_blob_proto = icing.CommitBlob(blob_handle);
ASSERT_THAT(commit_blob_proto.status(), ProtoIsOk());
BlobProto read_blob_proto = icing.OpenReadBlob(blob_handle);
ASSERT_THAT(read_blob_proto.status(), ProtoIsOk());
{
ScopedFd read_fd(read_blob_proto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<unsigned char[]> buf =
std::make_unique<unsigned char[]>(size);
EXPECT_TRUE(filesystem()->Read(read_fd.get(), buf.get(), size));
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
}
TEST_F(IcingSearchEngineBlobTest, RemovePendingBlob) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
blob_handle.set_namespace_("namespace1");
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest((void*)digest.data(), digest.size());
BlobProto write_blob_proto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(write_blob_proto.status(), ProtoIsOk());
{
ScopedFd write_fd(write_blob_proto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
std::vector<std::string> file_names;
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobFileDir().c_str(), &file_names));
ASSERT_THAT(file_names, SizeIs(1));
EXPECT_THAT(icing.RemoveBlob(blob_handle).status(), ProtoIsOk());
// Commit will return NOT_FOUND since the blob is removed.
BlobProto commit_blob_proto = icing.CommitBlob(blob_handle);
EXPECT_THAT(commit_blob_proto.status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
file_names = std::vector<std::string>();
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobFileDir().c_str(), &file_names));
// The pending file is deleted.
EXPECT_THAT(file_names, IsEmpty());
}
TEST_F(IcingSearchEngineBlobTest, RemoveCommittedBlob) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
blob_handle.set_namespace_("namespace1");
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest((void*)digest.data(), digest.size());
BlobProto write_blob_proto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(write_blob_proto.status(), ProtoIsOk());
{
ScopedFd write_fd(write_blob_proto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
// commit the blob
ASSERT_THAT(icing.CommitBlob(blob_handle).status(), ProtoIsOk());
std::vector<std::string> file_names;
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobFileDir().c_str(), &file_names));
ASSERT_THAT(file_names, SizeIs(1));
EXPECT_THAT(icing.RemoveBlob(blob_handle).status(), ProtoIsOk());
// Commit will return NOT_FOUND since the blob is removed.
BlobProto commit_blob_proto = icing.CommitBlob(blob_handle);
EXPECT_THAT(commit_blob_proto.status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
file_names = std::vector<std::string>();
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobFileDir().c_str(), &file_names));
// The pending file is deleted.
EXPECT_THAT(file_names, IsEmpty());
}
TEST_F(IcingSearchEngineBlobTest, WriteAndReadBlobByDocument) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest((void*)digest.data(), digest.size());
blob_handle.set_namespace_("namespaceA");
BlobProto write_blob_proto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(write_blob_proto.status(), ProtoIsOk());
{
ScopedFd write_fd(write_blob_proto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
BlobProto commit_blob_proto = icing.CommitBlob(blob_handle);
ASSERT_THAT(commit_blob_proto.status(), ProtoIsOk());
// Set schema and put a document that contains the blob handle
ASSERT_THAT(icing.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc1", blob_handle)).status(),
ProtoIsOk());
// Read the document and its blob handle property.
GetResultProto get_result =
icing.Get("namespace", "doc1", GetResultSpecProto::default_instance());
EXPECT_THAT(get_result.status(), ProtoIsOk());
PropertyProto::BlobHandleProto out_blob_handle =
get_result.document().properties().at(0).blob_handle_values().at(0);
// use the output blob handle to read blob data.
BlobProto read_blob_proto = icing.OpenReadBlob(out_blob_handle);
ASSERT_THAT(read_blob_proto.status(), ProtoIsOk());
{
ScopedFd read_fd(read_blob_proto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
EXPECT_TRUE(filesystem()->Read(read_fd.get(), buf.get(), size));
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
}
TEST_F(IcingSearchEngineBlobTest, CommitDigestMisMatch) {
IcingSearchEngine icing(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest(std::string(digest.begin(), digest.end()));
blob_handle.set_namespace_("namespaceA");
BlobProto write_blob_proto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(write_blob_proto.status(), ProtoIsOk());
std::vector<unsigned char> data2 = GenerateRandomBytes(24);
{
ScopedFd write_fd(write_blob_proto.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data2.data(), data2.size()));
}
BlobProto commit_blob_proto = icing.CommitBlob(blob_handle);
ASSERT_THAT(commit_blob_proto.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineBlobTest, ReadBlobWithoutPersistToDisk) {
IcingSearchEngine icing1(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing1.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest((void*)digest.data(), digest.size());
blob_handle.set_namespace_("namespaceA");
BlobProto write_blob_proto = icing1.OpenWriteBlob(blob_handle);
ASSERT_THAT(write_blob_proto.status(), ProtoIsOk());
{
ScopedFd write_fd(write_blob_proto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
BlobProto commit_blob_proto = icing1.CommitBlob(blob_handle);
ASSERT_THAT(commit_blob_proto.status(), ProtoIsOk());
// Recreate icing, the blob info will be dropped since we haven't called
// persistToDisk.
IcingSearchEngine icing2(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing2.Initialize().status(), ProtoIsOk());
BlobProto read_blob_proto = icing2.OpenReadBlob(blob_handle);
EXPECT_THAT(read_blob_proto.status(), ProtoStatusIs(StatusProto::NOT_FOUND));
}
TEST_F(IcingSearchEngineBlobTest, ReadBlobWithPersistToDiskFull) {
IcingSearchEngine icing1(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing1.Initialize().status(), ProtoIsOk());
// set a schema to icing to avoid wipe out all directories.
ASSERT_THAT(icing1.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest((void*)digest.data(), digest.size());
blob_handle.set_namespace_("namespaceA");
BlobProto write_blob_proto = icing1.OpenWriteBlob(blob_handle);
ASSERT_THAT(write_blob_proto.status(), ProtoIsOk());
{
ScopedFd write_fd(write_blob_proto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
BlobProto commit_blob_proto = icing1.CommitBlob(blob_handle);
ASSERT_THAT(commit_blob_proto.status(), ProtoIsOk());
EXPECT_THAT(icing1.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// Recreate icing, the blob info will be dropped since we haven't called
// persistToDisk.
IcingSearchEngine icing2(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
BlobProto read_blob_proto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(read_blob_proto.status(), ProtoIsOk());
{
ScopedFd read_fd(read_blob_proto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
EXPECT_TRUE(filesystem()->Read(read_fd.get(), buf.get(), size));
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
}
TEST_F(IcingSearchEngineBlobTest, ReadBlobWithPersistToDiskLite) {
IcingSearchEngine icing1(GetDefaultIcingOptions(), GetTestJniCache());
EXPECT_THAT(icing1.Initialize().status(), ProtoIsOk());
// set a schema to icing to avoid wipe out all directories.
ASSERT_THAT(icing1.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest((void*)digest.data(), digest.size());
blob_handle.set_namespace_("namespaceA");
BlobProto write_blob_proto = icing1.OpenWriteBlob(blob_handle);
ASSERT_THAT(write_blob_proto.status(), ProtoIsOk());
{
ScopedFd write_fd(write_blob_proto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
BlobProto commit_blob_proto = icing1.CommitBlob(blob_handle);
ASSERT_THAT(commit_blob_proto.status(), ProtoIsOk());
EXPECT_THAT(icing1.PersistToDisk(PersistType::LITE).status(), ProtoIsOk());
// Recreate icing, the blob info will be remained since we called
// persistToDisk.
IcingSearchEngine icing2(GetDefaultIcingOptions(), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
BlobProto read_blob_proto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(read_blob_proto.status(), ProtoIsOk());
{
ScopedFd read_fd(read_blob_proto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
EXPECT_TRUE(filesystem()->Read(read_fd.get(), buf.get(), size));
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
}
TEST_F(IcingSearchEngineBlobTest, BlobOptimize) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// set a schema to icing to avoid wipe out all directories.
ASSERT_THAT(icing.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest(std::string(digest.begin(), digest.end()));
blob_handle.set_namespace_("namespaceA");
BlobProto writeBlobProto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(writeBlobProto.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
std::vector<std::string> file_names;
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobFileDir().c_str(), &file_names));
// The blob file is created.
EXPECT_THAT(file_names, SizeIs(1));
BlobProto commitBlobProto = icing.CommitBlob(blob_handle);
ASSERT_THAT(commitBlobProto.status(), ProtoIsOk());
// persist blob to disk
EXPECT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// create second icing in 8 days later
auto fake_clock2 = std::make_unique<FakeClock>();
fake_clock2->SetSystemTimeMilliseconds(1000 + 8 * 24 * 60 * 60 *
1000); // pass 8 days
TestIcingSearchEngine icing2(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock2), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
// Blob remain before optimize
BlobProto readBlobProto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(readBlobProto.status(), ProtoIsOk());
ScopedFd read_fd(readBlobProto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
filesystem()->Read(read_fd.get(), buf.get(), size);
close(read_fd.get());
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
file_names = std::vector<std::string>();
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobDir().c_str(), &file_names));
int32_t cur_file_count = file_names.size();
// Optimize remove the expired orphan blob.
EXPECT_THAT(icing2.Optimize().status(), ProtoIsOk());
EXPECT_THAT(icing2.OpenReadBlob(blob_handle).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
file_names = std::vector<std::string>();
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobDir().c_str(), &file_names));
EXPECT_THAT(file_names, SizeIs(cur_file_count));
}
TEST_F(IcingSearchEngineBlobTest, BlobOptimizeWithoutCommit) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// set a schema to icing to avoid wipe out all directories.
ASSERT_THAT(icing.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
// write two blobs but not commit
PropertyProto::BlobHandleProto blob_handle1;
std::vector<unsigned char> data1 = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest1 = CalculateDigest(data1);
std::string digest_string1 = std::string(digest1.begin(), digest1.end());
blob_handle1.set_digest(std::move(digest_string1));
blob_handle1.set_namespace_("namespaceA");
BlobProto writeBlobProto = icing.OpenWriteBlob(blob_handle1);
ASSERT_THAT(writeBlobProto.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data1.data(), data1.size()));
}
PropertyProto::BlobHandleProto blob_handle2;
std::vector<unsigned char> data2 = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest2 = CalculateDigest(data2);
blob_handle2.set_digest(std::string(digest2.begin(), digest2.end()));
blob_handle2.set_namespace_("namespaceA");
writeBlobProto = icing.OpenWriteBlob(blob_handle2);
ASSERT_THAT(writeBlobProto.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data2.data(), data2.size()));
}
// persist blob to disk
EXPECT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// create second icing in 8 days later
auto fake_clock2 = std::make_unique<FakeClock>();
fake_clock2->SetSystemTimeMilliseconds(1000 + 8 * 24 * 60 * 60 *
1000); // pass 8 days
TestIcingSearchEngine icing2(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock2), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
// Blob is able to commit before optimize
EXPECT_THAT(icing2.CommitBlob(blob_handle1).status(), ProtoIsOk());
// Optimize remove the expired orphan blob. so it's not able to commit.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
EXPECT_THAT(icing2.CommitBlob(blob_handle2).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
}
TEST_F(IcingSearchEngineBlobTest, ReferenceCount) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest(std::string(digest.begin(), digest.end()));
blob_handle.set_namespace_("namespaceA");
BlobProto writeBlobProto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(writeBlobProto.status(), ProtoIsOk());
ScopedFd write_fd(writeBlobProto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
close(write_fd.get());
BlobProto commitBlobProto = icing.CommitBlob(blob_handle);
ASSERT_THAT(commitBlobProto.status(), ProtoIsOk());
// Set schema and put a document that contains the blob handle
ASSERT_THAT(icing.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc1", blob_handle)).status(),
ProtoIsOk());
// persist to disk
EXPECT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// create second icing in 8 days later
auto fake_clock2 = std::make_unique<FakeClock>();
fake_clock2->SetSystemTimeMilliseconds(1000 + 8 * 24 * 60 * 60 *
1000); // pass 8 days
TestIcingSearchEngine icing2(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock2), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
// Optimize won't remove the blob since there is reference document.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
BlobProto readBlobProto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(readBlobProto.status(), ProtoIsOk());
{
ScopedFd read_fd(readBlobProto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
ASSERT_TRUE(filesystem()->Read(read_fd.get(), buf.get(), size));
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
// remove the reference document, now the blob is an orphan.
ASSERT_THAT(icing2.Delete("namespace", "doc1").status(), ProtoIsOk());
// The blob remain before optimize.
readBlobProto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(readBlobProto.status(), ProtoIsOk());
{
ScopedFd read_fd2(readBlobProto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd2);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
ASSERT_TRUE(filesystem()->Read(read_fd2.get(), buf.get(), size));
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
// Optimize remove the expired orphan blob.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
EXPECT_THAT(icing2.OpenReadBlob(blob_handle).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
}
TEST_F(IcingSearchEngineBlobTest, ReferenceCountNestedDocument) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest(std::string(digest.begin(), digest.end()));
blob_handle.set_namespace_("namespaceA");
BlobProto writeBlobProto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(writeBlobProto.status(), ProtoIsOk());
ScopedFd write_fd(writeBlobProto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
close(write_fd.get());
BlobProto commitBlobProto = icing.CommitBlob(blob_handle);
ASSERT_THAT(commitBlobProto.status(), ProtoIsOk());
// Set an multi-level schema and put a document that contains the blob handle
// in the nested document property.
SchemaTypeConfigProto type_a =
SchemaTypeConfigBuilder()
.SetType("A")
.AddProperty(PropertyConfigBuilder()
.SetName("blob")
.SetDataType(TYPE_BLOB_HANDLE)
.SetCardinality(CARDINALITY_OPTIONAL))
.Build();
SchemaTypeConfigProto type_b =
SchemaTypeConfigBuilder()
.SetType("B")
.AddProperty(
PropertyConfigBuilder()
.SetName("nestedDoc")
.SetDataTypeDocument("A", /*index_nested_properties=*/false)
.SetCardinality(CARDINALITY_OPTIONAL))
.Build();
ASSERT_THAT(
icing.SetSchema(SchemaBuilder().AddType(type_a).AddType(type_b).Build())
.status(),
ProtoIsOk());
DocumentProto document_a = DocumentBuilder()
.SetKey("namespace", "doc_a")
.SetSchema("A")
.AddBlobHandleProperty("blob", blob_handle)
.Build();
DocumentProto document_b = DocumentBuilder()
.SetKey("namespace", "doc_b")
.SetSchema("B")
.AddDocumentProperty("nestedDoc", document_a)
.Build();
ASSERT_THAT(icing.Put(document_b).status(), ProtoIsOk());
// persist to disk
EXPECT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// create second icing in 8 days later
auto fake_clock2 = std::make_unique<FakeClock>();
fake_clock2->SetSystemTimeMilliseconds(1000 + 8 * 24 * 60 * 60 *
1000); // pass 8 days
TestIcingSearchEngine icing2(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock2), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
// Optimize won't remove the blob since there is reference document.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
BlobProto readBlobProto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(readBlobProto.status(), ProtoIsOk());
{
ScopedFd read_fd(readBlobProto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
ASSERT_TRUE(filesystem()->Read(read_fd.get(), buf.get(), size));
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
// remove the reference document, now the blob is an orphan.
ASSERT_THAT(icing2.Delete("namespace", "doc_b").status(), ProtoIsOk());
// The blob remain before optimize.
readBlobProto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(readBlobProto.status(), ProtoIsOk());
{
ScopedFd read_fd2(readBlobProto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd2);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
ASSERT_TRUE(filesystem()->Read(read_fd2.get(), buf.get(), size));
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
// Optimize remove the expired orphan blob.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
EXPECT_THAT(icing2.OpenReadBlob(blob_handle).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
}
TEST_F(IcingSearchEngineBlobTest, OptimizeMultipleReferenceDocument) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest(std::string(digest.begin(), digest.end()));
blob_handle.set_namespace_("namespaceA");
BlobProto writeBlobProto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(writeBlobProto.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
BlobProto commitBlobProto = icing.CommitBlob(blob_handle);
ASSERT_THAT(commitBlobProto.status(), ProtoIsOk());
// Set schema and put 3 documents that contains the blob handle
ASSERT_THAT(icing.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc1", blob_handle)).status(),
ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc2", blob_handle)).status(),
ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc3", blob_handle)).status(),
ProtoIsOk());
// persist to disk
EXPECT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// create second icing in 8 days later
auto fake_clock2 = std::make_unique<FakeClock>();
fake_clock2->SetSystemTimeMilliseconds(1000 + 8 * 24 * 60 * 60 *
1000); // pass 8 days
TestIcingSearchEngine icing2(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock2), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
// Optimize won't remove the blob since there are reference documents.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
BlobProto readBlobProto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(readBlobProto.status(), ProtoIsOk());
{
ScopedFd read_fd(readBlobProto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
filesystem()->Read(read_fd.get(), buf.get(), size);
close(read_fd.get());
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
// remove two reference documents.
ASSERT_THAT(icing2.Delete("namespace", "doc1").status(), ProtoIsOk());
ASSERT_THAT(icing2.Delete("namespace", "doc2").status(), ProtoIsOk());
// The blob remain after optimize.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
readBlobProto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(readBlobProto.status(), ProtoIsOk());
{
ScopedFd read_fd2(readBlobProto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd2);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
filesystem()->Read(read_fd2.get(), buf.get(), size);
close(read_fd2.get());
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
// remove the last reference document, now the blob become orphan.
ASSERT_THAT(icing2.Delete("namespace", "doc3").status(), ProtoIsOk());
// Optimize remove the expired orphan blob.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
EXPECT_THAT(icing2.OpenReadBlob(blob_handle).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
}
TEST_F(IcingSearchEngineBlobTest, OptimizeMultipleBlobHandles) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle1;
std::vector<unsigned char> data1 = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest1 = CalculateDigest(data1);
std::string digest_string1 = std::string(digest1.begin(), digest1.end());
blob_handle1.set_digest(std::move(digest_string1));
blob_handle1.set_namespace_("namespaceA");
BlobProto writeBlobProto1 = icing.OpenWriteBlob(blob_handle1);
ASSERT_THAT(writeBlobProto1.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto1.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data1.data(), data1.size()));
}
BlobProto commitBlobProto = icing.CommitBlob(blob_handle1);
ASSERT_THAT(commitBlobProto.status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle2;
std::vector<unsigned char> data2 = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest2 = CalculateDigest(data2);
blob_handle2.set_digest(std::string(digest2.begin(), digest2.end()));
blob_handle2.set_namespace_("namespaceA");
BlobProto writeBlobProto2 = icing.OpenWriteBlob(blob_handle2);
ASSERT_THAT(writeBlobProto2.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto2.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data2.data(), data2.size()));
}
BlobProto commitBlobProto2 = icing.CommitBlob(blob_handle2);
ASSERT_THAT(commitBlobProto2.status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle3;
std::vector<unsigned char> data3 = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest3 = CalculateDigest(data3);
blob_handle3.set_digest(std::string(digest3.begin(), digest3.end()));
blob_handle3.set_namespace_("namespaceA");
BlobProto writeBlobProto3 = icing.OpenWriteBlob(blob_handle3);
ASSERT_THAT(writeBlobProto3.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto3.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data3.data(), data3.size()));
}
BlobProto commitBlobProto3 = icing.CommitBlob(blob_handle3);
ASSERT_THAT(commitBlobProto3.status(), ProtoIsOk());
std::vector<std::string> file_names;
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobFileDir().c_str(), &file_names));
// 3 more blob files are created.
ASSERT_THAT(file_names, SizeIs(3));
// Set schema and put 3 documents that contains the blob handle
ASSERT_THAT(icing.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc1", blob_handle1)).status(),
ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc2", blob_handle2)).status(),
ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc3", blob_handle3)).status(),
ProtoIsOk());
// persist to disk
EXPECT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// create second icing in 8 days later
auto fake_clock2 = std::make_unique<FakeClock>();
fake_clock2->SetSystemTimeMilliseconds(1000 + 8 * 24 * 60 * 60 *
1000); // pass 8 days
TestIcingSearchEngine icing2(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock2), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
// Optimize won't remove the blob since there are reference documents.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
ASSERT_THAT(icing2.OpenReadBlob(blob_handle1).status(), ProtoIsOk());
ASSERT_THAT(icing2.OpenReadBlob(blob_handle2).status(), ProtoIsOk());
ASSERT_THAT(icing2.OpenReadBlob(blob_handle3).status(), ProtoIsOk());
// Remove first two reference documents.
ASSERT_THAT(icing2.Delete("namespace", "doc1").status(), ProtoIsOk());
ASSERT_THAT(icing2.Delete("namespace", "doc2").status(), ProtoIsOk());
// First two orphan blobs are removed after optimize .
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
EXPECT_THAT(icing2.OpenReadBlob(blob_handle1).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
EXPECT_THAT(icing2.OpenReadBlob(blob_handle2).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
ASSERT_THAT(icing2.OpenReadBlob(blob_handle3).status(), ProtoIsOk());
file_names = std::vector<std::string>();
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobFileDir().c_str(), &file_names));
// 2 blob files are removed
ASSERT_THAT(file_names, SizeIs(1));
// remove the last reference document, now the all blobs become orphan.
ASSERT_THAT(icing2.Delete("namespace", "doc3").status(), ProtoIsOk());
// Optimize remove the expired orphan blob.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
EXPECT_THAT(icing2.OpenReadBlob(blob_handle3).status(),
ProtoStatusIs(StatusProto::NOT_FOUND));
file_names = std::vector<std::string>();
ASSERT_TRUE(
filesystem()->ListDirectory(GetTestBlobFileDir().c_str(), &file_names));
// the last blob file is removed.
ASSERT_THAT(file_names, SizeIs(0));
}
TEST_F(IcingSearchEngineBlobTest, OptimizeBlobHandlesNoTTL) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
IcingSearchEngineOptions icing_options;
icing_options.set_base_dir(GetTestBaseDir());
icing_options.set_enable_blob_store(true);
// set orphan blob ttl to 0, which means no ttl
icing_options.set_orphan_blob_time_to_live_ms(0);
TestIcingSearchEngine icing(icing_options, std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// set a schema to icing to avoid wipe out all directories.
ASSERT_THAT(icing.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest(std::string(digest.begin(), digest.end()));
blob_handle.set_namespace_("namespaceA");
BlobProto writeBlobProto = icing.OpenWriteBlob(blob_handle);
ASSERT_THAT(writeBlobProto.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto.file_descriptor());
ASSERT_TRUE(filesystem()->Write(write_fd.get(), data.data(), data.size()));
}
BlobProto commitBlobProto = icing.CommitBlob(blob_handle);
ASSERT_THAT(commitBlobProto.status(), ProtoIsOk());
// persist blob to disk
EXPECT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// create second icing in a year later 365L * 24 * 60 * 60 * 1000;
auto fake_clock2 = std::make_unique<FakeClock>();
fake_clock2->SetSystemTimeMilliseconds(1471228928);
TestIcingSearchEngine icing2(icing_options, std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock2), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
// Blob remain after optimize
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
BlobProto readBlobProto = icing2.OpenReadBlob(blob_handle);
ASSERT_THAT(readBlobProto.status(), ProtoIsOk());
ScopedFd read_fd(readBlobProto.file_descriptor());
uint64_t size = filesystem()->GetFileSize(*read_fd);
std::unique_ptr<uint8_t[]> buf = std::make_unique<uint8_t[]>(size);
filesystem()->Read(read_fd.get(), buf.get(), size);
close(read_fd.get());
std::string expected_data = std::string(data.begin(), data.end());
std::string actual_data = std::string(buf.get(), buf.get() + size);
EXPECT_EQ(expected_data, actual_data);
}
TEST_F(IcingSearchEngineBlobTest, EmptyNamespace) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle;
std::vector<unsigned char> data = GenerateRandomBytes(12);
std::array<uint8_t, 32> digest = CalculateDigest(data);
blob_handle.set_digest(std::string(digest.begin(), digest.end()));
BlobProto writeBlobProto = icing.OpenWriteBlob(blob_handle);
EXPECT_THAT(writeBlobProto.status(),
ProtoStatusIs(StatusProto::INVALID_ARGUMENT));
}
TEST_F(IcingSearchEngineBlobTest, OptimizeNamespaceUsage) {
auto fake_clock = std::make_unique<FakeClock>();
fake_clock->SetSystemTimeMilliseconds(1000);
TestIcingSearchEngine icing(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock), GetTestJniCache());
ASSERT_THAT(icing.Initialize().status(), ProtoIsOk());
// insert 3 blobs from 3 different namespaces
PropertyProto::BlobHandleProto blob_handle1;
std::vector<unsigned char> data1 = GenerateRandomBytes(12);
std::array<uint8_t, 32> digest1 = CalculateDigest(data1);
blob_handle1.set_digest(std::string(digest1.begin(), digest1.end()));
blob_handle1.set_namespace_("namespaceA");
BlobProto writeBlobProto1 = icing.OpenWriteBlob(blob_handle1);
ASSERT_THAT(writeBlobProto1.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto1.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data1.data(), data1.size()));
}
BlobProto commitBlobProto = icing.CommitBlob(blob_handle1);
ASSERT_THAT(commitBlobProto.status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle2;
std::vector<unsigned char> data2 = GenerateRandomBytes(24);
std::array<uint8_t, 32> digest2 = CalculateDigest(data2);
blob_handle2.set_digest(std::string(digest2.begin(), digest2.end()));
blob_handle2.set_namespace_("namespaceB");
BlobProto writeBlobProto2 = icing.OpenWriteBlob(blob_handle2);
ASSERT_THAT(writeBlobProto2.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto2.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data2.data(), data2.size()));
}
BlobProto commitBlobProto2 = icing.CommitBlob(blob_handle2);
ASSERT_THAT(commitBlobProto2.status(), ProtoIsOk());
PropertyProto::BlobHandleProto blob_handle3;
std::vector<unsigned char> data3 = GenerateRandomBytes(36);
std::array<uint8_t, 32> digest3 = CalculateDigest(data3);
blob_handle3.set_digest(std::string(digest3.begin(), digest3.end()));
blob_handle3.set_namespace_("namespaceC");
BlobProto writeBlobProto3 = icing.OpenWriteBlob(blob_handle3);
ASSERT_THAT(writeBlobProto3.status(), ProtoIsOk());
{
ScopedFd write_fd(writeBlobProto3.file_descriptor());
ASSERT_TRUE(
filesystem()->Write(write_fd.get(), data3.data(), data3.size()));
}
BlobProto commitBlobProto3 = icing.CommitBlob(blob_handle3);
ASSERT_THAT(commitBlobProto3.status(), ProtoIsOk());
// Set schema and put a documents that contains the blob handle2 only
ASSERT_THAT(icing.SetSchema(CreateBlobSchema()).status(), ProtoIsOk());
ASSERT_THAT(
icing.Put(CreateBlobDocument("namespace", "doc", blob_handle2)).status(),
ProtoIsOk());
// persist blob to disk
EXPECT_THAT(icing.PersistToDisk(PersistType::FULL).status(), ProtoIsOk());
// Verify namespace usage
StorageInfoResultProto storage_info_result = icing.GetStorageInfo();
EXPECT_THAT(storage_info_result.status(), ProtoIsOk());
NamespaceBlobStorageInfoProto namespace_info_a;
namespace_info_a.set_namespace_("namespaceA");
namespace_info_a.set_blob_size(12);
namespace_info_a.set_num_blobs(1);
NamespaceBlobStorageInfoProto namespace_info_b;
namespace_info_b.set_namespace_("namespaceB");
namespace_info_b.set_blob_size(24);
namespace_info_b.set_num_blobs(1);
NamespaceBlobStorageInfoProto namespace_info_c;
namespace_info_c.set_namespace_("namespaceC");
namespace_info_c.set_blob_size(36);
namespace_info_c.set_num_blobs(1);
EXPECT_THAT(storage_info_result.storage_info().namespace_blob_storage_info(),
UnorderedElementsAre(EqualsProto(namespace_info_a),
EqualsProto(namespace_info_b),
EqualsProto(namespace_info_c)));
// create second icing in 8 days later
auto fake_clock2 = std::make_unique<FakeClock>();
fake_clock2->SetSystemTimeMilliseconds(1000 + 8 * 24 * 60 * 60 *
1000); // pass 8 days
TestIcingSearchEngine icing2(GetDefaultIcingOptions(),
std::make_unique<Filesystem>(),
std::make_unique<IcingFilesystem>(),
std::move(fake_clock2), GetTestJniCache());
ASSERT_THAT(icing2.Initialize().status(), ProtoIsOk());
// After optimize, blobs of namespaceA and namespaceC are removed.
ASSERT_THAT(icing2.Optimize().status(), ProtoIsOk());
storage_info_result = icing2.GetStorageInfo();
EXPECT_THAT(storage_info_result.status(), ProtoIsOk());
EXPECT_THAT(storage_info_result.storage_info().namespace_blob_storage_info(),
UnorderedElementsAre(EqualsProto(namespace_info_b)));
}
} // namespace
} // namespace lib
} // namespace icing