From 152873d3f9244155199764822b9a1a0be379eb8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 15 Feb 2021 18:41:54 +0100 Subject: [PATCH] Add Stack Traces in Tests (#1482) Co-authored-by: Ara Adkins --- distribution/std-lib/Test/src/Test.enso | 4 +- .../instrument/ReplDebuggerInstrument.java | 2 +- .../expression/builtin/bool/IfThenNode.java | 3 +- .../builtin/debug/DebugBreakpointNode.java | 2 +- .../builtin/error/GetStackTraceTextNode.java | 82 +++++++++++++++++++ .../expression/builtin/io/PrintErrNode.java | 6 +- .../expression/builtin/io/PrintlnNode.java | 6 +- .../node/scope/AssignmentNode.java | 4 +- .../org/enso/interpreter/runtime/Context.java | 6 +- .../runtime/builtin/DataflowError.java | 2 + .../runtime/scope/TopLevelScope.java | 2 +- .../runtime/src/main/resources/Builtins.enso | 8 +- 12 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java diff --git a/distribution/std-lib/Test/src/Test.enso b/distribution/std-lib/Test/src/Test.enso index 5625e0c74f7e..f2654926a718 100644 --- a/distribution/std-lib/Test/src/Test.enso +++ b/distribution/std-lib/Test/src/Test.enso @@ -164,8 +164,8 @@ run_spec ~behavior = case ex of Failure _ -> ex Finished_With_Error x -> - Failure ("An unexpected error was returned: " + x.to_text) - _ -> Failure ("An unexpected panic was thrown: " + ex.to_text) + Failure ("An unexpected error was returned: " + x.to_text + '\n' + maybeExc.get_stack_trace_text) + _ -> Failure ("An unexpected panic was thrown: " + ex.to_text + '\n' + maybeExc.get_stack_trace_text) result ## Creates a new test group, desribing properties of the object diff --git a/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java b/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java index 6c8ae05b0862..4e9f44da002a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java @@ -184,7 +184,7 @@ public void exit() { @Override protected void onEnter(VirtualFrame frame) { CallerInfo lastScope = Function.ArgumentsHelper.getCallerInfo(frame.getArguments()); - Object lastReturn = lookupContextReference(Language.class).get().getUnit().newInstance(); + Object lastReturn = lookupContextReference(Language.class).get().getNothing().newInstance(); // Note [Safe Access to State in the Debugger Instrument] Object lastState = Function.ArgumentsHelper.getState(frame.getArguments()); nodeState = new ReplExecutionEventNodeState(lastReturn, lastState, lastScope); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenNode.java index e7d608165c42..c1429f871409 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/bool/IfThenNode.java @@ -11,7 +11,6 @@ import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.callable.argument.Thunk; import org.enso.interpreter.runtime.state.Stateful; @BuiltinMethod( @@ -34,7 +33,7 @@ Stateful doExecute( if (condProfile.profile(_this)) { return leftThunkExecutorNode.executeThunk(if_true, state, BaseNode.TailStatus.TAIL_DIRECT); } else { - return new Stateful(state, context.getUnit().newInstance()); + return new Stateful(state, context.getNothing().newInstance()); } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugBreakpointNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugBreakpointNode.java index d053a765a48c..7c52061f9355 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugBreakpointNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/debug/DebugBreakpointNode.java @@ -48,7 +48,7 @@ Stateful doExecute( Object state, Object _this, @CachedContext(Language.class) Context context) { - return new Stateful(state, context.getUnit().newInstance()); + return new Stateful(state, context.getNothing().newInstance()); } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java new file mode 100644 index 000000000000..05bce7c6e9f2 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetStackTraceTextNode.java @@ -0,0 +1,82 @@ +package org.enso.interpreter.node.expression.builtin.error; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.TruffleStackTrace; +import com.oracle.truffle.api.TruffleStackTraceElement; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import java.util.ArrayList; +import java.util.Collections; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.DataflowError; + +@BuiltinMethod( + type = "Error", + name = "get_stack_trace_text", + description = "Returns a textual representation of the stack trace attached to an error.") +public class GetStackTraceTextNode extends Node { + + Text execute(DataflowError _this) { + String repr = printStackTrace(_this); + return Text.create(repr); + } + + @TruffleBoundary + String printStackTrace(Throwable throwable) { + var sb = new StringBuilder(); + var truffleStack = TruffleStackTrace.getStackTrace(throwable); + if (truffleStack == null) { + sb.append("The error has no stacktrace attached to it.\n"); + } else { + var fullStack = new ArrayList<>(truffleStack); + var dropInitJava = new ArrayList(); + boolean isInit = true; + for (int i = fullStack.size() - 1; i >= 0; i--) { + var elem = fullStack.get(i); + if (isInit) { + if (elem.getLocation().getRootNode().getLanguageInfo() != null) { + isInit = false; + } + } + + if (!isInit) { + dropInitJava.add(elem); + } + } + Collections.reverse(dropInitJava); + var stack = dropInitJava; + if (dropInitJava.isEmpty()) { + stack = fullStack; + } + + boolean first = true; + for (var errorFrame : stack) { + var rootNode = errorFrame.getLocation().getRootNode(); + var languageInfo = rootNode.getLanguageInfo(); + var langId = (languageInfo == null) ? "java" : languageInfo.getId(); + var fName = rootNode.getName(); + var src = "Internal"; + var sourceLoc = errorFrame.getLocation().getEncapsulatingSourceSection(); + if (sourceLoc != null) { + var path = sourceLoc.getSource().getPath(); + var ident = (path != null) ? path : sourceLoc.getSource().getName(); + var loc = (sourceLoc.getStartLine() == sourceLoc.getEndLine()) ? + (sourceLoc.getStartLine() + ":" + sourceLoc.getStartColumn() + "-" + sourceLoc.getEndColumn()) : + (sourceLoc.getStartLine() + "-" + sourceLoc.getEndLine()); + src = ident + ":" + loc; + } + if (first) { + first = false; + } else { + sb.append('\n'); + } + sb.append(" at <" + langId + "> " + fName + "(" + src + ")"); + } + } + return sb.toString(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java index e1a256796d63..573cc19aa792 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintErrNode.java @@ -16,11 +16,9 @@ import org.enso.interpreter.dsl.MonadicState; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; -import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; -import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.type.TypesGen; @@ -49,7 +47,7 @@ Stateful doPrintText( } catch (UnsupportedMessageException e) { throw new IllegalStateException("Impossible. self is guaranteed to be a string"); } - return new Stateful(state, ctx.getUnit().newInstance()); + return new Stateful(state, ctx.getNothing().newInstance()); } @Specialization(guards = "!strings.isString(message)") @@ -65,7 +63,7 @@ Stateful doPrint( @Cached ExpectStringNode expectStringNode) { Stateful str = invokeCallableNode.execute(symbol, frame, state, new Object[] {message}); print(ctx.getErr(), expectStringNode.execute(str.getValue())); - return new Stateful(str.getState(), ctx.getUnit().newInstance()); + return new Stateful(str.getState(), ctx.getNothing().newInstance()); } @CompilerDirectives.TruffleBoundary diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java index 268c64f93491..587fd0deb7e6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/io/PrintlnNode.java @@ -16,11 +16,9 @@ import org.enso.interpreter.dsl.MonadicState; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; -import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; -import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.type.TypesGen; @@ -48,7 +46,7 @@ Stateful doPrintText( } catch (UnsupportedMessageException e) { throw new IllegalStateException("Impossible. self is guaranteed to be a string"); } - return new Stateful(state, ctx.getUnit().newInstance()); + return new Stateful(state, ctx.getNothing().newInstance()); } @Specialization(guards = "!strings.isString(message)") @@ -64,7 +62,7 @@ Stateful doPrint( @Cached ExpectStringNode expectStringNode) { Stateful str = invokeCallableNode.execute(symbol, frame, state, new Object[] {message}); print(ctx.getOut(), expectStringNode.execute(str.getValue())); - return new Stateful(str.getState(), ctx.getUnit().newInstance()); + return new Stateful(str.getState(), ctx.getNothing().newInstance()); } boolean isText(Object o) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/scope/AssignmentNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/scope/AssignmentNode.java index 3fed740c8c14..a514aaf6a713 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/scope/AssignmentNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/scope/AssignmentNode.java @@ -44,7 +44,7 @@ protected Object writeLong( frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Long); frame.setLong(getFrameSlot(), value); - return ctx.getUnit().newInstance(); + return ctx.getNothing().newInstance(); } /** @@ -61,7 +61,7 @@ protected Object writeObject( frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Object); frame.setObject(getFrameSlot(), value); - return ctx.getUnit().newInstance(); + return ctx.getNothing().newInstance(); } boolean isLongOrIllegal(VirtualFrame frame) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java index e5e16dfdce7b..2c474c6b132e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java @@ -288,12 +288,12 @@ public TopLevelScope getTopScope() { } /** - * Returns the atom constructor corresponding to the {@code Unit} type, for builtin constructs + * Returns the atom constructor corresponding to the {@code Nothing} type, for builtin constructs * that need to return an atom of this type. * - * @return the builtin {@code Unit} atom constructor + * @return the builtin {@code Nothing} atom constructor */ - public AtomConstructor getUnit() { + public AtomConstructor getNothing() { return getBuiltins().nothing(); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/DataflowError.java index fd5c12d94509..30c584de1a66 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/DataflowError.java @@ -3,6 +3,7 @@ import org.enso.interpreter.Language; import org.enso.interpreter.node.expression.builtin.error.CatchErrorMethodGen; import org.enso.interpreter.node.expression.builtin.error.ErrorToTextMethodGen; +import org.enso.interpreter.node.expression.builtin.error.GetStackTraceTextMethodGen; import org.enso.interpreter.node.expression.builtin.error.ThrowErrorMethodGen; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.scope.ModuleScope; @@ -16,6 +17,7 @@ public DataflowError(Language language, ModuleScope scope) { scope.registerConstructor(error); scope.registerMethod(error, "throw", ThrowErrorMethodGen.makeFunction(language)); scope.registerMethod(error, "catch_primitive", CatchErrorMethodGen.makeFunction(language)); + scope.registerMethod(error, "get_stack_trace_text", GetStackTraceTextMethodGen.makeFunction(language)); scope.registerMethod(error, "to_text", ErrorToTextMethodGen.makeFunction(language)); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java index 0c07d450877f..82a4c54be10c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java @@ -180,7 +180,7 @@ private static Object unregisterModule(TopLevelScope scope, Object[] arguments, throws ArityException, UnsupportedTypeException { String name = Types.extractArguments(arguments, String.class); scope.modules.remove(name); - return context.getUnit().newInstance(); + return context.getNothing().newInstance(); } private static Object leakContext(Context context) { diff --git a/engine/runtime/src/main/resources/Builtins.enso b/engine/runtime/src/main/resources/Builtins.enso index 252a01631701..d22edc1974bd 100644 --- a/engine/runtime/src/main/resources/Builtins.enso +++ b/engine/runtime/src/main/resources/Builtins.enso @@ -224,6 +224,13 @@ type Error catch_primitive : (Error -> Any) -> Any catch_primitive handler = @Builtin_Method "Any.catch" + ## PRIVATE + UNSTABLE + + Returns a textual representation of the stack trace attached to an error. + get_stack_trace_text : Text + get_stack_trace_text = @Builtin_Method "Error.get_stack_trace_text" + ## Converts an error to a corresponding textual representation. to_text : Text to_text = @Builtin_Method "Error.to_text" @@ -1792,4 +1799,3 @@ type Ordering ## A representation that the first value orders as greater than the second. @Builtin_Type type Greater -