diff --git a/.gitignore b/.gitignore index 8e1dea9c88cf..e666c0ec53f9 100644 --- a/.gitignore +++ b/.gitignore @@ -75,5 +75,6 @@ /solenv/gdb/libreoffice/*.pyo /solenv/gdb/libreoffice/util/*.pyo /moz/zipped/*.zip +__pycache__ diff --git a/solenv/gbuild/PythonTest.mk b/solenv/gbuild/PythonTest.mk new file mode 100644 index 000000000000..d97b2a83f5e0 --- /dev/null +++ b/solenv/gbuild/PythonTest.mk @@ -0,0 +1,83 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +# PythonTest class + +# TODO: FixMe problem with internal python: +# Fatal Python error: Py_Initialize: Unable to get the locale encoding +#gb_Python_EXE := $(call gb_Executable_get_command,python) +gb_Python_EXE := python3 + +gb_PythonTest_COMMAND := $(gb_Python_EXE) -m unittest + +.PHONY : $(call gb_PythonTest_get_clean_target,%) +$(call gb_PythonTest_get_clean_target,%) : + $(call gb_Helper_abbreviate_dirs,\ + rm -f $@ $@.log) + +ifneq ($(DISABLE_PYTHON),TRUE) + +.PHONY : $(call gb_PythonTest_get_target,%) +$(call gb_PythonTest_get_target,%) : + $(call gb_Output_announce,$*,$(true),PYT,2) + $(call gb_Helper_abbreviate_dirs,\ + mkdir -p $(dir $(call gb_PythonTest_get_target,$*)) && \ + (PYTHONPATH=$(SRCDIR)/unotest/source/python:$(DEVINSTALLDIR)/opt/program \ + SOFFICE_BIN=$(DEVINSTALLDIR)/opt/program/soffice \ + URE_BOOTSTRAP=file://$(DEVINSTALLDIR)/opt/program/fundamentalrc \ + $(gb_PythonTest_COMMAND) \ + $(CLASSES) > $@.log 2>&1 || \ + (cat $@.log \ + && false))) + $(CLEAN_CMD) + +define gb_PythonTest_PythonTest +$(call gb_PythonTest_get_target,$(1)) : T_CP := +$(call gb_PythonTest_get_target,$(1)) : CLASSES := + +$(eval $(call gb_Module_register_target,$(call gb_PythonTest_get_target,$(1)),$(call gb_PythonTest_get_clean_target,$(1)))) +$(call gb_Helper_make_userfriendly_targets,$(1),PythonTest) + +endef + +define gb_PythonTest_add_classes +$(call gb_PythonTest_get_target,$(1)) : CLASSES += $(2) + +endef + +define gb_PythonTest_add_class +$(call gb_PythonTest_add_classes,$(1),$(2)) + +endef + +define gb_PythonTest_use_customtarget +$(call gb_PythonTest_get_target,$(1)) : $(call gb_CustomTarget_get_workdir,$(2)) + +endef + + +else # DISABLE_PYTHON + +.PHONY : $(call gb_PythonTest_get_target,$(1)) +$(call gb_PythonTest_get_target,%) : + $(call gb_Output_announce,$* (skipped - no PythonTest),$(true),PYT,2) + @true + +define gb_PythonTest_PythonTest +$(eval $(call gb_Module_register_target,$(call gb_PythonTest_get_target,$(1)),$(call gb_PythonTest_get_clean_target,$(1)))) +$(call gb_Helper_make_userfriendly_targets,$(1),PythonTest) + +endef + +gb_PythonTest_add_classes := +gb_PythonTest_add_class := +gb_JunitTest_use_customtarget := + +endif # DISABLE_PYTHON +# vim: set noet sw=4: diff --git a/solenv/gbuild/TargetLocations.mk b/solenv/gbuild/TargetLocations.mk index 7f50cf6a7574..3ea8951178ce 100644 --- a/solenv/gbuild/TargetLocations.mk +++ b/solenv/gbuild/TargetLocations.mk @@ -148,6 +148,7 @@ gb_JavaClassSet_get_target = $(WORKDIR)/JavaClassSet/$(1)/done gb_JunitTest_get_classsetname = JunitTest/$(1) gb_JunitTest_get_target = $(WORKDIR)/JunitTest/$(1)/done gb_JunitTest_get_userdir = $(WORKDIR)/JunitTest/$(1)/user +gb_PythonTest_get_target = $(WORKDIR)/PythonTest/$(1)/done gb_LinkTarget_get_external_headers_target = $(WORKDIR)/ExternalHeaders/$(1) gb_LinkTarget_get_headers_target = $(WORKDIR)/Headers/$(1) gb_LinkTarget_get_target = $(WORKDIR)/LinkTarget/$(1) @@ -290,6 +291,7 @@ $(eval $(call gb_Helper_make_clean_targets,\ Pagein \ PrecompiledHeader \ Pyuno \ + PythonTest \ Rdb \ ResTarget \ ScpMergeTarget \ diff --git a/solenv/gbuild/gbuild.mk b/solenv/gbuild/gbuild.mk index cb374dad3851..3d2796fe965d 100644 --- a/solenv/gbuild/gbuild.mk +++ b/solenv/gbuild/gbuild.mk @@ -313,6 +313,7 @@ include $(foreach class, \ Pagein \ PrecompiledHeaders \ Pyuno \ + PythonTest \ Rdb \ CppunitTest \ Jar \ diff --git a/sw/Module_sw.mk b/sw/Module_sw.mk index d11f167bc920..2f6e75b00b94 100644 --- a/sw/Module_sw.mk +++ b/sw/Module_sw.mk @@ -66,4 +66,13 @@ $(eval $(call gb_Module_add_subsequentcheck_targets,sw,\ )) endif +# TODO: FixMe restrict to system python +ifneq ($(DISABLE_PYTHON),TRUE) +ifeq ($(SYSTEM_PYTHON),YES) +$(eval $(call gb_Module_add_subsequentcheck_targets,sw,\ + PythonTest_sw_unoapi \ +)) +endif +endif + # vim: set noet sw=4 ts=4: diff --git a/sw/PythonTest_sw_unoapi.mk b/sw/PythonTest_sw_unoapi.mk new file mode 100644 index 000000000000..de486effec9a --- /dev/null +++ b/sw/PythonTest_sw_unoapi.mk @@ -0,0 +1,17 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_PythonTest_PythonTest,sw_unoapi)) + +$(eval $(call gb_PythonTest_add_classes,sw_unoapi,\ + $(SRCDIR)/sw/qa/unoapi/python/set_expression.py \ + $(SRCDIR)/sw/qa/unoapi/python/get_expression.py \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/sw/qa/unoapi/python/get_expression.py b/sw/qa/unoapi/python/get_expression.py new file mode 100644 index 000000000000..277d3cfa5ba9 --- /dev/null +++ b/sw/qa/unoapi/python/get_expression.py @@ -0,0 +1,53 @@ +import unittest +from org.libreoffice.unotest import UnoConnection + +class TestGetExpression(unittest.TestCase): + _unoCon = None + _xDoc = None + + @classmethod + def setUpClass(cls): + cls._unoCon = UnoConnection({}) + cls._unoCon.setUp() + cls._xDoc = cls._unoCon.openEmptyWriterDoc() + + @classmethod + def tearDownClass(cls): + cls._unoCon.tearDown() + + def test_get_expression(self): + self.__class__._unoCon.checkProperties( + self.__class__._xDoc.createInstance("com.sun.star.text.textfield.GetExpression"), + {"Content": "foo", + "CurrentPresentation": "bar", + "NumberFormat": 0, + "IsShowFormula": False, + "SubType": 0, + "VariableSubtype": 1, + "IsFixedLanguage": False, + }, + self + ) + + # property 'Value' is read only? + @unittest.expectedFailure + def test_get_expression_veto_read_only(self): + self.__class__._unoCon.checkProperties( + self.__class__._xDoc.createInstance("com.sun.star.text.textfield.GetExpression"), + {"Value": 0.0}, + self + ) + + # property 'NumberingType' is unknown? + @unittest.expectedFailure + def test_get_expression_unknown_property(self): + self.__class__._unoCon.checkProperties( + self.__class__._xDoc.createInstance("com.sun.star.text.textfield.GetExpression"), + {"NumberingType": 0}, + self + ) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sw/qa/unoapi/python/set_expression.py b/sw/qa/unoapi/python/set_expression.py new file mode 100644 index 000000000000..8f6d19e26396 --- /dev/null +++ b/sw/qa/unoapi/python/set_expression.py @@ -0,0 +1,40 @@ +import unittest +from org.libreoffice.unotest import UnoConnection + +#@unittest.skip("that seems to work") +class TestSetExpresion(unittest.TestCase): + _unoCon = None + _xDoc = None + + @classmethod + def setUpClass(cls): + cls._unoCon = UnoConnection({}) + cls._unoCon.setUp() + cls._xDoc = cls._unoCon.openEmptyWriterDoc() + + @classmethod + def tearDownClass(cls): + cls._unoCon.tearDown() + + def test_set_expression(self): + self.__class__._unoCon.checkProperties( + self.__class__._xDoc.createInstance("com.sun.star.text.textfield.SetExpression"), + {"NumberingType": 0, + "Content": "foo", + "CurrentPresentation": "bar", + "NumberFormat": 0, + "NumberingType": 0, + "IsShowFormula": False, + "IsInput": False, + "IsVisible": True, + "SequenceValue": 0, + "SubType": 0, + "Value": 1.0, + "IsFixedLanguage": False + }, + self + ) + +if __name__ == '__main__': + unittest.main() + diff --git a/unotest/source/python/org/__init__.py b/unotest/source/python/org/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/unotest/source/python/org/libreoffice/__init__.py b/unotest/source/python/org/libreoffice/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/unotest/source/python/org/libreoffice/unotest.py b/unotest/source/python/org/libreoffice/unotest.py new file mode 100644 index 000000000000..c5cfa596ee53 --- /dev/null +++ b/unotest/source/python/org/libreoffice/unotest.py @@ -0,0 +1,215 @@ +# -*- 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/. +# + +import subprocess +import time +import uuid +import argparse +import os + +try: + import pyuno + import uno +except ImportError: + print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables") + 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("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc") + raise + +### utilities ### + +def mkPropertyValue(name, value): + return uno.createUnoStruct("com.sun.star.beans.PropertyValue", name, 0, value, 0) + +### 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["programm"] + 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", "--nofirststartwizard", + "--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 +# WTF 255 return value? +# if ret != 0: +# raise Exception("Exit status indicates failure: " + str(ret)) +# return ret + + def getContext(self): + return self.xContext + +class UnoConnection: + 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 + +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() + +### 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="programm 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: