| /* |
| * 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. |
| */ |
| |
| package freemarker.test.templatesuite; |
| |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.junit.runner.RunWith; |
| import org.junit.runners.AllTests; |
| import org.w3c.dom.Attr; |
| 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.SAXException; |
| |
| import freemarker.ext.dom.NodeModel; |
| import freemarker.template.Configuration; |
| import freemarker.template.Version; |
| import freemarker.template.utility.StringUtil; |
| import junit.framework.TestSuite; |
| |
| /** |
| * Test suite where the test cases are defined in testcases.xml, and usually process |
| * templates and compare their output with the expected output. |
| * |
| * If you only want to run certain tests, you can specify a regular expression for |
| * the test name in the {@link #TEST_FILTER_PROPERTY_NAME} system property. |
| */ |
| @RunWith(AllTests.class) |
| public class TemplateTestSuite extends TestSuite { |
| |
| private static final String ELEM_TEST_CASE = "testCase"; |
| |
| private static final String ELEM_SETTING = "setting"; |
| |
| private static final String ATTR_NO_OUTPUT = "noOutput"; |
| |
| private static final String ATTR_EXPECTED = "expected"; |
| |
| private static final String ATTR_TEMPLATE = "template"; |
| |
| private static final String END_TEMPLATE_NAME_MARK = "[#endTN]"; |
| |
| public static final String CONFIGURATION_XML_FILE_NAME = "testcases.xml"; |
| |
| /** |
| * When setting this system property, only the tests whose name matches the |
| * given regular expression will be executed. |
| */ |
| public static final String TEST_FILTER_PROPERTY_NAME = "freemareker.templateTestSuite.testFilter"; |
| |
| /** |
| * Comma separated list of "incompatible improvements" versions to run the test cases with. |
| */ |
| public static final String INCOMPATIBLE_IMPROVEMENTS_PROPERTY_NAME |
| = "freemareker.templateTestSuite.incompatibleImprovements"; |
| |
| private final Map<String, String> testSuiteSettings = new LinkedHashMap<>(); |
| |
| private final ArrayList<Version> testSuiteIcis; |
| |
| private final Pattern testCaseNameFilter; |
| |
| public static TestSuite suite() throws Exception { |
| return new TemplateTestSuite(); |
| } |
| |
| public TemplateTestSuite() throws Exception { |
| NodeModel.useJaxenXPathSupport(); |
| |
| String filterStr = System.getProperty(TEST_FILTER_PROPERTY_NAME); |
| testCaseNameFilter = filterStr != null ? Pattern.compile(filterStr) : null; |
| if (testCaseNameFilter != null) { |
| System.out.println("Note: " + TEST_FILTER_PROPERTY_NAME + " is " + StringUtil.jQuote(testCaseNameFilter)); |
| } |
| |
| testSuiteIcis = new ArrayList<>(); |
| String testedIcIsStr = System.getProperty(INCOMPATIBLE_IMPROVEMENTS_PROPERTY_NAME); |
| if (testedIcIsStr != null) { |
| for (String iciStr : testedIcIsStr.split(",")) { |
| iciStr = iciStr.trim(); |
| if (iciStr.length() != 0) { |
| testSuiteIcis.add(new Version(iciStr)); |
| } |
| } |
| } |
| if (testSuiteIcis.isEmpty()) { |
| testSuiteIcis.add(getMinIcIVersion()); |
| testSuiteIcis.add(getMaxIcIVersion()); |
| } |
| |
| java.net.URL url = TemplateTestSuite.class.getResource(CONFIGURATION_XML_FILE_NAME); |
| if (url == null) { |
| throw new IOException("Resource not found: " |
| + TemplateTestSuite.class.getName() + ", " + CONFIGURATION_XML_FILE_NAME); |
| } |
| processConfigXML(url.toURI()); |
| } |
| |
| /** |
| * Read the test case configurations file and build up the test suite. |
| */ |
| public void processConfigXML(URI uri) throws Exception { |
| Element testCasesElem = loadXMLFromURL(uri); |
| |
| NodeList children = testCasesElem.getChildNodes(); |
| for (int childIdx = 0; childIdx < children.getLength(); childIdx++) { |
| Node n = children.item(childIdx); |
| if (n.getNodeType() == Node.ELEMENT_NODE) { |
| final String nodeName = n.getNodeName(); |
| if (nodeName.equals(ELEM_SETTING)) { |
| NamedNodeMap attrs = n.getAttributes(); |
| for (int attrIdx = 0; attrIdx < attrs.getLength(); attrIdx++) { |
| Attr attr = (Attr) attrs.item(attrIdx); |
| testSuiteSettings.put(attr.getName(), attr.getValue()); |
| } |
| } else if (nodeName.equals(ELEM_TEST_CASE)) { |
| for (TemplateTestCase testCase : createTestCasesFromElement((Element) n)) { |
| addTest(testCase); |
| } |
| } |
| } |
| } |
| } |
| |
| private Element loadXMLFromURL(URI uri) throws ParserConfigurationException, SAXException, IOException { |
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); |
| // dbf.setValidating(true); |
| DocumentBuilder db = dbf.newDocumentBuilder(); |
| Document d = db.parse(uri.toString()); |
| return d.getDocumentElement(); |
| } |
| |
| String getTextInElement(Element e) { |
| StringBuilder buf = new StringBuilder(); |
| NodeList children = e.getChildNodes(); |
| for (int i = 0; i < children.getLength(); i++) { |
| Node n = children.item(i); |
| short type = n.getNodeType(); |
| if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { |
| buf.append(n.getNodeValue()); |
| } |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Returns the list of test cases generated from the {@link #ELEM_TEST_CASE} element. |
| * There can be multiple generated test cases because of "incompatible improvements" variations, or none because |
| * of the {@code nameFilter}. |
| */ |
| private List<TemplateTestCase> createTestCasesFromElement(Element testCaseElem) |
| throws Exception { |
| final String caseName = StringUtil.emptyToNull(testCaseElem.getAttribute("name")); |
| if (caseName == null) throw new Exception("Invalid XML: the \"name\" attribute is mandatory."); |
| |
| if (testCaseNameFilter != null |
| && !testCaseNameFilter.matcher(caseName).matches()) { |
| return Collections.emptyList(); |
| } |
| |
| final String templateName; |
| final String expectedFileName; |
| { |
| final String beforeEndTN; |
| final String afterEndTN; |
| { |
| int tBNameSep = caseName.indexOf(END_TEMPLATE_NAME_MARK); |
| beforeEndTN = tBNameSep == -1 ? caseName : caseName.substring(0, tBNameSep); |
| afterEndTN = tBNameSep == -1 |
| ? "" : caseName.substring(tBNameSep + END_TEMPLATE_NAME_MARK.length()); |
| } |
| |
| { |
| String s = StringUtil.emptyToNull(testCaseElem.getAttribute(ATTR_TEMPLATE)); |
| templateName = s != null ? s : beforeEndTN + ".ftl"; |
| } |
| |
| { |
| String s = StringUtil.emptyToNull(testCaseElem.getAttribute(ATTR_EXPECTED)); |
| expectedFileName = s != null ? s : beforeEndTN + afterEndTN + ".txt"; |
| } |
| } |
| |
| final boolean noOutput; |
| { |
| String s = StringUtil.emptyToNull(testCaseElem.getAttribute(ATTR_NO_OUTPUT)); |
| noOutput = s == null ? false : StringUtil.getYesNo(s); |
| } |
| |
| final Map<String, String> testCaseSettings = getCaseFMSettings(testCaseElem); |
| |
| final List<Version> icisToTest; |
| { |
| final String testCaseIcis = testCaseSettings.get(Configuration.INCOMPATIBLE_IMPROVEMENTS) != null |
| ? testCaseSettings.get(Configuration.INCOMPATIBLE_IMPROVEMENTS) |
| : testCaseSettings.get(Configuration.INCOMPATIBLE_ENHANCEMENTS); |
| |
| icisToTest = testCaseIcis != null ? parseVersionList(testCaseIcis) : testSuiteIcis; |
| if (icisToTest.isEmpty()) { |
| throw new Exception("The incompatible_improvement list was empty"); |
| } |
| } |
| |
| List<TemplateTestCase> result = new ArrayList<>(); |
| for (Version iciToTest : icisToTest) { |
| TemplateTestCase testCase = new TemplateTestCase( |
| caseName + "(ici=" + iciToTest + ")", caseName, |
| templateName, expectedFileName, noOutput, iciToTest); |
| for (Map.Entry<String, String> setting : testSuiteSettings.entrySet()) { |
| testCase.setSetting(setting.getKey(), setting.getValue()); |
| } |
| for (Map.Entry<String, String> setting : testCaseSettings.entrySet()) { |
| testCase.setSetting(setting.getKey(), setting.getValue()); |
| } |
| |
| result.add(testCase); |
| } |
| |
| return result; |
| } |
| |
| private List<Version> parseVersionList(String versionsStr) { |
| List<Version> versions = new ArrayList<>(); |
| for (String versionStr : versionsStr.split(",")) { |
| versionStr = versionStr.trim(); |
| if (versionStr.length() != 0) { |
| final Version v; |
| if ("min".equals(versionStr)) { |
| v = getMinIcIVersion(); |
| } else if ("max".equals(versionStr)) { |
| v = getMaxIcIVersion(); |
| } else { |
| v = new Version(versionStr); |
| } |
| if (!versions.contains(v)) { |
| versions.add(v); |
| } |
| } |
| } |
| return versions; |
| } |
| |
| private Version getMaxIcIVersion() { |
| Version v = Configuration.getVersion(); |
| // Remove nightly, RC and such: |
| return new Version(v.getMajor(), v.getMinor(), v.getMicro()); |
| } |
| |
| private Version getMinIcIVersion() { |
| return Configuration.VERSION_2_3_0; |
| } |
| |
| private Map<String, String> getCaseFMSettings(Element e) { |
| final Map<String, String> caseFMSettings; |
| caseFMSettings = new LinkedHashMap<>(); |
| NodeList settingElems = e.getElementsByTagName(ELEM_SETTING); |
| for (int elemIdx = 0; elemIdx < settingElems.getLength(); elemIdx++) { |
| NamedNodeMap attrs = settingElems.item(elemIdx).getAttributes(); |
| for (int attrIdx = 0; attrIdx < attrs.getLength(); attrIdx++) { |
| Attr attr = (Attr) attrs.item(attrIdx); |
| |
| final String settingName = attr.getName(); |
| caseFMSettings.put(settingName, attr.getValue()); |
| } |
| } |
| return caseFMSettings; |
| } |
| |
| public static void main (String[] args) throws Exception { |
| junit.textui.TestRunner.run(new TemplateTestSuite()); |
| } |
| |
| } |