test/jdk/com/sun/jdi/OomDebugTest.java
author amenkov
Fri, 21 Sep 2018 11:28:14 -0700
changeset 51837 46ca82c15f6c
parent 50525 767cdb97f103
permissions -rw-r--r--
8210760: [TEST] rewrite com/sun/jdi shell tests to java version - step4 Reviewed-by: jcbeyler, sspitsyn, cjplummer

/*
 * Copyright (c) 2016 Red Hat Inc.
 *
 * 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
 *  @bug 8153711
 *  @summary JDWP: Memory Leak (global references not deleted after invokeMethod).
 *
 *  @author Severin Gehwolf <sgehwolf@redhat.com>
 *
 *  @requires vm.gc != "Z"
 *  @library ..
 *  @run build TestScaffold VMConnection TargetListener TargetAdapter
 *  @run compile -g OomDebugTest.java
 *  @run main OomDebugTest OomDebugTestTarget test1
 *  @run main OomDebugTest OomDebugTestTarget test2
 *  @run main OomDebugTest OomDebugTestTarget test3
 *  @run main OomDebugTest OomDebugTestTarget test4
 *  @run main OomDebugTest OomDebugTestTarget test5
 */
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import com.sun.jdi.ArrayReference;
import com.sun.jdi.ArrayType;
import com.sun.jdi.ClassType;
import com.sun.jdi.Field;
import com.sun.jdi.InvocationException;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.VMOutOfMemoryException;
import com.sun.jdi.Value;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.ExceptionEvent;

/***************** Target program **********************/

class OomDebugTestTarget {

    OomDebugTestTarget() {
        System.out.println("DEBUG: invoked constructor");
    }
    static class FooCls {
        @SuppressWarnings("unused")
        private byte[] bytes = new byte[3000000];
    };

    FooCls fooCls = new FooCls();
    byte[] byteArray = new byte[0];

    void testMethod(FooCls foo) {
        System.out.println("DEBUG: invoked 'void testMethod(FooCls)', foo == " + foo);
    }

    void testPrimitive(byte[] foo) {
        System.out.println("DEBUG: invoked 'void testPrimitive(byte[])', foo == " + foo);
    }

    byte[] testPrimitiveArrRetval() {
        System.out.println("DEBUG: invoked 'byte[] testPrimitiveArrRetval()'");
        return new byte[3000000];
    }

    FooCls testFooClsRetval() {
        System.out.println("DEBUG: invoked 'FooCls testFooClsRetval()'");
        return new FooCls();
    }

    public void entry() {}

    public static void main(String[] args){
        System.out.println("DEBUG: OomDebugTestTarget.main");
        new OomDebugTestTarget().entry();
    }
}

/***************** Test program ************************/

public class OomDebugTest extends TestScaffold {

    private static final String[] ALL_TESTS = new String[] {
            "test1", "test2", "test3", "test4", "test5"
    };
    private static final Set<String> ALL_TESTS_SET = new HashSet<String>();
    static {
        ALL_TESTS_SET.addAll(Arrays.asList(ALL_TESTS));
    }
    private static final String TEST_CLASSES = System.getProperty("test.classes", ".");
    private static final File RESULT_FILE = new File(TEST_CLASSES, "results.properties");
    private static final String LAST_TEST = ALL_TESTS[ALL_TESTS.length - 1];
    private ReferenceType targetClass;
    private ObjectReference thisObject;
    private int failedTests;
    private final String testMethod;

    public OomDebugTest(String[] args) {
        super(args);
        if (args.length != 2) {
            throw new RuntimeException("Test failed unexpectedly.");
        }
        this.testMethod = args[1];
    }

    @Override
    protected void runTests() throws Exception {
        try {
            addListener(new TargetAdapter() {

                @Override
                public void exceptionThrown(ExceptionEvent event) {
                    String name = event.exception().referenceType().name();
                    System.err.println("DEBUG: Exception thrown in debuggee was: " + name);
                }
            });
            /*
             * Get to the top of entry()
             * to determine targetClass and mainThread
             */
            BreakpointEvent bpe = startTo("OomDebugTestTarget", "entry", "()V");
            targetClass = bpe.location().declaringType();

            mainThread = bpe.thread();

            StackFrame frame = mainThread.frame(0);
            thisObject = frame.thisObject();
            java.lang.reflect.Method m = findTestMethod();
            m.invoke(this);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            failure();
        } catch (SecurityException e) {
            e.printStackTrace();
            failure();
        }
        /*
         * resume the target, listening for events
         */
        listenUntilVMDisconnect();
    }

    private java.lang.reflect.Method findTestMethod()
            throws NoSuchMethodException, SecurityException {
        return OomDebugTest.class.getDeclaredMethod(testMethod);
    }

    private void failure() {
        failedTests++;
    }

    /*
     * Test case: Object reference as method parameter.
     */
    @SuppressWarnings("unused") // called via reflection
    private void test1() throws Exception {
        System.out.println("DEBUG: ------------> Running test1");
        try {
            Field field = targetClass.fieldByName("fooCls");
            ClassType clsType = (ClassType)field.type();
            Method constructor = getConstructorForClass(clsType);
            for (int i = 0; i < 15; i++) {
                @SuppressWarnings({ "rawtypes", "unchecked" })
                ObjectReference objRef = clsType.newInstance(mainThread,
                                                             constructor,
                                                             new ArrayList(0),
                                                             ObjectReference.INVOKE_NONVIRTUAL);
                if (objRef.isCollected()) {
                    System.out.println("DEBUG: Object got GC'ed before we can use it. NO-OP.");
                    continue;
                }
                invoke("testMethod", "(LOomDebugTestTarget$FooCls;)V", objRef);
            }
        } catch (InvocationException e) {
            handleFailure(e);
        }
    }

    /*
     * Test case: Array reference as method parameter.
     */
    @SuppressWarnings("unused") // called via reflection
    private void test2() throws Exception {
        System.out.println("DEBUG: ------------> Running test2");
        try {
            Field field = targetClass.fieldByName("byteArray");
            ArrayType arrType = (ArrayType)field.type();

            for (int i = 0; i < 15; i++) {
                ArrayReference byteArrayVal = arrType.newInstance(3000000);
                if (byteArrayVal.isCollected()) {
                    System.out.println("DEBUG: Object got GC'ed before we can use it. NO-OP.");
                    continue;
                }
                invoke("testPrimitive", "([B)V", byteArrayVal);
            }
        } catch (VMOutOfMemoryException e) {
            defaultHandleOOMFailure(e);
        }
    }

    /*
     * Test case: Array reference as return value.
     */
    @SuppressWarnings("unused") // called via reflection
    private void test3() throws Exception {
        System.out.println("DEBUG: ------------> Running test3");
        try {
            for (int i = 0; i < 15; i++) {
                invoke("testPrimitiveArrRetval",
                       "()[B",
                       Collections.EMPTY_LIST,
                       vm().mirrorOfVoid());
            }
        } catch (InvocationException e) {
            handleFailure(e);
        }
    }

    /*
     * Test case: Object reference as return value.
     */
    @SuppressWarnings("unused") // called via reflection
    private void test4() throws Exception {
        System.out.println("DEBUG: ------------> Running test4");
        try {
            for (int i = 0; i < 15; i++) {
                invoke("testFooClsRetval",
                       "()LOomDebugTestTarget$FooCls;",
                       Collections.EMPTY_LIST,
                       vm().mirrorOfVoid());
            }
        } catch (InvocationException e) {
            handleFailure(e);
        }
    }

    /*
     * Test case: Constructor
     */
    @SuppressWarnings({ "unused", "unchecked", "rawtypes" }) // called via reflection
    private void test5() throws Exception {
        System.out.println("DEBUG: ------------> Running test5");
        try {
            ClassType type = (ClassType)thisObject.type();
            for (int i = 0; i < 15; i++) {
                type.newInstance(mainThread,
                                 findMethod(targetClass, "<init>", "()V"),
                                 new ArrayList(0),
                                 ObjectReference.INVOKE_NONVIRTUAL);
            }
        } catch (InvocationException e) {
            handleFailure(e);
        }
    }

    private Method getConstructorForClass(ClassType clsType) {
        List<Method> methods = clsType.methodsByName("<init>");
        if (methods.size() != 1) {
            throw new RuntimeException("FAIL. Expected only one, the default, constructor");
        }
        return methods.get(0);
    }

    private void handleFailure(InvocationException e) {
        // There is no good way to see the OOME diagnostic message in the target since the
        // TestScaffold might throw an exception while trying to print the stack trace. I.e
        // it might get a a VMDisconnectedException before the stack trace printing finishes.
        System.err.println("FAILURE: InvocationException thrown. Trying to determine cause...");
        defaultHandleOOMFailure(e);
    }

    private void defaultHandleOOMFailure(Exception e) {
        e.printStackTrace();
        failure();
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    void invoke(String methodName, String methodSig, Value value)
            throws Exception {
        List args = new ArrayList(1);
        args.add(value);
        invoke(methodName, methodSig, args, value);
    }

    void invoke(String methodName,
                String methodSig,
                @SuppressWarnings("rawtypes") List args,
                Value value) throws Exception {
        Method method = findMethod(targetClass, methodName, methodSig);
        if ( method == null) {
            failure("FAILED: Can't find method: "
                    + methodName  + " for class = " + targetClass);
            return;
        }
        invoke(method, args, value);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    void invoke(Method method, List args, Value value) throws Exception {
        thisObject.invokeMethod(mainThread, method, args, 0);
        System.out.println("DEBUG: Done invoking method via debugger.");
    }

    Value fieldValue(String fieldName) {
        Field field = targetClass.fieldByName(fieldName);
        return thisObject.getValue(field);
    }

    // Determine the pass/fail status on some heuristic and don't fail the
    // test if < 3 of the total number of tests (currently 5) fail. This also
    // has the nice side effect that all tests are first attempted and only
    // all tests ran an overall pass/fail status is determined.
    private static void determineOverallTestStatus(OomDebugTest oomTest)
                                   throws IOException, FileNotFoundException {
        Properties resultProps = new Properties();
        if (!RESULT_FILE.exists()) {
            RESULT_FILE.createNewFile();
        }
        FileInputStream fin = null;
        try {
            fin = new FileInputStream(RESULT_FILE);
            resultProps.load(fin);
            resultProps.put(oomTest.testMethod,
                            Integer.toString(oomTest.failedTests));
        } finally {
            if (fin != null) {
                fin.close();
            }
        }
        System.out.println("DEBUG: Finished running test '"
                           + oomTest.testMethod + "'.");
        if (LAST_TEST.equals(oomTest.testMethod)) {
            System.out.println("DEBUG: Determining overall test status.");
            Set<String> actualTestsRun = new HashSet<String>();
            int totalTests = ALL_TESTS.length;
            int failedTests = 0;
            for (Object key: resultProps.keySet()) {
                actualTestsRun.add((String)key);
                Object propVal = resultProps.get(key);
                int value = Integer.parseInt((String)propVal);
                failedTests += value;
            }
            if (!ALL_TESTS_SET.equals(actualTestsRun)) {
                String errorMsg = "Test failed! Expected to run tests '"
                        + ALL_TESTS_SET + "', but only these were run '"
                        + actualTestsRun + "'";
                throw new RuntimeException(errorMsg);
            }
            if (failedTests >= 3) {
                String errorMsg = "Test failed. Expected < 3 sub-tests to fail "
                                  + "for a pass. Got " + failedTests
                                  + " failed tests out of " + totalTests + ".";
                throw new RuntimeException(errorMsg);
            }
            RESULT_FILE.delete();
            System.out.println("All " + totalTests + " tests passed.");
        } else {
            System.out.println("DEBUG: More tests to run. Coninuing.");
            FileOutputStream fout = null;
            try {
                fout = new FileOutputStream(RESULT_FILE);
                resultProps.store(fout, "Storing results after test "
                                         + oomTest.testMethod);
            } finally {
                if (fout != null) {
                    fout.close();
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        System.setProperty("test.vm.opts", "-Xmx40m"); // Set debuggee VM option
        OomDebugTest oomTest = new OomDebugTest(args);
        try {
            oomTest.startTests();
        } catch (Throwable e) {
            System.out.println("DEBUG: Got exception for test run. " + e);
            e.printStackTrace();
            oomTest.failure();
        }
        determineOverallTestStatus(oomTest);
    }

}