// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "base/strings/utf_string_conversions.h"
#include "chromeos/ash/components/local_search_service/linear_map_search.h"
#include "chromeos/ash/components/local_search_service/shared_structs.h"
#include "chromeos/ash/components/local_search_service/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash::local_search_service {
namespace {
// This is (data-id, content-ids).
using ResultWithIds = std::pair<std::string, std::vector<std::string>>;
using ContentWithId = std::pair<std::string, std::string>;
void CheckSearchParams(const SearchParams& actual,
const SearchParams& expected) {
EXPECT_DOUBLE_EQ(actual.relevance_threshold, expected.relevance_threshold);
EXPECT_DOUBLE_EQ(actual.prefix_threshold, expected.prefix_threshold);
EXPECT_DOUBLE_EQ(actual.fuzzy_threshold, expected.fuzzy_threshold);
}
void GetSizeAndCheckResults(LinearMapSearch* index,
uint32_t expectd_num_items) {
bool callback_done = false;
uint32_t num_items = 0;
index->GetSize(base::BindOnce(
[](bool* callback_done, uint32_t* num_items, uint64_t size) {
*callback_done = true;
*num_items = size;
},
&callback_done, &num_items));
ASSERT_TRUE(callback_done);
EXPECT_EQ(num_items, expectd_num_items);
}
void AddOrUpdate(LinearMapSearch* index, const std::vector<Data>& data) {
bool callback_done = false;
index->AddOrUpdate(
data, base::BindOnce([](bool* callback_done) { *callback_done = true; },
&callback_done));
ASSERT_TRUE(callback_done);
}
void UpdateDocumentsAndCheckResults(LinearMapSearch* index,
const std::vector<Data>& data,
uint32_t expect_num_deleted) {
bool callback_done = false;
uint32_t num_deleted = 0u;
index->UpdateDocuments(data,
base::BindOnce(
[](bool* callback_done, uint32_t* num_deleted,
uint32_t num_deleted_callback) {
*callback_done = true;
*num_deleted = num_deleted_callback;
},
&callback_done, &num_deleted));
ASSERT_TRUE(callback_done);
EXPECT_EQ(num_deleted, expect_num_deleted);
}
void FindAndCheckResults(LinearMapSearch* index,
std::string query,
int32_t max_results,
ResponseStatus expected_status,
const std::vector<ResultWithIds>& expected_results) {
DCHECK(index);
bool callback_done = false;
ResponseStatus status;
std::vector<Result> results;
index->Find(
base::UTF8ToUTF16(query), max_results,
base::BindOnce(
[](bool* callback_done, ResponseStatus* status,
std::vector<Result>* results, ResponseStatus status_callback,
const std::optional<std::vector<Result>>& results_callback) {
*callback_done = true;
*status = status_callback;
if (results_callback.has_value())
*results = results_callback.value();
},
&callback_done, &status, &results));
ASSERT_TRUE(callback_done);
EXPECT_EQ(status, expected_status);
if (!results.empty()) {
// If results are returned, check size and values match the expected.
EXPECT_EQ(results.size(), expected_results.size());
for (size_t i = 0; i < results.size(); ++i) {
EXPECT_EQ(results[i].id, expected_results[i].first);
EXPECT_EQ(results[i].positions.size(), expected_results[i].second.size());
for (size_t j = 0; j < results[i].positions.size(); ++j) {
EXPECT_EQ(results[i].positions[j].content_id,
expected_results[i].second[j]);
}
// Scores should be non-increasing.
if (i < results.size() - 1) {
EXPECT_GE(results[i].score, results[i + 1].score);
}
}
return;
}
// If no results are returned, expected ids should be empty.
EXPECT_TRUE(expected_results.empty());
}
} // namespace
class LinearMapSearchTest : public testing::Test {
void SetUp() override {
index_ = std::make_unique<LinearMapSearch>(IndexId::kCrosSettings);
}
protected:
std::unique_ptr<LinearMapSearch> index_;
};
TEST_F(LinearMapSearchTest, SetSearchParams) {
{
// No params are specified so default values are used.
const SearchParams used_params = index_->GetSearchParamsForTesting();
CheckSearchParams(used_params, SearchParams());
}
{
// Params are specified and are used.
SearchParams search_params;
const SearchParams default_params;
search_params.relevance_threshold = default_params.relevance_threshold / 2;
search_params.prefix_threshold = default_params.prefix_threshold / 2;
search_params.fuzzy_threshold = default_params.fuzzy_threshold / 2;
index_->SetSearchParams(search_params);
const SearchParams used_params = index_->GetSearchParamsForTesting();
CheckSearchParams(used_params, search_params);
}
}
TEST_F(LinearMapSearchTest, RelevanceThreshold) {
const std::map<std::string, std::vector<ContentWithId>> data_to_register = {
{"id1", {{"tag1", "Wi-Fi"}}}, {"id2", {{"tag2", "famous"}}}};
std::vector<Data> data = CreateTestData(data_to_register);
AddOrUpdate(index_.get(), data);
GetSizeAndCheckResults(index_.get(), 2u);
{
SearchParams search_params;
search_params.relevance_threshold = 0.0;
index_->SetSearchParams(search_params);
const std::vector<ResultWithIds> expected_results = {{"id1", {"tag1"}},
{"id2", {"tag2"}}};
FindAndCheckResults(index_.get(), "wifi",
/*max_results=*/-1, ResponseStatus::kSuccess,
expected_results);
}
{
SearchParams search_params;
search_params.relevance_threshold = 0.3;
index_->SetSearchParams(search_params);
const std::vector<ResultWithIds> expected_results = {{"id1", {"tag1"}}};
FindAndCheckResults(index_.get(), "wifi",
/*max_results=*/-1, ResponseStatus::kSuccess,
expected_results);
}
{
SearchParams search_params;
search_params.relevance_threshold = 0.95;
index_->SetSearchParams(search_params);
FindAndCheckResults(index_.get(), "wifi",
/*max_results=*/-1, ResponseStatus::kSuccess, {});
}
}
TEST_F(LinearMapSearchTest, MaxResults) {
const std::map<std::string, std::vector<ContentWithId>> data_to_register = {
{"id1", {{"tag1", "abcde"}, {"tag2", "Wi-Fi"}}},
{"id2", {{"tag3", "wifi"}}}};
std::vector<Data> data = CreateTestData(data_to_register);
AddOrUpdate(index_.get(), data);
GetSizeAndCheckResults(index_.get(), 2u);
SearchParams search_params;
search_params.relevance_threshold = 0.3;
index_->SetSearchParams(search_params);
{
const std::vector<ResultWithIds> expected_results = {{"id2", {"tag3"}},
{"id1", {"tag2"}}};
FindAndCheckResults(index_.get(), "wifi",
/*max_results=*/-1, ResponseStatus::kSuccess,
expected_results);
}
{
const std::vector<ResultWithIds> expected_results = {{"id2", {"tag3"}}};
FindAndCheckResults(index_.get(), "wifi",
/*max_results=*/1, ResponseStatus::kSuccess,
expected_results);
}
}
TEST_F(LinearMapSearchTest, ResultFound) {
const std::map<std::string, std::vector<ContentWithId>> data_to_register = {
{"id1", {{"cid1", "id1"}, {"cid2", "tag1a"}, {"cid3", "tag1b"}}},
{"xyz", {{"cid4", "xyz"}}}};
std::vector<Data> data = CreateTestData(data_to_register);
EXPECT_EQ(data.size(), 2u);
AddOrUpdate(index_.get(), data);
GetSizeAndCheckResults(index_.get(), 2u);
// Find result with query "id1". It returns an exact match.
const std::vector<ResultWithIds> expected_results = {{"id1", {"cid1"}}};
FindAndCheckResults(index_.get(), "id1",
/*max_results=*/-1, ResponseStatus::kSuccess,
expected_results);
FindAndCheckResults(index_.get(), "abc",
/*max_results=*/-1, ResponseStatus::kSuccess, {});
}
TEST_F(LinearMapSearchTest, ClearIndex) {
const std::map<std::string, std::vector<ContentWithId>> data_to_register = {
{"id1", {{"cid1", "id1"}, {"cid2", "tag1a"}, {"cid3", "tag1b"}}},
{"xyz", {{"cid4", "xyz"}}}};
std::vector<Data> data = CreateTestData(data_to_register);
EXPECT_EQ(data.size(), 2u);
AddOrUpdate(index_.get(), data);
GetSizeAndCheckResults(index_.get(), 2u);
bool callback_done = false;
index_->ClearIndex(base::BindOnce(
[](bool* callback_done) { *callback_done = true; }, &callback_done));
ASSERT_TRUE(callback_done);
GetSizeAndCheckResults(index_.get(), 0u);
}
TEST_F(LinearMapSearchTest, UpdateDocuments) {
const std::map<std::string, std::vector<ContentWithId>> data_to_register = {
{"id1", {{"cid1", "id1"}, {"cid2", "tag1a"}, {"cid3", "tag1b"}}},
{"xyz", {{"cid4", "xyz"}}}};
std::vector<Data> data = CreateTestData(data_to_register);
EXPECT_EQ(data.size(), 2u);
AddOrUpdate(index_.get(), data);
GetSizeAndCheckResults(index_.get(), 2u);
const std::map<std::string, std::vector<ContentWithId>>
update_data_to_register = {{"id1",
{{"update cid1", "update id1"},
{"update cid2", "update tag1a"},
{"update cid3", "update tag1b"}}},
{"xyz", {}},
{"nonexistid", {}}};
std::vector<Data> update_data = CreateTestData(update_data_to_register);
EXPECT_EQ(update_data.size(), 3u);
UpdateDocumentsAndCheckResults(index_.get(), update_data, 1u);
GetSizeAndCheckResults(index_.get(), 1u);
}
} // namespace ash::local_search_service