org.xwiki.rendering.test.cts.CompatibilityTestSuite Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xwiki-rendering-test Show documentation
Show all versions of xwiki-rendering-test Show documentation
XWiki Rendering - Test Framework
/*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runners.Suite;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.rendering.parser.Parser;
import org.xwiki.rendering.renderer.BlockRenderer;
import org.xwiki.test.jmock.XWikiComponentInitializer;
/**
* Run all tests found in resources files located in the classpath, for a given Syntax.
*
* The algorithm is the following:
*
* - Look for {@code cts/[scope]} resources in the classpath where {@code [scope]} represents the value of the
* {@code @Scope} annotation prefixed by {@code cts\\.}. By default if no Scope annotation is defined,
* {@code .*\\.xml} is used, leading to a total regex of {@code cts\\..*\\.xml}. This is the regex that's used
* to look for resources in the classpath. For example the following test file would match:
* {@code cts/simple/bold/bold1.inout.xml}. We call these {@code CTS} resources.
* - For each {@code CTS} resource found look for equivalent test input and output files for the tested Syntax.
* For example if we have {@code cts/simple/bold/bold1.inout.xml} then if the Syntax is {@code xwiki/2.0} look
* for {@code xwiki20/simple/bold/bold1.[in|out|inout].txt} test files. We call them {@code SYN} resources.
*
* - For each {@code SYN IN} resource, parse it with the corresponding Syntax parser and render the generated XDOM
* with the CTS Renderer, and compare the results with the {@code CTS OUT} resource. Note that if no
* {@code SYN IN} resource is found generate a warning in the test logs.
* - For each {@code SYN OUT} resource, parse the {@code CTS IN} resource with the CTS Syntax parser and render the
* generated XDOM with the Syntax Renderer, and compare the results with the {@code SYN OUT} resource.
* Note that if no {@code SYN OUT} resource is found generate a warning in the test logs.
*
*
*
* Usage Example
*
*
* @RunWith(CompatibilityTestSuite.class)
* @Syntax("xwiki/2.0")
* @Scope("simple")
* public class IntegrationTests
* {
* }
*
*
* It's also possible to get access to the underlying Component Manager used, for example in order to register
* Mock implementations of components. For example:
*
*
* @RunWith(CompatibilityTestSuite.class)
* @Syntax("xwiki/2.0")
* @Scope("simple")
* public class IntegrationTests
* {
* @Initialized
* public void initialize(ComponentManager componentManager)
* {
* // Init mocks here for example
* }
* }
*
*
* @version $Id: d7b8432b89dd84bc83464de880868a8c331949f9 $
* @since 4.1M1
*/
public class CompatibilityTestSuite extends Suite
{
/**
* Used to locate and parse Test Data.
*/
private static final TestDataParser PARSER = new TestDataParser();
/**
* The Test instance (The Test instance is the class on which this Compatibility Test Suite is used).
*/
private final Object testInstance;
/**
* Used to find if there are Parser or Renderers for a given Syntax.
*/
private final ComponentManager componentManager;
/**
* We have one Test Runner per Syntax Test to execute, so that each test is reported individually and also to
* provide test isolation.
*/
private final List runners = new ArrayList<>();
/**
* Only called reflectively. Do not use programmatically.
*
* @param klass the test instance class on which this Test Suite is applied
* @throws Exception if we fail to locate or load test data, if the {@link RenderingTest} isn't a valid JUnit Test
* class or if we cannot locate the Component Manager
*/
public CompatibilityTestSuite(Class> klass) throws Exception
{
super(RenderingTest.class, Collections.emptyList());
try {
this.testInstance = klass.newInstance();
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to construct instance of [%s]", klass.getName()), e);
}
// If a Scope Annotation is present then use it to define the scope
Scope scopeAnnotation = klass.getAnnotation(Scope.class);
String packageFilter = "";
String pattern = Scope.DEFAULT_PATTERN;
if (scopeAnnotation != null) {
packageFilter = scopeAnnotation.value();
pattern = scopeAnnotation.pattern();
}
// Get the specified Syntax from the Syntax annotation
Syntax syntaxAnnotation = klass.getAnnotation(Syntax.class);
if (syntaxAnnotation == null) {
throw new RuntimeException("You must specify a Syntax using the @Syntax annotation");
}
String syntaxId = syntaxAnnotation.value();
String metadataSyntaxId = syntaxAnnotation.metadata();
if (StringUtils.isEmpty(metadataSyntaxId)) {
metadataSyntaxId = syntaxId;
}
// Initialize the Component Manager
this.componentManager = new XWikiComponentInitializer().getComponentManager();
// Note: We use the Reflections framework to find all ClassLoader URLs that contain the "cts" package.
List testDatas = PARSER.parseTestData(syntaxId, "cts", packageFilter, pattern);
for (TestData testData : testDatas) {
// The following cases can happen:
// - There's no syntax test for the CTS test and there's no Parser/Renderer for that syntax: we don't add
// the test at all
// - The test is configured to be not applicable: we don't add the test at all
// - The test is configured as not working: we ignore it in JUnit with a cause message in the test
// description
// - There's no syntax test for the CTS test but there's a Parser/Renderer for that syntax: we ignore it in
// JUnit with a cause message in the test description
if (isApplicable(testData)) {
if (testData.syntaxData != null && !testData.isFailingTest()) {
this.runners.add(new RenderingTestClassRunner(
this.testInstance, getTestClass().getJavaClass(), testData, metadataSyntaxId));
} else {
this.runners.add(new IgnoredRenderingTestClassRunner(getTestClass().getJavaClass(), testData));
}
}
}
}
@Override
protected List getChildren()
{
return this.runners;
}
/**
* {@inheritDoc}
*
*
* We override this method so that the JUnit results are not displayed in a test hierarchy with a single test
* result for each node (as it would be otherwise since RenderingTest has a single test method).
*
*/
@Override
public Description getDescription()
{
return Description.createSuiteDescription(getTestClass().getJavaClass());
}
/**
* Verify if a test is applicable (ie it should be executed, even as ignored). A test is applicable if:
*
* - it's not marked as not applicable
* - it has a Syntax test
* - it doesn't have a Syntax test but there's a Parser or Renderer for the Syntax
*
*
* @param testData the test data used to decide if the test is applicable or not
* @return if the test should be executed or false otherwise
*/
private boolean isApplicable(TestData testData)
{
boolean isApplicable;
if (testData.isNotApplicable()) {
isApplicable = false;
} else {
if (hasParserOrRenderer(testData)) {
isApplicable = true;
} else {
isApplicable = false;
}
}
return isApplicable;
}
/**
* @param testData the test data used to decide if the test has a Parser or Renderer for it
* @return true if there's a Parser or Renderer for the passed test data, false otherwise
*/
private boolean hasParserOrRenderer(TestData testData)
{
return (testData.isSyntaxInputTest && hasParserForSyntax(testData.syntaxId))
|| (!testData.isSyntaxInputTest && hasRendererForSyntax(testData.syntaxId));
}
/**
* @param syntaxId the syntax for which to verify if there's a Parser
* @return true if a Parser exists for the passed syntax, false otherwise
*/
private boolean hasParserForSyntax(String syntaxId)
{
return this.componentManager.hasComponent(Parser.class, syntaxId);
}
/**
* @param syntaxId the syntax for which to verify if there's a Renderer
* @return true if a Renderer exists for the passed syntax, false otherwise
*/
private boolean hasRendererForSyntax(String syntaxId)
{
return this.componentManager.hasComponent(BlockRenderer.class, syntaxId);
}
}