Skip to content

Commit

Permalink
Add Stack Traces in Tests (#1482)
Browse files Browse the repository at this point in the history
Co-authored-by: Ara Adkins <[email protected]>
  • Loading branch information
radeusgd and iamrecursion committed Feb 23, 2021
1 parent bf2f0ac commit 152873d
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 21 deletions.
4 changes: 2 additions & 2 deletions distribution/std-lib/Test/src/Test.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TruffleStackTraceElement>();
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)")
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)")
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/**
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion engine/runtime/src/main/resources/Builtins.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -1792,4 +1799,3 @@ type Ordering
## A representation that the first value orders as greater than the second.
@Builtin_Type
type Greater

0 comments on commit 152873d

Please sign in to comment.