EQL: Integrate TOML tests for function folding (#58748) (#58812)

Co-authored-by: Ross Wolf <31489089+rw-access@users.noreply.github.com>
(cherry picked from commit e9b1fa58cf8d510a4b4afb14f66b0d5f9c603ebb)
This commit is contained in:
Andrei Stefan 2020-07-01 13:50:54 +03:00 committed by GitHub
parent 2638809cba
commit 470bcee5bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1489 additions and 17 deletions

View File

@ -44,6 +44,8 @@ dependencies {
testImplementation project(path: ':modules:analysis-common')
testImplementation project(path: ':modules:transport-netty4') // for http in RestEqlCancellationIT
testImplementation project(path: ':plugins:transport-nio') // for http in RestEqlCancellationIT
testImplementation 'io.ous:jtoml:2.0.0'
}

View File

@ -27,26 +27,23 @@ public final class EqlTestUtils {
private EqlTestUtils() {
}
public static final EqlConfiguration TEST_CFG = new EqlConfiguration(new String[]{"none"},
public static final EqlConfiguration TEST_CFG_CASE_INSENSITIVE = new EqlConfiguration(new String[] {"none"},
org.elasticsearch.xpack.ql.util.DateUtils.UTC, "nobody", "cluster", null, TimeValue.timeValueSeconds(30), -1, false, false, "",
new TaskId(randomAlphaOfLength(10), randomNonNegativeLong()), randomTask());
new TaskId("test", 123), null);
public static final EqlConfiguration TEST_CFG_CASE_SENSITIVE = new EqlConfiguration(new String[] {"none"},
org.elasticsearch.xpack.ql.util.DateUtils.UTC, "nobody", "cluster", null, TimeValue.timeValueSeconds(30), -1, false, true, "",
new TaskId("test", 123), null);
public static EqlConfiguration randomConfiguration() {
return new EqlConfiguration(new String[]{randomAlphaOfLength(16)},
randomZone(),
randomAlphaOfLength(16),
randomAlphaOfLength(16),
null,
new TimeValue(randomNonNegativeLong()),
randomIntBetween(5, 100),
randomBoolean(),
randomBoolean(),
randomAlphaOfLength(16),
new TaskId(randomAlphaOfLength(10), randomNonNegativeLong()),
randomTask());
return internalRandomConfiguration(randomBoolean());
}
public static EqlConfiguration randomConfigurationWithCaseSensitive(boolean isCaseSensitive) {
return internalRandomConfiguration(isCaseSensitive);
}
private static EqlConfiguration internalRandomConfiguration(boolean isCaseSensitive) {
return new EqlConfiguration(new String[]{randomAlphaOfLength(16)},
randomZone(),
randomAlphaOfLength(16),

View File

@ -36,7 +36,7 @@ public class VerifierTests extends ESTestCase {
private LogicalPlan accept(IndexResolution resolution, String eql) {
PreAnalyzer preAnalyzer = new PreAnalyzer();
Analyzer analyzer = new Analyzer(EqlTestUtils.TEST_CFG, new EqlFunctionRegistry(), new Verifier());
Analyzer analyzer = new Analyzer(EqlTestUtils.TEST_CFG_CASE_INSENSITIVE, new EqlFunctionRegistry(), new Verifier());
return analyzer.analyze(preAnalyzer.preAnalyze(parser.createStatement(eql), resolution));
}

View File

@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.optimizer;
import org.elasticsearch.common.Strings;
import java.util.Objects;
public class EqlFoldSpec {
private final String name;
private final String description;
private final String expression;
private final Object expected;
// flag to dictate which modes are supported for the test
// null -> apply the test to both modes (case sensitive and case insensitive)
// TRUE -> case sensitive
// FALSE -> case insensitive
private Boolean caseSensitive = null;
EqlFoldSpec(String name, String description, Boolean caseSensitive, String expression, Object expected) {
this.name = name;
this.description = description;
this.caseSensitive = caseSensitive;
this.expression = expression;
this.expected = expected;
}
public String expression() {
return expression;
}
public Object expected() {
return expected;
}
public Boolean caseSensitive() {
return caseSensitive;
}
public String toString() {
StringBuilder sb = new StringBuilder();
appendWithComma(sb, "name", name);
appendWithComma(sb, "expression", expression);
appendWithComma(sb, "case_sensitive", caseSensitive == null ? "null" : caseSensitive);
appendWithComma(sb, "expected", expected == null ? "null" : expected);
return sb.toString();
}
private static void appendWithComma(StringBuilder builder, String key, Object value) {
if (value != null) {
String valueStr = value.toString();
if (Strings.isEmpty(valueStr) == false) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(key);
builder.append(": ");
builder.append(valueStr);
}
}
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
EqlFoldSpec that = (EqlFoldSpec) other;
return Objects.equals(this.expression, that.expression)
&& Objects.equals(this.caseSensitive, that.caseSensitive);
}
@Override
public int hashCode() {
return Objects.hash(this.expression, this.caseSensitive);
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.optimizer;
import io.ous.jtoml.JToml;
import io.ous.jtoml.Toml;
import io.ous.jtoml.TomlTable;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class EqlFoldSpecLoader {
public static List<EqlFoldSpec> load(String path) throws Exception {
try (InputStream is = EqlFoldSpecLoader.class.getResourceAsStream(path)) {
return readFromStream(is);
}
}
private static String getTrimmedString(TomlTable table, String key) {
String s = table.getString(key);
if (s != null) {
return s.trim();
}
return null;
}
private static List<EqlFoldSpec> readFromStream(InputStream is) throws Exception {
List<EqlFoldSpec> testSpecs = new ArrayList<>();
Toml toml = JToml.parse(is);
for (String name : toml.keySet()) {
TomlTable table = toml.getTomlTable(name);
TomlTable fold = table.getTomlTable("fold");
String description = getTrimmedString(table, "description");
Boolean cs = null;
Boolean caseSensitive = table.getBoolean("case_sensitive");
Boolean caseInsensitive = table.getBoolean("case_insensitive");
// if case_sensitive is TRUE and case_insensitive is not TRUE (FALSE or NULL), then the test is case sensitive only
if (Boolean.TRUE.equals(caseSensitive)) {
if (Boolean.FALSE.equals(caseInsensitive) || caseInsensitive == null) {
cs = true;
}
}
// if case_sensitive is not TRUE (FALSE or NULL) and case_insensitive is TRUE, then the test is case insensitive only
else if (Boolean.TRUE.equals(caseInsensitive)) {
cs = false;
}
// in all other cases, the test should run no matter the case sensitivity (should test both scenarios)
if (fold != null) {
List<TomlTable> tests = fold.getArrayTable("tests");
for (TomlTable test : tests) {
String expression = getTrimmedString(test, "expression");
Object expected = test.get("expected");
EqlFoldSpec spec = new EqlFoldSpec(name, description, cs, expression, expected);
testSpecs.add(spec);
}
}
}
return testSpecs;
}
}

View File

@ -51,7 +51,7 @@ import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.elasticsearch.xpack.eql.EqlTestUtils.TEST_CFG;
import static org.elasticsearch.xpack.eql.EqlTestUtils.TEST_CFG_CASE_INSENSITIVE;
import static org.elasticsearch.xpack.ql.tree.Source.EMPTY;
public class OptimizerTests extends ESTestCase {
@ -72,7 +72,7 @@ public class OptimizerTests extends ESTestCase {
private LogicalPlan accept(IndexResolution resolution, String eql) {
PreAnalyzer preAnalyzer = new PreAnalyzer();
Analyzer analyzer = new Analyzer(TEST_CFG, new EqlFunctionRegistry(), new Verifier());
Analyzer analyzer = new Analyzer(TEST_CFG_CASE_INSENSITIVE, new EqlFunctionRegistry(), new Verifier());
return optimizer.optimize(analyzer.analyze(preAnalyzer.preAnalyze(parser.createStatement(eql), resolution)));
}

View File

@ -0,0 +1,117 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.optimizer;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.eql.analysis.Analyzer;
import org.elasticsearch.xpack.eql.analysis.Verifier;
import org.elasticsearch.xpack.eql.expression.function.EqlFunctionRegistry;
import org.elasticsearch.xpack.eql.parser.EqlParser;
import org.elasticsearch.xpack.eql.plan.physical.LocalRelation;
import org.elasticsearch.xpack.ql.expression.Alias;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.ql.plan.logical.Project;
import org.elasticsearch.xpack.ql.tree.Source;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.elasticsearch.xpack.eql.EqlTestUtils.TEST_CFG_CASE_INSENSITIVE;
import static org.elasticsearch.xpack.eql.EqlTestUtils.TEST_CFG_CASE_SENSITIVE;
import static org.elasticsearch.xpack.ql.tree.Source.EMPTY;
public class TomlFoldTests extends ESTestCase {
protected static final String PARAM_FORMATTING = "%1$s.test -> %2$s";
private static EqlParser parser = new EqlParser();
private static final EqlFunctionRegistry functionRegistry = new EqlFunctionRegistry();
private static Verifier verifier = new Verifier();
private static Analyzer caseSensitiveAnalyzer = new Analyzer(TEST_CFG_CASE_SENSITIVE, functionRegistry, verifier);
private static Analyzer caseInsensitiveAnalyzer = new Analyzer(TEST_CFG_CASE_INSENSITIVE, functionRegistry, verifier);
private final int num;
private final EqlFoldSpec spec;
public TomlFoldTests(int num, EqlFoldSpec spec) {
this.num = num;
this.spec = spec;
}
@ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING)
public static List<Object[]> readTestSpecs() throws Exception {
List<EqlFoldSpec> foldSpecs = EqlFoldSpecLoader.load("/test_folding.toml");
foldSpecs.addAll(EqlFoldSpecLoader.load("/test_string_functions.toml"));
List<EqlFoldSpec> unsupportedSpecs = EqlFoldSpecLoader.load("/test_unsupported.toml");
HashSet<EqlFoldSpec> filteredSpecs = new HashSet<>(foldSpecs);
filteredSpecs.removeAll(unsupportedSpecs);
return asArray(filteredSpecs);
}
private static List<Object[]> asArray(Collection<EqlFoldSpec> specs) {
AtomicInteger counter = new AtomicInteger();
return specs.stream().map(spec -> new Object[] {
counter.incrementAndGet(), spec
}).collect(toList());
}
public void test() {
// run both tests if case sensitivity doesn't matter
if (spec.caseSensitive() == null) {
testCaseSensitive(spec);
testCaseInsensitive(spec);
}
// run only the case sensitive test
else if (spec.caseSensitive()) {
testCaseSensitive(spec);
}
// run only the case insensitive test
else {
testCaseInsensitive(spec);
}
}
private void testCaseSensitive(EqlFoldSpec spec) {
testWithAnalyzer(caseSensitiveAnalyzer, spec);
}
private void testCaseInsensitive(EqlFoldSpec spec) {
testWithAnalyzer(caseInsensitiveAnalyzer, spec);
}
private void testWithAnalyzer(Analyzer analyzer, EqlFoldSpec spec) {
Expression expr = parser.createExpression(spec.expression());
LogicalPlan logicalPlan = new Project(EMPTY, new LocalRelation(EMPTY, emptyList()),
singletonList(new Alias(Source.EMPTY, "test", expr)));
LogicalPlan analyzed = analyzer.analyze(logicalPlan);
assertTrue(analyzed instanceof Project);
List<?> projections = ((Project) analyzed).projections();
assertEquals(1, projections.size());
assertTrue(projections.get(0) instanceof Alias);
Alias a = (Alias) projections.get(0);
assertTrue(a.child().foldable());
Object folded = a.child().fold();
// upgrade to a long, because the parser typically downgrades Long -> Integer when possible
if (folded instanceof Integer) {
folded = ((Integer) folded).longValue();
}
assertEquals(spec.expected(), folded);
}
}

View File

@ -0,0 +1,521 @@
[arithmetic]
description = "Test that math integer and float operations fold as expected."
[[arithmetic.fold.tests]]
expression = "7 + 3"
expected = 10
[[arithmetic.fold.tests]]
expression = "7 + -3"
expected = 4
[[arithmetic.fold.tests]]
expression = "7 - 3"
expected = 4
[[arithmetic.fold.tests]]
expression = "7 - -3"
expected = 10
[[arithmetic.fold.tests]]
expression = "7 * 3"
expected = 21
[[arithmetic.fold.tests]]
expression = "7 * 3.0"
expected = 21.0
[[arithmetic.fold.tests]]
expression = "-7 * 3.0"
expected = -21.0
[[arithmetic.fold.tests]]
expression = "7 * -3.0"
expected = -21.0
[[arithmetic.fold.tests]]
expression = "7.0 * 3"
expected = 21.0
[[arithmetic.fold.tests]]
expression = "7.0 * 3.0"
expected = 21.0
[[arithmetic.fold.tests]]
expression = "7 / 3"
expected = 2
[[arithmetic.fold.tests]]
expression = "7 / 3.0"
expected = 2.33333333333333333
[[arithmetic.fold.tests]]
expression = "7 / 3"
expected = 2
[[arithmetic.fold.tests]]
expression = "7.0 / 3.0"
expected = 2.33333333333333333
[[arithmetic.fold.tests]]
expression = "7 % 3"
expected = 1
[[arithmetic.fold.tests]]
expression = "3 + 4 - 5 * 6 / 7"
expected = 3
[[arithmetic.fold.tests]]
expression = "((3 + 4) - 5) * 6 / 7"
expected = 1
[integer_comparison]
description = "Check that numeric comparisons are folded."
[[integer_comparison.fold.tests]]
expression = "-3 < 4"
expected = true
[[integer_comparison.fold.tests]]
expression = "3 <= 4"
expected = true
[[integer_comparison.fold.tests]]
expression = "3 != 4"
expected = true
[[integer_comparison.fold.tests]]
expression = "3 == 4"
expected = false
[[integer_comparison.fold.tests]]
expression = "3 >= 4"
expected = false
[[integer_comparison.fold.tests]]
expression = "3 > 4"
expected = false
[float_comparison]
description = "Check that numeric comparisons are folded."
[[float_comparison.fold.tests]]
expression = "3.0 < 4.0"
expected = true
[[float_comparison.fold.tests]]
expression = "3.0 <= 4.0"
expected = true
[[float_comparison.fold.tests]]
expression = "-3.0 != 4.0"
expected = true
[[float_comparison.fold.tests]]
expression = "-3.0 == 4.0"
expected = false
[[float_comparison.fold.tests]]
expression = "3.0 >= 4.0"
expected = false
[[float_comparison.fold.tests]]
expression = "3.0 > 4.0"
expected = false
[int_float_comparison]
description = "Check that numeric comparisons are folded."
[[int_float_comparison.fold.tests]]
expression = "-3 < 4.0"
expected = true
[[int_float_comparison.fold.tests]]
expression = "-3 <= 4.0"
expected = true
[[int_float_comparison.fold.tests]]
expression = "3 != 4.0"
expected = true
[[int_float_comparison.fold.tests]]
expression = "3 == 4.0"
expected = false
[[int_float_comparison.fold.tests]]
expression = "3 >= 4.0"
expected = false
[[int_float_comparison.fold.tests]]
expression = "3 > 4.0"
expected = false
[float_int_comparison]
description = "Check that numeric comparisons are folded."
[[float_int_comparison.fold.tests]]
expression = "3.0 < 4"
expected = true
[[float_int_comparison.fold.tests]]
expression = "3.0 <= 4"
expected = true
[[float_int_comparison.fold.tests]]
expression = "3.0 != 4"
expected = true
[[float_int_comparison.fold.tests]]
expression = "3.0 == 4"
expected = false
[[float_int_comparison.fold.tests]]
expression = "-3.0 >= 4"
expected = false
[[float_int_comparison.fold.tests]]
expression = "-3.0 > 4"
expected = false
[string_case_match_comparison]
description = "Fold string comparisons"
[[string_case_match_comparison.fold.tests]]
expression = '"Foo" == "Foo"'
expected = true
[[string_case_match_comparison.fold.tests]]
expression = '"Foo" != "Foo"'
expected = false
[[string_case_match_comparison.fold.tests]]
expression = '"Foo" == "Bar"'
expected = false
[[string_case_match_comparison.fold.tests]]
expression = '"Foo" != "Bar"'
expected = true
[[string_case_match_comparison.fold.tests]]
expression = '"Foo" <= "Bar"'
expected = false
[[string_case_match_comparison.fold.tests]]
expression = '"Foo" > "Bar"'
expected = true
[string_case_sensitive_match_comparison]
description = "Fold string comparisons"
case_sensitive = true
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"Foo" == "foo"'
expected = false
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"Foo" != "foo"'
expected = true
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"Foo" < "foo"'
expected = true
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"Foo" <= "foo"'
expected = true
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"Foo" >= "foo"'
expected = false
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"Foo" > "foo"'
expected = false
# now flip the order and check
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"foo" < "Foo"'
expected = false
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"foo" <= "Foo"'
expected = false
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"foo" >= "Foo"'
expected = true
[[string_case_sensitive_match_comparison.fold.tests]]
expression = '"foo" > "Foo"'
expected = true
[string_case_insensitive_match_comparison]
description = "Fold string comparisons"
case_insensitive = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" == "Bar"'
expected = false
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" != "Bar"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" == "foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" != "foo"'
expected = false
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" < "foo"'
expected = false
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" <= "foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" >= "foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" > "foo"'
expected = false
# now flip the order and check
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"foo" < "Foo"'
expected = false
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"foo" <= "Foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"foo" >= "Foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"foo" > "Foo"'
expected = false
[arithmetic_comparison]
description = "Fold arithmetic expressions cast as booleans"
[[arithmetic_comparison.fold.tests]]
expression = '(1 * 2 + 3 * 4 + 10 / 2) == 19'
expected = true
[[arithmetic_comparison.fold.tests]]
expression = '(1 * 2 + 3 * 4 + 10 / 2) == (2 + 12 + 5)'
expected = true
[[arithmetic_comparison.fold.tests]]
expression = '1 * 2 + 3 * 4 + 10 / 2 == 2 + 12 + 5'
expected = true
[in_set]
description = "Fold constant check in set"
[[in_set.fold.tests]]
expression = "345 in (123, 345)"
expected = true
[[in_set.fold.tests]]
expression = "345 not in (123, 456)"
expected = true
[[in_set.fold.tests]]
expression = "345 in (123, 456)"
expected = false
[[in_set.fold.tests]]
expression = "'foo' not in ('foo', 'bar', 'baz')"
expected = false
[[in_set.fold.tests]]
expression = "'foo' in ('foo', 'bar', 'baz')"
expected = true
[[in_set.fold.tests]]
expression = "'foo' in ('bar', 'baz')"
expected = false
[or]
description = "Check that three-value boolean logic works for `or`."
[[or.fold.tests]]
expression = "true or false"
expected = true
[[or.fold.tests]]
expression = "true or null"
expected = true
[[or.fold.tests]]
expression = "true or true"
expected = true
[[or.fold.tests]]
expression = "false or null"
[[or.fold.tests]]
expression = "false or false"
expected = false
[[or.fold.tests]]
expression = "false or true"
expected = true
[[or.fold.tests]]
expression = "null or false"
[[or.fold.tests]]
expression = "null or null"
[[or.fold.tests]]
expression = "null or true"
expected = true
[and]
description = "Check that three-value boolean logic works for `and`."
[[and.fold.tests]]
expression = "true and false"
expected = false
[[and.fold.tests]]
expression = "true and true"
expected = true
[[and.fold.tests]]
expression = "false and null "
expected = false
[[and.fold.tests]]
expression = "false and false"
expected = false
[[and.fold.tests]]
expression = "false and true"
expected = false
[[and.fold.tests]]
expression = "null and false"
expected = false
[[and.fold.tests]]
expression = "null and null"
# expected = null
[[and.fold.tests]]
expression = "null and true"
# expected = null
[not]
description = "Check that `not` negates `null` as null`"
[[not.fold.tests]]
expression = "not null"
# expected = null
[[not.fold.tests]]
expression = "not true"
expected = false
[[not.fold.tests]]
expression = "not false"
expected = true
[null_comparison]
description = "Check that any literals compared to `null` fold as `null`."
[[null_comparison.fold.tests]]
expression = "null == null"
expected = true
[[null_comparison.fold.tests]]
expression = "null != null"
expected = false
[[null_comparison.loop]]
[null_comparison.loop.param]
type = ["bool", "int", "string", "float"]
name = "value"
[null_comparison.loop.fold]
[[null_comparison.loop.fold.tests]]
expression = "$value == null"
expected = false
[[null_comparison.loop.fold.tests]]
expression = "$value != null"
expected = true
[[null_comparison.loop.fold.tests]]
expression = "null == $value"
expected = false
[[null_comparison.loop.fold.tests]]
expression = "null != $value"
expected = true
[[null_comparison.loop]]
[null_comparison.loop.param]
type = ["null", "int", "string", "float"]
name = "value"
[null_comparison.loop.fold]
[[null_comparison.loop.fold.tests]]
expression = "null < $value"
# expected = null
[[null_comparison.loop.fold.tests]]
expression = "null <= $value"
# expected = null
[[null_comparison.loop.fold.tests]]
expression = "null >= $value"
# expected = null
[[null_comparison.loop.fold.tests]]
expression = "null > $value"
# expected = null
[[null_comparison.loop.fold.tests]]
expression = "$value < null"
# expected = null
[[null_comparison.loop.fold.tests]]
expression = "$value <= null"
# expected = null
[[null_comparison.loop.fold.tests]]
expression = "$value >= null"
# expected = null
[[null_comparison.loop.fold.tests]]
expression = "$value > null"
# expected = null
[complex]
description = "Test recurisve folding for more complex expressions."
[[complex.fold.tests]]
expression = '(100 * 10) - ("hello":length())'
expected = 995
[[complex.fold.tests]]
expression = 'concat((100 * 10) - ("hello":length()) == 995, "...")'
expected = "true..."

View File

@ -0,0 +1,521 @@
[between]
description = "Test the proper evaluation of the `between` function"
[[between.fold.tests]]
expression = 'between("welcome to the planet", null, "planet")'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", null)'
# expected = null
[[between.fold.tests]]
expression = 'between(null, "welcome", "planet")'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "planet")'
expected = " to the "
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "village")'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "goodbye", "planet")'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "e", false)'
expected = " to th"
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "e", true)'
expected = " to the plan"
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "x", false)'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "x", true)'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "", "x", true)'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "", "", true)'
expected = "welcome to the planet"
[[between.fold.tests]]
expression = 'between("welcome to the planet", "", "", false)'
expected = ""
[[between.fold.tests]]
expression = 'between("welcome to the planet", "", "planet", false)'
expected = "welcome to the "
[concat]
description = "Test the `concat` function"
[[concat.fold.tests]]
expression = 'concat(null)'
# expected = null
[[concat.fold.tests]]
expression = 'concat(null, null, null)'
# expected = null
[[concat.fold.tests]]
expression = 'concat("a")'
expected = "a"
[[concat.fold.tests]]
expression = 'concat("a", "||", 1, "||", "b")'
expected = "a||1||b"
[[concat.fold.tests]]
expression = 'concat("a", "||", null, "||", "b")'
# expected = null
[[concat.fold.tests]]
expression = 'concat("a", "||", 1, "||", true, "||", "b")'
expected = "a||1||true||b"
[[concat.fold.tests]]
expression = 'concat("a", "||", 1, "||", true, "||", "b")'
expected = "a||1||true||b"
[endswith]
description = "Test the `endsWith` function with case matching"
[[endswith.fold.tests]]
expression = "endsWith('FooBarBaz', 'Baz')"
expected = true
[[endswith.fold.tests]]
expression = "endsWith('FooBarBaz', 'Foo')"
expected = false
[endswith_insensitive]
description = "Test the `endsWith` function with case insensitivity"
case_insensitive = true
[[endswith_insensitive.fold.tests]]
expression = "endsWith('FooBarBaz', 'baz')"
expected = true
[endswith_sensitive]
description = "Test the `endsWith` function with case sensitivity"
case_sensitive = true
[[endswith_sensitive.fold.tests]]
expression = "endsWith('FooBarBaz', 'baz')"
expected = false
[indexof]
description = "Test `indexOf()` with exact case specified"
[[indexof.fold.tests]]
expression = 'indexOf(null, "L")'
# expected = null
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", "L")'
# expected = null
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", null)'
# expected = null
[[indexof.fold.tests]]
expression = 'indexOf(null, null)'
# expected = null
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", "o")'
expected = 1
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", "o", null)'
expected = 1
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", "o", 0)'
expected = 1
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", "o", 1)'
expected = 1
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", "o", 2)'
expected = 2
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", "r")'
expected = 5
[[indexof.fold.tests]]
expression = 'indexOf("foobarbaz", "r", 2)'
expected = 5
[length]
description = "Test the folding of the `length` function."
[[length.fold.tests]]
expression = 'length(null)'
# expected = null
[[length.fold.tests]]
expression = 'length("")'
expected = 0
[[length.fold.tests]]
expression = 'length("foo")'
expected = 3
[match]
description = "Test the `match` function"
[match.verifier]
[[match.verifier.failures]]
expression = 'match(1, "*")'
[[match.verifier.failures]]
expression = 'match(1, "*")'
[[match.verifier.failures]]
expression = 'match("eql", 1)'
[match.fold]
[[match.fold.tests]]
expression = 'match(null, "[a-z]{3}")'
# expected = null
[[match.fold.tests]]
expression = 'match("foo", "[a-z]{3}")'
expected = true
[[match.fold.tests]]
expression = 'match("foo\nbarbaz", "[a-z]{3}\n[a-z]{6}")'
expected = true
[[match.fold.tests]]
expression = 'match("999", "[a-z]{3}")'
expected = false
[[match.fold.tests]]
expression = 'match("999", "[a-z]{3}", "[0-9]{5}")'
expected = false
[[match.fold.tests]]
expression = 'match("999", "[a-z]{3}", "[0-9]{5}", "[9][9][9]")'
expected = true
[number]
description = "Test the `number` function"
[number.verifier]
[[number.verifier.failures]]
expression = "number()"
[[number.verifier.failures]]
expression = "number(1)"
[[number.verifier.failures]]
expression = "number(true)"
[[number.verifier.failures]]
expression = "number(true)"
[[number.fold.tests]]
expression = 'number("314")'
expected = 314
[[number.fold.tests]]
expression = 'number("3.14")'
expected = 3.14
[[number.fold.tests]]
expression = 'number("-3.14", 10)'
expected = -3.14
[[number.fold.tests]]
expression = 'number("-314", 10)'
expected = -314
[[number.fold.tests]]
expression = 'number("+314", 10)'
expected = +314
[[number.fold.tests]]
expression = 'number("-3.14")'
expected = -3.14
[[number.fold.tests]]
expression = 'number("0x1337")'
expected = 4919
[[number.fold.tests]]
expression = 'number("0x1337", 16)'
expected = 4919
[[number.fold.tests]]
expression = 'number("52403")'
expected = 52403
[[number.fold.tests]]
expression = 'number("052403", 8)'
expected = 21763
[[number.fold.tests]]
expression = 'number("52403", 7)'
expected = 12890
[startswith]
description = "Test the `startsWith` function"
[startswith.verifier]
[[startswith.verifier.failures]]
expression = "startsWith()"
[[startswith.verifier.failures]]
expression = "startsWith(1, 'FOO')"
[[startswith.verifier.failures]]
expression = "startsWith('FOO', 123)"
[[startswith.fold.tests]]
expression = "startsWith('FooBarBaz', null)"
# expected = null
[[startswith.fold.tests]]
expression = "startsWith(null, 'Foo')"
# expected = null
[[startswith.fold.tests]]
expression = "startsWith('FooBarBaz', 'Foo')"
expected = true
[[startswith.fold.tests]]
expression = "startsWith('FooBarBaz', '')"
expected = true
[[startswith.fold.tests]]
expression = "startsWith('FooBarBaz', 'FooBar')"
expected = true
[[startswith.fold.tests]]
expression = "startsWith('FooBarBaz', 'Bar')"
expected = false
[[startswith.fold.tests]]
expression = "startsWith('FooBarBaz', 'Bar')"
expected = false
[startswith_case_sensitive]
description = "Test the `startsWith` function with case-sensitive matching"
case_sensitive = true
[[startswith_case_sensitive.tests]]
expression = "startsWith('FooBar', 'Foo')"
expected = true
[[startswith_case_sensitive.tests]]
expression = "startsWith('FooBar', 'foo')"
expected = false
[startswith_case_insensitive]
description = "Test the `startsWith` function with case-insensitive matching"
[[startswith_case_insensitive.fold.tests]]
expression = "startsWith('FooBar', 'Foo')"
expected = true
[[startswith_case_insensitive.fold.tests]]
expression = "startsWith('FooBar', 'FOO')"
expected = true
[[startswith_case_insensitive.fold.tests]]
expression = "startsWith('FooBar', 'foo')"
expected = true
[[startswith_case_insensitive.fold.tests]]
expression = "startsWith('FooBar', 'Bar')"
expected = false
[string]
description = "Test the `string` function"
[[string.fold.tests]]
expression = "string(null)"
# expected = null
[[string.fold.tests]]
expression = "string(1)"
expected = "1"
[[string.fold.tests]]
expression = "string(true)"
expected = "true"
[[string.fold.tests]]
expression = "string(2.1828)"
expected = "2.1828"
[[string.fold.tests]]
expression = "string('hello')"
expected = "hello"
[substring]
description = "Test the `substring` function when the case already matches"
[[substring.fold.tests]]
expression = "substring(null, 5)"
# expected = null
[[substring.fold.tests]]
expression = "substring(null, null)"
# expected = null
[[substring.fold.tests]]
expression = "substring('hello world', null, null)"
expected = "hello world"
[[substring.fold.tests]]
expression = "substring('hello world', 6, null)"
expected = "world"
[[substring.fold.tests]]
expression = "substring('hello world', null, 5)"
expected = "hello"
[[substring.fold.tests]]
expression = "substring('hello world', 0, 5)"
expected = "hello"
[[substring.fold.tests]]
expression = "substring('hello world', 0, 500)"
expected = "hello world"
[[substring.fold.tests]]
expression = "substring('hello world', 5, -1)"
expected = " worl"
[[substring.fold.tests]]
expression = "substring('hello world', 3, 9)"
expected = "lo wor"
[[substring.fold.tests]]
expression = "substring('hello world', -5, null)"
expected = "world"
[[substring.fold.tests]]
expression = "substring('hello world', -5, 11)"
expected = "world"
[[substring.fold.tests]]
expression = "substring('hello world', -5, 100)"
expected = "world"
[[substring.fold.tests]]
expression = "substring('hello world', -60, 5)"
expected = "hello"
[[substring.fold.tests]]
expression = "substring('hello world', -5)"
expected = "world"
[[substring.fold.tests]]
expression = "substring('hello world', -5, -1)"
expected = "worl"
[wildcard]
description = "Test that `wildcard` folds with correct case matches."
[[wildcard.fold.tests]]
expression = 'wildcard(null, "f*o*o*")'
# expected = null
[[wildcard.fold.tests]]
expression = 'wildcard("Foo", "F*o*o*")'
expected = true
[[wildcard.fold.tests]]
expression = 'wildcard("Foo", "*Foo")'
expected = true
[[wildcard.fold.tests]]
expression = 'wildcard("Foo", "*Foo*")'
expected = true
[[wildcard.fold.tests]]
expression = 'wildcard("Foo", "*")'
expected = true
[[wildcard.fold.tests]]
expression = 'wildcard("Foo", "Bar*")'
expected = false
[[wildcard.fold.tests]]
expression = 'wildcard("Foo", "*Bar*")'
expected = false
[[wildcard.fold.tests]]
expression = 'wildcard("Foo", "*Bar*", "Baz*")'
expected = false
[[wildcard.fold.tests]]
expression = 'wildcard("Foo", "Foo*", "*Bar*", "Baz*")'
expected = true
[[wildcard.fold.tests]]
expression = 'wildcard("Bar", "Foo*", "*Bar*", "Baz*")'
expected = true
[[wildcard.fold.tests]]
expression = 'wildcard("Baz", "Foo*", "*Bar*", "Baz*")'
expected = true
[wildcard_case_insensitive]
description = "Test that `wildcard` function folds case insensitive as expected."
case_insensitive = true
[[wildcard_case_insensitive.fold.tests]]
expression = 'wildcard("FOO", "f*o*o*")'
expected = true
[[wildcard_case_insensitive.fold.tests]]
expression = 'wildcard("bar", "f*o*o*")'
expected = false
[wildcard_case_sensitive]
description = "Test that `wildcard` folds case-sensitive matches."
case_sensitive = true
[[wildcard_case_sensitive.fold.tests]]
expression = 'wildcard("Foo", "F*o*o*")'
expected = true
[[wildcard_case_sensitive.fold.tests]]
expression = 'wildcard("foo", "F*o*o*")'
expected = false

View File

@ -0,0 +1,153 @@
[between]
[[between.fold.tests]]
expression = 'between("welcome to the planet", null, "planet")'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", null)'
# expected = null
[[between.fold.tests]]
expression = 'between(null, "welcome", "planet")'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "village")'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "goodbye", "planet")'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "x", false)'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "welcome", "x", true)'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "", "x", true)'
# expected = null
[[between.fold.tests]]
expression = 'between("welcome to the planet", "", "planet", false)'
expected = "welcome to the "
[[between.fold.tests]]
expression = 'between("welcome to the planet", "", "", false)'
expected = ""
[string_case_insensitive_match_comparison]
case_insensitive = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" == "Bar"'
expected = false
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" != "Bar"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" == "foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" != "foo"'
expected = false
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" < "foo"'
expected = false
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" <= "foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" >= "foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"Foo" > "foo"'
expected = false
# now flip the order and check
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"foo" < "Foo"'
expected = false
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"foo" <= "Foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"foo" >= "Foo"'
expected = true
[[string_case_insensitive_match_comparison.fold.tests]]
expression = '"foo" > "Foo"'
expected = false
[wildcard_case_insensitive]
case_insensitive = true
[[wildcard_case_insensitive.fold.tests]]
expression = 'wildcard("FOO", "f*o*o*")'
expected = true
[[wildcard_case_insensitive.fold.tests]]
expression = 'wildcard("bar", "f*o*o*")'
expected = false
[complex]
[[complex.fold.tests]]
expression = '(100 * 10) - ("hello":length())'
expected = 995
[[complex.fold.tests]]
expression = 'concat((100 * 10) - ("hello":length()) == 995, "...")'
expected = "true..."
[null_comparison]
[[null_comparison.fold.tests]]
expression = "null == null"
expected = true
[[null_comparison.fold.tests]]
expression = "null != null"
expected = false
[endswith_sensitive]
case_sensitive = true
[[endswith_sensitive.fold.tests]]
expression = "endsWith('FooBarBaz', 'baz')"
expected = false
[startswith_case_insensitive]
[[startswith_case_insensitive.fold.tests]]
expression = "startsWith('FooBar', 'FOO')"
expected = true
[[startswith_case_insensitive.fold.tests]]
expression = "startsWith('FooBar', 'foo')"
expected = true
[substring]
[[substring.fold.tests]]
expression = "substring('hello world', null, 5)"
expected = "hello"