Class LanguageTestContexts

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

public final class LanguageTestContexts extends Object
Creates the contexts the engine passes to an expression language, for unit tests of a language's compiler and compiled expressions. The context interfaces are sealed, so a test can't implement them. These are the engine's own implementations, and behave as they do in a run: for example, writing to the facts fails with the engine's message, and the evaluation and action contexts reject a fact with a null or blank name with a run's message, though not one with a name a language reserves: they have no engine, and so no languages to ask. Each context from evaluation or action is for a run of its own, so the values it keeps with EvaluationContext.runScoped(java.lang.Object, java.util.function.Supplier<? extends T>) aren't shared with another context; actionInRun(io.github.brantunger.unruly.api.language.EvaluationContext, java.lang.Object) creates one in the run of another context, and shares its values. A context's run started when the context was created, by the system clock, and has no tags, unless the context was created with evaluation(Map, Instant, Instant, Set) or action(Map, Object, Instant, Instant, Set), which give it the EvaluationContext.startedAt() and EvaluationContext.tags() of a run with an engine's clock and RunOptions.withTags(...), or with actionInRun(io.github.brantunger.unruly.api.language.EvaluationContext, java.lang.Object) from such a context, which has the same start and tags. No run ends by itself, so endRun(io.github.brantunger.unruly.api.language.EvaluationContext) closes the values a context keeps with EvaluationContext.runScopedClosing(java.lang.Object, java.util.function.Supplier<? extends T>), and the actions it registered with EvaluationContext.onCancel(Runnable) that haven't started, as the end of a run does.

To check a language against everything the engine promises for its rules, extend ExpressionLanguageContractTest.

  • Method Details

    • compile

      public static CompileContext compile()
      Creates a compile context without imports. Its class loader is the current thread's context class loader, or this class's class loader if the thread has none, as the engine chooses one.
      Returns:
      The context
    • compile

      public static CompileContext compile(Set<String> packageImports, Set<Class<?>> classImports, ClassLoader classLoader)
      Creates a compile context.
      Parameters:
      packageImports - The imported packages, such as java.util; copied
      classImports - The classes imported one by one; copied
      classLoader - The class loader to look up classes in the imported packages with
      Returns:
      The context
      Throws:
      NullPointerException - if an argument, or an element of a set, is null
      IllegalArgumentException - if an imported package has more than 1,000 characters or more than 64 dot-separated parts, or isn't a valid package name, which an engine rejects too, with the same message
      StackOverflowError - if an imported package has a code point above U+00FF, the JDK's tables of such characters, which the check of its name reads, aren't known to be built yet, as no engine has been built and no such import checked yet, and the stack has too little room left to build them, before any is touched, as EngineCompileContext says (#1366); the next call checks again
    • compile

      public static CompileContext compile(Set<String> packageImports, Set<Class<?>> classImports, ClassLoader classLoader, Class<?> outputType, Map<String,String> options)
      Creates a compile context with an output type and the language's options.
      Parameters:
      packageImports - The imported packages, such as java.util; copied
      classImports - The classes imported one by one; copied
      classLoader - The class loader to look up classes in the imported packages with
      outputType - The type of the output object, as the engine's builder was told; Object.class when it wasn't
      options - The language's options, as the engine's builder was given them; copied
      Returns:
      The context
      Throws:
      NullPointerException - if an argument, or an element of a set or of the options, is null
      IllegalArgumentException - if an imported package has more than 1,000 characters or more than 64 dot-separated parts, or isn't a valid package name, which an engine rejects too, with the same message
      StackOverflowError - if an imported package has a code point above U+00FF, the JDK's tables of such characters, which the check of its name reads, aren't known to be built yet, as no engine has been built and no such import checked yet, and the stack has too little room left to build them, before any is touched, as EngineCompileContext says (#1366); the next call checks again
    • compile

      public static CompileContext compile(Set<String> packageImports, Set<Class<?>> classImports, ClassLoader classLoader, Class<?> outputType, Map<String,String> options, Map<String,Class<?>> declaredFacts, boolean allFactsDeclared)
      Creates a compile context for an engine that was told which facts its rules use, so a language's tests can check what it makes of the declarations.
      Parameters:
      packageImports - The imported packages, such as java.util; copied
      classImports - The classes imported one by one; copied
      classLoader - The class loader to look up classes in the imported packages with
      outputType - The type of the output object, as the engine's builder was told; Object.class when it wasn't
      options - The language's options, as the engine's builder was given them; copied
      declaredFacts - The declared type of each fact, by name, as the engine's builder was told; copied
      allFactsDeclared - Whether a run may supply only the declared facts, as RulesEngineBuilder.requireDeclaredFacts() says
      Returns:
      The context. A fact declared with a primitive type is given as its wrapper, as an engine gives it.
      Throws:
      NullPointerException - if an argument, or an element of a set, of the options or of the declarations, is null
      IllegalArgumentException - if an imported package has more than 1,000 characters or more than 64 dot-separated parts, or isn't a valid package name, or a fact is declared with a blank name or the name output, which an engine rejects too when one of its languages reserves it, as the default ExpressionLanguage.reservedFactNames() does
      StackOverflowError - if an imported package has a code point above U+00FF, the JDK's tables of such characters, which the check of its name reads, aren't known to be built yet, as no engine has been built and no such import checked yet, and the stack has too little room left to build them, before any is touched, as EngineCompileContext says (#1366); the next call checks again
    • compile

      public static CompileContext compile(Set<String> packageImports, Set<Class<?>> classImports, ClassLoader classLoader, Class<?> outputType, Map<String,String> options, Map<String,Class<?>> declaredFacts, boolean allFactsDeclared, List<String> languageImports)
      Creates a compile context with the language's own imports, as the engine's builder was given them with RulesEngineBuilder.languageImports(...), so a language's tests can check what it makes of them.
      Parameters:
      packageImports - The imported packages, such as java.util; copied
      classImports - The classes imported one by one; copied
      classLoader - The class loader to look up classes in the imported packages with
      outputType - The type of the output object, as the engine's builder was told; Object.class when it wasn't
      options - The language's options, as the engine's builder was given them; copied
      declaredFacts - The declared type of each fact, by name, as the engine's builder was told; copied
      allFactsDeclared - Whether a run may supply only the declared facts, as RulesEngineBuilder.requireDeclaredFacts() says
      languageImports - The language's own imports, such as lodash/fp, as written and in order; copied
      Returns:
      The context. Its CompileContext.languageImports() are languageImports, duplicates included. A fact declared with a primitive type is given as its wrapper, as an engine gives it.
      Throws:
      NullPointerException - if an argument, or an element of a set, of the language imports, of the options or of the declarations, is null
      IllegalArgumentException - if an imported package has more than 1,000 characters or more than 64 dot-separated parts, or isn't a valid package name, a language import has more than 1,000 characters, or a fact is declared with a blank name or the name output, which an engine rejects too when one of its languages reserves it, as the default ExpressionLanguage.reservedFactNames() does
      StackOverflowError - if an imported package has a code point above U+00FF, the JDK's tables of such characters, which the check of its name reads, aren't known to be built yet, as no engine has been built and no such import checked yet, and the stack has too little room left to build them, before any is touched, as EngineCompileContext says (#1366); the next call checks again
    • compile

      public static CompileContext compile(ExpressionLanguage language, Set<String> packageImports, Set<Class<?>> classImports, ClassLoader classLoader, Class<?> outputType, Map<String,String> options, Map<String,Class<?>> declaredFacts, boolean allFactsDeclared, List<String> languageImports)
      Creates a compile context for one language, which rejects exactly the declarations an engine with that language rejects when it's built: a fact may not be declared with a name the language reserves, as its ExpressionLanguage.reservedFactNames() returns them, asked once, so a language that reserves no name may declare output. The other compile methods reject output, which the default reserves.
      Parameters:
      language - The language the context is for; asked for its name and the fact names it reserves
      packageImports - The imported packages, such as java.util; copied
      classImports - The classes imported one by one; copied
      classLoader - The class loader to look up classes in the imported packages with
      outputType - The type of the output object, as the engine's builder was told; Object.class when it wasn't
      options - The language's options, as the engine's builder was given them; copied
      declaredFacts - The declared type of each fact, by name, as the engine's builder was told; copied
      allFactsDeclared - Whether a run may supply only the declared facts, as RulesEngineBuilder.requireDeclaredFacts() says
      languageImports - The language's own imports, such as lodash/fp, as written and in order; copied
      Returns:
      The context. Its CompileContext.languageImports() are languageImports, duplicates included. A fact declared with a primitive type is given as its wrapper, as an engine gives it.
      Throws:
      NullPointerException - if an argument, or an element of a set, of the language imports, of the options or of the declarations, is null
      IllegalArgumentException - if the language's name is null or blank; if an imported package has more than 1,000 characters or more than 64 dot-separated parts, or isn't a valid package name, a language import has more than 1,000 characters, or a fact is declared with a blank name; or if a fact is declared with a name the language reserves, with the message build() gives
      IllegalStateException - if the language's reservedFactNames() returns null or a set holding null, as build() throws it
      StackOverflowError - if an imported package has a code point above U+00FF, the JDK's tables of such characters, which the check of its name reads, aren't known to be built yet, as no engine has been built and no such import checked yet, and the stack has too little room left to build them, before any is touched, as EngineCompileContext says (#1366); the next call checks again
    • evaluation

      public static EvaluationContext evaluation(Map<String,? extends @Nullable Object> facts)
      Creates the context a condition is evaluated against.
      Parameters:
      facts - The facts by name, whose values can be null; copied
      Returns:
      The context. Its facts are read-only, and a write fails with the message the engine uses for a condition. It equals only itself, as in a run, so two created from the same facts aren't equal.
      Throws:
      NullPointerException - if facts is null
      IllegalArgumentException - if a fact's name is null or blank, which a run rejects with the same message
    • evaluation

      public static EvaluationContext evaluation(Map<String,? extends @Nullable Object> facts, @Nullable Instant deadline)
      Creates the context a condition is evaluated against, for a run with a deadline.

      The context reads the system clock once, when it's created, to learn how far away the deadline is, and from then on times it with a monotonic clock, as a run does: a step of the system clock afterwards doesn't move when the context is cancelled. Its EvaluationContext.timeLeft() is measured the same way.

      Parameters:
      facts - The facts by name, whose values can be null; copied
      deadline - When the run must stop, or null if it has none, as an engine built without runTimeout gives it
      Returns:
      The context. Its EvaluationContext.isCancelled() answers as it does in a run: true once the deadline has passed, measured with a monotonic clock from when the context was created, or while the calling thread's interrupt status is set. It equals only itself, as in a run.
      Throws:
      NullPointerException - if facts is null
      IllegalArgumentException - if a fact's name is null or blank, which a run rejects with the same message
    • evaluation

      public static EvaluationContext evaluation(Map<String,? extends @Nullable Object> facts, @Nullable Instant deadline, Instant startedAt, Set<String> tags)
      Creates the context a condition is evaluated against, for a run that started at startedAt by its engine's clock and was given tags: so a language's test can check what its conditions make of the run's EvaluationContext.startedAt() and EvaluationContext.tags(), as a run with RulesEngineBuilder.clock(...) and RunOptions.withTags(...) gives them. The deadline is timed as evaluation(Map, Instant) describes.
      Parameters:
      facts - The facts by name, whose values can be null; copied
      deadline - When the run must stop, or null if it has none, as an engine built without runTimeout gives it
      startedAt - When the run started, by the engine's clock, as a fixed clock gives it
      tags - The run's tags, as RunOptions.withTags(...) was given them, or empty for a run that uses every rule; copied
      Returns:
      The context. Its EvaluationContext.startedAt() is startedAt, and its EvaluationContext.tags() are tags as a run has them: in String order and unmodifiable. It equals only itself, as in a run.
      Throws:
      NullPointerException - if facts, startedAt or tags is null
      IllegalArgumentException - if a fact's name is null or blank, or a tag is null or blank, which a run rejects with the same message
    • action

      public static ActionContext action(Map<String,? extends @Nullable Object> facts, Object output)
      Creates the context an action runs against.
      Parameters:
      facts - The facts by name, whose values can be null; copied
      output - The output object, which the action changes in place
      Returns:
      The context. Its facts are read-only, and a write fails with the message the engine uses for an action. It equals only itself, as in a run, so two created from the same facts and output aren't equal.
      Throws:
      NullPointerException - if facts or output is null
      IllegalArgumentException - if a fact's name is null or blank, which a run rejects with the same message
    • action

      public static ActionContext action(Map<String,? extends @Nullable Object> facts, Object output, @Nullable Instant deadline)
      Creates the context an action runs against, for a run with a deadline.

      The context reads the system clock once, when it's created, to learn how far away the deadline is, and from then on times it with a monotonic clock, as a run does: a step of the system clock afterwards doesn't move when the context is cancelled. Its EvaluationContext.timeLeft() is measured the same way.

      Parameters:
      facts - The facts by name, whose values can be null; copied
      output - The output object, which the action changes in place
      deadline - When the run must stop, or null if it has none, as an engine built without runTimeout gives it
      Returns:
      The context. Its EvaluationContext.isCancelled() answers as it does in a run, and it equals only itself.
      Throws:
      NullPointerException - if facts or output is null
      IllegalArgumentException - if a fact's name is null or blank, which a run rejects with the same message
    • action

      public static ActionContext action(Map<String,? extends @Nullable Object> facts, Object output, @Nullable Instant deadline, Instant startedAt, Set<String> tags)
      Creates the context an action runs against, for a run that started at startedAt by its engine's clock and was given tags: so a language's test can check what its actions make of the run's EvaluationContext.startedAt() and EvaluationContext.tags(), as a run with RulesEngineBuilder.clock(...) and RunOptions.withTags(...) gives them. The deadline is timed as action(Map, Object, Instant) describes.
      Parameters:
      facts - The facts by name, whose values can be null; copied
      output - The output object, which the action changes in place
      deadline - When the run must stop, or null if it has none, as an engine built without runTimeout gives it
      startedAt - When the run started, by the engine's clock, as a fixed clock gives it
      tags - The run's tags, as RunOptions.withTags(...) was given them, or empty for a run that uses every rule; copied
      Returns:
      The context. Its EvaluationContext.startedAt() is startedAt, and its EvaluationContext.tags() are tags as a run has them: in String order and unmodifiable. It equals only itself, as in a run.
      Throws:
      NullPointerException - if facts, output, startedAt or tags is null
      IllegalArgumentException - if a fact's name is null or blank, or a tag is null or blank, which a run rejects with the same message
    • actionInRun

      public static ActionContext actionInRun(EvaluationContext sameRun, Object output)
      Creates the context an action runs against, in the same run as another context: so a language's test can check that its actions find what its conditions kept with EvaluationContext.runScoped(java.lang.Object, java.util.function.Supplier<? extends T>).
      Parameters:
      sameRun - A context of the run, created by this class, such as with evaluation(Map)
      output - The output object, which the action changes in place
      Returns:
      The context. It has the facts, deadline, start and tags of sameRun, and shares its run-scoped values. Its facts are read-only, and a write fails with the message the engine uses for an action. It equals only itself.
      Throws:
      NullPointerException - if sameRun or output is null
    • endRun

      public static void endRun(EvaluationContext context) throws Exception
      Ends the run of a context, as the engine ends a run: closes the actions the run registered with EvaluationContext.onCancel(Runnable) that haven't started, then the values the run keeps with EvaluationContext.runScopedClosing(java.lang.Object, java.util.function.Supplier<? extends T>), in the reverse of the order they were made, so a language's test can check that it releases what it opened for the run. Every value is closed, whatever the others throw, unless both waits below fail, and from then on asking for one with runScopedClosing, or registering an action with onCancel, through any context of the run, throws IllegalStateException, as it does once a run has ended. It first waits, with no limit, for a runScopedClosing init running on another thread. If that wait fails, as it can when the stack or the heap runs out, it waits once more, as a run does, and closes what that hands over; if that fails too, it closes nothing, closes no action, and the run hasn't ended. A second call does nothing, unless both waits failed: it then closes the values left open.

      Called from a runScopedClosing init that goes on to return a value, it closes the run's values, and that value is closed too, and refused: runScopedClosing throws the same IllegalStateException, with what that close() threw suppressed on it, unless that is or carries a fatal Error, which it throws in its place, carrying the IllegalStateException. So a value is never left open, unless both of endRun's waits failed.

      It differs from a run in one way: a run logs what a close() throws at WARN, and throws only a fatal Error, but this throws whatever was thrown, so the test sees it.

      Parameters:
      context - A context of the run, created by this class, such as with evaluation(Map)
      Throws:
      NullPointerException - if context is null
      Exception - what waiting for an init on another thread threw, if it failed, or else the first Throwable a value's close() threw, as it is; every other close() failure, what waiting again threw, and the first failure to keep one of them are suppressed on it, as far as keeping them doesn't fail: each once, and none it already carries or that carries it
      Error - a VirtualMachineError other than StackOverflowError, and other than an InternalError that wraps one with no LinkageError between them, that keeping what failed before on the first failure threw, once every value is closed, in place of the first failure, which it doesn't carry