Procházet zdrojové kódy

Implement extra namespace for the generated code

- Add the EXTRA_NAMESPACE option to the qtprotobuf_generate
  function. The option adds the generating of the extra namespace
  that wraps the generated classes. This is usefull when generated
  classes are not supposed to be used directly, but extended using
  an inheritance.
- Implement tests
- Add docs

Fixes: #195
Alexey Edelev před 3 roky
rodič
revize
ea006374f7

+ 4 - 2
README.md

@@ -300,7 +300,7 @@ To intergrate QtProtobuf add it as a dependency to your conanfile.py:
 ## Direct usage of generator
 
 ```bash
-[QT_PROTOBUF_OPTIONS="[SINGLE|MULTI]:QML:COMMENTS:FOLDER:FIELDENUM"] protoc --plugin=protoc-gen-qtprotobuf=<path/to/bin/>qtprotobufgen --qtprotobuf_out=<output_dir> [-I/extra/proto/include/path] <protofile>.proto
+[QT_PROTOBUF_OPTIONS="[SINGLE|MULTI]:QML:COMMENTS:FOLDER:FIELDENUM:EXTRA_NAMESPACE=<value>"] protoc --plugin=protoc-gen-qtprotobuf=<path/to/bin/>qtprotobufgen --qtprotobuf_out=<output_dir> [-I/extra/proto/include/path] <protofile>.proto
 ```
 
 ### QT_PROTOBUF_OPTIONS
@@ -308,7 +308,7 @@ To intergrate QtProtobuf add it as a dependency to your conanfile.py:
 For protoc command you also may specify extra options using QT_PROTOBUF_OPTIONS environment variable and colon-separated format:
 
 ``` bash
-[QT_PROTOBUF_OPTIONS="[SINGLE|MULTI]:QML:COMMENTS:FOLDER:FIELDENUM"] protoc --plugin=protoc-gen-qtprotobuf=<path/to/bin/>qtprotobufgen --qtprotobuf_out=<output_dir> [-I/extra/proto/include/path] <protofile>.proto
+[QT_PROTOBUF_OPTIONS="[SINGLE|MULTI]:QML:COMMENTS:FOLDER:FIELDENUM:EXTRA_NAMESPACE=<value>"] protoc --plugin=protoc-gen-qtprotobuf=<path/to/bin/>qtprotobufgen --qtprotobuf_out=<output_dir> [-I/extra/proto/include/path] <protofile>.proto
 ```
 
 Following options are supported:
@@ -408,6 +408,8 @@ qtprotobuf_generate is cmake helper function that automatically generates STATIC
 
 *FIELDENUM* - Adds enumeration with message fields for generated messages.
 
+*EXTRA_NAMESPACE <namespace>* - Wraps the generated code with the specified namespace. (EXPERIMETAL)
+
 #### qtprotobuf_link_target
 
 qtprotobuf_link_target is cmake helper function that links generated protobuf target to your binary. It's useful when you try to link generated target to shared library or/and to executable that doesn't utilize all protobuf generated classes directly from C++ code, but requires them from QML.

+ 7 - 1
cmake/QtProtobufGen.cmake

@@ -6,7 +6,7 @@ endfunction()
 
 function(qtprotobuf_generate)
     set(options MULTI QML COMMENTS FOLDER FIELDENUM)
-    set(oneValueArgs OUT_DIR TARGET GENERATED_TARGET)
+    set(oneValueArgs OUT_DIR TARGET GENERATED_TARGET EXTRA_NAMESPACE)
     set(multiValueArgs GENERATED_HEADERS EXCLUDE_HEADERS PROTO_FILES PROTO_INCLUDES)
     cmake_parse_arguments(qtprotobuf_generate "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
@@ -52,6 +52,12 @@ function(qtprotobuf_generate)
         set(GENERATION_OPTIONS "${GENERATION_OPTIONS}:FIELDENUM")
     endif()
 
+    if(qtprotobuf_generate_EXTRA_NAMESPACE)
+        set(GENERATION_OPTIONS
+            "${GENERATION_OPTIONS}:EXTRA_NAMESPACE=\"${qtprotobuf_generate_EXTRA_NAMESPACE}\""
+        )
+    endif()
+
     if(WIN32)
         set(PROTOC_COMMAND set QT_PROTOBUF_OPTIONS=${GENERATION_OPTIONS}&& $<TARGET_FILE:protobuf::protoc>)
     else()

+ 7 - 4
cmake/QtProtobufInternalHelpers.cmake

@@ -15,7 +15,7 @@ endfunction()
 
 function(qt_protobuf_internal_add_test)
     set(options MULTI QML FIELDENUM)
-    set(oneValueArgs QML_DIR TARGET)
+    set(oneValueArgs QML_DIR TARGET EXTRA_NAMESPACE)
     set(multiValueArgs SOURCES GENERATED_HEADERS EXCLUDE_HEADERS PROTO_FILES PROTO_INCLUDES)
     cmake_parse_arguments(add_test_target "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
@@ -28,9 +28,9 @@ function(qt_protobuf_internal_add_test)
     set(GENERATED_SOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/${add_test_target_TARGET}_generated")
 
     if(DEFINED add_test_target_PROTO_FILES)
-        file(GLOB PROTO_FILES ABSOLUTE ${add_test_target_PROTO_FILES})
+        file(GLOB proto_files ABSOLUTE ${add_test_target_PROTO_FILES})
     else()
-        file(GLOB PROTO_FILES ABSOLUTE "${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto")
+        file(GLOB proto_files ABSOLUTE "${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto")
     endif()
 
     add_executable(${add_test_target_TARGET} ${add_test_target_SOURCES})
@@ -45,10 +45,13 @@ function(qt_protobuf_internal_add_test)
     if(add_test_target_FIELDENUM)
         set(EXTRA_OPTIONS ${EXTRA_OPTIONS} FIELDENUM)
     endif()
+    if(add_test_target_EXTRA_NAMESPACE)
+        set(EXTRA_OPTIONS ${EXTRA_OPTIONS} EXTRA_NAMESPACE ${add_test_target_EXTRA_NAMESPACE})
+    endif()
 
     qtprotobuf_generate(TARGET ${add_test_target_TARGET}
         OUT_DIR ${GENERATED_SOURCES_DIR}
-        PROTO_FILES ${PROTO_FILES}
+        PROTO_FILES ${proto_files}
         GENERATED_HEADERS ${add_test_target_GENERATED_HEADERS}
         EXCLUDE_HEADERS ${add_test_target_EXCLUDE_HEADERS}
         ${EXTRA_OPTIONS}

+ 8 - 2
src/generator/descriptorprinterbase.h

@@ -60,6 +60,9 @@ public:
 
     void printNamespaces() {
         auto namespaces = common::getNamespaces(mDescriptor);
+        if (!GeneratorOptions::instance().extraNamespace().empty()) {
+            namespaces.insert(namespaces.begin(), GeneratorOptions::instance().extraNamespace());
+        }
         mPrinter->Print("\n");
         for (auto ns : namespaces) {
             mPrinter->Print({{"namespace", ns}}, Templates::NamespaceTemplate);
@@ -67,9 +70,12 @@ public:
     }
 
     void encloseNamespaces() {
-        auto namespaces = common::getNamespaces(mDescriptor);
+        auto namespacesSize = common::getNamespaces(mDescriptor).size();
+        if (!GeneratorOptions::instance().extraNamespace().empty()) {
+            namespacesSize++;
+        }
         mPrinter->Print("\n");
-        for (size_t i = 0; i < namespaces.size(); ++i) {
+        for (size_t i = 0; i < namespacesSize; ++i) {
             mPrinter->Print(Templates::SimpleBlockEnclosureTemplate);
         }
         mPrinter->Print("\n");

+ 1 - 0
src/generator/generatorbase.h

@@ -101,6 +101,7 @@ namespace generator {
  * \param COMMENTS Enables comments copying from .proto files. If provided in parameter list message and field related comments will be copied to generated header files.
  * \param FOLDER Enables folder based generation. If provided in parameter list generator will place generated artifacts to folder structure according to package of corresponding .proto file
  * \param FIELDENUM Enables generation of field numbers as an enum within the message class.
+ * \param EXTRA_NAMESPACE <namespace> Wraps the generated code with the specified namespace(EXPERIMENTAL).
  *
  * \subsection cmake_qtprotobuf_link_target qtprotobuf_link_target
  *

+ 7 - 1
src/generator/generatorcommon.cpp

@@ -95,6 +95,10 @@ TypeMap common::produceQtTypeMap(const ::Descriptor *type, const Descriptor *sco
 TypeMap common::produceMessageTypeMap(const ::Descriptor *type, const Descriptor *scope)
 {
     std::vector<std::string> namespaceList = getNamespaces(type);
+    if (!GeneratorOptions::instance().extraNamespace().empty()) {
+        namespaceList.insert(namespaceList.begin(), GeneratorOptions::instance().extraNamespace());
+    }
+
     std::vector<std::string> nestedNamespaceList = namespaceList;
     if(isNested(type)) {
         nestedNamespaceList = getNestedNamespaces(type);
@@ -133,6 +137,9 @@ TypeMap common::produceEnumTypeMap(const EnumDescriptor *type, const Descriptor
 {
     EnumVisibility visibility = enumVisibility(type, scope);
     std::vector<std::string> namespaceList = getNamespaces(type);
+    if (!GeneratorOptions::instance().extraNamespace().empty()) {
+        namespaceList.insert(namespaceList.begin(), GeneratorOptions::instance().extraNamespace());
+    }
 
     std::string name = utils::upperCaseName(type->name());
     std::string qmlPackage = getNamespacesString(namespaceList, ".");//qml package should consist only from proto spackage
@@ -145,7 +152,6 @@ TypeMap common::produceEnumTypeMap(const EnumDescriptor *type, const Descriptor
     std::string namespaces = getNamespacesString(namespaceList, "::");
     std::string scopeNamespaces = getScopeNamespacesString(namespaces, getNamespacesString(getNamespaces(scope), "::"));
 
-
     std::string fullName = namespaces.empty() ? name : (namespaces + "::" + name);
     std::string scopeName = scopeNamespaces.empty() ? name : (scopeNamespaces + "::" + name);
 

+ 11 - 1
src/generator/generatoroptions.cpp

@@ -34,7 +34,7 @@ static const std::string QmlPluginOption("QML");
 static const std::string CommentsGenerationOption("COMMENTS");
 static const std::string FolderGenerationOption("FOLDER");
 static const std::string FieldEnumGenerationOption("FIELDENUM");
-
+static const std::string ExtraNamespaceGenerationOption("EXTRA_NAMESPACE");
 
 using namespace ::QtProtobuf::generator;
 
@@ -66,6 +66,16 @@ void GeneratorOptions::parseFromEnv(const std::string &options)
         } else if (option.compare(FieldEnumGenerationOption) == 0) {
             QT_PROTOBUF_DEBUG("set mGenerateFieldEnum: true");
             mGenerateFieldEnum = true;
+        } else if (option.find(ExtraNamespaceGenerationOption) == 0) {
+            QT_PROTOBUF_DEBUG("set mGenerateFieldEnum: true");
+            std::vector<std::string> compositeOption = utils::split(options, '=');
+            if (compositeOption.size() == 2) {
+                mExtraNamespace = compositeOption[1];
+                if (mExtraNamespace.size() > 1 && mExtraNamespace[0] == '\"'
+                        && mExtraNamespace[mExtraNamespace.size() - 1] == '\"') {
+                    mExtraNamespace = mExtraNamespace.substr(1, mExtraNamespace.size() - 2);
+                }
+            }
         }
     }
 }

+ 2 - 0
src/generator/generatoroptions.h

@@ -51,6 +51,7 @@ public:
     bool generateComments() const { return mGenerateComments; }
     bool isFolder() const { return mIsFolder; }
     bool generateFieldEnum() const { return mGenerateFieldEnum; }
+    const std::string &extraNamespace() const { return mExtraNamespace; }
 
 private:
     bool mIsMulti;
@@ -58,6 +59,7 @@ private:
     bool mGenerateComments;
     bool mIsFolder;
     bool mGenerateFieldEnum;
+    std::string mExtraNamespace;
 };
 
 }}

+ 2 - 0
tests/CMakeLists.txt

@@ -6,6 +6,8 @@ if(TARGET ${QT_VERSIONED_PREFIX}::QuickTest)
 endif()
 add_subdirectory("test_protobuf_multifile")
 add_subdirectory("test_qprotobuf_serializer_plugin")
+add_subdirectory("test_extra_namespace")
+add_subdirectory("test_extra_namespace_qml")
 
 if(TARGET ${QT_PROTOBUF_NAMESPACE}::ProtobufWellKnownTypes)
     add_subdirectory("test_wellknowntypes")

+ 17 - 0
tests/test_extra_namespace/CMakeLists.txt

@@ -0,0 +1,17 @@
+set(TARGET qtprotobuf_extranamespace_test)
+
+qt_protobuf_internal_find_dependencies()
+
+file(GLOB SOURCES
+    extra_namespace_test.cpp
+    serializationtest.cpp
+    deserializationtest.cpp)
+
+qt_protobuf_internal_add_test(TARGET ${TARGET}
+    SOURCES ${SOURCES}
+    QML FIELDENUM EXTRA_NAMESPACE "MyTestNamespace")
+qt_protobuf_internal_add_target_windeployqt(TARGET ${TARGET}
+    QML_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+add_test(NAME ${TARGET} COMMAND ${TARGET})
+

+ 75 - 0
tests/test_extra_namespace/extra_namespace_test.cpp

@@ -0,0 +1,75 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "extra_namespace.qpb.h"
+
+#include <QVariantList>
+#include <QMetaProperty>
+#include <QSignalSpy>
+
+#include <gtest/gtest.h>
+#include "../testscommon.h"
+
+using namespace MyTestNamespace::qtprotobufnamespace::tests;
+
+namespace QtProtobuf {
+namespace tests {
+
+class ExtraNamespaceTest : public ::testing::Test
+{
+public:
+    // see simpletest.proto for property names and their field indices
+    ExtraNamespaceTest()
+    {
+    }
+
+    static void SetUpTestCase();
+};
+
+void ExtraNamespaceTest::SetUpTestCase()
+{
+    QtProtobuf::qRegisterProtobufTypes();
+}
+
+TEST_F(ExtraNamespaceTest, EmptyMessageTest)
+{
+    ASSERT_EQ(MyTestNamespace::qtprotobufnamespace::tests::EmptyMessage::propertyOrdering.size(), 0);
+    ASSERT_EQ(MyTestNamespace::qtprotobufnamespace::tests::EmptyMessage::staticMetaObject.propertyCount(), 1);
+}
+
+TEST_F(ExtraNamespaceTest, ComplexMessageTest)
+{
+    const char *propertyName = "testComplexField";
+    assertMessagePropertyRegistered<MyTestNamespace::qtprotobufnamespace::tests::ComplexMessage, MyTestNamespace::qtprotobufnamespace::tests::SimpleStringMessage*>(
+                2, "MyTestNamespace::qtprotobufnamespace::tests::SimpleStringMessage*", propertyName);
+
+    ComplexMessage test;
+    ASSERT_TRUE(test.setProperty(propertyName, QVariant::fromValue<MyTestNamespace::qtprotobufnamespace::tests::SimpleStringMessage*>(new MyTestNamespace::qtprotobufnamespace::tests::SimpleStringMessage{"test qwerty"})));
+    ASSERT_TRUE(*(test.property(propertyName).value<MyTestNamespace::qtprotobufnamespace::tests::SimpleStringMessage*>()) == MyTestNamespace::qtprotobufnamespace::tests::SimpleStringMessage{"test qwerty"});
+    ASSERT_TRUE(test.testComplexField() == MyTestNamespace::qtprotobufnamespace::tests::SimpleStringMessage{"test qwerty"});
+}
+
+} // tests
+} // qtprotobuf

+ 16 - 0
tests/test_extra_namespace/proto/extra_namespace.proto

@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package qtprotobufnamespace.tests; // Generated namespaces will be prepended with
+                                   // the extra namespace
+message EmptyMessage {
+}
+
+message SimpleStringMessage {
+    string testFieldString = 6;
+}
+
+message ComplexMessage {
+    int32 testFieldInt = 1;
+    SimpleStringMessage testComplexField = 2;
+}
+

+ 27 - 0
tests/test_extra_namespace_qml/CMakeLists.txt

@@ -0,0 +1,27 @@
+set(TARGET extra_namespace_qml_test)
+
+qt_protobuf_internal_find_dependencies()
+
+set(CMAKE_AUTOMOC OFF)
+
+file(GLOB SOURCES main.cpp)
+file(GLOB QML_FILES qml/tst_extra_namespace.qml)
+
+qt5_wrap_cpp(MOC_SOURCES test.h)
+
+add_executable(${TARGET} ${MOC_SOURCES} ${SOURCES} ${QML_FILES})
+target_link_libraries(${TARGET} PRIVATE Qt5::Core Qt5::Qml Qt5::Network Qt5::Quick Qt5::Test Qt5::QuickTest ${QT_PROTOBUF_NAMESPACE}::Protobuf)
+
+if(QT_PROTOBUF_STATIC)
+    target_link_libraries(${TARGET} PRIVATE ${PROTOBUF_QUICK_PLUGIN_NAME})
+endif()
+
+qtprotobuf_link_target(${TARGET} qtprotobuf_extranamespace_test_qtprotobuf_gen)
+
+qt_protobuf_internal_add_target_qml(TARGET ${TARGET} QML_FILES ${QML_FILES})
+qt_protobuf_internal_add_target_windeployqt(TARGET ${TARGET} QML_DIR ${CMAKE_CURRENT_SOURCE_DIR}/qml)
+
+add_test(NAME ${TARGET} COMMAND ${TARGET})
+
+set_tests_properties(${TARGET} PROPERTIES
+    ENVIRONMENT QML2_IMPORT_PATH=$<TARGET_FILE_DIR:${PROTOBUF_QUICK_PLUGIN_NAME}>/..)

+ 28 - 0
tests/test_extra_namespace_qml/main.cpp

@@ -0,0 +1,28 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "test.h"
+
+QUICK_TEST_MAIN_WITH_SETUP(qtprotobuf_qml_test, TestSetup)

+ 16 - 0
tests/test_extra_namespace_qml/proto/extra_namespace.proto

@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package qtprotobufnamespace.tests; // Generated namespaces will be prepended with
+                                   // the extra namespace
+message EmptyMessage {
+}
+
+message SimpleStringMessage {
+    string testFieldString = 6;
+}
+
+message ComplexMessage {
+    int32 testFieldInt = 1;
+    SimpleStringMessage testComplexField = 2;
+}
+

+ 67 - 0
tests/test_extra_namespace_qml/qml/tst_extra_namespace.qml

@@ -0,0 +1,67 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+import QtQuick 2.12
+import QtTest 1.0
+
+import QtProtobuf 0.5
+import MyTestNamespace.qtprotobufnamespace.tests 1.0
+
+TestCase {
+    name: "Test extra namespace generation"
+    SimpleStringMessage {
+        id: stringMsg
+        testFieldString: "Test string"
+    }
+    ComplexMessage {
+        id: complexMsg
+        testComplexField: SimpleStringMessage {
+            id: innerMessage
+            testFieldString: "inner"
+        }
+    }
+
+    SimpleStringMessage {
+        id: outerMessage
+        testFieldString: "outer"
+    }
+
+    function test_simplesstringmessage() {
+        compare(stringMsg.testFieldString, "Test string", "SimpleStringMessage")
+    }
+
+    function test_complexMessage() {
+        compare(complexMsg.testComplexField, innerMessage, "Invalid object is inside complex message")
+        compare(complexMsg.testComplexField.testFieldString, "inner", "Invalid value of object inside complex message")
+
+        complexMsg.testComplexField = outerMessage
+        compare(complexMsg.testComplexField, outerMessage, "Invalid object is inside complex message")
+        compare(complexMsg.testComplexField.testFieldString, "outer", "Invalid value of object inside complex message")
+
+        complexMsg.testComplexField = innerMessage
+        compare(complexMsg.testComplexField, innerMessage, "Invalid object is inside complex message")
+        compare(complexMsg.testComplexField.testFieldString, "inner", "Invalid value of object inside complex message")
+    }
+}

+ 48 - 0
tests/test_extra_namespace_qml/test.h

@@ -0,0 +1,48 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <QtQuickTest>
+#include <QQmlEngine>
+#include <QQmlContext>
+#include <QQmlExtensionPlugin>
+
+#include "extra_namespace.qpb.h"
+
+using namespace MyTestNamespace::qtprotobufnamespace::tests;
+
+class TestSetup : public QObject {
+    Q_OBJECT
+public:
+    TestSetup() {
+        QtProtobuf::qRegisterProtobufTypes();
+        Q_PROTOBUF_IMPORT_QUICK_PLUGIN()
+    }
+    ~TestSetup() = default;
+public slots:
+    void qmlEngineAvailable(QQmlEngine *engine)
+    {
+        engine->rootContext()->setContextProperty("qVersion", QT_VERSION);
+    }
+};