blob: 51ee65328bc1e62e068bdaac886b43a78955b9dd [file] [log] [blame]
/// Tests for ICU compact format numbers (e.g. 1.2M instead of 1200000).
///
/// These tests check that the test cases match what ICU produces. They are not
/// testing the package:intl implementation, they only help verify consistent
/// behaviour across platforms.
// @dart=2.9
@TestOn("!browser")
@Tags(['ffi'])
@Skip(
"currently failing (see issue https://github.com/dart-lang/intl/issues/240)")
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:test/test.dart';
import 'compact_number_test_data.dart' as testdata35;
import 'more_compact_number_test_data.dart' as more_testdata;
main() {
var problemLocales = [
// ICU produces numerals in Arabic script, package:intl uses Latin script.
'ar',
// package:intl includes some tweaks to compact numbers for Bengali.
'bn',
];
runICUTests(systemIcuVersion: 63, skipLocales: problemLocales);
}
void runICUTests(
{int systemIcuVersion = null,
String specialIcuLib = null,
List<String> skipLocales = null}) {
if (!setupICU(
systemIcuVersion: systemIcuVersion, specialIcuLibPath: specialIcuLib)) {
return;
}
print("Skipping problem locales $skipLocales.");
testdata35.compactNumberTestData
.removeWhere((k, v) => skipLocales.contains(k));
testdata35.compactNumberTestData.forEach(validate);
more_testdata.cldr35CompactNumTests.forEach(validateFancy);
test('UNumberFormatter simple integer formatting', () {
expect(FormatWithUnumf('en', 'precision-integer', 5142.3), '5,142');
});
}
void validate(String locale, List<List<String>> expected) {
validateShort(locale, expected);
validateLong(locale, expected);
}
void validateShort(String locale, List<List<String>> expected) {
test('Validate $locale SHORT', () {
for (var data in expected) {
var number = num.parse(data.first);
expect(FormatWithUnumf(locale, 'compact-short', number), data[1]);
}
});
}
void validateLong(String locale, List<List<String>> expected) {
test('Validate $locale LONG', () {
for (var data in expected) {
var number = num.parse(data.first);
expect(FormatWithUnumf(locale, 'compact-long', number), data[2]);
}
});
}
void validateFancy(more_testdata.CompactRoundingTestCase t) {
var locale = 'en';
var skel = 'compact-short';
if (t.minimumIntegerDigits != null) {
skel += ' integer-width/+' + '0' * t.minimumIntegerDigits;
}
if (t.significantDigits != null) {
skel += ' ' + '@' * t.significantDigits;
}
if (t.minimumFractionDigits != null) {
skel += ' .' + '0' * t.minimumFractionDigits;
var maxFD = t.maximumFractionDigits ?? 3;
skel += '#' * (maxFD - t.minimumFractionDigits);
} else if (t.maximumFractionDigits != null) {
skel += ' .' + '#' * t.maximumFractionDigits;
}
test(t.toString(), () {
expect(FormatWithUnumf(locale, skel, t.number), t.expected,
reason: 'Skeleton: $skel');
});
}
UErrorNameOp u_errorName;
UnumfOpenForSkeletonAndLocaleOp unumf_openForSkeletonAndLocale;
UnumfOpenResultOp unumf_openResult;
UnumfFormatDoubleOp unumf_formatDouble;
UnumfFormatIntOp unumf_formatInt;
UnumfResultToStringOp unumf_resultToString;
UnumfCloseOp unumf_close;
UnumfCloseResultOp unumf_closeResult;
/// Sets up dart:ffi functions.
///
/// If [systemIcuVersion] is specified, and set to 63 for example, we load the
/// functions from libicuuc.so.63 and libicui18n.so.63 if available. If
/// libraries are lacking, function returns [false].
///
/// If [systemIcuVersion] is unspecified, we expect to find all functions in a
/// library with filename [specialIcuLibPath].
bool setupICU({int systemIcuVersion = null, String specialIcuLibPath = null}) {
DynamicLibrary libicui18n;
String icuVersionSuffix;
if (systemIcuVersion != null) {
icuVersionSuffix = '_$systemIcuVersion';
try {
DynamicLibrary libicuuc =
DynamicLibrary.open('libicuuc.so.$systemIcuVersion');
u_errorName = libicuuc.lookupFunction<NativeUErrorNameOp, UErrorNameOp>(
"u_errorName$icuVersionSuffix");
libicui18n = DynamicLibrary.open('libicui18n.so.$systemIcuVersion');
} on ArgumentError catch (e) {
print('Unable to test against ICU version $systemIcuVersion: $e');
return false;
}
} else {
icuVersionSuffix = '';
libicui18n = DynamicLibrary.open(specialIcuLibPath);
u_errorName = libicui18n.lookupFunction<NativeUErrorNameOp, UErrorNameOp>(
"u_errorName$icuVersionSuffix");
}
unumf_openForSkeletonAndLocale = libicui18n.lookupFunction<
NativeUnumfOpenForSkeletonAndLocaleOp,
UnumfOpenForSkeletonAndLocaleOp>(
"unumf_openForSkeletonAndLocale$icuVersionSuffix");
unumf_openResult =
libicui18n.lookupFunction<NativeUnumfOpenResultOp, UnumfOpenResultOp>(
"unumf_openResult$icuVersionSuffix");
unumf_formatDouble =
libicui18n.lookupFunction<NativeUnumfFormatDoubleOp, UnumfFormatDoubleOp>(
"unumf_formatDouble$icuVersionSuffix");
unumf_formatInt =
libicui18n.lookupFunction<NativeUnumfFormatIntOp, UnumfFormatIntOp>(
"unumf_formatInt$icuVersionSuffix");
unumf_resultToString = libicui18n.lookupFunction<NativeUnumfResultToStringOp,
UnumfResultToStringOp>("unumf_resultToString$icuVersionSuffix");
unumf_close = libicui18n.lookupFunction<NativeUnumfCloseOp, UnumfCloseOp>(
"unumf_close$icuVersionSuffix");
unumf_closeResult =
libicui18n.lookupFunction<NativeUnumfCloseResultOp, UnumfCloseResultOp>(
"unumf_closeResult$icuVersionSuffix");
return true;
}
String FormatWithUnumf(String locale, String skeleton, num number) {
// // Setup:
// UErrorCode ec = U_ZERO_ERROR;
// UNumberFormatter* uformatter =
// unumf_openForSkeletonAndLocale(u"precision-integer", -1, "en", &ec);
// UFormattedNumber* uresult = unumf_openResult(&ec);
// if (U_FAILURE(ec)) { return; }
final cLocale = Utf8.toUtf8(locale);
final cSkeleton = Utf16.toUtf16(skeleton);
final cErrorCode = allocate<Int32>(count: 1);
cErrorCode.value = 0;
final uformatter =
unumf_openForSkeletonAndLocale(cSkeleton, -1, cLocale, cErrorCode);
free(cSkeleton);
free(cLocale);
var errorCode = cErrorCode.value;
expect(errorCode, lessThanOrEqualTo(0),
reason: u_errorName(errorCode).toString());
final uresult = unumf_openResult(cErrorCode);
errorCode = cErrorCode.value;
// Try to improve this once dart:ffi has extension methods:
expect(errorCode, lessThanOrEqualTo(0),
reason: u_errorName(errorCode).toString());
// // Format a double:
// unumf_formatDouble(uformatter, 5142.3, uresult, &ec);
// if (U_FAILURE(ec)) { return; }
if (number is double) {
unumf_formatDouble(uformatter, number, uresult, cErrorCode);
} else {
unumf_formatInt(uformatter, number, uresult, cErrorCode);
}
errorCode = cErrorCode.value;
expect(errorCode, lessThanOrEqualTo(0),
reason: u_errorName(errorCode).toString());
// // Export the string to a malloc'd buffer:
// int32_t len = unumf_resultToString(uresult, NULL, 0, &ec);
// // at this point, ec == U_BUFFER_OVERFLOW_ERROR
// ec = U_ZERO_ERROR;
// UChar* buffer = (UChar*) malloc((len+1)*sizeof(UChar));
// unumf_resultToString(uresult, buffer, len+1, &ec);
// if (U_FAILURE(ec)) { return; }
// // buffer should equal "5,142"
final reqLen = unumf_resultToString(uresult, nullptr.cast(), 0, cErrorCode);
errorCode = cErrorCode.value;
expect(errorCode, equals(15), // U_BUFFER_OVERFLOW_ERROR
reason: u_errorName(errorCode).toString());
cErrorCode.value = 0;
final buffer = allocate<Utf16>(count: reqLen + 1);
unumf_resultToString(uresult, buffer, reqLen + 1, cErrorCode);
errorCode = cErrorCode.value;
expect(errorCode, lessThanOrEqualTo(0),
reason: u_errorName(errorCode).toString());
final result = buffer.toString();
// // Cleanup:
// unumf_close(uformatter);
// unumf_closeResult(uresult);
// free(buffer);
unumf_close(uformatter);
unumf_closeResult(uresult);
free(buffer);
free(cErrorCode);
return result;
}
/// C signature for
/// [u_errorName()](http://icu-project.org/apiref/icu4c/utypes_8h.html#a89eb455526bb29bf5350ee861d81df92)
typedef NativeUErrorNameOp = Pointer<Utf8> Function(Int32 code);
/// Dart signature for
/// [u_errorName()](http://icu-project.org/apiref/icu4c/utypes_8h.html#a89eb455526bb29bf5350ee861d81df92)
typedef UErrorNameOp = Pointer<Utf8> Function(int code);
/// [UNumberFormatter](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a7c1238b2dd08f32f1ea245ece41e71bd)
class UNumberFormatter extends Struct {}
/// [UFormattedNumber](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a9d4030bdc4dd1ec4de828bf1bcf4b1b6)
class UFormattedNumber extends Struct {}
/// C signature for
/// [unumf_openForSkeletonAndLocale()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a29339e144833880bda36fb7c17032698)
typedef NativeUnumfOpenForSkeletonAndLocaleOp
= Pointer<UNumberFormatter> Function(Pointer<Utf16> skeleton,
Int32 skeletonLen, Pointer<Utf8> locale, Pointer<Int32> ec);
/// Dart signature for
/// [unumf_openForSkeletonAndLocale()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a29339e144833880bda36fb7c17032698)
typedef UnumfOpenForSkeletonAndLocaleOp = Pointer<UNumberFormatter> Function(
Pointer<Utf16> skeleton,
int skeletonLen,
Pointer<Utf8> locale,
Pointer<Int32> ec);
/// C signature for
/// [unumf_openResult()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a5bd2d297cb2664b4964d25fd41671dad)
typedef NativeUnumfOpenResultOp = Pointer<UFormattedNumber> Function(
Pointer<Int32> ec);
/// Dart signature for
/// [unumf_openResult()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a5bd2d297cb2664b4964d25fd41671dad)
typedef UnumfOpenResultOp = Pointer<UFormattedNumber> Function(
Pointer<Int32> ec);
/// C signature for
/// [unumf_formatDouble()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#af5f79e43adc900f07b3ba90b6315944e)
typedef NativeUnumfFormatDoubleOp = Void Function(
Pointer<UNumberFormatter> uformatter,
Double value,
Pointer<UFormattedNumber> uresult,
Pointer<Int32> ec);
/// Dart signature for
/// [unumf_formatDouble()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#af5f79e43adc900f07b3ba90b6315944e)
typedef UnumfFormatDoubleOp = void Function(
Pointer<UNumberFormatter> uformatter,
double value,
Pointer<UFormattedNumber> uresult,
Pointer<Int32> ec);
/// C signature for
/// [unumf_formatInt()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a459b9313ed05fc98c9cd125eb9c1a625)
typedef NativeUnumfFormatIntOp = Void Function(
Pointer<UNumberFormatter> uformatter,
Int32 value,
Pointer<UFormattedNumber> uresult,
Pointer<Int32> ec);
/// Dart signature for
/// [unumf_formatInt()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a459b9313ed05fc98c9cd125eb9c1a625)
typedef UnumfFormatIntOp = void Function(Pointer<UNumberFormatter> uformatter,
int value, Pointer<UFormattedNumber> uresult, Pointer<Int32> ec);
/// C signature for
/// [unumf_resultToString()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a72131183633fda6851c35e37ffd821a1)
typedef NativeUnumfResultToStringOp = Int32 Function(
Pointer<UFormattedNumber> uresult,
Pointer<Utf16> buffer,
Int32 bufferCapacity,
Pointer<Int32> ec);
/// Dart signature for
/// [unumf_resultToString()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a72131183633fda6851c35e37ffd821a1)
typedef UnumfResultToStringOp = int Function(Pointer<UFormattedNumber> uresult,
Pointer<Utf16> buffer, int bufferCapacity, Pointer<Int32> ec);
/// C signature for
/// [unumf_close()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a6f47836ca05077fc912ad24e462312c6)
typedef NativeUnumfCloseOp = Void Function(
Pointer<UNumberFormatter> uformatter);
/// Dart signature for
/// [unumf_close()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a6f47836ca05077fc912ad24e462312c6)
typedef UnumfCloseOp = void Function(Pointer<UNumberFormatter> uformatter);
/// C signature for
/// [unumf_closeResult()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a78f19cef14a2db1a0eb62a8b724eb123)
typedef NativeUnumfCloseResultOp = Void Function(
Pointer<UFormattedNumber> uresult);
/// Dart signature for
/// [unumf_closeResult()](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a78f19cef14a2db1a0eb62a8b724eb123)
typedef UnumfCloseResultOp = void Function(Pointer<UFormattedNumber> uresult);