diff --git a/lib/livebook/evaluator/default_formatter.ex b/lib/livebook/evaluator/default_formatter.ex index a95d6bca6..dc7932ed0 100644 --- a/lib/livebook/evaluator/default_formatter.ex +++ b/lib/livebook/evaluator/default_formatter.ex @@ -35,14 +35,13 @@ defmodule Livebook.Evaluator.DefaultFormatter do defp to_output(value) do # Kino is a "client side" extension for Livebook that may be # installed into the runtime node. If it is installed we use - # its more precies output rendering rules. + # its more precise output rendering rules. if Code.ensure_loaded?(Kino.Render) do try do Kino.Render.to_livebook(value) catch kind, error -> - {error, stacktrace} = Exception.blame(kind, error, __STACKTRACE__) - formatted = Exception.format(kind, error, stacktrace) + formatted = format_error(kind, error, __STACKTRACE__) Logger.error(formatted) to_inspect_output(value) end @@ -52,8 +51,19 @@ defmodule Livebook.Evaluator.DefaultFormatter do end defp to_inspect_output(value, opts \\ []) do - inspected = inspect(value, inspect_opts(opts)) - {:text, inspected} + try do + inspected = inspect(value, inspect_opts(opts)) + {:text, inspected} + catch + kind, error -> + formatted = format_error(kind, error, __STACKTRACE__) + {:error, formatted, :other} + end + end + + defp format_error(kind, error, stacktrace) do + {error, stacktrace} = Exception.blame(kind, error, stacktrace) + Exception.format(kind, error, stacktrace) end defp inspect_opts(opts) do diff --git a/test/livebook/evaluator/default_formatter_test.exs b/test/livebook/evaluator/default_formatter_test.exs new file mode 100644 index 000000000..b9bd96591 --- /dev/null +++ b/test/livebook/evaluator/default_formatter_test.exs @@ -0,0 +1,16 @@ +defmodule Livebook.Evaluator.DefaultFormatterTest do + use ExUnit.Case, async: true + + alias Livebook.Evaluator.DefaultFormatter + + test "inspects successful results" do + result = 10 + assert {:text, "\e[34m10\e[0m"} = DefaultFormatter.format_response({:ok, result}) + end + + test "gracefully handles errors in the inspect protocol" do + result = %Livebook.TestModules.BadInspect{} + assert {:error, error, :other} = DefaultFormatter.format_response({:ok, result}) + assert error =~ ":bad_return" + end +end diff --git a/test/support/test_modules/bad_inspect.ex b/test/support/test_modules/bad_inspect.ex new file mode 100644 index 000000000..df89cd0e9 --- /dev/null +++ b/test/support/test_modules/bad_inspect.ex @@ -0,0 +1,9 @@ +defmodule Livebook.TestModules.BadInspect do + defstruct [] + + defimpl Inspect do + def inspect(%Livebook.TestModules.BadInspect{}, _opts) do + :bad_return + end + end +end