Skip to content

Commit

Permalink
Add ICU4J executor impl for collator (unicode-org#137)
Browse files Browse the repository at this point in the history
* Doc updates

* Add high level behavior of ICU4J executor

* Add interfaces to describe generic behavior per test type

* Add Collator specific concrete tester impl files

* Update Python Subprocess.run to run command string rather than array of command split parts
  • Loading branch information
echeran authored Dec 15, 2023
1 parent af71177 commit 20b10a0
Show file tree
Hide file tree
Showing 16 changed files with 658 additions and 38 deletions.
23 changes: 12 additions & 11 deletions executors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,34 @@ does not depend on the results of previous tests or stored states.

## Protocol for communication with testDriver

The testDriver program initiates an executor with a command line such as "nodejs
executor.js". TestDriver then sends information to the stdin of the running
The testDriver program initiates an executor with a command line such as `nodejs
executor.js`. TestDriver then sends information to the stdin of the running
executor program. These instructions are defined below.

A text executor accepts three kinds of input via the standard input (stdin):
* #VERSION: a command that requesting information about the executor’s
* `#VERSION` - a command that requesting information about the executor’s
configuration and version. This should be in JSON format. For example:
`{"cldrVersion":"41.0.0","icuVersion":"icu4x/2022-08-17/71.x","platform":"rust","platformVersion":"1.62.1"}'

* JSON-formatted information on each test to be performed
* #EXIT command - stop the execution and terminate the instance
* `#EXIT` - a command to stop the execution and terminate the instance

Empty lines should be ignored by the executor.

Output from an executor is strictly given through its STDOUT, not via
files. Returned values should be in JSON format.
files.

Returned values should be in JSON format.

### Errors and exceptions
Exceptions and errors may sometimes occur in the execution. An executor may return errors in two ways:
* The executor should return JSON data indicating all relevant parameters such

1. The executor should return JSON data indicating all relevant parameters such
as "label".

* When a error occurs, the executor will include a JSON key "error" with the
* When an error occurs, the executor will include a JSON key "error" with the
value describing the error condition.


* An executor may also return a line beginning with "#" or "!", followed by a
2. An executor may also return a line beginning with `#` or `!`, followed by a
warning or error message that should can be logged to stderr in the output of
testDriver.

Expand All @@ -65,7 +66,7 @@ framework under the testDriver.
Executors accept and parse data from stdin, implementing the protocol described
above.

* NodeJS: The file executor.js creates objects from classes of the [ECMAScript
* NodeJS: The file `executor.js` creates objects from classes of the [ECMAScript
Intl
object]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl). Routines
implement the following types of tests
Expand Down
70 changes: 65 additions & 5 deletions executors/icu4j/73/executor-icu4j/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,42 @@
<name>executor-icu4j</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!-- ICU4J 73 (library under test) -->
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>73.2</version>
</dependency>

<!-- JSON library -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>

<!-- Persistent Data Structure library -->
<dependency>
<groupId>io.lacuna</groupId>
<artifactId>bifurcan</artifactId>
<version>0.2.0-alpha4</version>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -70,13 +92,51 @@
<version>3.0.0</version>
</plugin>

<!-- JSON library -->
<!-- exec:exec build target enables running CLI commands -->
<plugin>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.1</version>
</plugin>

<!-- Create an all-in-one (statically linked) Jar file -->
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<!-- Maven Shade Plugin transformers don't work as expected, so -->
<!-- use jar plugin -->
<!-- https://stackoverflow.com/questions/30757739/no-main-manifest-attribute-in-jar -->
</plugin>
</plugins>
</pluginManagement>

<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<!-- Maven Shade Plugin transformers don't work as expected, so -->
<!-- use jar plugin -->
<!-- https://stackoverflow.com/questions/30757739/no-main-manifest-attribute-in-jar -->
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<transformers>
<transformer implementation=
"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>org.unicode.conformance.Icu4jExecutor</Main-Class>
<Build-Number>1.0</Build-Number>
</manifestEntries>
<!-- <mainClass>org.unicode.conformance.Icu4jExecutor</mainClass>-->
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.unicode.conformance;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.lacuna.bifurcan.IEntry;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ExecutorUtils {

public static Gson GSON = new Gson();

public static void printResponseString(String responseString) {
System.out.println(responseString);
}

public static io.lacuna.bifurcan.Map<String,String> parseInputLine(String inputLine) {
TypeToken<Map<String, String>> mapType = new TypeToken<Map<String, String>>(){};
Map<String,String> parsedInputJavaMap = ExecutorUtils.GSON.fromJson(inputLine, mapType);

io.lacuna.bifurcan.Map<String,String> parsedInputPersistentMap =
io.lacuna.bifurcan.Map.from(parsedInputJavaMap);

return parsedInputPersistentMap;
}

public static String formatAsJson(io.lacuna.bifurcan.IMap<String,String> mapData) {
java.util.Map<String,String> jMap = new HashMap<>();
for (Iterator<IEntry<String, String>> it = mapData.stream().iterator(); it.hasNext(); ) {
IEntry<String, String> entry = it.next();
jMap.put(entry.key(), entry.value());
}
return GSON.toJson(jMap);
}

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,145 @@
package org.unicode.conformance;

import com.google.gson.reflect.TypeToken;
import com.ibm.icu.impl.locale.XCldrStub.ImmutableMap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.unicode.conformance.testtype.ITestType;
import org.unicode.conformance.testtype.collator.CollatorTester;

/**
* Hello world!
*
*/
public class Icu4jExecutor
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
public class Icu4jExecutor {

public static final String PLATFORM = "ICU4J";
public static final String PLATFORM_VERSION = "73.2";
public static final String ICU_VERSION = "73";

public static final String CLDR_VERSION = "43";

/**
* Entry point for the executor.
*
* Run on an infinite loop until the input "#EXIT" is received.
*/
public static void main(String[] args) throws IOException {
try (InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr)) {
while (true) {
computeAndHandleResponse(br);
}
} catch (IOException ioe) {
String javaSetupErrorMsg = ioe.getMessage();
String executorErrorMsg = "! " + javaSetupErrorMsg;
ExecutorUtils.printResponseString(executorErrorMsg);

// exit with non-zero return code
throw ioe;
}
}

public static void computeAndHandleResponse(BufferedReader br) {
try {
String line = br.readLine();
String response = computeResponseString(line);
handleResponseString(response);
} catch (Exception e) {
// At this level, we assume the IOException is coming from BufferedReader.
// Any test case execution errors should be handled higher in the call stack (deeper in
// the code)
String javaErrorMsg = e.getMessage();
String executorErrorMsg = "! " + javaErrorMsg;
ExecutorUtils.printResponseString(executorErrorMsg);
}
}

/**
* Returns the string to be sent back to the testdriver caller, with the following cases:
*
* <ul>
* <li>For a test case input that was executed, return the JSON string of the result</li>
* <li>For empty input lines, return the empty string</li>
* <li>For end-of-input when <pre>#EXIT</pre> is sent in as input, return null</li>
* <li>For errors during test execution, return the error output string prefixed with
* <pre>#</pre></li>
* </ul>
*/
public static String computeResponseString(String inputLine) throws Exception {
if (inputLine.equals("#EXIT")) {
return null;
} else if (inputLine.trim().equals("")) {
return "";
} else if (inputLine.equals("#VERSION")) {
return getVersionResponse();
} else {
return getTestCaseResponse(inputLine);
}
}

public static String getVersionResponse() {
Map<String,String> versionMap = new HashMap<>();
versionMap.put("platform", PLATFORM);
versionMap.put("cldrVersion", CLDR_VERSION);
versionMap.put("icuVersion", ICU_VERSION);
versionMap.put("platformVersion", PLATFORM_VERSION);

String result = ExecutorUtils.GSON.toJson(versionMap);
return result;
}

public static String getTestCaseResponse(String inputLine) throws Exception {

io.lacuna.bifurcan.Map<String,String> parsedInputPersistentMap =
ExecutorUtils.parseInputLine(inputLine);

Optional<String> testTypeOpt = parsedInputPersistentMap.get("test_type");

if (!testTypeOpt.isPresent()) {
io.lacuna.bifurcan.IMap<String,String> response =
parsedInputPersistentMap
.put("error", "Error in input")
.put("error_msg", "Error in input found in executor before execution");

return ExecutorUtils.formatAsJson(response);
} else {
String testTypeStr = testTypeOpt.get();
ITestType testType;
if (testTypeStr.equals("collation_short")) {
testType = new CollatorTester();
} else {
io.lacuna.bifurcan.IMap<String,String> response =
parsedInputPersistentMap
.put("error", "Error in input")
.put("error_msg", "Error in input found in executor before execution");

return ExecutorUtils.formatAsJson(response);
}

return testType.getFinalOutputFromInput(parsedInputPersistentMap);
}
}

/**
* Perform behavior according to the executor spec at <code>REPO/executors/README.md</code>
* based on the output and associated semantics of <code>computeResponseString()</code>.
* @param responseString
*/
public static void handleResponseString(String responseString) {
if (responseString == null) {
System.exit(0);
}
if (responseString.equals("")) {
return; // empty input line, do nothing
}

// otherwise, response string carries test result
ExecutorUtils.printResponseString(responseString);
}

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

import org.unicode.conformance.ExecutorUtils;

public interface ITestType {

default io.lacuna.bifurcan.Map<String,String> parseInput(String inputLine) {
return ExecutorUtils.parseInputLine(inputLine);
}

ITestTypeInputJson inputMapToJson(io.lacuna.bifurcan.Map<String,String> inputMapData);

default ITestTypeInputJson parseInputJson(String inputLine) {
io.lacuna.bifurcan.Map<String,String> inputMapData =
parseInput(inputLine);
ITestTypeInputJson inputJson = inputMapToJson(inputMapData);

return inputJson;
}

ITestTypeOutputJson execute(ITestTypeInputJson inputJson);

String formatOutputJson(ITestTypeOutputJson outputJson);

default ITestTypeOutputJson getStructuredOutputFromInputStr(String inputLine) {
io.lacuna.bifurcan.Map<String,String> inputMapData = parseInput(inputLine);
return getStructuredOutputFromInput(inputMapData);
}

default ITestTypeOutputJson getStructuredOutputFromInput(io.lacuna.bifurcan.Map<String,String> inputMapData) {
ITestTypeInputJson inputJson = inputMapToJson(inputMapData);
ITestTypeOutputJson outputJson = execute(inputJson);
return outputJson;
}

default String getFinalOutputFromInput(io.lacuna.bifurcan.Map<String,String> inputMapData) throws Exception {
ITestTypeOutputJson outputJson = getStructuredOutputFromInput(inputMapData);
String formattedOutput = formatOutputJson(outputJson);
return formattedOutput;
}

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

public interface ITestTypeInputJson {

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

public interface ITestTypeOutputJson {

}
Loading

0 comments on commit 20b10a0

Please sign in to comment.