44 #include <qtest.h> |
44 #include <qtest.h> |
45 #include <QtTest/QtTest> |
45 #include <QtTest/QtTest> |
46 #include <QtNetwork/qnetworkreply.h> |
46 #include <QtNetwork/qnetworkreply.h> |
47 #include <QtNetwork/qnetworkrequest.h> |
47 #include <QtNetwork/qnetworkrequest.h> |
48 #include <QtNetwork/qnetworkaccessmanager.h> |
48 #include <QtNetwork/qnetworkaccessmanager.h> |
|
49 #include <QtNetwork/qtcpsocket.h> |
|
50 #include <QtNetwork/qtcpserver.h> |
49 #include "../../auto/network-settings.h" |
51 #include "../../auto/network-settings.h" |
50 |
52 |
|
53 |
|
54 class TimedSender: public QThread |
|
55 { |
|
56 Q_OBJECT |
|
57 qint64 totalBytes; |
|
58 QSemaphore ready; |
|
59 QByteArray dataToSend; |
|
60 QTcpSocket *client; |
|
61 int timeout; |
|
62 int port; |
|
63 public: |
|
64 int transferRate; |
|
65 TimedSender(int ms) |
|
66 : totalBytes(0), timeout(ms), port(-1), transferRate(-1) |
|
67 { |
|
68 dataToSend = QByteArray(16*1024, '@'); |
|
69 start(); |
|
70 ready.acquire(); |
|
71 } |
|
72 |
|
73 inline int serverPort() const { return port; } |
|
74 |
|
75 private slots: |
|
76 void writeMore() |
|
77 { |
|
78 while (client->bytesToWrite() < 128 * 1024) { |
|
79 writePacket(dataToSend); |
|
80 } |
|
81 } |
|
82 |
|
83 protected: |
|
84 void run() |
|
85 { |
|
86 QTcpServer server; |
|
87 server.listen(); |
|
88 port = server.serverPort(); |
|
89 ready.release(); |
|
90 |
|
91 server.waitForNewConnection(-1); |
|
92 client = server.nextPendingConnection(); |
|
93 |
|
94 writeMore(); |
|
95 connect(client, SIGNAL(bytesWritten(qint64)), SLOT(writeMore()), Qt::DirectConnection); |
|
96 |
|
97 QEventLoop eventLoop; |
|
98 QTimer::singleShot(timeout, &eventLoop, SLOT(quit())); |
|
99 |
|
100 QTime timer; |
|
101 timer.start(); |
|
102 eventLoop.exec(); |
|
103 disconnect(client, SIGNAL(bytesWritten(qint64)), this, 0); |
|
104 |
|
105 // wait for the connection to shut down |
|
106 client->disconnectFromHost(); |
|
107 if (!client->waitForDisconnected(10000)) |
|
108 return; |
|
109 |
|
110 transferRate = totalBytes * 1000 / timer.elapsed(); |
|
111 qDebug() << "TimedSender::run" << "receive rate:" << (transferRate / 1024) << "kB/s in" |
|
112 << timer.elapsed() << "ms"; |
|
113 } |
|
114 |
|
115 void writePacket(const QByteArray &array) |
|
116 { |
|
117 client->write(array); |
|
118 totalBytes += array.size(); |
|
119 } |
|
120 }; |
|
121 |
|
122 |
|
123 class QNetworkReplyPtr: public QSharedPointer<QNetworkReply> |
|
124 { |
|
125 public: |
|
126 inline QNetworkReplyPtr(QNetworkReply *ptr = 0) |
|
127 : QSharedPointer<QNetworkReply>(ptr) |
|
128 { } |
|
129 |
|
130 inline operator QNetworkReply *() const { return data(); } |
|
131 }; |
|
132 |
|
133 |
|
134 class DataReader: public QObject |
|
135 { |
|
136 Q_OBJECT |
|
137 public: |
|
138 qint64 totalBytes; |
|
139 QByteArray data; |
|
140 QIODevice *device; |
|
141 bool accumulate; |
|
142 DataReader(QIODevice *dev, bool acc = true) : totalBytes(0), device(dev), accumulate(acc) |
|
143 { |
|
144 connect(device, SIGNAL(readyRead()), SLOT(doRead())); |
|
145 } |
|
146 |
|
147 public slots: |
|
148 void doRead() |
|
149 { |
|
150 QByteArray buffer; |
|
151 buffer.resize(device->bytesAvailable()); |
|
152 qint64 bytesRead = device->read(buffer.data(), device->bytesAvailable()); |
|
153 if (bytesRead == -1) { |
|
154 QTestEventLoop::instance().exitLoop(); |
|
155 return; |
|
156 } |
|
157 buffer.truncate(bytesRead); |
|
158 totalBytes += bytesRead; |
|
159 |
|
160 if (accumulate) |
|
161 data += buffer; |
|
162 } |
|
163 }; |
|
164 |
|
165 class ThreadedDataReader: public QThread |
|
166 { |
|
167 Q_OBJECT |
|
168 // used to make the constructor only return after the tcp server started listening |
|
169 QSemaphore ready; |
|
170 QTcpSocket *client; |
|
171 int timeout; |
|
172 int port; |
|
173 public: |
|
174 qint64 transferRate; |
|
175 ThreadedDataReader() |
|
176 : port(-1), transferRate(-1) |
|
177 { |
|
178 start(); |
|
179 ready.acquire(); |
|
180 } |
|
181 |
|
182 inline int serverPort() const { return port; } |
|
183 |
|
184 protected: |
|
185 void run() |
|
186 { |
|
187 QTcpServer server; |
|
188 server.listen(); |
|
189 port = server.serverPort(); |
|
190 ready.release(); |
|
191 |
|
192 server.waitForNewConnection(-1); |
|
193 client = server.nextPendingConnection(); |
|
194 |
|
195 QEventLoop eventLoop; |
|
196 DataReader reader(client, false); |
|
197 QObject::connect(client, SIGNAL(disconnected()), &eventLoop, SLOT(quit())); |
|
198 |
|
199 QTime timer; |
|
200 timer.start(); |
|
201 eventLoop.exec(); |
|
202 qint64 elapsed = timer.elapsed(); |
|
203 |
|
204 transferRate = reader.totalBytes * 1000 / elapsed; |
|
205 qDebug() << "ThreadedDataReader::run" << "send rate:" << (transferRate / 1024) << "kB/s in" << elapsed << "msec"; |
|
206 } |
|
207 }; |
|
208 |
|
209 class DataGenerator: public QIODevice |
|
210 { |
|
211 Q_OBJECT |
|
212 enum { Idle, Started, Stopped } state; |
|
213 public: |
|
214 DataGenerator() : state(Idle) |
|
215 { open(ReadOnly); } |
|
216 |
|
217 virtual bool isSequential() const { return true; } |
|
218 virtual qint64 bytesAvailable() const { return state == Started ? 1024*1024 : 0; } |
|
219 |
|
220 public slots: |
|
221 void start() { state = Started; emit readyRead(); } |
|
222 void stop() { state = Stopped; emit readyRead(); } |
|
223 |
|
224 protected: |
|
225 virtual qint64 readData(char *data, qint64 maxlen) |
|
226 { |
|
227 if (state == Stopped) |
|
228 return -1; // EOF |
|
229 |
|
230 // return as many bytes as are wanted |
|
231 memset(data, '@', maxlen); |
|
232 return maxlen; |
|
233 } |
|
234 virtual qint64 writeData(const char *, qint64) |
|
235 { return -1; } |
|
236 }; |
|
237 |
|
238 class ThreadedDataReaderHttpServer: public QThread |
|
239 { |
|
240 Q_OBJECT |
|
241 // used to make the constructor only return after the tcp server started listening |
|
242 QSemaphore ready; |
|
243 QTcpSocket *client; |
|
244 int timeout; |
|
245 int port; |
|
246 public: |
|
247 qint64 transferRate; |
|
248 ThreadedDataReaderHttpServer() |
|
249 : port(-1), transferRate(-1) |
|
250 { |
|
251 start(); |
|
252 ready.acquire(); |
|
253 } |
|
254 |
|
255 inline int serverPort() const { return port; } |
|
256 |
|
257 protected: |
|
258 void run() |
|
259 { |
|
260 QTcpServer server; |
|
261 server.listen(); |
|
262 port = server.serverPort(); |
|
263 ready.release(); |
|
264 |
|
265 server.waitForNewConnection(-1); |
|
266 client = server.nextPendingConnection(); |
|
267 client->write("HTTP/1.0 200 OK\r\n"); |
|
268 client->write("Content-length: 0\r\n"); |
|
269 client->write("\r\n"); |
|
270 client->flush(); |
|
271 |
|
272 QCoreApplication::processEvents(); |
|
273 |
|
274 QEventLoop eventLoop; |
|
275 DataReader reader(client, false); |
|
276 QObject::connect(client, SIGNAL(disconnected()), &eventLoop, SLOT(quit())); |
|
277 |
|
278 QTime timer; |
|
279 timer.start(); |
|
280 eventLoop.exec(); |
|
281 qint64 elapsed = timer.elapsed(); |
|
282 |
|
283 transferRate = reader.totalBytes * 1000 / elapsed; |
|
284 qDebug() << "ThreadedDataReaderHttpServer::run" << "send rate:" << (transferRate / 1024) << "kB/s in" << elapsed << "msec"; |
|
285 } |
|
286 }; |
|
287 |
|
288 |
|
289 class FixedSizeDataGenerator : public QIODevice |
|
290 { |
|
291 Q_OBJECT |
|
292 enum { Idle, Started, Stopped } state; |
|
293 public: |
|
294 FixedSizeDataGenerator(qint64 size) : state(Idle) |
|
295 { open(ReadOnly | Unbuffered); |
|
296 toBeGeneratedTotalCount = toBeGeneratedCount = size; |
|
297 } |
|
298 |
|
299 virtual qint64 bytesAvailable() const |
|
300 { |
|
301 return state == Started ? toBeGeneratedCount + QIODevice::bytesAvailable() : 0; |
|
302 } |
|
303 |
|
304 virtual bool isSequential() const{ |
|
305 return false; |
|
306 } |
|
307 |
|
308 virtual bool reset() const{ |
|
309 return false; |
|
310 } |
|
311 |
|
312 qint64 size() const { |
|
313 return toBeGeneratedTotalCount; |
|
314 } |
|
315 |
|
316 public slots: |
|
317 void start() { state = Started; emit readyRead(); } |
|
318 |
|
319 protected: |
|
320 virtual qint64 readData(char *data, qint64 maxlen) |
|
321 { |
|
322 memset(data, '@', maxlen); |
|
323 |
|
324 if (toBeGeneratedCount <= 0) { |
|
325 return -1; |
|
326 } |
|
327 |
|
328 qint64 n = qMin(maxlen, toBeGeneratedCount); |
|
329 toBeGeneratedCount -= n; |
|
330 |
|
331 if (toBeGeneratedCount <= 0) { |
|
332 // make sure this is a queued connection! |
|
333 emit readChannelFinished(); |
|
334 } |
|
335 |
|
336 return n; |
|
337 } |
|
338 virtual qint64 writeData(const char *, qint64) |
|
339 { return -1; } |
|
340 |
|
341 qint64 toBeGeneratedCount; |
|
342 qint64 toBeGeneratedTotalCount; |
|
343 }; |
|
344 |
|
345 class HttpDownloadPerformanceServer : QObject { |
|
346 Q_OBJECT; |
|
347 qint64 dataSize; |
|
348 qint64 dataSent; |
|
349 QTcpServer server; |
|
350 QTcpSocket *client; |
|
351 bool serverSendsContentLength; |
|
352 bool chunkedEncoding; |
|
353 |
|
354 public: |
|
355 HttpDownloadPerformanceServer (qint64 ds, bool sscl, bool ce) : dataSize(ds), dataSent(0), |
|
356 client(0), serverSendsContentLength(sscl), chunkedEncoding(ce) { |
|
357 server.listen(); |
|
358 connect(&server, SIGNAL(newConnection()), this, SLOT(newConnectionSlot())); |
|
359 } |
|
360 |
|
361 int serverPort() { |
|
362 return server.serverPort(); |
|
363 } |
|
364 |
|
365 public slots: |
|
366 |
|
367 void newConnectionSlot() { |
|
368 client = server.nextPendingConnection(); |
|
369 client->setParent(this); |
|
370 connect(client, SIGNAL(readyRead()), this, SLOT(readyReadSlot())); |
|
371 connect(client, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWrittenSlot(qint64))); |
|
372 } |
|
373 |
|
374 void readyReadSlot() { |
|
375 client->readAll(); |
|
376 client->write("HTTP/1.0 200 OK\n"); |
|
377 if (serverSendsContentLength) |
|
378 client->write(QString("Content-Length: " + QString::number(dataSize) + "\n").toAscii()); |
|
379 if (chunkedEncoding) |
|
380 client->write(QString("Transfer-Encoding: chunked\n").toAscii()); |
|
381 client->write("Connection: close\n\n"); |
|
382 } |
|
383 |
|
384 void bytesWrittenSlot(qint64 amount) { |
|
385 Q_UNUSED(amount); |
|
386 if (dataSent == dataSize && client) { |
|
387 // close eventually |
|
388 |
|
389 // chunked encoding: we have to send a last "empty" chunk |
|
390 if (chunkedEncoding) |
|
391 client->write(QString("0\r\n\r\n").toAscii()); |
|
392 |
|
393 client->disconnectFromHost(); |
|
394 server.close(); |
|
395 client = 0; |
|
396 return; |
|
397 } |
|
398 |
|
399 // send data |
|
400 if (client && client->bytesToWrite() < 100*1024 && dataSent < dataSize) { |
|
401 qint64 amount = qMin(qint64(16*1024), dataSize - dataSent); |
|
402 QByteArray data(amount, '@'); |
|
403 |
|
404 if (chunkedEncoding) { |
|
405 client->write(QString(QString("%1").arg(amount,0,16).toUpper() + "\r\n").toAscii()); |
|
406 client->write(data.constData(), amount); |
|
407 client->write(QString("\r\n").toAscii()); |
|
408 } else { |
|
409 client->write(data.constData(), amount); |
|
410 } |
|
411 |
|
412 dataSent += amount; |
|
413 } |
|
414 } |
|
415 }; |
|
416 |
|
417 class HttpDownloadPerformanceClient : QObject { |
|
418 Q_OBJECT; |
|
419 QIODevice *device; |
|
420 public: |
|
421 HttpDownloadPerformanceClient (QIODevice *dev) : device(dev){ |
|
422 connect(dev, SIGNAL(readyRead()), this, SLOT(readyReadSlot())); |
|
423 } |
|
424 |
|
425 public slots: |
|
426 void readyReadSlot() { |
|
427 device->readAll(); |
|
428 } |
|
429 |
|
430 }; |
|
431 |
|
432 |
|
433 |
|
434 |
51 class tst_qnetworkreply : public QObject |
435 class tst_qnetworkreply : public QObject |
52 { |
436 { |
53 Q_OBJECT |
437 Q_OBJECT |
|
438 |
|
439 QNetworkAccessManager manager; |
54 private slots: |
440 private slots: |
55 void httpLatency(); |
441 void httpLatency(); |
56 |
442 |
57 #ifndef QT_NO_OPENSSL |
443 #ifndef QT_NO_OPENSSL |
58 void echoPerformance_data(); |
444 void echoPerformance_data(); |
59 void echoPerformance(); |
445 void echoPerformance(); |
60 #endif |
446 #endif |
|
447 |
|
448 void downloadPerformance(); |
|
449 void uploadPerformance(); |
|
450 void performanceControlRate(); |
|
451 void httpUploadPerformance(); |
|
452 void httpDownloadPerformance_data(); |
|
453 void httpDownloadPerformance(); |
|
454 |
61 }; |
455 }; |
62 |
456 |
63 void tst_qnetworkreply::httpLatency() |
457 void tst_qnetworkreply::httpLatency() |
64 { |
458 { |
65 QNetworkAccessManager manager; |
459 QNetworkAccessManager manager; |
105 delete reply; |
499 delete reply; |
106 } |
500 } |
107 } |
501 } |
108 #endif |
502 #endif |
109 |
503 |
|
504 void tst_qnetworkreply::downloadPerformance() |
|
505 { |
|
506 // unlike the above function, this one tries to send as fast as possible |
|
507 // and measures how fast it was. |
|
508 TimedSender sender(5000); |
|
509 QNetworkRequest request("debugpipe://127.0.0.1:" + QString::number(sender.serverPort()) + "/?bare=1"); |
|
510 QNetworkReplyPtr reply = manager.get(request); |
|
511 DataReader reader(reply, false); |
|
512 |
|
513 QTime loopTime; |
|
514 connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); |
|
515 loopTime.start(); |
|
516 QTestEventLoop::instance().enterLoop(40); |
|
517 int elapsedTime = loopTime.elapsed(); |
|
518 sender.wait(); |
|
519 |
|
520 qint64 receivedBytes = reader.totalBytes; |
|
521 qDebug() << "tst_QNetworkReply::downloadPerformance" << "receive rate:" << (receivedBytes * 1000 / elapsedTime / 1024) << "kB/s and" |
|
522 << elapsedTime << "ms"; |
|
523 } |
|
524 |
|
525 void tst_qnetworkreply::uploadPerformance() |
|
526 { |
|
527 ThreadedDataReader reader; |
|
528 DataGenerator generator; |
|
529 |
|
530 |
|
531 QNetworkRequest request("debugpipe://127.0.0.1:" + QString::number(reader.serverPort()) + "/?bare=1"); |
|
532 QNetworkReplyPtr reply = manager.put(request, &generator); |
|
533 generator.start(); |
|
534 connect(&reader, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); |
|
535 QTimer::singleShot(5000, &generator, SLOT(stop())); |
|
536 |
|
537 QTestEventLoop::instance().enterLoop(30); |
|
538 QCOMPARE(reply->error(), QNetworkReply::NoError); |
|
539 QVERIFY(!QTestEventLoop::instance().timeout()); |
|
540 } |
|
541 |
|
542 void tst_qnetworkreply::httpUploadPerformance() |
|
543 { |
|
544 #ifdef Q_OS_SYMBIAN |
|
545 // SHow some mercy for non-desktop platform/s |
|
546 enum {UploadSize = 4*1024*1024}; // 4 MB |
|
547 #else |
|
548 enum {UploadSize = 128*1024*1024}; // 128 MB |
|
549 #endif |
|
550 ThreadedDataReaderHttpServer reader; |
|
551 FixedSizeDataGenerator generator(UploadSize); |
|
552 |
|
553 QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(reader.serverPort()) + "/?bare=1")); |
|
554 request.setHeader(QNetworkRequest::ContentLengthHeader,UploadSize); |
|
555 |
|
556 QNetworkReplyPtr reply = manager.put(request, &generator); |
|
557 |
|
558 connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); |
|
559 |
|
560 QTime time; |
|
561 generator.start(); |
|
562 time.start(); |
|
563 QTestEventLoop::instance().enterLoop(40); |
|
564 QCOMPARE(reply->error(), QNetworkReply::NoError); |
|
565 QVERIFY(!QTestEventLoop::instance().timeout()); |
|
566 |
|
567 qint64 elapsed = time.elapsed(); |
|
568 qDebug() << "tst_QNetworkReply::httpUploadPerformance" << elapsed << "msec, " |
|
569 << ((UploadSize/1024.0)/(elapsed/1000.0)) << " kB/sec"; |
|
570 |
|
571 reader.exit(); |
|
572 reader.wait(); |
|
573 } |
|
574 |
|
575 |
|
576 void tst_qnetworkreply::performanceControlRate() |
|
577 { |
|
578 // this is a control comparison for the other two above |
|
579 // it does the same thing, but instead bypasses the QNetworkAccess system |
|
580 qDebug() << "The following are the maximum transfer rates that we can get in this system" |
|
581 " (bypassing QNetworkAccess)"; |
|
582 |
|
583 TimedSender sender(5000); |
|
584 QTcpSocket sink; |
|
585 sink.connectToHost("127.0.0.1", sender.serverPort()); |
|
586 DataReader reader(&sink, false); |
|
587 |
|
588 QTime loopTime; |
|
589 connect(&sink, SIGNAL(disconnected()), &QTestEventLoop::instance(), SLOT(exitLoop())); |
|
590 loopTime.start(); |
|
591 QTestEventLoop::instance().enterLoop(40); |
|
592 int elapsedTime = loopTime.elapsed(); |
|
593 sender.wait(); |
|
594 |
|
595 qint64 receivedBytes = reader.totalBytes; |
|
596 qDebug() << "tst_QNetworkReply::performanceControlRate" << "receive rate:" << (receivedBytes * 1000 / elapsedTime / 1024) << "kB/s and" |
|
597 << elapsedTime << "ms"; |
|
598 } |
|
599 |
|
600 void tst_qnetworkreply::httpDownloadPerformance_data() |
|
601 { |
|
602 QTest::addColumn<bool>("serverSendsContentLength"); |
|
603 QTest::addColumn<bool>("chunkedEncoding"); |
|
604 |
|
605 QTest::newRow("Server sends no Content-Length") << false << false; |
|
606 QTest::newRow("Server sends Content-Length") << true << false; |
|
607 QTest::newRow("Server uses chunked encoding") << false << true; |
|
608 |
|
609 } |
|
610 |
|
611 void tst_qnetworkreply::httpDownloadPerformance() |
|
612 { |
|
613 QFETCH(bool, serverSendsContentLength); |
|
614 QFETCH(bool, chunkedEncoding); |
|
615 #ifdef Q_OS_SYMBIAN |
|
616 // Show some mercy to non-desktop platform/s |
|
617 enum {UploadSize = 4*1024*1024}; // 4 MB |
|
618 #else |
|
619 enum {UploadSize = 128*1024*1024}; // 128 MB |
|
620 #endif |
|
621 HttpDownloadPerformanceServer server(UploadSize, serverSendsContentLength, chunkedEncoding); |
|
622 |
|
623 QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(server.serverPort()) + "/?bare=1")); |
|
624 QNetworkReplyPtr reply = manager.get(request); |
|
625 |
|
626 connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection); |
|
627 HttpDownloadPerformanceClient client(reply); |
|
628 |
|
629 QTime time; |
|
630 time.start(); |
|
631 QTestEventLoop::instance().enterLoop(40); |
|
632 QCOMPARE(reply->error(), QNetworkReply::NoError); |
|
633 QVERIFY(!QTestEventLoop::instance().timeout()); |
|
634 |
|
635 qint64 elapsed = time.elapsed(); |
|
636 qDebug() << "tst_QNetworkReply::httpDownloadPerformance" << elapsed << "msec, " |
|
637 << ((UploadSize/1024.0)/(elapsed/1000.0)) << " kB/sec"; |
|
638 } |
|
639 |
110 QTEST_MAIN(tst_qnetworkreply) |
640 QTEST_MAIN(tst_qnetworkreply) |
111 |
641 |
112 #include "tst_qnetworkreply.moc" |
642 #include "tst_qnetworkreply.moc" |