Search code examples
javaregexjunitjavassistrandoop

Programatically Remove try/catch blocks from test cases


I'm instrumenting test code for a research project. We start with test cases such as

@Test
public void test025() throws Throwable {
    if (debug)
        System.out.format("%n%s%n", "RegressionTest1.test025");
    java.lang.Double[] d_array2 = new java.lang.Double[] { 1.0d, (-1.0d) };
    org.apache.commons.math.linear.ArrayRealVector arrayRealVector3 = new org.apache.commons.math.linear.ArrayRealVector(d_array2);
    org.apache.commons.math.linear.ArrayRealVector arrayRealVector4 = new org.apache.commons.math.linear.ArrayRealVector(d_array2);
    try {
        org.apache.commons.math.linear.ArrayRealVector arrayRealVector7 = new org.apache.commons.math.linear.ArrayRealVector(d_array2, (int) '4', (int) ' ');
        org.junit.Assert.fail("Expected exception of type org.apache.commons.math.exception.NumberIsTooLargeException");
    } catch (org.apache.commons.math.exception.NumberIsTooLargeException e) {
    }
    org.junit.Assert.assertNotNull(d_array2);
}

The assert statements are commented out of the .java files with:

original = original.replace("org.junit.Assert.", "//org.junit.Assert.");
original = original.replace("assert", "//assert");

Then, they are instrumented with javassist to log any exceptions (to be sure they happen in all runs of the test cases):

    for (CtMethod testmethod : methods) {
        if (testmethod.getName().startsWith("test")) {
            try {
                testmethod.insertBefore("semanticrt.statedump.dumpObjectState.setDumpTestCase(\""+testClassName+ "." + testmethod.getName()+"\");");
                CtClass etype = null;
                try {
                    etype = pool.get("java.lang.Exception");
                } catch (NotFoundException e) {
                    e.printStackTrace();
                }

                String exceptionCode = "{ semanticrt.statedump.dumpObjectState.dumpExceptionToFile($e, " +
                        "\"" + outputFileRoot + "."+ testmethod.getName() + ".exception\", false); throw $e; }\n";
                testmethod.addCatch(exceptionCode, etype);
            } catch (CannotCompileException e) {
                System.out.println(testmethod);
                e.printStackTrace();
            }

        }
    }

Which yields:

@Test
public void test025() throws Throwable {
    try {
        dumpObjectState.setDumpTestCase("RegressionTest1.test025");
        if(debug) {
            System.out.format("%n%s%n", new Object[]{"RegressionTest1.test025"});
        }

        Double[] var1 = new Double[]{Double.valueOf(1.0D), Double.valueOf(-1.0D)};
        new ArrayRealVector(var1);
        new ArrayRealVector(var1);

        try {
            new ArrayRealVector(var1, 52, 32);
        } catch (NumberIsTooLargeException var6) {
            ;
        }

    } catch (Exception var7) {
        dumpObjectState.dumpExceptionToFile(var7, "/home/loren/repos/d4jBugs/Math_45/version/XMLOut//out-RegressionTest1.test025.exception", false);
    }
}

My problem is the NumberIsTooLargeException is swallowed by the try/catch block left in the code. (Note: The Exception class could be any class, this is just an example of one problematic case.) So I need a way to get rid of any try/catch blocks in the generate test cases before I wrap them in mine.

Does anyone know how to do this with javassist or perhaps a good regular-expression that I could run on the .java file before instrumentation to remove them?

I'd like to end up with:

@Test
public void test025() throws Throwable {
    try {
        dumpObjectState.setDumpTestCase("RegressionTest1.test025");
        if(debug) {
            System.out.format("%n%s%n", new Object[]{"RegressionTest1.test025"});
        }

        Double[] var1 = new Double[]{Double.valueOf(1.0D), Double.valueOf(-1.0D)};
        new ArrayRealVector(var1);
        new ArrayRealVector(var1);

        new ArrayRealVector(var1, 52, 32);

    } catch (Exception var7) {
        dumpObjectState.dumpExceptionToFile(var7, "/home/loren/repos/d4jBugs/Math_45/version/XMLOut//out-RegressionTest1.test025.exception", false);
    }
}

Solution

  • I found a solution which works. It doesn't remove the try/catch block, but it does add a throw to the beginning of the catch so it provides the needed functionality.

    testmethod.instrument(
        new ExprEditor() {
            public void edit(Handler m)
                    throws CannotCompileException
            {
                m.insertBefore("throw $1;");
            }
        }
    );
    

    Which makes the whole loop:

        for (CtMethod testmethod : methods) {
            if (testmethod.getName().startsWith("test")) {
                try {
                    testmethod.instrument(
                        new ExprEditor() {
                            public void edit(Handler m)
                                    throws CannotCompileException
                            {
                                m.insertBefore("throw $1;");
                            }
                        }
                    );
                    testmethod.insertBefore("semanticrt.statedump.dumpObjectState.setDumpTestCase(\""+testClassName+ "." + testmethod.getName()+"\");");
                    CtClass etype = null;
                    try {
                        etype = pool.get("java.lang.Exception");
                    } catch (NotFoundException e) {
                        e.printStackTrace();
                    }
                    // See addCatch() https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html
                    String exceptionCode = "{ semanticrt.statedump.dumpObjectState.dumpExceptionToFile($e, " +
                            "\"" + outputFileRoot + "."+ testmethod.getName() + ".exception\", false); return; }\n";
                    testmethod.addCatch(exceptionCode, etype);
                } catch (CannotCompileException e) {
                    System.out.println(testmethod);
                    e.printStackTrace();
                }
            }
        }