/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include QT_USE_NAMESPACE // this test only works with // * GLIBC // * MSVC - only debug builds (we need the crtdbg.h helpers) // * SYMBIAN - only when __UHEAP_BURSTFAILNEXT is available #if (defined(QT_NO_EXCEPTIONS) || (!defined(__GLIBC__) && !defined(Q_CC_MSVC) && (!defined(Q_OS_SYMBIAN) || !defined(__UHEAP_BURSTFAILNEXT)))) && !defined(Q_MOC_RUN) QTEST_NOOP_MAIN #else #include "oomsimulator.h" #if !defined(Q_OS_SYMBIAN) #include "3rdparty/memcheck.h" #endif class tst_ExceptionSafetyObjects: public QObject { Q_OBJECT public slots: void initTestCase(); private slots: void objects_data(); void objects(); void widgets_data(); void widgets(); void vector_data(); void vector(); void list_data(); void list(); void linkedList_data(); void linkedList(); }; // helper structs to create an arbitrary widget struct AbstractTester { virtual void operator()(QObject *parent) = 0; }; Q_DECLARE_METATYPE(AbstractTester *) typedef void (*TestFunction)(QObject*); Q_DECLARE_METATYPE(TestFunction) template struct ObjectCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer ptr(new T); } }; struct BitArrayCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer bitArray(new QBitArray(100, true)); } }; struct ByteArrayMatcherCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer ptr(new QByteArrayMatcher("ralf test",8)); } }; struct CryptographicHashCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer ptr(new QCryptographicHash(QCryptographicHash::Sha1)); ptr->addData("ralf test",8); } }; struct DataStreamCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer arr(new QByteArray("hallo, test")); QScopedPointer ptr(new QDataStream(arr.data(), QIODevice::ReadWrite)); ptr->writeBytes("ralf test",8); } }; struct DirCreator : public AbstractTester { void operator()(QObject *) { QDir::cleanPath("../////././"); QScopedPointer ptr(new QDir(".")); while( ptr->cdUp() ) ; // just going up ptr->count(); ptr->exists(ptr->path()); QStringList filters; filters << "*.cpp" << "*.cxx" << "*.cc"; ptr->setNameFilters(filters); } }; void tst_ExceptionSafetyObjects::objects_data() { QTest::addColumn("objectCreator"); #define NEWROW(T) QTest::newRow(#T) << static_cast(new ObjectCreator) NEWROW(QObject); NEWROW(QBuffer); NEWROW(QFile); NEWROW(QProcess); NEWROW(QSettings); NEWROW(QThread); NEWROW(QThreadPool); NEWROW(QTranslator); NEWROW(QFSFileEngine); #define NEWROW2(T, CREATOR) QTest::newRow(#T) << static_cast(new CREATOR) NEWROW2(QBitArray, BitArrayCreator); NEWROW2(QByteArrayMatcher, ByteArrayMatcherCreator); NEWROW2(QCryptographicHash, CryptographicHashCreator); NEWROW2(QDataStream, DataStreamCreator); NEWROW2(QDir, DirCreator); } // create and destructs an object, and lets each and every allocation // during construction and destruction fail. template static void doOOMTest(T &testFunc, QObject *parent, int start=0) { int currentOOMIndex = start; bool caught = false; bool done = false; AllocFailer allocFailer(0); int allocCountBefore = allocFailer.currentAllocIndex(); do { allocFailer.reactivateAt(++currentOOMIndex); caught = false; try { testFunc(parent); } catch (const std::bad_alloc &) { caught = true; } catch (const std::exception &ex) { if (strcmp(ex.what(), "autotest swallow") != 0) throw; caught = true; } if (!caught) { void *buf = malloc(42); if (buf) { // we got memory here - oom test is over. free(buf); done = true; } } // if we get a FAIL, stop executing now if (QTest::currentTestFailed()) done = true; //#define REALLY_VERBOSE #ifdef REALLY_VERBOSE fprintf(stderr, " OOM Index: %d\n", currentOOMIndex); #endif } while (caught || !done); allocFailer.deactivate(); //#define VERBOSE #ifdef VERBOSE fprintf(stderr, "OOM Test done, checked allocs: %d (range %d - %d)\n", currentOOMIndex, allocCountBefore, allocFailer.currentAllocIndex()); #else Q_UNUSED(allocCountBefore); #endif } static int alloc1Failed = 0; static int alloc2Failed = 0; static int alloc3Failed = 0; static int alloc4Failed = 0; static int malloc1Failed = 0; static int malloc2Failed = 0; // Tests that new, new[] and malloc() fail at least once during OOM testing. class SelfTestObject : public QObject { public: SelfTestObject(QObject *parent = 0) : QObject(parent) { try { delete new int; } catch (const std::bad_alloc &) { ++alloc1Failed; throw; } try { delete [] new double[5]; } catch (const std::bad_alloc &) { ++alloc2Failed; throw ;} void *buf = malloc(42); if (buf) free(buf); else ++malloc1Failed; } ~SelfTestObject() { try { delete new int; } catch (const std::bad_alloc &) { ++alloc3Failed; } try { delete [] new double[5]; } catch (const std::bad_alloc &) { ++alloc4Failed; } void *buf = malloc(42); if (buf) free(buf); else ++malloc2Failed = true; } }; void tst_ExceptionSafetyObjects::initTestCase() { QVERIFY(AllocFailer::initialize()); // sanity check whether OOM simulation works AllocFailer allocFailer(0); // malloc fail index is 0 -> this malloc should fail. void *buf = malloc(42); allocFailer.deactivate(); QVERIFY(!buf); // malloc fail index is 1 - second malloc should fail. allocFailer.reactivateAt(1); buf = malloc(42); void *buf2 = malloc(42); allocFailer.deactivate(); QVERIFY(buf); free(buf); QVERIFY(!buf2); #ifdef Q_OS_SYMBIAN // temporary workaround for INC138398 std::new_handler nh_func = std::set_new_handler(0); (void) std::set_new_handler(nh_func); #endif ObjectCreator *selfTest = new ObjectCreator; doOOMTest(*selfTest, 0); delete selfTest; QCOMPARE(alloc1Failed, 1); QCOMPARE(alloc2Failed, 1); QCOMPARE(alloc3Failed, 2); QCOMPARE(alloc4Failed, 3); QCOMPARE(malloc1Failed, 1); QCOMPARE(malloc2Failed, 1); } void tst_ExceptionSafetyObjects::objects() { QFETCH(AbstractTester *, objectCreator); doOOMTest(*objectCreator, 0); delete objectCreator; } template struct WidgetCreator : public AbstractTester { void operator()(QObject *parent) { Q_ASSERT(!parent || parent->isWidgetType()); QScopedPointer ptr(parent ? new T(static_cast(parent)) : new T); } }; // QSizeGrip doesn't have a default constructor - always pass parent (even though it might be 0) template <> struct WidgetCreator : public AbstractTester { void operator()(QObject *parent) { Q_ASSERT(!parent || parent->isWidgetType()); QScopedPointer ptr(new QSizeGrip(static_cast(parent))); } }; // QDesktopWidget doesn't need a parent. template <> struct WidgetCreator : public AbstractTester { void operator()(QObject *parent) { Q_ASSERT(!parent || parent->isWidgetType()); QScopedPointer ptr(new QDesktopWidget()); } }; void tst_ExceptionSafetyObjects::widgets_data() { QTest::addColumn("widgetCreator"); #undef NEWROW #define NEWROW(T) QTest::newRow(#T) << static_cast(new WidgetCreator) NEWROW(QWidget); NEWROW(QButtonGroup); NEWROW(QDesktopWidget); NEWROW(QCheckBox); NEWROW(QComboBox); NEWROW(QCommandLinkButton); NEWROW(QDateEdit); NEWROW(QDateTimeEdit); NEWROW(QDial); NEWROW(QDoubleSpinBox); NEWROW(QFocusFrame); NEWROW(QFontComboBox); NEWROW(QFrame); NEWROW(QGroupBox); NEWROW(QLCDNumber); NEWROW(QLabel); NEWROW(QLCDNumber); NEWROW(QLineEdit); NEWROW(QMenu); NEWROW(QPlainTextEdit); NEWROW(QProgressBar); NEWROW(QPushButton); NEWROW(QRadioButton); NEWROW(QScrollArea); NEWROW(QScrollBar); NEWROW(QSizeGrip); NEWROW(QSlider); NEWROW(QSpinBox); NEWROW(QSplitter); NEWROW(QStackedWidget); NEWROW(QStatusBar); NEWROW(QTabBar); NEWROW(QTabWidget); NEWROW(QTextBrowser); NEWROW(QTextEdit); NEWROW(QTimeEdit); NEWROW(QToolBox); NEWROW(QToolButton); NEWROW(QStatusBar); NEWROW(QSplitter); NEWROW(QTextEdit); NEWROW(QTextBrowser); NEWROW(QToolBar); NEWROW(QMenuBar); NEWROW(QMainWindow); NEWROW(QWorkspace); NEWROW(QColumnView); NEWROW(QListView); NEWROW(QListWidget); NEWROW(QTableView); NEWROW(QTableWidget); NEWROW(QTreeView); NEWROW(QTreeWidget); } void tst_ExceptionSafetyObjects::widgets() { QFETCH(AbstractTester *, widgetCreator); doOOMTest(*widgetCreator, 0, 00000); QWidget parent; doOOMTest(*widgetCreator, &parent, 00000); delete widgetCreator; // if the test reaches here without crashing, we passed :) QVERIFY(true); } struct Integer { Integer(int value = 42) : ptr(new int(value)) { ++instanceCount; } Integer(const Integer &other) : ptr(new int(*other.ptr)) { ++instanceCount; } Integer &operator=(const Integer &other) { int *newPtr = new int(*other.ptr); delete ptr; ptr = newPtr; return *this; } ~Integer() { --instanceCount; delete ptr; } int value() const { return *ptr; } int *ptr; static int instanceCount; }; int Integer::instanceCount = 0; struct IntegerMoveable { IntegerMoveable(int value = 42) : val(value) { delete new int; ++instanceCount; } IntegerMoveable(const IntegerMoveable &other) : val(other.val) { delete new int; ++instanceCount; } IntegerMoveable &operator=(const IntegerMoveable &other) { delete new int; val = other.val; return *this; } ~IntegerMoveable() { --instanceCount; } int value() const { return val; } int val; static int instanceCount; }; int IntegerMoveable::instanceCount = 0; Q_DECLARE_TYPEINFO(IntegerMoveable, Q_MOVABLE_TYPE); template class Container > void containerInsertTest(QObject*) { Container container; // insert an item in an empty container try { container.insert(container.begin(), 41); } catch (...) { QVERIFY(container.isEmpty()); QCOMPARE(T::instanceCount, 0); return; } QCOMPARE(container.size(), 1); QCOMPARE(T::instanceCount, 1); // insert an item before another item try { container.insert(container.begin(), 42); } catch (...) { QCOMPARE(container.size(), 1); QCOMPARE(container.first().value(), 41); QCOMPARE(T::instanceCount, 1); return; } QCOMPARE(T::instanceCount, 2); // insert an item in between try { container.insert(container.begin() + 1, 43); } catch (...) { QCOMPARE(container.size(), 2); QCOMPARE(container.first().value(), 41); QCOMPARE((container.begin() + 1)->value(), 42); QCOMPARE(T::instanceCount, 2); return; } QCOMPARE(T::instanceCount, 3); } template class Container> void containerAppendTest(QObject*) { Container container; // append to an empty container try { container.append(42); } catch (...) { QCOMPARE(container.size(), 0); QCOMPARE(T::instanceCount, 0); return; } // append to a container with one item try { container.append(43); } catch (...) { QCOMPARE(container.size(), 1); QCOMPARE(container.first().value(), 42); QCOMPARE(T::instanceCount, 1); return; } Container container2; try { container2.append(44); } catch (...) { // don't care return; } QCOMPARE(T::instanceCount, 3); // append another container with one item try { container += container2; } catch (...) { QCOMPARE(container.size(), 2); QCOMPARE(container.first().value(), 42); QCOMPARE((container.begin() + 1)->value(), 43); QCOMPARE(T::instanceCount, 3); return; } QCOMPARE(T::instanceCount, 4); } template class Container> void containerEraseTest(QObject*) { Container container; try { container.append(42); container.append(43); container.append(44); container.append(45); container.append(46); } catch (...) { // don't care return; } // sanity checks QCOMPARE(container.size(), 5); QCOMPARE(T::instanceCount, 5); // delete the first one try { container.erase(container.begin()); } catch (...) { QCOMPARE(container.size(), 5); QCOMPARE(container.first().value(), 42); QCOMPARE(T::instanceCount, 5); return; } QCOMPARE(container.size(), 4); QCOMPARE(container.first().value(), 43); QCOMPARE(T::instanceCount, 4); // delete the last one try { container.erase(container.end() - 1); } catch (...) { QCOMPARE(container.size(), 4); QCOMPARE(T::instanceCount, 4); return; } QCOMPARE(container.size(), 3); QCOMPARE(container.first().value(), 43); QCOMPARE((container.begin() + 1)->value(), 44); QCOMPARE((container.begin() + 2)->value(), 45); QCOMPARE(T::instanceCount, 3); // delete the middle one try { container.erase(container.begin() + 1); } catch (...) { QCOMPARE(container.size(), 3); QCOMPARE(container.first().value(), 43); QCOMPARE((container.begin() + 1)->value(), 44); QCOMPARE((container.begin() + 2)->value(), 45); QCOMPARE(T::instanceCount, 3); return; } QCOMPARE(container.size(), 2); QCOMPARE(container.first().value(), 43); QCOMPARE((container.begin() + 1)->value(), 45); QCOMPARE(T::instanceCount, 2); } template class Container> static void containerData() { QTest::addColumn("testFunction"); QTest::newRow("insert static") << static_cast(containerInsertTest); QTest::newRow("append static") << static_cast(containerAppendTest); QTest::newRow("erase static") << static_cast(containerEraseTest); QTest::newRow("insert moveable") << static_cast(containerInsertTest); QTest::newRow("append moveable") << static_cast(containerAppendTest); QTest::newRow("erase moveable") << static_cast(containerEraseTest); } void tst_ExceptionSafetyObjects::vector_data() { containerData(); } void tst_ExceptionSafetyObjects::vector() { QFETCH(TestFunction, testFunction); if (QLatin1String(QTest::currentDataTag()) == QLatin1String("insert static") || QLatin1String(QTest::currentDataTag()) == QLatin1String("insert moveable")) QSKIP("QVector::insert is currently not strongly exception safe", SkipSingle); doOOMTest(testFunction, 0); } void tst_ExceptionSafetyObjects::list_data() { containerData(); } void tst_ExceptionSafetyObjects::list() { QFETCH(TestFunction, testFunction); doOOMTest(testFunction, 0); } void tst_ExceptionSafetyObjects::linkedList_data() { containerData(); } void tst_ExceptionSafetyObjects::linkedList() { QFETCH(TestFunction, testFunction); doOOMTest(testFunction, 0); } QTEST_MAIN(tst_ExceptionSafetyObjects) #include "tst_exceptionsafety_objects.moc" #endif // QT_NO_EXCEPTIONS