| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.layoutengine; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.transform.Source; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.dom.DOMResult; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.sax.SAXResult; |
| import javax.xml.transform.sax.TransformerHandler; |
| |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.SAXException; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import org.apache.fop.DebugHelper; |
| import org.apache.fop.apps.FOUserAgent; |
| import org.apache.fop.apps.Fop; |
| import org.apache.fop.apps.FopFactory; |
| import org.apache.fop.apps.FormattingResults; |
| import org.apache.fop.area.AreaTreeModel; |
| import org.apache.fop.area.AreaTreeParser; |
| import org.apache.fop.area.RenderPagesModel; |
| import org.apache.fop.events.Event; |
| import org.apache.fop.events.EventListener; |
| import org.apache.fop.events.model.EventSeverity; |
| import org.apache.fop.fonts.FontInfo; |
| import org.apache.fop.intermediate.IFTester; |
| import org.apache.fop.intermediate.TestAssistant; |
| import org.apache.fop.layoutmgr.ElementListObserver; |
| import org.apache.fop.render.intermediate.IFContext; |
| import org.apache.fop.render.intermediate.IFRenderer; |
| import org.apache.fop.render.intermediate.IFSerializer; |
| import org.apache.fop.render.xml.XMLRenderer; |
| import org.apache.fop.util.ConsoleEventListenerForTests; |
| import org.apache.fop.util.DelegatingContentHandler; |
| |
| /** |
| * Class for testing the FOP's layout engine using testcases specified in XML |
| * files. |
| */ |
| @RunWith(Parameterized.class) |
| public class LayoutEngineTestCase { |
| private static File areaTreeBackupDir; |
| |
| @BeforeClass |
| public static void makeDirAndRegisterDebugHelper() throws IOException { |
| DebugHelper.registerStandardElementListObservers(); |
| areaTreeBackupDir = new File("build/test-results/layoutengine"); |
| if (!areaTreeBackupDir.mkdirs() && !areaTreeBackupDir.exists()) { |
| throw new IOException("Failed to create the layout engine directory at " |
| + "build/test-results/layoutengine"); |
| } |
| } |
| |
| /** |
| * Creates the parameters for this test. |
| * |
| * @return the list of file arrays populated with test files |
| * @throws IOException if an I/O error occurs while reading the test file |
| */ |
| @Parameters |
| public static Collection<File[]> getParameters() throws IOException { |
| return LayoutEngineTestUtils.getLayoutTestFiles(); |
| } |
| |
| private TestAssistant testAssistant = new TestAssistant(); |
| |
| private LayoutEngineChecksFactory layoutEngineChecksFactory = new LayoutEngineChecksFactory(); |
| |
| private IFTester ifTester; |
| private File testFile; |
| |
| private TransformerFactory tfactory = TransformerFactory.newInstance(); |
| |
| /** |
| * Constructs a new instance. |
| * |
| * @param testFile the test file |
| */ |
| public LayoutEngineTestCase(File testFile) { |
| this.ifTester = new IFTester(tfactory, areaTreeBackupDir); |
| this.testFile = testFile; |
| } |
| |
| /** |
| * Runs a single layout engine test case. |
| * @throws TransformerException In case of an XSLT/JAXP problem |
| * @throws IOException In case of an I/O problem |
| * @throws SAXException In case of a problem during SAX processing |
| * @throws ParserConfigurationException In case of a problem with the XML parser setup |
| */ |
| @Test |
| public void runTest() throws TransformerException, SAXException, IOException, |
| ParserConfigurationException { |
| |
| DOMResult domres = new DOMResult(); |
| |
| ElementListCollector elCollector = new ElementListCollector(); |
| ElementListObserver.addObserver(elCollector); |
| |
| Fop fop; |
| FopFactory effFactory; |
| EventsChecker eventsChecker = new EventsChecker( |
| new ConsoleEventListenerForTests(testFile.getName(), EventSeverity.WARN)); |
| try { |
| Document testDoc = testAssistant.loadTestCase(testFile); |
| effFactory = testAssistant.getFopFactory(testDoc); |
| |
| //Setup Transformer to convert the testcase XML to XSL-FO |
| Transformer transformer = testAssistant.getTestcase2FOStylesheet().newTransformer(); |
| Source src = new DOMSource(testDoc); |
| |
| //Setup Transformer to convert the area tree to a DOM |
| TransformerHandler athandler; |
| athandler = testAssistant.getTransformerFactory().newTransformerHandler(); |
| athandler.setResult(domres); |
| |
| //Setup FOP for area tree rendering |
| FOUserAgent ua = effFactory.newFOUserAgent(); |
| ua.getEventBroadcaster().addEventListener(eventsChecker); |
| |
| XMLRenderer atrenderer = new XMLRenderer(ua); |
| atrenderer.setContentHandler(athandler); |
| ua.setRendererOverride(atrenderer); |
| fop = effFactory.newFop(ua); |
| |
| SAXResult fores = new SAXResult(fop.getDefaultHandler()); |
| transformer.transform(src, fores); |
| } finally { |
| ElementListObserver.removeObserver(elCollector); |
| } |
| |
| Document doc = (Document)domres.getNode(); |
| if (areaTreeBackupDir != null) { |
| testAssistant.saveDOM(doc, |
| new File(areaTreeBackupDir, testFile.getName() + ".at.xml")); |
| } |
| FormattingResults results = fop.getResults(); |
| LayoutResult result = new LayoutResult(doc, elCollector, results); |
| checkAll(effFactory, testFile, result, eventsChecker); |
| } |
| |
| private static class EventsChecker implements EventListener { |
| |
| private final List<Event> events = new ArrayList<Event>(); |
| |
| private final EventListener defaultListener; |
| |
| /** |
| * @param fallbackListener the listener to which this class will pass through |
| * events that are not being checked |
| */ |
| public EventsChecker(EventListener fallbackListener) { |
| this.defaultListener = fallbackListener; |
| } |
| |
| public void processEvent(Event event) { |
| events.add(event); |
| } |
| |
| public void checkEvent(String expectedKey, Map<String, String> expectedParams) { |
| boolean eventFound = false; |
| for (Iterator<Event> iter = events.iterator(); !eventFound && iter.hasNext();) { |
| Event event = iter.next(); |
| if (event.getEventKey().equals(expectedKey)) { |
| eventFound = true; |
| iter.remove(); |
| checkParameters(event, expectedParams); |
| } |
| } |
| if (!eventFound) { |
| fail("Event did not occur but was expected to: " + expectedKey + expectedParams); |
| } |
| } |
| |
| private void checkParameters(Event event, Map<String, String> expectedParams) { |
| Map<String, Object> actualParams = event.getParams(); |
| for (Map.Entry<String, String> expectedParam : expectedParams.entrySet()) { |
| assertTrue("Event \"" + event.getEventKey() |
| + "\" is missing parameter \"" + expectedParam.getKey() + '"', |
| actualParams.containsKey(expectedParam.getKey())); |
| assertEquals("Event \"" + event.getEventKey() |
| + "\" has wrong value for parameter \"" + expectedParam.getKey() + "\";", |
| actualParams.get(expectedParam.getKey()).toString(), |
| expectedParam.getValue()); |
| } |
| } |
| |
| public void emitUncheckedEvents() { |
| for (Event event : events) { |
| defaultListener.processEvent(event); |
| } |
| } |
| } |
| |
| /** |
| * Perform all checks on the area tree and, optionally, on the intermediate format. |
| * @param fopFactory the FOP factory |
| * @param testFile Test case XML file |
| * @param result The layout results |
| * @throws TransformerException if a problem occurs in XSLT/JAXP |
| */ |
| protected void checkAll(FopFactory fopFactory, File testFile, LayoutResult result, |
| EventsChecker eventsChecker) throws TransformerException { |
| Element testRoot = testAssistant.getTestRoot(testFile); |
| |
| NodeList nodes; |
| //AT tests only when checks are available |
| nodes = testRoot.getElementsByTagName("at-checks"); |
| if (nodes.getLength() > 0) { |
| Element atChecks = (Element)nodes.item(0); |
| doATChecks(atChecks, result); |
| } |
| |
| //IF tests only when checks are available |
| nodes = testRoot.getElementsByTagName("if-checks"); |
| if (nodes.getLength() > 0) { |
| Element ifChecks = (Element)nodes.item(0); |
| Document ifDocument = createIF(fopFactory, testFile, result.getAreaTree()); |
| ifTester.doIFChecks(testFile.getName(), ifChecks, ifDocument); |
| } |
| |
| nodes = testRoot.getElementsByTagName("event-checks"); |
| if (nodes.getLength() > 0) { |
| Element eventChecks = (Element) nodes.item(0); |
| doEventChecks(eventChecks, eventsChecker); |
| } |
| eventsChecker.emitUncheckedEvents(); |
| } |
| |
| private Document createIF(FopFactory fopFactory, File testFile, Document areaTreeXML) |
| throws TransformerException { |
| try { |
| FOUserAgent ua = fopFactory.newFOUserAgent(); |
| ua.getEventBroadcaster().addEventListener( |
| new ConsoleEventListenerForTests(testFile.getName(), EventSeverity.WARN)); |
| |
| IFRenderer ifRenderer = new IFRenderer(ua); |
| |
| IFSerializer serializer = new IFSerializer(new IFContext(ua)); |
| DOMResult result = new DOMResult(); |
| serializer.setResult(result); |
| ifRenderer.setDocumentHandler(serializer); |
| |
| ua.setRendererOverride(ifRenderer); |
| FontInfo fontInfo = new FontInfo(); |
| //Construct the AreaTreeModel that will received the individual pages |
| final AreaTreeModel treeModel = new RenderPagesModel(ua, |
| null, fontInfo, null); |
| |
| //Iterate over all intermediate files |
| AreaTreeParser parser = new AreaTreeParser(); |
| ContentHandler handler = parser.getContentHandler(treeModel, ua); |
| |
| DelegatingContentHandler proxy = new DelegatingContentHandler() { |
| |
| public void endDocument() throws SAXException { |
| super.endDocument(); |
| //Signal the end of the processing. |
| //The renderer can finalize the target document. |
| treeModel.endDocument(); |
| } |
| |
| }; |
| proxy.setDelegateContentHandler(handler); |
| |
| Transformer transformer = tfactory.newTransformer(); |
| transformer.transform(new DOMSource(areaTreeXML), new SAXResult(proxy)); |
| |
| return (Document)result.getNode(); |
| } catch (Exception e) { |
| throw new TransformerException( |
| "Error while generating intermediate format file: " + e.getMessage(), e); |
| } |
| } |
| |
| private void doATChecks(Element checksRoot, LayoutResult result) { |
| List<LayoutEngineCheck> checks = layoutEngineChecksFactory.createCheckList(checksRoot); |
| if (checks.size() == 0) { |
| throw new RuntimeException("No available area tree check"); |
| } |
| for (LayoutEngineCheck check : checks) { |
| try { |
| check.check(result); |
| } catch (AssertionError ae) { |
| throw new AssertionError("Layout test (" + testFile.getName() + "): " + ae.getMessage()); |
| } catch (RuntimeException rte) { |
| throw new RuntimeException("Layout test (" + testFile.getName() + "): " + rte.getMessage()); |
| } |
| } |
| } |
| |
| private void doEventChecks(Element eventChecks, EventsChecker eventsChecker) { |
| NodeList events = eventChecks.getElementsByTagName("event"); |
| for (int i = 0; i < events.getLength(); i++) { |
| Element event = (Element) events.item(i); |
| NamedNodeMap attributes = event.getAttributes(); |
| Map<String, String> params = new HashMap<String, String>(); |
| String key = null; |
| for (int j = 0; j < attributes.getLength(); j++) { |
| Node attribute = attributes.item(j); |
| String name = attribute.getNodeName(); |
| String value = attribute.getNodeValue(); |
| if ("key".equals(name)) { |
| key = value; |
| } else { |
| params.put(name, value); |
| } |
| } |
| if (key == null) { |
| throw new RuntimeException("An event element must have a \"key\" attribute"); |
| } |
| eventsChecker.checkEvent(key, params); |
| } |
| } |
| |
| } |