1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include <qgeonetworkaccessmanager.h>
30 #include <qgeoroutereply_nokia.h>
31 
32 #include <QtTest/QtTest>
33 #include <QDebug>
34 #include <QNetworkReply>
35 #include <QtLocation/QGeoRouteReply>
36 #include <QtLocation/QGeoServiceProvider>
37 #include <QtLocation/QGeoRoutingManager>
38 #include <QtPositioning/QGeoRectangle>
39 #include <QtLocation/QGeoManeuver>
40 #include <QtLocation/QGeoRouteSegment>
41 
42 QT_USE_NAMESPACE
43 
44 #define CHECK_CLOSE_E(expected, actual, e) QVERIFY((qAbs(actual - expected) <= e))
45 #define CHECK_CLOSE(expected, actual) CHECK_CLOSE_E(expected, actual, qreal(1e-6))
46 
47 class MockGeoNetworkReply : public QNetworkReply
48 {
49 public:
50     MockGeoNetworkReply( QObject* parent = 0);
51     virtual void abort();
52 
53     void setFile(QFile* file);
54     void complete();
55     using QNetworkReply::setRequest;
56     using QNetworkReply::setOperation;
57     using QNetworkReply::setError;
58 
59 protected:
60     virtual qint64 readData(char *data, qint64 maxlen);
61     virtual qint64 writeData(const char *data, qint64 len);
62 
63 private:
64     QFile* m_file;
65 };
66 
MockGeoNetworkReply(QObject * parent)67 MockGeoNetworkReply::MockGeoNetworkReply(QObject* parent)
68 : QNetworkReply(parent)
69 , m_file(0)
70 {
71     setOpenMode(QIODevice::ReadOnly);
72 }
73 
abort()74 void MockGeoNetworkReply::abort()
75 {}
76 
readData(char * data,qint64 maxlen)77 qint64 MockGeoNetworkReply::readData(char *data, qint64 maxlen)
78 {
79     if (m_file) {
80         const qint64 read = m_file->read(data, maxlen);
81         if (read <= 0)
82             return -1;
83         return read;
84     }
85     return -1;
86 }
87 
writeData(const char * data,qint64 len)88 qint64 MockGeoNetworkReply::writeData(const char *data, qint64 len)
89 {
90     Q_UNUSED(data);
91     Q_UNUSED(len);
92     return -1;
93 }
94 
setFile(QFile * file)95 void MockGeoNetworkReply::setFile(QFile* file)
96 {
97     delete m_file;
98     m_file = file;
99     if (m_file)
100         m_file->setParent(this);
101 }
102 
complete()103 void MockGeoNetworkReply::complete()
104 {
105     if (error() != QNetworkReply::NoError)
106         emit errorOccurred(error());
107     setFinished(true);
108     emit finished();
109 }
110 
111 class MockGeoNetworkAccessManager : public QGeoNetworkAccessManager
112 {
113 public:
114     MockGeoNetworkAccessManager(QObject* parent = 0);
115     QNetworkReply* get(const QNetworkRequest& request);
116     QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data);
117 
118     void setReply(MockGeoNetworkReply* reply);
119 
120 private:
121     MockGeoNetworkReply* m_reply;
122 };
123 
MockGeoNetworkAccessManager(QObject * parent)124 MockGeoNetworkAccessManager::MockGeoNetworkAccessManager(QObject* parent)
125 : QGeoNetworkAccessManager(parent)
126 , m_reply(0)
127 {}
128 
get(const QNetworkRequest & request)129 QNetworkReply* MockGeoNetworkAccessManager::get(const QNetworkRequest& request)
130 {
131     MockGeoNetworkReply* r = m_reply;
132     m_reply = 0;
133     if (r) {
134         r->setRequest(request);
135         r->setOperation(QNetworkAccessManager::GetOperation);
136         r->setParent(0);
137     }
138 
139     return r;
140 }
141 
post(const QNetworkRequest & request,const QByteArray & data)142 QNetworkReply* MockGeoNetworkAccessManager::post(const QNetworkRequest &request, const QByteArray &data)
143 {
144     Q_UNUSED(request);
145     Q_UNUSED(data);
146     QTest::qFail("Not implemented", __FILE__, __LINE__);
147     return new MockGeoNetworkReply();
148 }
149 
setReply(MockGeoNetworkReply * reply)150 void MockGeoNetworkAccessManager::setReply(MockGeoNetworkReply* reply)
151 {
152     delete m_reply;
153     m_reply = reply;
154     if (m_reply)
155         m_reply->setParent(this);
156 }
157 
158 class tst_nokia_routing : public QObject
159 {
160     Q_OBJECT
161 
162 public:
163     tst_nokia_routing();
164 
165 private:
166     void calculateRoute();
167     void loadReply(const QString& filename);
168     void onReply(QGeoRouteReply* reply);
169     void verifySaneRoute(const QGeoRoute& route);
170 
171     // Infrastructure slots
172 private Q_SLOTS:
173     void routingFinished(QGeoRouteReply* reply);
174     void routingError(QGeoRouteReply* reply, QGeoRouteReply::Error error, QString errorString);
175 
176     // Test slots
177 private Q_SLOTS:
178     void initTestCase();
179     void cleanupTestCase();
180     void cleanup();
181     void can_compute_route_for_all_supported_travel_modes();
182     void can_compute_route_for_all_supported_travel_modes_data();
183     void can_compute_route_for_all_supported_optimizations();
184     void can_compute_route_for_all_supported_optimizations_data();
185     void can_handle_multiple_routes_in_response();
186     void can_handle_no_route_exists_case();
187     void can_handle_invalid_server_responses();
188     void can_handle_invalid_server_responses_data();
189     void can_handle_additions_to_routing_xml();
190     void foobar();
191     void foobar_data();
192 
193 private:
194     QGeoServiceProvider* m_geoServiceProvider;
195     MockGeoNetworkAccessManager* m_networkManager;
196     QGeoRoutingManager* m_routingManager;
197     QGeoRouteReply* m_reply;
198     MockGeoNetworkReply* m_replyUnowned;
199     QGeoRouteRequest m_dummyRequest;
200     bool m_calculationDone;
201     bool m_expectError;
202 };
203 
tst_nokia_routing()204 tst_nokia_routing::tst_nokia_routing()
205 : m_geoServiceProvider(0)
206 , m_networkManager(0)
207 , m_routingManager(0)
208 , m_reply(0)
209 , m_replyUnowned()
210 , m_calculationDone(true)
211 , m_expectError(false)
212 {
213 }
214 
loadReply(const QString & filename)215 void tst_nokia_routing::loadReply(const QString& filename)
216 {
217     QFile* file = new QFile(QFINDTESTDATA(filename));
218     if (!file->open(QIODevice::ReadOnly)) {
219         delete file;
220         file = 0;
221         qDebug() << filename;
222         QTest::qFail("Failed to open file", __FILE__, __LINE__);
223     }
224 
225     m_replyUnowned = new MockGeoNetworkReply();
226     m_replyUnowned->setFile(file);
227     m_networkManager->setReply(m_replyUnowned);
228 }
229 
calculateRoute()230 void tst_nokia_routing::calculateRoute()
231 {
232     QVERIFY2(m_replyUnowned, "No reply set");
233     m_calculationDone = false;
234     m_routingManager->calculateRoute(m_dummyRequest);
235     m_replyUnowned->complete();
236     m_replyUnowned = 0;
237     // Timeout of 200ms is required for slow targets (e.g. Qemu)
238     QTRY_VERIFY_WITH_TIMEOUT(m_calculationDone, 200);
239 }
240 
onReply(QGeoRouteReply * reply)241 void tst_nokia_routing::onReply(QGeoRouteReply* reply)
242 {
243     QVERIFY(reply);
244     //QVERIFY(0 == m_reply);
245     m_reply = reply;
246     if (m_reply)
247         m_reply->setParent(0);
248     m_calculationDone = true;
249 }
250 
verifySaneRoute(const QGeoRoute & route)251 void tst_nokia_routing::verifySaneRoute(const QGeoRoute& route)
252 {
253     QVERIFY(route.distance() > 0);
254     QVERIFY(route.travelTime() > 0);
255     QVERIFY(route.travelMode() != 0);
256 
257     const QGeoRectangle bounds = route.bounds();
258     QVERIFY(bounds.width() > 0);
259     QVERIFY(bounds.height() > 0);
260 
261     const QList<QGeoCoordinate> path = route.path();
262     QVERIFY(path.size() >= 2);
263 
264     foreach (const QGeoCoordinate& coord, path) {
265         QVERIFY(coord.isValid());
266         QVERIFY(bounds.contains(coord));
267     }
268 
269     QGeoRouteSegment segment = route.firstRouteSegment();
270     bool first = true, last = false;
271 
272     do {
273         const QGeoRouteSegment next = segment.nextRouteSegment();
274         last = next.isValid();
275 
276         QVERIFY(segment.isValid());
277         QVERIFY(segment.distance() >= 0);
278         QVERIFY(segment.travelTime() >= 0); // times are rounded and thus may end up being zero
279 
280         const QList<QGeoCoordinate> path = segment.path();
281         foreach (const QGeoCoordinate& coord, path) {
282             QVERIFY(coord.isValid());
283             if (!first && !last) {
284                 QVERIFY(bounds.contains(coord)); // on pt and pedestrian
285             }
286         }
287 
288         const QGeoManeuver maneuver = segment.maneuver();
289 
290         if (maneuver.isValid()) {
291             QVERIFY(!maneuver.instructionText().isEmpty());
292             QVERIFY(maneuver.position().isValid());
293             if (!first && !last) {
294                 QVERIFY(bounds.contains(maneuver.position())); // on pt and pedestrian
295             }
296         }
297 
298         segment = next;
299         first = false;
300     } while (!last);
301 }
302 
routingFinished(QGeoRouteReply * reply)303 void tst_nokia_routing::routingFinished(QGeoRouteReply* reply)
304 {
305     onReply(reply);
306 }
307 
routingError(QGeoRouteReply * reply,QGeoRouteReply::Error error,QString errorString)308 void tst_nokia_routing::routingError(QGeoRouteReply* reply, QGeoRouteReply::Error error, QString errorString)
309 {
310     Q_UNUSED(error);
311 
312     if (!m_expectError) {
313         QFAIL(qPrintable(errorString));
314     } else {
315         onReply(reply);
316     }
317 }
318 
initTestCase()319 void tst_nokia_routing::initTestCase()
320 {
321     QStringList providers = QGeoServiceProvider::availableServiceProviders();
322     QVERIFY(providers.contains(QStringLiteral("here")));
323 
324     m_networkManager = new MockGeoNetworkAccessManager();
325 
326     QVariantMap parameters;
327     parameters.insert(QStringLiteral("nam"), QVariant::fromValue<void*>(m_networkManager));
328     parameters.insert(QStringLiteral("here.app_id"), "stub");
329     parameters.insert(QStringLiteral("here.token"), "stub");
330 
331     m_geoServiceProvider = new QGeoServiceProvider(QStringLiteral("here"), parameters);
332     QVERIFY(m_geoServiceProvider);
333 
334     m_routingManager = m_geoServiceProvider->routingManager();
335     QVERIFY(m_routingManager);
336 
337     connect(m_routingManager, SIGNAL(finished(QGeoRouteReply*)),
338             this, SLOT(routingFinished(QGeoRouteReply*)));
339     connect(m_routingManager, SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString)),
340             this, SLOT(routingError(QGeoRouteReply*,QGeoRouteReply::Error,QString)));
341 
342     QList<QGeoCoordinate> waypoints;
343     waypoints.push_back(QGeoCoordinate(1, 1));
344     waypoints.push_back(QGeoCoordinate(2, 2));
345     m_dummyRequest.setWaypoints(waypoints);
346 }
347 
cleanupTestCase()348 void tst_nokia_routing::cleanupTestCase()
349 {
350     delete m_geoServiceProvider;
351 
352     // network access manager will be deleted by plugin
353 
354     m_geoServiceProvider = 0;
355     m_networkManager = 0;
356     m_routingManager = 0;
357 }
358 
cleanup()359 void tst_nokia_routing::cleanup()
360 {
361     delete m_reply;
362     m_reply = 0;
363     m_replyUnowned = 0;
364     m_expectError = false;
365 }
366 
can_compute_route_for_all_supported_travel_modes()367 void tst_nokia_routing::can_compute_route_for_all_supported_travel_modes()
368 {
369     QFETCH(int, travelMode);
370     QFETCH(QString, file);
371     QFETCH(qreal, distance);
372     QFETCH(int, duration);
373 
374     loadReply(file);
375     calculateRoute();
376 
377     QList<QGeoRoute> routes = m_reply->routes();
378     QCOMPARE(1, routes.size());
379     QGeoRoute& route = routes[0];
380     QCOMPARE(travelMode, (int)route.travelMode());
381     CHECK_CLOSE(distance, route.distance());
382     QCOMPARE(duration, route.travelTime());
383     verifySaneRoute(route);
384 }
385 
can_compute_route_for_all_supported_travel_modes_data()386 void tst_nokia_routing::can_compute_route_for_all_supported_travel_modes_data()
387 {
388     QTest::addColumn<int>("travelMode");
389     QTest::addColumn<QString>("file");
390     QTest::addColumn<qreal>("distance");
391     QTest::addColumn<int>("duration");
392 
393     QTest::newRow("Car") << (int)QGeoRouteRequest::CarTravel << QString("travelmode-car.xml") << (qreal)1271.0 << 243;
394     QTest::newRow("Pedestrian") << (int)QGeoRouteRequest::PedestrianTravel << QString("travelmode-pedestrian.xml") << (qreal)1107.0 << 798;
395     QTest::newRow("Public Transport") << (int)QGeoRouteRequest::PublicTransitTravel << QString("travelmode-public-transport.xml") << (qreal)1388.0 << 641;
396 }
397 
can_compute_route_for_all_supported_optimizations()398 void tst_nokia_routing::can_compute_route_for_all_supported_optimizations()
399 {
400     QFETCH(int, optimization);
401     QFETCH(QString, file);
402     QFETCH(qreal, distance);
403     QFETCH(int, duration);
404     m_dummyRequest.setRouteOptimization((QGeoRouteRequest::RouteOptimization)optimization);
405     loadReply(file);
406     calculateRoute();
407     QList<QGeoRoute> routes = m_reply->routes();
408     QCOMPARE(1, routes.size());
409     QGeoRoute& route = routes[0];
410     CHECK_CLOSE(distance, route.distance());
411     QCOMPARE(duration, route.travelTime());
412     verifySaneRoute(route);
413 }
414 
can_compute_route_for_all_supported_optimizations_data()415 void tst_nokia_routing::can_compute_route_for_all_supported_optimizations_data()
416 {
417     QTest::addColumn<int>("optimization");
418     QTest::addColumn<QString>("file");
419     QTest::addColumn<qreal>("distance");
420     QTest::addColumn<int>("duration");
421 
422     QTest::newRow("Shortest") << (int)QGeoRouteRequest::ShortestRoute << QString("optim-shortest.xml") << qreal(1177.0) << 309;
423     QTest::newRow("Fastest") << (int)QGeoRouteRequest::FastestRoute << QString("optim-fastest.xml") << qreal(1271.0) << 243;
424 }
425 
can_handle_multiple_routes_in_response()426 void tst_nokia_routing::can_handle_multiple_routes_in_response()
427 {
428     loadReply(QStringLiteral("multiple-routes-in-response.xml"));
429     calculateRoute();
430     QList<QGeoRoute> routes = m_reply->routes();
431     QCOMPARE(2, routes.size());
432 
433     verifySaneRoute(routes[0]);
434     verifySaneRoute(routes[1]);
435 }
436 
can_handle_no_route_exists_case()437 void tst_nokia_routing::can_handle_no_route_exists_case()
438 {
439     loadReply(QStringLiteral("error-no-route.xml"));
440     calculateRoute();
441     QCOMPARE(QGeoRouteReply::NoError, m_reply->error());
442     QList<QGeoRoute> routes = m_reply->routes();
443     QCOMPARE(0, routes.size());
444 }
445 
can_handle_additions_to_routing_xml()446 void tst_nokia_routing::can_handle_additions_to_routing_xml()
447 {
448     loadReply(QStringLiteral("littered-with-new-tags.xml"));
449     calculateRoute();
450     QCOMPARE(QGeoRouteReply::NoError, m_reply->error());
451     QList<QGeoRoute> routes = m_reply->routes();
452     QVERIFY(routes.size() > 0);
453 }
454 
can_handle_invalid_server_responses()455 void tst_nokia_routing::can_handle_invalid_server_responses()
456 {
457     QFETCH(QString, file);
458 
459     m_expectError = true;
460 
461     loadReply(file);
462     calculateRoute();
463     QCOMPARE(QGeoRouteReply::ParseError, m_reply->error());
464 }
465 
can_handle_invalid_server_responses_data()466 void tst_nokia_routing::can_handle_invalid_server_responses_data()
467 {
468     QTest::addColumn<QString>("file");
469 
470     QTest::newRow("Trash") << QString("invalid-response-trash.xml");
471     QTest::newRow("Half way through") << QString("invalid-response-half-way-through.xml");
472     QTest::newRow("No <CalculateRoute> tag") << QString("invalid-response-no-calculateroute-tag.xml");
473 }
474 
foobar()475 void tst_nokia_routing::foobar()
476 {
477     QFETCH(int, code);
478 
479     m_expectError = true;
480     m_replyUnowned = new MockGeoNetworkReply();
481     m_replyUnowned->setError(static_cast<QNetworkReply::NetworkError>(code), QStringLiteral("Test error"));
482     m_networkManager->setReply(m_replyUnowned);
483     calculateRoute();
484     QCOMPARE(QGeoRouteReply::CommunicationError, m_reply->error());
485 }
486 
foobar_data()487 void tst_nokia_routing::foobar_data()
488 {
489     QTest::addColumn<int>("code");
490 
491     QTest::newRow("QNetworkReply::ConnectionRefusedError") << int(QNetworkReply::ConnectionRefusedError);
492     QTest::newRow("QNetworkReply::RemoteHostClosedError") << int(QNetworkReply::RemoteHostClosedError);
493     QTest::newRow("QNetworkReply::HostNotFoundError") << int(QNetworkReply::HostNotFoundError);
494     QTest::newRow("QNetworkReply::TimeoutError") << int(QNetworkReply::TimeoutError);
495     QTest::newRow("QNetworkReply::OperationCanceledError") << int(QNetworkReply::OperationCanceledError);
496     QTest::newRow("QNetworkReply::SslHandshakeFailedError") << int(QNetworkReply::SslHandshakeFailedError);
497     QTest::newRow("QNetworkReply::TemporaryNetworkFailureError") << int(QNetworkReply::TemporaryNetworkFailureError);
498     QTest::newRow("QNetworkReply::ProxyConnectionRefusedError") << int(QNetworkReply::ProxyConnectionRefusedError);
499     QTest::newRow("QNetworkReply::ProxyConnectionClosedError") << int(QNetworkReply::ProxyConnectionClosedError);
500     QTest::newRow("QNetworkReply::ProxyNotFoundError") << int(QNetworkReply::ProxyNotFoundError);
501     QTest::newRow("QNetworkReply::ProxyTimeoutError") << int(QNetworkReply::ProxyTimeoutError);
502     QTest::newRow("QNetworkReply::ProxyAuthenticationRequiredError") << int(QNetworkReply::ProxyAuthenticationRequiredError);
503     QTest::newRow("QNetworkReply::ContentAccessDenied") << int(QNetworkReply::ContentAccessDenied);
504     QTest::newRow("QNetworkReply::ContentOperationNotPermittedError") << int(QNetworkReply::ContentOperationNotPermittedError);
505     QTest::newRow("QNetworkReply::ContentNotFoundError") << int(QNetworkReply::ContentNotFoundError);
506     QTest::newRow("QNetworkReply::ContentReSendError") << int(QNetworkReply::ContentReSendError);
507     QTest::newRow("QNetworkReply::ProtocolUnknownError") << int(QNetworkReply::ProtocolUnknownError);
508     QTest::newRow("QNetworkReply::ProtocolInvalidOperationError") << int(QNetworkReply::ProtocolInvalidOperationError);
509     QTest::newRow("QNetworkReply::UnknownNetworkError") << int(QNetworkReply::UnknownNetworkError);
510     QTest::newRow("QNetworkReply::UnknownProxyError") << int(QNetworkReply::UnknownProxyError);
511     QTest::newRow("QNetworkReply::ProxyAuthenticationRequiredError") << int(QNetworkReply::ProxyAuthenticationRequiredError);
512     QTest::newRow("QNetworkReply::ProtocolFailure") << int(QNetworkReply::ProtocolFailure);
513 }
514 
515 
516 QTEST_MAIN(tst_nokia_routing)
517 
518 #include "tst_routing.moc"
519