1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
12 #include <boost/property_tree/json_parser.hpp>
13 #include <cppunit/TestFixture.h>
14 #include <cppunit/plugin/TestPlugIn.h>
15 #include <cppunit/extensions/HelperMacros.h>
20 #include <osl/file.hxx>
21 #include <rtl/bootstrap.hxx>
23 #include <com/sun/star/awt/Key.hpp>
25 #if defined __clang__ && defined __linux__
27 #include <config_options.h>
28 #if defined _LIBCPPABI_VERSION || !ENABLE_RUNTIME_OPTIMIZATIONS
29 #define LOK_LOADLIB_GLOBAL
33 #include <LibreOfficeKit/LibreOfficeKitInit.h>
34 #include <LibreOfficeKit/LibreOfficeKit.hxx>
35 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
37 using namespace ::boost
;
38 using namespace ::lok
;
39 using namespace ::std
;
43 void processEventsToIdle()
45 typedef void (ProcessEventsToIdleFn
)(void);
46 static ProcessEventsToIdleFn
*processFn
= nullptr;
49 void *me
= dlopen(nullptr, RTLD_NOW
);
50 processFn
= reinterpret_cast<ProcessEventsToIdleFn
*>(dlsym(me
, "unit_lok_process_events_to_idle"));
53 CPPUNIT_ASSERT(processFn
);
58 void insertString(Document
& rDocument
, const std::string
& s
)
60 for (const char c
: s
)
62 rDocument
.postKeyEvent(LOK_KEYEVENT_KEYINPUT
, c
, 0);
63 rDocument
.postKeyEvent(LOK_KEYEVENT_KEYUP
, c
, 0);
64 processEventsToIdle();
70 static OUString
getFileURLFromSystemPath(OUString
const & path
)
73 osl::FileBase::RC e
= osl::FileBase::getFileURLFromSystemPath(path
, url
);
74 CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None
, e
);
75 if (!url
.endsWith("/"))
80 // We specifically don't use the usual BootStrapFixture, as LOK does
81 // all its own setup and bootstrapping, and should be usable in a
83 class TiledRenderingTest
: public ::CppUnit::TestFixture
86 const string m_sSrcRoot
;
87 const string m_sInstDir
;
88 const string m_sLOPath
;
90 std::unique_ptr
<Document
> loadDocument( Office
*pOffice
, const string
&pName
,
91 const char *pFilterOptions
= nullptr );
94 : m_sSrcRoot( getenv( "SRC_ROOT" ) )
95 , m_sInstDir( getenv( "INSTDIR" ) )
96 , m_sLOPath( m_sInstDir
+ "/program" )
100 // Currently it isn't possible to do multiple startup/shutdown
101 // cycle of LOK in a single process -- hence we run all our tests
102 // as one test, which simply carries out the individual test
103 // components on the one Office instance that we retrieve.
106 void testDocumentLoadFail( Office
* pOffice
);
107 void testDocumentTypes( Office
* pOffice
);
108 void testImpressSlideNames( Office
* pOffice
);
109 void testCalcSheetNames( Office
* pOffice
);
110 void testPaintPartTile( Office
* pOffice
);
111 void testDocumentLoadLanguage(Office
* pOffice
);
112 void testMultiKeyInput(Office
*pOffice
);
114 void testOverlay( Office
* pOffice
);
117 CPPUNIT_TEST_SUITE(TiledRenderingTest
);
118 CPPUNIT_TEST(runAllTests
);
119 CPPUNIT_TEST_SUITE_END();
122 void TiledRenderingTest::runAllTests()
124 // set UserInstallation to user profile dir in test/user-template
125 const char* pWorkdirRoot
= getenv("WORKDIR_FOR_BUILD");
126 OUString aWorkdirRootPath
= OUString::createFromAscii(pWorkdirRoot
);
127 OUString aWorkdirRootURL
= getFileURLFromSystemPath(aWorkdirRootPath
);
128 OUString sUserInstallURL
= aWorkdirRootURL
+ "/unittest";
129 rtl::Bootstrap::set("UserInstallation", sUserInstallURL
);
131 std::unique_ptr
< Office
> pOffice( lok_cpp_init(
132 m_sLOPath
.c_str() ) );
133 CPPUNIT_ASSERT( pOffice
);
135 testDocumentLoadFail( pOffice
.get() );
136 testDocumentTypes( pOffice
.get() );
137 testMultiKeyInput(pOffice
.get());
138 testImpressSlideNames( pOffice
.get() );
139 testCalcSheetNames( pOffice
.get() );
140 testPaintPartTile( pOffice
.get() );
141 testDocumentLoadLanguage(pOffice
.get());
143 testOverlay( pOffice
.get() );
147 void TiledRenderingTest::testDocumentLoadFail( Office
* pOffice
)
149 const string sDocPath
= m_sSrcRoot
+ "/libreofficekit/qa/data/IDONOTEXIST.odt";
150 std::unique_ptr
< Document
> pDocument( pOffice
->documentLoad( sDocPath
.c_str() ) );
151 CPPUNIT_ASSERT( !pDocument
);
152 // TODO: we probably want to have some way of returning what
153 // the cause of failure was. getError() will return
154 // something along the lines of:
155 // "Unsupported URL <file:///SRC_ROOT/libreofficekit/qa/data/IDONOTEXIST.odt>: "type detection failed""
158 // Our dumped .png files end up in
159 // workdir/CppunitTest/libreofficekit_tiledrendering.test.core
161 static int getDocumentType( Office
* pOffice
, const string
& rPath
)
163 std::unique_ptr
< Document
> pDocument( pOffice
->documentLoad( rPath
.c_str() ) );
164 CPPUNIT_ASSERT( pDocument
);
165 return pDocument
->getDocumentType();
168 std::unique_ptr
<Document
> TiledRenderingTest::loadDocument( Office
*pOffice
, const string
&pName
,
169 const char *pFilterOptions
)
171 const string sDocPath
= m_sSrcRoot
+ "/libreofficekit/qa/data/" + pName
;
172 const string sLockFile
= m_sSrcRoot
+"/libreofficekit/qa/data/.~lock." + pName
+ "#";
174 remove( sLockFile
.c_str() );
176 return std::unique_ptr
<Document
>(pOffice
->documentLoad( sDocPath
.c_str(), pFilterOptions
));
179 void TiledRenderingTest::testDocumentTypes( Office
* pOffice
)
181 std::unique_ptr
<Document
> pDocument(loadDocument(pOffice
, "blank_text.odt"));
183 CPPUNIT_ASSERT(pDocument
);
184 CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT
, static_cast<LibreOfficeKitDocumentType
>(pDocument
->getDocumentType()));
186 pDocument
->postUnoCommand(".uno:Bold");
187 processEventsToIdle();
189 const string sPresentationDocPath
= m_sSrcRoot
+ "/libreofficekit/qa/data/blank_presentation.odp";
190 const string sPresentationLockFile
= m_sSrcRoot
+"/libreofficekit/qa/data/.~lock.blank_presentation.odp#";
192 // FIXME: same comment as below wrt lockfile removal.
193 remove( sPresentationLockFile
.c_str() );
195 CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_PRESENTATION
, static_cast<LibreOfficeKitDocumentType
>(getDocumentType(pOffice
, sPresentationDocPath
)));
197 // TODO: do this for all supported document types
200 void TiledRenderingTest::testImpressSlideNames( Office
* pOffice
)
202 std::unique_ptr
<Document
> pDocument(loadDocument(pOffice
, "impress_slidenames.odp"));
204 CPPUNIT_ASSERT_EQUAL(3, pDocument
->getParts());
205 CPPUNIT_ASSERT_EQUAL(std::string("TestText1"), std::string(pDocument
->getPartName(0)));
206 CPPUNIT_ASSERT_EQUAL(std::string("TestText2"), std::string(pDocument
->getPartName(1)));
207 // The third slide hasn't had a name given to it (i.e. using the rename
208 // context menu in Impress), thus it should (as far as I can determine)
209 // have a localised version of "Slide 3".
212 void TiledRenderingTest::testCalcSheetNames( Office
* pOffice
)
214 std::unique_ptr
<Document
> pDocument(loadDocument(pOffice
, "calc_sheetnames.ods"));
216 CPPUNIT_ASSERT_EQUAL(3, pDocument
->getParts());
217 CPPUNIT_ASSERT_EQUAL(std::string("TestText1"), std::string(pDocument
->getPartName(0)));
218 CPPUNIT_ASSERT_EQUAL(std::string("TestText2"), std::string(pDocument
->getPartName(1)));
219 CPPUNIT_ASSERT_EQUAL(std::string("Sheet3"), std::string(pDocument
->getPartName(2)));
222 void TiledRenderingTest::testPaintPartTile(Office
* pOffice
)
224 std::unique_ptr
<Document
> pDocument(loadDocument(pOffice
, "blank_text.odt"));
226 CPPUNIT_ASSERT(pDocument
);
227 CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT
, static_cast<LibreOfficeKitDocumentType
>(pDocument
->getDocumentType()));
230 pDocument
->getView();
231 pDocument
->createView();
233 int nView2
= pDocument
->getView();
235 // Destroy the current view
236 pDocument
->destroyView(nView2
);
238 int nCanvasWidth
= 256;
239 int nCanvasHeight
= 256;
240 std::vector
<unsigned char> aBuffer(nCanvasWidth
* nCanvasHeight
* 4);
242 // And try to paintPartTile() - this used to crash when the current viewId
244 pDocument
->paintPartTile(aBuffer
.data(), /*nPart=*/0, nCanvasWidth
, nCanvasHeight
, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
247 void TiledRenderingTest::testDocumentLoadLanguage(Office
* pOffice
)
249 std::unique_ptr
<Document
> pDocument(loadDocument(pOffice
, "blank_text.odt", "Language=en-US"));
251 // assert that '.' is the decimal separator
252 insertString(*pDocument
, "1.5");
254 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 0, css::awt::Key::RIGHT
);
255 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, css::awt::Key::RIGHT
);
256 processEventsToIdle();
258 insertString(*pDocument
, "=2*A1");
260 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 0, css::awt::Key::RETURN
);
261 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, css::awt::Key::RETURN
);
262 processEventsToIdle();
263 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 0, css::awt::Key::UP
);
264 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, css::awt::Key::UP
);
265 processEventsToIdle();
268 // FIXME disabled, as occasionally fails
269 // we've got a meaningful result
270 OString aResult
= pDocument
->getTextSelection("text/plain;charset=utf-8");
271 CPPUNIT_ASSERT_EQUAL(OString("3\n"), aResult
);
275 // FIXME: LOK will fail when trying to open a locked file
276 remove(sLockFile
.c_str());
278 // load the file again, now in another language
279 pDocument
.reset(pOffice
->documentLoad(sDocPath
.c_str(), "Language=cs-CZ"));
281 // with cs-CZ, the decimal separator is ',' instead, assert that
282 insertString(*pDocument
, "1,5");
284 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 0, css::awt::Key::RIGHT
);
285 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, css::awt::Key::RIGHT
);
286 processEventsToIdle();
288 insertString(*pDocument
, "=2*A1");
290 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 0, css::awt::Key::RETURN
);
291 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, css::awt::Key::RETURN
);
292 processEventsToIdle();
293 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 0, css::awt::Key::UP
);
294 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, css::awt::Key::UP
);
295 processEventsToIdle();
297 // we've got a meaningful result
298 aResult
= pDocument
->getTextSelection("text/plain;charset=utf-8");
299 CPPUNIT_ASSERT_EQUAL(OString("3\n"), aResult
);
304 static void dumpRGBABitmap( const OUString
& rPath
, const unsigned char* pBuffer
,
305 const int nWidth
, const int nHeight
)
307 Bitmap
aBitmap( Size( nWidth
, nHeight
), 32 );
308 BitmapScopedWriteAccess
pWriteAccess( aBitmap
);
309 memcpy( pWriteAccess
->GetBuffer(), pBuffer
, 4*nWidth
*nHeight
);
311 BitmapEx
aBitmapEx( aBitmap
);
312 vcl::PNGWriter
aWriter( aBitmapEx
);
313 SvFileStream
sOutput( rPath
, StreamMode::WRITE
);
314 aWriter
.Write( sOutput
);
318 void TiledRenderingTest::testOverlay( Office
* /*pOffice*/ )
320 const string sDocPath
= m_sSrcRoot
+ "/odk/examples/java/DocumentHandling/test/test1.odt";
321 const string sLockFile
= m_sSrcRoot
+ "/odk/examples/java/DocumentHandling/test/.~lock.test1.odt#";
323 // FIXME: this is a temporary hack: LOK will fail when trying to open a
324 // locked file, and since we're reusing the file for a different unit
325 // test it's entirely possible that an unwanted lock file will remain.
326 // Hence forcefully remove it here.
327 remove( sLockFile
.c_str() );
328 std::unique_ptr
< Office
> pOffice( lok_cpp_init(
329 m_sLOPath
.c_str() ) );
330 assert( pOffice
.get() );
332 std::unique_ptr
< Document
> pDocument( pOffice
->documentLoad(
333 sDocPath
.c_str() ) );
335 if ( !pDocument
.get() )
337 fprintf( stderr
, "documentLoad failed: %s\n", pOffice
->getError() );
338 CPPUNIT_FAIL( "Document could not be loaded -- tiled rendering not possible." );
341 // We render one large tile, then subdivide it into 4 and render those parts, and finally
342 // iterate over each smaller tile and check whether their contents match the large
344 const int nTotalWidthPix
= 512;
345 const int nTotalHeightPix
= 512;
349 long nTotalHeightDoc
;
350 // pDocument->getDocumentSize( &nTotalWidthDoc, &nTotalHeightDoc );
351 // TODO: make sure we select an actually interesting part of the document
352 // for this comparison, i.e. ideally an image and lots of text, in order
353 // to test as many edge cases as possible.
354 // Alternatively we could rewrite this to actually grab the document size
355 // and iterate over it (subdividing into an arbitrary number of tiles rather
356 // than our less sophisticated test of just 4 sub-tiles).
357 nTotalWidthDoc
= 8000;
358 nTotalHeightDoc
= 9000;
360 std::unique_ptr
< unsigned char []> pLarge( new unsigned char[ 4*nTotalWidthPix
*nTotalHeightPix
] );
361 pDocument
->paintTile( pLarge
.get(), nTotalWidthPix
, nTotalHeightPix
, &nRowStride
,
363 nTotalWidthDoc
, nTotalHeightDoc
);
364 dumpRGBABitmap( "large.png", pLarge
.get(), nTotalWidthPix
, nTotalHeightPix
);
366 std::unique_ptr
< unsigned char []> pSmall
[4];
367 for ( int i
= 0; i
< 4; i
++ )
369 pSmall
[i
].reset( new unsigned char[ 4*(nTotalWidthPix
/2)*(nTotalHeightPix
/2) ] );
370 pDocument
->paintTile( pSmall
[i
].get(), nTotalWidthPix
/ 2, nTotalHeightPix
/ 2, &nRowStride
,
371 // Tile 0/2: left. Tile 1/3: right. Tile 0/1: top. Tile 2/3: bottom
372 ((i
%2 == 0) ? 0 : nTotalWidthDoc
/ 2), ((i
< 2 ) ? 0 : nTotalHeightDoc
/ 2),
373 nTotalWidthDoc
/ 2, nTotalHeightDoc
/ 2);
374 dumpRGBABitmap( "small_" + OUString::number(i
) + ".png",
375 pSmall
[i
].get(), nTotalWidthPix
/2, nTotalHeightPix
/2 );
378 // Iterate over each pixel of the sub-tile, and compare that pixel for every
379 // tile with the equivalent super-tile pixel.
380 for ( int i
= 0; i
< 4*nTotalWidthPix
/ 2 * nTotalHeightPix
/ 2; i
++ )
382 int xSmall
= i
% (4*nTotalWidthPix
/2);
383 int ySmall
= i
/ (4*nTotalWidthPix
/2);
384 // Iterate over our array of tiles
385 // However for now we only bother with the top-left
386 // tile as the other ones don't match yet...
387 for ( int x
= 0; x
< 2; x
++ )
389 for ( int y
= 0; y
< 2; y
++ )
391 int xLarge
= (x
* (4 * nTotalWidthPix
/ 2)) + xSmall
;
392 int yLarge
= (y
* (nTotalHeightPix
/ 2)) + ySmall
;
393 CPPUNIT_ASSERT( pSmall
[2*y
+x
][i
] == pLarge
[yLarge
*4*nTotalWidthPix
+ xLarge
] );
400 void TiledRenderingTest::testMultiKeyInput(Office
*pOffice
)
402 std::unique_ptr
<Document
> pDocument(loadDocument(pOffice
, "blank_text.odt"));
404 CPPUNIT_ASSERT(pDocument
);
405 CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT
, static_cast<LibreOfficeKitDocumentType
>(pDocument
->getDocumentType()));
408 int nViewA
= pDocument
->getView();
409 pDocument
->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"jill\"}}");
411 pDocument
->createView();
412 int nViewB
= pDocument
->getView();
413 pDocument
->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"jack\"}}");
415 // Enable change tracking
416 pDocument
->postUnoCommand(".uno:TrackChanges");
418 // First a key-stroke from a
419 pDocument
->setView(nViewA
);
420 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 97, 0); // a
421 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, 512); // 'a
423 // A space on 'a' - force commit
424 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 32, 0); // ' '
425 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, 1284); // '' '
428 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 97, 0); // a
429 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, 512); // 'a
431 // FIXME: Wait for writer input handler to commit that.
432 // without this we fall foul of edtwin's KeyInputFlushTimer
433 std::this_thread::sleep_for(std::chrono::milliseconds(300));
435 // Quickly a new key-stroke from b
436 pDocument
->setView(nViewB
);
437 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 98, 0); // b
438 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, 514); // 'b
440 // A space on 'b' - force commit
441 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYINPUT
, 32, 0); // ' '
442 pDocument
->postKeyEvent(LOK_KEYEVENT_KEYUP
, 0, 1284); // '' '
444 // Wait for writer input handler to commit that.
445 std::this_thread::sleep_for(std::chrono::milliseconds(300));
447 // get track changes ?
448 char *values
= pDocument
->getCommandValues(".uno:AcceptTrackedChanges");
449 std::cerr
<< "Values: '" << values
<< "'\n";
452 CPPUNIT_TEST_SUITE_REGISTRATION(TiledRenderingTest
);
454 CPPUNIT_PLUGIN_IMPLEMENT();
456 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */