Fix Runtime.assert (#8742)

This commit is contained in:
Pavel Marek 2024-01-12 18:47:40 +01:00 committed by GitHub
parent 58cf4e5244
commit 6ae35abc46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 140 deletions

View File

@ -8,10 +8,12 @@ import project.Errors.Common.Assertion_Error
import project.Errors.Common.Forbidden_Operation
import project.Errors.Common.Type_Error
import project.Function.Function
import project.IO
import project.Nothing.Nothing
import project.Panic.Panic
import project.Polyglot.Polyglot
import project.Runtime.Source_Location.Source_Location
import project.System
from project.Data.Index_Sub_Range.Index_Sub_Range import First, Last
from project.Data.Text.Extensions import all
from project.Runtime.Context import Input, Output
@ -58,13 +60,18 @@ gc = @Builtin_Method "Runtime.gc"
Asserts that the given action succeeds, otherwise throws a panic.
Assertions are disable by default, meaning that call to this method is
a no-op. To enable assertions, set the environment variable
`ENSO_ENABLE_ASSERTIONS=true`
assert : Boolean -> Text -> Nothing ! Assertion_Error
assert ~action message="" = assert_builtin action message
a no-op. To enable assertions, either set the environment variable
`ENSO_ENABLE_ASSERTIONS=true` or enable JVM assertions by passing `-ea`
cmd line option to java.
assert : Boolean -> Text -> Nothing
assert (~action : Boolean) (message : Text = "") =
if assertions_enabled.not then Nothing else
if action then Nothing else
Panic.throw <| Assertion_Error.Error message
## PRIVATE
assert_builtin ~action message = @Builtin_Method "Runtime.assert_builtin"
Returns True if assertions are enabled.
assertions_enabled = @Builtin_Method "Runtime.assertions_enabled"
## PRIVATE
ADVANCED

View File

@ -1,98 +0,0 @@
package org.enso.interpreter.node.expression.builtin.runtime;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Idempotent;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.Suspend;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.DataflowError;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.state.State;
@BuiltinMethod(
type = "Runtime",
name = "assert_builtin",
description = "Asserts that the given condition is true",
autoRegister = false)
public abstract class AssertNode extends Node {
public static AssertNode build() {
return AssertNodeGen.create();
}
public abstract Object execute(VirtualFrame frame, State state, @Suspend Object action, Text msg);
@Idempotent
protected boolean isAssertionsEnabled() {
return EnsoContext.get(this).isAssertionsEnabled();
}
@Specialization(guards = "!isAssertionsEnabled()")
Object doAssertionsDisabled(VirtualFrame frame, State state, Object action, Text msg) {
return EnsoContext.get(this).getNothing();
}
@Specialization(replaces = "doAssertionsDisabled")
Object doAssertionsEnabled(
VirtualFrame frame,
State state,
Object action,
Text msg,
@Cached("create()") ThunkExecutorNode thunkExecutorNode,
@Cached BranchProfile resultIsNotAtomProfile) {
var ctx = EnsoContext.get(this);
var builtins = ctx.getBuiltins();
Object actionRes =
thunkExecutorNode.executeThunk(frame, action, state, BaseNode.TailStatus.TAIL_DIRECT);
if (actionRes instanceof Atom resAtom) {
var isTrue = resAtom.getConstructor() == builtins.bool().getTrue();
if (isTrue) {
return ctx.getNothing();
} else {
throw new PanicException(builtins.error().makeAssertionError(msg), this);
}
} else {
resultIsNotAtomProfile.enter();
return checkResultSlowPath(actionRes, msg);
}
}
@TruffleBoundary
private Object checkResultSlowPath(Object actionRes, Text msg) {
var ctx = EnsoContext.get(this);
var builtins = ctx.getBuiltins();
try {
if (InteropLibrary.getUncached().asBoolean(actionRes)) {
return ctx.getNothing();
} else {
throw new PanicException(builtins.error().makeAssertionError(msg), this);
}
} catch (UnsupportedMessageException e) {
if (actionRes instanceof DataflowError dataflowError) {
var txt = Text.create("Result of assert action is a dataflow error: " + dataflowError);
throw new PanicException(builtins.error().makeAssertionError(txt), this);
} else {
var typeError =
builtins
.error()
.makeTypeError(
builtins.bool().getType(),
TypeOfNode.getUncached().execute(actionRes),
"Result of assert action");
throw new PanicException(typeError, this);
}
}
}
}

View File

@ -0,0 +1,20 @@
package org.enso.interpreter.node.expression.builtin.runtime;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.EnsoContext;
@BuiltinMethod(
type = "Runtime",
name = "assertions_enabled",
description = "Returns True iff assertions are enabled")
public class IsAssertionEnabledNode extends Node {
public static IsAssertionEnabledNode build() {
return new IsAssertionEnabledNode();
}
public boolean execute(VirtualFrame frame) {
return EnsoContext.get(this).isAssertionsEnabled();
}
}

View File

@ -24,6 +24,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
public class AssertionsTest extends TestBase {
private static Context ctx;
private static final ByteArrayOutputStream out = new ByteArrayOutputStream();
@ -63,9 +64,9 @@ public class AssertionsTest extends TestBase {
TestBase.evalModule(
ctx,
"""
from Standard.Base import False, Runtime
main = Runtime.assert False
""");
from Standard.Base import False, Runtime
main = Runtime.assert False
""");
fail("Should throw Assertion_Error");
} catch (PolyglotException e) {
assertThat(e.getGuestObject().isException(), is(true));
@ -78,9 +79,9 @@ public class AssertionsTest extends TestBase {
TestBase.evalModule(
ctx,
"""
from Standard.Base import False, Runtime
main = Runtime.assert False 'My fail message'
""");
from Standard.Base import False, Runtime
main = Runtime.assert False 'My fail message'
""");
fail("Should throw Assertion_Error");
} catch (PolyglotException e) {
assertThat(
@ -95,17 +96,18 @@ public class AssertionsTest extends TestBase {
TestBase.evalModule(
ctx,
"""
from Standard.Base import False, Runtime
foo = Runtime.assert False 'My fail message'
main = foo
""");
from Standard.Base import False, Runtime
foo = Runtime.assert False 'My fail message'
main = foo
""");
fail("Should throw Assertion_Error");
} catch (PolyglotException e) {
assertThat(e.getStackTrace().length, greaterThan(3));
assertThat(e.getStackTrace()[0].toString(), containsString("Runtime.assert_builtin"));
assertThat(e.getStackTrace().length, greaterThan(5));
assertThat(e.getStackTrace()[0].toString(), containsString("Panic"));
assertThat(e.getStackTrace()[1].toString(), containsString("Runtime.assert"));
assertThat(e.getStackTrace()[2].toString(), containsString("foo"));
assertThat(e.getStackTrace()[3].toString(), containsString("main"));
// Ignore the next two frames as they are implementation details
assertThat(e.getStackTrace()[4].toString(), containsString("foo"));
assertThat(e.getStackTrace()[5].toString(), containsString("main"));
}
}
@ -115,9 +117,9 @@ public class AssertionsTest extends TestBase {
TestBase.evalModule(
ctx,
"""
from Standard.Base import Runtime, True
main = Runtime.assert True
""");
from Standard.Base import Runtime, True
main = Runtime.assert True
""");
assertTrue(res.isNull());
}
@ -127,9 +129,9 @@ public class AssertionsTest extends TestBase {
TestBase.evalModule(
ctx,
"""
from Standard.Base import Runtime
main = Runtime.assert [1,2,3]
""");
from Standard.Base import Runtime
main = Runtime.assert [1,2,3]
""");
fail("Should throw Type_Error");
} catch (PolyglotException e) {
assertThat(e.getMessage(), stringContainsInOrder(List.of("Type", "error")));
@ -142,14 +144,14 @@ public class AssertionsTest extends TestBase {
TestBase.evalModule(
ctx,
"""
from Standard.Base import Runtime
import Standard.Base.Runtime.Ref.Ref
from Standard.Base import Runtime
import Standard.Base.Runtime.Ref.Ref
main =
ref = Ref.new 10
Runtime.assert (ref.put 23 . is_nothing . not)
ref.get
""");
main =
ref = Ref.new 10
Runtime.assert (ref.put 23 . is_nothing . not)
ref.get
""");
assertTrue(res.isNumber());
assertThat(res.asInt(), is(23));
}

View File

@ -9,10 +9,8 @@ import Standard.Test.Extensions
foreign js js_check = """
return (4 == 2 + 2)
on_ci = if Environment.get "ENSO_RUNNER_CONTAINER_NAME" . is_nothing . not then Nothing else "Not in CI"
spec = Test.group "Asserts" pending=on_ci <|
Test.specify "should be enabled on the CI" <|
spec = Test.group "Asserts" <|
Test.specify "should be enabled in tests" <|
p = Panic.catch Assertion_Error (Runtime.assert False) err->
err.payload
Meta.type_of p . should_be_a Assertion_Error
@ -26,15 +24,14 @@ spec = Test.group "Asserts" pending=on_ci <|
4 == 2 + 2
ret . should_be_a Nothing
Test.specify "should fail with type error when given dataflow error as expression" <|
p = Panic.catch Assertion_Error (Runtime.assert (Error.throw "foo")) err->
err.payload
Meta.type_of p . should_be_a Assertion_Error
p.message . should_start_with "Result of assert action is a dataflow error"
Test.specify "should be able to take values with warnings" <|
foo x = Warning.attach "My warning" (x+2)
Runtime.assert (foo 2 > 2) . should_be_a Nothing
Test.specify "should fail with Type_Error if action does not return Boolean" <|
p = Panic.catch Type_Error (Runtime.assert 42) err->
err
Meta.type_of p.payload . should_be_a Type_Error
main = Test_Suite.run_main spec