diff --git a/CHANGELOG.md b/CHANGELOG.md index 8738294ecbd5..a4c500fe401a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1010,6 +1010,7 @@ - [Export of non-existing symbols results in error][7960] - [Upgrade GraalVM to 23.1.0 JDK21][7991] - [Added opt-in type checks of return type][8502] +- [DataflowError.withoutTrace doesn't store stacktrace][8608] - [Added text_length to Column][8606] [3227]: https://github.com/enso-org/enso/pull/3227 @@ -1162,6 +1163,7 @@ [7960]: https://github.com/enso-org/enso/pull/7960 [7991]: https://github.com/enso-org/enso/pull/7991 [8502]: https://github.com/enso-org/enso/pull/8502 +[8608]: https://github.com/enso-org/enso/pull/8608 [8606]: https://github.com/enso-org/enso/pull/8606 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso index c37620bd2876..9ed9538b1693 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso @@ -20,4 +20,4 @@ type Time_Error Provides a human-readable representation of the time error. to_display_text : Text - to_display_text self = "Time_Error: " + self.error_message + to_display_text self = "Time_Error: " + self.error_message.to_text diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso index fa80d51557a1..c16d09ac0bf8 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso @@ -33,9 +33,8 @@ primitive_get_stack_trace = @Builtin_Method "Runtime.primitive_get_stack_trace" get_stack_trace : Vector Stack_Trace_Element get_stack_trace = prim_stack = primitive_get_stack_trace - stack_with_prims = Vector.from_polyglot_array prim_stack - # (First 2) drops the `Runtime.primitive_get_stack_trace` frame and this one - stack = stack_with_prims.drop (First 2) + stack_with_own_frame = Vector.from_polyglot_array prim_stack + stack = stack_with_own_frame.drop (First 1) stack.map wrap_primitive_stack_trace_element ## PRIVATE diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso index 391bac96df31..cb31d50c845e 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso @@ -254,7 +254,7 @@ type Invalid_Location Pretty print the invalid location error. to_display_text : Text to_display_text self = - "The location '"+self.location+"' is not valid." + "The location '"+self.location.to_text+"' is not valid." ## Indicates that some values did not match the expected datatype format. diff --git a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Preprocessor.enso b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Preprocessor.enso index 4a52804b0555..5a73a9fd54c4 100644 --- a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Preprocessor.enso +++ b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Preprocessor.enso @@ -24,7 +24,7 @@ error_preprocessor x = message = err.to_display_text stack_trace = x.get_stack_trace_text.if_nothing "" . split '\n' truncated_message = Helpers.truncate message - full_message = truncated_message + if stack_trace.length > 1 then " (" + stack_trace.at 1 . trim +")" else "" + full_message = truncated_message + if stack_trace.length > 0 then " (" + stack_trace.at 0 . trim +")" else "" JS_Object.from_pairs [['kind', 'Dataflow'], ['message', full_message]] . to_json if result.is_error then result.catch else ok diff --git a/docs/types/errors.md b/docs/types/errors.md index 4df9d166f56b..6bbbdb6bf2ef 100644 --- a/docs/types/errors.md +++ b/docs/types/errors.md @@ -68,3 +68,15 @@ with an automatic propagation mechanism: > - Ensure that we are okay with initially designing everything around async > exceptions as broken values are very hard to support without a type checker. > - Initially not supported for APIs. + +Broken values (implemented as `DataflowError` class in the interpreter) are fast +to allocate and pass around the program. They record line of their own +creation - e.g. where `Error.throw` has happened. Shall that not be enough, one +can run with `-ea` flag, like: + +```bash +enso$ JAVA_OPTS=-ea ./built-distribution/enso-engine-*/enso-*/bin/enso --run x.enso +``` + +to get full stack where the _broken value_ has been created. Collecting such +full stack trace however prevents the execution to run at _full speed_. diff --git a/engine/runner/src/main/scala/org/enso/runner/Main.scala b/engine/runner/src/main/scala/org/enso/runner/Main.scala index 3f9af6be3712..88474caf8888 100644 --- a/engine/runner/src/main/scala/org/enso/runner/Main.scala +++ b/engine/runner/src/main/scala/org/enso/runner/Main.scala @@ -867,7 +867,12 @@ object Main { ) val res = main.execute(parsedArgs: _*) if (!res.isNull) { - out.println(res); + val textRes = if (res.isString) { + res.asString + } else { + res.toString + } + out.println(textRes); } case None => err.println( diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ErrorResolver.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ErrorResolver.scala index 86357b3d29aa..c1f5573656d6 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ErrorResolver.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ErrorResolver.scala @@ -1,10 +1,10 @@ package org.enso.interpreter.instrument.execution -import com.oracle.truffle.api.{TruffleStackTrace, TruffleStackTraceElement} +import com.oracle.truffle.api.interop.InteropLibrary import org.enso.polyglot.runtime.Runtime.Api +import org.enso.interpreter.node.expression.builtin.runtime.GetStackTraceNode import java.io.File -import scala.jdk.CollectionConverters._ import scala.jdk.OptionConverters.RichOptional /** Methods for handling exceptions in the interpreter. */ @@ -19,42 +19,50 @@ object ErrorResolver { def getStackTrace( throwable: Throwable )(implicit ctx: RuntimeContext): Vector[Api.StackTraceElement] = { - TruffleStackTrace - .getStackTrace(throwable) - .asScala - .flatMap(toStackElement) - .toVector + val iop = InteropLibrary.getUncached + val arr = GetStackTraceNode.stackTraceToArray(iop, throwable) + val len = iop.getArraySize(arr) + val stackWithOptions = for (i <- 0L until len) yield { + val elem = iop.readArrayElement(arr, i) + toStackElement(iop, elem) + } + val stack = stackWithOptions.map(op => op.get) + stack.toVector } /** Convert from the truffle stack element to the runtime API representation. * + * @param iop interop library to use * @param element the trufle stack trace element * @param ctx the runtime context * @return the runtime API representation of the stack trace element */ private def toStackElement( - element: TruffleStackTraceElement + iop: InteropLibrary, + element: Any )(implicit ctx: RuntimeContext): Option[Api.StackTraceElement] = { - val node = Option(element.getLocation) - node.flatMap(x => { - x.getEncapsulatingSourceSection match { - case null if x.getRootNode == null => + if (!iop.hasExecutableName(element)) { + None + } else { + val name = iop.asString(iop.getExecutableName(element)) + if (!iop.hasSourceLocation(element)) { + Some(Api.StackTraceElement(name, None, None, None)) + } else { + val section = iop.getSourceLocation(element) + if (section.getSource.isInternal) { None - case null if x.getRootNode.isInternal => - None - case null => - Some(Api.StackTraceElement(x.getRootNode.getName, None, None, None)) - case section => + } else { Some( Api.StackTraceElement( - element.getTarget.getRootNode.getName, + name, findFileByModuleName(section.getSource.getName), Some(LocationResolver.sectionToRange(section)), LocationResolver.getExpressionId(section).map(_.externalId) ) ) + } } - }) + } } /** Find source file path by the module name. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetAttachedStackTraceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetAttachedStackTraceNode.java index 4860fddaeaa1..7197c2c1fbb9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetAttachedStackTraceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/GetAttachedStackTraceNode.java @@ -1,26 +1,19 @@ package org.enso.interpreter.node.expression.builtin.error; +import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.AcceptsError; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.expression.builtin.runtime.GetStackTraceNode; -import org.enso.interpreter.runtime.EnsoContext; -import org.enso.interpreter.runtime.builtin.Builtins; -import org.enso.interpreter.runtime.data.EnsoObject; -import org.enso.interpreter.runtime.error.PanicException; @BuiltinMethod( type = "Panic", name = "primitive_get_attached_stack_trace", description = "Gets the stack trace attached to the throwable.") public class GetAttachedStackTraceNode extends Node { - EnsoObject execute(@AcceptsError Object error) { - if (error instanceof Throwable) { - return GetStackTraceNode.stackTraceToArray((Throwable) error); - } else { - Builtins builtins = EnsoContext.get(this).getBuiltins(); - throw new PanicException( - builtins.error().makeTypeError("Throwable", error, "throwable"), this); - } + @Child private InteropLibrary iop = InteropLibrary.getFactory().createDispatched(3); + + Object execute(@AcceptsError Object error) { + return GetStackTraceNode.stackTraceToArray(iop, error); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/ThrowErrorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/ThrowErrorNode.java index 12ebeb6d4143..4b1b77ba06d0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/ThrowErrorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/ThrowErrorNode.java @@ -8,7 +8,8 @@ @BuiltinMethod( type = "Error", name = "throw", - description = "Returns a new value error with given payload.") + description = "Returns a new value error with given payload.", + inlineable = true) public class ThrowErrorNode extends Node { public Object execute(VirtualFrame giveMeAStackFrame, Object payload) { return DataflowError.withoutTrace(payload, this); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/immutable/AtVectorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/immutable/AtVectorNode.java index b583350e2bf1..73519af7a151 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/immutable/AtVectorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/immutable/AtVectorNode.java @@ -8,6 +8,7 @@ import org.enso.interpreter.runtime.data.vector.ArrayLikeAtNode; import org.enso.interpreter.runtime.data.vector.ArrayLikeLengthNode; import org.enso.interpreter.runtime.error.DataflowError; +import org.enso.interpreter.runtime.error.PanicException; @BuiltinMethod( type = "Array_Like_Helpers", @@ -25,7 +26,7 @@ Object execute(Object arrayLike, long index) { var len = len(arrayLike); var ctx = EnsoContext.get(this); var payload = ctx.getBuiltins().error().makeIndexOutOfBounds(index, len); - return DataflowError.withoutTrace(payload, this); + return DataflowError.withTrace(payload, new PanicException(payload, this)); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/GetStackTraceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/GetStackTraceNode.java index 90a7188645d3..8936415715ed 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/GetStackTraceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/GetStackTraceNode.java @@ -2,9 +2,14 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleStackTrace; +import com.oracle.truffle.api.TruffleStackTraceElement; import com.oracle.truffle.api.dsl.NeverDefault; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.nodes.Node; +import java.util.List; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.runtime.data.EnsoObject; import org.enso.interpreter.runtime.data.text.Text; @@ -23,34 +28,96 @@ public static GetStackTraceNode create() { return new GetStackTraceNode(); } + @CompilerDirectives.TruffleBoundary + private static EnsoObject wrapStackTraceElements(List elements) { + var arr = new Object[elements.size()]; + for (var i = 0; i < arr.length; i++) { + var e = elements.get(i); + arr[i] = e.getGuestObject(); + } + var vector = ArrayLikeHelpers.asVectorWithCheckAt(arr); + try { + return filterStackTraceVector(InteropLibrary.getUncached(), vector); + } catch (UnsupportedMessageException ex) { + assert raise(RuntimeException.class, ex); + return ArrayLikeHelpers.empty(); + } + } + + private static boolean includeFrame(InteropLibrary iop, Object elem) + throws UnsupportedMessageException { + if (!iop.hasSourceLocation(elem)) { + return false; + } + var ss = iop.getSourceLocation(elem); + if (ss == null) { + return false; + } + var src = ss.getSource(); + return src != null && src.hasCharacters() && !src.isInternal(); + } + + private static EnsoObject filterStackTraceVector(InteropLibrary iop, Object elements) + throws UnsupportedMessageException { + var size = iop.getArraySize(elements); + var count = 0; + for (long i = 0; i < size; i++) { + try { + var element = iop.readArrayElement(elements, i); + if (includeFrame(iop, element)) { + count++; + } + } catch (InvalidArrayIndexException ex) { + assert raise(RuntimeException.class, ex); + } + } + var arr = new Object[count]; + var at = 0; + for (long i = 0; i < size; i++) { + try { + var element = iop.readArrayElement(elements, i); + if (includeFrame(iop, element)) { + arr[at++] = element; + } + } catch (InvalidArrayIndexException ex) { + assert raise(RuntimeException.class, ex); + } + } + return ArrayLikeHelpers.wrapObjectsWithCheckAt(arr); + } + EnsoObject execute(VirtualFrame requestOwnStackFrame) { var exception = new PanicException(Text.create("Stacktrace"), this); TruffleStackTrace.fillIn(exception); return stackTraceToArray(exception); } + public static EnsoObject stackTraceToArray(InteropLibrary iop, Object exception) { + if (iop.hasExceptionStackTrace(exception)) { + try { + var elements = iop.getExceptionStackTrace(exception); + return filterStackTraceVector(iop, elements); + } catch (UnsupportedMessageException ex) { + assert raise(RuntimeException.class, ex); + // return empty + } + } else if (exception instanceof Throwable t) { + return stackTraceToArray(t); + } + return ArrayLikeHelpers.empty(); + } + @CompilerDirectives.TruffleBoundary - public static EnsoObject stackTraceToArray(Throwable exception) { + private static EnsoObject stackTraceToArray(Throwable exception) { var elements = TruffleStackTrace.getStackTrace(exception); if (elements == null) { return ArrayLikeHelpers.empty(); } - int count = 0; - for (int i = 0; i < elements.size(); i++) { - var element = elements.get(i); - if (element.getTarget().getRootNode().isInternal()) { - continue; - } - count++; - } - var arr = new Object[count]; - for (int i = 0, at = 0; i < elements.size(); i++) { - var element = elements.get(i); - if (element.getTarget().getRootNode().isInternal()) { - continue; - } - arr[at++] = element.getGuestObject(); - } - return ArrayLikeHelpers.wrapObjectsWithCheckAt(arr); + return wrapStackTraceElements(elements); + } + + @SuppressWarnings("unchecked") + private static boolean raise(Class type, Throwable t) throws E { + throw (E) t; } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java index 5ca69cda536f..80e3faecf168 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java @@ -2,6 +2,7 @@ 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.Cached; import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.interop.InteropLibrary; @@ -13,6 +14,7 @@ import java.util.Objects; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; /** @@ -27,7 +29,16 @@ public final class DataflowError extends AbstractTruffleException { /** Signals (local) values that haven't yet been initialized */ public static final DataflowError UNINITIALIZED = new DataflowError(null, (Node) null); + private static final boolean assertsOn; + + static { + var b = false; + assert b = true; + assertsOn = b; + } + private final Object payload; + private final boolean ownTrace; /** * Construct a new dataflow error with the default stack trace. @@ -40,9 +51,14 @@ public final class DataflowError extends AbstractTruffleException { */ public static DataflowError withoutTrace(Object payload, Node location) { assert payload != null; - DataflowError result = new DataflowError(payload, location); - TruffleStackTrace.fillIn(result); - return result; + if (assertsOn) { + var result = new DataflowError(payload, UNLIMITED_STACK_TRACE, location); + TruffleStackTrace.fillIn(result); + return result; + } else { + var result = new DataflowError(payload, location); + return result; + } } /** @@ -57,17 +73,27 @@ public static DataflowError withoutTrace(Object payload, Node location) { */ public static DataflowError withTrace(Object payload, AbstractTruffleException prototype) { assert payload != null; - return new DataflowError(payload, prototype); + var result = new DataflowError(payload, prototype); + TruffleStackTrace.fillIn(result); + return result; } - DataflowError(Object payload, Node location) { - super(location); + private DataflowError(Object payload, Node location) { + super(null, null, 1, location); this.payload = payload; + this.ownTrace = location != null && location.getRootNode() != null; } - DataflowError(Object payload, AbstractTruffleException prototype) { + private DataflowError(Object payload, AbstractTruffleException prototype) { super(prototype); this.payload = payload; + this.ownTrace = false; + } + + private DataflowError(Object payload, int stackTraceElementLimit, Node location) { + super(null, null, stackTraceElementLimit, location); + this.ownTrace = false; + this.payload = payload; } /** @@ -116,6 +142,21 @@ boolean isException() { return true; } + @ExportMessage + boolean hasExceptionStackTrace() { + return ownTrace; + } + + @ExportMessage + Object getExceptionStackTrace() throws UnsupportedMessageException { + if (!ownTrace) { + throw UnsupportedMessageException.create(); + } + var node = this.getLocation(); + var frame = TruffleStackTraceElement.create(node, node.getRootNode().getCallTarget(), null); + return ArrayLikeHelpers.asVectorWithCheckAt(frame.getGuestObject()); + } + @ExportMessage boolean isNull() { return payload == null; diff --git a/engine/runtime/src/test/java/org/enso/example/TestClass.java b/engine/runtime/src/test/java/org/enso/example/TestClass.java index e77898e54427..7dbe5d5ea247 100644 --- a/engine/runtime/src/test/java/org/enso/example/TestClass.java +++ b/engine/runtime/src/test/java/org/enso/example/TestClass.java @@ -1,6 +1,7 @@ package org.enso.example; import java.math.BigInteger; +import java.util.concurrent.Executor; import java.util.function.Function; /** A class used for testing Java Interop from Enso code */ @@ -64,6 +65,12 @@ public static long raiseException(int type) { }; } + public static Executor newDirectExecutor() { + return (command) -> { + command.run(); + }; + } + public static String enumToString(InnerEnum e) { return e.toString(); } diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/FindExceptionMessageTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/FindExceptionMessageTest.java index b8a7878b24ea..c47930d66420 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/FindExceptionMessageTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/FindExceptionMessageTest.java @@ -1,6 +1,9 @@ package org.enso.interpreter.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -67,6 +70,121 @@ public void testThrowNPEWithName() { } } + @Test + public void errorThrowDeep() { + var src = + """ + from Standard.Base import all + import Standard.Base.Errors.Illegal_Argument.Illegal_Argument + + deep n = if n <= 0 then Error.throw (Illegal_Argument.Error "Problem"+n.to_text) else deep n-1 + + main = + d = deep 10 + d + """; + + var res = evalModule(ctx, src); + assertTrue("Expecting error: " + res, res.isException()); + assertEquals("Standard.Base.Error.Error", res.getMetaObject().getMetaQualifiedName()); + + try { + throw res.throwException(); + } catch (PolyglotException ex) { + assertNotNull("Has source location", ex.getSourceLocation()); + var throwCode = ex.getSourceLocation().getCharacters().toString(); + assertNotEquals( + "Throw code found in the source code: " + throwCode, -1, src.indexOf(throwCode)); + } + } + + @Test + public void panicThrowDeepRecoverError() { + var src = + """ + from Standard.Base import all + import Standard.Base.Errors.Illegal_Argument.Illegal_Argument + + deep_panic n = if n <= 0 then Panic.throw (Illegal_Argument.Error "Problem") else + deep_panic n-1 + + main = + d = Panic.recover Any + deep_panic 10 + d + """; + + var res = evalModule(ctx, src); + assertTrue("Expecting recovered error: " + res, res.isException()); + assertEquals( + "Panic was converted to error", + "Standard.Base.Error.Error", + res.getMetaObject().getMetaQualifiedName()); + + try { + throw res.throwException(); + } catch (PolyglotException ex) { + assertNull("No source location...", ex.getSourceLocation()); + assertNotNull("... but stacktrace is fine", ex.getPolyglotStackTrace()); + var cnt = 0; + for (var f : ex.getPolyglotStackTrace()) { + if ("Unnamed.deep_panic".equals(f.getRootName())) { + cnt++; + } + } + assertEquals("Contains proper amount of deep_panic invocations", 11, cnt); + } + } + + @Test + public void panicThrowDeepMixingJava() { + var src = + """ + from Standard.Base import all + import Standard.Base.Errors.Illegal_Argument.Illegal_Argument + polyglot java import org.enso.example.TestClass + + exec e ~r = + e.execute r... + + deep_panic e n = if n <= 0 then Panic.throw (Illegal_Argument.Error "Problem") else + exec e + deep_panic e n-1 + + main = + e = TestClass.newDirectExecutor + d = Panic.recover Any + deep_panic e 10 + d + """; + + var res = evalModule(ctx, src); + assertTrue("Expecting recovered error: " + res, res.isException()); + assertEquals( + "Panic was converted to error", + "Standard.Base.Error.Error", + res.getMetaObject().getMetaQualifiedName()); + + try { + throw res.throwException(); + } catch (PolyglotException ex) { + assertNull("No source location...", ex.getSourceLocation()); + assertNotNull("... but stacktrace is fine", ex.getPolyglotStackTrace()); + var countDeepPanic = 0; + var countTestClass = 0; + for (var f : ex.getPolyglotStackTrace()) { + var rootName = f.getRootName(); + if ("Unnamed.deep_panic".equals(rootName)) { + countDeepPanic++; + } + if (rootName.contains("TestClass") && rootName.equals("newDirectExecutor")) { + countTestClass++; + } + } + assertEquals("Contains proper amount of deep_panic invocations", 11, countDeepPanic); + } + } + @Test public void testPanic() { String src = diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala index 97bb92fd34e6..3c08a2d32be2 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala @@ -1216,12 +1216,20 @@ class RuntimeErrorsTest | |main = | x = [1, 2, 3] - | y = Warning.attach_with_stacktrace x "foo" Runtime.primitive_get_stack_trace + | y = Warning.attach_with_stacktrace x 'foo' Runtime.primitive_get_stack_trace | y.at 10 |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode(xId, code, "[1, 2, 3]") + metadata.assertInCode( + yId, + code, + "Warning.attach_with_stacktrace x 'foo' Runtime.primitive_get_stack_trace" + ) + metadata.assertInCode(mainResId, code, "y.at 10") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -1304,6 +1312,8 @@ class RuntimeErrorsTest val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode(mainResId, code, "IO.println y") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -1881,6 +1891,10 @@ class RuntimeErrorsTest val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode(xId, code, "foo") + metadata.assertInCode(yId, code, "x + 1") + metadata.assertInCode(mainResId, code, "IO.println y") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala index 3f30657174bc..3eacfcbe2a42 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala @@ -1877,8 +1877,9 @@ class RuntimeVisualizationsTest message = "Method `does_not_exist` of type Main could not be found.", stack = Vector( - Api.StackTraceElement("", None, None, None), - Api.StackTraceElement("Debug.eval", None, None, None) +// empty stack for now +// Api.StackTraceElement("", None, None, None), +// Api.StackTraceElement("Debug.eval", None, None, None) ) ) ) diff --git a/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso b/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso index 150b3f73fa3e..32b28d236734 100644 --- a/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso +++ b/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso @@ -297,7 +297,9 @@ java_parse time_text pattern=Nothing = LocalTime.parse time_text (formatter.withLocale Locale.default.java_locale) python_time hour minute=0 second=0 nanoOfSecond=0 = - Panic.catch Any (python_time_impl hour minute second nanoOfSecond) (err -> Error.throw (Time_Error.Error <| err.payload)) + Panic.catch Any (python_time_impl hour minute second nanoOfSecond) err-> + time_error = Time_Error.Error err.payload + Error.throw time_error python_parse time_text pattern=Date_Time_Formatter.iso_time = t = Time_Of_Day.parse time_text pattern diff --git a/test/Tests/src/Network/Enso_Cloud/Enso_Cloud_Spec.enso b/test/Tests/src/Network/Enso_Cloud/Enso_Cloud_Spec.enso index 1436a0f3b5ef..5453327437c7 100644 --- a/test/Tests/src/Network/Enso_Cloud/Enso_Cloud_Spec.enso +++ b/test/Tests/src/Network/Enso_Cloud/Enso_Cloud_Spec.enso @@ -76,7 +76,7 @@ spec = Test.group "Enso_User - local mock integration tests" pending=pending_has_url <| # These tests should be kept in sync with tools/http-test-helper/src/main/java/org/enso/shttp/cloud_mock/UsersHandler.java - Test.specify "current user can be fetched from mock API" <| + Test.specify "current user can be fetched from mock API" pending=pending_has_url <| current = Enso_User.current current.id.should_equal "organization-27xJM00p8jWoL2qByTo6tQfciWC" current.name.should_equal "My test User 1" @@ -88,7 +88,7 @@ spec = home . should_be_a Enso_File home.is_directory.should_be_true - Test.specify "user list can be fetched from mock API" <| + Test.specify "user list can be fetched from mock API" pending=pending_has_url <| users = Enso_User.list users.length.should_equal 2 @@ -104,7 +104,7 @@ spec = Enso_User.current r.should_fail_with Cloud_Utils.Not_Logged_In - Test.specify "will fail if the token is invalid" <| + Test.specify "will fail if the token is invalid" pending=pending_has_url <| invalid_token_file = File.create_temporary_file "enso-test-credentials" "-invalid.txt" "invalid-token".write invalid_token_file . should_succeed reset_token diff --git a/test/Tests/src/Network/Http_Spec.enso b/test/Tests/src/Network/Http_Spec.enso index 5e56517a306d..b851c8889632 100644 --- a/test/Tests/src/Network/Http_Spec.enso +++ b/test/Tests/src/Network/Http_Spec.enso @@ -559,7 +559,7 @@ spec = ## Checking this error partially as a warning - I spent a lot of time debugging why I'm getting such an error. Apparently it happens when the httpbin server was crashing without sending any response. - Test.specify "should be able to handle server crash resulting in no response" <| + Test.specify "should be able to handle server crash resulting in no response" pending=pending_has_url <| err = Data.fetch (base_url_with_slash+"crash") err.should_fail_with Request_Error err.catch.error_type . should_equal "java.io.IOException" @@ -574,7 +574,7 @@ spec = err.should_fail_with HTTP_Error Test.group "Http Auth" <| - Test.specify "should support Basic user+password authentication" <| + Test.specify "should support Basic user+password authentication" pending=pending_has_url <| url = base_url_with_slash + "test_basic_auth" # Correct user and password @@ -598,7 +598,7 @@ spec = r4.should_fail_with HTTP_Error r4.catch.status_code.code . should_equal 403 - Test.specify "should support Bearer token authentication" <| + Test.specify "should support Bearer token authentication" pending=pending_has_url <| url = base_url_with_slash + "test_token_auth" # Correct token diff --git a/test/Tests/src/Runtime/Stack_Traces_Spec.enso b/test/Tests/src/Runtime/Stack_Traces_Spec.enso index 713414667774..91a72264039e 100644 --- a/test/Tests/src/Runtime/Stack_Traces_Spec.enso +++ b/test/Tests/src/Runtime/Stack_Traces_Spec.enso @@ -1,6 +1,6 @@ from Standard.Base import all -from Standard.Test import Test +from Standard.Test import Test, Test_Suite import Standard.Test.Extensions type My_Type diff --git a/test/Tests/src/Semantic/Error_Spec.enso b/test/Tests/src/Semantic/Error_Spec.enso index c8d164273afd..85ece8a4f4c3 100644 --- a/test/Tests/src/Semantic/Error_Spec.enso +++ b/test/Tests/src/Semantic/Error_Spec.enso @@ -104,14 +104,16 @@ spec = Test.specify "should allow to inspect their stacktrace" <| error = throw_a_bar error.catch . should_equal "bar" - error.stack_trace.second.name . should_equal "Error_Spec.throw_a_bar" - error.stack_trace.second.source_location.file.name . should_equal "Error_Spec.enso" - error.stack_trace.second.source_location.start_line . should_equal 32 + arr = error.stack_trace + arr.length>=1 . should_be_true + arr.first.name . should_equal "Error_Spec.throw_a_bar" + arr.first.source_location.file.name . should_equal "Error_Spec.enso" + arr.first.source_location.start_line . should_equal 32 Test.specify "should allow to inspect the stack trace of a recovered panic" <| error = Panic.recover Any <| throw_a_bar_panicking error.catch . should_equal "bar" - error.stack_trace.second.name . should_equal "Error_Spec.throw_a_bar_panicking" + error.stack_trace.first.name . should_equal "Error_Spec.throw_a_bar_panicking" Test.specify "it should be possible to introduce arbitrary dataflow dependencies between values using `if_not_error`" <| 42.if_not_error 23 . should_equal 23 @@ -144,7 +146,7 @@ spec = Test.specify "should provide access to stack traces" <| stack = Panic.catch Any throw_a_bar_panicking caught_panic-> caught_panic.stack_trace - stack.second.name . should_equal "Error_Spec.throw_a_bar_panicking" + stack.first.name . should_equal "Error_Spec.throw_a_bar_panicking" Test.specify "should provide access to Java stack traces" <| stack_1 = Panic.recover Any (do_a_parse "foo") . stack_trace @@ -170,7 +172,7 @@ spec = error = Panic.recover Any (perform_operation throw_a_foo_panicking) error.catch . should_equal "foo" - error.stack_trace.second.name . should_equal "Error_Spec.throw_a_foo_panicking" + error.stack_trace.first.name . should_equal "Error_Spec.throw_a_foo_panicking" Test.specify "should work as in the examples" <| fun ~act = @@ -194,10 +196,10 @@ spec = Test.specify "should allow to throw raw Java exceptions" <| exception = Panic.catch NumberFormatException (throw_raw_java "foo") (p -> p) exception.payload.getMessage . should_equal "foo" - Panic.get_attached_stack_trace exception . second . name . should_equal "Error_Spec.throw_raw_java" + Panic.get_attached_stack_trace exception . first . name . should_equal "Error_Spec.throw_raw_java" caught_panic = Panic.catch Any (throw_raw_java "foo") x->x - caught_panic.stack_trace.second.name . should_equal "Error_Spec.throw_raw_java" + caught_panic.stack_trace.first.name . should_equal "Error_Spec.throw_raw_java" caught_panic.payload . should_be_a JException Test.specify "should allow to re-throw raw Java exceptions" <| @@ -217,7 +219,7 @@ spec = Panic.throw caught_panic.payload message_2.get . should_equal "foo" caught_2.catch . should_be_a JException - caught_2.stack_trace.second.name . should_equal "Error_Spec.throw_raw_java" + caught_2.stack_trace.first.name . should_equal "Error_Spec.throw_raw_java" Test.specify "should allow to catch a specific panic type easily" <| message_1 = Panic.catch Illegal_Argument (Panic.throw (Illegal_Argument.Error "msg" Nothing)) caught_panic-> @@ -258,7 +260,7 @@ spec = Panic.recover [JException] (do_a_parse "foo") . catch . should_be_a JException Panic.recover Any throw_a_bar_panicking . catch . should_equal "bar" - Panic.recover Text throw_a_bar_panicking . stack_trace . second . name . should_equal "Error_Spec.throw_a_bar_panicking" + Panic.recover Text throw_a_bar_panicking . stack_trace . first . name . should_equal "Error_Spec.throw_a_bar_panicking" Test.specify "Unsupported_Argument_Types message should be readable" <| check err = diff --git a/test/Tests/src/Semantic/Warnings_Spec.enso b/test/Tests/src/Semantic/Warnings_Spec.enso index c25f780e697a..3594cca07c56 100644 --- a/test/Tests/src/Semantic/Warnings_Spec.enso +++ b/test/Tests/src/Semantic/Warnings_Spec.enso @@ -221,7 +221,7 @@ spec = Test.group "Dataflow Warnings" <| errored_3 = Panic.recover Any throw_a_bar mapped_3 = map_odd_warnings_and_errors errored_3 mapped_3.catch . should_equal "bar" - mapped_3.stack_trace.second.name . should_equal "Warnings_Spec.throw_a_bar" + mapped_3.stack_trace.first.name . should_equal "Warnings_Spec.throw_a_bar" Warning.get_all mapped_3 . catch . should_equal "bar" Test.specify "should allow to detach warnings, selectively" <|