diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /unotest/source | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'unotest/source')
24 files changed, 2179 insertions, 0 deletions
diff --git a/unotest/source/cpp/bootstrapfixturebase.cxx b/unotest/source/cpp/bootstrapfixturebase.cxx new file mode 100644 index 000000000..dd92d3822 --- /dev/null +++ b/unotest/source/cpp/bootstrapfixturebase.cxx @@ -0,0 +1,43 @@ +/* -*- 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 <sal/config.h> +#include <config_features.h> + +#include <unotest/bootstrapfixturebase.hxx> +#include <comphelper/processfactory.hxx> +#if HAVE_FEATURE_SCRIPTING +#include <basic/sbstar.hxx> +#endif + +using namespace ::com::sun::star; + +// NB. this constructor is called before any tests are run, once for each +// test function in a rather non-intuitive way. This is why all the 'real' +// heavy lifting is deferred until setUp. setUp and tearDown are interleaved +// between the tests as you might expect. +test::BootstrapFixtureBase::BootstrapFixtureBase() {} + +test::BootstrapFixtureBase::~BootstrapFixtureBase() {} + +void test::BootstrapFixtureBase::setUp() +{ + m_xContext = comphelper::getProcessComponentContext(); + m_xFactory = m_xContext->getServiceManager(); + m_xSFactory.set(m_xFactory, uno::UNO_QUERY_THROW); +} + +void test::BootstrapFixtureBase::tearDown() +{ +#if HAVE_FEATURE_SCRIPTING + StarBASIC::DetachAllDocBasicItems(); +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/directories.cxx b/unotest/source/cpp/directories.cxx new file mode 100644 index 000000000..93bcd4dae --- /dev/null +++ b/unotest/source/cpp/directories.cxx @@ -0,0 +1,66 @@ +/* -*- 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 <sal/config.h> + +#include <cppunit/TestAssert.h> +#include <osl/file.hxx> +#include <unotest/directories.hxx> + +namespace +{ +OUString getFileURLFromSystemPath(OUString const& path) +{ + OUString url; + osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath(path, url); + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, e); + if (!url.endsWith("/")) + { + url += "/"; + } + return url; +} +} + +test::Directories::Directories() +{ + const char* pSrcRoot = getenv("SRC_ROOT"); + CPPUNIT_ASSERT_MESSAGE("SRC_ROOT env variable not set", pSrcRoot != nullptr); + CPPUNIT_ASSERT_MESSAGE("SRC_ROOT env variable not set", pSrcRoot[0] != 0); + const char* pWorkdirRoot = getenv("WORKDIR_FOR_BUILD"); + CPPUNIT_ASSERT_MESSAGE("$WORKDIR_FOR_BUILD env variable not set", pWorkdirRoot != nullptr); + CPPUNIT_ASSERT_MESSAGE("$WORKDIR_FOR_BUILD env variable not set", pWorkdirRoot[0] != 0); + m_aSrcRootPath = OUString::createFromAscii(pSrcRoot); + m_aSrcRootURL = getFileURLFromSystemPath(m_aSrcRootPath); + + m_aWorkdirRootPath = OUString::createFromAscii(pWorkdirRoot); + m_aWorkdirRootURL = getFileURLFromSystemPath(m_aWorkdirRootPath); +} + +OUString test::Directories::getURLFromSrc(std::u16string_view rPath) const +{ + return m_aSrcRootURL + rPath; +} + +OUString test::Directories::getPathFromSrc(std::u16string_view rPath) const +{ + return m_aSrcRootPath + rPath; +} + +OUString test::Directories::getURLFromWorkdir(std::u16string_view rPath) const +{ + return m_aWorkdirRootURL + rPath; +} + +OUString test::Directories::getPathFromWorkdir(std::u16string_view rPath) const +{ + return m_aWorkdirRootPath + rPath; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/filters-test.cxx b/unotest/source/cpp/filters-test.cxx new file mode 100644 index 000000000..7adbd9ca4 --- /dev/null +++ b/unotest/source/cpp/filters-test.cxx @@ -0,0 +1,170 @@ +/* -*- 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 <sal/config.h> + +#include <set> +#include <string_view> + +#include <unotest/filters-test.hxx> +#include <osl/file.hxx> +#include <osl/thread.h> +#include <rtl/cipher.h> + +#include <cppunit/TestAssert.h> + +namespace test { + +static void decode(const OUString& rIn, const OUString &rOut) +{ + rtlCipher cipher = rtl_cipher_create(rtl_Cipher_AlgorithmARCFOUR, rtl_Cipher_ModeStream); + CPPUNIT_ASSERT_MESSAGE("cipher creation failed", cipher != nullptr); + + //mcrypt --bare -a arcfour -o hex -k 435645 -s 3 + const sal_uInt8 aKey[3] = {'C', 'V', 'E'}; + + rtlCipherError result = rtl_cipher_init(cipher, rtl_Cipher_DirectionDecode, aKey, SAL_N_ELEMENTS(aKey), nullptr, 0); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("cipher init failed", rtl_Cipher_E_None, result); + + osl::File aIn(rIn); + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, aIn.open(osl_File_OpenFlag_Read)); + + osl::File aOut(rOut); + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, aOut.open(osl_File_OpenFlag_Write)); + + sal_uInt8 in[8192]; + sal_uInt8 out[8192]; + sal_uInt64 nBytesRead, nBytesWritten; + while(true) + { + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, aIn.read(in, sizeof(in), nBytesRead)); + if (!nBytesRead) + break; + CPPUNIT_ASSERT_EQUAL(rtl_Cipher_E_None, rtl_cipher_decode(cipher, in, nBytesRead, out, sizeof(out))); + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, aOut.write(out, nBytesRead, nBytesWritten)); + CPPUNIT_ASSERT_EQUAL(nBytesRead, nBytesWritten); + } + + rtl_cipher_destroy(cipher); +} + +void FiltersTest::recursiveScan(filterStatus nExpected, + const OUString &rFilter, const OUString &rURL, + const OUString &rUserData, SfxFilterFlags nFilterFlags, + SotClipboardFormatId nClipboardID, unsigned int nFilterVersion, bool bExport) +{ + osl::Directory aDir(rURL); + + CPPUNIT_ASSERT_EQUAL_MESSAGE(OUString("Failed to open directory " + rURL).toUtf8().getStr(), osl::FileBase::E_None, aDir.open()); + osl::DirectoryItem aItem; + osl::FileStatus aFileStatus(osl_FileStatus_Mask_FileURL|osl_FileStatus_Mask_Type); + std::set<OUString> dirs; + std::set<OUString> files; + while (aDir.getNextItem(aItem) == osl::FileBase::E_None) + { + aItem.getFileStatus(aFileStatus); + OUString sURL = aFileStatus.getFileURL(); + if (aFileStatus.getFileType() == osl::FileStatus::Directory) + { + dirs.insert(sURL); + } + else + { + files.insert(sURL); + } + } + for (auto const & sURL: dirs) { + recursiveScan(nExpected, rFilter, sURL, rUserData, + nFilterFlags, nClipboardID, nFilterVersion, bExport); + } + for (auto const & sURL: files) { + OUString sTmpFile; + bool bEncrypted = false; + + sal_Int32 nLastSlash = sURL.lastIndexOf('/'); + + if ((nLastSlash != -1) && (nLastSlash+1 < sURL.getLength())) + { + //ignore .files + if (sURL[nLastSlash+1] == '.') + continue; + + if ( + (sURL.match("BID", nLastSlash+1)) || + (sURL.match("CVE", nLastSlash+1)) || + (sURL.match("EDB", nLastSlash+1)) || + (sURL.match("RC4", nLastSlash+1)) // just means "encrypted" + ) + { + bEncrypted = true; + } + } + + OString aRes( + OString::Concat(bExport ? std::string_view("save") : std::string_view("load")) + " " + + OUStringToOString(sURL, osl_getThreadTextEncoding())); + + OUString realUrl; + if (bEncrypted) + { + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, osl::FileBase::createTempFile(nullptr, nullptr, &sTmpFile)); + decode(sURL, sTmpFile); + realUrl = sTmpFile; + } + else + { + realUrl = sURL; + } + + //output name early, so in the case of a hang, the name of + //the hanging input file is visible + fprintf(stderr, "Testing %s:\n", aRes.getStr()); + sal_uInt32 nStartTime = osl_getGlobalTimer(); + bool bRes; + if (!bExport) + bRes = load(rFilter, realUrl, rUserData, nFilterFlags, + nClipboardID, nFilterVersion); + else + bRes = save(rFilter, realUrl, rUserData, nFilterFlags, + nClipboardID, nFilterVersion); + sal_uInt32 nEndTime = osl_getGlobalTimer(); + + if (bEncrypted) + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, osl::File::remove(sTmpFile)); + + fprintf(stderr, "Tested %s: %s (%" SAL_PRIuUINT32 "ms)\n", + aRes.getStr(), bRes?"Pass":"Fail", nEndTime-nStartTime); + if (nExpected == test::indeterminate) + continue; + filterStatus nResult = bRes ? test::pass : test::fail; + CPPUNIT_ASSERT_EQUAL_MESSAGE(aRes.getStr(), nExpected, nResult); + } + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, aDir.close()); +} + +void FiltersTest::testDir(const OUString &rFilter, + std::u16string_view rURL, const OUString &rUserData, + SfxFilterFlags nFilterFlags, SotClipboardFormatId nClipboardID, + unsigned int nFilterVersion, bool bExport) +{ + recursiveScan(test::pass, rFilter, + OUString::Concat(rURL) + "pass", + rUserData, nFilterFlags, nClipboardID, nFilterVersion, bExport); + recursiveScan(test::fail, rFilter, + OUString::Concat(rURL) + "fail", + rUserData, nFilterFlags, nClipboardID, nFilterVersion, bExport); + recursiveScan(test::indeterminate, rFilter, + OUString::Concat(rURL) + "indeterminate", + rUserData, nFilterFlags, nClipboardID, nFilterVersion, bExport); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/getargument.cxx b/unotest/source/cpp/getargument.cxx new file mode 100644 index 000000000..22cd39fc5 --- /dev/null +++ b/unotest/source/cpp/getargument.cxx @@ -0,0 +1,36 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <osl/diagnose.h> +#include <rtl/bootstrap.hxx> +#include <rtl/ustring.hxx> +#include <unotest/getargument.hxx> + +namespace test +{ +bool getArgument(std::u16string_view name, OUString* value) +{ + OSL_ASSERT(value != nullptr); + return rtl::Bootstrap::get(OUString::Concat("arg-") + name, *value); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/gettestargument.cxx b/unotest/source/cpp/gettestargument.cxx new file mode 100644 index 000000000..0fa181678 --- /dev/null +++ b/unotest/source/cpp/gettestargument.cxx @@ -0,0 +1,34 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <rtl/ustring.hxx> +#include <unotest/getargument.hxx> +#include <unotest/gettestargument.hxx> + +namespace test +{ +bool getTestArgument(std::u16string_view name, OUString* value) +{ + return getArgument(OUStringConcatenation(OUString::Concat("testarg.") + name), value); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/macros_test.cxx b/unotest/source/cpp/macros_test.cxx new file mode 100644 index 000000000..76105b88b --- /dev/null +++ b/unotest/source/cpp/macros_test.cxx @@ -0,0 +1,218 @@ +/* -*- 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 <unotest/macros_test.hxx> + +#include <vector> + +#include <com/sun/star/document/MacroExecMode.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/frame/DispatchHelper.hpp> +#include <com/sun/star/packages/zip/ZipFileAccess.hpp> +#include <com/sun/star/security/XCertificate.hpp> + +#include <basic/basrdll.hxx> +#include <cppunit/TestAssert.h> +#include <comphelper/sequence.hxx> +#include <comphelper/processfactory.hxx> +#include <unotest/directories.hxx> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <tools/datetime.hxx> +#include <unotools/tempfile.hxx> +#include <unotools/ucbstreamhelper.hxx> + +using namespace css; + +namespace unotest +{ +MacrosTest::MacrosTest() + : mpDll(std::make_unique<BasicDLL>()) +{ +} + +MacrosTest::~MacrosTest() = default; + +uno::Reference<css::lang::XComponent> +MacrosTest::loadFromDesktop(const OUString& rURL, const OUString& rDocService, + const uno::Sequence<beans::PropertyValue>& rExtraArgs) +{ + CPPUNIT_ASSERT_MESSAGE("no desktop", mxDesktop.is()); + std::vector<beans::PropertyValue> args; + beans::PropertyValue aMacroValue; + aMacroValue.Name = "MacroExecutionMode"; + aMacroValue.Handle = -1; + aMacroValue.Value <<= document::MacroExecMode::ALWAYS_EXECUTE_NO_WARN; + aMacroValue.State = beans::PropertyState_DIRECT_VALUE; + args.push_back(aMacroValue); + + if (!rDocService.isEmpty()) + { + beans::PropertyValue aValue; + aValue.Name = "DocumentService"; + aValue.Handle = -1; + aValue.Value <<= rDocService; + aValue.State = beans::PropertyState_DIRECT_VALUE; + args.push_back(aValue); + } + + args.insert(args.end(), rExtraArgs.begin(), rExtraArgs.end()); + + uno::Reference<lang::XComponent> xComponent = mxDesktop->loadComponentFromURL( + rURL, "_default", 0, comphelper::containerToSequence(args)); + OUString sMessage = "loading failed: " + rURL; + CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sMessage, RTL_TEXTENCODING_UTF8).getStr(), + xComponent.is()); + return xComponent; +} + +void MacrosTest::dispatchCommand(const uno::Reference<lang::XComponent>& xComponent, + const OUString& rCommand, + const uno::Sequence<beans::PropertyValue>& rPropertyValues) +{ + uno::Reference<frame::XController> xController + = uno::Reference<frame::XModel>(xComponent, uno::UNO_QUERY_THROW)->getCurrentController(); + CPPUNIT_ASSERT(xController.is()); + uno::Reference<frame::XDispatchProvider> xFrame(xController->getFrame(), uno::UNO_QUERY); + CPPUNIT_ASSERT(xFrame.is()); + + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + uno::Reference<frame::XDispatchHelper> xDispatchHelper(frame::DispatchHelper::create(xContext)); + CPPUNIT_ASSERT(xDispatchHelper.is()); + + xDispatchHelper->executeDispatch(xFrame, rCommand, OUString(), 0, rPropertyValues); +} + +std::unique_ptr<SvStream> MacrosTest::parseExportStream(const utl::TempFile& rTempFile, + const OUString& rStreamName) +{ + const OUString aUrl = rTempFile.GetURL(); + uno::Reference<uno::XComponentContext> xComponentContext + = comphelper::getProcessComponentContext(); + uno::Reference<packages::zip::XZipFileAccess2> const xZipNames( + packages::zip::ZipFileAccess::createWithURL(xComponentContext, aUrl)); + uno::Reference<io::XInputStream> const xInputStream(xZipNames->getByName(rStreamName), + uno::UNO_QUERY); + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true)); + return pStream; +} + +void MacrosTest::setUpNssGpg(const test::Directories& rDirectories, const OUString& rTestName) +{ + OUString aSourceDir = rDirectories.getURLFromSrc(u"/test/signing-keys/"); + OUString aTargetDir = rDirectories.getURLFromWorkdir( + OUStringConcatenation("CppunitTest/" + rTestName + ".test.user")); + + // Set up NSS database in workdir/CppunitTest/ + osl::File::copy(aSourceDir + "cert9.db", aTargetDir + "/cert9.db"); + osl::File::copy(aSourceDir + "key4.db", aTargetDir + "/key4.db"); + osl::File::copy(aSourceDir + "pkcs11.txt", aTargetDir + "/pkcs11.txt"); + + // Make gpg use our own defined setup & keys + osl::File::copy(aSourceDir + "pubring.gpg", aTargetDir + "/pubring.gpg"); + osl::File::copy(aSourceDir + "random_seed", aTargetDir + "/random_seed"); + osl::File::copy(aSourceDir + "secring.gpg", aTargetDir + "/secring.gpg"); + osl::File::copy(aSourceDir + "trustdb.gpg", aTargetDir + "/trustdb.gpg"); + + OUString aTargetPath; + osl::FileBase::getSystemPathFromFileURL(aTargetDir, aTargetPath); + +#ifdef _WIN32 + // CryptoAPI test certificates + osl::File::copy(aSourceDir + "test.p7b", aTargetDir + "/test.p7b"); + OUString caVar("LIBO_TEST_CRYPTOAPI_PKCS7"); + osl_setEnvironment(caVar.pData, aTargetPath.pData); +#else + OUString mozCertVar("MOZILLA_CERTIFICATE_FOLDER"); + // explicit prefix with "sql:" needed for CentOS7 system NSS 3.67 + osl_setEnvironment(mozCertVar.pData, OUString("sql:" + aTargetPath).pData); +#endif + OUString gpgHomeVar("GNUPGHOME"); + osl_setEnvironment(gpgHomeVar.pData, aTargetPath.pData); + +#if HAVE_GPGCONF_SOCKETDIR + auto const ldPath = std::getenv("LIBO_LD_PATH"); + m_gpgconfCommandPrefix + = ldPath == nullptr ? OString() : OString::Concat("LD_LIBRARY_PATH=") + ldPath + " "; + OString path; + bool ok = aTargetPath.convertToString(&path, osl_getThreadTextEncoding(), + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR + | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR); + // if conversion fails, at least provide a best-effort conversion in the message here, for + // context + CPPUNIT_ASSERT_MESSAGE(OUStringToOString(aTargetPath, RTL_TEXTENCODING_UTF8).getStr(), ok); + m_gpgconfCommandPrefix += "GNUPGHOME=" + path + " " GPGME_GPGCONF; + // HAVE_GPGCONF_SOCKETDIR is only defined in configure.ac for Linux for now, so (a) std::system + // behavior will conform to POSIX (and the relevant env var to set is named LD_LIBRARY_PATH), and + // (b) gpgconf --create-socketdir should return zero: + OString cmd = m_gpgconfCommandPrefix + " --create-socketdir"; + int res = std::system(cmd.getStr()); + CPPUNIT_ASSERT_EQUAL_MESSAGE(cmd.getStr(), 0, res); +#else + (void)this; +#endif +} + +void MacrosTest::tearDownNssGpg() +{ +#if HAVE_GPGCONF_SOCKETDIR + // HAVE_GPGCONF_SOCKETDIR is only defined in configure.ac for Linux for now, so (a) std::system + // behavior will conform to POSIX, and (b) gpgconf --remove-socketdir should return zero: + OString cmd = m_gpgconfCommandPrefix + " --remove-socketdir"; + int res = std::system(cmd.getStr()); + CPPUNIT_ASSERT_EQUAL_MESSAGE(cmd.getStr(), 0, res); +#else + (void)this; +#endif +} + +namespace +{ +struct Valid +{ + DateTime now; + OUString subjectName; + Valid(const css::uno::Sequence<css::beans::PropertyValue>& rFilterData) + : now(DateTime::SYSTEM) + { + for (const auto& propVal : rFilterData) + { + if (propVal.Name == "SignCertificateSubjectName") + propVal.Value >>= subjectName; + } + } + bool operator()(const css::uno::Reference<css::security::XCertificate>& cert) const + { + if (!now.IsBetween(cert->getNotValidBefore(), cert->getNotValidAfter())) + return false; + if (!subjectName.isEmpty() && subjectName != cert->getSubjectName()) + return false; + return true; + } +}; +} + +bool MacrosTest::IsValid(const css::uno::Reference<css::security::XCertificate>& cert) +{ + const Valid test({}); + return test(cert); +} + +css::uno::Reference<css::security::XCertificate> MacrosTest::GetValidCertificate( + const css::uno::Sequence<css::uno::Reference<css::security::XCertificate>>& certs, + const css::uno::Sequence<css::beans::PropertyValue>& rFilterData) +{ + if (auto it = std::find_if(certs.begin(), certs.end(), Valid(rFilterData)); it != certs.end()) + return *it; + return {}; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/officeconnection.cxx b/unotest/source/cpp/officeconnection.cxx new file mode 100644 index 000000000..649d636d0 --- /dev/null +++ b/unotest/source/cpp/officeconnection.cxx @@ -0,0 +1,152 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/bridge/UnoUrlResolver.hpp> +#include <com/sun/star/bridge/XUnoUrlResolver.hpp> +#include <com/sun/star/connection/NoConnectException.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/bootstrap.hxx> +#include <cppunit/TestAssert.h> +#include <osl/process.h> +#include <osl/test/uniquepipename.hxx> +#include <osl/time.h> +#include <sal/macros.h> +#include <unotest/getargument.hxx> +#include <unotest/officeconnection.hxx> +#include <unotest/toabsolutefileurl.hxx> + +namespace test { + +OfficeConnection::OfficeConnection(): process_(nullptr) {} + +OfficeConnection::~OfficeConnection() {} + +void OfficeConnection::setUp() { + css::uno::Reference< css::bridge::XUnoUrlResolver > resolver( + css::bridge::UnoUrlResolver::create( + cppu::defaultBootstrap_InitialComponentContext())); + OUString desc; + OUString argSoffice; + CPPUNIT_ASSERT( + getArgument( + u"soffice", + &argSoffice)); + if (argSoffice.match("path:")) { + desc = "pipe,name=" + osl::test::uniquePipeName("oootest"); + OUString noquickArg("--quickstart=no"); + OUString norestoreArg("--norestore"); + OUString nologoArg("--nologo"); + // disable use of the unix standalone splash screen app for the + // tests (probably not needed in combination with --headless?) + OUString headlessArg("--headless"); + OUString acceptArg("--accept=" + desc + ";urp"); + OUString argUser; + CPPUNIT_ASSERT( + getArgument(u"user", &argUser)); + OUString userArg("-env:UserInstallation=" + toAbsoluteFileUrl(argUser)); + OUString jreArg( + "-env:UNO_JAVA_JFW_ENV_JREHOME=true"); + rtl_uString * args[] = { + noquickArg.pData, norestoreArg.pData, + nologoArg.pData, headlessArg.pData, acceptArg.pData, userArg.pData, + jreArg.pData }; + rtl_uString ** envs = nullptr; + OUString argEnv; + if (getArgument(u"env", &argEnv)) + { + envs = &argEnv.pData; + } + // coverity[callee_ptr_arith] - arith is fine + CPPUNIT_ASSERT_EQUAL( + osl_Process_E_None, + osl_executeProcess( + toAbsoluteFileUrl( + argSoffice.copy(RTL_CONSTASCII_LENGTH("path:"))).pData, + args, SAL_N_ELEMENTS(args), 0, nullptr, nullptr, envs, envs == nullptr ? 0 : 1, + &process_)); + } else if (argSoffice.match("connect:")) { + desc = argSoffice.copy(RTL_CONSTASCII_LENGTH("connect:")); + } else { + CPPUNIT_FAIL( + "\"soffice\" argument starts with neither \"path:\" nor" + " \"connect:\""); + } + for (;;) { + try { + context_ = + css::uno::Reference< css::uno::XComponentContext >( + resolver->resolve( + "uno:" + desc + ";urp;StarOffice.ComponentContext"), + css::uno::UNO_QUERY_THROW); + break; + } catch (css::connection::NoConnectException &) {} + if (process_ != nullptr) { + TimeValue delay = { 1, 0 }; // 1 sec + CPPUNIT_ASSERT_EQUAL( + osl_Process_E_TimedOut, + osl_joinProcessWithTimeout(process_, &delay)); + } + } +} + +void OfficeConnection::tearDown() { + if (process_ == nullptr) + return; + + if (context_.is()) { + css::uno::Reference< css::frame::XDesktop2 > desktop = css::frame::Desktop::create( context_ ); + context_.clear(); + try { + CPPUNIT_ASSERT(desktop->terminate()); + desktop.clear(); + } catch (css::lang::DisposedException &) {} + // it appears that DisposedExceptions can already happen while + // receiving the response of the terminate call + } + CPPUNIT_ASSERT_EQUAL(osl_Process_E_None, osl_joinProcess(process_)); + oslProcessInfo info; + info.Size = sizeof info; + CPPUNIT_ASSERT_EQUAL( + osl_Process_E_None, + osl_getProcessInfo(process_, osl_Process_EXITCODE, &info)); + CPPUNIT_ASSERT_EQUAL(oslProcessExitCode(0), info.Code); + osl_freeProcessHandle(process_); + process_ = nullptr; // guard against subsequent calls to isStillAlive +} + + +bool OfficeConnection::isStillAlive() const { + if (process_ == nullptr) { + // In case "soffice" argument starts with "connect:" we have no direct + // control over the liveness of the soffice.bin process (would need to + // directly monitor the bridge) so can only assume the best here: + return true; + } + TimeValue delay = { 0, 0 }; // 0 sec + oslProcessError e = osl_joinProcessWithTimeout(process_, &delay); + CPPUNIT_ASSERT(e == osl_Process_E_None || e == osl_Process_E_TimedOut); + return e == osl_Process_E_TimedOut; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/toabsolutefileurl.cxx b/unotest/source/cpp/toabsolutefileurl.cxx new file mode 100644 index 000000000..ea8b6f66c --- /dev/null +++ b/unotest/source/cpp/toabsolutefileurl.cxx @@ -0,0 +1,61 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/uno/RuntimeException.hpp> +#include <osl/file.hxx> +#include <osl/process.h> +#include <rtl/ustring.hxx> +#include <unotest/toabsolutefileurl.hxx> + +namespace test { + +OUString toAbsoluteFileUrl(OUString const & relativePathname) { + OUString cwd; + oslProcessError e1 = osl_getProcessWorkingDir(&cwd.pData); + if (e1 != osl_Process_E_None) { + throw css::uno::RuntimeException( + "osl_getProcessWorkingDir failed with " + OUString::number(e1)); + } + OUString url; + osl::FileBase::RC e2 = osl::FileBase::getFileURLFromSystemPath( + relativePathname, url); + if (e2 != osl::FileBase::E_None) { + throw css::uno::RuntimeException( + "osl::FileBase::getFileURLFromSystemPath(" + + relativePathname + + ") failed with " + + OUString::number(e2)); + } + OUString absUrl; + e2 = osl::FileBase::getAbsoluteFileURL(cwd, url, absUrl); + if (e2 != osl::FileBase::E_None) { + throw css::uno::RuntimeException( + "osl::FileBase::getAbsoluteFileURL(" + + cwd + ", " + url + + ") failed with " + + OUString::number(e2)); + } + return absUrl; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/unobootstrapprotector/unobootstrapprotector.cxx b/unotest/source/cpp/unobootstrapprotector/unobootstrapprotector.cxx new file mode 100644 index 000000000..5ad175b9d --- /dev/null +++ b/unotest/source/cpp/unobootstrapprotector/unobootstrapprotector.cxx @@ -0,0 +1,93 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/uno/Exception.hpp> + +#include <cppuhelper/bootstrap.hxx> +#include <comphelper/processfactory.hxx> + +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <sal/types.h> + +#include <cppunit/Protector.h> + +namespace { + +using namespace com::sun::star; + +//cppunit calls instantiates a new TextFixture for each test and calls setUp +//and tearDown on that for every test in a fixture + +//We basically need to call dispose on our root component context to +//shut down cleanly in the right order. + +//But we can't setup and tear down the root component context for +//every test because all the uno singletons will be invalid after +//the first dispose. So lets setup the default context once before +//all tests are run, and tear it down once after all have finished + +class Prot : public CppUnit::Protector +{ +public: + Prot(); + + virtual ~Prot() override; + + Prot(const Prot&) = delete; + Prot& operator=(const Prot&) = delete; + + virtual bool protect( + CppUnit::Functor const & functor, + CppUnit::ProtectorContext const & context) override; +private: + uno::Reference<uno::XComponentContext> m_xContext; +}; + + +Prot::Prot() + : m_xContext(cppu::defaultBootstrap_InitialComponentContext()) +{ + uno::Reference<lang::XMultiComponentFactory> xFactory = m_xContext->getServiceManager(); + uno::Reference<lang::XMultiServiceFactory> xSFactory(xFactory, uno::UNO_QUERY_THROW); + + comphelper::setProcessServiceFactory(xSFactory); +} + +bool Prot::protect( + CppUnit::Functor const & functor, CppUnit::ProtectorContext const &) +{ + return functor(); +} + +Prot::~Prot() +{ + uno::Reference< lang::XComponent >(m_xContext, uno::UNO_QUERY_THROW)->dispose(); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT CppUnit::Protector * unobootstrapprotector() +{ + return new Prot; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/cpp/unoexceptionprotector/unoexceptionprotector.cxx b/unotest/source/cpp/unoexceptionprotector/unoexceptionprotector.cxx new file mode 100644 index 000000000..7a5b17b75 --- /dev/null +++ b/unotest/source/cpp/unoexceptionprotector/unoexceptionprotector.cxx @@ -0,0 +1,81 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <string> +#include <string_view> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <cppunit/Message.h> +#include <osl/thread.h> +#include <rtl/string.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +#include <cppunit/Protector.h> + +namespace { + +// Best effort conversion: +std::string convert(std::u16string_view s16) { + OString s8(OUStringToOString(s16, osl_getThreadTextEncoding())); + static_assert(sizeof (sal_Int32) <= sizeof (std::string::size_type), "got to be at least equal"); + // ensure following cast is legitimate + return std::string( + s8.getStr(), static_cast< std::string::size_type >(s8.getLength())); +} + +class Prot : public CppUnit::Protector +{ +public: + Prot() {} + + Prot(const Prot&) = delete; + Prot& operator=(const Prot&) = delete; + + virtual bool protect( + CppUnit::Functor const & functor, + CppUnit::ProtectorContext const & context) override; +}; + +bool Prot::protect( + CppUnit::Functor const & functor, CppUnit::ProtectorContext const & context) +{ + try { + return functor(); + } catch (const css::uno::Exception &e) { + css::uno::Any a(cppu::getCaughtException()); + reportError( + context, + CppUnit::Message( + "An uncaught exception of type " + convert(a.getValueTypeName()), + convert(e.Message))); + } + return false; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT CppUnit::Protector * +unoexceptionprotector() { + return new Prot; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotest/source/java/org/openoffice/test/Argument.java b/unotest/source/java/org/openoffice/test/Argument.java new file mode 100644 index 000000000..294e14489 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/Argument.java @@ -0,0 +1,27 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package org.openoffice.test; + +public final class Argument { + public static String get(String name) { + return System.getProperty("org.openoffice.test.arg." + name); + } + + private Argument() {} +} diff --git a/unotest/source/java/org/openoffice/test/FileHelper.java b/unotest/source/java/org/openoffice/test/FileHelper.java new file mode 100644 index 000000000..93907ed92 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/FileHelper.java @@ -0,0 +1,46 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package org.openoffice.test; + +/* + * Helper Functions for File handling + */ +public class FileHelper +{ + /* + * Concat a _sRelativePathToAdd to a _sPath and append a '/' to the _sPath only if need. + * + * @return a right concatenated path + */ + public static String appendPath(String _sPath, String _sRelativePathToAdd) + { + String sNewPath = _sPath; + String fs = System.getProperty("file.separator"); + if (_sPath.startsWith("file:")) + { + fs = "/"; // we use a file URL so only '/' is allowed. + } + if (! (sNewPath.endsWith("/") || sNewPath.endsWith("\\") ) ) + { + sNewPath += fs; + } + sNewPath += _sRelativePathToAdd; + return sNewPath; + } +} diff --git a/unotest/source/java/org/openoffice/test/OfficeConnection.java b/unotest/source/java/org/openoffice/test/OfficeConnection.java new file mode 100644 index 000000000..0801da6e0 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/OfficeConnection.java @@ -0,0 +1,285 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package org.openoffice.test; + +import com.sun.star.bridge.UnoUrlResolver; +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.comp.helper.Bootstrap; +import com.sun.star.connection.NoConnectException; +import com.sun.star.frame.XDesktop; +import com.sun.star.lang.DisposedException; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Map; +import java.util.UUID; +import static org.junit.Assert.*; + +/** Start up and shut down an OOo instance. + + Details about the OOo instance are tunneled in via + org.openoffice.test.arg... system properties. +*/ + + +public final class OfficeConnection { + /** Start up an OOo instance. + */ + public void setUp() throws Exception { + String sofficeArg = Argument.get("soffice"); + if (sofficeArg.startsWith("path:")) { + description = "pipe,name=oootest" + UUID.randomUUID(); + ProcessBuilder pb = new ProcessBuilder( + sofficeArg.substring("path:".length()), "--quickstart=no", + "--norestore", "--nologo", "--headless", + "--accept=" + description + ";urp", + "-env:UserInstallation=" + Argument.get("user"), + "-env:UNO_JAVA_JFW_ENV_JREHOME=true"); + String workdirArg = Argument.get("workdir"); + if (workdirArg != null) { + pb.directory(new File(workdirArg)); + } + String envArg = Argument.get("env"); + if (envArg != null) { + Map<String, String> env = pb.environment(); + int i = envArg.indexOf('='); + if (i == -1) { + env.remove(envArg); + } else { + env.put(envArg.substring(0, i), envArg.substring(i + 1)); + } + } + process = pb.start(); + outForward = new Forward(process.getInputStream(), System.out); + outForward.start(); + errForward = new Forward(process.getErrorStream(), System.err); + errForward.start(); + } else if (sofficeArg.startsWith("connect:")) { + description = sofficeArg.substring("connect:".length()); + } else { + fail( + "\"soffice\" argument \"" + sofficeArg + + " starts with neither \"path:\" nor \"connect:\""); + } + XUnoUrlResolver resolver = UnoUrlResolver.create( + Bootstrap.createInitialComponentContext(null)); + for (;;) { + try { + context = UnoRuntime.queryInterface( + XComponentContext.class, + resolver.resolve( + "uno:" + description + + ";urp;StarOffice.ComponentContext")); + break; + } catch (NoConnectException e) {} + if (process != null) { + assertNull(waitForProcess(process, 1000)); // 1 sec + } + } + } + + /** Shut down the OOo instance. + */ + public void tearDown() + throws InterruptedException, com.sun.star.uno.Exception + { + boolean cleanTermination = false; + int code = 0; + try { + if (process != null) { + if (context != null) { + XDesktop desktop = null; + try { + XMultiComponentFactory factory = + context.getServiceManager(); + assertNotNull(factory); + desktop = UnoRuntime.queryInterface(XDesktop.class, + factory.createInstanceWithContext( + "com.sun.star.frame.Desktop", context)); + } catch (DisposedException e) { + // it can happen that the Java bridge was disposed + // already, we want to ensure soffice.bin is killed + process.destroy(); + } + context = null; + if (desktop != null) { + try { + boolean desktopTerminated = desktop.terminate(); + if (!desktopTerminated) { + // in case terminate() fails we would wait + // forever for the process to die, so kill it + process.destroy(); + } + assertTrue(desktopTerminated); + } catch (DisposedException e) {} + // it appears that DisposedExceptions can already happen + // while receiving the response of the terminate call + } + desktop = null; + } else { + process.destroy(); + } + } + if (process != null) { + code = process.waitFor(); + } + boolean outTerminated = outForward == null + || outForward.terminated(); + boolean errTerminated = errForward == null + || errForward.terminated(); + assertEquals(0, code); + cleanTermination = true; + assertTrue(outTerminated); + assertTrue(errTerminated); + } finally { + if (!cleanTermination) { + try { + String sofficeArg = Argument.get("soffice"); + String workdir = Argument.get("workdir"); + String postprocesscommand = Argument.get( + "postprocesscommand"); + if (sofficeArg.startsWith("path:") && workdir != null + && postprocesscommand != null) + { + ProcessBuilder pb = new ProcessBuilder( + postprocesscommand, + sofficeArg.substring("path:".length()) + ".bin", + workdir, String.valueOf(code)); + Process postprocess = pb.start(); + Forward ppoutForward = new Forward( + postprocess.getInputStream(), System.out); + ppoutForward.start(); + Forward pperrForward = new Forward( + postprocess.getErrorStream(), System.err); + pperrForward.start(); + code = postprocess.waitFor(); + if (code != 0) { + throw new PostprocessFailedException(code); + } + } + } + catch (IOException e) { + throw new PostprocessFailedException(e); + } + } + } + } + + /** Obtain the component context of the running OOo instance. + */ + public XComponentContext getComponentContext() { + return context; + } + + //TODO: get rid of this hack for legacy qa/unoapi tests + public String getDescription() { + return description; + } + + private static Integer waitForProcess(Process process, final int millis) + throws InterruptedException + { + final Thread t1 = Thread.currentThread(); + Thread t2 = new Thread("waitForProcess") { + @Override + public void run() { + util.utils.pause(millis); + t1.interrupt(); + } + }; + boolean old = Thread.interrupted(); + // clear interrupted status, get old status + t2.start(); + int n = 0; + boolean done = false; + try { + n = process.waitFor(); + done = true; + } catch (InterruptedException e) {} + t2.interrupt(); + try { + t2.join(); + } catch (InterruptedException e) { + t2.join(); + } + Thread.interrupted(); // clear interrupted status + if (old) { + t1.interrupt(); // reset old status + } + return done ? Integer.valueOf(n) : null; + } + + private static final class Forward extends Thread { + public Forward(InputStream in, PrintStream out) { + super("process output forwarder"); + this.in = in; + this.out = out; + } + + @Override + public void run() { + for (;;) { + byte[] buf = new byte[1024]; + int n; + try { + n = in.read(buf); + } catch (IOException e) { + throw new RuntimeException("wrapping", e); + } + if (n == -1) { + break; + } + out.write(buf, 0, n); + } + done = true; + } + + public boolean terminated() throws InterruptedException { + join(); + return done; + } + + private final InputStream in; + private final PrintStream out; + private boolean done = false; + } + + private static final class PostprocessFailedException + extends RuntimeException + { + PostprocessFailedException(int exitCode) { + super("postprocessing failed with exit code " + exitCode); + } + + PostprocessFailedException(IOException cause) { + super("postprocessing failed with IOException " + cause, cause); + } + } + + private String description; + private Process process = null; + private Forward outForward = null; + private Forward errForward = null; + private XComponentContext context = null; +} +// vim:set et sw=4 sts=4: diff --git a/unotest/source/java/org/openoffice/test/OfficeFileUrl.java b/unotest/source/java/org/openoffice/test/OfficeFileUrl.java new file mode 100644 index 000000000..f908f3642 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/OfficeFileUrl.java @@ -0,0 +1,33 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package org.openoffice.test; + +import java.io.File; + +/** Obtain the office-internal absolute file URL of a given file. + */ +public final class OfficeFileUrl { + public static String getAbsolute(File file) { + return file.getAbsoluteFile().toURI().toString().replaceFirst( + "\\A[Ff][Ii][Ll][Ee]:/(?=[^/]|\\z)", "file:///"); + // file:/path -> file:///path + } + + private OfficeFileUrl() {} +} diff --git a/unotest/source/java/org/openoffice/test/UnoApiTest.java b/unotest/source/java/org/openoffice/test/UnoApiTest.java new file mode 100644 index 000000000..44dd47231 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/UnoApiTest.java @@ -0,0 +1,47 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package org.openoffice.test; + +import org.openoffice.Runner; +import static org.junit.Assert.*; +import org.junit.*; + +public final class UnoApiTest { + @Before public void setUp() throws Exception { + connection.setUp(); + } + + @After public void tearDown() + throws InterruptedException, com.sun.star.uno.Exception + { + connection.tearDown(); + } + + @Test public void test() throws Exception { + assertTrue("org.openoffice.Runner failed with params: -sce " + Argument.get("sce") + + "-xcl " + Argument.get("xcl") + + " -tdoc " + Argument.get("tdoc") + + " -cs " + connection.getDescription(), + Runner.run( + "-sce", Argument.get("sce"), "-xcl", Argument.get("xcl"), "-tdoc", + Argument.get("tdoc"), "-cs", connection.getDescription())); + } + + private final OfficeConnection connection = new OfficeConnection(); +} diff --git a/unotest/source/java/org/openoffice/test/tools/DocumentType.java b/unotest/source/java/org/openoffice/test/tools/DocumentType.java new file mode 100644 index 000000000..bb4745f7b --- /dev/null +++ b/unotest/source/java/org/openoffice/test/tools/DocumentType.java @@ -0,0 +1,37 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package org.openoffice.test.tools; + +/** a helper "enumeration class" for classifying a document type +*/ +public class DocumentType extends com.sun.star.uno.Enum +{ + private DocumentType( int value ) + { + super( value ); + } + + public static final DocumentType WRITER = new DocumentType(0); + public static final DocumentType CALC = new DocumentType(1); + public static final DocumentType DRAWING = new DocumentType(2); + public static final DocumentType XMLFORM = new DocumentType(3); + public static final DocumentType PRESENTATION = new DocumentType(4); + public static final DocumentType FORMULA = new DocumentType(5); + public static final DocumentType UNKNOWN = new DocumentType(-1); + +} diff --git a/unotest/source/java/org/openoffice/test/tools/OfficeDocument.java b/unotest/source/java/org/openoffice/test/tools/OfficeDocument.java new file mode 100644 index 000000000..bd996c011 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/tools/OfficeDocument.java @@ -0,0 +1,198 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package org.openoffice.test.tools; + +import com.sun.star.beans.PropertyState; +import com.sun.star.beans.PropertyValue; +import com.sun.star.document.MacroExecMode; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XController; +import com.sun.star.frame.XModel; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.util.CloseVetoException; +import com.sun.star.util.XCloseable; +import java.util.logging.Level; +import java.util.logging.Logger; + +/**************************************************************************/ + +/**************************************************************************/ +/** provides a small wrapper around a document +*/ +public class OfficeDocument +{ + /* ================================================================== */ + /* ------------------------------------------------------------------ */ + public OfficeDocument( XMultiServiceFactory orb, XComponent document ) + { + m_orb = orb; + m_documentComponent = document; + } + + /* ------------------------------------------------------------------ */ + protected static XComponent implLoadAsComponent( XMultiServiceFactory orb, String documentOrFactoryURL ) throws com.sun.star.uno.Exception + { + return implLoadAsComponent( orb, documentOrFactoryURL, new PropertyValue[0] ); + } + + /* ------------------------------------------------------------------ */ + private static XComponent implLoadAsComponent( XMultiServiceFactory orb, String documentOrFactoryURL, final PropertyValue[] i_args ) throws com.sun.star.uno.Exception + { + XComponentLoader aLoader = UnoRuntime.queryInterface( XComponentLoader.class, + orb.createInstance( "com.sun.star.frame.Desktop" ) ); + + XComponent document = UnoRuntime.queryInterface( XComponent.class, + aLoader.loadComponentFromURL( documentOrFactoryURL, "_blank", 0, i_args ) + ); + return document; + } + + /* ------------------------------------------------------------------ */ + private static OfficeDocument implLoadDocument( XMultiServiceFactory orb, String documentOrFactoryURL, final PropertyValue[] i_args ) throws com.sun.star.uno.Exception + { + XComponent document = implLoadAsComponent( orb, documentOrFactoryURL, i_args ); + + XServiceInfo xSI = UnoRuntime.queryInterface( XServiceInfo.class, document ); + if ( xSI.supportsService( "com.sun.star.sheet.SpreadsheetDocument" ) ) + return new SpreadsheetDocument( orb, document ); + return new OfficeDocument( orb, document ); + } + + /* ------------------------------------------------------------------ */ + public static OfficeDocument blankTextDocument( XMultiServiceFactory orb ) throws com.sun.star.uno.Exception + { + return blankDocument( orb, DocumentType.WRITER ); + } + + /* ------------------------------------------------------------------ */ + public static OfficeDocument blankDocument( XMultiServiceFactory orb, DocumentType eType ) throws com.sun.star.uno.Exception + { + final PropertyValue[] args = new PropertyValue[] { + new PropertyValue( "MacroExecutionMode", -1, MacroExecMode.ALWAYS_EXECUTE, PropertyState.DIRECT_VALUE ) + }; + return implLoadDocument( orb, getDocumentFactoryURL( eType ), args ); + } + + /* ------------------------------------------------------------------ */ + public boolean close() + { + try + { + XCloseable closeDoc = UnoRuntime.queryInterface( XCloseable.class, m_documentComponent ); + closeDoc.close( true ); + return true; + } + catch ( CloseVetoException e ) + { + Logger.getLogger( OfficeDocument.class.getName() ).log( Level.SEVERE, "closing the document was vetoed", e ); + } + return false; + } + + /* ================================================================== */ + /* ------------------------------------------------------------------ */ + public XComponent getDocument( ) + { + return m_documentComponent; + } + + /* ------------------------------------------------------------------ */ + /** retrieves the current view of the document + @return + the view component, queried for the interface described by aInterfaceClass + */ + public OfficeDocumentView getCurrentView( ) + { + // get the model interface for the document + XModel xDocModel = UnoRuntime.queryInterface( XModel.class, m_documentComponent ); + // get the current controller for the document - as a controller is tied to a view, + // this gives us the currently active view for the document. + XController xController = xDocModel.getCurrentController(); + + if ( classify() == DocumentType.CALC ) + return new SpreadsheetView( m_orb, xController ); + + return new OfficeDocumentView( m_orb, xController ); + } + + /* ------------------------------------------------------------------ */ + /** returns a URL which can be used to create a document of a certain type + */ + private static String getDocumentFactoryURL( DocumentType eType ) + { + if ( eType == DocumentType.WRITER ) + return "private:factory/swriter"; + if ( eType == DocumentType.CALC ) + return "private:factory/scalc"; + if ( eType == DocumentType.DRAWING ) + return "private:factory/sdraw"; + if ( eType == DocumentType.XMLFORM ) + return "private:factory/swriter?slot=21053"; + if ( eType == DocumentType.PRESENTATION ) + return "private:factory/simpress"; + if ( eType == DocumentType.FORMULA ) + return "private:factory/smath"; + return "private:factory/swriter"; + } + + /* ------------------------------------------------------------------ */ + /** classifies a document + */ + private DocumentType classify( ) + { + XServiceInfo xSI = UnoRuntime.queryInterface( XServiceInfo.class, m_documentComponent ); + + if ( xSI.supportsService( "com.sun.star.text.TextDocument" ) ) + return DocumentType.WRITER; + else if ( xSI.supportsService( "com.sun.star.sheet.SpreadsheetDocument" ) ) + return DocumentType.CALC; + else if ( xSI.supportsService( "com.sun.star.drawing.DrawingDocument" ) ) + return DocumentType.DRAWING; + else if ( xSI.supportsService( "com.sun.star.presentation.PresentationDocument" ) ) + return DocumentType.PRESENTATION; + else if ( xSI.supportsService( "com.sun.star.formula.FormulaProperties" ) ) + return DocumentType.FORMULA; + + return DocumentType.UNKNOWN; + } + + /* ------------------------------------------------------------------ */ + /** creates a component at the service factory provided by the document + */ + public XInterface createInstance( String serviceSpecifier ) throws com.sun.star.uno.Exception + { + XMultiServiceFactory xORB = UnoRuntime.queryInterface( XMultiServiceFactory.class, m_documentComponent ); + return (XInterface)xORB.createInstance( serviceSpecifier ); + } + + /* ------------------------------------------------------------------ */ + /** creates a component at the service factory provided by the document, queried for a given interface type + */ + public <T> T createInstance( String i_serviceSpecifier, Class<T> i_interfaceClass ) throws com.sun.star.uno.Exception + { + return UnoRuntime.queryInterface( i_interfaceClass, createInstance( i_serviceSpecifier ) ); + } + + private final XMultiServiceFactory m_orb; + private XComponent m_documentComponent; +} + diff --git a/unotest/source/java/org/openoffice/test/tools/OfficeDocumentView.java b/unotest/source/java/org/openoffice/test/tools/OfficeDocumentView.java new file mode 100644 index 000000000..c78537b71 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/tools/OfficeDocumentView.java @@ -0,0 +1,104 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package org.openoffice.test.tools; + +/**************************************************************************/ + +import com.sun.star.beans.PropertyValue; +import com.sun.star.frame.XController; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XDispatchProvider; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.util.URL; +import com.sun.star.util.XURLTransformer; + +/**************************************************************************/ +/** provides a small wrapper around a document view +*/ +public class OfficeDocumentView +{ + private final XMultiServiceFactory m_orb; + private final XController m_controller; + + /* ------------------------------------------------------------------ */ + final public XController getController() + { + return m_controller; + } + + /* ------------------------------------------------------------------ */ + public OfficeDocumentView( XMultiServiceFactory orb, XController controller ) + { + m_orb = orb; + m_controller = controller; + } + + /* ------------------------------------------------------------------ */ + /** retrieves a dispatcher for the given URL, obtained at the current view of the document + @param aURL + a one-element array. The first element must contain a valid + <member scope="com.sun.star.util">URL::Complete</member> value. Upon return, the URL is correctly + parsed. + @return + the dispatcher for the URL in question + */ + private XDispatch getDispatcher( URL[] aURL ) throws com.sun.star.uno.Exception + { + XDispatch xReturn = null; + + // go get the dispatch provider of its frame + XDispatchProvider xProvider = UnoRuntime.queryInterface( XDispatchProvider.class, m_controller.getFrame() ); + if ( null != xProvider ) + { + // need a URLTransformer + XURLTransformer xTransformer = UnoRuntime.queryInterface( XURLTransformer.class, + m_orb.createInstance( "com.sun.star.util.URLTransformer" ) ); + xTransformer.parseStrict( aURL ); + + xReturn = xProvider.queryDispatch( aURL[0], "", 0 ); + } + return xReturn; + } + + + /* ------------------------------------------------------------------ */ + /** dispatches the given URL into the view, if there's a dispatcher for it + + @return + <TRUE/> if the URL was successfully dispatched + */ + public boolean dispatch( String i_url ) throws com.sun.star.uno.Exception + { + return dispatch( i_url, new PropertyValue[0] ); + } + + /* ------------------------------------------------------------------ */ + private boolean dispatch( final String i_url, final PropertyValue[] i_arguments ) throws com.sun.star.uno.Exception + { + URL[] completeURL = new URL[] { new URL() }; + completeURL[0].Complete = i_url; + XDispatch dispatcher = getDispatcher( completeURL ); + if ( dispatcher == null ) + return false; + + dispatcher.dispatch( completeURL[0], i_arguments ); + return true; + } + +} diff --git a/unotest/source/java/org/openoffice/test/tools/SpreadsheetDocument.java b/unotest/source/java/org/openoffice/test/tools/SpreadsheetDocument.java new file mode 100644 index 000000000..7db429103 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/tools/SpreadsheetDocument.java @@ -0,0 +1,61 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package org.openoffice.test.tools; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XComponent; +import com.sun.star.table.XCellRange; +import com.sun.star.container.XIndexAccess; +import com.sun.star.sheet.XSpreadsheetDocument; +import com.sun.star.sheet.XSpreadsheets; +import com.sun.star.uno.UnoRuntime; + +public class SpreadsheetDocument extends OfficeDocument +{ + /** Creates a new blank spreadsheet document */ + /* ------------------------------------------------------------------ */ + public SpreadsheetDocument( XMultiServiceFactory orb ) throws com.sun.star.uno.Exception + { + super( orb, implLoadAsComponent( orb, "private:factory/scalc" ) ); + } + + /* ------------------------------------------------------------------ */ + public SpreadsheetDocument( XMultiServiceFactory orb, XComponent document ) + { + super( orb, document ); + } + + /* ------------------------------------------------------------------ */ + /** returns the sheets collection + */ + private XSpreadsheets getSheets() + { + XSpreadsheetDocument spreadsheetDoc = UnoRuntime.queryInterface( XSpreadsheetDocument.class, getDocument() ); + return spreadsheetDoc.getSheets(); + } + + /* ------------------------------------------------------------------ */ + /** returns the sheet with the given index + */ + public XCellRange getSheet( int index ) throws com.sun.star.uno.Exception + { + XIndexAccess sheets = UnoRuntime.queryInterface( XIndexAccess.class, getSheets() ); + return UnoRuntime.queryInterface( XCellRange.class, sheets.getByIndex( index ) ); + } +} diff --git a/unotest/source/java/org/openoffice/test/tools/SpreadsheetView.java b/unotest/source/java/org/openoffice/test/tools/SpreadsheetView.java new file mode 100644 index 000000000..21d3d23e4 --- /dev/null +++ b/unotest/source/java/org/openoffice/test/tools/SpreadsheetView.java @@ -0,0 +1,32 @@ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package org.openoffice.test.tools; + +import com.sun.star.frame.XController; +import com.sun.star.lang.XMultiServiceFactory; + +public class SpreadsheetView extends OfficeDocumentView +{ + + /** Creates a new instance of SpreadsheetView */ + public SpreadsheetView( XMultiServiceFactory orb, XController controller ) + { + super( orb, controller ); + } + +} diff --git a/unotest/source/python/org/__init__.py b/unotest/source/python/org/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/unotest/source/python/org/__init__.py diff --git a/unotest/source/python/org/libreoffice/__init__.py b/unotest/source/python/org/libreoffice/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/unotest/source/python/org/libreoffice/__init__.py diff --git a/unotest/source/python/org/libreoffice/unittest.py b/unotest/source/python/org/libreoffice/unittest.py new file mode 100644 index 000000000..f6f93080b --- /dev/null +++ b/unotest/source/python/org/libreoffice/unittest.py @@ -0,0 +1,29 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/. +# + +import gc +import pyuno +import unittest + +class LoTestResult(unittest.TextTestResult): + def stopTestRun(self): + # HACK calling gc.collect() to get rid of as many still existing UNO proxies to + # SwXTextDocument and friends as possible; those C++ classes' dtors call + # Application::GetSolarMutex via sw::UnoImplPtrDeleter, so the dtors must be called before + # DeInitVCL in the call to pyuno.private_deinitTestEnvironment(); any remaining proxies + # that are still referenced (UnoInProcess' self.xDoc in + # unotest/source/python/org/libreoffice/unotest.py, or per-class variables in the various + # PythonTests) need to be individually released (each marked as "HACK" in the code): + gc.collect() + pyuno.private_deinitTestEnvironment() + +if __name__ == '__main__': + unittest.main(module=None, testRunner=unittest.TextTestRunner(resultclass=LoTestResult)) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/unotest/source/python/org/libreoffice/unotest.py b/unotest/source/python/org/libreoffice/unotest.py new file mode 100644 index 000000000..e27f9e145 --- /dev/null +++ b/unotest/source/python/org/libreoffice/unotest.py @@ -0,0 +1,326 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/. +# + +import pathlib +import subprocess +import time +import uuid +import argparse +import os +import shutil +import urllib.parse +import urllib.request + +try: + import pyuno + import uno +except ImportError: + print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables") + print("to something like:") + print(" PYTHONPATH=/installation/opt/program") + print(" URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc") + raise + +try: + from com.sun.star.document import XDocumentEventListener +except ImportError: + print("UNO API class not found: try to set URE_BOOTSTRAP variable") + print("to something like:") + print(" URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc") + raise + +try: + from urllib.parse import quote +except ImportError: + from urllib import quote + +### utilities ### + +def mkPropertyValue(name, value): + return uno.createUnoStruct("com.sun.star.beans.PropertyValue", name, 0, value, 0) + +def mkPropertyValues(**kwargs): + '''mkPropertyValues(Name=Value, Name=Value,...) -> (PropertyValue, PropertyValue,...) + ex. : mkPropertyValues(Hidden=True, ReadOnly=False)''' + from com.sun.star.beans import PropertyValue + return tuple(PropertyValue(k,0,kwargs[k],0) for k in kwargs) + +def fileUrlToSystemPath(url): + return pyuno.fileUrlToSystemPath(url) + +def systemPathToFileUrl(systemPath): + return pyuno.systemPathToFileUrl(systemPath) + +### UNO utilities ### + +class OfficeConnection(object): + + def __init__(self, args): + self.args = args + self.soffice = None + self.xContext = None + self.channel = None + + def setUp(self): + try: + self.verbose = self.args["verbose"] + except KeyError: + self.verbose = False + try: + prog = self.args["program"] + except KeyError: + prog = os.getenv("SOFFICE_BIN") + if not (prog): + raise Exception("SOFFICE_BIN must be set") + channel = "pipe,name=pytest" + str(uuid.uuid1()) + try: + userdir = self.args["userdir"] + except KeyError: + userdir = "file:///tmp" + if not(userdir.startswith("file://")): + raise Exception("--userdir must be file URL") + self.soffice = self.bootstrap(prog, userdir, channel) + return self.connect(channel) + + def bootstrap(self, soffice, userdir, channel): + argv = [ soffice, "--accept=" + channel + ";urp", + "-env:UserInstallation=" + userdir, + "--quickstart=no", + "--norestore", "--nologo", "--headless"] + if "--valgrind" in self.args: + argv.append("--valgrind") + if self.verbose: + print ("starting LibreOffice with channel: ", channel) + return subprocess.Popen(argv) + + def connect(self, channel): + xLocalContext = uno.getComponentContext() + xUnoResolver = xLocalContext.ServiceManager.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", xLocalContext) + url = ("uno:%s;urp;StarOffice.ComponentContext" % channel) + if self.verbose: + print("Connecting to: ", url) + while True: + try: + self.xContext = xUnoResolver.resolve(url) + return self.xContext +# except com.sun.star.connection.NoConnectException + except pyuno.getClass("com.sun.star.connection.NoConnectException"): + print("WARN: NoConnectException: sleeping...") + time.sleep(1) + + def tearDown(self): + if self.soffice: + if self.xContext: + try: + if self.verbose: + print("tearDown: calling terminate()...") + xMgr = self.xContext.ServiceManager + xDesktop = xMgr.createInstanceWithContext( + "com.sun.star.frame.Desktop", self.xContext) + xDesktop.terminate() + if self.verbose: + print("...done") +# except com.sun.star.lang.DisposedException: + except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"): + print("caught UnknownPropertyException") + pass # ignore, also means disposed + except pyuno.getClass("com.sun.star.lang.DisposedException"): + print("caught DisposedException") + pass # ignore + else: + self.soffice.terminate() + ret = self.soffice.wait() + self.xContext = None + self.socket = None + self.soffice = None + if ret != 0: + raise Exception("Exit status indicates failure: " + str(ret)) + + def getContext(self): + return self.xContext + +class UnoRemoteConnection: + def __init__(self, args): + self.args = args + self.connection = None + def getContext(self): + return self.connection.xContext + def getDoc(self): + return self.xDoc + def setUp(self): + conn = OfficeConnection(self.args) + conn.setUp() + self.connection = conn + def openEmptyWriterDoc(self): + assert(self.connection) + smgr = self.getContext().ServiceManager + desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", self.getContext()) + props = [("Hidden", True), ("ReadOnly", False)] + loadProps = tuple([mkPropertyValue(name, value) for (name, value) in props]) + self.xDoc = desktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, loadProps) + return self.xDoc + + def checkProperties(self, obj, dict, test): + for k,v in dict.items(): + obj.setPropertyValue(k, v) + value = obj.getPropertyValue(k) + test.assertEqual(value, v) + + def postTest(self): + assert(self.connection) + def tearDown(self): + if self.connection: + try: + self.connection.tearDown() + finally: + self.connection = None + +havePonies = False + +class UnoInProcess: + def getContext(self): + return self.xContext + def getDoc(self): + return self.xDoc + def setUp(self): + self.xContext = pyuno.getComponentContext() + global havePonies + if not(havePonies): + pyuno.private_initTestEnvironment() + havePonies = True + + def openEmptyWriterDoc(self): + return self.openEmptyDoc("private:factory/swriter") + + def openEmptyCalcDoc(self): + return self.openEmptyDoc("private:factory/scalc") + + def openEmptyDoc(self, url, bHidden = True, bReadOnly = False): + props = [("Hidden", bHidden), ("ReadOnly", bReadOnly)] + return self.__openDocFromURL(url, props) + + def openTemplateFromTDOC(self, file): + return self.openDocFromTDOC(file, True) + + def openDocFromTDOC(self, file, asTemplate = False): + path = makeCopyFromTDOC(file) + return self.openDocFromAbsolutePath(path, asTemplate) + + def openDocFromAbsolutePath(self, file, asTemplate = False): + return self.openDocFromURL(pathlib.Path(file).as_uri(), asTemplate) + + def openDocFromURL(self, url, asTemplate = False): + props = [("Hidden", True), ("ReadOnly", False), ("AsTemplate", asTemplate)] + return self.__openDocFromURL(url, props) + + def __openDocFromURL(self, url, props): + assert(self.xContext) + smgr = self.getContext().ServiceManager + desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", self.getContext()) + loadProps = tuple([mkPropertyValue(name, value) for (name, value) in props]) + self.xDoc = desktop.loadComponentFromURL(url, "_blank", 0, loadProps) + assert(self.xDoc) + return self.xDoc + + def checkProperties(self, obj, dict, test): + for k,v in dict.items(): + obj.setPropertyValue(k, v) + value = obj.getPropertyValue(k) + test.assertEqual(value, v) + + def setProperties(self, obj, dict): + for k,v in dict.items(): + obj.setPropertyValue(k, v) + + def postTest(self): + assert(self.xContext) + def tearDown(self): + if hasattr(self, 'xDoc'): + if self.xDoc: + self.xDoc.close(True) + # HACK in case self.xDoc holds a UNO proxy to an SwXTextDocument (whose dtor calls + # Application::GetSolarMutex via sw::UnoImplPtrDeleter), which would potentially only be + # garbage-collected after VCL has already been deinitialized: + self.xDoc = None + +def simpleInvoke(connection, test): + try: + connection.preTest() + test.run(connection.getContext()) + finally: + connection.postTest() + +def retryInvoke(connection, test): + tries = 5 + while tries > 0: + try: + tries -= 1 + try: + connection.preTest() + test.run(connection.getContext()) + return + finally: + connection.postTest() + except KeyboardInterrupt: + raise # Ctrl+C should work + except: + print("retryInvoke: caught exception") + raise Exception("FAILED retryInvoke") + +def runConnectionTests(connection, invoker, tests): + try: + connection.setUp() + for test in tests: + invoker(connection, test) + finally: + connection.tearDown() + +def makeCopyFromTDOC(file): + src = os.getenv("TDOC") + assert(src is not None) + src = os.path.join(src, file) + dst = os.getenv("TestUserDir") + assert(dst is not None) + uri = urllib.parse.urlparse(dst) + assert(uri.scheme.casefold() == "file") + assert(uri.netloc == "" or uri.netloc.casefold() == "localhost") + assert(uri.params == "") + assert(uri.query == "") + assert(uri.fragment == "") + dst = urllib.request.url2pathname(uri.path) + dst = os.path.join(dst, "tmp", file) + os.makedirs(os.path.dirname(dst), exist_ok=True) + try: + os.remove(dst) + except FileNotFoundError: + pass + shutil.copyfile(src, dst) + return dst + +### tests ### + +if __name__ == "__main__": + parser = argparse.ArgumentParser("Help utilities for testing LibreOffice") + group = parser.add_mutually_exclusive_group() + group.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true") + #parser.add_argument("p", type=str, help="program name") + args = parser.parse_args() + if args.verbose: + verbose = True + con = PersistentConnection({"verbose" : args.verbose}) + print("starting soffice ... ", end="") + con.setUp() + print("done") + con.get + print ("shutting down ... ", end="") + con.tearDown() + print("done") + +# vim: set shiftwidth=4 softtabstop=4 expandtab: |