diff --git a/integration_tests/integration_tests.pro b/integration_tests/integration_tests.pro new file mode 100644 index 0000000000..5babe1bf5e --- /dev/null +++ b/integration_tests/integration_tests.pro @@ -0,0 +1,32 @@ +# This subproject implements integration tests. +# This tests are launched on the whole world dataset. + +# It is recommended to place tests here in the following cases: +# - tests are written to be launch on the whole world dataset; +# - tests covers significant number of subsystems; + +TARGET = integration_tests +CONFIG += console warn_on +CONFIG -= app_bundle +TEMPLATE = app + +ROOT_DIR = ../ +DEPENDENCIES = map routing search storage indexer platform geometry coding base osrm jansson protobuf tomcrypt + +macx-*: LIBS *= "-framework Foundation" "-framework IOKit" + +include($$ROOT_DIR/common.pri) + +QT *= core + +win32* : LIBS *= -lShell32 + + +SOURCES += \ + ../testing/testingmain.cpp \ + osrm_route_test.cpp \ + osrm_turn_test.cpp \ + osrm_test_tools.cpp \ + +HEADERS += \ + osrm_test_tools.hpp \ diff --git a/integration_tests/osrm_route_test.cpp b/integration_tests/osrm_route_test.cpp new file mode 100644 index 0000000000..745b279785 --- /dev/null +++ b/integration_tests/osrm_route_test.cpp @@ -0,0 +1,22 @@ +#include "../../testing/testing.hpp" + +#include "osrm_test_tools.hpp" + +using namespace routing; + +namespace +{ + UNIT_TEST(RussiaMoscowLenigradskiy39GerPanfilovtsev22RouteTest) + { + shared_ptr routerComponents = integration::GetAllMaps(); + TEST(integration::CalculateRouteAndTestRouteLength(routerComponents, {37.53758809983519, 67.536162466434234}, + {0., 0.}, {37.40993977728661, 67.644784047393685}, 14296.), ()); + } + + UNIT_TEST(RussiaMoscowGerPanfilovtsev22SolodshaPravdiRouteTest) + { + shared_ptr routerComponents = integration::GetAllMaps(); + TEST(integration::CalculateRouteAndTestRouteLength(routerComponents, {37.409929478750627, 67.644798619710073}, + {0., 0.}, {39.836562407458047, 65.774372510437971}, 253275.), ()); + } +} diff --git a/integration_tests/osrm_test_tools.cpp b/integration_tests/osrm_test_tools.cpp new file mode 100644 index 0000000000..7291d4335e --- /dev/null +++ b/integration_tests/osrm_test_tools.cpp @@ -0,0 +1,221 @@ +#include "osrm_test_tools.hpp" + +#include "../std/new.hpp" + +#include "../indexer/index.hpp" + +#include "../geometry/distance_on_sphere.hpp" + +#include "../routing/route.hpp" + +#include "../map/feature_vec_model.hpp" + +#include "../platform/platform.hpp" +#include "../platform/preferred_languages.hpp" + +#include "../search/search_engine.hpp" + + +using namespace routing; + +namespace integration +{ + class OsrmRouterWrapper : public OsrmRouter + { + public: + OsrmRouterWrapper(Index const * index, CountryFileFnT const & fn) : + OsrmRouter(index, fn) {} + ResultCode SyncCalculateRoute(m2::PointD const & startPt, m2::PointD const & startDr, m2::PointD const & finalPt, Route & route) + { + m_startPt = startPt; + m_startDr = startDr; + m_finalPt = finalPt; + m_cachedFinalNodes.clear(); + m_isFinalChanged = false; + m_requestCancel = false; + + return OsrmRouter::CalculateRouteImpl(startPt, startDr, finalPt, route); + } + }; + + shared_ptr CreateSearchEngine(search::Engine::IndexType const * pIndex) + { + ASSERT(pIndex, ()); + Platform const & pl = GetPlatform(); + try + { + shared_ptr searchEngine(new search::Engine( + pIndex, + pl.GetReader(SEARCH_CATEGORIES_FILE_NAME), + pl.GetReader(PACKED_POLYGONS_FILE), + pl.GetReader(COUNTRIES_FILE), + languages::GetCurrentOrig())); + return searchEngine; + } + catch (RootException const &) + { + ASSERT(false, ()); + return nullptr; + } + } + + shared_ptr CreateFeaturesFetcher(vector const & mapNames) + { + shared_ptr featuresFetcher(new model::FeaturesFetcher); + featuresFetcher->InitClassificator(); + + for (auto const mapName : mapNames) + { + if (featuresFetcher->AddMap(mapName) == -1) + { + ASSERT(false, ()); + return nullptr; + } + } + return featuresFetcher; + } + + shared_ptr CreateSearchEngine(shared_ptr featuresFetcher) + { + shared_ptr searchEngine = CreateSearchEngine(&featuresFetcher->GetIndex()); + if (!searchEngine.get()) + ASSERT(false, ()); + return searchEngine; + } + + shared_ptr CreateOsrmRouter(shared_ptr featuresFetcher, + shared_ptr searchEngine) + { + shared_ptr osrmRouter(new OsrmRouterWrapper(&featuresFetcher->GetIndex(), + [searchEngine] (m2::PointD const & pt) + { + return searchEngine->GetCountryFile(pt); + })); + return osrmRouter; + } + + class OsrmRouterComponents + { + public: + OsrmRouterComponents(vector const & mapNames) : + m_featuresFetcher(CreateFeaturesFetcher(mapNames)), + m_searchEngine(CreateSearchEngine(m_featuresFetcher)), + m_osrmRouter(CreateOsrmRouter(m_featuresFetcher, m_searchEngine)) {} + OsrmRouterWrapper * GetOsrmRouter() const { return m_osrmRouter.get(); } + private: + shared_ptr m_featuresFetcher; + shared_ptr m_searchEngine; + shared_ptr m_osrmRouter; + }; + + void GetMapNames(vector & maps) + { + Platform const & pl = GetPlatform(); + + pl.GetFilesByExt(pl.ResourcesDir(), DATA_FILE_EXTENSION, maps); + pl.GetFilesByExt(pl.WritableDir(), DATA_FILE_EXTENSION, maps); + + sort(maps.begin(), maps.end()); + maps.erase(unique(maps.begin(), maps.end()), maps.end()); + } + + shared_ptr LoadMaps(vector const & mapNames) + { + try{ + return shared_ptr(new OsrmRouterComponents(mapNames)); + } + catch(bad_alloc &) + { + ASSERT(false, ()); + return nullptr; + } + } + + shared_ptr LoadAllMaps() + { + vector maps; + GetMapNames(maps); + ASSERT(!maps.empty(), ()); + return LoadMaps(maps); + } + + shared_ptr GetAllMaps() + { + static shared_ptr inst = LoadAllMaps(); + ASSERT(inst.get(), ()); + return inst; + } + + RouteResultT CalculateRoute(shared_ptr routerComponents, + m2::PointD const & startPt, m2::PointD const & startDr, m2::PointD const & finalPt) + { + try + { + ASSERT(routerComponents.get(), ()); + OsrmRouterWrapper * osrmRouter = routerComponents->GetOsrmRouter(); + ASSERT(osrmRouter, ()); + shared_ptr route(new Route("mapsme")); + OsrmRouter::ResultCode result = osrmRouter->SyncCalculateRoute(startPt, startDr, finalPt, *route.get()); + return RouteResultT(route, result); + } + catch(bad_alloc &) + { + ASSERT(false, ()); + return RouteResultT(nullptr, OsrmRouter::InternalError); + } + } + + bool TestTurn(shared_ptr const route, uint32_t etalonTurnNumber, m2::PointD const & etalonTurnPnt, + turns::TurnDirection etalonTurnDirection, uint32_t etalonRoundAboutExitNum) + { + ASSERT(route.get(), ()); + turns::TurnsGeomT const & turnsGeom = route->GetTurnsGeometry(); + if (etalonTurnNumber >= turnsGeom.size()) + return false; + + Route::TurnsT const & turns = route->GetTurns(); + ASSERT_LESS(etalonTurnNumber, turns.size(), ()); + Route::TurnItem const & turn = turns[etalonTurnNumber]; + if (turn.m_turn != etalonTurnDirection) + return false; + if (turn.m_exitNum != etalonRoundAboutExitNum) + return false; + + turns::TurnGeom const & turnGeom = turnsGeom[etalonTurnNumber]; + ASSERT_LESS(turnGeom.m_turnIndex, turnGeom.m_points.size(), ()); + m2::PointD turnGeomPnt = turnGeom.m_points[turnGeom.m_turnIndex]; + + double const dist = ms::DistanceOnEarth(etalonTurnPnt.y, etalonTurnPnt.x, turnGeomPnt.y, turnGeomPnt.x); + if (dist > turnInaccuracy) + return false; + return true; + } + + bool TestTurnCount(shared_ptr const route, uint32_t etalonTurnCount) + { + ASSERT(route.get(), ()); + return route->GetTurnsGeometry().size() == etalonTurnCount; + } + + bool TestRouteLength(shared_ptr const route, double etalonRouteLength) + { + ASSERT(route.get(), ()); + double const delta = etalonRouteLength * routeLengthInaccurace; + double const routeLength = route->GetDistance(); + if (routeLength - delta <= etalonRouteLength && routeLength + delta >= etalonRouteLength) + return true; + else + return false; + } + + bool CalculateRouteAndTestRouteLength(shared_ptr routerComponents, m2::PointD const & startPt, + m2::PointD const & startDr, m2::PointD const & finalPt, double etalonRouteLength) + { + RouteResultT routeResult = CalculateRoute(routerComponents, startPt, startDr, finalPt); + shared_ptr const route = routeResult.first; + OsrmRouter::ResultCode const result = routeResult.second; + if (result != OsrmRouter::NoError) + return false; + return TestRouteLength(route, etalonRouteLength); + } +} diff --git a/integration_tests/osrm_test_tools.hpp b/integration_tests/osrm_test_tools.hpp new file mode 100644 index 0000000000..476a271f71 --- /dev/null +++ b/integration_tests/osrm_test_tools.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "../std/shared_ptr.hpp" +#include "../std/string.hpp" +#include "../std/vector.hpp" + +#include "../routing/osrm_router.hpp" + +/* + * These tests are developed to simplify routing integration tests writing. + * You can use the interface bellow however you want but there are some hints. + * 1. Most likely you want to use GetAllMaps() to get shared_ptr. + * It loads all the maps from directories Platform::WritableDir() + * and Platform::ResourcesDir() only once and then reuse it. + * Use LoadMaps() only if you want to test something on a special map set. + * 2. Loading maps and calculating routes is a time consumption process. + * Do this only if you really need it. + * 3. If you want to check that a turn is absent use TestTurnCount. + * 4. The easiest way to gather all the information for writing an integration test is + * - to put a break point in OsrmRouter::CalculateRouteImpl; + * - to make a route with MapWithMe desktop application; + * - to get all necessary parameters and result of route calculation; + * - to place them into the test you're writing. + * 5. The recommended way for naming tests for route from one place to another one is + * + */ + +/// Inaccuracy of turn point in meters +double const turnInaccuracy = 2.; +/// Inaccurace of the route length. It is used for checking if routes have etalon(sample) length. +/// So the a created route has etalon length if +/// etalonLength - etalonRouteLength * routeLengthInaccurace <= route->GetDistance() +/// && etalonRouteLength + etalonRouteLength * routeLengthInaccurace >= route->GetDistance() +/// is equal to true. +double const routeLengthInaccurace = .01; + +typedef pair, routing::OsrmRouter::ResultCode> RouteResultT; + +namespace integration +{ + class OsrmRouterComponents; + + shared_ptr GetAllMaps(); + shared_ptr LoadMaps(vector const & mapNames); + + RouteResultT CalculateRoute(shared_ptr routerComponents, + m2::PointD const & startPt, m2::PointD const & startDr, m2::PointD const & finalPt); + + bool TestTurn(shared_ptr const route, uint32_t etalonTurnNumber, m2::PointD const & etalonTurnPnt, + routing::turns::TurnDirection etalonTurnDirection, uint32_t etalonRoundAboutExitNum = 0); + + bool TestTurnCount(shared_ptr const route, uint32_t etalonTurnCount); + + bool TestRouteLength(shared_ptr const route, double etalonRouteLength); + + bool CalculateRouteAndTestRouteLength(shared_ptr routerComponents, m2::PointD const & startPt, + m2::PointD const & startDr, m2::PointD const & finalPt, double etalonRouteLength); +} diff --git a/integration_tests/osrm_turn_test.cpp b/integration_tests/osrm_turn_test.cpp new file mode 100644 index 0000000000..5c7ebde715 --- /dev/null +++ b/integration_tests/osrm_turn_test.cpp @@ -0,0 +1,50 @@ +#include "../../testing/testing.hpp" + +#include "osrm_test_tools.hpp" + +#include "../routing/route.hpp" + +using namespace routing; + +UNIT_TEST(RussiaMoscowLenigradskiy39UturnTurnTest) +{ + shared_ptr routerComponents = integration::GetAllMaps(); + + RouteResultT const routeResult = integration::CalculateRoute(routerComponents, {37.537544383032568, 67.536216737893028}, + {0., 0.}, {37.538908531885973, 67.54544090660923}); + + shared_ptr const route = routeResult.first; + OsrmRouter::ResultCode const result = routeResult.second; + TEST_EQUAL(result, OsrmRouter::NoError, ()); + + TEST(integration::TestTurn(route, 0, {37.545981835916507, 67.530713137468041}, turns::TurnLeft), ()); + TEST(integration::TestTurn(route, 1, {37.546738218864334, 67.531659957257347}, turns::TurnLeft), ()); + TEST(integration::TestTurn(route, 2, {37.539925407915746, 67.537083383925875}, turns::TurnRight), ()); + TEST(!integration::TestTurn(route, 2, {37., 67.}, turns::TurnRight), ()); + + TEST(integration::TestRouteLength(route, 2033.), ()); + TEST(!integration::TestRouteLength(route, 2533.), ()); + TEST(!integration::TestRouteLength(route, 1533.), ()); +} + +UNIT_TEST(RussiaMoscowSalameiNerisUturnTurnTest) +{ + shared_ptr routerComponents = integration::GetAllMaps(); + RouteResultT const routeResult = integration::CalculateRoute(routerComponents, {37.395332276656617, 67.633925439079519}, + {0., 0.}, {37.392503720352721, 67.61975260731343}); + shared_ptr const route = routeResult.first; + OsrmRouter::ResultCode const result = routeResult.second; + + TEST_EQUAL(result, OsrmRouter::NoError, ()); + + TEST(integration::TestTurn(route, 0, {37.388482521388539, 67.633382734905041}, turns::TurnSlightRight), ()); + TEST(integration::TestTurn(route, 1, {37.387117276989784, 67.633369323859881}, turns::TurnLeft), ()); + TEST(integration::TestTurn(route, 2, {37.387380133475205, 67.632781920081243}, turns::TurnLeft), ()); + TEST(integration::TestTurn(route, 3, {37.390526364673121, 67.633106467374461}, turns::TurnRight), ()); + + TEST(integration::TestTurnCount(route, 4), ()); + + TEST(integration::TestRouteLength(route, 1637.), ()); + +} + diff --git a/omim.pro b/omim.pro index 61a094d44d..6ef70df949 100644 --- a/omim.pro +++ b/omim.pro @@ -43,7 +43,8 @@ SUBDIRS = 3party \ gui/gui_tests \ qt \ drape_head \ - map_server + map_server \ + integration_tests \ } else:drape_device { # libraries which are used on mobile devices with drape engine SUBDIRS = 3party \ diff --git a/routing/osrm_router.hpp b/routing/osrm_router.hpp index dd11b17c4d..a3918e5b30 100644 --- a/routing/osrm_router.hpp +++ b/routing/osrm_router.hpp @@ -22,6 +22,12 @@ struct PhantomNode; struct PathData; class FeatureType; struct RawRouteData; + +namespace integration +{ + class OsrmRouterWrapper; +} + namespace routing { @@ -131,6 +137,10 @@ public: RoutingMappingPtrT GetMappingByName(string const & fName, Index const * pIndex); + friend class integration::OsrmRouterWrapper; + typedef function CountryFileFnT; + CountryFileFnT m_countryFn; + void Clear() { m_mapping.clear(); diff --git a/routing/route.hpp b/routing/route.hpp index 02beffc70e..f415d6e721 100644 --- a/routing/route.hpp +++ b/routing/route.hpp @@ -78,6 +78,7 @@ public: string const & GetRouterId() const { return m_router; } m2::PolylineD const & GetPoly() const { return m_poly; } turns::TurnsGeomT const & GetTurnsGeometry() const { return m_turnsGeom; } + TurnsT const & GetTurns() const { return m_turns; } string const & GetName() const { return m_name; } bool IsValid() const { return (m_poly.GetSize() > 1); } diff --git a/std/new.hpp b/std/new.hpp new file mode 100644 index 0000000000..fc519b585f --- /dev/null +++ b/std/new.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "common_defines.hpp" + +#ifdef new +#undef new +#endif + +#include + +using std::bad_alloc; +using std::bad_array_new_length; + +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif