Repository: incubator-freemarker Updated Branches: refs/heads/3 7486e23b3 -> d373a34d3
Changed FileTestCase again... now it works even if the resources/classes of the same package are loaded from multiple directories. Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/6a2b2ad5 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/6a2b2ad5 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/6a2b2ad5 Branch: refs/heads/3 Commit: 6a2b2ad57633cf1f142bb45a65149c786cae986b Parents: 7486e23 Author: ddekany <[email protected]> Authored: Sun May 7 17:52:05 2017 +0200 Committer: ddekany <[email protected]> Committed: Sun May 7 17:52:05 2017 +0200 ---------------------------------------------------------------------- .../apache/freemarker/core/Configuration.java | 8 +- .../freemarker/core/util/_StringUtil.java | 13 ++ .../org/apache/freemarker/core/ASTTest.java | 7 +- .../freemarker/core/util/StringUtilTest.java | 11 ++ .../test/servlet/Model2TesterServlet.java | 3 + .../test/templatesuite/TemplateTestCase.java | 76 ++++---- .../freemarker/test/util/FileTestCase.java | 176 ++++++++++++------- 7 files changed, 177 insertions(+), 117 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6a2b2ad5/src/main/java/org/apache/freemarker/core/Configuration.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/Configuration.java b/src/main/java/org/apache/freemarker/core/Configuration.java index df15c0f..e97014f 100644 --- a/src/main/java/org/apache/freemarker/core/Configuration.java +++ b/src/main/java/org/apache/freemarker/core/Configuration.java @@ -98,12 +98,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; * * <p>This class is meant to be used in a singleton pattern. That is, you create an instance of this at the beginning of * the application life-cycle with {@link Configuration.Builder}, set its settings - * (either - * with - * the - * setter methods like {@link Configuration.Builder#setTemplateLoader(TemplateLoader)} or by loading a - * {@code .properties} file and use that with {@link Configuration.Builder#setSettings(Properties)}}), and - * then + * (either with the setter methods like {@link Configuration.Builder#setTemplateLoader(TemplateLoader)} or by loading a + * {@code .properties} file and use that with {@link Configuration.Builder#setSettings(Properties)}}), and then * use that single instance everywhere in your application. Frequently re-creating {@link Configuration} is a typical * and grave mistake from performance standpoint, as the {@link Configuration} holds the template cache, and often also * the class introspection cache, which then will be lost. (Note that, naturally, having multiple long-lived instances, http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6a2b2ad5/src/main/java/org/apache/freemarker/core/util/_StringUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/util/_StringUtil.java b/src/main/java/org/apache/freemarker/core/util/_StringUtil.java index 50c1874..b53aae8 100644 --- a/src/main/java/org/apache/freemarker/core/util/_StringUtil.java +++ b/src/main/java/org/apache/freemarker/core/util/_StringUtil.java @@ -1658,5 +1658,18 @@ public class _StringUtil { public static boolean isUpperUSASCII(char c) { return c >= 'A' && c <= 'Z'; } + + private static final Pattern NORMALIZE_EOLS_REGEXP = Pattern.compile("\\r\\n?+"); + + /** + * Converts all non UN*X End-Of-Line character sequences (CR and CRLF) to UN*X format (LF). + * Returns {@code null} for {@code null} input. + */ + public static String normalizeEOLs(String s) { + if (s == null) { + return null; + } + return NORMALIZE_EOLS_REGEXP.matcher(s).replaceAll("\n"); + } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6a2b2ad5/src/test/java/org/apache/freemarker/core/ASTTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/ASTTest.java b/src/test/java/org/apache/freemarker/core/ASTTest.java index ff67cfb..5a6526e 100644 --- a/src/test/java/org/apache/freemarker/core/ASTTest.java +++ b/src/test/java/org/apache/freemarker/core/ASTTest.java @@ -21,7 +21,6 @@ package org.apache.freemarker.core; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.URL; import org.apache.freemarker.core.ASTPrinter.Options; import org.apache.freemarker.core.util._StringUtil; @@ -91,8 +90,10 @@ public class ASTTest extends FileTestCase { ASTPrinter.getASTAsString(templateName, TestUtil.removeFTLCopyrightComment( normalizeLineBreaks( - loadTestTextResource(new URL(getTestClassDirectory(), templateName)) - )), ops)); + loadTestTextResource( + getTestFileURL( + getExpectedContentFileDirectoryResourcePath(), templateName))) + ), ops)); } private String normalizeLineBreaks(final String s) throws FileNotFoundException, IOException { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6a2b2ad5/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java b/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java index 0df8394..2a0ae9d 100644 --- a/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java +++ b/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java @@ -388,5 +388,16 @@ public class StringUtilTest { _StringUtil.RTFEnc(in, sw); assertEquals(expected, sw.toString()); } + + @Test + public void testNormalizeEOLs() { + assertNull(_StringUtil.normalizeEOLs(null)); + assertEquals("", _StringUtil.normalizeEOLs("")); + assertEquals("x", _StringUtil.normalizeEOLs("x")); + assertEquals("x\ny", _StringUtil.normalizeEOLs("x\ny")); + assertEquals("x\ny", _StringUtil.normalizeEOLs("x\r\ny")); + assertEquals("x\ny", _StringUtil.normalizeEOLs("x\ry")); + assertEquals("\n\n\n\n\n\n", _StringUtil.normalizeEOLs("\n\r\r\r\n\r\n\r")); + } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6a2b2ad5/src/test/java/org/apache/freemarker/test/servlet/Model2TesterServlet.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/test/servlet/Model2TesterServlet.java b/src/test/java/org/apache/freemarker/test/servlet/Model2TesterServlet.java index 9a39705..b69a33c 100644 --- a/src/test/java/org/apache/freemarker/test/servlet/Model2TesterServlet.java +++ b/src/test/java/org/apache/freemarker/test/servlet/Model2TesterServlet.java @@ -22,6 +22,7 @@ package org.apache.freemarker.test.servlet; import java.io.IOException; import java.nio.charset.StandardCharsets; +import javax.el.ExpressionFactory; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -104,6 +105,8 @@ public class Model2TesterServlet extends HttpServlet { final String paramViewServlet = req.getParameter(VIEW_SERVLET_PARAM_NAME); if (paramViewServlet == null) { + LOG.info("Found ExpressionFactory at: {}", ExpressionFactory.class.getResource("ExpressionFactory" + + ".class")); //!!T req.getRequestDispatcher(viewPath).forward(req, resp); } else { final RequestDispatcher requestDispatcher = getServletContext().getNamedDispatcher(paramViewServlet); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6a2b2ad5/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java index 2a24b5b..07bf0ca 100644 --- a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java +++ b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java @@ -24,7 +24,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.math.BigDecimal; import java.math.BigInteger; -import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -413,7 +412,7 @@ public class TemplateTestCase extends FileTestCase { } if (out != null) { - assertExpectedFileEqualsString(getName(), out.toString()); + assertExpectedFileEqualsString(expectedFileName, out.toString()); } } @@ -424,19 +423,14 @@ public class TemplateTestCase extends FileTestCase { } @Override - protected URL getExpectedFileDirectory() throws IOException { - return new URL(super.getExpectedFileDirectory(), "expected/"); + protected String getExpectedContentFileDirectoryResourcePath() throws IOException { + return joinResourcePaths(super.getExpectedContentFileDirectoryResourcePath(), "expected"); } @Override - protected Charset getTestResourceCharset() { + protected Charset getTestResourceDefaultCharset() { return confB.getOutputEncoding() != null ? confB.getOutputEncoding() : StandardCharsets.UTF_8; } - - @Override - protected URL getExpectedFileFor(String testCaseFileName) throws IOException { - return new URL(getExpectedFileDirectory(), expectedFileName); - } static class TestBoolean implements TemplateBooleanModel, TemplateScalarModel { @Override @@ -449,40 +443,40 @@ public class TemplateTestCase extends FileTestCase { return "de"; } } - + static class TestMethod implements TemplateMethodModel { - @Override - public Object exec(List arguments) { - return "x"; - } + @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; - } + + @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() { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6a2b2ad5/src/test/java/org/apache/freemarker/test/util/FileTestCase.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/test/util/FileTestCase.java b/src/test/java/org/apache/freemarker/test/util/FileTestCase.java index 144592a..ba13558 100644 --- a/src/test/java/org/apache/freemarker/test/util/FileTestCase.java +++ b/src/test/java/org/apache/freemarker/test/util/FileTestCase.java @@ -20,22 +20,19 @@ package org.apache.freemarker.test.util; import java.io.File; -import java.io.FileOutputStream; +import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.apache.freemarker.core.util._NullArgumentException; import org.apache.freemarker.core.util._StringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import junit.framework.AssertionFailedError; import junit.framework.TestCase; @@ -44,20 +41,22 @@ import junit.framework.TestCase; */ public abstract class FileTestCase extends TestCase { + public static final Logger LOG = LoggerFactory.getLogger(FileTestCase.class); + public FileTestCase(String name) { super(name); } protected void assertExpectedFileEqualsString(String expectedFileName, String actualContent) { try { - final URL expectedFile = getExpectedFileFor(expectedFileName); + final URL expectedFile = getExpectedContentFileURL(expectedFileName); try { multilineAssertEquals(loadTestTextResource(expectedFile), actualContent); - } catch (AssertionFailedError e) { - File actualFile = getActualFileFor(expectedFileName); - if (actualFile == null) { - saveString(actualFile, actualContent); + } catch (AssertionFailedError | FileNotFoundException e) { + File actualFile = getActualContentFileFor(expectedFile); + if (actualFile != null) { + FileUtils.write(actualFile, actualContent); reportActualFileSaved(actualFile); } @@ -69,8 +68,8 @@ public abstract class FileTestCase extends TestCase { } private void multilineAssertEquals(String expected, String actual) { - String normExpected = normalizeNewLines(expected); - final String normActual = normalizeNewLines(actual); + String normExpected = _StringUtil.normalizeEOLs(expected); + final String normActual = _StringUtil.normalizeEOLs(actual); // Ignore final line-break difference: if (normActual.endsWith("\n") && !normExpected.endsWith("\n")) { @@ -82,65 +81,127 @@ public abstract class FileTestCase extends TestCase { assertEquals(normExpected, normActual); } - private String normalizeNewLines(String s) { - return _StringUtil.replace(s, "\r\n", "\n").replace('\r', '\n'); + protected void reportActualFileSaved(File actualContentFile) { + LOG.info("Saved actual output of the failed test to here: {}", actualContentFile.getAbsolutePath()); } - private void saveString(File actualFile, String actualContents) throws IOException { - Writer w = new OutputStreamWriter(new FileOutputStream(actualFile), StandardCharsets.UTF_8); - try { - w.write(actualContents); - } finally { - w.close(); + /** + * Convenience method for calling {@link #getTestFileURL(String, String)} with {@link + * #getExpectedContentFileDirectoryResourcePath()} as the first argument. + */ + protected final URL getExpectedContentFileURL(String expectedContentFileName) throws IOException { + return getTestFileURL(getExpectedContentFileDirectoryResourcePath(), expectedContentFileName); + } + + /** + * Gets the URL of the test file that contains the expected result. + * + * @param directoryResourcePath + * The class-loader resource path of the containing directory; if relative, it's interpreted relatively to + * the package of {@link #getTestResourcesBaseClass()}. + * + * @return Not {@code null}; if the file isn't found, throw {@link FileNotFoundException} + * + * @throws FileNotFoundException + * If the requested file wasn't found. + */ + protected final URL getTestFileURL(String directoryResourcePath, String fileName) throws IOException { + _NullArgumentException.check("directoryResourcePath", directoryResourcePath); + _NullArgumentException.check("testCaseFileName", fileName); + + Class baseClass = getTestResourcesBaseClass(); + String resourcePath = joinResourcePaths(directoryResourcePath, fileName); + // It's important that we only query an URL for the file (not for the parent package directory), because the + // parent URL can depend on the file name if the class loader uses multiple directories/jars. + URL resource = baseClass.getResource(resourcePath); + if (resource == null) { + throw new FileNotFoundException("Class-loader resource not found for: " + + "baseClass: " + baseClass.getName() + "; " + + "resourcePath (shown quoted): " + _StringUtil.jQuote(resourcePath)); } + return resource; } - protected URL getExpectedFileFor(String testCaseFileName) throws IOException { - return new URL(getExpectedFileDirectory(), testCaseFileName); + /** + * Concatenates two resource paths, taking care of the edge cases due to leading and trailing "/" + * characters in them. + */ + protected final String joinResourcePaths(String dirPath, String tailPath) { + if (tailPath.startsWith("/") || dirPath.isEmpty()) { + return tailPath; + } + return dirPath.endsWith("/") ? dirPath + tailPath : dirPath + "/" + tailPath; } /** - * @return {@code null} if there's no place to write the actual files to + * Gets the actual content file to create which belongs to an expected content file. Actual content files are + * created when the expected and the actual content differs. + * + * @return {@code null} if there's no place to write the files that contain the actual content */ - protected File getActualFileFor(String testCaseFileName) throws IOException { - File actualFileDirectory = getActualFileDirectory(); - if (actualFileDirectory == null) { + protected File getActualContentFileFor(URL expectedContentFile) throws IOException { + _NullArgumentException.check("expectedContentFile", expectedContentFile); + + File actualContentFileDir = getActualContentFileDirectory(expectedContentFile); + if (actualContentFileDir == null) { return null; } - return new File(actualFileDirectory, deduceActualFileName(testCaseFileName)); + + String expectedContentFileName = expectedContentFile.getPath(); + int lastSlashIdx = expectedContentFileName.lastIndexOf('/'); + if (lastSlashIdx != -1) { + expectedContentFileName = expectedContentFileName.substring(lastSlashIdx + 1); + } + + return new File(actualContentFileDir, deduceActualContentFileName(expectedContentFileName)); } - - private String deduceActualFileName(String testCaseFileName) { - int lastDotIdx = testCaseFileName.lastIndexOf('.'); + + /** + * Deduces the actual content file name from the expected content file name. + * + * @return Not {@code null} + */ + protected String deduceActualContentFileName(String expectedContentFileName) { + _NullArgumentException.check("expectedContentFileName", expectedContentFileName); + + int lastDotIdx = expectedContentFileName.lastIndexOf('.'); return lastDotIdx == -1 - ? testCaseFileName + ".actual" - : testCaseFileName.substring(0, lastDotIdx) + "-actual" + testCaseFileName.substring(lastDotIdx); + ? expectedContentFileName + ".actual" + : expectedContentFileName.substring(0, lastDotIdx) + "-actual" + expectedContentFileName.substring(lastDotIdx); } /** - * The URL of the directory that contains the expected files; must end with "/" or "/." or else relative paths won't - * be resolved correctly. + * The class loader resource path of the directory that contains the expected files; must start and end with "/"! */ - protected URL getExpectedFileDirectory() throws IOException { - return getTestClassDirectory(); + protected String getExpectedContentFileDirectoryResourcePath() throws IOException { + return getTestClassDirectoryResourcePath(); } /** * @return {@code null} if there's no directory to write the actual files to */ - protected File getActualFileDirectory() throws IOException { - return FileUtils.toFile(getExpectedFileDirectory()); + protected File getActualContentFileDirectory(URL expectedFile) throws IOException { + return FileUtils.toFile(expectedFile).getParentFile(); } - @SuppressFBWarnings(value="UI_INHERITANCE_UNSAFE_GETRESOURCE", justification="By design relative to subclass") - protected final URL getTestClassDirectory() throws IOException { - URL url = getClass().getResource("."); - if (url == null) throw new IOException("Couldn't get resource URL for \".\""); - return url; + /** + * The class loader resource path of the directory that contains the test files; must not end with "/"! + */ + protected String getTestClassDirectoryResourcePath() throws IOException { + return ""; + } + + /** + * Resource paths are loaded using this class's {@link Class#getResourceAsStream(String)} method; thus, if + * {@link #getTestClassDirectoryResourcePath()} and such return a relative paths, they will be relative to the + * package of this class. + */ + protected Class getTestResourcesBaseClass() { + return getClass(); } protected String loadTestTextResource(URL resource) throws IOException { - return loadTestTextResource(resource, getTestResourceCharset()); + return loadTestTextResource(resource, getTestResourceDefaultCharset()); } protected String loadTestTextResource(URL resource, Charset charset) throws IOException { @@ -148,27 +209,8 @@ public abstract class FileTestCase extends TestCase { IOUtils.toString(resource, charset.name())); } - protected Charset getTestResourceCharset() { + protected Charset getTestResourceDefaultCharset() { return StandardCharsets.UTF_8; } - - protected void reportActualFileSaved(File f) { - System.out.println("Note: Saved actual output of the failed test to here: " + f.getAbsolutePath()); - } - - private static String loadString(InputStream in, Charset charset) throws IOException { - Reader r = new InputStreamReader(in, charset); - StringBuilder sb = new StringBuilder(1024); - try { - char[] buf = new char[4096]; - int ln; - while ((ln = r.read(buf)) != -1) { - sb.append(buf, 0, ln); - } - } finally { - r.close(); - } - return sb.toString(); - } - + }
