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 2016/01/24 19:19:05 UTC

incubator-freemarker git commit: FREEMARKER-15 fixed: Got rid of jar-s (and class binaries inside) in the test source code. I haven't implemented this as part of the build script, to help migrating to other build tools later. Instead, the JUnit test itse

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3.24-gae-stabilization ec3d4618b -> 99d939c99


FREEMARKER-15 fixed: Got rid of jar-s (and class binaries inside) in the test source code. I haven't implemented this as part of the build script, to help migrating to other build tools later. Instead, the JUnit test itself builds the required jar-s on the fly.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/99d939c9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/99d939c9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/99d939c9

Branch: refs/heads/2.3.24-gae-stabilization
Commit: 99d939c99ab062a3be8491f5f64100bcc931b100
Parents: ec3d461
Author: ddekany <dd...@apache.org>
Authored: Sun Jan 24 19:18:50 2016 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Jan 24 19:18:50 2016 +0100

----------------------------------------------------------------------
 NOTICE                                          |   4 -
 .../config/WebappLocalFreemarkerServlet.java    |  25 ++
 .../freemarker/test/ResourcesExtractor.java     | 296 +++++++++++++++++++
 .../freemarker/test/servlet/WebAppTestCase.java | 175 +++++++----
 .../ext/jsp/webapps/basic/CONTENTS.txt          |  33 +++
 .../ext/jsp/webapps/config/CONTENTS.txt         |  33 +++
 .../lib/WebappLocalFreemarkerServlet.jar        | Bin 936 -> 0 bytes
 .../webapps/config/WEB-INF/lib/templates.jar    | Bin 1018 -> 0 bytes
 .../WEB-INF/lib/templates.jar/sub/test2.ftl     |  19 ++
 .../ext/jsp/webapps/config/WEB-INF/web.xml      |   4 +-
 .../ext/jsp/webapps/errors/CONTENTS.txt         |  28 ++
 .../jsp/webapps/multipleLoaders/CONTENTS.txt    |  24 ++
 .../ext/jsp/webapps/tldDiscovery/CONTENTS.txt   |  37 +++
 .../tldDiscovery/WEB-INF/lib/taglib-foo.jar     | Bin 1221 -> 0 bytes
 .../lib/taglib-foo.jar/META-INF/foo bar.tld     |  32 ++
 .../webapps/tldDiscovery/WEB-INF/taglib 2.jar   | Bin 1179 -> 0 bytes
 .../WEB-INF/taglib 2.jar/META-INF/taglib.tld    |  31 ++
 17 files changed, 672 insertions(+), 69 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/NOTICE
----------------------------------------------------------------------
diff --git a/NOTICE b/NOTICE
index af3d669..4c7b803 100644
--- a/NOTICE
+++ b/NOTICE
@@ -25,9 +25,5 @@ license of the FreeMarker project:
 
     src/main/misc/overloadedNumberRules/prices.ods
     src/manual/en_US/docgen-originals/figures/overview.odg
-    src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/WebappLocalFreemarkerServlet.jar
-    src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar
-    src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar
-    src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar
 
 =========================================================================

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/java/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.java b/src/test/java/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.java
new file mode 100644
index 0000000..9411633
--- /dev/null
+++ b/src/test/java/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.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 freemarker.ext.jsp.webapps.config;
+
+import freemarker.ext.servlet.FreemarkerServlet;
+
+public class WebappLocalFreemarkerServlet extends FreemarkerServlet {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/java/freemarker/test/ResourcesExtractor.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/test/ResourcesExtractor.java b/src/test/java/freemarker/test/ResourcesExtractor.java
new file mode 100644
index 0000000..88e1bed
--- /dev/null
+++ b/src/test/java/freemarker/test/ResourcesExtractor.java
@@ -0,0 +1,296 @@
+/*
+ * 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 freemarker.test;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.io.Files;
+
+import freemarker.template.utility.StringUtil;
+
+/**
+ * Extracts a collection of resources from a Java package into a file system directory, based on the {@value ResourcesExtractor#CONTENTS_TXT}
+ * resource in it. It won't scan to find resources automatically, so everything has to be added to this file.
+ * 
+ * <p>Regarding the content of {@value ResourcesExtractor#CONTENTS_TXT}:
+ * <ul>
+ *   <li>Lines starting with {code //} or {code /*} or {code *} are comments.
+ *   <li>Each non-empty line in the {@value ResourcesExtractor#CONTENTS_TXT} that isn't comment, there must be
+ *       a resource path relative to the "directory" that contains {@value ResourcesExtractor#CONTENTS_TXT}. Only the
+ *       files referred this way will be copied. (Referring to directories doesn't copy the files in them.)
+ *   <li>If the entry ends with {@code "/"}, then it denotes a directory that must be created even if there will be
+ *       no files in it. Otherwise it's redundant to add such entries. 
+ *   <li>The content of "subdirectories" whose name ends with  {@value ResourcesExtractor#SUFFIX_JAR} will be copied
+ *       into a jar file with similar name, instead of into a directory.
+ *   <li>An line may contains {@value #ARROW}, in which case the left side of the {@value #ARROW} is the
+ *       <em>absolute</em> path of the copied resource, and the right side of the {@value #ARROW} is the target
+ *       relative path (like the path in the usual lines). This is useful for copying class files generated by
+ *       the normal Java compilation mechanism into target subdirectories like {@code WEB-INF\classes\com\example}.
+ * </ul>
+ */
+public final class ResourcesExtractor {
+    
+    private static final String ARROW = "->";
+    private static final String DOT_TMP = ".tmp";
+    public static final String CONTENTS_TXT = "CONTENTS.txt";
+    public static final String SUFFIX_JAR = ".jar";
+
+    private static final Logger LOG = LoggerFactory.getLogger(ResourcesExtractor.class);
+    
+    private ResourcesExtractor() {
+        // Not meant to be instantiated
+    }
+    
+    /**
+     * @param resolverClass
+     *            The class with which the resources are loaded.
+     * @param srcDirResourcePath
+     *            The resource path to which the paths in {@value ResourcesExtractor#CONTENTS_TXT} are relative to. If
+     *            the value of this parameter doesn't start with {@code "/"}, then it's relative to the package of the
+     *            {@code resolverClass}.
+     * 
+     * @return The temporary directory into which the resource where extracted to. Don't forget to delete it when it's
+     *         not used anymore.
+     */
+    public static File extract(Class resolverClass, String srcDirResourcePath, File dstRootDir) throws IOException {
+        if (!srcDirResourcePath.endsWith("/")) {
+            srcDirResourcePath += "/";
+        }
+        
+        String contResource = srcDirResourcePath + CONTENTS_TXT;
+        InputStream contIn = resolverClass.getResourceAsStream(contResource);
+        if (contIn == null) {
+            throw new IOException("Can't find resource: class=" + resolverClass + ", path=" + contResource);
+        }
+        
+        boolean deleteDstRootDir;
+        if (dstRootDir == null) {
+            dstRootDir = Files.createTempDir();
+            deleteDstRootDir = true;
+        } else {
+            deleteDstRootDir = !dstRootDir.exists();
+        }
+        try {
+            BufferedReader contR = new BufferedReader(new InputStreamReader(contIn, "UTF-8"));
+            try {
+                String contLine;
+                while ((contLine = contR.readLine()) != null) {
+                    processLine(contLine, resolverClass, srcDirResourcePath, dstRootDir, contResource);
+                }
+            } finally {
+                contR.close();
+            }
+            jarMarkedSubdirectories(dstRootDir);
+            deleteDstRootDir = false;
+        } finally {
+            if (deleteDstRootDir) {
+                try {
+                    if (dstRootDir.getParentFile() == null) {
+                        throw new IOException("Won't delete the root directory");
+                    }
+                    FileUtils.deleteDirectory(dstRootDir);
+                } catch (IOException e) {
+                    LOG.error("Failed to delete destination directory: " + dstRootDir, e);
+                }
+            }
+        }
+        
+        return dstRootDir;
+    }
+    
+    private static void processLine(String contLine, Class<?> resolverClass, String srcDirResourcePath, File dstRootDir,
+            String contResource) throws IOException {
+        contLine = contLine.trim();
+        if (contLine.isEmpty() || contLine.startsWith("//") || contLine.startsWith("/*")
+                || contLine.startsWith("*")) {
+            return;
+        }
+        
+        String contSrcPath = contLine;
+        String contDstPath = contLine;
+        boolean contSrcPathRelative;
+        int arrowIdx = contLine.indexOf(ARROW);
+        if (arrowIdx != -1) {
+            if (!contLine.startsWith("/")) {
+                throw new IOException("In " + StringUtil.jQuote(contResource) + ", this line must start with "
+                        + "\"/\" as it uses the " + StringUtil.jQuote(ARROW) + " operator : "
+                        + contLine);
+            }
+            contSrcPath = contLine.substring(0, arrowIdx).trim();
+            contDstPath = contLine.substring(arrowIdx + ARROW.length()).trim();
+            contSrcPathRelative = false;
+        } else {
+            if (contLine.startsWith("/")) {
+                throw new IOException("In " + StringUtil.jQuote(contResource)
+                        + ", this line can't start with \"/\": " + contLine);
+            }
+            contSrcPathRelative = true;
+            contSrcPath = contLine;
+            contDstPath = contLine;
+        }
+        File dstFile = new File(dstRootDir, contDstPath);
+        if (contLine.endsWith("/")) {
+            if (!dstFile.mkdirs()) {
+                throw new IOException("Failed to create directory: " + dstFile);
+            }
+        } else {
+            String srcEntryPath = contSrcPathRelative ? srcDirResourcePath + contSrcPath : contSrcPath;
+            InputStream entryIn = resolverClass.getResourceAsStream(srcEntryPath);
+            if (entryIn == null) {
+                throw new IOException("Can't find resource: class=" + resolverClass + ", path=" + srcEntryPath);
+            }
+            try {
+                if (dstFile.exists()) {
+                    throw new IOException(
+                            "Destination already exists; check if " + StringUtil.jQuote(contDstPath)
+                            + " occurs for multiple times in \"" + CONTENTS_TXT + "\".");
+                }
+                FileUtils.copyInputStreamToFile(entryIn, dstFile);
+            } catch (IOException e) {
+                File parent = dstFile;
+                while ((parent = dstFile.getParentFile()) != null) {
+                    if (parent.isFile()) {
+                        throw new IOException("An ancestor directory of " + StringUtil.jQuote(dstFile) + ", "
+                        + StringUtil.jQuote(parent) + " already exists, but as a file, not as a directory. "
+                        + "Check if you have accidentally added the directory itself to \"" + CONTENTS_TXT
+                        + "\". Only files should be listed there.");
+                    }
+                }
+                throw e;
+            } finally {
+                entryIn.close();
+            }
+        }
+    }
+    
+    /**
+     * @param extension
+     *            The file extension of the resulting jar archive, or {@code null} if the archive name will be the same
+     *            as the directory name.
+     */
+    private static File replaceDirectoryWithJar(File srcDir, String extension) throws IOException {
+        String workJarFileName;
+        String finalJarFileName;
+        if (extension == null) {
+            finalJarFileName = srcDir.getName();
+            workJarFileName = finalJarFileName + DOT_TMP;
+        } else {
+            finalJarFileName = srcDir.getName() + "." + extension;
+            workJarFileName = finalJarFileName;
+        }
+        
+        File workJarFile = new File(srcDir.getParentFile(), workJarFileName);
+        jarDirectory(srcDir, workJarFile);
+        
+        if (srcDir.getParentFile() == null) {
+            throw new IOException("Won't delete the root directory");
+        }
+        FileUtils.deleteDirectory(srcDir);
+        
+        File finalJarFile;
+        if (!workJarFileName.equals(finalJarFileName)) {
+            finalJarFile = new File(workJarFile.getParentFile(), finalJarFileName);
+            FileUtils.moveFile(workJarFile, finalJarFile);
+        } else {
+            finalJarFile = workJarFile; 
+        }
+        
+        return finalJarFile;        
+    }
+
+    private static void jarDirectory(File srcDir, File jarFile) throws FileNotFoundException, IOException {
+        boolean finished = false;
+        try {
+            FileOutputStream fileOut = new FileOutputStream(jarFile);
+            try {
+                JarOutputStream jarOut = new JarOutputStream(fileOut);
+                try {
+                    addFilesToJar("", srcDir, jarOut);
+                } finally {
+                    jarOut.close();
+                }
+            } finally {
+                fileOut.close();
+            }
+            finished = true;
+        } finally {
+            if (!finished) {
+                if (!jarFile.delete()) {
+                    LOG.error("Failed to delete file: {}", jarFile);
+                }
+            }
+        }
+    }
+
+    private static void jarMarkedSubdirectories(File dir) throws IOException {
+        File[] entries = dir.listFiles();
+        if (entries == null) {
+            throw new IOException("Failed to list directory: " + dir);
+        }
+        for (File entry : entries) {
+            if (entry.isDirectory()) {
+                jarMarkedSubdirectories(entry);
+                if (entry.getName().endsWith(SUFFIX_JAR)) {
+                    replaceDirectoryWithJar(entry, null);
+                }
+            }
+        }
+    }
+
+    private static void addFilesToJar(String entryBasePath, File dir, JarOutputStream jarOut) throws IOException {
+        File[] entries = dir.listFiles();
+        if (entries == null) {
+            throw new IOException("Couldn't list directory: " + dir);
+        }
+        for (File entry : entries) {
+            if (entry.isFile()) {
+                jarOut.putNextEntry(new ZipEntry(entryBasePath + entry.getName()));
+                FileInputStream fileIn = new FileInputStream(entry);
+                try {
+                    IOUtils.copy(fileIn, jarOut);
+                } finally {
+                    fileIn.close();
+                }
+                jarOut.closeEntry();
+            } else if (entry.isDirectory()) {
+                String dirPath = entryBasePath + entry.getName() + "/";
+                jarOut.putNextEntry(new ZipEntry(dirPath));
+                jarOut.closeEntry();
+                addFilesToJar(dirPath, entry, jarOut);
+            } else {
+                throw new IOException("Couldn't open source entry: " + entry);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/java/freemarker/test/servlet/WebAppTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/test/servlet/WebAppTestCase.java b/src/test/java/freemarker/test/servlet/WebAppTestCase.java
index 223c696..6bd7f44 100644
--- a/src/test/java/freemarker/test/servlet/WebAppTestCase.java
+++ b/src/test/java/freemarker/test/servlet/WebAppTestCase.java
@@ -21,17 +21,18 @@ package freemarker.test.servlet;
 
 import static org.junit.Assert.*;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
@@ -42,10 +43,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import freemarker.test.ResourcesExtractor;
 import freemarker.test.TestUtil;
 
 public class WebAppTestCase {
-    
+
     public static final String IGNORED_MASK = "[IGNORED]";
 
     private static final Logger LOG = LoggerFactory.getLogger(WebAppTestCase.class);
@@ -53,32 +55,35 @@ public class WebAppTestCase {
     private static final String ATTR_JETTY_CONTAINER_INCLUDE_JAR_PATTERN
             = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern";
 
-    private static final String EXPECTED_DIR = "/WEB-INF/expected/";
+    private static final String EXPECTED_DIR = "WEB-INF/expected/";
 
     private static Server server;
     private static ContextHandlerCollection contextHandlers;
-    private static Map<String, WebAppContext> deployedWebApps = new HashMap<String, WebAppContext>(); 
-    
+    private static Map<String, WebAppContext> deployedWebApps = new HashMap<String, WebAppContext>();
+    private static volatile File testTempDirectory;
+
     @BeforeClass
     public static void beforeClass() throws Exception {
         // Work around Java 5 bug(?) that causes Jasper to fail with "zip file closed" when it reads the JSTL jar:
         org.eclipse.jetty.util.resource.Resource.setDefaultUseCaches(false);
-        
+
         LOG.info("Starting embedded Jetty...");
-        
+
         server = new Server(0);
-        
+
         contextHandlers = new ContextHandlerCollection();
         server.setHandler(contextHandlers);
-        
+
         server.start();
     }
-    
+
     @AfterClass
     public static void afterClass() throws Exception {
         LOG.info("Stopping embedded Jetty...");
         server.stop();
-        server.join(); // TODO redundant?
+        server.join();
+        LOG.info("Jetty stopped.");
+        deleteTemporaryDirectories();
     }
 
     protected final String getResponseContent(String webAppName, String webAppRelURL) throws Exception {
@@ -95,7 +100,7 @@ public class WebAppTestCase {
         HTTPResponse resp = getHTTPResponse(webAppName, webAppRelURL);
         return resp.getStatusCode();
     }
-    
+
     protected final HTTPResponse getHTTPResponse(String webAppName, String webAppRelURL) throws Exception {
         if (webAppName.startsWith("/") || webAppName.endsWith("/")) {
             throw new IllegalArgumentException("\"webAppName\" can't start or end with \"/\": " + webAppName);
@@ -103,9 +108,9 @@ public class WebAppTestCase {
         if (webAppRelURL.startsWith("/") || webAppRelURL.endsWith("/")) {
             throw new IllegalArgumentException("\"webappRelURL\" can't start or end with \"/\": " + webAppRelURL);
         }
-        
+
         ensureWebAppIsDeployed(webAppName);
-        
+
         final URI uri = new URI("http://localhost:" + server.getConnectors()[0].getLocalPort()
                 + "/" + webAppName + "/" + webAppRelURL);
 
@@ -115,7 +120,7 @@ public class WebAppTestCase {
             LOG.debug("HTTP GET: {}", uri);
 
             final int responseCode = httpCon.getResponseCode();
-            
+
             final String content;
             if (responseCode == 200) {
                 InputStream in = httpCon.getInputStream();
@@ -127,7 +132,7 @@ public class WebAppTestCase {
             } else {
                 content = null;
             }
-            
+
             return new HTTPResponse(
                     responseCode, httpCon.getResponseMessage(),
                     content,
@@ -136,12 +141,13 @@ public class WebAppTestCase {
             httpCon.disconnect();
         }
     }
-    
+
     /**
      * Compares the output of the JSP and the FTL version of the same page, ignoring some of the whitespace differences.
-     * @param webAppRelURLWithoutExt something like {@code "tester?view=foo"}, which will be extended to
-     *          {@code "tester?view=foo.jsp"} and {@code "tester?view=foo.ftl"}, and then the output of these extended
-     *          URL-s will be compared.
+     * 
+     * @param webAppRelURLWithoutExt
+     *            something like {@code "tester?view=foo"}, which will be extended to {@code "tester?view=foo.jsp"} and
+     *            {@code "tester?view=foo.ftl"}, and then the output of these extended URL-s will be compared.
      */
     protected void assertJSPAndFTLOutputEquals(String webAppName, String webAppRelURLWithoutExt) throws Exception {
         assertOutputsEqual(webAppName, webAppRelURLWithoutExt + ".jsp", webAppRelURLWithoutExt + ".ftl");
@@ -163,7 +169,7 @@ public class WebAppTestCase {
             boolean compressWS) throws Exception {
         assertExpectedEqualsOutput(webAppName, expectedFileName, webAppRelURL, compressWS, null);
     }
-    
+
     /**
      * @param expectedFileName
      *            The name of the file that stores the expected content, relatively to
@@ -177,7 +183,9 @@ public class WebAppTestCase {
         final String actual = normalizeWS(getResponseContent(webAppName, webAppRelURL), compressWS);
         final String expected;
         {
-            final InputStream in = new URL(getWebAppDirURL(webAppName) + EXPECTED_DIR + expectedFileName).openStream();
+            ClassPathResource cpResource = findWebAppDirectoryResource(webAppName);
+            final InputStream in = cpResource.resolverClass.getResourceAsStream(
+                    cpResource.path + EXPECTED_DIR + expectedFileName);
             try {
                 expected = TestUtil.removeTxtCopyrightComment(normalizeWS(IOUtils.toString(in, "utf-8"), compressWS));
             } finally {
@@ -186,10 +194,10 @@ public class WebAppTestCase {
         }
         assertEquals(maskIgnored(expected, ignoredParts), maskIgnored(actual, ignoredParts));
     }
-    
+
     private String maskIgnored(String s, List<Pattern> ignoredParts) {
         if (ignoredParts == null) return s;
-        
+
         for (Pattern ignoredPart : ignoredParts) {
             s = ignoredPart.matcher(s).replaceAll(IGNORED_MASK);
         }
@@ -203,11 +211,11 @@ public class WebAppTestCase {
             context.start();
         }
     }
-    
-    private Pattern BR = Pattern.compile("\r\n|\r"); 
-    private Pattern MULTI_LINE_WS = Pattern.compile("[\t ]*[\r\n][\t \r\n]*", Pattern.DOTALL); 
-    private Pattern SAME_LINE_WS = Pattern.compile("[\t ]+", Pattern.DOTALL); 
-    
+
+    private Pattern BR = Pattern.compile("\r\n|\r");
+    private Pattern MULTI_LINE_WS = Pattern.compile("[\t ]*[\r\n][\t \r\n]*", Pattern.DOTALL);
+    private Pattern SAME_LINE_WS = Pattern.compile("[\t ]+", Pattern.DOTALL);
+
     private String normalizeWS(String s, boolean compressWS) {
         if (compressWS) {
             return SAME_LINE_WS.matcher(
@@ -223,65 +231,106 @@ public class WebAppTestCase {
         if (deployedWebApps.containsKey(webAppName)) {
             return;
         }
-        
-        final String webAppDirURL = getWebAppDirURL(webAppName);
-        
+
+        final String webAppDirURL = createWebAppDirAndGetURI(webAppName);
+
         WebAppContext context = new WebAppContext(webAppDirURL, "/" + webAppName);
-        
+
         // Pattern of jar file names scanned for META-INF/*.tld:
         context.setAttribute(
                 ATTR_JETTY_CONTAINER_INCLUDE_JAR_PATTERN,
                 ".*taglib.*\\.jar$");
-
         contextHandlers.addHandler(context);
         // As we add this after the Server was started, it has to be started manually:
         context.start();
-        
+
         deployedWebApps.put(webAppName, context);
         LOG.info("Deployed web app.: {}", webAppName);
     }
 
-    @SuppressFBWarnings(value="UI_INHERITANCE_UNSAFE_GETRESOURCE", justification="By design relative to subclass")
-    private String getWebAppDirURL(String webAppName) throws IOException {
-        final URL webXmlURL;
-        {
-            final String relResPath = "webapps/" + webAppName + "/WEB-INF/web.xml";
-            
-            Class<?> baseClass = this.getClass();
-            findWebXmlURL: do {
-                URL r = baseClass.getResource(relResPath);
-                if (r != null) {
-                    webXmlURL = r;
-                    break findWebXmlURL;
-                }
-                
-                baseClass = baseClass.getSuperclass();
-                if (!WebAppTestCase.class.isAssignableFrom(baseClass)) {
-                    throw new IOException("Can't find test class relative resource: " + relResPath);
-                }
-            } while (true);
+    private static void deleteTemporaryDirectories() throws IOException {
+        if (testTempDirectory.getParentFile() == null) {
+            throw new IOException("Won't delete the root directory");
         }
-        
         try {
-            return webXmlURL.toURI().resolve("..").toString();
-        } catch (URISyntaxException e) {
-            throw new RuntimeException("Failed to get grandparent URL for " + webXmlURL, e);
+            FileUtils.forceDelete(testTempDirectory);
+        } catch (IOException e) {
+            LOG.warn("Failed to delete temporary file or directory; will re-try on JVM shutdown: "
+                    + testTempDirectory);
+            FileUtils.forceDeleteOnExit(testTempDirectory);
         }
     }
+
+    @SuppressFBWarnings(value = "UI_INHERITANCE_UNSAFE_GETRESOURCE", justification = "By design relative to subclass")
+    private String createWebAppDirAndGetURI(String webAppName) throws IOException {
+        ClassPathResource resourceDir = findWebAppDirectoryResource(webAppName);
+        File temporaryDir = ResourcesExtractor.extract(
+                resourceDir.resolverClass, resourceDir.path, new File(getTestTempDirectory(), webAppName));
+        return temporaryDir.toURI().toString();
+    }
+
+    private ClassPathResource findWebAppDirectoryResource(String webAppName) throws IOException {
+        final String appRelResPath = "webapps/" + webAppName + "/";
+        final String relResPath = appRelResPath + "WEB-INF/web.xml";
+
+        Class<?> baseClass = this.getClass();
+        do {
+            URL r = baseClass.getResource(relResPath);
+            if (r != null) {
+                return new ClassPathResource(baseClass, appRelResPath);
+            }
+
+            baseClass = baseClass.getSuperclass();
+            if (!WebAppTestCase.class.isAssignableFrom(baseClass)) {
+                throw new IOException("Can't find test class relative resource: " + relResPath);
+            }
+        } while (true);
+    }
     
+    private File getTestTempDirectory() throws IOException {
+        if (testTempDirectory == null) {
+            // As at least on Windows we have problem deleting the directories once Jetty has used them (the file
+            // handles of the jars remain open), we always use the same name, so that at most one will remain there.
+            File d = new File(
+                    new File(System.getProperty("java.io.tmpdir")),
+                    "freemarker-jetty-junit-tests-(delete-it)");
+            if (d.exists()) {
+                FileUtils.deleteDirectory(d);
+            }
+            if (!d.mkdirs()) {
+               throw new IOException("Failed to create Jetty temp directory: " + d); 
+            }
+            testTempDirectory = d;
+        }
+        return testTempDirectory;
+    }
+
+    private static class ClassPathResource {
+
+        private final Class<?> resolverClass;
+        private final String path;
+
+        public ClassPathResource(Class<?> resolverClass, String path) {
+            this.resolverClass = resolverClass;
+            this.path = path;
+        }
+
+    }
+
     private static class HTTPResponse {
+
         private final int statusCode;
         private final String content;
         private final String statusMessage;
         private final URI uri;
-        
+
         public HTTPResponse(int statusCode, String statusMessage, String content, URI uri) {
             this.statusCode = statusCode;
             this.content = content;
             this.statusMessage = statusMessage;
             this.uri = uri;
         }
-        
+
         public String getStatusMessage() {
             return statusMessage;
         }
@@ -289,7 +338,7 @@ public class WebAppTestCase {
         public int getStatusCode() {
             return statusCode;
         }
-        
+
         public String getContent() {
             return content;
         }
@@ -297,7 +346,7 @@ public class WebAppTestCase {
         public URI getURI() {
             return uri;
         }
-        
+
     }
-    
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt b/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
new file mode 100644
index 0000000..c3f6857
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+ 
+// Only the files listed here will be part of the tested Web application deployment! 
+
+attributes.ftl
+customELFunctions1.ftl
+customELFunctions1.jsp
+customTags1.ftl
+trivial-jstl-@Ignore.ftl
+trivial.ftl
+trivial.jsp
+
+WEB-INF/el-functions.tld
+WEB-INF/expected
+WEB-INF/test.tld
+WEB-INF/web.xml

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/config/CONTENTS.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/config/CONTENTS.txt b/src/test/resources/freemarker/ext/jsp/webapps/config/CONTENTS.txt
new file mode 100644
index 0000000..3157059
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/config/CONTENTS.txt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+ 
+// Only the files listed here will be part of the tested Web application deployment! 
+
+test.ftl
+
+WEB-INF/web.xml
+
+WEB-INF/classes/test.ftl
+
+WEB-INF/classes/sub/test.ftl
+
+WEB-INF/lib/templates.jar/sub/test2.ftl
+/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.class -> WEB-INF/lib/WebappLocalFreemarkerServlet.jar/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.class
+
+WEB-INF/templates/test.ftl

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/WebappLocalFreemarkerServlet.jar
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/WebappLocalFreemarkerServlet.jar b/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/WebappLocalFreemarkerServlet.jar
deleted file mode 100644
index 5af54da..0000000
Binary files a/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/WebappLocalFreemarkerServlet.jar and /dev/null differ

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar b/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar
deleted file mode 100644
index 8e5f2e5..0000000
Binary files a/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar and /dev/null differ
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl b/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl
new file mode 100644
index 0000000..71b1d86
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl
@@ -0,0 +1,19 @@
+<#--
+  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.
+-->
+from WEB-INF/lib/templates.jar/sub
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl b/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl
new file mode 100644
index 0000000..71b1d86
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl
@@ -0,0 +1,19 @@
+<#--
+  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.
+-->
+from WEB-INF/lib/templates.jar/sub
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml b/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml
index a8328b4..8818368 100644
--- a/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml
+++ b/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml
@@ -35,7 +35,7 @@
     <!-- Must use a webapp-local class for "class:", otherwise it won't find local resources. -->
     <servlet>
         <servlet-name>freemarker-class-root</servlet-name>
-        <servlet-class>webapps.cfg.WebappLocalFreemarkerServlet</servlet-class>
+        <servlet-class>freemarker.ext.jsp.webapps.config.WebappLocalFreemarkerServlet</servlet-class>
         <init-param>
             <param-name>TemplatePath</param-name>
             <param-value>class://</param-value>
@@ -43,7 +43,7 @@
     </servlet>
     <servlet>
         <servlet-name>freemarker-class-sub</servlet-name>
-        <servlet-class>webapps.cfg.WebappLocalFreemarkerServlet</servlet-class>
+        <servlet-class>freemarker.ext.jsp.webapps.config.WebappLocalFreemarkerServlet</servlet-class>
         <init-param>
             <param-name>TemplatePath</param-name>
             <param-value>class://sub</param-value>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/errors/CONTENTS.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/errors/CONTENTS.txt b/src/test/resources/freemarker/ext/jsp/webapps/errors/CONTENTS.txt
new file mode 100644
index 0000000..62d711e
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/errors/CONTENTS.txt
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+ 
+// Only the files listed here will be part of the tested Web application deployment! 
+
+failing-parsetime.ftlnv
+failing-parsetime.jsp
+failing-runtime.ftl
+failing-runtime.jsp
+not-failing.ftl
+
+WEB-INF/web.xml
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/CONTENTS.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/CONTENTS.txt b/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/CONTENTS.txt
new file mode 100644
index 0000000..5453db8
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/CONTENTS.txt
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+ 
+// Only the files listed here will be part of the tested Web application deployment! 
+
+WEB-INF/web.xml
+
+WEB-INF/templates/test.ftl

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/CONTENTS.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/CONTENTS.txt b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/CONTENTS.txt
new file mode 100644
index 0000000..d38119e
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/CONTENTS.txt
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+ 
+// Only the files listed here will be part of the tested Web application deployment! 
+
+test-noClasspath.ftl
+test1.ftl
+
+WEB-INF/fmtesttag 2.tld
+WEB-INF/fmtesttag4.tld
+WEB-INF/web.xml
+
+WEB-INF/taglib 2.jar/META-INF/taglib.tld
+
+WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld
+
+WEB-INF/subdir-with-tld/fmtesttag3.tld
+
+not-auto-scanned/fmtesttag.tld
+
+subdir/test-rel.ftl

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar
deleted file mode 100644
index aa6c542..0000000
Binary files a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar and /dev/null differ
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld
new file mode 100644
index 0000000..3f2e096
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld	
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+  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.
+-->
+<!DOCTYPE taglib
+        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
+        "http://java.sun.com/j2ee/dtds/web-jsptaglib_1_1.dtd">
+<taglib>
+  <tlibversion>2.0</tlibversion>
+  <jspversion>1.2</jspversion>
+  <shortname>FreeMarker JUnit Test Support 3</shortname>
+  <uri>http://freemarker.sf.net/taglibs/freemarker-junit-test-tag-2.2-foo</uri>
+  <tag>
+    <name>testtag</name>
+    <tagclass>freemarker.ext.jsp.taglibmembers.TestTag3</tagclass>
+  </tag>
+</taglib>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld
new file mode 100644
index 0000000..3f2e096
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld	
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+  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.
+-->
+<!DOCTYPE taglib
+        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
+        "http://java.sun.com/j2ee/dtds/web-jsptaglib_1_1.dtd">
+<taglib>
+  <tlibversion>2.0</tlibversion>
+  <jspversion>1.2</jspversion>
+  <shortname>FreeMarker JUnit Test Support 3</shortname>
+  <uri>http://freemarker.sf.net/taglibs/freemarker-junit-test-tag-2.2-foo</uri>
+  <tag>
+    <name>testtag</name>
+    <tagclass>freemarker.ext.jsp.taglibmembers.TestTag3</tagclass>
+  </tag>
+</taglib>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar
deleted file mode 100644
index a915da1..0000000
Binary files a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar and /dev/null differ
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld
new file mode 100644
index 0000000..ce5d873
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld	
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+  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.
+-->
+<!DOCTYPE taglib
+        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
+        "http://java.sun.com/j2ee/dtds/web-jsptaglib_1_1.dtd">
+<taglib>
+  <tlibversion>2.0</tlibversion>
+  <jspversion>1.2</jspversion>
+  <shortname>FreeMarker JUnit Test Support 2</shortname>
+  <tag>
+    <name>testtag</name>
+    <tagclass>freemarker.ext.jsp.taglibmembers.TestTag2</tagclass>
+  </tag>
+</taglib>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99d939c9/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld
new file mode 100644
index 0000000..ce5d873
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld	
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+  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.
+-->
+<!DOCTYPE taglib
+        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
+        "http://java.sun.com/j2ee/dtds/web-jsptaglib_1_1.dtd">
+<taglib>
+  <tlibversion>2.0</tlibversion>
+  <jspversion>1.2</jspversion>
+  <shortname>FreeMarker JUnit Test Support 2</shortname>
+  <tag>
+    <name>testtag</name>
+    <tagclass>freemarker.ext.jsp.taglibmembers.TestTag2</tagclass>
+  </tag>
+</taglib>
\ No newline at end of file