mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-09 13:07:37 +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
|
* 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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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, []}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
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…
Add table
Reference in a new issue