Source code: freemarker/testcase/TemplateTestCase.java
1 /*
2 * Copyright (c) 2005 The Visigoth Software Society. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The end-user documentation included with the redistribution, if
18 * any, must include the following acknowledgement:
19 * "This product includes software developed by the
20 * Visigoth Software Society (http://www.visigoths.org/)."
21 * Alternately, this acknowledgement may appear in the software itself,
22 * if and wherever such third-party acknowledgements normally appear.
23 *
24 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
25 * project contributors may be used to endorse or promote products derived
26 * from this software without prior written permission. For written
27 * permission, please contact visigoths@visigoths.org.
28 *
29 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
30 * nor may "FreeMarker" or "Visigoth" appear in their names
31 * without prior written permission of the Visigoth Software Society.
32 *
33 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
34 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36 * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
37 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
40 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
43 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44 * SUCH DAMAGE.
45 * ====================================================================
46 *
47 * This software consists of voluntary contributions made by many
48 * individuals on behalf of the Visigoth Software Society. For more
49 * information on the Visigoth Software Society, please see
50 * http://www.visigoths.org/
51 */
52
53 package freemarker.testcase;
54
55 import freemarker.template.*;
56 import freemarker.ext.beans.*;
57 import freemarker.ext.dom.NodeModel;
58 import freemarker.ext.jdom.NodeListModel;
59 import freemarker.testcase.models.*;
60 import freemarker.template.utility.*;
61 import freemarker.template.utility.NormalizeNewlines;
62 import freemarker.testcase.models.TransformHashWrapper;
63 import junit.framework.*;
64 import java.util.*;
65 import java.io.*;
66 import java.net.URL;
67 import javax.xml.parsers.DocumentBuilder;
68 import javax.xml.parsers.DocumentBuilderFactory;
69 import org.jdom.input.SAXBuilder;
70 import org.xml.sax.InputSource;
71
72
73 public class TemplateTestCase extends TestCase {
74
75 Template template;
76 HashMap dataModel = new HashMap();
77
78 String filename, testName;
79 String inputDir = "template";
80 String referenceDir = "reference";
81 File outputDir;
82
83 Configuration conf = new Configuration();
84
85 public TemplateTestCase(String name, String filename) {
86 super(name);
87 this.filename = filename;
88 this.testName = name;
89 }
90
91 public void setTemplateDirectory(String dirname) throws IOException {
92 URL url = getClass().getResource("TemplateTestCase.class");
93 File parent = new File(url.getFile()).getParentFile();
94 File dir = new File(parent, dirname);
95 conf.setDirectoryForTemplateLoading(dir);
96 System.out.println("Setting loading directory as: " + dir);
97 }
98
99 public void setOutputDirectory(String dirname) {
100 URL url = getClass().getResource("TemplateTestCase.class");
101 File parent = new File(url.getFile()).getParentFile();
102 this.outputDir = new File(parent, dirname);
103 System.out.println("Setting reference directory as: " + outputDir);
104 }
105
106 public void setConfigParam(String param, String value) throws IOException {
107 if ("templatedir".equals(param)) {
108 setTemplateDirectory(value);
109 }
110 else if ("auto_import".equals(param)) {
111 StringTokenizer st = new StringTokenizer(value);
112 if (!st.hasMoreTokens()) fail("Expecting libname");
113 String libname = st.nextToken();
114 if (!st.hasMoreTokens()) fail("Expecting 'as <alias>' in autoimport");
115 String as = st.nextToken();
116 if (!as.equals("as")) fail("Expecting 'as <alias>' in autoimport");
117 if (!st.hasMoreTokens()) fail("Expecting alias after 'as' in autoimport");
118 String alias = st.nextToken();
119 conf.addAutoImport(alias, libname);
120 }
121 else if ("clear_encoding_map".equals(param)) {
122 if (StringUtil.getYesNo(value)) {
123 conf.clearEncodingMap();
124 }
125 }
126 else if ("input_encoding".equals(param)) {
127 conf.setDefaultEncoding(value);
128 }
129 else if ("outputdir".equals(param)) {
130 setOutputDirectory(value);
131 }
132 else if ("output_encoding".equals(param)) {
133 conf.setOutputEncoding(value);
134 }
135 else if ("locale".equals(param)) {
136 String lang = "", country="", variant="";
137 StringTokenizer st = new StringTokenizer(value,"_", false);
138 if (st.hasMoreTokens()) {
139 lang = st.nextToken();
140 }
141 if (st.hasMoreTokens()) {
142 country = st.nextToken();
143 }
144 if (st.hasMoreTokens()){
145 variant = st.nextToken();
146 }
147 if (lang != "") {
148 Locale loc = new Locale(lang, country, variant);
149 conf.setLocale(loc);
150 }
151 }
152 else if ("object_wrapper".equals(param)) {
153 try {
154 Class cl = Class.forName(value);
155 ObjectWrapper ow = (ObjectWrapper) cl.newInstance();
156 conf.setObjectWrapper(ow);
157 } catch (Exception e) {
158 fail("Error setting object wrapper to " + value + "\n" + e.getMessage());
159 }
160 }
161 else if ("input_encoding".equals(param)) {
162 conf.setDefaultEncoding(value);
163 }
164 else if ("output_encoding".equals(param)) {
165 conf.setOutputEncoding(value);
166 }
167 else if ("strict_syntax".equals(param)) {
168 boolean b = StringUtil.getYesNo(value);
169 conf.setStrictSyntaxMode(b);
170 }
171 else if ("url_escaping_charset".equals(param)) {
172 conf.setURLEscapingCharset(value);
173 }
174 }
175
176 /*
177 * This method just contains all the code to seed the data model
178 * ported over from the individual classes. This seems ugly and unnecessary.
179 * We really might as well just expose pretty much
180 * the same tree to all our tests. (JR)
181 */
182
183 public void setUp() throws Exception {
184 dataModel.put("message", "Hello, world!");
185
186 if (testName.equals("bean-maps")) {
187 BeansWrapper w1 = new BeansWrapper();
188 BeansWrapper w2 = new BeansWrapper();
189 BeansWrapper w3 = new BeansWrapper();
190 BeansWrapper w4 = new BeansWrapper();
191 BeansWrapper w5 = new BeansWrapper();
192 BeansWrapper w6 = new BeansWrapper();
193 BeansWrapper w7 = new BeansWrapper();
194
195 w1.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
196 w2.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
197 w3.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
198 w4.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
199 w5.setExposureLevel(BeansWrapper.EXPOSE_ALL);
200 w6.setExposureLevel(BeansWrapper.EXPOSE_ALL);
201
202 w1.setMethodsShadowItems(true);
203 w2.setMethodsShadowItems(false);
204 w3.setMethodsShadowItems(true);
205 w4.setMethodsShadowItems(false);
206 w5.setMethodsShadowItems(true);
207 w6.setMethodsShadowItems(false);
208
209 w7.setSimpleMapWrapper(true);
210
211 Object test = getTestBean();
212
213 dataModel.put("m1", w1.wrap(test));
214 dataModel.put("m2", w2.wrap(test));
215 dataModel.put("m3", w3.wrap(test));
216 dataModel.put("m4", w4.wrap(test));
217 dataModel.put("m5", w5.wrap(test));
218 dataModel.put("m6", w6.wrap(test));
219 dataModel.put("m7", w7.wrap(test));
220
221 dataModel.put("s1", w1.wrap("hello"));
222 dataModel.put("s2", w1.wrap("world"));
223 dataModel.put("s3", w5.wrap("hello"));
224 dataModel.put("s4", w5.wrap("world"));
225 }
226
227 else if (testName.equals("beans")) {
228 dataModel.put("array", new String[] { "array-0", "array-1"});
229 dataModel.put("list", Arrays.asList(new String[] { "list-0", "list-1", "list-2"}));
230 Map tmap = new HashMap();
231 tmap.put("key", "value");
232 Object objKey = new Object();
233 tmap.put(objKey, "objValue");
234 dataModel.put("map", tmap);
235 dataModel.put("objKey", objKey);
236 dataModel.put("obj", new freemarker.testcase.models.BeanTestClass());
237 dataModel.put("resourceBundle", new ResourceBundleModel(ResourceBundle.getBundle("freemarker.testcase.models.BeansTestResources"), BeansWrapper.getDefaultInstance()));
238 dataModel.put("date", new GregorianCalendar(1974, 10, 14).getTime());
239 dataModel.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());
240 }
241
242 else if (testName.equals("boolean")) {
243 dataModel.put( "boolean1", TemplateBooleanModel.FALSE);
244 dataModel.put( "boolean2", TemplateBooleanModel.TRUE);
245 dataModel.put( "boolean3", TemplateBooleanModel.TRUE);
246 dataModel.put( "boolean4", TemplateBooleanModel.TRUE);
247 dataModel.put( "boolean5", TemplateBooleanModel.FALSE);
248
249 dataModel.put( "list1", new BooleanList1() );
250 dataModel.put( "list2", new BooleanList2() );
251
252 dataModel.put( "hash1", new BooleanHash1() );
253 dataModel.put( "hash2", new BooleanHash2() );
254 }
255
256 else if (testName.equals("dateformat")) {
257 GregorianCalendar cal = new GregorianCalendar(2002, 10, 15, 14, 54, 13);
258 cal.setTimeZone(TimeZone.getTimeZone("GMT"));
259 dataModel.put("date", new SimpleDate(cal.getTime(), TemplateDateModel.DATETIME));
260 dataModel.put("unknownDate", new SimpleDate(cal.getTime(), TemplateDateModel.UNKNOWN));
261 }
262 else if (testName.equals("default-xmlns")) {
263 InputSource is = new InputSource(getClass().getResourceAsStream("test-defaultxmlns1.xml"));
264 NodeModel nm = NodeModel.parse(is);
265 dataModel.put("doc", nm);
266 }
267
268 else if (testName.equals("multimodels")) {
269 dataModel.put("test", "selftest");
270 dataModel.put("self", "self");
271 dataModel.put("zero", new Integer(0));
272 dataModel.put("data", new MultiModel1());
273 }
274
275 else if (testName.equals("nodelistmodel")) {
276 org.jdom.Document doc = new SAXBuilder().build(new InputSource(getClass().getResourceAsStream("test-xml.xml")));
277 dataModel.put("doc", new NodeListModel(doc));
278 }
279
280 else if (testName.equals("string-builtins3")) {
281 dataModel.put("multi", new TestBoolean());
282 }
283
284 else if (testName.equals("type-builtins")) {
285 dataModel.put("testmethod", new TestMethod());
286 dataModel.put("testnode", new TestNode());
287 dataModel.put("testcollection", new SimpleCollection(new ArrayList()));
288 }
289
290 else if (testName.equals("var-layers")) {
291 dataModel.put("x", new Integer(4));
292 dataModel.put("z", new Integer(4));
293 conf.setSharedVariable("y", new Integer(7));
294 }
295
296 else if (testName.equals("xml-fragment")) {
297 DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
298 f.setNamespaceAware(true);
299 DocumentBuilder db = f.newDocumentBuilder();
300 org.w3c.dom.Document doc = db.parse(new InputSource(getClass().getResourceAsStream("test-xmlfragment.xml")));
301 dataModel.put("node", NodeModel.wrap(doc.getDocumentElement().getFirstChild().getFirstChild()));
302 }
303
304 else if (testName.equals("xmlns1")) {
305 InputSource is = new InputSource(getClass().getResourceAsStream("test-xmlns.xml"));
306 NodeModel nm = NodeModel.parse(is);
307 dataModel.put("doc", nm);
308 }
309
310 else if (testName.equals("xmlns2")) {
311 InputSource is = new InputSource(getClass().getResourceAsStream("test-xmlns2.xml"));
312 NodeModel nm = NodeModel.parse(is);
313 dataModel.put("doc", nm);
314 }
315
316 else if (testName.equals("xmlns3") || testName.equals("xmlns4")) {
317 InputSource is = new InputSource(getClass().getResourceAsStream("test-xmlns3.xml"));
318 NodeModel nm = NodeModel.parse(is);
319 dataModel.put("doc", nm);
320 }
321
322 else if (testName.equals("xmlns5")) {
323 InputSource is = new InputSource(getClass().getResourceAsStream("test-defaultxmlns1.xml"));
324 NodeModel nm = NodeModel.parse(is);
325 dataModel.put("doc", nm);
326 }
327 }
328
329 public void runTest() {
330 try {
331 template = conf.getTemplate(filename);
332 } catch (Exception e) {
333 StringWriter sw = new StringWriter();
334 PrintWriter pw = new PrintWriter(sw);
335 e.printStackTrace(pw);
336 fail("Could not load template " + filename + "\n" + sw.toString());
337 }
338 File refFile = new File (outputDir, filename);
339 File outFile = new File (outputDir, filename+".out");
340 Writer out = null;
341 String encoding = conf.getOutputEncoding();
342 if (encoding == null) encoding = "UTF-8";
343 try {
344 out = new OutputStreamWriter(new FileOutputStream(outFile),
345 encoding);
346 } catch (IOException ioe) {
347 fail("Cannot write to file: " + outFile + "\n" + ioe.getMessage());
348 }
349 try {
350 template.process(dataModel, out);
351 out.close();
352 } catch (Exception e) {
353 StringWriter sw = new StringWriter();
354 PrintWriter pw = new PrintWriter(sw);
355 e.printStackTrace(pw);
356 fail("Could not process template " + filename + "\n" + sw.toString());
357 }
358 try {
359 Reader ref = new InputStreamReader(new FileInputStream(refFile),
360 encoding);
361 Reader output = new InputStreamReader(new FileInputStream(outFile),
362 encoding);
363 compare(ref, output);
364 } catch (IOException e) {
365 StringWriter sw = new StringWriter();
366 PrintWriter pw = new PrintWriter(sw);
367 e.printStackTrace(pw);
368 fail("Error comparing files " + refFile + " and " + outFile + "\n" + sw.toString());
369 }
370 outFile.delete();
371 }
372
373 static public void compare(Reader reference, Reader output) throws IOException {
374 LineNumberReader ref = new LineNumberReader(reference);
375 LineNumberReader out = new LineNumberReader(output);
376 String refLine = "", outLine = "";
377 while (refLine != null || outLine != null) {
378 if (refLine == null) {
379 fail("Output text is longer than reference text");
380 }
381 if (outLine == null) {
382 fail("Output text is shorter than reference text");
383 }
384 refLine = ref.readLine();
385 outLine = out.readLine();
386 if (refLine != null && outLine != null & !refLine.equals(outLine)) {
387 fail("Difference found on line " + ref.getLineNumber() +
388 ".\nReference text is: " + refLine +
389 "\nOutput text is: " + outLine);
390 }
391 }
392 }
393
394 static class TestBoolean implements TemplateBooleanModel, TemplateScalarModel {
395 public boolean getAsBoolean() {
396 return true;
397 }
398
399 public String getAsString() {
400 return "de";
401 }
402 }
403
404 static class TestMethod implements TemplateMethodModel {
405 public Object exec(java.util.List arguments) {
406 return "x";
407 }
408 }
409
410 static class TestNode implements TemplateNodeModel {
411
412 public String getNodeName() {
413 return "name";
414 }
415
416 public TemplateNodeModel getParentNode() {
417 return null;
418 }
419
420 public String getNodeType() {
421 return "element";
422 }
423
424 public TemplateSequenceModel getChildNodes() {
425 return null;
426 }
427
428 public String getNodeNamespace() {
429 return null;
430 }
431 }
432
433 public Object getTestBean()
434 {
435 Map testBean = new TestBean();
436 testBean.put("name", "Chris");
437 testBean.put("location", "San Francisco");
438 testBean.put("age", new Integer(27));
439 return testBean;
440 }
441
442 public static class TestBean extends HashMap {
443 public String getName() {
444 return "Christopher";
445 }
446 public int getLuckyNumber() {
447 return 7;
448 }
449 }
450 }