Class ExpressionLanguageContractTest

java.lang.Object
io.github.brantunger.unruly.test.ExpressionLanguageContractTest

public abstract class ExpressionLanguageContractTest extends Object
What the engine promises for rules in any expression language. A language's test extends this class and supplies the expressions each check needs, written in that language:
class MyLanguageContractTest extends ExpressionLanguageContractTest {
    protected ExpressionLanguage language() { return new MyLanguage(); }
    protected String alwaysTrue() { return "true"; }
    protected String factEquals(String fact, int value) { return fact + " == " + value; }
    // ... one method for each expression the checks need
}

Every check but evaluateAgreesWithDetail runs rules through an engine, so a language passes only if it works with the engine as users will run it. On the module path, a test module of its own, one that requires the kit, must open the package of the extending test to org.junit.platform.commons. Tests that Surefire patches into the language's own named module need no opens clause: Surefire gives JUnit access to them.

The checks compare numbers in the output by value, so a language whose whole numbers are Longs, as CEL's are, or Doubles, as JsonLogic's and JavaScript's are, needs no conversion to pass on that count. What a language reads is a separate promise, and every language here makes it, whatever its own numbers are: a rule written for 1 is also run against a Long, a Short and a BigDecimal fact, and must fire for all three and not for a 2L one. A language that compares whole numbers by type opts out of that one check with comparesWholeNumbersByValue(). A language that converts the numbers its actions return for the output's setters, so that a whole double sets an int or a long property, and a fraction, an infinity, NaN or a whole number the setter's type doesn't hold exactly, as a Double or a BigDecimal, fails the rule, opts into two more checks with convertsOutputNumbers(), one of which converts on several threads at once.

See Also:
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static final class 
    An account as a JavaBean with a setter taking a long, the output outputNumbersConverted writes a whole Double no int holds to.
    static final record 
    An applicant, so a language's tests can run a rule against a record fact.
    static final class 
    An applicant as a JavaBean, so a language's tests can run a rule against a fact whose property is a getter.
    static final class 
    A customer as a JavaBean with a setter taking a String, the output outputUnconvertibleValueFailsTheRule writes a Double to, which no language converts to a String.
    static final class 
    The fact the nested-run checks run their rules against: its value property starts a run nested inside the run that reads it, on the same thread, as a getter a condition or an action reads, or a function it calls, does when it runs rules.
    static final class 
    An applicant as a JavaBean with a setter, so a language's tests can run a rule against a fact whose property a condition could write.
  • Constructor Summary

    Constructors
    Modifier
    Constructor
    Description
    protected
    Creates the test.
  • Method Summary

    Modifier and Type
    Method
    Description
    protected String
    Returns an action with a syntax error, one this language's own compiler rejects.
    protected boolean
    Whether a variable an action declares lasts until the end of the run, so that a later rule's action in the same run reads it, as in a language that keeps each run's variables with EvaluationContext.runScoped(java.lang.Object, java.util.function.Supplier<? extends T>).
    protected abstract String
    Returns a condition that is always true.
    protected abstract @Nullable String
    assignment(String fact, int value)
    Returns a condition that assigns a value to a fact, which the language must reject when it loads or runs the rule, rather than evaluate to a boolean.
    protected @Nullable String
    bothConditions(String condition, String other)
    Returns a condition that is true when both conditions are, and evaluates condition before other, as condition && other does in MVEL.
    protected @Nullable String
    changeSharedState(String name, int value)
    Returns an action that changes state the language keeps outside a run's variables and shares between runs, such as a property of a built-in object, as Math.discount = 50 is in JavaScript, or a global, setting what name refers to to value.
    protected boolean
    Whether this language compares whole numbers by value, so that a rule written for 1 fires for a Long, a Short and a BigDecimal fact that holds one, and doesn't fire for a 2L fact.
    protected CompileContext
    Returns the context evaluateAgreesWithDetail compiles its condition with, since that check compiles it with the language's compiler, outside an engine, and the context of the compiler outputUnconvertibleValueFailsTheRule asks to convert a value when the language's actions change the output in place.
    protected @Nullable String
    conditionDeclaration(String name, int value)
    Returns a condition that declares a variable and is then true, which the language must reject when it loads or runs the rule.
    protected void
    Configures each engine the checks build, for a language that needs what the builder carries to compile its expressions: declared facts, imports or options of its own.
    protected boolean
    Whether this language converts the numbers its actions return for the output's setters, in convertOutput, so that a whole Double reaches a setter taking an int or a long, and a fraction, an infinity, NaN or a whole number the setter's type doesn't hold exactly fails the rule rather than being rounded or narrowed.
    protected @Nullable String
    Returns an action that declares a variable holding a fact's value, and then puts the variable's value into the output map under a key.
    protected abstract @Nullable String
    declareVariable(String name, int value)
    Returns an action that declares a variable.
    protected @Nullable String
    declareVariableThenFail(String name, int value)
    Returns an action that declares a variable and then fails the run, as y = 2; Integer.parseInt('not a number') does in MVEL.
    protected @Nullable String
    Returns an action that runs until its run passes its deadline, and stops then, such as while (true) {} in a language that registers an action with EvaluationContext.onCancel(Runnable) which stops its runtime, or that polls EvaluationContext.isCancelled().
    protected abstract String
    factEquals(String fact, int value)
    Returns a condition that is true when a whole-number fact equals a value.
    protected abstract String
    factProperty(String fact, String property, int value)
    Returns a condition that compares one property of a fact with a value, as applicant.creditScore == 750 does in MVEL.
    protected abstract String
    Returns a condition whose result is a fact's value, whatever its type.
    protected abstract ExpressionLanguage
    Returns the language under test.
    protected abstract @Nullable String
    missingFactProperty(String fact, String property, int value)
    Returns a condition that reads a property the fact doesn't have, which the language must reject when it loads or runs the rule, rather than evaluate to false.
    protected @Nullable String
    propertyAssignment(String fact, String property, int value)
    Returns a condition that writes a property of a fact, which the language must reject when it loads or runs the rule.
    protected abstract String
    putFact(String key, String fact)
    Returns an action that puts a fact's value into the output map, by changing the output or by returning the property in an ActionResult.
    protected @Nullable String
    putFactProperty(String key, String fact, String property)
    Returns an action that puts one property of a fact, read through its getter, into the output map under a key.
    protected String
    putVariable(String key, String variable)
    Returns an action that puts a variable's value into the output map under a key.
    protected abstract @Nullable String
    Returns an action that assigns a new object to the output.
    protected @Nullable String
    sharedStateEquals(String name, int value)
    Returns a condition that is true when the shared state changeSharedState(java.lang.String, int) changes holds value, and false, without failing, while it doesn't.
    protected abstract String
    Returns a condition with a syntax error.
    protected abstract @Nullable String
    Returns a fact name that rules in this language can't refer to.
    protected Collection<String>
    Returns fact names that rules in this language can refer to, such as credit_score2.
    protected String
    variableEquals(String variable, int value)
    Returns a condition that is true when a whole-number variable equals a value.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • ExpressionLanguageContractTest

      protected ExpressionLanguageContractTest()
      Creates the test. JUnit creates an instance of the extending class for each check.
  • Method Details

    • language

      protected abstract ExpressionLanguage language()
      Returns the language under test.
      Returns:
      A new instance of the language
    • alwaysTrue

      protected abstract String alwaysTrue()
      Returns a condition that is always true.
      Returns:
      The condition
    • factEquals

      protected abstract String factEquals(String fact, int value)
      Returns a condition that is true when a whole-number fact equals a value.
      Parameters:
      fact - The fact's name
      value - The value to compare it with
      Returns:
      The condition
    • comparesWholeNumbersByValue

      protected boolean comparesWholeNumbersByValue()
      Whether this language compares whole numbers by value, so that a rule written for 1 fires for a Long, a Short and a BigDecimal fact that holds one, and doesn't fire for a 2L fact. By default, true. A strongly typed language that deliberately compares them by type returns false, which skips the whole check: there's no opting out of one of the three types.
      Returns:
      Whether whole numbers of different types compare equal
    • factValue

      protected abstract String factValue(String fact)
      Returns a condition whose result is a fact's value, whatever its type.
      Parameters:
      fact - The fact's name
      Returns:
      The condition
    • assignment

      protected abstract @Nullable String assignment(String fact, int value)
      Returns a condition that assigns a value to a fact, which the language must reject when it loads or runs the rule, rather than evaluate to a boolean.
      Parameters:
      fact - The fact's name
      value - The value to assign
      Returns:
      The condition, or null if the language's conditions can't express an assignment, which skips the check
    • propertyAssignment

      protected @Nullable String propertyAssignment(String fact, String property, int value)
      Returns a condition that writes a property of a fact, which the language must reject when it loads or runs the rule. conditionWritesRejected runs the same condition against a Map fact and a ExpressionLanguageContractTest.WritableApplicant bean fact. The condition must also write a bean's property, as MVEL's applicant.creditScore = 1 does. Each fact must be unchanged afterwards: a language that fails the rule only after the write has still changed the caller's fact. By default, null.
      Parameters:
      fact - The fact's name
      property - The property to write
      value - The value to write
      Returns:
      The condition, or null if the language's conditions can't write a property, which skips that part of the check
    • conditionDeclaration

      protected @Nullable String conditionDeclaration(String name, int value)
      Returns a condition that declares a variable and is then true, which the language must reject when it loads or runs the rule. conditionWritesRejected names a variable that is none of the checks' facts, so a language that declares its facts can't take the declaration for an assignment to one. By default, null.
      Parameters:
      name - The variable's name
      value - The value it holds
      Returns:
      The condition, or null if the language's conditions can't declare a variable, which skips that part of the check
    • putFact

      protected abstract String putFact(String key, String fact)
      Returns an action that puts a fact's value into the output map, by changing the output or by returning the property in an ActionResult.
      Parameters:
      key - The key to put the value under
      fact - The fact's name
      Returns:
      The action
    • declareVariable

      protected abstract @Nullable String declareVariable(String name, int value)
      Returns an action that declares a variable.
      Parameters:
      name - The variable's name
      value - The value it holds
      Returns:
      The action, or null if the language's actions have no variables, which skips the check
    • putVariable

      protected String putVariable(String key, String variable)
      Returns an action that puts a variable's value into the output map under a key. The variable checks read the variable an earlier action declared with it. By default, putFact(key, variable), for a language that reads a variable as it reads a fact. A language whose variables have a namespace of their own, such as SpEL's #y, overrides it, and variableEquals(java.lang.String, int) too: a variable read as a fact is never found, and the checks pass whether it leaked or not.
      Parameters:
      key - The key to put the value under
      variable - The variable's name
      Returns:
      The action
    • variableEquals

      protected String variableEquals(String variable, int value)
      Returns a condition that is true when a whole-number variable equals a value. actionVariablesStayLocal reads with it the variable an earlier run's action declared. By default, factEquals(variable, value), for a language that reads a variable as it reads a fact; see putVariable(java.lang.String, java.lang.String).
      Parameters:
      variable - The variable's name
      value - The value to compare it with
      Returns:
      The condition
    • declareVariableThenFail

      protected @Nullable String declareVariableThenFail(String name, int value)
      Returns an action that declares a variable and then fails the run, as y = 2; Integer.parseInt('not a number') does in MVEL. failedActionVariablesStayLocal checks that a later run doesn't read the variable, as a language that clears its variables only when an action succeeds would let it. The action must declare the variable before it fails: one that fails first declares nothing, and the check proves nothing. By default, null.
      Parameters:
      name - The variable's name
      value - The value it holds
      Returns:
      The action, or null if the language's actions have no variables, which skips the check
    • copyThroughVariable

      protected @Nullable String copyThroughVariable(String key, String fact)
      Returns an action that declares a variable holding a fact's value, and then puts the variable's value into the output map under a key. concurrentRuns runs it in many runs at once, each with its own value, so it is likely to catch an action whose variables every run shares, such as a map compiled into the action or a static: such an action puts another run's value, or fails, when another run changes the variable between its declaring the variable and putting it. By default, null.
      Parameters:
      key - The key to put the value under
      fact - The fact's name
      Returns:
      The action, or null if the language's actions have no variables, which leaves the variable out of concurrentRuns rather than skipping it
    • actionVariablesLastTheRun

      protected boolean actionVariablesLastTheRun()
      Whether a variable an action declares lasts until the end of the run, so that a later rule's action in the same run reads it, as in a language that keeps each run's variables with EvaluationContext.runScoped(java.lang.Object, java.util.function.Supplier<? extends T>). By default, false: a variable stays local to the action that declares it. Returning true turns one part of actionVariablesStayLocal around: a later rule in the same run must read the variable, so the check fails when load() refuses that rule, when it fails the run, or when it reads anything but the variable's value. The rest of the check is unchanged: the variable still mustn't change the facts later actions see, and no later run may read it.
      Returns:
      Whether an action's variables last the run
    • convertsOutputNumbers

      protected boolean convertsOutputNumbers()
      Whether this language converts the numbers its actions return for the output's setters, in convertOutput, so that a whole Double reaches a setter taking an int or a long, and a fraction, an infinity, NaN or a whole number the setter's type doesn't hold exactly fails the rule rather than being rounded or narrowed. By default, false, which skips outputNumbersConverted: a language whose whole numbers are Java's, Integers or Longs, needs no conversion to pass the other checks, and the default writer widens an Integer for a long setter by itself. A language whose numbers are all doubles, as JavaScript's and JsonLogic's are, opts in once it converts them; so may any language that converts a whole double its action returns, as one that reads a Double fact and returns it does.

      The check runs putFact("creditScore", "x") against a run whose fact x is the Double 1.0, which must set the credit score to 1, and then the Double 1.5, which must fail the rule and leave the score, with a writer that sets each property on a ExpressionLanguageContractTest.WritableApplicant through the default writer, as an application whose output is a bean has it set. Then 3.0E9, Infinity, -Infinity, NaN and -3.0E9, which must each fail the rule and leave the score too: a language that narrows a double it has only checked is whole sets Integer.MAX_VALUE for the first two, and one that casts a whole double it has bounded above only sets Integer.MIN_VALUE for the last. Then the BigDecimals 1.5 and 3E9, which must each fail the rule and leave the score as well: a language whose decimals are BigDecimals, as Groovy's are, and narrows one with intValue() sets 1 and the low 32 bits. Then putFact("balance", "x") on an ExpressionLanguageContractTest.Account, whose setter takes a long: 3.0E9 must set the balance to 3000000000, and Infinity, 253 + 2, the first whole double past where a double counts by ones, 1.5, NaN, -Infinity and -(253 + 2) must each fail the rule and leave it: a language that rounds with Math.round() sets 2 for 1.5, 0 for NaN and Long.MIN_VALUE for -Infinity. Then the BigDecimals 1.5 and 1E19, which must each fail the rule and leave the balance: a language that narrows one with longValue() sets 1 and the low 64 bits. Its engines are built as every other check's is, so configure(io.github.brantunger.unruly.api.RulesEngineBuilder<java.util.Map<java.lang.String, java.lang.Object>>) applies.

      It opts into outputNumbersConvertedConcurrently too, which converts whole doubles for an int setter on eight threads at once, as convertOutput must be thread-safe.

      Returns:
      Whether the language converts the numbers its actions return for a setter
    • reassignOutput

      protected abstract @Nullable String reassignOutput()
      Returns an action that assigns a new object to the output.
      Returns:
      The action, or null if the language's actions can't assign anything, as for a language that returns its results as properties, which skips the check
    • syntaxError

      protected abstract String syntaxError()
      Returns a condition with a syntax error.
      Returns:
      The condition
    • actionSyntaxError

      protected String actionSyntaxError()
      Returns an action with a syntax error, one this language's own compiler rejects. By default, whatever syntaxError() returns, which is a broken action in most languages. Override it when that text is a valid action in this one: the language then compiles it without complaint, and the check fails with nothing but JUnit's "expected RuleCompilationException to be thrown", naming neither the expression nor the hook that supplied it.

      The action must not be blank, and the check fails when it is: the engine rejects a blank action itself, naming the rule, before the language is asked to compile anything, so a blank one would pass the check without the language's compiler ever running. Unlike the @Nullable hooks around it, this one can't be skipped by returning null.

      Returns:
      The action, never blank
    • unusableFactName

      protected abstract @Nullable String unusableFactName()
      Returns a fact name that rules in this language can't refer to. It must not be blank or one of the names the language reserves, as ExpressionLanguage.reservedFactNames() returns them ("output" by default), and the check fails when it is: the engine rejects those names itself, before the language is asked, so they would pass the check without the language's checkFactName ever running. Nor may it be "x", the fact the check's rule reads, and the check fails when it is too.
      Returns:
      The name, or null if every name is accepted
    • usableFactNames

      protected Collection<String> usableFactNames()
      Returns fact names that rules in this language can refer to, such as credit_score2. Each is read by a condition and put into the output by an action, and the check fails if the language rejects the name or the rule doesn't fire. By default, none, which skips the check. List the names a language's users will write, so that a checkFactName stricter than the language itself fails here rather than in every run with that fact.
      Returns:
      The names, or an empty collection to skip the check
    • factProperty

      protected abstract String factProperty(String fact, String property, int value)
      Returns a condition that compares one property of a fact with a value, as applicant.creditScore == 750 does in MVEL. The same condition is run against a record fact, a JavaBean fact and a Map fact.
      Parameters:
      fact - The fact's name
      property - The property to read
      value - The value it must equal
      Returns:
      The condition
    • missingFactProperty

      protected abstract @Nullable String missingFactProperty(String fact, String property, int value)
      Returns a condition that reads a property the fact doesn't have, which the language must reject when it loads or runs the rule, rather than evaluate to false. It's usually factProperty(java.lang.String, java.lang.String, int) with the same arguments.

      A language whose own semantics read a missing property as null or undefined, as JsonLogic reads a var that isn't there, returns null. Such a language can't tell a misspelled property from an absent one, whether it reads the fact directly or as the map FactProperties.toData makes of it, so the check would fail it for being faithful to its own rules.

      Parameters:
      fact - The fact's name
      property - The property the fact doesn't have
      value - The value to compare it with
      Returns:
      The condition, or null if the language reads a missing property as null or undefined, which skips the check
    • putFactProperty

      protected @Nullable String putFactProperty(String key, String fact, String property)
      Returns an action that puts one property of a fact, read through its getter, into the output map under a key. nestedRunInsideAnAction, nestedRunFailsInsideAnAction and nestedRunFailureRethrownAsIs run it against a ExpressionLanguageContractTest.Nesting fact, whose getter starts a run nested inside the action, on the action's own thread. By default, null.
      Parameters:
      key - The key to put the value under
      fact - The fact's name
      property - The property to read
      Returns:
      The action, or null if the language's actions can't read a fact's property, which skips the checks
    • bothConditions

      protected @Nullable String bothConditions(String condition, String other)
      Returns a condition that is true when both conditions are, and evaluates condition before other, as condition && other does in MVEL. nestedRunInsideACondition and nestedRunFailsInsideACondition join factProperty("nest", "value", 7), whose getter starts a run nested inside the condition, with factEquals("x", 1), so that the second read is the one that finds any state the nested run left on the thread. A language that evaluates the right side first isn't checked by them: the first passes it, and the second is skipped when the nested run, stopping at the right side, never reads the getter that makes it fail. By default, null.
      Parameters:
      condition - The condition evaluated first
      other - The condition evaluated second
      Returns:
      The condition, or null if the language's conditions can't require both of two conditions, which skips the checks
    • changeSharedState

      protected @Nullable String changeSharedState(String name, int value)
      Returns an action that changes state the language keeps outside a run's variables and shares between runs, such as a property of a built-in object, as Math.discount = 50 is in JavaScript, or a global, setting what name refers to to value. sharedStateStaysLocal checks that a later run doesn't see the change: the language may refuse the action when it loads the rule, fail it when it runs before it changes anything, or keep the change to the run. An action that fails after the change is checked as one that succeeds. By default, null.
      Parameters:
      name - The name of the state to change
      value - The value to set it to
      Returns:
      The action, or null if the language's actions can reach no state shared between runs, which skips the check
    • sharedStateEquals

      protected @Nullable String sharedStateEquals(String name, int value)
      Returns a condition that is true when the shared state changeSharedState(java.lang.String, int) changes holds value, and false, without failing, while it doesn't. sharedStateStaysLocal evaluates it in a run before any action changes the state, in the run whose action makes the change, before that action runs, and in a later run. The check fails when it fails any of those runs, or is true in the run before the change. It must read the state the action writes: a condition that is never true passes the check whatever the language shares. By default, null.
      Parameters:
      name - The name of the state to read
      value - The value to compare it with
      Returns:
      The condition, or null if changeSharedState(java.lang.String, int) returns null too: with that hook overridden, the check fails on a null here rather than skip
    • endlessAction

      protected @Nullable String endlessAction()
      Returns an action that runs until its run passes its deadline, and stops then, such as while (true) {} in a language that registers an action with EvaluationContext.onCancel(Runnable) which stops its runtime, or that polls EvaluationContext.isCancelled(). endlessActionStopsAtTimeout runs it with a short run timeout, and checks that the run fails with the timeout well within 30 seconds, without its thread interrupted. An action that never stops leaves the thread that runs it going once the check has failed. By default, null.
      Returns:
      The action, or null if the language can't stop an action part-way, which skips the check
    • configure

      protected void configure(RulesEngineBuilder<Map<String,Object>> builder)
      Configures each engine the checks build, for a language that needs what the builder carries to compile its expressions: declared facts, imports or options of its own. By default, nothing. It's called once for each engine, after the kit has started an allMatches engine whose output is a HashMap, and added the language.

      The facts the checks' expressions refer to are x, y, applicant and nest, and the names usableFactNames() returns. The checks supply x as a Boolean, a String, null, an Integer, a Long, a Short and a BigDecimal, y as an Integer, applicant as an ExpressionLanguageContractTest.Applicant, an ExpressionLanguageContractTest.ApplicantBean, a ExpressionLanguageContractTest.WritableApplicant and a Map, nest as a ExpressionLanguageContractTest.Nesting, and each of the names usableFactNames() and unusableFactName() return as the Integer 1. So a language that declares them declares x and applicant as Object, and nest as Object or ExpressionLanguageContractTest.Nesting: the engine fails a run whose fact isn't an instance of its declared type with an IllegalArgumentException, before the language evaluates anything, and the check with it. Nor may the language reserve x, y, applicant or nest, as ExpressionLanguage.reservedFactNames() returns them: every check that builds an engine then fails, before it builds one, with the one message that it reserves them.

      It must not change what the checks depend on, or they fail for reasons that have nothing to do with the language:

      • requireDeclaredFacts(): each run supplies only the facts its check reads, so the engine fails it with an IllegalArgumentException before the language evaluates anything.
      • A declaration of the name unusableFactName() returns: load() checks every declared name with the language, or, for a compiler whose factNamesRead() returns a set, every declared name in it, and the language rejects that one. Most checks then fail to load their rules, with a RuleCompilationException, and the checks that expect a load to fail then pass or fail for reasons unrelated to the language.
      • runTimeout(...): a run that outlasts it fails, and its check with it.
      • maxCopies(...) below 2: two checks make two copies of the rules when they load, which build() refuses with more copies than the limit. A higher limit also caps how many sessions concurrentRuns asks for, so maxCopies(2) hides a newSession() that returns a session twice only after its second call.
      • Another language, or a defaultLanguage(...) other than the language's own name: build() throws an IllegalStateException for an engine with two languages and no default, or with a default that isn't one of its languages. defaultLanguage(language().name()) changes nothing.
      • outputWriter(...): the checks read what an action returned from the output map, where OutputWriter.beansAndMaps() puts it.

      The two checks that need copies made when the rules load, copiesAtLoad and sessionsClosed, set copiesAtLoad(2) after this. compilerClosed sets copiesAtLoad(1), so that each compiler has made a session when the rules load before it's closed; the engine warms up each session that isn't Session.none(), which has nothing to warm up. The two that need a run to make its own copy, sessionClosedWhileAnotherRuns and sessionClosedOnAnotherThread, set copiesAtLoad(0), so a copiesAtLoad set here doesn't change them. sessionClosedWhileAnotherRuns also sets maxCopies(1) after this, so that a run nested in another gets an extra copy, and a limit set here doesn't change it either; concurrentRuns sets none, so a limit set here caps the copies, and the sessions, its runs get. The checks that look at what a run leaves for a later one, actionVariablesStayLocal in its parts with a later run, failedActionVariablesStayLocal, sharedStateStaysLocal and conditionDetail, and those that run a rejected rule a second time, conditionAssignmentRejected, conditionWritesRejected, outputNotReplaceable and missingPropertyFailsTheRun, set maxCopies(1) and copiesAtLoad(0) after this, so that each run gets the one copy, and its sessions, that the run before it used: with two copies made when the rules load, a later run would get the other one. Listeners that don't change the output may be added: the engine only logs what a listener throws, unless it's a fatal Error, but beforeExecute and afterExecute are given the output the checks compare. evaluateAgreesWithDetail builds no engine, and compiles with compileContext() instead, as outputUnconvertibleValueFailsTheRule does for a language whose actions change the output in place: a language that overrides both keeps them consistent.

      Parameters:
      builder - The builder of an engine a check is about to build
    • compileContext

      protected CompileContext compileContext()
      Returns the context evaluateAgreesWithDetail compiles its condition with, since that check compiles it with the language's compiler, outside an engine, and the context of the compiler outputUnconvertibleValueFailsTheRule asks to convert a value when the language's actions change the output in place. By default, LanguageTestContexts.compile(), which has no imports, options or declared facts.

      A language that needs configure(io.github.brantunger.unruly.api.RulesEngineBuilder<java.util.Map<java.lang.String, java.lang.Object>>) to compile needs this too, with the same imports, options and declared facts, which the LanguageTestContexts compile methods take. The conditions are factEquals("x", 1), evaluated for an x that is an Integer, a Long, a Short and a BigDecimal, then alwaysTrue(), factValue("x"), for a Boolean x, and bothConditions(java.lang.String, java.lang.String): a context that declares x as a number may make the compiler refuse factValue("x"), which skips it.

      Returns:
      The context