office-gobmx/postprocess/qa/services.cxx
Noel Grandin 6e35794cad terminate XDesktop properly in unit tests
So that the UNO constructor work can continue - where we need the
desktop to be disposed properly so that all UNO constructors objects
have their dispose() called, and they can clean up their global state.

We detect this case by changing a SAL_WARN to an assert in
Desktop::disposing()

(*) in ~ScTabViewShell, don't call EnterHandler, because that tries to
create EditEngine's and other stuff, which crashes
(*) Need a fake singleton so that the servicemanager calls dispose()
 on the AnalysAddIn and we can clear the global variable there.

Change-Id: Id13b51e17afc16fcbbc65d64281cdf847e4a58cf
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/99640
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
2020-07-30 10:49:27 +02:00

514 lines
23 KiB
C++

/* -*- 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
// instantiatable implementations that appears to work well).
#include <sal/config.h>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <map>
#include <set>
#include <utility>
#include <vector>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/reflection/XServiceConstructorDescription.hpp>
#include <com/sun/star/reflection/XServiceTypeDescription2.hpp>
#include <com/sun/star/frame/XDesktop.hpp>
#include <comphelper/sequence.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <rtl/strbuf.hxx>
#include <test/bootstrapfixture.hxx>
#include <vcl/svapp.hxx>
namespace {
OString msg(OUString const & string) {
return OUStringToOString(string, osl_getThreadTextEncoding());
}
OString msg(css::uno::Sequence<OUString> 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<OUString> 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<OUString> const & strings, OUString const & string)
{
return comphelper::findValue(strings, string) != -1;
}
bool contains(
css::uno::Sequence<OUString> const & strings1,
css::uno::Sequence<OUString> 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<css::reflection::XServiceTypeDescription> const & service,
std::set<css::uno::Reference<css::reflection::XServiceTypeDescription>> * 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<css::container::XHierarchicalNameAccess> const & typeManager,
OUString const & name, bool withArguments,
OUString const & implementationName,
css::uno::Sequence<OUString> const & serviceNames,
std::vector<css::uno::Reference<css::lang::XComponent>> * 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::<anon>::FactoryLoader::execute ->
// framework::Desktop::findFrame -> framework::TaskCreator::createTask ->
// <anon>::TaskCreatorService::createInstanceWithArguments ->
// <anon>::TaskCreatorService::impls_createContainerWindow ->
// <anon>::VCLXToolkit::createWindow ->
// <anon>::VCLXToolkit::ImplCreateWindow ->
// <anon>::VCLXToolkit::ImplCreateWindow -> WorkWindow::WorkWindow ->
// WorkWindow::ImplInit -> ImplBorderWindow::ImplBorderWindow ->
// ImplBorderWindow::ImplInit -> Window::ImplInit ->
// WinSalInstance::CreateFrame -> ImplSendMessage -> SendMessageW):
std::vector<OUString> denylist;
denylist.emplace_back("com.sun.star.comp.report.OReportDefinition");
// <https://bugs.documentfoundation.org/show_bug.cgi?id=89343>
// "~SwXMailMerge() goes into endless SwCache::Check()":
denylist.emplace_back("SwXMailMerge");
css::uno::Reference<css::container::XContentEnumerationAccess> enumAcc(
m_xContext->getServiceManager(), css::uno::UNO_QUERY_THROW);
css::uno::Reference<css::container::XHierarchicalNameAccess> typeMgr(
m_xContext->getValueByName(
"/singletons/com.sun.star.reflection.theTypeDescriptionManager"),
css::uno::UNO_QUERY_THROW);
const css::uno::Sequence<OUString> 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<css::lang::XServiceInfo> const & theFactory,
css::uno::Sequence<OUString> const & theServiceNames):
factory(theFactory), serviceNames(theServiceNames),
accumulationBased(false)
{}
css::uno::Reference<css::lang::XServiceInfo> const factory;
css::uno::Sequence<OUString> const serviceNames;
std::vector<Constructor> constructors;
bool accumulationBased;
};
std::map<OUString, Implementation> impls;
for (const auto& rServiceName : serviceNames) {
css::uno::Reference<css::container::XEnumeration> serviceImpls1(
enumAcc->createContentEnumeration(rServiceName),
css::uno::UNO_SET_THROW);
std::vector<css::uno::Reference<css::lang::XServiceInfo>> serviceImpls2;
while (serviceImpls1->hasMoreElements()) {
serviceImpls2.emplace_back(
serviceImpls1->nextElement(), css::uno::UNO_QUERY_THROW);
}
css::uno::Reference<css::reflection::XServiceTypeDescription2> 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<OUString> 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<css::uno::Reference<css::lang::XComponent>> 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<css::frame::XDesktop>(i, css::uno::UNO_QUERY))
i->dispose();
}
}
void Test::createInstance(
css::uno::Reference<css::container::XHierarchicalNameAccess> const & typeManager,
OUString const & name, bool withArguments,
OUString const & implementationName,
css::uno::Sequence<OUString> const & serviceNames,
std::vector<css::uno::Reference<css::lang::XComponent>> * components)
{
assert(components != nullptr);
css::uno::Reference<css::uno::XInterface> inst;
try {
if (withArguments) {
inst = m_xContext->getServiceManager()
->createInstanceWithArgumentsAndContext(
name, css::uno::Sequence<css::uno::Any>(), 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<css::lang::XComponent> comp(inst, css::uno::UNO_QUERY);
if (comp.is()) {
components->push_back(comp);
}
css::uno::Reference<css::lang::XServiceInfo> 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<OUString> 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 = {"com.sun.star.configuration.DefaultProvider"};
} else if (name == "com.sun.star.datatransfer.clipboard.SystemClipboard") {
// SystemClipboard is a wrapper returning either a platform-specific or
// the generic VCLGenericClipboard:
#if defined(_WIN32)
expImpl = "com.sun.star.datatransfer.clipboard.ClipboardW32";
#else
expImpl = "com.sun.star.datatransfer.VCLGenericClipboard";
#endif
#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 = {"com.sun.star.datatransfer.dnd.GenericDragSource"};
} 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 = {"com.sun.star.datatransfer.dnd.GenericDropTarget"};
#endif
} else if (name == "com.sun.star.ui.dialogs.FolderPicker") {
// FolderPicker is a wrapper returning either a platform-specific or the
// generic OfficeFolderPicker:
#if defined(_WIN32)
expImpl = "com.sun.star.ui.dialogs.Win32FolderPicker";
expServs = {"com.sun.star.ui.dialogs.SystemFolderPicker"};
#else
expImpl = "com.sun.star.svtools.OfficeFolderPicker";
expServs = {"com.sun.star.ui.dialogs.OfficeFolderPicker"};
#endif
} 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<OUString> 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<css::uno::Reference<css::reflection::XServiceTypeDescription>> 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<css::reflection::XServiceTypeDescription>(
typeManager->getByHierarchicalName(serv), css::uno::UNO_QUERY_THROW),
&allservs);
}
css::uno::Reference<css::beans::XPropertySetInfo> 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<css::beans::XPropertySet> 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<std::pair<OUString, OUString>> 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: */