diff options
Diffstat (limited to '')
-rw-r--r-- | libreofficekit/qa/unit/tiledrendering.cxx | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/libreofficekit/qa/unit/tiledrendering.cxx b/libreofficekit/qa/unit/tiledrendering.cxx new file mode 100644 index 000000000..56d789b61 --- /dev/null +++ b/libreofficekit/qa/unit/tiledrendering.cxx @@ -0,0 +1,456 @@ +/* -*- 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/. + */ + +#include <memory> +#include <thread> +#include <boost/property_tree/json_parser.hpp> +#include <cppunit/TestFixture.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cstdlib> +#include <string> +#include <stdio.h> + +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> + +#include <com/sun/star/awt/Key.hpp> + +#if defined __clang__ && defined __linux__ +#include <cxxabi.h> +#include <config_options.h> +#if defined _LIBCPPABI_VERSION || !ENABLE_RUNTIME_OPTIMIZATIONS +#define LOK_LOADLIB_GLOBAL +#endif +#endif + +#include <LibreOfficeKit/LibreOfficeKitInit.h> +#include <LibreOfficeKit/LibreOfficeKit.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +using namespace ::boost; +using namespace ::lok; +using namespace ::std; + +namespace { + +void processEventsToIdle() +{ + typedef void (ProcessEventsToIdleFn)(void); + static ProcessEventsToIdleFn *processFn = nullptr; + if (!processFn) + { + void *me = dlopen(nullptr, RTLD_NOW); + processFn = reinterpret_cast<ProcessEventsToIdleFn *>(dlsym(me, "unit_lok_process_events_to_idle")); + } + + CPPUNIT_ASSERT(processFn); + + (*processFn)(); +} + +void insertString(Document& rDocument, const std::string& s) +{ + for (const char c : s) + { + rDocument.postKeyEvent(LOK_KEYEVENT_KEYINPUT, c, 0); + rDocument.postKeyEvent(LOK_KEYEVENT_KEYUP, c, 0); + processEventsToIdle(); + } +} + +} + +static OUString getFileURLFromSystemPath(OUString const & path) +{ + OUString url; + osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath(path, url); + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, e); + if (!url.endsWith("/")) + url += "/"; + return url; +} + +// We specifically don't use the usual BootStrapFixture, as LOK does +// all its own setup and bootstrapping, and should be usable in a +// raw C++ program. +class TiledRenderingTest : public ::CppUnit::TestFixture +{ +public: + const string m_sSrcRoot; + const string m_sInstDir; + const string m_sLOPath; + + std::unique_ptr<Document> loadDocument( Office *pOffice, const string &pName, + const char *pFilterOptions = nullptr ); + + TiledRenderingTest() + : m_sSrcRoot( getenv( "SRC_ROOT" ) ) + , m_sInstDir( getenv( "INSTDIR" ) ) + , m_sLOPath( m_sInstDir + "/program" ) + { + } + + // Currently it isn't possible to do multiple startup/shutdown + // cycle of LOK in a single process -- hence we run all our tests + // as one test, which simply carries out the individual test + // components on the one Office instance that we retrieve. + void runAllTests(); + + void testDocumentLoadFail( Office* pOffice ); + void testDocumentTypes( Office* pOffice ); + void testImpressSlideNames( Office* pOffice ); + void testCalcSheetNames( Office* pOffice ); + void testPaintPartTile( Office* pOffice ); + void testDocumentLoadLanguage(Office* pOffice); + void testMultiKeyInput(Office *pOffice); +#if 0 + void testOverlay( Office* pOffice ); +#endif + + CPPUNIT_TEST_SUITE(TiledRenderingTest); + CPPUNIT_TEST(runAllTests); + CPPUNIT_TEST_SUITE_END(); +}; + +void TiledRenderingTest::runAllTests() +{ + // set UserInstallation to user profile dir in test/user-template + const char* pWorkdirRoot = getenv("WORKDIR_FOR_BUILD"); + OUString aWorkdirRootPath = OUString::createFromAscii(pWorkdirRoot); + OUString aWorkdirRootURL = getFileURLFromSystemPath(aWorkdirRootPath); + OUString sUserInstallURL = aWorkdirRootURL + "/unittest"; + rtl::Bootstrap::set("UserInstallation", sUserInstallURL); + + std::unique_ptr< Office > pOffice( lok_cpp_init( + m_sLOPath.c_str() ) ); + CPPUNIT_ASSERT( pOffice ); + + testDocumentLoadFail( pOffice.get() ); + testDocumentTypes( pOffice.get() ); + testMultiKeyInput(pOffice.get()); + testImpressSlideNames( pOffice.get() ); + testCalcSheetNames( pOffice.get() ); + testPaintPartTile( pOffice.get() ); + testDocumentLoadLanguage(pOffice.get()); +#if 0 + testOverlay( pOffice.get() ); +#endif +} + +void TiledRenderingTest::testDocumentLoadFail( Office* pOffice ) +{ + const string sDocPath = m_sSrcRoot + "/libreofficekit/qa/data/IDONOTEXIST.odt"; + std::unique_ptr< Document> pDocument( pOffice->documentLoad( sDocPath.c_str() ) ); + CPPUNIT_ASSERT( !pDocument ); + // TODO: we probably want to have some way of returning what + // the cause of failure was. getError() will return + // something along the lines of: + // "Unsupported URL <file:///SRC_ROOT/libreofficekit/qa/data/IDONOTEXIST.odt>: "type detection failed"" +} + +// Our dumped .png files end up in +// workdir/CppunitTest/libreofficekit_tiledrendering.test.core + +static int getDocumentType( Office* pOffice, const string& rPath ) +{ + std::unique_ptr< Document> pDocument( pOffice->documentLoad( rPath.c_str() ) ); + CPPUNIT_ASSERT( pDocument ); + return pDocument->getDocumentType(); +} + +std::unique_ptr<Document> TiledRenderingTest::loadDocument( Office *pOffice, const string &pName, + const char *pFilterOptions ) +{ + const string sDocPath = m_sSrcRoot + "/libreofficekit/qa/data/" + pName; + const string sLockFile = m_sSrcRoot +"/libreofficekit/qa/data/.~lock." + pName + "#"; + + remove( sLockFile.c_str() ); + + return std::unique_ptr<Document>(pOffice->documentLoad( sDocPath.c_str(), pFilterOptions )); +} + +void TiledRenderingTest::testDocumentTypes( Office* pOffice ) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt")); + + CPPUNIT_ASSERT(pDocument); + CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType())); + // This crashed. + pDocument->postUnoCommand(".uno:Bold"); + processEventsToIdle(); + + const string sPresentationDocPath = m_sSrcRoot + "/libreofficekit/qa/data/blank_presentation.odp"; + const string sPresentationLockFile = m_sSrcRoot +"/libreofficekit/qa/data/.~lock.blank_presentation.odp#"; + + // FIXME: same comment as below wrt lockfile removal. + remove( sPresentationLockFile.c_str() ); + + CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_PRESENTATION, static_cast<LibreOfficeKitDocumentType>(getDocumentType(pOffice, sPresentationDocPath))); + + // TODO: do this for all supported document types +} + +void TiledRenderingTest::testImpressSlideNames( Office* pOffice ) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "impress_slidenames.odp")); + + CPPUNIT_ASSERT_EQUAL(3, pDocument->getParts()); + CPPUNIT_ASSERT_EQUAL(std::string("TestText1"), std::string(pDocument->getPartName(0))); + CPPUNIT_ASSERT_EQUAL(std::string("TestText2"), std::string(pDocument->getPartName(1))); + // The third slide hasn't had a name given to it (i.e. using the rename + // context menu in Impress), thus it should (as far as I can determine) + // have a localised version of "Slide 3". +} + +void TiledRenderingTest::testCalcSheetNames( Office* pOffice ) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "calc_sheetnames.ods")); + + CPPUNIT_ASSERT_EQUAL(3, pDocument->getParts()); + CPPUNIT_ASSERT_EQUAL(std::string("TestText1"), std::string(pDocument->getPartName(0))); + CPPUNIT_ASSERT_EQUAL(std::string("TestText2"), std::string(pDocument->getPartName(1))); + CPPUNIT_ASSERT_EQUAL(std::string("Sheet3"), std::string(pDocument->getPartName(2))); +} + +void TiledRenderingTest::testPaintPartTile(Office* pOffice) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt")); + + CPPUNIT_ASSERT(pDocument); + CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType())); + + // Create two views. + pDocument->getView(); + pDocument->createView(); + + int nView2 = pDocument->getView(); + + // Destroy the current view + pDocument->destroyView(nView2); + + int nCanvasWidth = 256; + int nCanvasHeight = 256; + std::vector<unsigned char> aBuffer(nCanvasWidth * nCanvasHeight * 4); + + // And try to paintPartTile() - this used to crash when the current viewId + // was destroyed + pDocument->paintPartTile(aBuffer.data(), /*nPart=*/0, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840); +} + +void TiledRenderingTest::testDocumentLoadLanguage(Office* pOffice) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt", "Language=en-US")); + + // assert that '.' is the decimal separator + insertString(*pDocument, "1.5"); + + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RIGHT); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RIGHT); + processEventsToIdle(); + + insertString(*pDocument, "=2*A1"); + + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RETURN); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RETURN); + processEventsToIdle(); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::UP); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::UP); + processEventsToIdle(); + +#if 0 + // FIXME disabled, as occasionally fails + // we've got a meaningful result + OString aResult = pDocument->getTextSelection("text/plain;charset=utf-8"); + CPPUNIT_ASSERT_EQUAL(OString("3\n"), aResult); + + pDocument.reset(); + + // FIXME: LOK will fail when trying to open a locked file + remove(sLockFile.c_str()); + + // load the file again, now in another language + pDocument.reset(pOffice->documentLoad(sDocPath.c_str(), "Language=cs-CZ")); + + // with cs-CZ, the decimal separator is ',' instead, assert that + insertString(*pDocument, "1,5"); + + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RIGHT); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RIGHT); + processEventsToIdle(); + + insertString(*pDocument, "=2*A1"); + + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RETURN); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RETURN); + processEventsToIdle(); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::UP); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::UP); + processEventsToIdle(); + + // we've got a meaningful result + aResult = pDocument->getTextSelection("text/plain;charset=utf-8"); + CPPUNIT_ASSERT_EQUAL(OString("3\n"), aResult); +#endif +} + +#if 0 +static void dumpRGBABitmap( const OUString& rPath, const unsigned char* pBuffer, + const int nWidth, const int nHeight ) +{ + Bitmap aBitmap( Size( nWidth, nHeight ), 32 ); + BitmapScopedWriteAccess pWriteAccess( aBitmap ); + memcpy( pWriteAccess->GetBuffer(), pBuffer, 4*nWidth*nHeight ); + + BitmapEx aBitmapEx( aBitmap ); + vcl::PNGWriter aWriter( aBitmapEx ); + SvFileStream sOutput( rPath, StreamMode::WRITE ); + aWriter.Write( sOutput ); + sOutput.Close(); +} + +void TiledRenderingTest::testOverlay( Office* /*pOffice*/ ) +{ + const string sDocPath = m_sSrcRoot + "/odk/examples/java/DocumentHandling/test/test1.odt"; + const string sLockFile = m_sSrcRoot + "/odk/examples/java/DocumentHandling/test/.~lock.test1.odt#"; + + // FIXME: this is a temporary hack: LOK will fail when trying to open a + // locked file, and since we're reusing the file for a different unit + // test it's entirely possible that an unwanted lock file will remain. + // Hence forcefully remove it here. + remove( sLockFile.c_str() ); + std::unique_ptr< Office > pOffice( lok_cpp_init( + m_sLOPath.c_str() ) ); + assert( pOffice.get() ); + + std::unique_ptr< Document> pDocument( pOffice->documentLoad( + sDocPath.c_str() ) ); + + if ( !pDocument.get() ) + { + fprintf( stderr, "documentLoad failed: %s\n", pOffice->getError() ); + CPPUNIT_FAIL( "Document could not be loaded -- tiled rendering not possible." ); + } + + // We render one large tile, then subdivide it into 4 and render those parts, and finally + // iterate over each smaller tile and check whether their contents match the large + // tile. + const int nTotalWidthPix = 512; + const int nTotalHeightPix = 512; + int nRowStride; + + long nTotalWidthDoc; + long nTotalHeightDoc; + // pDocument->getDocumentSize( &nTotalWidthDoc, &nTotalHeightDoc ); + // TODO: make sure we select an actually interesting part of the document + // for this comparison, i.e. ideally an image and lots of text, in order + // to test as many edge cases as possible. + // Alternatively we could rewrite this to actually grab the document size + // and iterate over it (subdividing into an arbitrary number of tiles rather + // than our less sophisticated test of just 4 sub-tiles). + nTotalWidthDoc = 8000; + nTotalHeightDoc = 9000; + + std::unique_ptr< unsigned char []> pLarge( new unsigned char[ 4*nTotalWidthPix*nTotalHeightPix ] ); + pDocument->paintTile( pLarge.get(), nTotalWidthPix, nTotalHeightPix, &nRowStride, + 0, 0, + nTotalWidthDoc, nTotalHeightDoc ); + dumpRGBABitmap( "large.png", pLarge.get(), nTotalWidthPix, nTotalHeightPix ); + + std::unique_ptr< unsigned char []> pSmall[4]; + for ( int i = 0; i < 4; i++ ) + { + pSmall[i].reset( new unsigned char[ 4*(nTotalWidthPix/2)*(nTotalHeightPix/2) ] ); + pDocument->paintTile( pSmall[i].get(), nTotalWidthPix / 2, nTotalHeightPix / 2, &nRowStride, + // Tile 0/2: left. Tile 1/3: right. Tile 0/1: top. Tile 2/3: bottom + ((i%2 == 0) ? 0 : nTotalWidthDoc / 2), ((i < 2 ) ? 0 : nTotalHeightDoc / 2), + nTotalWidthDoc / 2, nTotalHeightDoc / 2); + dumpRGBABitmap( "small_" + OUString::number(i) + ".png", + pSmall[i].get(), nTotalWidthPix/2, nTotalHeightPix/2 ); + } + + // Iterate over each pixel of the sub-tile, and compare that pixel for every + // tile with the equivalent super-tile pixel. + for ( int i = 0; i < 4*nTotalWidthPix / 2 * nTotalHeightPix / 2; i++ ) + { + int xSmall = i % (4*nTotalWidthPix/2); + int ySmall = i / (4*nTotalWidthPix/2); + // Iterate over our array of tiles + // However for now we only bother with the top-left + // tile as the other ones don't match yet... + for ( int x = 0; x < 2; x++ ) + { + for ( int y = 0; y < 2; y++ ) + { + int xLarge = (x * (4 * nTotalWidthPix / 2)) + xSmall; + int yLarge = (y * (nTotalHeightPix / 2)) + ySmall; + CPPUNIT_ASSERT( pSmall[2*y+x][i] == pLarge[yLarge*4*nTotalWidthPix + xLarge] ); + } + } + } +} +#endif + +void TiledRenderingTest::testMultiKeyInput(Office *pOffice) +{ + std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt")); + + CPPUNIT_ASSERT(pDocument); + CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType())); + + // Create two views. + int nViewA = pDocument->getView(); + pDocument->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"jill\"}}"); + + pDocument->createView(); + int nViewB = pDocument->getView(); + pDocument->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"jack\"}}"); + + // Enable change tracking + pDocument->postUnoCommand(".uno:TrackChanges"); + + // First a key-stroke from a + pDocument->setView(nViewA); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 97, 0); // a + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 512); // 'a + + // A space on 'a' - force commit + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, 0); // ' ' + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 1284); // '' ' + + // Another 'a' + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 97, 0); // a + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 512); // 'a + + // FIXME: Wait for writer input handler to commit that. + // without this we fall foul of edtwin's KeyInputFlushTimer + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // Quickly a new key-stroke from b + pDocument->setView(nViewB); + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 98, 0); // b + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 514); // 'b + + // A space on 'b' - force commit + pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, 0); // ' ' + pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 1284); // '' ' + + // Wait for writer input handler to commit that. + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // get track changes ? + char *values = pDocument->getCommandValues(".uno:AcceptTrackedChanges"); + std::cerr << "Values: '" << values << "'\n"; +} + +CPPUNIT_TEST_SUITE_REGISTRATION(TiledRenderingTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |