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/python | |
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 '')
-rw-r--r-- | unotest/source/python/org/__init__.py | 0 | ||||
-rw-r--r-- | unotest/source/python/org/libreoffice/__init__.py | 0 | ||||
-rw-r--r-- | unotest/source/python/org/libreoffice/unittest.py | 29 | ||||
-rw-r--r-- | unotest/source/python/org/libreoffice/unotest.py | 326 |
4 files changed, 355 insertions, 0 deletions
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: |