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:
Jonatan Kłosko 2021-10-13 22:25:33 +02:00 committed by GitHub
parent 79c0e73343
commit 5dea204fc9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 40 additions and 45 deletions

View file

@ -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:

View file

@ -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:

View file

@ -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

View file

@ -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 """

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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}")

View file

@ -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)