/* -*- 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/. */ // Try to instantiate as many implementations as possible. Finds all // implementations reachable via the service manager. If a given implementation // is the only implementor of some service that has a zero-parameter // constructor, instantiate the implementation through that service name. If a // given implementation does not offer any such constructors (because it does not // support any single-interface--based service, or because for each relevant // service there are multiple implementations or it does not have an appropriate // constructor) but does support at least one accumulation-based service, then // instantiate it through its implementation name (a heuristic to identify // instantiable implementations that appears to work well). #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { OString msg(std::u16string_view string) { return OUStringToOString(string, osl_getThreadTextEncoding()); } OString msg(css::uno::Sequence const & strings) { OStringBuffer buf("{"); for (sal_Int32 i = 0; i != strings.getLength(); ++i) { if (i != 0) { buf.append(", "); } buf.append('"'); buf.append(msg(strings[i])); buf.append('"'); } buf.append('}'); return buf.makeStringAndClear(); } bool unique(css::uno::Sequence const & strings) { // Assumes small sequences for which quadratic algorithm is acceptable: for (sal_Int32 i = 0; i < strings.getLength() - 1; ++i) { for (sal_Int32 j = i + 1; j != strings.getLength(); ++j) { if (strings[j] == strings[i]) { return false; } } } return true; } bool contains( css::uno::Sequence const & strings, OUString const & string) { return comphelper::findValue(strings, string) != -1; } bool contains( css::uno::Sequence const & strings1, css::uno::Sequence const & strings2) { // Assumes small sequences for which quadratic algorithm is acceptable: return std::all_of(strings2.begin(), strings2.end(), [&strings1](const OUString& rStr) { return contains(strings1, rStr); }); } void addService( css::uno::Reference const & service, std::set> * allServices) { assert(allServices != nullptr); if (!allServices->insert(service).second) { return; } const auto aMandatoryServices = service->getMandatoryServices(); for (auto const & serv : aMandatoryServices) { addService(serv, allServices); } } class Test: public test::BootstrapFixture { public: void test(); CPPUNIT_TEST_SUITE(Test); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); private: void createInstance( css::uno::Reference const & typeManager, OUString const & name, bool withArguments, OUString const & implementationName, css::uno::Sequence const & serviceNames, std::vector> * components); }; void Test::test() { // On Windows, denylist the com.sun.star.comp.report.OReportDefinition // implementation (reportdesign::OReportDefinition in // reportdesign/source/core/api/ReportDefinition.cxx), as it spawns a thread // that forever blocks in SendMessageW when no VCL event loop is running // (reportdesign::::FactoryLoader::execute -> // framework::Desktop::findFrame -> framework::TaskCreator::createTask -> // ::TaskCreatorService::createInstanceWithArguments -> // ::TaskCreatorService::impls_createContainerWindow -> // ::VCLXToolkit::createWindow -> // ::VCLXToolkit::ImplCreateWindow -> // ::VCLXToolkit::ImplCreateWindow -> WorkWindow::WorkWindow -> // WorkWindow::ImplInit -> ImplBorderWindow::ImplBorderWindow -> // ImplBorderWindow::ImplInit -> Window::ImplInit -> // WinSalInstance::CreateFrame -> ImplSendMessage -> SendMessageW): std::vector denylist; denylist.emplace_back("com.sun.star.comp.report.OReportDefinition"); // // "~SwXMailMerge() goes into endless SwCache::Check()": denylist.emplace_back("SwXMailMerge"); css::uno::Reference enumAcc( m_xContext->getServiceManager(), css::uno::UNO_QUERY_THROW); css::uno::Reference typeMgr( m_xContext->getValueByName( u"/singletons/com.sun.star.reflection.theTypeDescriptionManager"_ustr), css::uno::UNO_QUERY_THROW); const css::uno::Sequence serviceNames( m_xContext->getServiceManager()->getAvailableServiceNames()); struct Constructor { Constructor( OUString const & theServiceName, bool theDefaultConstructor): serviceName(theServiceName), defaultConstructor(theDefaultConstructor) {} OUString serviceName; bool defaultConstructor; }; struct Implementation { Implementation( css::uno::Reference const & theFactory, css::uno::Sequence const & theServiceNames): factory(theFactory), serviceNames(theServiceNames), accumulationBased(false) {} css::uno::Reference const factory; css::uno::Sequence const serviceNames; std::vector constructors; bool accumulationBased; }; std::map impls; for (const auto& rServiceName : serviceNames) { css::uno::Reference serviceImpls1( enumAcc->createContentEnumeration(rServiceName), css::uno::UNO_SET_THROW); std::vector> serviceImpls2; while (serviceImpls1->hasMoreElements()) { serviceImpls2.emplace_back( serviceImpls1->nextElement(), css::uno::UNO_QUERY_THROW); } css::uno::Reference desc; if (typeMgr->hasByHierarchicalName(rServiceName)) { desc.set( typeMgr->getByHierarchicalName(rServiceName), css::uno::UNO_QUERY_THROW); } if (serviceImpls2.empty()) { if (desc.is()) { CPPUNIT_ASSERT_MESSAGE( (OString( "no implementations of single-interface--based \"" + msg(rServiceName) + "\"") .getStr()), !desc->isSingleInterfaceBased()); std::cout << "accumulation-based service \"" << rServiceName << "\" without implementations\n"; } else { std::cout << "fantasy service name \"" << rServiceName << "\" without implementations\n"; } } else { for (auto const & j: serviceImpls2) { OUString name(j->getImplementationName()); auto k = impls.find(name); if (k == impls.end()) { css::uno::Sequence servs( j->getSupportedServiceNames()); CPPUNIT_ASSERT_MESSAGE( (OString( "implementation \"" + msg(name) + "\" supports non-unique " + msg(servs)) .getStr()), unique(servs)); k = impls.insert( std::make_pair(name, Implementation(j, servs))) .first; } else { CPPUNIT_ASSERT_MESSAGE( (OString( "multiple implementations named \"" + msg(name) + "\"") .getStr()), bool(j == k->second.factory)); } CPPUNIT_ASSERT_MESSAGE( (OString( "implementation \"" + msg(name) + "\" supports " + msg(k->second.serviceNames) + " but not \"" + msg(rServiceName) + "\"") .getStr()), contains(k->second.serviceNames, rServiceName)); if (desc.is()) { if (desc->isSingleInterfaceBased()) { if (serviceImpls2.size() == 1) { const css::uno::Sequence< css::uno::Reference< css::reflection::XServiceConstructorDescription>> ctors(desc->getConstructors()); auto pCtor = std::find_if(ctors.begin(), ctors.end(), [](const auto& rCtor) { return !rCtor->getParameters().hasElements(); }); if (pCtor != ctors.end()) k->second.constructors.emplace_back( rServiceName, (*pCtor)->isDefaultConstructor()); } } else { k->second.accumulationBased = true; } } else { std::cout << "implementation \"" << name << "\" supports fantasy service name \"" << rServiceName << "\"\n"; } } } } std::vector> comps; for (auto const & i: impls) { if (std::find(denylist.begin(), denylist.end(), i.first) == denylist.end()) { if (i.second.constructors.empty()) { if (i.second.accumulationBased) { createInstance( typeMgr, i.first, false, i.first, i.second.serviceNames, &comps); } else { std::cout << "no obvious way to instantiate implementation \"" << i.first << "\"\n"; } } else { for (auto const & j: i.second.constructors) { createInstance( typeMgr, j.serviceName, !j.defaultConstructor, i.first, i.second.serviceNames, &comps); } } } } SolarMutexReleaser rel; for (auto const & i: comps) { // cannot call dispose() on XDesktop before calling terminate() if (!css::uno::Reference(i, css::uno::UNO_QUERY)) i->dispose(); } } void Test::createInstance( css::uno::Reference const & typeManager, OUString const & name, bool withArguments, OUString const & implementationName, css::uno::Sequence const & serviceNames, std::vector> * components) { assert(components != nullptr); css::uno::Reference inst; try { if (withArguments) { inst = m_xContext->getServiceManager() ->createInstanceWithArgumentsAndContext( name, css::uno::Sequence(), m_xContext); } else { inst = m_xContext->getServiceManager()->createInstanceWithContext( name, m_xContext); } } catch (css::uno::Exception & e) { css::uno::Any a(cppu::getCaughtException()); CPPUNIT_FAIL( OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" caused " + msg(a.getValueTypeName()) + " \"" + msg(e.Message) + "\"") .getStr()); } CPPUNIT_ASSERT_MESSAGE( (OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" returned null reference") .getStr()), inst.is()); css::uno::Reference comp(inst, css::uno::UNO_QUERY); if (comp.is()) { components->push_back(comp); } css::uno::Reference info( inst, css::uno::UNO_QUERY); CPPUNIT_ASSERT_MESSAGE( (OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" does not provide XServiceInfo") .getStr()), info.is()); OUString expImpl(implementationName); css::uno::Sequence expServs(serviceNames); // Special cases: if (name == "com.sun.star.comp.configuration.ConfigurationProvider") { // Instantiating a ConfigurationProvider with no or empty args must // return theDefaultProvider: expImpl = "com.sun.star.comp.configuration.DefaultProvider"; expServs = {u"com.sun.star.configuration.DefaultProvider"_ustr}; } else if (name == "com.sun.star.datatransfer.clipboard.SystemClipboard") { // SystemClipboard is a wrapper returning either a platform-specific or // the generic VCLGenericClipboard: expImpl = "com.sun.star.datatransfer.VCLGenericClipboard"; #if !defined(_WIN32) } else if (name == "com.sun.star.comp.datatransfer.dnd.OleDragSource_V1" || name == "com.sun.star.datatransfer.dnd.XdndSupport") { expImpl = "com.sun.star.datatransfer.dnd.VclGenericDragSource"; expServs = {u"com.sun.star.datatransfer.dnd.GenericDragSource"_ustr}; } else if (name == "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1" || name == "com.sun.star.datatransfer.dnd.XdndDropTarget") { expImpl = "com.sun.star.datatransfer.dnd.VclGenericDropTarget"; expServs = {u"com.sun.star.datatransfer.dnd.GenericDropTarget"_ustr}; #endif } else if (name == "com.sun.star.ui.dialogs.FolderPicker") { // FolderPicker is a wrapper returning either a platform-specific or the // generic OfficeFolderPicker. In headless mode it is always the // generic one. expImpl = "com.sun.star.svtools.OfficeFolderPicker"; expServs = {u"com.sun.star.ui.dialogs.OfficeFolderPicker"_ustr}; } else if (expImpl == "com.sun.star.comp.Calc.SpreadsheetDocument") { expImpl = "ScModelObj"; } else if (expImpl == "com.sun.star.comp.Draw.DrawingDocument" || expImpl == "com.sun.star.comp.Draw.PresentationDocument") { expImpl = "SdXImpressDocument"; } else if (expImpl == "com.sun.star.comp.Writer.GlobalDocument" || expImpl == "com.sun.star.comp.Writer.TextDocument" || expImpl == "com.sun.star.comp.Writer.WebDocument") { expImpl = "SwXTextDocument"; } CPPUNIT_ASSERT_EQUAL_MESSAGE( (OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" reports wrong implementation name") .getStr()), expImpl, info->getImplementationName()); const css::uno::Sequence servs(info->getSupportedServiceNames()); CPPUNIT_ASSERT_MESSAGE( (OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" reports non-unique " + msg(servs)) .getStr()), unique(servs)); // Some implementations like "com.sun.star.comp.Calc.SpreadsheetDocument" // report sub-services like // "com.sun.star.sheet.SpreadsheetDocumentSettings", and // "com.sun.star.document.OfficeDocument" that are not listed in the // .component file, so check for containment instead of equality: CPPUNIT_ASSERT_MESSAGE( (OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" reports " + msg(servs) + " different from " + msg(expServs)) .getStr()), contains(servs, expServs)); std::set> allservs; for (auto const & serv: servs) { if (!typeManager->hasByHierarchicalName(serv)) { std::cout << "instantiating \"" << implementationName << "\" via \"" << name << "\" supports fantasy service name \"" << serv << "\"\n"; continue; } addService( css::uno::Reference( typeManager->getByHierarchicalName(serv), css::uno::UNO_QUERY_THROW), &allservs); } css::uno::Reference propsinfo; for (auto const & serv: allservs) { auto const props = serv->getProperties(); for (auto const & prop: props) { auto const optional = (prop->getPropertyFlags() & css::beans::PropertyAttribute::OPTIONAL) != 0; if (!propsinfo.is()) { css::uno::Reference propset(inst, css::uno::UNO_QUERY); if (!propset.is()) { CPPUNIT_ASSERT_MESSAGE( (OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" reports service " + msg(serv->getName()) + " with non-optional property \"" + msg(prop->getName()) + "\" but does not implement css.uno.XPropertySet") .getStr()), optional); continue; } propsinfo = propset->getPropertySetInfo(); if (!propsinfo.is()) { //TODO: legal to return null in more cases? ("@returns NULL if the // implementation cannot or will not provide information about the properties") CPPUNIT_ASSERT_MESSAGE( (OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" reports service " + msg(serv->getName()) + " with non-optional property \"" + msg(prop->getName()) + "\" but css.uno.XPropertySet::getPropertySetInfo returns null") .getStr()), optional); continue; } } if (!propsinfo->hasPropertyByName(prop->getName())) { static std::set> const denylist{ {"com.sun.star.comp.chart.DataSeries", "BorderDash"}, {"com.sun.star.comp.chart2.ChartDocumentWrapper", "UserDefinedAttributes"}, {"com.sun.star.comp.dbu.OColumnControlModel", "Tabstop"}, {"com.sun.star.comp.report.OFormattedField", "Align"}, {"com.sun.star.comp.report.OFormattedField", "BackgroundColor"}, {"com.sun.star.comp.report.OFormattedField", "Border"}, {"com.sun.star.comp.report.OFormattedField", "DefaultControl"}, {"com.sun.star.comp.report.OFormattedField", "EffectiveDefault"}, {"com.sun.star.comp.report.OFormattedField", "EffectiveMax"}, {"com.sun.star.comp.report.OFormattedField", "EffectiveMin"}, {"com.sun.star.comp.report.OFormattedField", "EffectiveValue"}, {"com.sun.star.comp.report.OFormattedField", "Enabled"}, {"com.sun.star.comp.report.OFormattedField", "FontEmphasisMark"}, {"com.sun.star.comp.report.OFormattedField", "FontRelief"}, {"com.sun.star.comp.report.OFormattedField", "HelpText"}, {"com.sun.star.comp.report.OFormattedField", "HelpURL"}, {"com.sun.star.comp.report.OFormattedField", "MaxTextLen"}, {"com.sun.star.comp.report.OFormattedField", "Printable"}, {"com.sun.star.comp.report.OFormattedField", "ReadOnly"}, {"com.sun.star.comp.report.OFormattedField", "Spin"}, {"com.sun.star.comp.report.OFormattedField", "Tabstop"}, {"com.sun.star.comp.report.OFormattedField", "Text"}, {"com.sun.star.comp.report.OFormattedField", "TextColor"}, {"com.sun.star.comp.report.OFormattedField", "TextLineColor"}, {"com.sun.star.comp.report.OFormattedField", "TreatAsNumber"}, {"stardiv.Toolkit.UnoControlRoadmapModel", "Interactive"}}; if (denylist.find({implementationName, prop->getName()}) != denylist.end()) { continue; } CPPUNIT_ASSERT_MESSAGE( (OString( "instantiating \"" + msg(implementationName) + "\" via \"" + msg(name) + "\" reports service " + msg(serv->getName()) + " with non-optional property \"" + msg(prop->getName()) + ("\" but css.uno.XPropertySet::getPropertySetInfo's hasPropertyByName" " returns false")) .getStr()), optional); } } } } CPPUNIT_TEST_SUITE_REGISTRATION(Test); } CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */