package jnpf.elnFormula;
|
|
import jnpf.util.JsonUtil;
|
import org.junit.Test;
|
|
import java.io.ByteArrayOutputStream;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.math.RoundingMode;
|
import java.nio.charset.StandardCharsets;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.fail;
|
|
public class FormulaSystemExamplesTest {
|
|
private final FormulaParser parser = new FormulaParser();
|
private final FormulaFunctionRegistry functions = new FormulaFunctionRegistry();
|
private final FormulaTypeChecker typeChecker = new FormulaTypeChecker(functions);
|
private final FormulaEvaluator evaluator = new FormulaEvaluator(functions);
|
|
@Test
|
public void evaluatesSystemExamplesAndStableFailures() throws Exception {
|
List<Example> examples = loadExamples();
|
assertEquals(25, examples.size());
|
|
for (Example example : examples) {
|
try {
|
FormulaAst.Node ast = parser.parse(example.expression);
|
Map<String, FormulaType> slots = slotTypes(example.slots);
|
typeChecker.check(ast, slots);
|
FormulaValue raw = evaluator.evaluate(ast, inputValues(example.inputs, slots));
|
FormulaValue rounded = example.roundScale == null ? raw
|
: evaluator.round(raw, example.roundScale,
|
RoundingMode.valueOf(example.roundMode));
|
if (example.errorCode != null) {
|
fail(example.name + " expected error " + example.errorCode);
|
}
|
assertEquals(example.name, example.expectedRaw, raw.toPlainString());
|
assertEquals(example.name, example.expectedRounded == null
|
? example.expectedRaw : example.expectedRounded, rounded.toPlainString());
|
} catch (FormulaParser.ParseException ex) {
|
assertError(example, ex.getCode());
|
} catch (FormulaException ex) {
|
assertError(example, ex.getCode());
|
}
|
}
|
}
|
|
@Test
|
public void exposesStableFunctionCatalog() {
|
assertEquals(asList("ABS", "POW", "SQRT", "MIN", "MAX", "IF", "COALESCE", "IS_BLANK",
|
"SUM", "MEAN", "STDDEV_S", "RSD", "ROUND", "ROUND_UP", "ROUND_DOWN",
|
"CONVERT", "LOOKUP_EXACT", "LOOKUP_LINEAR",
|
"LN", "LOG10", "LOG", "EXP", "NTH_ROOT",
|
"COUNT", "MEDIAN", "STDDEV_P", "VAR_S", "VAR_P", "GEOMEAN",
|
"WEIGHTED_MEAN", "SLOPE", "INTERCEPT", "CORREL", "LINEAR_PREDICT",
|
"ROUND_HALF_EVEN", "ROUND_SIG", "ROUND_SIG_HALF_EVEN"),
|
functions.codes());
|
for (FormulaFunction function : functions.catalog()) {
|
assertNotNull(function.getChineseName());
|
assertNotNull(function.getCategory());
|
assertNotNull(function.getSignature());
|
assertNotNull(function.getDescription());
|
assertNotNull(function.getExample());
|
}
|
}
|
|
private static void assertError(Example example, String actualCode) {
|
if (example.errorCode == null) {
|
fail(example.name + " unexpected error " + actualCode);
|
}
|
assertEquals(example.name, example.errorCode, actualCode);
|
}
|
|
private static Map<String, FormulaType> slotTypes(Map<String, String> values) {
|
Map<String, FormulaType> result = new LinkedHashMap<>();
|
for (Map.Entry<String, String> entry : values.entrySet()) {
|
result.put(entry.getKey(), FormulaType.valueOf(entry.getValue()));
|
}
|
return result;
|
}
|
|
private static Map<String, FormulaValue> inputValues(Map<String, Object> values,
|
Map<String, FormulaType> slots) {
|
Map<String, FormulaValue> result = new LinkedHashMap<>();
|
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
FormulaType type = slots.get(entry.getKey());
|
Object value = entry.getValue();
|
if (value == null) {
|
result.put(entry.getKey(), FormulaValue.nullValue());
|
} else if (type == FormulaType.DECIMAL) {
|
result.put(entry.getKey(), FormulaValue.decimal(value.toString()));
|
} else if (type == FormulaType.DECIMAL_LIST) {
|
List<FormulaValue> items = new ArrayList<>();
|
for (Object item : (List<?>) value) {
|
items.add(FormulaValue.decimal(item.toString()));
|
}
|
result.put(entry.getKey(), FormulaValue.decimalList(items));
|
} else if (type == FormulaType.BOOLEAN) {
|
result.put(entry.getKey(), FormulaValue.bool(Boolean.parseBoolean(value.toString())));
|
} else {
|
result.put(entry.getKey(), FormulaValue.text(value.toString()));
|
}
|
}
|
return result;
|
}
|
|
private static List<Example> loadExamples() throws IOException {
|
InputStream input = FormulaSystemExamplesTest.class.getResourceAsStream(
|
"/formula/system-examples.json");
|
assertNotNull(input);
|
try {
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
byte[] buffer = new byte[4096];
|
int read;
|
while ((read = input.read(buffer)) >= 0) {
|
output.write(buffer, 0, read);
|
}
|
return JsonUtil.getJsonToList(new String(output.toByteArray(), StandardCharsets.UTF_8),
|
Example.class);
|
} finally {
|
input.close();
|
}
|
}
|
|
private static List<String> asList(String... values) {
|
List<String> result = new ArrayList<>();
|
Collections.addAll(result, values);
|
return result;
|
}
|
|
public static class Example {
|
public String name;
|
public String expression;
|
public Map<String, String> slots = new LinkedHashMap<>();
|
public Map<String, Object> inputs = new LinkedHashMap<>();
|
public Integer roundScale;
|
public String roundMode;
|
public String expectedRaw;
|
public String expectedRounded;
|
public String errorCode;
|
}
|
}
|