visualizationResultToBytes converts anything that looks like a string (#4045)

Use `InteropLibrary.isString` and `asString` to convert any string value to `byte[]`

# Important Notes
Also contains a support for `Metadata.assertInCode` to help locating the right place in the code snippets.
This commit is contained in:
Jaroslav Tulach 2023-01-13 13:30:27 +01:00 committed by GitHub
parent ccde47e24e
commit 917176873d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 170 additions and 57 deletions

View File

@ -1612,6 +1612,7 @@ lazy val `runtime-with-polyglot` =
"ENSO_TEST_DISABLE_IR_CACHE" -> "false"
),
libraryDependencies ++= Seq(
"org.graalvm.sdk" % "graal-sdk" % graalVersion % "provided",
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)

View File

@ -27,11 +27,6 @@ json_from_table table =
Arguments:
- value: the value to be visualized.
process_to_json_text : Any -> Text
process_to_json_text value =
json = case value of
Table.Value _ -> json_from_table value . to_text
_ -> value.to_json
## Workaround so that the JS String is converted to a Text
https://www.pivotaltracker.com/story/show/184061302
"" + json
process_to_json_text value = case value of
Table.Value _ -> json_from_table value . to_text
_ -> value.to_json

View File

@ -68,6 +68,4 @@ from_value value =
- value: the value to be visualized.
process_to_json_text : Any -> Text
process_to_json_text value =
## Workaround so that the JS String is converted to a Text
https://www.pivotaltracker.com/story/show/184061302
"" + (from_value value . to_json)
from_value value . to_json

View File

@ -3,10 +3,7 @@ import project.Helpers
## PRIVATE
Default visualization preprocessor.
default_preprocessor x =
## Workaround so that the JS String is converted to a Text
https://www.pivotaltracker.com/story/show/184061302
"" + x.to_default_visualization_data
default_preprocessor x = x.to_default_visualization_data
## PRIVATE
Error visualization preprocessor.
@ -18,6 +15,4 @@ error_preprocessor x =
full_message = message + if stack_trace.length > 1 then " (" + stack_trace.at 1 . trim +")" else ""
JS_Object.from_pairs [['kind', 'Dataflow'], ['message', full_message]] . to_json
## Workaround so that the JS String is converted to a Text
https://www.pivotaltracker.com/story/show/184061302
"" + if result.is_error then result.catch else ok
if result.is_error then result.catch else ok

View File

@ -29,11 +29,7 @@ prepare_visualization x = Helpers.recover_errors <|
expected_enso_type = find_expected_enso_type_for_sql e.second
JS_Object.from_pairs [["value", value], ["actual_type", actual_type], ["expected_sql_type", expected_sql_type], ["expected_enso_type", expected_enso_type]]
dialect = x.connection.dialect.name
json = JS_Object.from_pairs [["dialect", dialect], ["code", code], ["interpolations", mapped]] . to_text
## Workaround so that the JS String is converted to a Text
https://www.pivotaltracker.com/story/show/184061302
"" + json
JS_Object.from_pairs [["dialect", dialect], ["code", code], ["interpolations", mapped]] . to_text
## PRIVATE

View File

@ -202,6 +202,4 @@ process_to_json_text value bounds=Nothing limit=Nothing =
_ : Vector -> json_from_vector value bounds limit
_ -> json_from_vector value.to_vector bounds limit
## Workaround so that the JS String is converted to a Text
https://www.pivotaltracker.com/story/show/184061302
"" + json
json

View File

@ -30,17 +30,13 @@ prepare_visualization y max_rows=1000 = Helpers.recover_errors <|
included_rows = dataframe.row_count
index = Dataframe_Column.from_vector "" (Vector.new included_rows i->i)
## Workaround so that the JS String is converted to a Text
https://www.pivotaltracker.com/story/show/184061302
"" + make_json dataframe [index] all_rows_count
make_json dataframe [index] all_rows_count
_ : Database_Table ->
df = x.read max_rows
all_rows_count = x.row_count
## Workaround so that the JS String is converted to a Text
https://www.pivotaltracker.com/story/show/184061302
"" + make_json df [] all_rows_count
make_json df [] all_rows_count
# We display columns as 1-column tables.
_ : Dataframe_Column ->

View File

@ -0,0 +1,46 @@
package org.enso.interpreter.test.instrument;
import java.io.OutputStream;
import java.nio.file.Paths;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.junit.AfterClass;
import static org.junit.Assert.assertNotNull;
import org.junit.BeforeClass;
import org.junit.Test;
public class VerifyJavaScriptIsAvailableTest {
private static Context ctx;
@BeforeClass
public static void initEnsoContext() {
ctx =
Context.newBuilder()
.allowExperimentalOptions(true)
.allowIO(true)
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath())
.logHandler(OutputStream.nullOutputStream())
.allowAllAccess(true)
.build();
assertNotNull("Enso language is supported", ctx.getEngine().getLanguages().get("enso"));
}
@AfterClass
public static void closeEnsoContext() throws Exception {
ctx.close();
}
@Test
public void javaScriptIsPresent() {
var js = ctx.getEngine().getLanguages().get("js");
assertNotNull("JavaScript is available", js);
}
@Test
public void ensoIsPresent() {
var enso = ctx.getEngine().getLanguages().get("enso");
assertNotNull("Enso is available", enso);
}
}

View File

@ -2807,6 +2807,9 @@ class RuntimeVisualizationsTest
|main =
| [Warning.attach "y" 42]
|""".stripMargin.linesIterator.mkString("\n")
metadata.assertInCode(idMain, code, "\n [Warning.attach \"y\" 42]")
val contents = metadata.appendToCode(code)
val mainFile = context.writeMain(contents)

View File

@ -0,0 +1,36 @@
package org.enso.interpreter.instrument.job;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import java.nio.charset.StandardCharsets;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.WithWarnings;
public final class VisualizationResult {
private VisualizationResult() {
}
public static byte[] visualizationResultToBytes(Object value) {
if (value instanceof byte[] arr) {
return arr;
}
if (value instanceof String text) {
return text.getBytes(StandardCharsets.UTF_8);
}
if (value instanceof Text text) {
return visualizationResultToBytes(text.toString());
}
if (value instanceof WithWarnings warn) {
return visualizationResultToBytes(warn.getValue());
}
var iop = InteropLibrary.getUncached();
if (iop.isString(value)) {
try {
return visualizationResultToBytes(iop.asString(value));
} catch (UnsupportedMessageException ex) {
// fallthru
}
}
return null;
}
}

View File

@ -17,19 +17,13 @@ import org.enso.interpreter.instrument._
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall
import org.enso.interpreter.runtime.`type`.Types
import org.enso.interpreter.runtime.control.ThreadInterruptedException
import org.enso.interpreter.runtime.data.text.Text
import org.enso.interpreter.runtime.error.{
DataflowError,
PanicSentinel,
WithWarnings
}
import org.enso.interpreter.runtime.error.{DataflowError, PanicSentinel}
import org.enso.interpreter.service.error._
import org.enso.polyglot.LanguageInfo
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.polyglot.runtime.Runtime.Api.ContextId
import java.io.File
import java.nio.charset.StandardCharsets
import java.util.UUID
import java.util.function.Consumer
import java.util.logging.Level
@ -528,26 +522,15 @@ object ProgramExecutionSupport {
* @return either a byte array representing the visualization result or an
* error
*/
@scala.annotation.tailrec
private def visualizationResultToBytes(
value: AnyRef
): Either[VisualisationException, Array[Byte]] =
value match {
case text: String =>
Right(text.getBytes(StandardCharsets.UTF_8))
case text: Text =>
Right(text.toString.getBytes(StandardCharsets.UTF_8))
case bytes: Array[Byte] =>
Right(bytes)
case withWarnings: WithWarnings =>
visualizationResultToBytes(withWarnings.getValue)
case other =>
Left(
new VisualisationException(
s"Cannot encode ${other.getClass} to byte array."
)
)
}
): Either[VisualisationException, Array[Byte]] = {
Option(VisualizationResult.visualizationResultToBytes(value)).toRight(
new VisualisationException(
s"Cannot encode ${value.getClass} to byte array."
)
)
}
/** Extract method pointer information from the expression value.
*

View File

@ -0,0 +1,46 @@
package org.enso.interpreter.test.instrument;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import java.io.OutputStream;
import java.nio.file.Paths;
import org.enso.interpreter.instrument.job.VisualizationResult;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.junit.AfterClass;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.BeforeClass;
import org.junit.Test;
public class ConvertAsStringObjectTest {
@Test
public void conversionOfStringObject() {
var wrap = new AsString("Hello");
var wrapRes = VisualizationResult.visualizationResultToBytes(wrap);
assertNotNull("Special String object converted", wrapRes);
var directRes = VisualizationResult.visualizationResultToBytes("Hello");
assertArrayEquals("Both arrays are the same", wrapRes, directRes);
}
@ExportLibrary(InteropLibrary.class)
static class AsString implements TruffleObject {
private final String value;
private AsString(String value) {
this.value = value;
}
@ExportMessage
boolean isString() {
return true;
}
@ExportMessage
String asString() {
return value;
}
}
}

View File

@ -41,4 +41,24 @@ class Metadata {
*/
def appendToCode(code: String): String =
s"$code\n\n\n#### METADATA ####\n$toJsonString\n[]"
/** Checks whether given UUID is assigned to expected string
* @param uuid the UUID to search for; defined by {@code #addItem}
* @param code whole code to search in
* @param expected the text that should be assigned to the UUID
*/
def assertInCode(uuid: UUID, code: String, expected: String): Unit = {
for (item <- items) {
if (item.id == uuid) {
val real = code.substring(item.start, item.start + item.len)
if (real != expected) {
throw new AssertionError(
"Expecting " + expected + " but found '" + real + "'"
)
}
return
}
}
throw new AssertionError("UUID " + uuid + " not found")
}
}