1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.rendering.test.cts

File RenderingTest.java

 

Code metrics

14
62
14
1
339
153
23
0.37
4.43
14
1.64

Classes

Class Line # Actions
RenderingTest 48 62 0% 23 8
0.911111191.1%
 

Contributing tests

This file is covered by 537 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.rendering.parser.ParseException;
34    import org.xwiki.rendering.parser.StreamParser;
35    import org.xwiki.rendering.renderer.PrintRenderer;
36    import org.xwiki.rendering.renderer.PrintRendererFactory;
37    import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
38    import org.xwiki.rendering.syntax.SyntaxFactory;
39    import org.xwiki.velocity.internal.log.SLF4JLogChute;
40    import org.xwiki.xml.XMLUtils;
41   
42    /**
43    * A generic JUnit Test used by {@link CompatibilityTestSuite} to run a single CTS test.
44    *
45    * @version $Id: b6c5992fa3d0c83833a1cc399a5e7e910ef3d5e8 $
46    * @since 4.1M1
47    */
 
48    public class RenderingTest
49    {
50    /**
51    * The Syntax id corresponding to the syntax in which the CTS tests are written in.
52    */
53    private static final String CTS_SYNTAX_ID = org.xwiki.rendering.syntax.Syntax.XDOMXML_CURRENT.toIdString();
54   
55    /**
56    * The Velocity Engine we use to evaluate the test data. We do this to allow Velocity scripts to be added to test
57    * data.
58    */
59    private static final VelocityEngine VELOCITY_ENGINE = new VelocityEngine();
60   
 
61  17 toggle static {
62    // Make velocity use SLF4J as logger
63  17 VELOCITY_ENGINE.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new SLF4JLogChute());
64    }
65   
66    /**
67    * Symbols to start a special syntax block. For example: <code>${{{regex:...}}}</code> or
68    * <code>${{{velocity:...}}}</code>
69    */
70    private static final String SPECIAL_SYNTAX_START = "${{{";
71   
72    /**
73    * Symbols to close a special syntax block. For example: <code>${{{regex:...}}}</code> or
74    * <code>${{{velocity:...}}}</code>
75    */
76    private static final String SPECIAL_SYNTAX_END = "}}}";
77   
78    /**
79    * @see RenderingTest
80    */
81    private TestData testData;
82   
83    /**
84    * @see RenderingTest
85    */
86    private ComponentManager componentManager;
87   
88    /**
89    * @see RenderingTest
90    */
91    private org.xwiki.rendering.syntax.Syntax metadataSyntax;
92   
93    /**
94    * @param testData the data for a single test
95    * @param metadataSyntaxId the Syntax id of the syntax used as Metadata in the generated XDOM for parsers
96    * @param componentManager see {@link #getComponentManager()}
97    */
 
98  537 toggle public RenderingTest(TestData testData, String metadataSyntaxId, ComponentManager componentManager)
99    {
100  537 this.testData = testData;
101  537 this.componentManager = componentManager;
102  537 this.metadataSyntax = parseSyntax(metadataSyntaxId);
103    }
104   
105    /**
106    * Executes a single test.
107    *
108    * @throws Exception if an error happened during the test
109    */
 
110  537 toggle @Test
111    public void execute() throws Exception
112    {
113  537 if (this.testData.isSyntaxInputTest) {
114  223 executeInputTest();
115    } else {
116  314 executeOutputTest();
117    }
118    }
119   
120    /**
121    * Executes the test as an input test. This means:
122    * <ul>
123    * <li>Parse the Syntax input</li>
124    * <li>Render the generated XDOM using the CTS Renderer</li>
125    * <li>Compare result with the CTS Output</li>
126    * </ul>
127    *
128    * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found
129    */
 
130  223 toggle private void executeInputTest() throws Exception
131    {
132  223 executeTest(this.testData.syntaxData, this.testData.syntaxId, this.testData.ctsData, CTS_SYNTAX_ID);
133    }
134   
135    /**
136    * Executes the test as an output test. This means:
137    * <ul>
138    * <li>Parse the CTS input</li>
139    * <li>Render the generated XDOM using the Syntax Renderer</li>
140    * <li>Compare result with the Syntax Output</li>
141    * </ul>
142    *
143    * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found
144    */
 
145  314 toggle private void executeOutputTest() throws Exception
146    {
147  314 executeTest(this.testData.ctsData, CTS_SYNTAX_ID, this.testData.syntaxData, this.testData.syntaxId);
148    }
149   
150    /**
151    * Executes a test in a generic manner.
152    *
153    * @param inputData the input data to parse
154    * @param inputSyntaxId the syntax in which the input data is written in
155    * @param expectedOutputData the output data to compare to
156    * @param outputSyntaxId the syntax in which the output data is written in
157    * @throws Exception if an error happens, for example if a Parser or Renderer cannot be found
158    */
 
159  537 toggle private void executeTest(String inputData, String inputSyntaxId, String expectedOutputData, String outputSyntaxId)
160    throws Exception
161    {
162  537 String evaluatedInputData = evaluateContent(inputData);
163  537 String evaluatedOutputData = evaluateContent(expectedOutputData);
164   
165  537 String result = convert(evaluatedInputData, inputSyntaxId, outputSyntaxId);
166  537 try {
167  537 if (isXMLSyntax(outputSyntaxId)) {
168  231 assertExpectedResult(
169    XMLUtils.formatXMLContent(normalizeXMLContent(evaluatedOutputData, outputSyntaxId)),
170    XMLUtils.formatXMLContent(result));
171    } else {
172  306 assertExpectedResult(evaluatedOutputData, result);
173    }
174    } catch (ParseException e) {
175  0 throw new RuntimeException(String.format("Failed to compare expected result with [%s]", result), e);
176    }
177    }
178   
179    /**
180    * @param syntaxId the syntax to check
181    * @return true if the passed syntax id represents an XML syntax
182    */
 
183  537 toggle private boolean isXMLSyntax(String syntaxId)
184    {
185  537 return syntaxId.startsWith("xdom+xml") || syntaxId.startsWith("docbook");
186    }
187   
188    /**
189    * @param source the source content
190    * @param sourceSyntaxId the source syntax
191    * @param targetSyntaxId the target syntax
192    * @return the target content
193    * @throws Exception when failing to convert
194    */
 
195  768 toggle private String convert(String source, String sourceSyntaxId, String targetSyntaxId) throws Exception
196    {
197  768 PrintRendererFactory rendererFactory =
198    getComponentManager().getInstance(PrintRendererFactory.class, targetSyntaxId);
199  768 PrintRenderer renderer = rendererFactory.createRenderer(new DefaultWikiPrinter());
200  768 StreamParser parser = getComponentManager().getInstance(StreamParser.class, sourceSyntaxId);
201   
202  768 parser.parse(new StringReader(source), renderer);
203   
204  768 return renderer.getPrinter().toString();
205    }
206   
207    /**
208    * Normalize the expected XML output by reading and rendering the passed content. We do this so that we can easily
209    * compare the expected result with the result of the test and not have to care about license comments, whitespaces,
210    * newlines, etc.
211    *
212    * @param content the XML content to normalize
213    * @param syntaxId the syntax in which the XML content is written in
214    * @return the normalized content
215    * @throws Exception if the XML parser or Renderer cannot be found
216    */
 
217  231 toggle private String normalizeXMLContent(String content, String syntaxId) throws Exception
218    {
219  231 return convert(content, syntaxId, syntaxId);
220    }
221   
222    /**
223    * Run Velocity when the <code>${{velocity:...}}}</code> syntax is used. The {@code $syntax} variable is replaced by
224    * the test Syntax object.
225    *
226    * @param content the content to evaluate
227    * @return the evaluated content
228    */
 
229  2685 toggle private String evaluateContent(String content)
230    {
231  2685 StringBuilder builder = new StringBuilder();
232  2685 String fullSpecialSyntaxStart = String.format("%svelocity:", SPECIAL_SYNTAX_START);
233  2685 int pos = content.indexOf(fullSpecialSyntaxStart);
234  2685 if (pos > -1) {
235  1611 builder.append(content.substring(0, pos));
236    // Find end of velocity definition
237  1611 int pos2 = content.indexOf(SPECIAL_SYNTAX_END, pos + fullSpecialSyntaxStart.length());
238  1611 if (pos2 == -1) {
239  0 throw new RuntimeException("Invalid velocity declaration: missing closing part " + SPECIAL_SYNTAX_END);
240    }
241   
242  1611 VelocityContext context = new VelocityContext();
243  1611 context.put("syntax", this.metadataSyntax);
244  1611 StringWriter writer = new StringWriter();
245  1611 VELOCITY_ENGINE.evaluate(context, writer, "Rendering CTS",
246    content.substring(pos + fullSpecialSyntaxStart.length(), pos2));
247  1611 builder.append(writer.toString());
248   
249  1611 builder.append(evaluateContent(content.substring(pos2 + SPECIAL_SYNTAX_END.length())));
250    } else {
251  1074 builder.append(content);
252    }
253  2685 return builder.toString();
254    }
255   
256    /**
257    * Compare the passed expected string with the passed result. We support regexes for comparison using the format:
258    * ${{{regex:...}}}. For example:
259    *
260    * <pre>
261    * <code>
262    * beginDocument
263    * beginMacroMarkerStandalone [useravatar] [username=XWiki.UserNotExisting]
264    * beginGroup [[class]=[xwikirenderingerror]]
265    * onWord [Failed to execute the [useravatar] macro]
266    * endGroup [[class]=[xwikirenderingerror]]
267    * beginGroup [[class]=[xwikirenderingerrordescription hidden]]
268    * onVerbatim [org.xwiki.rendering.macro.MacroExecutionException: User [XWiki.UserNotExisting]${{{regex:.*}}}]
269    * endGroup [[class]=[xwikirenderingerrordescription hidden]]
270    * endMacroMarkerStandalone [useravatar] [username=XWiki.UserNotExisting]
271    * endDocument
272    * </code>
273    * </pre>
274    *
275    * @param expected the content to compare to
276    * @param result the result from the test
277    */
 
278  537 toggle private void assertExpectedResult(String expected, String result)
279    {
280  537 String escapedExpected = escapeRegexContent(expected);
281   
282  537 Pattern pattern = Pattern.compile(escapedExpected, Pattern.DOTALL);
283  537 Matcher matcher = pattern.matcher(result);
284  537 if (!matcher.matches()) {
285  0 throw new ComparisonFailure("", expected, result);
286    }
287    }
288   
289    /**
290    * Escape the passed content by locating regex syntaxes inside and regex-escaping the text so that the whole content
291    * can be matched using a Regex Matcher.
292    *
293    * @param content the content to escape
294    * @return the escaped content
295    */
 
296  553 toggle private String escapeRegexContent(String content)
297    {
298  553 StringBuilder builder = new StringBuilder();
299  553 String fullSpecialSyntaxStart = String.format("%sregex:", SPECIAL_SYNTAX_START);
300  553 int pos = content.indexOf(fullSpecialSyntaxStart);
301  553 if (pos > -1) {
302  16 builder.append(Pattern.quote(content.substring(0, pos)));
303    // Find end of regex definition
304  16 int pos2 = content.indexOf(SPECIAL_SYNTAX_END, pos + fullSpecialSyntaxStart.length());
305  16 if (pos2 == -1) {
306  0 throw new RuntimeException("Invalid regex declaration: missing closing part " + SPECIAL_SYNTAX_END);
307    }
308  16 builder.append(content.substring(pos + fullSpecialSyntaxStart.length(), pos2));
309  16 builder.append(escapeRegexContent(content.substring(pos2 + SPECIAL_SYNTAX_END.length())));
310    } else {
311  537 builder.append(Pattern.quote(content));
312    }
313  553 return builder.toString();
314    }
315   
316    /**
317    * @return the component manager used to find Parser and Renderers
318    */
 
319  2073 toggle private ComponentManager getComponentManager()
320    {
321  2073 return this.componentManager;
322    }
323   
324    /**
325    * Create a Syntax object from a Syntax id string.
326    *
327    * @param syntaxId the id of the Syntax to create
328    * @return the Syntax object
329    */
 
330  537 toggle private org.xwiki.rendering.syntax.Syntax parseSyntax(String syntaxId)
331    {
332  537 try {
333  537 SyntaxFactory factory = getComponentManager().getInstance(SyntaxFactory.class);
334  537 return factory.createSyntaxFromIdString(syntaxId);
335    } catch (Exception e) {
336  0 throw new RuntimeException(String.format("Failed to parse Syntax [%s]", syntaxId), e);
337    }
338    }
339    }