8290836: Improve test coverage for XPath functions: String Functions

8290837: Improve test coverage for XPath functions: Boolean Functions
8290838: Improve test coverage for XPath functions: Number Functions

Reviewed-by: joehw
This commit is contained in:
Bill Huang 2022-08-09 18:26:54 +00:00 committed by Joe Wang
parent ae1a9a0b25
commit d889264c61
5 changed files with 818 additions and 15 deletions

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package xpath;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.w3c.dom.Document;
import javax.xml.xpath.XPathExpressionException;
/*
* @test
* @bug 8290837
* @library /javax/xml/jaxp/unittest
* @run testng xpath.XPathBooleanFnTest
* @summary Tests the XPath Boolean Functions
*/
public class XPathBooleanFnTest extends XPathTestBase {
private static final Document doc = getDtdDocument();
/*
* DataProvider for testing the boolean, not, true, false and lang
* functions.
* Data columns:
* see parameters of the test "testBooleanFn"
*/
@DataProvider(name = "booleanExpTestCases")
public Object[][] getBooleanExp() {
return new Object[][]{
{"true()", true},
{"false()", false},
{"boolean(true())", true},
{"boolean(false())", false},
{"boolean(1)", true},
{"boolean(0)", false},
{"boolean(-1)", true},
{"boolean(1+1)", true},
{"boolean(1-1)", false},
{"boolean(1+'abc')", false},
{"boolean('abc')", true},
{"boolean('')", false},
{"boolean(//Name)", true},
{"boolean(//LastName)", false},
{"boolean(//Customer[1]/ClubMember)", true},
{"boolean(//Customer[2]/ClubMember)", true},
{"boolean(//Customer[2]/ClubMember='true')", false},
{"boolean(//Customer[1]/ClubMember or " +
"//Customer[2]/ClubMember)", true},
{"boolean(//Customer[1]/ClubMember and " +
"//Customer[2]/ClubMember)", true},
{"boolean(//*[boolean(.)=true()])", true},
{"boolean(//*[boolean(.)=false()])", false},
{"not(1)", false},
{"not(-1)", false},
{"not(0)", true},
{"not(true())", false},
{"not(false())", true},
{"not(//Customer[1]/ClubMember)", false},
{"not(//Customer[2]/ClubMember)", false},
{"not(//Customer[2]/ClubMember='true')", true},
{"boolean(//*[not(.)=true()])", false},
{"boolean(//*[not(.)=false()])", true},
{"boolean(//*[lang('en')])", true},
{"boolean(//*[lang('es')])", false},
};
}
/*
* DataProvider for testing XPathExpressionException being thrown on
* invalid boolean function usage.
* Data columns:
* see parameters of the test "testExceptionOnEval"
*/
@DataProvider(name = "exceptionExpTestCases")
public Object[][] getExceptionExp() {
return new Object[][]{
// Argument is required for these functions
{"boolean()"},
{"//*[boolean()=true()]"},
{"not()"},
{"//*[not()=true()]"},
{"lang()"},
{"/*[lang()=true()]"},
// No arguments should be passed to these functions
{"true(1)"},
{"false(0)"},
{"//*[true(.)=true()]"},
{"//*[false(.)=false()]"},
};
}
/**
* Verifies that the result of evaluating the boolean, not, true, false
* and lang functions matches the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "booleanExpTestCases")
void testBooleanFn(String exp, boolean expected) throws Exception {
testExp(doc, exp, expected, Boolean.class);
}
/**
* Verifies that XPathExpressionException is thrown on xpath evaluation.
*
* @param exp XPath expression
*/
@Test(dataProvider = "exceptionExpTestCases")
void testExceptionOnEval(String exp) {
Assert.assertThrows(XPathExpressionException.class, () -> testEval(doc,
exp));
}
}

View File

@ -69,29 +69,35 @@ public class XPathNodeSetFnTest extends XPathTestBase {
{"count(//Customer)", CUSTOMERS},
{"count(//@id)", ID_ATTRIBUTES},
{"count(//Customer/@id)", CUSTOMERS},
{"count(//@*)", ID_ATTRIBUTES + FOO_ID_ATTRIBUTES},
{"count(//@*)",
LANG_ATTRIBUTES + ID_ATTRIBUTES + FOO_ID_ATTRIBUTES},
{"count(//*)",
ROOT + CUSTOMERS + FOO_CUSTOMERS +
(CUSTOMERS + FOO_CUSTOMERS) *
CUSTOMER_ELEMENTS},
(CUSTOMER_ELEMENTS + ADDRESS_ELEMENTS)},
{"count(//*[@id])", ID_ATTRIBUTES},
{"count(./*)", ROOT},
{"count(.)", ROOT},
{"count(//Customer[1]/following::*)",
CUSTOMERS - 1 + FOO_CUSTOMERS +
(CUSTOMERS - 1 + FOO_CUSTOMERS) *
CUSTOMER_ELEMENTS},
(CUSTOMER_ELEMENTS + ADDRESS_ELEMENTS)},
{"count(//Customer[1]/following-sibling::*)",
CUSTOMERS - 1 + FOO_CUSTOMERS},
{"count(//Customer[3]/preceding::*)",
CUSTOMERS - 1 + (CUSTOMERS - 1) * CUSTOMER_ELEMENTS},
CUSTOMERS - 1 + (CUSTOMERS - 1) *
(CUSTOMER_ELEMENTS + ADDRESS_ELEMENTS)},
{"count(//Customer[3]/preceding-sibling::*)", CUSTOMERS - 1},
{"count(//Customer[1]/ancestor::*)", ROOT},
{"count(//Customer[1]/ancestor-or-self::*)", ROOT + 1},
{"count(//Customer[1]/descendant::*)", CUSTOMER_ELEMENTS},
{"count(//Customer[1]/descendant::*)",
CUSTOMER_ELEMENTS + ADDRESS_ELEMENTS},
{"count(//Customer[1]/descendant-or-self::*)",
CUSTOMER_ELEMENTS + 1},
CUSTOMER_ELEMENTS + ADDRESS_ELEMENTS + 1},
// node() returns all children of the context node including
// element nodes and text nodes.
{"count(//Customer/node())",
ID_ATTRIBUTES + CUSTOMERS * CUSTOMER_ELEMENTS},
CUSTOMERS + CUSTOMERS * (CUSTOMER_ELEMENTS * 2)},
};
}
@ -104,6 +110,7 @@ public class XPathNodeSetFnTest extends XPathTestBase {
public Object[][] getPositionExp() {
return new Object[][]{
{"//Customer[position()=1]", "Customer_x1"},
{"//Customer[position()]", "Customer_x1"},
{"//Customer[position()=last()]", "Customer_x3"},
{"//Customer[position()>1 and position()<last()]",
"Customer_x2"},
@ -125,6 +132,7 @@ public class XPathNodeSetFnTest extends XPathTestBase {
{"local-name(//Customer/@id)", "id"},
{"local-name(//foo:Customer/@foo:id)", "id"},
{"local-name(//*[local-name()='Customer'])", "Customer"},
{"local-name(//*[local-name(.)='Customer'])", "Customer"},
{"namespace-uri(.)", ""},
{"namespace-uri(//Customers)", ""},
{"namespace-uri(//Customer)", ""},
@ -132,11 +140,54 @@ public class XPathNodeSetFnTest extends XPathTestBase {
{"namespace-uri(//@id)", ""},
{"namespace-uri(//@foo:id)", "foo"},
{"name(//*[namespace-uri()=\"foo\"])", "foo:Customer"},
{"name(//*[namespace-uri(.)=\"foo\"])", "foo:Customer"},
{"name(//Customer)", "Customer"},
{"name(//foo:Customer)", "foo:Customer"},
{"name(//Customer/@id)", "id"},
{"name(//foo:Customer/@foo:id)", "foo:id"},
{"name(//*[name()='foo:Customer'])", "foo:Customer"},
{"name(//*[name(.)='foo:Customer'])", "foo:Customer"},
};
}
/*
* DataProvider for testing XPathExpressionException being thrown on
* invalid node set function usage.
* Data columns:
* see parameters of the test "testExceptionOnEval"
*/
@DataProvider(name = "exceptionExpTestCases")
public Object[][] getExceptionExp() {
return new Object[][]{
// Argument is required for these functions
{"//Customer[id()]"},
{"//Customer[id()='x1']"},
{"//Customer[count()]"},
{"//*[count()=3]"},
// No argument should be passed to these functions
{"//Customer[position(.)]"},
{"//*[position(//Customer[1])]"},
{"//Customer[last(.)]"},
{"//*[last(//Customer[1])]"},
// Node-set argument is required for these functions
{"count(1)"},
{"count(true())"},
{"count('')"},
{"count('abc')"},
{"local-name(1)"},
{"local-name(true())"},
{"local-name('')"},
{"local-name('abc')"},
{"name(1)"},
{"name(true())"},
{"name('')"},
{"name('abc')"},
{"namespace-uri(1)"},
{"namespace-uri(true())"},
{"namespace-uri('')"},
{"namespace-uri('abc')"},
};
}
@ -219,4 +270,15 @@ public class XPathNodeSetFnTest extends XPathTestBase {
Assert.assertEquals(s, expected);
Assert.assertEquals(s2, s);
}
/**
* Verifies that XPathExpressionException is thrown on xpath evaluation.
*
* @param exp XPath expression
*/
@Test(dataProvider = "exceptionExpTestCases")
void testExceptionOnEval(String exp) {
Assert.assertThrows(XPathExpressionException.class, () -> testEval(doc,
exp));
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package xpath;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.w3c.dom.Document;
import javax.xml.xpath.XPathExpressionException;
import java.util.stream.IntStream;
/*
* @test
* @bug 8290838
* @library /javax/xml/jaxp/unittest
* @run testng xpath.XPathNumberFnTest
* @summary Tests the XPath Number Functions
*/
public class XPathNumberFnTest extends XPathTestBase {
private static final Document doc = getDtdDocument();
/*
* DataProvider for testing the number, sum, floor, ceiling and round
* functions.
* Data columns:
* see parameters of the test "testNumberFn"
*/
@DataProvider(name = "numberExpTestCases")
public Object[][] getNumberExp() {
return new Object[][]{
{"number(1)", 1.0},
{"number(-1)", -1.0},
{"number(0)", 0},
{"number(//Customer[2]/Age)", CUSTOMER_AGES[1]},
{"number(//Customer[1]/Age + //Customer[2]/Age)",
CUSTOMER_AGES[0] + CUSTOMER_AGES[1]},
{"number('abc')", Double.NaN},
{"number('')", Double.NaN},
{String.format("number(//Age[number()=%d])", CUSTOMER_AGES[1]),
CUSTOMER_AGES[1]},
{String.format("number(//Age[number(.)=%d])", CUSTOMER_AGES[1]),
CUSTOMER_AGES[1]},
{"number(//Customer[1]/Name)", Double.NaN},
{"number(//Customer[2]/Age + //Customer[1]/Name)", Double.NaN},
{"number(true())", 1},
{"number(false())", 0},
{"sum(//Age)", IntStream.of(CUSTOMER_AGES).sum()},
{"sum(//Customer[2]/Age)", CUSTOMER_AGES[1]},
{"floor(1.1)", 1.0},
{"floor(-1.6)", -2.0},
{"floor(1.0 div 0)", Double.POSITIVE_INFINITY},
{"floor(-1.0 div 0)", Double.NEGATIVE_INFINITY},
{"floor(true())", 1},
{"floor(false())", 0},
{"floor(abc)", Double.NaN},
{"floor('')", Double.NaN},
{"floor(//Customer[2]/Age)", CUSTOMER_AGES[1]},
{"floor(//Customer[1]/Name)", Double.NaN},
{String.format("number(//Age[floor(.)=%d])", CUSTOMER_AGES[1]),
CUSTOMER_AGES[1]},
{"ceiling(1.1)", 2.0},
{"ceiling(-1.4)", -1.0},
{"ceiling(1.0 div 0)", Double.POSITIVE_INFINITY},
{"ceiling(-1.0 div 0)", Double.NEGATIVE_INFINITY},
{"ceiling(true())", 1},
{"ceiling(false())", 0},
{"ceiling(abc)", Double.NaN},
{"ceiling('')", Double.NaN},
{"ceiling(//Customer[2]/Age)", CUSTOMER_AGES[1]},
{"ceiling(//Customer[1]/Name)", Double.NaN},
{String.format("number(//Age[ceiling(.)=%d])",
CUSTOMER_AGES[1]), CUSTOMER_AGES[1]},
{"round(1.49)", 1.0},
{"round(1.5)", 2.0},
{"round(-1.5)", -1.0},
{"round(-1.51)", -2.0},
{"round(1.0 div 0)", Double.POSITIVE_INFINITY},
{"round(-1.0 div 0)", Double.NEGATIVE_INFINITY},
{"round(true())", 1},
{"round(false())", 0},
{"round(abc)", Double.NaN},
{"round('')", Double.NaN},
{"round(//Customer[2]/Age)", CUSTOMER_AGES[1]},
{"round(//Customer[1]/Name)", Double.NaN},
{String.format("number(//Age[round(.)=%d])",
CUSTOMER_AGES[1]), CUSTOMER_AGES[1]},
};
}
/*
* DataProvider for testing XPathExpressionException being thrown on
* invalid number function usage.
* Data columns:
* see parameters of the test "testExceptionOnEval"
*/
@DataProvider(name = "exceptionExpTestCases")
public Object[][] getExceptionExp() {
return new Object[][]{
// Argument is required for these functions
{"//Age[floor()=1.0]"},
{"(/Age[ceiling()=1.0]"},
{"//Age[round()=1.0]"},
{"//Age[sum()]"},
// Node-set argument is required for these functions
{"sum(1)"},
{"sum(true())"},
{"sum('')"},
{"sum('abc')"},
};
}
/**
* Verifies that the result of evaluating the number, sum, floor, ceiling
* and round functions matches the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "numberExpTestCases")
void testNumberFn(String exp, double expected) throws Exception {
testExp(doc, exp, expected, Double.class);
}
/**
* Verifies that XPathExpressionException is thrown on xpath evaluation.
*
* @param exp XPath expression
*/
@Test(dataProvider = "exceptionExpTestCases")
void testExceptionOnEval(String exp) {
Assert.assertThrows(XPathExpressionException.class, () -> testEval(doc,
exp));
}
}

View File

@ -0,0 +1,384 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package xpath;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.w3c.dom.Document;
import javax.xml.xpath.XPathExpressionException;
/*
* @test
* @bug 8290836
* @library /javax/xml/jaxp/unittest
* @run testng xpath.XPathStringFnTest
* @summary Tests the XPath String Functions
*/
public class XPathStringFnTest extends XPathTestBase {
private static final Document doc = getDtdDocument();
/*
* DataProvider for testing the string function.
* Data columns:
* see parameters of the test "testStringFn"
*/
@DataProvider(name = "stringExpTestCases")
public Object[][] getStringExp() {
return new Object[][]{
{"string(-0.0)", "0"},
{"string(0-1)", "-1"},
{"string(1=1)", "true"},
{"string(1>2)", "false"},
{"string(1+a)", "NaN"},
{"string(1.0 div 0)", "Infinity"},
{"string(-1.0 div 0)", "-Infinity"},
{"string(//Customer/Name)", "name1"},
{"string(//Customer[1]/@id)", "x1"},
{"string(//Customer/LastName)", ""},
{"string(//Customer[1]/Age)",
Integer.toString(CUSTOMER_AGES[0])},
{"string(number(//Customer[2]/Age))",
Integer.toString(CUSTOMER_AGES[1])},
{"string(//Customer[1]/Age + //Customer[2]/Age)",
Integer.toString(CUSTOMER_AGES[0] + CUSTOMER_AGES[1])},
{"string(//Customer[1]/Age + //Customer[1]/Name)", "NaN"},
{"string(//Customer[1]/ClubMember='true')", "true"},
{"string(//Customer[2]/ClubMember='true')", "false"},
{"string(//Customer[1]/ClubMember or " +
"//Customer[2]/ClubMember)", "true"},
{"string(//Customer[1]/ClubMember and " +
"//Customer[2]/ClubMember='true')", "false"},
{"string(//*[string()='name2'])", "name2"},
{"string(//*[string(.)='name2'])", "name2"},
{"string(//*[string(Name)='name2']/Name)", "name2"},
{"string(//*[string(@id)='x1']//Street)", "1111 111st ave"},
{"string(//*[string(../@id)='x1']/Street)", "1111 111st ave"},
};
}
/*
* DataProvider for testing the concat function.
* Data columns:
* see parameters of the test "testConcatFn"
*/
@DataProvider(name = "concatExpTestCases")
public Object[][] getConcatExp() {
return new Object[][]{
{"concat('Hello', ' name', 1, true())", "Hello name1true"},
{"concat('Hello ', //Customer/Name)", "Hello name1"},
{"concat('Hello ', //Customer/LastName)", "Hello "},
{"concat('(', //@id, ')')", "(x1)"},
};
}
/*
* DataProvider for testing the substring, substring-before and
* substring-after functions.
* Data columns:
* see parameters of the test "testSubstringFn"
*/
@DataProvider(name = "substringExpTestCases")
public Object[][] getSubstringExp() {
return new Object[][]{
{"substring('123@xyz.com', 5, 7)", "xyz.com"},
{"substring('123@xyz.com', 5, 10)", "xyz.com"},
{"substring(//Email, 5, 7)", "xyz.com"},
{"substring(//Email, 5)", "xyz.com"},
{"substring(//Email, 1, string-length(.))", "123@xyz.com"},
{"substring(//Email, 1, string-length(.)+1)", "123@xyz.com"},
{"substring(//LastName, 1)", ""},
{"string(//*[substring(., 1)='name2'])", "name2"},
{"substring-before('123@xyz.com', '@')", "123"},
{"substring-before(//Email, '@')", "123"},
{"substring-before(//Email, '?')", ""},
{"substring-before(//Email, '')", ""},
{"substring-before(//LastName, '')", ""},
{"string(//*[substring-before(., ' ')='1111'])", "1111 111st " +
"ave"},
{"substring-after('123@xyz.com', '@')", "xyz.com"},
{"substring-after(//Email, '@')", "xyz.com"},
{"substring-after(//Email, '?')", ""},
{"substring-after(//Email, '')", "123@xyz.com"},
{"substring-after(//LastName, '')", ""},
{"string(//*[substring-after(., ' ')='111st ave'])", "1111 " +
"111st ave"},
};
}
/*
* DataProvider for testing the normalize-space function.
* Data columns:
* see parameters of the test "testNormalizeSpaceFn"
*/
@DataProvider(name = "normalizeExpTestCases")
public Object[][] getNormalizeExp() {
return new Object[][]{
{"normalize-space(' 1111 111st ave ')", "1111 111st ave"},
{"normalize-space(true())", "true"},
{"normalize-space(1.234)", "1.234"},
{"normalize-space(//Customer[1]//Street)", "1111 111st ave"},
{"normalize-space(//Customer[2]//Street)", "2222 222nd ave"},
{"normalize-space(//LastName)", ""},
{"string(//*[normalize-space()='name1'])", "name1"},
{"string(//*[normalize-space(.)='name1'])", "name1"},
{"string(//*[normalize-space(Name)='name2']/Name)", "name2"},
{"string(//*[normalize-space(@id)='x1']//Street)", "1111 " +
"111st ave"},
{"string(//*[normalize-space(../@id)='x1']/Street)", "1111 " +
"111st ave"},
};
}
/*
* DataProvider for testing the translate function.
* Data columns:
* see parameters of the test "testTranslateFn"
*/
@DataProvider(name = "translateExpTestCases")
public Object[][] getTranslateExp() {
return new Object[][]{
{"translate('1111 111st ave', ' ', '')", "1111111stave"},
{"translate('1111 111st ave', '', '')", "1111 111st ave"},
{"translate('1111 111st ave', '1 ', '')", "stave"},
{"translate('abcabcdcd', 'abcd', 'xyz')", "xyzxyzz"},
{"translate('abcabcdcd', 'bcd', 'uvwxyz')", "auvauvwvw"},
{"translate('aabccdacbabcb', 'aaccbbdd', 'wxyz')", "wwyywywy"},
{"translate(//Customer[1]//Street, " +
"'abcdefghijklmnopqrstuvwxyz', " +
"'ABCDEFGHIJKLMNOPQRSTUVWXYZ')",
"1111 111ST AVE"},
{"translate(//LastName, 'name', 'NAME')", ""},
{"translate(true(), true(), false())", "fals"},
{"translate(123, 2, 3)", "133"},
};
}
/*
* DataProvider for testing the string-length function.
* Data columns:
* see parameters of the test "testStringLengthFn"
*/
@DataProvider(name = "stringLengthExpTestCases")
public Object[][] getStringLengthExp() {
return new Object[][]{
{"string-length('')", 0},
{"string-length(123)", 3},
{"string-length(true())", 4},
{"string-length('1111 111st ave')", 14.0},
{"string-length(//Customer[1]//Street)", 14.0},
{"string-length(//LastName)", 0},
{"string-length(//Customer[1]/Name)", 5},
{"string-length(//Customer[1]/@id)", 2},
{"string-length(name(//*[string-length()=10]))", 5},
{"string-length(name(//*[string-length(.)=10]))", 5},
};
}
/*
* DataProvider for testing the starts-with function.
* Data columns:
* see parameters of the test "testStartsWithFn"
*/
@DataProvider(name = "startsWithExpTestCases")
public Object[][] getStartsWithExp() {
return new Object[][]{
{"starts-with(//Email, '123')", true},
{"starts-with(//Email, 123)", true},
{"starts-with(//Email, '?')", false},
{"starts-with(//Email, '')", true},
{"starts-with(//Customer/@id, 'x')", true},
{"starts-with(//LastName, '')", true},
{"boolean(//*[starts-with(., 'name2')]='name2')", true},
{"boolean(//*[starts-with(text(), 'name')]='name2')", true},
{"boolean(//*[starts-with(., 'name1')]/.='name2')", false},
};
}
/*
* DataProvider for testing the contains function.
* Data columns:
* see parameters of the test "testContainsFn"
*/
@DataProvider(name = "containsExpTestCases")
public Object[][] getContainsExp() {
return new Object[][]{
{"contains(//Email, '123')", true},
{"contains(//Email, 123)", true},
{"contains(//Email, '?')", false},
{"contains(//Email, '')", true},
{"contains(//Customer/@id, 'x')", true},
{"contains(//LastName, '')", true},
{"boolean(//*[contains(., 'name2')]='name2')", true},
{"boolean(//*[contains(text(), 'name')]='name2')", true},
{"boolean(//*[contains(., 'name1')]/.='name2')", false},
};
}
/*
* DataProvider for testing XPathExpressionException being thrown on
* invalid string function usage.
* Data columns:
* see parameters of the test "testExceptionOnEval"
*/
@DataProvider(name = "exceptionExpTestCases")
public Object[][] getExceptionExp() {
return new Object[][]{
// At least two arguments are required for these functions
{"//*[concat()='name2']"},
{"//*[concat(.)='name2']"},
{"//*[substring()='name2']"},
{"//*[substring(.)='name2']"},
{"//*[substring-before()='name2']"},
{"//*[substring-before(.)='name2']"},
{"//*[substring-after()='name2']"},
{"//*[substring-after(.)='name2']"},
{"//*[translate()='name2']"},
{"//*[translate(.)='name2']"},
{"//*[contains()]"},
{"//*[contains(.)]"},
{"//*[starts-with()]"},
{"//*[starts-with(.)]"},
};
}
/**
* Verifies that the result of evaluating the string function matches
* the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "stringExpTestCases")
void testStringFn(String exp, String expected) throws Exception {
testExp(doc, exp, expected, String.class);
}
/**
* Verifies that the result of evaluating the concat function matches
* the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "concatExpTestCases")
void testConcatFn(String exp, String expected) throws Exception {
testExp(doc, exp, expected, String.class);
}
/**
* Verifies that the result of evaluating the substring, substring-before
* and substring-after functions matches the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "substringExpTestCases")
void testSubstringFn(String exp, String expected) throws Exception {
testExp(doc, exp, expected, String.class);
}
/**
* Verifies that the result of evaluating the normalize-space function
* matches the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "normalizeExpTestCases")
void testNormalizeSpaceFn(String exp, String expected) throws Exception {
testExp(doc, exp, expected, String.class);
}
/**
* Verifies that the result of evaluating the translate function matches
* the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "translateExpTestCases")
void testTranslateFn(String exp, String expected) throws Exception {
testExp(doc, exp, expected, String.class);
}
/**
* Verifies that the result of evaluating the string-length function matches
* the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "stringLengthExpTestCases")
void testStringLengthFn(String exp, double expected) throws Exception {
testExp(doc, exp, expected, Double.class);
}
/**
* Verifies that the result of evaluating the starts-with function
* matches the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "startsWithExpTestCases")
void testStartsWithFn(String exp, boolean expected) throws Exception {
testExp(doc, exp, expected, Boolean.class);
}
/**
* Verifies that the result of evaluating the contains function matches
* the expected result.
*
* @param exp XPath expression
* @param expected expected result
* @throws Exception if test fails
*/
@Test(dataProvider = "containsExpTestCases")
void testContainsFn(String exp, Boolean expected) throws Exception {
testExp(doc, exp, expected, Boolean.class);
}
/**
* Verifies that XPathExpressionException is thrown on xpath evaluation.
*
* @param exp XPath expression
*/
@Test(dataProvider = "exceptionExpTestCases")
void testExceptionOnEval(String exp) {
Assert.assertThrows(XPathExpressionException.class, () -> testEval(doc,
exp));
}
}

View File

@ -29,15 +29,13 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathNodes;
import javax.xml.xpath.XPathEvaluationResult;
import javax.xml.xpath.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@ -52,11 +50,13 @@ class XPathTestBase {
static final String DTD = """
<!DOCTYPE Customers [
<!ELEMENT Customers (Customer*)>
<!ELEMENT Customer (Name, Phone, Email, Address)>
<!ELEMENT Customer (Name, Phone, Email, Address, Age, ClubMember)>
<!ELEMENT Name (#PCDATA)>
<!ELEMENT Phone (#PCDATA)>
<!ELEMENT Email (#PCDATA)>
<!ELEMENT Address (Street, City, State)>
<!ELEMENT Age (#PCDATA)>
<!ELEMENT ClubMember (#PCDATA)>
<!ELEMENT Street (#PCDATA)>
<!ELEMENT City (#PCDATA)>
<!ELEMENT State (#PCDATA)>
@ -67,7 +67,7 @@ class XPathTestBase {
""";
static final String RAW_XML
= "<Customers xmlns:foo=\"foo\">"
= "<Customers xmlns:foo=\"foo\" xml:lang=\"en\">"
+ " <Customer id=\"x1\">"
+ " <Name>name1</Name>"
+ " <Phone>1111111111</Phone>"
@ -77,16 +77,20 @@ class XPathTestBase {
+ " <City>The City</City>"
+ " <State>The State</State>"
+ " </Address>"
+ " <Age>0</Age>"
+ " <ClubMember>true</ClubMember>"
+ " </Customer>"
+ " <Customer id=\"x2\">"
+ " <Name>name2</Name>"
+ " <Phone>2222222222</Phone>"
+ " <Email id=\"y\">123@xyz.com</Email>"
+ " <Address>"
+ " <Street>2222 222nd ave</Street>"
+ " <Street> 2222 222nd ave </Street>"
+ " <City>The City</City>"
+ " <State>The State</State>"
+ " </Address>"
+ " <Age>100</Age>"
+ " <ClubMember>false</ClubMember>"
+ " </Customer>"
+ " <Customer id=\"x3\">"
+ " <Name>name3</Name>"
@ -97,6 +101,8 @@ class XPathTestBase {
+ " <City>The City</City>"
+ " <State>The State</State>"
+ " </Address>"
+ " <Age>-100</Age>"
+ " <ClubMember>false</ClubMember>"
+ " </Customer>"
+ " <foo:Customer foo:id=\"x1\">"
+ " <foo:Name>name1</foo:Name>"
@ -107,21 +113,28 @@ class XPathTestBase {
+ " <foo:City>The City</foo:City>"
+ " <foo:State>The State</foo:State>"
+ " </foo:Address>"
+ " <foo:Age>0</foo:Age>"
+ " <foo:ClubMember>true</foo:ClubMember>"
+ " </foo:Customer>"
+ "</Customers>";
// Number of root element.
final int ROOT = 1;
// Number of Customer elements.
final int LANG_ATTRIBUTES = 1;
final int CUSTOMERS = 3;
// Number of id attributes.
final int ID_ATTRIBUTES = 6;
// Number of child elements of Customer.
final int CUSTOMER_ELEMENTS = 7;
final int CUSTOMER_ELEMENTS = 6;
// Number of Address elements.
final int ADDRESS_ELEMENTS = 3;
// Number of Customer in the foo namespace.
final int FOO_CUSTOMERS = 1;
// Number of id attributes in the foo namespace.
final int FOO_ID_ATTRIBUTES = 2;
// Customer Ages
final int[] CUSTOMER_AGES = {0, 100, -100, 0};
/**
* Returns a {@link org.w3c.dom.Document} for XML with DTD.
@ -187,6 +200,47 @@ class XPathTestBase {
assertFalse(true, "Unsupported type");
}
/**
* Evaluates XPath expression and checks if it matches the expected result.
*
* @param doc xml document {@link org.w3c.dom.Document}
* @param exp xpath expression string
* @param expected expected result
* @param clazz expected result type for evaluation.
* @param <T> expected result type
*
* @throws Exception if test fails
*/
static <T> void testExp(Document doc, String exp, T expected,
Class<T> clazz) throws Exception {
XPath xPath = XPathFactory.newInstance().newXPath();
T result = xPath.evaluateExpression(exp, doc, clazz);
T result2 = (T) xPath.evaluate(exp, doc,
XPathEvaluationResult.XPathResultType.getQNameType(clazz));
Assert.assertEquals(result, expected);
Assert.assertEquals(result2, result);
}
/**
* Evaluates XPath expression.
*
* @param doc xml document {@link org.w3c.dom.Document}
* @param exp xpath expression string
*
* @throws Exception if test fails
*/
static void testEval(Document doc, String exp) throws Exception {
XPath xPath = XPathFactory.newInstance().newXPath();
try {
xPath.evaluateExpression(exp, doc);
} catch (XPathExpressionException e) {
xPath.evaluate(exp, doc);
}
}
/*
* DataProvider: XPath object
*/