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
42QT_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
47class MockGeoNetworkReply : public QNetworkReply
48{
49public:
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
59protected:
60 virtual qint64 readData(char *data, qint64 maxlen);
61 virtual qint64 writeData(const char *data, qint64 len);
62
63private:
64 QFile* m_file;
65};
66
67MockGeoNetworkReply::MockGeoNetworkReply(QObject* parent)
68: QNetworkReply(parent)
69, m_file(0)
70{
71 setOpenMode(QIODevice::ReadOnly);
72}
73
74void MockGeoNetworkReply::abort()
75{}
76
77qint64 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
88qint64 MockGeoNetworkReply::writeData(const char *data, qint64 len)
89{
90 Q_UNUSED(data);
91 Q_UNUSED(len);
92 return -1;
93}
94
95void MockGeoNetworkReply::setFile(QFile* file)
96{
97 delete m_file;
98 m_file = file;
99 if (m_file)
100 m_file->setParent(this);
101}
102
103void MockGeoNetworkReply::complete()
104{
105 if (error() != QNetworkReply::NoError)
106 emit errorOccurred(error());
107 setFinished(true);
108 emit finished();
109}
110
111class MockGeoNetworkAccessManager : public QGeoNetworkAccessManager
112{
113public:
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
120private:
121 MockGeoNetworkReply* m_reply;
122};
123
124MockGeoNetworkAccessManager::MockGeoNetworkAccessManager(QObject* parent)
125: QGeoNetworkAccessManager(parent)
126, m_reply(0)
127{}
128
129QNetworkReply* 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
142QNetworkReply* MockGeoNetworkAccessManager::post(const QNetworkRequest &request, const QByteArray &data)
143{
144 Q_UNUSED(request);
145 Q_UNUSED(data);
146 QTest::qFail(statementStr: "Not implemented", __FILE__, __LINE__);
147 return new MockGeoNetworkReply();
148}
149
150void MockGeoNetworkAccessManager::setReply(MockGeoNetworkReply* reply)
151{
152 delete m_reply;
153 m_reply = reply;
154 if (m_reply)
155 m_reply->setParent(this);
156}
157
158class tst_nokia_routing : public QObject
159{
160 Q_OBJECT
161
162public:
163 tst_nokia_routing();
164
165private:
166 void calculateRoute();
167 void loadReply(const QString& filename);
168 void onReply(QGeoRouteReply* reply);
169 void verifySaneRoute(const QGeoRoute& route);
170
171 // Infrastructure slots
172private Q_SLOTS:
173 void routingFinished(QGeoRouteReply* reply);
174 void routingError(QGeoRouteReply* reply, QGeoRouteReply::Error error, QString errorString);
175
176 // Test slots
177private 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
193private:
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
204tst_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
215void tst_nokia_routing::loadReply(const QString& filename)
216{
217 QFile* file = new QFile(QFINDTESTDATA(filename));
218 if (!file->open(flags: QIODevice::ReadOnly)) {
219 delete file;
220 file = 0;
221 qDebug() << filename;
222 QTest::qFail(statementStr: "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
230void tst_nokia_routing::calculateRoute()
231{
232 QVERIFY2(m_replyUnowned, "No reply set");
233 m_calculationDone = false;
234 m_routingManager->calculateRoute(request: 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
241void 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
251void 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
303void tst_nokia_routing::routingFinished(QGeoRouteReply* reply)
304{
305 onReply(reply);
306}
307
308void 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
319void 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"), avalue: QVariant::fromValue<void*>(value: m_networkManager));
328 parameters.insert(QStringLiteral("here.app_id"), avalue: "stub");
329 parameters.insert(QStringLiteral("here.token"), avalue: "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(sender: m_routingManager, SIGNAL(finished(QGeoRouteReply*)),
338 receiver: this, SLOT(routingFinished(QGeoRouteReply*)));
339 connect(sender: m_routingManager, SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString)),
340 receiver: this, SLOT(routingError(QGeoRouteReply*,QGeoRouteReply::Error,QString)));
341
342 QList<QGeoCoordinate> waypoints;
343 waypoints.push_back(t: QGeoCoordinate(1, 1));
344 waypoints.push_back(t: QGeoCoordinate(2, 2));
345 m_dummyRequest.setWaypoints(waypoints);
346}
347
348void 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
359void tst_nokia_routing::cleanup()
360{
361 delete m_reply;
362 m_reply = 0;
363 m_replyUnowned = 0;
364 m_expectError = false;
365}
366
367void 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(filename: 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
386void tst_nokia_routing::can_compute_route_for_all_supported_travel_modes_data()
387{
388 QTest::addColumn<int>(name: "travelMode");
389 QTest::addColumn<QString>(name: "file");
390 QTest::addColumn<qreal>(name: "distance");
391 QTest::addColumn<int>(name: "duration");
392
393 QTest::newRow(dataTag: "Car") << (int)QGeoRouteRequest::CarTravel << QString("travelmode-car.xml") << (qreal)1271.0 << 243;
394 QTest::newRow(dataTag: "Pedestrian") << (int)QGeoRouteRequest::PedestrianTravel << QString("travelmode-pedestrian.xml") << (qreal)1107.0 << 798;
395 QTest::newRow(dataTag: "Public Transport") << (int)QGeoRouteRequest::PublicTransitTravel << QString("travelmode-public-transport.xml") << (qreal)1388.0 << 641;
396}
397
398void 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(filename: 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
415void tst_nokia_routing::can_compute_route_for_all_supported_optimizations_data()
416{
417 QTest::addColumn<int>(name: "optimization");
418 QTest::addColumn<QString>(name: "file");
419 QTest::addColumn<qreal>(name: "distance");
420 QTest::addColumn<int>(name: "duration");
421
422 QTest::newRow(dataTag: "Shortest") << (int)QGeoRouteRequest::ShortestRoute << QString("optim-shortest.xml") << qreal(1177.0) << 309;
423 QTest::newRow(dataTag: "Fastest") << (int)QGeoRouteRequest::FastestRoute << QString("optim-fastest.xml") << qreal(1271.0) << 243;
424}
425
426void 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(route: routes[0]);
434 verifySaneRoute(route: routes[1]);
435}
436
437void 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
446void 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
455void tst_nokia_routing::can_handle_invalid_server_responses()
456{
457 QFETCH(QString, file);
458
459 m_expectError = true;
460
461 loadReply(filename: file);
462 calculateRoute();
463 QCOMPARE(QGeoRouteReply::ParseError, m_reply->error());
464}
465
466void tst_nokia_routing::can_handle_invalid_server_responses_data()
467{
468 QTest::addColumn<QString>(name: "file");
469
470 QTest::newRow(dataTag: "Trash") << QString("invalid-response-trash.xml");
471 QTest::newRow(dataTag: "Half way through") << QString("invalid-response-half-way-through.xml");
472 QTest::newRow(dataTag: "No <CalculateRoute> tag") << QString("invalid-response-no-calculateroute-tag.xml");
473}
474
475void tst_nokia_routing::foobar()
476{
477 QFETCH(int, code);
478
479 m_expectError = true;
480 m_replyUnowned = new MockGeoNetworkReply();
481 m_replyUnowned->setError(errorCode: 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
487void tst_nokia_routing::foobar_data()
488{
489 QTest::addColumn<int>(name: "code");
490
491 QTest::newRow(dataTag: "QNetworkReply::ConnectionRefusedError") << int(QNetworkReply::ConnectionRefusedError);
492 QTest::newRow(dataTag: "QNetworkReply::RemoteHostClosedError") << int(QNetworkReply::RemoteHostClosedError);
493 QTest::newRow(dataTag: "QNetworkReply::HostNotFoundError") << int(QNetworkReply::HostNotFoundError);
494 QTest::newRow(dataTag: "QNetworkReply::TimeoutError") << int(QNetworkReply::TimeoutError);
495 QTest::newRow(dataTag: "QNetworkReply::OperationCanceledError") << int(QNetworkReply::OperationCanceledError);
496 QTest::newRow(dataTag: "QNetworkReply::SslHandshakeFailedError") << int(QNetworkReply::SslHandshakeFailedError);
497 QTest::newRow(dataTag: "QNetworkReply::TemporaryNetworkFailureError") << int(QNetworkReply::TemporaryNetworkFailureError);
498 QTest::newRow(dataTag: "QNetworkReply::ProxyConnectionRefusedError") << int(QNetworkReply::ProxyConnectionRefusedError);
499 QTest::newRow(dataTag: "QNetworkReply::ProxyConnectionClosedError") << int(QNetworkReply::ProxyConnectionClosedError);
500 QTest::newRow(dataTag: "QNetworkReply::ProxyNotFoundError") << int(QNetworkReply::ProxyNotFoundError);
501 QTest::newRow(dataTag: "QNetworkReply::ProxyTimeoutError") << int(QNetworkReply::ProxyTimeoutError);
502 QTest::newRow(dataTag: "QNetworkReply::ProxyAuthenticationRequiredError") << int(QNetworkReply::ProxyAuthenticationRequiredError);
503 QTest::newRow(dataTag: "QNetworkReply::ContentAccessDenied") << int(QNetworkReply::ContentAccessDenied);
504 QTest::newRow(dataTag: "QNetworkReply::ContentOperationNotPermittedError") << int(QNetworkReply::ContentOperationNotPermittedError);
505 QTest::newRow(dataTag: "QNetworkReply::ContentNotFoundError") << int(QNetworkReply::ContentNotFoundError);
506 QTest::newRow(dataTag: "QNetworkReply::ContentReSendError") << int(QNetworkReply::ContentReSendError);
507 QTest::newRow(dataTag: "QNetworkReply::ProtocolUnknownError") << int(QNetworkReply::ProtocolUnknownError);
508 QTest::newRow(dataTag: "QNetworkReply::ProtocolInvalidOperationError") << int(QNetworkReply::ProtocolInvalidOperationError);
509 QTest::newRow(dataTag: "QNetworkReply::UnknownNetworkError") << int(QNetworkReply::UnknownNetworkError);
510 QTest::newRow(dataTag: "QNetworkReply::UnknownProxyError") << int(QNetworkReply::UnknownProxyError);
511 QTest::newRow(dataTag: "QNetworkReply::ProxyAuthenticationRequiredError") << int(QNetworkReply::ProxyAuthenticationRequiredError);
512 QTest::newRow(dataTag: "QNetworkReply::ProtocolFailure") << int(QNetworkReply::ProtocolFailure);
513}
514
515
516QTEST_MAIN(tst_nokia_routing)
517
518#include "tst_routing.moc"
519

source code of qtlocation/tests/auto/nokia_services/routing/tst_routing.cpp