mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-12-25 08:51:58 +08:00
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:
parent
f1c2db4118
commit
c4a96bc99c
12 changed files with 289 additions and 86 deletions
|
@ -98,8 +98,8 @@ The following environment variables configure Livebook:
|
|||
|
||||
* LIVEBOOK_DEFAULT_RUNTIME - sets the runtime type that is used
|
||||
by default when none is started explicitly for the given notebook.
|
||||
Must be either "standalone" (Elixir standalone) or "embedded" (Embedded).
|
||||
Defaults to "standalone".
|
||||
Must be either "standalone" (Elixir standalone), "mix[:path]" (Mix 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.
|
||||
|
||||
|
|
|
@ -20,10 +20,11 @@ config :livebook, :authentication_mode, :token
|
|||
# Sets the default runtime to ElixirStandalone.
|
||||
# This is the desired default most of the time,
|
||||
# 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
|
||||
# a synchronous `init/0` method.
|
||||
config :livebook, :default_runtime, Livebook.Runtime.ElixirStandalone
|
||||
# a synchronous `init` function that takes the
|
||||
# configured arguments.
|
||||
config :livebook, :default_runtime, {Livebook.Runtime.ElixirStandalone, []}
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
|
|
|
@ -29,4 +29,4 @@ config :livebook,
|
|||
config :livebook,
|
||||
:default_runtime,
|
||||
Livebook.Config.default_runtime!("LIVEBOOK_DEFAULT_RUNTIME") ||
|
||||
Livebook.Runtime.ElixirStandalone
|
||||
{Livebook.Runtime.ElixirStandalone, []}
|
||||
|
|
|
@ -15,7 +15,7 @@ config :livebook, :authentication_mode, :disabled
|
|||
# Use the embedded runtime in tests by default, so they
|
||||
# are cheaper to run. Other runtimes can be tested by starting
|
||||
# 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,
|
||||
# see https://github.com/elixir-nx/livebook/pull/173#issuecomment-819468549
|
||||
|
|
|
@ -16,9 +16,10 @@ defmodule Livebook.Config do
|
|||
end
|
||||
|
||||
@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
|
||||
Application.fetch_env!(:livebook, :default_runtime)
|
||||
end
|
||||
|
@ -141,18 +142,56 @@ defmodule Livebook.Config do
|
|||
"""
|
||||
def default_runtime!(env) do
|
||||
if runtime = System.get_env(env) do
|
||||
case runtime do
|
||||
"standalone" ->
|
||||
Livebook.Runtime.ElixirStandalone
|
||||
default_runtime!(env, runtime)
|
||||
end
|
||||
end
|
||||
|
||||
"embedded" ->
|
||||
Livebook.Runtime.Embedded
|
||||
@doc """
|
||||
Parses and validates default runtime within context.
|
||||
"""
|
||||
def default_runtime!(context, runtime) do
|
||||
case runtime do
|
||||
"standalone" ->
|
||||
{Livebook.Runtime.ElixirStandalone, []}
|
||||
|
||||
other ->
|
||||
abort!(
|
||||
~s{expected #{env} to be either "standalone" or "embedded", got: #{inspect(other)}}
|
||||
)
|
||||
end
|
||||
"embedded" ->
|
||||
{Livebook.Runtime.Embedded, []}
|
||||
|
||||
"mix" ->
|
||||
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
|
||||
|
||||
|
|
|
@ -74,6 +74,21 @@ defmodule Livebook.Runtime.MixStandalone do
|
|||
:ok
|
||||
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
|
||||
Emitter.emit(output_emitter, "Running mix #{task}...\n")
|
||||
|
||||
|
|
|
@ -379,15 +379,8 @@ defmodule Livebook.Session do
|
|||
end
|
||||
|
||||
def handle_cast({:queue_cell_evaluation, client_pid, cell_id}, state) do
|
||||
case ensure_runtime(state) do
|
||||
{:ok, state} ->
|
||||
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
|
||||
operation = {:queue_cell_evaluation, client_pid, cell_id}
|
||||
{:noreply, handle_operation(state, operation)}
|
||||
end
|
||||
|
||||
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))
|
||||
end
|
||||
|
||||
defp handle_action(state, {:start_evaluation, cell, section}) do
|
||||
start_evaluation(state, cell, section)
|
||||
defp handle_action(state, :start_runtime) do
|
||||
{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
|
||||
|
||||
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)
|
||||
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
|
||||
if state.data.path != nil and state.data.dirty do
|
||||
content = LiveMarkdown.Export.notebook_to_markdown(state.data.notebook)
|
||||
|
|
|
@ -101,7 +101,8 @@ defmodule Livebook.Session.Data do
|
|||
| {:mark_as_not_dirty, pid()}
|
||||
|
||||
@type action ::
|
||||
{:start_evaluation, Cell.t(), Section.t()}
|
||||
:start_runtime
|
||||
| {:start_evaluation, Cell.t(), Section.t()}
|
||||
| {:stop_evaluation, Section.t()}
|
||||
| {:forget_evaluation, Cell.t(), Section.t()}
|
||||
| {:broadcast_delta, pid(), Cell.t(), Delta.t()}
|
||||
|
@ -258,6 +259,7 @@ defmodule Livebook.Session.Data do
|
|||
|> with_actions()
|
||||
|> queue_prerequisite_cells_evaluation(cell)
|
||||
|> queue_cell_evaluation(cell, section)
|
||||
|> maybe_start_runtime(data)
|
||||
|> maybe_evaluate_queued()
|
||||
|> wrap_ok()
|
||||
else
|
||||
|
@ -431,8 +433,7 @@ defmodule Livebook.Session.Data do
|
|||
def apply_operation(data, {:set_runtime, _client_pid, runtime}) do
|
||||
data
|
||||
|> with_actions()
|
||||
|> set!(runtime: runtime)
|
||||
|> clear_evaluation()
|
||||
|> set_runtime(data, runtime)
|
||||
|> wrap_ok()
|
||||
end
|
||||
|
||||
|
@ -616,14 +617,27 @@ defmodule Livebook.Session.Data do
|
|||
|> reduce(invalidated_cells, &set_cell_info!(&1, &2.id, validity_status: :stale))
|
||||
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
|
||||
ongoing_evaluation? =
|
||||
Enum.any?(data.notebook.sections, fn section ->
|
||||
data.section_infos[section.id].evaluating_cell_id != nil
|
||||
end)
|
||||
|
||||
if ongoing_evaluation? do
|
||||
# A section is evaluating, so we don't start any new evaluation
|
||||
if ongoing_evaluation? or data.runtime == nil do
|
||||
# Don't tigger evaluation if there is one already,
|
||||
# or if we simply don't have a runtime started yet
|
||||
data_actions
|
||||
else
|
||||
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)))
|
||||
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
|
||||
# Given client at revision X and upstream revision Y,
|
||||
# we need Y - X last deltas that the client is not aware of,
|
||||
|
|
|
@ -19,15 +19,21 @@ defmodule LivebookCLI.Server do
|
|||
|
||||
Available options:
|
||||
|
||||
--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
|
||||
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
|
||||
--cookie Sets a cookie for the app distributed node
|
||||
--default-runtime Sets the runtime type that is used by default when none is started
|
||||
explicitly for the given notebook, defaults to standalone
|
||||
Supported options:
|
||||
* standalone - Elixir standalone
|
||||
* mix[:path] - Mix standalone
|
||||
* embedded - Embedded
|
||||
--ip The ip address to start the web application on, defaults to 127.0.0.1
|
||||
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.
|
||||
|
||||
|
@ -75,6 +81,7 @@ defmodule LivebookCLI.Server do
|
|||
|
||||
@switches [
|
||||
cookie: :string,
|
||||
default_runtime: :string,
|
||||
ip: :string,
|
||||
name: :string,
|
||||
port: :integer,
|
||||
|
@ -138,5 +145,10 @@ defmodule LivebookCLI.Server do
|
|||
opts_to_config(opts, [{:livebook, :cookie, cookie} | config])
|
||||
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)
|
||||
end
|
||||
|
|
|
@ -168,7 +168,7 @@ defmodule Livebook.EvaluatorTest do
|
|||
describe "request_completion_items/5" do
|
||||
test "sends completion response to the given process", %{evaluator: evaluator} do
|
||||
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
|
||||
|
||||
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, _}
|
||||
|
||||
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)
|
||||
|
||||
assert_receive {:completion_response, :comp_ref, [%{label: "bright/0"}]}
|
||||
assert_receive {:completion_response, :comp_ref, [%{label: "bright/0"}]}, 1_000
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ defmodule Livebook.Session.DataTest do
|
|||
use ExUnit.Case, async: true
|
||||
|
||||
alias Livebook.Session.Data
|
||||
alias Livebook.{Delta, Notebook, Runtime}
|
||||
alias Livebook.{Delta, Notebook}
|
||||
alias Livebook.Notebook.Cell
|
||||
alias Livebook.Users.User
|
||||
|
||||
alias Livebook.Runtime.NoopRuntime
|
||||
|
||||
describe "new/1" do
|
||||
test "called with no arguments defaults to a blank notebook" do
|
||||
empty_map = %{}
|
||||
|
@ -127,6 +129,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:queue_cell_evaluation, self(), "c2"}
|
||||
])
|
||||
|
@ -166,6 +169,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{: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", 1, :elixir, "c2"},
|
||||
# Evaluate both cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
|
||||
{: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", 3, :elixir, "c4"},
|
||||
# Evaluate cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
|
||||
{: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", 3, :elixir, "c4"},
|
||||
# Evaluate cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
|
@ -345,6 +352,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 1, "s2"},
|
||||
{:insert_cell, self(), "s2", 0, :elixir, "c3"},
|
||||
# Evaluate cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
|
||||
{: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", 1, :markdown, "c2"},
|
||||
# Evaluate the Elixir cell
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{: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", 1, :elixir, "c2"},
|
||||
# Evaluate the Elixir cell
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{: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", 2, :elixir, "c3"},
|
||||
# Evaluate cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
|
||||
{: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", 1, :elixir, "c4"},
|
||||
# Evaluate cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
|
||||
{: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", 1, :elixir, "c4"},
|
||||
# Evaluate cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
|
@ -549,6 +562,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_cell, self(), "s2", 1, :elixir, "c2"},
|
||||
{:insert_cell, self(), "s3", 0, :elixir, "c3"},
|
||||
# Evaluate cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
|
@ -574,6 +588,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s2", 0, :markdown, "c2"},
|
||||
# Evaluate the Elixir cell
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{: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(), "s2", 0, :elixir, "c2"},
|
||||
# Evaluate the Elixir cell
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:queue_cell_evaluation, self(), "c2"}
|
||||
])
|
||||
|
@ -624,6 +640,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_cell, self(), "s3", 0, :elixir, "c3"},
|
||||
{:insert_cell, self(), "s4", 0, :markdown, "c4"},
|
||||
# Evaluate cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
|
||||
{:queue_cell_evaluation, self(), "c3"},
|
||||
|
@ -665,6 +682,7 @@ defmodule Livebook.Session.DataTest do
|
|||
data_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"}
|
||||
])
|
||||
|
||||
|
@ -672,7 +690,7 @@ defmodule Livebook.Session.DataTest do
|
|||
assert :error = Data.apply_operation(data, operation)
|
||||
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_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
|
@ -681,6 +699,46 @@ defmodule Livebook.Session.DataTest do
|
|||
|
||||
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,
|
||||
%{
|
||||
cell_infos: %{"c1" => %{evaluation_status: :evaluating}},
|
||||
|
@ -692,7 +750,8 @@ defmodule Livebook.Session.DataTest do
|
|||
data =
|
||||
data_after_operations!([
|
||||
{: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"}
|
||||
|
@ -707,6 +766,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"}
|
||||
])
|
||||
|
||||
|
@ -726,6 +786,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_section, self(), 1, "s2"},
|
||||
{:insert_cell, self(), "s2", 0, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{: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", 1, :elixir, "c4"},
|
||||
# Evaluate first 2 cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
|
@ -795,6 +857,8 @@ defmodule Livebook.Session.DataTest do
|
|||
data_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"}
|
||||
])
|
||||
|
||||
|
@ -817,6 +881,7 @@ defmodule Livebook.Session.DataTest do
|
|||
data_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_output, self(), "c1", "Hola"}
|
||||
])
|
||||
|
@ -840,6 +905,7 @@ defmodule Livebook.Session.DataTest do
|
|||
data_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_output, self(), "c1", "Hola"}
|
||||
])
|
||||
|
@ -863,6 +929,7 @@ defmodule Livebook.Session.DataTest do
|
|||
data_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
|
||||
])
|
||||
|
@ -888,6 +955,7 @@ defmodule Livebook.Session.DataTest do
|
|||
data_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"}
|
||||
])
|
||||
|
||||
|
@ -911,6 +979,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"}
|
||||
])
|
||||
|
||||
|
@ -929,6 +998,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"}
|
||||
])
|
||||
|
||||
|
@ -955,6 +1025,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:queue_cell_evaluation, self(), "c2"}
|
||||
])
|
||||
|
@ -977,6 +1048,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_section, self(), 1, "s2"},
|
||||
{:insert_cell, self(), "s2", 0, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:queue_cell_evaluation, self(), "c2"}
|
||||
])
|
||||
|
@ -995,12 +1067,13 @@ defmodule Livebook.Session.DataTest do
|
|||
Data.apply_operation(data, operation)
|
||||
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_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{: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"}
|
||||
|
@ -1026,6 +1099,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 1, "s2"},
|
||||
{:insert_cell, self(), "s2", 0, :elixir, "c3"},
|
||||
# Evaluate all cells
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
|
||||
{: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", 1, :elixir, "c2"},
|
||||
{:insert_cell, self(), "s1", 2, :elixir, "c3"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
{:queue_cell_evaluation, self(), "c3"},
|
||||
|
@ -1090,6 +1165,7 @@ defmodule Livebook.Session.DataTest do
|
|||
data_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{: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_section, self(), 1, "s2"},
|
||||
{:insert_cell, self(), "s2", 0, :elixir, "c3"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
|
@ -1134,6 +1211,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:queue_cell_evaluation, self(), "c2"}
|
||||
])
|
||||
|
@ -1150,6 +1228,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{: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", 1, :elixir, "c2"},
|
||||
{:insert_cell, self(), "s1", 2, :elixir, "c3"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
{:queue_cell_evaluation, self(), "c3"}
|
||||
|
@ -1649,6 +1729,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :input, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
{: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
|
||||
data = Data.new()
|
||||
|
||||
{:ok, runtime} = Runtime.Embedded.init()
|
||||
Runtime.connect(runtime)
|
||||
|
||||
runtime = NoopRuntime.new()
|
||||
operation = {:set_runtime, self(), runtime}
|
||||
|
||||
assert {:ok, %{runtime: ^runtime}, []} = Data.apply_operation(data, operation)
|
||||
|
@ -1682,6 +1761,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:set_runtime, self(), NoopRuntime.new()},
|
||||
{:queue_cell_evaluation, self(), "c1"},
|
||||
{:queue_cell_evaluation, self(), "c2"},
|
||||
# Second section with evaluating and queued cells
|
||||
|
@ -1692,9 +1772,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{:queue_cell_evaluation, self(), "c4"}
|
||||
])
|
||||
|
||||
{:ok, runtime} = Runtime.Embedded.init()
|
||||
Runtime.connect(runtime)
|
||||
|
||||
runtime = NoopRuntime.new()
|
||||
operation = {:set_runtime, self(), runtime}
|
||||
|
||||
assert {:ok,
|
||||
|
@ -1711,6 +1789,30 @@ defmodule Livebook.Session.DataTest do
|
|||
}
|
||||
}, []} = Data.apply_operation(data, operation)
|
||||
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
|
||||
|
||||
describe "apply_operation/2 given :set_path" do
|
||||
|
|
19
test/support/noop_runtime.ex
Normal file
19
test/support/noop_runtime.ex
Normal 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
|
Loading…
Reference in a new issue