postprocess/CppunitTest_singletons.mk |   36 ++++++++++++
 postprocess/Module_postprocess.mk     |    4 +
 postprocess/qa/singletons.cxx         |   98 ++++++++++++++++++++++++++++++++++
 3 files changed, 138 insertions(+)

New commits:
commit 26e03a6571e61ba3589996f0fc19d16d495080e6
Author:     Neil Roberts <[email protected]>
AuthorDate: Mon Feb 23 14:16:50 2026 +0100
Commit:     Neil Roberts <[email protected]>
CommitDate: Mon Feb 23 16:39:43 2026 +0100

    Add a unit test that constructs every service-based singleton via class name
    
    The idea is to check that all of the class names match a singleton
    declared in a component somewhere and that they return an object that
    implements the advertised interface. Otherwise the generated getter for
    the singleton will fail with a runtime exception.
    
    Change-Id: I1352e7392e33e9182962e6279fcb62cf3b58352a
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/200056
    Reviewed-by: Neil Roberts <[email protected]>
    Tested-by: Jenkins

diff --git a/postprocess/CppunitTest_singletons.mk 
b/postprocess/CppunitTest_singletons.mk
new file mode 100644
index 000000000000..a8eccf83e31e
--- /dev/null
+++ b/postprocess/CppunitTest_singletons.mk
@@ -0,0 +1,36 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,singletons))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,singletons, \
+    postprocess/qa/singletons \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,singletons, \
+       comphelper \
+       cppu \
+       cppuhelper \
+       sal \
+       test \
+       unotest \
+       vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,singletons))
+$(eval $(call gb_CppunitTest_use_api,singletons,oovbaapi))
+
+$(eval $(call gb_CppunitTest_use_ure,singletons))
+$(eval $(call gb_CppunitTest_use_vcl,singletons))
+
+$(eval $(call gb_CppunitTest_use_rdb,singletons,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,singletons))
+
+# vim: set noet sw=4 ts=4:
diff --git a/postprocess/Module_postprocess.mk 
b/postprocess/Module_postprocess.mk
index 01aeb11afaf3..2f7290b23094 100644
--- a/postprocess/Module_postprocess.mk
+++ b/postprocess/Module_postprocess.mk
@@ -63,4 +63,8 @@ $(eval $(call gb_Module_add_check_targets,postprocess,\
 ))
 endif
 
+$(eval $(call gb_Module_add_check_targets,postprocess,\
+       CppunitTest_singletons \
+))
+
 # vim: set noet sw=4 ts=4:
diff --git a/postprocess/qa/singletons.cxx b/postprocess/qa/singletons.cxx
new file mode 100644
index 000000000000..0745aa36ea75
--- /dev/null
+++ b/postprocess/qa/singletons.cxx
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Tries to get every interface-based singleton registered in the type 
description manager using its
+// type name. See tdf#170930 for an example of a mistake that can make this 
fail.
+
+#include <sal/config.h>
+
+#include <com/sun/star/reflection/XTypeDescriptionEnumerationAccess.hpp>
+#include <com/sun/star/reflection/XSingletonTypeDescription2.hpp>
+#include <test/bootstrapfixture.hxx>
+
+namespace
+{
+constexpr OUString TYPE_DESCRIPTION_MANAGER
+    = u"/singletons/com.sun.star.reflection.theTypeDescriptionManager"_ustr;
+
+class Test : public test::BootstrapFixture
+{
+public:
+    void test();
+
+    CPPUNIT_TEST_SUITE(Test);
+    CPPUNIT_TEST(test);
+    CPPUNIT_TEST_SUITE_END();
+};
+
+void Test::test()
+{
+    css::uno::Reference<css::reflection::XTypeDescriptionEnumerationAccess> 
xTDMgr;
+
+    m_xContext->getValueByName(TYPE_DESCRIPTION_MANAGER) >>= xTDMgr;
+
+    if (!xTDMgr.is())
+        throw css::uno::RuntimeException("cannot get singleton " + 
TYPE_DESCRIPTION_MANAGER);
+
+    css::uno::Sequence<css::uno::TypeClass> aTypes = { 
css::uno::TypeClass_SINGLETON };
+
+    css::uno::Reference<css::reflection::XTypeDescriptionEnumeration> xTypeEnum
+        = xTDMgr->createTypeDescriptionEnumeration(
+            "", aTypes, css::reflection::TypeDescriptionSearchDepth_INFINITE);
+
+    while (true)
+    {
+        css::uno::Reference<css::reflection::XTypeDescription> xType;
+
+        try
+        {
+            xType = xTypeEnum->nextTypeDescription();
+        }
+        catch (css::container::NoSuchElementException&)
+        {
+            break;
+        }
+
+        css::uno::Reference<css::reflection::XSingletonTypeDescription2> 
xSingleton(
+            xType, css::uno::UNO_QUERY);
+
+        // Skip singletons that are not interface-based
+        if (!xSingleton.is() || !xSingleton->isInterfaceBased())
+            continue;
+
+        OUString sName = "/singletons/" + xSingleton->getName();
+
+        css::uno::Reference<css::uno::XInterface> xImpl;
+
+        m_xContext->getValueByName(sName) >>= xImpl;
+
+        if (!xImpl.is())
+            throw css::uno::RuntimeException("cannot get singleton " + sName);
+
+        css::uno::Reference<css::reflection::XTypeDescription> xInterface
+            = xSingleton->getInterface();
+
+        css::uno::Type interfaceType(xInterface->getTypeClass(), 
xInterface->getName());
+
+        // Make sure that the returned object supports the interface of the 
singleton
+        if (!xImpl->queryInterface(interfaceType).hasValue())
+        {
+            throw css::uno::RuntimeException("Singleton " + 
xSingleton->getName()
+                                             + " doesn’t support the " + 
xInterface->getName()
+                                             + " interface");
+        }
+    }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to