FlexTradeUKLtd / jfixture

JFixture is an open source library based on the popular .NET library, AutoFixture
MIT License
105 stars 22 forks source link

Always pop monitored requests stack in RecursionGuard, even if request throws an exception #27

Closed splynn closed 8 years ago

splynn commented 8 years ago

We're trying to fixture an array of TypeA, which is field aField in test class SomeTest. We get

com.flextrade.jfixture.exceptions.ObjectCreationException: Unable to create an instance of TypeA[] SomeTest.aField because it contains a circular reference.
        TypeA[] SomeTest.aField --> 
        [LTypeA; --> 
        TypeA --> 
        TypeA

But TypeA doesn't contain any fields of type TypeA, or any setters that require a TypeA so there shouldn't be a recursion error. What seems to be happening is that one of its setters requires a type that has multiple constructors, one of which throws a NumberFormatException when invoked by JFixture because the provided string can't be parsed as a number:

com.flextrade.jfixture.exceptions.ObjectCreationException: Unable to invoke constructor public Quantity(java.lang.String)
java.lang.reflect.InvocationTargetException
java.lang.NumberFormatException

The other constructor takes a Long and succeeds, but the exception is propagating past the RecursionGuard, which isn't popping the failed request for Quantity off the stack. This means it always thinks it's one level deeper in the request stack than it actually is, meaning when it finally goes to request the next TypeA for the array it hasn't popped the original TypeA request off the stack and it thinks there's a circular reference.

The following is a simple way to reproduce the issue:

class TypeWithThrowingConstructor {
    public TypeWithThrowingConstructor() {
        throw new RuntimeException();
    }
    public TypeWithThrowingConstructor(String string) {
    }
}

class TypeWithFieldWithThrowingConstructor {
    public TypeWithThrowingConstructor fieldWithThrowingConstructor;
}

JFixture fixture = new JFixture();
fixture.create(TypeWithFieldWithThrowingConstructor[].class);

The solution we employ here is to make sure that RecursionGuard always pops requests off the stack, even if they throw an exception.

richkeenan commented 8 years ago

Thanks @splynn. Merging in