SQLUnit is failing on outparam id 2, but it doesn't fail nicely. A null pointer exception was generated in the assertOutParamEqual method but not caught by the TestHandler which would have ordinarily caught and would indicate which test failed (very bad). Since it was not caught, there was no indication which test had failed, but there would be an indication that a test had failed.
I think I've found the root causes. The XMLUtils.getText method incorrectly returns a null value when symValue was a variable but was not found in the SymbolTable. I think that it was meant to return the text in this case which would be not null. If I am correct in my assumption, here is what the snippet of code should be in XMLUtils starting around line 87:
While this fixes the Null pointer exception, it still doesn't entirely solve the problem since the symbol table doesn't appear to be updated by the resultant out parameters from the call tag.
I've figured out what needs to be changed and can submit patched files. SqlHandler and CallHandler need to change slightly.
/**
+ * Clears output parameter symbol table variables. This is a no-op
+ * in the case of this handler, but will contain code for the CallHandler.
+ * @param ps the Statement object.
+ * @param params the array of Param objects.
+ * @exception Exception if one is thrown by this method.
+ */
+ protected void clrOutputParameters(final Statement ps,
+ final Param[] params)
+ throws Exception {
+ return;
+ }
+
+ /**
* Closes Statement and Connection objects and releases them.
* @param ps the Statement object.
* @param conn the Connection object.
--- CallHandler.java 2004-11-08 12:44:09.703970900 -0600
+++ CallHandler.java.newer 2004-11-08 12:08:37.317397100 -0600
@@ -155,7 +155,7 @@
for (int i = 0; i < params.length; i++) {
if (params[i].isOutParameter()) {
if (SymbolTable.isVariableName(params[i].getValue())) {
- SymbolTable.setValue(SymbolTable.OUT_PARAM + i + "}",
+ SymbolTable.setValue(SymbolTable.OUT_PARAM + tcs + ":" + i + "}",
params[i].getValue());
}
int sqltype = params[i].getSQLType();
@@ -214,16 +214,23 @@
outParam.setId(params[i].getId());
outParam.setName(params[i].getName());
outParam.setType(params[i].getType());
+ String outParamSymbol = SymbolTable.removeSymbol(SymbolTable.OUT_PARAM + tcs + ":" + i + "}");
// get the value from the CallableStatement
Object value = tcs.getObject(i + 1);
if (value instanceof ResultSet) {
// value is a resultset
ResultSetBean rsb = new ResultSetBean((ResultSet) value, 1);
outParam.setValue(rsb);
+ if ( outParamSymbol != null ) {
+ SymbolTable.setObject(outParamSymbol, rsb);
+ }
} else {
// value is a String
IType type = TypeFactory.getInstance(params[i].getType());
outParam.setValue(type.toString(value));
+ if (outParamSymbol != null) {
+ SymbolTable.setValue(outParamSymbol,type.toString(value));
+ }
}
outParamList.add(outParam);
}
@@ -234,4 +241,23 @@
}
result.setOutParams(ops);
}
+
+ /**
+ * Overrides the parent method to clear parameters into the
+ * DatabaseResult object.
+ * @param cs the Statement object.
+ * @param params the array of Param objects.
+ * @param result the DatabaseResult object.
+ * @exception Exception if there was a problem setting the OUT params.
+ */
+ protected final void clrOutputParameters(final Statement cs,
+ final Param[] params)
+ throws Exception {
+ CallableStatement tcs = (CallableStatement) cs;
+ for (int i = 0; i < params.length; i++) {
+ if (params[i].isOutParameter()) {
+ SymbolTable.removeSymbol(SymbolTable.OUT_PARAM + tcs + ":" + i + "}");
+ }
+ }
+ }
}
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
In this case it looks like ${keyVal} is being used as both an lvalue (when being assigned as an out parameter in the call) and as an rvalue (when being compared with the value returned from the procedure in the outparameter in the result tag).
From what I understand from your patch, you are clearing the declared outparam values at the beginning of each test, which would allow variables to be overwritten per test when reuse is requested, right? This is an excellent idea, and makes the variable substitution more intuitive.
If you can send me a patch (the formatting does not get carried over in email cut and paste), I can update the code and this can be made available in the mext release.
Thanks very much,
Sujit
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I put in a test case in the test/mock/variabletests.xml and the code changes you enumerated in your post. The test name="Reusing previously set variable". Its in CVS now, I still have to update the docs to remove the restriction that once you set a variable you can reset only with a set tag.
Thanks for the patch, it will be in the next release.
-sujit
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I discovered a defect with outparam tags that use variables that are set in a param tag from the preceeding call tag. Here is an example of my test:
<test name="running sp_TestKeyRequest">
<call>
<stmt>{? = call TestKeyRequest(?,?,?)}</stmt>
<param id="1" type="INTEGER" inout="out">${status}</param>
<param id="2" type="CHAR" inout="out">${keyVal}</param>
<param id="3" type="INTEGER" inout="out">${errorCode}</param>
<param id="4" type="VARCHAR" inout="out">${errorDesc}</param>
</call>
<result>
<outparam id="1" type="INTEGER">0</outparam>
<outparam id="2" type="CHAR">${keyVal}</outparam>
<outparam id="3" type="INTEGER">0</outparam>
<outparam id="4" type="VARCHAR">success</outparam>
</result>
</test>
SQLUnit is failing on outparam id 2, but it doesn't fail nicely. A null pointer exception was generated in the assertOutParamEqual method but not caught by the TestHandler which would have ordinarily caught and would indicate which test failed (very bad). Since it was not caught, there was no indication which test had failed, but there would be an indication that a test had failed.
I think I've found the root causes. The XMLUtils.getText method incorrectly returns a null value when symValue was a variable but was not found in the SymbolTable. I think that it was meant to return the text in this case which would be not null. If I am correct in my assumption, here is what the snippet of code should be in XMLUtils starting around line 87:
--- XMLUtils.java 2004-11-08 12:21:06.081395600 -0600
+++ XMLUtils.java.newer 2004-11-08 09:51:03.521921000 -0600
@@ -87,7 +87,7 @@
String text = element.getText();
if (SymbolTable.isVariableName(text)) {
String symValue = SymbolTable.getValue(text);
- if (symValue == null) {
+ if (symValue != null) {
return symValue;
} else {
return text;
While this fixes the Null pointer exception, it still doesn't entirely solve the problem since the symbol table doesn't appear to be updated by the resultant out parameters from the call tag.
I've figured out what needs to be changed and can submit patched files. SqlHandler and CallHandler need to change slightly.
Here are the patches:
--- SqlHandler.java 2004-11-08 12:44:24.375799400 -0600
+++ SqlHandler.java.newer 2004-11-08 11:55:48.503568800 -0600
@@ -145,10 +145,14 @@
try {
executeQuery(ps, result);
} catch (SQLException e) {
+ clrOutputParameters(ps, params);
if (!conn.getAutoCommit()) { conn.rollback(); }
ConnectionRegistry.invalidate(connectionId);
result.resetAsException(
(new Integer(e.getErrorCode())).toString(), e.getMessage());
+ } catch (Exception e) {
+ clrOutputParameters(ps, params);
+ throw e;
}
setOutputParameters(ps, params, result);
release(ps, conn);
@@ -303,6 +307,19 @@
}
/**
+ * Clears output parameter symbol table variables. This is a no-op
+ * in the case of this handler, but will contain code for the CallHandler.
+ * @param ps the Statement object.
+ * @param params the array of Param objects.
+ * @exception Exception if one is thrown by this method.
+ */
+ protected void clrOutputParameters(final Statement ps,
+ final Param[] params)
+ throws Exception {
+ return;
+ }
+
+ /**
* Closes Statement and Connection objects and releases them.
* @param ps the Statement object.
* @param conn the Connection object.
--- CallHandler.java 2004-11-08 12:44:09.703970900 -0600
+++ CallHandler.java.newer 2004-11-08 12:08:37.317397100 -0600
@@ -155,7 +155,7 @@
for (int i = 0; i < params.length; i++) {
if (params[i].isOutParameter()) {
if (SymbolTable.isVariableName(params[i].getValue())) {
- SymbolTable.setValue(SymbolTable.OUT_PARAM + i + "}",
+ SymbolTable.setValue(SymbolTable.OUT_PARAM + tcs + ":" + i + "}",
params[i].getValue());
}
int sqltype = params[i].getSQLType();
@@ -214,16 +214,23 @@
outParam.setId(params[i].getId());
outParam.setName(params[i].getName());
outParam.setType(params[i].getType());
+ String outParamSymbol = SymbolTable.removeSymbol(SymbolTable.OUT_PARAM + tcs + ":" + i + "}");
// get the value from the CallableStatement
Object value = tcs.getObject(i + 1);
if (value instanceof ResultSet) {
// value is a resultset
ResultSetBean rsb = new ResultSetBean((ResultSet) value, 1);
outParam.setValue(rsb);
+ if ( outParamSymbol != null ) {
+ SymbolTable.setObject(outParamSymbol, rsb);
+ }
} else {
// value is a String
IType type = TypeFactory.getInstance(params[i].getType());
outParam.setValue(type.toString(value));
+ if (outParamSymbol != null) {
+ SymbolTable.setValue(outParamSymbol,type.toString(value));
+ }
}
outParamList.add(outParam);
}
@@ -234,4 +241,23 @@
}
result.setOutParams(ops);
}
+
+ /**
+ * Overrides the parent method to clear parameters into the
+ * DatabaseResult object.
+ * @param cs the Statement object.
+ * @param params the array of Param objects.
+ * @param result the DatabaseResult object.
+ * @exception Exception if there was a problem setting the OUT params.
+ */
+ protected final void clrOutputParameters(final Statement cs,
+ final Param[] params)
+ throws Exception {
+ CallableStatement tcs = (CallableStatement) cs;
+ for (int i = 0; i < params.length; i++) {
+ if (params[i].isOutParameter()) {
+ SymbolTable.removeSymbol(SymbolTable.OUT_PARAM + tcs + ":" + i + "}");
+ }
+ }
+ }
}
Hi Mark,
Yes, the XMLUtils#getText() problem is definitely a bug, thanks for pointing it out.
BTW, here is a list of "rules" for variable substitution (this will need to be updated after your patch):
http://sqlunit.sourceforge.net/c2606.html
In this case it looks like ${keyVal} is being used as both an lvalue (when being assigned as an out parameter in the call) and as an rvalue (when being compared with the value returned from the procedure in the outparameter in the result tag).
From what I understand from your patch, you are clearing the declared outparam values at the beginning of each test, which would allow variables to be overwritten per test when reuse is requested, right? This is an excellent idea, and makes the variable substitution more intuitive.
If you can send me a patch (the formatting does not get carried over in email cut and paste), I can update the code and this can be made available in the mext release.
Thanks very much,
Sujit
Hi Mark,
I put in a test case in the test/mock/variabletests.xml and the code changes you enumerated in your post. The test name="Reusing previously set variable". Its in CVS now, I still have to update the docs to remove the restriction that once you set a variable you can reset only with a set tag.
Thanks for the patch, it will be in the next release.
-sujit