diff options
author | Jøger Hansegård <joger.hansegard@qt.io> | 2024-05-13 23:02:10 +0200 |
---|---|---|
committer | Jøger Hansegård <joger.hansegard@qt.io> | 2024-05-17 13:46:39 +0200 |
commit | 377c95c4a09f7c363c5c609c2bf7907373c39925 (patch) | |
tree | c78731796a1b6a9bcc68d086ffe060f1f9ed1676 | |
parent | 2013424a3eaff829891eb6c7bdafb3b971fb6cd0 (diff) |
Add tests framework for verifying calls to native COM servers
Testing ActiveQt behavior against native COM servers is currently a hard
and manual process because we don't have a customizable COM server to
test against.
This patch introduces a test COM server that can be extended to cover
multiple test scenarios where we want to verify that the native COM
server (not Active Qt) is called with the expected inputs and outputs.
We use registry free COM to activate the server, to make it suitable
for auto-test where we don't necessarily have admin access to register
COM dlls.
The idea of the test framework is that any calls through QAxObject
results in a COM call to the test server. The arguments passed by Active
Qt to the test server is then echoed back to test observer that receives
the function arguments.
In this first test, the test server has a single function that takes a
VARIANT argument. This way we can add tests that verifies that Active Qt
converts QVariant to VARIANT as we expect, while testing the entire
pipeline. This can potentially be used in addition to, or as a
replacement of existing QVariant to VARIANT conversion tests.
To enable automatically regenerating the QAxObject wrapper when the type
library changes, the qt6_target_typelibs function is updated to declare
its input dependency to CMake.
Change-Id: I503a9f61cf44294d1083761cab6a7be3ce1b278f
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
-rw-r--r-- | src/activeqt/container/Qt6AxContainerMacros.cmake | 1 | ||||
-rw-r--r-- | tests/auto/CMakeLists.txt | 6 | ||||
-rw-r--r-- | tests/auto/conversion/comutil_p.h | 14 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/CMakeLists.txt | 33 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/testserver/CMakeLists.txt | 47 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/testserver/testserver.cpp | 132 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/testserver/testserver.def | 3 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/testserver/testserver.h | 43 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/testserver/testserver.rc | 1 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/testserver/testserverlib.idl | 33 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/tst_qaxobjectcom.cpp | 87 | ||||
-rw-r--r-- | tests/auto/qaxobjectcom/tst_qaxobjectcom.exe.manifest.in | 12 |
12 files changed, 412 insertions, 0 deletions
diff --git a/src/activeqt/container/Qt6AxContainerMacros.cmake b/src/activeqt/container/Qt6AxContainerMacros.cmake index 4d3bd7e..78e16de 100644 --- a/src/activeqt/container/Qt6AxContainerMacros.cmake +++ b/src/activeqt/container/Qt6AxContainerMacros.cmake @@ -88,6 +88,7 @@ C indentifier") "${libpath}" -o "${out_filebasepath}" ${extra_args} DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::dumpcpp + MAIN_DEPENDENCY ${libpath} WORKING_DIRECTORY "${output_directory}" COMMENT "Generate type lib sources ${out_header} ${out_source}..." ) diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 6bcc7de..2f384ae 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -6,6 +6,12 @@ add_subdirectory(qaxobject) add_subdirectory(dumpcpp) add_subdirectory(cmake) add_subdirectory(qbstr) + +find_program(midl midl.exe) +if (midl) + add_subdirectory(qaxobjectcom) +endif() + if(NOT GCC) add_subdirectory(qaxscript) add_subdirectory(qaxscriptmanager) diff --git a/tests/auto/conversion/comutil_p.h b/tests/auto/conversion/comutil_p.h index 76cd4ac..e722ed5 100644 --- a/tests/auto/conversion/comutil_p.h +++ b/tests/auto/conversion/comutil_p.h @@ -140,6 +140,7 @@ public: ComVariant(const ComVariant &src) : ComVariant() { copy(&src); } ComVariant(const VARIANT &src) noexcept : ComVariant() { copy(&src); } + ComVariant(const QBStr &src) noexcept : ComVariant() { copy(src.bstr()); } template<typename T> ComVariant(const T &value) : ComVariant() @@ -182,6 +183,19 @@ public: Q_UNUSED(hr) } + void copy(const BSTR &src) + { + vt = VT_EMPTY; + if (!src) + return; + + vt = VT_BSTR; + bstrVal = ::SysAllocStringByteLen(reinterpret_cast<const char *>(src), + ::SysStringByteLen(src)); + + Q_ASSERT(bstrVal); + } + bool operator==(const ComVariant &rhs) const { auto *lhs = const_cast<tagVARIANT *>(static_cast<const tagVARIANT *>(this)); diff --git a/tests/auto/qaxobjectcom/CMakeLists.txt b/tests/auto/qaxobjectcom/CMakeLists.txt new file mode 100644 index 0000000..09ce6a1 --- /dev/null +++ b/tests/auto/qaxobjectcom/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Configure the manifest file to point to the com_server dll +# because the dll name is different in debug and release +set(TestServerDll "TestServer${CMAKE_DEBUG_POSTFIX}.dll") +configure_file( + tst_qaxobjectcom.exe.manifest.in + tst_qaxobjectcom.exe.manifest +) + +qt_internal_add_test(tst_qaxobjectcom + SOURCES + tst_qaxobjectcom.cpp + ${CMAKE_CURRENT_BINARY_DIR}/tst_qaxobjectcom.exe.manifest + LIBRARIES + Qt::AxContainer + Qt::CorePrivate + Qt::AxBasePrivate +) + +# Embed the manifest file into the binary for registry free COM +target_link_options(tst_qaxobjectcom PRIVATE "/MANIFEST") + +# Build the com_server automatically +add_subdirectory(testserver) +add_dependencies(tst_qaxobjectcom testserver) + +# Generate qaxobject COM wrapper for our COM test server +qt6_target_typelibs(tst_qaxobjectcom + LIBRARIES + ${CMAKE_CURRENT_BINARY_DIR}/testserver/testserverlib.tlb +) diff --git a/tests/auto/qaxobjectcom/testserver/CMakeLists.txt b/tests/auto/qaxobjectcom/testserver/CMakeLists.txt new file mode 100644 index 0000000..f05ebc0 --- /dev/null +++ b/tests/auto/qaxobjectcom/testserver/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Configure file allow execute_process to detect changes to idl file. +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/testserverlib.idl + ${CMAKE_CURRENT_BINARY_DIR}/testserverlib.idl + COPYONLY +) + +# Run midl at configure time because qt6_target_typelibs checks +# for presence of tlb file at configure time +execute_process( + COMMAND + midl /h testserverlib.h /tlb testserverlib.tlb testserverlib.idl + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND_ERROR_IS_FATAL ANY +) + +qt_add_library(testserver +SHARED + testserver.h + testserver.cpp + testserver.def + testserverlib.idl + testserver.rc +) + +# Trigger rebuild of resources if tlb file changed +set_property( + SOURCE + testserver.rc + PROPERTY + OBJECT_DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/testserverlib.tlb +) + +target_include_directories(testserver + PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} +) + +target_link_libraries(testserver + PRIVATE + Qt::CorePrivate + Qt::AxBasePrivate +) diff --git a/tests/auto/qaxobjectcom/testserver/testserver.cpp b/tests/auto/qaxobjectcom/testserver/testserver.cpp new file mode 100644 index 0000000..1145565 --- /dev/null +++ b/tests/auto/qaxobjectcom/testserver/testserver.cpp @@ -0,0 +1,132 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QString> +#include <QUuid> +#include <QtCore/qassert.h> +#include <QtCore/qdebug.h> +#include "testserverlib.h" +#include "testserver.h" + +extern "C" IMAGE_DOS_HEADER __ImageBase; // Trickery to get path to current dll +static long s_serverLock = 0; + +static const std::array<wchar_t, MAX_PATH> GetModuleFilename() +{ + std::array<wchar_t, MAX_PATH> filename; + const DWORD length = GetModuleFileName(reinterpret_cast<HMODULE>(&__ImageBase), filename.data(), + static_cast<DWORD>(filename.size())); + + if (0 == length || filename.size() == length) + return {}; + + return filename; +} + +// Create a dispatcher that implements IDispatch for the input owner +static ComPtr<IUnknown> CreateDispatcher(const QUuid &guid, IUnknown *owner) +{ + ComPtr<ITypeLib> typeLib; + if (LoadTypeLib(GetModuleFilename().data(), &typeLib) != S_OK) + { + qCritical("Failed to load type library"); + return {}; + } + + ComPtr<ITypeInfo> typeInfo; + if (typeLib->GetTypeInfoOfGuid(guid, &typeInfo) + != S_OK) { + qCritical("Failed to get type info"); + return {}; + } + + ComPtr<IUnknown> dispatcher; + if (CreateStdDispatch(owner, owner, typeInfo.Get(), &dispatcher) != S_OK) { + qCritical("Failed to create standard dispatch"); + return {}; + } + + return dispatcher; +} + +TestServer::TestServer() +{ + m_unkDisp = CreateDispatcher(__uuidof(IComServer), this); + ++s_serverLock; +} + +HRESULT __stdcall TestServer::QueryInterface(IID const &id, void **result) +{ + Q_ASSERT(result); + if (id == __uuidof(IDispatch)) { + ComPtr<IDispatch> disp; + m_unkDisp.As(&disp); + *result = disp.Detach(); + return S_OK; + } + return QComObject::QueryInterface(id, result); +} + +HRESULT TestServer::SetObserver(IUnknown* observer) +{ + const ComPtr<IUnknown> rcv{ observer }; + return rcv.As(&m_receiver); +} + +HRESULT TestServer::VariantIn(VARIANT v) +{ + return m_receiver->VariantIn(v); +} + +struct Factory : IClassFactory +{ + ULONG __stdcall AddRef() override { return 2; } + ULONG __stdcall Release() override { return 1; } + HRESULT __stdcall QueryInterface(IID const &id, void **result) override + { + Q_ASSERT(result); + + if (id == __uuidof(IClassFactory) || id == __uuidof(IUnknown)) { + *result = static_cast<IClassFactory *>(this); + return S_OK; + } + + *result = nullptr; + return E_NOINTERFACE; + } + + HRESULT __stdcall CreateInstance(IUnknown *outer, IID const &iid, void **result) override + { + Q_ASSERT(result); + Q_ASSERT(!outer); + + const ComPtr<TestServer> server{ new (std::nothrow) TestServer }; + return server->QueryInterface(iid, result); + } + + HRESULT __stdcall LockServer(BOOL lock) override + { + if (lock) + ++s_serverLock; + else + --s_serverLock; + + return S_OK; + } +}; + +HRESULT __stdcall DllGetClassObject(CLSID const &clsid, IID const &iid, void **result) +{ + if (__uuidof(TestServer) == clsid) { + static Factory farm; + + return farm.QueryInterface(iid, result); + } + + return CLASS_E_CLASSNOTAVAILABLE; +} + +HRESULT __stdcall DllCanUnloadNow() +{ + return s_serverLock ? S_FALSE : S_OK; +} diff --git a/tests/auto/qaxobjectcom/testserver/testserver.def b/tests/auto/qaxobjectcom/testserver/testserver.def new file mode 100644 index 0000000..56e1a71 --- /dev/null +++ b/tests/auto/qaxobjectcom/testserver/testserver.def @@ -0,0 +1,3 @@ +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE diff --git a/tests/auto/qaxobjectcom/testserver/testserver.h b/tests/auto/qaxobjectcom/testserver/testserver.h new file mode 100644 index 0000000..53663ef --- /dev/null +++ b/tests/auto/qaxobjectcom/testserver/testserver.h @@ -0,0 +1,43 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TEST_SERVER_H +#define TEST_SERVER_H + +#include <qt_windows.h> +#include "testserverlib.h" +#include <wrl/client.h> +#include <QtCore/private/qcomobject_p.h> +#include "../../conversion/comutil_p.h" + +using Microsoft::WRL::ComPtr; + +class __declspec(uuid("af732aba-95cf-4ee7-bd59-8f946b7f82e3")) +TestServer : public QComObject<IComServer> +{ +public: + TestServer(); + + HRESULT __stdcall QueryInterface(IID const &id, void **result) override; + HRESULT __stdcall SetObserver(IUnknown *observer) override; + HRESULT __stdcall VariantIn(VARIANT v) override; + +private: + ComPtr<IUnknown> m_unkDisp; + ComPtr<IComServer> m_receiver; +}; + +struct Receiver : public QComObject<IComServer> +{ + HRESULT SetObserver(IUnknown *observer) override { return E_NOTIMPL; } + + HRESULT VariantIn(VARIANT arg) override + { + lastArg = ComVariant{ arg }; + return S_OK; + } + + ComVariant lastArg{}; +}; + +#endif diff --git a/tests/auto/qaxobjectcom/testserver/testserver.rc b/tests/auto/qaxobjectcom/testserver/testserver.rc new file mode 100644 index 0000000..51dfeed --- /dev/null +++ b/tests/auto/qaxobjectcom/testserver/testserver.rc @@ -0,0 +1 @@ +1 TYPELIB "testserverlib.tlb" diff --git a/tests/auto/qaxobjectcom/testserver/testserverlib.idl b/tests/auto/qaxobjectcom/testserver/testserverlib.idl new file mode 100644 index 0000000..00d64ab --- /dev/null +++ b/tests/auto/qaxobjectcom/testserver/testserverlib.idl @@ -0,0 +1,33 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import "oaidl.idl"; +import "ocidl.idl"; + +[ + object, + dual, + uuid(f4811a11-3092-415e-a473-38c627544246), + oleautomation, + pointer_default(unique) +] +interface IComServer : IUnknown +{ + HRESULT SetObserver([in] IUnknown *observer); + HRESULT VariantIn([in] VARIANT arg); +}; + +[ + uuid(104d435f-7504-4786-aae7-b22745450c4c), + version(1.0), +] +library ComServerLib +{ + [ + uuid(af732aba-95cf-4ee7-bd59-8f946b7f82e3), + ] + coclass TestServer + { + interface IComServer; + }; +}; diff --git a/tests/auto/qaxobjectcom/tst_qaxobjectcom.cpp b/tests/auto/qaxobjectcom/tst_qaxobjectcom.cpp new file mode 100644 index 0000000..fec1d8d --- /dev/null +++ b/tests/auto/qaxobjectcom/tst_qaxobjectcom.cpp @@ -0,0 +1,87 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtTest/QtTest> +#include "testserverlib.h" +#include <testserver/testserverlib.h> +#include "testserver/testserver.h" +#include "../conversion/comutil_p.h" +#include <QtAxBase/private/qbstr_p.h> + +class tst_qaxobjectcom : public QObject +{ + Q_OBJECT +private slots: + void comObject_receivesVARIANT_whenCalledWithQVariant_data() + { + QTest::addColumn<QVariant>("value"); + QTest::addColumn<ComVariant>("expected"); + + QTest::addRow("bool") << QVariant{ true } << ComVariant{ VARIANT_TRUE }; + QTest::addRow("short") << QVariant{ static_cast<short>(3) } << ComVariant{ static_cast<int>(3) }; // TODO: Fixme + QTest::addRow("unsigned short") << QVariant{ static_cast<ushort>(3) } << ComVariant{ static_cast<int>(3) }; // TODO: Fixme + QTest::addRow("int") << QVariant{ 3 } << ComVariant{ 3 }; + QTest::addRow("unsigned int") << QVariant{ 3u } << ComVariant{ 3u }; + QTest::addRow("long long") << QVariant{ 3ll } << ComVariant{ 3ll }; + QTest::addRow("unsigned long long") << QVariant{ 3ull } << ComVariant{ 3ull }; + QTest::addRow("float") << QVariant{ 3.3f } << ComVariant{ 3.3f }; + QTest::addRow("double") << QVariant{ 3.3 } << ComVariant{ 3.3 }; + } + + void comObject_receivesVARIANT_whenCalledWithQVariant() + { + QFETCH(const QVariant, value); + QFETCH(const ComVariant, expected); + + // Arrange + ComServerLib::TestServer server; + const ComPtr<Receiver> observer = makeComObject<Receiver>(); + server.SetObserver(observer.Get()); + + // Act + server.VariantIn(value); + + // Assert + QCOMPARE_EQ(observer->lastArg, expected); + } + + void comObject_receivesVARIANT_whenCalledWithVariantStringList() + { + const QVariant value{ QList{ QVariant{ "A" }, QVariant{ "BC" } } }; + + // Arrange + ComServerLib::TestServer server; + const ComPtr<Receiver> observer = makeComObject<Receiver>(); + server.SetObserver(observer.Get()); + + // Act + server.VariantIn(value); + + // Assert + QVERIFY(V_ISARRAY(&observer->lastArg)); + + LPSAFEARRAY safeArray = V_ARRAY(&observer->lastArg); + + VARTYPE itemType; + if (SUCCEEDED(SafeArrayGetVartype(safeArray, &itemType))) + + QCOMPARE_EQ(itemType, VT_VARIANT); + + LPVOID data; + QCOMPARE_EQ(SafeArrayAccessData(safeArray, &data), S_OK); + + const auto *pItems = static_cast<const VARIANT*>(data); + ComVariant item1{ pItems[0] }; + ComVariant item2{ pItems[1] }; + + QCOMPARE_EQ(item1, ComVariant{ QBStr{ L"A" } }); + QCOMPARE_EQ(item2, ComVariant{ QBStr{ L"BC" } }); + + // end accessing data + QCOMPARE_EQ(SafeArrayUnaccessData(safeArray), S_OK); + + } +}; + +QTEST_MAIN(tst_qaxobjectcom) +#include "tst_qaxobjectcom.moc" diff --git a/tests/auto/qaxobjectcom/tst_qaxobjectcom.exe.manifest.in b/tests/auto/qaxobjectcom/tst_qaxobjectcom.exe.manifest.in new file mode 100644 index 0000000..7d6ef11 --- /dev/null +++ b/tests/auto/qaxobjectcom/tst_qaxobjectcom.exe.manifest.in @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> + <assemblyIdentity version="1.0.0.0" name="tst_qaxobjectcom.app"/> + <file name="testserver/${TestServerDll}"> + <comClass + description="Com Server" + clsid="{af732aba-95cf-4ee7-bd59-8f946b7f82e3}" + progid="Qt.TestServer" + threadingModel = "Both" + /> + </file> +</assembly> |