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

Add MessageFormat2 support with ICU4J executor #175

Merged
merged 35 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
aa5f8ae
Split testdata_gen.py into multiple modules
mradbourne Mar 1, 2024
a7a8d37
Add draft test schema
mradbourne Feb 5, 2024
2c35eea
Add MF2 schemas
mradbourne Feb 23, 2024
dfe297d
Add preliminary code for ICU4J executor for MF2 tests
echeran Feb 21, 2024
d7b7a5f
Add MF2 test generator
mradbourne Feb 27, 2024
7440beb
Fix unit test by setting time zone explicitly
echeran Feb 28, 2024
1c9b7f2
Revert "Fix unit test by setting time zone explicitly"
echeran Feb 28, 2024
1ca9227
Use Date constructor that uses local time zone, which matches implici…
echeran Feb 28, 2024
1bb65d5
Pin the default time zone and locale to make ICU formatting tests mor…
echeran Feb 28, 2024
bd92564
Re-add ability to read from JSON in testdata-gen
mradbourne Mar 12, 2024
05eec12
Add MF2 testgen logic to new file
mradbourne Mar 12, 2024
f8456cd
Extract TestType into separate file
mradbourne Mar 12, 2024
cb7b6b8
Re-add MF2 test generator to testdata_gen
mradbourne Mar 12, 2024
f9c254c
Add syntax tests
mradbourne Apr 7, 2024
741c80a
Add format_to_parts testType to MF2 schemas
mradbourne Apr 7, 2024
529dfe0
Add missing description for placeholder test
mradbourne Apr 7, 2024
7d331dd
Remove invalid comma
mradbourne Apr 7, 2024
a098c67
Reorganise syntax tests
mradbourne Apr 7, 2024
bb7d2e9
Add integer formatter tests
mradbourne Apr 7, 2024
92252e7
Merge branch 'main' into messageformat2-icu4j
echeran May 7, 2024
545053c
Move MF2 ICU4J executor code to directory for ICU4J v74 code
echeran May 7, 2024
fb292d9
Import MF2 tests from message-format-wg repo
mradbourne May 17, 2024
df2251e
Move TestType to new file
mradbourne May 17, 2024
f076512
Add separate README for testgen
mradbourne May 17, 2024
df81d70
Update MF2 test schema
mradbourne May 17, 2024
d7f0209
Revert test_schema changes
mradbourne May 17, 2024
ce7c4e9
Update testgen_schema
mradbourne May 17, 2024
69e0472
Update MF2 testgen and schemas
mradbourne May 17, 2024
e701e33
Rename pattern property to src
mradbourne May 21, 2024
5a1b3e8
Enable test resources from `testgen` directory
echeran Jun 17, 2024
56c825e
Update executor to recognize new schema for test input cases for MF2
echeran Jun 17, 2024
6519e04
Update testgen for new schema and arrangement of message_fmt2 source …
echeran Jun 22, 2024
683644a
Enable schema validation on generated test cases for message_fmt2
echeran Jun 22, 2024
bd478a6
Fix configuration for message_fmt2 executor
echeran Jun 23, 2024
81515d5
Add (currently ignored) test that can reproduce parsing error on numb…
echeran Jun 23, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

.pylintrc
.idea
.devcontainer

executors/rust/target/
executors/cpp/executor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.unicode.conformance.testtype.messageformat2;

public class MFInputArg {

public String name;

public MFInputArgType argType;

public Object value;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.unicode.conformance.testtype.messageformat2;

public enum MFInputArgType {
string,
number,
datetime
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.unicode.conformance.testtype.messageformat2;

public enum MFTestSubType {

syntax,
selector,
formatter
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.unicode.conformance.testtype.messageformat2;

import java.util.List;
import org.unicode.conformance.testtype.ITestTypeInputJson;

public class MessageFormatInputJson implements ITestTypeInputJson {

public String test_type;

public String label;

public MFTestSubType test_subtype;

public String locale;

public String pattern;

public String test_description;

public List<MFInputArg> inputs;

public String verify;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.unicode.conformance.testtype.messageformat2;

import org.unicode.conformance.testtype.ITestTypeOutputJson;

public class MessageFormatOutputJson implements ITestTypeOutputJson {

public String label;

public String verify;

public String error;

public String error_message;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.unicode.conformance.testtype.messageformat2;

import com.ibm.icu.message2.MessageFormatter;
import io.lacuna.bifurcan.IMap;
import io.lacuna.bifurcan.Map;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import org.unicode.conformance.ExecutorUtils;
import org.unicode.conformance.testtype.ITestType;
import org.unicode.conformance.testtype.ITestTypeInputJson;
import org.unicode.conformance.testtype.ITestTypeOutputJson;
public class MessageFormatTester implements ITestType {

public static MessageFormatTester INSTANCE = new MessageFormatTester();

@Override
public ITestTypeInputJson inputMapToJson(Map<String, Object> inputMapData) {
MessageFormatInputJson result = new MessageFormatInputJson();

result.test_type = (String) inputMapData.get("test_type", null);
result.label = (String) inputMapData.get("label", null);

result.test_subtype = (MFTestSubType) inputMapData.get("test_subtype", null);
result.locale = (String) inputMapData.get("locale", null);
result.pattern = (String) inputMapData.get("pattern", null);
result.test_description = (String) inputMapData.get("test_description", null);
result.inputs = (List<MFInputArg>) inputMapData.get("inputs", null);
result.verify = (String) inputMapData.get("verify", null);

return result;
}

@Override
public ITestTypeOutputJson execute(ITestTypeInputJson inputJson) {
MessageFormatInputJson input = (MessageFormatInputJson) inputJson;

// partially construct output
MessageFormatOutputJson output = (MessageFormatOutputJson) getDefaultOutputJson();
output.label = input.label;

try {
String messageFormatResult = getFormattedMessage(input);
output.verify = messageFormatResult;
} catch (Exception e) {
output.error = e.getMessage();
output.error_message = e.getMessage();
return output;
}

// If we get here, it's a pass/fail result (supported options and no runtime errors/exceptions)
return output;
}

@Override
public ITestTypeOutputJson getDefaultOutputJson() {
return new MessageFormatOutputJson();
}

@Override
public IMap<String, Object> convertOutputToMap(ITestTypeOutputJson outputJson) {
MessageFormatOutputJson output = (MessageFormatOutputJson) outputJson;
return new io.lacuna.bifurcan.Map<String,Object>()
.put("label", output.label)
.put("verify", output.verify);
}

@Override
public String formatOutputJson(ITestTypeOutputJson outputJson) {
return ExecutorUtils.GSON.toJson((MessageFormatOutputJson) outputJson);
}

public String getFormattedMessage(MessageFormatInputJson input) {
final Locale locale = Locale.forLanguageTag(input.locale);
java.util.Map arguments = new HashMap<String,Object>();
for (MFInputArg arg : input.inputs) {
arguments.put(arg.name, arg.value);
}

MessageFormatter formatter = MessageFormatter.builder()
.setPattern(input.pattern)
.setLocale(locale)
.build();

return formatter.formatToString(arguments);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package org.unicode.conformance.messageformat2;

import static org.junit.Assert.assertEquals;

import com.ibm.icu.util.ULocale;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.unicode.conformance.testtype.messageformat2.MFInputArg;
import org.unicode.conformance.testtype.messageformat2.MFInputArgType;
import org.unicode.conformance.testtype.messageformat2.MFTestSubType;
import org.unicode.conformance.testtype.messageformat2.MessageFormatInputJson;
import org.unicode.conformance.testtype.messageformat2.MessageFormatTester;

public class MessageFormatterTest {

/**
* The default locale used for all of our tests. Used in @Before
*/
protected final static Locale defaultLocale = Locale.US;

/**
* The default time zone for all of our tests. Used in @Before
*/
protected final static TimeZone defaultTimeZone = TimeZone.getTimeZone("America/Los_Angeles");

private com.ibm.icu.util.TimeZone testStartDefaultIcuTz;

private java.util.TimeZone testStartDefaultJdkTz;

private com.ibm.icu.util.ULocale testStartDefaultULocale;

private java.util.Locale testStartDefaultLocale;

@Rule
public TestName name = new TestName();

// Copying test setup behavior from ICU4J CoreTestFmwk / TestFmwk, which
// ensures we pin the default locale and TZ during the test. ICU Formatters
// implicitly use the system's default locale and TZ.
@Before
public final void setup() {
// Just like TestFmwk initializes JDK TimeZone and Locale before every test,
// do the same for ICU TimeZone and ULocale.
ULocale.setDefault(ULocale.forLocale(defaultLocale));
com.ibm.icu.util.TimeZone.setDefault(
com.ibm.icu.util.TimeZone.getTimeZone(defaultTimeZone.getID()));

// Save starting timezones
testStartDefaultIcuTz = com.ibm.icu.util.TimeZone.getDefault();
testStartDefaultJdkTz = java.util.TimeZone.getDefault();

// Save starting locales
testStartDefaultULocale = com.ibm.icu.util.ULocale.getDefault();
testStartDefaultLocale = java.util.Locale.getDefault();
}

// Copying test teardown beahvior from ICU4J CoreTestFmwk, corresponding to
// the setup work.
@After
public final void teardown() {
String testMethodName = name.getMethodName();

// Assert that timezones are in a good state

com.ibm.icu.util.TimeZone testEndDefaultIcuTz = com.ibm.icu.util.TimeZone.getDefault();
java.util.TimeZone testEndDefaultJdkTz = java.util.TimeZone.getDefault();

assertEquals("In [" + testMethodName + "] Test should keep in sync ICU & JDK TZs",
testEndDefaultIcuTz.getID(),
testEndDefaultJdkTz.getID());

assertEquals("In [" + testMethodName + "] Test should reset ICU default TZ",
testStartDefaultIcuTz.getID(), testEndDefaultIcuTz.getID());
assertEquals("In [" + testMethodName + "] Test should reset JDK default TZ",
testStartDefaultJdkTz.getID(), testEndDefaultJdkTz.getID());

// Assert that locales are in a good state

com.ibm.icu.util.ULocale testEndDefaultULocale = com.ibm.icu.util.ULocale.getDefault();
java.util.Locale testEndDefaultLocale = java.util.Locale.getDefault();

assertEquals("In [" + testMethodName + "] Test should reset ICU ULocale",
testStartDefaultULocale.toLanguageTag(), testEndDefaultULocale.toLanguageTag());
assertEquals("In [" + testMethodName + "] Test should reset JDK Locale",
testStartDefaultLocale.toLanguageTag(), testEndDefaultLocale.toLanguageTag());
}

@Test
public void testGetFormattedMessage() {
// Setup
MessageFormatInputJson inputJson = new MessageFormatInputJson();
inputJson.test_type = "message_fmt2";
inputJson.label = "00001";
inputJson.test_subtype = MFTestSubType.formatter;
inputJson.locale = "en-GB";
inputJson.pattern = "{Hello {$name}, your card expires on {$exp :datetime skeleton=yMMMdE}!}";
inputJson.test_description = "Test using the ICU4J API doc example for the MessageFormatter class";
List<MFInputArg> inputs = new ArrayList<>();
MFInputArg nameArg = new MFInputArg();
nameArg.name = "name";
nameArg.argType = MFInputArgType.string;
nameArg.value = "John";
inputs.add(nameArg);
MFInputArg expArg = new MFInputArg();
expArg.name = "exp";
expArg.argType = MFInputArgType.datetime;
expArg.value = new Date(2023 - 1900, 2, 27, 19, 42, 51); // March 27, 2023, 7:42:51 PM
inputs.add(expArg);
inputJson.inputs = inputs;

// Actual
String formattedString = MessageFormatTester.INSTANCE.getFormattedMessage(inputJson);

// Expect & assert test
String expected = "Hello John, your card expires on Mon, 27 Mar 2023!";
assertEquals(expected, formattedString);

}

}
Loading