summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJøger Hansegård <joger.hansegard@qt.io>2024-05-13 23:02:10 +0200
committerJøger Hansegård <joger.hansegard@qt.io>2024-05-17 13:46:39 +0200
commit377c95c4a09f7c363c5c609c2bf7907373c39925 (patch)
treec78731796a1b6a9bcc68d086ffe060f1f9ed1676
parent2013424a3eaff829891eb6c7bdafb3b971fb6cd0 (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.cmake1
-rw-r--r--tests/auto/CMakeLists.txt6
-rw-r--r--tests/auto/conversion/comutil_p.h14
-rw-r--r--tests/auto/qaxobjectcom/CMakeLists.txt33
-rw-r--r--tests/auto/qaxobjectcom/testserver/CMakeLists.txt47
-rw-r--r--tests/auto/qaxobjectcom/testserver/testserver.cpp132
-rw-r--r--tests/auto/qaxobjectcom/testserver/testserver.def3
-rw-r--r--tests/auto/qaxobjectcom/testserver/testserver.h43
-rw-r--r--tests/auto/qaxobjectcom/testserver/testserver.rc1
-rw-r--r--tests/auto/qaxobjectcom/testserver/testserverlib.idl33
-rw-r--r--tests/auto/qaxobjectcom/tst_qaxobjectcom.cpp87
-rw-r--r--tests/auto/qaxobjectcom/tst_qaxobjectcom.exe.manifest.in12
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>