/*
* Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package freemarker.test;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.junit.Ignore;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.StringTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.utility.StringUtil;
import freemarker.test.templatesuite.TemplateTestSuite;
/**
* Superclass of JUnit tests that process templates but aren't practical to implement via {@link TemplateTestSuite}.
*/
@Ignore
public abstract class TemplateTest {
private Configuration configuration;
private boolean dataModelCreated;
private Object dataModel;
protected final Configuration getConfiguration() {
if (configuration == null) {
try {
configuration = createConfiguration();
} catch (Exception e) {
throw new RuntimeException("Failed to set up configuration for the test", e);
}
}
return configuration;
}
protected final void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
protected void assertOutput(String ftl, String expectedOut) throws IOException, TemplateException {
Template t = new Template(null, ftl, getConfiguration());
assertOutput(t, expectedOut, false);
}
protected void assertOutputForNamed(String name, String expectedOut) throws IOException, TemplateException {
assertOutput(getConfiguration().getTemplate(name), expectedOut, false);
}
@SuppressFBWarnings(value="UI_INHERITANCE_UNSAFE_GETRESOURCE", justification="By design relative to subclass")
protected void assertOutputForNamed(String name) throws IOException, TemplateException {
String expectedOut;
{
String resName = name + ".out";
InputStream in = this.getClass().getResourceAsStream(resName);
if (in == null) {
throw new IOException("Reference output resource not found: " + this.getClass() + ", " + resName);
}
try {
expectedOut = IOUtils.toString(in, "utf-8");
} finally {
in.close();
}
}
assertOutput(getConfiguration().getTemplate(name), expectedOut, true);
}
protected void assertOutput(Template t, String expectedOut) throws TemplateException, IOException {
assertOutput(t, expectedOut, false);
}
protected void assertOutput(Template t, String expectedOut, boolean normalizeNewlines)
throws TemplateException, IOException {
StringWriter out = new StringWriter();
t.process(getDataModel(), out);
String actualOut = out.toString();
if (normalizeNewlines) {
expectedOut = normalizeNewLines(expectedOut);
actualOut = normalizeNewLines(actualOut);
}
assertEquals(expectedOut, actualOut);
}
protected Configuration createConfiguration() throws Exception {
return new Configuration(Configuration.VERSION_2_3_0);
}
protected Object getDataModel() {
if (!dataModelCreated) {
dataModel = createDataModel();
dataModelCreated = true;
}
return dataModel;
}
protected Object createDataModel() {
return null;
}
@SuppressWarnings("boxing")
protected Map<String, Object> createCommonTestValuesDataModel() {
Map<String, Object> dataModel = new HashMap<String, Object>();
dataModel.put("map", Collections.singletonMap("key", "value"));
dataModel.put("list", Collections.singletonList("item"));
dataModel.put("s", "text");
dataModel.put("n", 1);
dataModel.put("b", true);
dataModel.put("bean", new TestBean());
return dataModel;
}
protected void addTemplate(String name, String content) {
Configuration cfg = getConfiguration();
TemplateLoader tl = cfg.getTemplateLoader();
StringTemplateLoader stl;
if (tl != null) {
stl = extractStringTemplateLoader(tl);
} else {
stl = new StringTemplateLoader();
cfg.setTemplateLoader(stl);
}
stl.putTemplate(name, content);
}
private StringTemplateLoader extractStringTemplateLoader(TemplateLoader tl) {
if (tl instanceof MultiTemplateLoader) {
MultiTemplateLoader mtl = (MultiTemplateLoader) tl;
for (int i = 0; i < mtl.getTemplateLoaderCount(); i++) {
TemplateLoader tli = mtl.getTemplateLoader(i);
if (tli instanceof StringTemplateLoader) {
return (StringTemplateLoader) tli;
}
}
throw new IllegalStateException(
"The template loader was a MultiTemplateLoader that didn't contain StringTemplateLoader: "
+ tl);
} else if (tl instanceof StringTemplateLoader) {
return (StringTemplateLoader) tl;
} else {
throw new IllegalStateException(
"The template loader was already set to a non-StringTemplateLoader non-MultiTemplateLoader: "
+ tl);
}
}
protected void addToDataModel(String name, Object value) {
Object dm = getDataModel();
if (dm == null) {
dm = new HashMap<String, Object>();
dataModel = dm;
}
if (dm instanceof Map) {
((Map) dm).put(name, value);
} else {
throw new IllegalStateException("Can't add to non-Map data-model: " + dm);
}
}
protected Throwable assertErrorContains(String ftl, String... expectedSubstrings) {
return assertErrorContains(null, ftl, null, expectedSubstrings);
}
protected Throwable assertErrorContains(String ftl, Class<? extends Throwable> exceptionClass,
String... expectedSubstrings) {
return assertErrorContains(null, ftl, exceptionClass, expectedSubstrings);
}
protected void assertErrorContainsForNamed(String name, String... expectedSubstrings) {
assertErrorContains(name, null, null, expectedSubstrings);
}
protected void assertErrorContainsForNamed(String name, Class<? extends Throwable> exceptionClass,
String... expectedSubstrings) {
assertErrorContains(name, null, exceptionClass, expectedSubstrings);
}
private Throwable assertErrorContains(String name, String ftl, Class<? extends Throwable> exceptionClass,
String... expectedSubstrings) {
try {
Template t;
if (ftl == null) {
t = getConfiguration().getTemplate(name);
} else {
t = new Template("adhoc", ftl, getConfiguration());
}
t.process(getDataModel(), new StringWriter());
fail("The tempalte had to fail");
return null;
} catch (TemplateException e) {
if (exceptionClass != null) {
assertThat(e, instanceOf(exceptionClass));
}
assertContainsAll(e.getMessageWithoutStackTop(), expectedSubstrings);
return e;
} catch (ParseException e) {
if (exceptionClass != null) {
assertThat(e, instanceOf(exceptionClass));
}
assertContainsAll(e.getEditorMessage(), expectedSubstrings);
return e;
} catch (IOException e) {
if (exceptionClass != null) {
assertThat(e, instanceOf(exceptionClass));
}
throw new RuntimeException("Unexpected exception class: " + e.getClass().getName(), e);
}
}
private void assertContainsAll(String msg, String... expectedSubstrings) {
for (String needle: expectedSubstrings) {
if (needle.startsWith("\\!")) {
String netNeedle = needle.substring(2);
if (msg.contains(netNeedle)) {
fail("The message shouldn't contain substring " + StringUtil.jQuote(netNeedle) + ":\n" + msg);
}
} else if (!msg.contains(needle)) {
fail("The message didn't contain substring " + StringUtil.jQuote(needle) + ":\n" + msg);
}
}
}
private String normalizeNewLines(String s) {
return StringUtil.replace(s, "\r\n", "\n").replace('\r', '\n');
}
public static class TestBean {
private int x;
private boolean b;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public boolean isB() {
return b;
}
public void setB(boolean b) {
this.b = b;
}
public int intM() {
return 1;
}
public int intMP(int x) {
return x;
}
public void voidM() {
}
}
}