1 # -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
2 # Version: MPL 1.1 / GPLv3+ / LGPLv3+
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License or as specified alternatively below. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
14 # Major Contributor(s):
15 # Copyright (C) 2012 Red Hat, Inc., Michael Stahl <mstahl@redhat.com>
18 # All Rights Reserved.
20 # For minor contributions see the git repository.
22 # Alternatively, the contents of this file may be used under the terms of
23 # either the GNU General Public License Version 3 or later (the "GPLv3+"), or
24 # the GNU Lesser General Public License Version 3 or later (the "LGPLv3+"),
25 # in which case the provisions of the GPLv3+ or the LGPLv3+ are applicable
26 # instead of those above.
35 from urllib
.parse
import quote
37 from urllib
import quote
44 print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables")
45 print("PYTHONPATH=/installation/opt/program")
46 print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc")
50 from com
.sun
.star
.document
import XDocumentEventListener
52 print("UNO API class not found: try to set URE_BOOTSTRAP variable")
53 print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc")
58 def partition(list, pred
):
68 def filelist(dir, suffix
):
70 raise Exception("filelist: empty directory")
71 if not(dir[-1] == "/"):
73 files
= [dir + f
for f
in os
.listdir(dir)]
75 return [f
for f
in files
76 if os
.path
.isfile(f
) and os
.path
.splitext(f
)[1] == suffix
]
78 def getFiles(dirs
, suffix
):
81 files
+= filelist(dir, suffix
)
86 class OfficeConnection
:
87 def __init__(self
, args
):
93 (method
, sep
, rest
) = self
.args
["--soffice"].partition(":")
95 raise Exception("soffice parameter does not specify method")
97 socket
= "pipe,name=pytest" + str(uuid
.uuid1())
99 userdir
= self
.args
["--userdir"]
101 raise Exception("'path' method requires --userdir")
102 if not(userdir
.startswith("file://")):
103 raise Exception("--userdir must be file URL")
104 self
.soffice
= self
.bootstrap(rest
, userdir
, socket
)
105 elif method
== "connect":
108 raise Exception("unsupported connection method: " + method
)
109 self
.xContext
= self
.connect(socket
)
111 def bootstrap(self
, soffice
, userdir
, socket
):
112 argv
= [ soffice
, "--accept=" + socket
+ ";urp",
113 "-env:UserInstallation=" + userdir
,
114 "--quickstart=no", "--nofirststartwizard",
115 "--norestore", "--nologo", "--headless" ]
116 if "--valgrind" in self
.args
:
117 argv
.append("--valgrind")
118 return subprocess
.Popen(argv
)
120 def connect(self
, socket
):
121 xLocalContext
= uno
.getComponentContext()
122 xUnoResolver
= xLocalContext
.ServiceManager
.createInstanceWithContext(
123 "com.sun.star.bridge.UnoUrlResolver", xLocalContext
)
124 url
= "uno:" + socket
+ ";urp;StarOffice.ComponentContext"
125 print("OfficeConnection: connecting to: " + url
)
128 xContext
= xUnoResolver
.resolve(url
)
130 # except com.sun.star.connection.NoConnectException
131 except pyuno
.getClass("com.sun.star.connection.NoConnectException"):
132 print("NoConnectException: sleeping...")
139 print("tearDown: calling terminate()...")
140 xMgr
= self
.xContext
.ServiceManager
141 xDesktop
= xMgr
.createInstanceWithContext(
142 "com.sun.star.frame.Desktop", self
.xContext
)
145 # except com.sun.star.lang.DisposedException:
146 except pyuno
.getClass("com.sun.star.beans.UnknownPropertyException"):
147 print("caught UnknownPropertyException")
148 pass # ignore, also means disposed
149 except pyuno
.getClass("com.sun.star.lang.DisposedException"):
150 print("caught DisposedException")
153 self
.soffice
.terminate()
154 ret
= self
.soffice
.wait()
159 raise Exception("Exit status indicates failure: " + str(ret
))
162 class PerTestConnection
:
163 def __init__(self
, args
):
165 self
.connection
= None
166 def getContext(self
):
167 return self
.connection
.xContext
169 assert(not(self
.connection
))
171 conn
= OfficeConnection(self
.args
)
173 self
.connection
= conn
177 self
.connection
.tearDown()
179 self
.connection
= None
181 assert(not(self
.connection
))
183 class PersistentConnection
:
184 def __init__(self
, args
):
186 self
.connection
= None
187 def getContext(self
):
188 return self
.connection
.xContext
190 conn
= OfficeConnection(self
.args
)
192 self
.connection
= conn
194 assert(self
.connection
)
196 assert(self
.connection
)
200 self
.connection
.tearDown()
202 self
.connection
= None
204 def simpleInvoke(connection
, test
):
207 test
.run(connection
.getContext())
209 connection
.postTest()
211 def retryInvoke(connection
, test
):
218 test
.run(connection
.getContext())
221 connection
.postTest()
222 except KeyboardInterrupt:
223 raise # Ctrl+C should work
225 print("retryInvoke: caught exception")
226 raise Exception("FAILED retryInvoke")
228 def runConnectionTests(connection
, invoker
, tests
):
232 invoker(connection
, test
)
234 connection
.tearDown()
236 class EventListener(XDocumentEventListener
,unohelper
.Base
):
238 self
.layoutFinished
= False
239 def documentEventOccured(self
, event
):
240 # print(str(event.EventName))
241 if event
.EventName
== "OnLayoutFinished":
242 self
.layoutFinished
= True
243 def disposing(event
):
246 def mkPropertyValue(name
, value
):
247 return uno
.createUnoStruct("com.sun.star.beans.PropertyValue",
252 def loadFromURL(xContext
, url
):
253 xDesktop
= xContext
.ServiceManager
.createInstanceWithContext(
254 "com.sun.star.frame.Desktop", xContext
)
255 props
= [("Hidden", True), ("ReadOnly", True)] # FilterName?
256 loadProps
= tuple([mkPropertyValue(name
, value
) for (name
, value
) in props
])
257 xListener
= EventListener()
258 xGEB
= xContext
.ServiceManager
.createInstanceWithContext(
259 "com.sun.star.frame.GlobalEventBroadcaster", xContext
)
260 xGEB
.addDocumentEventListener(xListener
)
262 xDoc
= xDesktop
.loadComponentFromURL(url
, "_blank", 0, loadProps
)
265 if xListener
.layoutFinished
:
270 print("timeout: no OnLayoutFinished received")
279 xGEB
.removeDocumentEventListener(xListener
)
281 def printDoc(xContext
, xDoc
, url
):
282 props
= [ mkPropertyValue("FileName", url
) ]
284 uno
.invoke(xDoc
, "print", (tuple(props
),)) # damn, that's a keyword!
289 prt
= xDoc
.getPrinter()
291 if value
.Name
== "IsBusy":
293 print("...done printing")
295 class LoadPrintFileTest
:
296 def __init__(self
, file, prtsuffix
):
298 self
.prtsuffix
= prtsuffix
299 def run(self
, xContext
):
300 print("Loading document: " + self
.file)
302 url
= "file://" + quote(self
.file)
303 xDoc
= loadFromURL(xContext
, url
)
304 printDoc(xContext
, xDoc
, url
+ self
.prtsuffix
)
308 print("...done with: " + self
.file)
310 def runLoadPrintFileTests(opts
, dirs
, suffix
, reference
):
312 prtsuffix
= ".pdf.reference"
315 files
= getFiles(dirs
, suffix
)
316 tests
= (LoadPrintFileTest(file, prtsuffix
) for file in files
)
317 connection
= PersistentConnection(opts
)
318 # connection = PerTestConnection(opts)
319 runConnectionTests(connection
, simpleInvoke
, tests
)
321 def mkImages(file, resolution
):
322 argv
= [ "gs", "-r" + resolution
, "-sOutputFile=" + file + ".%04d.jpeg",
323 "-dNOPROMPT", "-dNOPAUSE", "-dBATCH", "-sDEVICE=jpeg", file ]
324 ret
= subprocess
.check_call(argv
)
326 def mkAllImages(dirs
, suffix
, resolution
, reference
):
328 prtsuffix
= ".pdf.reference"
332 files
= filelist(dir, suffix
)
335 mkImages(f
+ prtsuffix
, resolution
)
337 def identify(imagefile
):
338 argv
= ["identify", "-format", "%k", imagefile
]
339 process
= subprocess
.Popen(argv
, stdout
=subprocess
.PIPE
)
340 result
, _
= process
.communicate()
341 if process
.wait() != 0:
342 raise Exception("identify failed")
343 if result
.partition(b
"\n")[0] != b
"1":
344 print("identify result: " + result
)
345 print("DIFFERENCE in " + imagefile
)
347 def compose(refimagefile
, imagefile
, diffimagefile
):
348 argv
= [ "composite", "-compose", "difference",
349 refimagefile
, imagefile
, diffimagefile
]
350 subprocess
.check_call(argv
)
352 def compareImages(file):
353 allimages
= [f
for f
in filelist(os
.path
.dirname(file), ".jpeg")
354 if f
.startswith(file)]
355 # refimages = [f for f in filelist(os.path.dirname(file), ".jpeg")
356 # if f.startswith(file + ".reference")]
357 # print("compareImages: allimages:" + str(allimages))
358 (refimages
, images
) = partition(sorted(allimages
),
359 lambda f
: f
.startswith(file + ".pdf.reference"))
360 # print("compareImages: images" + str(images))
361 for (image
, refimage
) in zip(images
, refimages
):
362 compose(image
, refimage
, image
+ ".diff")
363 identify(image
+ ".diff")
364 if (len(images
) != len(refimages
)):
365 print("DIFFERENT NUMBER OF IMAGES FOR: " + file)
367 def compareAllImages(dirs
, suffix
):
368 print("compareAllImages...")
370 files
= filelist(dir, suffix
)
371 # print("compareAllImages:" + str(files))
374 print("...compareAllImages done")
378 (optlist
,args
) = getopt
.getopt(argv
[1:], "hr",
379 ["help", "soffice=", "userdir=", "reference", "valgrind"])
381 return (dict(optlist
), args
)
384 message
= """usage: {program} [option]... [directory]..."
385 -h | --help: print usage information
386 -r | --reference: generate new reference files (otherwise: compare)
387 --soffice=method:location
388 specify soffice instance to connect to
389 supported methods: 'path', 'connect'
390 --userdir=URL specify user installation directory for 'path' method
391 --valgrind pass --valgrind to soffice for 'path' method"""
392 print(message
.format(program
= os
.path
.basename(sys
.argv
[0])))
396 subprocess
.check_output(["gs", "--version"])
398 print("Cannot execute 'gs'. Please install ghostscript.")
401 subprocess
.check_output(["composite", "-version"])
402 subprocess
.check_output(["identify", "-version"])
404 print("Cannot execute 'composite' or 'identify'.")
405 print("Please install ImageMagick.")
408 if __name__
== "__main__":
410 (opts
,args
) = parseArgs(sys
.argv
)
414 if "-h" in opts
or "--help" in opts
:
417 elif "--soffice" in opts
:
418 reference
= "-r" in opts
or "--reference" in opts
419 runLoadPrintFileTests(opts
, args
, ".odt", reference
)
420 mkAllImages(args
, ".odt", "200", reference
)
422 compareAllImages(args
, ".odt")
427 # vim:set shiftwidth=4 softtabstop=4 expandtab: