1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.rendering.test.cts

File RenderingTest.java

 

Code metrics

24
102
19
1
416
232
33
0.32
5.37
19
1.74

Classes

Class Line # Actions
RenderingTest 57 102 0% 33 23
0.841379384.1%
 

Contributing tests

This file is covered by 505 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.rendering.test.cts;
21   
22    import java.io.StringReader;
23    import java.io.StringWriter;
24    import java.util.regex.Matcher;
25    import java.util.regex.Pattern;
26   
27    import org.apache.velocity.VelocityContext;
28    import org.apache.velocity.app.VelocityEngine;
29    import org.apache.velocity.runtime.RuntimeConstants;
30    import org.junit.ComparisonFailure;
31    import org.junit.Test;
32    import org.xwiki.component.manager.ComponentManager;
33    import org.xwiki.context.Execution;
34    import org.xwiki.context.ExecutionContext;
35    import org.xwiki.context.ExecutionContextManager;
36    import org.xwiki.rendering.block.XDOM;
37    import org.xwiki.rendering.internal.transformation.MutableRenderingContext;
38    import org.xwiki.rendering.parser.ParseException;
39    import org.xwiki.rendering.parser.Parser;
40    import org.xwiki.rendering.parser.StreamParser;
41    import org.xwiki.rendering.renderer.BlockRenderer;
42    import org.xwiki.rendering.renderer.PrintRenderer;
43    import org.xwiki.rendering.renderer.PrintRendererFactory;
44    import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
45    import org.xwiki.rendering.renderer.printer.WikiPrinter;
46    import org.xwiki.rendering.syntax.Syntax;
47    import org.xwiki.rendering.transformation.RenderingContext;
48    import org.xwiki.velocity.internal.log.SLF4JLogChute;
49    import org.xwiki.xml.XMLUtils;
50   
51    /**
52    * A generic JUnit Test used by {@link CompatibilityTestSuite} to run a single CTS test.
53    *
54    * @version $Id: a47b79b7ecfd125b2c9b780d4898c8e062562caa $
55    * @since 4.1M1
56    */
 
57    public class RenderingTest
58    {
59    /**
60    * The Syntax id corresponding to the syntax in which the CTS tests are written in.
61    */
62    private static final String CTS_SYNTAX_ID = org.xwiki.rendering.syntax.Syntax.XDOMXML_CURRENT.toIdString();
63   
64    /**
65    * The Velocity Engine we use to evaluate the test data. We do this to allow Velocity scripts to be added to test
66    * data.
67    */
68    private static final VelocityEngine VELOCITY_ENGINE = new VelocityEngine();
69   
 
70  14 toggle static {
71    // Make velocity use SLF4J as logger
72  14 VELOCITY_ENGINE.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new SLF4JLogChute());
73    }
74   
75    /**
76    * Symbols to start a special syntax block. For example: <code>${{{regex:...}}}</code> or
77    * <code>${{{velocity:...}}}</code>
78    */
79    private static final String SPECIAL_SYNTAX_START = "${{{";
80   
81    /**
82    * Symbols to close a special syntax block. For example: <code>${{{regex:...}}}</code> or
83    * <code>${{{velocity:...}}}</code>
84    */
85    private static final String SPECIAL_SYNTAX_END = "}}}";
86   
87    /**
88    * @see RenderingTest
89    */
90    private TestData testData;
91   
92    /**
93    * @see RenderingTest
94    */
95    private ComponentManager componentManager;
96   
97    /**
98    * @see RenderingTest
99    */
100    private String metadataSyntaxId;
101   
102    /**
103    * @param testData the data for a single test
104    * @param metadataSyntaxId the Syntax id of the syntax used as Metadata in the generated XDOM for parsers
105    * @param componentManager see {@link #getComponentManager()}
106    */
 
107  505 toggle public RenderingTest(TestData testData, String metadataSyntaxId, ComponentManager componentManager)
108    {
109  505 this.testData = testData;
110  505 this.componentManager = componentManager;
111  505 this.metadataSyntaxId = metadataSyntaxId;
112    }
113   
114    /**
115    * Executes a single test.
116    *
117    * @throws Exception if an error happened during the test
118    */
 
119  505 toggle @Test
120    public void execute() throws Exception
121    {
122  505 if (this.testData.isSyntaxInputTest) {
123  205 executeInputTest();
124    } else {
125  300 executeOutputTest();
126    }
127    }
128   
129    /**
130    * Executes the test as an input test. This means:
131    * <ul>
132    * <li>Parse the Syntax input</li>
133    * <li>Render the generated XDOM using the CTS Renderer</li>
134    * <li>Compare result with the CTS Output</li>
135    * </ul>
136    *
137    * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found
138    */
 
139  205 toggle private void executeInputTest() throws Exception
140    {
141  205 executeTest(this.testData.syntaxData, this.testData.syntaxId, this.testData.ctsData, CTS_SYNTAX_ID);
142    }
143   
144    /**
145    * Executes the test as an output test. This means:
146    * <ul>
147    * <li>Parse the CTS input</li>
148    * <li>Render the generated XDOM using the Syntax Renderer</li>
149    * <li>Compare result with the Syntax Output</li>
150    * </ul>
151    *
152    * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found
153    */
 
154  300 toggle private void executeOutputTest() throws Exception
155    {
156  300 executeTest(this.testData.ctsData, CTS_SYNTAX_ID, this.testData.syntaxData, this.testData.syntaxId);
157    }
158   
159    /**
160    * Executes a test in a generic manner.
161    *
162    * @param inputData the input data to parse
163    * @param inputSyntaxId the syntax in which the input data is written in
164    * @param expectedOutputData the output data to compare to
165    * @param outputSyntaxId the syntax in which the output data is written in
166    * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found
167    */
 
168  505 toggle private void executeTest(String inputData, String inputSyntaxId, String expectedOutputData, String outputSyntaxId)
169    throws Exception
170    {
171    // Get the syntax from the parser/renderer to be sure to get the right display name (Syntax#valueOf() may not
172    // know it)
173  505 org.xwiki.rendering.syntax.Syntax inputSyntax = getInputSyntax(inputSyntaxId, outputSyntaxId);
174  505 org.xwiki.rendering.syntax.Syntax expectedSyntax = getOutputSyntax(inputSyntaxId, outputSyntaxId);
175   
176  505 org.xwiki.rendering.syntax.Syntax validatedSyntax;
177  505 if (inputSyntax.toIdString().equals(this.metadataSyntaxId)) {
178  205 validatedSyntax = inputSyntax;
179    } else {
180  300 validatedSyntax = expectedSyntax;
181    }
182   
183  505 String evaluatedInputData = evaluateContent(inputData, validatedSyntax);
184  505 String evaluatedOutputData = evaluateContent(expectedOutputData, validatedSyntax);
185   
186  505 String result = convert(evaluatedInputData, inputSyntax.toIdString(), expectedSyntax.toIdString());
187  505 try {
188  505 if (isXMLSyntax(outputSyntaxId)) {
189  213 assertExpectedResult(
190    XMLUtils.formatXMLContent(normalizeXMLContent(evaluatedOutputData, outputSyntaxId)),
191    XMLUtils.formatXMLContent(result));
192    } else {
193  292 assertExpectedResult(evaluatedOutputData, result);
194    }
195    } catch (ParseException e) {
196  0 throw new RuntimeException(String.format("Failed to compare expected result with [%s]", result), e);
197    }
198    }
199   
 
200  1728 toggle private boolean isStreamingTest(String inputSyntaxId, String outputSyntaxId)
201    {
202  1728 return getComponentManager().hasComponent(StreamParser.class, inputSyntaxId)
203    && getComponentManager().hasComponent(PrintRendererFactory.class, outputSyntaxId);
204    }
205   
 
206  505 toggle private org.xwiki.rendering.syntax.Syntax getInputSyntax(String inputSyntaxId, String outputSyntaxId)
207    throws Exception
208    {
209  505 org.xwiki.rendering.syntax.Syntax syntax;
210  505 if (isStreamingTest(inputSyntaxId, outputSyntaxId)) {
211  505 StreamParser parser = getComponentManager().getInstance(StreamParser.class, inputSyntaxId);
212  505 syntax = parser.getSyntax();
213    } else {
214  0 Parser parser = getComponentManager().getInstance(Parser.class, inputSyntaxId);
215  0 syntax = parser.getSyntax();
216    }
217  505 return syntax;
218    }
219   
 
220  505 toggle private org.xwiki.rendering.syntax.Syntax getOutputSyntax(String inputSyntaxId, String outputSyntaxId)
221    throws Exception
222    {
223  505 org.xwiki.rendering.syntax.Syntax syntax;
224  505 if (isStreamingTest(inputSyntaxId, outputSyntaxId)) {
225  505 PrintRendererFactory rendererFactory =
226    getComponentManager().getInstance(PrintRendererFactory.class, outputSyntaxId);
227  505 syntax = rendererFactory.getSyntax();
228    } else {
229  0 syntax = org.xwiki.rendering.syntax.Syntax.valueOf(outputSyntaxId);
230    }
231  505 return syntax;
232    }
233   
234    /**
235    * @param syntaxId the syntax to check
236    * @return true if the passed syntax id represents an XML syntax
237    */
 
238  505 toggle private boolean isXMLSyntax(String syntaxId)
239    {
240  505 return syntaxId.startsWith("xdom+xml") || syntaxId.startsWith("docbook");
241    }
242   
 
243  718 toggle private String convert(String source, String sourceSyntaxId, String targetSyntaxId) throws Exception
244    {
245  718 String result;
246   
247  718 ExecutionContext executionContext = new ExecutionContext();
248  718 ExecutionContextManager executionContextManager = componentManager.getInstance(ExecutionContextManager.class);
249  718 executionContextManager.initialize(executionContext);
250    // Set TargetSyntax for Macro tests
251  718 RenderingContext renderingContext = componentManager.getInstance(RenderingContext.class);
252  718 ((MutableRenderingContext) renderingContext).push(renderingContext.getTransformation(),
253    renderingContext.getXDOM(), renderingContext.getDefaultSyntax(), renderingContext.getTransformationId(),
254    renderingContext.isRestricted(), Syntax.valueOf(targetSyntaxId));
255   
256  718 try {
257  718 if (isStreamingTest(sourceSyntaxId, targetSyntaxId)) {
258  718 StreamParser parser = getComponentManager().getInstance(StreamParser.class, sourceSyntaxId);
259  718 PrintRendererFactory rendererFactory =
260    getComponentManager().getInstance(PrintRendererFactory.class, targetSyntaxId);
261  718 result = convert(source, parser, rendererFactory);
262    } else {
263  0 Parser parser = getComponentManager().getInstance(Parser.class, sourceSyntaxId);
264  0 BlockRenderer blockRenderer = getComponentManager().getInstance(BlockRenderer.class, targetSyntaxId);
265  0 result = convert(source, parser, blockRenderer);
266    }
267    } finally {
268  718 ((MutableRenderingContext) renderingContext).pop();
269  718 Execution execution = componentManager.getInstance(Execution.class);
270  718 execution.removeContext();
271    }
272  718 return result;
273    }
274   
 
275  718 toggle private String convert(String source, StreamParser parser, PrintRendererFactory rendererFactory) throws Exception
276    {
277  718 PrintRenderer renderer = rendererFactory.createRenderer(new DefaultWikiPrinter());
278  718 parser.parse(new StringReader(source), renderer);
279  718 return renderer.getPrinter().toString();
280    }
281   
 
282  0 toggle private String convert(String source, Parser parser, BlockRenderer blockRenderer) throws Exception
283    {
284  0 XDOM xdom = parser.parse(new StringReader(source));
285  0 WikiPrinter printer = new DefaultWikiPrinter();
286  0 blockRenderer.render(xdom, printer);
287  0 return printer.toString();
288    }
289   
290    /**
291    * Normalize the expected XML output by reading and rendering the passed content. We do this so that we can easily
292    * compare the expected result with the result of the test and not have to care about license comments, whitespaces,
293    * newlines, etc.
294    *
295    * @param content the XML content to normalize
296    * @param syntaxId the syntax in which the XML content is written in
297    * @return the normalized content
298    * @throws Exception if the XML parser or Renderer cannot be found
299    */
 
300  213 toggle private String normalizeXMLContent(String content, String syntaxId) throws Exception
301    {
302  213 return convert(content, syntaxId, syntaxId);
303    }
304   
305    /**
306    * Run Velocity when the <code>${{velocity:...}}}</code> syntax is used. The {@code $syntax} variable is replaced by
307    * the test Syntax object.
308    *
309    * @param content the content to evaluate
310    * @return the evaluated content
311    */
 
312  2525 toggle private String evaluateContent(String content, org.xwiki.rendering.syntax.Syntax syntax)
313    {
314  2525 StringBuilder builder = new StringBuilder();
315  2525 String fullSpecialSyntaxStart = String.format("%svelocity:", SPECIAL_SYNTAX_START);
316  2525 int pos = content.indexOf(fullSpecialSyntaxStart);
317  2525 if (pos > -1) {
318  1515 builder.append(content.substring(0, pos));
319    // Find end of velocity definition
320  1515 int pos2 = content.indexOf(SPECIAL_SYNTAX_END, pos + fullSpecialSyntaxStart.length());
321  1515 if (pos2 == -1) {
322  0 throw new RuntimeException("Invalid velocity declaration: missing closing part " + SPECIAL_SYNTAX_END);
323    }
324   
325  1515 VelocityContext context = new VelocityContext();
326  1515 context.put("syntax", syntax);
327  1515 StringWriter writer = new StringWriter();
328  1515 VELOCITY_ENGINE.evaluate(context, writer, "Rendering CTS",
329    content.substring(pos + fullSpecialSyntaxStart.length(), pos2));
330  1515 builder.append(writer.toString());
331   
332  1515 builder.append(evaluateContent(content.substring(pos2 + SPECIAL_SYNTAX_END.length()), syntax));
333    } else {
334  1010 builder.append(content);
335    }
336  2525 return builder.toString();
337    }
338   
339    /**
340    * Compare the passed expected string with the passed result. We support regexes for comparison using the format:
341    * ${{{regex:...}}}. For example:
342    *
343    * <pre>
344    * <code>
345    * beginDocument
346    * beginMacroMarkerStandalone [useravatar] [username=XWiki.UserNotExisting]
347    * beginGroup [[class]=[xwikirenderingerror]]
348    * onWord [Failed to execute the [useravatar] macro]
349    * endGroup [[class]=[xwikirenderingerror]]
350    * beginGroup [[class]=[xwikirenderingerrordescription hidden]]
351    * onVerbatim [org.xwiki.rendering.macro.MacroExecutionException: User [XWiki.UserNotExisting]${{{regex:.*}}}]
352    * endGroup [[class]=[xwikirenderingerrordescription hidden]]
353    * endMacroMarkerStandalone [useravatar] [username=XWiki.UserNotExisting]
354    * endDocument
355    * </code>
356    * </pre>
357    *
358    * @param expected the content to compare to
359    * @param result the result from the test
360    */
 
361  505 toggle private void assertExpectedResult(String expected, String result)
362    {
363  505 String escapedExpected = escapeRegexContent(expected);
364   
365  505 Pattern pattern = Pattern.compile(escapedExpected, Pattern.DOTALL);
366  505 Matcher matcher = pattern.matcher(result);
367  505 if (!matcher.matches()) {
368  0 throw new ComparisonFailure("", expected, result);
369    }
370    }
371   
372    /**
373    * Escape the passed content by locating regex syntaxes inside and regex-escaping the text so that the whole content
374    * can be matched using a Regex Matcher.
375    *
376    * @param content the content to escape
377    * @return the escaped content
378    */
 
379  521 toggle private String escapeRegexContent(String content)
380    {
381  521 StringBuilder builder = new StringBuilder();
382  521 String fullSpecialSyntaxStart = String.format("%sregex:", SPECIAL_SYNTAX_START);
383  521 int pos = content.indexOf(fullSpecialSyntaxStart);
384  521 if (pos > -1) {
385  16 builder.append(Pattern.quote(content.substring(0, pos)));
386    // Find end of regex definition
387  16 int pos2 = findPositionOfRegexEnd(content, pos + fullSpecialSyntaxStart.length());
388  16 if (pos2 == -1) {
389  0 throw new RuntimeException("Invalid regex declaration: missing closing part " + SPECIAL_SYNTAX_END);
390    }
391  16 builder.append(content.substring(pos + fullSpecialSyntaxStart.length(), pos2));
392  16 builder.append(escapeRegexContent(content.substring(pos2 + SPECIAL_SYNTAX_END.length())));
393    } else {
394  505 builder.append(Pattern.quote(content));
395    }
396  521 return builder.toString();
397    }
398   
 
399  16 toggle private int findPositionOfRegexEnd(String content, int pos)
400    {
401  16 int result = content.indexOf(SPECIAL_SYNTAX_END, pos);
402    // Verify the first char of the SPECIAL_SYNTAX_END is not escaped
403  16 if (result > -1 && content.charAt(result - 1) == '\\') {
404  0 result = findPositionOfRegexEnd(content, result + 1);
405    }
406  16 return result;
407    }
408   
409    /**
410    * @return the component manager used to find Parser and Renderers
411    */
 
412  5902 toggle private ComponentManager getComponentManager()
413    {
414  5902 return this.componentManager;
415    }
416    }