diff options
author | Miguel Costa <miguel.costa@qt.io> | 2023-05-17 17:58:22 +0200 |
---|---|---|
committer | Miguel Costa <miguel.costa@qt.io> | 2023-06-12 11:03:49 +0000 |
commit | a1c6a7c1d48120ee477817257fee38f8adbfadb8 (patch) | |
tree | e3f456f2f6da80e0028a2208622b42fdf9a5ae62 /tests | |
parent | 12f60e442ec2b333fd880382bc0b6c3fbab79388 (diff) |
Add test projects
tst_qtdotnet:
* Auto tests targeting the full Qt/.NET stack.
* Tests are implemented with Qt Test.
Test_Qt.DotNet.Adapter:
* Auto tests targeting the Qt/.NET Adapter module.
* Uses the .NET unit-test SDK.
Perf_Qt.DotNet.Adapter:
* Performance tests targeting the Qt/.NET Adapter module.
* Can be tested with the VS performance profiler.
FooLib:
* "Dummy" .NET library, used for testing purposes.
Change-Id: Iefcf5ce8e2479e4dd7bfcf3298792989b38cce1f
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/FooLib/FooClass.cs | 87 | ||||
-rw-r--r-- | tests/FooLib/FooLib.csproj | 16 | ||||
-rw-r--r-- | tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.cs | 10 | ||||
-rw-r--r-- | tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj | 20 | ||||
-rw-r--r-- | tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.cs | 21 | ||||
-rw-r--r-- | tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj | 25 | ||||
-rw-r--r-- | tests/tst_qtdotnet/foo.cpp | 70 | ||||
-rw-r--r-- | tests/tst_qtdotnet/foo.h | 56 | ||||
-rw-r--r-- | tests/tst_qtdotnet/stringbuilder.cpp | 43 | ||||
-rw-r--r-- | tests/tst_qtdotnet/stringbuilder.h | 34 | ||||
-rw-r--r-- | tests/tst_qtdotnet/tst_qtdotnet.cpp | 508 | ||||
-rw-r--r-- | tests/tst_qtdotnet/tst_qtdotnet.vcxproj | 225 | ||||
-rw-r--r-- | tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters | 52 | ||||
-rw-r--r-- | tests/tst_qtdotnet/uri.cpp | 244 | ||||
-rw-r--r-- | tests/tst_qtdotnet/uri.h | 89 |
15 files changed, 1500 insertions, 0 deletions
diff --git a/tests/FooLib/FooClass.cs b/tests/FooLib/FooClass.cs new file mode 100644 index 0000000..5182192 --- /dev/null +++ b/tests/FooLib/FooClass.cs @@ -0,0 +1,87 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace FooLib +{ + public interface IBarTransformation + { + string Transform(string bar); + } + + public class BarIdentity : IBarTransformation + { + public string Transform(string bar) => bar; + } + + public class Foo : INotifyPropertyChanged + { + public Foo(IBarTransformation barTransformation) + { + BarTransformation = barTransformation ?? new BarIdentity(); + } + + public Foo() : this(null) + { } + + private IBarTransformation BarTransformation { get; } + + public event PropertyChangedEventHandler PropertyChanged; + + private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private string bar; + public string Bar + { + get => bar; + set + { + bar = BarTransformation?.Transform(value) ?? value; + NotifyPropertyChanged(); + } + } + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.LPWStr)] + public delegate string FormatNumberDelegate( + [In, MarshalAs(UnmanagedType.LPWStr)] string format, int number); + + public static string FormatNumber(string format, int number) + { + return string.Format(format, number); + } + + public static int EntryPoint(IntPtr arg, int argLength) + { + return Convert.ToInt32(Marshal.PtrToStringUni(arg, argLength)); + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public class Date + { + [MarshalAs(UnmanagedType.LPWStr)] + public string Year = ""; + [MarshalAs(UnmanagedType.LPWStr)] + public string Month = ""; + [MarshalAs(UnmanagedType.LPWStr)] + public string Day = ""; + } + + [return: MarshalAs(UnmanagedType.LPWStr)] + public delegate string FormatDateDelegate( + [In, MarshalAs(UnmanagedType.LPWStr)] string format, [In] Date date); + + public static string FormatDate(string format, Date date) + { + return string.Format(format, date.Year, date.Month, date.Day); + } + } +} diff --git a/tests/FooLib/FooLib.csproj b/tests/FooLib/FooLib.csproj new file mode 100644 index 0000000..2b8cb94 --- /dev/null +++ b/tests/FooLib/FooLib.csproj @@ -0,0 +1,16 @@ +<!-- +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ +--> + +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>disable</Nullable> + </PropertyGroup> + +</Project> diff --git a/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.cs b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.cs new file mode 100644 index 0000000..67e2a88 --- /dev/null +++ b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.cs @@ -0,0 +1,10 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +using Qt.DotNet; + +#if DEBUG || TESTS +Adapter.Test(); +#endif diff --git a/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj new file mode 100644 index 0000000..f49a9b8 --- /dev/null +++ b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj @@ -0,0 +1,20 @@ +<!-- +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ +--> + +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net6.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <Configurations>Debug;Release;Tests</Configurations> + </PropertyGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj" /> + <ProjectReference Include="..\FooLib\FooLib.csproj" /> + </ItemGroup> +</Project> diff --git a/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.cs b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.cs new file mode 100644 index 0000000..dad1c86 --- /dev/null +++ b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.cs @@ -0,0 +1,21 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Qt.DotNet.Test +{ + [TestClass] + public class Test_QDotNetAdapter + { + [TestMethod] + public void BuiltInTest() + { +#if DEBUG || TESTS + Assert.IsTrue(Adapter.Test()); +#endif + } + } +} diff --git a/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj new file mode 100644 index 0000000..37742b4 --- /dev/null +++ b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj @@ -0,0 +1,25 @@ +<!-- +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ +--> + +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>disable</Nullable> + <IsPackable>false</IsPackable> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.8" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.8" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj" /> + <ProjectReference Include="..\FooLib\FooLib.csproj" /> + </ItemGroup> +</Project> diff --git a/tests/tst_qtdotnet/foo.cpp b/tests/tst_qtdotnet/foo.cpp new file mode 100644 index 0000000..8c8c3d3 --- /dev/null +++ b/tests/tst_qtdotnet/foo.cpp @@ -0,0 +1,70 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#include "foo.h" + +#include <qdotnetevent.h> + +struct FooPrivate final : QDotNetObject::IEventHandler +{ + Foo *q; + FooPrivate(Foo *q) : q(q) {} + + QDotNetFunction<Foo, IBarTransformation> ctor = nullptr; + + QDotNetFunction<QString> bar; + QDotNetFunction<void, QString> setBar; + + void handleEvent(const QString &eventName, QDotNetObject &sender, QDotNetObject &args) override + { + if (eventName != "PropertyChanged") + return; + + if (args.type().fullName() != QDotNetPropertyEvent::FullyQualifiedTypeName) + return; + + const auto propertyChangedEvent = args.cast<QDotNetPropertyEvent>(); + if (propertyChangedEvent.propertyName() == "Bar") + emit q->barChanged(); + } +}; + +Q_DOTNET_OBJECT_IMPL(Foo, Q_DOTNET_OBJECT_INIT(d(new FooPrivate(this)))); + +Foo::Foo() : d(new FooPrivate(this)) +{ + const auto ctor = constructor<Foo, Null<IBarTransformation>>(); + *this = ctor(nullptr); + subscribeEvent("PropertyChanged", d); +} + +Foo::Foo(const IBarTransformation &transformation) : d(new FooPrivate(this)) +{ + *this = constructor(d->ctor).invoke(*this, transformation); + subscribeEvent("PropertyChanged", d); +} + +Foo::~Foo() +{ + delete d; +} + +QString Foo::bar() const +{ + return method("get_Bar", d->bar).invoke(*this); +} + +void Foo::setBar(const QString &value) +{ + method("set_Bar", d->setBar).invoke(*this, value); +} + +IBarTransformation::IBarTransformation() : QDotNetInterface(FullyQualifiedTypeName) +{ + setCallback<QString, QString>("Transform", { QDotNetParameter::String, UnmanagedType::LPWStr }, + [this](const QString &bar) { + return transform(bar); + }); +} diff --git a/tests/tst_qtdotnet/foo.h b/tests/tst_qtdotnet/foo.h new file mode 100644 index 0000000..1c8f9fc --- /dev/null +++ b/tests/tst_qtdotnet/foo.h @@ -0,0 +1,56 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#pragma once + +#include <qdotnetinterface.h> +#include <qdotnetobject.h> + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +#include <QObject> +#include <QString> +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +struct FooPrivate; + +class IBarTransformation : public QDotNetInterface +{ +public: + static inline const QString &FullyQualifiedTypeName = + QStringLiteral("FooLib.IBarTransformation, FooLib"); + + virtual QString transform(const QString &) = 0; + +protected: + IBarTransformation(); + ~IBarTransformation() override = default; +}; + +class Foo final : public QObject, public QDotNetObject +{ + Q_OBJECT + Q_PROPERTY(QString bar READ bar WRITE setBar NOTIFY barChanged) + +public: + Q_DOTNET_OBJECT(Foo, "FooLib.Foo, FooLib"); + + Foo(); + Foo(const IBarTransformation &transformation); + ~Foo() override; + + [[nodiscard]] QString bar() const; + void setBar(const QString &value); + +signals: + void barChanged(); + +private: + FooPrivate *d; +}; diff --git a/tests/tst_qtdotnet/stringbuilder.cpp b/tests/tst_qtdotnet/stringbuilder.cpp new file mode 100644 index 0000000..657bdd6 --- /dev/null +++ b/tests/tst_qtdotnet/stringbuilder.cpp @@ -0,0 +1,43 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#include <qdotnetsafemethod.h> + +#include "stringbuilder.h" + +struct StringBuilderPrivate final +{ + StringBuilderPrivate() = default; + StringBuilderPrivate(StringBuilder *q) : q(q) + {} + StringBuilder *q = nullptr; + QDotNetSafeMethod<StringBuilder, QString> append; +}; + +Q_DOTNET_OBJECT_IMPL(StringBuilder, Q_DOTNET_OBJECT_INIT(d(new StringBuilderPrivate(this)))); + +StringBuilder::StringBuilder() : + d(new StringBuilderPrivate(this)) +{ + const QDotNetFunction<StringBuilder> ctor = constructor<StringBuilder>(); + *this = ctor(); +} + +StringBuilder::StringBuilder(qint32 capacity, qint32 maxCapacity) : + d(new StringBuilderPrivate(this)) +{ + const auto ctor = constructor<StringBuilder, qint32, qint32>(); + *this = ctor(capacity, maxCapacity); +} + +StringBuilder::~StringBuilder() +{ + delete d; +} + +StringBuilder StringBuilder::append(const QString &str) +{ + return method("Append", d->append).invoke(*this, str); +} diff --git a/tests/tst_qtdotnet/stringbuilder.h b/tests/tst_qtdotnet/stringbuilder.h new file mode 100644 index 0000000..a475fe2 --- /dev/null +++ b/tests/tst_qtdotnet/stringbuilder.h @@ -0,0 +1,34 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#pragma once + +#include <qdotnetobject.h> + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +#include <QString> +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +struct StringBuilderPrivate; + +class StringBuilder final : public QDotNetObject +{ +public: + Q_DOTNET_OBJECT(StringBuilder, "System.Text.StringBuilder"); + + StringBuilder(); + StringBuilder(qint32 capacity, qint32 maxCapacity); + ~StringBuilder() override; + + StringBuilder append(const QString &str); + +private: + StringBuilderPrivate *d; +}; diff --git a/tests/tst_qtdotnet/tst_qtdotnet.cpp b/tests/tst_qtdotnet/tst_qtdotnet.cpp new file mode 100644 index 0000000..92dbe85 --- /dev/null +++ b/tests/tst_qtdotnet/tst_qtdotnet.cpp @@ -0,0 +1,508 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#include "foo.h" +#include "stringbuilder.h" +#include "uri.h" + +#include <qdotnetadapter.h> +#include <qdotnetarray.h> +#include <qdotnetcallback.h> +#include <qdotnethost.h> +#include <qdotnetmarshal.h> +#include <qdotnetobject.h> +#include <qdotnetsafemethod.h> +#include <qdotnettype.h> + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +#include <QChar> +#include <QDebug> +#include <QDir> +#include <QElapsedTimer> +#include <QList> +#include <QMap> +#include <QObject> +#include <QSignalSpy> +#include <QString> + +#include <QtTest> +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +class tst_qtdotnet : public QObject +{ + Q_OBJECT + +public: + tst_qtdotnet() = default; + +private slots: + void loadHost(); + void runtimeProperties(); + void resolveFunction(); + void callFunction(); + void callFunctionWithCustomMarshaling(); + void callDefaultEntryPoint(); + void callWithComplexArg(); + void adapterInit(); + void callStaticMethod(); + void handleException(); + void createObject(); + void callInstanceMethod(); + void useWrapperClass(); + void emitSignalFromEvent(); + void propertyBinding(); + void implementInterface(); + void arrayOfInts(); + void arrayOfStrings(); + void arrayOfObjects(); + void unloadHost(); +}; + +QDotNetHost dotNetHost; + +void tst_qtdotnet::loadHost() +{ + QVERIFY(!dotNetHost.isLoaded()); + QVERIFY(dotNetHost.load()); + QVERIFY(dotNetHost.isLoaded()); +} + +void tst_qtdotnet::runtimeProperties() +{ + QVERIFY(dotNetHost.isLoaded()); + QMap<QString, QString> runtimeProperties = dotNetHost.runtimeProperties(); + QVERIFY(!runtimeProperties.isEmpty()); + for (auto prop = runtimeProperties.constBegin(); prop != runtimeProperties.constEnd(); ++prop) { + qInfo() << prop.key() << "=" << QString("%1%2") + .arg(prop.value().left(100)).arg(prop.value().length() > 100 ? "..." : ""); + } +} + +QDotNetFunction<QString, QString, int> formatNumber; + +void tst_qtdotnet::resolveFunction() +{ + QVERIFY(dotNetHost.isLoaded()); + QVERIFY(!formatNumber.isValid()); + QVERIFY(dotNetHost.resolveFunction(formatNumber, + QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"), + Foo::FullyQualifiedTypeName, "FormatNumber", "FooLib.Foo+FormatNumberDelegate, FooLib")); + + QVERIFY(formatNumber.isValid()); +} + +void tst_qtdotnet::callFunction() +{ + QVERIFY(dotNetHost.isLoaded()); + QVERIFY(formatNumber.isValid()); + + const QString formattedText = formatNumber("[{0}]", 42); + + QCOMPARE(formattedText, "[42]"); +} + +struct DoubleAsInt {}; + +template<> +struct QDotNetOutbound<DoubleAsInt> +{ + using SourceType = double; + using OutboundType = int; + static OutboundType convert(SourceType arg) + { + return qRound(arg); + } +}; + +struct QUpperCaseString +{}; + +template<> +struct QDotNetNull<QUpperCaseString> +{ + static QString value() { return {}; } + static bool isNull(const QString& s) { return s.isNull() || s.isEmpty(); } +}; + +template<> +struct QDotNetInbound<QUpperCaseString> +{ + using InboundType = QChar*; + using TargetType = QString; + static TargetType convert(InboundType inboundValue) + { + return QString(inboundValue).toUpper(); + } +}; + +void tst_qtdotnet::callFunctionWithCustomMarshaling() +{ + QVERIFY(dotNetHost.isLoaded()); + + QDotNetFunction<QUpperCaseString, QString, DoubleAsInt> formatDouble; + QVERIFY(dotNetHost.resolveFunction(formatDouble, + QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"), + Foo::FullyQualifiedTypeName, "FormatNumber", "FooLib.Foo+FormatNumberDelegate, FooLib")); + + QVERIFY(formatDouble.isValid()); + + const QString formattedText = formatDouble("result = [{0}]", 41.5); + + QCOMPARE(formattedText, "RESULT = [42]"); +} + +void tst_qtdotnet::callDefaultEntryPoint() +{ + QVERIFY(dotNetHost.isLoaded()); + + QDotNetFunction<quint32, void*, qint32> entryPoint; + QVERIFY(dotNetHost.resolveFunction(entryPoint, + QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"), + Foo::FullyQualifiedTypeName, "EntryPoint")); + + QVERIFY(entryPoint.isValid()); + + QString fortyTwo("42"); + const qint32 returnValue = entryPoint(fortyTwo.data(), static_cast<qint32>(fortyTwo.length())); + + QCOMPARE(returnValue, 42); +} + +struct Date +{ + QString year; + QString month; + QString day; +}; + +struct DateOutbound +{ + const QChar* year; + const QChar* month; + const QChar* day; +}; + +template<> +struct QDotNetOutbound<Date> +{ + using SourceType = const Date&; + using OutboundType = const DateOutbound; + static DateOutbound convert(SourceType arg) + { + return { arg.year.data(), arg.month.data(), arg.day.data() }; + } +}; + +void tst_qtdotnet::callWithComplexArg() +{ + QVERIFY(dotNetHost.isLoaded()); + QDotNetFunction<QString, QString, Date> formatDate; + QVERIFY(dotNetHost.resolveFunction(formatDate, + QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"), + Foo::FullyQualifiedTypeName, "FormatDate", "FooLib.Foo+FormatDateDelegate, FooLib")); + + QVERIFY(formatDate.isValid()); + + const Date xmas{ "2022", "12", "25" }; + const QString formattedText = formatDate("Today is {0}-{1}-{2}", xmas); + + QCOMPARE(formattedText, "Today is 2022-12-25"); +} + +void tst_qtdotnet::adapterInit() +{ + QVERIFY(!QDotNetAdapter::instance().isValid()); + QDotNetAdapter::instance().init( + QDir(QCoreApplication::applicationDirPath()).filePath("Qt.DotNet.Adapter.dll"), + "Qt.DotNet.Adapter", "Qt.DotNet.Adapter", &dotNetHost); + QVERIFY(QDotNetAdapter::instance().isValid()); +} + +void tst_qtdotnet::callStaticMethod() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + const QDotNetType environment = QDotNetType::find("System.Environment"); + const auto getEnvironmentVariable + = environment.staticMethod<QString, QString>("GetEnvironmentVariable"); + const QString path = getEnvironmentVariable("PATH"); + QVERIFY(path.length() > 0); + const QString samePath = QtDotNet::call<QString, QString>( + "System.Environment", "GetEnvironmentVariable", "PATH"); + QVERIFY(path == samePath); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::createObject() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + const auto newStringBuilder = QDotNetObject::constructor("System.Text.StringBuilder"); + QDotNetObject stringBuilder = newStringBuilder(); + QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::callInstanceMethod() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + const auto newStringBuilder = QDotNetObject::constructor("System.Text.StringBuilder"); + const auto stringBuilder = newStringBuilder(); + const auto append = stringBuilder.method<QDotNetObject, QString>("Append"); + std::ignore = append("Hello"); + std::ignore = append(" World!"); + const QString helloWorld = stringBuilder.toString(); + QVERIFY(helloWorld == "Hello World!"); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::useWrapperClass() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + StringBuilder sb; + QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); + QVERIFY(sb.isValid()); + sb.append("Hello").append(" "); + StringBuilder sbCpy(sb); + QVERIFY(QDotNetAdapter::instance().stats().refCount == 2); + QVERIFY(sbCpy.isValid()); + sbCpy.append("World"); + sb = StringBuilder(std::move(sbCpy)); + QVERIFY(QDotNetAdapter::instance().stats().refCount == 1); + sb.append("!"); + QCOMPARE(sb.toString(), "Hello World!"); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + const Uri uri(QStringLiteral( + "https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName")); + QVERIFY(uri.segments().length() == 3); + QVERIFY(uri.segments()[0]->compare("/") == 0); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::handleException() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + StringBuilder stringBuilder(5, 5); + QString helloWorld; + try { + stringBuilder.append("Hello"); + QVERIFY(stringBuilder.toString() == "Hello"); + stringBuilder.append(" World!"); + helloWorld = stringBuilder.toString(); + } + catch (QDotNetException&) { + helloWorld = "<ERROR>"; + } + QVERIFY(helloWorld == "<ERROR>"); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +class Ping final : public QObject, public QDotNetObject, public QDotNetObject::IEventHandler +{ + Q_OBJECT + +public: + Q_DOTNET_OBJECT_INLINE(Ping, "System.Net.NetworkInformation.Ping, System", ); + + Ping() + : QDotNetObject(QDotNetSafeMethod(constructor<Ping>()).invoke(nullptr)) + { + subscribeEvent("PingCompleted", this); + } + ~Ping() override = default; + + void sendAsync(const QString& hostNameOrAddress) + { + method("SendAsync", safeSendAsync).invoke(*this, hostNameOrAddress, nullptr); + } + + void sendAsyncCancel() + { + method("SendAsyncCancel", safeSendAsyncCancel).invoke(*this); + } + +signals: + void pingCompleted(const QString& address, qint64 roundtripMsecs); + void pingError(); + +private: + void handleEvent(const QString& evName, QDotNetObject& evSrc, QDotNetObject& evArgs) override + { + if (evName != "PingCompleted") + return; + if (evArgs.type().fullName() != "System.Net.NetworkInformation.PingCompletedEventArgs") + return; + const auto getReply = evArgs.method<QDotNetObject>("get_Reply"); + const auto reply = getReply(); + if (reply.isValid()) { + const auto replyAddress = reply.method<QDotNetObject>("get_Address"); + const auto replyRoundtrip = reply.method<qint64>("get_RoundtripTime"); + emit pingCompleted(replyAddress().toString(), replyRoundtrip()); + } + else { + emit pingError(); + } + } + QDotNetSafeMethod<void, QString, QtDotNet::Null> safeSendAsync; + QDotNetSafeMethod<void> safeSendAsyncCancel; +}; + +void tst_qtdotnet::emitSignalFromEvent() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + Ping ping; + bool waiting = true; + int signalCount = 0; + connect(&ping, &Ping::pingCompleted, + [&waiting, &signalCount](const QString& address, qint64 roundtripMsecs) { + qInfo() << "Reply from" << address << "in" << roundtripMsecs << "msecs"; + signalCount++; + waiting = false; + }); + connect(&ping, &Ping::pingError, + [&waiting, &signalCount] { + qInfo() << "Ping error"; + signalCount++; + waiting = false; + }); + qInfo() << "Pinging www.qt.io:"; + QElapsedTimer waitTime; + for (int i = 0; i < 4; ++i) { + waitTime.restart(); + waiting = true; + ping.sendAsync("www.qt.io"); + while (waiting) { + QCoreApplication::processEvents(); + if (waitTime.elapsed() > 3000) { + ping.sendAsyncCancel(); + waiting = false; + qInfo() << "Ping timeout"; + } + } + } + QVERIFY(signalCount == 4); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::propertyBinding() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + Foo foo; + const QSignalSpy spy(&foo, &Foo::barChanged); + for (int i = 0; i < 1000; ++i) + foo.setBar(QString("hello x %1").arg(i + 1)); + QVERIFY(foo.bar() == "hello x 1000"); + QVERIFY(spy.count() == 1000); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +struct ToUpper : IBarTransformation +{ + QString transform(const QString& bar) override + { + return bar.toUpper(); + } +}; + +void tst_qtdotnet::implementInterface() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + const ToUpper transfToUpper; + Foo foo(transfToUpper); + const QSignalSpy spy(&foo, &Foo::barChanged); + for (int i = 0; i < 1000; ++i) + foo.setBar(QString("hello x %1").arg(i + 1)); + QVERIFY(foo.bar() == "HELLO X 1000"); + QVERIFY(spy.count() == 1000); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::arrayOfInts() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + QDotNetArray<qint32> a(11); + a[0] = 0; + a[1] = 1; + for (int i = 2; i < a.length(); ++i) + a[i] = a[i - 1] + a[i - 2]; + QVERIFY(a[10] == 55); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::arrayOfStrings() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + QDotNetArray<QString> a(8); + a[0] = "Lorem"; + a[1] = "ipsum"; + a[2] = "dolor"; + a[3] = "sit"; + a[4] = "amet,"; + a[5] = "consectetur"; + a[6] = "adipiscing"; + a[7] = "elit."; + const auto stringType = QDotNetType::find("System.String"); + const auto join = stringType.staticMethod<QString, QString, QDotNetArray<QString>>("Join"); + const auto loremIpsum = join(" ", a); + QVERIFY(loremIpsum == "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::arrayOfObjects() +{ + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); + { + QDotNetArray<StringBuilder> a(8); + for (int i = 0; i < a.length(); ++i) + a[i] = StringBuilder(); + a[0]->append("Lorem"); + a[1]->append(a[0]->toString()).append(" ipsum"); + a[2]->append(a[1]->toString()).append(" dolor"); + a[3]->append(a[2]->toString()).append(" sit"); + a[4]->append(a[3]->toString()).append(" amet,"); + a[5]->append(a[4]->toString()).append(" consectetur"); + a[6]->append(a[5]->toString()).append(" adipiscing"); + a[7]->append(a[6]->toString()).append(" elit."); + QVERIFY(a[7]->toString() == "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + QVERIFY(QDotNetAdapter::instance().stats().refCount == 0); +} + +void tst_qtdotnet::unloadHost() +{ + QVERIFY(dotNetHost.isLoaded()); + + dotNetHost.unload(); + + QVERIFY(!dotNetHost.isLoaded()); +} + +QTEST_MAIN(tst_qtdotnet) +#include "tst_qtdotnet.moc" diff --git a/tests/tst_qtdotnet/tst_qtdotnet.vcxproj b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj new file mode 100644 index 0000000..1d0efa1 --- /dev/null +++ b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj @@ -0,0 +1,225 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ +--> +<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{4E317E0F-0565-4B8A-84F3-56ADF18C65AD}</ProjectGuid> + <Keyword>QtVS_v304</Keyword> + <RootNamespace>tst_qtdotnet</RootNamespace> + <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='15.0'">10.0.17763.0</WindowsTargetPlatformVersion> + <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='16.0'">10.0</WindowsTargetPlatformVersion> + <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='17.0'">10.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup> + <PlatformToolset Condition="'$(VisualStudioVersion)'=='15.0'">v141</PlatformToolset> + <PlatformToolset Condition="'$(VisualStudioVersion)'=='16.0'">v142</PlatformToolset> + <PlatformToolset Condition="'$(VisualStudioVersion)'=='17.0'">v143</PlatformToolset> + </PropertyGroup> + <PropertyGroup Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir> + <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')"> + <Import Project="$(QtMsBuild)\qt_defaults.props" /> + </ImportGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings"> + <QtInstall>$(DefaultQtVersion)</QtInstall> + <QtModules>core;testlib</QtModules> + <QtBuildConfig>debug</QtBuildConfig> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="QtSettings"> + <QtInstall>$(DefaultQtVersion)</QtInstall> + <QtModules>core;testlib</QtModules> + <QtBuildConfig>debug</QtBuildConfig> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings"> + <QtInstall>$(DefaultQtVersion)</QtInstall> + <QtModules>core;testlib</QtModules> + <QtBuildConfig>release</QtBuildConfig> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="QtSettings"> + <QtInstall>$(DefaultQtVersion)</QtInstall> + <QtModules>core;testlib</QtModules> + <QtBuildConfig>release</QtBuildConfig> + </PropertyGroup> + <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')"> + <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." /> + </Target> + <ImportGroup Label="ExtensionSettings" /> + <ImportGroup Label="Shared" /> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="$(QtMsBuild)\Qt.props" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="$(QtMsBuild)\Qt.props" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="$(QtMsBuild)\Qt.props" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="$(QtMsBuild)\Qt.props" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> + <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir> + <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir> + <IncludePath>$(SolutionDir)include;$(IncludePath)</IncludePath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir> + <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir> + <IncludePath>$(SolutionDir)include;$(IncludePath)</IncludePath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> + <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir> + <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir> + <IncludePath>$(SolutionDir)include;$(IncludePath)</IncludePath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir> + <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir> + <IncludePath>$(SolutionDir)include;$(IncludePath)</IncludePath> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Link> + <Profile>true</Profile> + </Link> + <ClCompile /> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Link> + <Profile>true</Profile> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration"> + <ClCompile> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <Optimization>Disabled</Optimization> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ClCompile> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <Optimization>Disabled</Optimization> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration"> + <ClCompile> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <DebugInformationFormat>None</DebugInformationFormat> + <Optimization>MaxSpeed</Optimization> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>false</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ClCompile> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <DebugInformationFormat>None</DebugInformationFormat> + <Optimization>MaxSpeed</Optimization> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>false</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <QtMoc Include="tst_qtdotnet.cpp"> + <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">input</DynamicSource> + <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">input</DynamicSource> + <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(Filename).moc</QtMocFileName> + <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(Filename).moc</QtMocFileName> + <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|x64'">input</DynamicSource> + <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">input</DynamicSource> + <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(Filename).moc</QtMocFileName> + <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(Filename).moc</QtMocFileName> + </QtMoc> + </ItemGroup> + <ItemGroup> + <ClCompile Include="foo.cpp" /> + <ClCompile Include="uri.cpp" /> + <ClCompile Include="stringbuilder.cpp" /> + </ItemGroup> + <ItemGroup> + <QtMoc Include="foo.h" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="uri.h" /> + <ClInclude Include="stringbuilder.h" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj"> + <Project>{3863807c-2f87-4e27-a9c9-8675645a8da5}</Project> + </ProjectReference> + <ProjectReference Include="..\FooLib\FooLib.csproj"> + <Project>{45d3ddf3-135b-46ca-b3ee-3537fcfffbeb}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')"> + <Import Project="$(QtMsBuild)\qt.targets" /> + </ImportGroup> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters new file mode 100644 index 0000000..4c9a2f3 --- /dev/null +++ b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + <Filter Include="Form Files"> + <UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier> + <Extensions>ui</Extensions> + </Filter> + <Filter Include="Translation Files"> + <UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier> + <Extensions>ts</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <QtMoc Include="tst_qtdotnet.cpp"> + <Filter>Source Files</Filter> + </QtMoc> + <QtMoc Include="foo.h"> + <Filter>Header Files</Filter> + </QtMoc> + </ItemGroup> + <ItemGroup> + <ClCompile Include="foo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="stringbuilder.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="uri.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="stringbuilder.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="uri.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/tests/tst_qtdotnet/uri.cpp b/tests/tst_qtdotnet/uri.cpp new file mode 100644 index 0000000..92cbee6 --- /dev/null +++ b/tests/tst_qtdotnet/uri.cpp @@ -0,0 +1,244 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#include "uri.h" + +using namespace QDotNetTypes::System; + +template<> +struct QDotNetOutbound<UriHostNameType> +{ + using SourceType = UriHostNameType; + using OutboundType = qint32; + static inline const QDotNetParameter Parameter = QDotNetParameter( + QDotNetTypeOf<UriHostNameType>::TypeName, + QDotNetTypeOf<UriHostNameType>::MarshalAs); + static OutboundType convert(SourceType srvValue) + { + return static_cast<qint32>(srvValue); + } +}; + +template<> +struct QDotNetInbound<UriHostNameType> +{ + using InboundType = qint32; + using TargetType = UriHostNameType; + static inline const QDotNetParameter Parameter = QDotNetParameter( + QDotNetTypeOf<UriHostNameType>::TypeName, + QDotNetTypeOf<UriHostNameType>::MarshalAs); + static TargetType convert(InboundType inboundValue) + { + return static_cast<UriHostNameType>(inboundValue); + } +}; + +template<> +struct QDotNetNull<UriHostNameType> +{ + static UriHostNameType value() { return UriHostNameType::Unknown; } + static bool isNull(const UriHostNameType &) { return false; } +}; + +template<> +struct QDotNetOutbound<UriKind> +{ + using SourceType = UriKind; + using OutboundType = qint32; + static inline const QDotNetParameter Parameter = + QDotNetParameter(QDotNetTypeOf<UriKind>::TypeName, QDotNetTypeOf<UriKind>::MarshalAs); + static OutboundType convert(SourceType srvValue) + { + return static_cast<qint32>(srvValue); + } +}; + +template<> +struct QDotNetInbound<UriKind> +{ + using InboundType = qint32; + using TargetType = UriKind; + static inline const QDotNetParameter Parameter = + QDotNetParameter(QDotNetTypeOf<UriKind>::TypeName, QDotNetTypeOf<UriKind>::MarshalAs); + static TargetType convert(InboundType inboundValue) + { + return static_cast<UriKind>(inboundValue); + } +}; + +template<> +struct QDotNetNull<UriKind> +{ + static UriKind value() { return UriKind::RelativeOrAbsolute; } + static bool isNull(const UriKind &) { return false; } +}; + +struct UriPrivate +{ + QDotNetSafeMethod<QString> absolutePath; + QDotNetSafeMethod<QString> absoluteUri; + QDotNetSafeMethod<QString> authority; + QDotNetSafeMethod<QString> dnsSafeHost; + QDotNetSafeMethod<QString> fragment; + QDotNetSafeMethod<QString> host; + QDotNetSafeMethod<UriHostNameType> hostNameType; + QDotNetSafeMethod<QString> idnHost; + QDotNetSafeMethod<bool> isAbsoluteUri; + QDotNetSafeMethod<bool> isDefaultPort; + QDotNetSafeMethod<bool> isFile; + QDotNetSafeMethod<bool> isLoopback; + QDotNetSafeMethod<bool> isUnc; + QDotNetSafeMethod<QString> localPath; + QDotNetSafeMethod<QString> originalString; + QDotNetSafeMethod<QString> pathAndQuery; + QDotNetSafeMethod<qint32> port; + QDotNetSafeMethod<QString> query; + QDotNetSafeMethod<QString> scheme; + QDotNetSafeMethod<QDotNetArray<QString>> segments; + QDotNetSafeMethod<bool> userEscaped; + QDotNetSafeMethod<QString> userInfo; +}; + +Q_DOTNET_OBJECT_IMPL(Uri, + Q_DOTNET_OBJECT_INIT(d(new UriPrivate))); + +Uri::Uri() : d(new UriPrivate) +{} + +Uri::Uri(const QString &uriString, UriKind uriKind) + : d(new UriPrivate) +{ + const auto ctor = constructor<Uri, QString, UriKind>(); + *this = ctor(uriString, uriKind); +} + +Uri::Uri(const Uri &baseUri, const QString &relativeUri) + : d(new UriPrivate) +{ + const auto ctor = constructor<Uri, Uri, QString>(); + *this = ctor(&baseUri, relativeUri); +} + +Uri::Uri(const Uri &baseUri, const Uri &relativeUri) + : d(new UriPrivate) +{ + const auto ctor = constructor<Uri, Uri, Uri>(); + *this = ctor(&baseUri, &relativeUri); +} + +Uri::~Uri() +{ + delete d; +} + +QString Uri::absolutePath() const +{ + return method("get_AbsolutePath", d->absolutePath).invoke(*this); +} + +QString Uri::absoluteUri() const +{ + return method("get_AbsoluteUri", d->absoluteUri).invoke(*this); +} + +QString Uri::authority() const +{ + return method("get_Authority", d->authority).invoke(*this); +} + +QString Uri::dnsSafeHost() const +{ + return method("get_DnsSafeHost", d->dnsSafeHost).invoke(*this); +} + +QString Uri::fragment() const +{ + return method("get_Fragment", d->fragment).invoke(*this); +} + +QString Uri::host() const +{ + return method("get_Host", d->host).invoke(*this); +} + +UriHostNameType Uri::hostNameType() const +{ + return method("get_HostNameType", d->hostNameType).invoke(*this); +} + +QString Uri::idnHost() const +{ + return method("get_IdnHost", d->idnHost).invoke(*this); +} + +bool Uri::isAbsoluteUri() const +{ + return method("get_IsAbsoluteUri", d->isAbsoluteUri).invoke(*this); +} + +bool Uri::isDefaultPort() const +{ + return method("get_IsDefaultPort", d->isDefaultPort).invoke(*this); +} + +bool Uri::isFile() const +{ + return method("get_IsFile", d->isFile).invoke(*this); +} + +bool Uri::isLoopback() const +{ + return method("get_IsLoopback", d->isLoopback).invoke(*this); +} + +bool Uri::isUnc() const +{ + return method("get_IsUnc", d->isUnc).invoke(*this); +} + +QString Uri::localPath() const +{ + return method("get_LocalPath", d->localPath).invoke(*this); +} + +QString Uri::originalString() const +{ + return method("get_OriginalString", d->originalString).invoke(*this); +} + +QString Uri::pathAndQuery() const +{ + return method("get_PathAndQuery", d->pathAndQuery).invoke(*this); +} + +qint32 Uri::port() const +{ + return method("get_Port", d->port).invoke(*this); +} + +QString Uri::query() const +{ + return method("get_Query", d->query).invoke(*this); +} + +QString Uri::scheme() const +{ + return method("get_Scheme", d->scheme).invoke(*this); +} + +QDotNetArray<QString> Uri::segments() const +{ + return method("get_Segments", d->segments).invoke(*this); +} + +bool Uri::userEscaped() const +{ + return method("get_UserEscaped", d->userEscaped).invoke(*this); +} + +QString Uri::userInfo() const +{ + return method("get_UserInfo", d->userInfo).invoke(*this); +} diff --git a/tests/tst_qtdotnet/uri.h b/tests/tst_qtdotnet/uri.h new file mode 100644 index 0000000..8ddd89c --- /dev/null +++ b/tests/tst_qtdotnet/uri.h @@ -0,0 +1,89 @@ +/*************************************************************************************************** + Copyright (C) 2023 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +***************************************************************************************************/ + +#pragma once + +#include <qdotnetarray.h> +#include <qdotnetmarshal.h> +#include <qdotnetobject.h> + +struct UriPrivate; + +namespace QDotNetTypes +{ + namespace System + { + enum class UriHostNameType + { + Unknown = 0, + Basic = 1, + Dns = 2, + IPv4 = 3, + IPv6 = 4 + }; + + enum class UriKind + { + RelativeOrAbsolute = 0, + Absolute = 1, + Relative = 2 + }; + } +} + +template<> +struct QDotNetTypeOf<QDotNetTypes::System::UriHostNameType> +{ + static inline const QString TypeName = QString("System.UriHostNameType, System"); + static inline UnmanagedType MarshalAs = UnmanagedType::I4; +}; + +template<> +struct QDotNetTypeOf<QDotNetTypes::System::UriKind> +{ + static inline const QString TypeName = QString("System.UriKind, System"); + static inline UnmanagedType MarshalAs = UnmanagedType::I4; +}; + +class Uri : public QDotNetObject +{ +public: + Q_DOTNET_OBJECT(Uri, "System.Uri, System"); + Uri(); + Uri(const char *uriString) : Uri(QString(uriString)) {} + Uri( + const QString &uriString, + QDotNetTypes::System::UriKind uriKind = QDotNetTypes::System::UriKind::Absolute); + Uri(const Uri &baseUri, const QString &relativeUri); + Uri(const Uri &baseUri, const Uri &relativeUri); + ~Uri() override; + + QString absolutePath() const; + QString absoluteUri() const; + QString authority() const; + QString dnsSafeHost() const; + QString fragment() const; + QString host() const; + QDotNetTypes::System::UriHostNameType hostNameType() const; + QString idnHost() const; + bool isAbsoluteUri() const; + bool isDefaultPort() const; + bool isFile() const; + bool isLoopback() const; + bool isUnc() const; + QString localPath() const; + QString originalString() const; + QString pathAndQuery() const; + qint32 port() const; + QString query() const; + QString scheme() const; + QDotNetArray<QString> segments() const; + bool userEscaped() const; + QString userInfo() const; + + +private: + UriPrivate *d; +}; |