diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /sw/qa/extras/README | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/qa/extras/README')
-rw-r--r-- | sw/qa/extras/README | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/sw/qa/extras/README b/sw/qa/extras/README new file mode 100644 index 000000000..bb6a0ae31 --- /dev/null +++ b/sw/qa/extras/README @@ -0,0 +1,273 @@ += How to add a new Writer filter test + +The `sw/qa/extras/` subdirectory has multiple import and export filter unit +tests. This file documents how to add new testcases to this framework. + +== Import tests + +Import tests are the easier ones. First you need to use +`DECLARE_SW_IMPORT_TEST()`, so the framework will load the specified file to +`mxComponent`, which represents the UNO model of the document. + +The rest of the testcase is about implementing the test method asserting this +document model: use the UNO API to retrieve properties, then use +`CPPUNIT_ASSERT_EQUAL()` to test against an expected value. + +See below for more details on writing the UNO code see below. + +=== Direct XPath assertions on the layout dump + +In most cases you want to assert the document model, but sometimes asserting +the layout is easier. If you want to do so, the `parseDump()` method can be +used to parse the layout dump of the currently loaded document. If you want +to have a look at the XML document that can be asserted, start soffice with the +`SW_DEBUG=1` environment variable, load a document, press F12, and have a look +at the `layout.xml` file in the current directory. Once you find the needed +information in that file, you can write your XPath expression to turn that into +a testcase. + +(Similarly, Shift-F12 produces a `nodes.xml` for the document model dump, but +it's unlikely that you'll need that in a unit test.) + +== Export tests + +Export tests are similar. Given that test documents are easier to provide in +some format (instead of writing code to build the documents from scratch) in +most cases, we will do an import, then do an export (to invoke the code we want +to test) and then do an import again, so we can do the testing by asserting the +document model, just like we did for import tests. + +Yes, this means that you can only test the export code (using this framework) +if the importer is working correctly. (But that's not so bad, users usually +expect a feature to work in both the importer and the exporter.) + +The only difference is that in these tests the test method is called twice: +once after the initial import -- so you can see if the export fails due to an +import problem in fact -- and once after the export and import. + +=== Direct XPath assertions + +Another alternative is to assert the resulted export document directly. +Currently this is only implemented for DOCX, which is a zipped XML, so it's +possible to evaluate XPath checks. A check looks like this: + +if (xmlDocPtr pXmlDoc = parseExport("word/document.xml")) + assertXPath(pXmlDoc, <xpath selecting the node>, <attribute>, <value>); + +It's important to check for the NULL pointer here, it's expected that it'll be +NULL when the test runs first (after the first import), as there is nothing +exported yet. For other XPath assert variants, see the `XmlTestTools` class. + +== Helper methods + +When two or more tests do the same (for example determine the number of +characters in the document), helper methods are introduced to avoid code +duplication. When you need something more complex, check if there is already a +helper method, they are also good examples. + +Helper methods which are used by more than one testsuite are in the +`SwModelTestBase` class. For example the `getLength()` method uses the trick +that you can simply enumerate over the document model, getting the paragraphs +of it; and inside those, you can enumerate over their runs. That alone is +enough if you want to test a paragraph or character property. + +== Using UNO for tests + +Figuring out the UNO API just by reading the idl files under `offapi/` is not +that productive. Xray can help in this case. Download it from: + +https://dev-www.libreoffice.org/extern/XrayTool52_en.sxw + +It's a document file, start Writer, Tools -> Options -> LibreOffice -> Security, +Macro Security, and there choose Low. Then open the document, and click `Install +Xray`. Now you can close the file. Open your testcase, which is imported +correctly (from a fixed bugs's point of view). Then open the basic editor +(Tools -> Macros -> LibreOffice Basic -> Organize Macros, Edit), and start to +write your testcase as `Sub Main`. You don't have to know much about basic, for +a typical testcase you need no `if`, `for`, or anything like that. + +NOTE: Once you restart Writer, xray will no longer be loaded automatically. For +subsequent starts, place the following line in `Main` before you do anything +else: + +---- +GlobalScope.BasicLibraries.LoadLibrary("XrayTool") +---- + +The above `mxComponent` is available as `ThisComponent` in basic, and if you +want to inspect a variable here, you can use the `xray` command to inspect +properties, methods, interfaces, etc. + +Let's take for example fdo#49501. The problem there was the page was not +landscape (and a few more, let's ignore that). + +You can start with: + +---- +xray ThisComponent +---- + +and navigate around (it is a good idea to click Configuration and enable +alphabetical sorting). The good thing is that once you write the code, you can +just start F5 without restarting LibreOffice to see the result, so you can +develop quickly. + +With some experimenting, you'll end up with something like this: + +---- +oStyle = ThisComponent.StyleFamilies.PageStyles.getByName("Default Style") +xray oStyle.IsLandscape +---- + +Now all left is to rewrite that in cpp, where it'll be much easier to debug +when later this test fails for some reason. In cpp, you typically need to be +more verbose, so the code will look like: + +---- +uno::Reference<beans::XPropertySet> xStyle(getStyles("PageStyles")->getByName("Standard"), uno::UNO_QUERY); +CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xStyle, "IsLandscape")); +---- + +== CppUnit tips + +=== sal_Bool + +In case a UNO method returns sal_Bool, and the assert fails, CppUnit won't be +able to print a usable error message, as it will think that the value is a +printable character. Best to use `bool` for the expected value and cast the +actual value to `bool` as well before comparing. + +=== Running only a single test + +If you want to run only a single test to allow quick development iteration, +then use `CPPUNIT_TEST_NAME` to specify the name of the single test: + +---- +CPPUNIT_TEST_NAME="testTdf91074" make -sr CppunitTest_sw_rtfimport +---- + +== UNO, in more details, various tips: + +=== writing code based xray inspection: + +In general, if you want to access a property, in Basic it's enough to write 'object.property', +such as printing character count that 'xray ThisComponent' prints as 'CharacterCount': + +count = ThisComponent.CharacterCount +text = paragraph.String + +In C++, this can get more complicated, as you need to use the right interface for access. Xray +prints the internal name of the object (e.g. 'SwXTextDocument' for 'xray ThisComponent') +above the list of its properties. Inspect this class/interface in the code (that is, +under offapi/, udkapi/, or wherever it is implemented) and search for a function named +similarly to the property you want (getXYZ()). If there is none, it is most +probably a property that can be read using XPropertySet or using the getProperty helper: + +sal_Int32 val = getProperty< sal_Int32 >( textDocument, "CharacterCount" ); + +If there is a function to obtain the property, you need access it using the right interface. +If the class itself is not the right interface, then it is one of the classes it inherits +from, usually the block of functions that are implemented for this interface starts with +stating the name. For example see sw/inc/unoparagraph.hxx for class SwXParagraph, it has +function getString() in a block introduced with 'XTextRange', so XTextRange is the interface +it inherits from: + +// text of the paragraph +uno::Reference<text::XTextRange> text(paragraph, uno::UNO_QUERY); +OUString value = text->getString(); + +Some properties may be more complicated to access, such as using XEnumerationAccess, XIndexAccess +or XNamedAccess to enumerate items, index them by number of name (clicking 'Dbg_SupportedInterfaces' +in xray gives a list of interfaces the object implements, and 'Count' shows the number of items). + +=== XEnumerationAccess (e.g. get the 2nd paragraph of the document): + +Basic: + +enum = ThisComponent.Text.createEnumeration +para = enum.NextElement +para = enum.NextElement +xray para + +C++: + +uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY); +uno::Reference<container::XEnumerationAccess> paraEnumAccess(textDocument->getText(), uno::UNO_QUERY); +// list of paragraphs +uno::Reference<container::XEnumeration> paraEnum = paraEnumAccess->createEnumeration(); +// go to 1st paragraph +(void) paraEnum->nextElement(); +// get the 2nd paragraph +uno::Reference<uno::XInterface> paragraph(paraEnum->nextElement(), uno::UNO_QUERY); + +Note that for paragraphs it's easier to use getParagraph(), which gets the given +paragraph (counted from 1) and optionally checks the paragraph text. + +uno::Reference< text::XTextRange > paragraph = getParagraph( 2, "TEXT" ) + +=== XNamedAccess (e.g. get a bookmark named 'position1'): + +Basic: + +bookmark = ThisComponent.Bookmarks.getByName("position1") + +or even simpler + +bookmark = ThisComponent.Bookmarks.position1 + +C++: + +uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY); +// XBookmarksSupplier interface will be needed to access the bookmarks +uno::Reference<text::XBookmarksSupplier> bookmarksSupplier(textDocument, uno::UNO_QUERY); +// get the bookmarks +uno::Reference<container::XNameAccess> bookmarks(bookmarksSupplier->getBookmarks(), uno::UNO_QUERY); +uno::Reference<uno::XInterface> bookmark; +// get the bookmark by name +bookmarks->getByName("position1") >>= bookmark; + +=== XIndexAccess (e.g. get the first bookmark): + +Basic: + +bookmark = ThisComponent.Bookmarks.getByIndex(0) + +C++: + +uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY); +// XBookmarksSupplier interface will be needed to access the bookmarks +uno::Reference<text::XBookmarksSupplier> bookmarksSupplier(textDocument, uno::UNO_QUERY); +// get the bookmarks +uno::Reference<container::XIndexAccess> bookmarks(bookmarksSupplier->getBookmarks(), uno::UNO_QUERY); +uno::Reference<uno::XInterface> bookmark; +// get the bookmark by index +bookmarks->getByIndex(0) >>= bookmark; + +=== Images + +Embedded images seem to be accessed like this: + +Basic: + +image = ThisComponent.DrawPage.getByIndex(0) +graphic = image.Graphic + +C++: + +uno::Reference<drawing::XShape> image = getShape(1); +uno::Reference<graphic::XGraphic> graphic = getProperty< uno::Reference< graphic::XGraphic > >( image, "Graphic" ); + + +=== Styles + +Styles provide information about many properties of (parts of) the document, for example +page width: + +Basic: + +ThisComponent.StyleFamilies.PageStyles.getByName("Default Style").Width + +C++: + +getStyles("PageStyles")->getByName("Standard") >>= defaultStyle; +sal_Int32 width = getProperty< sal_Int32 >( defaultStyle, "Width" ); |