livebook/test/live_book/evaluator_test.exs
Jonatan Kłosko 79e5c432b3
Move evaluation to a separate Elixir runtime (#20)
* Isolate evaluation in separate node for each session

* Start new remote upon first evaluation and handle nodedown

* Add UI for managing evaluation node, improve naming and structure

* Show runtime initialization errors and some fixes

* Improve standalone node initialization

* Correctly handle multiple sessions connecting to the same node

* Fix session tests concerning evaluation

* Documentation and some refactoring

* Various improvements

* Configure schedulers to get to sleep immediately after evaluation

* Move EvaluatorSpervisor into the Remote namespace

* Fix evaluators cleanup

* Add tests

* Improve flash messages

* Introduce remote genserver taking care of cleanup

* Redefine the Runtime protocol to serve as an interface for evaluation

* Cleanup operations

* Use reference for communication with a standalone node

* Use shortnames for distribution by default

* Update node configuration and make sure epmd is running

* Rename Remote to ErlDist
2021-02-11 12:42:17 +01:00

121 lines
4 KiB
Elixir

defmodule LiveBook.EvaluatorTest do
use ExUnit.Case, async: true
alias LiveBook.Evaluator
setup do
{:ok, evaluator} = Evaluator.start_link()
%{evaluator: evaluator}
end
describe "evaluate_code/4" do
test "given a valid code returns evaluation result", %{evaluator: evaluator} do
code = """
x = 1
y = 2
x + y
"""
Evaluator.evaluate_code(evaluator, self(), code, :code_1)
assert_receive {:evaluation_response, :code_1, {:ok, 3}}
end
test "given no prev_ref does not see previous evaluation context", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, self(), "x = 1", :code_1)
assert_receive {:evaluation_response, :code_1, _}
Evaluator.evaluate_code(evaluator, self(), "x", :code_2)
assert_receive {:evaluation_response, :code_2,
{:error, _kind, %CompileError{description: "undefined function x/0"},
_stacktrace}}
end
test "given prev_ref sees previous evaluation context", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, self(), "x = 1", :code_1)
assert_receive {:evaluation_response, :code_1, _}
Evaluator.evaluate_code(evaluator, self(), "x", :code_2, :code_1)
assert_receive {:evaluation_response, :code_2, {:ok, 1}}
end
test "given invalid prev_ref just uses default context", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, self(), ":hey", :code_1, :code_nonexistent)
assert_receive {:evaluation_response, :code_1, {:ok, :hey}}
end
test "captures standard output and sends it to the caller", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, self(), ~s{IO.puts("hey")}, :code_1)
assert_receive {:evaluation_stdout, :code_1, "hey\n"}
end
test "using standard input results in an immediate error", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, self(), ~s{IO.gets("> ")}, :code_1)
assert_receive {:evaluation_response, :code_1, {:ok, {:error, :enotsup}}}
end
test "returns error along with its kind and stacktrace", %{evaluator: evaluator} do
code = """
List.first(%{})
"""
Evaluator.evaluate_code(evaluator, self(), code, :code_1)
assert_receive {:evaluation_response, :code_1,
{:error, :error, %FunctionClauseError{}, [{List, :first, 1, _location}]}}
end
test "in case of an error returns only the relevant part of stacktrace", %{
evaluator: evaluator
} do
code = """
defmodule LiveBook.EvaluatorTest.Stacktrace.Math do
def bad_math do
result = 1 / 0
{:ok, result}
end
end
defmodule LiveBook.EvaluatorTest.Stacktrace.Cat do
def meow do
LiveBook.EvaluatorTest.Stacktrace.Math.bad_math()
:ok
end
end
LiveBook.EvaluatorTest.Stacktrace.Cat.meow()
"""
Evaluator.evaluate_code(evaluator, self(), code, :code_1)
expected_stacktrace = [
{LiveBook.EvaluatorTest.Stacktrace.Math, :bad_math, 0, [file: 'nofile', line: 3]},
{LiveBook.EvaluatorTest.Stacktrace.Cat, :meow, 0, [file: 'nofile', line: 10]}
]
# Note: evaluating module definitions is relatively slow, so we use a higher wait timeout.
assert_receive {:evaluation_response, :code_1,
{:error, _kind, _error, ^expected_stacktrace}},
1000
end
end
describe "forget_evaluation/2" do
test "invalidates the given reference", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, self(), "x = 1", :code_1)
assert_receive {:evaluation_response, :code_1, _}
Evaluator.forget_evaluation(evaluator, :code_1)
Evaluator.evaluate_code(evaluator, self(), "x", :code_2, :code_1)
assert_receive {:evaluation_response, :code_2,
{:error, _kind, %CompileError{description: "undefined function x/0"},
_stacktrace}}
end
end
end