Add support for Mix runtime as the default one (#334)

* Add support for Mix runtime as a default

* Support default runtime options in the CLI

* Set cell status to queued while runtime is being started

* Clean up tests
This commit is contained in:
Jonatan Kłosko 2021-06-09 16:24:02 +02:00 committed by GitHub
parent f1c2db4118
commit c4a96bc99c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 289 additions and 86 deletions

View file

@ -98,8 +98,8 @@ The following environment variables configure Livebook:
* LIVEBOOK_DEFAULT_RUNTIME - sets the runtime type that is used * LIVEBOOK_DEFAULT_RUNTIME - sets the runtime type that is used
by default when none is started explicitly for the given notebook. by default when none is started explicitly for the given notebook.
Must be either "standalone" (Elixir standalone) or "embedded" (Embedded). Must be either "standalone" (Elixir standalone), "mix[:path]" (Mix standalone)
Defaults to "standalone". or "embedded" (Embedded). Defaults to "standalone".
* LIVEBOOK_IP - sets the ip address to start the web application on. Must be a valid IPv4 or IPv6 address. * LIVEBOOK_IP - sets the ip address to start the web application on. Must be a valid IPv4 or IPv6 address.

View file

@ -20,10 +20,11 @@ config :livebook, :authentication_mode, :token
# Sets the default runtime to ElixirStandalone. # Sets the default runtime to ElixirStandalone.
# This is the desired default most of the time, # This is the desired default most of the time,
# but in some specific use cases you may want # but in some specific use cases you may want
# to configure that to the Embedded runtime instead. # to configure that to the Embedded or Mix runtime instead.
# Also make sure the configured runtime has # Also make sure the configured runtime has
# a synchronous `init/0` method. # a synchronous `init` function that takes the
config :livebook, :default_runtime, Livebook.Runtime.ElixirStandalone # configured arguments.
config :livebook, :default_runtime, {Livebook.Runtime.ElixirStandalone, []}
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.

View file

@ -29,4 +29,4 @@ config :livebook,
config :livebook, config :livebook,
:default_runtime, :default_runtime,
Livebook.Config.default_runtime!("LIVEBOOK_DEFAULT_RUNTIME") || Livebook.Config.default_runtime!("LIVEBOOK_DEFAULT_RUNTIME") ||
Livebook.Runtime.ElixirStandalone {Livebook.Runtime.ElixirStandalone, []}

View file

@ -15,7 +15,7 @@ config :livebook, :authentication_mode, :disabled
# Use the embedded runtime in tests by default, so they # Use the embedded runtime in tests by default, so they
# are cheaper to run. Other runtimes can be tested by starting # are cheaper to run. Other runtimes can be tested by starting
# and setting them explicitly # and setting them explicitly
config :livebook, :default_runtime, Livebook.Runtime.Embedded config :livebook, :default_runtime, {Livebook.Runtime.Embedded, []}
# Use longnames when running tests in CI, so that no host resolution is required, # Use longnames when running tests in CI, so that no host resolution is required,
# see https://github.com/elixir-nx/livebook/pull/173#issuecomment-819468549 # see https://github.com/elixir-nx/livebook/pull/173#issuecomment-819468549

View file

@ -16,9 +16,10 @@ defmodule Livebook.Config do
end end
@doc """ @doc """
Returns the runtime module to be used by default. Returns the runtime module and `init` args used to start
the default runtime.
""" """
@spec default_runtime() :: Livebook.Runtime.t() @spec default_runtime() :: {Livebook.Runtime.t(), list()}
def default_runtime() do def default_runtime() do
Application.fetch_env!(:livebook, :default_runtime) Application.fetch_env!(:livebook, :default_runtime)
end end
@ -141,18 +142,56 @@ defmodule Livebook.Config do
""" """
def default_runtime!(env) do def default_runtime!(env) do
if runtime = System.get_env(env) do if runtime = System.get_env(env) do
case runtime do default_runtime!(env, runtime)
"standalone" -> end
Livebook.Runtime.ElixirStandalone end
"embedded" -> @doc """
Livebook.Runtime.Embedded Parses and validates default runtime within context.
"""
def default_runtime!(context, runtime) do
case runtime do
"standalone" ->
{Livebook.Runtime.ElixirStandalone, []}
other -> "embedded" ->
abort!( {Livebook.Runtime.Embedded, []}
~s{expected #{env} to be either "standalone" or "embedded", got: #{inspect(other)}}
) "mix" ->
end case mix_path(File.cwd!()) do
{:ok, path} ->
{Livebook.Runtime.MixStandalone, [path]}
:error ->
abort!(
"the current directory is not a Mix project, make sure to specify the path explicitly with mix:path"
)
end
"mix:" <> path ->
case mix_path(path) do
{:ok, path} ->
{Livebook.Runtime.MixStandalone, [path]}
:error ->
abort!(~s{"#{path}" does not point to a Mix project})
end
other ->
abort!(
~s{expected #{context} to be either "standalone", "mix[:path]" or "embedded", got: #{inspect(other)}}
)
end
end
defp mix_path(path) do
path = Path.expand(path)
mixfile = Path.join(path, "mix.exs")
if File.exists?(mixfile) do
{:ok, path}
else
:error
end end
end end

View file

@ -74,6 +74,21 @@ defmodule Livebook.Runtime.MixStandalone do
:ok :ok
end end
@doc """
A synchronous version of of `init_async/2`.
"""
@spec init(String.t()) :: {:ok, t()} | {:error, String.t()}
def init(project_path) do
%{ref: ref} = emitter = Livebook.Utils.Emitter.new(self())
init_async(project_path, emitter)
receive do
{:emitter, ^ref, {:ok, runtime}} -> {:ok, runtime}
{:emitter, ^ref, {:error, error}} -> {:error, error}
end
end
defp run_mix_task(task, project_path, output_emitter) do defp run_mix_task(task, project_path, output_emitter) do
Emitter.emit(output_emitter, "Running mix #{task}...\n") Emitter.emit(output_emitter, "Running mix #{task}...\n")

View file

@ -379,15 +379,8 @@ defmodule Livebook.Session do
end end
def handle_cast({:queue_cell_evaluation, client_pid, cell_id}, state) do def handle_cast({:queue_cell_evaluation, client_pid, cell_id}, state) do
case ensure_runtime(state) do operation = {:queue_cell_evaluation, client_pid, cell_id}
{:ok, state} -> {:noreply, handle_operation(state, operation)}
operation = {:queue_cell_evaluation, client_pid, cell_id}
{:noreply, handle_operation(state, operation)}
{:error, error} ->
broadcast_error(state.session_id, "failed to setup runtime - #{error}")
{:noreply, state}
end
end end
def handle_cast({:cancel_cell_evaluation, client_pid, cell_id}, state) do def handle_cast({:cancel_cell_evaluation, client_pid, cell_id}, state) do
@ -667,8 +660,34 @@ defmodule Livebook.Session do
Enum.reduce(actions, state, &handle_action(&2, &1)) Enum.reduce(actions, state, &handle_action(&2, &1))
end end
defp handle_action(state, {:start_evaluation, cell, section}) do defp handle_action(state, :start_runtime) do
start_evaluation(state, cell, section) {runtime_module, args} = Livebook.Config.default_runtime()
case apply(runtime_module, :init, args) do
{:ok, runtime} ->
runtime_monitor_ref = Runtime.connect(runtime)
%{state | runtime_monitor_ref: runtime_monitor_ref}
|> handle_operation({:set_runtime, self(), runtime})
{:error, error} ->
broadcast_error(state.session_id, "failed to setup runtime - #{error}")
handle_operation(state, {:set_runtime, self(), nil})
end
end
defp handle_action(state, {:start_evaluation, cell, _section}) do
prev_ref =
state.data.notebook
|> Notebook.parent_cells_with_section(cell.id)
|> Enum.find_value(fn {cell, _} -> is_struct(cell, Cell.Elixir) && cell.id end)
file = (state.data.path || "") <> "#cell"
opts = [file: file]
Runtime.evaluate_code(state.data.runtime, cell.source, :main, cell.id, prev_ref, opts)
state
end end
defp handle_action(state, {:stop_evaluation, _section}) do defp handle_action(state, {:stop_evaluation, _section}) do
@ -705,34 +724,6 @@ defmodule Livebook.Session do
Phoenix.PubSub.broadcast(Livebook.PubSub, "sessions:#{session_id}", message) Phoenix.PubSub.broadcast(Livebook.PubSub, "sessions:#{session_id}", message)
end end
defp start_evaluation(state, cell, _section) do
prev_ref =
state.data.notebook
|> Notebook.parent_cells_with_section(cell.id)
|> Enum.find_value(fn {cell, _} -> is_struct(cell, Cell.Elixir) && cell.id end)
file = (state.data.path || "") <> "#cell"
opts = [file: file]
Runtime.evaluate_code(state.data.runtime, cell.source, :main, cell.id, prev_ref, opts)
state
end
# Checks if a runtime already set, and if that's not the case
# starts a new standalone one.
defp ensure_runtime(%{data: %{runtime: nil}} = state) do
with {:ok, runtime} <- Livebook.Config.default_runtime().init() do
runtime_monitor_ref = Runtime.connect(runtime)
{:ok,
%{state | runtime_monitor_ref: runtime_monitor_ref}
|> handle_operation({:set_runtime, self(), runtime})}
end
end
defp ensure_runtime(state), do: {:ok, state}
defp maybe_save_notebook(state) do defp maybe_save_notebook(state) do
if state.data.path != nil and state.data.dirty do if state.data.path != nil and state.data.dirty do
content = LiveMarkdown.Export.notebook_to_markdown(state.data.notebook) content = LiveMarkdown.Export.notebook_to_markdown(state.data.notebook)

View file

@ -101,7 +101,8 @@ defmodule Livebook.Session.Data do
| {:mark_as_not_dirty, pid()} | {:mark_as_not_dirty, pid()}
@type action :: @type action ::
{:start_evaluation, Cell.t(), Section.t()} :start_runtime
| {:start_evaluation, Cell.t(), Section.t()}
| {:stop_evaluation, Section.t()} | {:stop_evaluation, Section.t()}
| {:forget_evaluation, Cell.t(), Section.t()} | {:forget_evaluation, Cell.t(), Section.t()}
| {:broadcast_delta, pid(), Cell.t(), Delta.t()} | {:broadcast_delta, pid(), Cell.t(), Delta.t()}
@ -258,6 +259,7 @@ defmodule Livebook.Session.Data do
|> with_actions() |> with_actions()
|> queue_prerequisite_cells_evaluation(cell) |> queue_prerequisite_cells_evaluation(cell)
|> queue_cell_evaluation(cell, section) |> queue_cell_evaluation(cell, section)
|> maybe_start_runtime(data)
|> maybe_evaluate_queued() |> maybe_evaluate_queued()
|> wrap_ok() |> wrap_ok()
else else
@ -431,8 +433,7 @@ defmodule Livebook.Session.Data do
def apply_operation(data, {:set_runtime, _client_pid, runtime}) do def apply_operation(data, {:set_runtime, _client_pid, runtime}) do
data data
|> with_actions() |> with_actions()
|> set!(runtime: runtime) |> set_runtime(data, runtime)
|> clear_evaluation()
|> wrap_ok() |> wrap_ok()
end end
@ -616,14 +617,27 @@ defmodule Livebook.Session.Data do
|> reduce(invalidated_cells, &set_cell_info!(&1, &2.id, validity_status: :stale)) |> reduce(invalidated_cells, &set_cell_info!(&1, &2.id, validity_status: :stale))
end end
defp maybe_start_runtime({data, _} = data_actions, prev_data) do
if data.runtime == nil and not any_cell_queued?(prev_data) and any_cell_queued?(data) do
add_action(data_actions, :start_runtime)
else
data_actions
end
end
defp any_cell_queued?(data) do
Enum.any?(data.section_infos, fn {_section_id, info} -> info.evaluation_queue != [] end)
end
defp maybe_evaluate_queued({data, _} = data_actions) do defp maybe_evaluate_queued({data, _} = data_actions) do
ongoing_evaluation? = ongoing_evaluation? =
Enum.any?(data.notebook.sections, fn section -> Enum.any?(data.notebook.sections, fn section ->
data.section_infos[section.id].evaluating_cell_id != nil data.section_infos[section.id].evaluating_cell_id != nil
end) end)
if ongoing_evaluation? do if ongoing_evaluation? or data.runtime == nil do
# A section is evaluating, so we don't start any new evaluation # Don't tigger evaluation if there is one already,
# or if we simply don't have a runtime started yet
data_actions data_actions
else else
Enum.find_value(data.notebook.sections, data_actions, fn section -> Enum.find_value(data.notebook.sections, data_actions, fn section ->
@ -801,6 +815,16 @@ defmodule Livebook.Session.Data do
|> set!(notebook: Notebook.update_cell(data.notebook, cell.id, &Map.merge(&1, attrs))) |> set!(notebook: Notebook.update_cell(data.notebook, cell.id, &Map.merge(&1, attrs)))
end end
defp set_runtime(data_actions, prev_data, runtime) do
{data, _} = data_actions = set!(data_actions, runtime: runtime)
if prev_data.runtime == nil and data.runtime != nil do
maybe_evaluate_queued(data_actions)
else
clear_evaluation(data_actions)
end
end
defp purge_deltas(cell_info) do defp purge_deltas(cell_info) do
# Given client at revision X and upstream revision Y, # Given client at revision X and upstream revision Y,
# we need Y - X last deltas that the client is not aware of, # we need Y - X last deltas that the client is not aware of,

View file

@ -19,15 +19,21 @@ defmodule LivebookCLI.Server do
Available options: Available options:
--cookie Sets a cookie for the app distributed node --cookie Sets a cookie for the app distributed node
--ip The ip address to start the web application on, defaults to 127.0.0.1 --default-runtime Sets the runtime type that is used by default when none is started
Must be a valid IPv4 or IPv6 address explicitly for the given notebook, defaults to standalone
--name Set a name for the app distributed node Supported options:
--no-token Disable token authentication, enabled by default * standalone - Elixir standalone
If LIVEBOOK_PASSWORD is set, it takes precedence over token auth * mix[:path] - Mix standalone
-p, --port The port to start the web application on, defaults to 8080 * embedded - Embedded
--root-path The root path to use for file selection --ip The ip address to start the web application on, defaults to 127.0.0.1
--sname Set a short name for the app distributed node Must be a valid IPv4 or IPv6 address
--name Set a name for the app distributed node
--no-token Disable token authentication, enabled by default
If LIVEBOOK_PASSWORD is set, it takes precedence over token auth
-p, --port The port to start the web application on, defaults to 8080
--root-path The root path to use for file selection
--sname Set a short name for the app distributed node
The --help option can be given to print this notice. The --help option can be given to print this notice.
@ -75,6 +81,7 @@ defmodule LivebookCLI.Server do
@switches [ @switches [
cookie: :string, cookie: :string,
default_runtime: :string,
ip: :string, ip: :string,
name: :string, name: :string,
port: :integer, port: :integer,
@ -138,5 +145,10 @@ defmodule LivebookCLI.Server do
opts_to_config(opts, [{:livebook, :cookie, cookie} | config]) opts_to_config(opts, [{:livebook, :cookie, cookie} | config])
end end
defp opts_to_config([{:default_runtime, default_runtime} | opts], config) do
default_runtime = Livebook.Config.default_runtime!("--default-runtime", default_runtime)
opts_to_config(opts, [{:livebook, :default_runtime, default_runtime} | config])
end
defp opts_to_config([_opt | opts], config), do: opts_to_config(opts, config) defp opts_to_config([_opt | opts], config), do: opts_to_config(opts, config)
end end

View file

@ -168,7 +168,7 @@ defmodule Livebook.EvaluatorTest do
describe "request_completion_items/5" do describe "request_completion_items/5" do
test "sends completion response to the given process", %{evaluator: evaluator} do test "sends completion response to the given process", %{evaluator: evaluator} do
Evaluator.request_completion_items(evaluator, self(), :comp_ref, "System.ver") Evaluator.request_completion_items(evaluator, self(), :comp_ref, "System.ver")
assert_receive {:completion_response, :comp_ref, [%{label: "version/0"}]} assert_receive {:completion_response, :comp_ref, [%{label: "version/0"}]}, 1_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
@ -181,11 +181,11 @@ defmodule Livebook.EvaluatorTest do
assert_receive {:evaluation_response, :code_1, _} assert_receive {:evaluation_response, :code_1, _}
Evaluator.request_completion_items(evaluator, self(), :comp_ref, "num", :code_1) Evaluator.request_completion_items(evaluator, self(), :comp_ref, "num", :code_1)
assert_receive {:completion_response, :comp_ref, [%{label: "number"}]} assert_receive {:completion_response, :comp_ref, [%{label: "number"}]}, 1_000
Evaluator.request_completion_items(evaluator, self(), :comp_ref, "ANSI.brigh", :code_1) Evaluator.request_completion_items(evaluator, self(), :comp_ref, "ANSI.brigh", :code_1)
assert_receive {:completion_response, :comp_ref, [%{label: "bright/0"}]} assert_receive {:completion_response, :comp_ref, [%{label: "bright/0"}]}, 1_000
end end
end end

View file

@ -2,10 +2,12 @@ defmodule Livebook.Session.DataTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
alias Livebook.Session.Data alias Livebook.Session.Data
alias Livebook.{Delta, Notebook, Runtime} alias Livebook.{Delta, Notebook}
alias Livebook.Notebook.Cell alias Livebook.Notebook.Cell
alias Livebook.Users.User alias Livebook.Users.User
alias Livebook.Runtime.NoopRuntime
describe "new/1" do describe "new/1" do
test "called with no arguments defaults to a blank notebook" do test "called with no arguments defaults to a blank notebook" do
empty_map = %{} empty_map = %{}
@ -127,6 +129,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
@ -166,6 +169,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
@ -185,6 +189,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
# Evaluate both cells # Evaluate both cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}, {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -241,6 +246,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
{:insert_cell, self(), "s1", 3, :elixir, "c4"}, {:insert_cell, self(), "s1", 3, :elixir, "c4"},
# Evaluate cells # Evaluate cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -281,6 +287,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
{:insert_cell, self(), "s1", 3, :elixir, "c4"}, {:insert_cell, self(), "s1", 3, :elixir, "c4"},
# Evaluate cells # Evaluate cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -345,6 +352,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 1, "s2"}, {:insert_section, self(), 1, "s2"},
{:insert_cell, self(), "s2", 0, :elixir, "c3"}, {:insert_cell, self(), "s2", 0, :elixir, "c3"},
# Evaluate cells # Evaluate cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -369,6 +377,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :markdown, "c2"}, {:insert_cell, self(), "s1", 1, :markdown, "c2"},
# Evaluate the Elixir cell # Evaluate the Elixir cell
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}} {:add_cell_evaluation_response, self(), "c1", {:ok, nil}}
]) ])
@ -391,6 +400,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
# Evaluate the Elixir cell # Evaluate the Elixir cell
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
@ -414,6 +424,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 1, :markdown, "c2"}, {:insert_cell, self(), "s1", 1, :markdown, "c2"},
{:insert_cell, self(), "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
# Evaluate cells # Evaluate cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, self(), "c3"}, {:queue_cell_evaluation, self(), "c3"},
@ -461,6 +472,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s2", 0, :elixir, "c3"}, {:insert_cell, self(), "s2", 0, :elixir, "c3"},
{:insert_cell, self(), "s2", 1, :elixir, "c4"}, {:insert_cell, self(), "s2", 1, :elixir, "c4"},
# Evaluate cells # Evaluate cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -505,6 +517,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s2", 0, :elixir, "c3"}, {:insert_cell, self(), "s2", 0, :elixir, "c3"},
{:insert_cell, self(), "s2", 1, :elixir, "c4"}, {:insert_cell, self(), "s2", 1, :elixir, "c4"},
# Evaluate cells # Evaluate cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -549,6 +562,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s2", 1, :elixir, "c2"}, {:insert_cell, self(), "s2", 1, :elixir, "c2"},
{:insert_cell, self(), "s3", 0, :elixir, "c3"}, {:insert_cell, self(), "s3", 0, :elixir, "c3"},
# Evaluate cells # Evaluate cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -574,6 +588,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s2", 0, :markdown, "c2"}, {:insert_cell, self(), "s2", 0, :markdown, "c2"},
# Evaluate the Elixir cell # Evaluate the Elixir cell
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}} {:add_cell_evaluation_response, self(), "c1", {:ok, nil}}
]) ])
@ -597,6 +612,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s2", 0, :elixir, "c2"}, {:insert_cell, self(), "s2", 0, :elixir, "c2"},
# Evaluate the Elixir cell # Evaluate the Elixir cell
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
@ -624,6 +640,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s3", 0, :elixir, "c3"}, {:insert_cell, self(), "s3", 0, :elixir, "c3"},
{:insert_cell, self(), "s4", 0, :markdown, "c4"}, {:insert_cell, self(), "s4", 0, :markdown, "c4"},
# Evaluate cells # Evaluate cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, self(), "c3"}, {:queue_cell_evaluation, self(), "c3"},
@ -665,6 +682,7 @@ defmodule Livebook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
@ -672,7 +690,7 @@ defmodule Livebook.Session.DataTest do
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "marks the cell as evaluating if the corresponding section is idle" do test "returns start runtime action if there is no runtime and this is the first evaluation" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
@ -681,6 +699,46 @@ defmodule Livebook.Session.DataTest do
operation = {:queue_cell_evaluation, self(), "c1"} operation = {:queue_cell_evaluation, self(), "c1"}
assert {:ok,
%{
cell_infos: %{"c1" => %{evaluation_status: :queued}},
section_infos: %{"s1" => %{evaluating_cell_id: nil, evaluation_queue: ["c1"]}}
}, [:start_runtime]} = Data.apply_operation(data, operation)
end
test "only queues the cell if runtime start has already been requested" do
data =
data_after_operations!([
{:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, self(), "c1"}
])
operation = {:queue_cell_evaluation, self(), "c2"}
assert {:ok,
%{
cell_infos: %{
"c1" => %{evaluation_status: :queued},
"c2" => %{evaluation_status: :queued}
},
section_infos: %{
"s1" => %{evaluating_cell_id: nil, evaluation_queue: ["c1", "c2"]}
}
}, []} = Data.apply_operation(data, operation)
end
test "marks the cell as evaluating if the corresponding section is idle" do
data =
data_after_operations!([
{:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()}
])
operation = {:queue_cell_evaluation, self(), "c1"}
assert {:ok, assert {:ok,
%{ %{
cell_infos: %{"c1" => %{evaluation_status: :evaluating}}, cell_infos: %{"c1" => %{evaluation_status: :evaluating}},
@ -692,7 +750,8 @@ defmodule Livebook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()}
]) ])
operation = {:queue_cell_evaluation, self(), "c1"} operation = {:queue_cell_evaluation, self(), "c1"}
@ -707,6 +766,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
@ -726,6 +786,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_section, self(), 1, "s2"}, {:insert_section, self(), 1, "s2"},
{:insert_cell, self(), "s2", 0, :elixir, "c2"}, {:insert_cell, self(), "s2", 0, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
@ -751,6 +812,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s2", 0, :elixir, "c3"}, {:insert_cell, self(), "s2", 0, :elixir, "c3"},
{:insert_cell, self(), "s2", 1, :elixir, "c4"}, {:insert_cell, self(), "s2", 1, :elixir, "c4"},
# Evaluate first 2 cells # Evaluate first 2 cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}, {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -795,6 +857,8 @@ defmodule Livebook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
@ -817,6 +881,7 @@ defmodule Livebook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_output, self(), "c1", "Hola"} {:add_cell_evaluation_output, self(), "c1", "Hola"}
]) ])
@ -840,6 +905,7 @@ defmodule Livebook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_output, self(), "c1", "Hola"} {:add_cell_evaluation_output, self(), "c1", "Hola"}
]) ])
@ -863,6 +929,7 @@ defmodule Livebook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}} {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
]) ])
@ -888,6 +955,7 @@ defmodule Livebook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
@ -911,6 +979,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
@ -929,6 +998,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
@ -955,6 +1025,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
@ -977,6 +1048,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_section, self(), 1, "s2"}, {:insert_section, self(), 1, "s2"},
{:insert_cell, self(), "s2", 0, :elixir, "c2"}, {:insert_cell, self(), "s2", 0, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
@ -995,12 +1067,13 @@ defmodule Livebook.Session.DataTest do
Data.apply_operation(data, operation) Data.apply_operation(data, operation)
end end
test "if parent cells are not executed, marks them for evaluation first" do test "if parent cells are not evaluated, marks them for evaluation first" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"} {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()}
]) ])
operation = {:queue_cell_evaluation, self(), "c2"} operation = {:queue_cell_evaluation, self(), "c2"}
@ -1026,6 +1099,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 1, "s2"}, {:insert_section, self(), 1, "s2"},
{:insert_cell, self(), "s2", 0, :elixir, "c3"}, {:insert_cell, self(), "s2", 0, :elixir, "c3"},
# Evaluate all cells # Evaluate all cells
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}, {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -1056,6 +1130,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:insert_cell, self(), "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
{:queue_cell_evaluation, self(), "c3"}, {:queue_cell_evaluation, self(), "c3"},
@ -1090,6 +1165,7 @@ defmodule Livebook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}} {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
]) ])
@ -1106,6 +1182,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:insert_section, self(), 1, "s2"}, {:insert_section, self(), 1, "s2"},
{:insert_cell, self(), "s2", 0, :elixir, "c3"}, {:insert_cell, self(), "s2", 0, :elixir, "c3"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}, {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
@ -1134,6 +1211,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
@ -1150,6 +1228,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
@ -1170,6 +1249,7 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:insert_cell, self(), "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
{:queue_cell_evaluation, self(), "c3"} {:queue_cell_evaluation, self(), "c3"}
@ -1649,6 +1729,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :input, "c1"}, {:insert_cell, self(), "s1", 0, :input, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
{:add_cell_evaluation_response, self(), "c2", {:ok, [1, 2, 3]}} {:add_cell_evaluation_response, self(), "c2", {:ok, [1, 2, 3]}}
]) ])
@ -1667,9 +1748,7 @@ defmodule Livebook.Session.DataTest do
test "updates data with the given runtime" do test "updates data with the given runtime" do
data = Data.new() data = Data.new()
{:ok, runtime} = Runtime.Embedded.init() runtime = NoopRuntime.new()
Runtime.connect(runtime)
operation = {:set_runtime, self(), runtime} operation = {:set_runtime, self(), runtime}
assert {:ok, %{runtime: ^runtime}, []} = Data.apply_operation(data, operation) assert {:ok, %{runtime: ^runtime}, []} = Data.apply_operation(data, operation)
@ -1682,6 +1761,7 @@ defmodule Livebook.Session.DataTest do
{:insert_section, self(), 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, self(), "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:set_runtime, self(), NoopRuntime.new()},
{:queue_cell_evaluation, self(), "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, self(), "c2"}, {:queue_cell_evaluation, self(), "c2"},
# Second section with evaluating and queued cells # Second section with evaluating and queued cells
@ -1692,9 +1772,7 @@ defmodule Livebook.Session.DataTest do
{:queue_cell_evaluation, self(), "c4"} {:queue_cell_evaluation, self(), "c4"}
]) ])
{:ok, runtime} = Runtime.Embedded.init() runtime = NoopRuntime.new()
Runtime.connect(runtime)
operation = {:set_runtime, self(), runtime} operation = {:set_runtime, self(), runtime}
assert {:ok, assert {:ok,
@ -1711,6 +1789,30 @@ defmodule Livebook.Session.DataTest do
} }
}, []} = Data.apply_operation(data, operation) }, []} = Data.apply_operation(data, operation)
end end
test "starts evaluation if there was no runtime before and there is now" do
data =
data_after_operations!([
{:insert_section, self(), 0, "s1"},
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:queue_cell_evaluation, self(), "c1"}
])
runtime = NoopRuntime.new()
operation = {:set_runtime, self(), runtime}
assert {:ok,
%{
cell_infos: %{
"c1" => %{evaluation_status: :evaluating}
},
section_infos: %{
"s1" => %{evaluating_cell_id: "c1", evaluation_queue: []}
}
},
[{:start_evaluation, %{id: "c1"}, %{id: "s1"}}]} =
Data.apply_operation(data, operation)
end
end end
describe "apply_operation/2 given :set_path" do describe "apply_operation/2 given :set_path" do

View file

@ -0,0 +1,19 @@
defmodule Livebook.Runtime.NoopRuntime do
@moduledoc false
# A runtime that doesn't do any actual evaluation,
# thus not requiring any underlying resources.
defstruct []
def new(), do: %__MODULE__{}
defimpl Livebook.Runtime do
def connect(_), do: :ok
def disconnect(_), do: :ok
def evaluate_code(_, _, _, _, _, _ \\ []), do: :ok
def forget_evaluation(_, _, _), do: :ok
def drop_container(_, _), do: :ok
def request_completion_items(_, _, _, _, _, _), do: :ok
end
end