diff options
author | Jøger Hansegård <joger.hansegard@qt.io> | 2023-06-13 20:59:26 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2023-06-19 10:43:40 +0000 |
commit | f4f797843a7b61c547c44598fffb8a738e2b1098 (patch) | |
tree | 741e483498fbc4d6d9db5887b008cb236900011a /tests | |
parent | cdeb330caa391c61df00d18d3fe4489a63e1e105 (diff) |
Improve test coverage of COM VARIANT/QVariant conversions
The code responsible for converting between COM VARIANT and QVariant is
complex, and having better test coverage can help prevent regressions
while fixing bugs.
Task-number: QTBUG-111191
Change-Id: I804175cda91b77f95cf7ede03717f3a812d636bf
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
(cherry picked from commit c5bb9b1051084bbe65ca5e8bf184e399434f8513)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/auto/conversion/CMakeLists.txt | 3 | ||||
-rw-r--r-- | tests/auto/conversion/comutil_p.h | 304 | ||||
-rw-r--r-- | tests/auto/conversion/testutil_p.h | 98 | ||||
-rw-r--r-- | tests/auto/conversion/tst_conversion.cpp | 296 |
4 files changed, 691 insertions, 10 deletions
diff --git a/tests/auto/conversion/CMakeLists.txt b/tests/auto/conversion/CMakeLists.txt index 9582dd5..f92afac 100644 --- a/tests/auto/conversion/CMakeLists.txt +++ b/tests/auto/conversion/CMakeLists.txt @@ -8,7 +8,10 @@ qt_internal_add_test(conversion SOURCES tst_conversion.cpp + INCLUDE_DIRECTORIES + ../../../src/activeqt/shared/ LIBRARIES Qt::AxContainer Qt::Gui + Qt::AxBasePrivate ) diff --git a/tests/auto/conversion/comutil_p.h b/tests/auto/conversion/comutil_p.h new file mode 100644 index 0000000..ac39c91 --- /dev/null +++ b/tests/auto/conversion/comutil_p.h @@ -0,0 +1,304 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef COMUTIL_P_H +#define COMUTIL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QtGlobal> +#include <comdef.h> +#include <type_traits> +#include <oleauto.h> +#include <wrl/client.h> + +using Microsoft::WRL::ComPtr; + +template<typename T> +ComPtr<T> makeComObject() +{ + ComPtr<T> ptr; + *ptr.GetAddressOf() = new T; + return ptr; +} + +class ComBstr +{ +public: + ComBstr() = default; + + ComBstr(decltype(nullptr)) { } + + ~ComBstr() { ::SysFreeString(m_str); } + + explicit ComBstr(const wchar_t *str) noexcept + { + if (!str) + return; + + m_str = ::SysAllocString(str); + Q_ASSERT(m_str); + } + + ComBstr(const ComBstr &src) noexcept + { + if (!src.m_str) + return; + + m_str = ::SysAllocStringByteLen(reinterpret_cast<char *>(src.m_str), + ::SysStringByteLen(m_str)); + Q_ASSERT(m_str); + } + + ComBstr(ComBstr &&src) noexcept : m_str{ src.m_str } { src.m_str = nullptr; } + + ComBstr &operator=(const ComBstr &rhs) noexcept + { + if (&rhs == this) + return *this; + + clear(); + + m_str = rhs.copy(); + + return *this; + } + + ComBstr &operator=(ComBstr &&rhs) noexcept + { + if (&rhs == this) + return *this; + + clear(); + + m_str = rhs.m_str; + rhs.m_str = nullptr; + + return *this; + } + + [[nodiscard]] BSTR copy() const + { + if (!m_str) + return nullptr; + + return ::SysAllocStringByteLen(reinterpret_cast<char *>(m_str), ::SysStringByteLen(m_str)); + } + +private: + void clear() { ::SysFreeString(m_str); } + + BSTR m_str = nullptr; +}; + +template<typename T> +constexpr VARTYPE ValueType() +{ + using ValueType = std::remove_cv_t<std::remove_pointer_t<T>>; + + constexpr VARTYPE maybeByref = std::is_pointer_v<T> ? VT_BYREF : VT_EMPTY; + if constexpr (std::is_same_v<ValueType, bool>) + return VT_BOOL | maybeByref; + else if constexpr (std::is_same_v<ValueType, char>) + return VT_I1 | maybeByref; + else if constexpr (std::is_same_v<ValueType, unsigned char>) + return VT_UI1 | maybeByref; + else if constexpr (std::is_same_v<ValueType, short>) + return VT_I2 | maybeByref; + else if constexpr (std::is_same_v<ValueType, unsigned short>) + return VT_UI2 | maybeByref; + else if constexpr (std::is_same_v<ValueType, int>) + return VT_I4 | maybeByref; + else if constexpr (std::is_same_v<ValueType, unsigned int>) + return VT_UI4 | maybeByref; + else if constexpr (std::is_same_v<ValueType, long long>) + return VT_I8 | maybeByref; + else if constexpr (std::is_same_v<ValueType, unsigned long long>) + return VT_UI8 | maybeByref; + else if constexpr (std::is_same_v<ValueType, float>) + return VT_R4 | maybeByref; + else if constexpr (std::is_same_v<ValueType, double>) + return VT_R8 | maybeByref; + else if constexpr (std::is_same_v<const ValueType *, const wchar_t *>) + return VT_BSTR; + else if constexpr (std::is_base_of_v<IDispatch, ValueType>) + return VT_DISPATCH; + else if constexpr (std::is_base_of_v<IUnknown, ValueType>) + return VT_UNKNOWN; + else + return VT_EMPTY; +}; + +template<typename T> +constexpr auto ValueField() +{ + using Type = std::remove_const_t<T>; + if constexpr (std::is_same_v<Type, bool>) + return &VARIANT::boolVal; + else if constexpr (std::is_same_v<Type, bool *>) + return &VARIANT::pboolVal; + else if constexpr (std::is_same_v<Type, char>) + return &VARIANT::cVal; + else if constexpr (std::is_same_v<Type, char *>) + return &VARIANT::pcVal; + else if constexpr (std::is_same_v<Type, unsigned char>) + return &VARIANT::bVal; + else if constexpr (std::is_same_v<Type, unsigned char *>) + return &VARIANT::pbVal; + else if constexpr (std::is_same_v<Type, short>) + return &VARIANT::iVal; + else if constexpr (std::is_same_v<Type, short *>) + return &VARIANT::piVal; + else if constexpr (std::is_same_v<Type, unsigned short>) + return &VARIANT::uiVal; + else if constexpr (std::is_same_v<Type, unsigned short *>) + return &VARIANT::puiVal; + else if constexpr (std::is_same_v<Type, int>) + return &VARIANT::intVal; + else if constexpr (std::is_same_v<Type, int *>) + return &VARIANT::pintVal; + else if constexpr (std::is_same_v<Type, unsigned int>) + return &VARIANT::uintVal; + else if constexpr (std::is_same_v<Type, unsigned int *>) + return &VARIANT::puintVal; + else if constexpr (std::is_same_v<Type, long long>) + return &VARIANT::llVal; + else if constexpr (std::is_same_v<Type, long long *>) + return &VARIANT::pllVal; + else if constexpr (std::is_same_v<Type, unsigned long long>) + return &VARIANT::ullVal; + else if constexpr (std::is_same_v<Type, unsigned long long *>) + return &VARIANT::pullVal; + else if constexpr (std::is_same_v<Type, float>) + return &VARIANT::fltVal; + else if constexpr (std::is_same_v<Type, float *>) + return &VARIANT::pfltVal; + else if constexpr (std::is_same_v<Type, double>) + return &VARIANT::dblVal; + else if constexpr (std::is_same_v<Type, double *>) + return &VARIANT::pdblVal; + else if constexpr (std::is_same_v<T, const wchar_t *>) + return &VARIANT::bstrVal; + else if constexpr (std::is_base_of_v<IDispatch, std::remove_pointer_t<Type>>) + return &VARIANT::pdispVal; + else if constexpr (std::is_base_of_v<IUnknown, std::remove_pointer_t<Type>>) + return &VARIANT::punkVal; +} + +class ComVariant : public tagVARIANT +{ +public: + ComVariant() noexcept : VARIANT{} { ::VariantInit(this); } + + ~ComVariant() + { + const HRESULT hr = ::VariantClear(this); + Q_ASSERT(hr == S_OK); + Q_UNUSED(hr) + } + + ComVariant(const ComVariant &src) : ComVariant() { copy(&src); } + ComVariant(const VARIANT &src) noexcept : ComVariant() { copy(&src); } + + template<typename T> + ComVariant(const T &value) : ComVariant() + { + assign(value); + } + + ComVariant &operator=(const ComVariant &rhs) noexcept + { + if (this == &rhs) + return *this; + + clear(); + copy(&rhs); + + return *this; + } + + template<typename T> + ComVariant &operator=(const T &rhs) + { + assign(rhs); + return *this; + } + + void clear() + { + const HRESULT hr = ::VariantClear(this); + + Q_ASSERT(hr == S_OK); + Q_UNUSED(hr) + } + + void copy(const VARIANT *src) + { + vt = VT_EMPTY; + const HRESULT hr = ::VariantCopy(this, const_cast<VARIANT *>(src)); + + Q_ASSERT(hr == S_OK); + Q_UNUSED(hr) + } + + bool operator==(const ComVariant &rhs) const + { + auto *lhs = const_cast<tagVARIANT *>(static_cast<const tagVARIANT *>(this)); + auto *other = const_cast<tagVARIANT *>(static_cast<const tagVARIANT *>(&rhs)); + return compare(lhs, other, LOCALE_USER_DEFAULT, 0) == static_cast<HRESULT>(VARCMP_EQ); + } + +private: + template<typename T> + void assign(const T &value) + { + constexpr VARTYPE valueType = ValueType<T>(); + static_assert(valueType != VT_EMPTY, "Invalid type for ComVariant"); + + vt = valueType; + + constexpr auto VARIANT::*field = ValueField<T>(); + if constexpr (valueType == VT_BSTR) + this->*field = ComBstr{ value }.copy(); + else + this->*field = value; + + if constexpr (valueType == VT_UNKNOWN || valueType == VT_DISPATCH) + value->AddRef(); + } + + static HRESULT compare(VARIANT *lhs, VARIANT *rhs, LCID lcid, ULONG flags) + { + // Workaround missing support for VT_I1, VT_UI2, VT_UI4 and VT_UI8 in VarCmp + switch (lhs->vt) { + case VT_I1: + if (lhs->cVal == rhs->cVal) + return VARCMP_EQ; + return lhs->cVal > rhs->cVal ? VARCMP_GT : VARCMP_LT; + case VT_UI2: + if (lhs->uiVal == rhs->uiVal) + return VARCMP_EQ; + return lhs->uiVal > rhs->uiVal ? VARCMP_GT : VARCMP_LT; + case VT_UI4: + if (lhs->uintVal == rhs->uintVal) + return VARCMP_EQ; + return lhs->uintVal > rhs->uintVal ? VARCMP_GT : VARCMP_LT; + case VT_UI8: + if (lhs->ullVal == rhs->ullVal) + return VARCMP_EQ; + return lhs->ullVal > rhs->ullVal ? VARCMP_GT : VARCMP_LT; + } + return ::VarCmp(lhs, rhs, lcid, flags); + } +}; + +#endif diff --git a/tests/auto/conversion/testutil_p.h b/tests/auto/conversion/testutil_p.h new file mode 100644 index 0000000..4521811 --- /dev/null +++ b/tests/auto/conversion/testutil_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TESTUTIL_P_H +#define TESTUTIL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qtcore/qt_windows.h> +#include <qtcore/qtglobal> +#include <wrl/client.h> +#include <atomic> + +using Microsoft::WRL::ComPtr; + +template<typename T> +struct ComBase : T +{ + ~ComBase() { Q_ASSERT(m_refCount == 0); } + + ULONG AddRef() final + { + return m_refCount++; + } + + ULONG Release() final + { + const int refCount = --m_refCount; + if (refCount == 0) + delete this; + return refCount; + } + + std::atomic_int m_refCount = 1u; +}; + +struct IUnknownStub : ComBase<IUnknown> +{ + HRESULT QueryInterface(const IID &riid, void **ppvObject) override + { + if (!ppvObject) + return E_POINTER; + + if (riid == IID_IUnknown) { + *ppvObject = this; + return S_OK; + } + + return E_NOINTERFACE; + } +}; + +struct IDispatchStub : ComBase<IDispatch> +{ + HRESULT QueryInterface(const IID &riid, void **ppvObject) override + { + if (!ppvObject) + return E_POINTER; + + if (riid == IID_IUnknown || riid == IID_IDispatch) { + *ppvObject = this; + return S_OK; + } + + return E_NOINTERFACE; + } + + HRESULT GetTypeInfoCount(UINT * /*pctinfo*/) override { return E_NOTIMPL; } + + HRESULT GetTypeInfo(UINT /*iTInfo*/, LCID /*lcid*/, ITypeInfo ** /*ppTInfo*/) override + { + return E_NOTIMPL; + } + + HRESULT GetIDsOfNames(const IID & /*riid*/, LPOLESTR * /*rgszNames*/, UINT /*cNames*/, + LCID /*lcid*/, DISPID * /*rgDispId*/) override + { + return E_NOTIMPL; + } + + HRESULT Invoke(DISPID /*dispIdMember*/, const IID & /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, + DISPPARAMS * /*pDispParams*/, VARIANT * /*pVarResult*/, + EXCEPINFO * /*pExcepInfo*/, UINT * /*puArgErr*/) override + { + return E_NOTIMPL; + } +}; + +#endif diff --git a/tests/auto/conversion/tst_conversion.cpp b/tests/auto/conversion/tst_conversion.cpp index aa08855..251854b 100644 --- a/tests/auto/conversion/tst_conversion.cpp +++ b/tests/auto/conversion/tst_conversion.cpp @@ -1,24 +1,23 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "comutil_p.h" +#include "testutil_p.h" + +#include <qaxtypes_p.h> +#include <ActiveQt/qaxobject.h> + #include <QtTest/QtTest> #include <QtCore/QVariant> #include <QtCore/QDateTime> #include <QtCore/QMetaType> #include <qt_windows.h> +#include <wrl/client.h> -QT_BEGIN_NAMESPACE - -// Conversion functions from statically linked library (axtypes.h) -bool QVariantToVARIANT_container(const QVariant &var, VARIANT &arg, - const QByteArray &typeName = QByteArray(), - bool out = false); +using Microsoft::WRL::ComPtr; -QVariant VARIANTToQVariant_container(const VARIANT &arg, const QByteArray &typeName, - int type = 0); - -QT_END_NAMESPACE +QT_BEGIN_NAMESPACE class tst_Conversion : public QObject { @@ -27,6 +26,35 @@ class tst_Conversion : public QObject private slots: void conversion_data(); void conversion(); + + void VARIANTToQVariant_ReturnsBool_WhenCalledWithVariantBool(); + void QVariantToVARIANT_ReturnsVariantBool_WhenCalledWithBool(); + + void VARIANTToQVariant_ReturnsString_WhenCalledWithString_data(); + void VARIANTToQVariant_ReturnsString_WhenCalledWithString(); + + void QVariantToVARIANT_ReturnsString_WhenCalledWithString_data(); + void QVariantToVARIANT_ReturnsString_WhenCalledWithString(); + + void VARIANTToQVariant_ReturnsCopyOfValue_WhenCalledWithMaxPossibleValueOrPointer_data(); + void VARIANTToQVariant_ReturnsCopyOfValue_WhenCalledWithMaxPossibleValueOrPointer(); + + void QVariantToVARIANT_ReturnsCopyOfValue_WhenCalledWithMaxPossibleValue_data(); + void QVariantToVARIANT_ReturnsCopyOfValue_WhenCalledWithMaxPossibleValue(); + + void VARIANTToQVariant_DoesNotIncreaseRefCount_WhenGivenAnIUnknown(); + void QVariantToVARIANT_RecoversIUnknown_WhenQVariantHasIUnknown(); + + void VARIANTToQVariant_DoesNotIncreaseRefCount_WhenGivenAnIDispatch(); + void QVariantToVARIANT_RecoversIDispatch_WhenQVariantHasIDispatch(); + + void VARIANTToQVariant_IncreasesRefCount_WhenCalledWithQVariantTypeName(); + + void ObserveThat_VARIANTToQVariant_ReturnsEmptyQVariant_WhenWrappingIDispatchInQAxObjectPtr(); + +private: + template<typename T> + static void addScalarMaxValueRow(); }; enum Mode { @@ -170,5 +198,253 @@ void tst_Conversion::conversion() delete variant.pullVal; } +void tst_Conversion::VARIANTToQVariant_ReturnsBool_WhenCalledWithVariantBool() +{ + { + const ComVariant v = true; + const QVariant result = VARIANTToQVariant(v, "canBeAnything"); + QVERIFY(result == true); + } + + { + const ComVariant v = false; + const QVariant result = VARIANTToQVariant(v, "canBeAnything"); + QVERIFY(result == false); + } +} + +void tst_Conversion::QVariantToVARIANT_ReturnsVariantBool_WhenCalledWithBool() +{ + { + const QVariant v = true; + ComVariant result; + QVERIFY(QVariantToVARIANT(v, result)); + QVERIFY(result.vt == VT_BOOL); + QVERIFY(result.boolVal == VARIANT_TRUE); + } + + { + const QVariant v = false; + ComVariant result; + QVERIFY(QVariantToVARIANT(v, result)); + QVERIFY(result.vt == VT_BOOL); + QVERIFY(result.boolVal == VARIANT_FALSE); + } +} + +void tst_Conversion::VARIANTToQVariant_ReturnsString_WhenCalledWithString_data() +{ + QTest::addColumn<QString>("text"); + QTest::newRow("empty") << QString{ "" }; + QTest::newRow("nonempty") << QString{ "Some Latin 1 text" }; +} + +void tst_Conversion::VARIANTToQVariant_ReturnsString_WhenCalledWithString() +{ + QFETCH(QString, text); + + const ComVariant comVariant = text.toStdWString().c_str(); + const QVariant actual = VARIANTToQVariant(comVariant, {}); + + QCOMPARE(actual, text); +} + +void tst_Conversion::QVariantToVARIANT_ReturnsString_WhenCalledWithString_data() +{ + QTest::addColumn<QString>("text"); + QTest::newRow("empty") << QString{ "" }; + QTest::newRow("nonempty") << QString{ "Some Latin 1 text" }; +} + +void tst_Conversion::QVariantToVARIANT_ReturnsString_WhenCalledWithString() +{ + QFETCH(QString, text); + + ComVariant comVariant; + QVERIFY(QVariantToVARIANT(text, comVariant)); + + const QString actual = QString::fromWCharArray(comVariant.bstrVal); + + QCOMPARE(actual, text); +} + +void tst_Conversion:: + VARIANTToQVariant_ReturnsCopyOfValue_WhenCalledWithMaxPossibleValueOrPointer_data() +{ + QTest::addColumn<ComVariant>("comVariant"); + QTest::addColumn<QVariant>("qvariant"); + + addScalarMaxValueRow<char>(); + addScalarMaxValueRow<unsigned char>(); + addScalarMaxValueRow<short>(); + addScalarMaxValueRow<unsigned short>(); + addScalarMaxValueRow<int>(); + addScalarMaxValueRow<unsigned int>(); + addScalarMaxValueRow<long long>(); + addScalarMaxValueRow<unsigned long long>(); + addScalarMaxValueRow<float>(); + addScalarMaxValueRow<double>(); + + addScalarMaxValueRow<char *>(); + addScalarMaxValueRow<unsigned char *>(); + addScalarMaxValueRow<short *>(); + addScalarMaxValueRow<unsigned short *>(); + addScalarMaxValueRow<int *>(); + addScalarMaxValueRow<unsigned int *>(); + addScalarMaxValueRow<long long *>(); + addScalarMaxValueRow<unsigned long long *>(); + addScalarMaxValueRow<float *>(); + addScalarMaxValueRow<double *>(); +} + +void tst_Conversion::VARIANTToQVariant_ReturnsCopyOfValue_WhenCalledWithMaxPossibleValueOrPointer() +{ + QFETCH(ComVariant, comVariant); + QFETCH(QVariant, qvariant); + + const QVariant actual = VARIANTToQVariant(comVariant, qvariant.typeName()); + + QCOMPARE(actual, qvariant); +} + +void tst_Conversion::QVariantToVARIANT_ReturnsCopyOfValue_WhenCalledWithMaxPossibleValue_data() +{ + QTest::addColumn<ComVariant>("comVariant"); + QTest::addColumn<QVariant>("qvariant"); + + addScalarMaxValueRow<int>(); + addScalarMaxValueRow<unsigned int>(); + addScalarMaxValueRow<long long>(); + addScalarMaxValueRow<unsigned long long>(); + addScalarMaxValueRow<float>(); + addScalarMaxValueRow<double>(); +} + +void tst_Conversion::QVariantToVARIANT_ReturnsCopyOfValue_WhenCalledWithMaxPossibleValue() +{ + QFETCH(ComVariant, comVariant); + QFETCH(QVariant, qvariant); + + ComVariant actual; + QVERIFY(QVariantToVARIANT(qvariant, actual)); + + QCOMPARE(actual, comVariant); +} + +void tst_Conversion::VARIANTToQVariant_DoesNotIncreaseRefCount_WhenGivenAnIUnknown() +{ + const auto stub = makeComObject<IUnknownStub>(); + + const ComVariant value = stub.Get(); + + QVERIFY(stub->m_refCount == 2u); + + const QVariant qVariant = VARIANTToQVariant(value, {}); + + QVERIFY(stub->m_refCount == 2u); + + Q_UNUSED(qVariant); +} + +void tst_Conversion::QVariantToVARIANT_RecoversIUnknown_WhenQVariantHasIUnknown() +{ + const auto stub = makeComObject<IUnknownStub>(); + const ComVariant value = stub.Get(); + + const QVariant qvar = VARIANTToQVariant(value, {}); + + ComVariant comVariant; + QVERIFY(QVariantToVARIANT(qvar, comVariant)); + + QCOMPARE(stub->m_refCount, 3u); + + const ComPtr<IUnknown> recovered = comVariant.punkVal; + + QCOMPARE(recovered, stub); +} + +void tst_Conversion::VARIANTToQVariant_DoesNotIncreaseRefCount_WhenGivenAnIDispatch() +{ + const auto stub = makeComObject<IDispatchStub>(); + + const ComVariant value = stub.Get(); + + QCOMPARE(stub->m_refCount, 2u); + const QVariant qVariant = VARIANTToQVariant(value, "IDispatch*"); + + QCOMPARE(stub->m_refCount, 2u); + + Q_UNUSED(qVariant); +} + +struct IDispatchFixture +{ + const ComPtr<IDispatchStub> m_iDispatchStub = makeComObject<IDispatchStub>(); + const ComVariant m_comVariant = m_iDispatchStub.Get(); + const QVariant m_qVariant = VARIANTToQVariant(m_comVariant, "IDispatch*"); +}; + +void tst_Conversion::QVariantToVARIANT_RecoversIDispatch_WhenQVariantHasIDispatch() +{ + const IDispatchFixture testFixture; + QCOMPARE(testFixture.m_iDispatchStub->m_refCount, 2u); + + ComVariant comVariant; + QVERIFY(QVariantToVARIANT(testFixture.m_qVariant, comVariant)); + + QCOMPARE(testFixture.m_iDispatchStub->m_refCount, 3u); + + const ComPtr<IUnknown> recovered = comVariant.pdispVal; + + QCOMPARE(recovered, testFixture.m_iDispatchStub); +} + +void tst_Conversion::VARIANTToQVariant_IncreasesRefCount_WhenCalledWithQVariantTypeName() +{ + const IDispatchFixture testFixture; + QCOMPARE(testFixture.m_iDispatchStub->m_refCount, 2u); + + QVariant qVariant = VARIANTToQVariant(testFixture.m_comVariant, "QVariant"); + qVariant = {}; + + // Observe that IDispatch interface is leaked here, since + // the QVariant destructor does not decrement the refcount + QCOMPARE(testFixture.m_iDispatchStub->m_refCount, 3u); + + // Workaround to ensure cleanup + testFixture.m_iDispatchStub->Release(); +} + +void tst_Conversion::ObserveThat_VARIANTToQVariant_ReturnsEmptyQVariant_WhenWrappingIDispatchInQAxObjectPtr() +{ + const IDispatchFixture testFixture; + QCOMPARE(testFixture.m_iDispatchStub->m_refCount, 2u); + + qRegisterMetaType<QAxObject *>("QAxObject*"); + qRegisterMetaType<QAxObject>("QAxObject"); + + const QVariant qVariant = VARIANTToQVariant(testFixture.m_comVariant, "QAxObject*"); + QVERIFY(qVariant.isNull()); +} + +template<typename T> +void tst_Conversion::addScalarMaxValueRow() +{ + using ValueType = std::remove_pointer_t<T>; + static ValueType v = std::numeric_limits<ValueType>::max(); + + ComVariant comVariant; + if constexpr (std::is_pointer_v<T>) + comVariant = &v; + else + comVariant = v; + + const char *typeName = QMetaType::fromType<T>().name(); + QTest::newRow(typeName) << comVariant << QVariant{ v }; +} + QTEST_MAIN(tst_Conversion) + +QT_END_NAMESPACE + #include "tst_conversion.moc" |