From 56396b4b2b66da3133aff1b3864c550ce892d233 Mon Sep 17 00:00:00 2001 From: Magne Pettersen Zachrisen Date: Tue, 10 Aug 2010 21:52:06 +0200 Subject: Added -random option to tests, making the test cases within a test execute in arbitrary order. Very useful for avoiding test cases being dependent on the running order. Added -seed option -random to make it possible to reroduce test results. The seed is printed out when -random is specified Added selftests for -random and -seed options Changed int parsing into strtol and QTime->QDateTime as suggested by Mr Macieira, fixed selftests Merge-request: 2362 Reviewed-by: Thiago Macieira --- src/testlib/qabstracttestlogger_p.h | 2 + src/testlib/qplaintestlogger.cpp | 22 +++- src/testlib/qplaintestlogger_p.h | 4 + src/testlib/qtestcase.cpp | 157 ++++++++++++++++++++------ src/testlib/qtestlightxmlstreamer.cpp | 8 +- src/testlib/qtestlog.cpp | 55 +++++---- src/testlib/qtestlog_p.h | 2 +- src/testlib/qtestlogger.cpp | 27 ++++- src/testlib/qtestlogger_p.h | 5 + src/testlib/qtestxmlstreamer.cpp | 10 +- src/testlib/qxmltestlogger.cpp | 27 ++++- src/testlib/qxmltestlogger_p.h | 4 + src/testlib/testlib.pro | 1 + tests/auto/selftests/expected_random.lightxml | 20 ++++ tests/auto/selftests/expected_random.txt | 9 ++ tests/auto/selftests/expected_random.xml | 23 ++++ tests/auto/selftests/expected_random.xunitxml | 14 +++ tests/auto/selftests/random/random.pro | 9 ++ tests/auto/selftests/random/tst_random.cpp | 28 +++++ tests/auto/selftests/selftests.pro | 2 +- tests/auto/selftests/selftests.qrc | 6 +- tests/auto/selftests/tst_selftests.cpp | 5 + 22 files changed, 368 insertions(+), 72 deletions(-) create mode 100644 tests/auto/selftests/expected_random.lightxml create mode 100644 tests/auto/selftests/expected_random.txt create mode 100644 tests/auto/selftests/expected_random.xml create mode 100644 tests/auto/selftests/expected_random.xunitxml create mode 100644 tests/auto/selftests/random/random.pro create mode 100644 tests/auto/selftests/random/tst_random.cpp diff --git a/src/testlib/qabstracttestlogger_p.h b/src/testlib/qabstracttestlogger_p.h index c84f327..a996e88 100644 --- a/src/testlib/qabstracttestlogger_p.h +++ b/src/testlib/qabstracttestlogger_p.h @@ -95,6 +95,8 @@ public: virtual void addMessage(MessageTypes type, const char *message, const char *file = 0, int line = 0) = 0; + virtual void registerRandomSeed(unsigned int seed) = 0; + static void outputString(const char *msg); static bool isTtyOutput(); }; diff --git a/src/testlib/qplaintestlogger.cpp b/src/testlib/qplaintestlogger.cpp index 1a0e737..59248a9 100644 --- a/src/testlib/qplaintestlogger.cpp +++ b/src/testlib/qplaintestlogger.cpp @@ -384,6 +384,7 @@ namespace QTest { } QPlainTestLogger::QPlainTestLogger() +: randomSeed(9), hasRandomSeed(false) { #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) InitializeCriticalSection(&QTest::outputCriticalSection); @@ -415,10 +416,17 @@ void QPlainTestLogger::startLogging() QTest::qt_snprintf(buf, sizeof(buf), "Testing %s\n", QTestResult::currentTestObjectName()); } else { - QTest::qt_snprintf(buf, sizeof(buf), - "********* Start testing of %s *********\n" - "Config: Using QTest library " QTEST_VERSION_STR - ", Qt %s\n", QTestResult::currentTestObjectName(), qVersion()); + if (hasRandomSeed) { + QTest::qt_snprintf(buf, sizeof(buf), + "********* Start testing of %s *********\n" + "Config: Using QTest library " QTEST_VERSION_STR + ", Qt %s, Random seed %d\n", QTestResult::currentTestObjectName(), qVersion(), randomSeed); + } else { + QTest::qt_snprintf(buf, sizeof(buf), + "********* Start testing of %s *********\n" + "Config: Using QTest library " QTEST_VERSION_STR + ", Qt %s\n", QTestResult::currentTestObjectName(), qVersion()); + } } QTest::outputMessage(buf); } @@ -480,4 +488,10 @@ void QPlainTestLogger::addMessage(MessageTypes type, const char *message, QTest::printMessage(QTest::messageType2String(type), message, file, line); } +void QPlainTestLogger::registerRandomSeed(unsigned int seed) +{ + randomSeed = seed; + hasRandomSeed = true; +} + QT_END_NAMESPACE diff --git a/src/testlib/qplaintestlogger_p.h b/src/testlib/qplaintestlogger_p.h index f1f1d4e..9195600 100644 --- a/src/testlib/qplaintestlogger_p.h +++ b/src/testlib/qplaintestlogger_p.h @@ -75,6 +75,10 @@ public: void addMessage(MessageTypes type, const char *message, const char *file = 0, int line = 0); + void registerRandomSeed(unsigned int seed); +private: + unsigned int randomSeed; + bool hasRandomSeed; }; QT_END_NAMESPACE diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp index 17f1a6b..5934850 100644 --- a/src/testlib/qtestcase.cpp +++ b/src/testlib/qtestcase.cpp @@ -826,25 +826,46 @@ namespace QTest { static QObject *currentTestObject = 0; - static struct TestFunction { - TestFunction():function(0), data(0) {} - ~TestFunction() { delete [] data; } - int function; - char *data; - } *testFuncs; - + class TestFunction { + public: + TestFunction() : function_(-1), data_(0) {} + void set(int function, char *data) { function_ = function; data_ = data; } + char *data() const { return data_; } + int function() const { return function_; } + ~TestFunction() { delete[] data_; } + private: + int function_; + char *data_; + }; /** - * Contains the count of test functions that was supplied - * on the command line, if any. Hence, if lastTestFuncIdx is - * more than zero, those functions should be run instead of + * Contains the list of test functions that was supplied + * on the command line, if any. Hence, if not empty, + * those functions should be run instead of * all appearing in the test case. */ - static int lastTestFuncIdx = -1; + static TestFunction * testFuncs = 0; + static int testFuncCount = 0; + + /** Don't leak testFuncs on exit even on error */ + static struct TestFuncCleanup + { + void cleanup() + { + delete[] testFuncs; + testFuncCount = 0; + testFuncs = 0; + } + + ~TestFuncCleanup() { cleanup(); } + } testFuncCleaner; static int keyDelay = -1; static int mouseDelay = -1; static int eventDelay = -1; + static bool randomOrder = false; static int keyVerbose = -1; + static unsigned int seed = 0; + static bool seedSet = false; #if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) static bool noCrashHandler = false; #endif @@ -930,6 +951,41 @@ int Q_TESTLIB_EXPORT defaultKeyDelay() return keyDelay; } +void seedRandom() +{ + static bool randomSeeded = false; + if (!randomSeeded) { + if (!QTest::seedSet) { + QTest::seed = QDateTime::currentDateTime().toTime_t(); + } + qsrand(QTest::seed); + randomSeeded = true; + } +} + +int qTestRandomSeed() +{ + Q_ASSERT(QTest::seedSet); + return QTest::seed; +} + +template +void swap(T * array, int pos, int otherPos) +{ + T tmp = array[pos]; + array[pos] = array[otherPos]; + array[otherPos] = tmp; +} + +template +static void randomizeList(T * array, int size) +{ + for (int i = 0; i != size; i++) { + int pos = qrand() % size; + swap(array, pos, i); + } +} + static bool isValidSlot(const QMetaMethod &sl) { if (sl.access() != QMetaMethod::Private || !sl.parameterTypes().isEmpty() @@ -971,8 +1027,6 @@ static int qToInt(char *str) static void qParseArgs(int argc, char *argv[]) { - lastTestFuncIdx = -1; - const char *testOptions = " options:\n" " -functions : Returns a list of current testfunctions\n" @@ -985,6 +1039,9 @@ static void qParseArgs(int argc, char *argv[]) " -v1 : Print enter messages for each testfunction\n" " -v2 : Also print out each QVERIFY/QCOMPARE/QTEST\n" " -vs : Print every signal emitted\n" + " -random : Run testcases within each test in random order\n" + " -seed n : Positive integer to be used as seed for -random. If not specified,\n" + " the current time will be used as seed.\n" " -eventdelay ms : Set default delay for mouse and keyboard simulation to ms milliseconds\n" " -keydelay ms : Set default delay for keyboard simulation to ms milliseconds\n" " -mousedelay ms : Set default delay for mouse simulation to ms milliseconds\n" @@ -1100,6 +1157,22 @@ static void qParseArgs(int argc, char *argv[]) #endif } else if (strcmp(argv[i], "-eventcounter") == 0) { QBenchmarkGlobalData::current->setMode(QBenchmarkGlobalData::EventCounter); + } else if (strcmp(argv[i], "-random") == 0) { + QTest::randomOrder = true; + } else if (strcmp(argv[i], "-seed") == 0) { + bool argumentOk = false; + if (i + 1 < argc) { + char * endpt = 0; + long longSeed = strtol(argv[++i], &endpt, 10); + argumentOk = (*endpt == '\0' && longSeed >= 0); + QTest::seed = longSeed; + } + if (!argumentOk) { + printf("-seed needs an extra positive integer parameter to specify the seed\n"); + exit(1); + } else { + QTest::seedSet = true; + } } else if (strcmp(argv[i], "-minimumvalue") == 0) { if (i + 1 >= argc) { printf("-minimumvalue needs an extra parameter to indicate the minimum time(ms)\n"); @@ -1140,6 +1213,10 @@ static void qParseArgs(int argc, char *argv[]) printf("Unknown option: '%s'\n\n%s", argv[i], testOptions); exit(1); } else { + if (!QTest::testFuncs) { + QTest::testFuncs = new QTest::TestFunction[512]; + } + int colon = -1; char buf[512], *data=0; int off; @@ -1161,17 +1238,16 @@ static void qParseArgs(int argc, char *argv[]) qPrintTestSlots(); exit(1); } - ++QTest::lastTestFuncIdx; - if (!QTest::testFuncs) { - struct Cleanup { ~Cleanup() { delete[] QTest::testFuncs; } }; - static Cleanup cleanup; - QTest::testFuncs = new TestFunction[512]; - } - QTest::testFuncs[QTest::lastTestFuncIdx].function = idx; - QTest::testFuncs[QTest::lastTestFuncIdx].data = data; - QTEST_ASSERT(QTest::lastTestFuncIdx < 512); + testFuncs[testFuncCount].set(idx, data); + testFuncCount++; + QTEST_ASSERT(QTest::testFuncCount < 512); } } + + if (QTest::seedSet && !QTest::randomOrder) { + printf("-seed requires -random\n"); + exit(1); + } } QBenchmarkResult qMedian(const QList &container) @@ -1466,9 +1542,11 @@ static void qInvokeTestMethods(QObject *testObject) { const QMetaObject *metaObject = testObject->metaObject(); QTEST_ASSERT(metaObject); - - QTestLog::startLogging(); - + if (QTest::randomOrder) { + QTestLog::startLogging(QTest::seed); + } else { + QTestLog::startLogging(); + } QTestResult::setCurrentTestFunction("initTestCase"); QTestResult::setCurrentTestLocation(QTestResult::DataFunc); QTestTable::globalTestTable(); @@ -1484,21 +1562,31 @@ static void qInvokeTestMethods(QObject *testObject) if(!QTestResult::skipCurrentTest() && !previousFailed) { - if (lastTestFuncIdx >= 0) { - for (int i = 0; i <= lastTestFuncIdx; ++i) { - if (!qInvokeTestMethod(metaObject->method(testFuncs[i].function).signature(), - testFuncs[i].data)) + if (QTest::testFuncs) { + if (QTest::randomOrder) + randomizeList(QTest::testFuncs, QTest::testFuncCount); + for (int i = 0; i != QTest::testFuncCount; i++) { + if (!qInvokeTestMethod(metaObject->method(QTest::testFuncs[i].function()).signature(), + QTest::testFuncs[i].data())) { break; + } } + testFuncCleaner.cleanup(); } else { int methodCount = metaObject->methodCount(); - for (int i = 0; i < methodCount; ++i) { - QMetaMethod slotMethod = metaObject->method(i); - if (!isValidSlot(slotMethod)) + QMetaMethod *testMethods = new QMetaMethod[methodCount]; + for (int i = 0; i != methodCount; i++) + testMethods[i] = metaObject->method(i); + if (QTest::randomOrder) + randomizeList(testMethods, methodCount); + for (int i = 0; i != methodCount; i++) { + if (!isValidSlot(testMethods[i])) continue; - if (!qInvokeTestMethod(slotMethod.signature())) + if (!qInvokeTestMethod(testMethods[i].signature())) break; } + delete[] testMethods; + testMethods = 0; } } @@ -1689,6 +1777,9 @@ int QTest::qExec(QObject *testObject, int argc, char **argv) QTestResult::setCurrentTestObject(metaObject->className()); qParseArgs(argc, argv); + if (QTest::randomOrder) { + seedRandom(); + } #ifdef QTESTLIB_USE_VALGRIND if (QBenchmarkGlobalData::current->mode() == QBenchmarkGlobalData::CallgrindParentProcess) { const QStringList origAppArgs(QCoreApplication::arguments()); diff --git a/src/testlib/qtestlightxmlstreamer.cpp b/src/testlib/qtestlightxmlstreamer.cpp index 0ac9ea8..cc5397a 100644 --- a/src/testlib/qtestlightxmlstreamer.cpp +++ b/src/testlib/qtestlightxmlstreamer.cpp @@ -42,6 +42,7 @@ #include "qtestlightxmlstreamer.h" #include "qtestelement.h" #include "qtestelementattribute.h" +#include "qtestlogger_p.h" #include "QtTest/private/qtestlog_p.h" #include "QtTest/private/qtestresult_p.h" @@ -164,8 +165,13 @@ void QTestLightXmlStreamer::formatBeforeAttributes(const QTestElement *element, void QTestLightXmlStreamer::output(QTestElement *element) const { QTestCharBuffer buf; - QTest::qt_asprintf(&buf, "\n %s\n %s\n", + if (logger()->hasRandomSeed()) { + QTest::qt_asprintf(&buf, "\n %s\n %s\n %d\n", + qVersion(), QTEST_VERSION_STR, logger()->randomSeed() ); + } else { + QTest::qt_asprintf(&buf, "\n %s\n %s\n", qVersion(), QTEST_VERSION_STR ); + } outputString(buf.constData()); QTest::qt_asprintf(&buf, "\n"); diff --git a/src/testlib/qtestlog.cpp b/src/testlib/qtestlog.cpp index 398dec5..d58e231 100644 --- a/src/testlib/qtestlog.cpp +++ b/src/testlib/qtestlog.cpp @@ -168,6 +168,29 @@ namespace QTest { } } +void initLogger() +{ + switch (QTest::logMode) { + case QTestLog::Plain: + QTest::testLogger = new QPlainTestLogger; + break; + case QTestLog::XML:{ + if(QTest::flushMode == QTestLog::FLushOn) + QTest::testLogger = new QXmlTestLogger(QXmlTestLogger::Complete); + else + QTest::testLogger = new QTestLogger(QTestLogger::TLF_XML); + break; + }case QTestLog::LightXML:{ + if(QTest::flushMode == QTestLog::FLushOn) + QTest::testLogger = new QXmlTestLogger(QXmlTestLogger::Light); + else + QTest::testLogger = new QTestLogger(QTestLogger::TLF_LightXml); + break; + }case QTestLog::XunitXML: + QTest::testLogger = new QTestLogger(QTestLogger::TLF_XunitXml); + } +} + } QTestLog::QTestLog() @@ -268,32 +291,20 @@ void QTestLog::addBenchmarkResult(const QBenchmarkResult &result) QTest::testLogger->addBenchmarkResult(result); } -void QTestLog::startLogging() +void QTestLog::startLogging(unsigned int randomSeed) { QTEST_ASSERT(!QTest::testLogger); - - switch (QTest::logMode) { - case QTestLog::Plain: - QTest::testLogger = new QPlainTestLogger; - break; - case QTestLog::XML:{ - if(QTest::flushMode == QTestLog::FLushOn) - QTest::testLogger = new QXmlTestLogger(QXmlTestLogger::Complete); - else - QTest::testLogger = new QTestLogger(QTestLogger::TLF_XML); - break; - }case QTestLog::LightXML:{ - if(QTest::flushMode == QTestLog::FLushOn) - QTest::testLogger = new QXmlTestLogger(QXmlTestLogger::Light); - else - QTest::testLogger = new QTestLogger(QTestLogger::TLF_LightXml); - break; - }case QTestLog::XunitXML: - QTest::testLogger = new QTestLogger(QTestLogger::TLF_XunitXml); - } - + QTest::initLogger(); + QTest::testLogger->registerRandomSeed(randomSeed); QTest::testLogger->startLogging(); + QTest::oldMessageHandler = qInstallMsgHandler(QTest::messageHandler); +} +void QTestLog::startLogging() +{ + QTEST_ASSERT(!QTest::testLogger); + QTest::initLogger(); + QTest::testLogger->startLogging(); QTest::oldMessageHandler = qInstallMsgHandler(QTest::messageHandler); } diff --git a/src/testlib/qtestlog_p.h b/src/testlib/qtestlog_p.h index d36ff04..01d39eb 100644 --- a/src/testlib/qtestlog_p.h +++ b/src/testlib/qtestlog_p.h @@ -83,6 +83,7 @@ public: static void info(const char *msg, const char *file, int line); static void startLogging(); + static void startLogging(unsigned int randomSeed); static void stopLogging(); static void setLogMode(LogMode mode); @@ -97,7 +98,6 @@ public: static void setMaxWarnings(int max); static void setFlushMode(FlushMode mode); - private: QTestLog(); ~QTestLog(); diff --git a/src/testlib/qtestlogger.cpp b/src/testlib/qtestlogger.cpp index f0be6be..cab97d1 100644 --- a/src/testlib/qtestlogger.cpp +++ b/src/testlib/qtestlogger.cpp @@ -62,7 +62,8 @@ QTestLogger::QTestLogger(int fm) warningCounter(0), skipCounter(0), systemCounter(0), qdebugCounter(0), qwarnCounter(0), qfatalCounter(0), - infoCounter(0) + infoCounter(0), randomSeed_(0), + hasRandomSeed_(false) { } @@ -133,6 +134,14 @@ void QTestLogger::stopLogging() property->addAttribute(QTest::AI_PropertyValue, qVersion()); properties->addLogElement(property); + if (hasRandomSeed()) { + property = new QTestElement(QTest::LET_Property); + property->addAttribute(QTest::AI_Name, "RandomSeed"); + QTest::qt_snprintf(buf, sizeof(buf), "%i", randomSeed()); + property->addAttribute(QTest::AI_PropertyValue, buf); + properties->addLogElement(property); + } + currentLogElement->addLogElement(properties); currentLogElement->addLogElement(iterator); @@ -420,5 +429,21 @@ int QTestLogger::infoCount() const return infoCounter; } +void QTestLogger::registerRandomSeed(unsigned int seed) +{ + randomSeed_ = seed; + hasRandomSeed_ = true; +} + +unsigned int QTestLogger::randomSeed() const +{ + return randomSeed_; +} + +bool QTestLogger::hasRandomSeed() const +{ + return hasRandomSeed_; +} + QT_END_NAMESPACE diff --git a/src/testlib/qtestlogger_p.h b/src/testlib/qtestlogger_p.h index bb7a358..8932a85 100644 --- a/src/testlib/qtestlogger_p.h +++ b/src/testlib/qtestlogger_p.h @@ -101,6 +101,9 @@ class QTestLogger : public QAbstractTestLogger int qwarnCount() const; int qfatalCount() const; int infoCount() const; + void registerRandomSeed(unsigned int seed); + unsigned int randomSeed() const; + bool hasRandomSeed() const; private: QTestElement *listOfTestcases; @@ -121,6 +124,8 @@ class QTestLogger : public QAbstractTestLogger int qwarnCounter; int qfatalCounter; int infoCounter; + unsigned int randomSeed_; + bool hasRandomSeed_; }; QT_END_NAMESPACE diff --git a/src/testlib/qtestxmlstreamer.cpp b/src/testlib/qtestxmlstreamer.cpp index f63c221..a6b8376 100644 --- a/src/testlib/qtestxmlstreamer.cpp +++ b/src/testlib/qtestxmlstreamer.cpp @@ -42,6 +42,7 @@ #include "qtestxmlstreamer.h" #include "qtestelement.h" #include "qtestelementattribute.h" +#include "qtestlogger_p.h" #include "QtTest/private/qtestlog_p.h" #include "QtTest/private/qtestresult_p.h" @@ -204,8 +205,13 @@ void QTestXmlStreamer::output(QTestElement *element) const quotedTc.constData()); outputString(buf.constData()); - QTest::qt_asprintf(&buf, "\n %s\n %s\n", - qVersion(), QTEST_VERSION_STR ); + if (logger()->hasRandomSeed()) { + QTest::qt_asprintf(&buf, "\n %s\n %s\n %d\n", + qVersion(), QTEST_VERSION_STR, logger()->randomSeed() ); + } else { + QTest::qt_asprintf(&buf, "\n %s\n %s\n", + qVersion(), QTEST_VERSION_STR ); + } outputString(buf.constData()); QTest::qt_asprintf(&buf, "\n"); diff --git a/src/testlib/qxmltestlogger.cpp b/src/testlib/qxmltestlogger.cpp index 07e8ef0..2bf7d77 100644 --- a/src/testlib/qxmltestlogger.cpp +++ b/src/testlib/qxmltestlogger.cpp @@ -93,7 +93,7 @@ namespace QTest { QXmlTestLogger::QXmlTestLogger(XmlMode mode ) - :xmlmode(mode) + :xmlmode(mode), randomSeed(0), hasRandomSeed(false) { } @@ -116,11 +116,20 @@ void QXmlTestLogger::startLogging() outputString(buf.constData()); } - QTest::qt_asprintf(&buf, - "\n" - " %s\n" - " "QTEST_VERSION_STR"\n" - "\n", qVersion()); + if (hasRandomSeed) { + QTest::qt_asprintf(&buf, + "\n" + " %s\n" + " "QTEST_VERSION_STR"\n" + " %d\n" + "\n", qVersion(), randomSeed); + } else { + QTest::qt_asprintf(&buf, + "\n" + " %s\n" + " "QTEST_VERSION_STR"\n" + "\n", qVersion()); + } outputString(buf.constData()); } @@ -441,4 +450,10 @@ int QXmlTestLogger::xmlCdata(QTestCharBuffer* str, char const* src) return allocateStringFn(str, src, QXmlTestLogger::xmlCdata); } +void QXmlTestLogger::registerRandomSeed(unsigned int seed) +{ + randomSeed = seed; + hasRandomSeed = true; +} + QT_END_NAMESPACE diff --git a/src/testlib/qxmltestlogger_p.h b/src/testlib/qxmltestlogger_p.h index ae918dc..82dd8a6 100644 --- a/src/testlib/qxmltestlogger_p.h +++ b/src/testlib/qxmltestlogger_p.h @@ -79,6 +79,8 @@ public: void addMessage(MessageTypes type, const char *message, const char *file = 0, int line = 0); + void registerRandomSeed(unsigned int seed); + static int xmlCdata(QTestCharBuffer *dest, char const* src); static int xmlQuote(QTestCharBuffer *dest, char const* src); static int xmlCdata(QTestCharBuffer *dest, char const* src, size_t n); @@ -86,6 +88,8 @@ public: private: XmlMode xmlmode; + unsigned int randomSeed; + bool hasRandomSeed; }; QT_END_NAMESPACE diff --git a/src/testlib/testlib.pro b/src/testlib/testlib.pro index a8186d8..6bb0492 100644 --- a/src/testlib/testlib.pro +++ b/src/testlib/testlib.pro @@ -1,6 +1,7 @@ TARGET = QtTest QPRO_PWD = $$PWD QT = core +CONFIG += debug INCLUDEPATH += . unix:!embedded:QMAKE_PKGCONFIG_DESCRIPTION = Qt \ Unit \ diff --git a/tests/auto/selftests/expected_random.lightxml b/tests/auto/selftests/expected_random.lightxml new file mode 100644 index 0000000..15f56a1 --- /dev/null +++ b/tests/auto/selftests/expected_random.lightxml @@ -0,0 +1,20 @@ + + @INSERT_QT_VERSION_HERE@ + @INSERT_QT_VERSION_HERE@ + 325 + + + + + + + + + + + + + + + + diff --git a/tests/auto/selftests/expected_random.txt b/tests/auto/selftests/expected_random.txt new file mode 100644 index 0000000..3f4c11d --- /dev/null +++ b/tests/auto/selftests/expected_random.txt @@ -0,0 +1,9 @@ +********* Start testing of tst_Random ********* +Config: Using QTest library @INSERT_QT_VERSION_HERE@, Qt @INSERT_QT_VERSION_HERE@, Random seed 325 +PASS : tst_Random::initTestCase() +PASS : tst_Random::testTwo() +PASS : tst_Random::testOne() +PASS : tst_Random::testThree() +PASS : tst_Random::cleanupTestCase() +Totals: 5 passed, 0 failed, 0 skipped +********* Finished testing of tst_Random ********* diff --git a/tests/auto/selftests/expected_random.xml b/tests/auto/selftests/expected_random.xml new file mode 100644 index 0000000..3677287 --- /dev/null +++ b/tests/auto/selftests/expected_random.xml @@ -0,0 +1,23 @@ + + + + @INSERT_QT_VERSION_HERE@ + @INSERT_QT_VERSION_HERE@ + 325 + + + + + + + + + + + + + + + + + diff --git a/tests/auto/selftests/expected_random.xunitxml b/tests/auto/selftests/expected_random.xunitxml new file mode 100644 index 0000000..262aa0a --- /dev/null +++ b/tests/auto/selftests/expected_random.xunitxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/tests/auto/selftests/random/random.pro b/tests/auto/selftests/random/random.pro new file mode 100644 index 0000000..19572ad --- /dev/null +++ b/tests/auto/selftests/random/random.pro @@ -0,0 +1,9 @@ +load(qttest_p4) +SOURCES += tst_random.cpp +QT = core + +mac:CONFIG -= app_bundle +CONFIG -= debug_and_release_target + + +TARGET = random diff --git a/tests/auto/selftests/random/tst_random.cpp b/tests/auto/selftests/random/tst_random.cpp new file mode 100644 index 0000000..1ee922e --- /dev/null +++ b/tests/auto/selftests/random/tst_random.cpp @@ -0,0 +1,28 @@ +#include +#include + +class tst_Random: public QObject +{ + Q_OBJECT + +private slots: + void testOne() const {} + void testTwo() const {} + void testThree() const {} +}; + +/* +int main(int argc, char * argv[]) +{ + int failures = 0; + tst_Random tst; + for (int i = 0; i != 10; i++) { + failures += QTest::qExec(&tst, argc, argv); + } + return failures; +} +*/ +QTEST_MAIN(tst_Random) + +#include "tst_random.moc" + diff --git a/tests/auto/selftests/selftests.pro b/tests/auto/selftests/selftests.pro index d854b5e..bfbd598 100644 --- a/tests/auto/selftests/selftests.pro +++ b/tests/auto/selftests/selftests.pro @@ -5,7 +5,7 @@ SUBDIRS = subtest test warnings maxwarnings cmptest globaldata skipglobal skip \ skipinit skipinitdata datetime singleskip assert waitwithoutgui differentexec \ exceptionthrow qexecstringlist datatable commandlinedata\ benchlibwalltime benchlibcallgrind benchlibeventcounter benchlibtickcounter \ - benchliboptions xunit badxml longstring + benchliboptions xunit badxml longstring random INSTALLS = diff --git a/tests/auto/selftests/selftests.qrc b/tests/auto/selftests/selftests.qrc index f82722b..ba7ca4f 100644 --- a/tests/auto/selftests/selftests.qrc +++ b/tests/auto/selftests/selftests.qrc @@ -90,6 +90,10 @@ expected_multiexec.xml expected_multiexec.xunitxml expected_qexecstringlist.txt + expected_random.txt + expected_random.lightxml + expected_random.xml + expected_random.xunitxml expected_singleskip.lightxml expected_singleskip.txt expected_singleskip.xml @@ -134,5 +138,5 @@ expected_xunit.txt expected_xunit.xml expected_xunit.xunitxml - + diff --git a/tests/auto/selftests/tst_selftests.cpp b/tests/auto/selftests/tst_selftests.cpp index 0818b4c..9fb7ce6 100644 --- a/tests/auto/selftests/tst_selftests.cpp +++ b/tests/auto/selftests/tst_selftests.cpp @@ -245,6 +245,7 @@ void tst_Selftests::runSubTest_data() << "xunit" << "longstring" << "badxml" + << "random" ; foreach (Logger const& logger, allLoggers()) { @@ -273,6 +274,9 @@ void tst_Selftests::runSubTest_data() else if (subtest == "badxml") { arguments << "-eventcounter"; } + else if (subtest == "random") { + arguments << QString("-random -seed 325").split(' '); + } // These tests don't work right with loggers other than plain, usually because // they internally supply arguments to themselves. @@ -632,3 +636,4 @@ void tst_Selftests::cleanupTestCase() QTEST_MAIN(tst_Selftests) #include "tst_selftests.moc" + -- cgit v0.12 From c74489031684a77bdec5f009e234cbec4497cd0d Mon Sep 17 00:00:00 2001 From: Rohan McGovern Date: Thu, 12 Aug 2010 08:46:38 +1000 Subject: Fixed linking against libQtTest on Mac. Fixes: `ld: framework not found QtTest' testlib was forcing CONFIG+=debug, which caused only the QtTest_debug variant of the framework to be produced on Mac. Qt libraries shouldn't force release or debug at this level. --- src/testlib/testlib.pro | 1 - 1 file changed, 1 deletion(-) diff --git a/src/testlib/testlib.pro b/src/testlib/testlib.pro index 6bb0492..a8186d8 100644 --- a/src/testlib/testlib.pro +++ b/src/testlib/testlib.pro @@ -1,7 +1,6 @@ TARGET = QtTest QPRO_PWD = $$PWD QT = core -CONFIG += debug INCLUDEPATH += . unix:!embedded:QMAKE_PKGCONFIG_DESCRIPTION = Qt \ Unit \ -- cgit v0.12 From cf13b06e88e68ca966d6327c0e12cc7b89d616fc Mon Sep 17 00:00:00 2001 From: John Brooks Date: Thu, 12 Aug 2010 10:14:25 +0200 Subject: Cleanup the connection list logic in QObject::sender Merge-request: 2433 Reviewed-by: Bradley T. Hughes --- src/corelib/kernel/qobject.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 143dbff..9ce111d 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -2298,13 +2298,12 @@ QObject *QObject::sender() const if (!d->currentSender) return 0; - // Return 0 if d->currentSender isn't in d->senders - bool found = false; - for (QObjectPrivate::Connection *c = d->senders; c && !found; c = c->next) - found = (c->sender == d->currentSender->sender); - if (!found) - return 0; - return d->currentSender->sender; + for (QObjectPrivate::Connection *c = d->senders; c; c = c->next) { + if (c->sender == d->currentSender->sender) + return d->currentSender->sender; + } + + return 0; } /*! -- cgit v0.12 From a6892d69281d3555f3c3fd03f3b3892b1940191d Mon Sep 17 00:00:00 2001 From: John Brooks Date: Thu, 12 Aug 2010 10:14:28 +0200 Subject: Added QObject::senderSignalIndex() Returns the metamethod index of the signal executing the current slot Merge-request: 2433 Reviewed-by: Bradley T. Hughes --- src/corelib/kernel/qobject.cpp | 43 +++++++++++++++++++++++++++++++++++++- src/corelib/kernel/qobject.h | 1 + tests/auto/qobject/tst_qobject.cpp | 24 +++++++++++++++++++-- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 9ce111d..ceffa66 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -2287,7 +2287,7 @@ static void err_info_about_objects(const char * func, a thread different from this object's thread. Do not use this function in this type of scenario. - \sa QSignalMapper + \sa senderSignalIndex(), QSignalMapper */ QObject *QObject::sender() const @@ -2307,6 +2307,47 @@ QObject *QObject::sender() const } /*! + \since 4.8 + + Returns the meta-method index of the signal that called the currently + executing slot, which is a member of the class returned by sender(). + If called outside of a slot activated by a signal, -1 is returned. + + For signals with default parameters, this function will always return + the index with all parameters, regardless of which was used with + connect(). For example, the signal \c {destroyed(QObject *obj = 0)} + will have two different indexes (with and without the parameter), but + this function will always return the index with a parameter. This does + not apply when overloading signals with different parameters. + + \warning This function violates the object-oriented principle of + modularity. However, getting access to the signal index might be useful + when many signals are connected to a single slot. + + \warning The return value of this function is not valid when the slot + is called via a Qt::DirectConnection from a thread different from this + object's thread. Do not use this function in this type of scenario. + + \sa sender(), QMetaObject::indexOfSignal(), QMetaObject::method() +*/ + +int QObject::senderSignalIndex() const +{ + Q_D(const QObject); + + QMutexLocker locker(signalSlotLock(this)); + if (!d->currentSender) + return -1; + + for (QObjectPrivate::Connection *c = d->senders; c; c = c->next) { + if (c->sender == d->currentSender->sender) + return d->currentSender->signal; + } + + return -1; +} + +/*! Returns the number of receivers connected to the \a signal. Since both slots and signals can be used as receivers for signals, diff --git a/src/corelib/kernel/qobject.h b/src/corelib/kernel/qobject.h index 21bad16..b5db936 100644 --- a/src/corelib/kernel/qobject.h +++ b/src/corelib/kernel/qobject.h @@ -280,6 +280,7 @@ public Q_SLOTS: protected: QObject *sender() const; + int senderSignalIndex() const; int receivers(const char* signal) const; virtual void timerEvent(QTimerEvent *); diff --git a/tests/auto/qobject/tst_qobject.cpp b/tests/auto/qobject/tst_qobject.cpp index fc38a84..abfe24b 100644 --- a/tests/auto/qobject/tst_qobject.cpp +++ b/tests/auto/qobject/tst_qobject.cpp @@ -1782,9 +1782,12 @@ class SuperObject : public QObject Q_OBJECT public: QObject *theSender; + int theSignalId; + SuperObject() { theSender = 0; + theSignalId = 0; } friend class tst_QObject; @@ -1795,16 +1798,17 @@ public slots: void rememberSender() { theSender = sender(); + theSignalId = senderSignalIndex(); } void deleteAndRememberSender() { delete theSender; - theSender = sender(); + rememberSender(); } signals: + void anotherSignal(); void theSignal(); - }; void tst_QObject::sender() @@ -1812,12 +1816,23 @@ void tst_QObject::sender() { SuperObject sender; SuperObject receiver; + connect(&sender, SIGNAL(anotherSignal()), + &receiver, SLOT(rememberSender())); connect(&sender, SIGNAL(theSignal()), &receiver, SLOT(rememberSender())); QCOMPARE(receiver.sender(), (QObject *)0); + QCOMPARE(receiver.senderSignalIndex(), -1); emit sender.theSignal(); QCOMPARE(receiver.theSender, (QObject *)&sender); QCOMPARE(receiver.sender(), (QObject *)0); + QCOMPARE(receiver.theSignalId, + sender.metaObject()->indexOfSignal("theSignal()")); + QCOMPARE(receiver.senderSignalIndex(), -1); + + emit sender.anotherSignal(); + QCOMPARE(receiver.theSignalId, + sender.metaObject()->indexOfSignal("anotherSignal()")); + QCOMPARE(receiver.senderSignalIndex(), -1); } { @@ -1834,11 +1849,16 @@ void tst_QObject::sender() Qt::DirectConnection); QCOMPARE(receiver->sender(), (QObject *)0); + QCOMPARE(receiver->senderSignalIndex(), -1); receiver->theSender = 0; + receiver->theSignalId = -1; thread.start(); emit sender->theSignal(); QCOMPARE(receiver->theSender, (QObject *) sender); QCOMPARE(receiver->sender(), (QObject *)0); + QCOMPARE(receiver->theSignalId, + sender->metaObject()->indexOfSignal("theSignal()")); + QCOMPARE(receiver->senderSignalIndex(), -1); QVERIFY(thread.wait(10000)); delete receiver; -- cgit v0.12 From 53e2167cfae5fd5275892ccc926b7436199e3590 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:14:51 +0200 Subject: QNativeSocketEngine: Fix wrong debug output --- src/network/socket/qnativesocketengine_unix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index f91ce5f..bb3f0e4 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -887,7 +887,7 @@ qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxSize) { Q_Q(QNativeSocketEngine); if (!q->isValid()) { - qWarning("QNativeSocketEngine::unbufferedRead: Invalid socket"); + qWarning("QNativeSocketEngine::nativeRead: Invalid socket"); return -1; } -- cgit v0.12 From 01057a2d328004d33983198e6afc8c9ab03cc65d Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:16:15 +0200 Subject: QAbstractSocket: Enable Unbuffered OpenMode for TCP Reviewed-by: Peter Hartmann Reviewed-by: Jeremy Katz Reviewed-by: Aleksandar Sasha Babic --- src/network/socket/qabstractsocket.cpp | 143 +++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 32 deletions(-) diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index 171fbb6..ca72271 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -627,8 +627,9 @@ bool QAbstractSocketPrivate::canReadNotification() // only emit readyRead() when not recursing, and only if there is data available bool hasData = newBytes > 0 #ifndef QT_NO_UDPSOCKET - || (!isBuffered && socketEngine && socketEngine->hasPendingDatagrams()) + || (!isBuffered && socketType != QAbstractSocket::TcpSocket && socketEngine && socketEngine->hasPendingDatagrams()) #endif + || (!isBuffered && socketType == QAbstractSocket::TcpSocket && socketEngine) ; if (!emittedReadyRead && hasData) { @@ -1350,8 +1351,11 @@ void QAbstractSocket::connectToHostImplementation(const QString &hostName, quint } #endif - if (!d_func()->isBuffered) - openMode |= QAbstractSocket::Unbuffered; + if (openMode & QIODevice::Unbuffered) + d->isBuffered = false; // Unbuffered QTcpSocket + else if (!d_func()->isBuffered) + openMode |= QAbstractSocket::Unbuffered; // QUdpSocket + QIODevice::open(openMode); d->state = HostLookupState; emit stateChanged(d->state); @@ -1431,10 +1435,12 @@ qint64 QAbstractSocket::bytesAvailable() const { Q_D(const QAbstractSocket); qint64 available = QIODevice::bytesAvailable(); - if (d->isBuffered) - available += (qint64) d->readBuffer.size(); - else if (d->socketEngine && d->socketEngine->isValid()) + + available += (qint64) d->readBuffer.size(); + + if (!d->isBuffered && d->socketEngine && d->socketEngine->isValid()) available += d->socketEngine->bytesAvailable(); + #if defined(QABSTRACTSOCKET_DEBUG) qDebug("QAbstractSocket::bytesAvailable() == %llu", available); #endif @@ -2067,42 +2073,51 @@ bool QAbstractSocket::flush() qint64 QAbstractSocket::readData(char *data, qint64 maxSize) { Q_D(QAbstractSocket); - if (d->socketEngine && !d->socketEngine->isReadNotificationEnabled() && d->socketEngine->isValid()) - d->socketEngine->setReadNotificationEnabled(true); - - if (!d->isBuffered) { - if (!d->socketEngine) - return -1; // no socket engine is probably EOF - qint64 readBytes = d->socketEngine->read(data, maxSize); - if (readBytes < 0) { - d->socketError = d->socketEngine->error(); - setErrorString(d->socketEngine->errorString()); - } - if (!d->socketEngine->isReadNotificationEnabled()) - d->socketEngine->setReadNotificationEnabled(true); -#if defined (QABSTRACTSOCKET_DEBUG) - qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld", - data, qt_prettyDebug(data, 32, readBytes).data(), maxSize, - readBytes); -#endif - return readBytes; - } - if (d->readBuffer.isEmpty()) + // This is for a buffered QTcpSocket + if (d->isBuffered && d->readBuffer.isEmpty()) // if we're still connected, return 0 indicating there may be more data in the future // if we're not connected, return -1 indicating EOF return d->state == QAbstractSocket::ConnectedState ? qint64(0) : qint64(-1); - // If readFromSocket() read data, copy it to its destination. - if (maxSize == 1) { + // short cut for a char read if we have something in the buffer + if (maxSize == 1 && !d->readBuffer.isEmpty()) { *data = d->readBuffer.getChar(); #if defined (QABSTRACTSOCKET_DEBUG) - qDebug("QAbstractSocket::readData(%p '%c (0x%.2x)', 1) == 1", + qDebug("QAbstractSocket::readData(%p '%c (0x%.2x)', 1) == 1 [char buffer]", data, isprint(int(uchar(*data))) ? *data : '?', *data); #endif + if (d->readBuffer.isEmpty() && d->socketEngine) + d->socketEngine->setReadNotificationEnabled(true); return 1; } + // Special case for an Unbuffered QTcpSocket + // Re-filling the buffer. + if (d->socketType == TcpSocket + && !d->isBuffered + && d->readBuffer.size() < maxSize + && d->readBufferMaxSize > 0 + && maxSize < d->readBufferMaxSize + && d->socketEngine) { + qDebug() << "QAbstractSocket::readData filling buffer"; + // Our buffer is empty and a read() was requested for a byte amount that is smaller + // than the readBufferMaxSize. This means that we should fill our buffer since we want + // such small reads come from the buffer and not always go to the costly socket engine read() + qint64 bytesToRead = d->socketEngine->bytesAvailable(); + if (bytesToRead > 0) { + char *ptr = d->readBuffer.reserve(bytesToRead); + qint64 readBytes = d->socketEngine->read(ptr, bytesToRead); + if (readBytes == -2) { + // No bytes currently available for reading. + d->readBuffer.chop(bytesToRead); + } else { + d->readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes))); + } + } + } + + // First try to satisfy the read from the buffer qint64 bytesToRead = qMin(qint64(d->readBuffer.size()), maxSize); qint64 readSoFar = 0; while (readSoFar < bytesToRead) { @@ -2114,8 +2129,50 @@ qint64 QAbstractSocket::readData(char *data, qint64 maxSize) d->readBuffer.free(bytesToReadFromThisBlock); } + if (d->socketEngine && !d->socketEngine->isReadNotificationEnabled() && d->socketEngine->isValid()) + d->socketEngine->setReadNotificationEnabled(true); + + if (readSoFar > 0) { + // we read some data from buffer. + // Just return, readyRead will be emitted again +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p '%c (0x%.2x)', %lli) == %lli [buffer]", + data, isprint(int(uchar(*data))) ? *data : '?', *data, maxSize, readSoFar); +#endif + + if (d->readBuffer.isEmpty() && d->socketEngine) + d->socketEngine->setReadNotificationEnabled(true); + return readSoFar; + } + + // This code path is for Unbuffered QTcpSocket or for connected UDP + + if (!d->isBuffered) { + if (!d->socketEngine) + return -1; // no socket engine is probably EOF + qint64 readBytes = d->socketEngine->read(data, maxSize); + if (readBytes == -2) { + // -2 from the engine means no bytes available (EAGAIN) so read more later + return 0; + } else if (readBytes < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + } else if (!d->socketEngine->isReadNotificationEnabled()) { + // Only do this when there was no error + d->socketEngine->setReadNotificationEnabled(true); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld [engine]", + data, qt_prettyDebug(data, 32, readBytes).data(), maxSize, + readBytes); +#endif + return readBytes; + } + + #if defined (QABSTRACTSOCKET_DEBUG) - qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld", + qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld [unreachable]", data, qt_prettyDebug(data, qMin(32, readSoFar), readSoFar).data(), maxSize, readSoFar); #endif @@ -2140,7 +2197,23 @@ qint64 QAbstractSocket::writeData(const char *data, qint64 size) return -1; } - if (!d->isBuffered) { + if (!d->isBuffered && d->socketType == TcpSocket && d->writeBuffer.isEmpty()) { + // This code is for the new Unbuffered QTcpSocket use case + qint64 written = d->socketEngine->write(data, size); + if (written < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + return written; + } else if (written < size) { + // Buffer what was not written yet + char *ptr = d->writeBuffer.reserve(size - written); + memcpy(ptr, data + written, size - written); + if (d->socketEngine) + d->socketEngine->setWriteNotificationEnabled(true); + } + return size; // size=actually written + what has been buffered + } else if (!d->isBuffered && d->socketType != TcpSocket) { + // This is for a QUdpSocket that was connect()ed qint64 written = d->socketEngine->write(data, size); if (written < 0) { d->socketError = d->socketEngine->error(); @@ -2159,6 +2232,12 @@ qint64 QAbstractSocket::writeData(const char *data, qint64 size) return written; } + // This is the code path for normal buffered QTcpSocket or + // unbuffered QTcpSocket when there was already something in the + // write buffer and therefore we could not do a direct engine write. + // We just write to our write buffer and enable the write notifier + // The write notifier then flush()es the buffer. + char *ptr = d->writeBuffer.reserve(size); if (size == 1) *ptr = *data; -- cgit v0.12 From 66a251c63191ae4dde4c9c3d6b2fcf75708c645a Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:19:26 +0200 Subject: QNAM Zerocopy: HTTP implementation part Reviewed-by: Peter Hartmann Reviewed-by: Jeremy Katz Reviewed-by: Aleksandar Sasha Babic --- src/network/access/qhttpnetworkconnection.cpp | 13 ++- .../access/qhttpnetworkconnectionchannel.cpp | 130 ++++++++++++++++----- .../access/qhttpnetworkconnectionchannel_p.h | 11 +- src/network/access/qhttpnetworkreply.cpp | 113 ++++++++++++++---- src/network/access/qhttpnetworkreply_p.h | 15 ++- src/network/access/qhttpnetworkrequest.cpp | 11 ++ src/network/access/qhttpnetworkrequest_p.h | 4 + 7 files changed, 230 insertions(+), 67 deletions(-) diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 9e2b85e..400bb12 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -117,6 +117,7 @@ void QHttpNetworkConnectionPrivate::init() { for (int i = 0; i < channelCount; i++) { channels[i].setConnection(this->q_func()); + channels[i].ssl = encrypt; channels[i].init(); } } @@ -530,33 +531,35 @@ void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket) || channels[i].state == QHttpNetworkConnectionChannel::ReadingState)) return; - - //qDebug() << "QHttpNetworkConnectionPrivate::fillPipeline processing highPriorityQueue, size=" << highPriorityQueue.size() << " alreadyPipelined=" << channels[i].alreadyPipelinedRequests.length(); int lengthBefore; while (!highPriorityQueue.isEmpty()) { lengthBefore = channels[i].alreadyPipelinedRequests.length(); fillPipeline(highPriorityQueue, channels[i]); - if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) + if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) { + channels[i].pipelineFlush(); return; + } if (lengthBefore == channels[i].alreadyPipelinedRequests.length()) break; // did not process anything, now do the low prio queue } - //qDebug() << "QHttpNetworkConnectionPrivate::fillPipeline processing lowPriorityQueue, size=" << lowPriorityQueue.size() << " alreadyPipelined=" << channels[i].alreadyPipelinedRequests.length(); while (!lowPriorityQueue.isEmpty()) { lengthBefore = channels[i].alreadyPipelinedRequests.length(); fillPipeline(lowPriorityQueue, channels[i]); - if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) + if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) { + channels[i].pipelineFlush(); return; + } if (lengthBefore == channels[i].alreadyPipelinedRequests.length()) break; // did not process anything } + channels[i].pipelineFlush(); } // returns true when the processing of a queue has been done diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 71a4de3..c0b5928 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -60,6 +60,7 @@ QT_BEGIN_NAMESPACE QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel() : socket(0) + , ssl(false) , state(IdleState) , reply(0) , written(0) @@ -90,11 +91,8 @@ void QHttpNetworkConnectionChannel::init() #else socket = new QTcpSocket; #endif - - // limit the socket read buffer size. we will read everything into - // the QHttpNetworkReply anyway, so let's grow only that and not - // here and there. - socket->setReadBufferSize(64*1024); + // Set by QNAM anyway, but let's be safe here + socket->setProxy(QNetworkProxy::NoProxy); QObject::connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(_q_bytesWritten(qint64)), @@ -164,11 +162,12 @@ bool QHttpNetworkConnectionChannel::sendRequest() written = 0; // excluding the header bytesTotal = 0; - reply->d_func()->clear(); - reply->d_func()->connection = connection; - reply->d_func()->connectionChannel = this; - reply->d_func()->autoDecompress = request.d->autoDecompress; - reply->d_func()->pipeliningUsed = false; + QHttpNetworkReplyPrivate *replyPrivate = reply->d_func(); + replyPrivate->clear(); + replyPrivate->connection = connection; + replyPrivate->connectionChannel = this; + replyPrivate->autoDecompress = request.d->autoDecompress; + replyPrivate->pipeliningUsed = false; pendingEncrypt = false; // if the url contains authentication parameters, use the new ones @@ -328,7 +327,6 @@ void QHttpNetworkConnectionChannel::_q_receiveReply() return; } - qint64 bytes = 0; QAbstractSocket::SocketState socketState = socket->state(); // connection might be closed to signal the end of data @@ -349,12 +347,14 @@ void QHttpNetworkConnectionChannel::_q_receiveReply() } // read loop for the response - while (socket->bytesAvailable()) { + qint64 bytes = 0; + qint64 lastBytes = bytes; + do { + lastBytes = bytes; + QHttpNetworkReplyPrivate::ReplyState state = reply->d_func()->state; switch (state) { case QHttpNetworkReplyPrivate::NothingDoneState: { - // only eat whitespace on the first call - eatWhitespace(); state = reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState; // fallthrough } @@ -378,6 +378,7 @@ void QHttpNetworkConnectionChannel::_q_receiveReply() return; } bytes += headerBytes; + // If headers were parsed successfully now it is the ReadingDataState if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) { if (replyPrivate->isGzipped() && replyPrivate->autoDecompress) { // remove the Content-Length from header @@ -392,6 +393,10 @@ void QHttpNetworkConnectionChannel::_q_receiveReply() } if (replyPrivate->shouldEmitSignals()) emit reply->headerChanged(); + // After headerChanged had been emitted + // we can suddenly have a replyPrivate->userProvidedDownloadBuffer + // this is handled in the ReadingDataState however + if (!replyPrivate->expectContent()) { replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState; allDone(); @@ -412,12 +417,24 @@ void QHttpNetworkConnectionChannel::_q_receiveReply() return; } - if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress - && replyPrivate->bodyLength > 0) { - // bulk files like images should fulfill these properties and - // we can therefore save on memory copying - bytes = replyPrivate->readBodyFast(socket, &replyPrivate->responseData); - replyPrivate->totalProgress += bytes; + if (replyPrivate->userProvidedDownloadBuffer) { + // the user provided a direct buffer where we should put all our data in. + // this only works when we can tell the user the content length and he/she can allocate + // the buffer in that size. + // note that this call will read only from the still buffered data + qint64 haveRead = replyPrivate->readBodyVeryFast(socket, replyPrivate->userProvidedDownloadBuffer + replyPrivate->totalProgress); + bytes += haveRead; + replyPrivate->totalProgress += haveRead; + + // the user will get notified of it via progress signal + emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); + } else if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress + && replyPrivate->bodyLength > 0) { + // bulk files like images should fulfill these properties and + // we can therefore save on memory copying + qint64 haveRead = replyPrivate->readBodyFast(socket, &replyPrivate->responseData); + bytes += haveRead; + replyPrivate->totalProgress += haveRead; if (replyPrivate->shouldEmitSignals()) { emit reply->readyRead(); emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength); @@ -428,8 +445,9 @@ void QHttpNetworkConnectionChannel::_q_receiveReply() // use the traditional slower reading (for compressed encoding, chunked encoding, // no content-length etc) QByteDataBuffer byteDatas; - bytes = replyPrivate->readBody(socket, &byteDatas); - if (bytes) { + qint64 haveRead = replyPrivate->readBody(socket, &byteDatas); + if (haveRead) { + bytes += haveRead; if (replyPrivate->autoDecompress) replyPrivate->appendCompressedReplyData(byteDatas); else @@ -463,7 +481,7 @@ void QHttpNetworkConnectionChannel::_q_receiveReply() default: break; } - } + } while (bytes != lastBytes && reply); } // called when unexpectedly reading a -1 or when data is expected but socket is closed @@ -506,7 +524,7 @@ bool QHttpNetworkConnectionChannel::ensureConnection() if (socketState != QAbstractSocket::ConnectedState) { // connect to the host if not already connected. state = QHttpNetworkConnectionChannel::ConnectingState; - pendingEncrypt = connection->d_func()->encrypt; + pendingEncrypt = ssl; // reset state pipeliningSupported = PipeliningSupportUnknown; @@ -529,23 +547,43 @@ bool QHttpNetworkConnectionChannel::ensureConnection() #ifndef QT_NO_NETWORKPROXY // HTTPS always use transparent proxy. - if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !connection->d_func()->encrypt) { + if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl) { connectHost = connection->d_func()->networkProxy.hostName(); connectPort = connection->d_func()->networkProxy.port(); } #endif - if (connection->d_func()->encrypt) { + if (ssl) { #ifndef QT_NO_OPENSSL QSslSocket *sslSocket = qobject_cast(socket); sslSocket->connectToHostEncrypted(connectHost, connectPort); if (ignoreAllSslErrors) sslSocket->ignoreSslErrors(); sslSocket->ignoreSslErrors(ignoreSslErrorsList); + + // limit the socket read buffer size. we will read everything into + // the QHttpNetworkReply anyway, so let's grow only that and not + // here and there. + socket->setReadBufferSize(64*1024); #else connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError); #endif } else { - socket->connectToHost(connectHost, connectPort); + // In case of no proxy we can use the Unbuffered QTcpSocket + if (connection->d_func()->networkProxy.type() == QNetworkProxy::NoProxy + && connection->cacheProxy().type() == QNetworkProxy::NoProxy + && connection->transparentProxy().type() == QNetworkProxy::NoProxy) { + socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite | QIODevice::Unbuffered); + // For an Unbuffered QTcpSocket, the read buffer size has a special meaning. + socket->setReadBufferSize(1*1024); + + } else { + socket->connectToHost(connectHost, connectPort); + + // limit the socket read buffer size. we will read everything into + // the QHttpNetworkReply anyway, so let's grow only that and not + // here and there. + socket->setReadBufferSize(64*1024); + } } return false; } @@ -593,6 +631,7 @@ bool QHttpNetworkConnectionChannel::expand(bool dataComplete) void QHttpNetworkConnectionChannel::allDone() { + eatWhitespace(); // FIXME HACK! #ifndef QT_NO_COMPRESS // expand the whole data. if (reply->d_func()->expectContent() && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd) @@ -789,7 +828,7 @@ bool QHttpNetworkConnectionChannel::resetUploadData() } -void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair) +void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair) { // this is only called for simple GET @@ -802,16 +841,32 @@ void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair) reply->d_func()->pipeliningUsed = true; #ifndef QT_NO_NETWORKPROXY - QByteArray header = QHttpNetworkRequestPrivate::header(request, - (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)); + pipeline.append(QHttpNetworkRequestPrivate::header(request, + (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy))); #else - QByteArray header = QHttpNetworkRequestPrivate::header(request, false); + pipeline.append(QHttpNetworkRequestPrivate::header(request, false)); #endif - socket->write(header); alreadyPipelinedRequests.append(pair); + + // pipelineFlush() needs to be called at some point afterwards } +void QHttpNetworkConnectionChannel::pipelineFlush() +{ + if (pipeline.isEmpty()) + return; + + // The goal of this is so that we have everything in one TCP packet. + // For the Unbuffered QTcpSocket this is manually needed, the buffered + // QTcpSocket does it automatically. + // Also, sometimes the OS does it for us (Nagle's algorithm) but that + // happens only sometimes. + socket->write(pipeline); + pipeline.clear(); +} + + void QHttpNetworkConnectionChannel::closeAndResendCurrentRequest() { requeueCurrentlyPipelinedRequests(); @@ -844,6 +899,17 @@ bool QHttpNetworkConnectionChannel::isSocketReading() const //private slots void QHttpNetworkConnectionChannel::_q_readyRead() { + // We got a readyRead but no bytes are available.. + // This happens for the Unbuffered QTcpSocket + if (socket->bytesAvailable() == 0) { + char c; + qint64 ret = socket->peek(&c, 1); + if (ret < 0) { + socket->disconnectFromHost(); + return; + } + } + if (isSocketWaiting() || isSocketReading()) { state = QHttpNetworkConnectionChannel::ReadingState; if (reply) diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index 41a896d..e384e9d 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -99,6 +99,7 @@ public: BusyState = (ConnectingState|WritingState|WaitingState|ReadingState|Wait4AuthState) }; QAbstractSocket *socket; + bool ssl; ChannelState state; QHttpNetworkRequest request; // current request QHttpNetworkReply *reply; // current reply for this request @@ -125,7 +126,11 @@ public: }; PipeliningSupport pipeliningSupported; QList alreadyPipelinedRequests; - + QByteArray pipeline; // temporary buffer that gets sent to socket in pipelineFlush + void pipelineInto(HttpMessagePair &pair); + void pipelineFlush(); + void requeueCurrentlyPipelinedRequests(); + void detectPipeliningSupport(); QHttpNetworkConnectionChannel(); @@ -145,10 +150,6 @@ public: bool resetUploadData(); // return true if resetting worked or there is no upload data - void pipelineInto(HttpMessagePair &pair); - void requeueCurrentlyPipelinedRequests(); - void detectPipeliningSupport(); - void handleUnexpectedEOF(); void closeAndResendCurrentRequest(); diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp index 108ba8a..1c55482 100644 --- a/src/network/access/qhttpnetworkreply.cpp +++ b/src/network/access/qhttpnetworkreply.cpp @@ -119,6 +119,7 @@ void QHttpNetworkReply::setRequest(const QHttpNetworkRequest &request) { Q_D(QHttpNetworkReply); d->request = request; + d->ssl = request.isSsl(); } int QHttpNetworkReply::statusCode() const @@ -195,6 +196,25 @@ void QHttpNetworkReply::setDownstreamLimited(bool dsl) d->connection->d_func()->readMoreLater(this); } +bool QHttpNetworkReply::supportsUserProvidedDownloadBuffer() +{ + Q_D(QHttpNetworkReply); + return (!d->isChunked() && !d->autoDecompress && d->bodyLength > 0); +} + +void QHttpNetworkReply::setUserProvidedDownloadBuffer(char* b) +{ + Q_D(QHttpNetworkReply); + if (supportsUserProvidedDownloadBuffer()) + d->userProvidedDownloadBuffer = b; +} + +char* QHttpNetworkReply::userProvidedDownloadBuffer() +{ + Q_D(QHttpNetworkReply); + return d->userProvidedDownloadBuffer; +} + bool QHttpNetworkReply::isFinished() const { return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState; @@ -207,7 +227,10 @@ bool QHttpNetworkReply::isPipeliningUsed() const QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) - : QHttpNetworkHeaderPrivate(newUrl), state(NothingDoneState), statusCode(100), + : QHttpNetworkHeaderPrivate(newUrl) + , state(NothingDoneState) + , ssl(false) + , statusCode(100), majorVersion(0), minorVersion(0), bodyLength(0), contentRead(0), totalProgress(0), chunkedTransferEncoding(false), connectionCloseEnabled(true), @@ -215,6 +238,7 @@ QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) currentChunkSize(0), currentChunkRead(0), connection(0), initInflate(false), autoDecompress(false), responseData(), requestIsPrepared(false) ,pipeliningUsed(false), downstreamLimited(false) + ,userProvidedDownloadBuffer(0) { } @@ -448,6 +472,8 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) return -1; // unexpected EOF else if (haveRead == 0) break; // read more later + else if (haveRead == 1 && bytes == 0 && (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31)) + continue; // Ignore all whitespace that was trailing froma previous request on that socket bytes++; @@ -623,12 +649,32 @@ bool QHttpNetworkReplyPrivate::isConnectionCloseEnabled() // note this function can only be used for non-chunked, non-compressed with // known content length +qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QAbstractSocket *socket, char *b) +{ + // This first read is to flush the buffer inside the socket + qint64 haveRead = 0; + haveRead = socket->read(b, bodyLength - contentRead); + if (haveRead == -1) { + return 0; // ### error checking here; + } + contentRead += haveRead; + + if (contentRead == bodyLength) { + state = AllDoneState; + } + + return haveRead; +} + +// note this function can only be used for non-chunked, non-compressed with +// known content length qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb) { + qint64 toBeRead = qMin(socket->bytesAvailable(), bodyLength - contentRead); QByteArray bd; bd.resize(toBeRead); - qint64 haveRead = socket->read(bd.data(), bd.size()); + qint64 haveRead = socket->read(bd.data(), toBeRead); if (haveRead == -1) { bd.clear(); return 0; // ### error checking here; @@ -650,29 +696,34 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff { qint64 bytes = 0; if (isChunked()) { - bytes += readReplyBodyChunked(socket, out); // chunked transfer encoding (rfc 2616, sec 3.6) - } else if (bodyLength > 0) { // we have a Content-Length + // chunked transfer encoding (rfc 2616, sec 3.6) + bytes += readReplyBodyChunked(socket, out); + } else if (bodyLength > 0) { + // we have a Content-Length bytes += readReplyBodyRaw(socket, out, bodyLength - contentRead); if (contentRead + bytes == bodyLength) state = AllDoneState; } else { + // no content length. just read what's possible bytes += readReplyBodyRaw(socket, out, socket->bytesAvailable()); } contentRead += bytes; return bytes; } -qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QIODevice *in, QByteDataBuffer *out, qint64 size) +qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QAbstractSocket *socket, QByteDataBuffer *out, qint64 size) { + // FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable() qint64 bytes = 0; - Q_ASSERT(in); + Q_ASSERT(socket); Q_ASSERT(out); - int toBeRead = qMin(128*1024, qMin(size, in->bytesAvailable())); + int toBeRead = qMin(128*1024, qMin(size, socket->bytesAvailable())); + while (toBeRead > 0) { QByteArray byteData; byteData.resize(toBeRead); - qint64 haveRead = in->read(byteData.data(), byteData.size()); + qint64 haveRead = socket->read(byteData.data(), byteData.size()); if (haveRead <= 0) { // ### error checking here byteData.clear(); @@ -684,25 +735,35 @@ qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QIODevice *in, QByteDataBuffer bytes += haveRead; size -= haveRead; - toBeRead = qMin(128*1024, qMin(size, in->bytesAvailable())); + toBeRead = qMin(128*1024, qMin(size, socket->bytesAvailable())); } return bytes; } -qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QIODevice *in, QByteDataBuffer *out) +qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QAbstractSocket *socket, QByteDataBuffer *out) { qint64 bytes = 0; - while (in->bytesAvailable()) { // while we can read from input - // if we are done with the current chunk, get the size of the new chunk + while (socket->bytesAvailable()) { if (currentChunkRead >= currentChunkSize) { + // For the first chunk and when we're done with a chunk currentChunkSize = 0; currentChunkRead = 0; if (bytes) { + // After a chunk char crlf[2]; - bytes += in->read(crlf, 2); // read the "\r\n" after the chunk + // read the "\r\n" after the chunk + qint64 haveRead = socket->read(crlf, 2); + // FIXME: This code is slightly broken and not optimal. What if the 2 bytes are not available yet?! + // For nice reasons (the toLong in getChunkSize accepting \n at the beginning + // it right now still works, but we should definitely fix this. + + if (haveRead != 2) + return bytes; // FIXME + bytes += haveRead; } - bytes += getChunkSize(in, ¤tChunkSize); + // Note that chunk size gets stored in currentChunkSize, what is returned is the bytes read + bytes += getChunkSize(socket, ¤tChunkSize); if (currentChunkSize == -1) break; } @@ -712,8 +773,8 @@ qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QIODevice *in, QByteDataBu break; } - // otherwise, try to read what is missing for this chunk - qint64 haveRead = readReplyBodyRaw (in, out, currentChunkSize - currentChunkRead); + // otherwise, try to begin reading this chunk / to read what is missing for this chunk + qint64 haveRead = readReplyBodyRaw (socket, out, currentChunkSize - currentChunkRead); currentChunkRead += haveRead; bytes += haveRead; @@ -723,22 +784,25 @@ qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QIODevice *in, QByteDataBu return bytes; } -qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *in, qint64 *chunkSize) +qint64 QHttpNetworkReplyPrivate::getChunkSize(QAbstractSocket *socket, qint64 *chunkSize) { qint64 bytes = 0; char crlf[2]; *chunkSize = -1; - int bytesAvailable = in->bytesAvailable(); + + int bytesAvailable = socket->bytesAvailable(); + // FIXME rewrite to permanent loop without bytesAvailable while (bytesAvailable > bytes) { - qint64 sniffedBytes = in->peek(crlf, 2); + qint64 sniffedBytes = socket->peek(crlf, 2); int fragmentSize = fragment.size(); + // check the next two bytes for a "\r\n", skip blank lines if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n') ||(fragmentSize > 1 && fragment.endsWith('\r') && crlf[0] == '\n')) { - bytes += in->read(crlf, 1); // read the \r or \n + bytes += socket->read(crlf, 1); // read the \r or \n if (crlf[0] == '\r') - bytes += in->read(crlf, 1); // read the \n + bytes += socket->read(crlf, 1); // read the \n bool ok = false; // ignore the chunk-extension fragment = fragment.mid(0, fragment.indexOf(';')).trimmed(); @@ -748,10 +812,15 @@ qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *in, qint64 *chunkSize) } else { // read the fragment to the buffer char c = 0; - bytes += in->read(&c, 1); + qint64 haveRead = socket->read(&c, 1); + if (haveRead < 0) { + return -1; // FIXME + } + bytes += haveRead; fragment.append(c); } } + return bytes; } diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h index 4011c78..02ce248 100644 --- a/src/network/access/qhttpnetworkreply_p.h +++ b/src/network/access/qhttpnetworkreply_p.h @@ -128,6 +128,10 @@ public: QByteArray readAny(); void setDownstreamLimited(bool t); + bool supportsUserProvidedDownloadBuffer(); + void setUserProvidedDownloadBuffer(char*); + char* userProvidedDownloadBuffer(); + bool isFinished() const; bool isPipeliningUsed() const; @@ -147,6 +151,7 @@ Q_SIGNALS: void finished(); void finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail = QString()); void headerChanged(); + // FIXME we need to change this to qint64! void dataReadProgress(int done, int total); void dataSendProgress(qint64 done, qint64 total); @@ -168,15 +173,16 @@ public: qint64 readHeader(QAbstractSocket *socket); void parseHeader(const QByteArray &header); qint64 readBody(QAbstractSocket *socket, QByteDataBuffer *out); + qint64 readBodyVeryFast(QAbstractSocket *socket, char *b); qint64 readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb); bool findChallenge(bool forProxy, QByteArray &challenge) const; QAuthenticatorPrivate::Method authenticationMethod(bool isProxy) const; void clear(); void clearHttpLayerInformation(); - qint64 readReplyBodyRaw(QIODevice *in, QByteDataBuffer *out, qint64 size); - qint64 readReplyBodyChunked(QIODevice *in, QByteDataBuffer *out); - qint64 getChunkSize(QIODevice *in, qint64 *chunkSize); + qint64 readReplyBodyRaw(QAbstractSocket *in, QByteDataBuffer *out, qint64 size); + qint64 readReplyBodyChunked(QAbstractSocket *in, QByteDataBuffer *out); + qint64 getChunkSize(QAbstractSocket *in, qint64 *chunkSize); void appendUncompressedReplyData(QByteArray &qba); void appendUncompressedReplyData(QByteDataBuffer &data); @@ -205,6 +211,7 @@ public: } state; QHttpNetworkRequest request; + bool ssl; int statusCode; int majorVersion; int minorVersion; @@ -234,6 +241,8 @@ public: bool pipeliningUsed; bool downstreamLimited; + + char* userProvidedDownloadBuffer; }; diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index 639025e..d2f3212 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -63,6 +63,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest pipeliningAllowed = other.pipeliningAllowed; customVerb = other.customVerb; withCredentials = other.withCredentials; + ssl = other.ssl; } QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate() @@ -73,6 +74,7 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot { return QHttpNetworkHeaderPrivate::operator==(other) && (operation == other.operation) + && (ssl == other.ssl) && (uploadByteDevice == other.uploadByteDevice); } @@ -199,6 +201,15 @@ void QHttpNetworkRequest::setUrl(const QUrl &url) d->url = url; } +bool QHttpNetworkRequest::isSsl() const +{ + return d->ssl; +} +void QHttpNetworkRequest::setSsl(bool s) +{ + d->ssl = s; +} + qint64 QHttpNetworkRequest::contentLength() const { return d->contentLength(); diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index 15cab73..123babc 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -116,6 +116,9 @@ public: bool withCredentials() const; void setWithCredentials(bool b); + bool isSsl() const; + void setSsl(bool); + void setUploadByteDevice(QNonContiguousByteDevice *bd); QNonContiguousByteDevice* uploadByteDevice() const; @@ -146,6 +149,7 @@ public: bool autoDecompress; bool pipeliningAllowed; bool withCredentials; + bool ssl; }; -- cgit v0.12 From 26af575213763c8202f1a142f42257d473076529 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:20:14 +0200 Subject: QNAM Zerocopy: QNAM implementation part Reviewed-by: Peter Hartmann Reviewed-by: Jeremy Katz Reviewed-by: Aleksandar Sasha Babic --- src/network/access/qnetworkaccessbackend.cpp | 11 ++++ src/network/access/qnetworkaccessbackend_p.h | 4 ++ src/network/access/qnetworkaccesshttpbackend.cpp | 33 +++++++++- src/network/access/qnetworkaccesshttpbackend_p.h | 1 + src/network/access/qnetworkreplyimpl.cpp | 80 ++++++++++++++++++++++++ src/network/access/qnetworkreplyimpl_p.h | 10 +++ 6 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp index 60f7dc6..45495f7 100644 --- a/src/network/access/qnetworkaccessbackend.cpp +++ b/src/network/access/qnetworkaccessbackend.cpp @@ -252,6 +252,17 @@ void QNetworkAccessBackend::writeDownstreamData(QIODevice *data) reply->appendDownstreamData(data); } +// not actually appending data, it was already written to the user buffer +void QNetworkAccessBackend::writeDownstreamDataDownloadBuffer(qint64 bytesReceived, qint64 bytesTotal) +{ + reply->appendDownstreamDataDownloadBuffer(bytesReceived, bytesTotal); +} + +char* QNetworkAccessBackend::getDownloadBuffer(qint64 size) +{ + return reply->getDownloadBuffer(size); +} + QVariant QNetworkAccessBackend::header(QNetworkRequest::KnownHeaders header) const { return reply->q_func()->header(header); diff --git a/src/network/access/qnetworkaccessbackend_p.h b/src/network/access/qnetworkaccessbackend_p.h index 4fe6de6..9f8a01f 100644 --- a/src/network/access/qnetworkaccessbackend_p.h +++ b/src/network/access/qnetworkaccessbackend_p.h @@ -177,6 +177,10 @@ protected: qint64 nextDownstreamBlockSize() const; void writeDownstreamData(QByteDataBuffer &list); + // not actually appending data, it was already written to the user buffer + void writeDownstreamDataDownloadBuffer(qint64, qint64); + char* getDownloadBuffer(qint64); + public slots: // for task 251801, needs to be a slot to be called asynchronously void writeDownstreamData(QIODevice *data); diff --git a/src/network/access/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp index f617244..8b9a99f 100644 --- a/src/network/access/qnetworkaccesshttpbackend.cpp +++ b/src/network/access/qnetworkaccesshttpbackend.cpp @@ -542,6 +542,9 @@ void QNetworkAccessHttpBackend::postRequest() break; // can't happen } + bool encrypt = (url().scheme().toLower() == QLatin1String("https")); + httpRequest.setSsl(encrypt); + httpRequest.setUrl(url()); QList headers = request().rawHeaderList(); @@ -595,7 +598,6 @@ void QNetworkAccessHttpBackend::postRequest() httpReply->ignoreSslErrors(pendingIgnoreSslErrorsList); #endif - connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead())); connect(httpReply, SIGNAL(finished()), SLOT(replyFinished())); connect(httpReply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)), SLOT(httpError(QNetworkReply::NetworkError,QString))); @@ -859,9 +861,33 @@ void QNetworkAccessHttpBackend::replyHeaderChanged() if (!isCachingEnabled()) setCachingEnabled(true); } + + // Check if a download buffer is supported from the HTTP reply + char *buf = 0; + if (httpReply->supportsUserProvidedDownloadBuffer()) { + // Check if a download buffer is supported by the user + buf = getDownloadBuffer(httpReply->contentLength()); + if (buf) { + httpReply->setUserProvidedDownloadBuffer(buf); + // If there is a download buffer we react on the progress signal + connect(httpReply, SIGNAL(dataReadProgress(int,int)), SLOT(replyDownloadProgressSlot(int,int))); + } + } + + // If there is no buffer, we react on the readyRead signal + if (!buf) { + connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead())); + } + metaDataChanged(); } +void QNetworkAccessHttpBackend::replyDownloadProgressSlot(int received, int total) +{ + // we can be sure here that there is a download buffer + writeDownstreamDataDownloadBuffer(received, total); +} + void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &, QAuthenticator *auth) { @@ -1169,6 +1195,11 @@ bool QNetworkAccessHttpBackend::canResume() const return false; } + // If we're using a download buffer then we don't support resuming/migration + // right now. Too much trouble. + if (httpReply->userProvidedDownloadBuffer()) + return false; + return true; } diff --git a/src/network/access/qnetworkaccesshttpbackend_p.h b/src/network/access/qnetworkaccesshttpbackend_p.h index c4c88ae..fb12781 100644 --- a/src/network/access/qnetworkaccesshttpbackend_p.h +++ b/src/network/access/qnetworkaccesshttpbackend_p.h @@ -104,6 +104,7 @@ private slots: void replyReadyRead(); void replyFinished(); void replyHeaderChanged(); + void replyDownloadProgressSlot(int,int); void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth); void httpCacheCredentials(const QHttpNetworkRequest &request, QAuthenticator *auth); void httpError(QNetworkReply::NetworkError error, const QString &errorString); diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp index 3798ac2..c8808af 100644 --- a/src/network/access/qnetworkreplyimpl.cpp +++ b/src/network/access/qnetworkreplyimpl.cpp @@ -49,11 +49,15 @@ #include "QtNetwork/qnetworksession.h" #include "qnetworkaccesshttpbackend_p.h" #include "qnetworkaccessmanager_p.h" +#include #include QT_BEGIN_NAMESPACE +typedef QSharedPointer > QVarLengthArraySharedPointer; +Q_DECLARE_METATYPE(QVarLengthArraySharedPointer) + inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate() : backend(0), outgoingData(0), outgoingDataBuffer(0), copyDevice(0), @@ -62,6 +66,8 @@ inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate() bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), preMigrationDownloaded(-1), httpStatusCode(0), state(Idle) + , downloadBuffer(0) + , downloadBufferPosition(0) { } @@ -125,6 +131,10 @@ void QNetworkReplyImplPrivate::_q_copyReadyRead() if (!copyDevice || !q->isOpen()) return; + // FIXME Optimize to use download buffer if it is a QBuffer. + // Needs to be done where sendCacheContents() (?) of HTTP is emitting + // metaDataChanged ? + forever { qint64 bytesToRead = nextDownstreamBlockSize(); if (bytesToRead == 0) @@ -590,6 +600,55 @@ void QNetworkReplyImplPrivate::appendDownstreamData(const QByteArray &data) qFatal("QNetworkReplyImplPrivate::appendDownstreamData not implemented"); } +char* QNetworkReplyImplPrivate::getDownloadBuffer(qint64 size) +{ + Q_Q(QNetworkReplyImpl); + + // Check attribute() if allocating a buffer of that size can be allowed + if (!downloadBuffer) { + QVariant bufferAllocationPolicy = request.attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute); + if (bufferAllocationPolicy.isValid() && bufferAllocationPolicy.toLongLong() >= size) { + downloadBufferArray = QSharedPointer >(new QVarLengthArray()); + downloadBufferArray->reserve(size); + + downloadBuffer = downloadBufferArray->data(); + + q->setAttribute(QNetworkRequest::DownloadBufferAttribute, qVariantFromValue > > (downloadBufferArray)); + } + } + + return downloadBuffer; +} + +void QNetworkReplyImplPrivate::appendDownstreamDataDownloadBuffer(qint64 bytesReceived, qint64 bytesTotal) +{ + Q_Q(QNetworkReplyImpl); + if (!q->isOpen()) + return; + + if (cacheEnabled && !cacheSaveDevice) + initCacheSaveDevice(); + + if (cacheSaveDevice && bytesReceived == bytesTotal) { +// if (lastBytesDownloaded == -1) +// lastBytesDownloaded = 0; +// cacheSaveDevice->write(downloadBuffer + lastBytesDownloaded, bytesReceived - lastBytesDownloaded); + + // Write everything in one go if we use a download buffer. might be more performant. + cacheSaveDevice->write(downloadBuffer, bytesTotal); + } + + bytesDownloaded = bytesReceived; + lastBytesDownloaded = bytesReceived; + + // Update the array so our user (e.g. QtWebKit) knows the real size + if (bytesReceived > 0) + downloadBufferArray->resize(bytesReceived); + + emit q->downloadProgress(bytesDownloaded, bytesTotal); + emit q->readyRead(); +} + void QNetworkReplyImplPrivate::finished() { Q_Q(QNetworkReplyImpl); @@ -784,6 +843,13 @@ bool QNetworkReplyImpl::canReadLine () const */ qint64 QNetworkReplyImpl::bytesAvailable() const { + // Special case for the "zero copy" download buffer + Q_D(const QNetworkReplyImpl); + if (d->downloadBuffer) { + qint64 maxAvail = d->downloadBufferArray->size() - d->downloadBufferPosition; + return QNetworkReply::bytesAvailable() + maxAvail; + } + return QNetworkReply::bytesAvailable() + d_func()->readBuffer.byteAmount(); } @@ -838,8 +904,22 @@ void QNetworkReplyImpl::ignoreSslErrorsImplementation(const QList &er qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen) { Q_D(QNetworkReplyImpl); + + // Special case code if we have the "zero copy" download buffer + if (d->downloadBuffer) { + qint64 maxAvail = qMin(d->downloadBufferArray->size() - d->downloadBufferPosition, maxlen); + if (maxAvail == 0) + return d->state == QNetworkReplyImplPrivate::Finished ? -1 : 0; + // FIXME what about "Aborted" state? + qMemCopy(data, d->downloadBuffer + d->downloadBufferPosition, maxAvail); + d->downloadBufferPosition += maxAvail; + return maxAvail; + } + + if (d->readBuffer.isEmpty()) return d->state == QNetworkReplyImplPrivate::Finished ? -1 : 0; + // FIXME what about "Aborted" state? d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite); if (maxlen == 1) { diff --git a/src/network/access/qnetworkreplyimpl_p.h b/src/network/access/qnetworkreplyimpl_p.h index 38084bd..ab11ebe 100644 --- a/src/network/access/qnetworkreplyimpl_p.h +++ b/src/network/access/qnetworkreplyimpl_p.h @@ -62,6 +62,7 @@ #include "QtCore/qbuffer.h" #include "private/qringbuffer_p.h" #include "private/qbytedata_p.h" +#include QT_BEGIN_NAMESPACE @@ -163,6 +164,9 @@ public: void appendDownstreamData(QIODevice *data); void appendDownstreamData(const QByteArray &data); + char* getDownloadBuffer(qint64 size); + void appendDownstreamDataDownloadBuffer(qint64, qint64); + void finished(); void error(QNetworkReply::NetworkError code, const QString &errorString); void metaDataChanged(); @@ -191,6 +195,7 @@ public: QList proxyList; #endif + // Used for normal downloading. For "zero copy" the downloadBuffer is used QByteDataBuffer readBuffer; qint64 bytesDownloaded; qint64 lastBytesDownloaded; @@ -202,6 +207,11 @@ public: State state; + // only used when the "zero copy" style is used. Else readBuffer is used. + QSharedPointer< QVarLengthArray > downloadBufferArray; + char* downloadBuffer; + qint64 downloadBufferPosition; + Q_DECLARE_PUBLIC(QNetworkReplyImpl) }; -- cgit v0.12 From aa669904ff869a8b4f65a6e5cb87a8e85051a47e Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:21:16 +0200 Subject: tst_qhttpnetworkconnection: Pipelining auto test --- .../tst_qhttpnetworkconnection.cpp | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/auto/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp b/tests/auto/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp index 89f608e..188ed29 100644 --- a/tests/auto/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp +++ b/tests/auto/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp @@ -110,6 +110,8 @@ private Q_SLOTS: void getEmptyWithPipelining(); + void getAndEverythingShouldBePipelined(); + void getAndThenDeleteObject(); void getAndThenDeleteObject_data(); }; @@ -1036,6 +1038,52 @@ void tst_QHttpNetworkConnection::getEmptyWithPipelining() qDeleteAll(replies); } +class GetAndEverythingShouldBePipelinedReceiver : public QObject +{ + Q_OBJECT +public: + int receivedCount; + int requestCount; + GetAndEverythingShouldBePipelinedReceiver(int rq) : receivedCount(0),requestCount(rq) { } +public Q_SLOTS: + void finishedSlot() { + QHttpNetworkReply *reply = (QHttpNetworkReply*) sender(); + receivedCount++; + + if (receivedCount == requestCount) + QTestEventLoop::instance().exitLoop(); + } +}; + +void tst_QHttpNetworkConnection::getAndEverythingShouldBePipelined() +{ + quint16 requestCount = 100; + // use 1 connection. + QHttpNetworkConnection connection(1, QtNetworkSettings::serverName()); + QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt"); + QList requests; + QList replies; + + GetAndEverythingShouldBePipelinedReceiver receiver(requestCount); + + for (int i = 0; i < requestCount; i++) { + QHttpNetworkRequest *request = 0; + request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get); + request->setPipeliningAllowed(true); + requests.append(request); + QHttpNetworkReply *reply = connection.sendRequest(*request); + connect(reply, SIGNAL(finished()), &receiver, SLOT(finishedSlot())); + replies.append(reply); + } + QTestEventLoop::instance().enterLoop(40); + QVERIFY(!QTestEventLoop::instance().timeout()); + + qDeleteAll(requests); + qDeleteAll(replies); + +} + + void tst_QHttpNetworkConnection::getAndThenDeleteObject_data() { QTest::addColumn("replyFirst"); -- cgit v0.12 From 690598adbcb76b07dc791ab7ee5e3513ddd4096e Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:22:29 +0200 Subject: tst_qnetworkreply: Zerocopy auto test --- tests/auto/qnetworkreply/tst_qnetworkreply.cpp | 96 +++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp index 5af8034..bb91965 100644 --- a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp @@ -75,7 +75,8 @@ #include "../network-settings.h" - +typedef QSharedPointer > QVarLengthArraySharedPointer; +Q_DECLARE_METATYPE(QVarLengthArraySharedPointer) Q_DECLARE_METATYPE(QNetworkReply*) Q_DECLARE_METATYPE(QAuthenticator*) Q_DECLARE_METATYPE(QNetworkProxy) @@ -289,6 +290,13 @@ private Q_SLOTS: void symbianOpenCDataUrlCrash(); + void getFromHttpIntoBuffer_data(); + void getFromHttpIntoBuffer(); + + void ioGetFromHttpWithoutContentLength(); + + void ioGetFromHttpBrokenChunkedEncoding(); + // NOTE: This test must be last! void parentingRepliesToTheApp(); }; @@ -3936,6 +3944,7 @@ void tst_QNetworkReply::authorizationError() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), httpStatusCode); QFETCH(QString, httpBody); + QCOMPARE(qint64(reply->size()), qint64(httpBody.size())); QCOMPARE(QString(reply->readAll()), httpBody); } @@ -4280,6 +4289,91 @@ void tst_QNetworkReply::symbianOpenCDataUrlCrash() QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), qint64(598)); } +void tst_QNetworkReply::getFromHttpIntoBuffer_data() +{ + QTest::addColumn("url"); + + QTest::newRow("rfc-internal") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt"); +} + +void tst_QNetworkReply::getFromHttpIntoBuffer() +{ + QFETCH(QUrl, url); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute, 1024*128); // 128 kB + + QNetworkAccessManager manager; + QNetworkReply *reply = manager.get(request); + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + QVERIFY(reply->isFinished()); + + QFile reference(SRCDIR "/rfc3252.txt"); + QVERIFY(reference.open(QIODevice::ReadOnly)); + + QCOMPARE(reference.bytesAvailable(), reply->bytesAvailable()); + QCOMPARE(reference.size(), reply->size()); + + // Compare the memory buffer + QVariant downloadBufferAttribute = reply->attribute(QNetworkRequest::DownloadBufferAttribute); + bool memoryComparison = + (0 == memcmp(static_cast(reference.readAll().data()), + downloadBufferAttribute.value > >()->constData(), reference.size())); + QVERIFY(memoryComparison); + + // Make sure the normal reading works + reference.seek(0); + QCOMPARE(reply->read(42), reference.read(42)); + QCOMPARE(reply->getChar(0), reference.getChar(0)); + QCOMPARE(reply->peek(23), reference.peek(23)); + QCOMPARE(reply->readLine(), reference.readLine()); + QCOMPARE(reference.bytesAvailable(), reply->bytesAvailable()); + QCOMPARE(reply->readAll(), reference.readAll()); + QVERIFY(reply->atEnd()); +} + +// Is handled somewhere else too, introduced this special test to have it more accessible +void tst_QNetworkReply::ioGetFromHttpWithoutContentLength() +{ + QByteArray dataToSend("HTTP/1.0 200 OK\r\n\r\nHALLO! 123!"); + MiniHttpServer server(dataToSend); + server.doClose = true; + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(reply->url(), request.url()); + QVERIFY(reply->isFinished()); + QVERIFY(reply->error() == QNetworkReply::NoError); +} + +// Is handled somewhere else too, introduced this special test to have it more accessible +void tst_QNetworkReply::ioGetFromHttpBrokenChunkedEncoding() +{ + // This is wrong chunked encoding because of the X. What actually has to follow is \r\n + // and then the declaration of the final 0 chunk + QByteArray dataToSend("HTTP/1.0 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nABCX"); + MiniHttpServer server(dataToSend); + server.doClose = false; // FIXME + + QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort()))); + QNetworkReplyPtr reply = manager.get(request); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + QTestEventLoop::instance().enterLoop(10); + + QEXPECT_FAIL(0, "We should close the socket and not just do nothing", Continue); + QVERIFY(!QTestEventLoop::instance().timeout()); + QEXPECT_FAIL(0, "We should close the socket and not just do nothing", Continue); + QVERIFY(reply->isFinished()); + QCOMPARE(reply->error(), QNetworkReply::NoError); +} // NOTE: This test must be last testcase in tst_qnetworkreply! -- cgit v0.12 From f28eb5e37c726ecd44bbbf1a5ad746570d2cac9b Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:23:04 +0200 Subject: tst_qiodevice: Add peek()ing auto test --- tests/auto/qiodevice/tst_qiodevice.cpp | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/auto/qiodevice/tst_qiodevice.cpp b/tests/auto/qiodevice/tst_qiodevice.cpp index 7048754..0226402 100644 --- a/tests/auto/qiodevice/tst_qiodevice.cpp +++ b/tests/auto/qiodevice/tst_qiodevice.cpp @@ -81,6 +81,8 @@ private slots: void readLine2_data(); void readLine2(); + + void peekBug(); }; // Testing get/set functions @@ -591,5 +593,62 @@ void tst_QIODevice::readLine2() } } + +class PeekBug : public QIODevice { + Q_OBJECT +public: + char alphabet[27]; + qint64 counter; + PeekBug() : QIODevice(), counter(0) { + memcpy(alphabet,"abcdefghijklmnopqrstuvqxyz",27); + }; + qint64 readData(char *data, qint64 maxlen) { + qint64 pos = 0; + while (pos < maxlen) { + *(data + pos) = alphabet[counter]; + pos++; + counter++; + if (counter == 26) + counter = 0; + } + return maxlen; + } + qint64 writeData(const char *data, qint64 maxlen) { + return -1; + } + +}; + +// This is a testcase for the bug fixed with bd287865 +void tst_QIODevice::peekBug() +{ + PeekBug peekBug; + peekBug.open(QIODevice::ReadOnly | QIODevice::Unbuffered); + + char onetwo[2]; + peekBug.peek(onetwo, 2); + QCOMPARE(onetwo[0], 'a'); + QCOMPARE(onetwo[1], 'b'); + + peekBug.read(onetwo, 1); + QCOMPARE(onetwo[0], 'a'); + + peekBug.peek(onetwo, 2); + QCOMPARE(onetwo[0], 'b'); + QCOMPARE(onetwo[1], 'c'); + + peekBug.read(onetwo, 1); + QCOMPARE(onetwo[0], 'b'); + peekBug.read(onetwo, 1); + QCOMPARE(onetwo[0], 'c'); + peekBug.read(onetwo, 1); + QCOMPARE(onetwo[0], 'd'); + + peekBug.peek(onetwo, 2); + QCOMPARE(onetwo[0], 'e'); + QCOMPARE(onetwo[1], 'f'); + +} + QTEST_MAIN(tst_QIODevice) #include "tst_qiodevice.moc" -- cgit v0.12 From d70d5981c5f07d938c3fbddf3bafc486ef797251 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:24:10 +0200 Subject: tst_qbytearray: Auto test for reserve() This auto test tests undocumented but expected/intuitive behaviour. --- tests/auto/qbytearray/tst_qbytearray.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/auto/qbytearray/tst_qbytearray.cpp b/tests/auto/qbytearray/tst_qbytearray.cpp index 07fdbc3..e3341d1 100644 --- a/tests/auto/qbytearray/tst_qbytearray.cpp +++ b/tests/auto/qbytearray/tst_qbytearray.cpp @@ -142,6 +142,8 @@ private slots: void repeated_data() const; void byteRefDetaching() const; + + void reserve(); }; tst_QByteArray::tst_QByteArray() @@ -1516,6 +1518,22 @@ void tst_QByteArray::byteRefDetaching() const } } +void tst_QByteArray::reserve() +{ + int capacity = 100; + QByteArray qba; + qba.reserve(capacity); + QVERIFY(qba.capacity() == capacity); + char *data = qba.data(); + + // FIXME count from 0 to make it fail + for (int i = 1; i < capacity; i++) { + qba.resize(i); + QVERIFY(capacity == qba.capacity()); + QVERIFY(data == qba.data()); + } +} + const char globalChar = '1'; QTEST_APPLESS_MAIN(tst_QByteArray) -- cgit v0.12 From f08e07eb3f3beb2d0607272d645b23a310a67f8f Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:25:45 +0200 Subject: QNAM Zerocopy: Benchmark --- .../access/qnetworkreply/tst_qnetworkreply.cpp | 72 +++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp index 90ae153..0098d8e 100644 --- a/tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -51,6 +51,9 @@ #include "../../../../auto/network-settings.h" +typedef QSharedPointer > QVarLengthArraySharedPointer; +Q_DECLARE_METATYPE(QVarLengthArraySharedPointer) + class TimedSender: public QThread { Q_OBJECT @@ -465,7 +468,8 @@ private slots: void httpUploadPerformance(); void httpDownloadPerformance_data(); void httpDownloadPerformance(); - + void httpDownloadPerformanceDownloadBuffer_data(); + void httpDownloadPerformanceDownloadBuffer(); }; void tst_qnetworkreply::httpLatency() @@ -649,6 +653,72 @@ void tst_qnetworkreply::httpDownloadPerformance() qint64 elapsed = time.elapsed(); qDebug() << "tst_QNetworkReply::httpDownloadPerformance" << elapsed << "msec, " << ((UploadSize/1024.0)/(elapsed/1000.0)) << " kB/sec"; +}; + +class HttpDownloadPerformanceClientDownloadBuffer : QObject { + Q_OBJECT +private: + bool useDownloadBuffer; + QNetworkReply *reply; + qint64 uploadSize; +public: + HttpDownloadPerformanceClientDownloadBuffer (QNetworkReply *reply, bool useDownloadBuffer, qint64 uploadSize) + : useDownloadBuffer(useDownloadBuffer), reply(reply), uploadSize(uploadSize) + { + connect(reply, SIGNAL(finished()), this, SLOT(finishedSlot())); + } + + public slots: + void finishedSlot() { + if (useDownloadBuffer) { + QVariant downloadBufferAttribute = reply->attribute(QNetworkRequest::DownloadBufferAttribute); + QSharedPointer > data = downloadBufferAttribute.value > >(); + } else { + // We did not have a download buffer but we still need to benchmark having the data, e.g. reading it all. + char* replyData = (char*) qMalloc(uploadSize); + QVERIFY(reply->read(replyData, uploadSize) == uploadSize); + qFree(replyData); + } + } +}; + +void tst_qnetworkreply::httpDownloadPerformanceDownloadBuffer_data() +{ + QTest::addColumn("useDownloadBuffer"); + + QTest::newRow("use-download-buffer") << true; + QTest::newRow("do-not-use-download-buffer") << false; +} + +void tst_qnetworkreply::httpDownloadPerformanceDownloadBuffer() +{ + QFETCH(bool, useDownloadBuffer); + + // On my Linux Desktop the results are already visible with 128 kB, however we use this to have good results. +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_WINCE_WM) + // Show some mercy to non-desktop platform/s + enum {UploadSize = 4*1024*1024}; // 4 MB +#else + enum {UploadSize = 32*1024*1024}; // 32 MB +#endif + + HttpDownloadPerformanceServer server(UploadSize, true, false); + + QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(server.serverPort()) + "/?bare=1")); + if (useDownloadBuffer) + request.setAttribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute, 1024*1024*128); // 128 MB is max allowed + + QNetworkAccessManager manager; + QNetworkReplyPtr reply = manager.get(request); + + HttpDownloadPerformanceClientDownloadBuffer client(reply, useDownloadBuffer, UploadSize); + + connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection); + QBENCHMARK { + QTestEventLoop::instance().enterLoop(40); + QCOMPARE(reply->error(), QNetworkReply::NoError); + QVERIFY(!QTestEventLoop::instance().timeout()); + } } QTEST_MAIN(tst_qnetworkreply) -- cgit v0.12 From 58a92e8fd06b868feae06f68221d08e2dcbf1d78 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 11:27:56 +0200 Subject: QAbstractSocket: Remove warning --- src/network/socket/qabstractsocket.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index ca72271..c0347dd 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -2100,7 +2100,6 @@ qint64 QAbstractSocket::readData(char *data, qint64 maxSize) && d->readBufferMaxSize > 0 && maxSize < d->readBufferMaxSize && d->socketEngine) { - qDebug() << "QAbstractSocket::readData filling buffer"; // Our buffer is empty and a read() was requested for a byte amount that is smaller // than the readBufferMaxSize. This means that we should fill our buffer since we want // such small reads come from the buffer and not always go to the costly socket engine read() -- cgit v0.12 From 43b9ccb7284601247c474215f342ef13966de85b Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 12 Aug 2010 12:46:55 +0200 Subject: Revert the addition of the test of QtTest's random feature. It was added in commit 56396b4b2b66da3133aff1b3864c550ce892d233. Even though we have a -seed argument to provide reproduceability, it's only reproduceable if the PRNG algorithm is also kept. So we can't autotest this when we don't control the algorithm (for example, it's different from glibc to MSVC's runtime). --- tests/auto/selftests/expected_random.lightxml | 20 ------------------- tests/auto/selftests/expected_random.txt | 9 --------- tests/auto/selftests/expected_random.xml | 23 ---------------------- tests/auto/selftests/expected_random.xunitxml | 14 -------------- tests/auto/selftests/random/random.pro | 9 --------- tests/auto/selftests/random/tst_random.cpp | 28 --------------------------- tests/auto/selftests/selftests.pro | 2 +- tests/auto/selftests/selftests.qrc | 6 +----- tests/auto/selftests/tst_selftests.cpp | 5 ----- 9 files changed, 2 insertions(+), 114 deletions(-) delete mode 100644 tests/auto/selftests/expected_random.lightxml delete mode 100644 tests/auto/selftests/expected_random.txt delete mode 100644 tests/auto/selftests/expected_random.xml delete mode 100644 tests/auto/selftests/expected_random.xunitxml delete mode 100644 tests/auto/selftests/random/random.pro delete mode 100644 tests/auto/selftests/random/tst_random.cpp diff --git a/tests/auto/selftests/expected_random.lightxml b/tests/auto/selftests/expected_random.lightxml deleted file mode 100644 index 15f56a1..0000000 --- a/tests/auto/selftests/expected_random.lightxml +++ /dev/null @@ -1,20 +0,0 @@ - - @INSERT_QT_VERSION_HERE@ - @INSERT_QT_VERSION_HERE@ - 325 - - - - - - - - - - - - - - - - diff --git a/tests/auto/selftests/expected_random.txt b/tests/auto/selftests/expected_random.txt deleted file mode 100644 index 3f4c11d..0000000 --- a/tests/auto/selftests/expected_random.txt +++ /dev/null @@ -1,9 +0,0 @@ -********* Start testing of tst_Random ********* -Config: Using QTest library @INSERT_QT_VERSION_HERE@, Qt @INSERT_QT_VERSION_HERE@, Random seed 325 -PASS : tst_Random::initTestCase() -PASS : tst_Random::testTwo() -PASS : tst_Random::testOne() -PASS : tst_Random::testThree() -PASS : tst_Random::cleanupTestCase() -Totals: 5 passed, 0 failed, 0 skipped -********* Finished testing of tst_Random ********* diff --git a/tests/auto/selftests/expected_random.xml b/tests/auto/selftests/expected_random.xml deleted file mode 100644 index 3677287..0000000 --- a/tests/auto/selftests/expected_random.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - @INSERT_QT_VERSION_HERE@ - @INSERT_QT_VERSION_HERE@ - 325 - - - - - - - - - - - - - - - - - diff --git a/tests/auto/selftests/expected_random.xunitxml b/tests/auto/selftests/expected_random.xunitxml deleted file mode 100644 index 262aa0a..0000000 --- a/tests/auto/selftests/expected_random.xunitxml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/tests/auto/selftests/random/random.pro b/tests/auto/selftests/random/random.pro deleted file mode 100644 index 19572ad..0000000 --- a/tests/auto/selftests/random/random.pro +++ /dev/null @@ -1,9 +0,0 @@ -load(qttest_p4) -SOURCES += tst_random.cpp -QT = core - -mac:CONFIG -= app_bundle -CONFIG -= debug_and_release_target - - -TARGET = random diff --git a/tests/auto/selftests/random/tst_random.cpp b/tests/auto/selftests/random/tst_random.cpp deleted file mode 100644 index 1ee922e..0000000 --- a/tests/auto/selftests/random/tst_random.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -class tst_Random: public QObject -{ - Q_OBJECT - -private slots: - void testOne() const {} - void testTwo() const {} - void testThree() const {} -}; - -/* -int main(int argc, char * argv[]) -{ - int failures = 0; - tst_Random tst; - for (int i = 0; i != 10; i++) { - failures += QTest::qExec(&tst, argc, argv); - } - return failures; -} -*/ -QTEST_MAIN(tst_Random) - -#include "tst_random.moc" - diff --git a/tests/auto/selftests/selftests.pro b/tests/auto/selftests/selftests.pro index bfbd598..d854b5e 100644 --- a/tests/auto/selftests/selftests.pro +++ b/tests/auto/selftests/selftests.pro @@ -5,7 +5,7 @@ SUBDIRS = subtest test warnings maxwarnings cmptest globaldata skipglobal skip \ skipinit skipinitdata datetime singleskip assert waitwithoutgui differentexec \ exceptionthrow qexecstringlist datatable commandlinedata\ benchlibwalltime benchlibcallgrind benchlibeventcounter benchlibtickcounter \ - benchliboptions xunit badxml longstring random + benchliboptions xunit badxml longstring INSTALLS = diff --git a/tests/auto/selftests/selftests.qrc b/tests/auto/selftests/selftests.qrc index ba7ca4f..f82722b 100644 --- a/tests/auto/selftests/selftests.qrc +++ b/tests/auto/selftests/selftests.qrc @@ -90,10 +90,6 @@ expected_multiexec.xml expected_multiexec.xunitxml expected_qexecstringlist.txt - expected_random.txt - expected_random.lightxml - expected_random.xml - expected_random.xunitxml expected_singleskip.lightxml expected_singleskip.txt expected_singleskip.xml @@ -138,5 +134,5 @@ expected_xunit.txt expected_xunit.xml expected_xunit.xunitxml - + diff --git a/tests/auto/selftests/tst_selftests.cpp b/tests/auto/selftests/tst_selftests.cpp index 9fb7ce6..0818b4c 100644 --- a/tests/auto/selftests/tst_selftests.cpp +++ b/tests/auto/selftests/tst_selftests.cpp @@ -245,7 +245,6 @@ void tst_Selftests::runSubTest_data() << "xunit" << "longstring" << "badxml" - << "random" ; foreach (Logger const& logger, allLoggers()) { @@ -274,9 +273,6 @@ void tst_Selftests::runSubTest_data() else if (subtest == "badxml") { arguments << "-eventcounter"; } - else if (subtest == "random") { - arguments << QString("-random -seed 325").split(' '); - } // These tests don't work right with loggers other than plain, usually because // they internally supply arguments to themselves. @@ -636,4 +632,3 @@ void tst_Selftests::cleanupTestCase() QTEST_MAIN(tst_Selftests) #include "tst_selftests.moc" - -- cgit v0.12 From f0912434632b1f17cb60688ed38bf830a5f25106 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Thu, 12 Aug 2010 15:13:10 +0200 Subject: QNAM: Fix namespaced compilation --- src/network/access/qnetworkreplyimpl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp index c8808af..5345d63 100644 --- a/src/network/access/qnetworkreplyimpl.cpp +++ b/src/network/access/qnetworkreplyimpl.cpp @@ -54,10 +54,12 @@ #include QT_BEGIN_NAMESPACE - typedef QSharedPointer > QVarLengthArraySharedPointer; +QT_END_NAMESPACE Q_DECLARE_METATYPE(QVarLengthArraySharedPointer) +QT_BEGIN_NAMESPACE + inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate() : backend(0), outgoingData(0), outgoingDataBuffer(0), copyDevice(0), -- cgit v0.12 From 6c2662c9d4d0813622ee5a9783911dbe2e48e72c Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Fri, 13 Aug 2010 14:51:13 +0200 Subject: QNAM HTTP: Remove eatWhitespace() hack After the Unbuffered TCP rewrite this brought more trouble than it helped. Better fix the chunked decoder soon. Reviewed-by: Peter Hartmann --- .../access/qhttpnetworkconnectionchannel.cpp | 36 +++++----------------- .../access/qhttpnetworkconnectionchannel_p.h | 2 -- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index c0b5928..b06b2d3 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -631,7 +631,6 @@ bool QHttpNetworkConnectionChannel::expand(bool dataComplete) void QHttpNetworkConnectionChannel::allDone() { - eatWhitespace(); // FIXME HACK! #ifndef QT_NO_COMPRESS // expand the whole data. if (reply->d_func()->expectContent() && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd) @@ -693,10 +692,15 @@ void QHttpNetworkConnectionChannel::allDone() // this was wrong, allDone gets called from that function anyway. } } else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) { - eatWhitespace(); // this is weird. we had nothing pipelined but still bytes available. better close it. - if (socket->bytesAvailable() > 0) - close(); + //if (socket->bytesAvailable() > 0) + // close(); + // + // FIXME + // We do not close it anymore now, but should introduce this again after having fixed + // the chunked decoder in QHttpNetworkReply to read the whitespace after the last chunk. + // (Currently this is worked around by readStatus in the QHttpNetworkReply ignoring + // leading whitespace. QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); } else if (alreadyPipelinedRequests.isEmpty()) { if (qobject_cast(connection)) @@ -743,30 +747,6 @@ void QHttpNetworkConnectionChannel::requeueCurrentlyPipelinedRequests() QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); } -void QHttpNetworkConnectionChannel::eatWhitespace() -{ - char c; - do { - qint64 ret = socket->peek(&c, 1); - - // nothing read, fine. - if (ret == 0) - return; - - // EOF from socket? - if (ret == -1) - return; // FIXME, we need to stop processing. however the next stuff done will also do that. - - // read all whitespace and line endings - if (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31) { - socket->read(&c, 1); - continue; - } else { - break; - } - } while(true); -} - void QHttpNetworkConnectionChannel::handleStatus() { Q_ASSERT(socket); diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index e384e9d..4f3a65c 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -153,8 +153,6 @@ public: void handleUnexpectedEOF(); void closeAndResendCurrentRequest(); - void eatWhitespace(); - bool isSocketBusy() const; bool isSocketWriting() const; bool isSocketWaiting() const; -- cgit v0.12 From 3411ac72ffcbd8152342603152193aa7283bd149 Mon Sep 17 00:00:00 2001 From: Prasanth Ullattil Date: Fri, 13 Aug 2010 16:42:16 +0200 Subject: Cannot enter text through character viewer on Mac (Cocoa) This text is delivered through the NSTextInput protocol. Since it was not initiated from a keyDown message, we were ignoring the insertText: message. Reviewed-by: Denis --- src/gui/kernel/qcocoaview_mac.mm | 8 +++++++- src/gui/kernel/qcocoaview_mac_p.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gui/kernel/qcocoaview_mac.mm b/src/gui/kernel/qcocoaview_mac.mm index 3229e71..0282c79 100644 --- a/src/gui/kernel/qcocoaview_mac.mm +++ b/src/gui/kernel/qcocoaview_mac.mm @@ -207,6 +207,7 @@ static int qCocoaViewCount = 0; composing = false; sendKeyEvents = true; + fromKeyDownEvent = false; [self setHidden:YES]; return self; } @@ -1215,7 +1216,9 @@ static int qCocoaViewCount = 0; && !(widgetToGetKey->inputMethodHints() & Qt::ImhDigitsOnly || widgetToGetKey->inputMethodHints() & Qt::ImhFormattedNumbersOnly || widgetToGetKey->inputMethodHints() & Qt::ImhHiddenText)) { + fromKeyDownEvent = true; [qt_mac_nativeview_for(widgetToGetKey) interpretKeyEvents:[NSArray arrayWithObject: theEvent]]; + fromKeyDownEvent = false; } if (sendKeyEvents && !composing) { bool keyOK = qt_dispatchKeyEvent(theEvent, widgetToGetKey); @@ -1285,7 +1288,10 @@ static int qCocoaViewCount = 0; }; } - if ([aString length] && composing) { + // When entering characters through Character Viewer or Keyboard Viewer, the text is passed + // through this insertText method. Since we dont receive a keyDown Event in such cases, the + // composing flag will be false. + if (([aString length] && composing) || !fromKeyDownEvent) { // Send the commit string to the widget. composing = false; sendKeyEvents = false; diff --git a/src/gui/kernel/qcocoaview_mac_p.h b/src/gui/kernel/qcocoaview_mac_p.h index b6b63ca..511423357 100644 --- a/src/gui/kernel/qcocoaview_mac_p.h +++ b/src/gui/kernel/qcocoaview_mac_p.h @@ -86,6 +86,7 @@ Q_GUI_EXPORT bool composing; int composingLength; bool sendKeyEvents; + bool fromKeyDownEvent; QString *composingText; NSInteger dragEnterSequence; } -- cgit v0.12