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
376
test/jdk/java/util/Currency/CurrencyTest.java
Normal file
376
test/jdk/java/util/Currency/CurrencyTest.java
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* Copyright (c) 2007, 2025, 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 4290801 4692419 4693631 5101540 5104960 6296410 6336600 6371531
|
||||
* 6488442 7036905 8008577 8039317 8074350 8074351 8150324 8167143
|
||||
* 8264792 8334653 8353713
|
||||
* @summary Basic tests for Currency class.
|
||||
* @modules java.base/java.util:open
|
||||
* jdk.localedata
|
||||
* @run junit CurrencyTest
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Currency;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
public class CurrencyTest {
|
||||
|
||||
// 'ISO4217-list-one.txt' should be up-to-date before testing
|
||||
@Test
|
||||
public void dataVersionTest() {
|
||||
CheckDataVersion.check();
|
||||
}
|
||||
|
||||
@Nested
|
||||
class CodeValidationTests {
|
||||
// Calling getInstance() on equal currency codes should return equal currencies
|
||||
@ParameterizedTest
|
||||
@MethodSource("validCurrencies")
|
||||
public void validCurrencyTest(String currencyCode) {
|
||||
compareCurrencies(currencyCode);
|
||||
}
|
||||
|
||||
private static Stream<String> validCurrencies() {
|
||||
return Stream.of("USD", "EUR", "GBP", "JPY", "CNY", "CHF");
|
||||
}
|
||||
|
||||
// Calling getInstance() with an invalid currency code should throw an IAE
|
||||
@ParameterizedTest
|
||||
@MethodSource("non4217Currencies")
|
||||
public void invalidCurrencyTest(String currencyCode) {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
|
||||
Currency.getInstance(currencyCode), "getInstance() did not throw IAE");
|
||||
assertEquals("The input currency code: \"%s\" is not a valid ISO 4217 code"
|
||||
.formatted(currencyCode), ex.getMessage());
|
||||
}
|
||||
|
||||
private static Stream<String> non4217Currencies() {
|
||||
return Stream.of("AQD", "US$");
|
||||
}
|
||||
|
||||
// Provide 3 length code, but first 2 chars should not be able to index
|
||||
// the main table, thus resulting as incorrect country code
|
||||
@Test
|
||||
void invalidCountryInCodeTest() {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
|
||||
Currency.getInstance("..A"), "getInstance() did not throw IAE");
|
||||
assertEquals("The country code: \"%s\" is not a valid ISO 3166 code"
|
||||
.formatted(".."), ex.getMessage());
|
||||
}
|
||||
|
||||
// Calling getInstance() with a currency code not 3 characters long should throw
|
||||
// an IAE
|
||||
@ParameterizedTest
|
||||
@MethodSource("invalidLengthCurrencies")
|
||||
public void invalidCurrencyLengthTest(String currencyCode) {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () ->
|
||||
Currency.getInstance(currencyCode), "getInstance() did not throw IAE");
|
||||
assertEquals("The input currency code: \"%s\" must have a length of 3 characters"
|
||||
.formatted(currencyCode), ex.getMessage());
|
||||
}
|
||||
|
||||
private static Stream<String> invalidLengthCurrencies() {
|
||||
return Stream.of("\u20AC", "", "12345");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FundsCodesTests {
|
||||
// Calling getInstance() on equal currency codes should return equal currencies
|
||||
@ParameterizedTest
|
||||
@MethodSource("fundsCodes")
|
||||
public void validCurrencyTest(String currencyCode) {
|
||||
compareCurrencies(currencyCode);
|
||||
}
|
||||
|
||||
// Verify a currency has the expected fractional digits
|
||||
@ParameterizedTest
|
||||
@MethodSource("fundsCodes")
|
||||
public void fractionDigitTest(String currencyCode, int expectedFractionDigits) {
|
||||
compareFractionDigits(currencyCode, expectedFractionDigits);
|
||||
}
|
||||
|
||||
// Verify a currency has the expected numeric code
|
||||
@ParameterizedTest
|
||||
@MethodSource("fundsCodes")
|
||||
public void numericCodeTest(String currencyCode, int ignored, int expectedNumeric) {
|
||||
int numeric = Currency.getInstance(currencyCode).getNumericCode();
|
||||
assertEquals(numeric, expectedNumeric, String.format(
|
||||
"Wrong numeric code for currency %s, expected %s, got %s",
|
||||
currencyCode, expectedNumeric, numeric));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> fundsCodes() {
|
||||
return Stream.of(
|
||||
Arguments.of("BOV", 2, 984), Arguments.of("CHE", 2, 947),
|
||||
Arguments.of("CHW", 2, 948), Arguments.of("CLF", 4, 990),
|
||||
Arguments.of("COU", 2, 970), Arguments.of("MXV", 2, 979),
|
||||
Arguments.of("USN", 2, 997), Arguments.of("UYI", 0, 940)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class LocaleMappingTests {
|
||||
|
||||
// very basic test: most countries have their own currency, and then
|
||||
// their currency code is an extension of their country code.
|
||||
@Test
|
||||
public void localeMappingTest() {
|
||||
Locale[] locales = Locale.getAvailableLocales();
|
||||
int goodCountries = 0;
|
||||
int ownCurrencies = 0;
|
||||
for (Locale locale : locales) {
|
||||
String ctryCode = locale.getCountry();
|
||||
int ctryLength = ctryCode.length();
|
||||
if (ctryLength == 0 ||
|
||||
ctryLength == 3 || // UN M.49 code
|
||||
ctryCode.matches("AA|Q[M-Z]|X[A-JL-Z]|ZZ" + // user defined codes, excluding "XK" (Kosovo)
|
||||
"AC|CP|DG|EA|EU|FX|IC|SU|TA|UK")) { // exceptional reservation codes
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() -> Currency.getInstance(locale), "Did not throw IAE");
|
||||
assertEquals("The country of the input locale: \"%s\" is not a valid ISO 3166 country code"
|
||||
.formatted(locale), ex.getMessage());
|
||||
} else {
|
||||
goodCountries++;
|
||||
Currency currency = Currency.getInstance(locale);
|
||||
if (currency.getCurrencyCode().indexOf(locale.getCountry()) == 0) {
|
||||
ownCurrencies++;
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("Countries tested: " + goodCountries +
|
||||
", own currencies: " + ownCurrencies);
|
||||
if (ownCurrencies < (goodCountries / 2 + 1)) {
|
||||
throw new RuntimeException("suspicious: not enough countries have their own currency.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check an invalid country code supplied via the region override
|
||||
@Test
|
||||
public void invalidCountryRegionOverrideTest() {
|
||||
// Override US with nonsensical country
|
||||
var loc = Locale.forLanguageTag("en-US-u-rg-XXzzzz");
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
()-> Currency.getInstance(loc), "Did not throw IAE");
|
||||
assertEquals("The country of the input locale: \"%s\" is not a valid ISO 3166 country code"
|
||||
.formatted(loc), ex.getMessage());
|
||||
}
|
||||
|
||||
// Ensure a selection of countries have the expected currency
|
||||
@ParameterizedTest
|
||||
@MethodSource({"countryProvider", "switchedOverCountries"})
|
||||
public void countryCurrencyTest(String countryCode, String expected) {
|
||||
Locale locale = Locale.of("", countryCode);
|
||||
Currency currency = Currency.getInstance(locale);
|
||||
String code = (currency != null) ? currency.getCurrencyCode() : null;
|
||||
assertEquals(expected, code, generateErrMsg(
|
||||
"currency for", locale.getDisplayCountry(), expected, code));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> countryProvider() {
|
||||
return Stream.of(
|
||||
// Check country that does not have a currency
|
||||
Arguments.of("AQ", null),
|
||||
// Check some countries that don't change their currencies often
|
||||
Arguments.of("US", "USD"),
|
||||
Arguments.of("CA", "CAD"),
|
||||
Arguments.of("JP", "JPY"),
|
||||
Arguments.of("CN", "CNY"),
|
||||
Arguments.of("SG", "SGD"),
|
||||
Arguments.of("CH", "CHF")
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check Currency Changes
|
||||
* In the current implementation, there is no data of old currency and transition
|
||||
* date at jdk/src/java.base/share/data/currency/CurrencyData.properties.
|
||||
* So, all the switch data arrays are empty. In the future, if data of old
|
||||
* currency and transition date are necessary for any country, the
|
||||
* arrays here can be updated so that the program can check the currency switch.
|
||||
*/
|
||||
private static List<Arguments> switchedOverCountries() {
|
||||
List<Arguments> switched = new ArrayList<Arguments>();
|
||||
String[] switchOverCtry = {};
|
||||
String[] switchOverOld = {};
|
||||
String[] switchOverNew = {};
|
||||
String[] switchOverTZ = {};
|
||||
int[] switchOverYear = {};
|
||||
int[] switchOverMonth = {}; // java.time APIs accept month starting from 1 i.e. 01 for January
|
||||
int[] switchOverDay = {};
|
||||
|
||||
for (int i = 0; i < switchOverCtry.length; i++) {
|
||||
ZoneId zoneId = ZoneId.of(switchOverTZ[i]);
|
||||
ZonedDateTime zonedDateAndTime = ZonedDateTime.of(LocalDate.of(
|
||||
switchOverYear[i], switchOverMonth[i], switchOverDay[i]), LocalTime.MIDNIGHT, zoneId);
|
||||
ZonedDateTime currentZonedDateAndTime = ZonedDateTime.now(zoneId);
|
||||
switched.add(Arguments.of(switchOverCtry[i], (currentZonedDateAndTime.isAfter(zonedDateAndTime)
|
||||
|| currentZonedDateAndTime.isEqual(zonedDateAndTime)) ? switchOverNew[i] : switchOverOld[i]));
|
||||
}
|
||||
return switched;
|
||||
}
|
||||
}
|
||||
|
||||
// NON-NESTED TESTS
|
||||
|
||||
// Ensure selection of currencies have the correct fractional digits
|
||||
@ParameterizedTest
|
||||
@MethodSource("expectedFractionsProvider")
|
||||
public void fractionDigitsTest(String currencyCode, int expectedFractionDigits) {
|
||||
compareFractionDigits(currencyCode, expectedFractionDigits);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> expectedFractionsProvider() {
|
||||
return Stream.of(
|
||||
Arguments.of("USD", 2), Arguments.of("EUR", 2),
|
||||
Arguments.of("JPY", 0), Arguments.of("XDR", -1),
|
||||
Arguments.of("BHD", 3), Arguments.of("IQD", 3),
|
||||
Arguments.of("JOD", 3), Arguments.of("KWD", 3),
|
||||
Arguments.of("LYD", 3), Arguments.of("OMR", 3),
|
||||
Arguments.of("TND", 3),
|
||||
|
||||
// Old and New Turkish Lira
|
||||
Arguments.of("TRL", 0), Arguments.of("TRY", 2)
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure selection of currencies have the expected symbol
|
||||
@ParameterizedTest
|
||||
@MethodSource("symbolProvider")
|
||||
public void symbolTest(String currencyCode, Locale locale, String expectedSymbol) {
|
||||
String symbol = Currency.getInstance(currencyCode).getSymbol(locale);
|
||||
assertEquals(symbol, expectedSymbol, generateErrMsg(
|
||||
"symbol for", currencyCode, expectedSymbol, symbol));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> symbolProvider() {
|
||||
return Stream.of(
|
||||
Arguments.of("USD", Locale.US, "$"),
|
||||
Arguments.of("EUR", Locale.GERMANY, "\u20AC"),
|
||||
Arguments.of("USD", Locale.PRC, "US$")
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure serialization does not break class invariant.
|
||||
// Currency should be able to round-trip and remain the same value.
|
||||
@Test
|
||||
public void serializationTest() throws Exception {
|
||||
Currency currency1 = Currency.getInstance("DEM");
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oStream = new ObjectOutputStream(baos);
|
||||
oStream.writeObject(currency1);
|
||||
oStream.flush();
|
||||
byte[] bytes = baos.toByteArray();
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream iStream = new ObjectInputStream(bais);
|
||||
Currency currency2 = (Currency) iStream.readObject();
|
||||
assertEquals(currency1, currency2, "serialization breaks class invariant");
|
||||
}
|
||||
|
||||
// Ensure getInstance() throws null when passed a null locale
|
||||
@Test
|
||||
public void nullDisplayNameTest() {
|
||||
assertThrows(NullPointerException.class, ()->
|
||||
Currency.getInstance("USD").getDisplayName(null));
|
||||
}
|
||||
|
||||
// Ensure a selection of currencies/locale combos have the correct display name
|
||||
@ParameterizedTest
|
||||
@MethodSource("displayNameProvider")
|
||||
public void displayNameTest(String currencyCode, Locale locale, String expectedName) {
|
||||
String name = Currency.getInstance(currencyCode).getDisplayName(locale);
|
||||
assertEquals(name, expectedName, generateErrMsg(
|
||||
"display name for", currencyCode, expectedName, name));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> displayNameProvider() {
|
||||
return Stream.of(
|
||||
Arguments.of("USD", Locale.ENGLISH, "US Dollar"),
|
||||
Arguments.of("FRF", Locale.FRENCH, "franc fran\u00e7ais"),
|
||||
Arguments.of("DEM", Locale.GERMAN, "Deutsche Mark"),
|
||||
Arguments.of("ESP", Locale.of("es"), "peseta espa\u00f1ola"),
|
||||
Arguments.of("ITL", Locale.ITALIAN, "lira italiana"),
|
||||
Arguments.of("JPY", Locale.JAPANESE, "\u65e5\u672c\u5186"),
|
||||
Arguments.of("KRW", Locale.KOREAN, "\ub300\ud55c\ubbfc\uad6d \uc6d0"),
|
||||
Arguments.of("SEK", Locale.of("sv"), "svensk krona"),
|
||||
Arguments.of("CNY", Locale.SIMPLIFIED_CHINESE, "\u4eba\u6c11\u5e01"),
|
||||
Arguments.of("TWD", Locale.TRADITIONAL_CHINESE, "\u65b0\u53f0\u5e63")
|
||||
);
|
||||
}
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
// A Currency instance returned from getInstance() should always be
|
||||
// equal if supplied the same currencyCode. getCurrencyCode() should
|
||||
// always be equal to the currencyCode used to create the Currency.
|
||||
private static void compareCurrencies(String currencyCode) {
|
||||
Currency currency1 = Currency.getInstance(currencyCode);
|
||||
Currency currency2 = Currency.getInstance(currencyCode);
|
||||
assertEquals(currency1, currency2, "Didn't get same instance for same currency code");
|
||||
assertEquals(currency1.getCurrencyCode(), currencyCode, "getCurrencyCode()" +
|
||||
" did not return the expected value");
|
||||
}
|
||||
|
||||
// Ensures the getDefaultFractionDigits() method returns the expected amount
|
||||
private static void compareFractionDigits(String currencyCode,
|
||||
int expectedFractionDigits) {
|
||||
int digits = Currency.getInstance(currencyCode).getDefaultFractionDigits();
|
||||
assertEquals(digits, expectedFractionDigits, generateErrMsg(
|
||||
"number of fraction digits for currency",
|
||||
currencyCode, Integer.toString(expectedFractionDigits), Integer.toString(digits)));
|
||||
}
|
||||
|
||||
// Used for logging on failing tests
|
||||
private static String generateErrMsg(String subject, String currency,
|
||||
String expected, String got) {
|
||||
return String.format("Wrong %s %s: expected '%s', got '%s'",
|
||||
subject, currency, expected, got);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue