Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View 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
)

View 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);
}
}

View 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

View 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

View 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));
}
}

View 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

View 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 с", "3/7 строение 1 Б", true /* queryIsPrefix */), ());
TEST(HouseNumbersMatch("3/7 с", "3/7 строение 1 Б", false /* queryIsPrefix */), ());
TEST(!HouseNumbersMatch("3/7 с", "3/7 с", 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 с", false /* isPrefix */), ());
TEST(LooksLikeHouseNumber("3/7 с", 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

View 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, ());
}
}

View 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

View 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

View 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°4520.99″ E37°3703.62″", lat, lon), ());
TestAlmostEqual(lat, 55.755830555555556);
TestAlmostEqual(lon, 37.617672222222222);
{
TEST(MatchLatLonDegree("N-55°4520.99″ E-37°3703.62″", lat, lon), ());
double lat1, lon1;
TEST(MatchLatLonDegree("S55°4520.99″ W37°3703.62″", lat1, lon1), ());
TestAlmostEqual(lat, lat1);
TestAlmostEqual(lon, lon1);
}
TEST(MatchLatLonDegree("55°4520.9916\"N, 37°373.6228\"E hsdfjgkdsjbv", lat, lon), ());
TestAlmostEqual(lat, 55.755831);
TestAlmostEqual(lon, 37.617673);
TEST(MatchLatLonDegree("55°4520.9916″S, 37°373.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°4520.9916S, 37°373.6228W", lat, lon), ());
TestAlmostEqual(lat, -55.755831);
TestAlmostEqual(lon, -37.617673);
TEST(MatchLatLonDegree("W55°4520.9916″, S37°373.6228″", lat, lon), ());
TestAlmostEqual(lon, -55.755831);
TestAlmostEqual(lat, -37.617673);
TEST(MatchLatLonDegree("55°4520.9916″ W 37°373.6228″ N", lat, lon), ());
TestAlmostEqual(lon, -55.755831);
TestAlmostEqual(lat, 37.617673);
TEST(!MatchLatLonDegree("55°4520.9916″W 37°373.6228″E", lat, lon), ());
TEST(!MatchLatLonDegree("N55°4520.9916″ S37°373.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

View 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, ());
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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")), ());
}

View 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

View 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