Repository: eclipse-vertx/vert.x

Problem statement:
Nested zip tests fails on Windows
```
Failed tests:
  NestedZipFileResolverTest>AsyncTestBase.lambda$onSuccess$1:622->FileResolverTestBase.lambda$testCacheDirDeletedOnVertxClose$3:197->AsyncTestBase.assertFalse:208 null
  NestedZipFileResolverTest>AsyncTestBase.lambda$onSuccess$1:620->AsyncTestBase.fail:262 java.nio.file.FileSystemException: C:\Users\FTM\Documents\NetBeansProjects\vert.x\.vertx\file-cache-bd9e09f4-0e7a-4400-b8cf-a97a8735ea7c\lib\nested.zip: The process cannot access the file because it is being used by another process.

  NestedZipFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedZipFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedZipFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedZipFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedZipFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedZipFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null

Tests run: 17, Failures: 8, Errors: 0, Skipped: 0
```

reported by @FainTMako on Window 7



Hints:
same for nested jar:

```
Results :

Failed tests:
  NestedJarFileResolverTest>AsyncTestBase.lambda$onSuccess$1:622->FileResolverTestBase.lambda$testCacheDirDeletedOnVertxClose$3:197->AsyncTestBase.assertFalse:208 null
  NestedJarFileResolverTest>AsyncTestBase.lambda$onSuccess$1:620->AsyncTestBase.fail:262 java.nio.file.FileSystemException: C:\Users\FTM\Documents\NetBeansProjects\vert.x\.vertx\file-cache-57edbe91-cbfb-42f6-ac78-37a39f53a324\lib\nested.jar: The process cannot access the file because it is being used by another process.

  NestedJarFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedJarFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedJarFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedJarFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedJarFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null
  NestedJarFileResolverTest.after:60->AsyncTestBase.after:81->FileResolverTestBase.tearDown:52->AsyncTestBase.awaitLatch:592->AsyncTestBase.assertTrue:372 null

Tests run: 17, Failures: 8, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
```

Will have a look during my next windows sprint (should not be too far, as we also have an issue on nested JS modules)

There are some file handlers that we don't close... Investigating...


Patch:
diff --git a/src/main/java/io/vertx/core/Starter.java b/src/main/java/io/vertx/core/Starter.java
--- a/src/main/java/io/vertx/core/Starter.java
+++ b/src/main/java/io/vertx/core/Starter.java
@@ -477,14 +477,20 @@ private String readMainVerticleFromManifest() {
     try {
       Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
       while (resources.hasMoreElements()) {
-        Manifest manifest = new Manifest(resources.nextElement().openStream());
-        Attributes attributes = manifest.getMainAttributes();
-        String mainClass = attributes.getValue("Main-Class");
-        if (Starter.class.getName().equals(mainClass)) {
-          String theMainVerticle = attributes.getValue("Main-Verticle");
-          if (theMainVerticle != null) {
-            return theMainVerticle;
+        InputStream stream = null;
+        try {
+          stream = resources.nextElement().openStream();
+          Manifest manifest = new Manifest(stream);
+          Attributes attributes = manifest.getMainAttributes();
+          String mainClass = attributes.getValue("Main-Class");
+          if (Starter.class.getName().equals(mainClass)) {
+            String theMainVerticle = attributes.getValue("Main-Verticle");
+            if (theMainVerticle != null) {
+              return theMainVerticle;
+            }
           }
+        } finally {
+          closeQuietly(stream);
         }
       }
     } catch (IOException e) {
@@ -493,6 +499,16 @@ private String readMainVerticleFromManifest() {
     return null;
   }
 
+  private void closeQuietly(InputStream stream) {
+    if (stream != null) {
+      try {
+        stream.close();
+      } catch (IOException e) {
+        // Ignore it.
+      }
+    }
+  }
+
   private void displaySyntax() {
 
     String usage =
diff --git a/src/main/java/io/vertx/core/impl/FileResolver.java b/src/main/java/io/vertx/core/impl/FileResolver.java
--- a/src/main/java/io/vertx/core/impl/FileResolver.java
+++ b/src/main/java/io/vertx/core/impl/FileResolver.java
@@ -18,10 +18,8 @@
 
 import io.vertx.core.*;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
+import java.io.*;
+import java.io.Closeable;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.nio.file.FileAlreadyExistsException;
@@ -164,8 +162,8 @@ private synchronized File unpackFromFileURL(URL url, String fileName, ClassLoade
   }
 
   private synchronized File unpackFromJarURL(URL url, String fileName, ClassLoader cl) {
+    ZipFile zip = null;
     try {
-      ZipFile zip;
       String path = url.getPath();
       int idx1 = path.lastIndexOf(".jar!");
       if (idx1 == -1) {
@@ -208,11 +206,23 @@ private synchronized File unpackFromJarURL(URL url, String fileName, ClassLoader
       }
     } catch (IOException e) {
       throw new VertxException(e);
+    } finally {
+      closeQuietly(zip);
     }
 
     return new File(cacheDir, fileName);
   }
 
+  private void closeQuietly(Closeable zip) {
+    if (zip != null) {
+      try {
+        zip.close();
+      } catch (IOException e) {
+        // Ignored.
+      }
+    }
+  }
+
   /**
    * bundle:// urls are used by OSGi implementations to refer to a file contained in a bundle, or in a fragment. There
    * is not much we can do to get the file from it, except reading it from the url. This method copies the files by


Test patch:
diff --git a/src/test/java/io/vertx/core/logging/SLF4JLogDelegateTest.java b/src/test/java/io/vertx/core/logging/SLF4JLogDelegateTest.java
--- a/src/test/java/io/vertx/core/logging/SLF4JLogDelegateTest.java
+++ b/src/test/java/io/vertx/core/logging/SLF4JLogDelegateTest.java
@@ -29,6 +29,7 @@
 
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
 
 /**
  * Theses test checks the SLF4J log delegate. It assumes the binding used by SLF4J is slf4j-simple with the default
@@ -56,30 +57,30 @@ public void testInfo() {
     Logger logger = LoggerFactory.getLogger("my-slf4j-logger");
 
     String result = record(() -> logger.info("hello"));
-    assertEquals("[main] INFO my-slf4j-logger - hello\n", result);
+    assertContains("[main] INFO my-slf4j-logger - hello", result);
 
     result = record(() -> logger.info("exception", new NullPointerException()));
-    assertTrue(result.contains("[main] INFO my-slf4j-logger - exception\n"));
+    assertTrue(result.contains("[main] INFO my-slf4j-logger - exception"));
     assertTrue(result.contains("java.lang.NullPointerException"));
 
     result = record(() -> logger.info("hello {} and {}", "Paulo", "Julien"));
-    assertEquals("[main] INFO my-slf4j-logger - hello Paulo and Julien\n", result);
+    assertContains("[main] INFO my-slf4j-logger - hello Paulo and Julien", result);
 
     result = record(() -> logger.info("hello {}", "vert.x"));
-    assertEquals("[main] INFO my-slf4j-logger - hello vert.x\n", result);
+    assertContains("[main] INFO my-slf4j-logger - hello vert.x", result);
 
     result = record(() -> logger.info("hello {} - {}", "vert.x"));
-    assertEquals("[main] INFO my-slf4j-logger - hello vert.x - {}\n", result);
+    assertContains("[main] INFO my-slf4j-logger - hello vert.x - {}", result);
 
     result = record(() -> logger.info("hello {}", "vert.x", "foo"));
-    assertEquals("[main] INFO my-slf4j-logger - hello vert.x\n", result);
+    assertContains("[main] INFO my-slf4j-logger - hello vert.x", result);
 
     result = record(() -> logger.info("{}, an exception has been thrown", new IllegalStateException(), "Luke"));
-    assertTrue(result.contains("[main] INFO my-slf4j-logger - Luke, an exception has been thrown\n"));
+    assertTrue(result.contains("[main] INFO my-slf4j-logger - Luke, an exception has been thrown"));
     assertTrue(result.contains("java.lang.IllegalStateException"));
 
     result = record(() -> logger.info("{}, an exception has been thrown", "Luke", new IllegalStateException()));
-    assertTrue(result.contains("[main] INFO my-slf4j-logger - Luke, an exception has been thrown\n"));
+    assertTrue(result.contains("[main] INFO my-slf4j-logger - Luke, an exception has been thrown"));
     assertTrue(result.contains("java.lang.IllegalStateException"));
   }
 
@@ -87,62 +88,67 @@ public void testInfo() {
   public void testError() {
     Logger logger = LoggerFactory.getLogger("my-slf4j-logger");
     String result = record(() -> logger.error("hello"));
-    assertEquals("[main] ERROR my-slf4j-logger - hello\n", result);
+    assertContains("[main] ERROR my-slf4j-logger - hello", result);
 
     result = record(() -> logger.error("exception", new NullPointerException()));
-    assertTrue(result.contains("[main] ERROR my-slf4j-logger - exception\n"));
+    assertTrue(result.contains("[main] ERROR my-slf4j-logger - exception"));
     assertTrue(result.contains("java.lang.NullPointerException"));
 
     result = record(() -> logger.error("hello {} and {}", "Paulo", "Julien"));
-    assertEquals("[main] ERROR my-slf4j-logger - hello Paulo and Julien\n", result);
+    assertContains("[main] ERROR my-slf4j-logger - hello Paulo and Julien", result);
 
     result = record(() -> logger.error("hello {}", "vert.x"));
-    assertEquals("[main] ERROR my-slf4j-logger - hello vert.x\n", result);
+    assertContains("[main] ERROR my-slf4j-logger - hello vert.x", result);
 
     result = record(() -> logger.error("hello {} - {}", "vert.x"));
-    assertEquals("[main] ERROR my-slf4j-logger - hello vert.x - {}\n", result);
+    assertContains("[main] ERROR my-slf4j-logger - hello vert.x - {}", result);
 
     result = record(() -> logger.error("hello {}", "vert.x", "foo"));
-    assertEquals("[main] ERROR my-slf4j-logger - hello vert.x\n", result);
+    assertContains("[main] ERROR my-slf4j-logger - hello vert.x", result);
 
     result = record(() -> logger.error("{}, an exception has been thrown", new IllegalStateException(), "Luke"));
-    assertTrue(result.contains("[main] ERROR my-slf4j-logger - Luke, an exception has been thrown\n"));
+    assertTrue(result.contains("[main] ERROR my-slf4j-logger - Luke, an exception has been thrown"));
     assertTrue(result.contains("java.lang.IllegalStateException"));
 
     result = record(() -> logger.error("{}, an exception has been thrown", "Luke", new IllegalStateException()));
-    assertTrue(result.contains("[main] ERROR my-slf4j-logger - Luke, an exception has been thrown\n"));
+    assertTrue(result.contains("[main] ERROR my-slf4j-logger - Luke, an exception has been thrown"));
     assertTrue(result.contains("java.lang.IllegalStateException"));
   }
 
+  private void assertContains(String expectedExcerpt, String object) {
+    assertNotNull(object);
+    assertTrue(object.contains(expectedExcerpt));
+  }
+
   @Test
   public void testWarning() {
     Logger logger = LoggerFactory.getLogger("my-slf4j-logger");
 
     String result = record(() -> logger.warn("hello"));
-    assertEquals("[main] WARN my-slf4j-logger - hello\n", result);
+    assertContains("[main] WARN my-slf4j-logger - hello", result);
 
     result = record(() -> logger.warn("exception", new NullPointerException()));
-    assertTrue(result.contains("[main] WARN my-slf4j-logger - exception\n"));
+    assertTrue(result.contains("[main] WARN my-slf4j-logger - exception"));
     assertTrue(result.contains("java.lang.NullPointerException"));
 
     result = record(() -> logger.warn("hello {} and {}", "Paulo", "Julien"));
-    assertEquals("[main] WARN my-slf4j-logger - hello Paulo and Julien\n", result);
+    assertContains("[main] WARN my-slf4j-logger - hello Paulo and Julien", result);
 
     result = record(() -> logger.warn("hello {}", "vert.x"));
-    assertEquals("[main] WARN my-slf4j-logger - hello vert.x\n", result);
+    assertContains("[main] WARN my-slf4j-logger - hello vert.x", result);
 
     result = record(() -> logger.warn("hello {} - {}", "vert.x"));
-    assertEquals("[main] WARN my-slf4j-logger - hello vert.x - {}\n", result);
+    assertContains("[main] WARN my-slf4j-logger - hello vert.x - {}", result);
 
     result = record(() -> logger.warn("hello {}", "vert.x", "foo"));
-    assertEquals("[main] WARN my-slf4j-logger - hello vert.x\n", result);
+    assertContains("[main] WARN my-slf4j-logger - hello vert.x", result);
 
     result = record(() -> logger.warn("{}, an exception has been thrown", new IllegalStateException(), "Luke"));
-    assertTrue(result.contains("[main] WARN my-slf4j-logger - Luke, an exception has been thrown\n"));
+    assertTrue(result.contains("[main] WARN my-slf4j-logger - Luke, an exception has been thrown"));
     assertTrue(result.contains("java.lang.IllegalStateException"));
 
     result = record(() -> logger.warn("{}, an exception has been thrown", "Luke", new IllegalStateException()));
-    assertTrue(result.contains("[main] WARN my-slf4j-logger - Luke, an exception has been thrown\n"));
+    assertTrue(result.contains("[main] WARN my-slf4j-logger - Luke, an exception has been thrown"));
     assertTrue(result.contains("java.lang.IllegalStateException"));
   }