ICU-13743 Adding number permutation test.

Adds a test suite in C++ and Java to test many permutations of options in NumberFormatter.
This commit is contained in:
Shane Carr 2019-08-13 04:16:25 +00:00 committed by Shane F. Carr
parent b4d41b0561
commit 513b0c20b0
11 changed files with 18614 additions and 7 deletions

View file

@ -67,7 +67,7 @@ numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \
string_segment_test.o \
numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \
static_unisets_test.o numfmtdatadriventest.o numbertest_range.o erarulestest.o \
formattedvaluetest.o formatted_string_builder_test.o
formattedvaluetest.o formatted_string_builder_test.o numbertest_permutation.o
DEPS = $(OBJECTS:.o=.d)

View file

@ -593,6 +593,7 @@ void IntlTest::setCaller( IntlTest* callingTest )
quick = caller->quick;
threadCount = caller->threadCount;
testoutfp = caller->testoutfp;
write_golden_data = caller->write_golden_data;
LL_indentlevel = caller->LL_indentlevel + indentLevel_offset;
numProps = caller->numProps;
for (int32_t i = 0; i < numProps; i++) {
@ -637,6 +638,13 @@ UBool IntlTest::setWarnOnMissingData( UBool warn_on_missing_dataVal )
return rval;
}
UBool IntlTest::setWriteGoldenData( UBool write_golden_data )
{
UBool rval = this->write_golden_data;
this->write_golden_data = write_golden_data;
return rval;
}
UBool IntlTest::setNoErrMsg( UBool no_err_msgVal )
{
UBool rval = this->no_err_msg;
@ -1224,6 +1232,7 @@ main(int argc, char* argv[])
UBool utf8 = FALSE;
const char *summary_file = NULL;
UBool warnOnMissingData = FALSE;
UBool writeGoldenData = FALSE;
UBool defaultDataFound = FALSE;
int32_t threadCount = 12;
UErrorCode errorCode = U_ZERO_ERROR;
@ -1265,6 +1274,9 @@ main(int argc, char* argv[])
else if (strcmp("notime", str) == 0 ||
strcmp("T", str) == 0)
no_time = TRUE;
else if (strcmp("goldens", str) == 0 ||
strcmp("G", str) == 0)
writeGoldenData = TRUE;
else if (strncmp("E", str, 1) == 0)
summary_file = str+1;
else if (strcmp("x", str)==0) {
@ -1338,6 +1350,7 @@ main(int argc, char* argv[])
major.setLeaks( leaks );
major.setThreadCount( threadCount );
major.setWarnOnMissingData( warnOnMissingData );
major.setWriteGoldenData( writeGoldenData );
major.setNotime (no_time);
for (int32_t i = 0; i < nProps; i++) {
major.setProperty(props[i]);
@ -1371,9 +1384,10 @@ main(int argc, char* argv[])
fprintf(stdout, " Exhaustive (e) : %s\n", (!quick? "On" : "Off"));
fprintf(stdout, " Leaks (l) : %s\n", (leaks? "On" : "Off"));
fprintf(stdout, " utf-8 (u) : %s\n", (utf8? "On" : "Off"));
fprintf(stdout, " notime (T) : %s\n", (no_time? "On" : "Off"));
fprintf(stdout, " noknownissues (K) : %s\n", (noKnownIssues? "On" : "Off"));
fprintf(stdout, " notime (T) : %s\n", (no_time? "On" : "Off"));
fprintf(stdout, " noknownissues (K) : %s\n", (noKnownIssues? "On" : "Off"));
fprintf(stdout, " Warn on missing data (w) : %s\n", (warnOnMissingData? "On" : "Off"));
fprintf(stdout, " Write golden data (G) : %s\n", (writeGoldenData? "On" : "Off"));
fprintf(stdout, " Threads : %d\n", threadCount);
for (int32_t i = 0; i < nProps; i++) {
fprintf(stdout, " Custom property (prop:) : %s\n", props[i]);

View file

@ -150,6 +150,7 @@ public:
virtual UBool setLeaks( UBool leaks = TRUE );
virtual UBool setNotime( UBool no_time = TRUE );
virtual UBool setWarnOnMissingData( UBool warn_on_missing_data = TRUE );
virtual UBool setWriteGoldenData( UBool write_golden_data = TRUE );
virtual int32_t setThreadCount( int32_t count = 1);
virtual int32_t getErrors( void );
@ -339,6 +340,7 @@ public:
UBool quick;
UBool leaks;
UBool warn_on_missing_data;
UBool write_golden_data;
UBool no_time;
int32_t threadCount;

View file

@ -257,6 +257,7 @@
<ClCompile Include="numbertest_doubleconversion.cpp" />
<ClCompile Include="numbertest_skeletons.cpp" />
<ClCompile Include="numbertest_range.cpp" />
<ClCompile Include="numbertest_permutation.cpp" />
<ClCompile Include="numfmtst.cpp" />
<ClCompile Include="numfmtdatadriventest.cpp" />
<ClCompile Include="numrgts.cpp" />

View file

@ -292,6 +292,9 @@
<ClCompile Include="numbertest_range.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_permutation.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numfmtst.cpp">
<Filter>formatting</Filter>
</ClCompile>

View file

@ -287,6 +287,13 @@ class NumberRangeFormatterTest : public IntlTestWithFieldPosition {
const char16_t* expected);
};
class NumberPermutationTest : public IntlTest {
public:
void testPermutations();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
};
// NOTE: This macro is identical to the one in itformat.cpp
#define TESTCLASS(id, TestClass) \
@ -318,6 +325,7 @@ class NumberTest : public IntlTest {
TESTCLASS(7, NumberParserTest);
TESTCLASS(8, NumberSkeletonTest);
TESTCLASS(9, NumberRangeFormatterTest);
TESTCLASS(10, NumberPermutationTest);
default: name = ""; break; // needed to end loop
}
}

View file

@ -0,0 +1,198 @@
// © 2019 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 <fstream>
#include <iostream>
#include <vector>
#include "numbertest.h"
#include "ucbuf.h"
#include "unicode/numberformatter.h"
void NumberPermutationTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
if (exec) {
logln("TestSuite NumberPermutationTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testPermutations);
TESTCASE_AUTO_END;
}
static const char16_t* kSkeletonParts[] = {
// Notation
u"compact-short",
u"scientific/+ee/sign-always",
nullptr,
// Unit
u"percent",
u"currency/EUR",
u"measure-unit/length-furlong",
nullptr,
// Unit Width
u"unit-width-narrow",
u"unit-width-full-name",
nullptr,
// Precision
u"precision-integer",
u".000",
u".##/@@@+",
u"@@",
nullptr,
// Rounding Mode
u"rounding-mode-floor",
nullptr,
// Integer Width
u"integer-width/##00",
nullptr,
// Scale
u"scale/0.5",
nullptr,
// Grouping
u"group-on-aligned",
nullptr,
// Symbols
u"latin",
nullptr,
// Sign Display
u"sign-accounting-except-zero",
nullptr,
// Decimal Separator Display
u"decimal-always",
nullptr,
};
static const double kNumbersToTest[]{0, 91827.3645, -0.22222};
/**
* Test permutations of 3 orthogonal skeleton parts from the list above.
* Compare the results against the golden data file:
* numberpermutationtest.txt
* To regenerate that file, run intltest with the -G option.
*/
void NumberPermutationTest::testPermutations() {
IcuTestErrorCode status(*this, "testPermutations");
const struct LocaleData {
Locale locale;
const char16_t* ustring;
} localesToTest[] = {
{"es-MX", u"es-MX"},
{"zh-TW", u"zh-TW"},
{"bn-BD", u"bn-BD"},
};
// Convert kSkeletonParts to a more convenient data structure
auto skeletonParts = std::vector<std::vector<const char16_t*>>();
auto currentSection = std::vector<const char16_t*>();
for (int32_t i = 0; i < UPRV_LENGTHOF(kSkeletonParts); i++) {
const char16_t* skeletonPart = kSkeletonParts[i];
if (skeletonPart == nullptr) {
skeletonParts.push_back(currentSection);
currentSection.clear();
} else {
currentSection.push_back(skeletonPart);
}
}
// Build up the golden data string as we evaluate all permutations
std::vector<UnicodeString> resultLines;
resultLines.push_back(u"# © 2019 and later: Unicode, Inc. and others.");
resultLines.push_back(u"# License & terms of use: http://www.unicode.org/copyright.html");
resultLines.push_back(UnicodeString());
// Take combinations of 3 orthogonal options
for (size_t i = 0; i < skeletonParts.size() - 2; i++) {
const auto& skeletons1 = skeletonParts[i];
for (size_t j = i + 1; j < skeletonParts.size() - 1; j++) {
const auto& skeletons2 = skeletonParts[j];
for (size_t k = j + 1; k < skeletonParts.size(); k++) {
const auto& skeletons3 = skeletonParts[k];
// Evaluate all combinations of skeletons for these options
for (const auto& skel1 : skeletons1) {
for (const auto& skel2 : skeletons2) {
for (const auto& skel3 : skeletons3) {
// Compute the skeleton
UnicodeString skeleton;
skeleton
.append(skel1) //
.append(u' ') //
.append(skel2) //
.append(u' ') //
.append(skel3);
resultLines.push_back(skeleton);
// Check several locales and several numbers in each locale
for (const auto& locData : localesToTest) {
auto lnf = NumberFormatter::forSkeleton(skeleton, status)
.locale(locData.locale);
resultLines.push_back(UnicodeString(u" ").append(locData.ustring));
for (const auto& input : kNumbersToTest) {
resultLines.push_back(UnicodeString(u" ").append(
lnf.formatDouble(input, status).toTempString(status)));
}
}
resultLines.push_back(UnicodeString());
}
}
}
}
// Quick mode: test all fields at least once but stop early.
if (quick) {
infoln(u"Quick mode: stopped after " + Int64ToUnicodeString(resultLines.size()) +
u" lines");
goto outerEnd;
}
}
}
outerEnd:
void();
CharString goldenFilePath(getSourceTestData(status), status);
goldenFilePath.appendPathPart("numberpermutationtest.txt", status);
// Compare it to the golden file
const char* codePage = "UTF-8";
LocalUCHARBUFPointer f(ucbuf_open(goldenFilePath.data(), &codePage, TRUE, FALSE, status));
if (!assertSuccess("Can't open data file", status)) {
return;
}
int32_t lineNumber = 1;
int32_t lineLength;
for (const auto& actualLine : resultLines) {
const UChar* lineBuf = ucbuf_readline(f.getAlias(), &lineLength, status);
if (lineBuf == nullptr) {
errln("More lines generated than are in the data file!");
break;
}
UnicodeString expectedLine(lineBuf, lineLength - 1);
assertEquals(u"Line #" + Int64ToUnicodeString(lineNumber) + u" differs", //
expectedLine, actualLine);
lineNumber++;
}
// Quick mode: test all fields at least once but stop early.
if (!quick && ucbuf_readline(f.getAlias(), &lineLength, status) != nullptr) {
errln("Fewer lines generated than are in the data file!");
}
// Overwrite the golden data if requested
if (write_golden_data) {
std::ofstream outFile;
outFile.open(goldenFilePath.data());
for (const auto& uniLine : resultLines) {
std::string byteLine;
uniLine.toUTF8String(byteLine);
outFile << byteLine << std::endl;
}
outFile.close();
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

File diff suppressed because it is too large Load diff

View file

@ -431,11 +431,13 @@ public abstract class Precision implements Cloneable {
static Precision constructFractionSignificant(FractionPrecision base_, int minSig, int maxSig) {
assert base_ instanceof FractionRounderImpl;
FractionRounderImpl base = (FractionRounderImpl) base_;
Precision returnValue;
if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) {
return COMPACT_STRATEGY;
returnValue = COMPACT_STRATEGY;
} else {
return new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig);
returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig);
}
return returnValue.withMode(base.mathContext);
}
static Precision constructIncrement(BigDecimal increment) {
@ -474,13 +476,15 @@ public abstract class Precision implements Cloneable {
assert base_ instanceof CurrencyRounderImpl;
CurrencyRounderImpl base = (CurrencyRounderImpl) base_;
double incrementDouble = currency.getRoundingIncrement(base.usage);
Precision returnValue;
if (incrementDouble != 0.0) {
BigDecimal increment = BigDecimal.valueOf(incrementDouble);
return constructIncrement(increment);
returnValue = constructIncrement(increment);
} else {
int minMaxFrac = currency.getDefaultFractionDigits(base.usage);
return constructFraction(minMaxFrac, minMaxFrac);
returnValue = constructFraction(minMaxFrac, minMaxFrac);
}
return returnValue.withMode(base.mathContext);
}
static Precision constructPassThrough() {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,171 @@
// © 2019 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.dev.test.number;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import org.junit.Test;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.TestUtil;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.util.ULocale;
/**
* @author sffc
*
*/
public class NumberPermutationTest extends TestFmwk {
static final String[] kSkeletonParts = {
// Notation
"compact-short",
"scientific/+ee/sign-always",
null,
// Unit
"percent",
"currency/EUR",
"measure-unit/length-furlong",
null,
// Unit Width
"unit-width-narrow",
"unit-width-full-name",
null,
// Precision
"precision-integer",
".000",
".##/@@@+",
"@@",
null,
// Rounding Mode
"rounding-mode-floor",
null,
// Integer Width
"integer-width/##00",
null,
// Scale
"scale/0.5",
null,
// Grouping
"group-on-aligned",
null,
// Symbols
"latin",
null,
// Sign Display
"sign-accounting-except-zero",
null,
// Decimal Separator Display
"decimal-always",
null,
};
static final double[] kNumbersToTest = {0, 91827.3645, -0.22222};
static final ULocale[] kLocales = {
new ULocale("es-MX"),
new ULocale("zh-TW"),
new ULocale("bn-BD")
};
/**
* Test permutations of 3 orthogonal skeleton parts from the list above.
* Compare the results against the golden data file:
* numberpermutationtest.txt
* To regenerate that file, run C++ intltest with the -G option.
*/
@Test
public void testPermutations() throws IOException {
boolean quick = getExhaustiveness() <= 5;
// Convert kSkeletonParts to a more convenient data structure
ArrayList<ArrayList<String>> skeletonParts = new ArrayList<>();
ArrayList<String> currentSection = new ArrayList<>();
for (int i = 0; i < kSkeletonParts.length; i++) {
String skeletonPart = kSkeletonParts[i];
if (skeletonPart == null) {
skeletonParts.add(currentSection);
currentSection = new ArrayList<>();
} else {
currentSection.add(skeletonPart);
}
}
// Build up the golden data string as we evaluate all permutations
ArrayList<String> resultLines = new ArrayList<>();
resultLines.add("# © 2017 and later: Unicode, Inc. and others.");
resultLines.add("# License & terms of use: http://www.unicode.org/copyright.html");
resultLines.add("");
// Take combinations of 3 orthogonal options
outer:
for (int i = 0; i < skeletonParts.size() - 2; i++) {
ArrayList<String> skeletons1 = skeletonParts.get(i);
for (int j = i + 1; j < skeletonParts.size() - 1; j++) {
ArrayList<String> skeletons2 = skeletonParts.get(j);
for (int k = j + 1; k < skeletonParts.size(); k++) {
ArrayList<String> skeletons3 = skeletonParts.get(k);
// Evaluate all combinations of skeletons for these options
for (String skel1 : skeletons1) {
for (String skel2 : skeletons2) {
for (String skel3 : skeletons3) {
// Compute the skeleton
StringBuilder skeletonBuilder = new StringBuilder();
skeletonBuilder
.append(skel1) //
.append(' ') //
.append(skel2) //
.append(' ') //
.append(skel3);
String skeleton = skeletonBuilder.toString();
resultLines.add(skeleton);
// Check several locales and several numbers in each locale
for (ULocale locale : kLocales) {
LocalizedNumberFormatter lnf =
NumberFormatter.forSkeleton(skeleton).locale(locale);
resultLines.add(" " + locale.toLanguageTag());
for (double input : kNumbersToTest) {
resultLines.add(" " + lnf.format(input).toString());
}
}
resultLines.add("");
}
}
}
}
// Quick mode: test all fields at least once but stop early.
if (quick) {
logln("Quick mode: stopped after " + resultLines.size() + " lines");
break outer;
}
}
}
// Compare it to the golden file
String codePage = "UTF-8";
BufferedReader f = TestUtil.getDataReader("numberpermutationtest.txt", codePage);
int lineNumber = 1;
for (String actualLine : resultLines) {
String expectedLine = f.readLine();
if (expectedLine == null) {
errln("More lines generated than are in the data file!");
break;
}
assertEquals("Line #" + lineNumber + " differs", //
expectedLine, actualLine);
lineNumber++;
}
// Quick mode: test all fields at least once but stop early.
if (!quick && f.readLine() != null) {
errln("Fewer lines generated than are in the data file!");
}
f.close();
}
}