1/****************************************************************************
2**
3** Copyright (C) 2016 Jolla Ltd.
4** Contact: Aaron McCarthy <aaron.mccarthy@jollamobile.com>
5** Copyright (C) 2016 The Qt Company Ltd.
6** Contact: https://www.qt.io/licensing/
7**
8** This file is part of the test suite of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:GPL-EXCEPT$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see https://www.qt.io/terms-conditions. For further
17** information use the contact form at https://www.qt.io/contact-us.
18**
19** GNU General Public License Usage
20** Alternatively, this file may be used under the terms of the GNU
21** General Public License version 3 as published by the Free Software
22** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
23** included in the packaging of this file. Please review the following
24** information to ensure the GNU General Public License requirements will
25** be met: https://www.gnu.org/licenses/gpl-3.0.html.
26**
27** $QT_END_LICENSE$
28**
29****************************************************************************/
30
31//TESTED_COMPONENT=src/location
32
33#include "tst_qnmeapositioninfosource.h"
34
35#include <QtCore/QDateTime>
36#include <QtCore/QElapsedTimer>
37#include <QtCore/QtNumeric>
38
39#ifdef Q_OS_WIN
40
41// Windows seems to require longer timeouts and step length
42// We override the standard QTestCase related macros
43
44#ifdef QTRY_COMPARE_WITH_TIMEOUT
45#undef QTRY_COMPARE_WITH_TIMEOUT
46#endif
47#define QTRY_COMPARE_WITH_TIMEOUT(__expr, __expected, __timeout) \
48do { \
49 const int __step = 100; \
50 const int __timeoutValue = __timeout; \
51 if ((__expr) != (__expected)) { \
52 QTest::qWait(0); \
53 } \
54 for (int __i = 0; __i < __timeoutValue && ((__expr) != (__expected)); __i+=__step) { \
55 QTest::qWait(__step); \
56 } \
57 QCOMPARE(__expr, __expected); \
58} while (0)
59
60#ifdef QTRY_COMPARE
61#undef QTRY_COMPARE
62#endif
63#define QTRY_COMPARE(__expr, __expected) QTRY_COMPARE_WITH_TIMEOUT(__expr, __expected, 10000)
64
65#endif
66
67tst_QNmeaPositionInfoSource::tst_QNmeaPositionInfoSource(QNmeaPositionInfoSource::UpdateMode mode, QObject *parent)
68 : QObject(parent),
69 m_mode(mode)
70{
71}
72
73void tst_QNmeaPositionInfoSource::initTestCase()
74{
75 qRegisterMetaType<QNmeaPositionInfoSource::UpdateMode>();
76}
77
78void tst_QNmeaPositionInfoSource::constructor()
79{
80 QObject o;
81 QNmeaPositionInfoSource source(m_mode, &o);
82 QCOMPARE(source.updateMode(), m_mode);
83 QCOMPARE(source.parent(), &o);
84}
85
86void tst_QNmeaPositionInfoSource::supportedPositioningMethods()
87{
88 QNmeaPositionInfoSource source(m_mode);
89 QCOMPARE(source.supportedPositioningMethods(), QNmeaPositionInfoSource::SatellitePositioningMethods);
90}
91
92void tst_QNmeaPositionInfoSource::minimumUpdateInterval()
93{
94 QNmeaPositionInfoSource source(m_mode);
95 QCOMPARE(source.minimumUpdateInterval(), 2);
96}
97
98void tst_QNmeaPositionInfoSource::userEquivalentRangeError()
99{
100 QNmeaPositionInfoSource source(m_mode);
101 QVERIFY(qIsNaN(source.userEquivalentRangeError()));
102 source.setUserEquivalentRangeError(5.1);
103 QVERIFY(qFuzzyCompare(source.userEquivalentRangeError(), 5.1));
104}
105
106void tst_QNmeaPositionInfoSource::setUpdateInterval_delayedUpdate()
107{
108 // If an update interval is set, and an update is not available at a
109 // particular interval, the source should emit the next update as soon
110 // as it becomes available
111
112 QNmeaPositionInfoSource source(m_mode);
113 QNmeaPositionInfoSourceProxyFactory factory;
114 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
115
116 QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
117 proxy->source()->setUpdateInterval(500);
118 proxy->source()->startUpdates();
119
120 QTest::qWait(ms: 600);
121 QDateTime now = QDateTime::currentDateTime();
122 proxy->feedUpdate(dt: now);
123 QTRY_COMPARE(spyUpdate.count(), 1);
124
125 // should have gotten the update immediately, and not have needed to
126 // wait until the next interval
127 QVERIFY(now.time().msecsTo(QDateTime::currentDateTime().time()) < 200);
128}
129
130void tst_QNmeaPositionInfoSource::lastKnownPosition()
131{
132 QNmeaPositionInfoSource source(m_mode);
133 QNmeaPositionInfoSourceProxyFactory factory;
134 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
135
136 QCOMPARE(proxy->source()->lastKnownPosition(), QGeoPositionInfo());
137
138 // source may need requestUpdate() or startUpdates() to be called to
139 // trigger reading of data channel
140 QSignalSpy spyTimeout(proxy->source(), SIGNAL(updateTimeout()));
141 proxy->source()->requestUpdate(timeout: proxy->source()->minimumUpdateInterval());
142 QTRY_COMPARE(spyTimeout.count(), 1);
143
144 // If an update is received and startUpdates() or requestUpdate() hasn't
145 // been called, it should still be available through lastKnownPosition()
146 QDateTime dt = QDateTime::currentDateTime().toUTC();
147 proxy->feedUpdate(dt);
148 QTRY_COMPARE(proxy->source()->lastKnownPosition().timestamp(), dt);
149
150 QList<QDateTime> dateTimes = createDateTimes(count: 5);
151 for (int i=0; i<dateTimes.count(); i++) {
152 proxy->source()->requestUpdate(); // Irrelevant for this test
153 proxy->feedUpdate(dt: dateTimes[i]);
154 QTRY_COMPARE(proxy->source()->lastKnownPosition().timestamp(), dateTimes[i]);
155 }
156
157 proxy->source()->startUpdates();
158 // if dateTimes are older than before, they will be ignored.
159 dateTimes = createDateTimes(dt: dateTimes.last().addMSecs(msecs: 100), count: 5);
160 for (int i=0; i<dateTimes.count(); i++) {
161 proxy->feedUpdate(dt: dateTimes[i]);
162 QTRY_COMPARE(proxy->source()->lastKnownPosition().timestamp(), dateTimes[i]);
163 }
164}
165
166void tst_QNmeaPositionInfoSource::beginWithBufferedData()
167{
168 // In SimulationMode, data stored in the QIODevice is read when
169 // startUpdates() or requestUpdate() is called.
170 // In RealTimeMode, all existing data in the QIODevice is ignored -
171 // only new data will be read.
172
173 QFETCH(QList<QDateTime>, dateTimes);
174 QFETCH(UpdateTriggerMethod, trigger);
175
176 QByteArray bytes;
177 for (int i=0; i<dateTimes.count(); i++)
178 bytes += QLocationTestUtils::createRmcSentence(dt: dateTimes[i]).toLatin1();
179 QBuffer buffer;
180 buffer.setData(bytes);
181
182 QNmeaPositionInfoSource source(m_mode);
183 QSignalSpy spy(&source, SIGNAL(positionUpdated(QGeoPositionInfo)));
184 source.setDevice(&buffer);
185
186 if (trigger == StartUpdatesMethod)
187 source.startUpdates();
188 else if (trigger == RequestUpdatesMethod)
189 source.requestUpdate();
190
191 if (m_mode == QNmeaPositionInfoSource::RealTimeMode) {
192 QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 0, 300);
193 } else {
194 if (trigger == StartUpdatesMethod) {
195 QTRY_COMPARE(spy.count(), dateTimes.count());
196 for (int i=0; i<dateTimes.count(); i++)
197 QCOMPARE(spy.at(i).at(0).value<QGeoPositionInfo>().timestamp(), dateTimes[i]);
198 } else if (trigger == RequestUpdatesMethod) {
199 QTRY_COMPARE(spy.count(), 1);
200 QCOMPARE(spy.at(0).at(0).value<QGeoPositionInfo>().timestamp(), dateTimes.first());
201 }
202 }
203}
204
205void tst_QNmeaPositionInfoSource::beginWithBufferedData_data()
206{
207 QTest::addColumn<QList<QDateTime> >(name: "dateTimes");
208 QTest::addColumn<UpdateTriggerMethod>(name: "trigger");
209
210 QList<QDateTime> dateTimes;
211 dateTimes << QDateTime::currentDateTime().toUTC();
212
213 QTest::newRow(dataTag: "startUpdates(), 1 update in buffer") << dateTimes << StartUpdatesMethod;
214 QTest::newRow(dataTag: "requestUpdate(), 1 update in buffer") << dateTimes << RequestUpdatesMethod;
215
216 for (int i=1; i<3; i++)
217 dateTimes << dateTimes[0].addMSecs(msecs: i * 100);
218 QTest::newRow(dataTag: "startUpdates(), multiple updates in buffer") << dateTimes << StartUpdatesMethod;
219 QTest::newRow(dataTag: "requestUpdate(), multiple updates in buffer") << dateTimes << RequestUpdatesMethod;
220}
221
222void tst_QNmeaPositionInfoSource::startUpdates()
223{
224 QFETCH(QList<QDateTime>, dateTimes);
225
226 QNmeaPositionInfoSource source(m_mode);
227 QNmeaPositionInfoSourceProxyFactory factory;
228 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
229
230 QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
231 proxy->source()->startUpdates();
232
233 for (int i=0; i<dateTimes.count(); i++)
234 proxy->feedUpdate(dt: dateTimes[i]);
235 QTRY_COMPARE(spyUpdate.count(), dateTimes.count());
236}
237
238void tst_QNmeaPositionInfoSource::startUpdates_data()
239{
240 QTest::addColumn<QList<QDateTime> >(name: "dateTimes");
241
242 QTest::newRow(dataTag: "1 update") << createDateTimes(count: 1);
243 QTest::newRow(dataTag: "2 updates") << createDateTimes(count: 2);
244 QTest::newRow(dataTag: "10 updates") << createDateTimes(count: 10);
245}
246
247void tst_QNmeaPositionInfoSource::startUpdates_withTimeout()
248{
249 QNmeaPositionInfoSource source(m_mode);
250 QNmeaPositionInfoSourceProxyFactory factory;
251 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
252
253 QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
254 QSignalSpy spyTimeout(proxy->source(), SIGNAL(updateTimeout()));
255
256 proxy->source()->setUpdateInterval(1000);
257 proxy->source()->startUpdates();
258
259 QDateTime dt = QDateTime::currentDateTime().toUTC();
260
261 if (m_mode == QNmeaPositionInfoSource::SimulationMode) {
262 // the first sentence primes the simulation
263 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt).toLatin1());
264 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt: dt.addMSecs(msecs: 10)).toLatin1());
265 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt: dt.addMSecs(msecs: 1100)).toLatin1());
266 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt: dt.addMSecs(msecs: 2200)).toLatin1());
267 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt: dt.addSecs(secs: 9)).toLatin1());
268
269 QElapsedTimer t;
270 t.start();
271
272 for (int j = 1; j < 4; ++j) {
273 QTRY_COMPARE(spyUpdate.count(), j);
274 QCOMPARE(spyTimeout.count(), 0);
275 int time = t.elapsed();
276 QVERIFY((time > j*1000 - 300) && (time < j*1000 + 300));
277 }
278
279 spyUpdate.clear();
280
281 QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() == 0) && (spyTimeout.count() == 1), 7500);
282 spyTimeout.clear();
283
284 QTRY_VERIFY_WITH_TIMEOUT((spyUpdate.count() == 1) && (spyTimeout.count() == 0), 7500);
285
286 } else {
287 // dt + 900
288 QTRY_VERIFY(spyUpdate.count() == 0 && spyTimeout.count() == 0);
289
290 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt: dt.addSecs(secs: 1)).toLatin1());
291 // dt + 1200
292 QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0);
293 spyUpdate.clear();
294
295 // dt + 1900
296 QTRY_VERIFY(spyUpdate.count() == 0 && spyTimeout.count() == 0);
297 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt: dt.addSecs(secs: 2)).toLatin1());
298
299 // dt + 2200
300 QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0);
301 spyUpdate.clear();
302
303 // dt + 2900
304 QTRY_VERIFY(spyUpdate.count() == 0 && spyTimeout.count() == 0);
305 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt: dt.addSecs(secs: 3)).toLatin1());
306
307 // dt + 3200
308 QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0);
309 spyUpdate.clear();
310
311 // dt + 6900
312 QTRY_VERIFY(spyUpdate.count() == 0 && spyTimeout.count() == 1);
313 spyTimeout.clear();
314 proxy->feedBytes(bytes: QLocationTestUtils::createRmcSentence(dt: dt.addSecs(secs: 7)).toLatin1());
315
316 // dt + 7200
317 QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0);
318 spyUpdate.clear();
319 }
320}
321
322void tst_QNmeaPositionInfoSource::startUpdates_expectLatestUpdateOnly()
323{
324 // If startUpdates() is called and an interval has been set, if multiple'
325 // updates are in the buffer, only the latest update should be emitted
326
327 QNmeaPositionInfoSource source(m_mode);
328 QNmeaPositionInfoSourceProxyFactory factory;
329 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
330
331 QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
332 proxy->source()->setUpdateInterval(500);
333 proxy->source()->startUpdates();
334
335 QList<QDateTime> dateTimes = createDateTimes(count: 3);
336 for (int i=0; i<dateTimes.count(); i++)
337 proxy->feedUpdate(dt: dateTimes[i]);
338
339 QTRY_COMPARE(spyUpdate.count(), 1);
340 QCOMPARE(spyUpdate[0][0].value<QGeoPositionInfo>().timestamp(), dateTimes.last());
341}
342
343void tst_QNmeaPositionInfoSource::startUpdates_waitForValidDateTime()
344{
345 // Tests that the class does not emit an update until it receives a
346 // sentences with a valid date *and* time. All sentences before this
347 // should be ignored, and any sentences received after this that do
348 // not have a date should use the known date.
349
350 QFETCH(QByteArray, bytes);
351 QFETCH(QList<QDateTime>, dateTimes);
352 QFETCH(QList<bool>, expectHorizontalAccuracy);
353 QFETCH(QList<bool>, expectVerticalAccuracy);
354
355 QNmeaPositionInfoSource source(m_mode);
356 source.setUserEquivalentRangeError(5.1);
357 QNmeaPositionInfoSourceProxyFactory factory;
358 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
359
360 QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
361 QObject::connect(sender: proxy->source(), signal: &QNmeaPositionInfoSource::positionUpdated, slot: [](const QGeoPositionInfo &info) {
362 qDebug() << info.timestamp();
363 });
364
365 proxy->source()->startUpdates();
366 proxy->feedBytes(bytes);
367 QTest::qWait(ms: 1000); // default push delay is 20ms
368 QTRY_COMPARE(spy.count(), dateTimes.count());
369
370 for (int i=0; i<spy.count(); i++) {
371 QGeoPositionInfo pInfo = spy[i][0].value<QGeoPositionInfo>();
372
373 QCOMPARE(pInfo.timestamp(), dateTimes[i]);
374
375 // Generated GGA/GSA sentences have hard coded HDOP of 3.5, which corrisponds to a
376 // horizontal accuracy of 35.7, for the user equivalent range error of 5.1 set above.
377 QCOMPARE(pInfo.hasAttribute(QGeoPositionInfo::HorizontalAccuracy),
378 expectHorizontalAccuracy[i]);
379 if (pInfo.hasAttribute(attribute: QGeoPositionInfo::HorizontalAccuracy))
380 QVERIFY(qFuzzyCompare(pInfo.attribute(QGeoPositionInfo::HorizontalAccuracy), 35.7));
381
382 // Generated GSA sentences have hard coded VDOP of 4.0, which corrisponds to a vertical
383 // accuracy of 40.8, for the user equivalent range error of 5.1 set above.
384 QCOMPARE(pInfo.hasAttribute(QGeoPositionInfo::VerticalAccuracy),
385 expectVerticalAccuracy[i]);
386 if (pInfo.hasAttribute(attribute: QGeoPositionInfo::VerticalAccuracy))
387 QVERIFY(qFuzzyCompare(pInfo.attribute(QGeoPositionInfo::VerticalAccuracy), 40.8));
388 }
389}
390
391void tst_QNmeaPositionInfoSource::startUpdates_waitForValidDateTime_data()
392{
393 QTest::addColumn<QByteArray>(name: "bytes");
394 QTest::addColumn<QList<QDateTime> >(name: "dateTimes");
395 QTest::addColumn<QList<bool> >(name: "expectHorizontalAccuracy");
396 QTest::addColumn<QList<bool> >(name: "expectVerticalAccuracy");
397
398 QDateTime dt = QDateTime::currentDateTime().toUTC();
399 QByteArray bytes;
400
401 // should only receive RMC sentence and the GGA sentence *after* it
402 bytes += QLocationTestUtils::createGgaSentence(time: dt.addMSecs(msecs: 100).time()).toLatin1();
403 bytes += QLocationTestUtils::createRmcSentence(dt: dt.addMSecs(msecs: 200)).toLatin1();
404 bytes += QLocationTestUtils::createGgaSentence(time: dt.addMSecs(msecs: 300).time()).toLatin1();
405 // The first GGA does not have date, and there's no cached date to inject, so that update will be invalid
406 QTest::newRow(dataTag: "Feed GGA,RMC,GGA; expect RMC, second GGA only")
407 << bytes << (QList<QDateTime>() << dt.addMSecs(msecs: 200) << dt.addMSecs(msecs: 300))
408 << (QList<bool>() << true << true) // accuracies are currently cached and injected in QGeoPositionInfos that do not have it
409 << (QList<bool>() << false << false);
410
411 // should not receive ZDA (has no coordinates) but should get the GGA
412 // sentence after it since it got the date/time from ZDA
413 bytes.clear();
414 bytes += QLocationTestUtils::createGgaSentence(time: dt.addMSecs(msecs: 100).time()).toLatin1();
415 bytes += QLocationTestUtils::createZdaSentence(dt: dt.addMSecs(msecs: 200)).toLatin1();
416 bytes += QLocationTestUtils::createGgaSentence(time: dt.addMSecs(msecs: 300).time()).toLatin1();
417 QTest::newRow(dataTag: "Feed GGA,ZDA,GGA; expect second GGA only")
418 << bytes << (QList<QDateTime>() << dt.addMSecs(msecs: 300))
419 << (QList<bool>() << true)
420 << (QList<bool>() << false);
421
422 // Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA.
423 bytes.clear();
424 bytes += QLocationTestUtils::createZdaSentence(dt: dt.addMSecs(msecs: 100)).toLatin1();
425 bytes += QLocationTestUtils::createGgaSentence(time: dt.addMSecs(msecs: 200).time()).toLatin1();
426 bytes += QLocationTestUtils::createGsaSentence().toLatin1();
427 bytes += QLocationTestUtils::createGgaSentence(time: dt.addMSecs(msecs: 300).time()).toLatin1();
428 if (m_mode == QNmeaPositionInfoSource::SimulationMode) {
429 QTest::newRow(dataTag: "Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA")
430 << bytes << (QList<QDateTime>() << dt.addMSecs(msecs: 200) << dt.addMSecs(msecs: 300))
431 << (QList<bool>() << true << true)
432 << (QList<bool>() << true << true); // First GGA gets VDOP from GSA bundled into previous, as it has no timestamp, second GGA gets the cached value.
433 }
434
435 if (m_mode == QNmeaPositionInfoSource::SimulationMode) {
436 // In sim m_mode, should ignore sentence with a date/time before the known date/time
437 // (in real time m_mode, everything is passed on regardless)
438 bytes.clear();
439 bytes += QLocationTestUtils::createRmcSentence(dt: dt.addMSecs(msecs: 100)).toLatin1();
440 bytes += QLocationTestUtils::createRmcSentence(dt: dt.addMSecs(msecs: -200)).toLatin1();
441 bytes += QLocationTestUtils::createRmcSentence(dt: dt.addMSecs(msecs: 200)).toLatin1();
442 QTest::newRow(dataTag: "Feed good RMC, RMC with bad date/time, good RMC; expect first and third RMC only")
443 << bytes << (QList<QDateTime>() << dt.addMSecs(msecs: 100) << dt.addMSecs(msecs: 200))
444 << (QList<bool>() << false << false)
445 << (QList<bool>() << false << false);
446 }
447}
448
449void tst_QNmeaPositionInfoSource::requestUpdate_waitForValidDateTime()
450{
451 QFETCH(QByteArray, bytes);
452 QFETCH(QList<QDateTime>, dateTimes);
453
454 QNmeaPositionInfoSource source(m_mode);
455 QNmeaPositionInfoSourceProxyFactory factory;
456 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
457
458 QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
459 proxy->source()->requestUpdate();
460
461 proxy->feedBytes(bytes);
462 QTRY_COMPARE(spy.count(), 1);
463 QCOMPARE(spy[0][0].value<QGeoPositionInfo>().timestamp(), dateTimes[0]);
464}
465
466void tst_QNmeaPositionInfoSource::requestUpdate_waitForValidDateTime_data()
467{
468 startUpdates_waitForValidDateTime_data();
469}
470
471void tst_QNmeaPositionInfoSource::requestUpdate()
472{
473 QNmeaPositionInfoSource source(m_mode);
474 QNmeaPositionInfoSourceProxyFactory factory;
475 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
476
477 QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
478 QSignalSpy spyTimeout(proxy->source(), SIGNAL(updateTimeout()));
479 QDateTime dt;
480
481 proxy->source()->requestUpdate(timeout: 100);
482 QTRY_COMPARE(spyTimeout.count(), 1);
483 spyTimeout.clear();
484
485 dt = QDateTime::currentDateTime().toUTC();
486 proxy->feedUpdate(dt);
487 proxy->source()->requestUpdate();
488 QTRY_COMPARE(spyUpdate.count(), 1);
489 QCOMPARE(spyUpdate[0][0].value<QGeoPositionInfo>().timestamp(), dt);
490 QCOMPARE(spyTimeout.count(), 0);
491 spyUpdate.clear();
492
493 // delay the update and expect it to be emitted after 300ms
494 dt = QDateTime::currentDateTime().toUTC();
495 proxy->source()->requestUpdate(timeout: 1000);
496 QTest::qWait(ms: 300);
497 proxy->feedUpdate(dt);
498 QTRY_COMPARE(spyUpdate.count(), 1);
499 QCOMPARE(spyUpdate[0][0].value<QGeoPositionInfo>().timestamp(), dt);
500 QCOMPARE(spyTimeout.count(), 0);
501 spyUpdate.clear();
502
503 // delay the update and expect updateTimeout() to be emitted
504 dt = QDateTime::currentDateTime().toUTC();
505 proxy->source()->requestUpdate(timeout: 500);
506 QTest::qWait(ms: 1000);
507 proxy->feedUpdate(dt);
508 QCOMPARE(spyTimeout.count(), 1);
509 QCOMPARE(spyUpdate.count(), 0);
510 spyUpdate.clear();
511}
512
513void tst_QNmeaPositionInfoSource::requestUpdate_after_start()
514{
515 QNmeaPositionInfoSource source(m_mode);
516 QNmeaPositionInfoSourceProxyFactory factory;
517 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
518
519 QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
520 QSignalSpy spyTimeout(proxy->source(), SIGNAL(updateTimeout()));
521
522 // Start updates with 500ms interval and requestUpdate() with 100ms
523 // timeout. Feed an update, and it should be emitted immediately due to
524 // the requestUpdate(). The update should not be emitted again after that
525 // (i.e. the startUpdates() interval should not cause it to be re-emitted).
526
527 QDateTime dt = QDateTime::currentDateTime().toUTC();
528 proxy->source()->setUpdateInterval(500);
529 proxy->source()->startUpdates();
530 proxy->source()->requestUpdate(timeout: 100);
531 proxy->feedUpdate(dt);
532 QTRY_COMPARE(spyUpdate.count(), 1);
533 QCOMPARE(spyUpdate[0][0].value<QGeoPositionInfo>().timestamp(), dt);
534 QCOMPARE(spyTimeout.count(), 0);
535 spyUpdate.clear();
536
537 // Update has been emitted for requestUpdate(), shouldn't be emitted for startUpdates()
538 QTRY_COMPARE_WITH_TIMEOUT(spyUpdate.count(), 0, 1000);
539}
540
541void tst_QNmeaPositionInfoSource::testWithBadNmea()
542{
543 QFETCH(QByteArray, bytes);
544 QFETCH(QList<QDateTime>, dateTimes);
545 QFETCH(UpdateTriggerMethod, trigger);
546
547 QNmeaPositionInfoSource source(m_mode);
548 QNmeaPositionInfoSourceProxyFactory factory;
549 QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(source: &source));
550
551 QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
552 if (trigger == StartUpdatesMethod)
553 proxy->source()->startUpdates();
554 else
555 proxy->source()->requestUpdate();
556
557 proxy->feedBytes(bytes);
558 QTRY_COMPARE(spy.count(), dateTimes.count());
559 for (int i=0; i<dateTimes.count(); i++)
560 QCOMPARE(spy[i][0].value<QGeoPositionInfo>().timestamp(), dateTimes[i]);
561}
562
563void tst_QNmeaPositionInfoSource::testWithBadNmea_data()
564{
565 QTest::addColumn<QByteArray>(name: "bytes");
566 QTest::addColumn<QList<QDateTime> >(name: "dateTimes");
567 QTest::addColumn<UpdateTriggerMethod>(name: "trigger");
568
569 QDateTime firstDateTime = QDateTime::currentDateTime().toUTC();
570 QByteArray bad = QLocationTestUtils::createRmcSentence(dt: firstDateTime.addSecs(secs: 1)).toLatin1();
571 bad = bad.mid(index: bad.length()/2);
572 QDateTime lastDateTime = firstDateTime.addSecs(secs: 2);
573
574 QByteArray bytes;
575 bytes += QLocationTestUtils::createRmcSentence(dt: firstDateTime).toLatin1();
576 bytes += bad;
577 bytes += QLocationTestUtils::createRmcSentence(dt: lastDateTime).toLatin1();
578 QTest::newRow(dataTag: "requestUpdate(), bad second sentence") << bytes
579 << (QList<QDateTime>() << firstDateTime) << RequestUpdatesMethod;
580 QTest::newRow(dataTag: "startUpdates(), bad second sentence") << bytes
581 << (QList<QDateTime>() << firstDateTime << lastDateTime) << StartUpdatesMethod;
582}
583

source code of qtlocation/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp