blob: 8d14790e416225dbaf6aa29ae653495ac1b1af78 [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.
@TestOn('!browser')
@Tags(['ffi'])
@Skip('currently failing (see https://github.com/dart-lang/intl/issues/240)')
// ignore_for_file: non_constant_identifier_names
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;
void 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,
String? specialIcuLib,
Set<String> skipLocales = const {}}) {
if (!_setupICU(
systemIcuVersion: systemIcuVersion, specialIcuLibPath: specialIcuLib)) {
return;
}
void validate(String locale, List<List<String>> expected) {
_validateShort(locale, expected, skipLocales);
_validateLong(locale, expected, skipLocales);
}
testdata35.compactNumberTestData.forEach(validate);
more_testdata.cldr35CompactNumTests.forEach(_validateFancy);
test('UNumberFormatter simple integer formatting', () {
expect(_formatWithUnumf('en', 'precision-integer', 5142.3), '5,142');
});
}
void _validateShort(
String locale, List<List<String>> expected, Set<String> skipLocales) {
var skip =
skipLocales.contains(locale) ? 'Skipping problem locale $locale' : false;
test('Validate $locale SHORT', () {
for (var data in expected) {
var number = num.parse(data.first);
expect(_formatWithUnumf(locale, 'compact-short', number), data[1]);
}
}, skip: skip);
}
void _validateLong(
String locale, List<List<String>> expected, Set<String> skipLocales) {
var skip =
skipLocales.contains(locale) ? 'Skipping problem locale $locale' : false;
test('Validate $locale LONG', () {
for (var data in expected) {
var number = num.parse(data.first);
expect(_formatWithUnumf(locale, 'compact-long', number), data[2]);
}
}, skip: skip);
}
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.maximumSignificantDigits != null) {
skel += ' ${'@' * t.maximumSignificantDigits!}';
if (t.minimumSignificantDigits != t.maximumSignificantDigits) {
// Pattern doesn't support min/max significant digits. Ignore.
return;
}
}
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, String? specialIcuLibPath}) {
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 = locale.toNativeUtf8();
final cSkeleton = skeleton.toNativeUtf16();
final cErrorCode = calloc<Int32>();
cErrorCode.value = 0;
final uformatter =
unumf_openForSkeletonAndLocale!(cSkeleton, -1, cLocale, cErrorCode);
calloc.free(cSkeleton);
calloc.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 as int, 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 = calloc<Uint16>(reqLen + 1).cast<Utf16>();
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);
// calloc.free(buffer);
unumf_close!(uformatter);
unumf_closeResult!(uresult);
calloc.free(buffer);
calloc.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 Opaque {}
/// [UFormattedNumber](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html#a9d4030bdc4dd1ec4de828bf1bcf4b1b6)
class UFormattedNumber extends Opaque {}
/// 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);