diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 999da8c1..13c985f1 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -19,6 +19,10 @@ jobs: name: End-to-end (Gen data, run tests, gen GH Pages) runs-on: ubuntu-latest steps: + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + with: + sdk: stable + - uses: actions/checkout@v2 - run: bash generateDataAndRun.sh - name: Setup Pages diff --git a/.gitignore b/.gitignore index 7db2a1b0..df107287 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,8 @@ testgen/icu*/*.json # C / CPP compiled *.o + +testdriver/.local-chrome/ +testgen/*.json + +TEMP_DATA/* \ No newline at end of file diff --git a/executors/dart_native/.gitignore b/executors/dart_native/.gitignore new file mode 100644 index 00000000..e288bd98 --- /dev/null +++ b/executors/dart_native/.gitignore @@ -0,0 +1,5 @@ +bin/executor.exe + +.dart_tool +build/ +pubspec.lock \ No newline at end of file diff --git a/executors/dart_native/analysis_options.yaml b/executors/dart_native/analysis_options.yaml new file mode 100644 index 00000000..a20fa93f --- /dev/null +++ b/executors/dart_native/analysis_options.yaml @@ -0,0 +1,18 @@ +include: package:lints/recommended.yaml + +analyzer: + language: + strict-raw-types: true + errors: + deprecated_member_use_from_same_package: ignore + +linter: + rules: + - always_declare_return_types + - directives_ordering + - prefer_single_quotes + - sort_pub_dependencies + - unnecessary_parenthesis + - avoid_dynamic_calls + - type_annotate_public_apis + - non_constant_identifier_names diff --git a/executors/dart_native/bin/executor.dart b/executors/dart_native/bin/executor.dart new file mode 100644 index 00000000..1882bbce --- /dev/null +++ b/executors/dart_native/bin/executor.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:intl4x/collation.dart'; +import 'package:intl4x/intl4x.dart'; + +Map> supportedTests = { + 'supported_tests': [ + 'coll_shift_short', + 'decimal_fmt', + 'number_fmt', + 'display_names', + 'language_display_name', + ], +}; + +enum TestTypes { + coll_shift_short, + decimal_fmt, + datetime_fmt, + display_names, + lang_names, + number_fmt; +} + +void main() { + stdin.listen((event) { + var lines = utf8.decode(event); + for (var line in lines.split('\n')) { + if (line == '#EXIT') { + exit(0); + } else if (line == '#VERSION') { + printVersion(); + } else if (line == '#TESTS') { + print(json.encode(supportedTests)); + } else { + Map decoded; + try { + decoded = json.decode(line); + } catch (e) { + print('ERROR $line'); + rethrow; + } + + var testType = TestTypes.values + .firstWhere((element) => element.name == decoded['test_type']); + Object result; + switch (testType) { + case TestTypes.coll_shift_short: + result = testCollator(decoded); + break; + case TestTypes.decimal_fmt: + // TODO: Handle this case. + case TestTypes.datetime_fmt: + // TODO: Handle this case. + case TestTypes.display_names: + // TODO: Handle this case. + case TestTypes.lang_names: + // TODO: Handle this case. + case TestTypes.number_fmt: + // TODO: Handle this case. + default: + throw UnsupportedError(''); + } + + var outputLine = {'label': decoded['label'], 'result': result}; + print(json.encode(outputLine)); + } + } + }); +} + +bool testCollator(Map decoded) { + var compared = + Intl().collation(CollationOptions(ignorePunctuation: true)).compare( + decoded['string1'], + decoded['string2'], + ); + var result = compared <= 0 ? true : false; + return result; +} + +void printVersion() { + var version = Platform.version; + var parsedVersion = version.substring(0, version.indexOf(' ')); + var versionInfo = { + 'icuVersion': '71.1', + 'platform': 'Dart', + 'platformVersion': parsedVersion, + }; + print(json.encode(versionInfo)); +} diff --git a/executors/dart_native/pubspec.yaml b/executors/dart_native/pubspec.yaml new file mode 100644 index 00000000..9e107d7f --- /dev/null +++ b/executors/dart_native/pubspec.yaml @@ -0,0 +1,17 @@ +name: dart +description: A sample command-line application. +version: 1.0.0 +publish_to: none + +environment: + sdk: ^3.0.0 + +# Add regular dependencies here. +dependencies: + intl4x: ^0.4.0 + +dev_dependencies: + build_runner: ^2.4.4 + build_web_compilers: ^4.0.3 + lints: ^2.0.0 + test: ^1.21.0 diff --git a/executors/dart_web/.gitignore b/executors/dart_web/.gitignore new file mode 100644 index 00000000..37549c64 --- /dev/null +++ b/executors/dart_web/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +out/collatorDart.js +out/collatorDart.js.deps +out/collatorDart.js.map +pubspec.lock \ No newline at end of file diff --git a/executors/dart_web/bin/all_executors.dart b/executors/dart_web/bin/all_executors.dart new file mode 100644 index 00000000..3410063a --- /dev/null +++ b/executors/dart_web/bin/all_executors.dart @@ -0,0 +1,5 @@ +import 'collator.dart'; + +void main(List args) { + testCollationShort(args.first); //just some call to not treeshake the function +} diff --git a/executors/dart_web/bin/collator.dart b/executors/dart_web/bin/collator.dart new file mode 100644 index 00000000..5888ccae --- /dev/null +++ b/executors/dart_web/bin/collator.dart @@ -0,0 +1,45 @@ +// The Collator used for the actual testing. + +// !!! TODO: Collation: determine the sensitivity that corresponds +// to the strength. +import 'dart:convert'; + +import 'package:intl4x/collation.dart'; +import 'package:intl4x/intl4x.dart'; + +String testCollationShort(String jsonEncoded) { + var json = + jsonDecode(jsonEncoded); // For the moment, use strings for easier interop + // Global default locale + var testLocale = ''; + Map outputLine; + + // Set up collator object with optional locale and testOptions. + try { + Intl coll; + if (testLocale.isNotEmpty) { + coll = Intl(locale: Locale.parse(testLocale)); + } else { + coll = Intl(); + } + var d1 = json['string1']; + var d2 = json['string2']; + + var collationOptions = CollationOptions(ignorePunctuation: true); + var compared = coll.collation(collationOptions).compare(d1, d2); + var result = compared <= 0 ? true : false; + outputLine = {'label': json['label'], "result": result}; + + if (result != true) { + // Additional info for the comparison + outputLine['compare'] = compared; + } + } catch (error) { + outputLine = { + 'label': json['label'], + 'error_message': error.toString(), + 'error': 'Collator compare failed' + }; + } + return jsonEncode(outputLine); +} diff --git a/executors/dart_web/bin/make_runnable_by_node.dart b/executors/dart_web/bin/make_runnable_by_node.dart new file mode 100644 index 00000000..f18c592a --- /dev/null +++ b/executors/dart_web/bin/make_runnable_by_node.dart @@ -0,0 +1,47 @@ +import 'dart:io'; + +Future main(List args) async { + var name = 'collatorDart'; + var compile = await Process.run('dart', [ + 'compile', + 'js', + 'bin/all_executors.dart', + '-o', + 'out/$name.js', + ]); + print(compile.stderr); + + prepareOutFile(name, ['testCollationShort']); +} + +/// Prepare the file to export `testCollationShort` +void prepareOutFile(String name, List functions) { + var outFile = File('out/$name.js'); + var s = outFile.readAsStringSync(); + s = s.replaceAll('self.', ''); + s = s.replaceFirst('(function dartProgram() {', + 'module.exports = (function dartProgram() {'); + + s = s.replaceFirst('(function dartProgram() {', + 'module.exports = (function dartProgram() {'); + + var exportFunctions = functions + .map( + (e) => '''$e: function(arg) { + return A.$e(arg); + }''', + ) + .join(',\n'); + s = s.replaceFirst( + '})();\n\n//# sourceMappingURL=$name.js.map', + ''' + return { + $exportFunctions + }; + })(); + //# sourceMappingURL=$name.js.map + ''', + ); + s = 'function dartMainRunner(main, args){}' + s; + outFile.writeAsStringSync(s); +} diff --git a/executors/dart_web/out/collator.js b/executors/dart_web/out/collator.js new file mode 100644 index 00000000..6edc7f45 --- /dev/null +++ b/executors/dart_web/out/collator.js @@ -0,0 +1,10 @@ +var tools = require('./collatorDart'); +// The Collator used for the actual testing. + +// !!! TODO: Collation: determine the sensitivity that corresponds +// to the strength. +module.exports = { + testCollationShort: function (json) { + return JSON.parse(tools.testCollationShort(JSON.stringify(json))); + } +}; diff --git a/executors/dart_web/out/executor.js b/executors/dart_web/out/executor.js new file mode 100644 index 00000000..04376897 --- /dev/null +++ b/executors/dart_web/out/executor.js @@ -0,0 +1,191 @@ +/* Main execution program for Data Driven Test of ICU / Unicode / CLDR + functions and data. + + This accepts test statements via StdIn in JSON format. + + The type of test determines the corresponding test function that then + receives the test case. This includes the type of operation, e.g., + collation, number format, locale matching, etc. + + The data includes parameters needed to specify the function called as well + as the test data passed to the function. + + Expected results are not read by this program. + + Output includes test ID and actual results from the test, written to StdOut. +*/ + + +let collator = require('./collator.js') + +/** + * TODOs: + * 1. Handle other types of test cases. + */ + +/** + * 16-Sep-2022: Modularize this, moving functions to other files. + * 29-Aug-2022: Adding basic decimal test + * 16-Aug-2022: Collation tests all working now. + * 09-Aug-2022: Using updated Collation test data, about 10% of the tests fail + * + * Started 28-July-2022, ccornelius@google.com + */ + +let doLogInput = 0; +let doLogOutput = 0; + +// Test type support. Add new items as they are implemented +const testTypes = { + TestCollShiftShort: Symbol("coll_shift_short"), + TestDecimalFormat: Symbol("decimal_fmt"), + TestNumberFormat: Symbol("number_fmt"), + TestDateTimeFormat: Symbol("datetime_fmtl"), + TestRelativeDateTimeFormat: Symbol("relative_datetime_fmt"), + TestPluralRules: Symbol("plural_rules"), + TestDisplayNames: Symbol("display_names"), + TestLangNames: Symbol("language_display_name"), +} + +const supported_test_types = [ + Symbol("coll_shift_short"), + Symbol("decimal_fmt"), + Symbol("number_fmt"), + Symbol("display_names"), + Symbol("language_display_name") +]; +const supported_tests_json = { + "supported_tests": + [ + "coll_shift_short", + "decimal_fmt", + "number_fmt", + "display_names", + "language_display_name" + ] +}; + +// Test line-by-line input, with output as string. +// Check on using Intl functions, e.g., DateTimeFormat() + +let readline = require('readline'); +let rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + +/** + * Given a JSON data structure, check for "test_type". If not present, then + * infer the test ID from the label + * !!! Not used now. + */ +function parseJsonForTestId(parsed) { + let testId = parsed["test_type"]; + + if (testId == "coll_shift_short") { + return testTypes.TestCollShiftShort; + } + if (testId == "decimal_fmt" || testId == "number_fmt") { + return testTypes.TestDecimalFormat; + } + if (testId == "display_names") { + return testTypes.TestDisplayNames; + } + if (testId == "language_display_name") { + return testTypes.TestLangNames; + } + console.log("#*********** NODE Unknown test type = " + testId); + return null; + + // No test found. + return null; +} + +// Read JSON tests, each on a single line. +// Process the test and output a line of JSON results. +let lineId = 0; +rl.on('line', function (line) { + + // if logging input. + if (doLogInput > 0) { + console.log("## NODE RECEIVED " + lineId + ' ' + line + ' !!!!!'); + } + + // Protocol: + // #VERSION to get version information + // {....} test line + // EXIT + // Check for commands starting with "#". + if (line == "#VERSION") { + // JSON output of the test enviroment. + let versionJson = { + 'platform': 'NodeJS', + 'platformVersion': process.version, + 'icuVersion': process.versions.icu, + }; + + // TODO: Make this more specific JSON info. + lineOut = JSON.stringify(versionJson); + process.stdout.write(lineOut); + } else + if (line == "#EXIT") { + process.exit(); + } else + if (line == "#TESTS") { + lineOut = JSON.stringify(supported_tests_json); + process.stdout.write(lineOut); + } + else { + // Handle test cases. + let testId; + let parsedJson; + try { + parsedJson = JSON.parse(line); + } catch (error) { + outputLine = { + 'Cannot parse input line': error, + 'input_line': line, + "testId": testId + }; + + // Send result to stdout for verification + jsonOut = JSON.stringify(outputLine); + if (doLogOutput > 0) { + console.log("## ERROR " + lineId + ' ' + outputLine + ' !!!!!'); + } + process.stdout.write(jsonOut); + } + + if (doLogInput > 0) { + console.log("#----- PARSED JSON: " + JSON.stringify(parsedJson)); + } + + // testId = parseJsonForTestId(parsedJson); + // Handle the string directly to call the correct function. + const test_type = parsedJson["test_type"]; + if (test_type == "coll_shift_short") { + outputLine = collator.testCollationShort(parsedJson); + } else { + outputLine = { + 'error': 'unknown test type', 'testId': testId, + 'unsupported_test': testId + }; + } + + if ('error' in outputLine) { + // To get the attention of the driver + console.log("#!! ERROR in NODE call: " + JSON.stringify(outputLine)); + } + + // Send result to stdout for verification + jsonOut = JSON.stringify(outputLine); + process.stdout.write(jsonOut + '\n'); + if (doLogOutput > 0) { + console.log("##### NODE RETURNS " + lineId + ' ' + jsonOut + ' !!!!!'); + } + + } + lineId += 1; +} +) diff --git a/executors/dart_web/pubspec.yaml b/executors/dart_web/pubspec.yaml new file mode 100644 index 00000000..155704d4 --- /dev/null +++ b/executors/dart_web/pubspec.yaml @@ -0,0 +1,15 @@ +name: dart_web +description: A sample command-line application. +version: 1.0.0 +publish_to: none + +environment: + sdk: ^3.0.0 + +# Add regular dependencies here. +dependencies: + intl4x: ^0.4.0 + +dev_dependencies: + lints: ^2.0.0 + test: ^1.21.0 diff --git a/generateDataAndRun.sh b/generateDataAndRun.sh index b4d1bb69..a52f47fe 100755 --- a/generateDataAndRun.sh +++ b/generateDataAndRun.sh @@ -41,6 +41,16 @@ rustup install 1.61 rustup run 1.61 cargo build --release popd +pushd executors/dart_native/ +dart pub get +dart compile exe bin/executor.dart +popd + +pushd executors/dart_web/ +dart pub get +dart run bin/make_runnable_by_node.dart +popd + # Executes all tests on that new data in the new directory mkdir -p $TEMP_DIR/testOutput @@ -50,6 +60,12 @@ pushd testdriver # Set to use NVM source "$HOME/.nvm/nvm.sh" +#Dart ICU73 +nvm install 20.1.0 +nvm use 20.1.0 +python3 testdriver.py --icu_version icu73 --exec dart_web --test_type coll_shift_short --file_base ../$TEMP_DIR --per_execution 10000 +echo $? + #ICU73 nvm install 20.1.0 nvm use 20.1.0 @@ -97,6 +113,7 @@ mkdir -p $TEMP_DIR/testReports pushd verifier python3 verifier.py --file_base ../$TEMP_DIR --exec rust node --test_type coll_shift_short number_fmt lang_names +python3 verifier.py --file_base ../$TEMP_DIR --exec dart_web --test_type coll_shift_short #python3 verifier.py --file_base ../$TEMP_DIR --exec cpp--test_type coll_shift_short number_fmt lang_names popd diff --git a/testdriver/datasets.py b/testdriver/datasets.py index 2b93d6d9..0717f781 100644 --- a/testdriver/datasets.py +++ b/testdriver/datasets.py @@ -144,11 +144,14 @@ class ExecutorLang(Enum): RUST = "rust" CPP = "cpp" JAVA = "java" - DART = "dart" + DARTWEB = "dart_web" + DARTNATIVE = "dart_native" # Actual commmands to run the executors. ExecutorCommands = { "node" : "node ../executors/node/executor.js", + "dart_web" : "node ../executors/dart_web/out/executor.js", + "dart_native" : "executors/dart_native/bin/executor.exe", "rust" : "../executors/rust/target/release/executor", "cpp": "../executors/cpp/executor", "java" : None @@ -170,6 +173,9 @@ class NodeVersion(Enum): Node14_16 = "14.16.0" Node14_0 = "14.0.0" +class DartVersion(Enum): + Dart3 = "3.1.0-39.0.dev" + class RustVersion(Enum): Rust01 = "0.1" Rust1 = "1.0" @@ -228,6 +234,7 @@ class ICU4XVersion(Enum): 'node': NodeICUVersionMap, 'icu4x': ICU4XVersionMap, 'rust': ICU4XVersionMap, + 'dart_web': NodeICUVersionMap, } # Executor programs organized by langs and version @@ -339,6 +346,20 @@ def has(self, exec) : system = ExecutorLang.JAVA +system = ExecutorLang.DARTWEB.value +allExecutors.addSystem(system, NodeVersion.Node19, + 'node ../executors/dart_web/out/executor.js', + CLDRVersion.CLDR42, versionICU=ICUVersion.ICU71) + +allExecutors.addSystem(system, NodeVersion.Node18_7, + 'node ../executors/dart_web/out/executor.js', + CLDRVersion.CLDR41, versionICU=ICUVersion.ICU71) + +system = ExecutorLang.DARTNATIVE.value +allExecutors.addSystem(system, DartVersion.Dart3, + '../executors/dart_native/bin/executor.exe', + CLDRVersion.CLDR42, versionICU=ICUVersion.ICU71) + # TESTING def printExecutors(executors): print('Executor paths:')