8057719: Develop new tests for LambdaForm Reduction and Caching feature
authorkshefov
Fri, 12 Sep 2014 22:33:09 +0400
changeset 26600 27b115fe8b3c
parent 26599 e82af948593b
child 26614 0c77cea106f6
8057719: Develop new tests for LambdaForm Reduction and Caching feature Reviewed-by: iignatyev, vlivanov, psandoz
jdk/test/java/lang/invoke/LFCaching/LFCachingTestCase.java
jdk/test/java/lang/invoke/LFCaching/LFGarbageCollectedTest.java
jdk/test/java/lang/invoke/LFCaching/LFMultiThreadCachingTest.java
jdk/test/java/lang/invoke/LFCaching/LFSingleThreadCachingTest.java
jdk/test/java/lang/invoke/LFCaching/LambdaFormTestCase.java
jdk/test/java/lang/invoke/LFCaching/TestMethods.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/LFCaching/LFCachingTestCase.java	Fri Sep 12 22:33:09 2014 +0400
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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.
+ */
+
+import java.lang.invoke.MethodHandle;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Abstract class for lambda forms caching testing.
+ *
+ * @author kshefov
+ */
+public abstract class LFCachingTestCase extends LambdaFormTestCase {
+
+    /**
+     * Constructor for lambda forms caching test case.
+     *
+     * @param testMethod A method from {@code j.l.i.MethodHandles} class that
+     * returns a {@code j.l.i.MethodHandle} instance.
+     */
+    protected LFCachingTestCase(TestMethods testMethod) {
+        super(testMethod);
+    }
+
+    /**
+     * Checks that the lambda forms of the two adapter method handles adapter1
+     * and adapter2 are the same.
+     *
+     * @param adapter1 First method handle.
+     * @param adapter2 Second method handle.
+     */
+    public void checkLFCaching(MethodHandle adapter1, MethodHandle adapter2) {
+        try {
+
+            if (!adapter1.type().equals(adapter2.type())) {
+                throw new Error("TESTBUG: Types of the two method handles are not the same");
+            }
+
+            Object lambdaForm0 = LambdaFormTestCase.INTERNAL_FORM.invoke(adapter1);
+            Object lambdaForm1 = LambdaFormTestCase.INTERNAL_FORM.invoke(adapter2);
+
+            if (lambdaForm0 == null || lambdaForm1 == null) {
+                throw new Error("Unexpected error: One or both lambda forms of the method handles are null");
+            }
+
+            if (lambdaForm0 != lambdaForm1) {
+                System.err.println("Lambda form 0 toString is:");
+                System.err.println(lambdaForm0);
+                System.err.println("Lambda form 1 toString is:");
+                System.err.println(lambdaForm1);
+                throw new AssertionError("Error: Lambda forms of the two method handles"
+                        + " are not the same. LF cahing does not work");
+            }
+        } catch (IllegalAccessException | IllegalArgumentException |
+                SecurityException | InvocationTargetException ex) {
+            throw new Error("Unexpected exception: ", ex);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/LFCaching/LFGarbageCollectedTest.java	Fri Sep 12 22:33:09 2014 +0400
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test LFGarbageCollectedTest
+ * @bug 8046703
+ * @summary Test verifies that lambda forms are garbage collected
+ * @author kshefov
+ * @library /lib/testlibrary/jsr292 /lib/testlibrary
+ * @build TestMethods
+ * @build LambdaFormTestCase
+ * @build LFGarbageCollectedTest
+ * @run main/othervm/timeout=600 -Djava.lang.invoke.MethodHandle.USE_LF_EDITOR=true -DtestLimit=150 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI LFGarbageCollectedTest
+ */
+
+import java.lang.invoke.MethodHandle;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Map;
+
+/**
+ * Lambda forms garbage collection test class.
+ */
+public final class LFGarbageCollectedTest extends LambdaFormTestCase {
+
+    /**
+     * Constructor for a lambda forms garbage collection test case.
+     *
+     * @param testMethod A method from {@code j.l.i.MethodHandles} class that
+     * returns a {@code j.l.i.MethodHandle} instance.
+     */
+    public LFGarbageCollectedTest(TestMethods testMethod) {
+        super(testMethod);
+    }
+
+    @Override
+    public void doTest() {
+        try {
+            Map<String, Object> data = getTestMethod().getTestCaseData();
+            MethodHandle adapter;
+            try {
+                adapter = getTestMethod().getTestCaseMH(data, TestMethods.Kind.ONE);
+            } catch (NoSuchMethodException ex) {
+                throw new Error("Unexpected exception: ", ex);
+            }
+            Object lambdaForm = LambdaFormTestCase.INTERNAL_FORM.invoke(adapter);
+            if (lambdaForm == null) {
+                throw new Error("Unexpected error: Lambda form of the method handle is null");
+            }
+            ReferenceQueue rq = new ReferenceQueue();
+            PhantomReference ph = new PhantomReference(lambdaForm, rq);
+            lambdaForm = null;
+            data = null;
+            adapter = null;
+            for (int i = 0; i < 1000 && !ph.isEnqueued(); i++) {
+                System.gc();
+            }
+            if (!ph.isEnqueued()) {
+                throw new AssertionError("Error: Lambda form is not garbage collected");
+            }
+        } catch (IllegalAccessException | IllegalArgumentException |
+                InvocationTargetException ex) {
+            throw new Error("Unexpected exception: ", ex);
+        }
+    }
+
+    /**
+     * Main routine for lambda forms garbage collection test.
+     *
+     * @param args Accepts no arguments.
+     */
+    public static void main(String[] args) {
+        // The "identity" and "constant" methods should be removed from this test,
+        // because their lambda forms are stored in a static filed and are not GC'ed.
+        // There can be only 5 such LFs for each method, so no memory leak happens.
+        EnumSet<TestMethods> testMethods = EnumSet.complementOf(EnumSet.of(TestMethods.IDENTITY, TestMethods.CONSTANT));
+        LambdaFormTestCase.runTests(LFGarbageCollectedTest::new, testMethods);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/LFCaching/LFMultiThreadCachingTest.java	Fri Sep 12 22:33:09 2014 +0400
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test LFMultiThreadCachingTest
+ * @bug 8046703
+ * @summary Test verifies that lambda forms are cached when run with multiple threads
+ * @author kshefov
+ * @library /lib/testlibrary/jsr292 /lib/testlibrary
+ * @build TestMethods
+ * @build LambdaFormTestCase
+ * @build LFCachingTestCase
+ * @build LFMultiThreadCachingTest
+ * @run main/othervm/timeout=300 -Djava.lang.invoke.MethodHandle.USE_LF_EDITOR=true LFMultiThreadCachingTest
+ */
+
+import java.lang.invoke.MethodHandle;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+
+/**
+ * Multiple threaded lambda forms caching test class.
+ */
+public final class LFMultiThreadCachingTest extends LFCachingTestCase {
+    private static final TestMethods.Kind[] KINDS;
+    static {
+        EnumSet<TestMethods.Kind> set = EnumSet.complementOf(EnumSet.of(TestMethods.Kind.EXCEPT));
+        KINDS = set.toArray(new TestMethods.Kind[set.size()]);
+        if (KINDS.length < 2) {
+            throw new Error("TESTBUG: KINDS.length[" + KINDS.length + "] should be at least 2");
+        }
+    }
+    private static final int CORES = Math.max(KINDS.length, Runtime.getRuntime().availableProcessors());
+
+    /**
+     * Constructor a for multiple threaded lambda forms caching test case.
+     *
+     * @param testMethod A method from {@code j.l.i.MethodHandles} class that
+     * returns a {@code j.l.i.MethodHandle} instance.
+     */
+    public LFMultiThreadCachingTest(TestMethods testMethod) {
+        super(testMethod);
+    }
+
+    @Override
+    public void doTest() {
+        Map<String, Object> data = getTestMethod().getTestCaseData();
+        ConcurrentLinkedQueue<MethodHandle> adapters = new ConcurrentLinkedQueue<>();
+        CyclicBarrier begin = new CyclicBarrier(CORES);
+        CountDownLatch end = new CountDownLatch(CORES);
+        for (int i = 0; i < CORES; ++i) {
+            TestMethods.Kind kind = KINDS[i % KINDS.length];
+            new Thread(() -> {
+                try {
+                    begin.await();
+                    adapters.add(getTestMethod().getTestCaseMH(data, kind));
+                } catch (InterruptedException | BrokenBarrierException | IllegalAccessException | NoSuchMethodException ex) {
+                    throw new Error("Unexpected exception: ", ex);
+                } finally {
+                    end.countDown();
+                }
+            }).start();
+        }
+        try {
+            end.await();
+        } catch (InterruptedException ex) {
+            throw new Error("Unexpected exception: ", ex);
+        }
+        if (adapters.size() < CORES) {
+            throw new Error("adapters size[" + adapters.size() + "] is less than " + CORES);
+        }
+        MethodHandle prev = adapters.poll();
+        for (MethodHandle current : adapters) {
+            checkLFCaching(prev, current);
+            prev = current;
+        }
+    }
+
+    /**
+     * Main routine for multiple threaded lambda forms caching test.
+     *
+     * @param args Accepts no arguments.
+     */
+    public static void main(String[] args) {
+        LambdaFormTestCase.runTests(LFMultiThreadCachingTest::new, EnumSet.allOf(TestMethods.class));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/LFCaching/LFSingleThreadCachingTest.java	Fri Sep 12 22:33:09 2014 +0400
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test LFSingleThreadCachingTest
+ * @bug 8046703
+ * @summary Test verifies that lambda forms are cached when run with single thread
+ * @author kshefov
+ * @library /lib/testlibrary/jsr292 /lib/testlibrary
+ * @build TestMethods
+ * @build LambdaFormTestCase
+ * @build LFCachingTestCase
+ * @build LFSingleThreadCachingTest
+ * @run main/othervm/timeout=300 -Djava.lang.invoke.MethodHandle.USE_LF_EDITOR=true LFSingleThreadCachingTest
+ */
+
+import java.lang.invoke.MethodHandle;
+import java.util.EnumSet;
+import java.util.Map;
+
+/**
+ * Single threaded lambda forms caching test class.
+ */
+public final class LFSingleThreadCachingTest extends LFCachingTestCase {
+
+    /**
+     * Constructor for a single threaded lambda forms caching test case.
+     *
+     * @param testMethod A method from {@code j.l.i.MethodHandles} class that
+     * returns a {@code j.l.i.MethodHandle} instance.
+     */
+    public LFSingleThreadCachingTest(TestMethods testMethod) {
+        super(testMethod);
+    }
+
+    @Override
+    public void doTest() {
+        MethodHandle adapter1;
+        MethodHandle adapter2;
+        Map<String, Object> data = getTestMethod().getTestCaseData();
+        try {
+            adapter1 = getTestMethod().getTestCaseMH(data, TestMethods.Kind.ONE);
+            adapter2 = getTestMethod().getTestCaseMH(data, TestMethods.Kind.TWO);
+        } catch (NoSuchMethodException | IllegalAccessException ex) {
+            throw new Error("Unexpected exception: ", ex);
+        }
+        checkLFCaching(adapter1, adapter2);
+    }
+
+    /**
+     * Main routine for single threaded lambda forms caching test.
+     *
+     * @param args Accepts no arguments.
+     */
+    public static void main(String[] args) {
+        LambdaFormTestCase.runTests(LFSingleThreadCachingTest::new, EnumSet.allOf(TestMethods.class));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/LFCaching/LambdaFormTestCase.java	Fri Sep 12 22:33:09 2014 +0400
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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.
+ */
+
+import com.oracle.testlibrary.jsr292.Helper;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.function.Function;
+
+/**
+ * Lambda forms caching test case class. Contains all necessary test routines to
+ * test lambda forms caching in method handles returned by methods of
+ * MethodHandles class.
+ *
+ * @author kshefov
+ */
+public abstract class LambdaFormTestCase {
+
+    private final static String METHOD_HANDLE_CLASS_NAME = "java.lang.invoke.MethodHandle";
+    private final static String INTERNAL_FORM_METHOD_NAME = "internalForm";
+
+    /**
+     * Reflection link to {@code j.l.i.MethodHandle.internalForm} method. It is
+     * used to get a lambda form from a method handle.
+     */
+    protected final static Method INTERNAL_FORM;
+
+    static {
+        try {
+            Class mhClass = Class.forName(METHOD_HANDLE_CLASS_NAME);
+            INTERNAL_FORM = mhClass.getDeclaredMethod(INTERNAL_FORM_METHOD_NAME);
+            INTERNAL_FORM.setAccessible(true);
+        } catch (Exception ex) {
+            throw new Error("Unexpected exception: ", ex);
+        }
+    }
+
+    private final TestMethods testMethod;
+
+    /**
+     * Test case constructor. Generates test cases with random method types for
+     * given methods form {@code j.l.i.MethodHandles} class.
+     *
+     * @param testMethod A method from {@code j.l.i.MethodHandles} class which
+     * returns a {@code j.l.i.MethodHandle}.
+     */
+    protected LambdaFormTestCase(TestMethods testMethod) {
+        this.testMethod = testMethod;
+    }
+
+    public TestMethods getTestMethod() {
+        return testMethod;
+    }
+
+    /**
+     * Routine that executes a test case.
+     */
+    public abstract void doTest();
+
+    /**
+     * Runs a number of test cases defined by the size of testCases list.
+     *
+     * @param ctor constructor of LambdaFormCachingTest or its child classes
+     * object.
+     * @param testMethods list of test methods
+     */
+    public static void runTests(Function<TestMethods, LambdaFormTestCase> ctor, Collection<TestMethods> testMethods) {
+        boolean passed = true;
+        int testCounter = 0;
+        int failCounter = 0;
+        long iterations = Math.max(1, Helper.TEST_LIMIT / testMethods.size());
+        for (long i = 0; i < iterations; i++) {
+            System.err.println(String.format("Iteration %d:", i));
+            for (TestMethods testMethod : testMethods) {
+                LambdaFormTestCase testCase = ctor.apply(testMethod);
+                try {
+                    System.err.printf("Tested LF caching feature with MethodHandles.%s method.%n",
+                            testCase.getTestMethod().name);
+                    testCase.doTest();
+                    System.err.println("PASSED");
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                    System.err.println("FAILED");
+                    passed = false;
+                    failCounter++;
+                }
+                testCounter++;
+            }
+        }
+        if (!passed) {
+            throw new Error(String.format("%d of %d test cases FAILED! %n"
+                    + "Rerun the test with the same \"-Dseed=\" option as in the log file!",
+                    failCounter, testCounter));
+        } else {
+            System.err.println(String.format("All %d test cases PASSED!", testCounter));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/LFCaching/TestMethods.java	Fri Sep 12 22:33:09 2014 +0400
@@ -0,0 +1,698 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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.
+ */
+
+import com.oracle.testlibrary.jsr292.Helper;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Enumeration containing information about methods from
+ * {@code j.l.i.MethodHandles} class that are used for testing lambda forms
+ * caching.
+ *
+ * @author kshefov
+ */
+public enum TestMethods {
+
+    FOLD_ARGUMENTS("foldArguments") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int modifierMHArgNum = Helper.RNG.nextInt(realArity + 1);
+                    data.put("modifierMHArgNum", modifierMHArgNum);
+                    Class<?> combinerReturnType;
+                    if (realArity == 0) {
+                        combinerReturnType = void.class;
+                    } else {
+                        combinerReturnType = Helper.RNG.nextBoolean() ? void.class : mtTarget.parameterType(0);
+                    }
+                    data.put("combinerReturnType", combinerReturnType);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    Class<?> combinerReturnType = (Class) data.get("combinerReturnType");
+                    int modifierMHArgNum = (int) data.get("modifierMHArgNum");
+                    MethodHandle target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), kind);
+                    Class<?> rType = mtTarget.returnType();
+                    int combListStart = (combinerReturnType == void.class) ? 0 : 1;
+                    if (modifierMHArgNum < combListStart) {
+                        modifierMHArgNum = combListStart;
+                    }
+                    MethodHandle combiner = TestMethods.methodHandleGenerator(combinerReturnType,
+                            mtTarget.parameterList().subList(combListStart,
+                                    modifierMHArgNum), kind);
+                    return MethodHandles.foldArguments(target, combiner);
+                }
+            },
+    DROP_ARGUMENTS("dropArguments") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int dropArgsPos = Helper.RNG.nextInt(realArity + 1);
+                    data.put("dropArgsPos", dropArgsPos);
+                    MethodType mtDropArgs = TestMethods.randomMethodTypeGenerator(
+                            Helper.RNG.nextInt(Helper.MAX_ARITY - realArity));
+                    data.put("mtDropArgs", mtDropArgs);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    MethodType mtDropArgs = (MethodType) data.get("mtDropArgs");
+                    int dropArgsPos = (int) data.get("dropArgsPos");
+                    MethodHandle target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), kind);
+                    int mtTgtSlotsCount = TestMethods.argSlotsCount(mtTarget);
+                    int mtDASlotsCount = TestMethods.argSlotsCount(mtDropArgs);
+                    List<Class<?>> fakeParList;
+                    if (mtTgtSlotsCount + mtDASlotsCount > Helper.MAX_ARITY - 1) {
+                        fakeParList = TestMethods.reduceArgListToSlotsCount(mtDropArgs.parameterList(),
+                                Helper.MAX_ARITY - mtTgtSlotsCount - 1);
+                    } else {
+                        fakeParList = mtDropArgs.parameterList();
+                    }
+                    return MethodHandles.dropArguments(target, dropArgsPos, fakeParList);
+                }
+            },
+    EXPLICIT_CAST_ARGUMENTS("explicitCastArguments") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY / 2);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    MethodType mtExcplCastArgs = TestMethods.randomMethodTypeGenerator(realArity);
+                    if (mtTarget.returnType() == void.class) {
+                        mtExcplCastArgs = MethodType.methodType(void.class,
+                                mtExcplCastArgs.parameterArray());
+                    }
+                    if (mtExcplCastArgs.returnType() == void.class) {
+                        mtExcplCastArgs = MethodType.methodType(mtTarget.returnType(),
+                                mtExcplCastArgs.parameterArray());
+                    }
+                    data.put("mtExcplCastArgs", mtExcplCastArgs);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    MethodType mtExcplCastArgs = (MethodType) data.get("mtExcplCastArgs");
+                    MethodHandle target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), kind);
+                    return MethodHandles.explicitCastArguments(target, mtExcplCastArgs);
+                }
+            },
+    FILTER_ARGUMENTS("filterArguments") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY / 2);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int filterArgsPos = Helper.RNG.nextInt(realArity + 1);
+                    data.put("filterArgsPos", filterArgsPos);
+                    int filtersArgsArrayLength = Helper.RNG.nextInt(realArity + 1 - filterArgsPos);
+                    data.put("filtersArgsArrayLength", filtersArgsArrayLength);
+                    MethodType mtFilter = TestMethods.randomMethodTypeGenerator(filtersArgsArrayLength);
+                    data.put("mtFilter", mtFilter);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    MethodType mtFilter = (MethodType) data.get("mtFilter");
+                    int filterArgsPos = (int) data.get("filterArgsPos");
+                    int filtersArgsArrayLength = (int) data.get("filtersArgsArrayLength");
+                    MethodHandle target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), kind);
+                    MethodHandle[] filters = new MethodHandle[filtersArgsArrayLength];
+                    for (int i = 0; i < filtersArgsArrayLength; i++) {
+                        filters[i] = TestMethods.filterGenerator(mtFilter.parameterType(i),
+                                mtTarget.parameterType(filterArgsPos + i), kind);
+                    }
+                    return MethodHandles.filterArguments(target, filterArgsPos, filters);
+                }
+            },
+    FILTER_RETURN_VALUE("filterReturnValue") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int filterArgsPos = Helper.RNG.nextInt(realArity + 1);
+                    int filtersArgsArrayLength = Helper.RNG.nextInt(realArity + 1 - filterArgsPos);
+                    MethodType mtFilter = TestMethods.randomMethodTypeGenerator(filtersArgsArrayLength);
+                    data.put("mtFilter", mtFilter);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    MethodType mtFilter = (MethodType) data.get("mtFilter");
+                    MethodHandle target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), kind);
+                    MethodHandle filter = TestMethods.filterGenerator(mtTarget.returnType(),
+                            mtFilter.returnType(), kind);
+                    return MethodHandles.filterReturnValue(target, filter);
+                }
+            },
+    INSERT_ARGUMENTS("insertArguments") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int insertArgsPos = Helper.RNG.nextInt(realArity + 1);
+                    data.put("insertArgsPos", insertArgsPos);
+                    int insertArgsArrayLength = Helper.RNG.nextInt(realArity + 1 - insertArgsPos);
+                    MethodType mtInsertArgs = MethodType.methodType(void.class, mtTarget.parameterList()
+                            .subList(insertArgsPos, insertArgsPos + insertArgsArrayLength));
+                    data.put("mtInsertArgs", mtInsertArgs);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    MethodType mtInsertArgs = (MethodType) data.get("mtInsertArgs");
+                    int insertArgsPos = (int) data.get("insertArgsPos");
+                    MethodHandle target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), kind);
+                    Object[] insertList = Helper.randomArgs(mtInsertArgs.parameterList());
+                    return MethodHandles.insertArguments(target, insertArgsPos, insertList);
+                }
+            },
+    PERMUTE_ARGUMENTS("permuteArguments") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY / 2);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int[] permuteArgsReorderArray = new int[realArity];
+                    int mtParmuteArgsNum = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    mtParmuteArgsNum = mtParmuteArgsNum == 0 ? 1 : mtParmuteArgsNum;
+                    MethodType mtPermuteArgs = TestMethods.randomMethodTypeGenerator(mtParmuteArgsNum);
+                    mtTarget = mtTarget.changeReturnType(mtPermuteArgs.returnType());
+                    for (int i = 0; i < realArity; i++) {
+                        int mtPermuteArgsParNum = Helper.RNG.nextInt(mtPermuteArgs.parameterCount());
+                        permuteArgsReorderArray[i] = mtPermuteArgsParNum;
+                        mtTarget = mtTarget.changeParameterType(
+                                i, mtPermuteArgs.parameterType(mtPermuteArgsParNum));
+                    }
+                    data.put("mtTarget", mtTarget);
+                    data.put("permuteArgsReorderArray", permuteArgsReorderArray);
+                    data.put("mtPermuteArgs", mtPermuteArgs);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    MethodType mtPermuteArgs = (MethodType) data.get("mtPermuteArgs");
+                    int[] permuteArgsReorderArray = (int[]) data.get("permuteArgsReorderArray");
+                    MethodHandle target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), kind);
+                    return MethodHandles.permuteArguments(target, mtPermuteArgs, permuteArgsReorderArray);
+                }
+            },
+    THROW_EXCEPTION("throwException") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    Class<?> rType = mtTarget.returnType();
+                    return MethodHandles.throwException(rType, Exception.class
+                    );
+                }
+            },
+    GUARD_WITH_TEST("guardWithTest") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int modifierMHArgNum = Helper.RNG.nextInt(realArity + 1);
+                    data.put("modifierMHArgNum", modifierMHArgNum);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    int modifierMHArgNum = (int) data.get("modifierMHArgNum");
+                    TestMethods.Kind targetKind;
+                    TestMethods.Kind fallbackKind;
+                    if (kind.equals(TestMethods.Kind.ONE)) {
+                        targetKind = TestMethods.Kind.ONE;
+                        fallbackKind = TestMethods.Kind.TWO;
+                    } else {
+                        targetKind = TestMethods.Kind.TWO;
+                        fallbackKind = TestMethods.Kind.ONE;
+                    }
+                    MethodHandle target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), targetKind);
+                    MethodHandle fallback = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                            mtTarget.parameterList(), fallbackKind);
+                    MethodHandle test = TestMethods.methodHandleGenerator(boolean.class,
+                            mtTarget.parameterList().subList(0, modifierMHArgNum), kind);
+                    return MethodHandles.guardWithTest(test, target, fallback);
+                }
+            },
+    CATCH_EXCEPTION("catchException") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int modifierMHArgNum = Helper.RNG.nextInt(realArity + 1);
+                    data.put("modifierMHArgNum", modifierMHArgNum);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    int modifierMHArgNum = (int) data.get("modifierMHArgNum");
+                    MethodHandle target;
+                    if (kind.equals(TestMethods.Kind.ONE)) {
+                        target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                                mtTarget.parameterList(), TestMethods.Kind.ONE);
+                    } else {
+                        target = TestMethods.methodHandleGenerator(mtTarget.returnType(),
+                                mtTarget.parameterList(), TestMethods.Kind.EXCEPT);
+                    }
+                    List<Class<?>> handlerParamList = new ArrayList<>(mtTarget.parameterCount() + 1);
+                    handlerParamList.add(Exception.class);
+                    handlerParamList.addAll(mtTarget.parameterList().subList(0, modifierMHArgNum));
+                    MethodHandle handler = TestMethods.methodHandleGenerator(
+                            mtTarget.returnType(), handlerParamList, TestMethods.Kind.TWO);
+                    return MethodHandles.catchException(target, Exception.class, handler);
+                }
+            },
+    INVOKER("invoker") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    return MethodHandles.invoker(mtTarget);
+                }
+            },
+    EXACT_INVOKER("exactInvoker") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    return MethodHandles.exactInvoker(mtTarget);
+                }
+            },
+    SPREAD_INVOKER("spreadInvoker") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    // Arity after reducing because of long and double take 2 slots.
+                    int realArity = mtTarget.parameterCount();
+                    int modifierMHArgNum = Helper.RNG.nextInt(realArity + 1);
+                    data.put("modifierMHArgNum", modifierMHArgNum);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    int modifierMHArgNum = (int) data.get("modifierMHArgNum");
+                    return MethodHandles.spreadInvoker(mtTarget, modifierMHArgNum);
+                }
+            },
+    ARRAY_ELEMENT_GETTER("arrayElementGetter") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    Class<?> rType = mtTarget.returnType();
+                    if (rType == void.class) {
+                        rType = Object.class;
+                    }
+                    return MethodHandles.arrayElementGetter(Array.newInstance(rType, 2).getClass());
+                }
+            },
+    ARRAY_ELEMENT_SETTER("arrayElementSetter") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    Class<?> rType = mtTarget.returnType();
+                    if (rType == void.class) {
+                        rType = Object.class;
+                    }
+                    return MethodHandles.arrayElementSetter(Array.newInstance(rType, 2).getClass());
+                }
+            },
+    CONSTANT("constant") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    Class<?> rType = mtTarget.returnType();
+                    if (rType == void.class) {
+                        rType = Object.class;
+                    }
+                    if (rType.equals(boolean.class)) {
+                        // There should be the same return values because for default values there are special "zero" forms
+                        return MethodHandles.constant(rType, true);
+                    } else {
+                        return MethodHandles.constant(rType, kind.getValue(rType));
+                    }
+                }
+            },
+    IDENTITY("identity") {
+                @Override
+                public Map<String, Object> getTestCaseData() {
+                    Map<String, Object> data = new HashMap<>();
+                    int desiredArity = Helper.RNG.nextInt(Helper.MAX_ARITY);
+                    MethodType mtTarget = TestMethods.randomMethodTypeGenerator(desiredArity);
+                    data.put("mtTarget", mtTarget);
+                    return data;
+                }
+
+                @Override
+                protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) {
+                    MethodType mtTarget = (MethodType) data.get("mtTarget");
+                    Class<?> rType = mtTarget.returnType();
+                    if (rType == void.class) {
+                        rType = Object.class;
+                    }
+                    return MethodHandles.identity(rType);
+                }
+            };
+
+    /**
+     * Test method's name.
+     */
+    public final String name;
+
+    private TestMethods(String name) {
+        this.name = name;
+    }
+
+    protected MethodHandle getMH(Map<String, Object> data, TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+        throw new UnsupportedOperationException("TESTBUG: getMH method is not implemented for test method " + this);
+    }
+
+    /**
+     * Creates an adapter method handle depending on a test method from
+     * MethodHandles class. Adapter is what is returned by the test method. This
+     * method is able to create two kinds of adapters, their type will be the
+     * same, but return values are different.
+     *
+     * @param data a Map containing data to create a method handle, can be
+     * obtained by {@link #getTestCaseData} method
+     * @param kind defines whether adapter ONE or adapter TWO will be
+     * initialized. Should be equal to TestMethods.Kind.ONE or
+     * TestMethods.Kind.TWO
+     * @return Method handle adapter that behaves according to
+     * TestMethods.Kind.ONE or TestMethods.Kind.TWO
+     * @throws java.lang.NoSuchMethodException
+     * @throws java.lang.IllegalAccessException
+     */
+    public MethodHandle getTestCaseMH(Map<String, Object> data, TestMethods.Kind kind)
+            throws NoSuchMethodException, IllegalAccessException {
+        if (data == null) {
+            throw new Error(String.format("TESTBUG: Data for test method %s is not prepared",
+                    this.name));
+        }
+        if (!kind.equals(TestMethods.Kind.ONE) && !kind.equals(TestMethods.Kind.TWO)) {
+            throw new IllegalArgumentException("TESTBUG: Wrong \"kind\" (" + kind
+                    + ") arg to getTestCaseMH function."
+                    + " Should be Kind.ONE or Kind.TWO");
+        }
+        return getMH(data, kind);
+    }
+
+    /**
+     * Returns a data Map needed for {@link #getTestCaseMH} method.
+     *
+     * @return data Map needed for {@link #getTestCaseMH} method
+     */
+    public Map<String, Object> getTestCaseData() {
+        throw new UnsupportedOperationException(
+                "TESTBUG: getTestCaseData method is not implemented for test method " + this);
+    }
+
+    /**
+     * Enumeration used in methodHandleGenerator to define whether a MH returned
+     * by this method returns "2" in different type representations, "4", or
+     * throw an Exception.
+     */
+    public static enum Kind {
+
+        ONE(2),
+        TWO(4),
+        EXCEPT(0);
+
+        private final int value;
+
+        private Object getValue(Class<?> cl) {
+            return Helper.castToWrapper(value, cl);
+        }
+
+        private MethodHandle getBasicMH(Class<?> rType) throws NoSuchMethodException, IllegalAccessException {
+            MethodHandle result = null;
+            switch (this) {
+                case ONE:
+                case TWO:
+                    if (rType.equals(void.class)) {
+                        result = MethodHandles.lookup().findVirtual(Kind.class, "returnVoid", MethodType.methodType(void.class));
+                        result = MethodHandles.insertArguments(result, 0, this);
+                    } else {
+                        result = MethodHandles.constant(rType, getValue(rType));
+                    }
+                    break;
+                case EXCEPT:
+                    result = MethodHandles.throwException(rType, Exception.class);
+                    result = MethodHandles.insertArguments(result, 0, new Exception());
+                    break;
+            }
+            return result;
+        }
+
+        private void returnVoid() {
+        }
+
+        private Kind(int value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * Routine used to obtain a randomly generated method type.
+     *
+     * @param arity Arity of returned method type.
+     * @return MethodType generated randomly.
+     */
+    private static MethodType randomMethodTypeGenerator(int arity) {
+        final Class<?>[] CLASSES = {
+            Object.class,
+            int.class,
+            boolean.class,
+            byte.class,
+            short.class,
+            char.class,
+            long.class,
+            float.class,
+            double.class
+        };
+        if (arity > Helper.MAX_ARITY) {
+            throw new IllegalArgumentException(
+                    String.format("Arity should not exceed %d!", Helper.MAX_ARITY));
+        }
+        List<Class<?>> list = Helper.randomClasses(CLASSES, arity);
+        list = Helper.getParams(list, false, arity);
+        int i = Helper.RNG.nextInt(CLASSES.length + 1);
+        Class<?> rtype = i == CLASSES.length ? void.class : CLASSES[i];
+        return MethodType.methodType(rtype, list);
+    }
+
+    /**
+     * Routine used to obtain a method handles of a given type an kind (return
+     * value).
+     *
+     * @param returnType Type of MH return value.
+     * @param argTypes Types of MH args.
+     * @param kind Defines whether the obtained MH returns "1" or "2".
+     * @return Method handle of the given type.
+     * @throws NoSuchMethodException
+     * @throws IllegalAccessException
+     */
+    private static MethodHandle methodHandleGenerator(Class<?> returnType,
+            List<Class<?>> argTypes, TestMethods.Kind kind)
+            throws NoSuchMethodException, IllegalAccessException {
+        MethodHandle result;
+        result = kind.getBasicMH(returnType);
+        return Helper.addTrailingArgs(result, argTypes.size(), argTypes);
+    }
+
+    /**
+     * Routine that generates filter method handles to test
+     * MethodHandles.filterArguments method.
+     *
+     * @param inputType Filter's argument type.
+     * @param returnType Filter's return type.
+     * @param kind Filter's return value definer.
+     * @return A filter method handle, that takes one argument.
+     * @throws NoSuchMethodException
+     * @throws IllegalAccessException
+     */
+    private static MethodHandle filterGenerator(Class<?> inputType, Class<?> returnType,
+            TestMethods.Kind kind) throws NoSuchMethodException, IllegalAccessException {
+        MethodHandle tmpMH = kind.getBasicMH(returnType);
+        if (inputType.equals(void.class)) {
+            return tmpMH;
+        }
+        ArrayList<Class<?>> inputTypeList = new ArrayList<>(1);
+        inputTypeList.add(inputType);
+        return Helper.addTrailingArgs(tmpMH, 1, inputTypeList);
+    }
+
+    private static int argSlotsCount(MethodType mt) {
+        int result = 0;
+        for (Class cl : mt.parameterArray()) {
+            if (cl.equals(long.class) || cl.equals(double.class)) {
+                result += 2;
+            } else {
+                result++;
+            }
+        }
+        return result;
+    }
+
+    private static List<Class<?>> reduceArgListToSlotsCount(List<Class<?>> list,
+            int desiredSlotCount) {
+        List<Class<?>> result = new ArrayList<>(desiredSlotCount);
+        int count = 0;
+        for (Class<?> cl : list) {
+            if (count >= desiredSlotCount) {
+                break;
+            }
+            if (cl.equals(long.class) || cl.equals(double.class)) {
+                count += 2;
+            } else {
+                count++;
+            }
+            result.add(cl);
+        }
+        return result;
+    }
+}