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) \
48 do { \
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 
tst_QNmeaPositionInfoSource(QNmeaPositionInfoSource::UpdateMode mode,QObject * parent)67 tst_QNmeaPositionInfoSource::tst_QNmeaPositionInfoSource(QNmeaPositionInfoSource::UpdateMode mode, QObject *parent)
68     : QObject(parent),
69       m_mode(mode)
70 {
71 }
72 
initTestCase()73 void tst_QNmeaPositionInfoSource::initTestCase()
74 {
75     qRegisterMetaType<QNmeaPositionInfoSource::UpdateMode>();
76 }
77 
constructor()78 void 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 
supportedPositioningMethods()86 void tst_QNmeaPositionInfoSource::supportedPositioningMethods()
87 {
88     QNmeaPositionInfoSource source(m_mode);
89     QCOMPARE(source.supportedPositioningMethods(), QNmeaPositionInfoSource::SatellitePositioningMethods);
90 }
91 
minimumUpdateInterval()92 void tst_QNmeaPositionInfoSource::minimumUpdateInterval()
93 {
94     QNmeaPositionInfoSource source(m_mode);
95     QCOMPARE(source.minimumUpdateInterval(), 2);
96 }
97 
userEquivalentRangeError()98 void 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 
setUpdateInterval_delayedUpdate()106 void 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));
115 
116     QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
117     proxy->source()->setUpdateInterval(500);
118     proxy->source()->startUpdates();
119 
120     QTest::qWait(600);
121     QDateTime now = QDateTime::currentDateTime();
122     proxy->feedUpdate(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 
lastKnownPosition()130 void tst_QNmeaPositionInfoSource::lastKnownPosition()
131 {
132     QNmeaPositionInfoSource source(m_mode);
133     QNmeaPositionInfoSourceProxyFactory factory;
134     QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(&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(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(5);
151     for (int i=0; i<dateTimes.count(); i++) {
152         proxy->source()->requestUpdate(); // Irrelevant for this test
153         proxy->feedUpdate(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(dateTimes.last().addMSecs(100), 5);
160     for (int i=0; i<dateTimes.count(); i++) {
161         proxy->feedUpdate(dateTimes[i]);
162         QTRY_COMPARE(proxy->source()->lastKnownPosition().timestamp(), dateTimes[i]);
163     }
164 }
165 
beginWithBufferedData()166 void 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(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 
beginWithBufferedData_data()205 void tst_QNmeaPositionInfoSource::beginWithBufferedData_data()
206 {
207     QTest::addColumn<QList<QDateTime> >("dateTimes");
208     QTest::addColumn<UpdateTriggerMethod>("trigger");
209 
210     QList<QDateTime> dateTimes;
211     dateTimes << QDateTime::currentDateTime().toUTC();
212 
213     QTest::newRow("startUpdates(), 1 update in buffer") << dateTimes << StartUpdatesMethod;
214     QTest::newRow("requestUpdate(), 1 update in buffer") << dateTimes << RequestUpdatesMethod;
215 
216     for (int i=1; i<3; i++)
217         dateTimes << dateTimes[0].addMSecs(i * 100);
218     QTest::newRow("startUpdates(), multiple updates in buffer") << dateTimes << StartUpdatesMethod;
219     QTest::newRow("requestUpdate(), multiple updates in buffer") << dateTimes << RequestUpdatesMethod;
220 }
221 
startUpdates()222 void 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));
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(dateTimes[i]);
235     QTRY_COMPARE(spyUpdate.count(), dateTimes.count());
236 }
237 
startUpdates_data()238 void tst_QNmeaPositionInfoSource::startUpdates_data()
239 {
240     QTest::addColumn<QList<QDateTime> >("dateTimes");
241 
242     QTest::newRow("1 update") << createDateTimes(1);
243     QTest::newRow("2 updates") << createDateTimes(2);
244     QTest::newRow("10 updates") << createDateTimes(10);
245 }
246 
startUpdates_withTimeout()247 void tst_QNmeaPositionInfoSource::startUpdates_withTimeout()
248 {
249     QNmeaPositionInfoSource source(m_mode);
250     QNmeaPositionInfoSourceProxyFactory factory;
251     QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(&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(QLocationTestUtils::createRmcSentence(dt).toLatin1());
264         proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addMSecs(10)).toLatin1());
265         proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addMSecs(1100)).toLatin1());
266         proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addMSecs(2200)).toLatin1());
267         proxy->feedBytes(QLocationTestUtils::createRmcSentence(dt.addSecs(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(QLocationTestUtils::createRmcSentence(dt.addSecs(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(QLocationTestUtils::createRmcSentence(dt.addSecs(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(QLocationTestUtils::createRmcSentence(dt.addSecs(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(QLocationTestUtils::createRmcSentence(dt.addSecs(7)).toLatin1());
315 
316         // dt + 7200
317         QTRY_VERIFY(spyUpdate.count() == 1 && spyTimeout.count() == 0);
318         spyUpdate.clear();
319     }
320 }
321 
startUpdates_expectLatestUpdateOnly()322 void 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));
330 
331     QSignalSpy spyUpdate(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
332     proxy->source()->setUpdateInterval(500);
333     proxy->source()->startUpdates();
334 
335     QList<QDateTime> dateTimes = createDateTimes(3);
336     for (int i=0; i<dateTimes.count(); i++)
337         proxy->feedUpdate(dateTimes[i]);
338 
339     QTRY_COMPARE(spyUpdate.count(), 1);
340     QCOMPARE(spyUpdate[0][0].value<QGeoPositionInfo>().timestamp(), dateTimes.last());
341 }
342 
startUpdates_waitForValidDateTime()343 void 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));
359 
360     QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo)));
361     QObject::connect(proxy->source(), &QNmeaPositionInfoSource::positionUpdated, [](const QGeoPositionInfo &info) {
362                                                                             qDebug() << info.timestamp();
363                                                                         });
364 
365     proxy->source()->startUpdates();
366     proxy->feedBytes(bytes);
367     QTest::qWait(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(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(QGeoPositionInfo::VerticalAccuracy))
387             QVERIFY(qFuzzyCompare(pInfo.attribute(QGeoPositionInfo::VerticalAccuracy), 40.8));
388     }
389 }
390 
startUpdates_waitForValidDateTime_data()391 void tst_QNmeaPositionInfoSource::startUpdates_waitForValidDateTime_data()
392 {
393     QTest::addColumn<QByteArray>("bytes");
394     QTest::addColumn<QList<QDateTime> >("dateTimes");
395     QTest::addColumn<QList<bool> >("expectHorizontalAccuracy");
396     QTest::addColumn<QList<bool> >("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(dt.addMSecs(100).time()).toLatin1();
403     bytes += QLocationTestUtils::createRmcSentence(dt.addMSecs(200)).toLatin1();
404     bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(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("Feed GGA,RMC,GGA; expect RMC, second GGA only")
407             << bytes << (QList<QDateTime>() << dt.addMSecs(200) << dt.addMSecs(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(dt.addMSecs(100).time()).toLatin1();
415     bytes += QLocationTestUtils::createZdaSentence(dt.addMSecs(200)).toLatin1();
416     bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(300).time()).toLatin1();
417     QTest::newRow("Feed GGA,ZDA,GGA; expect second GGA only")
418             << bytes << (QList<QDateTime>() << dt.addMSecs(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.addMSecs(100)).toLatin1();
425     bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(200).time()).toLatin1();
426     bytes += QLocationTestUtils::createGsaSentence().toLatin1();
427     bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(300).time()).toLatin1();
428     if (m_mode == QNmeaPositionInfoSource::SimulationMode) {
429         QTest::newRow("Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA")
430                 << bytes << (QList<QDateTime>() << dt.addMSecs(200) << dt.addMSecs(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.addMSecs(100)).toLatin1();
440         bytes += QLocationTestUtils::createRmcSentence(dt.addMSecs(-200)).toLatin1();
441         bytes += QLocationTestUtils::createRmcSentence(dt.addMSecs(200)).toLatin1();
442         QTest::newRow("Feed good RMC, RMC with bad date/time, good RMC; expect first and third RMC only")
443                 << bytes << (QList<QDateTime>() << dt.addMSecs(100) << dt.addMSecs(200))
444                 << (QList<bool>() << false << false)
445                 << (QList<bool>() << false << false);
446     }
447 }
448 
requestUpdate_waitForValidDateTime()449 void 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));
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 
requestUpdate_waitForValidDateTime_data()466 void tst_QNmeaPositionInfoSource::requestUpdate_waitForValidDateTime_data()
467 {
468     startUpdates_waitForValidDateTime_data();
469 }
470 
requestUpdate()471 void tst_QNmeaPositionInfoSource::requestUpdate()
472 {
473     QNmeaPositionInfoSource source(m_mode);
474     QNmeaPositionInfoSourceProxyFactory factory;
475     QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(&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(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(1000);
496     QTest::qWait(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(500);
506     QTest::qWait(1000);
507     proxy->feedUpdate(dt);
508     QCOMPARE(spyTimeout.count(), 1);
509     QCOMPARE(spyUpdate.count(), 0);
510     spyUpdate.clear();
511 }
512 
requestUpdate_after_start()513 void tst_QNmeaPositionInfoSource::requestUpdate_after_start()
514 {
515     QNmeaPositionInfoSource source(m_mode);
516     QNmeaPositionInfoSourceProxyFactory factory;
517     QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(&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(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 
testWithBadNmea()541 void 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));
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 
testWithBadNmea_data()563 void tst_QNmeaPositionInfoSource::testWithBadNmea_data()
564 {
565     QTest::addColumn<QByteArray>("bytes");
566     QTest::addColumn<QList<QDateTime> >("dateTimes");
567     QTest::addColumn<UpdateTriggerMethod>("trigger");
568 
569     QDateTime firstDateTime = QDateTime::currentDateTime().toUTC();
570     QByteArray bad = QLocationTestUtils::createRmcSentence(firstDateTime.addSecs(1)).toLatin1();
571     bad = bad.mid(bad.length()/2);
572     QDateTime lastDateTime = firstDateTime.addSecs(2);
573 
574     QByteArray bytes;
575     bytes += QLocationTestUtils::createRmcSentence(firstDateTime).toLatin1();
576     bytes += bad;
577     bytes += QLocationTestUtils::createRmcSentence(lastDateTime).toLatin1();
578     QTest::newRow("requestUpdate(), bad second sentence") << bytes
579             << (QList<QDateTime>() << firstDateTime) << RequestUpdatesMethod;
580     QTest::newRow("startUpdates(), bad second sentence") << bytes
581             << (QList<QDateTime>() << firstDateTime << lastDateTime) << StartUpdatesMethod;
582 }
583