blob: b9c6babebbf6526ea050e2b9851d5bce1fb0295d [file] [log] [blame]
/*
* Copyright (c) 2005 The Visigoth Software Society. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed by the
* Visigoth Software Society (http://www.visigoths.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
* project contributors may be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact visigoths@visigoths.org.
*
* 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
* nor may "FreeMarker" or "Visigoth" appear in their names
* without prior written permission of the Visigoth Software Society.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/
package freemarker.test.templatesuite;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.GregorianCalendar;
import java.util.HashMap;
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.xml.sax.InputSource;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BooleanModel;
import freemarker.ext.beans.ResourceBundleModel;
import freemarker.ext.dom.NodeModel;
import freemarker.template.Configuration;
import freemarker.template.SimpleCollection;
import freemarker.template.SimpleDate;
import freemarker.template.SimpleNumber;
import freemarker.template.Template;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.utility.NullWriter;
import freemarker.template.utility.StringUtil;
import freemarker.test.templatesuite.models.BooleanAndStringTemplateModel;
import freemarker.test.templatesuite.models.BooleanHash1;
import freemarker.test.templatesuite.models.BooleanHash2;
import freemarker.test.templatesuite.models.BooleanList1;
import freemarker.test.templatesuite.models.BooleanList2;
import freemarker.test.templatesuite.models.BooleanVsStringMethods;
import freemarker.test.templatesuite.models.MultiModel1;
import freemarker.test.templatesuite.models.OverloadedMethods;
import freemarker.test.templatesuite.models.OverloadedMethods2;
import freemarker.test.templatesuite.models.VarArgTestModel;
import freemarker.test.utility.AssertDirective;
import freemarker.test.utility.AssertEqualsDirective;
import freemarker.test.utility.AssertFailsDirective;
import freemarker.test.utility.FileTestCase;
public class TemplateTestCase extends FileTestCase {
private Template template;
private HashMap dataModel = new HashMap();
private final String templateName;
private final String expectedFileName;
private final boolean noOutput;
private Configuration conf = new Configuration();
public TemplateTestCase(String name, String templateName, String expectedFileName, boolean noOutput) {
super(name);
this.templateName = templateName;
this.expectedFileName = expectedFileName;
this.noOutput = noOutput;
}
public void setConfigParam(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();
conf.addAutoImport(alias, libname);
}
else if ("clear_encoding_map".equals(param)) {
if (StringUtil.getYesNo(value)) {
conf.clearEncodingMap();
}
}
else if ("input_encoding".equals(param)) {
conf.setDefaultEncoding(value);
}
else {
try {
conf.setSetting(param, value);
} catch (TemplateException 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)
*/
public void setUp() throws Exception {
conf.setDirectoryForTemplateLoading(new File(getTestClassDirectory(), "templates"));
dataModel.put("assert", AssertDirective.INSTANCE);
dataModel.put("assertEquals", AssertEqualsDirective.INSTANCE);
dataModel.put("assertFails", AssertFailsDirective.INSTANCE);
dataModel.put("message", "Hello, world!");
final String testName = getName();
if (testName.equals("bean-maps")) {
BeansWrapper w1 = new BeansWrapper();
BeansWrapper w2 = new BeansWrapper();
BeansWrapper w3 = new BeansWrapper();
BeansWrapper w4 = new BeansWrapper();
BeansWrapper w5 = new BeansWrapper();
BeansWrapper w6 = new BeansWrapper();
BeansWrapper w7 = new BeansWrapper();
w1.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
w2.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
w3.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
w4.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
w5.setExposureLevel(BeansWrapper.EXPOSE_ALL);
w6.setExposureLevel(BeansWrapper.EXPOSE_ALL);
w1.setMethodsShadowItems(true);
w2.setMethodsShadowItems(false);
w3.setMethodsShadowItems(true);
w4.setMethodsShadowItems(false);
w5.setMethodsShadowItems(true);
w6.setMethodsShadowItems(false);
w7.setSimpleMapWrapper(true);
Object test = getTestBean();
dataModel.put("m1", w1.wrap(test));
dataModel.put("m2", w2.wrap(test));
dataModel.put("m3", w3.wrap(test));
dataModel.put("m4", w4.wrap(test));
dataModel.put("m5", w5.wrap(test));
dataModel.put("m6", w6.wrap(test));
dataModel.put("m7", w7.wrap(test));
dataModel.put("s1", w1.wrap("hello"));
dataModel.put("s2", w1.wrap("world"));
dataModel.put("s3", w5.wrap("hello"));
dataModel.put("s4", w5.wrap("world"));
}
else if (testName.equals("beans")) {
dataModel.put("array", new String[] { "array-0", "array-1"});
dataModel.put("list", Arrays.asList(new String[] { "list-0", "list-1", "list-2"}));
Map 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 freemarker.test.templatesuite.models.BeanTestClass());
dataModel.put("resourceBundle", new ResourceBundleModel(ResourceBundle.getBundle("freemarker.test.templatesuite.models.BeansTestResources"), BeansWrapper.getDefaultInstance()));
dataModel.put("date", new GregorianCalendar(1974, 10, 14).getTime());
dataModel.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());
dataModel.put("enums", BeansWrapper.getDefaultInstance().getEnumModels());
}
else if (testName.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() );
dataModel.put( "list2", new BooleanList2() );
dataModel.put( "hash1", new BooleanHash1() );
dataModel.put( "hash2", new BooleanHash2() );
}
else if (testName.equals("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));
}
else if (testName.equals("number-format")) {
dataModel.put("int", new SimpleNumber(new Integer(1)));
dataModel.put("double", new SimpleNumber(new Double(1.0)));
dataModel.put("double2", new SimpleNumber(new Double(1 + 1e-15)));
dataModel.put("double3", new SimpleNumber(new Double(1e-16)));
dataModel.put("double4", new SimpleNumber(new Double(-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 (testName.equals("default-xmlns")) {
InputSource is = new InputSource(getClass().getResourceAsStream("models/defaultxmlns1.xml"));
NodeModel nm = NodeModel.parse(is);
dataModel.put("doc", nm);
}
else if (testName.equals("multimodels")) {
dataModel.put("test", "selftest");
dataModel.put("self", "self");
dataModel.put("zero", new Integer(0));
dataModel.put("data", new MultiModel1());
}
else if (testName.equals("stringbimethods")) {
dataModel.put("multi", new TestBoolean());
}
else if (testName.equals("type-builtins")) {
dataModel.put("testmethod", new TestMethod());
dataModel.put("testnode", new TestNode());
dataModel.put("testcollection", new SimpleCollection(new ArrayList()));
}
else if (testName.equals("var-layers")) {
dataModel.put("x", new Integer(4));
dataModel.put("z", new Integer(4));
conf.setSharedVariable("y", new Integer(7));
}
else if (testName.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")));
dataModel.put("node", NodeModel.wrap(doc.getDocumentElement().getFirstChild().getFirstChild()));
}
else if (testName.equals("xmlns1")) {
InputSource is = new InputSource(getClass().getResourceAsStream("models/xmlns.xml"));
NodeModel nm = NodeModel.parse(is);
dataModel.put("doc", nm);
}
else if (testName.equals("xmlns2")) {
InputSource is = new InputSource(getClass().getResourceAsStream("models/xmlns2.xml"));
NodeModel nm = NodeModel.parse(is);
dataModel.put("doc", nm);
}
else if (testName.equals("xmlns3") || testName.equals("xmlns4")) {
InputSource is = new InputSource(getClass().getResourceAsStream("models/xmlns3.xml"));
NodeModel nm = NodeModel.parse(is);
dataModel.put("doc", nm);
}
else if (testName.equals("xmlns5")) {
InputSource is = new InputSource(getClass().getResourceAsStream("models/defaultxmlns1.xml"));
NodeModel nm = NodeModel.parse(is);
dataModel.put("doc", nm);
}
else if (testName.startsWith("sequence-builtins-with-")) {
Set abcSet = new TreeSet();
abcSet.add("a");
abcSet.add("b");
abcSet.add("c");
dataModel.put("abcSet", abcSet);
List listWithNull = new ArrayList();
listWithNull.add("a");
listWithNull.add(null);
listWithNull.add("c");
dataModel.put("listWithNull", listWithNull);
List listWithNullsOnly = new ArrayList();
listWithNull.add(null);
listWithNull.add(null);
listWithNull.add(null);
dataModel.put("listWithNullsOnly", listWithNullsOnly);
dataModel.put("abcCollection", new SimpleCollection(abcSet));
}
else if (testName.equals("iso8601")) {
dataModel.put("javaGMT02", TimeZone.getTimeZone("GMT+02"));
dataModel.put("javaUTC", TimeZone.getTimeZone("UTC"));
dataModel.put("adaptedToStringScalar", new Object() {
public String toString() {
return "GMT+02";
}
});
}
else if (testName.equals("number-to-date")) {
dataModel.put("bigInteger", new BigInteger("1305575275540"));
dataModel.put("bigDecimal", new BigDecimal("1305575275539.5"));
}
else if (testName.equals("varargs")) {
dataModel.put("m", new VarArgTestModel());
}
else if (testName.startsWith("overloaded-methods-2-")) {
dataModel.put("obj", new OverloadedMethods2());
}
else if (testName.startsWith("overloaded-methods-")) {
dataModel.put("obj", new OverloadedMethods());
}
else if (testName.startsWith("boolean-formatting")) {
dataModel.put("beansBoolean", new BooleanModel(Boolean.TRUE, (BeansWrapper) conf.getObjectWrapper()));
dataModel.put("booleanAndString", new BooleanAndStringTemplateModel());
dataModel.put("booleanVsStringMethods", new BooleanVsStringMethods());
}
else if (testName.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 (testName.startsWith("classic-compatible")) {
dataModel.put("array", new String[] { "a", "b", "c" });
dataModel.put("beansArray", new BeansWrapper().wrap(new String[] { "a", "b", "c" }));
dataModel.put("beanTrue", new BeansWrapper().wrap(Boolean.TRUE));
dataModel.put("beanFalse", new BeansWrapper().wrap(Boolean.FALSE));
}
}
public void runTest() {
try {
template = conf.getTemplate(templateName);
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
fail("Could not load template " + StringUtil.jQuote(templateName) + "\n" + sw.toString());
}
StringWriter out = noOutput ? null : new StringWriter();
try {
template.process(dataModel, out != null ? out : NullWriter.INSTANCE);
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
fail("Could not process template " + templateName + "\n" + sw.toString());
}
if (out != null) {
assertExpectedFileEqualsString(getName(), out.toString());
}
}
@Override
protected File getExpectedFileDirectory() throws IOException {
return new File(super.getExpectedFileDirectory(), "references");
}
@Override
protected String getDefaultCharset() {
return conf.getOutputEncoding() != null ? conf.getOutputEncoding() : "UTF-8";
}
@Override
protected File getExpectedFileFor(String testCaseFileName) throws IOException {
return new File(getExpectedFileDirectory(), expectedFileName);
}
static class TestBoolean implements TemplateBooleanModel, TemplateScalarModel {
public boolean getAsBoolean() {
return true;
}
public String getAsString() {
return "de";
}
}
static class TestMethod implements TemplateMethodModel {
public Object exec(java.util.List arguments) {
return "x";
}
}
static class TestNode implements TemplateNodeModel {
public String getNodeName() {
return "name";
}
public TemplateNodeModel getParentNode() {
return null;
}
public String getNodeType() {
return "element";
}
public TemplateSequenceModel getChildNodes() {
return null;
}
public String getNodeNamespace() {
return null;
}
}
public Object getTestBean()
{
Map testBean = new TestBean();
testBean.put("name", "Chris");
testBean.put("location", "San Francisco");
testBean.put("age", new Integer(27));
return testBean;
}
public static class TestBean extends HashMap {
public String getName() {
return "Christopher";
}
public int getLuckyNumber() {
return 7;
}
}
}