You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/05/15 21:23:50 UTC

[34/51] [abbrv] [partial] incubator-freemarker git commit: Restructured project so that freemarker-test-utils depends on freemarker-core (and hence can provide common classes for testing templates, and can use utility classes defined in the core). As a c

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/Product.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/Product.java b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/Product.java
new file mode 100644
index 0000000..fcb34e8
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/Product.java
@@ -0,0 +1,49 @@
+/*
+ * 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.manualtest;
+
+/**
+ * Product bean; note that it must be a public class!
+ */
+public class Product {
+
+    private String url;
+    private String name;
+
+    // As per the JavaBeans spec., this defines the "url" bean property
+    // It must be public!
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    // As per the JavaBean spec., this defines the "name" bean property
+    // It must be public!
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
new file mode 100644
index 0000000..8c515bd
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/TemplateConfigurationExamples.java
@@ -0,0 +1,191 @@
+/*
+ * 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.manualtest;
+
+import static org.junit.Assert.*;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.TemplateConfiguration;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
+import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileExtensionMatcher;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.MergingTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.OrMatcher;
+import org.apache.freemarker.core.templateresolver.PathGlobMatcher;
+import org.apache.freemarker.core.util._DateUtil;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+public class TemplateConfigurationExamples extends TemplateTest {
+
+    @Test
+    public void example1JavaCfg() throws Exception {
+        example1(true);
+    }
+
+    @Test
+    public void example1PropertiesCfg() throws Exception {
+        example1(false);
+    }
+
+    private void example1(boolean javaCfg) throws Exception {
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder(this.getClass());
+        if (javaCfg) {
+            cfgB.setTemplateConfigurations(new ConditionalTemplateConfigurationFactory(
+                    new FileExtensionMatcher("xml"),
+                    new TemplateConfiguration.Builder()
+                            .sourceEncoding(StandardCharsets.UTF_8)
+                            .outputFormat(XMLOutputFormat.INSTANCE)
+                            .build()));
+
+        } else {
+            cfgB.setTemplateConfigurations(null);
+            cfgB.setSettings(loadPropertiesFile("TemplateConfigurationExamples1.properties"));
+        }
+        setConfiguration(cfgB.build());
+
+        addTemplate("t.xml", "");
+
+        Template t = getConfiguration().getTemplate("t.xml");
+        assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
+        assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat());
+    }
+
+    @Test
+    public void example2JavaCfg() throws Exception {
+        example2(true);
+    }
+
+    @Test
+    public void example2PropertiesCfg() throws Exception {
+        example2(false);
+    }
+
+    private void example2(boolean javaCfg) throws Exception {
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder(this.getClass());
+        if (javaCfg) {
+            cfgB.setTemplateConfigurations(
+                    new ConditionalTemplateConfigurationFactory(
+                            new PathGlobMatcher("mail/**"),
+                            new FirstMatchTemplateConfigurationFactory(
+                                    new ConditionalTemplateConfigurationFactory(
+                                            new FileNameGlobMatcher("*.subject.*"),
+                                            new TemplateConfiguration.Builder()
+                                                    .outputFormat(PlainTextOutputFormat.INSTANCE)
+                                                    .build()),
+                                    new ConditionalTemplateConfigurationFactory(
+                                            new FileNameGlobMatcher("*.body.*"),
+                                            new TemplateConfiguration.Builder()
+                                                    .outputFormat(HTMLOutputFormat.INSTANCE)
+                                                    .build())
+                            )
+                            .noMatchErrorDetails(
+                                    "Mail template names must contain \".subject.\" or \".body.\"!")));
+        } else{
+            cfgB.setSettings(loadPropertiesFile("TemplateConfigurationExamples2.properties"));
+        }
+        setConfiguration(cfgB.build());
+
+        addTemplate("t.subject.ftl", "");
+        addTemplate("mail/t.subject.ftl", "");
+        addTemplate("mail/t.body.ftl", "");
+
+        Configuration cfg = getConfiguration();
+        assertEquals(UndefinedOutputFormat.INSTANCE, cfg.getTemplate("t.subject.ftl").getOutputFormat());
+        assertEquals(PlainTextOutputFormat.INSTANCE, cfg.getTemplate("mail/t.subject.ftl").getOutputFormat());
+        assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("mail/t.body.ftl").getOutputFormat());
+    }
+
+    @Test
+    public void example3JavaCfg() throws Exception {
+        example3(true);
+    }
+
+    @Test
+    public void example3PropertiesCfg() throws Exception {
+        example3(false);
+    }
+
+    private void example3(boolean javaCfg) throws Exception {
+        TestConfigurationBuilder cfgB = new TestConfigurationBuilder(this.getClass())
+                .sourceEncoding(StandardCharsets.ISO_8859_1);
+        if (javaCfg) {
+            cfgB.setTemplateConfigurations(
+                    new MergingTemplateConfigurationFactory(
+                            new ConditionalTemplateConfigurationFactory(
+                                    new FileNameGlobMatcher("*.stats.*"),
+                                    new TemplateConfiguration.Builder()
+                                            .dateTimeFormat("iso")
+                                            .dateFormat("iso")
+                                            .timeFormat("iso")
+                                            .timeZone(_DateUtil.UTC)
+                                            .build()),
+                            new ConditionalTemplateConfigurationFactory(
+                                    new PathGlobMatcher("mail/**"),
+                                    new TemplateConfiguration.Builder()
+                                            .sourceEncoding(StandardCharsets.UTF_8)
+                                            .build()),
+                            new FirstMatchTemplateConfigurationFactory(
+                                    new ConditionalTemplateConfigurationFactory(
+                                            new FileExtensionMatcher("xml"),
+                                            new TemplateConfiguration.Builder()
+                                                    .outputFormat(XMLOutputFormat.INSTANCE)
+                                                    .build()),
+                                    new ConditionalTemplateConfigurationFactory(
+                                            new OrMatcher(
+                                                    new FileExtensionMatcher("html"),
+                                                    new FileExtensionMatcher("htm")),
+                                            new TemplateConfiguration.Builder()
+                                                    .outputFormat(HTMLOutputFormat.INSTANCE)
+                                                    .build())
+                            ).allowNoMatch(true)));
+        } else {
+            cfgB.setSettings(loadPropertiesFile("TemplateConfigurationExamples3.properties"));
+        }
+        setConfiguration(cfgB.build());
+
+        addTemplate("t.stats.html", "${ts?datetime} ${ts?date} ${ts?time}");
+        addTemplate("t.html", "");
+        addTemplate("t.htm", "");
+        addTemplate("t.xml", "");
+        addTemplate("mail/t.html", "");
+
+        addToDataModel("ts", new Date(1440431606011L));
+
+        Configuration cfg = getConfiguration();
+        assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.html").getOutputFormat());
+        assertEquals(StandardCharsets.ISO_8859_1, cfg.getTemplate("t.html").getActualSourceEncoding());
+        assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.htm").getOutputFormat());
+        assertEquals(XMLOutputFormat.INSTANCE, cfg.getTemplate("t.xml").getOutputFormat());
+        assertEquals(HTMLOutputFormat.INSTANCE, cfg.getTemplate("t.stats.html").getOutputFormat());
+        assertOutputForNamed("t.stats.html", "2015-08-24T15:53:26.011Z 2015-08-24 15:53:26.011Z");
+        assertEquals(StandardCharsets.UTF_8, cfg.getTemplate("mail/t.html").getActualSourceEncoding());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java
new file mode 100644
index 0000000..89f20fa
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberFormatFactory.java
@@ -0,0 +1,80 @@
+/*
+ * 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.manualtest;
+
+import java.util.Locale;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+
+/**
+ * A number format that takes any other number format as parameter (specified as a string, as
+ * usual in FreeMarker), then if the model is a {@link UnitAwareTemplateNumberModel}, it  shows
+ * the unit after the number formatted with the other format, otherwise it just shows the formatted
+ * number without unit.
+ */
+public class UnitAwareTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
+
+    public static final UnitAwareTemplateNumberFormatFactory INSTANCE
+            = new UnitAwareTemplateNumberFormatFactory();
+
+    private UnitAwareTemplateNumberFormatFactory() {
+        // Defined to decrease visibility
+    }
+
+    @Override
+    public TemplateNumberFormat get(String params, Locale locale, Environment env)
+            throws TemplateValueFormatException {
+        return new UnitAwareNumberFormat(env.getTemplateNumberFormat(params, locale));
+    }
+
+    private static class UnitAwareNumberFormat extends TemplateNumberFormat {
+
+        private final TemplateNumberFormat innerFormat;
+
+        private UnitAwareNumberFormat(TemplateNumberFormat innerFormat) {
+            this.innerFormat = innerFormat;
+        }
+
+        @Override
+        public String formatToPlainText(TemplateNumberModel numberModel)
+                throws TemplateModelException, TemplateValueFormatException {
+            String innerResult = innerFormat.formatToPlainText(numberModel);
+            return numberModel instanceof UnitAwareTemplateNumberModel
+                    ? innerResult + " " + ((UnitAwareTemplateNumberModel) numberModel).getUnit()
+                    : innerResult;
+        }
+
+        @Override
+        public boolean isLocaleBound() {
+            return innerFormat.isLocaleBound();
+        }
+
+        @Override
+        public String getDescription() {
+            return "unit-aware " + innerFormat.getDescription();
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberModel.java b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberModel.java
new file mode 100644
index 0000000..1e54254
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/UnitAwareTemplateNumberModel.java
@@ -0,0 +1,43 @@
+/*
+ * 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.manualtest;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+public class UnitAwareTemplateNumberModel implements TemplateNumberModel {
+
+    private final Number value;
+    private final String unit;
+    
+    public UnitAwareTemplateNumberModel(Number value, String unit) {
+        this.value = value;
+        this.unit = unit;
+    }
+
+    @Override
+    public Number getAsNumber() throws TemplateModelException {
+        return value;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
new file mode 100644
index 0000000..cf3c0f7
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
@@ -0,0 +1,515 @@
+/*
+ * 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.templatesuite;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.freemarker.core.ASTPrinter;
+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.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateNodeModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.DefaultNonListCollectionAdapter;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.ResourceBundleModel;
+import org.apache.freemarker.core.model.impl.SimpleCollection;
+import org.apache.freemarker.core.model.impl.SimpleDate;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+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.dom.NodeModel;
+import org.apache.freemarker.test.CopyrightCommentRemoverTemplateLoader;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.apache.freemarker.test.templatesuite.models.BooleanAndStringTemplateModel;
+import org.apache.freemarker.test.templatesuite.models.BooleanHash1;
+import org.apache.freemarker.test.templatesuite.models.BooleanHash2;
+import org.apache.freemarker.test.templatesuite.models.BooleanList1;
+import org.apache.freemarker.test.templatesuite.models.BooleanList2;
+import org.apache.freemarker.test.templatesuite.models.BooleanVsStringMethods;
+import org.apache.freemarker.test.templatesuite.models.JavaObjectInfo;
+import org.apache.freemarker.test.templatesuite.models.Listables;
+import org.apache.freemarker.test.templatesuite.models.MultiModel1;
+import org.apache.freemarker.test.templatesuite.models.OverloadedMethods2;
+import org.apache.freemarker.test.templatesuite.models.VarArgTestModel;
+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.FileTestCase;
+import org.apache.freemarker.test.templateutil.NoOutputDirective;
+import org.apache.freemarker.test.XMLLoader;
+import org.junit.Ignore;
+import org.xml.sax.InputSource;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+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
+public 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 JAVA_OBJECT_INFO_VAR_NAME = "javaObjectInfo";
+    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 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<>();
+    
+    public TemplateTestCase(String testName, String simpleTestName, String templateName, String expectedFileName, boolean noOutput,
+            Version incompatibleImprovements) {
+        super(testName);
+        _NullArgumentException.check("testName", testName);
+        
+        _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);
+    }
+    
+    public 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")
+    public void setUp() throws Exception {
+        confB.setTemplateLoader(
+                new CopyrightCommentRemoverTemplateLoader(
+                        new ClassTemplateLoader(TemplateTestCase.class, "templates")));
+        
+        DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        
+        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(JAVA_OBJECT_INFO_VAR_NAME, JavaObjectInfo.INSTANCE);
+        dataModel.put(TEST_NAME_VAR_NAME, simpleTestName);
+        dataModel.put(ICI_INT_VALUE_VAR_NAME, confB.getIncompatibleImprovements().intValue());
+        
+        dataModel.put("message", "Hello, world!");
+
+        if (simpleTestName.startsWith("api-builtin")) {
+            dataModel.put("map", ImmutableMap.of(1, "a", 2, "b", 3, "c"));
+            dataModel.put("list", ImmutableList.of(1, 2, 3));
+            dataModel.put("set", ImmutableSet.of("a", "b", "c"));
+            dataModel.put("s", "test");
+        } else if (simpleTestName.equals("default-object-wrapper")) {
+            dataModel.put("array", new String[] { "array-0", "array-1"});
+            dataModel.put("list", Arrays.asList("list-0", "list-1", "list-2"));
+            Map<Object, Object> tmap = new HashMap<>();
+            tmap.put("key", "value");
+            Object objKey = new Object();
+            tmap.put(objKey, "objValue");
+            dataModel.put("map", tmap);
+            dataModel.put("objKey", objKey);
+            dataModel.put("obj", new org.apache.freemarker.test.templatesuite.models.BeanTestClass());
+            dataModel.put("resourceBundle",
+                    new ResourceBundleModel(ResourceBundle.getBundle(
+                            "org.apache.freemarker.test.templatesuite.models.BeansTestResources"), dow));
+            dataModel.put("date", new GregorianCalendar(1974, 10, 14).getTime());
+            dataModel.put("statics", dow.getStaticModels());
+            dataModel.put("enums", dow.getEnumModels());
+        } else if (simpleTestName.equals("boolean")) {
+            dataModel.put( "boolean1", TemplateBooleanModel.FALSE);
+            dataModel.put( "boolean2", TemplateBooleanModel.TRUE);
+            dataModel.put( "boolean3", TemplateBooleanModel.TRUE);
+            dataModel.put( "boolean4", TemplateBooleanModel.TRUE);
+            dataModel.put( "boolean5", TemplateBooleanModel.FALSE);
+            
+            dataModel.put( "list1", new BooleanList1(dow) );
+            dataModel.put( "list2", new BooleanList2(dow) );
+    
+            dataModel.put( "hash1", new BooleanHash1() );
+            dataModel.put( "hash2", new BooleanHash2() );
+        } else if (simpleTestName.startsWith("dateformat")) {
+            GregorianCalendar cal = new GregorianCalendar(2002, 10, 15, 14, 54, 13);
+            cal.setTimeZone(TimeZone.getTimeZone("GMT"));
+            dataModel.put("date", new SimpleDate(cal.getTime(), TemplateDateModel.DATETIME));
+            dataModel.put("unknownDate", new SimpleDate(cal.getTime(), TemplateDateModel.UNKNOWN));
+            dataModel.put("javaGMT02", TimeZone.getTimeZone("GMT+02"));
+            dataModel.put("javaUTC", TimeZone.getTimeZone("UTC"));
+            dataModel.put("adaptedToStringScalar", new Object() {
+                @Override
+                public String toString() {
+                    return "GMT+02";
+                }
+            });
+            dataModel.put("sqlDate", new java.sql.Date(1273955885023L));
+            dataModel.put("sqlTime", new java.sql.Time(74285023L));
+        } else if (
+                templateName.equals("list.ftl") || templateName.equals("list2.ftl") || templateName.equals("list3.ftl")
+                || simpleTestName.equals("listhash")) {
+            dataModel.put("listables", new Listables());
+        } else if (simpleTestName.startsWith("number-format")) {
+            dataModel.put("int", new SimpleNumber(Integer.valueOf(1)));
+            dataModel.put("double", new SimpleNumber(Double.valueOf(1.0)));
+            dataModel.put("double2", new SimpleNumber(Double.valueOf(1 + 1e-15)));
+            dataModel.put("double3", new SimpleNumber(Double.valueOf(1e-16)));
+            dataModel.put("double4", new SimpleNumber(Double.valueOf(-1e-16)));
+            dataModel.put("bigDecimal", new SimpleNumber(java.math.BigDecimal.valueOf(1)));
+            dataModel.put("bigDecimal2", new SimpleNumber(java.math.BigDecimal.valueOf(1, 16)));
+        } else if (simpleTestName.equals("simplehash-char-key")) {
+            HashMap<String, String> mStringC = new HashMap<>();
+            mStringC.put("c", "string");
+            dataModel.put("mStringC", mStringC);
+            
+            HashMap<String, String> mStringCNull = new HashMap<>();
+            mStringCNull.put("c", null);
+            dataModel.put("mStringCNull", mStringCNull);
+            
+            HashMap<Character, String> mCharC = new HashMap<>();
+            mCharC.put(Character.valueOf('c'), "char");
+            dataModel.put("mCharC", mCharC);
+            
+            HashMap<String, String> mCharCNull = new HashMap<>();
+            mCharCNull.put("c", null);
+            dataModel.put("mCharCNull", mCharCNull);
+            
+            HashMap<Object, String> mMixed = new HashMap<>();
+            mMixed.put(Character.valueOf('c'), "char");
+            mMixed.put("s", "string");
+            mMixed.put("s2", "string2");
+            mMixed.put("s2n", null);
+            dataModel.put("mMixed", mMixed);
+        } else if (simpleTestName.equals("default-xmlns")) {
+            InputSource is = new InputSource(getClass().getResourceAsStream("models/defaultxmlns1.xml"));
+            NodeModel nm = XMLLoader.toModel(is);
+            dataModel.put("doc", nm);
+        } else if (simpleTestName.equals("multimodels")) {
+            dataModel.put("test", "selftest");
+            dataModel.put("self", "self");
+            dataModel.put("zero", Integer.valueOf(0));
+            dataModel.put("data", new MultiModel1());
+        } else if (simpleTestName.equals("stringbimethods")) {
+            dataModel.put("multi", new TestBoolean());
+        } else if (simpleTestName.startsWith("type-builtins")) {
+            dataModel.put("testmethod", new TestMethod());
+            dataModel.put("testnode", new TestNode());
+            dataModel.put("testcollection", new SimpleCollection(new ArrayList<>(), dow));
+            dataModel.put("testcollectionEx", DefaultNonListCollectionAdapter.adapt(new HashSet<>(), dow));
+            dataModel.put("bean", new TestBean());
+        } else if (simpleTestName.equals("date-type-builtins")) {
+            GregorianCalendar cal = new GregorianCalendar(2003, 4 - 1, 5, 6, 7, 8);
+            cal.setTimeZone(TimeZone.getTimeZone("UTC"));
+            Date d = cal.getTime();
+            dataModel.put("unknown", d);
+            dataModel.put("timeOnly", new java.sql.Time(d.getTime()));
+            dataModel.put("dateOnly", new java.sql.Date(d.getTime()));
+            dataModel.put("dateTime", new java.sql.Timestamp(d.getTime()));
+        } else if (simpleTestName.equals("var-layers")) {
+            dataModel.put("x", Integer.valueOf(4));
+            dataModel.put("z", Integer.valueOf(4));
+            confB.setSharedVariable("y", Integer.valueOf(7));
+        } else if (simpleTestName.equals("xml-fragment")) {
+            DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+            f.setNamespaceAware(true);
+            DocumentBuilder db = f.newDocumentBuilder();
+            org.w3c.dom.Document doc = db.parse(new InputSource(getClass().getResourceAsStream("models/xmlfragment.xml")));
+            NodeModel.simplify(doc);
+            dataModel.put("node", NodeModel.wrap(doc.getDocumentElement().getFirstChild().getFirstChild()));
+        } else if (simpleTestName.equals("xmlns1")) {
+            InputSource is = new InputSource(getClass().getResourceAsStream("models/xmlns.xml"));
+            NodeModel nm = XMLLoader.toModel(is);
+            dataModel.put("doc", nm);
+        } else if (simpleTestName.equals("xmlns2")) {
+            InputSource is = new InputSource(getClass().getResourceAsStream("models/xmlns2.xml"));
+            NodeModel nm = XMLLoader.toModel(is);
+            dataModel.put("doc", nm);
+        } else if (simpleTestName.equals("xmlns3") || simpleTestName.equals("xmlns4")) {
+            InputSource is = new InputSource(getClass().getResourceAsStream("models/xmlns3.xml"));
+            NodeModel nm = XMLLoader.toModel(is);
+            dataModel.put("doc", nm);
+        } else if (simpleTestName.equals("xmlns5")) {
+            InputSource is = new InputSource(getClass().getResourceAsStream("models/defaultxmlns1.xml"));
+            NodeModel nm = XMLLoader.toModel(is);
+            dataModel.put("doc", nm);
+        } else if (simpleTestName.equals("xml-ns_prefix-scope")) {
+            InputSource is = new InputSource(getClass().getResourceAsStream("models/xml-ns_prefix-scope.xml"));
+            NodeModel nm = XMLLoader.toModel(is);
+            dataModel.put("doc", nm);
+        } else if (simpleTestName.startsWith("sequence-builtins")) {
+            Set<String> abcSet = new TreeSet<>();
+            abcSet.add("a");
+            abcSet.add("b");
+            abcSet.add("c");
+            dataModel.put("abcSet", abcSet);
+            dataModel.put("abcSetNonSeq", DefaultNonListCollectionAdapter.adapt(abcSet, dow));
+            
+            List<String> listWithNull = new ArrayList<>();
+            listWithNull.add("a");
+            listWithNull.add(null);
+            listWithNull.add("c");
+            dataModel.put("listWithNull", listWithNull);
+            
+            List<String> listWithNullsOnly = new ArrayList<>();
+            listWithNull.add(null);
+            listWithNull.add(null);
+            listWithNull.add(null);
+            dataModel.put("listWithNullsOnly", listWithNullsOnly);
+            
+            dataModel.put("abcCollection", new SimpleCollection(abcSet, dow));
+            
+            Set<String> set = new HashSet<>();
+            set.add("a");
+            set.add("b");
+            set.add("c");
+            dataModel.put("set", set);
+        } else if (simpleTestName.equals("number-to-date")) {
+          dataModel.put("bigInteger", new BigInteger("1305575275540"));
+          dataModel.put("bigDecimal", new BigDecimal("1305575275539.5"));
+        } else if (simpleTestName.equals("varargs")) {
+          dataModel.put("m", new VarArgTestModel());
+        } else if (simpleTestName.startsWith("boolean-formatting")) {
+          dataModel.put("booleanAndString", new BooleanAndStringTemplateModel());
+          dataModel.put("booleanVsStringMethods", new BooleanVsStringMethods());
+        } else if (simpleTestName.startsWith("number-math-builtins")) {
+            dataModel.put("fNan", Float.valueOf(Float.NaN));
+            dataModel.put("dNan", Double.valueOf(Double.NaN));
+            dataModel.put("fNinf", Float.valueOf(Float.NEGATIVE_INFINITY));
+            dataModel.put("dPinf", Double.valueOf(Double.POSITIVE_INFINITY));
+            
+            dataModel.put("fn", Float.valueOf(-0.05f));
+            dataModel.put("dn", Double.valueOf(-0.05));
+            dataModel.put("ineg", Integer.valueOf(-5));
+            dataModel.put("ln", Long.valueOf(-5));
+            dataModel.put("sn", Short.valueOf((short) -5));
+            dataModel.put("bn", Byte.valueOf((byte) -5));
+            dataModel.put("bin", BigInteger.valueOf(5));
+            dataModel.put("bdn", BigDecimal.valueOf(-0.05));
+            
+            dataModel.put("fp", Float.valueOf(0.05f));
+            dataModel.put("dp", Double.valueOf(0.05));
+            dataModel.put("ip", Integer.valueOf(5));
+            dataModel.put("lp", Long.valueOf(5));
+            dataModel.put("sp", Short.valueOf((short) 5));
+            dataModel.put("bp", Byte.valueOf((byte) 5));
+            dataModel.put("bip", BigInteger.valueOf(5));
+            dataModel.put("bdp", BigDecimal.valueOf(0.05));
+        } else if (simpleTestName.startsWith("overloaded-methods")) {
+            dataModel.put("obj", new OverloadedMethods2());
+        }
+    }
+    
+    @Override
+    public 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));
+        }
+        ASTPrinter.validateAST(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;
+    }
+
+    static class TestBoolean implements TemplateBooleanModel, TemplateScalarModel {
+        @Override
+        public boolean getAsBoolean() {
+            return true;
+        }
+        
+        @Override
+        public String getAsString() {
+            return "de";
+        }
+    }
+
+    static class TestMethod implements TemplateMethodModel {
+        @Override
+        public Object exec(List arguments) {
+            return "x";
+        }
+    }
+
+    static class TestNode implements TemplateNodeModel {
+
+        @Override
+        public String getNodeName() {
+            return "name";
+        }
+
+        @Override
+        public TemplateNodeModel getParentNode() {
+            return null;
+        }
+
+        @Override
+        public String getNodeType() {
+            return "element";
+        }
+
+        @Override
+        public TemplateSequenceModel getChildNodes() {
+            return null;
+        }
+
+        @Override
+        public String getNodeNamespace() {
+            return null;
+        }
+    }
+
+   public Object getTestMapBean() {
+        Map<String, Object> testBean = new TestMapBean();
+        testBean.put("name", "Chris");
+        testBean.put("location", "San Francisco");
+        testBean.put("age", Integer.valueOf(27));
+        return testBean;
+    }
+
+    public static class TestMapBean extends HashMap<String, Object> {
+        public String getName() {
+            return "Christopher";
+        }
+        public int getLuckyNumber() {
+            return 7;
+        }
+    }
+
+    public static class TestBean {
+
+        public int m(int n) {
+            return n * 10;
+        }
+
+        public int mOverloaded(int n) {
+            return n * 10;
+        }
+
+        public String mOverloaded(String s) {
+            return s.toUpperCase();
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java
new file mode 100644
index 0000000..0edae99
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestSuite.java
@@ -0,0 +1,298 @@
+/*
+ * 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.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.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Version;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.dom.NodeModel;
+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;
+
+/**
+ * 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.
+ */
+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 && _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(
+                    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;
+    }
+    
+    public static void main (String[] args) throws Exception {
+        junit.textui.TestRunner.run(new TemplateTestSuite());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/AllTemplateModels.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/AllTemplateModels.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/AllTemplateModels.java
new file mode 100644
index 0000000..974b3f9
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/AllTemplateModels.java
@@ -0,0 +1,128 @@
+/*
+ * 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.templatesuite.models;
+
+import java.util.Date;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+
+/**
+ * Implements all template models that are interesting when calling overloaded Java methods.
+ */
+public class AllTemplateModels implements
+        TemplateScalarModel, TemplateNumberModel, TemplateDateModel, TemplateBooleanModel,
+        TemplateHashModelEx, TemplateSequenceModel, TemplateCollectionModel {
+
+    public static final AllTemplateModels INSTANCE = new AllTemplateModels();
+    
+    private final TemplateModelIterator EMPTY_ITERATOR = new TemplateModelIterator() {
+
+        @Override
+        public TemplateModel next() throws TemplateModelException {
+            return null;
+        }
+
+        @Override
+        public boolean hasNext() throws TemplateModelException {
+            return false;
+        }
+        
+    };
+    
+    private final TemplateCollectionModel EMPTY_COLLECTION = new TemplateCollectionModel() {
+
+        @Override
+        public TemplateModelIterator iterator() throws TemplateModelException {
+            return EMPTY_ITERATOR;
+        }
+    };
+    
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        return new SimpleScalar("value for key " + key);
+    }
+
+    @Override
+    public boolean isEmpty() throws TemplateModelException {
+        return true;
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateModelException {
+        return EMPTY_ITERATOR;
+    }
+
+    @Override
+    public TemplateModel get(int index) throws TemplateModelException {
+        return null;
+    }
+
+    @Override
+    public int size() throws TemplateModelException {
+        return 0;
+    }
+
+    @Override
+    public TemplateCollectionModel keys() throws TemplateModelException {
+        return EMPTY_COLLECTION;
+    }
+
+    @Override
+    public TemplateCollectionModel values() throws TemplateModelException {
+        return EMPTY_COLLECTION;
+    }
+
+    @Override
+    public boolean getAsBoolean() throws TemplateModelException {
+        return true;
+    }
+
+    @Override
+    public Date getAsDate() throws TemplateModelException {
+        return new Date(0);
+    }
+
+    @Override
+    public int getDateType() {
+        return TemplateDateModel.DATETIME;
+    }
+
+    @Override
+    @SuppressWarnings("boxing")
+    public Number getAsNumber() throws TemplateModelException {
+        return 1;
+    }
+
+    @Override
+    public String getAsString() throws TemplateModelException {
+        return "s";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestClass.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestClass.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestClass.java
new file mode 100644
index 0000000..7e7fa82
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestClass.java
@@ -0,0 +1,93 @@
+/*
+ * 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.templatesuite.models;
+
+/**
+ */
+public class BeanTestClass extends BeanTestSuperclass implements BeanTestInterface<Integer> {
+    public static final String STATIC_FINAL_FIELD = "static-final-field";
+    public static String STATIC_FIELD = "static-field";
+    
+	public String getFoo() {
+	    return "foo-value";
+	}
+	
+	public String getBar(int index) {
+	    return "bar-value-" + index;
+	}
+
+	public String[] getBar() {
+		return new String[] { "bar-value-0", "bar-value-1", "bar-value-2" };
+	}
+
+	public String overloaded(int i) {
+	    return "overloaded-int-" + i;
+	}
+	
+	public String overloaded(String s) {
+	    return "overloaded-String-" + s;
+	}
+	
+	public static String staticMethod() {
+	    return "static-method";
+	}
+	
+	public static String staticOverloaded(int i) {
+	    return "static-overloaded-int-" + i;
+	}
+
+	public static String staticOverloaded(String s) {
+	    return "static-overloaded-String-" + s;
+	}
+	
+	public PrivateInner getPrivateInner() {
+	    return new PrivateInner();
+	}
+
+        public PublicInner getPublicInner() {
+            return new PublicInner();
+        }
+	
+        public class PublicInner {
+            
+            public int getX() {
+                return 1;
+            }
+            
+            public String m() {
+                return "m";
+            }
+            
+        }
+        
+        @SuppressWarnings("unused")
+	private class PrivateInner {
+	    
+            public int getX() {
+	        return 2;
+	    }
+	    
+	    public String m() {
+	        return "M";
+	    }
+	    
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestInterface.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestInterface.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestInterface.java
new file mode 100644
index 0000000..6737b87
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestInterface.java
@@ -0,0 +1,25 @@
+/*
+ * 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.templatesuite.models;
+
+public interface BeanTestInterface<T> {
+    T getSomething();
+    void setSomething(T s);
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestSuperclass.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestSuperclass.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestSuperclass.java
new file mode 100644
index 0000000..b462e9a
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BeanTestSuperclass.java
@@ -0,0 +1,30 @@
+/*
+ * 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.templatesuite.models;
+
+public class BeanTestSuperclass {
+    public Integer getSomething() {
+        return 42;
+    }
+    
+    public void setSomething(Integer x) {
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanAndScalarModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanAndScalarModel.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanAndScalarModel.java
new file mode 100644
index 0000000..4af030e
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanAndScalarModel.java
@@ -0,0 +1,40 @@
+/*
+ * 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.templatesuite.models;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+public class BooleanAndScalarModel implements TemplateBooleanModel, TemplateScalarModel {
+
+    public static final BooleanAndScalarModel INSTANCE = new BooleanAndScalarModel();
+
+    @Override
+    public String getAsString() throws TemplateModelException {
+        return "s";
+    }
+
+    @Override
+    public boolean getAsBoolean() throws TemplateModelException {
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanAndStringTemplateModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanAndStringTemplateModel.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanAndStringTemplateModel.java
new file mode 100644
index 0000000..d87b65c
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanAndStringTemplateModel.java
@@ -0,0 +1,38 @@
+/*
+ * 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.templatesuite.models;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+public class BooleanAndStringTemplateModel implements TemplateBooleanModel, TemplateScalarModel {
+
+    @Override
+    public String getAsString() throws TemplateModelException {
+        return "theStringValue";
+    }
+
+    @Override
+    public boolean getAsBoolean() throws TemplateModelException {
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanHash1.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanHash1.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanHash1.java
new file mode 100644
index 0000000..524d003
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanHash1.java
@@ -0,0 +1,58 @@
+/*
+ * 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.templatesuite.models;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+
+/**
+ * Tests the impact that the isEmpty() has on template hash models.
+ */
+public class BooleanHash1 implements TemplateHashModel {
+
+    /**
+     * Gets a <tt>TemplateModel</tt> from the hash.
+     *
+     * @param key the name by which the <tt>TemplateModel</tt>
+     * is identified in the template.
+     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * or null if not found.
+     */
+    @Override
+    public TemplateModel get(String key) {
+        if ( key.equals( "temp" )) {
+            return new SimpleScalar( "Hello, world." );
+        } else if ( key.equals( "boolean" )) {
+            return TemplateBooleanModel.FALSE;
+        } else {
+            return new SimpleScalar( "Just another key..." );
+        }
+    }
+
+    /**
+     * @return true if this object is empty.
+     */
+    @Override
+    public boolean isEmpty() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanHash2.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanHash2.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanHash2.java
new file mode 100644
index 0000000..8bae7ab
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanHash2.java
@@ -0,0 +1,50 @@
+/*
+ * 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.templatesuite.models;
+
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Tests the impact that the isEmpty() has on template hash models.
+ */
+public class BooleanHash2 implements TemplateHashModel {
+
+    /**
+     * Gets a <tt>TemplateModel</tt> from the hash.
+     *
+     * @param key the name by which the <tt>TemplateModel</tt>
+     * is identified in the template.
+     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * or null if not found.
+     */
+    @Override
+    public TemplateModel get(String key) {
+        return null;
+    }
+
+    /**
+     * @return true if this object is empty.
+     */
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanList1.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanList1.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanList1.java
new file mode 100644
index 0000000..3e0a69c
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanList1.java
@@ -0,0 +1,62 @@
+/*
+ * 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.templatesuite.models;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleSequence;
+
+/**
+ * Model for testing the impact of isEmpty() on template list models. Every
+ * other method simply delegates to a SimpleList model.
+ */
+public class BooleanList1 implements TemplateSequenceModel {
+
+    private SimpleSequence cList;
+
+    /** Creates new BooleanList1 */
+    public BooleanList1(ObjectWrapper ow) {
+        cList = new SimpleSequence(ow);
+        cList.add( "false" );
+        cList.add( "0" );
+        cList.add(TemplateBooleanModel.FALSE);
+        cList.add(TemplateBooleanModel.TRUE);
+        cList.add(TemplateBooleanModel.TRUE);
+        cList.add(TemplateBooleanModel.TRUE);
+        cList.add(TemplateBooleanModel.FALSE);
+    }
+
+    /**
+     * @return the specified index in the list
+     */
+    @Override
+    public TemplateModel get(int i) throws TemplateModelException {
+        return cList.get(i);
+    }
+
+    @Override
+    public int size() {
+        return cList.size();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanList2.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanList2.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanList2.java
new file mode 100644
index 0000000..939fb5f
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanList2.java
@@ -0,0 +1,53 @@
+/*
+ * 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.templatesuite.models;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleSequence;
+
+/**
+ * Model for testing list models. Every
+ * other method simply delegates to a SimpleList model.
+ */
+public class BooleanList2 implements TemplateSequenceModel {
+
+    private SimpleSequence cList;
+
+    /** Creates new BooleanList2 */
+    public BooleanList2(ObjectWrapper ow) {
+        cList = new SimpleSequence(ow);
+    }
+
+    /**
+     * @return the specified index in the list
+     */
+    @Override
+    public TemplateModel get(int i) throws TemplateModelException {
+        return cList.get(i);
+    }
+
+    @Override
+    public int size() {
+        return cList.size();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanVsStringMethods.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanVsStringMethods.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanVsStringMethods.java
new file mode 100644
index 0000000..3e13623
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/BooleanVsStringMethods.java
@@ -0,0 +1,40 @@
+/*
+ * 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.templatesuite.models;
+
+public class BooleanVsStringMethods {
+    
+    public String expectsString(String s) {
+        return s;
+    }
+
+    public boolean expectsBoolean(boolean b) {
+        return b;
+    }
+    
+    public String overloaded(String s) {
+        return "String " + s;
+    }
+    
+    public String overloaded(boolean s) {
+        return "boolean " + s;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/EnumTestClass.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/EnumTestClass.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/EnumTestClass.java
new file mode 100644
index 0000000..359e4a4
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/EnumTestClass.java
@@ -0,0 +1,34 @@
+/*
+ * 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.templatesuite.models;
+
+/**
+ */
+public enum EnumTestClass
+{
+    ONE, 
+    TWO, 
+    THREE;
+    
+    @Override
+    public String toString() {
+        return name() + "x";
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/ExceptionModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/ExceptionModel.java b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/ExceptionModel.java
new file mode 100644
index 0000000..ca0f9be
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/test/templatesuite/models/ExceptionModel.java
@@ -0,0 +1,39 @@
+/*
+ * 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.templatesuite.models;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+/**
+ * A template that always throws an exception whenever we call getAsString()
+ */
+public class ExceptionModel implements TemplateScalarModel {
+
+    /**
+     * Returns the scalar's value as a String.
+     *
+     * @return the String value of this scalar.
+     */
+    @Override
+    public String getAsString () throws TemplateModelException {
+        throw new TemplateModelException( "Throwing from ExceptionModel!" );
+    }
+}