Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.

[SDK][Recognizers-Text] Add Recognizer-Text unit tests into the temporal Recognizer-Text folder in bot-dialogs library #1168

  •  
  •  
  •  
11 changes: 11 additions & 0 deletions libraries/bot-dialogs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,15 @@
</plugins>
</reporting>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
package com.microsoft.recognizers.text.tests;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.recognizers.text.*;
import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult;
import com.microsoft.recognizers.text.tests.helpers.DateTimeParseResultMixIn;
import com.microsoft.recognizers.text.tests.helpers.ExtendedModelResultMixIn;
import com.microsoft.recognizers.text.tests.helpers.ExtractResultMixIn;
import com.microsoft.recognizers.text.tests.helpers.ModelResultMixIn;
import org.apache.commons.io.FileUtils;
import org.javatuples.Pair;
import org.junit.*;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@RunWith(Parameterized.class)
public abstract class AbstractTest {

private static final String SpecsPath = "Specs/..";

private static final List<String> SupportedCultures = Arrays.asList("English", "Spanish", "Portuguese", "French", "German", "Chinese");

// FEFF - UTF-8 byte order mark (EF BB BF) as Unicode char representation.
private static final String UTF8_BOM = "\uFEFF";

protected final TestCase currentCase;

public AbstractTest(TestCase currentCase) {
this.currentCase = currentCase;
}

private static Map<String, Integer> testCounter;
private static Map<String, Integer> passCounter;
private static Map<String, Integer> failCounter;
private static Map<String, Integer> skipCounter;

@BeforeClass
public static void before() {
testCounter = new LinkedHashMap<>();
passCounter = new LinkedHashMap<>();
failCounter = new LinkedHashMap<>();
skipCounter = new LinkedHashMap<>();
}

@AfterClass
public static void after() {

Map<String, String> counter = new LinkedHashMap<>();

for (Map.Entry<String, Integer> entry : testCounter.entrySet()) {
int skipped = skipCounter.getOrDefault(entry.getKey(), 0);
if (entry.getValue() > skipped) {
counter.put(entry.getKey(), String.format("%7d", entry.getValue()));
}
}

for (Map.Entry<String, String> entry : counter.entrySet()) {
Integer passValue = passCounter.getOrDefault(entry.getKey(), 0);
Integer failValue = failCounter.getOrDefault(entry.getKey(), 0);
Integer skipValue = skipCounter.getOrDefault(entry.getKey(), 0);
counter.put(entry.getKey(), String.format("|%s |%7d |%7d |%7d ", entry.getValue(), passValue, skipValue, failValue));
}

print(counter);
}

private static void print(Map<String, String> map) {
System.out.println("| TOTAL | Passed | Skipped | Failed || Key");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getValue() + "|| " + entry.getKey());
}
}

private void count(TestCase testCase) {
String key = testCase.recognizerName + "-" + testCase.language + "-" + testCase.modelName;
Integer current = testCounter.getOrDefault(key, 0);
testCounter.put(key, current + 1);
}

private void countPass(TestCase testCase) {
String key = testCase.recognizerName + "-" + testCase.language + "-" + testCase.modelName;
Integer current = passCounter.getOrDefault(key, 0);
passCounter.put(key, current + 1);
}

private void countSkip(TestCase testCase) {
String key = testCase.recognizerName + "-" + testCase.language + "-" + testCase.modelName;
Integer current = skipCounter.getOrDefault(key, 0);
skipCounter.put(key, current + 1);
}

private void countFail(TestCase testCase) {
String key = testCase.recognizerName + "-" + testCase.language + "-" + testCase.modelName;
Integer current = failCounter.getOrDefault(key, 0);
failCounter.put(key, current + 1);
}

@Test
public void test() {

count(currentCase);

if (!isJavaSupported(this.currentCase.notSupported)) {
countSkip(currentCase);
throw new AssumptionViolatedException("Test case wih input '" + this.currentCase.input + "' not supported.");
}

if (this.currentCase.debug) {
// Add breakpoint here to stop on those TestCases marked with "Debug": true
System.out.println("Debug Break!");
}

try {
recognizeAndAssert(currentCase);
countPass(this.currentCase);
} catch (AssumptionViolatedException ex) {
countSkip(currentCase);
throw ex;
} catch (Throwable err) {
countFail(currentCase);
throw err;
}
}

// TODO Override in specific models
protected abstract List<ModelResult> recognize(TestCase currentCase);

protected void recognizeAndAssert(TestCase currentCase) {
List<ModelResult> results = recognize(currentCase);
assertResults(currentCase, results);
}

public static void assertResults(TestCase currentCase, List<ModelResult> results) {
assertResultsWithKeys(currentCase, results, Collections.emptyList());
}

public static void assertResultsWithKeys(TestCase currentCase, List<ModelResult> results, List<String> testResolutionKeys) {

List<ModelResult> expectedResults = readExpectedResults(ModelResult.class, currentCase.results);
Assert.assertEquals(getMessage(currentCase, "\"Result Count\""), expectedResults.size(), results.size());

IntStream.range(0, expectedResults.size())
.mapToObj(i -> Pair.with(expectedResults.get(i), results.get(i)))
.forEach(t -> {
ModelResult expected = t.getValue0();
ModelResult actual = t.getValue1();

Assert.assertEquals(getMessage(currentCase, "typeName"), expected.typeName, actual.typeName);
Assert.assertEquals(getMessage(currentCase, "text"), expected.text, actual.text);

if (expected.resolution.containsKey(ResolutionKey.Value)) {
Assert.assertEquals(getMessage(currentCase, "resolution.value"),
expected.resolution.get(ResolutionKey.Value), actual.resolution.get(ResolutionKey.Value));
}

for (String key : testResolutionKeys) {
Assert.assertEquals(getMessage(currentCase, key), expected.resolution.get(key), actual.resolution.get(key));
}
});
}

public static Collection<TestCase> enumerateTestCases(String recognizerType, String modelName) {

String recognizerTypePath = String.format(File.separator + recognizerType + File.separator);

// Deserializer
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);

// Map json to TestCases
return FileUtils.listFiles(new File(SpecsPath), new String[]{"json"}, true)
.stream().filter(f -> f.getPath().contains(recognizerTypePath))
.map(f -> parseSpecFile(f, mapper))
.flatMap(ts -> Arrays.stream(ts))
// Ignore tests with NotSupportedByDesign = Java
.filter(ts -> isJavaSupported(ts.notSupportedByDesign))
// Filter supported languages only
.filter(ts -> SupportedCultures.contains(ts.language))
.filter(ts -> ts.modelName.contains(modelName))
.collect(Collectors.toCollection(ArrayList::new));
}

public static TestCase[] parseSpecFile(File f, ObjectMapper mapper) {

List<String> paths = Arrays.asList(f.toPath().toString().split(Pattern.quote(File.separator)));
List<String> testInfo = paths.subList(paths.size() - 3, paths.size());

try {

// Workaround to consume a possible UTF-8 BOM byte
// https://stackoverflow.com/questions/4897876/reading-utf-8-bom-marker
String contents = new String(Files.readAllBytes(f.toPath()));
String json = StringUtf8Bom(contents);

TestCase[] tests = mapper.readValue(json, TestCase[].class);
Arrays.stream(tests).forEach(t -> {
t.recognizerName = testInfo.get(0);
t.language = testInfo.get(1);
t.modelName = testInfo.get(2).split(Pattern.quote("."))[0];
});

return tests;

} catch (IOException ex) {

System.out.println("Error reading Spec file: " + f.toString() + " | " + ex.getMessage());

// @TODO: This should cause a test run failure.
return new TestCase[0];
}
}

public static <T extends ExtractResult> T parseExtractResult(Class<T> extractorResultClass, Object result) {
// Deserializer
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
mapper.addMixIn(ExtractResult.class, ExtractResultMixIn.class);

try {
String json = mapper.writeValueAsString(result);
return mapper.readValue(json, extractorResultClass);

} catch (JsonProcessingException e) {
e.printStackTrace();
return null;

} catch (IOException e) {
e.printStackTrace();
return null;
}
}

public static <T extends DateTimeParseResult> T parseDateTimeParseResult(Class<T> dateTimeParseResultClass, Object result) {
// Deserializer
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
mapper.addMixIn(DateTimeParseResult.class, DateTimeParseResultMixIn.class);

try {
String json = mapper.writeValueAsString(result);
return mapper.readValue(json, dateTimeParseResultClass);

} catch (JsonProcessingException e) {
e.printStackTrace();
return null;

} catch (IOException e) {
e.printStackTrace();
return null;
}
}

public static <T extends ModelResult> T parseResult(Class<T> modelResultClass, Object result) {
// Deserializer
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
mapper.addMixIn(ModelResult.class, ModelResultMixIn.class);
mapper.addMixIn(ExtendedModelResult.class, ExtendedModelResultMixIn.class);

try {
String json = mapper.writeValueAsString(result);
return mapper.readValue(json, modelResultClass);

} catch (JsonProcessingException e) {
e.printStackTrace();
return null;

} catch (IOException e) {
e.printStackTrace();
return null;
}
}

public static <T extends ModelResult> List<T> readExpectedResults(Class<T> modelResultClass, List<Object> results) {
return results.stream().map(r -> parseResult(modelResultClass, r))
.collect(Collectors.toCollection(ArrayList::new));
}

public static <T extends ExtractResult> List<T> readExpectedExtractResults(Class<T> extractorResultClass, List<Object> results) {
return results.stream().map(r -> parseExtractResult(extractorResultClass, r))
.collect(Collectors.toCollection(ArrayList::new));
}

public static <T extends DateTimeParseResult> List<T> readExpectedDateTimeParseResults(Class<T> dateTimeParseResultClass, List<Object> results) {
return results.stream().map(r -> parseDateTimeParseResult(dateTimeParseResultClass, r))
.collect(Collectors.toCollection(ArrayList::new));
}

public static String getCultureCode(String language) {
return Arrays.stream(Culture.SupportedCultures)
.filter(c -> c.cultureName.equalsIgnoreCase(language))
.findFirst().get().cultureCode;
}

public static boolean isJavaSupported(String notSupported) {
// definition for "not supported" missing, should be supported then
if (notSupported == null) return true;

return !Arrays.asList(notSupported.toLowerCase().trim().split("\\s*,\\s*")).contains("java");
}

public static String getMessage(TestCase testCase, String propName) {
return "Does not match " + propName + " on Input: \"" + testCase.input + "\"";
}

private static String StringUtf8Bom(String input) {

if (input.startsWith(UTF8_BOM)) {
input = input.substring(1);
}

return input;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.microsoft.recognizers.text.tests;

public class DependencyConstants {

public static final String BASE_RECOGNIZERS_MODEL_UNAVAILABLE = "could not find model with the specified configuration";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.microsoft.recognizers.text.tests;

public enum Models {
Number,
NumberPercentMode,
Ordinal,
Percent,
PercentPercentMode,
NumberRange,
CustomNumber,
Age,
Currency,
Dimension,
Temperature,
DateTime,
DateTimeSplitDateAndTime,
DateTimeCalendarMode,
DateTimeExtendedTypes,
DateTimeComplexCalendar,
PhoneNumber,
IpAddress,
Mention,
Hashtag,
Email,
URL,
Boolean,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.microsoft.recognizers.text.tests;

// This Exception represents a extractor/parser/model not yet implemented in Java
public class NotSupportedException extends Exception {

public NotSupportedException(String message) {
super(message);
}

public NotSupportedException(String message, Exception ex) {
super(message, ex);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.microsoft.recognizers.text.tests;

public enum Platform {
dotNet,
javascript,
python,
java
}
Loading