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 | |
67 | MockGeoNetworkReply::MockGeoNetworkReply(QObject* parent) |
68 | : QNetworkReply(parent) |
69 | , m_file(0) |
70 | { |
71 | setOpenMode(QIODevice::ReadOnly); |
72 | } |
73 | |
74 | void MockGeoNetworkReply::abort() |
75 | {} |
76 | |
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 | |
88 | qint64 MockGeoNetworkReply::writeData(const char *data, qint64 len) |
89 | { |
90 | Q_UNUSED(data); |
91 | Q_UNUSED(len); |
92 | return -1; |
93 | } |
94 | |
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 | |
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 | |
124 | MockGeoNetworkAccessManager::MockGeoNetworkAccessManager(QObject* parent) |
125 | : QGeoNetworkAccessManager(parent) |
126 | , m_reply(0) |
127 | {} |
128 | |
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 | |
142 | QNetworkReply* 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 | |
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 | |
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 | |
215 | void 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 | |
230 | void 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 | |
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 | |
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 | |
303 | void tst_nokia_routing::routingFinished(QGeoRouteReply* reply) |
304 | { |
305 | onReply(reply); |
306 | } |
307 | |
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 | |
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" ), 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 | |
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 | |
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 | |
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(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 | |
386 | void 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 | |
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(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 | |
415 | void 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 | |
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(route: routes[0]); |
434 | verifySaneRoute(route: routes[1]); |
435 | } |
436 | |
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 | |
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 | |
455 | void 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 | |
466 | void 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 | |
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(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 | |
487 | void 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 | |
516 | QTEST_MAIN(tst_nokia_routing) |
517 | |
518 | #include "tst_routing.moc" |
519 | |