summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorJøger Hansegård <joger.hansegard@qt.io>2023-06-13 20:59:26 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2023-06-19 10:43:40 +0000
commitf4f797843a7b61c547c44598fffb8a738e2b1098 (patch)
tree741e483498fbc4d6d9db5887b008cb236900011a /tests
parentcdeb330caa391c61df00d18d3fe4489a63e1e105 (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.txt3
-rw-r--r--tests/auto/conversion/comutil_p.h304
-rw-r--r--tests/auto/conversion/testutil_p.h98
-rw-r--r--tests/auto/conversion/tst_conversion.cpp296
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"