Skip to content

Commit

Permalink
Fix displaying of host values in chrome devtools (#11468)
Browse files Browse the repository at this point in the history
Chrome inspector now displays polyglot objects that have a corresponding Enso builtin type as enso objects:

![image](https://github.com/user-attachments/assets/c9dfee50-9cbb-45ed-b602-74ad3a663a4b)
  • Loading branch information
Akirathan authored Nov 27, 2024
1 parent 456031b commit 5d7e89b
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ public void resultOfConversionIsTypeChecked() throws Exception {
ex.getMessage().toLowerCase(),
AllOf.allOf(containsString("type"), containsString("error")));
var typeError = ex.getGuestObject();
assertEquals("Expected type", "First_Type", typeError.getMember("expected").toString());
assertEquals("Expected type", "First_Type", typeError.getMember("expected").asString());
assertEquals("Got wrong value", 42, typeError.getMember("actual").asInt());
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.enso.interpreter.test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
Expand Down Expand Up @@ -177,4 +179,39 @@ public void doubleBigInteger() throws Exception {
var fourtyTwo = assertMul(6.0, new BigInteger("7"));
assertEquals(42, fourtyTwo.asInt());
}

@Test
public void everyValueSmallerThanIntegerMaxVal_IsPrimitiveInt() {
var almostMaxInt = Integer.toString(Integer.MAX_VALUE - 1);
var intVal = ContextUtils.evalModule(ctx, "main = " + almostMaxInt);
assertThat("Is a number", intVal.isNumber(), is(true));
assertThat("Fits in int", intVal.fitsInInt(), is(true));
assertThat("Fits in long", intVal.fitsInLong(), is(true));
assertThat("Fits in double", intVal.fitsInDouble(), is(true));
assertThat("Fits in big int", intVal.fitsInBigInteger(), is(true));
}

@Test
public void everyValueSmallerThanLongMaxVal_IsPrimitiveLong() {
var almostMaxLong = Long.toString(Long.MAX_VALUE - 1);
var longVal = ContextUtils.evalModule(ctx, "main = " + almostMaxLong);
assertThat("Is a number", longVal.isNumber(), is(true));
assertThat("Does not fit in int", longVal.fitsInInt(), is(false));
assertThat("Fits in long", longVal.fitsInLong(), is(true));
// Does not fit in double, because it is not a power of 2 and therefore a precision would
// be lost if converted to double.
assertThat("Does not fit in double", longVal.fitsInDouble(), is(false));
assertThat("Fits in big int", longVal.fitsInBigInteger(), is(true));
}

@Test
public void everyValueBiggerThanLongMaxVal_IsEnsoBigInt() {
// This number is bigger than Long.MAX_VALUE, and not a power of 2.
var bigIntVal = ContextUtils.evalModule(ctx, "main = 9223372036854775808");
assertThat("Is a number", bigIntVal.isNumber(), is(true));
assertThat("Does not fit in int", bigIntVal.fitsInInt(), is(false));
assertThat("Does not fit in long", bigIntVal.fitsInLong(), is(false));
assertThat("Does not fit in double (not a power of 2)", bigIntVal.fitsInDouble(), is(false));
assertThat("Fits in big int", bigIntVal.fitsInBigInteger(), is(true));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import com.oracle.truffle.api.debug.SuspendedCallback;
import com.oracle.truffle.api.debug.SuspendedEvent;
import com.oracle.truffle.api.nodes.LanguageInfo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand All @@ -42,23 +45,30 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

public class DebuggingEnsoTest {
private Context context;
private Engine engine;
private Debugger debugger;
private final ByteArrayOutputStream out = new ByteArrayOutputStream();

@Before
public void initContext() {
out.reset();
engine =
Engine.newBuilder()
.allowExperimentalOptions(true)
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath())
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
.logHandler(System.err)
.logHandler(out)
.err(out)
.out(out)
.build();

context =
Expand All @@ -76,13 +86,26 @@ public void initContext() {
}

@After
public void disposeContext() {
public void disposeContext() throws IOException {
context.close();
context = null;
engine.close();
engine = null;
}

/** Only print warnings from the compiler if a test fails. */
@Rule
public TestWatcher testWatcher =
new TestWatcher() {
@Override
protected void failed(Throwable e, Description description) {
System.err.println("Test failed: " + description.getMethodName());
System.err.println("Error: " + e.getMessage());
System.err.println("Logs from the compiler and the engine: ");
System.err.println(out);
}
};

private static void expectStackFrame(
DebugStackFrame actualFrame, Map<String, String> expectedValues) {
Map<String, String> actualValues = new HashMap<>();
Expand Down Expand Up @@ -246,6 +269,125 @@ public void testHostValues() {
}
}

/**
* Both {@code Date.new 2024 12 15} and {@code Date.parse "2024-12-15"} should be seen by the
* debugger as the exact same objects. Internally, the value from {@code Date.parse} is a host
* value.
*/
@Test
public void hostValueIsTreatedAsItsEnsoCounterpart() {
Value fooFunc =
createEnsoMethod(
"""
from Standard.Base import Date, Date_Time, Dictionary
polyglot java import java.lang.String
polyglot java import java.util.List as JList
polyglot java import java.util.Map as JMap
foreign js js_date = '''
return new Date();
foreign js js_str = '''
return "Hello_World";
foreign js js_list = '''
return [1, 2, 3];
foreign js js_map = '''
let m = new Map();
m.set('A', 1);
m.set('B', 2);
return m;
foreign python py_list = '''
return [1, 2, 3]
foreign python py_dict = '''
return {'A': 1, 'B': 2}
foo _ =
d_enso = Date.new 2024 12 15
d_js = js_date
d_java = Date.parse "2024-12-15"
dt_enso = Date_Time.now
dt_java = Date_Time.parse "2020-05-06 04:30:20" "yyyy-MM-dd HH:mm:ss"
str_enso = "Hello_World"
str_js = js_str
str_java = String.new "Hello_World"
list_enso = [1, 2, 3]
list_js = js_list
list_py = py_list
list_java = JList.of 1 2 3
dict_enso = Dictionary.from_vector [["A", 1], ["B", 2]]
dict_js = js_map
dict_py = py_dict
dict_java = JMap.of "A" 1 "B" 2
end = 42
""",
"foo");

try (DebuggerSession session =
debugger.startSession(
(SuspendedEvent event) -> {
switch (event.getSourceSection().getCharacters().toString().strip()) {
case "end = 42" -> {
DebugScope scope = event.getTopStackFrame().getScope();

DebugValue ensoDate = scope.getDeclaredValue("d_enso");
DebugValue javaDate = scope.getDeclaredValue("d_java");
DebugValue jsDate = scope.getDeclaredValue("d_js");
assertSameProperties(ensoDate.getProperties(), javaDate.getProperties());
assertSameProperties(ensoDate.getProperties(), jsDate.getProperties());

DebugValue ensoDateTime = scope.getDeclaredValue("dt_enso");
DebugValue javaDateTime = scope.getDeclaredValue("dt_java");
assertSameProperties(ensoDateTime.getProperties(), javaDateTime.getProperties());

DebugValue ensoString = scope.getDeclaredValue("str_enso");
DebugValue javaString = scope.getDeclaredValue("str_java");
DebugValue jsString = scope.getDeclaredValue("str_js");
assertSameProperties(ensoString.getProperties(), javaString.getProperties());
assertSameProperties(ensoString.getProperties(), jsString.getProperties());

DebugValue ensoList = scope.getDeclaredValue("list_enso");
DebugValue javaList = scope.getDeclaredValue("list_java");
DebugValue jsList = scope.getDeclaredValue("list_js");
DebugValue pyList = scope.getDeclaredValue("list_py");
assertSameProperties(ensoList.getProperties(), javaList.getProperties());
assertSameProperties(ensoList.getProperties(), jsList.getProperties());
assertSameProperties(ensoList.getProperties(), pyList.getProperties());

DebugValue ensoDict = scope.getDeclaredValue("dict_enso");
DebugValue javaDict = scope.getDeclaredValue("dict_java");
DebugValue jsDict = scope.getDeclaredValue("dict_js");
DebugValue pyDict = scope.getDeclaredValue("dict_py");
assertSameProperties(ensoDict.getProperties(), javaDict.getProperties());
assertSameProperties(ensoDict.getProperties(), jsDict.getProperties());
assertSameProperties(ensoDict.getProperties(), pyDict.getProperties());
}
}
event.getSession().suspendNextExecution();
})) {
session.suspendNextExecution();
fooFunc.execute(0);
}
}

/** Asserts that the given values have same property names. */
private void assertSameProperties(
Collection<DebugValue> expectedProps, Collection<DebugValue> actualProps) {
if (expectedProps == null) {
assertThat(actualProps, anyOf(empty(), nullValue()));
return;
}
assertThat(actualProps.size(), is(expectedProps.size()));
var expectedPropNames =
expectedProps.stream().map(DebugValue::getName).collect(Collectors.toUnmodifiableSet());
var actualPropNames =
actualProps.stream().map(DebugValue::getName).collect(Collectors.toUnmodifiableSet());
assertThat(actualPropNames, is(expectedPropNames));
}

@Test
public void testHostValueAsAtomField() {
Value fooFunc =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand Down Expand Up @@ -102,6 +103,17 @@ public void wrapAndUnwrap() {
fail("One shall not be created WithWarnings without any warnings " + without);
}

@Test
public void withWarningsDelegatesToMetaObject() {
var warning42 = wrap.execute("warn:1", "Text");
var meta = warning42.getMetaObject();
assertThat(
"Value (" + warning42 + ") wrapped in warning must have a meta object",
meta,
is(notNullValue()));
assertThat(meta.toString(), containsString("Text"));
}

@Test
public void warningIsAnException() {
var warning42 = wrap.execute("warn:1", 42);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ class TypeSignaturesTest
)
}

"XX resolve imported names" in {
"resolve imported names" in {
val code =
"""
|from project.Util import all
Expand Down
Loading

0 comments on commit 5d7e89b

Please sign in to comment.