Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
39
libs/search/search_tests/CMakeLists.txt
Normal file
39
libs/search/search_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
project(search_tests)
|
||||
|
||||
set(SRC
|
||||
algos_tests.cpp
|
||||
bookmarks_processor_tests.cpp
|
||||
feature_offset_match_tests.cpp
|
||||
highlighting_tests.cpp
|
||||
house_detector_tests.cpp
|
||||
house_numbers_matcher_test.cpp
|
||||
interval_set_test.cpp
|
||||
keyword_lang_matcher_test.cpp
|
||||
keyword_matcher_test.cpp
|
||||
latlon_match_test.cpp
|
||||
localities_source_tests.cpp
|
||||
locality_finder_test.cpp
|
||||
locality_scorer_test.cpp
|
||||
locality_selector_test.cpp
|
||||
mem_search_index_tests.cpp
|
||||
point_rect_matcher_tests.cpp
|
||||
query_saver_tests.cpp
|
||||
ranking_tests.cpp
|
||||
results_tests.cpp
|
||||
region_info_getter_tests.cpp
|
||||
segment_tree_tests.cpp
|
||||
suggest_tests.cpp
|
||||
string_match_test.cpp
|
||||
text_index_tests.cpp
|
||||
utm_mgrs_coords_match_test.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
generator_tests_support
|
||||
platform_tests_support
|
||||
search_tests_support
|
||||
search
|
||||
kml
|
||||
)
|
||||
50
libs/search/search_tests/algos_tests.cpp
Normal file
50
libs/search/search_tests/algos_tests.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/algos.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CompWithEqual
|
||||
{
|
||||
bool Less(int i1, int i2) const { return i1 <= i2; }
|
||||
bool Greater(int i1, int i2) const { return i1 >= i2; }
|
||||
};
|
||||
|
||||
void TestLongestSequence(int in[], size_t inSz, int eta[])
|
||||
{
|
||||
vector<int> res;
|
||||
search::LongestSubsequence(vector<int>(in, in + inSz), back_inserter(res), CompWithEqual());
|
||||
reverse(res.begin(), res.end());
|
||||
TEST(equal(res.begin(), res.end(), eta), (res));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(LS_Smoke)
|
||||
{
|
||||
{
|
||||
int arr[] = {1};
|
||||
TestLongestSequence(arr, ARRAY_SIZE(arr), arr);
|
||||
}
|
||||
|
||||
{
|
||||
int arr[] = {1, 2};
|
||||
TestLongestSequence(arr, ARRAY_SIZE(arr), arr);
|
||||
}
|
||||
|
||||
{
|
||||
int arr[] = {2, 1};
|
||||
TestLongestSequence(arr, ARRAY_SIZE(arr), arr);
|
||||
}
|
||||
|
||||
{
|
||||
int arr[] = {1, 9, 2, 3, 8, 0, 7, -1, 7, -2, 7};
|
||||
int res[] = {1, 2, 3, 7, 7, 7};
|
||||
TestLongestSequence(arr, ARRAY_SIZE(arr), res);
|
||||
}
|
||||
}
|
||||
203
libs/search/search_tests/bookmarks_processor_tests.cpp
Normal file
203
libs/search/search_tests/bookmarks_processor_tests.cpp
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "generator/generator_tests_support/test_with_classificator.hpp"
|
||||
|
||||
#include "search/bookmarks/data.hpp"
|
||||
#include "search/bookmarks/processor.hpp"
|
||||
#include "search/emitter.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/search_string_utils.hpp"
|
||||
|
||||
#include "base/cancellable.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bookmarks_processor_tests
|
||||
{
|
||||
using namespace search::bookmarks;
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
|
||||
using Ids = vector<Id>;
|
||||
|
||||
string const kLocale = "en";
|
||||
|
||||
class BookmarksProcessorTest : public generator::tests_support::TestWithClassificator
|
||||
{
|
||||
public:
|
||||
BookmarksProcessorTest() : m_processor(m_emitter, m_cancellable) {}
|
||||
|
||||
Processor & GetProcessor() { return m_processor; }
|
||||
|
||||
void Add(Id const & id, GroupId const & group, kml::BookmarkData const & data)
|
||||
{
|
||||
Doc const doc(data, kLocale);
|
||||
m_processor.Add(id, doc);
|
||||
AttachToGroup(id, group);
|
||||
}
|
||||
|
||||
void Erase(Id const & id) { m_processor.Erase(id); }
|
||||
|
||||
void Update(Id const & id, kml::BookmarkData const & data)
|
||||
{
|
||||
Doc const doc(data, kLocale);
|
||||
m_processor.Update(id, doc);
|
||||
}
|
||||
|
||||
void AttachToGroup(Id const & id, GroupId const & group) { m_processor.AttachToGroup(id, group); }
|
||||
void DetachFromGroup(Id const & id, GroupId const & group) { m_processor.DetachFromGroup(id, group); }
|
||||
|
||||
Ids Search(string const & query, GroupId const & groupId = kInvalidGroupId)
|
||||
{
|
||||
m_emitter.Init([](::search::Results const & /* results */) {} /* onResults */);
|
||||
|
||||
vector<strings::UniString> tokens;
|
||||
auto const isPrefix = TokenizeStringAndCheckIfLastTokenIsPrefix(query, tokens);
|
||||
|
||||
Processor::Params params;
|
||||
params.Init(query, tokens, isPrefix);
|
||||
|
||||
params.m_groupId = groupId;
|
||||
|
||||
m_processor.Search(params);
|
||||
Ids ids;
|
||||
for (auto const & result : m_emitter.GetResults().GetBookmarksResults())
|
||||
ids.emplace_back(result.m_id);
|
||||
return ids;
|
||||
}
|
||||
|
||||
protected:
|
||||
Emitter m_emitter;
|
||||
base::Cancellable m_cancellable;
|
||||
Processor m_processor;
|
||||
};
|
||||
|
||||
kml::BookmarkData MakeBookmarkData(string const & name, string const & customName, string const & description,
|
||||
vector<string> const & types)
|
||||
{
|
||||
kml::BookmarkData b;
|
||||
b.m_name = {{kml::kDefaultLangCode, name}};
|
||||
b.m_customName = {{kml::kDefaultLangCode, customName}};
|
||||
b.m_description = {{kml::kDefaultLangCode, description}};
|
||||
b.m_featureTypes.reserve(types.size());
|
||||
|
||||
auto const & c = classif();
|
||||
for (auto const & typeString : types)
|
||||
{
|
||||
auto const t = c.GetTypeByPath(strings::Tokenize(typeString, "-"));
|
||||
CHECK_NOT_EQUAL(t, 0, ());
|
||||
b.m_featureTypes.emplace_back(c.GetIndexForType(t));
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(BookmarksProcessorTest, Smoke)
|
||||
{
|
||||
GetProcessor().EnableIndexingOfDescriptions(true);
|
||||
|
||||
Add(Id{10}, GroupId{0},
|
||||
MakeBookmarkData("Double R Diner" /* name */, "2R Diner" /* customName */,
|
||||
"They've got a cherry pie there that'll kill ya!" /* description */,
|
||||
{"amenity-cafe"} /* types */));
|
||||
|
||||
Add(Id{18}, GroupId{0},
|
||||
MakeBookmarkData("Silver Mustang Casino" /* name */, "Ag Mustang" /* customName */,
|
||||
"Joyful place, owners Bradley and Rodney are very friendly!" /* description */,
|
||||
{"amenity-casino"} /* types */));
|
||||
Add(Id{20}, GroupId{1},
|
||||
MakeBookmarkData("Great Northern Hotel" /* name */, "N Hotel" /* customName */,
|
||||
"Clean place with a reasonable price" /* description */, {"tourism-hotel"} /* types */));
|
||||
|
||||
TEST_EQUAL(Search("R&R food"), Ids{}, ());
|
||||
GetProcessor().EnableIndexingOfBookmarkGroup(GroupId{0}, true /* enable */);
|
||||
TEST_EQUAL(Search("R&R food"), Ids({10}), ());
|
||||
GetProcessor().EnableIndexingOfBookmarkGroup(GroupId{0}, false /* enable */);
|
||||
TEST_EQUAL(Search("R&R food"), Ids{}, ());
|
||||
GetProcessor().EnableIndexingOfBookmarkGroup(GroupId{0}, true /* enable */);
|
||||
TEST_EQUAL(Search("R&R food"), Ids({10}), ());
|
||||
|
||||
GetProcessor().EnableIndexingOfBookmarkGroup(GroupId{1}, true /* enable */);
|
||||
|
||||
TEST_EQUAL(Search("cherry pie"), Ids({10}), ());
|
||||
TEST_EQUAL(Search("great silver hotel"), Ids({20, 18}), ());
|
||||
TEST_EQUAL(Search("double r cafe"), Ids({10}), ());
|
||||
TEST_EQUAL(Search("dine"), Ids({10}), ());
|
||||
TEST_EQUAL(Search("2R"), Ids({10}), ());
|
||||
TEST_EQUAL(Search("Ag"), Ids({18}), ());
|
||||
|
||||
TEST_EQUAL(Search("place"), Ids({20, 18}), ());
|
||||
TEST_EQUAL(Search("place", GroupId{0}), Ids({18}), ());
|
||||
DetachFromGroup(Id{20}, GroupId{1});
|
||||
AttachToGroup(Id{20}, GroupId{0});
|
||||
TEST_EQUAL(Search("place", GroupId{0}), Ids({20, 18}), ());
|
||||
|
||||
Update(20, MakeBookmarkData("Great Northern Hotel" /* name */, "N Hotel" /* customName */,
|
||||
"Clean establishment with a reasonable price" /* description */,
|
||||
{"tourism-hotel"} /* types */));
|
||||
TEST_EQUAL(Search("place", GroupId{0}), Ids({18}), ());
|
||||
|
||||
GetProcessor().Reset();
|
||||
TEST_EQUAL(Search("place", GroupId{0}), Ids{}, ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(BookmarksProcessorTest, SearchByType)
|
||||
{
|
||||
GetProcessor().EnableIndexingOfDescriptions(true);
|
||||
GetProcessor().EnableIndexingOfBookmarkGroup(GroupId{0}, true /* enable */);
|
||||
|
||||
Add(Id{10}, GroupId{0},
|
||||
MakeBookmarkData("Double R Diner" /* name */, "2R Diner" /* customName */,
|
||||
"They've got a cherry pie there that'll kill ya!" /* description */,
|
||||
{"amenity-cafe"} /* types */));
|
||||
|
||||
Add(Id{12}, GroupId{0},
|
||||
MakeBookmarkData("" /* name */, "" /* customName */, "" /* description */, {"amenity-cafe"} /* types */));
|
||||
|
||||
Add(Id{0}, GroupId{0}, MakeBookmarkData("" /* name */, "" /* customName */, "" /* description */, {} /* types */));
|
||||
|
||||
TEST_EQUAL(Search("cafe", GroupId{0}), Ids({12, 10}), ());
|
||||
TEST_EQUAL(Search("кафе", GroupId{0}), Ids{}, ());
|
||||
TEST_EQUAL(Search("", GroupId{0}), Ids{}, ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(BookmarksProcessorTest, IndexDescriptions)
|
||||
{
|
||||
GetProcessor().EnableIndexingOfDescriptions(true);
|
||||
GetProcessor().EnableIndexingOfBookmarkGroup(GroupId{0}, true /* enable */);
|
||||
|
||||
Add(Id{10}, GroupId{0},
|
||||
MakeBookmarkData("Double R Diner" /* name */, "2R Diner" /* customName */,
|
||||
"They've got a cherry pie there that'll kill ya!" /* description */,
|
||||
{"amenity-cafe"} /* types */));
|
||||
TEST_EQUAL(Search("diner"), Ids({10}), ());
|
||||
TEST_EQUAL(Search("cherry pie"), Ids({10}), ());
|
||||
|
||||
DetachFromGroup(Id{10}, GroupId{0});
|
||||
Erase(Id{10});
|
||||
TEST_EQUAL(Search("diner"), Ids{}, ());
|
||||
TEST_EQUAL(Search("cherry pie"), Ids{}, ());
|
||||
|
||||
GetProcessor().EnableIndexingOfDescriptions(false);
|
||||
Add(Id{10}, GroupId{0},
|
||||
MakeBookmarkData("Double R Diner" /* name */, "2R Diner" /* customName */,
|
||||
"They've got a cherry pie there that'll kill ya!" /* description */,
|
||||
{"amenity-cafe"} /* types */));
|
||||
TEST_EQUAL(Search("diner"), Ids({10}), ());
|
||||
TEST_EQUAL(Search("cherry pie"), Ids{}, ());
|
||||
|
||||
// Results for already indexed bookmarks don't change.
|
||||
GetProcessor().EnableIndexingOfDescriptions(true);
|
||||
TEST_EQUAL(Search("diner"), Ids({10}), ());
|
||||
TEST_EQUAL(Search("cherry pie"), Ids{}, ());
|
||||
|
||||
DetachFromGroup(Id{10}, GroupId{0});
|
||||
Erase(Id{10});
|
||||
TEST_EQUAL(Search("diner"), Ids{}, ());
|
||||
TEST_EQUAL(Search("cherry pie"), Ids{}, ());
|
||||
}
|
||||
|
||||
} // namespace bookmarks_processor_tests
|
||||
102
libs/search/search_tests/feature_offset_match_tests.cpp
Normal file
102
libs/search/search_tests/feature_offset_match_tests.cpp
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/feature_offset_match.hpp"
|
||||
|
||||
#include "indexer/search_string_utils.hpp"
|
||||
#include "indexer/trie.hpp"
|
||||
|
||||
#include "base/mem_trie.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace feature_offset_match_tests
|
||||
{
|
||||
using namespace strings;
|
||||
using namespace std;
|
||||
|
||||
using Key = UniString;
|
||||
using Value = uint32_t;
|
||||
using ValueList = base::VectorValues<Value>;
|
||||
using Trie = base::MemTrie<Key, ValueList>;
|
||||
using DFA = LevenshteinDFA;
|
||||
using PrefixDFA = PrefixDFAModifier<DFA>;
|
||||
|
||||
UNIT_TEST(MatchInTrieTest)
|
||||
{
|
||||
Trie trie;
|
||||
|
||||
vector<pair<string, uint32_t>> const data = {{"hotel", 1}, {"homel", 2}, {"hotel", 3}};
|
||||
|
||||
for (auto const & kv : data)
|
||||
trie.Add(MakeUniString(kv.first), kv.second);
|
||||
|
||||
trie::MemTrieIterator<Key, ValueList> const rootIterator(trie.GetRootIterator());
|
||||
map<uint32_t, bool> vals;
|
||||
auto saveResult = [&vals](uint32_t v, bool exactMatch) { vals[v] = exactMatch; };
|
||||
|
||||
auto const hotelDFA = DFA("hotel", 1 /* maxErrors */);
|
||||
search::impl::MatchInTrie(rootIterator, nullptr, 0 /* prefixSize */, hotelDFA, saveResult);
|
||||
TEST(vals.at(1), (vals));
|
||||
TEST(vals.at(3), (vals));
|
||||
TEST(!vals.at(2), (vals));
|
||||
|
||||
vals.clear();
|
||||
auto const homelDFA = DFA("homel", 1 /* maxErrors */);
|
||||
search::impl::MatchInTrie(rootIterator, nullptr, 0 /* prefixSize */, homelDFA, saveResult);
|
||||
TEST(vals.at(2), (vals));
|
||||
TEST(!vals.at(1), (vals));
|
||||
TEST(!vals.at(3), (vals));
|
||||
|
||||
vals.clear();
|
||||
auto const hoDFA = search::BuildLevenshteinDFA(MakeUniString("ho"));
|
||||
// If somebody cares about return value - it indicates existing of node in trie, but not the actual values.
|
||||
TEST(search::impl::MatchInTrie(rootIterator, nullptr, 0 /* prefixSize */, hoDFA, saveResult), ());
|
||||
TEST(vals.empty(), (vals));
|
||||
}
|
||||
|
||||
UNIT_TEST(MatchPrefixInTrieTest)
|
||||
{
|
||||
Trie trie;
|
||||
|
||||
vector<pair<string, uint32_t>> const data = {{"лермонтовъ", 1}, {"лермонтово", 2}};
|
||||
|
||||
for (auto const & kv : data)
|
||||
trie.Add(MakeUniString(kv.first), kv.second);
|
||||
|
||||
trie::MemTrieIterator<Key, ValueList> const rootIterator(trie.GetRootIterator());
|
||||
map<uint32_t, bool> vals;
|
||||
auto saveResult = [&vals](uint32_t v, bool exactMatch) { vals[v] = exactMatch; };
|
||||
|
||||
{
|
||||
vals.clear();
|
||||
auto const lermontov = PrefixDFA(DFA("лермонтовъ", 2 /* maxErrors */));
|
||||
search::impl::MatchInTrie(rootIterator, nullptr, 0 /* prefixSize */, lermontov, saveResult);
|
||||
TEST(vals.at(1), (vals));
|
||||
TEST(!vals.at(2), (vals));
|
||||
}
|
||||
{
|
||||
vals.clear();
|
||||
auto const lermontovo = PrefixDFA(DFA("лермонтово", 2 /* maxErrors */));
|
||||
search::impl::MatchInTrie(rootIterator, nullptr, 0 /* prefixSize */, lermontovo, saveResult);
|
||||
TEST(vals.at(2), (vals));
|
||||
TEST(!vals.at(1), (vals));
|
||||
}
|
||||
{
|
||||
vals.clear();
|
||||
auto const commonPrexif = PrefixDFA(DFA("лермонтов", 2 /* maxErrors */));
|
||||
search::impl::MatchInTrie(rootIterator, nullptr, 0 /* prefixSize */, commonPrexif, saveResult);
|
||||
TEST(vals.at(2), (vals));
|
||||
TEST(vals.at(1), (vals));
|
||||
}
|
||||
{
|
||||
vals.clear();
|
||||
auto const commonPrexif = PrefixDFA(DFA("лер", 2 /* maxErrors */));
|
||||
search::impl::MatchInTrie(rootIterator, nullptr, 0 /* prefixSize */, commonPrexif, saveResult);
|
||||
TEST(vals.at(2), (vals));
|
||||
TEST(vals.at(1), (vals));
|
||||
}
|
||||
}
|
||||
} // namespace feature_offset_match_tests
|
||||
126
libs/search/search_tests/highlighting_tests.cpp
Normal file
126
libs/search/search_tests/highlighting_tests.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/highlighting.hpp"
|
||||
|
||||
#include "indexer/feature_covering.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
using TestResult = pair<uint16_t, uint16_t>;
|
||||
using TokensVector = vector<strings::UniString>;
|
||||
using TestResultVector = vector<TestResult>;
|
||||
|
||||
struct TestData
|
||||
{
|
||||
string m_input;
|
||||
TokensVector m_lowTokens;
|
||||
TestResultVector m_results;
|
||||
|
||||
TestData(char const * inp, char const ** lToks, size_t lowTokCount, size_t resCount, ...)
|
||||
{
|
||||
m_input = inp;
|
||||
for (size_t i = 0; i < lowTokCount; ++i)
|
||||
m_lowTokens.push_back(strings::MakeUniString(lToks[i]));
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, resCount);
|
||||
for (size_t i = 0; i < resCount; ++i)
|
||||
{
|
||||
uint16_t const pos = va_arg(ap, int);
|
||||
uint16_t const len = va_arg(ap, int);
|
||||
AddResult(pos, len);
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void AddResult(uint16_t pos, uint16_t len) { m_results.emplace_back(pos, len); }
|
||||
};
|
||||
|
||||
class CheckRange
|
||||
{
|
||||
size_t m_idx;
|
||||
TestResultVector const & m_results;
|
||||
|
||||
public:
|
||||
explicit CheckRange(TestResultVector const & results) : m_idx(0), m_results(results) {}
|
||||
|
||||
~CheckRange() { TEST_EQUAL(m_idx, m_results.size(), ()); }
|
||||
|
||||
void operator()(pair<uint16_t, uint16_t> const & range)
|
||||
{
|
||||
ASSERT(m_idx < m_results.size(), ());
|
||||
TEST_EQUAL(range, m_results[m_idx], ());
|
||||
++m_idx;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(SearchStringTokensIntersectionRange)
|
||||
{
|
||||
char const * str0 = "улица Карла Маркса";
|
||||
char const * str1 = "ул. Карла Маркса";
|
||||
char const * str2 = "Карлов Мост";
|
||||
char const * str3 = "цирк";
|
||||
char const * str4 = "Беларусь Минск Цирк";
|
||||
char const * str5 = "Беларусь, Цирк, Минск";
|
||||
char const * str6 = "";
|
||||
char const * str7 = ".-карла маркса ";
|
||||
char const * str8 = ".-карла, маркса";
|
||||
char const * str9 = "улица Карла Либнехта";
|
||||
char const * str10 = "улица Карбышева";
|
||||
char const * str11 = "улица Максима Богдановича";
|
||||
|
||||
char const * lowTokens0[] = {"карла", "маркса"};
|
||||
char const * lowTokens1[] = {"цирк", "минск"};
|
||||
char const * lowTokens2[] = {"ар"};
|
||||
char const * lowTokens3[] = {"карла", "м"};
|
||||
char const * lowTokens4[] = {"кар", "мар"};
|
||||
char const * lowTokens5[] = {"минск", "цирк"};
|
||||
char const * lowTokens6[] = {""};
|
||||
char const * lowTokens7[] = {"цирк", ""};
|
||||
char const * lowTokens8[] = {"ул", "кар"};
|
||||
char const * lowTokens9[] = {"ул", "бог"};
|
||||
|
||||
vector<TestData> tests;
|
||||
// fill test data
|
||||
tests.push_back(TestData(str0, lowTokens0, 2, 2, 6, 5, 12, 6));
|
||||
tests.push_back(TestData(str1, lowTokens0, 2, 2, 4, 5, 10, 6));
|
||||
tests.push_back(TestData(str2, lowTokens0, 2, 0));
|
||||
tests.push_back(TestData(str10, lowTokens8, 2, 2, 0, 2, 6, 3));
|
||||
tests.push_back(TestData(str4, lowTokens1, 2, 2, 9, 5, 15, 4));
|
||||
|
||||
tests.push_back(TestData(str0, lowTokens2, 1, 0));
|
||||
tests.push_back(TestData(str2, lowTokens2, 1, 0));
|
||||
tests.push_back(TestData(str0, lowTokens3, 2, 2, 6, 5, 12, 1));
|
||||
tests.push_back(TestData(str1, lowTokens3, 2, 2, 4, 5, 10, 1));
|
||||
tests.push_back(TestData(str0, lowTokens4, 2, 2, 6, 3, 12, 3));
|
||||
|
||||
tests.push_back(TestData(str3, lowTokens1, 2, 1, 0, 4));
|
||||
tests.push_back(TestData(str5, lowTokens5, 2, 2, 10, 4, 16, 5));
|
||||
tests.push_back(TestData(str6, lowTokens6, 1, 0));
|
||||
tests.push_back(TestData(str6, lowTokens7, 2, 0));
|
||||
tests.push_back(TestData(str5, lowTokens7, 2, 1, 10, 4));
|
||||
|
||||
tests.push_back(TestData(str8, lowTokens3, 2, 2, 2, 5, 9, 1));
|
||||
tests.push_back(TestData(str7, lowTokens0, 2, 2, 2, 5, 8, 6));
|
||||
tests.push_back(TestData(str0, lowTokens8, 2, 2, 0, 2, 6, 3));
|
||||
tests.push_back(TestData(str9, lowTokens8, 2, 2, 0, 2, 6, 3));
|
||||
tests.push_back(TestData(str11, lowTokens9, 2, 2, 0, 2, 14, 3));
|
||||
|
||||
for (TestData const & data : tests)
|
||||
{
|
||||
search::SearchStringTokensIntersectionRanges(data.m_input, data.m_lowTokens.begin(), data.m_lowTokens.end(),
|
||||
CheckRange(data.m_results));
|
||||
}
|
||||
}
|
||||
494
libs/search/search_tests/house_detector_tests.cpp
Normal file
494
libs/search/search_tests/house_detector_tests.cpp
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/house_detector.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_header.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
#include "indexer/search_string_utils.hpp"
|
||||
|
||||
#include "platform/local_country_file.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace house_detector_tests
|
||||
{
|
||||
using namespace std;
|
||||
using platform::LocalCountryFile;
|
||||
|
||||
class StreetIDsByName
|
||||
{
|
||||
vector<FeatureID> vect;
|
||||
|
||||
public:
|
||||
vector<string> streetNames;
|
||||
|
||||
void operator()(FeatureType & f)
|
||||
{
|
||||
if (f.GetGeomType() == feature::GeomType::Line)
|
||||
{
|
||||
string_view const name = f.GetName(StringUtf8Multilang::kDefaultCode);
|
||||
if (!name.empty() && base::IsExist(streetNames, name))
|
||||
vect.push_back(f.GetID());
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetCount() const { return vect.size(); }
|
||||
|
||||
vector<FeatureID> const & GetFeatureIDs()
|
||||
{
|
||||
sort(vect.begin(), vect.end());
|
||||
return vect;
|
||||
}
|
||||
};
|
||||
|
||||
class CollectStreetIDs
|
||||
{
|
||||
static bool GetKey(string_view name, string & key)
|
||||
{
|
||||
TEST(!name.empty(), ());
|
||||
key = strings::ToUtf8(search::GetStreetNameAsKey(name, false /* ignoreStreetSynonyms */));
|
||||
|
||||
if (key.empty())
|
||||
{
|
||||
LOG(LWARNING, ("Empty street key for name", name));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
using Cont = map<string, vector<FeatureID>>;
|
||||
Cont m_ids;
|
||||
vector<FeatureID> m_empty;
|
||||
|
||||
public:
|
||||
void operator()(FeatureType & f)
|
||||
{
|
||||
if (f.GetGeomType() == feature::GeomType::Line)
|
||||
{
|
||||
string_view const name = f.GetName(StringUtf8Multilang::kDefaultCode);
|
||||
if (!name.empty() && ftypes::IsWayChecker::Instance()(f))
|
||||
{
|
||||
string key;
|
||||
if (GetKey(name, key))
|
||||
m_ids[key].push_back(f.GetID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Finish()
|
||||
{
|
||||
for (Cont::iterator i = m_ids.begin(); i != m_ids.end(); ++i)
|
||||
sort(i->second.begin(), i->second.end());
|
||||
}
|
||||
|
||||
vector<FeatureID> const & Get(string const & key) const
|
||||
{
|
||||
Cont::const_iterator i = m_ids.find(key);
|
||||
return (i == m_ids.end() ? m_empty : i->second);
|
||||
}
|
||||
};
|
||||
|
||||
UNIT_TEST(HS_ParseNumber)
|
||||
{
|
||||
using Number = search::ParsedNumber;
|
||||
|
||||
{
|
||||
Number n("135");
|
||||
TEST(n.IsOdd(), ());
|
||||
TEST_EQUAL(n.GetIntNumber(), 135, ());
|
||||
|
||||
Number n1("133");
|
||||
Number n2("137");
|
||||
TEST(n.IsIntersect(n1, 2), ());
|
||||
TEST(!n.IsIntersect(n1, 1), ());
|
||||
TEST(n.IsIntersect(n2, 2), ());
|
||||
TEST(!n.IsIntersect(n2, 1), ());
|
||||
}
|
||||
|
||||
{
|
||||
Number n("135 1к/2");
|
||||
TEST(n.IsOdd(), ());
|
||||
TEST_EQUAL(n.GetIntNumber(), 135, ());
|
||||
|
||||
TEST(!n.IsIntersect(Number("134")), ());
|
||||
TEST(!n.IsIntersect(Number("136")), ());
|
||||
}
|
||||
|
||||
{
|
||||
Number n("135A");
|
||||
TEST(n.IsOdd(), ());
|
||||
TEST_EQUAL(n.GetIntNumber(), 135, ());
|
||||
|
||||
TEST(!n.IsIntersect(Number("134")), ());
|
||||
TEST(!n.IsIntersect(Number("136")), ());
|
||||
}
|
||||
|
||||
{
|
||||
Number n("135-к1", false);
|
||||
TEST(n.IsOdd(), ());
|
||||
TEST_EQUAL(n.GetIntNumber(), 135, ());
|
||||
|
||||
TEST(!n.IsIntersect(Number("134")), ());
|
||||
TEST(!n.IsIntersect(Number("136")), ());
|
||||
}
|
||||
|
||||
{
|
||||
Number n("135-12", false);
|
||||
TEST(n.IsOdd(), ());
|
||||
TEST_EQUAL(n.GetIntNumber(), 135, ());
|
||||
|
||||
TEST(!n.IsIntersect(Number("134")), ());
|
||||
TEST(!n.IsIntersect(Number("136")), ());
|
||||
}
|
||||
|
||||
{
|
||||
Number n("135-24", true);
|
||||
TEST(!n.IsOdd(), ());
|
||||
TEST_EQUAL(n.GetIntNumber(), 13524, ());
|
||||
}
|
||||
|
||||
{
|
||||
Number n("135;133;131");
|
||||
TEST(n.IsOdd(), ());
|
||||
TEST_EQUAL(n.GetIntNumber(), 131, ());
|
||||
|
||||
for (int i = 131; i <= 135; ++i)
|
||||
TEST(n.IsIntersect(Number(strings::to_string(i))), ());
|
||||
TEST(!n.IsIntersect(Number("130")), ());
|
||||
TEST(!n.IsIntersect(Number("136")), ());
|
||||
}
|
||||
|
||||
{
|
||||
Number n("6-10", false);
|
||||
TEST(!n.IsOdd(), ());
|
||||
TEST_EQUAL(n.GetIntNumber(), 6, ());
|
||||
|
||||
for (int i = 6; i <= 10; ++i)
|
||||
TEST(n.IsIntersect(Number(strings::to_string(i))), ());
|
||||
|
||||
TEST(!n.IsIntersect(Number("5")), ());
|
||||
TEST(!n.IsIntersect(Number("11")), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(HS_StreetsMerge)
|
||||
{
|
||||
classificator::Load();
|
||||
|
||||
FrozenDataSource dataSource;
|
||||
LocalCountryFile localFile(LocalCountryFile::MakeForTesting("minsk-pass"));
|
||||
// Clean indexes to avoid jenkins errors.
|
||||
platform::CountryIndexes::DeleteFromDisk(localFile);
|
||||
|
||||
auto const p = dataSource.Register(localFile);
|
||||
TEST(p.first.IsAlive(), ());
|
||||
TEST_EQUAL(MwmSet::RegResult::Success, p.second, ());
|
||||
|
||||
{
|
||||
search::HouseDetector houser(dataSource);
|
||||
StreetIDsByName toDo;
|
||||
toDo.streetNames.push_back("улица Володарского");
|
||||
dataSource.ForEachInScale([&toDo](FeatureType & ft) { toDo(ft); }, scales::GetUpperScale());
|
||||
houser.LoadStreets(toDo.GetFeatureIDs());
|
||||
TEST_EQUAL(houser.MergeStreets(), 1, ());
|
||||
}
|
||||
|
||||
{
|
||||
search::HouseDetector houser(dataSource);
|
||||
StreetIDsByName toDo;
|
||||
toDo.streetNames.push_back("Московская улица");
|
||||
dataSource.ForEachInScale([&toDo](FeatureType & ft) { toDo(ft); }, scales::GetUpperScale());
|
||||
houser.LoadStreets(toDo.GetFeatureIDs());
|
||||
TEST_GREATER_OR_EQUAL(houser.MergeStreets(), 1, ());
|
||||
TEST_LESS_OR_EQUAL(houser.MergeStreets(), 3, ());
|
||||
}
|
||||
|
||||
{
|
||||
search::HouseDetector houser(dataSource);
|
||||
StreetIDsByName toDo;
|
||||
toDo.streetNames.push_back("проспект Независимости");
|
||||
toDo.streetNames.push_back("Московская улица");
|
||||
dataSource.ForEachInScale([&toDo](FeatureType & ft) { toDo(ft); }, scales::GetUpperScale());
|
||||
houser.LoadStreets(toDo.GetFeatureIDs());
|
||||
TEST_GREATER_OR_EQUAL(houser.MergeStreets(), 1, ());
|
||||
TEST_LESS_OR_EQUAL(houser.MergeStreets(), 5, ());
|
||||
}
|
||||
|
||||
{
|
||||
search::HouseDetector houser(dataSource);
|
||||
StreetIDsByName toDo;
|
||||
toDo.streetNames.push_back("проспект Независимости");
|
||||
toDo.streetNames.push_back("Московская улица");
|
||||
toDo.streetNames.push_back("Вишнёвый переулок");
|
||||
toDo.streetNames.push_back("Студенческий переулок");
|
||||
toDo.streetNames.push_back("Полоцкий переулок");
|
||||
dataSource.ForEachInScale([&toDo](FeatureType & ft) { toDo(ft); }, scales::GetUpperScale());
|
||||
houser.LoadStreets(toDo.GetFeatureIDs());
|
||||
TEST_GREATER_OR_EQUAL(houser.MergeStreets(), 1, ());
|
||||
TEST_LESS_OR_EQUAL(houser.MergeStreets(), 8, ());
|
||||
}
|
||||
|
||||
{
|
||||
search::HouseDetector houser(dataSource);
|
||||
StreetIDsByName toDo;
|
||||
toDo.streetNames.push_back("проспект Независимости");
|
||||
toDo.streetNames.push_back("Московская улица");
|
||||
toDo.streetNames.push_back("улица Кирова");
|
||||
toDo.streetNames.push_back("улица Городской Вал");
|
||||
dataSource.ForEachInScale([&toDo](FeatureType & ft) { toDo(ft); }, scales::GetUpperScale());
|
||||
houser.LoadStreets(toDo.GetFeatureIDs());
|
||||
TEST_GREATER_OR_EQUAL(houser.MergeStreets(), 1, ());
|
||||
|
||||
// May vary according to the new minsk-pass data.
|
||||
int const count = houser.MergeStreets();
|
||||
TEST(count >= 10 && count <= 15, (count));
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
m2::PointD FindHouse(DataSource & dataSource, vector<string> const & streets, string const & houseName, double offset)
|
||||
{
|
||||
search::HouseDetector houser(dataSource);
|
||||
|
||||
StreetIDsByName toDo;
|
||||
toDo.streetNames = streets;
|
||||
dataSource.ForEachInScale([&toDo](FeatureType & ft) { toDo(ft); }, scales::GetUpperScale());
|
||||
|
||||
if (houser.LoadStreets(toDo.GetFeatureIDs()) > 0)
|
||||
TEST_GREATER(houser.MergeStreets(), 0, ());
|
||||
|
||||
houser.ReadAllHouses(offset);
|
||||
|
||||
vector<search::HouseResult> houses;
|
||||
houser.GetHouseForName(houseName, houses);
|
||||
|
||||
TEST_EQUAL(houses.size(), 1, (houses));
|
||||
return houses[0].m_house->GetPosition();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(HS_FindHouseSmoke)
|
||||
{
|
||||
classificator::Load();
|
||||
|
||||
FrozenDataSource dataSource;
|
||||
auto const p = dataSource.Register(LocalCountryFile::MakeForTesting("minsk-pass"));
|
||||
TEST(p.first.IsAlive(), ());
|
||||
TEST_EQUAL(MwmSet::RegResult::Success, p.second, ());
|
||||
|
||||
double constexpr epsPoints = 1.0E-4;
|
||||
{
|
||||
vector<string> streetName = {"Московская улица"};
|
||||
TEST_ALMOST_EQUAL_ABS(FindHouse(dataSource, streetName, "7", 100),
|
||||
m2::PointD(27.539850827603416406, 64.222406776416349317), epsPoints, ());
|
||||
}
|
||||
/// @todo HouseDetector used in tests only, so do not want to waste time here ..
|
||||
/*{
|
||||
vector<string> streetName = {"проспект Независимости"};
|
||||
TEST_ALMOST_EQUAL_ABS(FindHouse(dataSource, streetName, "10", 40),
|
||||
m2::PointD(27.551428582902474318, 64.234707387050306693), epsPoints, ());
|
||||
}*/
|
||||
{
|
||||
vector<string> streetName = {"улица Ленина"};
|
||||
|
||||
/// @todo This cases doesn't work, but should in new search algorithms.
|
||||
// m2::PointD pt = FindHouse(dataSource, streetName, "28", 50);
|
||||
// m2::PointD pt = FindHouse(dataSource, streetName, "30", 50);
|
||||
|
||||
m2::PointD pt = FindHouse(dataSource, streetName, "21", 50);
|
||||
TEST_ALMOST_EQUAL_ABS(pt, m2::PointD(27.56477391395549148, 64.234502198059132638), epsPoints, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(HS_StreetsCompare)
|
||||
{
|
||||
search::Street A, B;
|
||||
TEST(search::Street::IsSameStreets(&A, &B), ());
|
||||
string str[8][2] = {{"Московская", "Московская"},
|
||||
{"ул. Московская", "Московская ул."},
|
||||
{"ул. Московская", "Московская улица"},
|
||||
{"ул. Московская", "улица Московская"},
|
||||
{"ул. Московская", "площадь Московская"},
|
||||
{"ул. мОСКОВСКАЯ", "Московская улица"},
|
||||
{"Московская", "площадь Московская"},
|
||||
{"Московская ", "аллея Московская"}};
|
||||
for (size_t i = 0; i < ARRAY_SIZE(str); ++i)
|
||||
{
|
||||
A.SetName(str[i][0]);
|
||||
B.SetName(str[i][0]);
|
||||
TEST(search::Street::IsSameStreets(&A, &B), ());
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
string GetStreetKey(string_view name)
|
||||
{
|
||||
return strings::ToUtf8(search::GetStreetNameAsKey(name, false /* ignoreStreetSynonyms */));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(HS_StreetKey)
|
||||
{
|
||||
TEST_EQUAL("улицакрупскои", GetStreetKey("улица Крупской"), ());
|
||||
TEST_EQUAL("уручскаяул", GetStreetKey("Уручская ул."), ());
|
||||
TEST_EQUAL("пргазетыправда", GetStreetKey("Пр. Газеты Правда"), ());
|
||||
TEST_EQUAL("улицаякупалы", GetStreetKey("улица Я. Купалы"), ());
|
||||
TEST_EQUAL("францискаскоринытракт", GetStreetKey("Франциска Скорины Тракт"), ());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Address
|
||||
{
|
||||
string m_streetKey;
|
||||
string m_house;
|
||||
double m_lat, m_lon;
|
||||
|
||||
bool operator<(Address const & rhs) const { return (m_streetKey < rhs.m_streetKey); }
|
||||
};
|
||||
|
||||
void swap(Address & a1, Address & a2)
|
||||
{
|
||||
a1.m_streetKey.swap(a2.m_streetKey);
|
||||
a1.m_house.swap(a2.m_house);
|
||||
std::swap(a1.m_lat, a2.m_lat);
|
||||
std::swap(a1.m_lon, a2.m_lon);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(HS_MWMSearch)
|
||||
{
|
||||
// "Minsk", "Belarus", "Lithuania", "USA_New York", "USA_California"
|
||||
string const country = "minsk-pass";
|
||||
|
||||
string const path = base::JoinPath(GetPlatform().WritableDir(), country + ".addr");
|
||||
ifstream file(path.c_str());
|
||||
if (!file.good())
|
||||
{
|
||||
LOG(LWARNING, ("Address file not found", path));
|
||||
return;
|
||||
}
|
||||
|
||||
FrozenDataSource dataSource;
|
||||
auto p = dataSource.Register(LocalCountryFile::MakeForTesting(country));
|
||||
if (p.second != MwmSet::RegResult::Success)
|
||||
{
|
||||
LOG(LWARNING, ("MWM file not found"));
|
||||
return;
|
||||
}
|
||||
TEST(p.first.IsAlive(), ());
|
||||
|
||||
CollectStreetIDs streetIDs;
|
||||
dataSource.ForEachInScale(streetIDs, scales::GetUpperScale());
|
||||
streetIDs.Finish();
|
||||
|
||||
string line;
|
||||
vector<Address> addresses;
|
||||
while (file.good())
|
||||
{
|
||||
getline(file, line);
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
auto const v = strings::Tokenize(line, "|");
|
||||
|
||||
string key = GetStreetKey(v[0]);
|
||||
if (key.empty())
|
||||
continue;
|
||||
|
||||
addresses.push_back(Address());
|
||||
Address & a = addresses.back();
|
||||
|
||||
a.m_streetKey.swap(key);
|
||||
|
||||
// House number is in v[1], sometime it contains house name after comma.
|
||||
strings::SimpleTokenizer house(v[1], ",");
|
||||
TEST(house, ());
|
||||
a.m_house = *house;
|
||||
TEST(!a.m_house.empty(), ());
|
||||
|
||||
TEST(strings::to_double(v[2], a.m_lat), (v[2]));
|
||||
TEST(strings::to_double(v[3], a.m_lon), (v[3]));
|
||||
}
|
||||
|
||||
sort(addresses.begin(), addresses.end());
|
||||
|
||||
search::HouseDetector detector(dataSource);
|
||||
size_t all = 0, matched = 0, notMatched = 0;
|
||||
|
||||
size_t const percent = max(size_t(1), addresses.size() / 100);
|
||||
for (size_t i = 0; i < addresses.size(); ++i)
|
||||
{
|
||||
if (i % percent == 0)
|
||||
LOG(LINFO, ("%", i / percent, "%"));
|
||||
|
||||
Address const & a = addresses[i];
|
||||
|
||||
vector<FeatureID> const & streets = streetIDs.Get(a.m_streetKey);
|
||||
if (streets.empty())
|
||||
{
|
||||
LOG(LWARNING, ("Missing street in mwm", a.m_streetKey));
|
||||
continue;
|
||||
}
|
||||
|
||||
++all;
|
||||
|
||||
detector.LoadStreets(streets);
|
||||
detector.MergeStreets();
|
||||
detector.ReadAllHouses();
|
||||
|
||||
vector<search::HouseResult> houses;
|
||||
detector.GetHouseForName(a.m_house, houses);
|
||||
if (houses.empty())
|
||||
{
|
||||
LOG(LINFO, ("No houses", a.m_streetKey, a.m_house));
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
size_t const count = houses.size();
|
||||
for (; j < count; ++j)
|
||||
{
|
||||
search::House const * h = houses[j].m_house;
|
||||
m2::PointD p = h->GetPosition();
|
||||
p.x = mercator::XToLon(p.x);
|
||||
p.y = mercator::YToLat(p.y);
|
||||
|
||||
// double constexpr eps = 3.0E-4;
|
||||
// if (fabs(p.x - a.m_lon) < eps && fabs(p.y - a.m_lat) < eps)
|
||||
if (ms::DistanceOnEarth(a.m_lat, a.m_lon, p.y, p.x) < 3.0)
|
||||
{
|
||||
++matched;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j == count)
|
||||
{
|
||||
++notMatched;
|
||||
LOG(LINFO, ("Bad matched", a.m_streetKey, a.m_house));
|
||||
}
|
||||
}
|
||||
|
||||
LOG(LINFO, ("Matched =", matched, "Not matched =", notMatched, "Not found =", all - matched - notMatched));
|
||||
LOG(LINFO, ("All count =", all, "Percent matched =", matched / double(all)));
|
||||
}
|
||||
} // namespace house_detector_tests
|
||||
273
libs/search/search_tests/house_numbers_matcher_test.cpp
Normal file
273
libs/search/search_tests/house_numbers_matcher_test.cpp
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/house_numbers_matcher.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace house_number_matcher_test
|
||||
{
|
||||
using namespace search::house_numbers;
|
||||
using namespace strings;
|
||||
using namespace std;
|
||||
|
||||
bool HouseNumbersMatch(string const & houseNumber, string const & query, bool queryIsPrefix = false)
|
||||
{
|
||||
vector<Token> queryParse;
|
||||
ParseQuery(MakeUniString(query), queryIsPrefix, queryParse);
|
||||
return search::house_numbers::HouseNumbersMatch(MakeUniString(houseNumber), queryParse);
|
||||
}
|
||||
|
||||
bool HouseNumbersMatchConscription(string const & houseNumber, string const & query, bool queryIsPrefix = false)
|
||||
{
|
||||
vector<Token> queryParse;
|
||||
ParseQuery(MakeUniString(query), queryIsPrefix, queryParse);
|
||||
return search::house_numbers::HouseNumbersMatchConscription(MakeUniString(houseNumber), queryParse);
|
||||
}
|
||||
|
||||
bool HouseNumbersMatchRange(string_view const & hnRange, string const & query, feature::InterpolType interpol)
|
||||
{
|
||||
vector<Token> queryParse;
|
||||
ParseQuery(MakeUniString(query), false /* isPrefix */, queryParse);
|
||||
return search::house_numbers::HouseNumbersMatchRange(hnRange, queryParse, interpol);
|
||||
}
|
||||
|
||||
bool CheckTokenizer(string const & utf8s, vector<string> const & expected)
|
||||
{
|
||||
UniString utf32s = MakeUniString(utf8s);
|
||||
vector<Token> tokens;
|
||||
Tokenize(utf32s, false /* isPrefix */, tokens);
|
||||
|
||||
vector<string> actual;
|
||||
for (auto const & token : tokens)
|
||||
actual.push_back(ToUtf8(token.m_value));
|
||||
if (actual != expected)
|
||||
{
|
||||
LOG(LINFO, ("actual:", actual, "expected:", expected));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckParser(string const & utf8s, string const & expected)
|
||||
{
|
||||
vector<vector<Token>> parses;
|
||||
ParseHouseNumber(MakeUniString(utf8s), parses);
|
||||
|
||||
if (parses.size() != 1)
|
||||
{
|
||||
LOG(LINFO, ("Actual:", parses, "expected:", expected));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const & parse = parses[0];
|
||||
string actual;
|
||||
for (size_t i = 0; i < parse.size(); ++i)
|
||||
{
|
||||
actual.append(ToUtf8(parse[i].m_value));
|
||||
if (i + 1 != parse.size())
|
||||
actual.push_back(' ');
|
||||
}
|
||||
|
||||
if (actual != expected)
|
||||
{
|
||||
LOG(LINFO, ("Actual:", parses, "expected:", expected));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UNIT_TEST(HouseNumber_ToUInt)
|
||||
{
|
||||
TEST_EQUAL(ToUInt(MakeUniString("1234987650")), 1234987650ULL, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(HouseNumber_Tokenizer)
|
||||
{
|
||||
TEST(CheckTokenizer("123Б", {"123", "б"}), ());
|
||||
TEST(CheckTokenizer("123/Б", {"123", "/", "б"}), ());
|
||||
TEST(CheckTokenizer("123/34 корп. 4 стр1", {"123", "/", "34", "корп", "4", "стр", "1"}), ());
|
||||
TEST(CheckTokenizer("1-100", {"1", "-", "100"}), ());
|
||||
TEST(CheckTokenizer("19/1А литБ", {"19", "/", "1", "а", "лит", "б"}), ());
|
||||
TEST(CheckTokenizer("9 литер аб1", {"9", "литер", "аб", "1"}), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(HouseNumber_Parser)
|
||||
{
|
||||
TEST(CheckParser("123Б", "123 б"), ());
|
||||
TEST(CheckParser("123/4 Литер А", "123 4 а"), ());
|
||||
TEST(CheckParser("123а корп. 2б", "123 2 а б"), ());
|
||||
TEST(CheckParser("123к4", "123 4"), ());
|
||||
TEST(CheckParser("123к Корпус 2", "123 2 к"), ());
|
||||
TEST(CheckParser("9 литер А корпус 2", "9 2 а"), ());
|
||||
TEST(CheckParser("39с79", "39 79"), ());
|
||||
TEST(CheckParser("9 литер аб1", "9 1"), ());
|
||||
TEST(CheckParser("1-100", "1 100"), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(HouseNumber_Matcher)
|
||||
{
|
||||
TEST(HouseNumbersMatch("39с79", "39"), ());
|
||||
TEST(HouseNumbersMatch("39с79", "39 Строение 79"), ());
|
||||
TEST(HouseNumbersMatch("39с79", "39 к. 79"), ());
|
||||
TEST(HouseNumbersMatch("39 - 79", "39 строение 79"), ());
|
||||
TEST(!HouseNumbersMatch("39-79", "49"), ());
|
||||
TEST(HouseNumbersMatch("39/79", "39 строение 79"), ());
|
||||
TEST(HouseNumbersMatch("127а корпус 2", "127а"), ());
|
||||
TEST(HouseNumbersMatch("127а корпус 2", "127а кор. 2"), ());
|
||||
TEST(HouseNumbersMatch("1234abcdef", "1234 abcdef"), ());
|
||||
TEST(HouseNumbersMatch("10/42 корпус 2", "10"), ());
|
||||
TEST(HouseNumbersMatch("10 к2 с2", "10 корпус 2"), ());
|
||||
TEST(HouseNumbersMatch("10 к2 с2", "10 корпус 2 с 2"), ());
|
||||
TEST(HouseNumbersMatch("10 корпус 2 строение 2", "10 к2 с2"), ());
|
||||
TEST(HouseNumbersMatch("10 корпус 2 строение 2", "10к2с2"), ());
|
||||
TEST(HouseNumbersMatch("10к2а", "10 2а"), ());
|
||||
TEST(HouseNumbersMatch("10 к2с", "10 2с"), ());
|
||||
TEST(HouseNumbersMatch("22к", "22 к"), ());
|
||||
TEST(HouseNumbersMatch("22к корпус 2а строение 7", "22к к 2а стр 7"), ());
|
||||
TEST(HouseNumbersMatch("22к к 2а с 7", "22к корпус 2а"), ());
|
||||
TEST(HouseNumbersMatch("124к корпус к", "124к к"), ());
|
||||
TEST(HouseNumbersMatch("127а корпус 2", "127"), ());
|
||||
TEST(HouseNumbersMatch("22к", "22 корпус"), ());
|
||||
|
||||
TEST(HouseNumbersMatch("39 корпус 79", "39", true /* queryIsPrefix */), ());
|
||||
TEST(HouseNumbersMatch("39 корпус 79", "39 кор", true /* queryIsPrefix */), ());
|
||||
TEST(HouseNumbersMatch("39", "39 корп", true /* queryIsPrefix */), ());
|
||||
TEST(HouseNumbersMatch("39 корпус 7", "39", true /* queryIsPrefix */), ());
|
||||
TEST(HouseNumbersMatch("39К корпус 7", "39 к", true /* queryIsPrefix */), ());
|
||||
TEST(HouseNumbersMatch("39К корпус 7", "39к", true /* queryIsPrefix */), ());
|
||||
TEST(HouseNumbersMatch("39 К корпус 7", "39 к", false /* queryIsPrefix */), ());
|
||||
TEST(HouseNumbersMatch("39 К корпус 7", "39", false /* queryIsPrefix */), ());
|
||||
|
||||
TEST(HouseNumbersMatch("3/7 с1Б", "3/7 строение 1 Б", true /* queryIsPrefix */), ());
|
||||
TEST(HouseNumbersMatch("3/7 с1Б", "3/7 строение 1 Б", false /* queryIsPrefix */), ());
|
||||
TEST(!HouseNumbersMatch("3/7 с1Б", "3/7 с 1Д", false /* queryIsPrefix */), ());
|
||||
|
||||
TEST(HouseNumbersMatch("39с80", "39"), ());
|
||||
TEST(HouseNumbersMatch("39", "39 с 80"), ());
|
||||
TEST(!HouseNumbersMatch("39 c 80", "39 с 79"), ());
|
||||
TEST(!HouseNumbersMatch("39 c 79", "39 с 80"), ());
|
||||
|
||||
TEST(!HouseNumbersMatch("6 корпус 2", "7"), ());
|
||||
TEST(!HouseNumbersMatch("10/42 корпус 2", "42"), ());
|
||||
|
||||
TEST(HouseNumbersMatch("22", "22к"), ());
|
||||
TEST(HouseNumbersMatch("22к", "22"), ());
|
||||
TEST(!HouseNumbersMatch("22к", "22я"), ());
|
||||
TEST(!HouseNumbersMatch("22к", "22л"), ());
|
||||
|
||||
TEST(HouseNumbersMatch("16 к1", "д 16 к 1"), ());
|
||||
TEST(HouseNumbersMatch("16 к1", "д 16 к1"), ());
|
||||
TEST(HouseNumbersMatch("16 к1", "16 к1"), ());
|
||||
TEST(HouseNumbersMatch("16 к1", "дом 16 к1"), ());
|
||||
TEST(HouseNumbersMatch("14 д 1", "дом 14 д1"), ());
|
||||
|
||||
TEST(HouseNumbersMatch("12;14", "12"), ());
|
||||
TEST(HouseNumbersMatch("12;14", "14"), ());
|
||||
TEST(!HouseNumbersMatch("12;14", "13"), ());
|
||||
TEST(HouseNumbersMatch("12,14", "12"), ());
|
||||
TEST(HouseNumbersMatch("12,14", "14"), ());
|
||||
TEST(!HouseNumbersMatch("12,14", "13"), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(HouseNumber_Matcher_Conscription)
|
||||
{
|
||||
TEST(HouseNumbersMatchConscription("77/21", "77"), ());
|
||||
TEST(HouseNumbersMatchConscription("77 b/21", "77b"), ());
|
||||
|
||||
TEST(HouseNumbersMatchConscription("77/21", "21"), ());
|
||||
TEST(HouseNumbersMatchConscription("77/21 a", "21a"), ());
|
||||
|
||||
TEST(HouseNumbersMatchConscription("77/21", "77/21"), ());
|
||||
TEST(HouseNumbersMatchConscription("77/21", "21/77"), ());
|
||||
|
||||
TEST(HouseNumbersMatchConscription("77x/21y", "77"), ());
|
||||
TEST(HouseNumbersMatchConscription("77x/21y", "21"), ());
|
||||
|
||||
/// @todo Controversial, but need skip ParseQuery and treat query as 2 separate inputs.
|
||||
/// @{
|
||||
TEST(HouseNumbersMatchConscription("77/21", "77x/21y"), ());
|
||||
TEST(!HouseNumbersMatchConscription("77x/21y", "77/21"), ());
|
||||
TEST(!HouseNumbersMatchConscription("78/21", "77/21"), ());
|
||||
/// @}
|
||||
}
|
||||
|
||||
UNIT_TEST(HouseNumber_Matcher_Range)
|
||||
{
|
||||
using IType = feature::InterpolType;
|
||||
|
||||
TEST(HouseNumbersMatchRange("3000:3100", "3088", IType::Even), ());
|
||||
TEST(!HouseNumbersMatchRange("3000:3100", "3088", IType::Odd), ());
|
||||
TEST(!HouseNumbersMatchRange("3000:3100", "3000", IType::Any), ());
|
||||
TEST(!HouseNumbersMatchRange("3000:3100", "3100", IType::Any), ());
|
||||
TEST(HouseNumbersMatchRange("2:40", "32A", IType::Even), ());
|
||||
TEST(!HouseNumbersMatchRange("2:40", "33B", IType::Even), ());
|
||||
|
||||
/// @todo Maybe some day ..
|
||||
// TEST(HouseNumbersMatchRange("30A:30D", "30B", IType::Any), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(HouseNumber_LooksLike)
|
||||
{
|
||||
TEST(LooksLikeHouseNumber("1", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("ev 10", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("ev.1", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("14 к", true /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("14 кор", true /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("14 корпус", true /*isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("14 корпус 1", true /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("14 корпус 1", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("39 c 79", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("владение 14", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("4", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("4 2", false /* isPrefix */), ());
|
||||
TEST(!LooksLikeHouseNumber("4 2 останкинская", false /* isPrefix */), ());
|
||||
|
||||
TEST(!LooksLikeHouseNumber("39 c 79 ленинградский", false /* isPrefix */), ());
|
||||
TEST(!LooksLikeHouseNumber("каптерка 1", false /* isPrefix */), ());
|
||||
TEST(!LooksLikeHouseNumber("1 канал", false /* isPrefix */), ());
|
||||
TEST(!LooksLikeHouseNumber("2 останкинская", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("39 строе", true /* isPrefix */), ());
|
||||
TEST(!LooksLikeHouseNumber("39 строе", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("39", true /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("39", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("строе", true /* isPrefix */), ());
|
||||
TEST(!LooksLikeHouseNumber("строе", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("дом ", true /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("дом ", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("house", true /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("house ", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("дом 39 строение 79", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("3/7 с1Б", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("3/7 с1Б", true /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("16 к 1", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("д 16 к 1", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("дом 16 к 1", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("д 16", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("дом 16", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("дом 14 д 1", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("12;14", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("12,14", true /* isPrefix */), ());
|
||||
|
||||
TEST(!LooksLikeHouseNumber("улица", false /* isPrefix */), ());
|
||||
TEST(!LooksLikeHouseNumber("avenida", false /* isPrefix */), ());
|
||||
TEST(!LooksLikeHouseNumber("street", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("2:40", false /* isPrefix */), ());
|
||||
|
||||
TEST(LooksLikeHouseNumber("к424", false /* isPrefix */), ());
|
||||
TEST(LooksLikeHouseNumber("к12", true /* isPrefix */), ());
|
||||
}
|
||||
} // namespace house_number_matcher_test
|
||||
126
libs/search/search_tests/interval_set_test.cpp
Normal file
126
libs/search/search_tests/interval_set_test.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/interval_set.hpp"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename Elem>
|
||||
using Interval = typename search::IntervalSet<Elem>::Interval;
|
||||
|
||||
template <typename Elem>
|
||||
void CheckSet(search::IntervalSet<Elem> const & actual, std::initializer_list<Interval<Elem>> intervals)
|
||||
{
|
||||
std::set<Interval<Elem>> expected(intervals);
|
||||
TEST_EQUAL(actual.Elems(), expected, ());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(IntervalSet_Add)
|
||||
{
|
||||
search::IntervalSet<int> set;
|
||||
TEST(set.Elems().empty(), ());
|
||||
|
||||
set.Add(Interval<int>(0, 2));
|
||||
CheckSet(set, {Interval<int>(0, 2)});
|
||||
|
||||
set.Add(Interval<int>(1, 3));
|
||||
CheckSet(set, {Interval<int>(0, 3)});
|
||||
|
||||
set.Add(Interval<int>(-2, 0));
|
||||
CheckSet(set, {Interval<int>(-2, 3)});
|
||||
|
||||
set.Add(Interval<int>(-4, -3));
|
||||
CheckSet(set, {Interval<int>(-4, -3), Interval<int>(-2, 3)});
|
||||
|
||||
set.Add(Interval<int>(7, 10));
|
||||
CheckSet(set, {Interval<int>(-4, -3), Interval<int>(-2, 3), Interval<int>(7, 10)});
|
||||
|
||||
set.Add(Interval<int>(-3, -2));
|
||||
CheckSet(set, {Interval<int>(-4, 3), Interval<int>(7, 10)});
|
||||
|
||||
set.Add(Interval<int>(2, 8));
|
||||
CheckSet(set, {Interval<int>(-4, 10)});
|
||||
|
||||
set.Add(Interval<int>(2, 3));
|
||||
CheckSet(set, {Interval<int>(-4, 10)});
|
||||
}
|
||||
|
||||
UNIT_TEST(IntervalSet_AdjacentIntervalAdd)
|
||||
{
|
||||
search::IntervalSet<int> set;
|
||||
TEST(set.Elems().empty(), ());
|
||||
|
||||
set.Add(Interval<int>(100, 106));
|
||||
CheckSet(set, {Interval<int>(100, 106)});
|
||||
|
||||
set.Add(Interval<int>(106, 110));
|
||||
CheckSet(set, {Interval<int>(100, 110)});
|
||||
|
||||
set.Add(Interval<int>(90, 100));
|
||||
CheckSet(set, {Interval<int>(90, 110)});
|
||||
}
|
||||
|
||||
UNIT_TEST(IntervalSet_SubtractFrom)
|
||||
{
|
||||
search::IntervalSet<int> set;
|
||||
TEST(set.Elems().empty(), ());
|
||||
|
||||
set.Add(Interval<int>(0, 2));
|
||||
set.Add(Interval<int>(4, 7));
|
||||
set.Add(Interval<int>(10, 11));
|
||||
|
||||
CheckSet(set, {Interval<int>(0, 2), Interval<int>(4, 7), Interval<int>(10, 11)});
|
||||
|
||||
{
|
||||
std::vector<Interval<int>> difference;
|
||||
set.SubtractFrom(Interval<int>(1, 5), difference);
|
||||
std::vector<Interval<int>> expected{Interval<int>(2, 4)};
|
||||
TEST_EQUAL(difference, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<Interval<int>> difference;
|
||||
set.SubtractFrom(Interval<int>(-10, -5), difference);
|
||||
std::vector<Interval<int>> expected{Interval<int>(-10, -5)};
|
||||
TEST_EQUAL(difference, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<Interval<int>> difference;
|
||||
set.SubtractFrom(Interval<int>(0, 11), difference);
|
||||
std::vector<Interval<int>> expected{Interval<int>(2, 4), Interval<int>(7, 10)};
|
||||
TEST_EQUAL(difference, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<Interval<int>> difference;
|
||||
set.SubtractFrom(Interval<int>(-1, 11), difference);
|
||||
std::vector<Interval<int>> expected{Interval<int>(-1, 0), Interval<int>(2, 4), Interval<int>(7, 10)};
|
||||
TEST_EQUAL(difference, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<Interval<int>> difference;
|
||||
set.SubtractFrom(Interval<int>(5, 7), difference);
|
||||
TEST(difference.empty(), ());
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<Interval<int>> difference;
|
||||
set.SubtractFrom(Interval<int>(4, 7), difference);
|
||||
TEST(difference.empty(), ());
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<Interval<int>> difference;
|
||||
set.SubtractFrom(Interval<int>(3, 7), difference);
|
||||
std::vector<Interval<int>> expected{Interval<int>(3, 4)};
|
||||
TEST_EQUAL(difference, expected, ());
|
||||
}
|
||||
}
|
||||
61
libs/search/search_tests/keyword_lang_matcher_test.cpp
Normal file
61
libs/search/search_tests/keyword_lang_matcher_test.cpp
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/keyword_lang_matcher.hpp"
|
||||
#include "search/string_utils.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace keyword_lang_matcher_test
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
using search::KeywordLangMatcher;
|
||||
using Score = search::KeywordLangMatcher::Score;
|
||||
|
||||
enum
|
||||
{
|
||||
LANG_UNKNOWN = 1,
|
||||
LANG_SOME = 2,
|
||||
LANG_SOME_OTHER = 3,
|
||||
LANG_HIGH_PRIORITY = 10
|
||||
};
|
||||
|
||||
KeywordLangMatcher CreateMatcher(string const & query)
|
||||
{
|
||||
size_t const kNumTestTiers = 4;
|
||||
KeywordLangMatcher matcher(kNumTestTiers);
|
||||
|
||||
{
|
||||
vector<vector<int8_t>> langPriorities(kNumTestTiers);
|
||||
langPriorities[0].push_back(LANG_HIGH_PRIORITY);
|
||||
// langPriorities[1] is intentionally left empty.
|
||||
langPriorities[2].push_back(LANG_SOME);
|
||||
langPriorities[2].push_back(LANG_SOME_OTHER);
|
||||
// langPriorities[3] is intentionally left empty.
|
||||
|
||||
for (size_t i = 0; i < langPriorities.size(); ++i)
|
||||
matcher.SetLanguages(i /* tier */, std::move(langPriorities[i]));
|
||||
}
|
||||
|
||||
matcher.SetKeywords(search::MakeQueryString(query));
|
||||
return matcher;
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_LanguageMatchIsUsedWhenTokenMatchIsTheSame)
|
||||
{
|
||||
char const * query = "test";
|
||||
char const * name = "test";
|
||||
KeywordLangMatcher matcher = CreateMatcher(query);
|
||||
|
||||
TEST(matcher.CalcScore(LANG_UNKNOWN, name) < matcher.CalcScore(LANG_SOME, name), ());
|
||||
TEST(matcher.CalcScore(LANG_UNKNOWN, name) < matcher.CalcScore(LANG_SOME_OTHER, name), ());
|
||||
TEST(matcher.CalcScore(LANG_UNKNOWN, name) < matcher.CalcScore(LANG_HIGH_PRIORITY, name), ());
|
||||
|
||||
TEST(!(matcher.CalcScore(LANG_SOME, name) < matcher.CalcScore(LANG_SOME_OTHER, name)), ());
|
||||
TEST(!(matcher.CalcScore(LANG_SOME_OTHER, name) < matcher.CalcScore(LANG_SOME, name)), ());
|
||||
|
||||
TEST(matcher.CalcScore(LANG_SOME, name) < matcher.CalcScore(LANG_HIGH_PRIORITY, name), ());
|
||||
TEST(matcher.CalcScore(LANG_SOME_OTHER, name) < matcher.CalcScore(LANG_HIGH_PRIORITY, name), ());
|
||||
}
|
||||
|
||||
} // namespace keyword_lang_matcher_test
|
||||
299
libs/search/search_tests/keyword_matcher_test.cpp
Normal file
299
libs/search/search_tests/keyword_matcher_test.cpp
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/common.hpp"
|
||||
#include "search/keyword_matcher.hpp"
|
||||
#include "search/string_utils.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace keyword_matcher_test
|
||||
{
|
||||
using namespace std;
|
||||
using search::KeywordMatcher;
|
||||
using search::kMaxNumTokens;
|
||||
|
||||
enum ExpectedMatchResult
|
||||
{
|
||||
NOMATCH,
|
||||
MATCHES,
|
||||
ANY_RES
|
||||
};
|
||||
|
||||
enum ExpectedScoreComparison
|
||||
{
|
||||
DOES_NOT_MATTER, // Score does not matter.
|
||||
PERFECTLY_EQUAL, // Matches with the score == previous.
|
||||
BETTER_OR_EQUAL, // Matches with the score <= previous.
|
||||
STRONGLY_BETTER // Matched with the score < previous.
|
||||
};
|
||||
|
||||
struct KeywordMatcherTestCase
|
||||
{
|
||||
ExpectedMatchResult m_eMatch;
|
||||
ExpectedScoreComparison m_eMatchType;
|
||||
char const * m_name;
|
||||
};
|
||||
|
||||
void InitMatcher(char const * query, KeywordMatcher & matcher)
|
||||
{
|
||||
matcher.SetKeywords(search::MakeQueryString(query));
|
||||
}
|
||||
|
||||
class TestScore
|
||||
{
|
||||
using Score = KeywordMatcher::Score;
|
||||
Score m_score;
|
||||
|
||||
public:
|
||||
TestScore() {}
|
||||
explicit TestScore(Score const & score) : m_score(score) {}
|
||||
|
||||
bool operator<(TestScore const & s) const
|
||||
{
|
||||
if (m_score < s.m_score)
|
||||
return true;
|
||||
return m_score.LessInTokensLength(s.m_score);
|
||||
}
|
||||
|
||||
bool IsQueryMatched() const { return m_score.IsQueryMatched(); }
|
||||
|
||||
friend string DebugPrint(TestScore const & score);
|
||||
};
|
||||
|
||||
string DebugPrint(TestScore const & s)
|
||||
{
|
||||
return DebugPrint(s.m_score);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void TestKeywordMatcher(char const * const query, KeywordMatcherTestCase const (&testCases)[N])
|
||||
{
|
||||
KeywordMatcher matcher;
|
||||
InitMatcher(query, matcher);
|
||||
|
||||
TestScore prevScore;
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
{
|
||||
char const * const name = testCases[i].m_name;
|
||||
char const * const prevName = (i == 0 ? "N/A" : testCases[i - 1].m_name);
|
||||
TestScore const testScore(matcher.CalcScore(name));
|
||||
|
||||
// Test that a newly created matcher returns the same result
|
||||
{
|
||||
KeywordMatcher freshMatcher;
|
||||
InitMatcher(query, freshMatcher);
|
||||
|
||||
TestScore const freshScore(freshMatcher.CalcScore(name));
|
||||
// TEST_EQUAL(testScore, freshScore, (query, name));
|
||||
TEST(!(testScore < freshScore), (query, name));
|
||||
TEST(!(freshScore < testScore), (query, name));
|
||||
}
|
||||
|
||||
if (testCases[i].m_eMatch != ANY_RES)
|
||||
TEST_EQUAL(testCases[i].m_eMatch == MATCHES, testScore.IsQueryMatched(), (query, name, testScore));
|
||||
|
||||
switch (testCases[i].m_eMatchType)
|
||||
{
|
||||
case DOES_NOT_MATTER: break;
|
||||
case PERFECTLY_EQUAL:
|
||||
TEST(!(testScore < prevScore), (query, name, testScore, prevName, prevScore));
|
||||
TEST(!(prevScore < testScore), (query, name, testScore, prevName, prevScore));
|
||||
break;
|
||||
case BETTER_OR_EQUAL: TEST(!(testScore < prevScore), (query, name, testScore, prevName, prevScore)); break;
|
||||
case STRONGLY_BETTER: TEST(prevScore < testScore, (query, name, testScore, prevName, prevScore)); break;
|
||||
default: ASSERT(false, ());
|
||||
}
|
||||
|
||||
prevScore = testScore;
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_Prefix)
|
||||
{
|
||||
char const query[] = "new";
|
||||
KeywordMatcherTestCase const testCases[] = {
|
||||
{NOMATCH, DOES_NOT_MATTER, ""},
|
||||
{NOMATCH, DOES_NOT_MATTER, "zzz"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "ne"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "the newark"},
|
||||
{MATCHES, BETTER_OR_EQUAL, "york new"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "new york gym"},
|
||||
{MATCHES, BETTER_OR_EQUAL, "new new york"},
|
||||
{MATCHES, STRONGLY_BETTER, "new york"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "newark"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "new"},
|
||||
};
|
||||
TestKeywordMatcher(query, testCases);
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_Keyword)
|
||||
{
|
||||
char const query[] = "new ";
|
||||
KeywordMatcherTestCase const testCases[] = {
|
||||
{NOMATCH, DOES_NOT_MATTER, ""},
|
||||
{NOMATCH, DOES_NOT_MATTER, "zzz"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "ne"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "the netherlands"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "newark"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "york new"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "new york gym"},
|
||||
{MATCHES, BETTER_OR_EQUAL, "new new york"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "new york"},
|
||||
};
|
||||
TestKeywordMatcher(query, testCases);
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_SanSa_ShouldMatch_SanSalvador_BetterThan_San)
|
||||
{
|
||||
char const query[] = "San Sa";
|
||||
|
||||
KeywordMatcherTestCase const testCases[] = {
|
||||
{NOMATCH, DOES_NOT_MATTER, "San"},
|
||||
{MATCHES, STRONGLY_BETTER, "San Salvador"},
|
||||
};
|
||||
TestKeywordMatcher(query, testCases);
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_KeywordAndPrefix)
|
||||
{
|
||||
char const query[] = "new yo";
|
||||
|
||||
KeywordMatcherTestCase const testCases[] = {
|
||||
{NOMATCH, DOES_NOT_MATTER, "new"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "new old"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "old york"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "the york new"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "the new york"},
|
||||
{MATCHES, BETTER_OR_EQUAL, "york new the"},
|
||||
{MATCHES, BETTER_OR_EQUAL, "york new"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "yo new"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "new york pizza"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "new york"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "new yo"},
|
||||
};
|
||||
TestKeywordMatcher(query, testCases);
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_KeywordAndKeyword)
|
||||
{
|
||||
char const query[] = "new york ";
|
||||
|
||||
KeywordMatcherTestCase const testCases[] = {
|
||||
{NOMATCH, DOES_NOT_MATTER, "new"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "new old"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "old york"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "new yorkshire"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "york newcastle"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "the york new"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "the new york"},
|
||||
{MATCHES, BETTER_OR_EQUAL, "york new the"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "york new"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, "new york pizza"},
|
||||
{MATCHES, STRONGLY_BETTER, "new york"},
|
||||
};
|
||||
TestKeywordMatcher(query, testCases);
|
||||
}
|
||||
|
||||
string GetManyTokens(string tokenPrefix, int tokenCount, bool countForward = true)
|
||||
{
|
||||
ostringstream out;
|
||||
for (int i = 0; i < tokenCount; ++i)
|
||||
out << tokenPrefix << (countForward ? i : tokenCount - 1 - i) << " ";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_QueryTooLong)
|
||||
{
|
||||
static_assert(kMaxNumTokens >= 2, "");
|
||||
int const minLength = kMaxNumTokens - 2;
|
||||
int const maxLength = kMaxNumTokens + 2;
|
||||
for (int queryLength = minLength; queryLength <= maxLength; ++queryLength)
|
||||
{
|
||||
string const query = GetManyTokens("Q", queryLength);
|
||||
string const queryWithPrefix = query + " Prefix";
|
||||
string const queryWithPrefixAndSomethingElse = query + " PrefixAndSomethingElse";
|
||||
|
||||
KeywordMatcherTestCase const testCases[] = {
|
||||
{NOMATCH, DOES_NOT_MATTER, ""}, {NOMATCH, DOES_NOT_MATTER, "Q"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "Q "}, {NOMATCH, DOES_NOT_MATTER, "Q3"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "Q3 "}, {NOMATCH, DOES_NOT_MATTER, "Q3 Q"},
|
||||
{NOMATCH, DOES_NOT_MATTER, "Q3 Q4"}, {NOMATCH, DOES_NOT_MATTER, "zzz"},
|
||||
|
||||
{NOMATCH, DOES_NOT_MATTER, "Q"}, {ANY_RES, STRONGLY_BETTER, query.c_str()},
|
||||
|
||||
{NOMATCH, DOES_NOT_MATTER, "Q"}, {ANY_RES, STRONGLY_BETTER, queryWithPrefix.c_str()},
|
||||
|
||||
{NOMATCH, DOES_NOT_MATTER, "Q"}, {ANY_RES, STRONGLY_BETTER, queryWithPrefixAndSomethingElse.c_str()},
|
||||
};
|
||||
TestKeywordMatcher(query.c_str(), testCases);
|
||||
TestKeywordMatcher(queryWithPrefix.c_str(), testCases);
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_NameTooLong)
|
||||
{
|
||||
string const name[] = {
|
||||
"Aa Bb " + GetManyTokens("T", kMaxNumTokens + 1),
|
||||
"Aa Bb " + GetManyTokens("T", kMaxNumTokens),
|
||||
"Aa Bb " + GetManyTokens("T", kMaxNumTokens - 1),
|
||||
};
|
||||
|
||||
KeywordMatcherTestCase const testCases[] = {
|
||||
{NOMATCH, DOES_NOT_MATTER, "zzz"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, name[0].c_str()},
|
||||
{MATCHES, BETTER_OR_EQUAL, name[1].c_str()},
|
||||
{MATCHES, BETTER_OR_EQUAL, name[2].c_str()},
|
||||
};
|
||||
|
||||
char const * queries[] = {"a", "aa", "aa ", "b", "bb", "bb ", "t"};
|
||||
for (auto const & query : queries)
|
||||
TestKeywordMatcher(query, testCases);
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_ManyTokensInReverseOrder)
|
||||
{
|
||||
string const query = GetManyTokens("Q", kMaxNumTokens);
|
||||
string const name = GetManyTokens("Q", kMaxNumTokens);
|
||||
string const reversedName = GetManyTokens("Q", kMaxNumTokens, false);
|
||||
|
||||
KeywordMatcherTestCase const testCases[] = {
|
||||
{NOMATCH, DOES_NOT_MATTER, "zzz"},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, reversedName.c_str()},
|
||||
|
||||
{MATCHES, STRONGLY_BETTER, name.c_str()},
|
||||
};
|
||||
TestKeywordMatcher(query.c_str(), testCases);
|
||||
}
|
||||
|
||||
UNIT_TEST(KeywordMatcher_DifferentLangs)
|
||||
{
|
||||
KeywordMatcher matcher;
|
||||
|
||||
InitMatcher("не", matcher);
|
||||
|
||||
char const * arr[] = {"Невский переулок", "Неўскі завулак"};
|
||||
TEST(!(matcher.CalcScore(arr[0]) < matcher.CalcScore(arr[1])), ());
|
||||
TEST(!(matcher.CalcScore(arr[1]) < matcher.CalcScore(arr[0])), ());
|
||||
}
|
||||
|
||||
} // namespace keyword_matcher_test
|
||||
192
libs/search/search_tests/latlon_match_test.cpp
Normal file
192
libs/search/search_tests/latlon_match_test.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/latlon_match.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
namespace latlon_match_test
|
||||
{
|
||||
using namespace search;
|
||||
|
||||
// We expect the results to be quite precise.
|
||||
double const kEps = 1e-12;
|
||||
|
||||
void TestAlmostEqual(double actual, double expected)
|
||||
{
|
||||
TEST(AlmostEqualAbsOrRel(actual, expected, kEps), (actual, expected));
|
||||
}
|
||||
|
||||
UNIT_TEST(LatLon_Match_Smoke)
|
||||
{
|
||||
double lat, lon;
|
||||
|
||||
TEST(!MatchLatLonDegree("10,20", lat, lon), ());
|
||||
|
||||
TEST(MatchLatLonDegree("10, 20", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.0);
|
||||
TestAlmostEqual(lon, 20.0);
|
||||
|
||||
TEST(MatchLatLonDegree("10.0 20.0", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.0);
|
||||
TestAlmostEqual(lon, 20.0);
|
||||
|
||||
TEST(MatchLatLonDegree("10.0, 20,0", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.0);
|
||||
TestAlmostEqual(lon, 20.0);
|
||||
|
||||
TEST(MatchLatLonDegree("10.10, 20.20", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.1);
|
||||
TestAlmostEqual(lon, 20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("10,10 20,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.1);
|
||||
TestAlmostEqual(lon, 20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("10,10, 20,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.1);
|
||||
TestAlmostEqual(lon, 20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("-10,10, 20,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, -10.1);
|
||||
TestAlmostEqual(lon, 20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("10,10, -20,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.1);
|
||||
TestAlmostEqual(lon, -20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("-10,10, -20,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, -10.1);
|
||||
TestAlmostEqual(lon, -20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("-10,10 20,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, -10.1);
|
||||
TestAlmostEqual(lon, 20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("10,10 -20,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.1);
|
||||
TestAlmostEqual(lon, -20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("-10,10 -20,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, -10.1);
|
||||
TestAlmostEqual(lon, -20.2);
|
||||
|
||||
TEST(MatchLatLonDegree("-22.3534 -42.7076\n", lat, lon), ());
|
||||
TestAlmostEqual(lat, -22.3534);
|
||||
TestAlmostEqual(lon, -42.7076);
|
||||
|
||||
TEST(MatchLatLonDegree("-22,3534 -42,7076\n", lat, lon), ());
|
||||
TestAlmostEqual(lat, -22.3534);
|
||||
TestAlmostEqual(lon, -42.7076);
|
||||
|
||||
// The ".123" form is not accepted, so our best-effort
|
||||
// parse results in "10" and "20".
|
||||
TEST(MatchLatLonDegree(".10, ,20", lat, lon), ());
|
||||
TestAlmostEqual(lat, 10.0);
|
||||
TestAlmostEqual(lon, 20.0);
|
||||
|
||||
TEST(!MatchLatLonDegree("34-31", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("34/31", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("34,31", lat, lon), ());
|
||||
|
||||
/// @todo 5E-5 eats as full double here. This is a very fancy case, but anyway ...
|
||||
TEST(!MatchLatLonDegree("N5E-5", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("5E-5", lat, lon), ());
|
||||
|
||||
TEST(MatchLatLonDegree("N5W-5", lat, lon), ());
|
||||
TestAlmostEqual(lat, 5);
|
||||
TestAlmostEqual(lon, 5);
|
||||
// Same as "N5 E-5"
|
||||
TEST(MatchLatLonDegree("5 E-5", lat, lon), ());
|
||||
TestAlmostEqual(lat, 5);
|
||||
TestAlmostEqual(lon, -5);
|
||||
|
||||
TEST(!MatchLatLonDegree("., .", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("10, .", lat, lon), ());
|
||||
|
||||
TEST(MatchLatLonDegree("0*30\', 1*0\'30\"", lat, lon), ());
|
||||
TestAlmostEqual(lat, 0.5);
|
||||
TestAlmostEqual(lon, 1.00833333333333);
|
||||
|
||||
TEST(MatchLatLonDegree("50 *, 40 *", lat, lon), ());
|
||||
TestAlmostEqual(lat, 50.0);
|
||||
TestAlmostEqual(lon, 40.0);
|
||||
|
||||
TEST(!MatchLatLonDegree("50* 40*, 30*", lat, lon), ());
|
||||
|
||||
TEST(MatchLatLonDegree("(-50°30\'30\" -49°59\'59\"", lat, lon), ());
|
||||
TestAlmostEqual(lat, -50.50833333333333);
|
||||
TestAlmostEqual(lon, -49.99972222222222);
|
||||
|
||||
TEST(!MatchLatLonDegree("50°, 30\"", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("50\', -50°", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("-90*50\'50\", -50°", lat, lon), ());
|
||||
|
||||
TEST(MatchLatLonDegree("(-89*, 360*)", lat, lon), ());
|
||||
TestAlmostEqual(lat, -89.0);
|
||||
TestAlmostEqual(lon, 0.0);
|
||||
|
||||
TEST(MatchLatLonDegree("-89*15.5\' N; 120*30\'50.5\" e", lat, lon), ());
|
||||
TestAlmostEqual(lat, -89.25833333333333);
|
||||
TestAlmostEqual(lon, 120.51402777777778);
|
||||
|
||||
TEST(MatchLatLonDegree("N55°45′20.99″ E37°37′03.62″", lat, lon), ());
|
||||
TestAlmostEqual(lat, 55.755830555555556);
|
||||
TestAlmostEqual(lon, 37.617672222222222);
|
||||
|
||||
{
|
||||
TEST(MatchLatLonDegree("N-55°45′20.99″ E-37°37′03.62″", lat, lon), ());
|
||||
double lat1, lon1;
|
||||
TEST(MatchLatLonDegree("S55°45′20.99″ W37°37′03.62″", lat1, lon1), ());
|
||||
TestAlmostEqual(lat, lat1);
|
||||
TestAlmostEqual(lon, lon1);
|
||||
}
|
||||
|
||||
TEST(MatchLatLonDegree("55°45’20.9916\"N, 37°37’3.6228\"E hsdfjgkdsjbv", lat, lon), ());
|
||||
TestAlmostEqual(lat, 55.755831);
|
||||
TestAlmostEqual(lon, 37.617673);
|
||||
|
||||
TEST(MatchLatLonDegree("55°45′20.9916″S, 37°37′3.6228″W", lat, lon), ());
|
||||
TestAlmostEqual(lat, -55.755831);
|
||||
TestAlmostEqual(lon, -37.617673);
|
||||
|
||||
// We can receive already normalized string, and double quotes become two single quotes
|
||||
TEST(MatchLatLonDegree("55°45′20.9916′′S, 37°37′3.6228′′W", lat, lon), ());
|
||||
TestAlmostEqual(lat, -55.755831);
|
||||
TestAlmostEqual(lon, -37.617673);
|
||||
|
||||
TEST(MatchLatLonDegree("W55°45′20.9916″, S37°37′3.6228″", lat, lon), ());
|
||||
TestAlmostEqual(lon, -55.755831);
|
||||
TestAlmostEqual(lat, -37.617673);
|
||||
|
||||
TEST(MatchLatLonDegree("55°45′20.9916″ W 37°37′3.6228″ N", lat, lon), ());
|
||||
TestAlmostEqual(lon, -55.755831);
|
||||
TestAlmostEqual(lat, 37.617673);
|
||||
|
||||
TEST(!MatchLatLonDegree("55°45′20.9916″W 37°37′3.6228″E", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("N55°45′20.9916″ S37°37′3.6228″", lat, lon), ());
|
||||
|
||||
TEST(MatchLatLonDegree("54° 25' 0N 1° 53' 46W", lat, lon), ());
|
||||
TestAlmostEqual(lat, 54.41666666666667);
|
||||
TestAlmostEqual(lon, -1.89611111111111);
|
||||
|
||||
TEST(MatchLatLonDegree("47.33471°N 8.53112°E", lat, lon), ());
|
||||
TestAlmostEqual(lat, 47.33471);
|
||||
TestAlmostEqual(lon, 8.53112);
|
||||
|
||||
TEST(MatchLatLonDegree("N 51* 33.217 E 11* 10.113", lat, lon), ());
|
||||
TestAlmostEqual(lat, 51.55361666666667);
|
||||
TestAlmostEqual(lon, 11.16855);
|
||||
|
||||
TEST(!MatchLatLonDegree("N 51* 33.217 E 11* 60.113", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("N 51* -33.217 E 11* 10.113", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("N 33.217\' E 11* 10.113", lat, lon), ());
|
||||
TEST(!MatchLatLonDegree("N 51* 33.217 E 11* 10.113\"", lat, lon), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LatLon_Match_False)
|
||||
{
|
||||
double lat, lon;
|
||||
TEST(!MatchLatLonDegree("2 1st", lat, lon), ());
|
||||
}
|
||||
|
||||
} // namespace latlon_match_test
|
||||
47
libs/search/search_tests/localities_source_tests.cpp
Normal file
47
libs/search/search_tests/localities_source_tests.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "generator/generator_tests_support/test_with_classificator.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
|
||||
#include "search/localities_source.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using generator::tests_support::TestWithClassificator;
|
||||
|
||||
UNIT_CLASS_TEST(TestWithClassificator, Smoke)
|
||||
{
|
||||
search::LocalitiesSource ls;
|
||||
|
||||
vector<vector<string>> const expectedPaths = {
|
||||
{"place", "town"},
|
||||
{"place", "city"},
|
||||
{"place", "city", "capital"},
|
||||
{"place", "city", "capital", "2"},
|
||||
{"place", "city", "capital", "3"},
|
||||
{"place", "city", "capital", "4"},
|
||||
{"place", "city", "capital", "5"},
|
||||
{"place", "city", "capital", "6"},
|
||||
{"place", "city", "capital", "7"},
|
||||
{"place", "city", "capital", "8"},
|
||||
{"place", "city", "capital", "9"},
|
||||
{"place", "city", "capital", "10"},
|
||||
{"place", "city", "capital", "11"},
|
||||
};
|
||||
|
||||
vector<uint32_t> expectedTypes;
|
||||
for (auto const & path : expectedPaths)
|
||||
expectedTypes.push_back(classif().GetTypeByPath(path));
|
||||
sort(expectedTypes.begin(), expectedTypes.end());
|
||||
|
||||
vector<uint32_t> localitiesSourceTypes;
|
||||
ls.ForEachType([&localitiesSourceTypes](uint32_t type) { localitiesSourceTypes.push_back(type); });
|
||||
sort(localitiesSourceTypes.begin(), localitiesSourceTypes.end());
|
||||
|
||||
TEST_EQUAL(expectedTypes, localitiesSourceTypes, ());
|
||||
}
|
||||
118
libs/search/search_tests/locality_finder_test.cpp
Normal file
118
libs/search/search_tests/locality_finder_test.cpp
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "generator/generator_tests_support/test_with_classificator.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_header.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "search/categories_cache.hpp"
|
||||
#include "search/locality_finder.hpp"
|
||||
|
||||
#include "platform/country_file.hpp"
|
||||
#include "platform/local_country_file.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/cancellable.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace locality_finder_test
|
||||
{
|
||||
class LocalityFinderTest : public generator::tests_support::TestWithClassificator
|
||||
{
|
||||
platform::LocalCountryFile m_worldFile;
|
||||
|
||||
FrozenDataSource m_dataSource;
|
||||
|
||||
base::Cancellable m_cancellable;
|
||||
search::VillagesCache m_villagesCache;
|
||||
search::CitiesBoundariesTable m_boundariesTable;
|
||||
|
||||
search::LocalityFinder m_finder;
|
||||
m2::RectD m_worldRect;
|
||||
|
||||
public:
|
||||
LocalityFinderTest()
|
||||
: m_villagesCache(m_cancellable)
|
||||
, m_boundariesTable(m_dataSource)
|
||||
, m_finder(m_dataSource, m_boundariesTable, m_villagesCache)
|
||||
{
|
||||
m_worldFile = platform::LocalCountryFile::MakeForTesting("World");
|
||||
|
||||
try
|
||||
{
|
||||
auto const p = m_dataSource.Register(m_worldFile);
|
||||
TEST_EQUAL(MwmSet::RegResult::Success, p.second, ());
|
||||
|
||||
MwmSet::MwmId const & id = p.first;
|
||||
TEST(id.IsAlive(), ());
|
||||
|
||||
m_worldRect = id.GetInfo()->m_bordersRect;
|
||||
m_boundariesTable.Load();
|
||||
}
|
||||
catch (RootException const & ex)
|
||||
{
|
||||
LOG(LERROR, ("Read World.mwm error:", ex.Msg()));
|
||||
}
|
||||
}
|
||||
|
||||
~LocalityFinderTest() { platform::CountryIndexes::DeleteFromDisk(m_worldFile); }
|
||||
|
||||
void RunTests(std::vector<ms::LatLon> const & input, char const * results[])
|
||||
{
|
||||
for (size_t i = 0; i < input.size(); ++i)
|
||||
{
|
||||
std::string_view result;
|
||||
m_finder.GetLocality(mercator::FromLatLon(input[i]), [&](search::LocalityItem const & item)
|
||||
{ item.GetSpecifiedOrDefaultName(StringUtf8Multilang::kEnglishCode, result); });
|
||||
TEST_EQUAL(result, results[i], ());
|
||||
}
|
||||
}
|
||||
|
||||
m2::RectD const & GetWorldRect() const { return m_worldRect; }
|
||||
|
||||
void ClearCaches() { m_finder.ClearCache(); }
|
||||
};
|
||||
|
||||
UNIT_CLASS_TEST(LocalityFinderTest, Smoke)
|
||||
{
|
||||
std::vector<ms::LatLon> input;
|
||||
input.emplace_back(53.8993094, 27.5433964); // Minsk
|
||||
input.emplace_back(48.856517, 2.3521); // Paris
|
||||
input.emplace_back(52.5193859, 13.3908289); // Berlin
|
||||
|
||||
char const * results[] = {"Minsk", "Paris", "Berlin"};
|
||||
|
||||
RunTests(input, results);
|
||||
|
||||
ClearCaches();
|
||||
input.clear();
|
||||
|
||||
input.emplace_back(41.875, -87.624367); // Chicago
|
||||
input.emplace_back(-22.911225, -43.209384); // Rio de Janeiro
|
||||
input.emplace_back(-37.8142, 144.96); // Melbourne (Australia)
|
||||
input.emplace_back(53.883931, 27.69341); // Parking Minsk (near MKAD)
|
||||
input.emplace_back(53.917306, 27.707875); // Lipki airport (Minsk)
|
||||
input.emplace_back(42.285901, 18.834407); // Budva (Montenegro)
|
||||
input.emplace_back(43.9363996, 12.4466991); // City of San Marino
|
||||
input.emplace_back(47.3345002, 8.531262); // Zurich
|
||||
|
||||
char const * results3[] = {"Chicago", "Rio de Janeiro", "Melbourne", "Minsk",
|
||||
"Minsk", "Budva", "City of San Marino", "Zurich"};
|
||||
|
||||
RunTests(input, results3);
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(LocalityFinderTest, Moscow)
|
||||
{
|
||||
std::vector<ms::LatLon> input;
|
||||
input.emplace_back(55.80166, 37.54066); // Krasnoarmeyskaya 30
|
||||
|
||||
char const * results[] = {"Moscow"};
|
||||
|
||||
RunTests(input, results);
|
||||
}
|
||||
} // namespace locality_finder_test
|
||||
325
libs/search/search_tests/locality_scorer_test.cpp
Normal file
325
libs/search/search_tests/locality_scorer_test.cpp
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
#include "search/locality_scorer.hpp"
|
||||
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/cbv.hpp"
|
||||
#include "search/geocoder_context.hpp"
|
||||
#include "search/ranking_utils.hpp"
|
||||
|
||||
#include "indexer/search_string_utils.hpp"
|
||||
|
||||
#include "coding/compressed_bit_vector.hpp"
|
||||
|
||||
#include "base/mem_trie.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace locality_scorer_test
|
||||
{
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
using namespace strings;
|
||||
|
||||
class LocalityScorerTest : public LocalityScorer::Delegate
|
||||
{
|
||||
public:
|
||||
using Ids = vector<uint32_t>;
|
||||
|
||||
LocalityScorerTest() : m_scorer(m_params, m2::PointD(), static_cast<LocalityScorer::Delegate &>(*this)) {}
|
||||
|
||||
void InitParams(string const & query, bool lastTokenIsPrefix) { InitParams(query, m2::PointD(), lastTokenIsPrefix); }
|
||||
|
||||
void InitParams(string const & query, m2::PointD const & pivot, bool lastTokenIsPrefix)
|
||||
{
|
||||
m_params.Clear();
|
||||
|
||||
m_scorer.SetPivotForTesting(pivot);
|
||||
|
||||
vector<UniString> tokens;
|
||||
search::ForEachNormalizedToken(query, [&tokens](strings::UniString && token)
|
||||
{
|
||||
if (!IsStopWord(token))
|
||||
tokens.push_back(std::move(token));
|
||||
});
|
||||
|
||||
m_params.Init(query, tokens, lastTokenIsPrefix);
|
||||
}
|
||||
|
||||
void AddLocality(string const & name, uint32_t featureId, uint8_t rank = 0, m2::PointD const & center = {},
|
||||
bool belongsToMatchedRegion = false)
|
||||
{
|
||||
set<UniString> tokens;
|
||||
SplitUniString(NormalizeAndSimplifyString(name), base::MakeInsertFunctor(tokens), Delimiters());
|
||||
|
||||
for (auto const & token : tokens)
|
||||
m_searchIndex.Add(token, featureId);
|
||||
|
||||
m_names[featureId].push_back(name);
|
||||
m_ranks[featureId] = rank;
|
||||
m_centers[featureId] = center;
|
||||
m_belongsToMatchedRegion[center] = belongsToMatchedRegion;
|
||||
}
|
||||
|
||||
Ids GetTopLocalities(size_t limit)
|
||||
{
|
||||
BaseContext ctx;
|
||||
size_t const numTokens = m_params.GetNumTokens();
|
||||
ctx.m_tokens.assign(numTokens, BaseContext::TOKEN_TYPE_COUNT);
|
||||
|
||||
for (size_t i = 0; i < numTokens; ++i)
|
||||
{
|
||||
auto const & token = m_params.GetToken(i);
|
||||
bool const isPrefixToken = m_params.IsPrefixToken(i);
|
||||
|
||||
vector<uint64_t> ids;
|
||||
token.ForOriginalAndSynonyms([&](UniString const & synonym)
|
||||
{
|
||||
if (isPrefixToken)
|
||||
{
|
||||
m_searchIndex.ForEachInSubtree(
|
||||
synonym, [&](UniString const & /* prefix */, uint32_t featureId) { ids.push_back(featureId); });
|
||||
}
|
||||
else
|
||||
{
|
||||
m_searchIndex.ForEachInNode(synonym, [&](uint32_t featureId) { ids.push_back(featureId); });
|
||||
}
|
||||
});
|
||||
|
||||
base::SortUnique(ids);
|
||||
ctx.m_features.emplace_back(CBV(coding::CompressedBitVectorBuilder::FromBitPositions(ids)));
|
||||
}
|
||||
|
||||
CBV filter;
|
||||
filter.SetFull();
|
||||
|
||||
vector<Locality> localities;
|
||||
m_scorer.GetTopLocalities(MwmSet::MwmId(), ctx, filter, limit, localities);
|
||||
sort(localities.begin(), localities.end(), base::LessBy(&Locality::m_featureId));
|
||||
|
||||
Ids ids;
|
||||
for (auto const & locality : localities)
|
||||
ids.push_back(locality.GetFeatureIndex());
|
||||
return ids;
|
||||
}
|
||||
|
||||
// LocalityScorer::Delegate overrides:
|
||||
void GetNames(uint32_t featureId, vector<string> & names) const override
|
||||
{
|
||||
auto it = m_names.find(featureId);
|
||||
if (it != m_names.end())
|
||||
names.insert(names.end(), it->second.begin(), it->second.end());
|
||||
}
|
||||
|
||||
uint8_t GetRank(uint32_t featureId) const override
|
||||
{
|
||||
auto it = m_ranks.find(featureId);
|
||||
return it == m_ranks.end() ? 0 : it->second;
|
||||
}
|
||||
|
||||
optional<m2::PointD> GetCenter(uint32_t featureId) override
|
||||
{
|
||||
auto it = m_centers.find(featureId);
|
||||
return it == m_centers.end() ? optional<m2::PointD>() : it->second;
|
||||
}
|
||||
|
||||
bool BelongsToMatchedRegion(m2::PointD const & p) const override
|
||||
{
|
||||
auto it = m_belongsToMatchedRegion.find(p);
|
||||
return it == m_belongsToMatchedRegion.end() ? false : it->second;
|
||||
}
|
||||
|
||||
protected:
|
||||
QueryParams m_params;
|
||||
unordered_map<uint32_t, vector<string>> m_names;
|
||||
unordered_map<uint32_t, uint8_t> m_ranks;
|
||||
unordered_map<uint32_t, m2::PointD> m_centers;
|
||||
map<m2::PointD, bool> m_belongsToMatchedRegion;
|
||||
LocalityScorer m_scorer;
|
||||
|
||||
base::MemTrie<UniString, base::VectorValues<uint32_t>> m_searchIndex;
|
||||
};
|
||||
|
||||
UNIT_CLASS_TEST(LocalityScorerTest, Smoke)
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_NEW_ORLEANS,
|
||||
ID_YORK,
|
||||
ID_NEW_YORK,
|
||||
};
|
||||
|
||||
InitParams("New York Time Square", false /* lastTokenIsPrefix */);
|
||||
|
||||
AddLocality("New Orleans", ID_NEW_ORLEANS);
|
||||
AddLocality("York", ID_YORK);
|
||||
AddLocality("New York", ID_NEW_YORK);
|
||||
|
||||
TEST_EQUAL(GetTopLocalities(100 /* limit */), Ids({ID_NEW_ORLEANS, ID_YORK, ID_NEW_YORK}), ());
|
||||
TEST_EQUAL(GetTopLocalities(2 /* limit */), Ids({ID_YORK, ID_NEW_YORK}), ());
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_NEW_YORK}), ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(LocalityScorerTest, NumbersMatch)
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_MARCH,
|
||||
ID_APRIL,
|
||||
ID_MAY,
|
||||
ID_TVER
|
||||
};
|
||||
|
||||
InitParams("тверь советская 1", false /* lastTokenIsPrefix */);
|
||||
|
||||
AddLocality("поселок 1 марта", ID_MARCH);
|
||||
AddLocality("поселок 1 апреля", ID_APRIL);
|
||||
AddLocality("поселок 1 мая", ID_MAY);
|
||||
AddLocality("тверь", ID_TVER);
|
||||
|
||||
// Tver is the only matched locality as other localities were
|
||||
// matched only by number.
|
||||
TEST_EQUAL(GetTopLocalities(100 /* limit */), Ids({ID_TVER}), ());
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_TVER}), ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(LocalityScorerTest, NumbersComplexMatch)
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_MAY,
|
||||
ID_SAINT_PETERSBURG
|
||||
};
|
||||
|
||||
InitParams("saint petersburg may 1", false /* lastTokenIsPrefix */);
|
||||
|
||||
AddLocality("may 1", ID_MAY);
|
||||
AddLocality("saint petersburg", ID_SAINT_PETERSBURG);
|
||||
|
||||
TEST_EQUAL(GetTopLocalities(2 /* limit */), Ids({ID_MAY, ID_SAINT_PETERSBURG}), ());
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_MAY}), ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(LocalityScorerTest, PrefixMatch)
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_SAN_ANTONIO,
|
||||
ID_NEW_YORK,
|
||||
ID_YORK,
|
||||
ID_MOSCOW
|
||||
};
|
||||
|
||||
InitParams("New York San Anto", true /* lastTokenIsPrefix */);
|
||||
|
||||
AddLocality("San Antonio", ID_SAN_ANTONIO);
|
||||
AddLocality("New York", ID_NEW_YORK);
|
||||
AddLocality("York", ID_YORK);
|
||||
AddLocality("Moscow", ID_MOSCOW);
|
||||
|
||||
// All localities except Moscow match to the search query.
|
||||
TEST_EQUAL(GetTopLocalities(100 /* limit */), Ids({ID_SAN_ANTONIO, ID_NEW_YORK, ID_YORK}), ());
|
||||
TEST_EQUAL(GetTopLocalities(2 /* limit */), Ids({ID_SAN_ANTONIO, ID_NEW_YORK}), ());
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_SAN_ANTONIO}), ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(LocalityScorerTest, Ranks)
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_SAN_MARINO,
|
||||
ID_SAN_ANTONIO,
|
||||
ID_SAN_FRANCISCO
|
||||
};
|
||||
|
||||
AddLocality("San Marino", ID_SAN_MARINO, 10 /* rank */);
|
||||
AddLocality("Citta di San Antonio", ID_SAN_ANTONIO, 20 /* rank */);
|
||||
AddLocality("San Francisco", ID_SAN_FRANCISCO, 30 /* rank */);
|
||||
|
||||
InitParams("San", false /* lastTokenIsPrefix */);
|
||||
TEST_EQUAL(GetTopLocalities(100 /* limit */), Ids({ID_SAN_MARINO, ID_SAN_ANTONIO, ID_SAN_FRANCISCO}), ());
|
||||
TEST_EQUAL(GetTopLocalities(2 /* limit */), Ids({ID_SAN_MARINO, ID_SAN_FRANCISCO}), ());
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_SAN_FRANCISCO}), ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(LocalityScorerTest, Similarity)
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_SAN_CARLOS,
|
||||
ID_SAN_CARLOS_BARILOCHE,
|
||||
ID_SAN_CARLOS_APOQUINDO
|
||||
};
|
||||
|
||||
AddLocality("San Carlos", ID_SAN_CARLOS, 20 /* rank */);
|
||||
AddLocality("San Carlos de Bariloche", ID_SAN_CARLOS_BARILOCHE, 30 /* rank */);
|
||||
AddLocality("San Carlos de Apoquindo", ID_SAN_CARLOS_APOQUINDO, 10 /* rank */);
|
||||
|
||||
InitParams("San Carlos", false /* lastTokenIsPrefix */);
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_SAN_CARLOS}), ());
|
||||
|
||||
InitParams("San Carlos de Bariloche", false /* lastTokenIsPrefix */);
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_SAN_CARLOS_BARILOCHE}), ());
|
||||
|
||||
InitParams("San Carlos de Apoquindo", false /* lastTokenIsPrefix */);
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_SAN_CARLOS_APOQUINDO}), ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(LocalityScorerTest, DistanceToPivot)
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_ABERDEEN_CLOSE,
|
||||
ID_ABERDEEN_RANK1,
|
||||
ID_ABERDEEN_RANK2,
|
||||
ID_ABERDEEN_RANK3
|
||||
};
|
||||
|
||||
AddLocality("Aberdeen", ID_ABERDEEN_CLOSE, 10 /* rank */, m2::PointD(11.0, 11.0));
|
||||
AddLocality("Aberdeen", ID_ABERDEEN_RANK1, 100 /* rank */, m2::PointD(0.0, 0.0));
|
||||
AddLocality("Aberdeen", ID_ABERDEEN_RANK2, 50 /* rank */, m2::PointD(2.0, 2.0));
|
||||
AddLocality("Aberdeen", ID_ABERDEEN_RANK3, 5 /* rank */, m2::PointD(4.0, 4.0));
|
||||
|
||||
InitParams("Aberdeen", m2::PointD(10.0, 10.0) /* pivot */, false /* lastTokenIsPrefix */);
|
||||
|
||||
// Expected order is: the closest one (ID_ABERDEEN_CLOSE) first, then sorted by rank.
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_ABERDEEN_CLOSE}), ());
|
||||
TEST_EQUAL(GetTopLocalities(2 /* limit */), Ids({ID_ABERDEEN_CLOSE, ID_ABERDEEN_RANK1}), ());
|
||||
TEST_EQUAL(GetTopLocalities(3 /* limit */), Ids({ID_ABERDEEN_CLOSE, ID_ABERDEEN_RANK1, ID_ABERDEEN_RANK2}), ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(LocalityScorerTest, MatchedRegion)
|
||||
{
|
||||
enum
|
||||
{
|
||||
ID_SPRINGFIELD_MATCHED_REGION,
|
||||
ID_SPRINGFIELD_CLOSE,
|
||||
ID_SPRINGFIELD_RANK1,
|
||||
ID_SPRINGFIELD_RANK2
|
||||
};
|
||||
|
||||
AddLocality("Springfield", ID_SPRINGFIELD_MATCHED_REGION, 5 /* rank */, m2::PointD(0.0, 0.0),
|
||||
true /* belongsToMatchedRegion */);
|
||||
AddLocality("Springfield", ID_SPRINGFIELD_CLOSE, 10 /* rank */, m2::PointD(11.0, 11.0),
|
||||
false /* belongsToMatchedRegion */);
|
||||
AddLocality("Springfield", ID_SPRINGFIELD_RANK1, 100 /* rank */, m2::PointD(2.0, 2.0),
|
||||
false /* belongsToMatchedRegion */);
|
||||
AddLocality("Springfield", ID_SPRINGFIELD_RANK2, 50 /* rank */, m2::PointD(4.0, 4.0),
|
||||
false /* belongsToMatchedRegion */);
|
||||
|
||||
InitParams("Springfield", m2::PointD(10.0, 10.0) /* pivot */, false /* lastTokenIsPrefix */);
|
||||
|
||||
// Expected order is: the city from the matched region, then the closest one, then sorted by rank.
|
||||
TEST_EQUAL(GetTopLocalities(1 /* limit */), Ids({ID_SPRINGFIELD_MATCHED_REGION}), ());
|
||||
TEST_EQUAL(GetTopLocalities(2 /* limit */), Ids({ID_SPRINGFIELD_MATCHED_REGION, ID_SPRINGFIELD_CLOSE}), ());
|
||||
TEST_EQUAL(GetTopLocalities(3 /* limit */),
|
||||
Ids({ID_SPRINGFIELD_MATCHED_REGION, ID_SPRINGFIELD_CLOSE, ID_SPRINGFIELD_RANK1}), ());
|
||||
}
|
||||
} // namespace locality_scorer_test
|
||||
89
libs/search/search_tests/locality_selector_test.cpp
Normal file
89
libs/search/search_tests/locality_selector_test.cpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/locality_finder.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
StringUtf8Multilang ToMultilang(string const & name)
|
||||
{
|
||||
StringUtf8Multilang s;
|
||||
s.AddString(StringUtf8Multilang::kEnglishCode, name);
|
||||
return s;
|
||||
}
|
||||
|
||||
struct City
|
||||
{
|
||||
City(string const & name, m2::PointD const & center, uint64_t population, FeatureID const & id)
|
||||
: m_item(ToMultilang(name), center, {} /* boundaries */, population, id)
|
||||
{}
|
||||
|
||||
LocalityItem m_item;
|
||||
};
|
||||
|
||||
struct MatchedCity
|
||||
{
|
||||
MatchedCity(string const & name, FeatureID const & id) : m_name(name), m_id(id) {}
|
||||
|
||||
string const m_name;
|
||||
FeatureID const m_id;
|
||||
};
|
||||
|
||||
MatchedCity GetMatchedCity(m2::PointD const & point, vector<City> const & cities)
|
||||
{
|
||||
LocalitySelector selector(point);
|
||||
for (auto const & city : cities)
|
||||
selector(city.m_item);
|
||||
|
||||
string_view name;
|
||||
FeatureID id;
|
||||
selector.WithBestLocality([&](LocalityItem const & item)
|
||||
{
|
||||
item.GetName(StringUtf8Multilang::kEnglishCode, name);
|
||||
id = item.m_id;
|
||||
});
|
||||
|
||||
return {std::string(name), id};
|
||||
}
|
||||
|
||||
UNIT_TEST(LocalitySelector_Test1)
|
||||
{
|
||||
MwmSet::MwmId mwmId;
|
||||
auto const city = GetMatchedCity(m2::PointD(-97.56345, 26.79672),
|
||||
{{"Matamoros", m2::PointD(-97.50665, 26.79718), 918536, {mwmId, 0}},
|
||||
|
||||
{"Brownsville", m2::PointD(-97.48910, 26.84558), 180663, {mwmId, 1}}});
|
||||
TEST_EQUAL(city.m_name, "Matamoros", ());
|
||||
TEST_EQUAL(city.m_id.m_index, 0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(LocalitySelector_Test2)
|
||||
{
|
||||
MwmSet::MwmId mwmId;
|
||||
vector<City> const cities = {{"Moscow", m2::PointD(37.61751, 67.45398), 11971516, {mwmId, 0}},
|
||||
{"Krasnogorsk", m2::PointD(37.34040, 67.58036), 135735, {mwmId, 1}},
|
||||
{"Khimki", m2::PointD(37.44499, 67.70070), 240463, {mwmId, 2}},
|
||||
{"Mytishchi", m2::PointD(37.73394, 67.73675), 180663, {mwmId, 3}},
|
||||
{"Dolgoprudny", m2::PointD(37.51425, 67.78073), 101979, {mwmId, 4}}};
|
||||
|
||||
{
|
||||
auto const city = GetMatchedCity(m2::PointD(37.53826, 67.53554), cities);
|
||||
TEST_EQUAL(city.m_name, "Moscow", ());
|
||||
TEST_EQUAL(city.m_id.m_index, 0, ());
|
||||
}
|
||||
|
||||
{
|
||||
auto const city = GetMatchedCity(m2::PointD(37.46980, 67.66650), cities);
|
||||
TEST_EQUAL(city.m_name, "Khimki", ());
|
||||
TEST_EQUAL(city.m_id.m_index, 2, ());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
109
libs/search/search_tests/mem_search_index_tests.cpp
Normal file
109
libs/search/search_tests/mem_search_index_tests.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/base/mem_search_index.hpp"
|
||||
#include "search/feature_offset_match.hpp"
|
||||
|
||||
#include "indexer/search_string_utils.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
#include "base/uni_string_dfa.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace search_base;
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
using namespace strings;
|
||||
|
||||
namespace
|
||||
{
|
||||
using Id = uint64_t;
|
||||
|
||||
class Doc
|
||||
{
|
||||
public:
|
||||
Doc(string const & text, string const & lang) : m_lang(StringUtf8Multilang::GetLangIndex(lang))
|
||||
{
|
||||
m_tokens = NormalizeAndTokenizeString(text);
|
||||
}
|
||||
|
||||
template <typename ToDo>
|
||||
void ForEachToken(ToDo && toDo) const
|
||||
{
|
||||
for (auto const & token : m_tokens)
|
||||
toDo(m_lang, token);
|
||||
}
|
||||
|
||||
private:
|
||||
vector<strings::UniString> m_tokens;
|
||||
int8_t m_lang;
|
||||
};
|
||||
|
||||
class MemSearchIndexTest
|
||||
{
|
||||
public:
|
||||
using Index = MemSearchIndex<Id>;
|
||||
|
||||
void Add(Id const & id, Doc const & doc) { m_index.Add(id, doc); }
|
||||
|
||||
void Erase(Id const & id, Doc const & doc) { m_index.Erase(id, doc); }
|
||||
|
||||
vector<Id> StrictQuery(string const & query, string const & lang) const
|
||||
{
|
||||
auto prev = m_index.GetAllIds();
|
||||
TEST(base::IsSortedAndUnique(prev), ());
|
||||
|
||||
ForEachNormalizedToken(query, [&](strings::UniString const & token)
|
||||
{
|
||||
SearchTrieRequest<UniStringDFA> request;
|
||||
request.m_names.emplace_back(token);
|
||||
request.m_langs.insert(StringUtf8Multilang::GetLangIndex(lang));
|
||||
|
||||
vector<Id> curr;
|
||||
MatchFeaturesInTrie(request, m_index.GetRootIterator(), [](Id const & /* id */) { return true; } /* filter */,
|
||||
[&curr](Id const & id, bool /* exactMatch */) { curr.push_back(id); } /* toDo */);
|
||||
base::SortUnique(curr);
|
||||
|
||||
vector<Id> intersection;
|
||||
set_intersection(prev.begin(), prev.end(), curr.begin(), curr.end(), back_inserter(intersection));
|
||||
prev = intersection;
|
||||
});
|
||||
|
||||
return prev;
|
||||
}
|
||||
|
||||
protected:
|
||||
Index m_index;
|
||||
};
|
||||
|
||||
UNIT_CLASS_TEST(MemSearchIndexTest, Smoke)
|
||||
{
|
||||
Id const kHamlet{31337};
|
||||
Id const kMacbeth{600613};
|
||||
Doc const hamlet{"To be or not to be: that is the question...", "en"};
|
||||
Doc const macbeth{"When shall we three meet again? In thunder, lightning, or in rain? ...", "en"};
|
||||
|
||||
Add(kHamlet, hamlet);
|
||||
Add(kMacbeth, macbeth);
|
||||
|
||||
TEST_EQUAL(StrictQuery("Thunder", "en"), vector<Id>({kMacbeth}), ());
|
||||
TEST_EQUAL(StrictQuery("Question", "en"), vector<Id>({kHamlet}), ());
|
||||
TEST_EQUAL(StrictQuery("or", "en"), vector<Id>({kHamlet, kMacbeth}), ());
|
||||
TEST_EQUAL(StrictQuery("thunder lightning rain", "en"), vector<Id>({kMacbeth}), ());
|
||||
|
||||
Erase(kMacbeth, macbeth);
|
||||
|
||||
TEST_EQUAL(StrictQuery("Thunder", "en"), vector<Id>{}, ());
|
||||
TEST_EQUAL(StrictQuery("to be or not to be", "en"), vector<Id>({kHamlet}), ());
|
||||
|
||||
Erase(kHamlet, hamlet);
|
||||
TEST_EQUAL(StrictQuery("question", "en"), vector<Id>{}, ());
|
||||
}
|
||||
} // namespace
|
||||
99
libs/search/search_tests/point_rect_matcher_tests.cpp
Normal file
99
libs/search/search_tests/point_rect_matcher_tests.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/point_rect_matcher.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
|
||||
using PointIdPair = PointRectMatcher::PointIdPair;
|
||||
using RectIdPair = PointRectMatcher::RectIdPair;
|
||||
|
||||
namespace
|
||||
{
|
||||
auto const kInvalidId = numeric_limits<size_t>::max();
|
||||
|
||||
UNIT_TEST(PointRectMatcher_Smoke)
|
||||
{
|
||||
PointRectMatcher matcher;
|
||||
for (auto requestType : {PointRectMatcher::RequestType::Any, PointRectMatcher::RequestType::All})
|
||||
{
|
||||
matcher.Match(vector<PointIdPair>{}, vector<RectIdPair>{}, requestType,
|
||||
[](size_t /* pointId */, size_t /* rectId */)
|
||||
{ TEST(false, ("This callback should not be called!")); });
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(PointRectMatcher_Simple)
|
||||
{
|
||||
vector<PointIdPair> const points = {
|
||||
{PointIdPair(m2::PointD(0, 0), 0 /* id */), PointIdPair(m2::PointD(4, 5), 1 /* id */),
|
||||
PointIdPair(m2::PointD(-10, 3), 2 /* id */), PointIdPair(m2::PointD(-10, -10), 3 /* id */)}};
|
||||
|
||||
vector<RectIdPair> const rects = {{RectIdPair(m2::RectD(-1, -1, 1, 1), 0 /* id */),
|
||||
RectIdPair(m2::RectD(-9, -9, -8, 8), 1 /* id */),
|
||||
RectIdPair(m2::RectD(-11, 2, 1000, 1000), 2 /* id */)}};
|
||||
|
||||
PointRectMatcher matcher;
|
||||
|
||||
{
|
||||
matcher.Match(points, vector<RectIdPair>{}, PointRectMatcher::RequestType::Any, [](size_t pointId, size_t rectId)
|
||||
{ TEST(false, ("Callback should not be called:", pointId, rectId)); });
|
||||
}
|
||||
|
||||
{
|
||||
matcher.Match(vector<PointIdPair>{}, rects, PointRectMatcher::RequestType::Any, [](size_t pointId, size_t rectId)
|
||||
{ TEST(false, ("Callback should not be called:", pointId, rectId)); });
|
||||
}
|
||||
|
||||
{
|
||||
vector<size_t> actualMatching(points.size(), kInvalidId);
|
||||
matcher.Match(points, rects, PointRectMatcher::RequestType::Any, [&](size_t pointId, size_t rectId)
|
||||
{
|
||||
TEST_LESS(pointId, actualMatching.size(), ());
|
||||
TEST_EQUAL(actualMatching[pointId], kInvalidId, ());
|
||||
actualMatching[pointId] = rectId;
|
||||
});
|
||||
|
||||
vector<size_t> const expectedMatching = {{0, 2, 2, kInvalidId}};
|
||||
TEST_EQUAL(actualMatching, expectedMatching, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(PointRectMatcher_MultiplePointsInRect)
|
||||
{
|
||||
vector<PointIdPair> const points = {
|
||||
{PointIdPair(m2::PointD(1, 1), 0 /* id */), PointIdPair(m2::PointD(2, 2), 1 /* id */),
|
||||
PointIdPair(m2::PointD(11, 1), 2 /* id */), PointIdPair(m2::PointD(12, 2), 3 /* id */),
|
||||
PointIdPair(m2::PointD(7, 1), 4 /* id */), PointIdPair(m2::PointD(8, 2), 5 /* id */)}};
|
||||
|
||||
vector<RectIdPair> const rects = {{RectIdPair(m2::RectD(0, 0, 5, 5), 0 /* id */),
|
||||
RectIdPair(m2::RectD(10, 0, 15, 5), 1 /* id */),
|
||||
RectIdPair(m2::RectD(0, 0, 1000, 1000), 2 /* id */)}};
|
||||
|
||||
PointRectMatcher matcher;
|
||||
|
||||
{
|
||||
vector<pair<size_t, size_t>> actualCalls;
|
||||
matcher.Match(points, rects, PointRectMatcher::RequestType::All, [&](size_t pointId, size_t rectId)
|
||||
{
|
||||
TEST_LESS(pointId, points.size(), ());
|
||||
TEST_LESS(rectId, rects.size(), ());
|
||||
actualCalls.emplace_back(pointId, rectId);
|
||||
});
|
||||
|
||||
vector<pair<size_t, size_t>> const expectedCalls = {{0, 0}, {0, 2}, {1, 0}, {1, 2}, {2, 1},
|
||||
{2, 2}, {3, 1}, {3, 2}, {4, 2}, {5, 2}};
|
||||
sort(actualCalls.begin(), actualCalls.end());
|
||||
CHECK(is_sorted(expectedCalls.begin(), expectedCalls.end()), ());
|
||||
TEST_EQUAL(actualCalls, expectedCalls, ());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
139
libs/search/search_tests/query_saver_tests.cpp
Normal file
139
libs/search/search_tests/query_saver_tests.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/query_saver.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
search::QuerySaver::SearchRequest const record1("RU_ru", "test record1");
|
||||
search::QuerySaver::SearchRequest const record2("En_us", "sometext");
|
||||
} // namespace
|
||||
|
||||
namespace search
|
||||
{
|
||||
UNIT_TEST(QuerySaverFogTest)
|
||||
{
|
||||
QuerySaver saver;
|
||||
saver.Clear();
|
||||
saver.Add(record1);
|
||||
list<QuerySaver::SearchRequest> const & result = saver.Get();
|
||||
TEST_EQUAL(result.size(), 1, ());
|
||||
TEST_EQUAL(result.front(), record1, ());
|
||||
saver.Clear();
|
||||
}
|
||||
|
||||
UNIT_TEST(QuerySaverClearTest)
|
||||
{
|
||||
QuerySaver saver;
|
||||
saver.Clear();
|
||||
saver.Add(record1);
|
||||
TEST_GREATER(saver.Get().size(), 0, ());
|
||||
saver.Clear();
|
||||
TEST_EQUAL(saver.Get().size(), 0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(QuerySaverOrderingTest)
|
||||
{
|
||||
QuerySaver saver;
|
||||
saver.Clear();
|
||||
saver.Add(record1);
|
||||
saver.Add(record2);
|
||||
{
|
||||
list<QuerySaver::SearchRequest> const & result = saver.Get();
|
||||
TEST_EQUAL(result.size(), 2, ());
|
||||
TEST_EQUAL(result.back(), record1, ());
|
||||
TEST_EQUAL(result.front(), record2, ());
|
||||
}
|
||||
saver.Add(record1);
|
||||
{
|
||||
list<QuerySaver::SearchRequest> const & result = saver.Get();
|
||||
TEST_EQUAL(result.size(), 2, ());
|
||||
TEST_EQUAL(result.front(), record1, ());
|
||||
TEST_EQUAL(result.back(), record2, ());
|
||||
}
|
||||
saver.Clear();
|
||||
}
|
||||
|
||||
UNIT_TEST(QuerySaverSerializerTest)
|
||||
{
|
||||
QuerySaver saver;
|
||||
saver.Clear();
|
||||
saver.Add(record1);
|
||||
saver.Add(record2);
|
||||
string data;
|
||||
saver.Serialize(data);
|
||||
TEST_GREATER(data.size(), 0, ());
|
||||
saver.Clear();
|
||||
TEST_EQUAL(saver.Get().size(), 0, ());
|
||||
saver.Deserialize(data);
|
||||
|
||||
list<QuerySaver::SearchRequest> const & result = saver.Get();
|
||||
TEST_EQUAL(result.size(), 2, ());
|
||||
TEST_EQUAL(result.back(), record1, ());
|
||||
TEST_EQUAL(result.front(), record2, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(QuerySaverCorruptedStringTest)
|
||||
{
|
||||
QuerySaver saver;
|
||||
string corrupted("DEADBEEF");
|
||||
bool exceptionThrown = false;
|
||||
try
|
||||
{
|
||||
saver.Deserialize(corrupted);
|
||||
}
|
||||
catch (RootException const & /* exception */)
|
||||
{
|
||||
exceptionThrown = true;
|
||||
}
|
||||
list<QuerySaver::SearchRequest> const & result = saver.Get();
|
||||
TEST_EQUAL(result.size(), 0, ());
|
||||
TEST(exceptionThrown, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(QuerySaverPersistanceStore)
|
||||
{
|
||||
{
|
||||
QuerySaver saver;
|
||||
saver.Clear();
|
||||
saver.Add(record1);
|
||||
saver.Add(record2);
|
||||
}
|
||||
{
|
||||
QuerySaver saver;
|
||||
list<QuerySaver::SearchRequest> const & result = saver.Get();
|
||||
TEST_EQUAL(result.size(), 2, ());
|
||||
TEST_EQUAL(result.back(), record1, ());
|
||||
TEST_EQUAL(result.front(), record2, ());
|
||||
saver.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(QuerySaverTrimRequestTest)
|
||||
{
|
||||
QuerySaver saver;
|
||||
saver.Clear();
|
||||
|
||||
search::QuerySaver::SearchRequest const rec1("RU_ru", "test record1");
|
||||
search::QuerySaver::SearchRequest const rec2("RU_ru", "test record1 ");
|
||||
|
||||
saver.Add(rec1);
|
||||
saver.Add(rec2);
|
||||
|
||||
list<QuerySaver::SearchRequest> const & result = saver.Get();
|
||||
TEST_EQUAL(result.size(), 1, ());
|
||||
TEST_EQUAL(result.front(), rec2, ());
|
||||
saver.Clear();
|
||||
|
||||
saver.Add(rec2);
|
||||
saver.Add(rec1);
|
||||
|
||||
TEST_EQUAL(result.size(), 1, ());
|
||||
TEST_EQUAL(result.front(), rec1, ());
|
||||
saver.Clear();
|
||||
}
|
||||
} // namespace search
|
||||
302
libs/search/search_tests/ranking_tests.cpp
Normal file
302
libs/search/search_tests/ranking_tests.cpp
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/pre_ranker.hpp"
|
||||
#include "search/query_params.hpp"
|
||||
#include "search/ranking_info.hpp"
|
||||
#include "search/ranking_utils.hpp"
|
||||
#include "search/token_range.hpp"
|
||||
#include "search/token_slice.hpp"
|
||||
|
||||
#include "indexer/search_delimiters.hpp"
|
||||
#include "indexer/search_string_utils.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ranking_tests
|
||||
{
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
NameScores GetScore(string const & name, string const & query)
|
||||
{
|
||||
Delimiters delims;
|
||||
QueryParams params;
|
||||
|
||||
auto const tokens = NormalizeAndTokenizeString(query);
|
||||
|
||||
params.Init(query, tokens, !query.empty() && !delims(strings::LastUniChar(query)));
|
||||
|
||||
return GetNameScores(name, StringUtf8Multilang::kDefaultCode, TokenSlice(params, {0, tokens.size()}));
|
||||
}
|
||||
|
||||
void AssignRankingInfo(NameScores const & scores, RankingInfo & info, size_t totalLength)
|
||||
{
|
||||
info.m_nameScore = scores.m_nameScore;
|
||||
info.m_errorsMade = scores.m_errorsMade;
|
||||
info.m_isAltOrOldName = scores.m_isAltOrOldName;
|
||||
info.m_matchedFraction = scores.m_matchedLength / static_cast<float>(totalLength);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(NameScore_Smoke)
|
||||
{
|
||||
auto const test =
|
||||
[](string const & name, string const & query, NameScore nameScore, size_t errorsMade, size_t matchedLength)
|
||||
{
|
||||
NameScores const expected(nameScore, nameScore == NameScore::ZERO ? ErrorsMade() : ErrorsMade(errorsMade),
|
||||
false /* isAltOrOldNAme */, matchedLength);
|
||||
TEST_EQUAL(GetScore(name, query), expected, (name, query));
|
||||
};
|
||||
|
||||
base::ScopedLogLevelChanger const enableDebug(LDEBUG);
|
||||
// name, query, expected score, errors, match length
|
||||
test("New York", "New York", NameScore::FULL_MATCH, 0, 7);
|
||||
test("New York", "York", NameScore::SUBSTRING, 0, 4);
|
||||
test("New York", "Chicago", NameScore::ZERO, 0, 0);
|
||||
test("Moscow", "Mosc", NameScore::PREFIX, 0, 4);
|
||||
test("Moscow", "Moscow", NameScore::FULL_MATCH, 0, 6);
|
||||
test("Moscow", "Moscw", NameScore::FULL_MATCH, 1, 5);
|
||||
test("San Francisco", "Fran", NameScore::SUBSTRING, 0, 4);
|
||||
test("San Francisco", "Fran ", NameScore::ZERO, 0, 0);
|
||||
test("San Francisco", "Sa", NameScore::PREFIX, 0, 2);
|
||||
test("San Francisco", "San ", NameScore::FULL_PREFIX, 0, 3);
|
||||
test("San Francisco", "san fr", NameScore::PREFIX, 0, 5);
|
||||
test("San Francisco", "san fracis", NameScore::PREFIX, 1, 9);
|
||||
test("South Fredrick Street", "S Fredrick St", NameScore::FULL_MATCH, 0, 11);
|
||||
test("South Fredrick Street", "S Fredrick", NameScore::FULL_PREFIX, 0, 9);
|
||||
test("South Fredrick Street", "Fredrick St", NameScore::SUBSTRING, 0, 10);
|
||||
test("North Scott Boulevard", "N Scott Blvd", NameScore::FULL_MATCH, 0, 10);
|
||||
test("North Scott Boulevard", "N Scott", NameScore::FULL_PREFIX, 0, 6);
|
||||
test("North Scott Boulevard", "N Sco", NameScore::PREFIX, 0, 4);
|
||||
test("Лермонтовъ", "Лермон", NameScore::PREFIX, 0, 6);
|
||||
test("Лермонтовъ", "Лермонтов", NameScore::PREFIX, 0, 9);
|
||||
test("Лермонтовъ", "Лермонтово", NameScore::FULL_MATCH, 1, 10);
|
||||
test("Лермонтовъ", "Лермнтовъ", NameScore::FULL_MATCH, 1, 9);
|
||||
test("фото на документы", "фото", NameScore::FULL_PREFIX, 0, 4);
|
||||
test("фотоателье", "фото", NameScore::PREFIX, 0, 4);
|
||||
|
||||
test("Pennsylvania Ave NW, Washington, DC", "1600 Pennsylvania Ave", NameScore::SUBSTRING, 0, 15);
|
||||
test("Pennsylvania Ave NW, Washington, DC", "Pennsylvania Ave, Chicago", NameScore::FIRST_MATCH, 0, 15);
|
||||
|
||||
test("Barnes & Noble", "barne & noble", NameScore::FULL_MATCH, 1, 10);
|
||||
test("Barnes Avenue", "barne ", NameScore::FULL_PREFIX, 1, 5);
|
||||
test("Barnes Avenue", "barne & noble", NameScore::FIRST_MATCH, 1, 5);
|
||||
|
||||
test("Barnes Avenue", "barne's & noble", NameScore::FIRST_MATCH, 0, 6);
|
||||
test("Barnes & Noble", "barne's & noble", NameScore::FULL_MATCH, 0, 11);
|
||||
test("Barne's & Noble", "barnes & noble", NameScore::FULL_MATCH, 0, 11);
|
||||
|
||||
test("Зона №51", "зона 51", NameScore::FULL_MATCH, 0, 6);
|
||||
test("Зона №51", "зона №", NameScore::FULL_PREFIX, 0, 4);
|
||||
|
||||
test("Göztepe 60. Yıl Parkı", "goztepe parki", NameScore::FIRST_MATCH, 0, 12);
|
||||
test("Göztepe 60. Yıl Parkı", "goztepe 60 parki", NameScore::FIRST_MATCH, 0, 14);
|
||||
test("Göztepe 60. Yıl Parkı", "60 parki", NameScore::SUBSTRING, 0, 7);
|
||||
test("Göztepe 60. Yıl Parkı", "yil parki", NameScore::SUBSTRING, 0, 8);
|
||||
|
||||
test("Mariano Acosta", "arcos", NameScore::SUBSTRING, 1, 5); /// @todo PREFIX?
|
||||
/// @todo Matched, rank calculation is bad.
|
||||
// test("Marcos Paz", "arcos", NameScore::FULL_PREFIX, 1, 5);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
ErrorsMade GetErrorsMade(QueryParams::Token const & token, strings::UniString const & text)
|
||||
{
|
||||
return search::impl::GetErrorsMade(token, text, search::BuildLevenshteinDFA(text));
|
||||
}
|
||||
ErrorsMade GetPrefixErrorsMade(QueryParams::Token const & token, strings::UniString const & text)
|
||||
{
|
||||
return search::impl::GetPrefixErrorsMade(token, text, search::BuildLevenshteinDFA(text));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(ErrorsMade_Smoke)
|
||||
{
|
||||
{
|
||||
QueryParams::Token const searchToken = strings::MakeUniString("hairdressers");
|
||||
|
||||
auto nameToken = strings::MakeUniString("h");
|
||||
TEST(!GetErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
TEST(!GetPrefixErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
|
||||
nameToken = strings::MakeUniString("hair");
|
||||
TEST(!GetErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
TEST(!GetPrefixErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
}
|
||||
|
||||
{
|
||||
auto nameToken = strings::MakeUniString("hair");
|
||||
|
||||
QueryParams::Token searchToken = strings::MakeUniString("hair");
|
||||
TEST_EQUAL(GetErrorsMade(searchToken, nameToken).m_errorsMade, 0, ());
|
||||
TEST_EQUAL(GetPrefixErrorsMade(searchToken, nameToken).m_errorsMade, 0, ());
|
||||
|
||||
searchToken = strings::MakeUniString("gair");
|
||||
TEST_EQUAL(GetErrorsMade(searchToken, nameToken).m_errorsMade, 1, ());
|
||||
TEST_EQUAL(GetPrefixErrorsMade(searchToken, nameToken).m_errorsMade, 1, ());
|
||||
|
||||
searchToken = strings::MakeUniString("gai");
|
||||
TEST(!GetErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
TEST_EQUAL(GetPrefixErrorsMade(searchToken, nameToken).m_errorsMade, 1, ());
|
||||
|
||||
searchToken = strings::MakeUniString("hairrr");
|
||||
TEST(!GetErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
TEST(!GetPrefixErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
}
|
||||
|
||||
{
|
||||
auto nameToken = strings::MakeUniString("hairdresser");
|
||||
|
||||
QueryParams::Token searchToken = strings::MakeUniString("hair");
|
||||
TEST(!GetErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
TEST_EQUAL(GetPrefixErrorsMade(searchToken, nameToken).m_errorsMade, 0, ());
|
||||
|
||||
searchToken = strings::MakeUniString("gair");
|
||||
TEST_EQUAL(GetPrefixErrorsMade(searchToken, nameToken).m_errorsMade, 1, ());
|
||||
|
||||
searchToken = strings::MakeUniString("gairdrese");
|
||||
TEST(!GetErrorsMade(searchToken, nameToken).IsValid(), ());
|
||||
TEST_EQUAL(GetPrefixErrorsMade(searchToken, nameToken).m_errorsMade, 2, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(NameScore_Prefix)
|
||||
{
|
||||
TEST_EQUAL(GetScore("H Nicks", "hairdressers").m_nameScore, NameScore::ZERO, ());
|
||||
TEST_EQUAL(GetScore("Hair E14", "hairdressers").m_nameScore, NameScore::ZERO, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(NameScore_SubstringVsErrors)
|
||||
{
|
||||
string const query = "Simon";
|
||||
|
||||
RankingInfo info;
|
||||
info.m_type = Model::TYPE_SUBPOI;
|
||||
info.m_tokenRanges[Model::TYPE_SUBPOI] = {0, 1};
|
||||
info.m_numTokens = 1;
|
||||
info.m_allTokensUsed = true;
|
||||
info.m_exactMatch = false;
|
||||
|
||||
{
|
||||
RankingInfo poi1 = info;
|
||||
AssignRankingInfo(GetScore("Symon Budny and Vasil Tsiapinski", query), poi1, query.size());
|
||||
TEST_EQUAL(poi1.m_nameScore, NameScore::FULL_PREFIX, ());
|
||||
TEST_EQUAL(poi1.m_errorsMade, ErrorsMade(1), ());
|
||||
|
||||
RankingInfo poi2 = info;
|
||||
AssignRankingInfo(GetScore("Church of Saints Simon and Helen", query), poi2, query.size());
|
||||
TEST_EQUAL(poi2.m_nameScore, NameScore::SUBSTRING, ());
|
||||
TEST_EQUAL(poi2.m_errorsMade, ErrorsMade(0), ());
|
||||
|
||||
TEST_LESS(poi1.GetLinearModelRank(), poi2.GetLinearModelRank(), (poi1, poi2));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(RankingInfo_PreferCountry)
|
||||
{
|
||||
RankingInfo info;
|
||||
info.m_nameScore = NameScore::FULL_MATCH;
|
||||
info.m_errorsMade = ErrorsMade(0);
|
||||
info.m_numTokens = 1;
|
||||
info.m_matchedFraction = 1;
|
||||
info.m_allTokensUsed = true;
|
||||
info.m_exactMatch = false;
|
||||
|
||||
auto cafe = info;
|
||||
cafe.m_distanceToPivot = 1e3;
|
||||
cafe.m_tokenRanges[Model::TYPE_SUBPOI] = TokenRange(0, 1);
|
||||
cafe.m_type = Model::TYPE_SUBPOI;
|
||||
cafe.m_classifType.poi = PoiType::Eat;
|
||||
|
||||
auto country = info;
|
||||
country.m_distanceToPivot = 1e6;
|
||||
country.m_tokenRanges[Model::TYPE_COUNTRY] = TokenRange(0, 1);
|
||||
country.m_type = Model::TYPE_COUNTRY;
|
||||
country.m_rank = 100; // This is rather small rank for a country.
|
||||
|
||||
// Country should be preferred even if cafe is much closer to viewport center.
|
||||
TEST_LESS(cafe.GetLinearModelRank(), country.GetLinearModelRank(), (cafe, country));
|
||||
}
|
||||
|
||||
UNIT_TEST(RankingInfo_PrefixVsFull)
|
||||
{
|
||||
RankingInfo info;
|
||||
info.m_numTokens = 3;
|
||||
info.m_matchedFraction = 1;
|
||||
info.m_allTokensUsed = true;
|
||||
info.m_exactMatch = false;
|
||||
info.m_distanceToPivot = 1000;
|
||||
info.m_type = Model::TYPE_SUBPOI;
|
||||
info.m_tokenRanges[Model::TYPE_SUBPOI] = TokenRange(0, 2);
|
||||
|
||||
{
|
||||
// Ensure that NameScore::PREFIX with 0 errors is better than NameScore::FULL_MATCH with 1 error.
|
||||
|
||||
auto full = info;
|
||||
full.m_nameScore = NameScore::FULL_MATCH;
|
||||
full.m_errorsMade = ErrorsMade(1);
|
||||
|
||||
auto prefix = info;
|
||||
prefix.m_nameScore = NameScore::PREFIX;
|
||||
prefix.m_errorsMade = ErrorsMade(0);
|
||||
|
||||
TEST_LESS(full.GetLinearModelRank(), prefix.GetLinearModelRank(), (full, prefix));
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class MwmIdWrapper
|
||||
{
|
||||
FeatureID m_id;
|
||||
|
||||
public:
|
||||
MwmIdWrapper(MwmSet::MwmId id) : m_id(std::move(id), 0) {}
|
||||
FeatureID const & GetId() const { return m_id; }
|
||||
};
|
||||
|
||||
size_t UniqueMwmIdCount(std::vector<MwmIdWrapper> & test)
|
||||
{
|
||||
set<MwmSet::MwmId> mwmSet;
|
||||
size_t count = 0;
|
||||
MwmSet::MwmId curr;
|
||||
PreRanker::ForEachMwmOrder(test, [&](MwmIdWrapper & w)
|
||||
{
|
||||
auto const & id = w.GetId().m_mwmId;
|
||||
if (curr != id)
|
||||
{
|
||||
curr = id;
|
||||
TEST(mwmSet.insert(curr).second, ());
|
||||
}
|
||||
++count;
|
||||
});
|
||||
|
||||
TEST_EQUAL(count, test.size(), ());
|
||||
return mwmSet.size();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(PreRanker_ForEachMwmOrder)
|
||||
{
|
||||
MwmSet::MwmId id1(make_shared<MwmInfo>());
|
||||
MwmSet::MwmId id2(make_shared<MwmInfo>());
|
||||
MwmSet::MwmId id3(make_shared<MwmInfo>());
|
||||
|
||||
{
|
||||
std::vector<MwmIdWrapper> test{id1, id1};
|
||||
TEST_EQUAL(1, UniqueMwmIdCount(test), ());
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<MwmIdWrapper> test{id1, id2, id1, id3, id2};
|
||||
TEST_EQUAL(3, UniqueMwmIdCount(test), ());
|
||||
}
|
||||
}
|
||||
} // namespace ranking_tests
|
||||
67
libs/search/search_tests/region_info_getter_tests.cpp
Normal file
67
libs/search/search_tests/region_info_getter_tests.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/region_info_getter.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace search;
|
||||
|
||||
namespace
|
||||
{
|
||||
class RegionInfoGetterTest
|
||||
{
|
||||
public:
|
||||
RegionInfoGetterTest()
|
||||
{
|
||||
m_regionInfoGetter.LoadCountriesTree();
|
||||
SetLocale("default");
|
||||
}
|
||||
|
||||
void SetLocale(std::string const & locale) { m_regionInfoGetter.SetLocale(locale); }
|
||||
|
||||
std::string GetLocalizedFullName(storage::CountryId const & id) const
|
||||
{
|
||||
return m_regionInfoGetter.GetLocalizedFullName(id);
|
||||
}
|
||||
|
||||
std::string GetLocalizedCountryName(storage::CountryId const & id) const
|
||||
{
|
||||
return m_regionInfoGetter.GetLocalizedCountryName(id);
|
||||
}
|
||||
|
||||
protected:
|
||||
RegionInfoGetter m_regionInfoGetter;
|
||||
};
|
||||
|
||||
UNIT_CLASS_TEST(RegionInfoGetterTest, CountryName)
|
||||
{
|
||||
SetLocale("en");
|
||||
TEST_EQUAL(GetLocalizedCountryName("Russia_Moscow Oblast_East"), "Moscow Oblast", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("Russia_Moscow"), "Moscow", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("United States of America"), "USA", ());
|
||||
|
||||
SetLocale("ru");
|
||||
TEST_EQUAL(GetLocalizedCountryName("Russia_Moscow Oblast_East"), "Московская область", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("Russia_Moscow"), "Москва", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("United States of America"), "США", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("Crimea"), "Крым", ());
|
||||
|
||||
// En locale should be actually used.
|
||||
SetLocale("broken locale");
|
||||
TEST_EQUAL(GetLocalizedCountryName("Russia_Moscow Oblast_East"), "Moscow Oblast", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("Russia_Moscow"), "Moscow", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("United States of America"), "USA", ());
|
||||
|
||||
SetLocale("zh-Hans");
|
||||
TEST_EQUAL(GetLocalizedCountryName("Russia_Moscow Oblast_East"), "莫斯科州", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("Russia_Moscow"), "莫斯科", ());
|
||||
TEST_EQUAL(GetLocalizedCountryName("United States of America"), "美国", ());
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(RegionInfoGetterTest, FullName)
|
||||
{
|
||||
SetLocale("ru");
|
||||
TEST_EQUAL(GetLocalizedFullName("Russia_Moscow Oblast_East"), "Московская область, Россия", ());
|
||||
TEST_EQUAL(GetLocalizedFullName("Crimea"), "Крым", ());
|
||||
}
|
||||
} // namespace
|
||||
58
libs/search/search_tests/results_tests.cpp
Normal file
58
libs/search/search_tests/results_tests.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/result.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace search_tests
|
||||
{
|
||||
|
||||
UNIT_TEST(Results_Sorting)
|
||||
{
|
||||
FrozenDataSource dataSource;
|
||||
MwmSet::MwmId const id = dataSource.Register(platform::LocalCountryFile::MakeForTesting("minsk-pass")).first;
|
||||
|
||||
search::Results r;
|
||||
for (uint32_t i = 5; i != 0; --i)
|
||||
{
|
||||
search::Result res(m2::PointD::Zero(), {});
|
||||
res.FromFeature({id, i}, 0, 0, {});
|
||||
r.AddResultNoChecks(std::move(res));
|
||||
}
|
||||
|
||||
for (auto it = r.begin(); it != r.end(); ++it)
|
||||
{
|
||||
auto const & result = *it;
|
||||
TEST_EQUAL(result.GetFeatureID().m_index, std::distance(it, r.end()), ());
|
||||
TEST_EQUAL(result.GetPositionInResults(), std::distance(r.begin(), it), ());
|
||||
}
|
||||
|
||||
r.SortBy([](auto const & lhs, auto const & rhs) { return lhs.GetFeatureID().m_index < rhs.GetFeatureID().m_index; });
|
||||
|
||||
for (auto it = r.begin(); it != r.end(); ++it)
|
||||
{
|
||||
auto const & result = *it;
|
||||
TEST_EQUAL(result.GetFeatureID().m_index, std::distance(r.begin(), it) + 1, ());
|
||||
TEST_EQUAL(result.GetPositionInResults(), std::distance(r.begin(), it), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Result_PrependCity)
|
||||
{
|
||||
{
|
||||
search::Result r(m2::PointD::Zero(), {});
|
||||
r.SetAddress("Moscow, Russia");
|
||||
r.PrependCity("Moscow");
|
||||
TEST_EQUAL(r.GetAddress(), "Moscow, Russia", ());
|
||||
}
|
||||
|
||||
{
|
||||
search::Result r(m2::PointD::Zero(), "улица Михася Лынькова");
|
||||
r.SetAddress("Минская область, Беларусь");
|
||||
r.PrependCity("Минск");
|
||||
TEST_EQUAL(r.GetAddress(), "Минск, Минская область, Беларусь", ());
|
||||
}
|
||||
}
|
||||
} // namespace search_tests
|
||||
94
libs/search/search_tests/segment_tree_tests.cpp
Normal file
94
libs/search/search_tests/segment_tree_tests.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/segment_tree.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace segment_tree_tests
|
||||
{
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
|
||||
using Segment = SegmentTree::Segment;
|
||||
|
||||
auto const kInvalidId = numeric_limits<size_t>::max();
|
||||
|
||||
size_t FindAny(SegmentTree const & tree, double x)
|
||||
{
|
||||
size_t id = kInvalidId;
|
||||
bool called = false;
|
||||
tree.FindAny(x, [&](SegmentTree::Segment const & segment)
|
||||
{
|
||||
CHECK(!called, ());
|
||||
id = segment.m_id;
|
||||
called = true;
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
set<size_t> FindAll(SegmentTree & tree, double x)
|
||||
{
|
||||
set<size_t> result;
|
||||
tree.FindAll(x, [&](SegmentTree::Segment const & segment)
|
||||
{
|
||||
auto const id = segment.m_id;
|
||||
CHECK(result.find(id) == result.end(), ());
|
||||
result.insert(id);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
UNIT_TEST(SegmentTree_Smoke)
|
||||
{
|
||||
vector<Segment> const segments;
|
||||
SegmentTree tree(segments);
|
||||
TEST_EQUAL(kInvalidId, FindAny(tree, -10), ());
|
||||
TEST_EQUAL(kInvalidId, FindAny(tree, 0), ());
|
||||
TEST_EQUAL(kInvalidId, FindAny(tree, 3.14), ());
|
||||
TEST_EQUAL((set<size_t>()), FindAll(tree, 1.0), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(SegmentTree_Simple)
|
||||
{
|
||||
vector<Segment> segments = {Segment(-10000 /* from */, -10000 /* to */, 0 /* id */), Segment(-10, -6, 1),
|
||||
Segment(-7, -3, 2), Segment(-5, -2, 3)};
|
||||
CHECK(is_sorted(segments.begin(), segments.end()), ());
|
||||
|
||||
SegmentTree tree(segments);
|
||||
|
||||
TEST_EQUAL(kInvalidId, FindAny(tree, -4), ());
|
||||
|
||||
tree.Add(segments[0]);
|
||||
TEST_EQUAL(kInvalidId, FindAny(tree, -7), ());
|
||||
TEST_EQUAL(kInvalidId, FindAny(tree, -4), ());
|
||||
TEST_EQUAL((set<size_t>{}), FindAll(tree, -7), ());
|
||||
TEST_EQUAL((set<size_t>{0}), FindAll(tree, -10000), ());
|
||||
|
||||
tree.Add(segments[1]);
|
||||
TEST_EQUAL(1, FindAny(tree, -7), ());
|
||||
TEST_EQUAL(kInvalidId, FindAny(tree, -4), ());
|
||||
TEST_EQUAL((set<size_t>{1}), FindAll(tree, -7), ());
|
||||
|
||||
tree.Add(segments[3]);
|
||||
TEST_EQUAL(1, FindAny(tree, -7), ());
|
||||
TEST_EQUAL(3, FindAny(tree, -4), ());
|
||||
TEST_EQUAL((set<size_t>{3}), FindAll(tree, -4), ());
|
||||
|
||||
tree.Erase(segments[1]);
|
||||
TEST_EQUAL(kInvalidId, FindAny(tree, -7), ());
|
||||
TEST_EQUAL(3, FindAny(tree, -4), ());
|
||||
|
||||
tree.Add(segments[1]);
|
||||
tree.Add(segments[2]);
|
||||
TEST_EQUAL((set<size_t>{1, 2}), FindAll(tree, -6), ());
|
||||
TEST_EQUAL((set<size_t>{2}), FindAll(tree, -5.5), ());
|
||||
TEST_EQUAL((set<size_t>{2, 3}), FindAll(tree, -5), ());
|
||||
}
|
||||
} // namespace segment_tree_tests
|
||||
132
libs/search/search_tests/string_match_test.cpp
Normal file
132
libs/search/search_tests/string_match_test.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/approximate_string_match.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace string_match_test
|
||||
{
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
using namespace strings;
|
||||
|
||||
struct MatchCostMock
|
||||
{
|
||||
uint32_t Cost10(char) const { return 1; }
|
||||
uint32_t Cost01(char) const { return 1; }
|
||||
uint32_t Cost11(char, char) const { return 1; }
|
||||
uint32_t Cost12(char a, char const * pB) const
|
||||
{
|
||||
if (a == 'X' && pB[0] == '>' && pB[1] == '<')
|
||||
return 0;
|
||||
return 2;
|
||||
}
|
||||
uint32_t Cost21(char const * pA, char b) const { return Cost12(b, pA); }
|
||||
uint32_t Cost22(char const *, char const *) const { return 2; }
|
||||
uint32_t SwapCost(char, char) const { return 1; }
|
||||
};
|
||||
|
||||
uint32_t FullMatchCost(char const * a, char const * b, uint32_t maxCost = 1000)
|
||||
{
|
||||
return StringMatchCost(a, strlen(a), b, strlen(b), MatchCostMock(), maxCost);
|
||||
}
|
||||
|
||||
uint32_t PrefixMatchCost(char const * a, char const * b)
|
||||
{
|
||||
return StringMatchCost(a, strlen(a), b, strlen(b), MatchCostMock(), 1000, true);
|
||||
}
|
||||
|
||||
void TestEqual(vector<UniString> const & v, base::StringIL const & expected)
|
||||
{
|
||||
TEST_EQUAL(v.size(), expected.size(), (expected));
|
||||
|
||||
size_t i = 0;
|
||||
for (auto const & e : expected)
|
||||
{
|
||||
TEST_EQUAL(ToUtf8(v[i]), e, ());
|
||||
TEST_EQUAL(v[i], MakeUniString(e), ());
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(StringMatchCost_FullMatch)
|
||||
{
|
||||
TEST_EQUAL(FullMatchCost("", ""), 0, ());
|
||||
TEST_EQUAL(FullMatchCost("a", "b"), 1, ());
|
||||
TEST_EQUAL(FullMatchCost("a", ""), 1, ());
|
||||
TEST_EQUAL(FullMatchCost("", "b"), 1, ());
|
||||
TEST_EQUAL(FullMatchCost("ab", "cd"), 2, ());
|
||||
TEST_EQUAL(FullMatchCost("ab", "ba"), 1, ());
|
||||
TEST_EQUAL(FullMatchCost("abcd", "efgh"), 4, ());
|
||||
TEST_EQUAL(FullMatchCost("Hello!", "Hello!"), 0, ());
|
||||
TEST_EQUAL(FullMatchCost("Hello!", "Helo!"), 1, ());
|
||||
TEST_EQUAL(FullMatchCost("X", "X"), 0, ());
|
||||
TEST_EQUAL(FullMatchCost("X", "><"), 0, ());
|
||||
TEST_EQUAL(FullMatchCost("XXX", "><><><"), 0, ());
|
||||
TEST_EQUAL(FullMatchCost("XXX", "><X><"), 0, ());
|
||||
TEST_EQUAL(FullMatchCost("TeXt", "Te><t"), 0, ());
|
||||
TEST_EQUAL(FullMatchCost("TeXt", "Te><"), 1, ());
|
||||
TEST_EQUAL(FullMatchCost("TeXt", "TetX"), 1, ());
|
||||
TEST_EQUAL(FullMatchCost("TeXt", "Tet><"), 2, ());
|
||||
TEST_EQUAL(FullMatchCost("", "ALongString"), 11, ());
|
||||
TEST_EQUAL(FullMatchCost("x", "ALongString"), 11, ());
|
||||
TEST_EQUAL(FullMatchCost("g", "ALongString"), 10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(StringMatchCost_MaxCost)
|
||||
{
|
||||
TEST_EQUAL(FullMatchCost("g", "ALongString", 1), 2, ());
|
||||
TEST_EQUAL(FullMatchCost("g", "ALongString", 5), 6, ());
|
||||
TEST_EQUAL(FullMatchCost("g", "ALongString", 9), 10, ());
|
||||
TEST_EQUAL(FullMatchCost("g", "ALongString", 9), 10, ());
|
||||
TEST_EQUAL(FullMatchCost("g", "ALongString", 10), 10, ());
|
||||
TEST_EQUAL(FullMatchCost("g", "ALongString", 11), 10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(StringMatchCost_PrefixMatch)
|
||||
{
|
||||
TEST_EQUAL(PrefixMatchCost("", "Hello!"), 0, ());
|
||||
TEST_EQUAL(PrefixMatchCost("H", "Hello!"), 0, ());
|
||||
TEST_EQUAL(PrefixMatchCost("He", "Hello!"), 0, ());
|
||||
TEST_EQUAL(PrefixMatchCost("Hel", "Hello!"), 0, ());
|
||||
TEST_EQUAL(PrefixMatchCost("Hell", "Hello!"), 0, ());
|
||||
TEST_EQUAL(PrefixMatchCost("Hello", "Hello!"), 0, ());
|
||||
TEST_EQUAL(PrefixMatchCost("Hello!", "Hello!"), 0, ());
|
||||
TEST_EQUAL(PrefixMatchCost("Hx", "Hello!"), 1, ());
|
||||
TEST_EQUAL(PrefixMatchCost("Helpo", "Hello!"), 1, ());
|
||||
TEST_EQUAL(PrefixMatchCost("Happo", "Hello!"), 3, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(StringSplit_Smoke)
|
||||
{
|
||||
TestEqual(NormalizeAndTokenizeString("1/2"), {"1", "2"});
|
||||
TestEqual(NormalizeAndTokenizeString("xxx-yyy"), {"xxx", "yyy"});
|
||||
}
|
||||
|
||||
UNIT_TEST(StringSplit_Apostrophe)
|
||||
{
|
||||
TestEqual(NormalizeAndTokenizeString("Barne's & Noble"), {"barnes", "noble"});
|
||||
TestEqual(NormalizeAndTokenizeString("Michael's"), {"michaels"});
|
||||
TestEqual(NormalizeAndTokenizeString("'s"), {"s"});
|
||||
TestEqual(NormalizeAndTokenizeString("xyz'"), {"xyz"});
|
||||
TestEqual(NormalizeAndTokenizeString("'''"), {});
|
||||
}
|
||||
|
||||
UNIT_TEST(StringSplit_NumeroHashtag)
|
||||
{
|
||||
TestEqual(NormalizeAndTokenizeString("Зона №51"), {"зона", "51"});
|
||||
TestEqual(NormalizeAndTokenizeString("Зона № 51"), {"зона", "51"});
|
||||
TestEqual(NormalizeAndTokenizeString("Area #51"), {"area", "51"});
|
||||
TestEqual(NormalizeAndTokenizeString("Area # "), {"area"});
|
||||
TestEqual(NormalizeAndTokenizeString("Area #One"), {"area", "one"});
|
||||
TestEqual(NormalizeAndTokenizeString("xxx#yyy"), {"xxx", "yyy"});
|
||||
TestEqual(NormalizeAndTokenizeString("#'s"), {"s"});
|
||||
TestEqual(NormalizeAndTokenizeString("##osm's"), {"osms"});
|
||||
}
|
||||
|
||||
} // namespace string_match_test
|
||||
19
libs/search/search_tests/suggest_tests.cpp
Normal file
19
libs/search/search_tests/suggest_tests.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/string_utils.hpp"
|
||||
#include "search/suggest.hpp"
|
||||
|
||||
UNIT_TEST(Suggest_Smoke)
|
||||
{
|
||||
std::string const street = "Avenida Santa Fe";
|
||||
TEST_EQUAL(street + ' ', search::GetSuggestion(street, search::MakeQueryString("santa f")), ());
|
||||
TEST_EQUAL("3655 " + street + ' ', search::GetSuggestion(street, search::MakeQueryString("3655 santa f")), ());
|
||||
TEST_EQUAL("3655 east " + street + ' ', search::GetSuggestion(street, search::MakeQueryString("3655 santa east f")),
|
||||
());
|
||||
|
||||
// Full prefix match -> no suggest.
|
||||
TEST_EQUAL("", search::GetSuggestion(street, search::MakeQueryString("east santa fe")), ());
|
||||
|
||||
/// @todo Process street shorts like: st, av, ne, w, ..
|
||||
// TEST_EQUAL(street, search::GetSuggestion(street, search::MakeQueryString("av sant")), ());
|
||||
}
|
||||
197
libs/search/search_tests/text_index_tests.cpp
Normal file
197
libs/search/search_tests/text_index_tests.cpp
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/base/text_index/mem.hpp"
|
||||
#include "search/base/text_index/merger.hpp"
|
||||
#include "search/base/text_index/reader.hpp"
|
||||
#include "search/base/text_index/text_index.hpp"
|
||||
|
||||
#include "indexer/search_string_utils.hpp"
|
||||
|
||||
#include "platform/platform_tests_support/scoped_file.hpp"
|
||||
|
||||
#include "coding/file_writer.hpp"
|
||||
#include "coding/reader.hpp"
|
||||
#include "coding/write_to_sink.hpp"
|
||||
#include "coding/writer.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
|
||||
namespace text_index_tests
|
||||
{
|
||||
using namespace platform::tests_support;
|
||||
using namespace search_base;
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
|
||||
using boost::make_transform_iterator;
|
||||
|
||||
// Prepend several bytes to serialized indexes in order to check the relative offsets.
|
||||
size_t const kSkip = 10;
|
||||
|
||||
search_base::MemTextIndex BuildMemTextIndex(vector<string> const & docsCollection)
|
||||
{
|
||||
MemTextIndex memIndex;
|
||||
|
||||
for (size_t docId = 0; docId < docsCollection.size(); ++docId)
|
||||
{
|
||||
strings::Tokenize(docsCollection[docId], " ", [&memIndex, docId](std::string_view tok)
|
||||
{ memIndex.AddPosting(std::string(tok), static_cast<uint32_t>(docId)); });
|
||||
}
|
||||
|
||||
return memIndex;
|
||||
}
|
||||
|
||||
void Serdes(MemTextIndex & memIndex, MemTextIndex & deserializedMemIndex, vector<uint8_t> & buf)
|
||||
{
|
||||
buf.clear();
|
||||
{
|
||||
MemWriter<vector<uint8_t>> writer(buf);
|
||||
WriteZeroesToSink(writer, kSkip);
|
||||
memIndex.Serialize(writer);
|
||||
}
|
||||
|
||||
{
|
||||
MemReaderWithExceptions reader(buf.data() + kSkip, buf.size());
|
||||
ReaderSource<decltype(reader)> source(reader);
|
||||
deserializedMemIndex.Deserialize(source);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Index, typename Token>
|
||||
void TestForEach(Index const & index, Token const & token, vector<uint32_t> const & expected)
|
||||
{
|
||||
vector<uint32_t> actual;
|
||||
index.ForEachPosting(token, base::MakeBackInsertFunctor(actual));
|
||||
TEST_EQUAL(actual, expected, (token));
|
||||
}
|
||||
|
||||
UNIT_TEST(TextIndex_Smoke)
|
||||
{
|
||||
vector<search_base::Token> const docsCollection = {
|
||||
"a b c",
|
||||
"a c",
|
||||
};
|
||||
|
||||
auto memIndex = BuildMemTextIndex(docsCollection);
|
||||
|
||||
vector<uint8_t> indexData;
|
||||
MemTextIndex deserializedMemIndex;
|
||||
Serdes(memIndex, deserializedMemIndex, indexData);
|
||||
|
||||
for (auto const & index : {memIndex, deserializedMemIndex})
|
||||
{
|
||||
TestForEach(index, "a", {0, 1});
|
||||
TestForEach(index, "b", {0});
|
||||
TestForEach(index, "c", {0, 1});
|
||||
TestForEach(index, "d", {});
|
||||
}
|
||||
|
||||
{
|
||||
string contents;
|
||||
copy_n(indexData.begin() + kSkip, indexData.size() - kSkip, back_inserter(contents));
|
||||
ScopedFile file("text_index_tmp", contents);
|
||||
FileReader fileReader(file.GetFullPath());
|
||||
TextIndexReader textIndexReader(fileReader);
|
||||
TestForEach(textIndexReader, "a", {0, 1});
|
||||
TestForEach(textIndexReader, "b", {0});
|
||||
TestForEach(textIndexReader, "c", {0, 1});
|
||||
TestForEach(textIndexReader, "d", {});
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(TextIndex_UniString)
|
||||
{
|
||||
vector<std::string> const docsCollectionUtf8s = {
|
||||
"â b ç",
|
||||
"â ç",
|
||||
};
|
||||
vector<strings::UniString> const docsCollection(
|
||||
make_transform_iterator(docsCollectionUtf8s.begin(), &strings::MakeUniString),
|
||||
make_transform_iterator(docsCollectionUtf8s.end(), &strings::MakeUniString));
|
||||
|
||||
MemTextIndex memIndex;
|
||||
|
||||
for (size_t docId = 0; docId < docsCollection.size(); ++docId)
|
||||
{
|
||||
auto addToIndex = [&](strings::UniString const & token)
|
||||
{ memIndex.AddPosting(strings::ToUtf8(token), static_cast<uint32_t>(docId)); };
|
||||
auto delims = [](strings::UniChar const & c) { return c == ' '; };
|
||||
SplitUniString(docsCollection[docId], addToIndex, delims);
|
||||
}
|
||||
|
||||
vector<uint8_t> indexData;
|
||||
MemTextIndex deserializedMemIndex;
|
||||
Serdes(memIndex, deserializedMemIndex, indexData);
|
||||
|
||||
for (auto const & index : {memIndex, deserializedMemIndex})
|
||||
{
|
||||
TestForEach(index, strings::MakeUniString("a"), {});
|
||||
TestForEach(index, strings::MakeUniString("â"), {0, 1});
|
||||
TestForEach(index, strings::MakeUniString("b"), {0});
|
||||
TestForEach(index, strings::MakeUniString("ç"), {0, 1});
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(TextIndex_Merging)
|
||||
{
|
||||
// todo(@m) Arrays? docsCollection[i]
|
||||
vector<search_base::Token> const docsCollection1 = {
|
||||
"a b c",
|
||||
"",
|
||||
"d",
|
||||
};
|
||||
vector<search_base::Token> const docsCollection2 = {
|
||||
"",
|
||||
"a c",
|
||||
"e",
|
||||
};
|
||||
|
||||
auto memIndex1 = BuildMemTextIndex(docsCollection1);
|
||||
vector<uint8_t> indexData1;
|
||||
MemTextIndex deserializedMemIndex1;
|
||||
Serdes(memIndex1, deserializedMemIndex1, indexData1);
|
||||
|
||||
auto memIndex2 = BuildMemTextIndex(docsCollection2);
|
||||
vector<uint8_t> indexData2;
|
||||
MemTextIndex deserializedMemIndex2;
|
||||
Serdes(memIndex2, deserializedMemIndex2, indexData2);
|
||||
|
||||
{
|
||||
string contents1;
|
||||
copy_n(indexData1.begin() + kSkip, indexData1.size() - kSkip, back_inserter(contents1));
|
||||
ScopedFile file1("text_index_tmp1", contents1);
|
||||
FileReader fileReader1(file1.GetFullPath());
|
||||
TextIndexReader textIndexReader1(fileReader1);
|
||||
|
||||
string contents2;
|
||||
copy_n(indexData2.begin() + kSkip, indexData2.size() - kSkip, back_inserter(contents2));
|
||||
ScopedFile file2("text_index_tmp2", contents2);
|
||||
FileReader fileReader2(file2.GetFullPath());
|
||||
TextIndexReader textIndexReader2(fileReader2);
|
||||
|
||||
ScopedFile file3("text_index_tmp3", ScopedFile::Mode::Create);
|
||||
{
|
||||
FileWriter fileWriter(file3.GetFullPath());
|
||||
TextIndexMerger::Merge(textIndexReader1, textIndexReader2, fileWriter);
|
||||
}
|
||||
|
||||
FileReader fileReader3(file3.GetFullPath());
|
||||
TextIndexReader textIndexReader3(fileReader3);
|
||||
TestForEach(textIndexReader3, "a", {0, 1});
|
||||
TestForEach(textIndexReader3, "b", {0});
|
||||
TestForEach(textIndexReader3, "c", {0, 1});
|
||||
TestForEach(textIndexReader3, "x", {});
|
||||
TestForEach(textIndexReader3, "d", {2});
|
||||
TestForEach(textIndexReader3, "e", {2});
|
||||
}
|
||||
}
|
||||
} // namespace text_index_tests
|
||||
164
libs/search/search_tests/utm_mgrs_coords_match_test.cpp
Normal file
164
libs/search/search_tests/utm_mgrs_coords_match_test.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/utm_mgrs_coords_match.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace utm_mgrs_coords_match_test
|
||||
{
|
||||
void TestAlmostEqual(std::optional<ms::LatLon> const & maybeLatLon, double expectedLat, double expectedLon)
|
||||
{
|
||||
TEST(maybeLatLon, ());
|
||||
|
||||
// We expect the results to be quite precise.
|
||||
static double constexpr kEps = 1e-5;
|
||||
|
||||
auto const actualLat = maybeLatLon->m_lat;
|
||||
TEST(AlmostEqualAbsOrRel(actualLat, expectedLat, kEps), ("Lat is not close", actualLat, expectedLat));
|
||||
|
||||
auto const actualLon = maybeLatLon->m_lon;
|
||||
TEST(AlmostEqualAbsOrRel(actualLon, expectedLon, kEps), ("Lon is not close", actualLon, expectedLon));
|
||||
}
|
||||
|
||||
UNIT_TEST(MatchUTMCoords)
|
||||
{
|
||||
using search::MatchUTMCoords;
|
||||
|
||||
TEST(!MatchUTMCoords(" "), ());
|
||||
|
||||
// Extra spaces shouldn't break format
|
||||
TEST(MatchUTMCoords("15 N 500000 4649776"), ());
|
||||
TEST(MatchUTMCoords("15 N 500000 4649776"), ());
|
||||
TEST(MatchUTMCoords("15 N 500000 4649776"), ());
|
||||
|
||||
TestAlmostEqual(MatchUTMCoords("15N 500000 4649776"), 42.0, -93.0);
|
||||
TestAlmostEqual(MatchUTMCoords("15 N 500000 4649776"), 42.0, -93.0);
|
||||
TestAlmostEqual(MatchUTMCoords("32U 294409 5628898"), 50.77535, 6.08389);
|
||||
TestAlmostEqual(MatchUTMCoords("32 U 294409 5628898"), 50.77535, 6.08389);
|
||||
TestAlmostEqual(MatchUTMCoords("30X 476594 9328501"), 84.0, -5.00601);
|
||||
TestAlmostEqual(MatchUTMCoords("30 N 476594 9328501"), 84.0, -5.00601);
|
||||
}
|
||||
|
||||
UNIT_TEST(MatchUTMCoords_False)
|
||||
{
|
||||
using search::MatchUTMCoords;
|
||||
|
||||
TEST(!MatchUTMCoords("2 1st"), ());
|
||||
TEST(!MatchUTMCoords("15N5000004649776"), ());
|
||||
|
||||
// Wrong zone number (first two digits)
|
||||
TEST(!MatchUTMCoords("0X 476594 9328501"), ());
|
||||
TEST(!MatchUTMCoords("0 X 476594 9328501"), ());
|
||||
TEST(!MatchUTMCoords("61N 294409 5628898"), ());
|
||||
TEST(!MatchUTMCoords("61 N 294409 5628898"), ());
|
||||
|
||||
// Wrong zone letter
|
||||
TEST(!MatchUTMCoords("25I 500000 4649776"), ());
|
||||
TEST(!MatchUTMCoords("25 I 500000 4649776"), ());
|
||||
TEST(!MatchUTMCoords("25O 500000 4649776"), ());
|
||||
TEST(!MatchUTMCoords("25 O 500000 4649776"), ());
|
||||
TEST(!MatchUTMCoords("5A 500000 4649776"), ());
|
||||
TEST(!MatchUTMCoords("5 A 500000 4649776"), ());
|
||||
TEST(!MatchUTMCoords("7B 500000 4649776"), ());
|
||||
TEST(!MatchUTMCoords("7 B 500000 4649776"), ());
|
||||
|
||||
// easting out of range (must be between 100,000 m and 999,999 m)
|
||||
TEST(!MatchUTMCoords("19S 999 6360877"), ());
|
||||
TEST(!MatchUTMCoords("19S 99999 6360877"), ());
|
||||
TEST(!MatchUTMCoords("19S 1000000 6360877"), ());
|
||||
TEST(!MatchUTMCoords("19S 2000000 6360877"), ());
|
||||
|
||||
// northing out of range (must be between 0 m and 10,000,000 m)
|
||||
TEST(!MatchUTMCoords("30N 476594 10000001"), ());
|
||||
TEST(!MatchUTMCoords("30N 476594 20000000"), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(MatchMGRSCoords_parsing)
|
||||
{
|
||||
using search::MatchMGRSCoords;
|
||||
|
||||
TEST(MatchMGRSCoords("30N YF 67993 00000"), ());
|
||||
TEST(MatchMGRSCoords("30N YF 67993 00000 "), ());
|
||||
TEST(MatchMGRSCoords("30N YF 67993 00000 "), ());
|
||||
TEST(MatchMGRSCoords("30N YF 67993 00000 "), ());
|
||||
TEST(MatchMGRSCoords("30NYF 67993 00000"), ());
|
||||
TEST(MatchMGRSCoords("30NYF 67993 00000 "), ());
|
||||
TEST(MatchMGRSCoords("30NYF 67993 00000 "), ());
|
||||
TEST(MatchMGRSCoords("30NYF67993 00000"), ());
|
||||
TEST(MatchMGRSCoords("30NYF67993 00000 "), ());
|
||||
TEST(MatchMGRSCoords("30NYF67993 00000 "), ());
|
||||
TEST(MatchMGRSCoords("30NYF6799300000"), ());
|
||||
TEST(MatchMGRSCoords("30NYF6799300000 "), ());
|
||||
|
||||
// Wrong number of digits
|
||||
TEST(!MatchMGRSCoords("30NYF 679930000 "), ());
|
||||
TEST(!MatchMGRSCoords("30NYF 679930000"), ());
|
||||
|
||||
TEST(!MatchMGRSCoords("30N YF 693 23020"), ());
|
||||
TEST(!MatchMGRSCoords("30N YF 693 23 "), ());
|
||||
|
||||
// Invalid zone
|
||||
TEST(!MatchMGRSCoords("30 FF 693 230"), ());
|
||||
TEST(!MatchMGRSCoords("30A YF 693 230"), ());
|
||||
TEST(!MatchMGRSCoords("30Z YF 693 230"), ());
|
||||
TEST(!MatchMGRSCoords("30Z F 693 230"), ());
|
||||
TEST(!MatchMGRSCoords("30Z 3F 693 230"), ());
|
||||
TEST(!MatchMGRSCoords("30Z K? 693 230"), ());
|
||||
TEST(!MatchMGRSCoords("30Z IB 693 230"), ());
|
||||
TEST(!MatchMGRSCoords("30Z DO 693 230"), ());
|
||||
|
||||
// Wrong easting or northing
|
||||
TEST(!MatchMGRSCoords("30NYF 679_3 00000"), ());
|
||||
TEST(!MatchMGRSCoords("30NYF 6930&000"), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(MatchMGRSCoords_convert)
|
||||
{
|
||||
using search::MatchMGRSCoords;
|
||||
|
||||
TestAlmostEqual(MatchMGRSCoords("30N YF 67993 00000"), 0.000000, -0.592330);
|
||||
TestAlmostEqual(MatchMGRSCoords("31N BA 00000 00000"), 0.000000, 0.304980);
|
||||
TestAlmostEqual(MatchMGRSCoords("32R LR 00000 00000"), 27.107980, 6.982490);
|
||||
TestAlmostEqual(MatchMGRSCoords("32R MR 00000 00000"), 27.118850, 7.991060);
|
||||
TestAlmostEqual(MatchMGRSCoords("33R TK 05023 99880"), 27.089890, 12.025310);
|
||||
TestAlmostEqual(MatchMGRSCoords("31R GQ 69322 99158"), 31.596040, 5.838410);
|
||||
TestAlmostEqual(MatchMGRSCoords("35L KF 50481 01847"), -13.541120, 24.694510);
|
||||
TestAlmostEqual(MatchMGRSCoords("33L YL 59760 01153"), -9.028520, 17.362850);
|
||||
TestAlmostEqual(MatchMGRSCoords("36G VR 27441 12148"), -45.040410, 32.078730);
|
||||
TestAlmostEqual(MatchMGRSCoords("41R KQ 30678 99158"), 31.596040, 60.161590);
|
||||
TestAlmostEqual(MatchMGRSCoords("43F CF 66291 06635"), -49.578080, 73.150400);
|
||||
TestAlmostEqual(MatchMGRSCoords("43F DF 65832 14591"), -49.520340, 74.527920);
|
||||
TestAlmostEqual(MatchMGRSCoords("41G NL 72559 12148"), -45.040410, 63.921270);
|
||||
TestAlmostEqual(MatchMGRSCoords("41G PL 72152 04746"), -45.089800, 65.187670);
|
||||
TestAlmostEqual(MatchMGRSCoords("42G UR 00000 00000"), -45.125150, 66.456880);
|
||||
TestAlmostEqual(MatchMGRSCoords("42G VR 00000 00000"), -45.146390, 67.727970);
|
||||
TestAlmostEqual(MatchMGRSCoords("42G WR 00000 00000"), -45.153480, 69.000000);
|
||||
TestAlmostEqual(MatchMGRSCoords("42G XR 00000 00000"), -45.146390, 70.272030);
|
||||
TestAlmostEqual(MatchMGRSCoords("42G YR 00000 00000"), -45.125150, 71.543120);
|
||||
TestAlmostEqual(MatchMGRSCoords("43G CL 27848 04746"), -45.089800, 72.812330);
|
||||
TestAlmostEqual(MatchMGRSCoords("43G DL 27441 12148"), -45.040410, 74.078730);
|
||||
TestAlmostEqual(MatchMGRSCoords("41G PR 08066 09951"), -40.554160, 64.276370);
|
||||
TestAlmostEqual(MatchMGRSCoords("41G QR 07714 03147"), -40.596410, 65.454770);
|
||||
TestAlmostEqual(MatchMGRSCoords("42G UA 00000 00000"), -40.626640, 66.635320);
|
||||
TestAlmostEqual(MatchMGRSCoords("43L GF 49519 01847"), -13.541120, 77.305490);
|
||||
TestAlmostEqual(MatchMGRSCoords("45L VL 00000 00000"), -9.045430, 86.090120);
|
||||
TestAlmostEqual(MatchMGRSCoords("50Q KE 64726 97325"), 18.051740, 114.777360);
|
||||
TestAlmostEqual(MatchMGRSCoords("53F LF 66291 06635"), -49.578080, 133.150400);
|
||||
TestAlmostEqual(MatchMGRSCoords("60F VL 65832 14591"), -49.520340, 176.527920);
|
||||
|
||||
TestAlmostEqual(MatchMGRSCoords("04X ER 00000 00000"), 81.060880, -159.000000);
|
||||
TestAlmostEqual(MatchMGRSCoords("05X MK 95564 95053"), 81.016470, -153.254480);
|
||||
TestAlmostEqual(MatchMGRSCoords("08X ME 93476 90354"), 72.012660, -135.189280);
|
||||
TestAlmostEqual(MatchMGRSCoords("12H TF 59828 01847"), -36.098350, -113.667820);
|
||||
TestAlmostEqual(MatchMGRSCoords("15N YA 67993 00000"), 0.000000, -90.592330);
|
||||
TestAlmostEqual(MatchMGRSCoords("16N BF 00000 00000"), 0.000000, -89.695020);
|
||||
TestAlmostEqual(MatchMGRSCoords("16N CF 00000 00000"), 0.000000, -88.797050);
|
||||
TestAlmostEqual(MatchMGRSCoords("21L TL 40240 01153"), -9.028520, -59.362850);
|
||||
TestAlmostEqual(MatchMGRSCoords("24P YV 49519 98153"), 13.541120, -36.694510);
|
||||
TestAlmostEqual(MatchMGRSCoords("31K BA 64726 02675"), -18.051740, 0.777360);
|
||||
TestAlmostEqual(MatchMGRSCoords("31N BA 32007 00000"), 0.000000, 0.592330);
|
||||
}
|
||||
|
||||
} // namespace utm_mgrs_coords_match_test
|
||||
Loading…
Add table
Add a link
Reference in a new issue