Test LibreOffice automatically
Signs of Life at the Office
Companies that depend on LibreOffice have a reason to wonder whether the office suite is working on all systems. You can use Python and the LibreOffice API to check.
A reliable office suite is one of the key applications in many environments, so users will quickly notice if the suite won't start or individual functions fail after an update. However, it's not just after an Office update that the heart of modern offices can stumble – renewal of operating system components also appears to be a point where established workflows experience issues.
If your network uses a specific set of templates, macros, and forms again and again, in practice, it is enough to at least test the key documents once before rolling out the update in all departments.
Automating the Test
What is even more helpful than the test itself is its automation – at least for administrators. Given that manually clicking through the various types of documents before each update is both boring and error-prone.
LibreOffice offers a whole range of options to control the software via the application API via script. The most common variant is probably to control it using macros from LibreOffice itself (also thank to other known Office suites). The Office suites for writing these macros may usually offer a basic dialect, but – after performing a little setup work – LibreOffice also lets you formulate these macros in Python.
LibreOffice can be controlled just as well remotely – such as from an external process or from a Python script. The same access to the LibreOffice API is available as in the macro environment.
Administrators therefore have the option to test LibreOffice on a network computer without having to open or manually operate LibreOffice. This means, for example, that admins can run a quick automated check before rolling out a new version to make sure LibreOffice will continue to work as desired after the update.
Administrators or developers can also use the same method to conveniently automate document processes, to create form letters or PDF documents, to address LibreOffice behind a web front end, or much more.
The Games Begin
The first step in setting up LibreOffice testing is to install the libreoffice-script-provider-python
package. However, this package is essentially a meta package that mainly deals with ensuring LibreOffice and the Python package python3-uno
are installed.
On distributions where there is no package called libreoffice-script-provider-python
, it should be enough to install the LibreOffice packages and the python-uno
, python3-uno
, or python2-uno
package – depending on the Python version used. The script developed here is based on Python 3.
On Mac OS X, the LibreOffice package contains its own Python interpreter under /Applications/LibreOffice.app/Contents/MacOS/python
. It also has the uno
package, meaning you can start with scripting on Mac OS X right away.
The Python interpreter certainly readily informs you if access to the LibreOffice interface is possible. The >>> import uno
import must not fail after starting using the command python3
or under Mac OS /Applications/LibreOffice.app/Contents/MacOS/python
.
If this Python line returns an Import Error
, either the uno
package is missing or it is not in the expected path for Python libraries. However, if Python is able to import the package, there should be no problem accessing LibreOffice.
Stimulating LibreOffice
Before you can control LibreOffice remotely, you need to restart the application and prompt it to listen to remote commands. The soffice
binary needs the slightly elongated option for this:
--accept=socket,host=localhost,port=8100,tcpNoDelay=1;urp;
The option value indicates what LibreOffice is doing now. The application then accepts (accept=...
) incoming requests on the open socket (socket
) listening to port 8100 (port=8100
) – at least, when the requests come from its own computer (host=localhost
).
The tcpNoDelay=1
option is recommended for sending network packages immediately – even those with minimal amounts of data. Many remote control commands for LibreOffice are very short, and you'll want an immediate response. Without tcpNoDelay=1
, the network layer waits to see whether yet more data needs to be sent via the connection.
The final addition urp;
signalizes to LibreOffice that the communication should use UNO Remote Protocol. This is the standard protocol used by LibreOffice for communication. UN stands for "Universal Network Object" and corresponds to the name of the Python package that you initially needs to install to give Python the opportunity to communicate with LibreOffice.
In addition to the open port, it is a good idea to use the --headless
option to get LibreOffice to dispense with all displays when starting the application and not to allow any further user input. This means it is only possible to control the application using the open port and it responds more quickly to the script commands. However, if you make changes to the script, you should comment out this option for logical reasons.
Then you can observe what exactly LibreOffice does. The complete call looks like this:
soffice '--accept=socket,host=localhost,port=8100,tcpNoDelay=1;urp;' --headless
The accept
option on the shell belongs in single quotes so that the semicolons are passed on to LibreOffice uninterpreted.
As it would be inconvenient to start LibreOffice manually before a test run, there is nothing to prevent the Office application being opened from the Python script. That is what lines 13 to 27 from Listing 1 do. The parameters from the socket connections are stored in the script in the SOCKET
variable because, on one hand, these values are passed to LibreOffice as an option at start up and, on the other, are also required if the script is supposed to establish the connection to the LibreOffice Socket.
Listing 1
Controlling LibreOffice Remotely
001 import filecmp 002 import sys 003 import uno 004 from com.sun.star.beans import PropertyValue 005 from com.sun.star.connection import NoConnectException 006 from glob import glob 007 from os import mkdir, path 008 from shutil import rmtree 009 from subprocess import Popen 010 from tempfile import NamedTemporaryFile 011 from time import sleep 012 013 SOCKET = 'socket,host=localhost,port=8100,tcpNoDelay=1;urp;' 014 exitCode = 0 015 016 # Starting LibreOffice 017 try: 018 app = Popen([ 019 '/usr/lib/libreoffice/program/soffice', 020 '--headless', 021 '--accept=' + SOCKET 022 ]) 023 except Exception as e: 024 raise Exception("It was not possible to start LibreOffice: %s" % e.message) 025 026 if app.pid <= 0: 027 raise Exception('It was not possible to start LibreOffice!') 028 029 # Establishing the connection to LibreOffice 030 context = uno.getComponentContext() 031 resolver = context.ServiceManager.createInstanceWithContext( 032 'com.sun.star.bridge.UnoUrlResolver', 033 context 034 ) 035 036 n = 0 037 while n < 12: 038 try: 039 context = resolver.resolve( 040 'uno:' + SOCKET + 'StarOffice.ComponentContext' 041 ) 042 break 043 except NoConnectException: 044 pass 045 sleep(0.5) 046 n += 1 047 048 desktop = context.ServiceManager.createInstanceWithContext( 049 'com.sun.star.frame.Desktop', 050 context 051 ) 052 053 if not (desktop): 054 raise Exception('It was not possible to generate the LibreOffice desktop!') 055 056 # LibreOffice is controlled via the connection from here 057 058 # Inital "Hello world" test 059 document = desktop.loadComponentFromURL( 060 'private:factory/swriter', 061 '_blank', 062 0, 063 () 064 ) 065 cursor = document.Text.createTextCursor() 066 cursor.ParaStyleName = 'Heading 1' 067 document.Text.insertString(cursor, 'Hello world!', 0) 068 069 txtFile = NamedTemporaryFile('w+', encoding='utf-8-sig') 070 071 textFilter = PropertyValue() 072 textFilter.Name = 'FilterName' 073 textFilter.Value = 'Text' 074 075 document.storeToURL('file://' + txtFile.name, (textFilter,)) 076 077 text = txtFile.read() 078 if (text != 'Hello world!\n'): 079 print("FAIL: \"Hello world\"-test failed! The result was: %s" % text) 080 exitCode = 1 081 else: 082 print('OK: "Hello world!" is exported as expected.') 083 084 document.close(False) 085 086 # Preparing the test and result folder 087 if sys.argv[1] and path.isdir(sys.argv[1]): 088 testdir = path.abspath(sys.argv[1]) 089 resultdir = path.join(testdir, 'Result_files') 090 if path.isdir(resultdir): 091 rmtree(resultdir) 092 mkdir(resultdir, 0o700) 093 094 # Run through of the test documents 095 pdfFilter = PropertyValue() 096 pdfFilter.Name = 'FilterName' 097 pdfFilter.Value = 'writer_pdf_Export' 098 099 testfiles = glob(path.join(testdir, 'Test_*')) 100 for testfile in testfiles: 101 102 document = desktop.loadComponentFromURL( 103 'file://' + testfile, 104 '_blank', 105 0, 106 () 107 ) 108 109 resultFile = path.basename(testfile)[5:-4] 110 111 fullPath = 'file://' + path.join(resultdir, 'PDF_' + resultFile + '.pdf') 112 document.storeToURL(fullPath, (pdfFilter,)) 113 114 textResultFile = path.join(resultdir, 'Text_' + resultFile + '.txt') 115 fullPath = 'file://' + textResultFile 116 document.storeToURL(fullPath, (textFilter,)) 117 118 expectationFile = path.join(testdir, 'Text_' + resultFile + '.txt') 119 if (path.isfile(expectationFile)): 120 if filecmp.cmp(textResultFile, expectationFile, False): 121 print("OK: File %s is exported as expected." % testfile) 122 else: 123 print("FAIL: File %s is not exported as expected!" % testfile) 124 exitCode = 1 125 else: 126 print("--: No comparison file for %s." % testfile) 127 document.close(False) 128 129 130 # Closing LibreOffice 131 desktop.terminate() 132 133 sleep(2) 134 135 app.wait() 136 137 sys.exit(exitCode)
Ultimately, the Python script doesn't run anything at the start other than the soffice
call described above from the command line. The script uses the subprocess.Popen
class for this in line 18. And, soffice
responds itself with the full path.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.