junitjava-7try-with-resourcesbyteman

Byteman JUnit Runner - impossible to trigger IOException on auto-closed InputStream#close


I have got the following code:

Collection<String> errors = ...;
try (InputStream stream = My.class.getResourceAsStream(resource)) {
   // do stuff
}
catch(IOException ex) {
   errors.add("Fail");
}

I'm trying with Byteman Junit Runner to trigger an IOException when the (valid) input stream I give is supposedly closed:

@RunWith(BMUnitRunner.class)
public class MyTest {

    private My my = new My();

    @BMRule(
       name = "force_read_error",
       targetClass = "java.io.InputStream",
       targetMethod = "close()",
       action = "throw new IOException(\"bazinga\")"
    )
    @Test
    public void catches_read_error() throws IOException {
       Collection<String> errors = my.foo("/valid-resource-in-classpath");

       assertThat(errors).containsExactly("Fail");
    }
}

My test fails: errors is always empty, which means the Byteman rule obviously isn't executed (it's well loaded by the agent, so I don't understand what's going on).

How can I trigger an IOException on close method called via try-with-resources?


Solution

  • Your rule ist not firing, because the class of the stream object received when calling

    InputStream stream = My.class.getResourceAsStream(resource)
    

    is not a "java.io.InputStream" class. It is a class extending "java.io.InputStream", most likely a "BufferedInputStream".

    To tell byteman to "trigger rule for any class extending java.io.InputStream", you need to put a '^' before the class name:

    targetClass = "^java.io.InputStream"
    

    This change might have the unwanted side effect, that the rule gets triggered also when other objects extending "java.io.InputStream" get closed. To prevent this from happening, a condition should be added to the rule to only get triggered, when the caller matches the "foo" method of the "My" class. Byteman has a helper method for that called "callerMatches" (Please see also the advanced tutorial)

    A working condition for your case would be:

    condition = "callerMatches(\".+My.foo\",true,true)"
    

    The complete Byteman rule definition as BMRule annotation should look like:

    @BMRule(
        name = "force_read_error",
        targetClass = "^java.io.InputStream",
        targetMethod = "close()",
        condition = "callerMatches(\".+My.foo\",true,true)",
        action = "throw new java.io.IOException(\"bazinga\")"
    )