Repository: incubator-freemarker Updated Branches: refs/heads/3 bb9acc9da -> 4b75ea930
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4b75ea93/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/templates/xmlns4.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/templates/xmlns4.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/templates/xmlns4.ftl deleted file mode 100644 index e97bfc0..0000000 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/templates/xmlns4.ftl +++ /dev/null @@ -1,70 +0,0 @@ -<#ftl ns_prefixes = {"x" : "http://x", "y" : "http://y"}> -<#-- - 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. ---> -<#recurse doc > - -<#macro book> - <html> - <head> - <title><#recurse .node["x:title"]></title> - </head> - <body> - <h1><#recurse .node["x:title"]></h1> - <#recurse> - </body> - </html> -</#macro> - -<#macro chapter> - <h2><#recurse .node["y:title"]></h2> - <#recurse> -</#macro> - -<#macro 'x:chapter'> - <h2><#recurse .node["y:title"]></h2> - <#recurse> -</#macro> - -<#macro para> - <p><#recurse> -</#macro> - -<#macro 'x:para'> - <p><#recurse> -</#macro> - -<#macro 'y:para'> - <p><#recurse> -</#macro> - -<#macro "x:title"> - <#-- - We have handled this element imperatively, - so we do nothing here. - --> -</#macro> - -<#macro "y:title"> - <#-- - We have handled this element imperatively, - so we do nothing here. - --> -</#macro> - -<#macro @text>${.node?html}</#macro> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4b75ea93/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/templates/xmlns5.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/templates/xmlns5.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/templates/xmlns5.ftl deleted file mode 100644 index edc3b4a..0000000 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/templates/xmlns5.ftl +++ /dev/null @@ -1,28 +0,0 @@ -<#ftl ns_prefixes = {"D": "http://y.com", "xx" : "http://x.com"}> -<#-- - 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. ---> -<#assign r = doc["N:root"]> -${r["N:t1"][0]?default('-')} = No NS -${r["xx:t2"][0]?default('-')} = x NS -${r["t3"][0]?default('-')} = y NS -${r["xx:t4"][0]?default('-')} = x NS -${r["//t1"][0]?default('-')} = No NS -${r["//t2"][0]?default('-')} = - -${r["//t3"][0]?default('-')} = - -${r["//t4"][0]?default('-')} = - http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4b75ea93/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml b/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml deleted file mode 100644 index 2cfe290..0000000 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml +++ /dev/null @@ -1,211 +0,0 @@ -<?xml version="1.0" ?> -<!-- - 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. ---> - -<!DOCTYPE testCases [ - <!ELEMENT testCases (setting?, testCase*)> - <!ELEMENT testCase (setting?)> - <!ATTLIST testCase - name CDATA #REQUIRED - template CDATA #IMPLIED - expected CDATA #IMPLIED - noOutput CDATA #IMPLIED - > - <!-- The default of `template` is "${name?keep_before('[#endTN]')}.ftl" --> - <!-- The default of `expected` is "${name}.txt" --> - <!-- The default of `noOutput` is false --> - - <!ELEMENT setting EMPTY> - <!ATTLIST setting - auto_import CDATA #IMPLIED - source_encoding CDATA #IMPLIED - locale CDATA #IMPLIED - object_wrapper CDATA #IMPLIED - output_encoding CDATA #IMPLIED - output_dir CDATA #IMPLIED - new_builtin_class_resolver CDATA #IMPLIED - url_escaping_charset CDATA #IMPLIED - incompatible_improvements CDATA #IMPLIED - time_zone CDATA #IMPLIED - api_builtin_enabled CDATA #IMPLIED - > -]> -<!-- -Note that for the incompatible_improvements setting you can specify a list of versions, for example: -<setting incompatible_improvements="min, 3.0.5, max" /> ---> - -<testCases> - <setting source_encoding="UTF-8" output_encoding="UTF-8" /> - - <testCase name="api-builtins" noOutput="true"> - <setting api_builtin_enabled="true" /> - </testCase> - <testCase name="arithmetic" /> - <testCase name="assignments" noOutput="true" /> - <testCase name="boolean" /> - <testCase name="charset-in-header" /> - <testCase name="comment" /> - <testCase name="comparisons" /> - <testCase name="compress" /> - <testCase name="then-builtin" noOutput="true" /> - <testCase name="dateformat-java" /> - <testCase name="dateformat-iso-like" noOutput="true" /> - <testCase name="dateformat-iso-bi" noOutput="true" /> - <testCase name="dateparsing" noOutput="true" /> - <testCase name="default"/> - <testCase name="default-object-wrapper"> - <setting api_builtin_enabled="true" /> - </testCase> - <testCase name="default-xmlns" /> - <testCase name="encoding-builtins" /> - <testCase name="escapes" /> - <testCase name="hashliteral" /> - <testCase name="identifier-non-ascii" /> - <testCase name="identifier-escaping" /> - <testCase name="import"> - <setting auto_import="import_lib.ftl as my"/> - </testCase> - <testCase name="include" /> - <testCase name="include2"> - <setting source_encoding="utf-8" /> - </testCase> - <testCase name="interpret"/> - <testCase name="iterators"/> - <testCase name="lastcharacter"/> - <testCase name="list[#endTN]-simpleTemplateModels" expected="list.txt"> - <setting object_wrapper="org.apache.freemarker.test.templatesuite.models.SimpleMapAndCollectionObjectWrapper(3.0.0)" /> - </testCase> - <testCase name="list[#endTN]-collectionAdapter" expected="list.txt"> - <setting object_wrapper="DefaultObjectWrapper(3.0.0)" /> - </testCase> - <testCase name="list2[#endTN]-simpleTemplateModels" expected="list2.txt"> - <setting object_wrapper="org.apache.freemarker.test.templatesuite.models.SimpleMapAndCollectionObjectWrapper(3.0.0)" /> - </testCase> - <testCase name="list2[#endTN]-collectionAdapter" expected="list2.txt"> - <setting object_wrapper="DefaultObjectWrapper(3.0.0)" /> - </testCase> - <testCase name="list3[#endTN]-simpleTemplateModels" expected="list3.txt"> - <setting object_wrapper="org.apache.freemarker.test.templatesuite.models.SimpleMapAndCollectionObjectWrapper(3.0.0)" /> - </testCase> - <testCase name="list3[#endTN]-collectionAdapter" expected="list3.txt"> - <setting object_wrapper="DefaultObjectWrapper(3.0.0)" /> - </testCase> - <testCase name="list-bis" /> - <testCase name="list-bis[#endTN]-collectionAdapter" expected="list-bis.txt"> - <setting object_wrapper="DefaultObjectWrapper(3.0.0)" /> - </testCase> - <testCase name="listhash" /> - <testCase name="listhashliteral" /> - <testCase name="listliteral" /> - <testCase name="localization" > - <setting locale="en_AU"/> - </testCase> - <testCase name="loopvariable" /> - <testCase name="macros"/> - <testCase name="macros2"/> - <testCase name="macros-return"/> - <testCase name="multimodels"/> - <testCase name="nested" /> - <testCase name="newlines1" /> - <testCase name="newlines2" /> - <testCase name="noparse" /> - <testCase name="number-format" /> - <testCase name="number-literal" > - <setting locale="fr_FR"/> - </testCase> - <testCase name="numerical-cast" /> - <testCase name="output-encoding1"/> - <testCase name="output-encoding2"> - <setting output_encoding="UTF-16"/> - </testCase> - <testCase name="output-encoding3"> - <setting output_encoding="ISO-8859-1" url_escaping_charset="UTF-16" /> - </testCase> - <testCase name="precedence"/> - <testCase name="range" noOutput="true" /> - <testCase name="recover" /> - <testCase name="root" /> - <testCase name="setting" noOutput="true" /> - <testCase name="sequence-builtins[#endTN]-with-SimpleTemplateModel" expected="sequence-builtins.txt"> - <setting object_wrapper="org.apache.freemarker.test.templatesuite.models.SimpleMapAndCollectionObjectWrapper(3.0.0)" /> - </testCase> - <testCase name="sequence-builtins[#endTN]-with-DefaultObjectWrapper" expected="sequence-builtins.txt"> - <setting object_wrapper="default"/> - </testCase> - <testCase name="sequence-builtins[#endTN]-with-DefaultObjectWrapper-collAdapters" expected="sequence-builtins.txt"> - <setting object_wrapper="DefaultObjectWrapper(3.0.0)"/> - </testCase> - <testCase name="simplehash-char-key" noOutput="true" /> - <testCase name="existence-operators" noOutput="true" /> - <testCase name="string-builtins1" /> - <testCase name="string-builtins2" /> - <testCase name="string-builtins3" noOutput="true" /> - <testCase name="string-builtins-regexps" /> - <testCase name="string-builtins-regexps-matches" /> - <testCase name="stringbimethods" /> - <testCase name="stringliteral"/> - <testCase name="if" /> - <testCase name="switch" /> - <testCase name="switch-builtin" noOutput="true" /> - <testCase name="transforms"/> - <testCase name="type-builtins" /> - <testCase name="date-type-builtins" noOutput="true" /> - <testCase name="url" noOutput="true" /> - <testCase name="var-layers"/> - <testCase name="variables"/> - <testCase name="whitespace-trim"/> - <testCase name="wstrip-in-header"/> - <testCase name="xml-fragment" /> - <testCase name="xmlns1" /> - <testCase name="xmlns2" template="xmlns1.ftl" expected="xmlns1.txt" /> - <testCase name="xmlns3" /> - <testCase name="xmlns4" /> - <testCase name="xmlns5" /> - <testCase name="xml-ns_prefix-scope" template="xml-ns_prefix-scope-main.ftl" /> - <testCase name="hashconcat"/> - <testCase name="new-defaultresolver" /> - <testCase name="new-unrestricted" template="new-defaultresolver.ftl" expected="new-defaultresolver.txt"> - <setting new_builtin_class_resolver="unrestricted"/> - </testCase> - <testCase name="new-allowsnothing" template="new-defaultresolver.ftl"> - <setting new_builtin_class_resolver="allows_nothing"/> - </testCase> - <testCase name="new-optin"> - <setting new_builtin_class_resolver=" - allowed_classes: org.apache.freemarker.test.templatesuite.models.NewTestModel, - trusted_templates: subdir/new-optin.ftl, subdir/subsub/*" - /> - </testCase> - <testCase name="specialvars"> - <setting locale="en_US" output_encoding="utf-8" url_escaping_charset="iso-8859-1"/> - </testCase> - <testCase name="number-to-date" /> - <testCase name="varargs" /> - <testCase name="boolean-formatting" /> - <testCase name="number-math-builtins" noOutput="true" /> - <testCase name="string-builtin-coercion" noOutput="true" /> - - <testCase name="overloaded-methods[#endTN]-inc-dow" noOutput="true"> - <setting object_wrapper="org.apache.freemarker.core.model.impl.DefaultObjectWrapperInc"/> - </testCase> - <testCase name="overloaded-methods[#endTN]-desc-dow" noOutput="true"> - <setting object_wrapper="org.apache.freemarker.core.model.impl.DefaultObjectWrapperDesc"/> - </testCase> -</testCases> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4b75ea93/freemarker-core/build.gradle ---------------------------------------------------------------------- diff --git a/freemarker-core/build.gradle b/freemarker-core/build.gradle index 3419439..39af628 100644 --- a/freemarker-core/build.gradle +++ b/freemarker-core/build.gradle @@ -39,14 +39,6 @@ dependencies { // xml-apis is part of Java SE since version 1.4: exclude group: "xml-apis", module: "xml-apis" } - - testRuntime "jaxen:jaxen:1.0-FCS" - testRuntime "saxpath:saxpath:1.0-FCS" - testRuntime("xalan:xalan:2.7.0") { - // xml-apis is part of Java SE since version 1.4: - exclude group: "xml-apis", module: "xml-apis" - } - } compileJavacc { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4b75ea93/freemarker-test-utils/src/main/java/org/apache/freemarker/test/TemplateTestCase.java ---------------------------------------------------------------------- diff --git a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/TemplateTestCase.java b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/TemplateTestCase.java new file mode 100644 index 0000000..a8a0c55 --- /dev/null +++ b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/TemplateTestCase.java @@ -0,0 +1,194 @@ +/* + * 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 org.apache.freemarker.test; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.StringTokenizer; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.ConfigurationException; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.Version; +import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader; +import org.apache.freemarker.core.util._NullArgumentException; +import org.apache.freemarker.core.util._NullWriter; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.test.templateutil.AssertDirective; +import org.apache.freemarker.test.templateutil.AssertEqualsDirective; +import org.apache.freemarker.test.templateutil.AssertFailsDirective; +import org.apache.freemarker.test.templateutil.NoOutputDirective; +import org.junit.Ignore; + +import junit.framework.AssertionFailedError; + +/** + * Instances of this are created and called by {@link TemplateTestSuite}. (It's on "Ignore" so that Eclipse doesn't try + * to run this alone.) + */ +@Ignore +class TemplateTestCase extends FileTestCase { + + // Name of variables exposed to all test FTL-s: + private static final String ICI_INT_VALUE_VAR_NAME = "iciIntValue"; + private static final String TEST_NAME_VAR_NAME = "testName"; + private static final String NO_OUTPUT_VAR_NAME = "noOutput"; + private static final String ASSERT_FAILS_VAR_NAME = "assertFails"; + private static final String ASSERT_EQUALS_VAR_NAME = "assertEquals"; + private static final String ASSERT_VAR_NAME = "assert"; + + private final TemplateTestSuite testSuite; + + private final String simpleTestName; + private final String templateName; + private final String expectedFileName; + private final boolean noOutput; + + private final Configuration.ExtendableBuilder<?> confB; + private final HashMap<String, Object> dataModel = new HashMap<>(); + + TemplateTestCase( + TemplateTestSuite testSuite, + String testName, String simpleTestName, String templateName, String expectedFileName, boolean noOutput, + Version incompatibleImprovements) { + super(testName); + _NullArgumentException.check("testName", testName); + + _NullArgumentException.check("testSuite", testSuite); + this.testSuite = testSuite; + + _NullArgumentException.check("simpleTestName", simpleTestName); + this.simpleTestName = simpleTestName; + + _NullArgumentException.check("templateName", templateName); + this.templateName = templateName; + + _NullArgumentException.check("expectedFileName", expectedFileName); + this.expectedFileName = expectedFileName; + + this.noOutput = noOutput; + + confB = new TestConfigurationBuilder(incompatibleImprovements); + } + + void setSetting(String param, String value) throws IOException { + if ("auto_import".equals(param)) { + StringTokenizer st = new StringTokenizer(value); + if (!st.hasMoreTokens()) fail("Expecting libname"); + String libname = st.nextToken(); + if (!st.hasMoreTokens()) fail("Expecting 'as <alias>' in autoimport"); + String as = st.nextToken(); + if (!as.equals("as")) fail("Expecting 'as <alias>' in autoimport"); + if (!st.hasMoreTokens()) fail("Expecting alias after 'as' in autoimport"); + String alias = st.nextToken(); + confB.addAutoImport(alias, libname); + } else if ("source_encoding".equals(param)) { + confB.setSourceEncoding(Charset.forName(value)); + // INCOMPATIBLE_IMPROVEMENTS is a list here, and was already set in the constructor. + } else if (!Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY.equals(param)) { + try { + confB.setSetting(param, value); + } catch (ConfigurationException e) { + throw new RuntimeException( + "Failed to set setting " + + _StringUtil.jQuote(param) + " to " + + _StringUtil.jQuote(value) + "; see cause exception.", + e); + } + } + } + + /* + * This method just contains all the code to seed the data model + * ported over from the individual classes. This seems ugly and unnecessary. + * We really might as well just expose pretty much + * the same tree to all our tests. (JR) + */ + @Override + @SuppressWarnings("boxing") + protected void setUp() throws Exception { + confB.setTemplateLoader( + new CopyrightCommentRemoverTemplateLoader( + new ClassTemplateLoader(testSuite.getClass(), "templates"))); + + dataModel.put(ASSERT_VAR_NAME, AssertDirective.INSTANCE); + dataModel.put(ASSERT_EQUALS_VAR_NAME, AssertEqualsDirective.INSTANCE); + dataModel.put(ASSERT_FAILS_VAR_NAME, AssertFailsDirective.INSTANCE); + dataModel.put(NO_OUTPUT_VAR_NAME, NoOutputDirective.INSTANCE); + + dataModel.put(TEST_NAME_VAR_NAME, simpleTestName); + dataModel.put(ICI_INT_VALUE_VAR_NAME, confB.getIncompatibleImprovements().intValue()); + + testSuite.setUpTestCase(simpleTestName, dataModel, confB); + } + + @Override + protected void runTest() throws IOException, ConfigurationException { + Template template; + try { + template = confB.build().getTemplate(templateName); + } catch (IOException e) { + throw new AssertionFailedError( + "Could not load template " + _StringUtil.jQuote(templateName) + ":\n" + getStackTrace(e)); + } + + testSuite.validateTemplate(template); + + StringWriter out = noOutput ? null : new StringWriter(); + try { + template.process(dataModel, out != null ? out : _NullWriter.INSTANCE); + } catch (TemplateException e) { + throw new AssertionFailedError("Template " + _StringUtil.jQuote(templateName) + " has stopped with error:\n" + + getStackTrace(e)); + } + + if (out != null) { + assertExpectedFileEqualsString(expectedFileName, out.toString()); + } + } + + private String getStackTrace(Throwable e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } + + @Override + protected String getExpectedContentFileDirectoryResourcePath() throws IOException { + return joinResourcePaths(super.getExpectedContentFileDirectoryResourcePath(), "expected"); + } + + @Override + protected Charset getTestResourceDefaultCharset() { + return confB.getOutputEncoding() != null ? confB.getOutputEncoding() : StandardCharsets.UTF_8; + } + + @Override + protected Class getTestResourcesBaseClass() { + return testSuite.getClass(); + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4b75ea93/freemarker-test-utils/src/main/java/org/apache/freemarker/test/TemplateTestSuite.java ---------------------------------------------------------------------- diff --git a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/TemplateTestSuite.java b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/TemplateTestSuite.java new file mode 100644 index 0000000..c9b4e4c --- /dev/null +++ b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/TemplateTestSuite.java @@ -0,0 +1,332 @@ +/* + * 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 org.apache.freemarker.test; + +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.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.Version; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.dom.NodeModel; +import org.junit.Test; +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 junit.framework.TestSuite; + +/** + * Abstract superclass for JUnit test suites where the test cases are defined in + * {@code <suiteClassPackage>/testcases.xml}, and process templates and compare their output with the expected output. + * It's important to add this static method like <code>public static TestSuite suite() { return new + * SomeTemplateTestSuite(); }</code> to concrete subclasses, or else it won't be detected and run by build tools and + * IDE-s. + * <p> + * 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. + * <p> + * To add a test-case, go to the resource directory that corresponds to the package of the {@link TemplateTestSuite} + * subclass and inside that directory:</p> + * <ol> + * <li>Add a new <tt>testcase</tt> element to <tt>testcases.xml</tt></li> + * <li>Add a template to <tt>templates/</tt> with fits the <tt>testcase</tt> added to the XML (by default it's the test + * case name + ".ftl")</li> + * <li>Add the expected output to <tt>references/</tt> with fits the <tt>testcase</tt> added to the XML (by default + * it's the test name + ".txt")</li> + * <li>If you want to add items to the data-model or change the {@link Configuration}, modify the + * {@link #setUpTestCase(String, Map, Configuration.ExtendableBuilder)}} method in the {@link TemplateTestSuite} + * subclass.</li> + * </ol> + */ +public abstract 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 TemplateTestSuite() { + try { + 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 = getClass().getResource(CONFIGURATION_XML_FILE_NAME); + if (url == null) { + throw new IOException("Resource not found: " + + getClass().getName() + ", " + CONFIGURATION_XML_FILE_NAME); + } + processConfigXML(url.toURI()); + } catch (Exception e) { + throw new IllegalStateException("Failed to initialize test suite", e); + } + } + + /** + * At least with Gradle 3.5 TestSuite-s aren't run even if we explicitly include the class, unless we have a + * test method in them (which won't be run). + */ + @Test + public final void gradleTestPluginWorkaround() { + // Does nothing + } + + protected abstract void setUpTestCase(String simpleTestName, Map<String, Object> dataModel, + Configuration.ExtendableBuilder<?> confB) throws Exception; + + /** + * Read the test case configurations file and build up the test suite. + */ + private 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 && _StringUtil.getYesNo(s); + } + + final Map<String, String> testCaseSettings = getCaseFMSettings(testCaseElem); + + final List<Version> icisToTest; + { + final String testCaseIcis = testCaseSettings.get(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY); + + 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( + this, + 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_3_0_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; + } + + /** + * Override this if you want to check something in the main {@link Template} before running the test. + */ + protected void validateTemplate(Template template) { + // Does nothing by default + } +}
