mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-11-10 14:11:29 +08:00
Fix intermittent test failures (#596)
* Bump Elixir on the CI * Start permanent node manager in tests * Adjust timeouts * Make sure random node cookies are alphanumeric
This commit is contained in:
parent
79c0e73343
commit
5dea204fc9
9 changed files with 40 additions and 45 deletions
2
.github/workflows/deploy.yaml
vendored
2
.github/workflows/deploy.yaml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
uses: erlef/setup-beam@v1
|
uses: erlef/setup-beam@v1
|
||||||
with:
|
with:
|
||||||
otp-version: '24.0'
|
otp-version: '24.0'
|
||||||
elixir-version: '1.12.0'
|
elixir-version: '1.12.3'
|
||||||
- name: Cache Mix
|
- name: Cache Mix
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
||||||
uses: erlef/setup-beam@v1
|
uses: erlef/setup-beam@v1
|
||||||
with:
|
with:
|
||||||
otp-version: '24.0'
|
otp-version: '24.0'
|
||||||
elixir-version: '1.12.0'
|
elixir-version: '1.12.3'
|
||||||
- name: Cache Mix
|
- name: Cache Mix
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ defmodule Livebook.Runtime.ErlDist.NodeManager do
|
||||||
In most cases we enforce a single manager per node
|
In most cases we enforce a single manager per node
|
||||||
and identify it by a name, but this can be opted-out
|
and identify it by a name, but this can be opted-out
|
||||||
from by using this option. Defaults to `false`.
|
from by using this option. Defaults to `false`.
|
||||||
|
|
||||||
|
* `:auto_termination` - whether to terminate the manager
|
||||||
|
when the last runtime server terminates. Defaults to `true`.
|
||||||
"""
|
"""
|
||||||
def start(opts \\ []) do
|
def start(opts \\ []) do
|
||||||
{opts, gen_opts} = split_opts(opts)
|
{opts, gen_opts} = split_opts(opts)
|
||||||
|
|
@ -74,6 +77,7 @@ defmodule Livebook.Runtime.ErlDist.NodeManager do
|
||||||
@impl true
|
@impl true
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
unload_modules_on_termination = Keyword.get(opts, :unload_modules_on_termination, true)
|
unload_modules_on_termination = Keyword.get(opts, :unload_modules_on_termination, true)
|
||||||
|
auto_termination = Keyword.get(opts, :auto_termination, true)
|
||||||
|
|
||||||
## Initialize the node
|
## Initialize the node
|
||||||
|
|
||||||
|
|
@ -97,6 +101,7 @@ defmodule Livebook.Runtime.ErlDist.NodeManager do
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
unload_modules_on_termination: unload_modules_on_termination,
|
unload_modules_on_termination: unload_modules_on_termination,
|
||||||
|
auto_termination: auto_termination,
|
||||||
server_supevisor: server_supevisor,
|
server_supevisor: server_supevisor,
|
||||||
runtime_servers: [],
|
runtime_servers: [],
|
||||||
initial_ignore_module_conflict: initial_ignore_module_conflict,
|
initial_ignore_module_conflict: initial_ignore_module_conflict,
|
||||||
|
|
@ -124,7 +129,7 @@ defmodule Livebook.Runtime.ErlDist.NodeManager do
|
||||||
def handle_info({:DOWN, _, :process, pid, _}, state) do
|
def handle_info({:DOWN, _, :process, pid, _}, state) do
|
||||||
if pid in state.runtime_servers do
|
if pid in state.runtime_servers do
|
||||||
case update_in(state.runtime_servers, &List.delete(&1, pid)) do
|
case update_in(state.runtime_servers, &List.delete(&1, pid)) do
|
||||||
%{runtime_servers: []} = state -> {:stop, :normal, state}
|
%{runtime_servers: [], auto_termination: true} = state -> {:stop, :normal, state}
|
||||||
state -> {:noreply, state}
|
state -> {:noreply, state}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ defmodule Livebook.Utils do
|
||||||
"""
|
"""
|
||||||
@spec random_cookie() :: atom()
|
@spec random_cookie() :: atom()
|
||||||
def random_cookie() do
|
def random_cookie() do
|
||||||
:crypto.strong_rand_bytes(42) |> Base.url_encode64() |> String.to_atom()
|
:crypto.strong_rand_bytes(35) |> Base.encode32(case: :lower) |> String.to_atom()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ defmodule Livebook.EvaluatorTest do
|
||||||
assert_receive {:evaluation_response, :code_1,
|
assert_receive {:evaluation_response, :code_1,
|
||||||
{:error, _kind, _error, ^expected_stacktrace},
|
{:error, _kind, _error, ^expected_stacktrace},
|
||||||
%{evaluation_time_ms: _time_ms}},
|
%{evaluation_time_ms: _time_ms}},
|
||||||
1_000
|
2_000
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -160,12 +160,13 @@ defmodule Livebook.EvaluatorTest do
|
||||||
assert_receive {:evaluation_response, :code_1, {:ok, widget_pid1},
|
assert_receive {:evaluation_response, :code_1, {:ok, widget_pid1},
|
||||||
%{evaluation_time_ms: _time_ms}}
|
%{evaluation_time_ms: _time_ms}}
|
||||||
|
|
||||||
|
ref = Process.monitor(widget_pid1)
|
||||||
|
|
||||||
Evaluator.evaluate_code(evaluator, self(), spawn_widget_code(), :code_1)
|
Evaluator.evaluate_code(evaluator, self(), spawn_widget_code(), :code_1)
|
||||||
|
|
||||||
assert_receive {:evaluation_response, :code_1, {:ok, widget_pid2},
|
assert_receive {:evaluation_response, :code_1, {:ok, widget_pid2},
|
||||||
%{evaluation_time_ms: _time_ms}}
|
%{evaluation_time_ms: _time_ms}}
|
||||||
|
|
||||||
ref = Process.monitor(widget_pid1)
|
|
||||||
assert_receive {:DOWN, ^ref, :process, ^widget_pid1, :shutdown}
|
assert_receive {:DOWN, ^ref, :process, ^widget_pid1, :shutdown}
|
||||||
|
|
||||||
assert Process.alive?(widget_pid2)
|
assert Process.alive?(widget_pid2)
|
||||||
|
|
@ -212,9 +213,9 @@ defmodule Livebook.EvaluatorTest do
|
||||||
assert_receive {:evaluation_response, :code_1, {:ok, widget_pid1},
|
assert_receive {:evaluation_response, :code_1, {:ok, widget_pid1},
|
||||||
%{evaluation_time_ms: _time_ms}}
|
%{evaluation_time_ms: _time_ms}}
|
||||||
|
|
||||||
|
ref = Process.monitor(widget_pid1)
|
||||||
Evaluator.forget_evaluation(evaluator, :code_1)
|
Evaluator.forget_evaluation(evaluator, :code_1)
|
||||||
|
|
||||||
ref = Process.monitor(widget_pid1)
|
|
||||||
assert_receive {:DOWN, ^ref, :process, ^widget_pid1, :shutdown}
|
assert_receive {:DOWN, ^ref, :process, ^widget_pid1, :shutdown}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -225,7 +226,7 @@ defmodule Livebook.EvaluatorTest do
|
||||||
Evaluator.handle_intellisense(evaluator, self(), :ref, request)
|
Evaluator.handle_intellisense(evaluator, self(), :ref, request)
|
||||||
|
|
||||||
assert_receive {:intellisense_response, :ref, ^request, %{items: [%{label: "version/0"}]}},
|
assert_receive {:intellisense_response, :ref, ^request, %{items: [%{label: "version/0"}]}},
|
||||||
1_000
|
2_000
|
||||||
end
|
end
|
||||||
|
|
||||||
test "given evaluation reference uses its bindings and env", %{evaluator: evaluator} do
|
test "given evaluation reference uses its bindings and env", %{evaluator: evaluator} do
|
||||||
|
|
@ -241,13 +242,13 @@ defmodule Livebook.EvaluatorTest do
|
||||||
Evaluator.handle_intellisense(evaluator, self(), :ref, request, :code_1)
|
Evaluator.handle_intellisense(evaluator, self(), :ref, request, :code_1)
|
||||||
|
|
||||||
assert_receive {:intellisense_response, :ref, ^request, %{items: [%{label: "number"}]}},
|
assert_receive {:intellisense_response, :ref, ^request, %{items: [%{label: "number"}]}},
|
||||||
1_000
|
2_000
|
||||||
|
|
||||||
request = {:completion, "ANSI.brigh"}
|
request = {:completion, "ANSI.brigh"}
|
||||||
Evaluator.handle_intellisense(evaluator, self(), :ref, request, :code_1)
|
Evaluator.handle_intellisense(evaluator, self(), :ref, request, :code_1)
|
||||||
|
|
||||||
assert_receive {:intellisense_response, :ref, ^request, %{items: [%{label: "bright/0"}]}},
|
assert_receive {:intellisense_response, :ref, ^request, %{items: [%{label: "bright/0"}]}},
|
||||||
1_000
|
2_000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ defmodule Livebook.Runtime.MixStandaloneTest do
|
||||||
|
|
||||||
ref = emitter.ref
|
ref = emitter.ref
|
||||||
# Wait for the Mix setup to finish and for node initialization
|
# Wait for the Mix setup to finish and for node initialization
|
||||||
assert_receive {:emitter, ^ref, {:output, "Running mix deps.get...\n"}}, 10_000
|
assert_receive {:emitter, ^ref, {:output, "Running mix deps.get...\n"}}, 15_000
|
||||||
assert_receive {:emitter, ^ref, {:ok, runtime}}, 10_000
|
assert_receive {:emitter, ^ref, {:ok, runtime}}, 15_000
|
||||||
|
|
||||||
Runtime.connect(runtime)
|
Runtime.connect(runtime)
|
||||||
%{node: node} = runtime
|
%{node: node} = runtime
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,6 @@ defmodule Livebook.SessionTest do
|
||||||
alias Livebook.{Session, Delta, Runtime, Utils, Notebook, FileSystem}
|
alias Livebook.{Session, Delta, Runtime, Utils, Notebook, FileSystem}
|
||||||
alias Livebook.Notebook.{Section, Cell}
|
alias Livebook.Notebook.{Section, Cell}
|
||||||
|
|
||||||
# Note: queueing evaluation in most of the tests below
|
|
||||||
# requires the runtime to synchronously start first,
|
|
||||||
# so we use a longer timeout just to make sure the tests
|
|
||||||
# pass reliably
|
|
||||||
@evaluation_wait_timeout 3_000
|
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
session = start_session()
|
session = start_session()
|
||||||
%{session: session}
|
%{session: session}
|
||||||
|
|
@ -87,7 +81,8 @@ defmodule Livebook.SessionTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "queue_cell_evaluation/2" do
|
describe "queue_cell_evaluation/2" do
|
||||||
test "sends a queue evaluation operation to subscribers", %{session: session} do
|
test "triggers evaluation and sends update operation once it finishes",
|
||||||
|
%{session: session} do
|
||||||
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
||||||
pid = self()
|
pid = self()
|
||||||
|
|
||||||
|
|
@ -95,22 +90,11 @@ defmodule Livebook.SessionTest do
|
||||||
|
|
||||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||||
|
|
||||||
assert_receive {:operation, {:queue_cell_evaluation, ^pid, ^cell_id}},
|
assert_receive {:operation, {:queue_cell_evaluation, ^pid, ^cell_id}}
|
||||||
@evaluation_wait_timeout
|
|
||||||
end
|
|
||||||
|
|
||||||
test "triggers evaluation and sends update operation once it finishes",
|
|
||||||
%{session: session} do
|
|
||||||
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
|
||||||
|
|
||||||
{_section_id, cell_id} = insert_section_and_cell(session.pid)
|
|
||||||
|
|
||||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
|
||||||
|
|
||||||
assert_receive {:operation,
|
assert_receive {:operation,
|
||||||
{:add_cell_evaluation_response, _, ^cell_id, _,
|
{:add_cell_evaluation_response, _, ^cell_id, _,
|
||||||
%{evaluation_time_ms: _time_ms}}},
|
%{evaluation_time_ms: _time_ms}}}
|
||||||
@evaluation_wait_timeout
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -124,8 +108,7 @@ defmodule Livebook.SessionTest do
|
||||||
|
|
||||||
Session.cancel_cell_evaluation(session.pid, cell_id)
|
Session.cancel_cell_evaluation(session.pid, cell_id)
|
||||||
|
|
||||||
assert_receive {:operation, {:cancel_cell_evaluation, ^pid, ^cell_id}},
|
assert_receive {:operation, {:cancel_cell_evaluation, ^pid, ^cell_id}}
|
||||||
@evaluation_wait_timeout
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -258,7 +241,7 @@ defmodule Livebook.SessionTest do
|
||||||
Session.set_file(session.pid, file)
|
Session.set_file(session.pid, file)
|
||||||
|
|
||||||
# Wait for the session to deal with the files
|
# Wait for the session to deal with the files
|
||||||
Process.sleep(50)
|
Process.sleep(100)
|
||||||
|
|
||||||
assert {:ok, true} =
|
assert {:ok, true} =
|
||||||
FileSystem.File.exists?(FileSystem.File.resolve(tmp_dir, "images/test.jpg"))
|
FileSystem.File.exists?(FileSystem.File.resolve(tmp_dir, "images/test.jpg"))
|
||||||
|
|
@ -280,7 +263,7 @@ defmodule Livebook.SessionTest do
|
||||||
Session.set_file(session.pid, nil)
|
Session.set_file(session.pid, nil)
|
||||||
|
|
||||||
# Wait for the session to deal with the files
|
# Wait for the session to deal with the files
|
||||||
Process.sleep(50)
|
Process.sleep(200)
|
||||||
|
|
||||||
assert {:ok, true} = FileSystem.File.exists?(image_file)
|
assert {:ok, true} = FileSystem.File.exists?(image_file)
|
||||||
|
|
||||||
|
|
@ -419,8 +402,7 @@ defmodule Livebook.SessionTest do
|
||||||
# Give it a bit more time as this involves starting a system process.
|
# Give it a bit more time as this involves starting a system process.
|
||||||
assert_receive {:operation,
|
assert_receive {:operation,
|
||||||
{:add_cell_evaluation_response, _, ^cell_id, _,
|
{:add_cell_evaluation_response, _, ^cell_id, _,
|
||||||
%{evaluation_time_ms: _time_ms}}},
|
%{evaluation_time_ms: _time_ms}}}
|
||||||
@evaluation_wait_timeout
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "if the runtime node goes down, notifies the subscribers" do
|
test "if the runtime node goes down, notifies the subscribers" do
|
||||||
|
|
@ -481,8 +463,7 @@ defmodule Livebook.SessionTest do
|
||||||
|
|
||||||
assert_receive {:operation,
|
assert_receive {:operation,
|
||||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output},
|
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output},
|
||||||
%{evaluation_time_ms: _time_ms}}},
|
%{evaluation_time_ms: _time_ms}}}
|
||||||
@evaluation_wait_timeout
|
|
||||||
|
|
||||||
assert text_output =~ "Jake Peralta"
|
assert text_output =~ "Jake Peralta"
|
||||||
end
|
end
|
||||||
|
|
@ -512,7 +493,7 @@ defmodule Livebook.SessionTest do
|
||||||
assert_receive {:operation,
|
assert_receive {:operation,
|
||||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output},
|
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output},
|
||||||
%{evaluation_time_ms: _time_ms}}},
|
%{evaluation_time_ms: _time_ms}}},
|
||||||
@evaluation_wait_timeout
|
2_000
|
||||||
|
|
||||||
assert text_output =~ "no matching Livebook input found"
|
assert text_output =~ "no matching Livebook input found"
|
||||||
end
|
end
|
||||||
|
|
@ -544,7 +525,7 @@ defmodule Livebook.SessionTest do
|
||||||
assert_receive {:operation,
|
assert_receive {:operation,
|
||||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output},
|
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output},
|
||||||
%{evaluation_time_ms: _time_ms}}},
|
%{evaluation_time_ms: _time_ms}}},
|
||||||
@evaluation_wait_timeout
|
2_000
|
||||||
|
|
||||||
assert text_output =~ "no matching Livebook input found"
|
assert text_output =~ "no matching Livebook input found"
|
||||||
end
|
end
|
||||||
|
|
@ -623,7 +604,7 @@ defmodule Livebook.SessionTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "session created_at is before now", %{session: session} do
|
test "session created_at is before now", %{session: session} do
|
||||||
assert session.created_at < DateTime.utc_now()
|
assert DateTime.compare(session.created_at, DateTime.utc_now()) == :lt
|
||||||
end
|
end
|
||||||
|
|
||||||
defp start_session(opts \\ []) do
|
defp start_session(opts \\ []) do
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
||||||
|
|
||||||
test "queueing cell evaluation", %{conn: conn, session: session} do
|
test "queueing cell evaluation", %{conn: conn, session: session} do
|
||||||
section_id = insert_section(session.pid)
|
section_id = insert_section(session.pid)
|
||||||
cell_id = insert_text_cell(session.pid, section_id, :elixir, "Process.sleep(10)")
|
cell_id = insert_text_cell(session.pid, section_id, :elixir, "Process.sleep(50)")
|
||||||
|
|
||||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
|
# Start manager on the current node and configure it not to
|
||||||
|
# terminate automatically, so there is no race condition
|
||||||
|
# when starting/stopping Embedded runtimes in parallel
|
||||||
|
Livebook.Runtime.ErlDist.NodeManager.start(
|
||||||
|
auto_termination: false,
|
||||||
|
unload_modules_on_termination: false
|
||||||
|
)
|
||||||
|
|
||||||
erl_docs_available? = Code.fetch_docs(:gen_server) != {:error, :chunk_not_found}
|
erl_docs_available? = Code.fetch_docs(:gen_server) != {:error, :chunk_not_found}
|
||||||
|
|
||||||
exclude = []
|
exclude = []
|
||||||
exclude = if erl_docs_available?, do: exclude, else: Keyword.put(exclude, :erl_docs, true)
|
exclude = if erl_docs_available?, do: exclude, else: Keyword.put(exclude, :erl_docs, true)
|
||||||
|
|
||||||
ExUnit.start(assert_receive_timeout: 300, exclude: exclude)
|
ExUnit.start(assert_receive_timeout: 1_000, exclude: exclude)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue