1 #!/usr/bin/env python # -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
3 # Version: MPL 1.1 / GPLv3+ / LGPLv3+
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License or as specified alternatively below. You may obtain a copy of
8 # the License at http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
15 # Major Contributor(s):
16 # Copyright (C) 2012 Red Hat, Inc., Michael Stahl <mstahl@redhat.com>
19 # All Rights Reserved.
21 # For minor contributions see the git repository.
23 # Alternatively, the contents of this file may be used under the terms of
24 # either the GNU General Public License Version 3 or later (the "GPLv3+"), or
25 # the GNU Lesser General Public License Version 3 or later (the "LGPLv3+"),
26 # in which case the provisions of the GPLv3+ or the LGPLv3+ are applicable
27 # instead of those above.
29 # Simple script to load a bunch of documents and export them as Flat ODF
31 # Personally I run it like this:
32 # ~/lo/master-suse/instdir/program/python ~/lo/master-suse/bin/benchmark-document-loading --soffice=path:/home/tml/lo/master-suse/instdir/program/soffice --outdir=file://$PWD/out --userdir=file:///tmp/test $PWD/docs
47 from urllib
.parse
import quote
49 from urllib
import quote
56 print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables")
57 print("PYTHONPATH=/installation/opt/program")
58 print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc")
62 from com
.sun
.star
.beans
import PropertyValue
63 from com
.sun
.star
.document
import XDocumentEventListener
64 from com
.sun
.star
.io
import IOException
, XOutputStream
66 print("UNO API class not found: try to set URE_BOOTSTRAP variable")
67 print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc")
70 validCalcFileExtensions
= [ ".xlsx", ".xls", ".ods", ".fods" ]
71 validWriterFileExtensions
= [ ".docx" , ".rtf", ".odt", ".fodt", ".doc" ]
72 validImpressFileExtensions
= [ ".ppt", ".pptx", ".odp", ".fodp" ]
73 validDrawFileExtensions
= [ ".odg", ".fodg" ]
74 validRevereseFileExtensions
= [ ".vsd", ".vdx", ".cdr", ".pub", ".wpd" ]
75 validFileExtensions
= dict([("calc", validCalcFileExtensions
), ("writer", validWriterFileExtensions
), ("impress", validImpressFileExtensions
), ("draw", validDrawFileExtensions
), ("reverse", validRevereseFileExtensions
) ])
76 flatODFTypes
= dict([("calc", (".fods", "OpenDocument Spreadsheet Flat XML")),
77 ("writer", (".fodt", "OpenDocument Text Flat XML")),
78 ("impress", (".fodp", "OpenDocument Presentation Flat XML")),
79 ("draw", (".fodg", "OpenDocument Drawing Flat XML"))])
83 def partition(list, pred
):
93 def filelist(dir, suffix
):
95 raise Exception("filelist: empty directory")
96 if not(dir[-1] == "/"):
98 files
= [dir + f
for f
in os
.listdir(dir)]
100 return [f
for f
in files
101 if os
.path
.isfile(f
) and os
.path
.splitext(f
)[1] == suffix
]
103 def getFiles(dirs
, suffix
):
107 files
+= filelist(dir, suffix
)
110 ### UNO utilities ###
112 class OutputStream( unohelper
.Base
, XOutputStream
):
113 def __init__( self
):
116 def closeOutput(self
):
119 def writeBytes( self
, seq
):
120 sys
.stdout
.write( seq
.value
)
125 class OfficeConnection
:
126 def __init__(self
, args
):
133 (method
, sep
, rest
) = self
.args
["--soffice"].partition(":")
135 raise Exception("soffice parameter does not specify method")
137 socket
= "pipe,name=pytest" + str(uuid
.uuid1())
139 userdir
= self
.args
["--userdir"]
141 raise Exception("'path' method requires --userdir")
142 if not(userdir
.startswith("file://")):
143 raise Exception("--userdir must be file URL")
144 self
.soffice
= self
.bootstrap(rest
, userdir
, socket
)
145 elif method
== "connect":
148 raise Exception("unsupported connection method: " + method
)
149 self
.xContext
= self
.connect(socket
)
151 def bootstrap(self
, soffice
, userdir
, socket
):
152 argv
= [ soffice
, "--accept=" + socket
+ ";urp",
153 "-env:UserInstallation=" + userdir
,
155 "--norestore", "--nologo", "--headless" ]
156 if "--valgrind" in self
.args
:
157 argv
.append("--valgrind")
158 os
.putenv("SAL_LOG", "-INFO-WARN")
159 os
.putenv("LIBO_ONEWAY_STABLE_ODF_EXPORT", "YES")
160 self
.pro
= subprocess
.Popen(argv
)
161 # print(self.pro.pid)
163 def connect(self
, socket
):
164 xLocalContext
= uno
.getComponentContext()
165 xUnoResolver
= xLocalContext
.ServiceManager
.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", xLocalContext
)
166 url
= "uno:" + socket
+ ";urp;StarOffice.ComponentContext"
167 # print("OfficeConnection: connecting to: " + url)
170 xContext
= xUnoResolver
.resolve(url
)
172 # except com.sun.star.connection.NoConnectException
173 except pyuno
.getClass("com.sun.star.connection.NoConnectException"):
174 # print("NoConnectException: sleeping...")
181 # print("tearDown: calling terminate()...")
182 xMgr
= self
.xContext
.ServiceManager
183 xDesktop
= xMgr
.createInstanceWithContext("com.sun.star.frame.Desktop", self
.xContext
)
186 # except com.sun.star.lang.DisposedException:
187 except pyuno
.getClass("com.sun.star.beans.UnknownPropertyException"):
188 # print("caught UnknownPropertyException while TearDown")
189 pass # ignore, also means disposed
190 except pyuno
.getClass("com.sun.star.lang.DisposedException"):
191 # print("caught DisposedException while TearDown")
194 self
.soffice
.terminate()
195 ret
= self
.soffice
.wait()
200 raise Exception("Exit status indicates failure: " + str(ret
))
203 command
= "kill " + str(self
.pro
.pid
)
204 killFile
= open("killFile.log", "a")
205 killFile
.write(command
+ "\n")
211 class PersistentConnection
:
212 def __init__(self
, args
):
214 self
.connection
= None
215 def getContext(self
):
216 return self
.connection
.xContext
218 assert(not self
.connection
)
219 conn
= OfficeConnection(self
.args
)
221 self
.connection
= conn
223 assert(self
.connection
)
225 assert(self
.connection
)
229 self
.connection
.tearDown()
231 self
.connection
= None
234 self
.connection
.kill()
236 def simpleInvoke(connection
, test
):
239 test
.run(connection
.getContext(), connection
)
241 connection
.postTest()
243 def runConnectionTests(connection
, invoker
, tests
):
247 invoker(connection
, test
)
250 #connection.tearDown()
252 class EventListener(XDocumentEventListener
,unohelper
.Base
):
254 self
.layoutFinished
= False
255 def documentEventOccured(self
, event
):
256 # print(str(event.EventName))
257 if event
.EventName
== "OnLayoutFinished":
258 self
.layoutFinished
= True
259 def disposing(event
):
262 def mkPropertyValue(name
, value
):
263 return uno
.createUnoStruct("com.sun.star.beans.PropertyValue",
268 def logTimeSpent(url
, startTime
):
269 print(os
.path
.basename(urllib
.parse
.urlparse(url
).path
) + "\t" + str(time
.time()-startTime
))
271 def loadFromURL(xContext
, url
, t
, component
):
272 xDesktop
= xContext
.ServiceManager
.createInstanceWithContext("com.sun.star.frame.Desktop", xContext
)
273 props
= [("Hidden", True), ("ReadOnly", True)] # FilterName?
274 loadProps
= tuple([mkPropertyValue(name
, value
) for (name
, value
) in props
])
276 if component
== "writer":
277 xListener
= EventListener()
278 xGEB
= xContext
.getValueByName(
279 "/singletons/com.sun.star.frame.theGlobalEventBroadcaster")
280 xGEB
.addDocumentEventListener(xListener
)
283 startTime
= time
.time()
284 xDoc
= xDesktop
.loadComponentFromURL(url
, "_blank", 0, loadProps
)
285 if component
== "calc":
289 except AttributeError:
292 logTimeSpent(url
, startTime
)
294 elif component
== "writer":
298 if xListener
.layoutFinished
:
299 logTimeSpent(url
, startTime
)
301 # print("delaying...")
306 logTimeSpent(uri
, startTime
)
308 file = open("file.log", "a")
309 file.write("layout did not finish\n")
312 except pyuno
.getClass("com.sun.star.beans.UnknownPropertyException"):
314 raise # means crashed, handle it later
315 except pyuno
.getClass("com.sun.star.lang.DisposedException"):
317 raise # means crashed, handle it later
318 except pyuno
.getClass("com.sun.star.lang.IllegalArgumentException"):
319 pass # means could not open the file, ignore it
327 xGEB
.removeDocumentEventListener(xListener
)
329 def exportToODF(xContext
, xDoc
, baseName
, t
, component
):
330 exportFileName
= outdir
+ "/" + os
.path
.splitext(baseName
)[0] + flatODFTypes
[component
][0]
331 print("exportToODF " + baseName
+ " => " + exportFileName
)
332 props
= [("FilterName", flatODFTypes
[component
][1]),
334 storeProps
= tuple([mkPropertyValue(name
, value
) for (name
, value
) in props
])
335 xDoc
.storeToURL(exportFileName
, tuple(storeProps
))
337 def handleCrash(file, disposed
):
338 # print("File: " + file + " crashed")
339 crashLog
= open("crashlog.txt", "a")
340 crashLog
.write('Crash:' + file + ' ')
342 crashLog
.write('through disposed\n')
344 # crashed_files.append(file)
345 # add here the remaining handling code for crashed files
347 def alarm_handler(args
):
350 class HandleFileTest
:
351 def __init__(self
, file, state
, component
):
354 self
.component
= component
355 def run(self
, xContext
, connection
):
356 # print("Loading document: " + self.file)
360 url
= "file://" + quote(self
.file)
361 file = open("file.log", "a")
362 file.write(url
+ "\n")
366 t
= threading
.Timer(60, alarm_handler
, args
)
368 xDoc
= loadFromURL(xContext
, url
, t
, self
.component
)
369 self
.state
.goodFiles
.append(self
.file)
370 exportToODF(xContext
, xDoc
, os
.path
.basename(urllib
.parse
.urlparse(url
).path
), t
, self
.component
)
371 except pyuno
.getClass("com.sun.star.beans.UnknownPropertyException"):
372 # print("caught UnknownPropertyException " + self.file)
375 self
.state
.timeoutFiles
.append(self
.file)
378 handleCrash(self
.file, 0)
379 self
.state
.badPropertyFiles
.append(self
.file)
380 connection
.tearDown()
382 except pyuno
.getClass("com.sun.star.lang.DisposedException"):
383 # print("caught DisposedException " + self.file)
386 self
.state
.timeoutFiles
.append(self
.file)
389 handleCrash(self
.file, 1)
390 self
.state
.badDisposedFiles
.append(self
.file)
391 connection
.tearDown()
398 t
= threading
.Timer(10, alarm_handler
, args
)
402 except pyuno
.getClass("com.sun.star.beans.UnknownPropertyException"):
403 print("caught UnknownPropertyException while closing")
404 self
.state
.badPropertyFiles
.append(self
.file)
405 connection
.tearDown()
407 except pyuno
.getClass("com.sun.star.lang.DisposedException"):
408 print("caught DisposedException while closing")
412 self
.state
.badDisposedFiles
.append(self
.file)
413 connection
.tearDown()
415 # print("...done with: " + self.file)
420 self
.badDisposedFiles
= []
421 self
.badPropertyFiles
= []
422 self
.timeoutFiles
= []
425 def writeReport(state
, startTime
):
426 goodFiles
= open("goodFiles.log", "w")
427 goodFiles
.write("Files which loaded perfectly:\n")
428 goodFiles
.write("Starttime: " + startTime
.isoformat() +"\n")
429 for file in state
.goodFiles
:
430 goodFiles
.write(file)
431 goodFiles
.write("\n")
433 badDisposedFiles
= open("badDisposedFiles.log", "w")
434 badDisposedFiles
.write("Files which crashed with DisposedException:\n")
435 badDisposedFiles
.write("Starttime: " + startTime
.isoformat() + "\n")
436 for file in state
.badDisposedFiles
:
437 badDisposedFiles
.write(file)
438 badDisposedFiles
.write("\n")
439 badDisposedFiles
.close()
440 badPropertyFiles
= open("badPropertyFiles.log", "w")
441 badPropertyFiles
.write("Files which crashed with UnknownPropertyException:\n")
442 badPropertyFiles
.write("Starttime: " + startTime
.isoformat() + "\n")
443 for file in state
.badPropertyFiles
:
444 badPropertyFiles
.write(file)
445 badPropertyFiles
.write("\n")
446 badPropertyFiles
.close()
447 timeoutFiles
= open("timeoutFiles.log", "w")
448 timeoutFiles
.write("Files which timed out:\n")
449 timeoutFiles
.write("Starttime: " + startTime
.isoformat() + "\n")
450 for file in state
.timeoutFiles
:
451 timeoutFiles
.write(file)
452 timeoutFiles
.write("\n")
455 def runHandleFileTests(opts
, dirs
):
456 startTime
= datetime
.datetime
.now()
457 connection
= PersistentConnection(opts
)
459 outdir
= opts
["--outdir"] + "/" + startTime
.strftime('%Y%m%d.%H%M%S')
463 # print("before map")
464 for component
, validExtension
in validFileExtensions
.items():
466 for suffix
in validExtension
:
467 files
.extend(getFiles(dirs
, suffix
))
469 tests
.extend( (HandleFileTest(file, state
, component
) for file in files
) )
470 runConnectionTests(connection
, simpleInvoke
, tests
)
473 writeReport(state
, startTime
)
476 (optlist
,args
) = getopt
.getopt(argv
[1:], "hr",
477 ["help", "soffice=", "userdir=", "outdir=", "valgrind"])
479 return (dict(optlist
), args
)
482 message
= """usage: {program} [option]... [directory]..."
483 -h | --help: print usage information
484 --soffice=method:location
485 specify soffice instance to connect to
486 supported methods: 'path', 'connect'
487 --outdir=URL specify the output directory for flat ODF exports
488 --userdir=URL specify user installation directory for 'path' method
489 --valgrind pass --valgrind to soffice for 'path' method
491 'location' is a pathname, not a URL. 'outdir' and 'userdir' are URLs.
492 The 'directory' parameters should be full absolute pathnames, not URLs."""
493 print(message
.format(program
= os
.path
.basename(sys
.argv
[0])))
496 if __name__
== "__main__":
497 (opts
,args
) = parseArgs(sys
.argv
)
501 if "-h" in opts
or "--help" in opts
:
504 elif "--soffice" in opts
and "--outdir" in opts
:
505 runHandleFileTests(opts
, args
)
510 # vim:set shiftwidth=4 softtabstop=4 expandtab: