d573e2ae17
Change-Id: I9f399b3752da9df930e0647536ffcd4e82beb1ac Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167856 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
507 lines
22 KiB
C++
507 lines
22 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
|
|
// instantiable implementations that appears to work well).
|
|
|
|
#include <sal/config.h>
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <set>
|
|
#include <string_view>
|
|
#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(std::u16string_view 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(
|
|
u"/singletons/com.sun.star.reflection.theTypeDescriptionManager"_ustr),
|
|
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 = {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<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: */
|