undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
618
test/jdk/java/text/Format/NumberFormat/StrictParseTest.java
Normal file
618
test/jdk/java/text/Format/NumberFormat/StrictParseTest.java
Normal file
|
|
@ -0,0 +1,618 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8327640 8331485 8333755 8335668
|
||||
* @summary Test suite for NumberFormat parsing with strict leniency
|
||||
* @run junit/othervm -Duser.language=en -Duser.country=US StrictParseTest
|
||||
* @run junit/othervm -Duser.language=ja -Duser.country=JP StrictParseTest
|
||||
* @run junit/othervm -Duser.language=zh -Duser.country=CN StrictParseTest
|
||||
* @run junit/othervm -Duser.language=tr -Duser.country=TR StrictParseTest
|
||||
* @run junit/othervm -Duser.language=de -Duser.country=DE StrictParseTest
|
||||
* @run junit/othervm -Duser.language=fr -Duser.country=FR StrictParseTest
|
||||
* @run junit/othervm -Duser.language=ar -Duser.country=AR StrictParseTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.text.CompactNumberFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
// Tests strict parsing, this is done by testing the NumberFormat factory instances
|
||||
// against a number of locales with different formatting conventions. The locales
|
||||
// used all use a grouping size of 3.
|
||||
public class StrictParseTest {
|
||||
|
||||
// Used to retrieve the locale's expected symbols
|
||||
private static final DecimalFormatSymbols dfs =
|
||||
new DecimalFormatSymbols(Locale.getDefault());
|
||||
// We re-use these formats for the respective factory tests
|
||||
private static final DecimalFormat dFmt =
|
||||
(DecimalFormat) NumberFormat.getNumberInstance(Locale.getDefault());
|
||||
private static final DecimalFormat cFmt =
|
||||
(DecimalFormat) NumberFormat.getCurrencyInstance(Locale.getDefault());
|
||||
private static final DecimalFormat pFmt =
|
||||
(DecimalFormat) NumberFormat.getPercentInstance(Locale.getDefault());
|
||||
private static final CompactNumberFormat cmpctFmt =
|
||||
(CompactNumberFormat) NumberFormat.getCompactNumberInstance(Locale.getDefault(),
|
||||
NumberFormat.Style.SHORT);
|
||||
private static final NumberFormat[] FORMATS = new NumberFormat[]{dFmt, cFmt, pFmt, cmpctFmt};
|
||||
|
||||
// Restore defaults before runs
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
for (NumberFormat fmt : FORMATS) {
|
||||
fmt.setStrict(true);
|
||||
fmt.setParseIntegerOnly(false);
|
||||
fmt.setGroupingUsed(true);
|
||||
}
|
||||
// Grouping Size is not defined at NumberFormat level
|
||||
// Compact needs to manually init grouping size
|
||||
cmpctFmt.setGroupingSize(3);
|
||||
}
|
||||
|
||||
// ---- NumberFormat tests ----
|
||||
|
||||
// Guarantee some edge case test input
|
||||
@Test // Non-localized, run once
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void uniqueCaseNumberFormatTest() {
|
||||
// Format with grouping size = 3, prefix = a, suffix = b
|
||||
DecimalFormat nonLocalizedDFmt = new DecimalFormat("a#,#00.00b");
|
||||
nonLocalizedDFmt.setStrict(true);
|
||||
// Text after suffix
|
||||
failParse(nonLocalizedDFmt, "a12bfoo", 3);
|
||||
failParse(nonLocalizedDFmt, "a123,456.00bc", 11);
|
||||
// Text after prefix
|
||||
failParse(nonLocalizedDFmt, "ac123", 0);
|
||||
// Missing suffix
|
||||
failParse(nonLocalizedDFmt, "a123", 4);
|
||||
// Prefix contains a decimal separator
|
||||
failParse(nonLocalizedDFmt, ".a123", 0);
|
||||
// Test non grouping size of 3
|
||||
nonLocalizedDFmt.setGroupingSize(1);
|
||||
successParse(nonLocalizedDFmt, "a1,2,3,4b");
|
||||
failParse(nonLocalizedDFmt, "a1,2,3,45,6b", 8);
|
||||
nonLocalizedDFmt.setGroupingSize(5);
|
||||
successParse(nonLocalizedDFmt, "a12345,67890b");
|
||||
successParse(nonLocalizedDFmt, "a1234,67890b");
|
||||
failParse(nonLocalizedDFmt, "a123456,7890b", 6);
|
||||
}
|
||||
|
||||
|
||||
// 8333755: Check that parsing with integer only against a suffix value works
|
||||
@Test // Non-localized, run once
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void integerOnlyParseWithSuffixTest() {
|
||||
// Some pattern with a suffix
|
||||
DecimalFormat fmt = new DecimalFormat("0.00b");
|
||||
fmt.setParseIntegerOnly(true);
|
||||
assertEquals(5d, successParse(fmt, "5.55b", 1));
|
||||
assertEquals(5d, successParse(fmt, "5b", 2));
|
||||
assertEquals(5555d, successParse(fmt, "5,555.55b", 5));
|
||||
assertEquals(5d, successParse(fmt, "5.55E55b", 1));
|
||||
}
|
||||
|
||||
@Test // Non-localized, only run once
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void badExponentParseNumberFormatTest() {
|
||||
// Some fmt, with an "E" exponent string
|
||||
DecimalFormat fmt = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
|
||||
fmt.setStrict(true);
|
||||
// Upon non-numeric in exponent, parse will exit early and suffix will not
|
||||
// exactly match, causing failure
|
||||
failParse(fmt, "1.23E45.1", 7);
|
||||
failParse(fmt, "1.23E45.", 7);
|
||||
failParse(fmt, "1.23E45FOO3222", 7);
|
||||
}
|
||||
|
||||
// All input Strings should fail
|
||||
@ParameterizedTest
|
||||
@MethodSource("badParseStrings")
|
||||
public void numFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(dFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
// All input Strings should pass and return expected value.
|
||||
@ParameterizedTest
|
||||
@MethodSource("validParseStrings")
|
||||
public void numFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(dFmt, toParse));
|
||||
}
|
||||
|
||||
// All input Strings should fail
|
||||
@ParameterizedTest
|
||||
@MethodSource("negativeBadParseStrings")
|
||||
public void negNumFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(dFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
// All input Strings should pass and return expected value.
|
||||
@ParameterizedTest
|
||||
@MethodSource("negativeValidParseStrings")
|
||||
public void negNumFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(dFmt, toParse));
|
||||
}
|
||||
|
||||
// Exception should be thrown if grouping separator occurs anywhere
|
||||
// Don't pass badParseStrings as a data source, since they may fail for other reasons
|
||||
@ParameterizedTest
|
||||
@MethodSource({"validParseStrings", "noGroupingParseStrings"})
|
||||
public void numFmtStrictGroupingNotUsed(String toParse) {
|
||||
// When grouping is not used, if a grouping separator is found,
|
||||
// a failure should occur
|
||||
dFmt.setGroupingUsed(false);
|
||||
int failIndex = toParse.indexOf(
|
||||
dFmt.getDecimalFormatSymbols().getGroupingSeparator());
|
||||
if (failIndex > -1) {
|
||||
failParse(dFmt, toParse, failIndex);
|
||||
} else {
|
||||
successParse(dFmt, toParse);
|
||||
}
|
||||
}
|
||||
|
||||
// 8333755: Parsing behavior should follow normal strict behavior
|
||||
// However the index returned, should be before decimal point
|
||||
// and the value parsed equal to the integer portion
|
||||
@ParameterizedTest
|
||||
@MethodSource("validIntegerOnlyParseStrings")
|
||||
public void numFmtStrictIntegerOnlyUsedTest(String toParse, Number expVal) {
|
||||
dFmt.setParseIntegerOnly(true);
|
||||
int expectedIndex = toParse.indexOf(dfs.getDecimalSeparator());
|
||||
if (expectedIndex > -1) {
|
||||
assertEquals(successParse(dFmt, toParse, expectedIndex), expVal);
|
||||
} else {
|
||||
assertEquals(successParse(dFmt, toParse), expVal);
|
||||
}
|
||||
}
|
||||
|
||||
// 8335668: Parsing with integer only against String with no integer portion
|
||||
// should fail, not return 0. Expected error index should be 0
|
||||
@Test
|
||||
public void integerParseOnlyFractionOnlyTest() {
|
||||
var fmt = NumberFormat.getIntegerInstance();
|
||||
failParse(fmt, localizeText("."), 0);
|
||||
failParse(fmt, localizeText(".0"), 0);
|
||||
failParse(fmt, localizeText(".55"), 0);
|
||||
}
|
||||
|
||||
// 8335668: Parsing with integer only against String with no integer portion
|
||||
// should fail, not return 0. Expected error index should be 0
|
||||
@Test // Non-localized, run once
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactIntegerParseOnlyFractionOnlyTest() {
|
||||
var fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
|
||||
fmt.setParseIntegerOnly(true);
|
||||
failParse(fmt, ".K", 0);
|
||||
failParse(fmt, ".0K", 0);
|
||||
failParse(fmt, ".55K", 0);
|
||||
}
|
||||
|
||||
// 8333755: Parsing behavior should follow normal strict behavior
|
||||
// when it comes to failures.
|
||||
@ParameterizedTest
|
||||
@MethodSource("badParseStrings")
|
||||
public void numFmtStrictIntegerOnlyUsedFailTest(String toParse, int expectedErrorIndex) {
|
||||
dFmt.setParseIntegerOnly(true);
|
||||
failParse(dFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
// ---- CurrencyFormat tests ----
|
||||
@ParameterizedTest
|
||||
@MethodSource("currencyBadParseStrings")
|
||||
public void currFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(cFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("currencyValidParseStrings")
|
||||
public void currFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(cFmt, toParse));
|
||||
}
|
||||
|
||||
// ---- PercentFormat tests ----
|
||||
@ParameterizedTest
|
||||
@MethodSource("percentBadParseStrings")
|
||||
public void percentFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(pFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("percentValidParseStrings")
|
||||
public void percentFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(pFmt, toParse));
|
||||
}
|
||||
|
||||
// ---- CompactNumberFormat tests ----
|
||||
// Can match to both the decimalFormat patterns and the compact patterns
|
||||
// Thus we test leniency for both. Unlike the other tests, this test
|
||||
// is only ran against the US Locale and tests against data built with the
|
||||
// thousands format (K).
|
||||
@ParameterizedTest
|
||||
@MethodSource("compactBadParseStrings")
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(cmpctFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("compactValidParseStrings")
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(cmpctFmt, toParse));
|
||||
}
|
||||
|
||||
// Checks some odd leniency edge cases between matching of default pattern
|
||||
// and compact pattern.
|
||||
@Test // Non-localized, run once
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtEdgeParseTest() {
|
||||
// Uses a compact format with unique and non-empty prefix/suffix for both
|
||||
// default and compact patterns
|
||||
CompactNumberFormat cnf = new CompactNumberFormat("a##0.0#b", DecimalFormatSymbols
|
||||
.getInstance(Locale.US), new String[]{"", "c0d"});
|
||||
cnf.setStrict(true);
|
||||
|
||||
// Existing behavior of failed prefix parsing has errorIndex return
|
||||
// the beginning of prefix, even if the error occurred later in the prefix.
|
||||
// Prefix empty
|
||||
failParse(cnf, "12345d", 0);
|
||||
failParse(cnf, "1b", 0);
|
||||
// Prefix bad
|
||||
failParse(cnf, "aa1d", 0);
|
||||
failParse(cnf, "cc1d", 0);
|
||||
failParse(cnf, "aa1b", 0);
|
||||
failParse(cnf, "cc1b", 0);
|
||||
|
||||
// Suffix error index is always the start of the failed suffix
|
||||
// not necessarily where the error occurred in the suffix. This is
|
||||
// consistent with the prefix error index behavior.
|
||||
// Suffix empty
|
||||
failParse(cnf, "a1", 2);
|
||||
failParse(cnf, "c1", 2);
|
||||
// Suffix bad
|
||||
failParse(cnf, "a1dd", 2);
|
||||
failParse(cnf, "c1dd", 2);
|
||||
failParse(cnf, "a1bb", 2);
|
||||
failParse(cnf, "c1bb", 2);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource({"validIntegerOnlyParseStrings", "compactValidIntegerOnlyParseStrings"})
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtSuccessParseIntOnlyTest(String toParse, double expectedValue) {
|
||||
// compact does not accept exponents
|
||||
if (toParse.indexOf('E') > -1) {
|
||||
return;
|
||||
}
|
||||
cmpctFmt.setParseIntegerOnly(true);
|
||||
assertEquals(expectedValue, successParse(cmpctFmt, toParse, toParse.length()));
|
||||
}
|
||||
|
||||
// Ensure that on failure, the original index of the PP remains the same
|
||||
@Test
|
||||
public void parsePositionIndexTest() {
|
||||
failParse(dFmt, localizeText("123,456,,789.00"), 8, 4);
|
||||
}
|
||||
|
||||
// ---- Helper test methods ----
|
||||
|
||||
// Should parse entire String successfully, and return correctly parsed value.
|
||||
private Number successParse(NumberFormat fmt, String toParse) {
|
||||
return successParse(fmt, toParse, toParse.length());
|
||||
}
|
||||
|
||||
// Overloaded method that allows for an expected ParsePosition index value
|
||||
// that is not the string length.
|
||||
private Number successParse(NumberFormat fmt, String toParse, int expectedIndex) {
|
||||
// For Strings that don't have grouping separators, we test them with
|
||||
// grouping off so that they do not fail under the expectation that
|
||||
// grouping symbols should occur
|
||||
if (!toParse.contains(String.valueOf(dfs.getGroupingSeparator())) &&
|
||||
!toParse.contains(String.valueOf(dfs.getMonetaryGroupingSeparator()))) {
|
||||
fmt.setGroupingUsed(false);
|
||||
}
|
||||
Number parsedValue = assertDoesNotThrow(() -> fmt.parse(toParse));
|
||||
ParsePosition pp = new ParsePosition(0);
|
||||
assertDoesNotThrow(() -> fmt.parse(toParse, pp));
|
||||
assertEquals(-1, pp.getErrorIndex(),
|
||||
"ParsePosition ErrorIndex is not in correct location");
|
||||
assertEquals(expectedIndex, pp.getIndex(),
|
||||
"ParsePosition Index is not in correct location");
|
||||
fmt.setGroupingUsed(true);
|
||||
return parsedValue.doubleValue();
|
||||
}
|
||||
|
||||
// Method which tests a parsing failure. Either a ParseException is thrown,
|
||||
// or null is returned depending on which parse method is invoked. When failing,
|
||||
// index should remain the initial index set to the ParsePosition while
|
||||
// errorIndex is the index of failure.
|
||||
private void failParse(NumberFormat fmt, String toParse, int expectedErrorIndex) {
|
||||
failParse(fmt, toParse, expectedErrorIndex, 0);
|
||||
}
|
||||
|
||||
// Variant to check non 0 initial parse index
|
||||
private void failParse(NumberFormat fmt, String toParse,
|
||||
int expectedErrorIndex, int initialParseIndex) {
|
||||
ParsePosition pp = new ParsePosition(initialParseIndex);
|
||||
assertThrows(ParseException.class, () -> fmt.parse(toParse));
|
||||
assertNull(fmt.parse(toParse, pp));
|
||||
assertEquals(expectedErrorIndex, pp.getErrorIndex());
|
||||
assertEquals(initialParseIndex, pp.getIndex());
|
||||
}
|
||||
|
||||
// ---- Data Providers ----
|
||||
// These data providers use US locale grouping and decimal separators
|
||||
// for readability, however, the data is tested against multiple locales
|
||||
// and is converted appropriately at runtime.
|
||||
|
||||
// Strings that should fail when parsed with strict leniency.
|
||||
// Given as Arguments<String, expectedErrorIndex>
|
||||
private static Stream<Arguments> badParseStrings() {
|
||||
return Stream.of(
|
||||
// Grouping symbol focus
|
||||
// Grouping symbol right before decimal
|
||||
Arguments.of("1,.", 2),
|
||||
Arguments.of("1,.1", 2),
|
||||
// Does not end with proper grouping size
|
||||
Arguments.of("1,1", 2),
|
||||
Arguments.of("1,11", 3),
|
||||
Arguments.of("1,1111", 5),
|
||||
Arguments.of("11,111,11", 8),
|
||||
// Does not end with proper grouping size (with decimal)
|
||||
Arguments.of("1,1.", 3),
|
||||
Arguments.of("1,11.", 4),
|
||||
Arguments.of("1,1111.", 5),
|
||||
Arguments.of("11,111,11.", 9),
|
||||
// Ends on a grouping symbol
|
||||
// Suffix matches correctly, so failure is on the ","
|
||||
Arguments.of("11,111,", 6),
|
||||
Arguments.of("11,", 2),
|
||||
Arguments.of("11,,", 3),
|
||||
// Ends with grouping symbol. Failure should occur on grouping,
|
||||
// even if non recognized char after
|
||||
Arguments.of("11,a", 2),
|
||||
// Improper grouping size (with decimal and digits after)
|
||||
Arguments.of("1,1.1", 3),
|
||||
Arguments.of("1,11.1", 4),
|
||||
Arguments.of("1,1111.1", 5),
|
||||
Arguments.of("11,111,11.1", 9),
|
||||
// Subsequent grouping symbols
|
||||
Arguments.of("1,,1", 2),
|
||||
Arguments.of("1,1,,1", 3),
|
||||
Arguments.of("1,,1,1", 2),
|
||||
// Invalid grouping sizes
|
||||
Arguments.of("1,11,111", 4),
|
||||
Arguments.of("11,11,111", 5),
|
||||
Arguments.of("111,11,11", 6),
|
||||
// First group is too large
|
||||
Arguments.of("1111,11,111", 3),
|
||||
Arguments.of("00000,11,111", 3),
|
||||
Arguments.of("111,1111111111", 7),
|
||||
Arguments.of("111,11", 5),
|
||||
Arguments.of("111,1111111111.", 7),
|
||||
Arguments.of("111,11.", 6),
|
||||
Arguments.of("111,1111111111.", 7),
|
||||
// Starts with grouping symbol
|
||||
Arguments.of(",111,,1,1", 0),
|
||||
Arguments.of(",1", 0),
|
||||
Arguments.of(",,1", 0),
|
||||
// Leading Zeros (not digits)
|
||||
Arguments.of("000,1,1", 5),
|
||||
Arguments.of("000,111,11,,1", 10),
|
||||
Arguments.of("0,000,1,,1,1", 7),
|
||||
// Bad suffix
|
||||
Arguments.of("1a", 1),
|
||||
// Bad chars in numerical portion
|
||||
Arguments.of("123a4", 3),
|
||||
Arguments.of("123.4a5", 5),
|
||||
// Variety of edge cases
|
||||
Arguments.of("123,456.77a", 10),
|
||||
Arguments.of("1,234a", 5),
|
||||
Arguments.of("1,.a", 2),
|
||||
Arguments.of("1.a", 2),
|
||||
Arguments.of("1.22a", 4),
|
||||
Arguments.of("1.1a1", 3),
|
||||
Arguments.of("1,234,a", 5),
|
||||
// Double decimal
|
||||
Arguments.of("1,234..5", 5))
|
||||
.map(args -> Arguments.of(
|
||||
localizeText(String.valueOf(args.get()[0])), args.get()[1]));
|
||||
}
|
||||
|
||||
// Strings that should parse fully. (Both in lenient and strict)
|
||||
// Given as Arguments<String, expectedParsedNumber>
|
||||
private static Stream<Arguments> validParseStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("1,234.55", 1234.55d),
|
||||
Arguments.of("1,234.5", 1234.5d),
|
||||
Arguments.of("1,234.00", 1234d),
|
||||
Arguments.of("1,234.0", 1234d),
|
||||
Arguments.of("1,234.", 1234d),
|
||||
Arguments.of("1", 1d),
|
||||
Arguments.of("10", 10d),
|
||||
Arguments.of("100", 100d),
|
||||
Arguments.of("1000", 1000d),
|
||||
Arguments.of("1,000", 1000d),
|
||||
Arguments.of("10,000", 10000d),
|
||||
Arguments.of("10000", 10000d),
|
||||
Arguments.of("100,000", 100000d),
|
||||
Arguments.of("1,000,000", 1000000d),
|
||||
Arguments.of("10,000,000", 10000000d),
|
||||
// Smaller value cases (w/ decimal)
|
||||
Arguments.of(".1", .1d),
|
||||
Arguments.of("1.1", 1.1d),
|
||||
Arguments.of("11.1", 11.1d))
|
||||
.map(args -> Arguments.of(
|
||||
localizeText(String.valueOf(args.get()[0])), args.get()[1]));
|
||||
}
|
||||
|
||||
// Separate test data set for integer only.
|
||||
// Valid parse strings, that would parse successfully for integer/non-integer parse
|
||||
private static Stream<Arguments> validIntegerOnlyParseStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("234", 234d),
|
||||
Arguments.of("234.", 234d),
|
||||
Arguments.of("234.1", 234d),
|
||||
Arguments.of("1,234.1", 1234d),
|
||||
Arguments.of("234.12345", 234d),
|
||||
Arguments.of("234.543E23", 234d),
|
||||
Arguments.of("234,000.55E22", 234000d),
|
||||
Arguments.of("234E22", 234E22))
|
||||
.map(args -> Arguments.of(localizeText(String.valueOf(args.get()[0])), args.get()[1]));
|
||||
}
|
||||
|
||||
// Separate test data set for no grouping. Can not use "badParseStrings", as
|
||||
// there is test data where the failure may occur from some other issue,
|
||||
// not related to grouping
|
||||
private static Stream<Arguments> noGroupingParseStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("12,34.a"),
|
||||
Arguments.of("123,.a1"),
|
||||
Arguments.of(",1234"),
|
||||
Arguments.of("123,"))
|
||||
.map(args -> Arguments.of(localizeText(String.valueOf(args.get()[0]))));
|
||||
}
|
||||
|
||||
// Negative variant of a numerical format
|
||||
private static Stream<Arguments> negativeBadParseStrings() {
|
||||
return badParseStrings().map(args -> Arguments.of(
|
||||
dFmt.getNegativePrefix() + args.get()[0] + dFmt.getNegativeSuffix(),
|
||||
(int)args.get()[1] + dFmt.getNegativePrefix().length())
|
||||
);
|
||||
}
|
||||
|
||||
// Negative variant of a numerical format
|
||||
private static Stream<Arguments> negativeValidParseStrings() {
|
||||
return validParseStrings().map(args -> Arguments.of(
|
||||
dFmt.getNegativePrefix() + args.get()[0] + dFmt.getNegativeSuffix(),
|
||||
(double) args.get()[1] * -1)
|
||||
);
|
||||
}
|
||||
|
||||
// Same as original with a percent prefix/suffix.
|
||||
// Additionally, increment expected error index if a prefix is added
|
||||
private static Stream<Arguments> percentBadParseStrings() {
|
||||
return badParseStrings().map(args -> Arguments.of(
|
||||
pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(),
|
||||
(int)args.get()[1] + pFmt.getPositivePrefix().length())
|
||||
);
|
||||
}
|
||||
|
||||
// Expected parsed value should be / 100 as it is a percent format.
|
||||
private static Stream<Arguments> percentValidParseStrings() {
|
||||
return validParseStrings().map(args -> Arguments.of(
|
||||
pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(),
|
||||
(double)args.get()[1] / 100.0)
|
||||
);
|
||||
}
|
||||
|
||||
// Same as original with a currency prefix/suffix, but replace separators
|
||||
// with monetary variants. Additionally, increment expected error index
|
||||
// if a prefix is added
|
||||
private static Stream<Arguments> currencyBadParseStrings() {
|
||||
return badParseStrings().map(args -> Arguments.of(
|
||||
cFmt.getPositivePrefix() + String.valueOf(args.get()[0])
|
||||
.replace(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator())
|
||||
.replace(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator())
|
||||
+ cFmt.getPositiveSuffix(),
|
||||
(int)args.get()[1] + cFmt.getPositivePrefix().length())
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> currencyValidParseStrings() {
|
||||
return validParseStrings().map(args -> Arguments.of(
|
||||
cFmt.getPositivePrefix() + String.valueOf(args.get()[0])
|
||||
.replace(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator())
|
||||
.replace(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator())
|
||||
+ cFmt.getPositiveSuffix(),
|
||||
args.get()[1])
|
||||
);
|
||||
}
|
||||
|
||||
// Compact Pattern Data Provider provides test input for both DecimalFormat patterns
|
||||
// and the compact patterns. As there is no method to retrieve compact patterns,
|
||||
// thus test only against US English locale, and use a hard coded K - 1000
|
||||
private static Stream<Arguments> compactBadParseStrings() {
|
||||
return Stream.concat(
|
||||
badParseStrings().map(args -> Arguments.of(args.get()[0], args.get()[1])),
|
||||
badParseStrings().map(args -> Arguments.of(args.get()[0] + "K", args.get()[1]))
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> compactValidParseStrings() {
|
||||
return Stream.concat(
|
||||
validParseStrings().map(args -> Arguments.of(
|
||||
args.get()[0], args.get()[1])),
|
||||
validParseStrings().map(args -> Arguments.of(
|
||||
args.get()[0] + "K", (double) args.get()[1] * 1000))
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> compactValidIntegerOnlyParseStrings() {
|
||||
return validIntegerOnlyParseStrings().map(args -> Arguments.of(
|
||||
args.get()[0] + "K", (double) args.get()[1] * 1000)
|
||||
);
|
||||
}
|
||||
|
||||
// Replace the grouping and decimal separators with localized variants
|
||||
// Used during localization of data
|
||||
private static String localizeText(String text) {
|
||||
// As this is a single pass conversion, this is safe for multiple replacement,
|
||||
// even if a ',' could be a decimal separator for a locale.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (c == ',') {
|
||||
sb.append(dfs.getGroupingSeparator());
|
||||
} else if (c == '.') {
|
||||
sb.append(dfs.getDecimalSeparator());
|
||||
} else if (c == 'E') {
|
||||
sb.append(dfs.getExponentSeparator());
|
||||
}
|
||||
else if (c == '0') {
|
||||
sb.append(dfs.getZeroDigit());
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue