All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xwiki.rendering.test.cts.AbstractRenderingTest Maven / Gradle / Ivy

There is a newer version: 16.8.0-rc-1
Show newest version
/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.rendering.test.cts;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.context.ExecutionContextManager;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.internal.transformation.MutableRenderingContext;
import org.xwiki.rendering.parser.ParseException;
import org.xwiki.rendering.parser.Parser;
import org.xwiki.rendering.parser.StreamParser;
import org.xwiki.rendering.renderer.BlockRenderer;
import org.xwiki.rendering.renderer.PrintRenderer;
import org.xwiki.rendering.renderer.PrintRendererFactory;
import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
import org.xwiki.rendering.renderer.printer.WikiPrinter;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.syntax.SyntaxRegistry;
import org.xwiki.rendering.test.cts.junit5.RenderingTest;
import org.xwiki.rendering.transformation.RenderingContext;
import org.xwiki.xml.XMLUtils;

import static org.xwiki.rendering.syntax.Syntax.XDOMXML_CURRENT;

/**
 * A generic JUnit Test used by {@link CompatibilityTestSuite} to run a single CTS test.
 *
 * @version $Id: 045f45bb77709ea40e90c72b3f16acad6ed7e5fd $
 * @since 13.0
 */
public abstract class AbstractRenderingTest
{
    /**
     * The Syntax id corresponding to the syntax in which the CTS tests are written in.
     */
    private static final String CTS_SYNTAX_ID = XDOMXML_CURRENT.toIdString();

    /**
     * The Velocity Engine we use to evaluate the test data. We do this to allow Velocity scripts to be added to test
     * data.
     */
    private static final VelocityEngine VELOCITY_ENGINE = new VelocityEngine();

    /**
     * Symbols to start a special syntax block. For example: ${{{regex:...}}} or
     * ${{{velocity:...}}}
     */
    private static final String SPECIAL_SYNTAX_START = "${{{";

    /**
     * Symbols to close a special syntax block. For example: ${{{regex:...}}} or
     * ${{{velocity:...}}}
     */
    private static final String SPECIAL_SYNTAX_END = "}}}";

    /**
     * @see org.xwiki.rendering.test.cts.junit5.RenderingTest
     */
    private TestData testData;

    /**
     * @see org.xwiki.rendering.test.cts.junit5.RenderingTest
     */
    private ComponentManager componentManager;

    /**
     * @see RenderingTest
     */
    private String metadataSyntaxId;

    /**
     * @param testData the data for a single test
     * @param metadataSyntaxId the Syntax id of the syntax used as Metadata in the generated XDOM for parsers
     * @param componentManager see {@link #getComponentManager()}
     */
    public AbstractRenderingTest(TestData testData, String metadataSyntaxId, ComponentManager componentManager)
    {
        this.testData = testData;
        this.componentManager = componentManager;
        this.metadataSyntaxId = metadataSyntaxId;
    }

    /**
     * Executes a single test.
     *
     * @throws Exception if an error happened during the test
     */
    public void execute() throws Exception
    {
        if (getTestData().isSyntaxInputTest) {
            executeInputTest();
        } else {
            executeOutputTest();
        }
    }

    protected TestData getTestData()
    {
        return this.testData;
    }

    /**
     * @return the component manager used to find Parser and Renderers
     */
    private ComponentManager getComponentManager()
    {
        return this.componentManager;
    }

    private String getMetadataSyntaxId()
    {
        return this.metadataSyntaxId;
    }

    /**
     * Executes the test as an input test. This means:
     * 
    *
  • Parse the Syntax input
  • *
  • Render the generated XDOM using the CTS Renderer
  • *
  • Compare result with the CTS Output
  • *
* * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found */ protected void executeInputTest() throws Exception { executeTest(getTestData().syntaxData, getTestData().syntaxId, getTestData().ctsData, CTS_SYNTAX_ID); } /** * Executes the test as an output test. This means: *
    *
  • Parse the CTS input
  • *
  • Render the generated XDOM using the Syntax Renderer
  • *
  • Compare result with the Syntax Output
  • *
* * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found */ protected void executeOutputTest() throws Exception { executeTest(getTestData().ctsData, CTS_SYNTAX_ID, getTestData().syntaxData, getTestData().syntaxId); } /** * Executes a test in a generic manner. * * @param inputData the input data to parse * @param inputSyntaxId the syntax in which the input data is written in * @param expectedOutputData the output data to compare to * @param outputSyntaxId the syntax in which the output data is written in * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found */ private void executeTest(String inputData, String inputSyntaxId, String expectedOutputData, String outputSyntaxId) throws Exception { // Get the syntax from the parser/renderer to be sure to get the right display name // (SyntaxRegistry#resolveSyntax() may not know it if the syntax was registered to the Syntax Registry). Syntax inputSyntax = getInputSyntax(inputSyntaxId, outputSyntaxId); Syntax expectedSyntax = getOutputSyntax(inputSyntaxId, outputSyntaxId); Syntax validatedSyntax; if (inputSyntax.toIdString().equals(getMetadataSyntaxId())) { validatedSyntax = inputSyntax; } else { validatedSyntax = expectedSyntax; } String fullExpectedOutputData = expectedOutputData; String fullInputData = inputData; if (isXDOMXMLSyntax(inputSyntaxId)) { fullInputData = insertXMLMetadata(inputData, validatedSyntax); } if (isXDOMXMLSyntax(outputSyntaxId)) { fullExpectedOutputData = insertXMLMetadata(expectedOutputData, validatedSyntax); } String evaluatedInputData = evaluateContent(fullInputData, validatedSyntax); String evaluatedOutputData = evaluateContent(fullExpectedOutputData, validatedSyntax); String result = convert(evaluatedInputData, inputSyntax.toIdString(), expectedSyntax.toIdString()); try { if (isXMLSyntax(outputSyntaxId)) { assertExpectedResult( XMLUtils.formatXMLContent(normalizeXMLContent(evaluatedOutputData, outputSyntaxId)), XMLUtils.formatXMLContent(result)); } else { assertExpectedResult(evaluatedOutputData, result); } } catch (ParseException e) { throw new RuntimeException(String.format("Failed to compare expected result with [%s]", result), e); } } private boolean isStreamingTest(String inputSyntaxId, String outputSyntaxId) { return getComponentManager().hasComponent(StreamParser.class, inputSyntaxId) && getComponentManager().hasComponent(PrintRendererFactory.class, outputSyntaxId); } private Syntax getInputSyntax(String inputSyntaxId, String outputSyntaxId) throws Exception { Syntax syntax; if (isStreamingTest(inputSyntaxId, outputSyntaxId)) { StreamParser parser = getComponentManager().getInstance(StreamParser.class, inputSyntaxId); syntax = parser.getSyntax(); } else { Parser parser = getComponentManager().getInstance(Parser.class, inputSyntaxId); syntax = parser.getSyntax(); } return syntax; } private Syntax getOutputSyntax(String inputSyntaxId, String outputSyntaxId) throws Exception { Syntax syntax; if (isStreamingTest(inputSyntaxId, outputSyntaxId)) { PrintRendererFactory rendererFactory = getComponentManager().getInstance(PrintRendererFactory.class, outputSyntaxId); syntax = rendererFactory.getSyntax(); } else { SyntaxRegistry syntaxRegistry = getComponentManager().getInstance(SyntaxRegistry.class); syntax = syntaxRegistry.resolveSyntax(outputSyntaxId); } return syntax; } /** * @param syntaxId the syntax to check * @return true if the passed syntax id represents an XML syntax */ private boolean isXMLSyntax(String syntaxId) { return isXDOMXMLSyntax(syntaxId) || syntaxId.startsWith("docbook"); } private boolean isXDOMXMLSyntax(String syntaxId) { return syntaxId.startsWith("xdom+xml"); } private String convert(String source, String sourceSyntaxId, String targetSyntaxId) throws Exception { String result; ExecutionContext executionContext = new ExecutionContext(); ExecutionContextManager executionContextManager = getComponentManager().getInstance(ExecutionContextManager.class); executionContextManager.initialize(executionContext); // Set TargetSyntax for Macro tests SyntaxRegistry syntaxRegistry = getComponentManager().getInstance(SyntaxRegistry.class); RenderingContext renderingContext = getComponentManager().getInstance(RenderingContext.class); ((MutableRenderingContext) renderingContext).push(renderingContext.getTransformation(), renderingContext.getXDOM(), renderingContext.getDefaultSyntax(), renderingContext.getTransformationId(), renderingContext.isRestricted(), syntaxRegistry.resolveSyntax(targetSyntaxId)); try { if (isStreamingTest(sourceSyntaxId, targetSyntaxId)) { StreamParser parser = getComponentManager().getInstance(StreamParser.class, sourceSyntaxId); PrintRendererFactory rendererFactory = getComponentManager().getInstance(PrintRendererFactory.class, targetSyntaxId); result = convert(source, parser, rendererFactory); } else { Parser parser = getComponentManager().getInstance(Parser.class, sourceSyntaxId); BlockRenderer blockRenderer = getComponentManager().getInstance(BlockRenderer.class, targetSyntaxId); result = convert(source, parser, blockRenderer); } } finally { ((MutableRenderingContext) renderingContext).pop(); Execution execution = getComponentManager().getInstance(Execution.class); execution.removeContext(); } return result; } private String convert(String source, StreamParser parser, PrintRendererFactory rendererFactory) throws Exception { PrintRenderer renderer = rendererFactory.createRenderer(new DefaultWikiPrinter()); parser.parse(new StringReader(source), renderer); return renderer.getPrinter().toString(); } private String convert(String source, Parser parser, BlockRenderer blockRenderer) throws Exception { XDOM xdom = parser.parse(new StringReader(source)); WikiPrinter printer = new DefaultWikiPrinter(); blockRenderer.render(xdom, printer); return printer.toString(); } /** * Normalize the expected XML output by reading and rendering the passed content. We do this so that we can easily * compare the expected result with the result of the test and not have to care about license comments, whitespaces, * newlines, etc. * * @param content the XML content to normalize * @param syntaxId the syntax in which the XML content is written in * @return the normalized content * @throws Exception if the XML parser or Renderer cannot be found */ private String normalizeXMLContent(String content, String syntaxId) throws Exception { return convert(content, syntaxId, syntaxId); } private String insertXMLMetadata(String content, Syntax validatedSyntax) { // We insert the metadata after the part String variantsText; if (validatedSyntax.getType().getVariants().isEmpty()) { variantsText = ""; } else { StringBuilder builder = new StringBuilder(); builder.append(""); for (String variant : validatedSyntax.getType().getVariants()) { builder.append("").append(variant).append(""); } builder.append(""); variantsText = builder.toString(); } String metadataXML = "

" + " " + " " + " " + " syntax" + " " + " " + " " + validatedSyntax.getType().getName() + "" + " " + variantsText + " " + validatedSyntax.getType().getId() + "" + " " + " " + validatedSyntax.getVersion() + "" + " " + " " + " " + " " + "

"; StringBuilder builder = new StringBuilder(content); builder.insert(StringUtils.indexOf(content, "") + 10, metadataXML); return builder.toString(); } /** * Run Velocity when the ${{velocity:...}}} syntax is used. The {@code $syntax} variable is replaced by * the test Syntax object. * * @param content the content to evaluate * @return the evaluated content */ private String evaluateContent(String content, Syntax syntax) { StringBuilder builder = new StringBuilder(); String fullSpecialSyntaxStart = String.format("%svelocity:", SPECIAL_SYNTAX_START); int pos = content.indexOf(fullSpecialSyntaxStart); if (pos > -1) { builder.append(content, 0, pos); // Find end of velocity definition int pos2 = content.indexOf(SPECIAL_SYNTAX_END, pos + fullSpecialSyntaxStart.length()); if (pos2 == -1) { throw new RuntimeException("Invalid velocity declaration: missing closing part " + SPECIAL_SYNTAX_END); } VelocityContext context = new VelocityContext(); context.put("syntax", syntax); StringWriter writer = new StringWriter(); VELOCITY_ENGINE.evaluate(context, writer, "Rendering CTS", content.substring(pos + fullSpecialSyntaxStart.length(), pos2)); builder.append(writer.toString()); builder.append(evaluateContent(content.substring(pos2 + SPECIAL_SYNTAX_END.length()), syntax)); } else { builder.append(content); } return builder.toString(); } /** * Compare the passed expected string with the passed result. We support regexes for comparison using the format: * ${{{regex:...}}}. For example: * *
     * 
     * beginDocument
     * beginMacroMarkerStandalone [useravatar] [username=XWiki.UserNotExisting]
     * beginGroup [[class]=[xwikirenderingerror]]
     * onWord [Failed to execute the [useravatar] macro]
     * endGroup [[class]=[xwikirenderingerror]]
     * beginGroup [[class]=[xwikirenderingerrordescription hidden]]
     * onVerbatim [org.xwiki.rendering.macro.MacroExecutionException: User [XWiki.UserNotExisting]${{{regex:.*}}}]
     * endGroup [[class]=[xwikirenderingerrordescription hidden]]
     * endMacroMarkerStandalone [useravatar] [username=XWiki.UserNotExisting]
     * endDocument
     * 
     * 
* * @param expected the content to compare to * @param result the result from the test */ private void assertExpectedResult(String expected, String result) { String escapedExpected = escapeRegexContent(expected); Pattern pattern = Pattern.compile(escapedExpected, Pattern.DOTALL); Matcher matcher = pattern.matcher(result); if (!matcher.matches()) { throwAssertionException("", expected, result); } } protected abstract void throwAssertionException(String message, String expected, String result); /** * Escape the passed content by locating regex syntaxes inside and regex-escaping the text so that the whole content * can be matched using a Regex Matcher. * * @param content the content to escape * @return the escaped content */ private String escapeRegexContent(String content) { StringBuilder builder = new StringBuilder(); String fullSpecialSyntaxStart = String.format("%sregex:", SPECIAL_SYNTAX_START); int pos = content.indexOf(fullSpecialSyntaxStart); if (pos > -1) { builder.append(Pattern.quote(content.substring(0, pos))); // Find end of regex definition int pos2 = findPositionOfRegexEnd(content, pos + fullSpecialSyntaxStart.length()); if (pos2 == -1) { throw new RuntimeException("Invalid regex declaration: missing closing part " + SPECIAL_SYNTAX_END); } builder.append(content, pos + fullSpecialSyntaxStart.length(), pos2); builder.append(escapeRegexContent(content.substring(pos2 + SPECIAL_SYNTAX_END.length()))); } else { builder.append(Pattern.quote(content)); } return builder.toString(); } private int findPositionOfRegexEnd(String content, int pos) { int result = content.indexOf(SPECIAL_SYNTAX_END, pos); // Verify the first char of the SPECIAL_SYNTAX_END is not escaped if (result > -1 && content.charAt(result - 1) == '\\') { result = findPositionOfRegexEnd(content, result + 1); } return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy