mirror of
https://github.com/enso-org/enso.git
synced 2024-11-23 08:08:34 +03:00
Handle TailCallException in Runtime Instrument (#1068)
PR implements TailCallException handler in the IdExecutionInstrument sending correct value updates. - implement onReturnExceptional of the runtime instrument - add onExceptionalCallback to the runtime instrument
This commit is contained in:
parent
b2fbf1a848
commit
65dec91bc0
@ -6,12 +6,15 @@ import com.oracle.truffle.api.frame.FrameInstance;
|
||||
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.instrumentation.*;
|
||||
import com.oracle.truffle.api.interop.InteropException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.nodes.RootNode;
|
||||
import org.enso.interpreter.node.EnsoRootNode;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
import org.enso.interpreter.node.MethodRootNode;
|
||||
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode;
|
||||
import org.enso.interpreter.runtime.control.TailCallException;
|
||||
import org.enso.interpreter.runtime.tag.IdentifiedTag;
|
||||
import org.enso.interpreter.runtime.type.Types;
|
||||
import org.enso.pkg.QualifiedName;
|
||||
@ -104,14 +107,22 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExpressionValue{" +
|
||||
"expressionId=" + expressionId +
|
||||
", value=" + value +
|
||||
", type='" + type + '\'' +
|
||||
", cachedType='" + cachedType + '\'' +
|
||||
", callInfo=" + callInfo +
|
||||
", cachedCallInfo=" + cachedCallInfo +
|
||||
'}';
|
||||
return "ExpressionValue{"
|
||||
+ "expressionId="
|
||||
+ expressionId
|
||||
+ ", value="
|
||||
+ value
|
||||
+ ", type='"
|
||||
+ type
|
||||
+ '\''
|
||||
+ ", cachedType='"
|
||||
+ cachedType
|
||||
+ '\''
|
||||
+ ", callInfo="
|
||||
+ callInfo
|
||||
+ ", cachedCallInfo="
|
||||
+ cachedCallInfo
|
||||
+ '}';
|
||||
}
|
||||
|
||||
/** @return the id of the expression computed. */
|
||||
@ -184,9 +195,9 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
return false;
|
||||
}
|
||||
FunctionCallInfo that = (FunctionCallInfo) o;
|
||||
return Objects.equals(moduleName, that.moduleName) &&
|
||||
Objects.equals(typeName, that.typeName) &&
|
||||
functionName.equals(that.functionName);
|
||||
return Objects.equals(moduleName, that.moduleName)
|
||||
&& Objects.equals(typeName, that.typeName)
|
||||
&& functionName.equals(that.functionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -221,6 +232,7 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
private final Consumer<ExpressionCall> functionCallCallback;
|
||||
private final Consumer<ExpressionValue> onComputedCallback;
|
||||
private final Consumer<ExpressionValue> onCachedCallback;
|
||||
private final Consumer<Throwable> onExceptionalCallback;
|
||||
private final RuntimeCache cache;
|
||||
private final MethodCallsCache callsCache;
|
||||
private final UUID nextExecutionItem;
|
||||
@ -236,6 +248,7 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
* @param functionCallCallback the consumer of function call events.
|
||||
* @param onComputedCallback the consumer of the computed value events.
|
||||
* @param onCachedCallback the consumer of the cached value events.
|
||||
* @param onExceptionalCallback the consumer of the exceptional events.
|
||||
*/
|
||||
public IdExecutionEventListener(
|
||||
CallTarget entryCallTarget,
|
||||
@ -244,7 +257,8 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
UUID nextExecutionItem,
|
||||
Consumer<ExpressionCall> functionCallCallback,
|
||||
Consumer<ExpressionValue> onComputedCallback,
|
||||
Consumer<ExpressionValue> onCachedCallback) {
|
||||
Consumer<ExpressionValue> onCachedCallback,
|
||||
Consumer<Throwable> onExceptionalCallback) {
|
||||
this.entryCallTarget = entryCallTarget;
|
||||
this.cache = cache;
|
||||
this.callsCache = methodCallsCache;
|
||||
@ -252,6 +266,7 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
this.functionCallCallback = functionCallCallback;
|
||||
this.onComputedCallback = onComputedCallback;
|
||||
this.onCachedCallback = onCachedCallback;
|
||||
this.onExceptionalCallback = onExceptionalCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -332,8 +347,22 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReturnExceptional(
|
||||
EventContext context, VirtualFrame frame, Throwable exception) {}
|
||||
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
|
||||
if (exception instanceof TailCallException) {
|
||||
try {
|
||||
TailCallException tailCallException = (TailCallException) exception;
|
||||
FunctionCallInstrumentationNode.FunctionCall functionCall =
|
||||
new FunctionCallInstrumentationNode.FunctionCall(
|
||||
tailCallException.getFunction(),
|
||||
tailCallException.getState(),
|
||||
tailCallException.getArguments());
|
||||
Object result = InteropLibrary.getFactory().getUncached().execute(functionCall);
|
||||
onReturnValue(context, frame, result);
|
||||
} catch (InteropException e) {
|
||||
onExceptionalCallback.accept(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're not inside a recursive call, i.e. the {@link #entryCallTarget} only appears
|
||||
@ -375,9 +404,10 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
* @param cache the precomputed expression values.
|
||||
* @param methodCallsCache the storage tracking the executed method calls.
|
||||
* @param nextExecutionItem the next item scheduled for execution.
|
||||
* @param functionCallCallback the consumer of function call events.
|
||||
* @param onComputedCallback the consumer of the computed value events.
|
||||
* @param onCachedCallback the consumer of the cached value events.
|
||||
* @param functionCallCallback the consumer of function call events.
|
||||
* @param onExceptionalCallback the consumer of the exceptional events.
|
||||
* @return a reference to the attached event listener.
|
||||
*/
|
||||
public EventBinding<ExecutionEventListener> bind(
|
||||
@ -387,9 +417,10 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
RuntimeCache cache,
|
||||
MethodCallsCache methodCallsCache,
|
||||
UUID nextExecutionItem,
|
||||
Consumer<ExpressionValue> onComputedCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionCall> functionCallCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onComputedCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onCachedCallback,
|
||||
Consumer<ExpressionCall> functionCallCallback) {
|
||||
Consumer<Throwable> onExceptionalCallback) {
|
||||
SourceSectionFilter filter =
|
||||
SourceSectionFilter.newBuilder()
|
||||
.tagIs(StandardTags.ExpressionTag.class, StandardTags.CallTag.class)
|
||||
@ -397,18 +428,17 @@ public class IdExecutionInstrument extends TruffleInstrument {
|
||||
.indexIn(funSourceStart, funSourceLength)
|
||||
.build();
|
||||
|
||||
EventBinding<ExecutionEventListener> binding =
|
||||
env.getInstrumenter()
|
||||
.attachExecutionEventListener(
|
||||
filter,
|
||||
new IdExecutionEventListener(
|
||||
entryCallTarget,
|
||||
cache,
|
||||
methodCallsCache,
|
||||
nextExecutionItem,
|
||||
functionCallCallback,
|
||||
onComputedCallback,
|
||||
onCachedCallback));
|
||||
return binding;
|
||||
return env.getInstrumenter()
|
||||
.attachExecutionEventListener(
|
||||
filter,
|
||||
new IdExecutionEventListener(
|
||||
entryCallTarget,
|
||||
cache,
|
||||
methodCallsCache,
|
||||
nextExecutionItem,
|
||||
functionCallCallback,
|
||||
onComputedCallback,
|
||||
onCachedCallback,
|
||||
onExceptionalCallback));
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import org.enso.interpreter.runtime.Module;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
import org.enso.interpreter.runtime.state.data.EmptyMap;
|
||||
import org.enso.polyglot.LanguageInfo;
|
||||
import org.enso.polyglot.MethodNames;
|
||||
import org.enso.text.buffer.Rope;
|
||||
@ -75,7 +76,7 @@ public class ExecutionService {
|
||||
}
|
||||
FunctionCallInstrumentationNode.FunctionCall call =
|
||||
new FunctionCallInstrumentationNode.FunctionCall(
|
||||
function, context.getBuiltins().unit(), new Object[] {atomConstructor.newInstance()});
|
||||
function, EmptyMap.create(), new Object[] {atomConstructor.newInstance()});
|
||||
return Optional.of(call);
|
||||
}
|
||||
|
||||
@ -86,18 +87,20 @@ public class ExecutionService {
|
||||
* @param cache the precomputed expression values.
|
||||
* @param methodCallsCache the storage tracking the executed method calls.
|
||||
* @param nextExecutionItem the next item scheduled for execution.
|
||||
* @param funCallCallback the consumer for function call events.
|
||||
* @param onComputedCallback the consumer of the computed value events.
|
||||
* @param onCachedCallback the consumer of the cached value events.
|
||||
* @param funCallCallback the consumer for function call events.
|
||||
* @param onExceptionalCallback the consumer of the exceptional events.
|
||||
*/
|
||||
public void execute(
|
||||
FunctionCallInstrumentationNode.FunctionCall call,
|
||||
RuntimeCache cache,
|
||||
MethodCallsCache methodCallsCache,
|
||||
UUID nextExecutionItem,
|
||||
Consumer<IdExecutionInstrument.ExpressionCall> funCallCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onComputedCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onCachedCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionCall> funCallCallback)
|
||||
Consumer<Throwable> onExceptionalCallback)
|
||||
throws UnsupportedMessageException, ArityException, UnsupportedTypeException {
|
||||
|
||||
SourceSection src = call.getFunction().getSourceSection();
|
||||
@ -112,9 +115,10 @@ public class ExecutionService {
|
||||
cache,
|
||||
methodCallsCache,
|
||||
nextExecutionItem,
|
||||
funCallCallback,
|
||||
onComputedCallback,
|
||||
onCachedCallback,
|
||||
funCallCallback);
|
||||
onExceptionalCallback);
|
||||
interopLibrary.execute(call);
|
||||
listener.dispose();
|
||||
}
|
||||
@ -129,9 +133,10 @@ public class ExecutionService {
|
||||
* @param cache the precomputed expression values.
|
||||
* @param methodCallsCache the storage tracking the executed method calls.
|
||||
* @param nextExecutionItem the next item scheduled for execution.
|
||||
* @param funCallCallback the consumer for function call events.
|
||||
* @param onComputedCallback the consumer of the computed value events.
|
||||
* @param onCachedCallback the consumer of the cached value events.
|
||||
* @param funCallCallback the consumer for function call events.
|
||||
* @param onExceptionalCallback the consumer of the exceptional events.
|
||||
*/
|
||||
public void execute(
|
||||
String moduleName,
|
||||
@ -140,9 +145,10 @@ public class ExecutionService {
|
||||
RuntimeCache cache,
|
||||
MethodCallsCache methodCallsCache,
|
||||
UUID nextExecutionItem,
|
||||
Consumer<IdExecutionInstrument.ExpressionCall> funCallCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onComputedCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onCachedCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionCall> funCallCallback)
|
||||
Consumer<Throwable> onExceptionalCallback)
|
||||
throws UnsupportedMessageException, ArityException, UnsupportedTypeException {
|
||||
Optional<FunctionCallInstrumentationNode.FunctionCall> callMay =
|
||||
context
|
||||
@ -156,9 +162,10 @@ public class ExecutionService {
|
||||
cache,
|
||||
methodCallsCache,
|
||||
nextExecutionItem,
|
||||
funCallCallback,
|
||||
onComputedCallback,
|
||||
onCachedCallback,
|
||||
funCallCallback);
|
||||
onExceptionalCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,6 +5,8 @@ import java.util.function.Consumer
|
||||
import java.util.logging.Level
|
||||
|
||||
import cats.implicits._
|
||||
import com.oracle.truffle.api.TruffleException
|
||||
import com.oracle.truffle.api.interop.InteropException
|
||||
import org.enso.interpreter.instrument.IdExecutionInstrument.{
|
||||
ExpressionCall,
|
||||
ExpressionValue
|
||||
@ -39,6 +41,7 @@ trait ProgramExecutionSupport {
|
||||
* @param cachedMethodCallsCallback a listener for cached method calls
|
||||
* @param onComputedCallback a listener of computed values
|
||||
* @param onCachedCallback a listener of cached values
|
||||
* @param onExceptionalCallback the consumer of the exceptional events.
|
||||
*/
|
||||
@scala.annotation.tailrec
|
||||
final private def executeProgram(
|
||||
@ -46,7 +49,8 @@ trait ProgramExecutionSupport {
|
||||
callStack: List[LocalCallFrame],
|
||||
cachedMethodCallsCallback: Consumer[ExpressionValue],
|
||||
onComputedCallback: Consumer[ExpressionValue],
|
||||
onCachedCallback: Consumer[ExpressionValue]
|
||||
onCachedCallback: Consumer[ExpressionValue],
|
||||
onExceptionalCallback: Consumer[Throwable]
|
||||
)(implicit ctx: RuntimeContext): Unit = {
|
||||
val methodCallsCache = new MethodCallsCache
|
||||
var enterables = Map[UUID, FunctionCall]()
|
||||
@ -68,9 +72,10 @@ trait ProgramExecutionSupport {
|
||||
cache,
|
||||
methodCallsCache,
|
||||
callStack.headOption.map(_.expressionId).orNull,
|
||||
callablesCallback,
|
||||
computedCallback,
|
||||
onCachedCallback,
|
||||
callablesCallback
|
||||
onExceptionalCallback
|
||||
)
|
||||
case ExecutionFrame(ExecutionItem.CallData(callData), cache) =>
|
||||
ctx.executionService.execute(
|
||||
@ -78,9 +83,10 @@ trait ProgramExecutionSupport {
|
||||
cache,
|
||||
methodCallsCache,
|
||||
callStack.headOption.map(_.expressionId).orNull,
|
||||
callablesCallback,
|
||||
computedCallback,
|
||||
onCachedCallback,
|
||||
callablesCallback
|
||||
onExceptionalCallback
|
||||
)
|
||||
}
|
||||
|
||||
@ -108,7 +114,8 @@ trait ProgramExecutionSupport {
|
||||
tail,
|
||||
cachedMethodCallsCallback,
|
||||
onComputedCallback,
|
||||
onCachedCallback
|
||||
onCachedCallback,
|
||||
onExceptionalCallback
|
||||
)
|
||||
case None =>
|
||||
()
|
||||
@ -171,9 +178,14 @@ trait ProgramExecutionSupport {
|
||||
fireVisualisationUpdates(contextId, value)
|
||||
}
|
||||
|
||||
val onExceptionalCallback: Consumer[Throwable] = { value =>
|
||||
ctx.executionService.getLogger.finer(s"ON_ERROR $value")
|
||||
sendErrorUpdate(contextId, value)
|
||||
}
|
||||
|
||||
val (explicitCallOpt, localCalls) = unwind(stack, Nil, Nil)
|
||||
for {
|
||||
stackItem <- Either.fromOption(explicitCallOpt, "stack is empty")
|
||||
stackItem <- Either.fromOption(explicitCallOpt, "Stack is empty.")
|
||||
_ <-
|
||||
Either
|
||||
.catchNonFatal(
|
||||
@ -182,20 +194,38 @@ trait ProgramExecutionSupport {
|
||||
localCalls,
|
||||
cachedMethodCallsCallback,
|
||||
onComputedValueCallback,
|
||||
onCachedValueCallback
|
||||
onCachedValueCallback,
|
||||
onExceptionalCallback
|
||||
)
|
||||
)
|
||||
.leftMap { ex =>
|
||||
ctx.executionService.getLogger.log(
|
||||
Level.FINE,
|
||||
s"Error executing a function '${getName(stackItem.item)}'",
|
||||
s"Error executing a function '${getName(stackItem.item)}.'",
|
||||
ex
|
||||
)
|
||||
s"error in function: ${getName(stackItem.item)}"
|
||||
getErrorMessage(ex)
|
||||
.getOrElse(s"Error in function ${getName(stackItem.item)}.")
|
||||
}
|
||||
} yield ()
|
||||
}
|
||||
|
||||
/** Get error message from throwable. */
|
||||
private def getErrorMessage(t: Throwable): Option[String] =
|
||||
t match {
|
||||
case ex: TruffleException => Some(ex.getMessage)
|
||||
case ex: InteropException => Some(ex.getMessage)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
private def sendErrorUpdate(contextId: ContextId, error: Throwable)(implicit
|
||||
ctx: RuntimeContext
|
||||
): Unit = {
|
||||
ctx.endpoint.sendToClient(
|
||||
Api.Response(Api.ExecutionFailed(contextId, error.getMessage))
|
||||
)
|
||||
}
|
||||
|
||||
private def sendMethodPointerUpdate(
|
||||
contextId: ContextId,
|
||||
value: ExpressionValue
|
||||
|
@ -251,19 +251,6 @@ class RuntimeServerTest
|
||||
}
|
||||
}
|
||||
|
||||
object MainWithError {
|
||||
|
||||
val metadata = new Metadata
|
||||
|
||||
val idMain = metadata.addItem(8, 6)
|
||||
|
||||
val code = metadata.appendToCode(
|
||||
"""
|
||||
|main = 1 + 2L
|
||||
|""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
object Visualisation {
|
||||
|
||||
val code =
|
||||
@ -378,19 +365,20 @@ class RuntimeServerTest
|
||||
)
|
||||
}
|
||||
|
||||
it should "send updates when the type is changed" in {
|
||||
it should "send updates from last line" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
|
||||
val metadata = new Metadata
|
||||
val idResult = metadata.addItem(20, 4)
|
||||
val idPrintln = metadata.addItem(29, 17)
|
||||
val idMain = metadata.addItem(6, 40)
|
||||
val idMain = metadata.addItem(23, 17)
|
||||
val idMainFoo = metadata.addItem(28, 12)
|
||||
|
||||
val code =
|
||||
"""main =
|
||||
| result = 1337
|
||||
| IO.println result
|
||||
"""foo a b = a + b
|
||||
|
|
||||
|main =
|
||||
| this.foo 1 2
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
val mainFile = context.writeMain(contents)
|
||||
@ -421,18 +409,218 @@ class RuntimeServerTest
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(6) should contain theSameElementsAs Seq(
|
||||
context.receive(4) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idResult, Some("Number"), None))
|
||||
Vector(
|
||||
Api.ExpressionValueUpdate(
|
||||
idMainFoo,
|
||||
Some("Number"),
|
||||
Some(Api.MethodPointer(moduleName, "Main", "foo"))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idPrintln, Some("Unit"), None))
|
||||
Vector(Api.ExpressionValueUpdate(idMain, Some("Number"), None))
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.SuggestionsDatabaseReIndexNotification(
|
||||
moduleName,
|
||||
Seq(
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
None,
|
||||
moduleName,
|
||||
"foo",
|
||||
Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None),
|
||||
Suggestion.Argument("a", "Any", false, false, None),
|
||||
Suggestion.Argument("b", "Any", false, false, None)
|
||||
),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
),
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
Some(idMain),
|
||||
moduleName,
|
||||
"main",
|
||||
List(Suggestion.Argument("this", "Any", false, false, None)),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
it should "compute side effects correctly from last line" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
|
||||
val metadata = new Metadata
|
||||
val idMain = metadata.addItem(23, 30)
|
||||
val idMainFoo = metadata.addItem(40, 12)
|
||||
|
||||
val code =
|
||||
"""foo a b = a + b
|
||||
|
|
||||
|main =
|
||||
| IO.println (this.foo 1 2)
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
val mainFile = context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// open file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, contents, false))
|
||||
)
|
||||
context.receive shouldEqual None
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(5) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(
|
||||
Api.ExpressionValueUpdate(
|
||||
idMainFoo,
|
||||
Some("Number"),
|
||||
Some(Api.MethodPointer(moduleName, "Main", "foo"))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idMain, Some("Unit"), None))
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.SuggestionsDatabaseReIndexNotification(
|
||||
moduleName,
|
||||
Seq(
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
None,
|
||||
moduleName,
|
||||
"foo",
|
||||
Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None),
|
||||
Suggestion.Argument("a", "Any", false, false, None),
|
||||
Suggestion.Argument("b", "Any", false, false, None)
|
||||
),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
),
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
Some(idMain),
|
||||
moduleName,
|
||||
"main",
|
||||
List(Suggestion.Argument("this", "Any", false, false, None)),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.consumeOut shouldEqual List("3")
|
||||
}
|
||||
|
||||
it should "Run State getting the initial state" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
|
||||
val metadata = new Metadata
|
||||
val idMain = metadata.addItem(7, 41)
|
||||
val idMainBar = metadata.addItem(39, 8)
|
||||
|
||||
val code =
|
||||
"""main = IO.println (State.run Number 42 this.bar)
|
||||
|
|
||||
|bar = State.get Number
|
||||
|""".stripMargin
|
||||
val contents = metadata.appendToCode(code)
|
||||
val mainFile = context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// open file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, contents, false))
|
||||
)
|
||||
context.receive shouldEqual None
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(4) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(
|
||||
Api.ExpressionValueUpdate(
|
||||
idMainBar,
|
||||
Some("Number"),
|
||||
Some(Api.MethodPointer(moduleName, "Main", "bar"))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
@ -457,46 +645,220 @@ class RuntimeServerTest
|
||||
)
|
||||
),
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Local(
|
||||
Some(idResult),
|
||||
Suggestion.Method(
|
||||
None,
|
||||
moduleName,
|
||||
"result",
|
||||
"bar",
|
||||
Seq(Suggestion.Argument("this", "Any", false, false, None)),
|
||||
"here",
|
||||
"Any",
|
||||
Suggestion.Scope(
|
||||
Suggestion.Position(0, 6),
|
||||
Suggestion.Position(2, 21)
|
||||
)
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.consumeOut shouldEqual List("1337")
|
||||
context.consumeOut shouldEqual List("42")
|
||||
}
|
||||
|
||||
// Modify the file
|
||||
it should "Run State setting the state" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
|
||||
val metadata = new Metadata
|
||||
val idMain = metadata.addItem(7, 40)
|
||||
val idMainBar = metadata.addItem(38, 8)
|
||||
|
||||
val code =
|
||||
"""main = IO.println (State.run Number 0 this.bar)
|
||||
|
|
||||
|bar =
|
||||
| State.put Number 10
|
||||
| State.get Number
|
||||
|""".stripMargin
|
||||
val contents = metadata.appendToCode(code)
|
||||
val mainFile = context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// open file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, contents, false))
|
||||
)
|
||||
context.receive shouldEqual None
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
Api.EditFileNotification(
|
||||
mainFile,
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(4) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(
|
||||
Api.ExpressionValueUpdate(
|
||||
idMainBar,
|
||||
Some("Number"),
|
||||
Some(Api.MethodPointer(moduleName, "Main", "bar"))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idMain, Some("Unit"), None))
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.SuggestionsDatabaseReIndexNotification(
|
||||
moduleName,
|
||||
Seq(
|
||||
TextEdit(
|
||||
model.Range(model.Position(1, 13), model.Position(1, 17)),
|
||||
"\"Hi\""
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
Some(idMain),
|
||||
moduleName,
|
||||
"main",
|
||||
Seq(Suggestion.Argument("this", "Any", false, false, None)),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
),
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
None,
|
||||
moduleName,
|
||||
"bar",
|
||||
Seq(Suggestion.Argument("this", "Any", false, false, None)),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(2) should contain theSameElementsAs Seq(
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
context.consumeOut shouldEqual List("10")
|
||||
}
|
||||
|
||||
it should "send updates of a function call" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
|
||||
val metadata = new Metadata
|
||||
val idMain = metadata.addItem(23, 23)
|
||||
val idMainFoo = metadata.addItem(28, 12)
|
||||
|
||||
val code =
|
||||
"""foo a b = a + b
|
||||
|
|
||||
|main =
|
||||
| this.foo 1 2
|
||||
| 1
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
val mainFile = context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// open file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, contents, false))
|
||||
)
|
||||
context.receive shouldEqual None
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idResult, Some("Text"), None))
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(4) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(
|
||||
Api.ExpressionValueUpdate(
|
||||
idMainFoo,
|
||||
Some("Number"),
|
||||
Some(Api.MethodPointer(moduleName, "Main", "foo"))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idMain, Some("Number"), None))
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.SuggestionsDatabaseReIndexNotification(
|
||||
moduleName,
|
||||
Seq(
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
None,
|
||||
moduleName,
|
||||
"foo",
|
||||
Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None),
|
||||
Suggestion.Argument("a", "Any", false, false, None),
|
||||
Suggestion.Argument("b", "Any", false, false, None)
|
||||
),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
),
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
Some(idMain),
|
||||
moduleName,
|
||||
"main",
|
||||
List(Suggestion.Argument("this", "Any", false, false, None)),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.consumeOut shouldEqual List("Hi")
|
||||
}
|
||||
|
||||
it should "not send updates when the type is not changed" in {
|
||||
@ -668,6 +1030,127 @@ class RuntimeServerTest
|
||||
)
|
||||
}
|
||||
|
||||
it should "send updates when the type is changed" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
|
||||
val metadata = new Metadata
|
||||
val idResult = metadata.addItem(20, 4)
|
||||
val idPrintln = metadata.addItem(29, 17)
|
||||
val idMain = metadata.addItem(6, 40)
|
||||
val code =
|
||||
"""main =
|
||||
| result = 1337
|
||||
| IO.println result
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
val mainFile = context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// open file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, contents, false))
|
||||
)
|
||||
context.receive shouldEqual None
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(6) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idResult, Some("Number"), None))
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idPrintln, Some("Unit"), None))
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idMain, Some("Unit"), None))
|
||||
)
|
||||
),
|
||||
Api.Response(
|
||||
Api.SuggestionsDatabaseReIndexNotification(
|
||||
moduleName,
|
||||
Seq(
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Method(
|
||||
Some(idMain),
|
||||
moduleName,
|
||||
"main",
|
||||
Seq(Suggestion.Argument("this", "Any", false, false, None)),
|
||||
"here",
|
||||
"Any",
|
||||
None
|
||||
)
|
||||
),
|
||||
Api.SuggestionsDatabaseUpdate.Add(
|
||||
Suggestion.Local(
|
||||
Some(idResult),
|
||||
moduleName,
|
||||
"result",
|
||||
"Any",
|
||||
Suggestion.Scope(
|
||||
Suggestion.Position(0, 6),
|
||||
Suggestion.Position(2, 21)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.consumeOut shouldEqual List("1337")
|
||||
|
||||
// Modify the file
|
||||
context.send(
|
||||
Api.Request(
|
||||
Api.EditFileNotification(
|
||||
mainFile,
|
||||
Seq(
|
||||
TextEdit(
|
||||
model.Range(model.Position(1, 13), model.Position(1, 17)),
|
||||
"\"Hi\""
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(2) should contain theSameElementsAs Seq(
|
||||
Api.Response(
|
||||
Api.ExpressionValuesComputed(
|
||||
contextId,
|
||||
Vector(Api.ExpressionValueUpdate(idResult, Some("Text"), None))
|
||||
)
|
||||
)
|
||||
)
|
||||
context.consumeOut shouldEqual List("Hi")
|
||||
}
|
||||
|
||||
it should "send updates when the method pointer is changed" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
@ -1562,10 +2045,18 @@ class RuntimeServerTest
|
||||
)
|
||||
}
|
||||
|
||||
it should "return error when computing erroneous code" in {
|
||||
context.writeMain(context.MainWithError.code)
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
it should "return error not invocable" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
val metadata = new Metadata
|
||||
val code =
|
||||
"""main = this.bar 40 2 9
|
||||
|
|
||||
|bar x y = x + y
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
@ -1574,26 +2065,148 @@ class RuntimeServerTest
|
||||
)
|
||||
|
||||
// push main
|
||||
val item1 = Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer("Test.Main", "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
context.send(
|
||||
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(2) should contain theSameElementsAs Seq(
|
||||
context.receive(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(Api.ExecutionFailed(contextId, "error in function: main"))
|
||||
Api.Response(
|
||||
Api.ExecutionFailed(contextId, "Object 42 is not invokable.")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
it should "return error in function main" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
val metadata = new Metadata
|
||||
val code =
|
||||
"""main = this.bar x y
|
||||
|
|
||||
|bar x y = x + y
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// recompute
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(requestId, Api.RecomputeContextRequest(contextId, None))
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(2) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.RecomputeContextResponse(contextId)),
|
||||
Api.Response(Api.ExecutionFailed(contextId, "error in function: main"))
|
||||
context.receive(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(Api.ExecutionFailed(contextId, "Error in function main."))
|
||||
)
|
||||
}
|
||||
|
||||
it should "return error unexpected type" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
val metadata = new Metadata
|
||||
val code =
|
||||
"""main = this.bar "one" 2
|
||||
|
|
||||
|bar x y = x + y
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExecutionFailed(
|
||||
contextId,
|
||||
"Unexpected type provided for argument `that` in Text.+"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
it should "return error method does not exist" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Test.Main"
|
||||
val metadata = new Metadata
|
||||
|
||||
val code = "main = Number.pi"
|
||||
val contents = metadata.appendToCode(code)
|
||||
context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receive(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExecutionFailed(
|
||||
contextId,
|
||||
"Object Number does not define method pi."
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -2107,7 +2720,7 @@ class RuntimeServerTest
|
||||
)
|
||||
context.receive(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
Api.Response(Api.ExecutionFailed(contextId, "stack is empty"))
|
||||
Api.Response(Api.ExecutionFailed(contextId, "Stack is empty."))
|
||||
)
|
||||
// push main
|
||||
val item1 = Api.StackItem.ExplicitCall(
|
||||
|
Loading…
Reference in New Issue
Block a user