diff --git a/lib/live_book/session.ex b/lib/live_book/session.ex index 821dd87e2..a165b35a5 100644 --- a/lib/live_book/session.ex +++ b/lib/live_book/session.ex @@ -104,7 +104,7 @@ defmodule LiveBook.Session do """ @spec insert_section(id(), non_neg_integer()) :: :ok def insert_section(session_id, index) do - GenServer.cast(name(session_id), {:insert_section, index}) + GenServer.cast(name(session_id), {:insert_section, self(), index}) end @doc """ @@ -113,7 +113,7 @@ defmodule LiveBook.Session do @spec insert_cell(id(), Section.id(), non_neg_integer(), Cell.type()) :: :ok def insert_cell(session_id, section_id, index, type) do - GenServer.cast(name(session_id), {:insert_cell, section_id, index, type}) + GenServer.cast(name(session_id), {:insert_cell, self(), section_id, index, type}) end @doc """ @@ -121,7 +121,7 @@ defmodule LiveBook.Session do """ @spec delete_section(id(), Section.id()) :: :ok def delete_section(session_id, section_id) do - GenServer.cast(name(session_id), {:delete_section, section_id}) + GenServer.cast(name(session_id), {:delete_section, self(), section_id}) end @doc """ @@ -129,7 +129,7 @@ defmodule LiveBook.Session do """ @spec delete_cell(id(), Cell.id()) :: :ok def delete_cell(session_id, cell_id) do - GenServer.cast(name(session_id), {:delete_cell, cell_id}) + GenServer.cast(name(session_id), {:delete_cell, self(), cell_id}) end @doc """ @@ -137,7 +137,7 @@ defmodule LiveBook.Session do """ @spec move_cell(id(), Cell.id(), integer()) :: :ok def move_cell(session_id, cell_id, offset) do - GenServer.cast(name(session_id), {:move_cell, cell_id, offset}) + GenServer.cast(name(session_id), {:move_cell, self(), cell_id, offset}) end @doc """ @@ -145,7 +145,7 @@ defmodule LiveBook.Session do """ @spec queue_cell_evaluation(id(), Cell.id()) :: :ok def queue_cell_evaluation(session_id, cell_id) do - GenServer.cast(name(session_id), {:queue_cell_evaluation, cell_id}) + GenServer.cast(name(session_id), {:queue_cell_evaluation, self(), cell_id}) end @doc """ @@ -153,7 +153,7 @@ defmodule LiveBook.Session do """ @spec cancel_cell_evaluation(id(), Cell.id()) :: :ok def cancel_cell_evaluation(session_id, cell_id) do - GenServer.cast(name(session_id), {:cancel_cell_evaluation, cell_id}) + GenServer.cast(name(session_id), {:cancel_cell_evaluation, self(), cell_id}) end @doc """ @@ -161,7 +161,7 @@ defmodule LiveBook.Session do """ @spec set_notebook_name(id(), String.t()) :: :ok def set_notebook_name(session_id, name) do - GenServer.cast(name(session_id), {:set_notebook_name, name}) + GenServer.cast(name(session_id), {:set_notebook_name, self(), name}) end @doc """ @@ -169,15 +169,15 @@ defmodule LiveBook.Session do """ @spec set_section_name(id(), Section.id(), String.t()) :: :ok def set_section_name(session_id, section_id, name) do - GenServer.cast(name(session_id), {:set_section_name, section_id, name}) + GenServer.cast(name(session_id), {:set_section_name, self(), section_id, name}) end @doc """ Asynchronously sends a cell delta to apply to the server. """ - @spec apply_cell_delta(id(), pid(), Cell.id(), Delta.t(), Data.cell_revision()) :: :ok - def apply_cell_delta(session_id, client_pid, cell_id, delta, revision) do - GenServer.cast(name(session_id), {:apply_cell_delta, client_pid, cell_id, delta, revision}) + @spec apply_cell_delta(id(), Cell.id(), Delta.t(), Data.cell_revision()) :: :ok + def apply_cell_delta(session_id, cell_id, delta, revision) do + GenServer.cast(name(session_id), {:apply_cell_delta, self(), cell_id, delta, revision}) end @doc """ @@ -185,9 +185,9 @@ defmodule LiveBook.Session do This helps to remove old deltas that are no longer necessary. """ - @spec report_cell_revision(id(), pid(), Cell.id(), Data.cell_revision()) :: :ok - def report_cell_revision(session_id, client_pid, cell_id, revision) do - GenServer.cast(name(session_id), {:report_cell_revision, client_pid, cell_id, revision}) + @spec report_cell_revision(id(), Cell.id(), Data.cell_revision()) :: :ok + def report_cell_revision(session_id, cell_id, revision) do + GenServer.cast(name(session_id), {:report_cell_revision, self(), cell_id, revision}) end @doc """ @@ -198,7 +198,7 @@ defmodule LiveBook.Session do """ @spec connect_runtime(id(), Runtime.t()) :: :ok def connect_runtime(session_id, runtime) do - GenServer.cast(name(session_id), {:connect_runtime, runtime}) + GenServer.cast(name(session_id), {:connect_runtime, self(), runtime}) end @doc """ @@ -208,7 +208,7 @@ defmodule LiveBook.Session do """ @spec disconnect_runtime(id()) :: :ok def disconnect_runtime(session_id) do - GenServer.cast(name(session_id), :disconnect_runtime) + GenServer.cast(name(session_id), {:disconnect_runtime, self()}) end @doc """ @@ -216,7 +216,7 @@ defmodule LiveBook.Session do """ @spec set_path(id(), String.t() | nil) :: :ok def set_path(session_id, path) do - GenServer.cast(name(session_id), {:set_path, path}) + GenServer.cast(name(session_id), {:set_path, self(), path}) end @doc """ @@ -301,37 +301,37 @@ defmodule LiveBook.Session do end @impl true - def handle_cast({:insert_section, index}, state) do + def handle_cast({:insert_section, client_pid, index}, state) do # Include new id in the operation, so it's reproducible - operation = {:insert_section, index, Utils.random_id()} + operation = {:insert_section, client_pid, index, Utils.random_id()} {:noreply, handle_operation(state, operation)} end - def handle_cast({:insert_cell, section_id, index, type}, state) do + def handle_cast({:insert_cell, client_pid, section_id, index, type}, state) do # Include new id in the operation, so it's reproducible - operation = {:insert_cell, section_id, index, type, Utils.random_id()} + operation = {:insert_cell, client_pid, section_id, index, type, Utils.random_id()} {:noreply, handle_operation(state, operation)} end - def handle_cast({:delete_section, section_id}, state) do - operation = {:delete_section, section_id} + def handle_cast({:delete_section, client_pid, section_id}, state) do + operation = {:delete_section, client_pid, section_id} {:noreply, handle_operation(state, operation)} end - def handle_cast({:delete_cell, cell_id}, state) do - operation = {:delete_cell, cell_id} + def handle_cast({:delete_cell, client_pid, cell_id}, state) do + operation = {:delete_cell, client_pid, cell_id} {:noreply, handle_operation(state, operation)} end - def handle_cast({:move_cell, cell_id, offset}, state) do - operation = {:move_cell, cell_id, offset} + def handle_cast({:move_cell, client_pid, cell_id, offset}, state) do + operation = {:move_cell, client_pid, cell_id, offset} {:noreply, handle_operation(state, operation)} end - def handle_cast({:queue_cell_evaluation, cell_id}, state) do + def handle_cast({:queue_cell_evaluation, client_pid, cell_id}, state) do case ensure_runtime(state) do {:ok, state} -> - operation = {:queue_cell_evaluation, cell_id} + operation = {:queue_cell_evaluation, client_pid, cell_id} {:noreply, handle_operation(state, operation)} {:error, error} -> @@ -340,18 +340,18 @@ defmodule LiveBook.Session do end end - def handle_cast({:cancel_cell_evaluation, cell_id}, state) do - operation = {:cancel_cell_evaluation, cell_id} + def handle_cast({:cancel_cell_evaluation, client_pid, cell_id}, state) do + operation = {:cancel_cell_evaluation, client_pid, cell_id} {:noreply, handle_operation(state, operation)} end - def handle_cast({:set_notebook_name, name}, state) do - operation = {:set_notebook_name, name} + def handle_cast({:set_notebook_name, client_pid, name}, state) do + operation = {:set_notebook_name, client_pid, name} {:noreply, handle_operation(state, operation)} end - def handle_cast({:set_section_name, section_id, name}, state) do - operation = {:set_section_name, section_id, name} + def handle_cast({:set_section_name, client_pid, section_id, name}, state) do + operation = {:set_section_name, client_pid, section_id, name} {:noreply, handle_operation(state, operation)} end @@ -365,7 +365,7 @@ defmodule LiveBook.Session do {:noreply, handle_operation(state, operation)} end - def handle_cast({:connect_runtime, runtime}, state) do + def handle_cast({:connect_runtime, client_pid, runtime}, state) do if state.data.runtime do Runtime.disconnect(state.data.runtime) end @@ -374,18 +374,18 @@ defmodule LiveBook.Session do {:noreply, %{state | runtime_monitor_ref: runtime_monitor_ref} - |> handle_operation({:set_runtime, runtime})} + |> handle_operation({:set_runtime, client_pid, runtime})} end - def handle_cast(:disconnect_runtime, state) do + def handle_cast({:disconnect_runtime, client_pid}, state) do Runtime.disconnect(state.data.runtime) {:noreply, %{state | runtime_monitor_ref: nil} - |> handle_operation({:set_runtime, nil})} + |> handle_operation({:set_runtime, client_pid, nil})} end - def handle_cast({:set_path, path}, state) do + def handle_cast({:set_path, client_pid, path}, state) do if path do FileGuard.lock(path, self()) else @@ -397,7 +397,7 @@ defmodule LiveBook.Session do FileGuard.unlock(state.data.path) end - {:noreply, handle_operation(state, {:set_path, path})} + {:noreply, handle_operation(state, {:set_path, client_pid, path})} {:error, :already_in_use} -> broadcast_error(state.session_id, "failed to set new path because it is already in use") @@ -422,7 +422,7 @@ defmodule LiveBook.Session do {:noreply, %{state | runtime_monitor_ref: nil} - |> handle_operation({:set_runtime, nil})} + |> handle_operation({:set_runtime, self(), nil})} end def handle_info({:DOWN, _, :process, pid, _}, state) do @@ -437,12 +437,12 @@ defmodule LiveBook.Session do end def handle_info({:evaluation_stdout, cell_id, string}, state) do - operation = {:add_cell_evaluation_stdout, cell_id, string} + operation = {:add_cell_evaluation_stdout, self(), cell_id, string} {:noreply, handle_operation(state, operation)} end def handle_info({:evaluation_response, cell_id, response}, state) do - operation = {:add_cell_evaluation_response, cell_id, response} + operation = {:add_cell_evaluation_response, self(), cell_id, response} {:noreply, handle_operation(state, operation)} end @@ -546,7 +546,7 @@ defmodule LiveBook.Session do {:ok, %{state | runtime_monitor_ref: runtime_monitor_ref} - |> handle_operation({:set_runtime, runtime})} + |> handle_operation({:set_runtime, self(), runtime})} end end @@ -558,7 +558,7 @@ defmodule LiveBook.Session do case File.write(state.data.path, content) do :ok -> - handle_operation(state, :mark_as_not_dirty) + handle_operation(state, {:mark_as_not_dirty, self()}) {:error, reason} -> broadcast_error(state.session_id, "failed to save notebook - #{reason}") diff --git a/lib/live_book/session/data.ex b/lib/live_book/session/data.ex index 097680041..27fae1267 100644 --- a/lib/live_book/session/data.ex +++ b/lib/live_book/session/data.ex @@ -62,25 +62,33 @@ defmodule LiveBook.Session.Data do @type index :: non_neg_integer() + # Note that all operations carry the pid of whatever + # process originated the operation. Some operations + # like :apply_cell_delta and :report_cell_revision + # require the pid to be a registered client, as in these + # cases it's necessary for the operation to be properly applied. + # For other operations the pid can represent an arbitrary process + # and is passed for informative purposes only. + @type operation :: - {:insert_section, index(), Section.id()} - | {:insert_cell, Section.id(), index(), Cell.type(), Cell.id()} - | {:delete_section, Section.id()} - | {:delete_cell, Cell.id()} - | {:move_cell, Cell.id(), offset :: integer()} - | {:queue_cell_evaluation, Cell.id()} - | {:add_cell_evaluation_stdout, Cell.id(), String.t()} - | {:add_cell_evaluation_response, Cell.id(), Evaluator.evaluation_response()} - | {:cancel_cell_evaluation, Cell.id()} - | {:set_notebook_name, String.t()} - | {:set_section_name, Section.id(), String.t()} + {:insert_section, pid(), index(), Section.id()} + | {:insert_cell, pid(), Section.id(), index(), Cell.type(), Cell.id()} + | {:delete_section, pid(), Section.id()} + | {:delete_cell, pid(), Cell.id()} + | {:move_cell, pid(), Cell.id(), offset :: integer()} + | {:queue_cell_evaluation, pid(), Cell.id()} + | {:add_cell_evaluation_stdout, pid(), Cell.id(), String.t()} + | {:add_cell_evaluation_response, pid(), Cell.id(), Evaluator.evaluation_response()} + | {:cancel_cell_evaluation, pid(), Cell.id()} + | {:set_notebook_name, pid(), String.t()} + | {:set_section_name, pid(), Section.id(), String.t()} | {:client_join, pid()} | {:client_leave, pid()} | {:apply_cell_delta, pid(), Cell.id(), Delta.t(), cell_revision()} | {:report_cell_revision, pid(), Cell.id(), cell_revision()} - | {:set_runtime, Runtime.t() | nil} - | {:set_path, String.t() | nil} - | :mark_as_not_dirty + | {:set_runtime, pid(), Runtime.t() | nil} + | {:set_path, pid(), String.t() | nil} + | {:mark_as_not_dirty, pid()} @type action :: {:start_evaluation, Cell.t(), Section.t()} @@ -145,7 +153,7 @@ defmodule LiveBook.Session.Data do @spec apply_operation(t(), operation()) :: {:ok, t(), list(action())} | :error def apply_operation(data, operation) - def apply_operation(data, {:insert_section, index, id}) do + def apply_operation(data, {:insert_section, _client_pid, index, id}) do section = %{Section.new() | id: id} data @@ -155,7 +163,7 @@ defmodule LiveBook.Session.Data do |> wrap_ok() end - def apply_operation(data, {:insert_cell, section_id, index, type, id}) do + def apply_operation(data, {:insert_cell, _client_pid, section_id, index, type, id}) do with {:ok, _section} <- Notebook.fetch_section(data.notebook, section_id) do cell = %{Cell.new(type) | id: id} @@ -167,7 +175,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:delete_section, id}) do + def apply_operation(data, {:delete_section, _client_pid, id}) do with {:ok, section} <- Notebook.fetch_section(data.notebook, id) do data |> with_actions() @@ -177,7 +185,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:delete_cell, id}) do + def apply_operation(data, {:delete_cell, _client_pid, id}) do with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id) do case data.cell_infos[cell.id].evaluation_status do :evaluating -> @@ -205,7 +213,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:move_cell, id, offset}) do + def apply_operation(data, {:move_cell, _client_pid, id, offset}) do with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id), true <- offset != 0 do data @@ -218,7 +226,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:queue_cell_evaluation, id}) do + def apply_operation(data, {:queue_cell_evaluation, _client_pid, id}) do with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id), :elixir <- cell.type, :ready <- data.cell_infos[cell.id].evaluation_status do @@ -233,7 +241,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:add_cell_evaluation_stdout, id, string}) do + def apply_operation(data, {:add_cell_evaluation_stdout, _client_pid, id, string}) do with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id), :evaluating <- data.cell_infos[cell.id].evaluation_status do data @@ -245,7 +253,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:add_cell_evaluation_response, id, response}) do + def apply_operation(data, {:add_cell_evaluation_response, _client_pid, id, response}) do with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id), :evaluating <- data.cell_infos[cell.id].evaluation_status do data @@ -260,7 +268,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:cancel_cell_evaluation, id}) do + def apply_operation(data, {:cancel_cell_evaluation, _client_pid, id}) do with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id) do case data.cell_infos[cell.id].evaluation_status do :evaluating -> @@ -284,7 +292,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:set_notebook_name, name}) do + def apply_operation(data, {:set_notebook_name, _client_pid, name}) do data |> with_actions() |> set_notebook_name(name) @@ -292,7 +300,7 @@ defmodule LiveBook.Session.Data do |> wrap_ok() end - def apply_operation(data, {:set_section_name, section_id, name}) do + def apply_operation(data, {:set_section_name, _client_pid, section_id, name}) do with {:ok, section} <- Notebook.fetch_section(data.notebook, section_id) do data |> with_actions() @@ -353,7 +361,7 @@ defmodule LiveBook.Session.Data do end end - def apply_operation(data, {:set_runtime, runtime}) do + def apply_operation(data, {:set_runtime, _client_pid, runtime}) do data |> with_actions() |> set!(runtime: runtime) @@ -361,7 +369,7 @@ defmodule LiveBook.Session.Data do |> wrap_ok() end - def apply_operation(data, {:set_path, path}) do + def apply_operation(data, {:set_path, _client_pid, path}) do data |> with_actions() |> set!(path: path) @@ -369,7 +377,7 @@ defmodule LiveBook.Session.Data do |> wrap_ok() end - def apply_operation(data, :mark_as_not_dirty) do + def apply_operation(data, {:mark_as_not_dirty, _client_pid}) do data |> with_actions() |> set_dirty(false) diff --git a/lib/live_book_web/live/session_live.ex b/lib/live_book_web/live/session_live.ex index 6373154b1..026fb4de2 100644 --- a/lib/live_book_web/live/session_live.ex +++ b/lib/live_book_web/live/session_live.ex @@ -261,7 +261,7 @@ defmodule LiveBookWeb.SessionLive do socket ) do delta = Delta.from_compressed(delta) - Session.apply_cell_delta(socket.assigns.session_id, self(), cell_id, delta, revision) + Session.apply_cell_delta(socket.assigns.session_id, cell_id, delta, revision) {:noreply, socket} end @@ -271,7 +271,7 @@ defmodule LiveBookWeb.SessionLive do %{"cell_id" => cell_id, "revision" => revision}, socket ) do - Session.report_cell_revision(socket.assigns.session_id, self(), cell_id, revision) + Session.report_cell_revision(socket.assigns.session_id, cell_id, revision) {:noreply, socket} end @@ -421,20 +421,34 @@ defmodule LiveBookWeb.SessionLive do def handle_info(_message, socket), do: {:noreply, socket} - defp after_operation(socket, _prev_socket, {:insert_section, _index, section_id}) do - assign(socket, selected_section_id: section_id) + defp after_operation(socket, _prev_socket, {:insert_section, client_pid, _index, section_id}) do + if client_pid == self() do + assign(socket, selected_section_id: section_id) + else + socket + end end - defp after_operation(socket, _prev_socket, {:delete_section, _section_id}) do - assign(socket, selected_section_id: nil) + defp after_operation(socket, _prev_socket, {:delete_section, _client_pid, section_id}) do + if section_id == socket.assigns.selected_section_id do + assign(socket, selected_section_id: nil) + else + socket + end end - defp after_operation(socket, _prev_socket, {:insert_cell, _, _, _, cell_id}) do - {:ok, cell, _section} = Notebook.fetch_cell_and_section(socket.assigns.data.notebook, cell_id) - focus_cell(socket, cell, insert_mode: true) + defp after_operation(socket, _prev_socket, {:insert_cell, client_pid, _, _, _, cell_id}) do + if client_pid == self() do + {:ok, cell, _section} = + Notebook.fetch_cell_and_section(socket.assigns.data.notebook, cell_id) + + focus_cell(socket, cell, insert_mode: true) + else + socket + end end - defp after_operation(socket, prev_socket, {:delete_cell, cell_id}) do + defp after_operation(socket, prev_socket, {:delete_cell, _client_pid, cell_id}) do if cell_id == socket.assigns.focused_cell_id do case Notebook.fetch_cell_sibling(prev_socket.assigns.data.notebook, cell_id, 1) do {:ok, next_cell} -> @@ -460,8 +474,8 @@ defmodule LiveBookWeb.SessionLive do Enum.reduce(actions, socket, &handle_action(&2, &1)) end - defp handle_action(socket, {:broadcast_delta, from, cell, delta}) do - if from == self() do + defp handle_action(socket, {:broadcast_delta, client_pid, cell, delta}) do + if client_pid == self() do push_event(socket, "cell_acknowledgement:#{cell.id}", %{}) else push_event(socket, "cell_delta:#{cell.id}", %{delta: Delta.to_compressed(delta)}) diff --git a/test/live_book/session/data_test.exs b/test/live_book/session/data_test.exs index be5aab9b3..d3a722e37 100644 --- a/test/live_book/session/data_test.exs +++ b/test/live_book/session/data_test.exs @@ -26,10 +26,10 @@ defmodule LiveBook.Session.DataTest do end describe "apply_operation/2 given :insert_section" do - test "adds new section to notebook and session info" do + test "adds new section to notebook and section info" do data = Data.new() - operation = {:insert_section, 0, "s1"} + operation = {:insert_section, self(), 0, "s1"} assert {:ok, %{ @@ -44,17 +44,17 @@ defmodule LiveBook.Session.DataTest do describe "apply_operation/2 given :insert_cell" do test "returns an error given invalid section id" do data = Data.new() - operation = {:insert_cell, "nonexistent", 0, :elixir, "c1"} + operation = {:insert_cell, self(), "nonexistent", 0, :elixir, "c1"} assert :error = Data.apply_operation(data, operation) end test "insert_cell adds new cell to notebook and cell info" do data = data_after_operations!([ - {:insert_section, 0, "s1"} + {:insert_section, self(), 0, "s1"} ]) - operation = {:insert_cell, "s1", 0, :elixir, "c1"} + operation = {:insert_cell, self(), "s1", 0, :elixir, "c1"} assert {:ok, %{ @@ -73,10 +73,10 @@ defmodule LiveBook.Session.DataTest do data = data_after_operations!([ {:client_join, client_pid}, - {:insert_section, 0, "s1"} + {:insert_section, self(), 0, "s1"} ]) - operation = {:insert_cell, "s1", 0, :elixir, "c1"} + operation = {:insert_cell, self(), "s1", 0, :elixir, "c1"} assert {:ok, %{ @@ -88,17 +88,17 @@ defmodule LiveBook.Session.DataTest do describe "apply_operation/2 given :delete_section" do test "returns an error given invalid section id" do data = Data.new() - operation = {:delete_section, "nonexistent"} + operation = {:delete_section, self(), "nonexistent"} assert :error = Data.apply_operation(data, operation) end - test "removes the section from notebook and session info, adds to deleted sections" do + test "removes the section from notebook and section info, adds to deleted sections" do data = data_after_operations!([ - {:insert_section, 0, "s1"} + {:insert_section, self(), 0, "s1"} ]) - operation = {:delete_section, "s1"} + operation = {:delete_section, self(), "s1"} empty_map = %{} assert {:ok, @@ -115,21 +115,21 @@ defmodule LiveBook.Session.DataTest do describe "apply_operation/2 given :delete_cell" do test "returns an error given invalid cell id" do data = Data.new() - operation = {:delete_cell, "nonexistent"} + operation = {:delete_cell, self(), "nonexistent"} assert :error = Data.apply_operation(data, operation) end test "if the cell is evaluating, cencels section evaluation" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"} ]) - operation = {:delete_cell, "c1"} + operation = {:delete_cell, self(), "c1"} assert {:ok, %{ @@ -138,14 +138,14 @@ defmodule LiveBook.Session.DataTest do }, _actions} = Data.apply_operation(data, operation) end - test "removes the cell from notebook and session info, adds to deleted cells" do + test "removes the cell from notebook and section info, adds to deleted cells" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) - operation = {:delete_cell, "c1"} + operation = {:delete_cell, self(), "c1"} empty_map = %{} assert {:ok, @@ -161,14 +161,14 @@ defmodule LiveBook.Session.DataTest do test "unqueues the cell if it's queued for evaluation" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"} ]) - operation = {:delete_cell, "c2"} + operation = {:delete_cell, self(), "c2"} assert {:ok, %{ @@ -179,17 +179,17 @@ defmodule LiveBook.Session.DataTest do test "marks evaluated child cells as stale" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, # Evaluate both cells - {:queue_cell_evaluation, "c1"}, - {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}}, - {:queue_cell_evaluation, "c2"}, - {:add_cell_evaluation_response, "c2", {:ok, [1, 2, 3]}} + {:queue_cell_evaluation, self(), "c1"}, + {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}, + {:queue_cell_evaluation, self(), "c2"}, + {:add_cell_evaluation_response, self(), "c2", {:ok, [1, 2, 3]}} ]) - operation = {:delete_cell, "c1"} + operation = {:delete_cell, self(), "c1"} assert {:ok, %{ @@ -200,11 +200,11 @@ defmodule LiveBook.Session.DataTest do test "returns forget evaluation action" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) - operation = {:delete_cell, "c1"} + operation = {:delete_cell, self(), "c1"} assert {:ok, _data, [{:forget_evaluation, %{id: "c1"}, %{id: "s1"}}]} = Data.apply_operation(data, operation) @@ -214,42 +214,42 @@ defmodule LiveBook.Session.DataTest do describe "apply_operation/2 given :move_cell" do test "returns an error given invalid cell id" do data = Data.new() - operation = {:move_cell, "nonexistent", 1} + operation = {:move_cell, self(), "nonexistent", 1} assert :error = Data.apply_operation(data, operation) end test "returns an error given no offset" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) - operation = {:move_cell, "c1", 0} + operation = {:move_cell, self(), "c1", 0} assert :error = Data.apply_operation(data, operation) end test "given negative offset moves the cell and marks relevant cells as stale" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, + {:insert_section, self(), 0, "s1"}, # Add cells - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:insert_cell, "s1", 2, :elixir, "c3"}, - {:insert_cell, "s1", 3, :elixir, "c4"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:insert_cell, self(), "s1", 2, :elixir, "c3"}, + {:insert_cell, self(), "s1", 3, :elixir, "c4"}, # Evaluate cells - {:queue_cell_evaluation, "c1"}, - {:add_cell_evaluation_response, "c1", {:ok, nil}}, - {:queue_cell_evaluation, "c2"}, - {:add_cell_evaluation_response, "c2", {:ok, nil}}, - {:queue_cell_evaluation, "c3"}, - {:add_cell_evaluation_response, "c3", {:ok, nil}}, - {:queue_cell_evaluation, "c4"}, - {:add_cell_evaluation_response, "c4", {:ok, nil}} + {:queue_cell_evaluation, self(), "c1"}, + {:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, + {:queue_cell_evaluation, self(), "c2"}, + {:add_cell_evaluation_response, self(), "c2", {:ok, nil}}, + {:queue_cell_evaluation, self(), "c3"}, + {:add_cell_evaluation_response, self(), "c3", {:ok, nil}}, + {:queue_cell_evaluation, self(), "c4"}, + {:add_cell_evaluation_response, self(), "c4", {:ok, nil}} ]) - operation = {:move_cell, "c3", -1} + operation = {:move_cell, self(), "c3", -1} assert {:ok, %{ @@ -272,24 +272,24 @@ defmodule LiveBook.Session.DataTest do test "given positive offset moves the cell and marks relevant cells as stale" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, + {:insert_section, self(), 0, "s1"}, # Add cells - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:insert_cell, "s1", 2, :elixir, "c3"}, - {:insert_cell, "s1", 3, :elixir, "c4"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:insert_cell, self(), "s1", 2, :elixir, "c3"}, + {:insert_cell, self(), "s1", 3, :elixir, "c4"}, # Evaluate cells - {:queue_cell_evaluation, "c1"}, - {:add_cell_evaluation_response, "c1", {:ok, nil}}, - {:queue_cell_evaluation, "c2"}, - {:add_cell_evaluation_response, "c2", {:ok, nil}}, - {:queue_cell_evaluation, "c3"}, - {:add_cell_evaluation_response, "c3", {:ok, nil}}, - {:queue_cell_evaluation, "c4"}, - {:add_cell_evaluation_response, "c4", {:ok, nil}} + {:queue_cell_evaluation, self(), "c1"}, + {:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, + {:queue_cell_evaluation, self(), "c2"}, + {:add_cell_evaluation_response, self(), "c2", {:ok, nil}}, + {:queue_cell_evaluation, self(), "c3"}, + {:add_cell_evaluation_response, self(), "c3", {:ok, nil}}, + {:queue_cell_evaluation, self(), "c4"}, + {:add_cell_evaluation_response, self(), "c4", {:ok, nil}} ]) - operation = {:move_cell, "c2", 1} + operation = {:move_cell, self(), "c2", 1} assert {:ok, %{ @@ -312,16 +312,16 @@ defmodule LiveBook.Session.DataTest do test "moving a markdown cell does not change validity" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, + {:insert_section, self(), 0, "s1"}, # Add cells - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :markdown, "c2"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :markdown, "c2"}, # Evaluate the Elixir cell - {:queue_cell_evaluation, "c1"}, - {:add_cell_evaluation_response, "c1", {:ok, nil}} + {:queue_cell_evaluation, self(), "c1"}, + {:add_cell_evaluation_response, self(), "c1", {:ok, nil}} ]) - operation = {:move_cell, "c2", -1} + operation = {:move_cell, self(), "c2", -1} assert {:ok, %{ @@ -334,16 +334,16 @@ defmodule LiveBook.Session.DataTest do test "affected queued cell is unqueued" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, + {:insert_section, self(), 0, "s1"}, # Add cells - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, # Evaluate the Elixir cell - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"} + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"} ]) - operation = {:move_cell, "c2", -1} + operation = {:move_cell, self(), "c2", -1} assert {:ok, %{ @@ -356,19 +356,19 @@ defmodule LiveBook.Session.DataTest do test "does not invalidate the moved cell if the order of Elixir cells stays the same" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, + {:insert_section, self(), 0, "s1"}, # Add cells - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :markdown, "c2"}, - {:insert_cell, "s1", 2, :elixir, "c3"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :markdown, "c2"}, + {:insert_cell, self(), "s1", 2, :elixir, "c3"}, # Evaluate cells - {:queue_cell_evaluation, "c1"}, - {:add_cell_evaluation_response, "c1", {:ok, nil}}, - {:queue_cell_evaluation, "c3"}, - {:add_cell_evaluation_response, "c3", {:ok, nil}} + {:queue_cell_evaluation, self(), "c1"}, + {:add_cell_evaluation_response, self(), "c1", {:ok, nil}}, + {:queue_cell_evaluation, self(), "c3"}, + {:add_cell_evaluation_response, self(), "c3", {:ok, nil}} ]) - operation = {:move_cell, "c1", 1} + operation = {:move_cell, self(), "c1", 1} assert {:ok, %{ @@ -383,41 +383,41 @@ defmodule LiveBook.Session.DataTest do describe "apply_operation/2 given :queue_cell_evaluation" do test "returns an error given invalid cell id" do data = Data.new() - operation = {:queue_cell_evaluation, "nonexistent"} + operation = {:queue_cell_evaluation, self(), "nonexistent"} assert :error = Data.apply_operation(data, operation) end test "returns an error given non-elixir cell" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :markdown, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :markdown, "c1"} ]) - operation = {:queue_cell_evaluation, "c1"} + operation = {:queue_cell_evaluation, self(), "c1"} assert :error = Data.apply_operation(data, operation) end test "returns an error for an evaluating cell" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:queue_cell_evaluation, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:queue_cell_evaluation, self(), "c1"} ]) - operation = {:queue_cell_evaluation, "c1"} + operation = {:queue_cell_evaluation, self(), "c1"} assert :error = 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, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) - operation = {:queue_cell_evaluation, "c1"} + operation = {:queue_cell_evaluation, self(), "c1"} assert {:ok, %{ @@ -429,11 +429,11 @@ defmodule LiveBook.Session.DataTest do test "returns start evaluation action if the corresponding section is idle" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) - operation = {:queue_cell_evaluation, "c1"} + operation = {:queue_cell_evaluation, self(), "c1"} assert {:ok, _data, [{:start_evaluation, %{id: "c1"}, %{id: "s1"}}]} = Data.apply_operation(data, operation) @@ -442,13 +442,13 @@ defmodule LiveBook.Session.DataTest do test "marks the cell as queued if the corresponding section is already evaluating" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"} + {: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, "c2"} + operation = {:queue_cell_evaluation, self(), "c2"} assert {:ok, %{ @@ -462,12 +462,12 @@ defmodule LiveBook.Session.DataTest do test "updates the cell outputs" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:queue_cell_evaluation, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:queue_cell_evaluation, self(), "c1"} ]) - operation = {:add_cell_evaluation_stdout, "c1", "Hello!"} + operation = {:add_cell_evaluation_stdout, self(), "c1", "Hello!"} assert {:ok, %{ @@ -484,13 +484,13 @@ defmodule LiveBook.Session.DataTest do test "merges consecutive stdout results" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:queue_cell_evaluation, "c1"}, - {:add_cell_evaluation_stdout, "c1", "Hello"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:queue_cell_evaluation, self(), "c1"}, + {:add_cell_evaluation_stdout, self(), "c1", "Hello"} ]) - operation = {:add_cell_evaluation_stdout, "c1", " amigo!"} + operation = {:add_cell_evaluation_stdout, self(), "c1", " amigo!"} assert {:ok, %{ @@ -509,12 +509,12 @@ defmodule LiveBook.Session.DataTest do test "updates the cell outputs" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:queue_cell_evaluation, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:queue_cell_evaluation, self(), "c1"} ]) - operation = {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} + operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}} assert {:ok, %{ @@ -531,13 +531,13 @@ defmodule LiveBook.Session.DataTest do test "marks the cell as evaluated" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"} + {: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 = {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} + operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}} assert {:ok, %{ @@ -549,14 +549,14 @@ defmodule LiveBook.Session.DataTest do test "marks next queued cell in this section as evaluating if there is one" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"} ]) - operation = {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} + operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}} assert {:ok, %{ @@ -570,12 +570,12 @@ defmodule LiveBook.Session.DataTest do test "if parent cells are not executed, marks them for evaluation first" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"} ]) - operation = {:queue_cell_evaluation, "c2"} + operation = {:queue_cell_evaluation, self(), "c2"} assert {:ok, %{ @@ -592,19 +592,19 @@ defmodule LiveBook.Session.DataTest do test "marks evaluated child cells as stale" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, # Evaluate both cells - {:queue_cell_evaluation, "c1"}, - {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}}, - {:queue_cell_evaluation, "c2"}, - {:add_cell_evaluation_response, "c2", {:ok, [1, 2, 3]}}, + {:queue_cell_evaluation, self(), "c1"}, + {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}, + {:queue_cell_evaluation, self(), "c2"}, + {:add_cell_evaluation_response, self(), "c2", {:ok, [1, 2, 3]}}, # Queue the first cell again - {:queue_cell_evaluation, "c1"} + {:queue_cell_evaluation, self(), "c1"} ]) - operation = {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} + operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}} assert {:ok, %{ @@ -616,34 +616,34 @@ defmodule LiveBook.Session.DataTest do describe "apply_operation/2 given :cancel_cell_evaluation" do test "returns an error given invalid cell id" do data = Data.new() - operation = {:cancel_cell_evaluation, "nonexistent"} + operation = {:cancel_cell_evaluation, self(), "nonexistent"} assert :error = Data.apply_operation(data, operation) end test "returns an error for an evaluated cell" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:queue_cell_evaluation, "c1"}, - {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:queue_cell_evaluation, self(), "c1"}, + {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}} ]) - operation = {:cancel_cell_evaluation, "c1"} + operation = {:cancel_cell_evaluation, self(), "c1"} assert :error = Data.apply_operation(data, operation) end test "if the cell is evaluating, clears the corresponding section evaluation and the queue" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"} ]) - operation = {:cancel_cell_evaluation, "c1"} + operation = {:cancel_cell_evaluation, self(), "c1"} assert {:ok, %{ @@ -660,14 +660,14 @@ defmodule LiveBook.Session.DataTest do test "if the cell is evaluating, returns stop evaluation action" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"} ]) - operation = {:cancel_cell_evaluation, "c1"} + operation = {:cancel_cell_evaluation, self(), "c1"} assert {:ok, _data, [{:stop_evaluation, %{id: "s1"}}]} = Data.apply_operation(data, operation) @@ -676,14 +676,14 @@ defmodule LiveBook.Session.DataTest do test "if the cell is queued, unqueues it" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"} ]) - operation = {:cancel_cell_evaluation, "c2"} + operation = {:cancel_cell_evaluation, self(), "c2"} assert {:ok, %{ @@ -695,16 +695,16 @@ defmodule LiveBook.Session.DataTest do test "if the cell is queued, unqueues dependent cells that are also queued" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:insert_cell, "s1", 2, :elixir, "c3"}, - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"}, - {:queue_cell_evaluation, "c3"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:insert_cell, self(), "s1", 2, :elixir, "c3"}, + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"}, + {:queue_cell_evaluation, self(), "c3"} ]) - operation = {:cancel_cell_evaluation, "c2"} + operation = {:cancel_cell_evaluation, self(), "c2"} assert {:ok, %{ @@ -718,7 +718,7 @@ defmodule LiveBook.Session.DataTest do test "updates notebook name with the given string" do data = Data.new() - operation = {:set_notebook_name, "Cat's guide to life"} + operation = {:set_notebook_name, self(), "Cat's guide to life"} assert {:ok, %{notebook: %{name: "Cat's guide to life"}}, []} = Data.apply_operation(data, operation) @@ -728,17 +728,17 @@ defmodule LiveBook.Session.DataTest do describe "apply_operation/2 given :set_section_name" do test "returns an error given invalid cell id" do data = Data.new() - operation = {:set_section_name, "nonexistent", "Chapter 1"} + operation = {:set_section_name, self(), "nonexistent", "Chapter 1"} assert :error = Data.apply_operation(data, operation) end test "updates section name with the given string" do data = data_after_operations!([ - {:insert_section, 0, "s1"} + {:insert_section, self(), 0, "s1"} ]) - operation = {:set_section_name, "s1", "Cat's guide to life"} + operation = {:set_section_name, self(), "s1", "Cat's guide to life"} assert {:ok, %{notebook: %{sections: [%{name: "Cat's guide to life"}]}}, []} = Data.apply_operation(data, operation) @@ -771,8 +771,8 @@ defmodule LiveBook.Session.DataTest do data = data_after_operations!([ {:client_join, client1_pid}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:apply_cell_delta, client1_pid, "c1", delta1, 1} ]) @@ -814,8 +814,8 @@ defmodule LiveBook.Session.DataTest do data_after_operations!([ {:client_join, client1_pid}, {:client_join, client2_pid}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:apply_cell_delta, client1_pid, "c1", delta1, 1} ]) @@ -846,8 +846,8 @@ defmodule LiveBook.Session.DataTest do test "returns an error given non-joined client pid" do data = data_after_operations!([ - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) delta = Delta.new() |> Delta.insert("cats") @@ -859,8 +859,8 @@ defmodule LiveBook.Session.DataTest do data = data_after_operations!([ {:client_join, self()}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) delta = Delta.new() |> Delta.insert("cats") @@ -873,8 +873,8 @@ defmodule LiveBook.Session.DataTest do data = data_after_operations!([ {:client_join, self()}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) delta = Delta.new() |> Delta.insert("cats") @@ -901,8 +901,8 @@ defmodule LiveBook.Session.DataTest do data_after_operations!([ {:client_join, client1_pid}, {:client_join, client2_pid}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:apply_cell_delta, client1_pid, "c1", delta1, 1} ]) @@ -930,8 +930,8 @@ defmodule LiveBook.Session.DataTest do data_after_operations!([ {:client_join, client1_pid}, {:client_join, client2_pid}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:apply_cell_delta, client1_pid, "c1", delta1, 1} ]) @@ -950,8 +950,8 @@ defmodule LiveBook.Session.DataTest do data = data_after_operations!([ {:client_join, client_pid}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) delta = Delta.new() |> Delta.insert("cats") @@ -971,8 +971,8 @@ defmodule LiveBook.Session.DataTest do data_after_operations!([ {:client_join, client1_pid}, {:client_join, client2_pid}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) delta = Delta.new() |> Delta.insert("cats") @@ -1003,8 +1003,8 @@ defmodule LiveBook.Session.DataTest do data = data_after_operations!([ {:client_join, client1_pid}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:apply_cell_delta, client1_pid, "c1", Delta.new(insert: "cats"), 1} ]) @@ -1016,8 +1016,8 @@ defmodule LiveBook.Session.DataTest do data = data_after_operations!([ {:client_join, self()}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"} + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"} ]) operation = {:report_cell_revision, self(), "c1", 1} @@ -1034,8 +1034,8 @@ defmodule LiveBook.Session.DataTest do data_after_operations!([ {:client_join, client1_pid}, {:client_join, client2_pid}, - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, {:apply_cell_delta, client1_pid, "c1", delta1, 1} ]) @@ -1059,7 +1059,7 @@ defmodule LiveBook.Session.DataTest do {:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init() - operation = {:set_runtime, runtime} + operation = {:set_runtime, self(), runtime} assert {:ok, %{runtime: ^runtime}, []} = Data.apply_operation(data, operation) end @@ -1068,22 +1068,22 @@ defmodule LiveBook.Session.DataTest do data = data_after_operations!([ # First section with evaluating and queued cells - {:insert_section, 0, "s1"}, - {:insert_cell, "s1", 0, :elixir, "c1"}, - {:insert_cell, "s1", 1, :elixir, "c2"}, - {:queue_cell_evaluation, "c1"}, - {:queue_cell_evaluation, "c2"}, + {:insert_section, self(), 0, "s1"}, + {:insert_cell, self(), "s1", 0, :elixir, "c1"}, + {:insert_cell, self(), "s1", 1, :elixir, "c2"}, + {:queue_cell_evaluation, self(), "c1"}, + {:queue_cell_evaluation, self(), "c2"}, # Second section with evaluating and queued cells - {:insert_section, 1, "s2"}, - {:insert_cell, "s2", 0, :elixir, "c3"}, - {:insert_cell, "s2", 1, :elixir, "c4"}, - {:queue_cell_evaluation, "c3"}, - {:queue_cell_evaluation, "c4"} + {:insert_section, self(), 1, "s2"}, + {:insert_cell, self(), "s2", 0, :elixir, "c3"}, + {:insert_cell, self(), "s2", 1, :elixir, "c4"}, + {:queue_cell_evaluation, self(), "c3"}, + {:queue_cell_evaluation, self(), "c4"} ]) {:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init() - operation = {:set_runtime, runtime} + operation = {:set_runtime, self(), runtime} assert {:ok, %{ @@ -1104,7 +1104,7 @@ defmodule LiveBook.Session.DataTest do describe "apply_operation/2 given :set_path" do test "updates data with the given path" do data = Data.new() - operation = {:set_path, "path"} + operation = {:set_path, self(), "path"} assert {:ok, %{path: "path"}, []} = Data.apply_operation(data, operation) end @@ -1114,10 +1114,10 @@ defmodule LiveBook.Session.DataTest do test "sets dirty flag to false" do data = data_after_operations!([ - {:insert_section, 0, "s1"} + {:insert_section, self(), 0, "s1"} ]) - operation = :mark_as_not_dirty + operation = {:mark_as_not_dirty, self()} assert {:ok, %{dirty: false}, []} = Data.apply_operation(data, operation) end diff --git a/test/live_book/session_test.exs b/test/live_book/session_test.exs index 1bc1b62cd..f4b3a2b03 100644 --- a/test/live_book/session_test.exs +++ b/test/live_book/session_test.exs @@ -11,54 +11,59 @@ defmodule LiveBook.SessionTest do describe "insert_section/2" do test "sends an insert opreation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() Session.insert_section(session_id, 0) - assert_receive {:operation, {:insert_section, 0, _id}} + assert_receive {:operation, {:insert_section, ^pid, 0, _id}} end end describe "insert_cell/4" do test "sends an insert opreation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() Session.insert_section(session_id, 0) - assert_receive {:operation, {:insert_section, 0, section_id}} + assert_receive {:operation, {:insert_section, ^pid, 0, section_id}} Session.insert_cell(session_id, section_id, 0, :elixir) - assert_receive {:operation, {:insert_cell, ^section_id, 0, :elixir, _id}} + assert_receive {:operation, {:insert_cell, ^pid, ^section_id, 0, :elixir, _id}} end end describe "delete_section/2" do test "sends a delete opreation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() {section_id, _cell_id} = insert_section_and_cell(session_id) Session.delete_section(session_id, section_id) - assert_receive {:operation, {:delete_section, ^section_id}} + assert_receive {:operation, {:delete_section, ^pid, ^section_id}} end end describe "delete_cell/2" do test "sends a delete opreation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() {_section_id, cell_id} = insert_section_and_cell(session_id) Session.delete_cell(session_id, cell_id) - assert_receive {:operation, {:delete_cell, ^cell_id}} + assert_receive {:operation, {:delete_cell, ^pid, ^cell_id}} end end describe "queue_cell_evaluation/2" do test "sends a queue evaluation operation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() {_section_id, cell_id} = insert_section_and_cell(session_id) Session.queue_cell_evaluation(session_id, cell_id) - assert_receive {:operation, {:queue_cell_evaluation, ^cell_id}} + assert_receive {:operation, {:queue_cell_evaluation, ^pid, ^cell_id}} end test "triggers evaluation and sends update operation once it finishes", @@ -68,75 +73,80 @@ defmodule LiveBook.SessionTest do {_section_id, cell_id} = insert_section_and_cell(session_id) Session.queue_cell_evaluation(session_id, cell_id) - assert_receive {:operation, {:add_cell_evaluation_response, ^cell_id, _}} + assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _}} end end describe "cancel_cell_evaluation/2" do test "sends a cancel evaluation operation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() {_section_id, cell_id} = insert_section_and_cell(session_id) queue_evaluation(session_id, cell_id) Session.cancel_cell_evaluation(session_id, cell_id) - assert_receive {:operation, {:cancel_cell_evaluation, ^cell_id}} + assert_receive {:operation, {:cancel_cell_evaluation, ^pid, ^cell_id}} end end describe "set_notebook_name/2" do test "sends a notebook name update operation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() Session.set_notebook_name(session_id, "Cat's guide to life") - assert_receive {:operation, {:set_notebook_name, "Cat's guide to life"}} + assert_receive {:operation, {:set_notebook_name, ^pid, "Cat's guide to life"}} end end describe "set_section_name/3" do test "sends a section name update operation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() {section_id, _cell_id} = insert_section_and_cell(session_id) Session.set_section_name(session_id, section_id, "Chapter 1") - assert_receive {:operation, {:set_section_name, ^section_id, "Chapter 1"}} + assert_receive {:operation, {:set_section_name, ^pid, ^section_id, "Chapter 1"}} end end describe "apply_cell_delta/5" do test "sends a cell delta operation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() {_section_id, cell_id} = insert_section_and_cell(session_id) - from = self() delta = Delta.new() |> Delta.insert("cats") revision = 1 - Session.apply_cell_delta(session_id, from, cell_id, delta, revision) - assert_receive {:operation, {:apply_cell_delta, ^from, ^cell_id, ^delta, ^revision}} + Session.apply_cell_delta(session_id, cell_id, delta, revision) + assert_receive {:operation, {:apply_cell_delta, ^pid, ^cell_id, ^delta, ^revision}} end end describe "connect_runtime/2" do test "sends a runtime update operation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() {:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init() Session.connect_runtime(session_id, runtime) - assert_receive {:operation, {:set_runtime, ^runtime}} + assert_receive {:operation, {:set_runtime, ^pid, ^runtime}} end end describe "disconnect_runtime/1" do test "sends a runtime update operation to subscribers", %{session_id: session_id} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() Session.disconnect_runtime(session_id) - assert_receive {:operation, {:set_runtime, nil}} + assert_receive {:operation, {:set_runtime, ^pid, nil}} end end @@ -145,11 +155,12 @@ defmodule LiveBook.SessionTest do test "sends a path update operation to subscribers", %{session_id: session_id, tmp_dir: tmp_dir} do Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") + pid = self() path = Path.join(tmp_dir, "notebook.livemd") Session.set_path(session_id, path) - assert_receive {:operation, {:set_path, ^path}} + assert_receive {:operation, {:set_path, ^pid, ^path}} end @tag :tmp_dir @@ -181,7 +192,7 @@ defmodule LiveBook.SessionTest do Session.save(session_id) - assert_receive {:operation, :mark_as_not_dirty} + assert_receive {:operation, {:mark_as_not_dirty, _}} assert File.exists?(path) assert File.read!(path) =~ "My notebook" end @@ -234,7 +245,7 @@ defmodule LiveBook.SessionTest do Session.queue_cell_evaluation(session_id, cell_id) # Give it a bit more time as this involves starting a system process. - assert_receive {:operation, {:add_cell_evaluation_response, ^cell_id, _}}, 1000 + assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _}}, 1000 end test "if the runtime node goes down, notifies the subscribers" do @@ -246,12 +257,12 @@ defmodule LiveBook.SessionTest do # Wait for the runtime to be set Session.connect_runtime(session_id, runtime) - assert_receive {:operation, {:set_runtime, ^runtime}} + assert_receive {:operation, {:set_runtime, _, ^runtime}} # Terminate the other node, the session should detect that Node.spawn(runtime.node, System, :halt, []) - assert_receive {:operation, {:set_runtime, nil}} + assert_receive {:operation, {:set_runtime, _, nil}} assert_receive {:info, "runtime node terminated unexpectedly"} end @@ -267,15 +278,15 @@ defmodule LiveBook.SessionTest do defp insert_section_and_cell(session_id) do Session.insert_section(session_id, 0) - assert_receive {:operation, {:insert_section, 0, section_id}} + assert_receive {:operation, {:insert_section, _, 0, section_id}} Session.insert_cell(session_id, section_id, 0, :elixir) - assert_receive {:operation, {:insert_cell, ^section_id, 0, :elixir, cell_id}} + assert_receive {:operation, {:insert_cell, _, ^section_id, 0, :elixir, cell_id}} {section_id, cell_id} end defp queue_evaluation(session_id, cell_id) do Session.queue_cell_evaluation(session_id, cell_id) - assert_receive {:operation, {:add_cell_evaluation_response, ^cell_id, _}} + assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _}} end end diff --git a/test/live_book_web/live/session_live_test.exs b/test/live_book_web/live/session_live_test.exs index 3dc7a65d1..1c4ecd5d9 100644 --- a/test/live_book_web/live/session_live_test.exs +++ b/test/live_book_web/live/session_live_test.exs @@ -197,7 +197,7 @@ defmodule LiveBookWeb.SessionLiveTest do %{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_id) delta = Delta.new(insert: content) - Session.apply_cell_delta(session_id, self(), cell.id, delta, 1) + Session.apply_cell_delta(session_id, cell.id, delta, 1) cell.id end