The following page summarizes the results for the manual inspection of 20
randomly chosen mutations
for the Jaxen XPath engine.
Further details on the study can be found in the corresponding paper The Impact of Equivalent Mutants
Every mutation is described as follows:
- First, a table gives the location, the type of the mutation, and whether it was considered non-equivalent
- Next, the source code context of this mutation, with the mutated line in bold, is depicted.
- After that a source code statement that corresponds to the mutation is shown. Although the mutations are applied to bytecode directly, the mutated statements are given in Java source code, if possible. (Not all mutations that are legal bytecode can be expressed in Java source code)
- Then there is a text that explains why the mutation was considered to be equivalent or not.
- Finally, if possible, we provide a unit test that detects the mutation.
20 randomly chosen mutations
Mutation 1
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.saxpath.base.Verifier | 278 | replace numerical constant (X-1) | true |
Source Code Context
if (c < 0x1F18) return false; if (c <= 0x1F1D) return true;
if (c < 0x1F20) return false; if (c <= 0x1F45) return true;
if (c < 0x1F48) return false; if (c <= 0x1F4D) return true;
if (c < 0x1F50) return false; if (c <= 0x1F57) return true;
Mutated Statement
Description
Mutation causes a character to be qualified as non-valid XML character, although it is a valid character.
Unit Test for Mutation
char c = 0x1F20 - 1;
String s = "" + c;
try {
XPath xpath = new DOMXPath(s);
fail("Expected an exception");
} catch (XPathSyntaxException e) {
// Expect this
}
};
Mutation 2
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.dom.DocumentNavigator | 360 | omit method call | false |
Source Code Context
String myPrefix = n.getPrefix();
if (!nsMap.containsKey(myPrefix) ) {
NamespaceNode ns = new NamespaceNode((Node) contextNode, myPrefix, myNamespace);
nsMap.put(myPrefix, ns);
}
}
Mutated Statement
Description
Mutation suppresses a check if a value for a key is already contained in the map, which lets the if-condition to always evaluate to true. This however does not alter the program behavior, since the object that is constructed, as a value for the map is always the same, and thus an equal element gets added again.
Unit Test for Mutation
No test since the mutation is considered to be equivalent
Mutation 3
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.pattern.UnionPattern | 62 | replace numerical constant (X+1) | false |
Source Code Context
{
this.nodeType = ANY_NODE;
this.lhs = lhs;
this.rhs = rhs;
init();
}
Note: The first statement in the constructor cannot be found in this form in the original source code. It corresponds to a field declaration and initialization that is inserted into the constructor by the compiler. Since the mutations are carried out on bytecode this statement gets mutated.
Mutated Statement
Description
Mutation sets a different value for the field nodeType at the beginning of the constructor. This value however is reset in the init() method that is called at the end of the constructor.
Unit Test for Mutation
No test since the mutation is considered to be equivalent
Mutation 4
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.jdom.DocumentNavigator | 256 | omit method call | undecided |
Source Code Context
if ( !nsMap.containsKey(ns.getPrefix()) )
nsMap.put( ns.getPrefix(), new XPathNamespace(elem, ns) );
}
Mutated Statement
Description
Mutation could result in a namespace bound to its last occurrence rather than
its first. However none of the authors could write a test triggering this
behavior. When specifying a namespace twice for an element another exception is
thrown such that this erroneous behavior cannot be triggered.
Since the two authors of the study disagreed on the equivalence of this mutant
and none could write a test this mutation was left undecided.
Unit Test for Mutation
No test since the mutation was undecided
Mutation 5
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.expr.DefaultStep | 159 | omit method call | true |
Source Code Context
{
unique.add( eachAxisNode );
interimSet.add( eachAxisNode );
}
Mutated Statement
Description
The mutation can result in a returned set with more entries than necessary.
Unit Test for Mutation
Navigator nav = new DocumentNavigator();
String url = "xml/testNamespaces.xml";
Object document = nav.getDocument(url);
XPath contextpath = new BaseXPath("/Template/Application1", nav);
List list = contextpath.selectNodes(document);
Object context = list.get(0);
String xpathStr = "/descendant-or-self::node()";
DOMXPath xpath = new DOMXPath(xpathStr);
List results = xpath.selectNodes(getContext(context));
assertEquals(25, results.size());
}
Note: This test uses some methods from org.jaxen.test.BaseXPathTest
Mutation 6
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.dom.NamespaceNode | 147 | omit method call | false |
Source Code Context
if (attributeName.equals("xmlns")) {
this.name = "";
}
Mutated Statement
Description
The mutation would cause an error if the name of the namespace node, however inserting such a node in an xml element produces another exception ( [Fatal Error] testNamespaces14.xml:4:54: The prefix ""xmlns"" cannot be bound to any namespace explicitly; neither can the namespace for ""xmlns"" be bound to any prefix explicitly).
Unit Test for Mutation
No test since the mutation is considered to be equivalent
Mutation 7
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.pattern.LocationPathPattern | 103 | omit method call | true |
Source Code Context
{
ancestorPattern = ancestorPattern.simplify() ;
}
Mutated Statement
Description
Mutation causes a wrong path for some Xpath expressions.
Unit Test for Mutation
String path = "foo//bar";
Pattern pattern = PatternParser.parse(path);
Navigator nav = getNavigator();
String url = "xml/text.xml";
Object document = nav.getDocument(url);
XPath xpath = new BaseXPath(path, getNavigator());
List result = xpath.selectNodes(document);
boolean matches = pattern.matches(result.get(0), getContext(document));
assertFalse(matches);
}
Note: This test uses some methods from org.jaxen.test.BaseXPathTest
Mutation 8
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.saxpath.base.XPathLexer | 491 | omit method call | undecided |
Source Code Context
getXPath(),
currentPosition(),
currentPosition()+2 );
Mutated Statement
null,
currentPosition(),
currentPosition()+2 );
Description
The mutation results in a text that is not set for an XPath node. This text may
be used for node checks or exception messages.
Since the two authors disagreed on the equivalence of this mutation and none of
them could write a test it was left undecided.
Unit Test for Mutation
No test since the mutation was undecided
Mutation 9
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.expr.NodeComparator | 147 | replace numerical constant (X+1) | false |
Source Code Context
int depth = 0;
Object parent = o;
while ((parent = navigator.getParentNode(parent)) != null) {
depth++;
}
return depth;
}
Mutated Statement
Description
Due to the mutation, the method returns an increased depth. However this is a private method and is only called in the compareTo() method. Since a higher depth value is returned for both nodes that are compared, the compareTo() method remains correct.
Unit Test for Mutation
No test since the mutation is considered to be equivalent
Mutation 10
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.function.ext.LocaleFunctionSupport | 98 | negate jump | true |
Source Code Context
else {
String text = StringFunction.evaluate( value, navigator );
if (text != null && text.length() > 0)
{
return findLocale( text );
}
}
return null;
}
Mutated Statement
Description
Mutation returns null in some cases although a locale may be returned. When a Turkish locale is used to build an uppercase string this erroneous behavior is triggered.
Unit Test for Mutation
{
Navigator nav = getNavigator();
String url = "xml/webLocaleUpperCase.xml";
log("Document [" + url + "]");
Object document = nav.getDocument(url);
XPath contextpath = new BaseXPath("/web-app/servlet[1]", nav);
log("Initial Context :: " + contextpath);
List list = contextpath.selectNodes(document);
Iterator iter = list.iterator();
while (iter.hasNext())
{
Object context = iter.next();
String xpathStr= "upper-case( servlet-class, 'tr' )";
DOMXPath xpath = new DOMXPath(xpathStr);
Object node = xpath.evaluate(getContext(context));
String result = StringFunction.evaluate(node,
getNavigator());
assertEquals(0x130, (int) result.charAt(0));
}
}
Mutation 11
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.pattern.PatternParser | 104 | omit method call | false |
Source Code Context
PathReader reader = XPathReaderFactory.createReader();
JaxenHandler handler = new JaxenHandler();
handler.setXPathFactory( new DefaultXPathFactory() );
reader.setXPathHandler( handler );
reader.parse( text );
...
Mutated Statement
Description
Mutation suppresses the call to setXPathFactory(). However this call just sets the default value again, such that its omission has no effect.
Unit Test for Mutation
No test since the mutation is considered to be equivalent
Mutation 12
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.expr.DefaultLocationPath | 147 | replace numerical constant (0) | false |
Source Code Context
Collections.sort(contextNodeSet, new NodeComparator(support.getNavigator()));
}
Mutated Statement
Description
Mutation causes the collection to be sorted when it has a size of at least one instead of two. This mutation has does not change the program behavior since a sorting a list of size one has no effect on the program's semantics.
Unit Test for Mutation
No test since the mutation is considered to be equivalent
Mutation 13
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.saxpath.base.XPathReader | 131 | omit method call | true |
Source Code Context
filterExpr();
if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH )
{
XPathSyntaxException ex = createSyntaxException("Node-set expected");
throw ex;
}
...
Mutated Statement
Description
Mutation causes an exception not to be thrown when a literal is followed by a double slash
Unit Test for Mutation
{
Navigator nav = getNavigator();
String url = "xml/nitf.xml";
Object document = nav.getDocument(url);
XPath contextpath = new BaseXPath("/", nav);
List list = contextpath.selectNodes(document);
Iterator iter = list.iterator();
while (iter.hasNext())
{
Object context = iter.next();
try{
assertCountXPath(1, context, "/nitf//meta[@name='ap-cycle'//]");
fail("Expected exception");
}
catch(XPathSyntaxException exc){
//expect this;
assertTrue(exc.getMessage().contains("Node-set expected"));
}
}
}
Mutation 14
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.xom.DocumentNavigator | 377 | replace numerical constant (0) | false |
Source Code Context
{
if (uri != null && uri.length() > 0 && (! map.containsKey(prefix))) {
map.put(prefix, new XPathNamespace(elt, uri, prefix));
return true;
}
return false;
}
Mutated Statement
Description
Mutation returns false instead of true. The return value, however, is not checked in the program and the method is private. Thus the mutation has no effect on the program.
Unit Test for Mutation
No test since the mutation is considered to be equivalent
Mutation 15
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.xom.DocumentNavigator$IndexIterator | 217 | replace numerical constant (0) | false |
Source Code Context
private Object o = null;
private int pos = 0, end = -1;
public IndexIterator(Object o, int pos, int end) {
this.o = o;
this.pos = pos;
this.end = end;
}
Mutated Statement
Description
Mutation sets a different default value for a field. In the constructor the field is set to another value. Thus, the default value is never used and changing the default value has no effect.
Unit Test for Mutation
No test since the mutation is considered to be equivalent
Mutation 16
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.function.NumberFunction | 210 | replace numerical constant (X-1) | true |
Source Code Context
{
if ( obj == Boolean.TRUE )
{
return new Double( 1 );
}
else
{
return new Double(0);
}
}
...
Mutated Statement
Description
Mutation sets a different double representation for the false value. When comparing zero directly to false this error can be triggered.
Unit Test for Mutation
XPath xpath = new DOMXPath("( 0 <= false())");
Boolean result = (Boolean) xpath.evaluate(doc);
assertTrue(result.booleanValue());
}
Mutation 17
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.JaxenRuntimeException | 66 | replace numerical constant (X-1) | true |
Source Code Context
super(message);
causeSet = false;
}
Mutated Statement
Description
Mutation replaces the initial value of causeSet from false to true. This prevents the possibility to set a cause.
Unit Test for Mutation
JaxenRuntimeException e = new JaxenRuntimeException("A");
try {
e.initCause(e);
fail();
} catch (IllegalArgumentException iae) {
// Expect this
}
e.initCause(new RuntimeException("B"));
try {
e.initCause(new RuntimeException("C"));
fail();
} catch (IllegalStateException ise) {
// Expect this
}
}
Mutation 18
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.XPathSyntaxException | 135 | replace numerical constant (X-1) | true |
Source Code Context
{
StringBuffer buf = new StringBuffer();
int pos = getPosition();
for ( int i = -1; i < pos ; ++i )
{
buf.append(" ");
}
...
Mutated Statement
Description
Mutation sets the beginning of a loop from -1 to 0. This causes a different length for a String that points to an error in the XPath query.
Unit Test for Mutation
try {
new DOMXPath("///triple slash");
fail("Bad parsing");
}
catch (XPathSyntaxException ex) {
assertEquals(" ^", ex.getPositionMarker());
}
}
Mutation 19
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.Context | 101 | replace numerical constant (X-1) | true |
Source Code Context
{
this.contextSupport = contextSupport;
this.nodeSet = Collections.EMPTY_LIST;
this.size = 0;
this.position = 0;
}
Mutated Statement
Description
Mutation causes the size field to be initialized with a wrong value, which can be checked by a test.
Unit Test for Mutation
Context c = new Context(null);
assertEquals(0, c.getSize());
}
Mutation 20
Class | Line Number | Mutation Type | Non-Equivalent |
---|---|---|---|
org.jaxen.expr.DefaultMultiplyExpr | 75 | omit method call | true |
Source Code Context
{
Number lhsValue = NumberFunction.evaluate( getLHS().evaluate( context ),
context.getNavigator() );
Number rhsValue = NumberFunction.evaluate( getRHS().evaluate( context ),
context.getNavigator() );
double result = lhsValue.doubleValue() * rhsValue.doubleValue();
return new Double( result );
}
Mutated Statement
Description
Mutation sets the second argument of the evaluate call to null, such that it is invoked without a navigator. This cause different behavior when the left hand side is of a multiplicative expression is using a query to compute its value.
Unit Test for Mutation
Navigator nav = getNavigator();
String url = "xml/numbers.xml";
Object document = nav.getDocument(url);
XPath contextpath = new BaseXPath("/", nav);
List list = contextpath.selectNodes(document);
Iterator iter = list.iterator();
while (iter.hasNext()) {
Object context = iter.next();
assertValueOfXPath("true", context,
"(/numbers/set/nr = '-3') *(8 * 2 + 1) = 17");
}
}
Appendix
Mutation Types
- Replace a numerical constant X by X+1, X-1, or 0.
- Negate jump condition. Replace a conditional jump by its counterpart. This is equivalent to negating a conditional statement in the source code.
- Replace arithmetic operator. Replace an arithmetic operator by another arithmetic operator, e.g. + by -, or <<, >>
- Omit a method call. If the method has a return value, a default value is used instead, e.g. x = Math.random() is replaced by x = 0.0 .