summaryrefslogtreecommitdiffstats
path: root/framework/qa/cppunit
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--framework/qa/cppunit/data/double-loading.odtbin0 -> 13729 bytes
-rw-r--r--framework/qa/cppunit/data/empty.fodp2
-rw-r--r--framework/qa/cppunit/dispatchtest.cxx219
-rw-r--r--framework/qa/cppunit/loadenv.cxx86
-rw-r--r--framework/qa/cppunit/services.cxx157
5 files changed, 464 insertions, 0 deletions
diff --git a/framework/qa/cppunit/data/double-loading.odt b/framework/qa/cppunit/data/double-loading.odt
new file mode 100644
index 000000000..ce990fc5c
--- /dev/null
+++ b/framework/qa/cppunit/data/double-loading.odt
Binary files differ
diff --git a/framework/qa/cppunit/data/empty.fodp b/framework/qa/cppunit/data/empty.fodp
new file mode 100644
index 000000000..3c2a4cf2c
--- /dev/null
+++ b/framework/qa/cppunit/data/empty.fodp
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.presentation"><office:body><office:presentation><draw:page/></office:presentation></office:body></office:document>
diff --git a/framework/qa/cppunit/dispatchtest.cxx b/framework/qa/cppunit/dispatchtest.cxx
new file mode 100644
index 000000000..16a4ecb51
--- /dev/null
+++ b/framework/qa/cppunit/dispatchtest.cxx
@@ -0,0 +1,219 @@
+/* -*- 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/.
+ */
+
+#include <cppuhelper/implbase.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/DispatchHelper.hpp>
+#include <com/sun/star/frame/XDispatchProviderInterceptor.hpp>
+#include <com/sun/star/frame/XInterceptorInfo.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <rtl/ref.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Sample interception implementation that asserts getInterceptedURLs() and queryDispatch() is in sync.
+class MyInterceptor
+ : public cppu::WeakImplHelper<frame::XDispatchProviderInterceptor, frame::XInterceptorInfo>
+{
+ uno::Reference<frame::XDispatchProvider> m_xMaster;
+ uno::Reference<frame::XDispatchProvider> m_xSlave;
+ uno::Sequence<OUString> m_aDisabledCommands;
+ int m_nExpected;
+ int m_nUnexpected;
+
+public:
+ MyInterceptor();
+
+ /// Number of queryDispatch() calls that operate on a command advertised by getInterceptedURLs().
+ int getExpected();
+ /// Number of queryDispatch() calls that operate on a command not advertised by getInterceptedURLs().
+ int getUnexpected();
+
+ // frame::XInterceptorInfo
+ virtual uno::Sequence<OUString> SAL_CALL getInterceptedURLs() override;
+
+ // frame::XDispatchProviderInterceptor
+ virtual void SAL_CALL setMasterDispatchProvider(
+ const uno::Reference<frame::XDispatchProvider>& xNewSupplier) override;
+ virtual uno::Reference<frame::XDispatchProvider> SAL_CALL getMasterDispatchProvider() override;
+ virtual void SAL_CALL
+ setSlaveDispatchProvider(const uno::Reference<frame::XDispatchProvider>& xNewSupplier) override;
+ virtual uno::Reference<frame::XDispatchProvider> SAL_CALL getSlaveDispatchProvider() override;
+
+ // frame::XDispatchProvider
+ virtual uno::Sequence<uno::Reference<frame::XDispatch>> SAL_CALL
+ queryDispatches(const uno::Sequence<frame::DispatchDescriptor>& rRequests) override;
+ virtual uno::Reference<frame::XDispatch>
+ SAL_CALL queryDispatch(const util::URL& rURL, const OUString& rTargetFrameName,
+ sal_Int32 SearchFlags) override;
+};
+
+MyInterceptor::MyInterceptor()
+ : m_aDisabledCommands{ ".uno:Bold" }
+ , m_nExpected(0)
+ , m_nUnexpected(0)
+{
+}
+
+int MyInterceptor::getExpected()
+{
+ int nRet = m_nExpected;
+ m_nExpected = 0;
+ return nRet;
+}
+
+int MyInterceptor::getUnexpected()
+{
+ int nRet = m_nUnexpected;
+ m_nUnexpected = 0;
+ return nRet;
+}
+
+uno::Sequence<OUString> MyInterceptor::getInterceptedURLs() { return m_aDisabledCommands; }
+
+void MyInterceptor::setMasterDispatchProvider(
+ const uno::Reference<frame::XDispatchProvider>& xNewSupplier)
+{
+ m_xMaster = xNewSupplier;
+}
+
+uno::Reference<frame::XDispatchProvider> MyInterceptor::getMasterDispatchProvider()
+{
+ return m_xMaster;
+}
+
+void MyInterceptor::setSlaveDispatchProvider(
+ const uno::Reference<frame::XDispatchProvider>& xNewSupplier)
+{
+ m_xSlave = xNewSupplier;
+}
+
+uno::Reference<frame::XDispatchProvider> MyInterceptor::getSlaveDispatchProvider()
+{
+ return m_xSlave;
+}
+
+uno::Sequence<uno::Reference<frame::XDispatch>>
+MyInterceptor::queryDispatches(const uno::Sequence<frame::DispatchDescriptor>& rRequests)
+{
+ uno::Sequence<uno::Reference<frame::XDispatch>> aResult(rRequests.getLength());
+ auto aResultRange = asNonConstRange(aResult);
+
+ for (sal_Int32 i = 0; i < rRequests.getLength(); ++i)
+ {
+ aResultRange[i] = queryDispatch(rRequests[i].FeatureURL, rRequests[i].FrameName,
+ rRequests[i].SearchFlags);
+ }
+
+ return aResult;
+}
+
+uno::Reference<frame::XDispatch> MyInterceptor::queryDispatch(const util::URL& rURL,
+ const OUString& /*rTargetFrameName*/,
+ sal_Int32 /*SearchFlags*/)
+{
+ if (std::find(std::cbegin(m_aDisabledCommands), std::cend(m_aDisabledCommands), rURL.Complete)
+ != std::cend(m_aDisabledCommands))
+ ++m_nExpected;
+ else
+ ++m_nUnexpected;
+
+ return uno::Reference<frame::XDispatch>();
+}
+
+/// Tests how InterceptionHelper invokes a registered interceptor.
+class DispatchTest : public test::BootstrapFixture, public unotest::MacrosTest
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ virtual void setUp() override;
+ virtual void tearDown() override;
+};
+
+void DispatchTest::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+void DispatchTest::tearDown()
+{
+ if (mxComponent.is())
+ mxComponent->dispose();
+
+ test::BootstrapFixture::tearDown();
+}
+
+CPPUNIT_TEST_FIXTURE(DispatchTest, testInterception)
+{
+ mxComponent = loadFromDesktop("private:factory/swriter", "com.sun.star.text.TextDocument");
+ uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xModel.is());
+
+ uno::Reference<frame::XDispatchProviderInterception> xRegistration(
+ xModel->getCurrentController()->getFrame(), uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xRegistration.is());
+
+ rtl::Reference<MyInterceptor> pInterceptor(new MyInterceptor());
+ xRegistration->registerDispatchProviderInterceptor(pInterceptor);
+
+ dispatchCommand(mxComponent, ".uno:Bold", {});
+ CPPUNIT_ASSERT_EQUAL(1, pInterceptor->getExpected());
+ CPPUNIT_ASSERT_EQUAL(0, pInterceptor->getUnexpected());
+ dispatchCommand(mxComponent, ".uno:Italic", {});
+ CPPUNIT_ASSERT_EQUAL(1, pInterceptor->getExpected());
+ // This was 1: MyInterceptor::queryDispatch() was called for .uno:Italic.
+ CPPUNIT_ASSERT_EQUAL(0, pInterceptor->getUnexpected());
+}
+
+constexpr OUStringLiteral DATA_DIRECTORY = u"/framework/qa/cppunit/data/";
+
+CPPUNIT_TEST_FIXTURE(DispatchTest, testSfxOfficeDispatchDispose)
+{
+ // this test doesn't work with a new document because of aURL.Main check in SfxBaseController::dispatch()
+ mxComponent = loadFromDesktop(m_directories.getURLFromSrc(DATA_DIRECTORY) + "empty.fodp",
+ "com.sun.star.presentation.PresentationDocument");
+ uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xModel.is());
+ uno::Reference<frame::XController> xController(xModel->getCurrentController());
+ CPPUNIT_ASSERT(xController.is());
+ uno::Reference<frame::XDispatchProvider> xFrame(xController->getFrame(), uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xFrame.is());
+
+ uno::Reference<util::XURLTransformer> xParser(util::URLTransformer::create(mxComponentContext));
+ util::URL url;
+ url.Complete = xModel->getURL() + "#dummy";
+ xParser->parseStrict(url);
+
+ uno::Reference<frame::XDispatch> xDisp(xFrame->queryDispatch(url, "", 0));
+ CPPUNIT_ASSERT(xDisp.is());
+
+ mxComponent->dispose();
+
+ util::URL urlSlot;
+ urlSlot.Complete = "slot:5598";
+ xParser->parseStrict(urlSlot);
+ // crashed with UAF
+ xDisp->dispatch(urlSlot, {});
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/qa/cppunit/loadenv.cxx b/framework/qa/cppunit/loadenv.cxx
new file mode 100644
index 000000000..4842645bb
--- /dev/null
+++ b/framework/qa/cppunit/loadenv.cxx
@@ -0,0 +1,86 @@
+/* -*- 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/.
+ */
+
+#include <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/svapp.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Covers framework/source/loadenv/ fixes.
+class Test : public test::BootstrapFixture, public unotest::MacrosTest
+{
+public:
+ void setUp() override;
+};
+
+void Test::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+constexpr OUStringLiteral DATA_DIRECTORY = u"/framework/qa/cppunit/data/";
+
+class DocumentOpener
+{
+public:
+ DECL_STATIC_LINK(DocumentOpener, OpenDocument, void*, void);
+};
+
+IMPL_STATIC_LINK(DocumentOpener, OpenDocument, void*, pArg, void)
+{
+ CPPUNIT_ASSERT(pArg);
+ auto pURL = static_cast<OUString*>(pArg);
+ uno::Reference<uno::XComponentContext> xComponentContext
+ = comphelper::getProcessComponentContext();
+ uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(xComponentContext);
+ xDesktop->loadComponentFromURL(*pURL, "_default", 0, {});
+ delete pURL;
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testDoubleLoading)
+{
+ // Try to load the same document twice. This is similar to trying to execute the soffice process
+ // twice: in that case the 2nd instance forwards to the 1st instance and then uses the same code
+ // path.
+ for (int i = 0; i < 2; ++i)
+ {
+ auto pURL = std::make_unique<OUString>(m_directories.getURLFromSrc(DATA_DIRECTORY)
+ + "double-loading.odt");
+ Application::PostUserEvent(LINK(nullptr, DocumentOpener, OpenDocument), pURL.release());
+ }
+ Scheduler::ProcessEventsToIdle();
+
+ // Verify that the 2nd load didn't happen, since it's the same document.
+ uno::Reference<frame::XFrames> xFrames = mxDesktop->getFrames();
+ // Without the accompanying fix in place, this failed with:
+ // - Expected: 1
+ // - Actual : 2
+ // i.e. the document was loaded twice, into two separate frames/windows.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xFrames->getCount());
+
+ // Close the document, now that we know we have a single one.
+ uno::Reference<frame::XFrame> xFrame(xFrames->getByIndex(0), uno::UNO_QUERY);
+ xFrame->getController()->getModel()->dispose();
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/qa/cppunit/services.cxx b/framework/qa/cppunit/services.cxx
new file mode 100644
index 000000000..873ea5938
--- /dev/null
+++ b/framework/qa/cppunit/services.cxx
@@ -0,0 +1,157 @@
+/* -*- 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/.
+ */
+
+#include <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/XComponentLoader.hpp>
+#include <com/sun/star/frame/FrameSearchFlag.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <salhelper/thread.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/wrkwin.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Covers framework/source/services/ fixes.
+class Test : public test::BootstrapFixture, public unotest::MacrosTest
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ void setUp() override;
+ void tearDown() override;
+ uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+};
+
+void Test::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+void Test::tearDown()
+{
+ if (mxComponent.is())
+ mxComponent->dispose();
+
+ test::BootstrapFixture::tearDown();
+}
+
+/// Invokes XFrameImpl::loadComponentFromURL() on a thread.
+class TestThread : public salhelper::Thread
+{
+ uno::Reference<frame::XComponentLoader> mxComponentLoader;
+ uno::Reference<lang::XComponent>& mrComponent;
+
+public:
+ TestThread(const uno::Reference<frame::XComponentLoader>& xComponentLoader,
+ uno::Reference<lang::XComponent>& rComponent);
+ void execute() override;
+};
+
+TestThread::TestThread(const uno::Reference<frame::XComponentLoader>& xComponentLoader,
+ uno::Reference<lang::XComponent>& rComponent)
+ : salhelper::Thread("TestThread")
+ , mxComponentLoader(xComponentLoader)
+ , mrComponent(rComponent)
+{
+}
+
+void TestThread::execute()
+{
+ sal_Int32 nSearchFlags = frame::FrameSearchFlag::AUTO;
+ uno::Sequence<beans::PropertyValue> aArguments = {
+ comphelper::makePropertyValue("OnMainThread", true),
+ };
+ // Note how this is invoking loadComponentFromURL() on a frame, not on the desktop, as usual.
+ mrComponent = mxComponentLoader->loadComponentFromURL("private:factory/swriter", "_self",
+ nSearchFlags, aArguments);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testLoadComponentFromURL)
+{
+ // Without the accompanying fix in place, this test would have failed with:
+ // thread 1: comphelper::SolarMutex::doRelease end: m_nCount is 1
+ // thread 2: vcl::SolarThreadExecutor::execute: before SolarMutexReleaser ctor
+ // thread 2: comphelper::SolarMutex::doRelease start: m_nCount is 1, bUnlockAll is 1
+ // thread 2: comphelper::SolarMutex::doRelease: failed IsCurrentThread() check, will abort
+ // Notice how thread 2 attempts to release the solar mutex while thread 1 holds it.
+
+ // Create a default window, so by the time the thread would post a user event, it doesn't need
+ // the solar mutex to process a SendMessageW() call on Windows.
+ ScopedVclPtrInstance<WorkWindow> xWindow(nullptr, WB_APP | WB_STDWORK);
+ // Variable is not used, it holds the default window.
+ (void)xWindow;
+
+ rtl::Reference<TestThread> xThread;
+ {
+ // Start the thread that will load the component, but hold the solar mutex for now, so we
+ // can see if it blocks.
+ SolarMutexGuard guard;
+ uno::Reference<frame::XFrame> xFrame = mxDesktop->findFrame("_blank", /*nSearchFlags=*/0);
+ uno::Reference<frame::XComponentLoader> xComponentLoader(xFrame, uno::UNO_QUERY);
+ xThread = new TestThread(xComponentLoader, getComponent());
+ xThread->launch();
+ // If loadComponentFromURL() doesn't lock the solar mutex, the test will abort here.
+ osl::Thread::wait(std::chrono::seconds(1));
+ }
+ {
+ // Now release the solar mutex, so the thread can post the task on the main loop.
+ SolarMutexReleaser releaser;
+ osl::Thread::wait(std::chrono::seconds(1));
+ }
+ {
+ // Spin the main loop.
+ SolarMutexGuard guard;
+ Scheduler::ProcessEventsToIdle();
+ }
+ {
+ // Stop the thread.
+ SolarMutexReleaser releaser;
+ xThread->join();
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testURLTransformer_parseSmart)
+{
+ // Without the accompanying fix in place, this test would have failed with
+ // "www.example.com:" treated as scheme, "/8080/foo/" as path, "bar?q=baz"
+ // as name, and "F" as fragment.
+
+ css::util::URL aURL;
+ aURL.Complete = "www.example.com:8080/foo/bar?q=baz#F";
+ css::uno::Reference xParser(css::util::URLTransformer::create(mxComponentContext));
+ CPPUNIT_ASSERT(xParser->parseSmart(aURL, "http:"));
+ CPPUNIT_ASSERT_EQUAL(OUString("http://www.example.com:8080/foo/bar?q=baz#F"), aURL.Complete);
+ CPPUNIT_ASSERT_EQUAL(OUString("http://www.example.com:8080/foo/bar"), aURL.Main);
+ CPPUNIT_ASSERT_EQUAL(OUString("http://"), aURL.Protocol);
+ CPPUNIT_ASSERT(aURL.User.isEmpty());
+ CPPUNIT_ASSERT(aURL.Password.isEmpty());
+ CPPUNIT_ASSERT_EQUAL(OUString("www.example.com"), aURL.Server);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(8080), aURL.Port);
+ CPPUNIT_ASSERT_EQUAL(OUString("/foo/"), aURL.Path);
+ CPPUNIT_ASSERT_EQUAL(OUString("bar"), aURL.Name);
+ CPPUNIT_ASSERT_EQUAL(OUString("q=baz"), aURL.Arguments);
+ CPPUNIT_ASSERT_EQUAL(OUString("F"), aURL.Mark);
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */