Repository: assertj/assertj (Java)

Issue:
Updating from 3.11.1 to 3.14.0 breaks
When I try to upgrade assertj-core from 3.11.1 to the latest version 3.14.0. The following code breaks.
```
import static org.assertj.core.api.Assertions.assertThat;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

import org.junit.Test;

public class Testassertj {

    @Test
    public void demo() {
        
        Map<Path, String> test = new HashMap<Path, String>();
        Path path = Paths.get("/tmp/test/file");
        test.put(path, "69bf414bf3c9e4a1f9b08bc1bdfbf758b2deb65225eed120e4ff80a065099bd3cfee2f8b815fa2e64206eb447fbd1690a3d196f344852df68f3d45dc820a2cad");
        assertThat(test)
        .containsOnlyKeys(path)
        .containsValue("69bf414bf3c9e4a1f9b08bc1bdfbf758b2deb65225eed120e4ff80a065099bd3cfee2f8b815fa2e64206eb447fbd1690a3d196f344852df68f3d45dc820a2cad");
    }
}
```

The test should pass, but it failed with error:
```
Testassertj
demo(Testassertj)
java.lang.AssertionError: 
Expecting:
  <{/tmp/test/file="69bf414bf3c9e4a1f9b08bc1bdfbf758b2deb65225eed120e4ff80a065099bd3cfee2f8b815fa2e64206eb447fbd1690a3d196f344852df68f3d45dc820a2cad"}>
to contain only following keys:
  <[tmp, test, file]>
keys not found:
  <[tmp, test, file]>
and keys not expected:
  <[/tmp/test/file]>

	at Testassertj.demo(Testassertj.java:22)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
```



Hints:
Thanks for reporting this, we will look at it shortly.
The (subtle) issue is the addition of `containsOnlyKeys(Iterable<? extends K> keys)` in 3.12.0, it turns out to be a breaking change when given a `Path`, as `Path` is an `Iterable` the new method is called which check the elements (`tmp, test, file`) instead of the given `Path` itself.

In this case the best way forward is not to use `containsOnlyKeys` but a combination of `hasSize` chained with `containsKeys` (which does not have an overload with Iterable<? extends K> keys).

There is no way to fix this issue, except by removing the assertion but I'm not sure we should do that since it can be useful for "real" iterables.
At minimum, we will document the breaking change and add a warning in the javadoc.

 
What if we change `containsOnlyKeys(Iterable<? extends K> keys)` to:

```java
  public SELF containsOnlyKeys(Iterable<? extends K> keys) {
    if (keys instanceof Path) {
      K path = (K) keys;
      maps.assertContainsOnlyKeys(info, actual, path); // assertContainsOnlyKeys(AssertionInfo, Map<K, V>, K...)
    } else {
      maps.assertContainsOnlyKeys(info, actual, keys); // assertContainsOnlyKeys(AssertionInfo, Map<K, V>, Iterable<? extends K>)
    }
    return myself;
  }
```

In this case `Path`cannot be used as an `Iterable` anymore, but probably this is not a real use case.

Patch:
diff --git a/src/main/java/org/assertj/core/api/AbstractMapAssert.java b/src/main/java/org/assertj/core/api/AbstractMapAssert.java
--- a/src/main/java/org/assertj/core/api/AbstractMapAssert.java
+++ b/src/main/java/org/assertj/core/api/AbstractMapAssert.java
@@ -22,6 +22,7 @@
 import static org.assertj.core.util.Arrays.isArray;
 import static org.assertj.core.util.IterableUtil.toCollection;
 
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashSet;
@@ -1146,8 +1147,15 @@ public SELF containsOnlyKeys(@SuppressWarnings("unchecked") K... keys) {
    * @throws IllegalArgumentException if the given argument is an empty array.
    * @since 3.12.0
    */
+  @SuppressWarnings("unchecked")
   public SELF containsOnlyKeys(Iterable<? extends K> keys) {
-    maps.assertContainsOnlyKeys(info, actual, keys);
+    if (keys instanceof Path) {
+      // do not treat Path as an Iterable
+      K path = (K) keys;
+      maps.assertContainsOnlyKeys(info, actual, path);
+    } else {
+      maps.assertContainsOnlyKeys(info, actual, keys);
+    }
     return myself;
   }
 


Test patch:
diff --git a/src/test/java/org/assertj/core/api/map/MapAssert_containsOnlyKeys_with_Path_Test.java b/src/test/java/org/assertj/core/api/map/MapAssert_containsOnlyKeys_with_Path_Test.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/org/assertj/core/api/map/MapAssert_containsOnlyKeys_with_Path_Test.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2019 the original author or authors.
+ */
+package org.assertj.core.api.map;
+
+import static org.mockito.Mockito.verify;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.assertj.core.api.MapAssert;
+import org.assertj.core.api.MapAssertBaseTest;
+import org.junit.jupiter.api.DisplayName;
+
+/**
+ * Tests for <code>{@link MapAssert#containsOnlyKeys(Iterable)}</code> with {@link Path} parameter.
+ *
+ * @author Stefano Cordio
+ */
+@DisplayName("MapAssert containsOnlyKeys(Path)")
+class MapAssert_containsOnlyKeys_with_Path_Test extends MapAssertBaseTest {
+
+  private final Path path = Paths.get("file");
+
+  @Override
+  protected MapAssert<Object, Object> invoke_api_method() {
+    return assertions.containsOnlyKeys(path);
+  }
+
+  @Override
+  protected void verify_internal_effects() {
+    // Path parameter should be treated as a single key and not as an Iterable, therefore the target for verification is
+    // assertContainsOnlyKeys(AssertionInfo, Map<K, V>, K...) instead of
+    // assertContainsOnlyKeys(AssertionInfo, Map<K, V>, Iterable<? extends K>).
+    // Casting the Path parameter to Object allows to invoke the overloaded method expecting vararg keys.
+    verify(maps).assertContainsOnlyKeys(getInfo(assertions), getActual(assertions), (Object) path);
+  }
+
+}