You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
16 KiB
496 lines
16 KiB
// © 2017 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
|
|
#include "unicode/utypes.h"
|
|
|
|
#if !UCONFIG_NO_FORMATTING
|
|
|
|
#include "unicode/dcfmtsym.h"
|
|
|
|
#include "cstr.h"
|
|
#include "numbertest.h"
|
|
#include "number_utils.h"
|
|
#include "number_skeletons.h"
|
|
#include "putilimp.h"
|
|
|
|
using namespace icu::number::impl;
|
|
|
|
|
|
void NumberSkeletonTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
|
|
if (exec) {
|
|
logln("TestSuite AffixUtilsTest: ");
|
|
}
|
|
TESTCASE_AUTO_BEGIN;
|
|
TESTCASE_AUTO(validTokens);
|
|
TESTCASE_AUTO(invalidTokens);
|
|
TESTCASE_AUTO(unknownTokens);
|
|
TESTCASE_AUTO(unexpectedTokens);
|
|
TESTCASE_AUTO(duplicateValues);
|
|
TESTCASE_AUTO(stemsRequiringOption);
|
|
TESTCASE_AUTO(defaultTokens);
|
|
TESTCASE_AUTO(flexibleSeparators);
|
|
TESTCASE_AUTO(wildcardCharacters);
|
|
TESTCASE_AUTO(perUnitInArabic);
|
|
TESTCASE_AUTO(perUnitToSkeleton);
|
|
TESTCASE_AUTO_END;
|
|
}
|
|
|
|
void NumberSkeletonTest::validTokens() {
|
|
IcuTestErrorCode status(*this, "validTokens");
|
|
|
|
// This tests only if the tokens are valid, not their behavior.
|
|
// Most of these are from the design doc.
|
|
static const char16_t* cases[] = {
|
|
u"precision-integer",
|
|
u"precision-unlimited",
|
|
u"@@@##",
|
|
u"@@*",
|
|
u"@@+",
|
|
u".000##",
|
|
u".00*",
|
|
u".00+",
|
|
u".",
|
|
u".*",
|
|
u".+",
|
|
u".######",
|
|
u".00/@@*",
|
|
u".00/@@+",
|
|
u".00/@##",
|
|
u"precision-increment/3.14",
|
|
u"precision-currency-standard",
|
|
u"precision-integer rounding-mode-half-up",
|
|
u".00# rounding-mode-ceiling",
|
|
u".00/@@* rounding-mode-floor",
|
|
u".00/@@+ rounding-mode-floor",
|
|
u"scientific",
|
|
u"scientific/*ee",
|
|
u"scientific/+ee",
|
|
u"scientific/sign-always",
|
|
u"scientific/*ee/sign-always",
|
|
u"scientific/+ee/sign-always",
|
|
u"scientific/sign-always/*ee",
|
|
u"scientific/sign-always/+ee",
|
|
u"scientific/sign-except-zero",
|
|
u"engineering",
|
|
u"engineering/*eee",
|
|
u"engineering/+eee",
|
|
u"compact-short",
|
|
u"compact-long",
|
|
u"notation-simple",
|
|
u"percent",
|
|
u"permille",
|
|
u"measure-unit/length-meter",
|
|
u"measure-unit/area-square-meter",
|
|
u"measure-unit/energy-joule per-measure-unit/length-meter",
|
|
u"unit/square-meter-per-square-meter",
|
|
u"currency/XXX",
|
|
u"currency/ZZZ",
|
|
u"currency/usd",
|
|
u"group-off",
|
|
u"group-min2",
|
|
u"group-auto",
|
|
u"group-on-aligned",
|
|
u"group-thousands",
|
|
u"integer-width/00",
|
|
u"integer-width/#0",
|
|
u"integer-width/*00",
|
|
u"integer-width/+00",
|
|
u"sign-always",
|
|
u"sign-auto",
|
|
u"sign-never",
|
|
u"sign-accounting",
|
|
u"sign-accounting-always",
|
|
u"sign-except-zero",
|
|
u"sign-accounting-except-zero",
|
|
u"unit-width-narrow",
|
|
u"unit-width-short",
|
|
u"unit-width-iso-code",
|
|
u"unit-width-full-name",
|
|
u"unit-width-hidden",
|
|
u"decimal-auto",
|
|
u"decimal-always",
|
|
u"scale/5.2",
|
|
u"scale/-5.2",
|
|
u"scale/100",
|
|
u"scale/1E2",
|
|
u"scale/1",
|
|
u"latin",
|
|
u"numbering-system/arab",
|
|
u"numbering-system/latn",
|
|
u"precision-integer/@##",
|
|
u"precision-integer rounding-mode-ceiling",
|
|
u"precision-currency-cash rounding-mode-ceiling",
|
|
u"0",
|
|
u"00",
|
|
u"000",
|
|
u"E0",
|
|
u"E00",
|
|
u"E000",
|
|
u"EE0",
|
|
u"EE00",
|
|
u"EE+?0",
|
|
u"EE+?00",
|
|
u"EE+!0",
|
|
u"EE+!00",
|
|
};
|
|
|
|
for (auto& cas : cases) {
|
|
UnicodeString skeletonString(cas);
|
|
status.setScope(skeletonString);
|
|
UParseError perror;
|
|
NumberFormatter::forSkeleton(skeletonString, perror, status);
|
|
assertSuccess(CStr(skeletonString)(), status, true);
|
|
assertEquals(skeletonString, -1, perror.offset);
|
|
status.errIfFailureAndReset();
|
|
}
|
|
}
|
|
|
|
void NumberSkeletonTest::invalidTokens() {
|
|
static const char16_t* cases[] = {
|
|
u".00x",
|
|
u".00##0",
|
|
u".##*",
|
|
u".00##*",
|
|
u".0#*",
|
|
u"@#*",
|
|
u".##+",
|
|
u".00##+",
|
|
u".0#+",
|
|
u"@#+",
|
|
u"@@x",
|
|
u"@@##0",
|
|
u".00/@",
|
|
u".00/@@",
|
|
u".00/@@x",
|
|
u".00/@@#",
|
|
u".00/@@#*",
|
|
u".00/floor/@@*", // wrong order
|
|
u".00/@@#+",
|
|
u".00/floor/@@+", // wrong order
|
|
u"precision-increment/français", // non-invariant characters for C++
|
|
u"scientific/ee",
|
|
u"precision-increment/xxx",
|
|
u"precision-increment/NaN",
|
|
u"precision-increment/0.1.2",
|
|
u"scale/xxx",
|
|
u"scale/NaN",
|
|
u"scale/0.1.2",
|
|
u"scale/français", // non-invariant characters for C++
|
|
u"currency/dummy",
|
|
u"currency/ççç", // three characters but not ASCII
|
|
u"measure-unit/foo",
|
|
u"integer-width/xxx",
|
|
u"integer-width/0*",
|
|
u"integer-width/*0#",
|
|
u"integer-width/*#",
|
|
u"integer-width/*#0",
|
|
u"integer-width/0+",
|
|
u"integer-width/+0#",
|
|
u"integer-width/+#",
|
|
u"integer-width/+#0",
|
|
u"scientific/foo",
|
|
u"E",
|
|
u"E1",
|
|
u"E+",
|
|
u"E+?",
|
|
u"E+!",
|
|
u"E+0",
|
|
u"EE",
|
|
u"EE+",
|
|
u"EEE",
|
|
u"EEE0",
|
|
u"001",
|
|
u"00*",
|
|
u"00+",
|
|
};
|
|
|
|
expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases));
|
|
}
|
|
|
|
void NumberSkeletonTest::unknownTokens() {
|
|
static const char16_t* cases[] = {
|
|
u"maesure-unit",
|
|
u"measure-unit/foo-bar",
|
|
u"numbering-system/dummy",
|
|
u"français",
|
|
u"measure-unit/français-français", // non-invariant characters for C++
|
|
u"numbering-system/français", // non-invariant characters for C++
|
|
u"currency-USD"};
|
|
|
|
expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases));
|
|
}
|
|
|
|
void NumberSkeletonTest::unexpectedTokens() {
|
|
static const char16_t* cases[] = {
|
|
u"group-thousands/foo",
|
|
u"precision-integer//@## group-off",
|
|
u"precision-integer//@## group-off",
|
|
u"precision-integer/ group-off",
|
|
u"precision-integer// group-off"};
|
|
|
|
expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases));
|
|
}
|
|
|
|
void NumberSkeletonTest::duplicateValues() {
|
|
static const char16_t* cases[] = {
|
|
u"precision-integer precision-integer",
|
|
u"precision-integer .00+",
|
|
u"precision-integer precision-unlimited",
|
|
u"precision-integer @@@",
|
|
u"scientific engineering",
|
|
u"engineering compact-long",
|
|
u"sign-auto sign-always"};
|
|
|
|
expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases));
|
|
}
|
|
|
|
void NumberSkeletonTest::stemsRequiringOption() {
|
|
static const char16_t* stems[] = {
|
|
u"precision-increment",
|
|
u"measure-unit",
|
|
u"per-measure-unit",
|
|
u"currency",
|
|
u"integer-width",
|
|
u"numbering-system",
|
|
u"scale"};
|
|
static const char16_t* suffixes[] = {u"", u"/@##", u" scientific", u"/@## scientific"};
|
|
|
|
for (auto& stem : stems) {
|
|
for (auto& suffix : suffixes) {
|
|
UnicodeString skeletonString = UnicodeString(stem) + suffix;
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
UParseError perror;
|
|
NumberFormatter::forSkeleton(skeletonString, perror, status);
|
|
assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status);
|
|
|
|
// Check the UParseError for integrity.
|
|
// If an option is present, the option is wrong; error offset is at the start of the option
|
|
// If an option is not present, the error offset is at the token separator (end of stem)
|
|
int32_t expectedOffset = u_strlen(stem) + ((suffix[0] == u'/') ? 1 : 0);
|
|
assertEquals(skeletonString, expectedOffset, perror.offset);
|
|
UnicodeString expectedPreContext = skeletonString.tempSubString(0, expectedOffset);
|
|
if (expectedPreContext.length() >= U_PARSE_CONTEXT_LEN - 1) {
|
|
expectedPreContext = expectedPreContext.tempSubString(expectedOffset - U_PARSE_CONTEXT_LEN + 1);
|
|
}
|
|
assertEquals(skeletonString, expectedPreContext, perror.preContext);
|
|
UnicodeString expectedPostContext = skeletonString.tempSubString(expectedOffset);
|
|
// None of the postContext strings in this test exceed U_PARSE_CONTEXT_LEN
|
|
assertEquals(skeletonString, expectedPostContext, perror.postContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NumberSkeletonTest::defaultTokens() {
|
|
IcuTestErrorCode status(*this, "defaultTokens");
|
|
|
|
static const char16_t* cases[] = {
|
|
u"notation-simple",
|
|
u"base-unit",
|
|
u"group-auto",
|
|
u"integer-width/+0",
|
|
u"sign-auto",
|
|
u"unit-width-short",
|
|
u"decimal-auto"};
|
|
|
|
for (auto& cas : cases) {
|
|
UnicodeString skeletonString(cas);
|
|
status.setScope(skeletonString);
|
|
UnicodeString normalized = NumberFormatter::forSkeleton(
|
|
skeletonString, status).toSkeleton(status);
|
|
// Skeleton should become empty when normalized
|
|
assertEquals(skeletonString, u"", normalized);
|
|
status.errIfFailureAndReset();
|
|
}
|
|
}
|
|
|
|
void NumberSkeletonTest::flexibleSeparators() {
|
|
IcuTestErrorCode status(*this, "flexibleSeparators");
|
|
|
|
static struct TestCase {
|
|
const char16_t* skeleton;
|
|
const char16_t* expected;
|
|
} cases[] = {{u"precision-integer group-off", u"5142"},
|
|
{u"precision-integer group-off", u"5142"},
|
|
{u"precision-integer/@## group-off", u"5140"},
|
|
{u"precision-integer/@## group-off", u"5140"}};
|
|
|
|
for (auto& cas : cases) {
|
|
UnicodeString skeletonString(cas.skeleton);
|
|
UnicodeString expected(cas.expected);
|
|
status.setScope(skeletonString);
|
|
UnicodeString actual = NumberFormatter::forSkeleton(skeletonString, status).locale("en")
|
|
.formatDouble(5142.3, status)
|
|
.toString(status);
|
|
if (!status.errDataIfFailureAndReset()) {
|
|
assertEquals(skeletonString, expected, actual);
|
|
}
|
|
status.errIfFailureAndReset();
|
|
}
|
|
}
|
|
|
|
void NumberSkeletonTest::wildcardCharacters() {
|
|
IcuTestErrorCode status(*this, "wildcardCharacters");
|
|
|
|
struct TestCase {
|
|
const char16_t* star;
|
|
const char16_t* plus;
|
|
} cases[] = {
|
|
{ u".00*", u".00+" },
|
|
{ u"@@*", u"@@+" },
|
|
{ u".00/@@*", u".00/@@+" },
|
|
{ u"scientific/*ee", u"scientific/+ee" },
|
|
{ u"integer-width/*00", u"integer-width/+00" },
|
|
};
|
|
|
|
for (const auto& cas : cases) {
|
|
UnicodeString star(cas.star);
|
|
UnicodeString plus(cas.plus);
|
|
status.setScope(star);
|
|
|
|
UnicodeString normalized = NumberFormatter::forSkeleton(plus, status)
|
|
.toSkeleton(status);
|
|
assertEquals("Plus should normalize to star", star, normalized);
|
|
status.errIfFailureAndReset();
|
|
}
|
|
}
|
|
|
|
// In C++, there is no distinguishing between "invalid", "unknown", and "unexpected" tokens.
|
|
void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t casesLen) {
|
|
for (int32_t i = 0; i < casesLen; i++) {
|
|
UnicodeString skeletonString(cases[i]);
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
NumberFormatter::forSkeleton(skeletonString, status);
|
|
assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status);
|
|
}
|
|
}
|
|
|
|
void NumberSkeletonTest::perUnitInArabic() {
|
|
IcuTestErrorCode status(*this, "perUnitInArabic");
|
|
|
|
struct TestCase {
|
|
const char16_t* type;
|
|
const char16_t* subtype;
|
|
} cases[] = {
|
|
{u"area", u"acre"},
|
|
{u"digital", u"bit"},
|
|
{u"digital", u"byte"},
|
|
{u"temperature", u"celsius"},
|
|
{u"length", u"centimeter"},
|
|
{u"duration", u"day"},
|
|
{u"angle", u"degree"},
|
|
{u"temperature", u"fahrenheit"},
|
|
{u"volume", u"fluid-ounce"},
|
|
{u"length", u"foot"},
|
|
{u"volume", u"gallon"},
|
|
{u"digital", u"gigabit"},
|
|
{u"digital", u"gigabyte"},
|
|
{u"mass", u"gram"},
|
|
{u"area", u"hectare"},
|
|
{u"duration", u"hour"},
|
|
{u"length", u"inch"},
|
|
{u"digital", u"kilobit"},
|
|
{u"digital", u"kilobyte"},
|
|
{u"mass", u"kilogram"},
|
|
{u"length", u"kilometer"},
|
|
{u"volume", u"liter"},
|
|
{u"digital", u"megabit"},
|
|
{u"digital", u"megabyte"},
|
|
{u"length", u"meter"},
|
|
{u"length", u"mile"},
|
|
{u"length", u"mile-scandinavian"},
|
|
{u"volume", u"milliliter"},
|
|
{u"length", u"millimeter"},
|
|
{u"duration", u"millisecond"},
|
|
{u"duration", u"minute"},
|
|
{u"duration", u"month"},
|
|
{u"mass", u"ounce"},
|
|
{u"concentr", u"percent"},
|
|
{u"digital", u"petabyte"},
|
|
{u"mass", u"pound"},
|
|
{u"duration", u"second"},
|
|
{u"mass", u"stone"},
|
|
{u"digital", u"terabit"},
|
|
{u"digital", u"terabyte"},
|
|
{u"duration", u"week"},
|
|
{u"length", u"yard"},
|
|
{u"duration", u"year"},
|
|
};
|
|
|
|
for (const auto& cas1 : cases) {
|
|
for (const auto& cas2 : cases) {
|
|
UnicodeString skeleton(u"measure-unit/");
|
|
skeleton += cas1.type;
|
|
skeleton += u"-";
|
|
skeleton += cas1.subtype;
|
|
skeleton += u" ";
|
|
skeleton += u"per-measure-unit/";
|
|
skeleton += cas2.type;
|
|
skeleton += u"-";
|
|
skeleton += cas2.subtype;
|
|
|
|
status.setScope(skeleton);
|
|
UnicodeString actual = NumberFormatter::forSkeleton(skeleton, status).locale("ar")
|
|
.formatDouble(5142.3, status)
|
|
.toString(status);
|
|
status.errIfFailureAndReset();
|
|
}
|
|
}
|
|
}
|
|
|
|
void NumberSkeletonTest::perUnitToSkeleton() {
|
|
IcuTestErrorCode status(*this, "perUnitToSkeleton");
|
|
struct TestCase {
|
|
const char16_t* type;
|
|
const char16_t* subtype;
|
|
} cases[] = {
|
|
{u"area", u"acre"},
|
|
{u"concentr", u"percent"},
|
|
{u"concentr", u"permille"},
|
|
{u"concentr", u"permillion"},
|
|
{u"concentr", u"permyriad"},
|
|
{u"digital", u"bit"},
|
|
{u"length", u"yard"},
|
|
};
|
|
|
|
for (const auto& cas1 : cases) {
|
|
for (const auto& cas2 : cases) {
|
|
UnicodeString skeleton(u"measure-unit/");
|
|
skeleton += cas1.type;
|
|
skeleton += u"-";
|
|
skeleton += cas1.subtype;
|
|
skeleton += u" ";
|
|
skeleton += u"per-measure-unit/";
|
|
skeleton += cas2.type;
|
|
skeleton += u"-";
|
|
skeleton += cas2.subtype;
|
|
|
|
status.setScope(skeleton);
|
|
if (cas1.type != cas2.type && cas1.subtype != cas2.subtype) {
|
|
UnicodeString toSkeleton = NumberFormatter::forSkeleton(
|
|
skeleton, status).toSkeleton(status);
|
|
if (status.errIfFailureAndReset()) {
|
|
continue;
|
|
}
|
|
// Ensure both subtype are in the toSkeleton.
|
|
UnicodeString msg;
|
|
msg.append(toSkeleton)
|
|
.append(" should contain '")
|
|
.append(UnicodeString(cas1.subtype))
|
|
.append("' when constructed from ")
|
|
.append(skeleton);
|
|
assertTrue(msg, toSkeleton.indexOf(cas1.subtype) >= 0);
|
|
|
|
msg.remove();
|
|
msg.append(toSkeleton)
|
|
.append(" should contain '")
|
|
.append(UnicodeString(cas2.subtype))
|
|
.append("' when constructed from ")
|
|
.append(skeleton);
|
|
assertTrue(msg, toSkeleton.indexOf(cas2.subtype) >= 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* #if !UCONFIG_NO_FORMATTING */
|