diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ade43f94b..dbcb8052b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,21 +67,21 @@ jobs: PYTHON_VERSION=$(python3 --version | cut -d " " -f 2 | cut -d "." -f1,2) \ PYTHON_DIR=$(which python3 | xargs dirname | xargs dirname) make -j $(nproc) - PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ - UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ - make check TESTARGS="-platform offscreen" - - - name: Run memory tests with sanitizers - run: | - QT_VERSION_FULL=$(qmake -query QT_VERSION) if [[ "$QT_VERSION_FULL" == 5.12* ]]; then echo "leak:QPlatformIntegrationFactory::create" >> $PWD/lsan.supp export LSAN_OPTIONS="suppressions=$PWD/lsan.supp" fi + FULL_VERSION=$(python3 --version | cut -d " " -f 2 | cut -d "." -f1,2,3) + + MIN_VERSION="3.12.0" + MAX_VERSION="3.12.4" + export PYTHONQT_RUN_ONLY_MEMORY_TESTS=1 + if printf "%s\n" "$MIN_VERSION" "$FULL_VERSION" "$MAX_VERSION" | sort -V -C; then + unset PYTHONQT_RUN_ONLY_MEMORY_TESTS + fi PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=1:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ - PYTHONQT_RUN_ONLY_MEMORY_TESTS=1 \ - make check TESTARGS="-platform minimal" + make check - name: Generate Wrappers run: | @@ -142,7 +142,7 @@ jobs: "PYTHON_VERSION=${PYTHON_VERSION_SHORT}" "PYTHON_DIR=${PYTHON_DIR}" make -j $(nproc) && \ PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ - make check TESTARGS="-platform offscreen" + make check - name: Generate Wrappers run: | @@ -232,7 +232,7 @@ jobs: make -j $(nproc) PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ - make check TESTARGS="-platform offscreen" + make check - name: Generate Wrappers if: ${{ contains(matrix.configuration, 'release') }} @@ -342,8 +342,8 @@ jobs: set PYTHONDEVMODE=1 set PYTHONASYNCIODEBUG=1 set PYTHONWARNINGS=error - mingw32-make -j 2 && mingw32-make check "TESTARGS=-platform offscreen" ^ - || set CL=/MP && nmake && nmake check "TESTARGS=-platform offscreen" + mingw32-make -j 2 && mingw32-make check ^ + || set CL=/MP && nmake && nmake check - name: Generate Wrappers shell: cmd diff --git a/.github/workflows/build_latest.yml b/.github/workflows/build_latest.yml index 215ed0b51..10e65f6d6 100644 --- a/.github/workflows/build_latest.yml +++ b/.github/workflows/build_latest.yml @@ -109,7 +109,7 @@ jobs: make -j $(nproc) PYTHONDEVMODE=1 PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=error PYTHONMALLOC=malloc_debug \ UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ - make check TESTARGS="-platform offscreen" + make check - name: Build PythonQt Windows shell: cmd @@ -125,4 +125,4 @@ jobs: set PYTHONDEVMODE=1 set PYTHONASYNCIODEBUG=1 set PYTHONWARNINGS=error - nmake && nmake check "TESTARGS=-platform offscreen" + nmake && nmake check diff --git a/src/PythonQt.cpp b/src/PythonQt.cpp index 4d01a15bb..1db96ebe8 100644 --- a/src/PythonQt.cpp +++ b/src/PythonQt.cpp @@ -323,11 +323,17 @@ void PythonQt::init(int flags, const QByteArray& pythonQtModuleName) void PythonQt::cleanup() { if (_self) { + _self->removeSignalHandlers(); delete _self; _self = nullptr; } } +void PythonQt::preCleanup() +{ + _self->priv()->preCleanup(); +} + PythonQt* PythonQt::self() { return _self; } PythonQt::PythonQt(int flags, const QByteArray& pythonQtModuleName) @@ -348,6 +354,12 @@ PythonQt::PythonQt(int flags, const QByteArray& pythonQtModuleName) Py_Initialize(); } +#if defined(PYTHONQT_FULL_THREAD_SUPPORT) && PY_VERSION_HEX < 0x03090000 + if (!PyEval_ThreadsInitialized()) { + PyEval_InitThreads(); + } +#endif + // add our own python object types for qt object slots if (PyType_Ready(&PythonQtSlotFunction_Type) < 0) { std::cerr << "could not initialize PythonQtSlotFunction_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl; @@ -414,9 +426,9 @@ PythonQtPrivate::~PythonQtPrivate() { delete _defaultImporter; _defaultImporter = nullptr; - { - qDeleteAll(_knownClassInfos); - } + qDeleteAll(_knownClassInfos); + _knownClassInfos.clear(); + PythonQtClassInfo::clearInteralStaticData(); PythonQtMethodInfo::cleanupCachedMethodInfos(); PythonQtArgumentFrame::cleanupFreeList(); @@ -1558,6 +1570,16 @@ PythonQtClassInfo* PythonQtPrivate::currentClassInfoForClassWrapperCreation() return info; } +void PythonQtPrivate::preCleanup() +{ + _pySourceFileLoader = nullptr; + _pySourcelessFileLoader = nullptr; + _pyEnsureFuture = nullptr; + _pyFutureClass = nullptr; + _pyTaskDoneCallback = nullptr; + _pythonQtModule = nullptr; +} + void PythonQtPrivate::addDecorators(QObject* o, int decoTypes) { o->setParent(this); @@ -2353,7 +2375,7 @@ const QMetaObject* PythonQtPrivate::buildDynamicMetaObject(PythonQtClassWrapper* } if (needsMetaObject) { type->_dynamicClassInfo->_dynamicMetaObject = builder.toMetaObject(); - type->_dynamicClassInfo->_classInfo = new PythonQtClassInfo(); + type->_dynamicClassInfo->_classInfo.reset(new PythonQtClassInfo()); type->_dynamicClassInfo->_classInfo->setupQObject(type->_dynamicClassInfo->_dynamicMetaObject); } else { // we don't need an own meta object, just use the one from our base class @@ -2635,6 +2657,23 @@ PythonQtClassInfo* PythonQtPrivate::getClassInfo( const QByteArray& className ) } } } + + if (!result) { + bool ambiguity = false; + for(auto &&key: _knownClassInfos.keys()) { + if (key.indexOf(QByteArray("::") + className) >= 0) { + if (!result) { + result = _knownClassInfos.value(key); + } else { + ambiguity = true; + std::cerr << "Multiple candidates found for" << '\n'; + } + } + } + if (ambiguity) { + return nullptr; + } + } return result; } diff --git a/src/PythonQt.h b/src/PythonQt.h index 761d2446b..16c9dad19 100644 --- a/src/PythonQt.h +++ b/src/PythonQt.h @@ -241,6 +241,8 @@ class PYTHONQT_EXPORT PythonQt : public QObject { //! get the singleton instance static PythonQt* self(); + static void preCleanup(); + //@} //! defines the object types for introspection @@ -662,6 +664,8 @@ class PYTHONQT_EXPORT PythonQtPrivate : public QObject { PythonQtPrivate(); ~PythonQtPrivate() override; + void preCleanup(); + enum DecoratorTypes { StaticDecorator = 1, ConstructorDecorator = 2, diff --git a/src/PythonQtClassInfo.cpp b/src/PythonQtClassInfo.cpp index 5e17f0d11..ea0afe23c 100644 --- a/src/PythonQtClassInfo.cpp +++ b/src/PythonQtClassInfo.cpp @@ -86,8 +86,8 @@ PythonQtClassInfo::~PythonQtClassInfo() if (_destructor) { _destructor->deleteOverloadsAndThis(); } - Q_FOREACH(PythonQtSlotInfo* info, _decoratorSlots) { - info->deleteOverloadsAndThis(); + for(auto &&info: _decoratorSlots) { + info->deleteOverloadsAndThis(); } } @@ -290,9 +290,7 @@ bool PythonQtClassInfo::lookForEnumAndCache(const QMetaObject* meta, const char* if (escapeReservedNames(e.key(j)) == memberName) { PyObject* enumType = findEnumWrapper(e.name()); if (enumType) { - PythonQtObjectPtr enumValuePtr; - enumValuePtr.setNewRef(PythonQtPrivate::createEnumValueInstance(enumType, e.value(j))); - PythonQtMemberInfo newInfo(enumValuePtr); + PythonQtMemberInfo newInfo(PythonQtPrivate::createEnumValueInstance(enumType, e.value(j))); _cachedMembers.insert(memberName, newInfo); #ifdef PYTHONQT_DEBUG std::cout << "caching enum " << memberName << " on " << meta->className() << std::endl; @@ -875,7 +873,7 @@ void PythonQtClassInfo::createEnumWrappers(const QMetaObject* meta) for (int i = meta->enumeratorOffset();ienumeratorCount();i++) { QMetaEnum e = meta->enumerator(i); PythonQtObjectPtr p; - p.setNewRef(PythonQtPrivate::createNewPythonQtEnumWrapper(e.name(), _pythonQtClassWrapper)); + p.setNewRef(PythonQtPrivate::createNewPythonQtEnumWrapper(e.name(), _pythonQtClassWrapper.object())); // add enum values to the enum type itself, in case enum value names are so generic // that they are not unique for (int j = 0; j < e.keyCount(); j++) { @@ -1043,6 +1041,11 @@ void PythonQtClassInfo::addGlobalNamespaceWrapper(PythonQtClassInfo* namespaceWr _globalNamespaceWrappers.insert(0, namespaceWrapper); } +void PythonQtClassInfo::clearInteralStaticData() +{ + _globalNamespaceWrappers.clear(); +} + void PythonQtClassInfo::updateRefCountingCBs() { if (!_refCallback) { @@ -1116,38 +1119,3 @@ bool PythonQtClassInfo::supportsRichCompare() } return (_typeSlots & PythonQt::Type_RichCompare); } - -//------------------------------------------------------------------------- - -PythonQtMemberInfo::PythonQtMemberInfo( PythonQtSlotInfo* info ) : _slot(info) -{ - if (info->metaMethod()->methodType() == QMetaMethod::Signal) { - _type = Signal; - } else { - _type = Slot; - } - _enumValue = nullptr; - _pythonType = nullptr; -} - -PythonQtMemberInfo::PythonQtMemberInfo( const PythonQtObjectPtr& enumValue ) -{ - _type = EnumValue; - _slot = nullptr; - _enumValue = enumValue; - _pythonType = nullptr; -} - -PythonQtMemberInfo::PythonQtMemberInfo( const QMetaProperty& prop ) -{ - _type = Property; - _slot = nullptr; - _property = prop; - _enumValue = nullptr; - _pythonType = nullptr; -} - -PythonQtDynamicClassInfo::~PythonQtDynamicClassInfo() -{ - delete _classInfo; -} diff --git a/src/PythonQtClassInfo.h b/src/PythonQtClassInfo.h index fa439f5df..90285e12a 100644 --- a/src/PythonQtClassInfo.h +++ b/src/PythonQtClassInfo.h @@ -41,16 +41,12 @@ #include #include -class PythonQtSlotInfo; -class PythonQtClassInfo; +#include struct PythonQtDynamicClassInfo { - PythonQtDynamicClassInfo() { _dynamicMetaObject = nullptr; _classInfo = nullptr; } - ~PythonQtDynamicClassInfo(); - - const QMetaObject* _dynamicMetaObject; - PythonQtClassInfo* _classInfo; + const QMetaObject* _dynamicMetaObject {}; + QScopedPointer _classInfo; }; struct PythonQtMemberInfo { @@ -58,20 +54,27 @@ struct PythonQtMemberInfo { Invalid, Slot, Signal, EnumValue, EnumWrapper, Property, NestedClass, NotFound }; - PythonQtMemberInfo():_type(Invalid),_slot(nullptr),_pythonType(nullptr),_enumValue(nullptr) { } + PythonQtMemberInfo() = default; - PythonQtMemberInfo(PythonQtSlotInfo* info); + explicit PythonQtMemberInfo(PythonQtSlotInfo* info) + : _type(info->metaMethod()->methodType() == QMetaMethod::Signal? Signal : Slot) + , _slot(info) + {} - PythonQtMemberInfo(const PythonQtObjectPtr& enumValue); + explicit PythonQtMemberInfo(PyObject* enumValue) + : _type (EnumValue), _enumValue(enumValue) + {} - PythonQtMemberInfo(const QMetaProperty& prop); + explicit PythonQtMemberInfo(const QMetaProperty& prop) + : _type (Property), _property(prop) + {} - Type _type; + Type _type { Invalid }; // TODO: this could be a union... - PythonQtSlotInfo* _slot; - PyObject* _pythonType; - PythonQtObjectPtr _enumValue; + PythonQtSlotInfo* _slot {}; + PyObject* _pythonType {}; + PyObject* _enumValue {}; QMetaProperty _property; }; @@ -177,10 +180,10 @@ class PYTHONQT_EXPORT PythonQtClassInfo { void addParentClass(const ParentClassInfo& info) { _parentClasses.append(info); } //! set the associated PythonQtClassWrapper (which handles instance creation of this type) - void setPythonQtClassWrapper(PyObject* obj) { _pythonQtClassWrapper = obj; } + void setPythonQtClassWrapper(PyObject* obj) { _pythonQtClassWrapper.setNewRef(obj); } //! get the associated PythonQtClassWrapper (which handles instance creation of this type) - PyObject* pythonQtClassWrapper() { return _pythonQtClassWrapper; } + PyObject* pythonQtClassWrapper() { return _pythonQtClassWrapper.object(); } //! set the shell set instance wrapper cb void setShellSetInstanceWrapperCB(PythonQtShellSetInstanceWrapperCB* cb) { @@ -244,6 +247,9 @@ class PYTHONQT_EXPORT PythonQtClassInfo { //! Add a wrapper that contains global enums static void addGlobalNamespaceWrapper(PythonQtClassInfo* namespaceWrapper); + //! Clear all statically allocated caches and wrappers + static void clearInteralStaticData(); + private: void updateRefCountingCBs(); @@ -294,7 +300,7 @@ class PYTHONQT_EXPORT PythonQtClassInfo { QObject* _decoratorProvider; PythonQtQObjectCreatorFunctionCB* _decoratorProviderCB; - PyObject* _pythonQtClassWrapper; + PythonQtObjectPtr _pythonQtClassWrapper; PythonQtShellSetInstanceWrapperCB* _shellSetInstanceWrapperCB; diff --git a/src/PythonQtInstanceWrapper.cpp b/src/PythonQtInstanceWrapper.cpp index c560810ab..fba73899a 100644 --- a/src/PythonQtInstanceWrapper.cpp +++ b/src/PythonQtInstanceWrapper.cpp @@ -458,7 +458,7 @@ static PyObject *PythonQtInstanceWrapper_getattro(PyObject *obj,PyObject *name) PythonQtClassInfo* classInfo = nullptr; PythonQtClassWrapper* classType = (PythonQtClassWrapper*)Py_TYPE(wrapper); while (classType->_dynamicClassInfo) { - classInfo = classType->_dynamicClassInfo->_classInfo; + classInfo = classType->_dynamicClassInfo->_classInfo.data(); if (classInfo) { PythonQtMemberInfo member = classInfo->member(attributeName); if (member._type == PythonQtMemberInfo::Signal) { diff --git a/src/PythonQtMethodInfo.cpp b/src/PythonQtMethodInfo.cpp index f428ce5f2..f17872e59 100644 --- a/src/PythonQtMethodInfo.cpp +++ b/src/PythonQtMethodInfo.cpp @@ -43,7 +43,7 @@ #include "PythonQtClassInfo.h" #include -QHash PythonQtMethodInfo::_cachedSignatures; +QHash> PythonQtMethodInfo::_cachedSignatures; QHash PythonQtMethodInfo::_cachedParameterInfos; QHash PythonQtMethodInfo::_parameterNameAliases; @@ -100,12 +100,11 @@ const PythonQtMethodInfo* PythonQtMethodInfo::getCachedMethodInfo(const QMetaMet QByteArray sig(PythonQtUtils::signature(signal)); sig = sig.mid(sig.indexOf('(')); QByteArray fullSig = QByteArray(signal.typeName()) + " " + sig; - PythonQtMethodInfo* result = _cachedSignatures.value(fullSig); + auto &result = _cachedSignatures[fullSig]; if (!result) { - result = new PythonQtMethodInfo(signal, classInfo); - _cachedSignatures.insert(fullSig, result); + result.reset(new PythonQtMethodInfo(signal, classInfo)); } - return result; + return result.data(); } const PythonQtMethodInfo* PythonQtMethodInfo::getCachedMethodInfoFromArgumentList(int numArgs, const char** args) @@ -123,12 +122,11 @@ const PythonQtMethodInfo* PythonQtMethodInfo::getCachedMethodInfoFromArgumentLis arguments << arg; } fullSig += ")"; - PythonQtMethodInfo* result = _cachedSignatures.value(fullSig); + auto &result = _cachedSignatures[fullSig]; if (!result) { - result = new PythonQtMethodInfo(typeName, arguments); - _cachedSignatures.insert(fullSig, result); + result.reset(new PythonQtMethodInfo(typeName, arguments)); } - return result; + return result.data(); } void PythonQtMethodInfo::fillParameterInfo(ParameterInfo& type, const QByteArray& orgName, PythonQtClassInfo* classInfo) @@ -403,7 +401,7 @@ int PythonQtMethodInfo::nameToType(const char* name) _parameterTypeDict.insert("QVariant", PythonQtMethodInfo::Variant); // own special types... (none so far, could be e.g. ObjectList } - QHash::const_iterator it = _parameterTypeDict.find(name); + auto it = _parameterTypeDict.find(name); if (it!=_parameterTypeDict.end()) { return it.value(); } else { @@ -413,10 +411,6 @@ int PythonQtMethodInfo::nameToType(const char* name) void PythonQtMethodInfo::cleanupCachedMethodInfos() { - QHashIterator i(_cachedSignatures); - while (i.hasNext()) { - delete i.next().value(); - } _cachedSignatures.clear(); _cachedParameterInfos.clear(); } diff --git a/src/PythonQtMethodInfo.h b/src/PythonQtMethodInfo.h index db8499044..30953c14e 100644 --- a/src/PythonQtMethodInfo.h +++ b/src/PythonQtMethodInfo.h @@ -139,7 +139,7 @@ class PYTHONQT_EXPORT PythonQtMethodInfo static QHash _parameterNameAliases; //! stores the cached signatures of methods to speedup mapping from Qt to Python types - static QHash _cachedSignatures; + static QHash> _cachedSignatures; static QHash _cachedParameterInfos; diff --git a/src/PythonQtObjectPtr.h b/src/PythonQtObjectPtr.h index 84e6ed70c..4dd22804e 100644 --- a/src/PythonQtObjectPtr.h +++ b/src/PythonQtObjectPtr.h @@ -197,15 +197,15 @@ class PYTHONQT_EXPORT PythonQtObjectPtr class PYTHONQT_EXPORT PythonQtSafeObjectPtr { public: - PythonQtSafeObjectPtr() :_object(nullptr) {} + PythonQtSafeObjectPtr() = default; PythonQtSafeObjectPtr(const PythonQtSafeObjectPtr &p) - :_object(nullptr) { + { setObject(p.object()); } PythonQtSafeObjectPtr(const PythonQtObjectPtr &p) - :_object(nullptr) { + { setObject(p.object()); } @@ -299,7 +299,7 @@ class PYTHONQT_EXPORT PythonQtSafeObjectPtr void setObjectUnsafe(PyObject* o); private: - PyObject* _object; + PyObject* _object {}; }; // We don't want QVariant to take PythonQtObjectPtr via QVariant::fromValue, because it is unsafe when using multi-threading/GIL diff --git a/src/PythonQtSignalReceiver.cpp b/src/PythonQtSignalReceiver.cpp index d2a04efea..f6c6945d6 100644 --- a/src/PythonQtSignalReceiver.cpp +++ b/src/PythonQtSignalReceiver.cpp @@ -52,7 +52,7 @@ int PythonQtSignalReceiver::_destroyedSignal2Id = -2; void PythonQtSignalTarget::call(void **arguments) const { PYTHONQT_GIL_SCOPE - PyObject* result = call(_callable, methodInfo(), arguments); + PyObject* result = call(_callable.object(), methodInfo(), arguments); if (result) { PythonQt::priv()->checkAndRunCoroutine(result); Py_DECREF(result); @@ -144,7 +144,7 @@ PyObject* PythonQtSignalTarget::call(PyObject* callable, const PythonQtMethodInf bool PythonQtSignalTarget::isSame( int signalId, PyObject* callable ) const { - return PyObject_RichCompareBool(callable, _callable, Py_EQ) && (signalId == _signalId); + return PyObject_RichCompareBool(callable, _callable.object(), Py_EQ) && (signalId == _signalId); } //------------------------------------------------------------------------------ diff --git a/tests/PythonQtTestMain.cpp b/tests/PythonQtTestMain.cpp index 051301465..176d3bc69 100644 --- a/tests/PythonQtTestMain.cpp +++ b/tests/PythonQtTestMain.cpp @@ -42,27 +42,40 @@ #include "PythonQt.h" #include "PythonQtTests.h" -#include +#include int main( int argc, char **argv ) { - QApplication qapp(argc, argv); - - if (QProcessEnvironment::systemEnvironment().contains("PYTHONQT_RUN_ONLY_MEMORY_TESTS")) { + QCoreApplication qapp(argc, argv); + int failCount = 0; + if (QProcessEnvironment::systemEnvironment().contains("PYTHONQT_RUN_ONLY_MEMORY_TESTS")) + { PythonQtMemoryTests test; - QTest::qExec(&test, argc, argv); - return 0; + failCount += QTest::qExec(&test, argc, argv); } PythonQt::init(PythonQt::IgnoreSiteModule | PythonQt::RedirectStdOut); - int failCount = 0; - PythonQtTestApi api; - failCount += QTest::qExec(&api, argc, argv); - PythonQtTestSignalHandler signalHandler; - failCount += QTest::qExec(&signalHandler, argc, argv); - PythonQtTestSlotCalling slotCalling; - failCount += QTest::qExec(&slotCalling, argc, argv); + { + PythonQtTestApi api; + failCount += QTest::qExec(&api, argc, argv); + } + + { + PythonQtTestSignalHandler signalHandler; + failCount += QTest::qExec(&signalHandler, argc, argv); + } + + { + PythonQtTestSlotCalling slotCalling; + failCount += QTest::qExec(&slotCalling, argc, argv); + } + PythonQt::preCleanup(); +#if PY_VERSION_HEX < 0x03060000 + Py_Finalize(); +#else + Py_FinalizeEx(); +#endif PythonQt::cleanup(); if (failCount) { diff --git a/tests/PythonQtTests.cpp b/tests/PythonQtTests.cpp index bd440812a..5399e8804 100644 --- a/tests/PythonQtTests.cpp +++ b/tests/PythonQtTests.cpp @@ -41,31 +41,44 @@ #include "PythonQtTests.h" +void PythonQtMemoryTests::cleanup() +{ + if(PythonQt::self()) { + PythonQt::preCleanup(); +#if PY_VERSION_HEX < 0x03060000 + Py_Finalize(); +#else + Py_FinalizeEx(); +#endif + PythonQt::cleanup(); + } +} + void PythonQtMemoryTests::testBaseCleanup() { PythonQt::init(); - PythonQt::cleanup(); + cleanup(); } void PythonQtMemoryTests::testCleanupWithFlags() { PythonQt::init(PythonQt::IgnoreSiteModule | PythonQt::RedirectStdOut); - PythonQt::cleanup(); + cleanup(); } void PythonQtMemoryTests::testInitAlreadyInitialized() { Py_InitializeEx(true); PythonQt::init(PythonQt::PythonAlreadyInitialized); - PythonQt::cleanup(); + cleanup(); } void PythonQtMemoryTests::testSeveralCleanup() { PythonQt::init(); - PythonQt::cleanup(); + cleanup(); PythonQt::init(); - PythonQt::cleanup(); + cleanup(); } void PythonQtMemoryTests::testInitWithPreconfig() { @@ -74,7 +87,7 @@ void PythonQtMemoryTests::testInitWithPreconfig() { PyConfig_InitPythonConfig(&config); Py_InitializeFromConfig(&config); PythonQt::init(PythonQt::RedirectStdOut | PythonQt::PythonAlreadyInitialized); - PythonQt::cleanup(); + cleanup(); #endif } @@ -90,6 +103,11 @@ void PythonQtTestSlotCalling::init() { } +void PythonQtTestSlotCalling::cleanupTestCase() +{ + cleanupPtr(); + delete _helper; _helper = nullptr; +} void* polymorphic_ClassB_Handler(const void* ptr, const char** className) { ClassB* o = (ClassB*)ptr; @@ -302,13 +320,13 @@ void PythonQtTestSlotCalling::testObjectSlotCalls() void PythonQtTestSlotCalling::testCppFactory() { - PythonQtTestCppFactory* f = new PythonQtTestCppFactory; + PythonQtTestCppFactory f; PythonQt::self()->addInstanceDecorators(new PQCppObjectDecorator); // do not register, since we want to know if that works as well //qRegisterMetaType("PQCppObjectNoWrap"); PythonQt::self()->addDecorators(new PQCppObjectNoWrapDecorator); - PythonQt::self()->addWrapperFactory(f); + PythonQt::self()->addWrapperFactory(&f); QVERIFY(_helper->runScript("if obj.createPQCppObject(12).getHeight()==12: obj.setPassed();\n")); QVERIFY(_helper->runScript("if obj.createPQCppObject(12).getH()==12: obj.setPassed();\n")); QVERIFY(_helper->runScript("pq1 = obj.createPQCppObject(12);\n" @@ -346,7 +364,7 @@ void PythonQtTestSlotCalling::testCppFactory() QVERIFY(_helper->runScript("obj.testNoArg()\nfrom PythonQt.private import PQCppObject2\na = PQCppObject2()\nif a.testEnumFlag2(PQCppObject2.TestEnumValue2)==PQCppObject2.TestEnumValue2: obj.setPassed();\n")); // with int overload to check overloading QVERIFY(_helper->runScript("obj.testNoArg()\nfrom PythonQt.private import PQCppObject2\na = PQCppObject2()\nif a.testEnumFlag3(PQCppObject2.TestEnumValue2)==PQCppObject2.TestEnumValue2: obj.setPassed();\n")); - + PythonQt::self()->removeWrapperFactory(&f); } PQCppObject2Decorator::TestEnumFlag PQCppObject2Decorator::testEnumFlag1(PQCppObject2* /*obj*/, PQCppObject2Decorator::TestEnumFlag flag) { @@ -401,6 +419,11 @@ void PythonQtTestSignalHandler::initTestCase() PythonQt::self()->addObject(main, "obj", _helper); } +void PythonQtTestSignalHandler::cleanupTestCase() +{ + delete _helper; +} + void PythonQtTestSignalHandler::testSignalHandler() { PythonQtObjectPtr main = PythonQt::self()->getMainModule(); @@ -457,17 +480,25 @@ void PythonQtTestSignalHandler::testRecursiveSignalHandler() QVERIFY(PythonQt::self()->addSignalHandler(_helper, SIGNAL(signal2(const QString&)), main, "testSignal2")); QVERIFY(PythonQt::self()->addSignalHandler(_helper, SIGNAL(signal3(float)), main, "testSignal3")); QVERIFY(_helper->emitSignal1(12)); + + QVERIFY(PythonQt::self()->removeSignalHandler(_helper, SIGNAL(signal1(int)), main, "testSignal1")); + QVERIFY(PythonQt::self()->removeSignalHandler(_helper, SIGNAL(signal2(const QString&)), main, "testSignal2")); + QVERIFY(PythonQt::self()->removeSignalHandler(_helper, SIGNAL(signal3(float)), main, "testSignal3")); } void PythonQtTestApi::initTestCase() { - _helper = new PythonQtTestApiHelper(); + _helper = new PythonQtTestApiHelper(this); _main = PythonQt::self()->getMainModule(); _main.evalScript("import PythonQt"); _main.addObject("obj", _helper); } +void PythonQtTestApi::cleanupTestCase() +{ +} + void PythonQtTestApi::testProperties() { PythonQtObjectPtr main = PythonQt::self()->getMainModule(); @@ -681,7 +712,9 @@ void PythonQtTestApiHelper::stdErr(const QString& s) QObject* PythonQtTestCppFactory::create(const QByteArray& name, void *ptr) { if (name == "PQCppObject") { - return new PQCppObjectWrapper(ptr); + auto wrapper = new PQCppObjectWrapper(ptr); + wrapper->setParent(&_genericParent); + return wrapper; } return NULL; } diff --git a/tests/PythonQtTests.h b/tests/PythonQtTests.h index 00f06cf46..8981945cb 100644 --- a/tests/PythonQtTests.h +++ b/tests/PythonQtTests.h @@ -68,8 +68,30 @@ private Q_SLOTS: void testSeveralCleanup(); void testInitWithPreconfig(); void testInitAlreadyInitialized(); + void cleanup(); }; +// For the moment, it's not critical for tests; +// the main thing is to be able to build with the correct g++ and sanitizers. +template +class PtrRegistry { +public: + static T* registerPtr(T* object) { + _objects.append(object); + return object; + } + + static void clear() { + qDeleteAll(_objects); + } + +private: + static QList _objects; +}; + +template +QList PtrRegistry::_objects; + //! test the PythonQt api class PythonQtTestApi : public QObject { @@ -77,6 +99,7 @@ class PythonQtTestApi : public QObject private Q_SLOTS: void initTestCase(); + void cleanupTestCase(); void testCall(); void testVariables(); void testRedirect(); @@ -98,19 +121,21 @@ class ClassA { public: ClassA() { x = 1; } int x; + virtual ~ClassA() = default; }; class ClassB { public: ClassB() { y = 2; } int y; - + virtual ~ClassB() = default; virtual int type() { return 2; } }; class ClassC : public ClassA, public ClassB { public: ClassC() { z = 3; } + virtual ~ClassC() override = default; int z; virtual int type() { return 3; } @@ -120,6 +145,7 @@ class ClassD : public QObject, public ClassA, public ClassB { Q_OBJECT public: ClassD() { d = 4; } + virtual ~ClassD() override = default; public Q_SLOTS: int getD() { return d; } private: @@ -133,6 +159,7 @@ class ClassAWrapper : public QObject { public Q_SLOTS: ClassA* new_ClassA() { return new ClassA; } int getX(ClassA* o) { return o->x; } + void delete_ClassA(ClassA* o) { delete o; } }; class ClassBWrapper : public QObject { @@ -140,6 +167,8 @@ class ClassBWrapper : public QObject { public Q_SLOTS: ClassB* new_ClassB() { return new ClassB; } int getY(ClassB* o) { return o->y; } + void delete_ClassB(ClassB* o) { delete o; } + }; class ClassCWrapper : public QObject { @@ -147,12 +176,14 @@ class ClassCWrapper : public QObject { public Q_SLOTS: ClassC* new_ClassC() { return new ClassC; } int getZ(ClassC* o) { return o->z; } + void delete_ClassC(ClassC* o) { delete o; } }; class ClassDWrapper : public QObject { Q_OBJECT public Q_SLOTS: ClassD* new_ClassD() { return new ClassD; } + void delete_ClassD(ClassD* o) { delete o; } }; @@ -161,8 +192,7 @@ class PythonQtTestApiHelper : public QObject , public PythonQtImportFileInterfac { Q_OBJECT public: - PythonQtTestApiHelper() { - }; + PythonQtTestApiHelper(QObject* parent = nullptr): QObject(parent) {} bool call(const QString& function, const QVariantList& args, const QVariant& expectedResult); @@ -192,6 +222,7 @@ public Q_SLOTS: // test implementation of the wrapper factory class PythonQtTestCppFactory : public PythonQtCppWrapperFactory { + QObject _genericParent; public: virtual QObject* create(const QByteArray& name, void *ptr); }; @@ -260,6 +291,8 @@ public Q_SLOTS: return new PQCppObjectNoWrap(2); } + void delete_PQCppObjectNoWrap(PQCppObjectNoWrap* o) { delete o; } + int getH(PQCppObjectNoWrap* obj) { return obj->getHeight(); } }; @@ -297,6 +330,8 @@ class PQCppObject2Decorator : public QObject { return new PQCppObject2(); } + void delete_PQCppObject2(PQCppObject2* o) { delete o; } + TestEnumFlag testEnumFlag1(PQCppObject2* obj, TestEnumFlag flag); PQCppObject2::TestEnumFlag testEnumFlag2(PQCppObject2* obj, PQCppObject2::TestEnumFlag flag); @@ -327,7 +362,7 @@ class PythonQtTestSlotCalling : public QObject private Q_SLOTS: void initTestCase(); void init(); - + void cleanupTestCase(); void testNoArgSlotCall(); void testPODSlotCalls(); void testCPPSlotCalls(); @@ -502,18 +537,18 @@ public Q_SLOTS: QObject* getQObject(QObject* obj) { _called = true; return obj; } QWidget* getQWidget(QWidget* obj) { _called = true; return obj; } //! testing if an object that was not wrapped is wrapped earlier is wrapped correctly - QObject* getNewObject() { _called = true; return new PythonQtTestSlotCallingHelper(NULL); } + QObject* getNewObject() { _called = true; return PtrRegistry::registerPtr(new PythonQtTestSlotCallingHelper(NULL)); } QVariantList getMultiArgs(int a, double b, const QString& str) { _called = true; return (QVariantList() << a << b << str); } //! cpp wrapper factory test - PQCppObject* createPQCppObject(int h) { _called = true; return new PQCppObject(h); } + PQCppObject* createPQCppObject(int h) { _called = true; return PtrRegistry::registerPtr(new PQCppObject(h)); } //! cpp wrapper factory test PQCppObject* getPQCppObject(PQCppObject* p) { _called = true; return p; } //! cpp wrapper factory test - PQCppObjectNoWrap* createPQCppObjectNoWrap(int h) { _called = true; return new PQCppObjectNoWrap(h); } + PQCppObjectNoWrap* createPQCppObjectNoWrap(int h) { _called = true; return PtrRegistry::registerPtr(new PQCppObjectNoWrap(h)); } //! cpp wrapper factory test PQCppObjectNoWrap* getPQCppObjectNoWrap(PQCppObjectNoWrap* p) { _called = true; return p; } @@ -524,22 +559,24 @@ public Q_SLOTS: PQUnknownButRegisteredValueObject getUnknownButRegisteredValueObjectAsValue() { _called = true; return PQUnknownButRegisteredValueObject(); } PQUnknownValueObject getUnknownValueObjectAsValue() { _called = true; return PQUnknownValueObject(); } - PQUnknownButRegisteredValueObject* getUnknownButRegisteredValueObjectAsPtr() { _called = true; return new PQUnknownButRegisteredValueObject(); } - PQUnknownValueObject* getUnknownValueObjectAsPtr() { _called = true; return new PQUnknownValueObject(); } + PQUnknownButRegisteredValueObject* getUnknownButRegisteredValueObjectAsPtr() { _called = true; + return PtrRegistry::registerPtr(new PQUnknownButRegisteredValueObject()); } + PQUnknownValueObject* getUnknownValueObjectAsPtr() { _called = true; + return PtrRegistry::registerPtr(new PQUnknownValueObject()); } ClassA* getClassAPtr(ClassA* o) { _called = true; return o; } ClassB* getClassBPtr(ClassB* o) { _called = true; return o; } ClassC* getClassCPtr(ClassC* o) { _called = true; return o; } ClassD* getClassDPtr(ClassD* o) { _called = true; return o; } - ClassA* createClassA() { _called = true; return new ClassA; } - ClassB* createClassB() { _called = true; return new ClassB; } - ClassC* createClassC() { _called = true; return new ClassC; } - ClassD* createClassD() { _called = true; return new ClassD; } - ClassA* createClassCAsA() { _called = true; return new ClassC; } - ClassB* createClassCAsB() { _called = true; return new ClassC; } - ClassA* createClassDAsA() { _called = true; return new ClassD; } - ClassB* createClassDAsB() { _called = true; return new ClassD; } + ClassA* createClassA() { _called = true; return PtrRegistry::registerPtr(new ClassA); } + ClassB* createClassB() { _called = true; return PtrRegistry::registerPtr(new ClassB); } + ClassC* createClassC() { _called = true; return PtrRegistry::registerPtr(new ClassC); } + ClassD* createClassD() { _called = true; return PtrRegistry::registerPtr(new ClassD); } + ClassA* createClassCAsA() { _called = true; return PtrRegistry::registerPtr(new ClassC); } + ClassB* createClassCAsB() { _called = true; return PtrRegistry::registerPtr(new ClassC); } + ClassA* createClassDAsA() { _called = true; return PtrRegistry::registerPtr(new ClassD); } + ClassB* createClassDAsB() { _called = true; return PtrRegistry::registerPtr(new ClassD); } QColor setAutoConvertColor(const QColor& color) { _called = true; return color; } QBrush setAutoConvertBrush(const QBrush& brush) { _called = true; return brush; } @@ -562,7 +599,7 @@ class PythonQtTestSignalHandler : public QObject private Q_SLOTS: void initTestCase(); - + void cleanupTestCase(); void testSignalHandler(); void testRecursiveSignalHandler(); @@ -577,9 +614,8 @@ class PythonQtTestSignalHandlerHelper : public QObject Q_OBJECT public: - PythonQtTestSignalHandlerHelper(PythonQtTestSignalHandler* test) { - _test = test; - } + PythonQtTestSignalHandlerHelper(PythonQtTestSignalHandler* test): _test(test) + {} public Q_SLOTS: void setPassed() { _passed = true; } @@ -616,4 +652,18 @@ public Q_SLOTS: PythonQtTestSignalHandler* _test; }; + +inline void cleanupPtr() +{ + PtrRegistry::clear(); + PtrRegistry::clear(); + PtrRegistry::clear(); + PtrRegistry::clear(); + PtrRegistry::clear(); + PtrRegistry::clear(); + PtrRegistry::clear(); + PtrRegistry::clear(); + PtrRegistry::clear(); +} + #endif