package org.exist.xquery;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Item;
import org.easymock.classextension.EasyMock;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;
import org.junit.Test;
import static org.junit.Assert.assertNull;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify;
import static org.easymock.classextension.EasyMock.expect;
import static org.easymock.classextension.EasyMock.anyObject;
/**
* @author Adam Retter <adam@exist-db.org>
*/
public class DeferredFunctionCallTest {
/**
* resetState() make be called on the UserDefinedFunction of a DeferredFunctionCall
* before the function is eval'd, this is because the evaluation is deferred!
* resetState() clears the currentArguments to the function, however for a deferred
* function call we must ensure that we still have these when eval() is called
* otherwise we will get an NPE!
*
* This test tries to prove that eval can be called after resetState without
* causing problems for a DeferredFunctionCall
*
* The test implementation, due to the nature of the code under test, is rather horrible
* and mostly consists of tightly coupled mocking code making it very brittle.
* The interesting aspect of this test case is at the bottom of the function itself.
*/
@Test
public void ensure_argumentsToDeferredFunctionCall_AreNotLost_AfterReset_And_BeforeEval() throws XPathException {
//mocks for FunctionCall constructor
XQueryContext mockContext = EasyMock.createNiceMock(XQueryContext.class);
//mocks for evalFunction()
Sequence mockContextSequence = EasyMock.createMock(Sequence.class);
Item mockContextItem = EasyMock.createMock(Item.class);
Sequence[] mockSeq = { Sequence.EMPTY_SEQUENCE };
int nextExpressionId = 1234;
SequenceType[] mockArgumentTypes = { new SequenceType(Type.NODE, Cardinality.ZERO) };
//mock for functionDef
FunctionSignature mockFunctionSignature = EasyMock.createMock(FunctionSignature.class);
SequenceType mockReturnType = EasyMock.createMock(SequenceType.class);
LocalVariable mockMark = EasyMock.createMock(LocalVariable.class);
Expression mockExpression = EasyMock.createMock(Expression.class);
//expectations for UserDefinedFunction constructor
expect(mockContext.nextExpressionId()).andReturn(nextExpressionId++);
expect(mockExpression.simplify()).andReturn(mockExpression);
//expectations for FunctionCall constructor
expect(mockContext.nextExpressionId()).andReturn(nextExpressionId++);
//expectations for FunctionCall.setFunction
expect(mockFunctionSignature.getReturnType()).andReturn(mockReturnType);
expect(mockReturnType.getCardinality()).andReturn(Cardinality.ZERO_OR_MORE);
expect(mockReturnType.getPrimaryType()).andReturn(Type.NODE).times(4);
expect(mockContext.nextExpressionId()).andReturn(nextExpressionId++);
//expectations for functionCall.evalFunction
expect(mockContext.isProfilingEnabled()).andReturn(false);
expect(mockContext.getPDP()).andReturn(null);
//expectations for DeferredFunctionCall.execute
mockContext.pushDocumentContext();
mockContext.functionStart(mockFunctionSignature);
mockContext.stackEnter((Expression)anyObject());
expect(mockContext.declareVariableBinding((LocalVariable)anyObject())).andReturn(null);
expect(mockFunctionSignature.getArgumentTypes()).andReturn(mockArgumentTypes);
expect(mockExpression.eval(mockContextSequence, mockContextItem)).andReturn(Sequence.EMPTY_SEQUENCE);
mockContext.stackLeave((Expression)anyObject());
mockContext.functionEnd();
mockContext.popDocumentContext();
replay(mockContext, mockFunctionSignature, mockReturnType, mockExpression);
UserDefinedFunction userDefinedFunction = new UserDefinedFunction(mockContext, mockFunctionSignature);
userDefinedFunction.addVariable("testParam");
userDefinedFunction.setFunctionBody(mockExpression);
FunctionCall functionCall = new FunctionCall(mockContext, userDefinedFunction);
functionCall.setRecursive(true); //ensure DeferredFunction
/*** this is the interesting bit ***/
// 1) Call reset, this should set current arguments to null
functionCall.resetState(true);
// 2) check UserDefinedFunction.currentArguments == null
assertNull(userDefinedFunction.getCurrentArguments());
//so the currentArguments have been set to null, but deferredFunction should have its own copy
// 3) Call functionCall.eval, if we dont get an NPE on reading currentArguments, then success :-)
DeferredFunctionCall dfc = (DeferredFunctionCall)functionCall.evalFunction(mockContextSequence, mockContextItem, mockSeq);
dfc.execute();
/** end interesting bit ***/
verify(mockContext, mockFunctionSignature, mockReturnType, mockExpression);
}
}