Repository: eclipse-vertx/vert.x

Problem statement:
File resolver should replace the file separator before parent file class loader lookup
The `FileResolver` replaces the file separator for a `ClassLoader` lookup however it does not do it for the parent file `ClassLoader` leading to issues on Windows.


Hints:

Patch:
diff --git a/src/main/java/io/vertx/core/file/impl/FileResolver.java b/src/main/java/io/vertx/core/file/impl/FileResolver.java
--- a/src/main/java/io/vertx/core/file/impl/FileResolver.java
+++ b/src/main/java/io/vertx/core/file/impl/FileResolver.java
@@ -22,7 +22,6 @@
 import java.net.URL;
 import java.util.Enumeration;
 import java.util.function.IntPredicate;
-import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
@@ -80,8 +79,6 @@ public class FileResolver {
 
   private static final String FILE_SEP = System.getProperty("file.separator");
   private static final boolean NON_UNIX_FILE_SEP = !FILE_SEP.equals("/");
-  private static final String JAR_URL_SEP = "!/";
-  private static final Pattern JAR_URL_SEP_PATTERN = Pattern.compile(JAR_URL_SEP);
 
   private final File cwd;
   private final boolean enableCaching;
@@ -141,9 +138,6 @@ public File resolveFile(String fileName) {
         }
         // Look for file on classpath
         ClassLoader cl = getClassLoader();
-        if (NON_UNIX_FILE_SEP) {
-          fileName = fileName.replace(FILE_SEP, "/");
-        }
 
         //https://github.com/eclipse/vert.x/issues/2126
         //Cache all elements in the parent directory if it exists
@@ -151,12 +145,18 @@ public File resolveFile(String fileName) {
         //been read works.
         String parentFileName = file.getParent();
         if (parentFileName != null) {
+          if (NON_UNIX_FILE_SEP) {
+            parentFileName = parentFileName.replace(FILE_SEP, "/");
+          }
           URL directoryContents = getValidClassLoaderResource(cl, parentFileName);
           if (directoryContents != null) {
             unpackUrlResource(directoryContents, parentFileName, cl, true);
           }
         }
 
+        if (NON_UNIX_FILE_SEP) {
+          fileName = fileName.replace(FILE_SEP, "/");
+        }
         URL url = getValidClassLoaderResource(cl, fileName);
         if (url != null) {
           return unpackUrlResource(url, fileName, cl, false);
@@ -254,26 +254,17 @@ private File unpackFromJarURL(URL url, String fileName, ClassLoader cl) {
         zip = new ZipFile(file);
       }
 
-      String inJarPath = path.substring(idx1 + 6);
-      String[] parts = JAR_URL_SEP_PATTERN.split(inJarPath);
-      StringBuilder prefixBuilder = new StringBuilder();
-      for (int i = 0; i < parts.length - 1; i++) {
-        prefixBuilder.append(parts[i]).append("/");
-      }
-      String prefix = prefixBuilder.toString();
-
       Enumeration<? extends ZipEntry> entries = zip.entries();
       while (entries.hasMoreElements()) {
         ZipEntry entry = entries.nextElement();
         String name = entry.getName();
-        if (name.startsWith(prefix.isEmpty() ? fileName : prefix + fileName)) {
-          String p = prefix.isEmpty() ? name : name.substring(prefix.length());
+        if (name.startsWith(fileName)) {
           if (name.endsWith("/")) {
             // Directory
-            cache.cacheDir(p);
+            cache.cacheDir(name);
           } else {
             try (InputStream is = zip.getInputStream(entry)) {
-              cache.cacheFile(p, is, !enableCaching);
+              cache.cacheFile(name, is, !enableCaching);
             }
           }
 


Test patch:
diff --git a/src/test/fileresolver/dir with spaces/files.jar b/src/test/fileresolver/dir with spaces/files.jar
new file mode 100644
Binary files /dev/null and b/src/test/fileresolver/dir with spaces/files.jar differ
diff --git a/src/test/fileresolver/files.jar b/src/test/fileresolver/files.jar
new file mode 100644
Binary files /dev/null and b/src/test/fileresolver/files.jar differ
diff --git a/src/test/fileresolver/files.zip b/src/test/fileresolver/files.zip
new file mode 100644
Binary files /dev/null and b/src/test/fileresolver/files.zip differ
diff --git a/src/test/resources/afile with spaces.html b/src/test/fileresolver/files/afile with spaces.html
similarity index 100%
rename from src/test/resources/afile with spaces.html
rename to src/test/fileresolver/files/afile with spaces.html
diff --git a/src/test/resources/afile.html b/src/test/fileresolver/files/afile.html
similarity index 100%
rename from src/test/resources/afile.html
rename to src/test/fileresolver/files/afile.html
diff --git a/src/test/resources/webroot/somefile.html b/src/test/fileresolver/files/webroot/somefile.html
similarity index 100%
rename from src/test/resources/webroot/somefile.html
rename to src/test/fileresolver/files/webroot/somefile.html
diff --git a/src/test/resources/webroot/someotherfile.html b/src/test/fileresolver/files/webroot/someotherfile.html
similarity index 100%
rename from src/test/resources/webroot/someotherfile.html
rename to src/test/fileresolver/files/webroot/someotherfile.html
diff --git a/src/test/resources/webroot/subdir/subdir2/subfile2.html b/src/test/fileresolver/files/webroot/subdir/subdir2/subfile2.html
similarity index 100%
rename from src/test/resources/webroot/subdir/subdir2/subfile2.html
rename to src/test/fileresolver/files/webroot/subdir/subdir2/subfile2.html
diff --git a/src/test/resources/webroot/subdir/subfile.html b/src/test/fileresolver/files/webroot/subdir/subfile.html
similarity index 100%
rename from src/test/resources/webroot/subdir/subfile.html
rename to src/test/fileresolver/files/webroot/subdir/subfile.html
diff --git a/src/test/resources/webroot/this+that b/src/test/fileresolver/files/webroot/this+that
similarity index 100%
rename from src/test/resources/webroot/this+that
rename to src/test/fileresolver/files/webroot/this+that
diff --git a/src/test/fileresolver/nested-files.jar b/src/test/fileresolver/nested-files.jar
new file mode 100644
Binary files /dev/null and b/src/test/fileresolver/nested-files.jar differ
diff --git a/src/test/fileresolver/nested-files.zip b/src/test/fileresolver/nested-files.zip
new file mode 100644
Binary files /dev/null and b/src/test/fileresolver/nested-files.zip differ
diff --git a/src/test/java/io/vertx/core/file/FileResolverTestBase.java b/src/test/java/io/vertx/core/file/FileResolverTestBase.java
--- a/src/test/java/io/vertx/core/file/FileResolverTestBase.java
+++ b/src/test/java/io/vertx/core/file/FileResolverTestBase.java
@@ -28,6 +28,7 @@
 
 import java.io.File;
 import java.net.URL;
+import java.net.URLClassLoader;
 import java.nio.file.Files;
 import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.PosixFilePermissions;
@@ -49,16 +50,26 @@ public abstract class FileResolverTestBase extends VertxTestBase {
 
   protected FileResolver resolver;
 
-  protected String webRoot;
+  private ClassLoader testCL;
 
   @Override
   public void setUp() throws Exception {
     super.setUp();
+    testCL = Thread.currentThread().getContextClassLoader();
+    File baseDir = new File(new File(new File("src"), "test"), "fileresolver");
+    assertTrue(baseDir.exists() && baseDir.isDirectory());
+    ClassLoader resourcesLoader = resourcesLoader(baseDir);
+    Thread.currentThread().setContextClassLoader(resourcesLoader);
     resolver = new FileResolver();
   }
 
+  protected ClassLoader resourcesLoader(File baseDir) throws Exception {
+    return Thread.currentThread().getContextClassLoader();
+  }
+
   @Override
   protected void tearDown() throws Exception {
+    Thread.currentThread().setContextClassLoader(testCL);
     resolver.close();
     super.tearDown();
   }
@@ -124,6 +135,8 @@ public void testResolveFileFromClasspathDisableCaching() throws Exception {
 
   @Test
   public void testResolveFileWithSpacesFromClasspath() throws Exception {
+    Assume.assumeFalse(Utils.isWindows());
+
     for (int i = 0; i < 2; i++) {
       File file = resolver.resolveFile("afile with spaces.html");
       assertTrue(file.exists());
@@ -149,7 +162,7 @@ public void testCacheDirIsPosix0700() throws Exception {
   @Test
   public void testResolveDirectoryFromClasspath() throws Exception {
     for (int i = 0; i < 2; i++) {
-      File file = resolver.resolveFile(webRoot);
+      File file = resolver.resolveFile("webroot");
       assertTrue(file.exists());
       assertTrue(file.getPath().startsWith(cacheBaseDir + "-"));
       assertTrue(file.isDirectory());
@@ -159,7 +172,7 @@ public void testResolveDirectoryFromClasspath() throws Exception {
   @Test
   public void testResolveFileInDirectoryFromClasspath() throws Exception {
     for (int i = 0; i < 2; i++) {
-      File file = resolver.resolveFile(webRoot + "/somefile.html");
+      File file = resolver.resolveFile("webroot/somefile.html");
       assertTrue(file.exists());
       assertTrue(file.getPath().startsWith(cacheBaseDir + "-"));
       assertFalse(file.isDirectory());
@@ -170,7 +183,7 @@ public void testResolveFileInDirectoryFromClasspath() throws Exception {
   @Test
   public void testResolveSubDirectoryFromClasspath() throws Exception {
     for (int i = 0; i < 2; i++) {
-      File file = resolver.resolveFile(webRoot + "/subdir");
+      File file = resolver.resolveFile("webroot/subdir");
       assertTrue(file.exists());
       assertTrue(file.getPath().startsWith(cacheBaseDir + "-"));
       assertTrue(file.isDirectory());
@@ -180,7 +193,7 @@ public void testResolveSubDirectoryFromClasspath() throws Exception {
   @Test
   public void testResolveFileInSubDirectoryFromClasspath() throws Exception {
     for (int i = 0; i < 2; i++) {
-      File file = resolver.resolveFile(webRoot + "/subdir/subfile.html");
+      File file = resolver.resolveFile("webroot/subdir/subfile.html");
       assertTrue(file.exists());
       assertTrue(file.getPath().startsWith(cacheBaseDir + "-"));
       assertFalse(file.isDirectory());
@@ -190,7 +203,7 @@ public void testResolveFileInSubDirectoryFromClasspath() throws Exception {
 
   @Test
   public void testRecursivelyUnpack() throws Exception {
-    File file = resolver.resolveFile(webRoot + "/subdir");
+    File file = resolver.resolveFile("webroot/subdir");
     assertTrue(file.exists());
     File sub = new File(file, "subfile.html");
     assertTrue(sub.exists());
@@ -199,7 +212,7 @@ public void testRecursivelyUnpack() throws Exception {
 
   @Test
   public void testRecursivelyUnpack2() throws Exception {
-    File file = resolver.resolveFile(webRoot + "/subdir");
+    File file = resolver.resolveFile("webroot/subdir");
     assertTrue(file.exists());
     File sub = new File(new File(file, "subdir2"), "subfile2.html");
     assertTrue(sub.exists());
@@ -209,7 +222,7 @@ public void testRecursivelyUnpack2() throws Exception {
   @Test
   public void testDeleteCacheDir() throws Exception {
     FileResolver resolver2 = new FileResolver();
-    File file = resolver2.resolveFile(webRoot + "/somefile.html");
+    File file = resolver2.resolveFile("webroot/somefile.html");
     assertTrue(file.exists());
     File cacheDir = file.getParentFile().getParentFile();
     assertTrue(cacheDir.exists());
@@ -220,7 +233,7 @@ public void testDeleteCacheDir() throws Exception {
   @Test
   public void testCacheDirDeletedOnVertxClose() {
     VertxInternal vertx2 = (VertxInternal)vertx();
-    File file = vertx2.resolveFile(webRoot + "/somefile.html");
+    File file = vertx2.resolveFile("webroot/somefile.html");
     assertTrue(file.exists());
     File cacheDir = file.getParentFile().getParentFile();
     assertTrue(cacheDir.exists());
@@ -249,7 +262,7 @@ public void testFileSystemReadDirectory() {
   @Test
   public void testSendFileFromClasspath() {
     vertx.createHttpServer(new HttpServerOptions().setPort(8080)).requestHandler(res -> {
-      res.response().sendFile(webRoot + "/somefile.html");
+      res.response().sendFile("webroot/somefile.html");
     }).listen(onSuccess(res -> {
       vertx.createHttpClient(new HttpClientOptions())
         .request(HttpMethod.GET, 8080, "localhost", "/")
@@ -351,14 +364,14 @@ private String readFile(File file) {
   @Test
   public void testResolveAfterCloseThrowsISE() throws Exception {
     FileResolver resolver2 = new FileResolver();
-    File file = resolver2.resolveFile(webRoot + "/somefile.html");
+    File file = resolver2.resolveFile("webroot/somefile.html");
     assertTrue(file.exists());
     File cacheDir = file.getParentFile().getParentFile();
     assertTrue(cacheDir.exists());
     resolver2.close();
     assertFalse(cacheDir.exists());
     try {
-      resolver2.resolveFile(webRoot + "/somefile.html");
+      resolver2.resolveFile("webroot/somefile.html");
       fail("Should fail");
     } catch (IllegalStateException e) {
       // OK
diff --git a/src/test/java/io/vertx/core/file/FileSystemFileResolverTest.java b/src/test/java/io/vertx/core/file/FileSystemFileResolverTest.java
--- a/src/test/java/io/vertx/core/file/FileSystemFileResolverTest.java
+++ b/src/test/java/io/vertx/core/file/FileSystemFileResolverTest.java
@@ -26,9 +26,8 @@
 public class FileSystemFileResolverTest extends FileResolverTestBase {
 
   @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    webRoot = "webroot";
+  protected ClassLoader resourcesLoader(File baseDir) throws Exception {
+    return new URLClassLoader(new URL[]{new File(baseDir, "files").toURI().toURL()}, Thread.currentThread().getContextClassLoader());
   }
 
   @Test
diff --git a/src/test/java/io/vertx/core/file/JarFileResolverTest.java b/src/test/java/io/vertx/core/file/JarFileResolverTest.java
--- a/src/test/java/io/vertx/core/file/JarFileResolverTest.java
+++ b/src/test/java/io/vertx/core/file/JarFileResolverTest.java
@@ -11,7 +11,9 @@
 
 package io.vertx.core.file;
 
-import io.vertx.core.file.FileResolverTestBase;
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
 
 /**
  * @author <a href="http://tfox.org">Tim Fox</a>
@@ -19,10 +21,7 @@
 public class JarFileResolverTest extends FileResolverTestBase {
 
   @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    // This is inside the jar webroot2.jar
-    webRoot = "webroot2";
+  protected ClassLoader resourcesLoader(File baseDir) throws Exception {
+    return new URLClassLoader(new URL[]{new File(baseDir, "files.jar").toURI().toURL()}, Thread.currentThread().getContextClassLoader());
   }
-
 }
diff --git a/src/test/java/io/vertx/core/file/JarFileResolverWithSpacesTest.java b/src/test/java/io/vertx/core/file/JarFileResolverWithSpacesTest.java
--- a/src/test/java/io/vertx/core/file/JarFileResolverWithSpacesTest.java
+++ b/src/test/java/io/vertx/core/file/JarFileResolverWithSpacesTest.java
@@ -20,23 +20,8 @@
  */
 public class JarFileResolverWithSpacesTest extends FileResolverTestBase {
 
-  private ClassLoader original;
-
   @Override
-  public void setUp() throws Exception {
-    original = Thread.currentThread().getContextClassLoader();
-    URLClassLoader someClassloader = new URLClassLoader(new URL[] { new File("src/test/resources/dir with " +
-        "spaces/webroot3.jar").toURI().toURL()}, JarFileResolverWithSpacesTest.class.getClassLoader());
-    Thread.currentThread().setContextClassLoader(someClassloader);
-    super.setUp();
-    // This is inside the jar webroot2.jar
-    webRoot = "webroot3";
+  protected ClassLoader resourcesLoader(File baseDir) throws Exception {
+    return new URLClassLoader(new URL[]{new File(baseDir, "dir with spaces/files.jar").toURI().toURL()}, Thread.currentThread().getContextClassLoader());
   }
-
-  @Override
-  public void tearDown() throws Exception {
-    super.tearDown();
-    Thread.currentThread().setContextClassLoader(original);
-  }
-
 }
diff --git a/src/test/java/io/vertx/core/file/NestedJarFileResolverTest.java b/src/test/java/io/vertx/core/file/NestedJarFileResolverTest.java
--- a/src/test/java/io/vertx/core/file/NestedJarFileResolverTest.java
+++ b/src/test/java/io/vertx/core/file/NestedJarFileResolverTest.java
@@ -11,8 +11,7 @@
 
 package io.vertx.core.file;
 
-import io.vertx.core.file.FileResolverTestBase;
-
+import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
 
@@ -21,24 +20,21 @@
  */
 public class NestedJarFileResolverTest extends FileResolverTestBase {
 
-  private ClassLoader prevCL;
-
   @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    // This folder is inside the embedded jar file called nested.jar, inside webroot4.jar
-    webRoot = "webroot4";
-
-    prevCL = Thread.currentThread().getContextClassLoader();
-    URL webroot4URL = prevCL.getResource("webroot4.jar");
-    ClassLoader loader = new ClassLoader(prevCL = Thread.currentThread().getContextClassLoader()) {
+  protected ClassLoader resourcesLoader(File baseDir) throws Exception {
+    URL webroot4URL = new File(baseDir, "nested-files.jar").toURI().toURL();
+    return new ClassLoader(Thread.currentThread().getContextClassLoader()) {
       @Override
       public URL getResource(String name) {
         try {
           if (name.startsWith("lib/")) {
             return new URL("jar:" + webroot4URL + "!/" + name);
-          } else if (name.startsWith("webroot4")) {
+          } else if (name.startsWith("webroot")) {
             return new URL("jar:" + webroot4URL + "!/lib/nested.jar!/" + name.substring(7));
+          } else if (name.equals("afile.html")) {
+            return new URL("jar:" + webroot4URL + "!/lib/nested.jar!afile.html/");
+          } else if (name.equals("afile with spaces.html")) {
+            return new URL("jar:" + webroot4URL + "!/lib/nested.jar!afile with spaces.html/");
           }
         } catch (MalformedURLException e) {
           throw new AssertionError(e);
@@ -46,14 +42,5 @@ public URL getResource(String name) {
         return super.getResource(name);
       }
     };
-    Thread.currentThread().setContextClassLoader(loader);
-  }
-
-  @Override
-  public void after() throws Exception {
-    if (prevCL != null) {
-      Thread.currentThread().setContextClassLoader(prevCL);
-    }
-    super.after();
   }
 }
diff --git a/src/test/java/io/vertx/core/file/NestedRootJarResolverTest.java b/src/test/java/io/vertx/core/file/NestedRootJarResolverTest.java
deleted file mode 100644
--- a/src/test/java/io/vertx/core/file/NestedRootJarResolverTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
- * which is available at https://www.apache.org/licenses/LICENSE-2.0.
- *
- * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
- */
-
-package io.vertx.core.file;
-
-import io.vertx.core.file.FileResolverTestBase;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-
-/**
- * @author Thomas Segismont
- */
-public class NestedRootJarResolverTest extends FileResolverTestBase {
-
-  private ClassLoader prevCL;
-
-  @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    // This folder is inside the nested-inf/classes directory, inside nestedroot.jar
-    webRoot = "webroot2";
-
-    prevCL = Thread.currentThread().getContextClassLoader();
-    URL jarUrl = prevCL.getResource("nestedroot.jar");
-    URL rootUrl = new URL("jar:" + jarUrl + "!/nested-inf/classes!/");
-    URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{rootUrl}, prevCL);
-    Thread.currentThread().setContextClassLoader(urlClassLoader);
-  }
-
-  @Override
-  public void after() throws Exception {
-    if (prevCL != null) {
-      Thread.currentThread().setContextClassLoader(prevCL);
-    }
-    super.after();
-  }
-}
diff --git a/src/test/java/io/vertx/core/file/NestedZipFileResolverTest.java b/src/test/java/io/vertx/core/file/NestedZipFileResolverTest.java
--- a/src/test/java/io/vertx/core/file/NestedZipFileResolverTest.java
+++ b/src/test/java/io/vertx/core/file/NestedZipFileResolverTest.java
@@ -11,8 +11,7 @@
 
 package io.vertx.core.file;
 
-import io.vertx.core.file.FileResolverTestBase;
-
+import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
 
@@ -21,24 +20,21 @@
  */
 public class NestedZipFileResolverTest extends FileResolverTestBase {
 
-  private ClassLoader prevCL;
-
   @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    // This folder is inside the embedded zip file called nested.zip, inside webroot6.zip
-    webRoot = "webroot6";
-    // We will store the current classloader
-    prevCL = Thread.currentThread().getContextClassLoader();
-    URL webroot6URL = prevCL.getResource("webroot6.zip");
-    ClassLoader loader = new ClassLoader(prevCL = Thread.currentThread().getContextClassLoader()) {
+  protected ClassLoader resourcesLoader(File baseDir) throws Exception {
+    URL webroot6URL = new File(baseDir, "nested-files.zip").toURI().toURL();
+    return new ClassLoader(Thread.currentThread().getContextClassLoader()) {
       @Override
       public URL getResource(String name) {
         try {
           if (name.startsWith("lib/")) {
             return new URL("jar:" + webroot6URL + "!/" + name);
-          } else if (name.startsWith("webroot6")) {
+          } else if (name.startsWith("webroot")) {
             return new URL("jar:" + webroot6URL + "!/lib/nested.zip!/" + name.substring(7));
+          } else if (name.equals("afile.html")) {
+            return new URL("jar:" + webroot6URL + "!/lib/nested.zip!afile.html/");
+          } else if (name.equals("afile with spaces.html")) {
+            return new URL("jar:" + webroot6URL + "!/lib/nested.zip!afile with spaces.html/");
           }
         } catch (MalformedURLException e) {
           throw new AssertionError(e);
@@ -46,14 +42,5 @@ public URL getResource(String name) {
         return super.getResource(name);
       }
     };
-    Thread.currentThread().setContextClassLoader(loader);
-  }
-
-  @Override
-  public void after() throws Exception {
-    if (prevCL != null) {
-      Thread.currentThread().setContextClassLoader(prevCL);
-    }
-    super.after();
   }
 }
diff --git a/src/test/java/io/vertx/core/file/ZipFileResolverTest.java b/src/test/java/io/vertx/core/file/ZipFileResolverTest.java
--- a/src/test/java/io/vertx/core/file/ZipFileResolverTest.java
+++ b/src/test/java/io/vertx/core/file/ZipFileResolverTest.java
@@ -11,7 +11,9 @@
 
 package io.vertx.core.file;
 
-import io.vertx.core.file.FileResolverTestBase;
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
 
 /**
  * @author <a href="http://www.ernestojpg.com">Ernesto J. Perez</a>
@@ -19,10 +21,7 @@
 public class ZipFileResolverTest extends FileResolverTestBase {
 
   @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    // This is inside the jar webroot5.zip
-    webRoot = "webroot5";
+  protected ClassLoader resourcesLoader(File baseDir) throws Exception {
+    return new URLClassLoader(new URL[]{new File(baseDir, "files.zip").toURI().toURL()}, Thread.currentThread().getContextClassLoader());
   }
-
 }
diff --git a/src/test/java/io/vertx/core/http/Http1xTest.java b/src/test/java/io/vertx/core/http/Http1xTest.java
--- a/src/test/java/io/vertx/core/http/Http1xTest.java
+++ b/src/test/java/io/vertx/core/http/Http1xTest.java
@@ -3585,15 +3585,16 @@ private void testHttpServerRequestDecodeError(Handler<NetSocket> bodySender, Han
   public void testInvalidChunkInHttpClientResponse() throws Exception {
     NetServer server = vertx.createNetServer();
     CountDownLatch listenLatch = new CountDownLatch(1);
+    CompletableFuture<Void> cont = new CompletableFuture<>();
     server.connectHandler(so -> {
-      so.write("HTTP/1.1 200 OK\r\n");
-      so.write("Transfer-Encoding: chunked\r\n");
-      so.write("\r\n");
-      so.write("invalid\r\n"); // Empty chunk
+      so.write("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n");
+      cont.whenComplete((v,e) -> {
+        so.write("invalid\r\n"); // Invalid chunk
+      });
     }).listen(testAddress, onSuccess(v -> listenLatch.countDown()));
     awaitLatch(listenLatch);
     AtomicInteger status = new AtomicInteger();
-    testHttpClientResponseDecodeError(err -> {
+    testHttpClientResponseDecodeError(cont::complete, err -> {
       switch (status.incrementAndGet()) {
         case 1:
           assertTrue(err instanceof NumberFormatException);
@@ -3611,19 +3612,22 @@ public void testInvalidChunkInHttpClientResponse() throws Exception {
   public void testInvalidTrailersInHttpClientResponse() throws Exception {
     NetServer server = vertx.createNetServer();
     CountDownLatch listenLatch = new CountDownLatch(1);
+    CompletableFuture<Void> cont = new CompletableFuture<>();
     server.connectHandler(so -> {
-      so.write("HTTP/1.1 200 OK\r\n");
-      so.write("Transfer-Encoding: chunked\r\n");
-      so.write("\r\n");
-      so.write("0\r\n"); // Empty chunk
+      so.write("HTTP/1.1 200 OK\r\n" +
+        "Transfer-Encoding: chunked\r\n" +
+        "\r\n" +
+        "0\r\n"); // Empty chunk
       // Send large trailer
-      for (int i = 0;i < 2000;i++) {
-        so.write("01234567");
-      }
+      cont.whenComplete((v, e) -> {
+        for (int i = 0;i < 2000;i++) {
+          so.write("01234567");
+        }
+      });
     }).listen(testAddress, onSuccess(v -> listenLatch.countDown()));
     awaitLatch(listenLatch);
     AtomicInteger status = new AtomicInteger();
-    testHttpClientResponseDecodeError(err -> {
+    testHttpClientResponseDecodeError(cont::complete, err -> {
       switch (status.incrementAndGet()) {
         case 1:
           assertTrue(err instanceof TooLongFrameException);
@@ -3637,11 +3641,12 @@ public void testInvalidTrailersInHttpClientResponse() throws Exception {
     });
   }
 
-  private void testHttpClientResponseDecodeError(Handler<Throwable> errorHandler) throws Exception {
+  private void testHttpClientResponseDecodeError(Handler<Void> continuation, Handler<Throwable> errorHandler) throws Exception {
     client.request(requestOptions)
       .onComplete(onSuccess(req -> {
         req.send(onSuccess(resp -> {
           resp.exceptionHandler(errorHandler);
+          continuation.handle(null);
         }));
       }));
     await();
@@ -4749,11 +4754,11 @@ public void start(Promise<Void> startFuture) {
   public void testHttpServerWithIdleTimeoutSendChunkedFile() throws Exception {
     // Does not pass reliably in CI (timeout)
     Assume.assumeFalse(vertx.isNativeTransportEnabled());
-    int expected = 16 * 1024 * 1024; // We estimate this will take more than 200ms to transfer with a 1ms pause in chunks
+    int expected = 64 * 1024 * 1024; // We estimate this will take more than 200ms to transfer with a 1ms pause in chunks
     File sent = TestUtils.tmpFile(".dat", expected);
     server.close();
     server = vertx
-      .createHttpServer(createBaseServerOptions().setIdleTimeout(400).setIdleTimeoutUnit(TimeUnit.MILLISECONDS))
+      .createHttpServer(createBaseServerOptions().setIdleTimeout(1000).setIdleTimeoutUnit(TimeUnit.MILLISECONDS))
       .requestHandler(
         req -> {
           req.response().sendFile(sent.getAbsolutePath());
diff --git a/src/test/java/io/vertx/core/http/Http2ServerTest.java b/src/test/java/io/vertx/core/http/Http2ServerTest.java
--- a/src/test/java/io/vertx/core/http/Http2ServerTest.java
+++ b/src/test/java/io/vertx/core/http/Http2ServerTest.java
@@ -54,6 +54,7 @@
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.http.impl.Http1xOrH2CHandler;
 import io.vertx.core.http.impl.HttpUtils;
+import io.vertx.core.impl.Utils;
 import io.vertx.core.impl.VertxInternal;
 import io.vertx.core.net.impl.SSLHelper;
 import io.vertx.core.streams.ReadStream;
@@ -61,6 +62,7 @@
 import io.vertx.test.core.DetectFileDescriptorLeaks;
 import io.vertx.test.core.TestUtils;
 import io.vertx.test.tls.Trust;
+import org.junit.Assume;
 import org.junit.Test;
 
 import java.io.ByteArrayInputStream;
@@ -1903,6 +1905,9 @@ public void testClientSendGoAwayNoError() throws Exception {
 
   @Test
   public void testClientSendGoAwayInternalError() throws Exception {
+    // On windows the client will close the channel immediately (since it's an error)
+    // and the server might see the channel inactive without receiving the close frame before
+    Assume.assumeFalse(Utils.isWindows());
     Promise<Void> abc = Promise.promise();
     Context ctx = vertx.getOrCreateContext();
     Handler<HttpServerRequest> requestHandler = req -> {
diff --git a/src/test/java/io/vertx/core/http/HttpTLSTest.java b/src/test/java/io/vertx/core/http/HttpTLSTest.java
--- a/src/test/java/io/vertx/core/http/HttpTLSTest.java
+++ b/src/test/java/io/vertx/core/http/HttpTLSTest.java
@@ -1435,7 +1435,7 @@ private void testStore(HttpServerOptions serverOptions, List<String> expectedPos
         ok |= cause.getMessage().startsWith(expectedPossiblePrefix);
       }
       if (!ok) {
-        fail("Was expecting e.getCause().getMessage() to be prefixed by one of " + expectedPossiblePrefixes);
+        fail("Was expecting <" + cause.getMessage() + "> e.getCause().getMessage() to be prefixed by one of " + expectedPossiblePrefixes);
       }
       assertTrue(cause.getMessage().endsWith(expectedSuffix));
     }
diff --git a/src/test/java/io/vertx/core/http/HttpTest.java b/src/test/java/io/vertx/core/http/HttpTest.java
--- a/src/test/java/io/vertx/core/http/HttpTest.java
+++ b/src/test/java/io/vertx/core/http/HttpTest.java
@@ -20,6 +20,7 @@
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.dns.AddressResolverOptions;
 import io.vertx.core.file.AsyncFile;
+import io.vertx.core.impl.Utils;
 import io.vertx.core.net.*;
 import io.vertx.core.net.impl.HAProxyMessageCompletionHandler;
 import io.vertx.core.streams.Pump;
@@ -2075,13 +2076,13 @@ public void testSendFileDirectoryWithHandler() throws Exception {
   @Test
   public void testSendOpenRangeFileFromClasspath() {
     server.requestHandler(res -> {
-      res.response().sendFile("webroot/somefile.html", 6);
+      res.response().sendFile("hosts_config.txt", 13);
     }).listen(testAddress, onSuccess(res -> {
       client.request(requestOptions).onComplete(onSuccess(req -> {
         client.request(requestOptions)
           .compose(HttpClientRequest::send)
           .compose(HttpClientResponse::body).onComplete(onSuccess(body -> {
-          assertTrue(body.toString().startsWith("<body>blah</body></html>"));
+          assertTrue(body.toString().startsWith("server.net"));
           testComplete();
         }));
       }));
@@ -2092,12 +2093,12 @@ public void testSendOpenRangeFileFromClasspath() {
   @Test
   public void testSendRangeFileFromClasspath() {
     server.requestHandler(res -> {
-      res.response().sendFile("webroot/somefile.html", 6, 6);
+      res.response().sendFile("hosts_config.txt", 13, 10);
     }).listen(testAddress, onSuccess(res -> {
       client.request(requestOptions)
         .compose(HttpClientRequest::send)
         .compose(HttpClientResponse::body).onComplete(onSuccess(body -> {
-        assertEquals("<body>", body.toString());
+        assertEquals("server.net", body.toString());
         testComplete();
       }));
     }));
@@ -2330,9 +2331,10 @@ public void testRequestTimesOutWhenIndicatedPeriodExpiresWithoutAResponseFromRem
 
   @Test
   public void testRequestTimeoutCanceledWhenRequestHasAnOtherError() {
+    Assume.assumeFalse(Utils.isWindows());
     AtomicReference<Throwable> exception = new AtomicReference<>();
     // There is no server running, should fail to connect
-    client.request(new RequestOptions().setTimeout(800))
+    client.request(new RequestOptions().setPort(5000).setTimeout(800))
       .onComplete(onFailure(exception::set));
 
     vertx.setTimer(1500, id -> {
@@ -4821,9 +4823,8 @@ public void testDisableIdleTimeoutInPool() throws Exception {
       .setMaxPoolSize(1)
       .setKeepAliveTimeout(10)
     );
-    client.request(requestOptions)
-      .compose(HttpClientRequest::send)
-      .onComplete(onSuccess(resp -> {
+    client.request(requestOptions, onSuccess(req -> {
+      req.send(onSuccess(resp -> {
         resp.endHandler(v1 -> {
           AtomicBoolean closed = new AtomicBoolean();
           resp.request().connection().closeHandler(v2 -> {
@@ -4835,6 +4836,7 @@ public void testDisableIdleTimeoutInPool() throws Exception {
           });
         });
       }));
+    }));
     await();
   }
 
diff --git a/src/test/java/io/vertx/core/impl/logging/LoggingBackendSelectionTest.java b/src/test/java/io/vertx/core/impl/logging/LoggingBackendSelectionTest.java
--- a/src/test/java/io/vertx/core/impl/logging/LoggingBackendSelectionTest.java
+++ b/src/test/java/io/vertx/core/impl/logging/LoggingBackendSelectionTest.java
@@ -15,12 +15,12 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.net.URL;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -111,8 +111,14 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
         if (url == null) {
           throw new ClassNotFoundException(name);
         }
-        try {
-          byte[] bytes = Files.readAllBytes(FileSystems.getDefault().getPath(url.getPath()));
+        try (InputStream in = url.openStream()) {
+          ByteArrayOutputStream baos = new ByteArrayOutputStream();
+          byte[] buff = new byte[256];
+          int l;
+          while ((l = in.read(buff)) != -1) {
+            baos.write(buff, 0, l);;
+          }
+          byte[] bytes = baos.toByteArray();
           Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
           if (resolve) {
             resolveClass(clazz);
diff --git a/src/test/java/io/vertx/core/spi/metrics/MetricsTest.java b/src/test/java/io/vertx/core/spi/metrics/MetricsTest.java
--- a/src/test/java/io/vertx/core/spi/metrics/MetricsTest.java
+++ b/src/test/java/io/vertx/core/spi/metrics/MetricsTest.java
@@ -399,6 +399,7 @@ private void testHandlerProcessMessage(Vertx from, Vertx to, int expectedLocalCo
 
   @Test
   public void testHandlerMetricReply() throws Exception {
+    AtomicReference<HandlerMetric> replyRegistration = new AtomicReference<>();
     CountDownLatch latch = new CountDownLatch(1);
     FakeEventBusMetrics metrics = FakeMetricsBase.getMetrics(vertx.eventBus());
     vertx.eventBus().consumer(ADDRESS1, msg -> {
@@ -409,6 +410,7 @@ public void testHandlerMetricReply() throws Exception {
       assertEquals(0, registration.scheduleCount.get());
       assertEquals(0, registration.deliveredCount.get());
       assertEquals(0, registration.localDeliveredCount.get());
+      replyRegistration.set(registration);
       msg.reply("pong");
     }).completionHandler(ar -> {
       assertTrue(ar.succeeded());
@@ -417,7 +419,7 @@ public void testHandlerMetricReply() throws Exception {
     awaitLatch(latch);
     vertx.eventBus().request(ADDRESS1, "ping", reply -> {
       assertEquals(ADDRESS1, metrics.getRegistrations().get(0).address);
-      HandlerMetric registration = metrics.getRegistrations().get(1);
+      HandlerMetric registration = replyRegistration.get();
       assertEquals(ADDRESS1, registration.repliedAddress);
       assertEquals(1, registration.scheduleCount.get());
       assertEquals(1, registration.deliveredCount.get());
@@ -900,7 +902,7 @@ private void testDatagram(String host, Consumer<PacketMetric> checker) throws Ex
   }
 
   @Test
-  public void testThreadPoolMetricsWithExecuteBlocking() {
+  public void testThreadPoolMetricsWithExecuteBlocking() throws Exception {
     Map<String, PoolMetrics> all = FakePoolMetrics.getPoolMetrics();
 
     FakePoolMetrics metrics = (FakePoolMetrics) all.get("vert.x-worker-thread");
@@ -910,7 +912,7 @@ public void testThreadPoolMetricsWithExecuteBlocking() {
 
     Handler<Promise<Void>> job = getSomeDumbTask();
 
-    AtomicInteger counter = new AtomicInteger();
+    CountDownLatch counter = new CountDownLatch(100);
     AtomicBoolean hadWaitingQueue = new AtomicBoolean();
     AtomicBoolean hadIdle = new AtomicBoolean();
     AtomicBoolean hadRunning = new AtomicBoolean();
@@ -927,17 +929,15 @@ public void testThreadPoolMetricsWithExecuteBlocking() {
             if (metrics.numberOfRunningTasks() > 0) {
               hadRunning.set(true);
             }
-            if (counter.incrementAndGet() == 100) {
-              testComplete();
-            }
+            counter.countDown();
           }
       );
     }
 
-    await();
+    awaitLatch(counter);
 
-    assertEquals(metrics.numberOfSubmittedTask(), 100);
-    assertEquals(metrics.numberOfCompletedTasks(), 100);
+    assertWaitUntil(() -> metrics.numberOfSubmittedTask() == 100);
+    assertWaitUntil(() -> metrics.numberOfCompletedTasks() == 100);
     assertTrue(hadIdle.get());
     assertTrue(hadWaitingQueue.get());
     assertTrue(hadRunning.get());
@@ -948,22 +948,23 @@ public void testThreadPoolMetricsWithExecuteBlocking() {
   }
 
   @Test
-  public void testThreadPoolMetricsWithInternalExecuteBlocking() {
+  public void testThreadPoolMetricsWithInternalExecuteBlocking() throws InterruptedException {
     Map<String, PoolMetrics> all = FakePoolMetrics.getPoolMetrics();
     FakePoolMetrics metrics = (FakePoolMetrics) all.get("vert.x-internal-blocking");
 
     assertThat(metrics.getPoolSize(), is(getOptions().getInternalBlockingPoolSize()));
     assertThat(metrics.numberOfIdleThreads(), is(getOptions().getInternalBlockingPoolSize()));
 
-    AtomicInteger counter = new AtomicInteger();
+    int num = VertxOptions.DEFAULT_INTERNAL_BLOCKING_POOL_SIZE;
+    int count = num * 5;
+
+    CountDownLatch counter = new CountDownLatch(count);
     AtomicBoolean hadWaitingQueue = new AtomicBoolean();
     AtomicBoolean hadIdle = new AtomicBoolean();
     AtomicBoolean hadRunning = new AtomicBoolean();
 
     VertxInternal v = (VertxInternal) vertx;
     Map<Integer, CountDownLatch> latches = new HashMap<>();
-    int num = VertxOptions.DEFAULT_INTERNAL_BLOCKING_POOL_SIZE;
-    int count = num * 5;
     for (int i = 0; i < count; i++) {
       CountDownLatch latch = latches.computeIfAbsent(i / num, k -> new CountDownLatch(num));
       v.executeBlockingInternal(fut -> {
@@ -986,13 +987,11 @@ public void testThreadPoolMetricsWithInternalExecuteBlocking() {
         if (metrics.numberOfIdleThreads() > 0) {
           hadIdle.set(true);
         }
-        if (counter.incrementAndGet() == count) {
-          testComplete();
-        }
+        counter.countDown();
       });
     }
 
-    await();
+    awaitLatch(counter);
 
     assertEquals(metrics.numberOfSubmittedTask(), 100);
     assertEquals(metrics.numberOfCompletedTasks(), 100);
@@ -1075,7 +1074,7 @@ public void testThreadPoolMetricsWithWorkerVerticle() throws Exception {
   }
 
   @Test
-  public void testThreadPoolMetricsWithNamedExecuteBlocking() {
+  public void testThreadPoolMetricsWithNamedExecuteBlocking() throws InterruptedException {
     vertx.close(); // Close the instance automatically created
     vertx = Vertx.vertx(new VertxOptions().setMetricsOptions(new MetricsOptions().setEnabled(true).setFactory(new FakeMetricsFactory())));
 
@@ -1090,7 +1089,7 @@ public void testThreadPoolMetricsWithNamedExecuteBlocking() {
 
     Handler<Promise<Void>> job = getSomeDumbTask();
 
-    AtomicInteger counter = new AtomicInteger();
+    CountDownLatch counter = new CountDownLatch(100);
     AtomicBoolean hadWaitingQueue = new AtomicBoolean();
     AtomicBoolean hadIdle = new AtomicBoolean();
     AtomicBoolean hadRunning = new AtomicBoolean();
@@ -1108,13 +1107,11 @@ public void testThreadPoolMetricsWithNamedExecuteBlocking() {
             if (metrics.numberOfRunningTasks() > 0) {
               hadRunning.set(true);
             }
-            if (counter.incrementAndGet() == 100) {
-              testComplete();
-            }
+            counter.countDown();
           });
     }
 
-    await();
+    awaitLatch(counter);
 
     assertEquals(metrics.numberOfSubmittedTask(), 100);
     assertEquals(metrics.numberOfCompletedTasks(), 100);
diff --git a/src/test/resources/dir with spaces/webroot3.jar b/src/test/resources/dir with spaces/webroot3.jar
deleted file mode 100644
Binary files a/src/test/resources/dir with spaces/webroot3.jar and /dev/null differ
diff --git a/src/test/resources/nestedroot.jar b/src/test/resources/nestedroot.jar
deleted file mode 100644
Binary files a/src/test/resources/nestedroot.jar and /dev/null differ
diff --git a/src/test/resources/webroot2.jar b/src/test/resources/webroot2.jar
deleted file mode 100644
Binary files a/src/test/resources/webroot2.jar and /dev/null differ
diff --git a/src/test/resources/webroot4.jar b/src/test/resources/webroot4.jar
deleted file mode 100644
Binary files a/src/test/resources/webroot4.jar and /dev/null differ
diff --git a/src/test/resources/webroot5.zip b/src/test/resources/webroot5.zip
deleted file mode 100644
Binary files a/src/test/resources/webroot5.zip and /dev/null differ
diff --git a/src/test/resources/webroot6.zip b/src/test/resources/webroot6.zip
deleted file mode 100644
Binary files a/src/test/resources/webroot6.zip and /dev/null differ