Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICU-22908 DRAFT!!! MF2 ICU4J: Update spec tests and update implementation for recent spec changes #3206

Closed
wants to merge 19 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix the json reading of expErrors (array and bool)
mihnita committed Jan 31, 2025
commit ab51e528f48604c0d391cae407ad4a39b0182413
Original file line number Diff line number Diff line change
@@ -29,24 +29,24 @@ public class CoreTest extends CoreTestFmwk {
"matches-whitespace.json",
"more-data-model-errors.json",
"more-functions.json",
"normalization.json", // new // FAILS 10 / 11
// "normalization.json", // new // FAILS 10 / 11
"resolution-errors.json",
"runtime-errors.json",
"spec/bidi.json", // new // FAILS 16 / 27
// "spec/bidi.json", // new // FAILS 16 / 27
"spec/data-model-errors.json", // FAILS 1 / 23
"spec/syntax-errors.json", // FAILS 1 / 134
"spec/syntax.json", // FAILS 8 / 107
"spec/fallback.json", // new // FAILS 3 / 8
"spec/functions/currency.json", // new // FAILS: ERROR PARSING JSON // FAILS 3 / 12
// "spec/fallback.json", // new
// "spec/functions/currency.json", // new // FAILS 3 / 12
"spec/functions/date.json", // FAILS 2 / 7
"spec/functions/datetime.json", // FAILS 1 / 8
"spec/functions/integer.json", // FAILS 1 / 6
"spec/functions/math.json", // new // FAILS 9 / 16
// "spec/functions/math.json", // new // FAILS 9 / 16
"spec/functions/number.json", // FAILS 10 / 23
"spec/functions/string.json", // FAILS 7 / 9
"spec/functions/time.json", // FAILS 2 / 6
"spec/pattern-selection.json", // new // FAILS 15 / 22
"spec/u-options.json", // new // FAILS 10 / 11
// "spec/pattern-selection.json", // new // FAILS 15 / 22
// "spec/u-options.json", // new // FAILS 10 / 11
"syntax-errors-diagnostics.json", // FAILS 1 / 123
"syntax-errors-diagnostics-multiline.json",
"syntax-errors-end-of-input.json",
@@ -57,10 +57,25 @@ public class CoreTest extends CoreTestFmwk {
"valid-tests.json" // FAILS 2 / 84
};

static boolean reportError(boolean firstTime, String jsonFile, String message) {
if (firstTime) {
System.out.println();
System.out.println("============================");
System.out.printf("= Reading json file: %s%n", jsonFile);
System.out.println("============================");
}
System.out.println(message);
System.out.println("----------");
return false;
}

@Test
public void test() throws Exception {
for (String jsonFile : JSON_FILES) {
// System.out.printf("Reading json file: %s%n", jsonFile);
boolean firstTime = true;
// System.out.println("============================");
// System.out.printf("= Reading json file: %s%n", jsonFile);
// System.out.println("============================");
try (Reader reader = TestUtils.jsonReader(jsonFile)) {
int errorCount = 0;
MF2Test tests;
@@ -69,34 +84,21 @@ public void test() throws Exception {
} catch (com.google.gson.JsonSyntaxException e) {
tests = new MF2Test(null, new Unit[0]);
errorCount = 1_000_000;
System.out.printf("----%n"
+ "Failing: %s%n"
+ "----%n", e.getMessage());
firstTime = reportError(firstTime, jsonFile, "JSON error: " + e.getMessage());
}
for (Unit unit : tests.tests) {
try {
TestUtils.runTestCase(tests.defaultTestProperties, unit);
} catch (AssertionError e) {
// System.out.printf("----%n"
// + "Failing: %s%n"
// + "%s%n"
// + "----%n", unit, e.getMessage());
firstTime = reportError(firstTime, jsonFile, e.getMessage());
errorCount++;
}
}
String color = errorCount == 0 ? "\033[32m" : "\033[91m";
if (errorCount != 0) {
// System.out.printf("\"%s\", // FAILS %d / %d%n",
// jsonFile, errorCount, tests.tests.length);
System.out.printf("%s\t%d\t%d%n",
jsonFile, errorCount, tests.tests.length);
} else {
// System.out.printf("\"%s\",%n", jsonFile);
System.out.printf("%s\t%d\t%d%n",
jsonFile, errorCount, tests.tests.length);
}
// System.out.printf(" %sResult for '%s': total %d failures: %d\033[m%n",
// color, jsonFile, tests.tests.length, errorCount);
}
}
}
Original file line number Diff line number Diff line change
@@ -3,20 +3,17 @@

package com.ibm.icu.dev.test.message2;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;

// See https://github.com/unicode-org/conformance/blob/main/schema/message_fmt2/testgen_schema.json

// Class corresponding to the json test files.
// Since this is serialized by Gson, the field names should match the keys in the .json files.
class DefaultTestProperties {
private static final Object[] NO_ERRORS = {};
private static final ExpErrors NO_ERRORS = new ExpErrors(false);
// Unused fields ignored
private final String locale;
private final JsonElement expErrors;
private final ExpErrors expErrors;

DefaultTestProperties(String locale, JsonElement expErrors) {
DefaultTestProperties(String locale, ExpErrors expErrors) {
this.locale = locale;
this.expErrors = expErrors;
}
@@ -25,15 +22,7 @@ String getLocale() {
return this.locale;
}

Object[] getExpErrors() {
if (expErrors == null || !expErrors.isJsonArray()) {
return NO_ERRORS;
}
JsonArray arr = expErrors.getAsJsonArray();
Object [] result = new Object[arr.size()];
for (int i = 0; i < result.length; i++) {
result[i] = arr.get(i);
}
return result;
ExpErrors getExpErrors() {
return expErrors == null ? NO_ERRORS : expErrors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// © 2024 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html

package com.ibm.icu.dev.test.message2;

import java.util.ArrayList;
import java.util.List;

// Class corresponding to the json test files.
// See Unit.java and StringToListAdapter.java for how this is used.
// Workaround for not being able to get the class of a generic type.

class ExpErrors {
public final String ANY_ERROR = "*-any";
final List<String> errors;
final boolean hasErrors;

ExpErrors(boolean hasErrors) {
this.hasErrors = hasErrors;
this.errors = new ArrayList<>();
if (hasErrors) {
// This is problematic if we start testing for the exact error we expect.
// Unlikely, since in Java we only report the error by throwing,
// without a good way to specify the exact error type.
// If we get there we might change this to an enum, so there is some
// refactoring to be done anyway.
this.errors.add(ANY_ERROR);
}
}

ExpErrors(List<String> errors) {
this.errors = errors == null ? new ArrayList<>() : errors;
this.hasErrors = errors != null && !errors.isEmpty();
}

boolean expectErrors() {
return this.hasErrors;
}

@Override
public String toString() {
return ("[" + errors.toString() + "]");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// © 2024 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html

package com.ibm.icu.dev.test.message2;

import java.io.IOException;
import java.util.ArrayList;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

/* Helper class that converts an array of objects with key named "type"
* and value `String` to a List<String>.
* so that the ExpErrors property can be either a boolean or an array
* of strings objects.
*
* Example (json):
* ```
* "expErrors": false,
* "expErrors": true,
* "expErrors": [],
* "expErrors": [{ "type": "syntax-error" }, { "type": "unknown-function" }]
*
* Used in the TestUtils class.
*/

// Uses ArrayList instead of List so that when registering, it's possible
// to get ArrayList<String>.class
public class ExpectedErrorAdapter extends TypeAdapter<ExpErrors> {
public ExpErrors read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
if (reader.peek() == JsonToken.BEGIN_ARRAY) {
ArrayList<String> result = new ArrayList<String>();
reader.beginArray();
while (reader.hasNext()) {
reader.beginObject();
String name = reader.nextName();
new AssertionError(name.equals("type"));
String value = reader.nextString();
result.add(value);
reader.endObject();
}
reader.endArray();
return new ExpErrors(result);
}
if (reader.peek() == JsonToken.BOOLEAN) {
return new ExpErrors(reader.nextBoolean());
}
throw new IOException();
}

public void write(JsonWriter writer, ExpErrors value) throws IOException {
writer.beginArray();
for (String s : value.errors) {
writer.beginObject();
writer.name("type");
writer.value(s);
writer.endObject();
}
writer.endArray();
}
}

Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ public class TestUtils {
static final Gson GSON = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.registerTypeAdapter(Sources.class, new StringToListAdapter())
.registerTypeAdapter(ExpErrors.class, new ExpectedErrorAdapter())
.create();

// ======= Legacy TestCase utilities, no json-compatible ========
@@ -129,8 +130,8 @@ static Map<String, Object> paramsToMap(Param[] params) {
}

static boolean expectsErrors(DefaultTestProperties defaults, Unit unit) {
return (unit.expErrors != null && !unit.expErrors.isEmpty())
|| (defaults.getExpErrors().length > 0);
return (unit.expErrors != null && unit.expErrors.expectErrors())
|| (defaults.getExpErrors().expectErrors());
}

static void runTestCase(DefaultTestProperties defaults, Unit unit) {
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@

package com.ibm.icu.dev.test.message2;

import java.util.List;
import java.util.StringJoiner;

// Class corresponding to the json test files.
@@ -15,15 +14,15 @@ class Unit {
final Param[] params;
final String exp;
final String ignoreJava;
final List<Error> expErrors;
final ExpErrors expErrors;

Unit(
Sources src,
String locale,
Param[] params,
String exp,
String ignoreJava,
List<Error> expErrors) {
ExpErrors expErrors) {
this.src = src;
this.locale = locale;
this.params = params;
@@ -82,7 +81,7 @@ public Unit merge(Unit other) {
Param[] newParams = other.params != null ? other.params : this.params;
String newExp = other.exp != null ? other.exp : this.exp;
String newIgnore = other.ignoreJava != null ? other.ignoreJava : this.ignoreJava;
List<Error> newExpErrors = other.expErrors != null ? other.expErrors : this.expErrors;
ExpErrors newExpErrors = other.expErrors != null ? other.expErrors : this.expErrors;
return new Unit(newSrc, newLocale, newParams, newExp, newIgnore, newExpErrors);
}