blob: 47da785c28f38949816190fcf1546472fbd36594 [file] [log] [blame]
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.dynalink.beans.test;
import static jdk.dynalink.StandardNamespace.ELEMENT;
import static jdk.dynalink.StandardNamespace.METHOD;
import static jdk.dynalink.StandardNamespace.PROPERTY;
import static jdk.dynalink.StandardOperation.CALL;
import static jdk.dynalink.StandardOperation.GET;
import static jdk.dynalink.StandardOperation.NEW;
import static jdk.dynalink.StandardOperation.REMOVE;
import static jdk.dynalink.StandardOperation.SET;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jdk.dynalink.CallSiteDescriptor;
import jdk.dynalink.DynamicLinker;
import jdk.dynalink.DynamicLinkerFactory;
import jdk.dynalink.NamedOperation;
import jdk.dynalink.NoSuchDynamicMethodException;
import jdk.dynalink.Operation;
import jdk.dynalink.beans.BeansLinker;
import jdk.dynalink.beans.StaticClass;
import jdk.dynalink.support.SimpleRelinkableCallSite;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class BeanLinkerTest {
private DynamicLinker linker;
private static final MethodHandles.Lookup MY_LOOKUP = MethodHandles.lookup();
@SuppressWarnings("unused")
@DataProvider
private static Object[][] flags() {
return new Object[][]{
{Boolean.FALSE},
{Boolean.TRUE}
};
}
// helpers to create callsite objects
private CallSite createCallSite(final boolean publicLookup, final Operation op, final MethodType mt) {
return linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
publicLookup ? MethodHandles.publicLookup() : MY_LOOKUP, op, mt)));
}
private CallSite createCallSite(final boolean publicLookup, final Operation op, final Object name, final MethodType mt) {
return createCallSite(publicLookup, op.named(name), mt);
}
private CallSite createGetMethodCallSite(final boolean publicLookup, final String name) {
return createCallSite(publicLookup, GET_METHOD, name, MethodType.methodType(Object.class, Object.class));
}
private static final MethodHandle throwArrayIndexOutOfBounds = findThrower("throwArrayIndexOutOfBounds");
private static final MethodHandle throwIndexOutOfBounds = findThrower("throwIndexOutOfBounds");
private static final Operation GET_PROPERTY = GET.withNamespace(PROPERTY);
private static final Operation GET_ELEMENT = GET.withNamespace(ELEMENT);
private static final Operation GET_METHOD = GET.withNamespace(METHOD);
private static final Operation SET_ELEMENT = SET.withNamespace(ELEMENT);
private static final Operation REMOVE_ELEMENT = REMOVE.withNamespace(ELEMENT);
private static final MethodHandle findThrower(final String name) {
try {
return MethodHandles.lookup().findStatic(BeanLinkerTest.class, name,
MethodType.methodType(Object.class, Object.class, Object.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
Assert.fail("Unexpected exception", e);
return null;
}
}
private static Object throwArrayIndexOutOfBounds(final Object receiver, final Object index) {
throw new ArrayIndexOutOfBoundsException(String.valueOf(index));
}
private static Object throwIndexOutOfBounds(final Object receiver, final Object index) {
throw new IndexOutOfBoundsException(String.valueOf(index));
}
@BeforeTest
public void initLinker() {
final DynamicLinkerFactory factory = new DynamicLinkerFactory();
factory.setFallbackLinkers(new BeansLinker((req, services) -> {
// This is a MissingMemberHandlerFactory that creates a missing
// member handler for element getters and setters that throw an
// ArrayIndexOutOfBoundsException when applied to an array and an
// IndexOutOfBoundsException when applied to a list.
final CallSiteDescriptor desc = req.getCallSiteDescriptor();
final Operation op = desc.getOperation();
final Operation baseOp = NamedOperation.getBaseOperation(op);
if (baseOp != GET_ELEMENT && baseOp != SET_ELEMENT && baseOp != REMOVE_ELEMENT) {
// We only handle GET_ELEMENT, SET_ELEMENT and REMOVE_ELEMENT.
return null;
}
final Object receiver = req.getReceiver();
Assert.assertNotNull(receiver);
final Class<?> clazz = receiver.getClass();
final MethodHandle throwerHandle;
if (clazz.isArray()) {
throwerHandle = throwArrayIndexOutOfBounds;
} else if (List.class.isAssignableFrom(clazz)) {
throwerHandle = throwIndexOutOfBounds;
} else {
Assert.fail("Unexpected receiver type " + clazz.getName());
return null;
}
final Object name = NamedOperation.getName(op);
final MethodHandle nameBoundHandle;
if (name == null) {
nameBoundHandle = throwerHandle;
} else {
// If the operation is for a fixed index, bind it
nameBoundHandle = MethodHandles.insertArguments(throwerHandle, 1, name);
}
final MethodType callSiteType = desc.getMethodType();
final MethodHandle arityMatchedHandle;
if (baseOp == SET_ELEMENT) {
// Drop "value" parameter for a setter
final int handleArity = nameBoundHandle.type().parameterCount();
arityMatchedHandle = MethodHandles.dropArguments(nameBoundHandle,
handleArity, callSiteType.parameterType(handleArity));
} else {
arityMatchedHandle = nameBoundHandle;
}
return arityMatchedHandle.asType(callSiteType);
}));
this.linker = factory.createLinker();
}
@AfterTest
public void afterTest() {
this.linker = null;
}
@Test(dataProvider = "flags")
public void getPropertyTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(Object.class, Object.class, String.class);
final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt);
Assert.assertEquals(cs.getTarget().invoke(new Object(), "class"), Object.class);
Assert.assertEquals(cs.getTarget().invoke(new Date(), "class"), Date.class);
}
@Test(dataProvider = "flags")
public void getPropertyNegativeTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(Object.class, Object.class, String.class);
final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt);
Assert.assertNull(cs.getTarget().invoke(new Object(), "DOES_NOT_EXIST"));
}
@Test(dataProvider = "flags")
public void getPropertyTest2(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(Object.class, Object.class);
final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "class", mt);
Assert.assertEquals(cs.getTarget().invoke(new Object()), Object.class);
Assert.assertEquals(cs.getTarget().invoke(new Date()), Date.class);
}
@Test(dataProvider = "flags")
public void getPropertyNegativeTest2(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(Object.class, Object.class);
final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "DOES_NOT_EXIST", mt);
try {
cs.getTarget().invoke(new Object());
throw new RuntimeException("Expected NoSuchDynamicMethodException");
} catch (final Throwable th) {
Assert.assertTrue(th instanceof NoSuchDynamicMethodException);
}
}
@Test(dataProvider = "flags")
public void getLengthPropertyTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(int.class, Object.class, String.class);
final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt);
Assert.assertEquals((int) cs.getTarget().invoke(new int[10], "length"), 10);
Assert.assertEquals((int) cs.getTarget().invoke(new String[33], "length"), 33);
}
@Test(dataProvider = "flags")
public void getElementTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(int.class, Object.class, int.class);
final CallSite cs = createCallSite(publicLookup, GET_ELEMENT, mt);
final int[] arr = {23, 42};
Assert.assertEquals((int) cs.getTarget().invoke(arr, 0), 23);
Assert.assertEquals((int) cs.getTarget().invoke(arr, 1), 42);
try {
final int x = (int) cs.getTarget().invoke(arr, -1);
throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
} catch (final ArrayIndexOutOfBoundsException ex) {
}
try {
final int x = (int) cs.getTarget().invoke(arr, arr.length);
throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
} catch (final ArrayIndexOutOfBoundsException ex) {
}
final List<Integer> list = new ArrayList<>();
list.add(23);
list.add(430);
list.add(-4354);
Assert.assertEquals((int) cs.getTarget().invoke(list, 0), (int) list.get(0));
Assert.assertEquals((int) cs.getTarget().invoke(list, 1), (int) list.get(1));
Assert.assertEquals((int) cs.getTarget().invoke(list, 2), (int) list.get(2));
try {
cs.getTarget().invoke(list, -1);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
try {
cs.getTarget().invoke(list, list.size());
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
}
private Object invokeWithFixedKey(boolean publicLookup, Operation op, Object name, MethodType mt, Object... args) throws Throwable {
return createCallSite(publicLookup, op.named(name), mt).getTarget().invokeWithArguments(args);
}
@Test(dataProvider = "flags")
public void getElementWithFixedKeyTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(int.class, Object.class);
final int[] arr = {23, 42};
Assert.assertEquals((int) invokeWithFixedKey(publicLookup, GET_ELEMENT, 0, mt, arr), 23);
Assert.assertEquals((int) invokeWithFixedKey(publicLookup, GET_ELEMENT, 1, mt, arr), 42);
try {
invokeWithFixedKey(publicLookup, GET_ELEMENT, -1, mt, arr);
throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
} catch (final ArrayIndexOutOfBoundsException ex) {
}
try {
invokeWithFixedKey(publicLookup, GET_ELEMENT, arr.length, mt, arr);
throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
} catch (final ArrayIndexOutOfBoundsException ex) {
}
final List<Integer> list = new ArrayList<>();
list.add(23);
list.add(430);
list.add(-4354);
for (int i = 0; i < 3; ++i) {
Assert.assertEquals((int) invokeWithFixedKey(publicLookup, GET_ELEMENT, i, mt, list), (int) list.get(i));
}
try {
invokeWithFixedKey(publicLookup, GET_ELEMENT, -1, mt, list);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
try {
invokeWithFixedKey(publicLookup, GET_ELEMENT, list.size(), mt, list);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
}
@Test(dataProvider = "flags")
public void setElementTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(void.class, Object.class, int.class, int.class);
final CallSite cs = createCallSite(publicLookup, SET_ELEMENT, mt);
final int[] arr = {23, 42};
cs.getTarget().invoke(arr, 0, 0);
Assert.assertEquals(arr[0], 0);
cs.getTarget().invoke(arr, 1, -5);
Assert.assertEquals(arr[1], -5);
try {
cs.getTarget().invoke(arr, -1, 12);
throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
} catch (final ArrayIndexOutOfBoundsException ex) {
}
try {
cs.getTarget().invoke(arr, arr.length, 20);
throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
} catch (final ArrayIndexOutOfBoundsException ex) {
}
final List<Integer> list = new ArrayList<>();
list.add(23);
list.add(430);
list.add(-4354);
cs.getTarget().invoke(list, 0, -list.get(0));
Assert.assertEquals((int) list.get(0), -23);
cs.getTarget().invoke(list, 1, -430);
Assert.assertEquals((int) list.get(1), -430);
cs.getTarget().invoke(list, 2, 4354);
Assert.assertEquals((int) list.get(2), 4354);
try {
cs.getTarget().invoke(list, -1, 343);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
try {
cs.getTarget().invoke(list, list.size(), 43543);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
}
@Test(dataProvider = "flags")
public void setElementWithFixedKeyTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(void.class, Object.class, int.class);
final int[] arr = {23, 42};
invokeWithFixedKey(publicLookup, SET_ELEMENT, 0, mt, arr, 0);
Assert.assertEquals(arr[0], 0);
invokeWithFixedKey(publicLookup, SET_ELEMENT, 1, mt, arr, -5);
Assert.assertEquals(arr[1], -5);
try {
invokeWithFixedKey(publicLookup, SET_ELEMENT, -1, mt, arr, 12);
throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
} catch (final ArrayIndexOutOfBoundsException ex) {
}
try {
invokeWithFixedKey(publicLookup, SET_ELEMENT, arr.length, mt, arr, 20);
throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
} catch (final ArrayIndexOutOfBoundsException ex) {
}
final List<Integer> list = new ArrayList<>();
list.add(23);
list.add(430);
list.add(-4354);
invokeWithFixedKey(publicLookup, SET_ELEMENT, 0, mt, list, -list.get(0));
Assert.assertEquals((int) list.get(0), -23);
invokeWithFixedKey(publicLookup, SET_ELEMENT, 1, mt, list, -430);
Assert.assertEquals((int) list.get(1), -430);
invokeWithFixedKey(publicLookup, SET_ELEMENT, 2, mt, list, 4354);
Assert.assertEquals((int) list.get(2), 4354);
try {
invokeWithFixedKey(publicLookup, SET_ELEMENT, -1, mt, list, 343);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
try {
invokeWithFixedKey(publicLookup, SET_ELEMENT, list.size(), mt, list, 43543);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
}
@Test(dataProvider = "flags")
public void newObjectTest(final boolean publicLookup) {
final MethodType mt = MethodType.methodType(Object.class, Object.class);
final CallSite cs = createCallSite(publicLookup, NEW, mt);
Object obj = null;
try {
obj = cs.getTarget().invoke(StaticClass.forClass(Date.class));
} catch (final Throwable th) {
throw new RuntimeException(th);
}
Assert.assertTrue(obj instanceof Date);
}
@Test(dataProvider = "flags")
public void staticPropertyTest(final boolean publicLookup) {
final MethodType mt = MethodType.methodType(Object.class, Class.class);
final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "static", mt);
Object obj = null;
try {
obj = cs.getTarget().invoke(Object.class);
} catch (final Throwable th) {
throw new RuntimeException(th);
}
Assert.assertTrue(obj instanceof StaticClass);
Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Object.class);
try {
obj = cs.getTarget().invoke(Date.class);
} catch (final Throwable th) {
throw new RuntimeException(th);
}
Assert.assertTrue(obj instanceof StaticClass);
Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Date.class);
try {
obj = cs.getTarget().invoke(Object[].class);
} catch (final Throwable th) {
throw new RuntimeException(th);
}
Assert.assertTrue(obj instanceof StaticClass);
Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Object[].class);
}
@Test(dataProvider = "flags")
public void instanceMethodCallTest(final boolean publicLookup) {
final CallSite cs = createGetMethodCallSite(publicLookup, "getClass");
final MethodType mt2 = MethodType.methodType(Class.class, Object.class, Object.class);
final CallSite cs2 = createCallSite(publicLookup, CALL, mt2);
Object method = null;
try {
method = cs.getTarget().invoke(new Date());
} catch (final Throwable th) {
throw new RuntimeException(th);
}
Assert.assertNotNull(method);
Assert.assertTrue(BeansLinker.isDynamicMethod(method));
Class clz = null;
try {
clz = (Class) cs2.getTarget().invoke(method, new Date());
} catch (final Throwable th) {
throw new RuntimeException(th);
}
Assert.assertEquals(clz, Date.class);
}
@Test(dataProvider = "flags")
public void staticMethodCallTest(final boolean publicLookup) {
final CallSite cs = createGetMethodCallSite(publicLookup, "getProperty");
final MethodType mt2 = MethodType.methodType(String.class, Object.class, Object.class, String.class);
final CallSite cs2 = createCallSite(publicLookup, CALL, mt2);
Object method = null;
try {
method = cs.getTarget().invoke(StaticClass.forClass(System.class));
} catch (final Throwable th) {
throw new RuntimeException(th);
}
Assert.assertNotNull(method);
Assert.assertTrue(BeansLinker.isDynamicMethod(method));
String str = null;
try {
str = (String) cs2.getTarget().invoke(method, null, "os.name");
} catch (final Throwable th) {
throw new RuntimeException(th);
}
Assert.assertEquals(str, System.getProperty("os.name"));
}
// try calling System.getenv and expect security exception
@Test(dataProvider = "flags")
public void systemGetenvTest(final boolean publicLookup) {
final CallSite cs1 = createGetMethodCallSite(publicLookup, "getenv");
final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(Object.class, Object.class, Object.class));
try {
final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class));
cs2.getTarget().invoke(method, StaticClass.forClass(System.class));
throw new RuntimeException("should not reach here in any case!");
} catch (final Throwable th) {
Assert.assertTrue(th instanceof SecurityException);
}
}
// try getting a specific sensitive System property and expect security exception
@Test(dataProvider = "flags")
public void systemGetPropertyTest(final boolean publicLookup) {
final CallSite cs1 = createGetMethodCallSite(publicLookup, "getProperty");
final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(String.class, Object.class, Object.class, String.class));
try {
final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class));
cs2.getTarget().invoke(method, StaticClass.forClass(System.class), "java.home");
throw new RuntimeException("should not reach here in any case!");
} catch (final Throwable th) {
Assert.assertTrue(th instanceof SecurityException);
}
}
// check a @CallerSensitive API and expect appropriate access check exception
@Test(dataProvider = "flags")
public void systemLoadLibraryTest(final boolean publicLookup) {
final CallSite cs1 = createGetMethodCallSite(publicLookup, "loadLibrary");
final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(void.class, Object.class, Object.class, String.class));
try {
final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class));
cs2.getTarget().invoke(method, StaticClass.forClass(System.class), "foo");
throw new RuntimeException("should not reach here in any case!");
} catch (final Throwable th) {
if (publicLookup) {
Assert.assertTrue(th instanceof IllegalAccessError);
} else {
Assert.assertTrue(th instanceof AccessControlException);
}
}
}
@Test(dataProvider = "flags")
public void removeElementFromListTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(void.class, Object.class, int.class);
final CallSite cs = createCallSite(publicLookup, REMOVE_ELEMENT, mt);
final List<Integer> list = new ArrayList<>(List.of(23, 430, -4354));
cs.getTarget().invoke(list, 1);
Assert.assertEquals(list, List.of(23, -4354));
cs.getTarget().invoke(list, 1);
Assert.assertEquals(list, List.of(23));
cs.getTarget().invoke(list, 0);
Assert.assertEquals(list, List.of());
try {
cs.getTarget().invoke(list, -1);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
try {
cs.getTarget().invoke(list, list.size());
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
}
@Test(dataProvider = "flags")
public void removeElementFromListWithFixedKeyTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(void.class, Object.class);
final List<Integer> list = new ArrayList<>(List.of(23, 430, -4354));
createCallSite(publicLookup, REMOVE_ELEMENT.named(1), mt).getTarget().invoke(list);
Assert.assertEquals(list, List.of(23, -4354));
createCallSite(publicLookup, REMOVE_ELEMENT.named(1), mt).getTarget().invoke(list);
Assert.assertEquals(list, List.of(23));
createCallSite(publicLookup, REMOVE_ELEMENT.named(0), mt).getTarget().invoke(list);
Assert.assertEquals(list, List.of());
try {
createCallSite(publicLookup, REMOVE_ELEMENT.named(-1), mt).getTarget().invoke(list);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
try {
createCallSite(publicLookup, REMOVE_ELEMENT.named(list.size()), mt).getTarget().invoke(list);
throw new RuntimeException("expected IndexOutOfBoundsException");
} catch (final IndexOutOfBoundsException ex) {
}
}
@Test(dataProvider = "flags")
public void removeElementFromMapTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(void.class, Object.class, Object.class);
final CallSite cs = createCallSite(publicLookup, REMOVE_ELEMENT, mt);
final Map<String, String> map = new HashMap<>(Map.of("k1", "v1", "k2", "v2", "k3", "v3"));
cs.getTarget().invoke(map, "k2");
Assert.assertEquals(map, Map.of("k1", "v1", "k3", "v3"));
cs.getTarget().invoke(map, "k4");
Assert.assertEquals(map, Map.of("k1", "v1", "k3", "v3"));
cs.getTarget().invoke(map, "k1");
Assert.assertEquals(map, Map.of("k3", "v3"));
}
@Test(dataProvider = "flags")
public void removeElementFromMapWithFixedKeyTest(final boolean publicLookup) throws Throwable {
final MethodType mt = MethodType.methodType(void.class, Object.class);
final Map<String, String> map = new HashMap<>(Map.of("k1", "v1", "k2", "v2", "k3", "v3"));
createCallSite(publicLookup, REMOVE_ELEMENT.named("k2"), mt).getTarget().invoke(map);
Assert.assertEquals(map, Map.of("k1", "v1", "k3", "v3"));
createCallSite(publicLookup, REMOVE_ELEMENT.named("k4"), mt).getTarget().invoke(map);
Assert.assertEquals(map, Map.of("k1", "v1", "k3", "v3"));
createCallSite(publicLookup, REMOVE_ELEMENT.named("k1"), mt).getTarget().invoke(map);
Assert.assertEquals(map, Map.of("k3", "v3"));
}
}