diff --git a/dev/live_components/crash.ex b/dev/live_components/crash.ex new file mode 100644 index 000000000..b7e608c47 --- /dev/null +++ b/dev/live_components/crash.ex @@ -0,0 +1,13 @@ +defmodule LiveDebuggerDev.LiveComponents.Crash do + use DevWeb, :live_component + + def render(assigns) do + ~H""" +
+ <.box title="Crash [LiveComponent]" color="red"> + <.button phx-click="crash" color="red" phx-target={@myself}>Crash + +
+ """ + end +end diff --git a/dev/live_views/main.ex b/dev/live_views/main.ex index b74bc16b9..b9757d9a2 100644 --- a/dev/live_views/main.ex +++ b/dev/live_views/main.ex @@ -58,6 +58,7 @@ defmodule LiveDebuggerDev.LiveViews.Main do <.live_component id="many_assigns" module={LiveComponents.ManyAssigns} /> <.live_component id="name_outer" name={@name} module={LiveComponents.Name} /> + <.live_component id="crash" module={LiveComponents.Crash} /> <.live_component id="send_outer" module={LiveComponents.Send}> <.live_component id="name_inner" name={@name} module={LiveComponents.Name} /> <.live_component id="long_name" module={LiveComponents.LiveComponentWithVeryVeryLongName} /> diff --git a/lib/live_debugger.ex b/lib/live_debugger.ex index 646d4ee6f..b3ccb6316 100644 --- a/lib/live_debugger.ex +++ b/lib/live_debugger.ex @@ -39,6 +39,7 @@ defmodule LiveDebugger do else children ++ [ + {LiveDebugger.GenServers.StateServer, []}, {LiveDebugger.GenServers.CallbackTracingServer, []}, {LiveDebugger.GenServers.EtsTableServer, []} ] diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index 21450a223..a3532d13f 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -177,23 +177,18 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do @spec do_publish(Trace.t()) :: :ok defp do_publish(%{module: Phoenix.LiveView.Diff} = trace) do - trace - |> PubSubUtils.component_deleted_topic() - |> PubSubUtils.broadcast({:new_trace, trace}) + PubSubUtils.component_deleted_topic() + |> PubSubUtils.broadcast({:component_deleted, trace}) end - defp do_publish(trace) do + defp do_publish(%Trace{} = trace) do socket_id = trace.socket_id node_id = Trace.node_id(trace) transport_pid = trace.transport_pid fun = trace.function socket_id - |> PubSubUtils.tsnf_topic(transport_pid, node_id, fun, :call) - |> PubSubUtils.broadcast({:new_trace, trace}) - - socket_id - |> PubSubUtils.ts_f_topic(transport_pid, fun) + |> PubSubUtils.trace_topic(transport_pid, node_id, fun, :call) |> PubSubUtils.broadcast({:new_trace, trace}) end @@ -204,8 +199,13 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do transport_pid = trace.transport_pid fun = trace.function + if fun == :render do + PubSubUtils.node_rendered_topic() + |> PubSubUtils.broadcast({:render_trace, trace}) + end + socket_id - |> PubSubUtils.tsnf_topic(transport_pid, node_id, fun, :return) + |> PubSubUtils.trace_topic(transport_pid, node_id, fun, :return) |> PubSubUtils.broadcast({:updated_trace, trace}) end end diff --git a/lib/live_debugger/gen_servers/ets_table_server.ex b/lib/live_debugger/gen_servers/ets_table_server.ex index 440093073..aae06e893 100644 --- a/lib/live_debugger/gen_servers/ets_table_server.ex +++ b/lib/live_debugger/gen_servers/ets_table_server.ex @@ -21,37 +21,13 @@ defmodule LiveDebugger.GenServers.EtsTableServer do It creates table if none is associated with given pid """ @spec table!(pid :: pid()) :: :ets.table() - def table!(pid) when is_pid(pid) do - impl().table!(pid) - end + def table!(pid) when is_pid(pid), do: impl().table!(pid) @doc """ If table for given `pid` exists it deletes it from ETS. """ @spec delete_table!(pid :: pid()) :: :ok - def delete_table!(pid) when is_pid(pid) do - impl().delete_table!(pid) - end - - def impl() do - Application.get_env(:live_debugger, :ets_table_server, __MODULE__.Impl) - end - - defmodule Impl do - @moduledoc false - @behaviour LiveDebugger.GenServers.EtsTableServer - @server_module LiveDebugger.GenServers.EtsTableServer - - @impl true - def table!(pid) do - GenServer.call(@server_module, {:get_or_create_table, pid}, 1000) - end - - @impl true - def delete_table!(pid) do - GenServer.call(@server_module, {:delete_table, pid}, 1000) - end - end + def delete_table!(pid) when is_pid(pid), do: impl().delete_table!(pid) ## GenServer @@ -69,9 +45,8 @@ defmodule LiveDebugger.GenServers.EtsTableServer do def handle_info({:DOWN, _, :process, closed_pid, _}, table_refs) do {_, table_refs} = delete_ets_table(closed_pid, table_refs) - closed_pid - |> PubSubUtils.process_status_topic() - |> PubSubUtils.broadcast({:process_status, :dead}) + PubSubUtils.process_status_topic() + |> PubSubUtils.broadcast({:process_status, {:dead, closed_pid}}) {:noreply, table_refs} end @@ -95,6 +70,26 @@ defmodule LiveDebugger.GenServers.EtsTableServer do {:reply, :ok, table_refs} end + defp impl() do + Application.get_env(:live_debugger, :ets_table_server, __MODULE__.Impl) + end + + defmodule Impl do + @moduledoc false + @behaviour LiveDebugger.GenServers.EtsTableServer + @server_module LiveDebugger.GenServers.EtsTableServer + + @impl true + def table!(pid) do + GenServer.call(@server_module, {:get_or_create_table, pid}, 1000) + end + + @impl true + def delete_table!(pid) do + GenServer.call(@server_module, {:delete_table, pid}, 1000) + end + end + @spec create_ets_table() :: :ets.table() defp create_ets_table() do :ets.new(@ets_table_name, [:ordered_set, :public]) diff --git a/lib/live_debugger/gen_servers/state_server.ex b/lib/live_debugger/gen_servers/state_server.ex new file mode 100644 index 000000000..c2d2c3a3d --- /dev/null +++ b/lib/live_debugger/gen_servers/state_server.ex @@ -0,0 +1,114 @@ +defmodule LiveDebugger.GenServers.StateServer do + @moduledoc """ + This gen_server is responsible for storing the state of the application. + It collects state when `render` or `delete_component` callbacks are traced. + It uses named ETS table to store the state of the LiveView channel process. + When process dies, it removes the state from the table. + """ + + use GenServer + + alias LiveDebugger.Services.System.ProcessService + alias LiveDebugger.Utils.PubSub, as: PubSubUtils + alias LiveDebugger.CommonTypes + alias LiveDebugger.Structs.Trace + + @ets_table_name :lvdbg_states + + @callback get(pid :: pid()) :: {:ok, CommonTypes.channel_state()} | {:error, term()} + + @doc """ + Returns previously stored state of the LiveView channel process identified by `pid`. + If the state is not found, it returns `{:error, :not_found}`. + """ + @spec get(pid :: pid()) :: {:ok, CommonTypes.channel_state()} | {:error, term()} + def get(pid) when is_pid(pid) do + impl().get(pid) + end + + @doc false + @spec ets_table_name() :: atom() + def ets_table_name(), do: @ets_table_name + + @doc false + def record_id(pid), do: "#{inspect(pid)}" + + @doc false + def start_link(args \\ []) do + GenServer.start_link(__MODULE__, args, name: __MODULE__) + end + + @impl true + def init(_args) do + :ets.new(@ets_table_name, [:named_table, :public, :ordered_set]) + + PubSubUtils.node_rendered_topic() + |> PubSubUtils.subscribe!() + + PubSubUtils.component_deleted_topic() + |> PubSubUtils.subscribe!() + + PubSubUtils.process_status_topic() + |> PubSubUtils.subscribe!() + + {:ok, []} + end + + @impl true + def handle_info({:component_deleted, trace}, state) do + save_state(trace) + + {:noreply, state} + end + + def handle_info({:render_trace, trace}, state) do + save_state(trace) + + {:noreply, state} + end + + def handle_info({:process_status, {:dead, pid}}, state) do + :ets.delete(@ets_table_name, record_id(pid)) + + {:noreply, state} + end + + defp save_state(%Trace{pid: pid} = trace) do + with {:ok, channel_state} <- ProcessService.state(pid) do + record_id = record_id(pid) + :ets.insert(@ets_table_name, {record_id, channel_state}) + + publish_state_changed(trace, channel_state) + end + end + + defp publish_state_changed(%Trace{} = trace, channel_state) do + socket_id = trace.socket_id + transport_pid = trace.transport_pid + node_id = trace.cid || trace.pid + + PubSubUtils.state_changed_topic(socket_id, transport_pid, node_id) + |> PubSubUtils.broadcast({:state_changed, channel_state, trace}) + + PubSubUtils.state_changed_topic(socket_id, transport_pid) + |> PubSubUtils.broadcast({:state_changed, channel_state, trace}) + end + + defp impl() do + Application.get_env(:live_debugger, :state_server, __MODULE__.Impl) + end + + defmodule Impl do + @moduledoc false + + @behaviour LiveDebugger.GenServers.StateServer + @server_module LiveDebugger.GenServers.StateServer + + def get(pid) do + case :ets.lookup(@server_module.ets_table_name(), @server_module.record_id(pid)) do + [{_, channel_state}] -> {:ok, channel_state} + [] -> {:error, :not_found} + end + end + end +end diff --git a/lib/live_debugger/services/channel_service.ex b/lib/live_debugger/services/channel_service.ex index 209d10a65..c9770e8e7 100644 --- a/lib/live_debugger/services/channel_service.ex +++ b/lib/live_debugger/services/channel_service.ex @@ -4,20 +4,15 @@ defmodule LiveDebugger.Services.ChannelService do """ alias LiveDebugger.Structs.TreeNode - alias LiveDebugger.Services.System.ProcessService alias LiveDebugger.CommonTypes + alias LiveDebugger.GenServers.StateServer @doc """ Retrieves the state of the LiveView channel process identified by `pid`. """ @spec state(pid :: pid()) :: {:ok, CommonTypes.channel_state()} | {:error, term()} def state(pid) do - case ProcessService.state(pid) do - {:ok, %{socket: %Phoenix.LiveView.Socket{}, components: _} = state} -> {:ok, state} - {:ok, _} -> {:error, "PID: #{inspect(pid)} is not a LiveView process"} - {:error, :not_alive} -> {:error, :not_alive} - {:error, _} -> {:error, "Could not get state from pid: #{inspect(pid)}"} - end + StateServer.get(pid) end @doc """ diff --git a/lib/live_debugger/services/system/dbg_service.ex b/lib/live_debugger/services/system/dbg_service.ex index b0c9f041f..93f82ba2a 100644 --- a/lib/live_debugger/services/system/dbg_service.ex +++ b/lib/live_debugger/services/system/dbg_service.ex @@ -29,9 +29,7 @@ defmodule LiveDebugger.Services.System.DbgService do @spec tracer(:process, handler_spec()) :: {:ok, pid()} | {:error, term()} @spec tracer(:module, module_spec()) :: {:ok, pid()} | {:error, term()} @spec tracer(:file, filename :: :file.name_all()) :: {:ok, pid()} | {:error, term()} - def tracer(type, handler_spec) do - impl().tracer(type, handler_spec) - end + def tracer(type, handler_spec), do: impl().tracer(type, handler_spec) @doc """ Wrapper for `:dbg.p/2`. @@ -39,18 +37,14 @@ defmodule LiveDebugger.Services.System.DbgService do `p` stands for **p**rocess. """ @spec p(item :: term(), flags :: term()) :: {:ok, match_desc()} | {:error, term()} - def p(item, flags) do - impl().p(item, flags) - end + def p(item, flags), do: impl().p(item, flags) @doc """ Wrapper for `:dbg.tp/2` that sets up a trace pattern. Enables call trace for one or more exported functions specified by `ModuleOrMFA`. """ @spec tp(module() | mfa(), match_spec :: term()) :: {:ok, match_desc()} | {:error, term()} - def tp(module, match_spec) do - impl().tp(module, match_spec) - end + def tp(module, match_spec), do: impl().tp(module, match_spec) defp impl() do Application.get_env( diff --git a/lib/live_debugger/structs/trace.ex b/lib/live_debugger/structs/trace.ex index 7ae7e456e..cbea26ce8 100644 --- a/lib/live_debugger/structs/trace.ex +++ b/lib/live_debugger/structs/trace.ex @@ -31,7 +31,7 @@ defmodule LiveDebugger.Structs.Trace do @type t() :: %__MODULE__{ id: integer(), - module: atom(), + module: module(), function: atom(), arity: non_neg_integer(), args: list(), @@ -39,14 +39,14 @@ defmodule LiveDebugger.Structs.Trace do transport_pid: pid() | nil, pid: pid(), cid: struct() | nil, - timestamp: timestamp(), + timestamp: integer(), execution_time: non_neg_integer() | nil } @doc """ Creates a new trace struct. """ - @spec new(integer(), atom(), atom(), list(), pid(), timestamp(), Keyword.t()) :: t() + @spec new(integer(), module(), atom(), list(), pid(), timestamp(), Keyword.t()) :: t() def new(id, module, function, args, pid, timestamp, opts \\ []) do socket_id = Keyword.get(opts, :socket_id, get_socket_id_from_args(args)) transport_pid = Keyword.get(opts, :transport_pid, get_transport_pid_from_args(args)) diff --git a/lib/live_debugger/utils/pubsub.ex b/lib/live_debugger/utils/pubsub.ex index d4a169426..ea37420e2 100644 --- a/lib/live_debugger/utils/pubsub.ex +++ b/lib/live_debugger/utils/pubsub.ex @@ -3,7 +3,6 @@ defmodule LiveDebugger.Utils.PubSub do This module provides helpers for LiveDebugger's PubSub. """ - alias LiveDebugger.Structs.Trace alias LiveDebugger.Structs.TreeNode @callback broadcast(topic :: String.t(), payload :: term()) :: :ok @@ -13,85 +12,84 @@ defmodule LiveDebugger.Utils.PubSub do @callback unsubscribe(topic :: String.t()) :: :ok @spec broadcast(topic :: String.t(), payload :: term()) :: :ok - def broadcast(topic, payload) do - impl().broadcast(topic, payload) - end + def broadcast(topic, payload), do: impl().broadcast(topic, payload) @spec subscribe!(topics :: [String.t()]) :: :ok - def subscribe!(topics) when is_list(topics) do - impl().subscribe!(topics) - end + def subscribe!(topics) when is_list(topics), do: impl().subscribe!(topics) @spec subscribe!(topic :: String.t()) :: :ok - def subscribe!(topic) do - impl().subscribe!(topic) - end + def subscribe!(topic), do: impl().subscribe!(topic) @spec unsubscribe(topics :: [String.t()]) :: :ok - def unsubscribe(topics) when is_list(topics) do - impl().unsubscribe(topics) - end + def unsubscribe(topics) when is_list(topics), do: impl().unsubscribe(topics) @spec unsubscribe(topic :: String.t()) :: :ok - def unsubscribe(topic) do - impl().unsubscribe(topic) - end - - @spec component_deleted_topic(trace :: Trace.t()) :: String.t() - def component_deleted_topic(trace) do - socket_id = trace.socket_id - transport_pid = trace.transport_pid + def unsubscribe(topic), do: impl().unsubscribe(topic) - component_deleted_topic(socket_id, transport_pid) + @doc "Use `{:component_deleted, delete_trace}` for broadcasting" + @spec component_deleted_topic() :: String.t() + def component_deleted_topic() do + "lvdbg/component_deleted" end + @doc "Use `{:node_changed, node_id}` for broadcasting" @spec node_changed_topic(socket_id :: String.t()) :: String.t() - def node_changed_topic(socket_id) do + def node_changed_topic(socket_id) when is_binary(socket_id) do "lvdbg/#{socket_id}/node_changed" end - @spec component_deleted_topic(socket_id :: String.t(), transport_pid :: pid()) :: String.t() - def component_deleted_topic(socket_id, transport_pid) do - "lvdbg/#{inspect(transport_pid)}/#{socket_id}/component_deleted" + @doc "Use `{:process_status, {status, pid}}` for broadcasting" + @spec process_status_topic() :: String.t() + def process_status_topic() do + "lvdbg/process_status" + end + + @doc "Use `{:state_changed, new_state, triggered_trace}` for broadcasting" + @spec state_changed_topic( + socket_id :: String.t(), + transport_pid :: pid() + ) :: String.t() + def state_changed_topic(socket_id, transport_pid) + when is_pid(transport_pid) and is_binary(socket_id) do + "lvdbg/#{inspect(transport_pid)}/#{socket_id}/*/state_changed" + end + + @doc "Use `{:state_changed, new_state, triggered_trace}` for broadcasting" + @spec state_changed_topic( + socket_id :: String.t(), + transport_pid :: pid(), + node_id :: TreeNode.id() + ) :: String.t() + def state_changed_topic(socket_id, transport_pid, node_id) + when is_pid(transport_pid) and is_binary(socket_id) do + "lvdbg/#{inspect(transport_pid)}/#{socket_id}/#{inspect(node_id)}/state_changed" end - @spec process_status_topic(pid :: pid()) :: String.t() - def process_status_topic(pid) when is_pid(pid) do - "lvdbg/#{inspect(pid)}/status" + @doc "Use `{:render_trace, trace}` for broadcasting." + @spec node_rendered_topic() :: String.t() + def node_rendered_topic() do + "lvdbg/node_rendered" end @doc """ - It stands for transport_pid/socket_id/node_id/function. + It stands for `transport_pid/socket_id/node_id/function/type`. It gives you traces of given callback in given node in given LiveView Used to update assigns based on render callback and for filtering traces + + Use `{:new_trace, trace}` or `{:updated_trace, trace}` for broadcasting. """ - @spec tsnf_topic( + @spec trace_topic( socket_id :: String.t(), transport_pid :: pid(), node_id :: TreeNode.id(), fun :: atom(), type :: :call | :return ) :: String.t() - def tsnf_topic(socket_id, transport_pid, node_id, fun, type \\ :call) do + def trace_topic(socket_id, transport_pid, node_id, fun, type \\ :call) do "#{inspect(transport_pid)}/#{socket_id}/#{inspect(node_id)}/#{inspect(fun)}/#{inspect(type)}" end - @doc """ - It stands for transport_pid/socket_id/*/function. - - It gives you traces of given callback in all nodes of given LiveView - Used for detecting new nodes in sidebar - """ - @spec ts_f_topic( - socket_id :: String.t(), - transport_pid :: pid(), - fun :: atom() - ) :: String.t() - def ts_f_topic(socket_id, transport_pid, fun) do - "#{inspect(transport_pid)}/#{socket_id}/*/#{inspect(fun)}" - end - @spec impl() :: module() defp impl() do Application.get_env( diff --git a/lib/live_debugger_web/helpers/state_helper.ex b/lib/live_debugger_web/helpers/state_helper.ex new file mode 100644 index 000000000..97f373ecb --- /dev/null +++ b/lib/live_debugger_web/helpers/state_helper.ex @@ -0,0 +1,20 @@ +defmodule LiveDebuggerWeb.Helpers.StateHelper do + @moduledoc """ + This module has helper functions for managing state + """ + alias LiveDebugger.CommonTypes + alias LiveDebugger.Services.ChannelService + + @doc """ + Fetches state using ChannelService when no state is passed + """ + @spec maybe_get_state(pid :: pid(), channel_state :: CommonTypes.channel_state() | nil) :: + {:ok, CommonTypes.channel_state()} | {:error, term()} + def maybe_get_state(pid, channel_state \\ nil) when is_pid(pid) do + if is_nil(channel_state) do + ChannelService.state(pid) + else + {:ok, channel_state} + end + end +end diff --git a/lib/live_debugger_web/helpers/tracing_helper.ex b/lib/live_debugger_web/helpers/tracing_helper.ex index b4aa7b9f9..f6b349a20 100644 --- a/lib/live_debugger_web/helpers/tracing_helper.ex +++ b/lib/live_debugger_web/helpers/tracing_helper.ex @@ -142,14 +142,14 @@ defmodule LiveDebuggerWeb.Helpers.TracingHelper do |> Enum.filter(fn {_, active?} -> active? end) |> Enum.flat_map(fn {function, _} -> [ - PubSubUtils.tsnf_topic( + PubSubUtils.trace_topic( lv_process.socket_id, lv_process.transport_pid, node_id, function, :call ), - PubSubUtils.tsnf_topic( + PubSubUtils.trace_topic( lv_process.socket_id, lv_process.transport_pid, node_id, @@ -166,7 +166,7 @@ defmodule LiveDebuggerWeb.Helpers.TracingHelper do socket.assigns.current_filters |> Enum.map(fn {function, _} -> - PubSubUtils.tsnf_topic( + PubSubUtils.trace_topic( lv_process.socket_id, lv_process.transport_pid, node_id, diff --git a/lib/live_debugger_web/hooks/linked_view.ex b/lib/live_debugger_web/hooks/linked_view.ex index 93c18bf03..c9a8fb27f 100644 --- a/lib/live_debugger_web/hooks/linked_view.ex +++ b/lib/live_debugger_web/hooks/linked_view.ex @@ -60,7 +60,8 @@ defmodule LiveDebuggerWeb.Hooks.LinkedView do # When fetching LvProcess succeeds, we subscribe to its process state def handle_async(:fetch_lv_process, {:ok, fetched_lv_process}, socket) do - subscribe_process_state(fetched_lv_process.pid) + PubSubUtils.process_status_topic() + |> PubSubUtils.subscribe!() socket |> assign(:lv_process, AsyncResult.ok(fetched_lv_process)) @@ -80,12 +81,18 @@ defmodule LiveDebuggerWeb.Hooks.LinkedView do def handle_async(_, _, socket), do: {:cont, socket} - def handle_info({:process_status, :dead}, socket) do + def handle_info( + {:process_status, {:dead, pid}}, + %{assigns: %{lv_process: %{result: %LvProcess{pid: pid}}}} = socket + ) + when is_pid(pid) do socket |> find_successor_lv_process() |> halt() end + def handle_info({:process_status, _}, socket), do: halt(socket) + def handle_info(_, socket), do: {:cont, socket} defp find_successor_lv_process(socket) do @@ -125,12 +132,6 @@ defmodule LiveDebuggerWeb.Hooks.LinkedView do end end - defp subscribe_process_state(pid) do - pid - |> PubSubUtils.process_status_topic() - |> PubSubUtils.subscribe!() - end - defp fetch_after(function, milliseconds) do Process.sleep(milliseconds) function.() diff --git a/lib/live_debugger_web/live/sidebar_live.ex b/lib/live_debugger_web/live/sidebar_live.ex index d24356bad..1511a79b2 100644 --- a/lib/live_debugger_web/live/sidebar_live.ex +++ b/lib/live_debugger_web/live/sidebar_live.ex @@ -18,6 +18,7 @@ defmodule LiveDebuggerWeb.SidebarLive do alias LiveDebugger.Utils.URL alias LiveDebuggerWeb.LiveComponents.NestedLiveViewsLinks alias LiveDebugger.Utils.PubSub, as: PubSubUtils + alias LiveDebuggerWeb.Helpers.StateHelper attr(:socket, :map, required: true) attr(:id, :string, required: true) @@ -56,11 +57,7 @@ defmodule LiveDebuggerWeb.SidebarLive do |> PubSubUtils.subscribe!() lv_process.socket_id - |> PubSubUtils.component_deleted_topic(lv_process.transport_pid) - |> PubSubUtils.subscribe!() - - lv_process.socket_id - |> PubSubUtils.ts_f_topic(lv_process.transport_pid, :render) + |> PubSubUtils.state_changed_topic(lv_process.transport_pid) |> PubSubUtils.subscribe!() end @@ -111,7 +108,7 @@ defmodule LiveDebuggerWeb.SidebarLive do end @impl true - def handle_info({:new_trace, trace}, socket) do + def handle_info({:state_changed, new_state, trace}, socket) do existing_node_ids = socket.assigns.existing_node_ids trace_node_id = Trace.node_id(trace) @@ -120,7 +117,7 @@ defmodule LiveDebuggerWeb.SidebarLive do updated_map_set = MapSet.put(existing_node_ids.result, trace_node_id) socket - |> assign_async_tree() + |> assign_async_tree(new_state) |> update_nested_live_views_links() |> assign(:existing_node_ids, Map.put(existing_node_ids, :result, updated_map_set)) @@ -128,7 +125,7 @@ defmodule LiveDebuggerWeb.SidebarLive do updated_map_set = MapSet.delete(existing_node_ids.result, trace_node_id) socket - |> assign_async_tree() + |> assign_async_tree(new_state) |> update_nested_live_views_links() |> assign(:existing_node_ids, Map.put(existing_node_ids, :result, updated_map_set)) @@ -348,11 +345,11 @@ defmodule LiveDebuggerWeb.SidebarLive do end) end - defp assign_async_tree(socket) do + defp assign_async_tree(socket, state \\ nil) do pid = socket.assigns.lv_process.pid assign_async(socket, [:tree, :max_opened_node_level], fn -> - with {:ok, channel_state} <- ChannelService.state(pid), + with {:ok, channel_state} <- StateHelper.maybe_get_state(pid, state), {:ok, tree} <- ChannelService.build_tree(channel_state) do {:ok, %{tree: tree, max_opened_node_level: Tree.max_opened_node_level(tree)}} else diff --git a/lib/live_debugger_web/live/state_live.ex b/lib/live_debugger_web/live/state_live.ex index 38634a34c..eca5647cd 100644 --- a/lib/live_debugger_web/live/state_live.ex +++ b/lib/live_debugger_web/live/state_live.ex @@ -13,6 +13,7 @@ defmodule LiveDebuggerWeb.StateLive do alias LiveDebugger.Services.ChannelService alias LiveDebugger.Utils.TermParser alias LiveDebugger.Utils.PubSub, as: PubSubUtils + alias LiveDebuggerWeb.Helpers.StateHelper attr(:socket, :map, required: true) attr(:id, :string, required: true) @@ -50,7 +51,7 @@ defmodule LiveDebuggerWeb.StateLive do |> PubSubUtils.subscribe!() lv_process.socket_id - |> PubSubUtils.tsnf_topic(lv_process.transport_pid, node_id, :render) + |> PubSubUtils.state_changed_topic(lv_process.transport_pid, node_id) |> PubSubUtils.subscribe!() end @@ -92,15 +93,9 @@ defmodule LiveDebuggerWeb.StateLive do end @impl true - def handle_info({:new_trace, _trace}, socket) do - socket - |> assign_async_node_with_type() - |> noreply() - end - - @impl true - def handle_info({:updated_trace, _trace}, socket) do + def handle_info({:state_changed, channel_state, _trace}, socket) do socket + |> assign_async_node_with_type(channel_state) |> noreply() end @@ -165,12 +160,15 @@ defmodule LiveDebuggerWeb.StateLive do """ end + defp assign_async_node_with_type(socket, channel_state \\ nil) + defp assign_async_node_with_type( - %{assigns: %{node_id: node_id, lv_process: %{pid: pid}}} = socket + %{assigns: %{node_id: node_id, lv_process: %{pid: pid}}} = socket, + channel_state ) when not is_nil(node_id) do assign_async(socket, [:node, :node_type], fn -> - with {:ok, channel_state} <- ChannelService.state(pid), + with {:ok, channel_state} <- StateHelper.maybe_get_state(pid, channel_state), {:ok, node} <- ChannelService.get_node(channel_state, node_id), true <- not is_nil(node) do {:ok, %{node: node, node_type: TreeNode.type(node)}} @@ -181,7 +179,7 @@ defmodule LiveDebuggerWeb.StateLive do end) end - defp assign_async_node_with_type(socket) do + defp assign_async_node_with_type(socket, _channel_state) do socket |> assign(:node, AsyncResult.failed(%AsyncResult{}, :no_node_id)) |> assign(:node_type, AsyncResult.failed(%AsyncResult{}, :no_node_id)) diff --git a/test/gen_servers/callback_tracing_server_test.exs b/test/gen_servers/callback_tracing_server_test.exs index b11f2c459..ad3ad54ca 100644 --- a/test/gen_servers/callback_tracing_server_test.exs +++ b/test/gen_servers/callback_tracing_server_test.exs @@ -10,7 +10,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do alias LiveDebugger.MockDbg alias LiveDebugger.MockEtsTableServer alias LiveDebugger.MockPubSubUtils - alias LiveDebugger.MockProcessService + alias LiveDebugger.MockStateServer @modules [ CoolApp.LiveViews.UserDashboard, @@ -80,16 +80,16 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do fun = :delete_component args = [cid, %{}] - expected_topic = - PubSubUtils.component_deleted_topic(%{socket_id: socket_id, transport_pid: transport_pid}) + component_deleted_topic = + PubSubUtils.component_deleted_topic() - MockProcessService - |> expect(:state, fn ^pid -> + MockStateServer + |> expect(:get, fn ^pid -> {:ok, LiveDebugger.Fakes.state(transport_pid: transport_pid, socket_id: socket_id)} end) MockPubSubUtils - |> expect(:broadcast, fn ^expected_topic, {:new_trace, trace} -> + |> expect(:broadcast, fn ^component_deleted_topic, {:component_deleted, trace} -> send(parent, {:trace, trace}) end) @@ -125,15 +125,13 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do table = :ets.new(:test_table, [:ordered_set, :public]) - expected_tsnf_topic = PubSubUtils.tsnf_topic(socket_id, transport_pid, pid, fun) - expected_ts_f_topic = PubSubUtils.ts_f_topic(socket_id, transport_pid, fun) + expected_trace_topic = PubSubUtils.trace_topic(socket_id, transport_pid, pid, fun) MockEtsTableServer |> expect(:table!, fn ^pid -> table end) MockPubSubUtils - |> expect(:broadcast, fn ^expected_tsnf_topic, {:new_trace, _trace} -> :ok end) - |> expect(:broadcast, fn ^expected_ts_f_topic, {:new_trace, _trace} -> :ok end) + |> expect(:broadcast, fn ^expected_trace_topic, {:new_trace, _trace} -> :ok end) assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) assert_receive handle_trace diff --git a/test/gen_servers/ets_table_server_test.exs b/test/gen_servers/ets_table_server_test.exs index a0c91525a..0368d02cb 100644 --- a/test/gen_servers/ets_table_server_test.exs +++ b/test/gen_servers/ets_table_server_test.exs @@ -47,10 +47,10 @@ defmodule LiveDebugger.GenServers.EtsTableServerTest do table_refs = %{pid => ref, other_pid => other_ref} - topic = PubSubUtils.process_status_topic(pid) + topic = PubSubUtils.process_status_topic() LiveDebugger.MockPubSubUtils - |> expect(:broadcast, fn ^topic, {:process_status, :dead} -> :ok end) + |> expect(:broadcast, fn ^topic, {:process_status, {:dead, _}} -> :ok end) assert {:noreply, new_table_refs} = EtsTableServer.handle_info({:DOWN, :_, :process, pid, :_}, table_refs) diff --git a/test/gen_servers/state_server_test.exs b/test/gen_servers/state_server_test.exs new file mode 100644 index 000000000..3c2d6877b --- /dev/null +++ b/test/gen_servers/state_server_test.exs @@ -0,0 +1,113 @@ +defmodule LiveDebugger.GenServers.StateServerTest do + use ExUnit.Case, async: true + + import Mox + + alias LiveDebugger.Fakes + alias LiveDebugger.Utils.PubSub, as: PubSubUtils + alias LiveDebugger.GenServers.StateServer + alias LiveDebugger.MockPubSubUtils + alias LiveDebugger.MockProcessService + + setup :verify_on_exit! + + test "init/1" do + node_rendered_topic = PubSubUtils.node_rendered_topic() + process_status_topic = PubSubUtils.process_status_topic() + component_deleted_topic = PubSubUtils.component_deleted_topic() + + MockPubSubUtils + |> expect(:subscribe!, fn ^node_rendered_topic -> :ok end) + |> expect(:subscribe!, fn ^component_deleted_topic -> :ok end) + |> expect(:subscribe!, fn ^process_status_topic -> :ok end) + + assert {:ok, []} = StateServer.init([]) + + assert Enum.find(:ets.all(), false, &(&1 == StateServer.ets_table_name())) + end + + test "record_id/1" do + pid = self() + assert StateServer.record_id(pid) == "#{inspect(pid)}" + end + + describe "handle_info/2" do + test "handles component deleted trace and updates state" do + pid = :c.pid(0, 1, 0) + transport_pid = :c.pid(0, 7, 0) + socket_id = "socket_id" + :ets.new(StateServer.ets_table_name(), [:named_table, :public, :ordered_set]) + :ets.insert(StateServer.ets_table_name(), {inspect(pid), :old_state}) + + trace = + Fakes.trace( + function: :render, + pid: pid, + transport_pid: transport_pid, + socket_id: socket_id + ) + + state_changed_node_topic = PubSubUtils.state_changed_topic(socket_id, transport_pid, pid) + state_changed_topic = PubSubUtils.state_changed_topic(socket_id, transport_pid) + + state = Fakes.state() + + MockProcessService + |> expect(:state, fn ^pid -> {:ok, state} end) + + MockPubSubUtils + |> expect(:broadcast, fn ^state_changed_node_topic, {:state_changed, ^state, ^trace} -> + :ok + end) + |> expect(:broadcast, fn ^state_changed_topic, {:state_changed, ^state, ^trace} -> :ok end) + + StateServer.handle_info({:component_deleted, trace}, []) + + assert [{_, ^state}] = :ets.lookup(StateServer.ets_table_name(), inspect(pid)) + end + + test "handles render trace and updates state" do + pid = :c.pid(0, 1, 0) + transport_pid = :c.pid(0, 7, 0) + socket_id = "socket_id" + :ets.new(StateServer.ets_table_name(), [:named_table, :public, :ordered_set]) + :ets.insert(StateServer.ets_table_name(), {inspect(pid), :old_state}) + + trace = + Fakes.trace( + function: :render, + pid: pid, + transport_pid: transport_pid, + socket_id: socket_id + ) + + state_changed_node_topic = PubSubUtils.state_changed_topic(socket_id, transport_pid, pid) + state_changed_topic = PubSubUtils.state_changed_topic(socket_id, transport_pid) + + state = Fakes.state() + + MockProcessService + |> expect(:state, fn ^pid -> {:ok, state} end) + + MockPubSubUtils + |> expect(:broadcast, fn ^state_changed_node_topic, {:state_changed, ^state, ^trace} -> + :ok + end) + |> expect(:broadcast, fn ^state_changed_topic, {:state_changed, ^state, ^trace} -> :ok end) + + StateServer.handle_info({:render_trace, trace}, []) + + assert [{_, ^state}] = :ets.lookup(StateServer.ets_table_name(), inspect(pid)) + end + + test "handles dead process status and deletes table record" do + pid = :c.pid(0, 1, 0) + :ets.new(StateServer.ets_table_name(), [:named_table, :public, :ordered_set]) + :ets.insert(StateServer.ets_table_name(), {inspect(pid), :old_state}) + + StateServer.handle_info({:process_status, {:dead, pid}}, []) + + assert [] = :ets.lookup(StateServer.ets_table_name(), inspect(pid)) + end + end +end diff --git a/test/services/channel_service_test.exs b/test/services/channel_service_test.exs index 4484d0b35..9be394a02 100644 --- a/test/services/channel_service_test.exs +++ b/test/services/channel_service_test.exs @@ -38,27 +38,6 @@ defmodule LiveDebugger.Services.ChannelServiceTest do } end - describe "state/1" do - test "returns the state of the LiveView channel process identified by pid", %{ - live_view_pid: pid - } do - {:ok, state} = ProcessService.state(pid) - assert {:ok, ^state} = ChannelService.state(pid) - end - - test "returns an error when the process is not a LiveView", %{non_live_view_pid: pid} do - assert {:error, "PID:" <> _} = ChannelService.state(pid) - end - - test "returns an error when there is no process with the given pid", %{not_alive_pid: pid} do - assert {:error, :not_alive} = ChannelService.state(pid) - end - - test "returns and error when something went wrong", %{exited_pid: pid} do - assert {:error, "Could not get state from pid:" <> _} = ChannelService.state(pid) - end - end - describe "get_node/2" do test "returns LiveView node with the given id from the channel state when pid is passed", %{ live_view_pid: pid diff --git a/test/services/live_view_discovery_service_test.exs b/test/services/live_view_discovery_service_test.exs index 888f994c2..f0314ffb4 100644 --- a/test/services/live_view_discovery_service_test.exs +++ b/test/services/live_view_discovery_service_test.exs @@ -5,6 +5,7 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do alias LiveDebugger.Services.LiveViewDiscoveryService alias LiveDebugger.MockProcessService + alias LiveDebugger.MockStateServer alias LiveDebugger.Structs.LvProcess alias LiveDebugger.Fakes @@ -18,10 +19,12 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> expect(:list, fn -> [live_view_pid_1, live_view_pid_2] end) |> expect(:initial_call, 2, fn _ -> {module, :mount} end) - |> expect(:state, fn ^live_view_pid_1 -> + + MockStateServer + |> expect(:get, fn ^live_view_pid_1 -> {:ok, Fakes.state(root_pid: live_view_pid_1, module: module)} end) - |> expect(:state, fn ^live_view_pid_2 -> + |> expect(:get, fn ^live_view_pid_2 -> {:ok, Fakes.state(root_pid: live_view_pid_2, module: module)} end) @@ -42,10 +45,12 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do |> expect(:list, fn -> [live_view_pid, debugger_pid] end) |> expect(:initial_call, fn _ -> {module, :mount} end) |> expect(:initial_call, fn _ -> {live_debugger_module, :mount} end) - |> expect(:state, fn ^live_view_pid -> + + MockStateServer + |> expect(:get, fn ^live_view_pid -> {:ok, Fakes.state(root_pid: live_view_pid, module: module)} end) - |> expect(:state, fn ^debugger_pid -> + |> expect(:get, fn ^debugger_pid -> {:ok, Fakes.state(root_pid: debugger_pid, module: live_debugger_module)} end) @@ -66,10 +71,12 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do |> expect(:list, fn -> [live_debugger_pid, live_view_pid] end) |> expect(:initial_call, fn _ -> {live_debugger_module, :mount} end) |> expect(:initial_call, fn _ -> {live_view_module, :mount} end) - |> expect(:state, fn ^live_debugger_pid -> + + MockStateServer + |> expect(:get, fn ^live_debugger_pid -> {:ok, Fakes.state(root_pid: live_debugger_pid, module: live_debugger_module)} end) - |> expect(:state, fn ^live_view_pid -> + |> expect(:get, fn ^live_view_pid -> {:ok, Fakes.state(root_pid: live_view_pid, module: live_view_module)} end) @@ -93,7 +100,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do |> expect(:list, fn -> [searched_live_view_pid, live_view_pid_1, live_view_pid_2] end) |> expect(:initial_call, fn _ -> {searched_module, :mount} end) |> expect(:initial_call, 2, fn _ -> {other_module, :mount} end) - |> expect(:state, fn ^searched_live_view_pid -> + + MockStateServer + |> expect(:get, fn ^searched_live_view_pid -> {:ok, Fakes.state( root_pid: searched_live_view_pid, @@ -101,7 +110,7 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do socket_id: socket_id )} end) - |> expect(:state, 2, fn live_view_pid -> + |> expect(:get, 2, fn live_view_pid -> {:ok, Fakes.state(root_pid: live_view_pid, module: other_module, socket_id: other_socket_id)} end) @@ -117,7 +126,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> expect(:list, fn -> [:c.pid(0, 0, 0)] end) |> expect(:initial_call, fn _ -> {module, :mount} end) - |> expect(:state, fn _ -> {:ok, Fakes.state()} end) + + MockStateServer + |> expect(:get, fn _ -> {:ok, Fakes.state()} end) assert LiveViewDiscoveryService.lv_process(bad_socket_id) == nil end @@ -129,7 +140,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> expect(:list, fn -> [:c.pid(0, 0, 1), :c.pid(0, 0, 2)] end) |> expect(:initial_call, 2, fn _ -> {module, :mount} end) - |> expect(:state, 2, fn _ -> {:ok, Fakes.state()} end) + + MockStateServer + |> expect(:get, 2, fn _ -> {:ok, Fakes.state()} end) assert LiveViewDiscoveryService.lv_process(socket_id) == nil end @@ -148,13 +161,15 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do |> expect(:list, fn -> [searched_live_view_pid, live_view_pid_1, live_view_pid_2] end) |> expect(:initial_call, fn _ -> {searched_module, :mount} end) |> expect(:initial_call, 2, fn _ -> {other_module, :mount} end) - |> expect(:state, fn ^searched_live_view_pid -> + + MockStateServer + |> expect(:get, fn ^searched_live_view_pid -> {:ok, Fakes.state(module: searched_module)} end) - |> expect(:state, fn _pid -> + |> expect(:get, fn _pid -> {:ok, Fakes.state(module: other_module)} end) - |> expect(:state, fn _pid -> + |> expect(:get, fn _pid -> {:ok, Fakes.state(module: other_module)} end) @@ -176,21 +191,23 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do |> expect(:list, fn -> [searched_live_view_pid, live_view_pid_1, live_view_pid_2] end) |> expect(:initial_call, fn _ -> {searched_module, :mount} end) |> expect(:initial_call, 2, fn _ -> {other_module, :mount} end) - |> expect(:state, fn ^searched_live_view_pid -> + + MockStateServer + |> expect(:get, fn ^searched_live_view_pid -> {:ok, Fakes.state( module: searched_module, socket_id: searched_socket_id )} end) - |> expect(:state, fn _pid -> + |> expect(:get, fn _pid -> {:ok, Fakes.state( module: other_module, socket_id: other_socket_id )} end) - |> expect(:state, fn _pid -> + |> expect(:get, fn _pid -> {:ok, Fakes.state( module: other_module, @@ -211,7 +228,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> expect(:list, fn -> [live_view_pid_1, live_view_pid_2] end) |> expect(:initial_call, 2, fn _ -> {:"Elixir.SomeLiveView", :mount} end) - |> expect(:state, 2, fn _ -> {:ok, Fakes.state()} end) + + MockStateServer + |> expect(:get, 2, fn _ -> {:ok, Fakes.state()} end) assert nil == LiveViewDiscoveryService.lv_process(searched_live_view_pid) @@ -227,7 +246,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> expect(:list, fn -> [live_view_pid_1, live_view_pid_2] end) |> expect(:initial_call, 2, fn _ -> {:"Elixir.SomeLiveView", :mount} end) - |> expect(:state, 2, fn _ -> {:ok, Fakes.state(socket_id: other_socket_id)} end) + + MockStateServer + |> expect(:get, 2, fn _ -> {:ok, Fakes.state(socket_id: other_socket_id)} end) assert nil == LiveViewDiscoveryService.lv_process(searched_socket_id) @@ -253,7 +274,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [successor_pid, other_pid] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn pid -> + + MockStateServer + |> stub(:get, fn pid -> case pid do ^successor_pid -> {:ok, @@ -299,7 +322,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [successor_pid, other_pid] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn pid -> + + MockStateServer + |> stub(:get, fn pid -> case pid do ^successor_pid -> {:ok, @@ -347,7 +372,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [successor_pid, other_pid] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn pid -> + + MockStateServer + |> stub(:get, fn pid -> case pid do ^successor_pid -> {:ok, @@ -394,7 +421,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [successor_pid, other_pid] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn pid -> + + MockStateServer + |> stub(:get, fn pid -> case pid do ^successor_pid -> {:ok, @@ -441,7 +470,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [successor_pid, other_pid] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn pid -> + + MockStateServer + |> stub(:get, fn pid -> case pid do ^successor_pid -> {:ok, @@ -487,7 +518,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [successor_pid] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn ^successor_pid -> + + MockStateServer + |> stub(:get, fn ^successor_pid -> {:ok, Fakes.state( pid: successor_pid, @@ -522,7 +555,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [other_pid_1, other_pid_2] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn pid -> + + MockStateServer + |> stub(:get, fn pid -> case pid do ^other_pid_1 -> {:ok, @@ -621,10 +656,12 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do |> expect(:list, fn -> [live_view_pid_1, live_view_pid_2, non_live_view_pid] end) |> expect(:initial_call, 2, fn _ -> {module, :mount} end) |> expect(:initial_call, fn _ -> {non_live_view_module, :some_initial_call} end) - |> expect(:state, fn ^live_view_pid_1 -> + + MockStateServer + |> expect(:get, fn ^live_view_pid_1 -> {:ok, Fakes.state(root_pid: live_view_pid_1, module: module)} end) - |> expect(:state, fn ^live_view_pid_2 -> + |> expect(:get, fn ^live_view_pid_2 -> {:ok, Fakes.state(root_pid: live_view_pid_2, module: module)} end) @@ -645,7 +682,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [parent_pid, child_pid_1, child_pid_2] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn pid -> + + MockStateServer + |> stub(:get, fn pid -> if pid == parent_pid do {:ok, Fakes.state(root_pid: parent_pid, module: module, parent_pid: nil)} else @@ -670,7 +709,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do MockProcessService |> stub(:list, fn -> [parent_pid, child_pid_1, child_pid_2, grandchild_pid] end) |> stub(:initial_call, fn _ -> {module, :mount} end) - |> stub(:state, fn pid -> + + MockStateServer + |> stub(:get, fn pid -> case pid do ^parent_pid -> {:ok, Fakes.state(root_pid: parent_pid, module: module, parent_pid: nil)} diff --git a/test/services/trace_service_test.exs b/test/services/trace_service_test.exs index 2003ac0ad..e1da1db61 100644 --- a/test/services/trace_service_test.exs +++ b/test/services/trace_service_test.exs @@ -3,7 +3,7 @@ defmodule Services.TraceServiceTest do import Mox - alias LiveDebugger.Structs.Trace + alias LiveDebugger.Fakes alias LiveDebugger.Services.TraceService alias LiveDebugger.MockEtsTableServer @@ -23,7 +23,7 @@ defmodule Services.TraceServiceTest do end test "insert/1", %{module: module, pid: pid, table: table} do - trace = new_trace(1, module, :render, [], pid) + trace = Fakes.trace(id: 1, module: module, function: :render, args: [], pid: pid) MockEtsTableServer |> expect(:table!, fn ^pid -> table end) @@ -33,8 +33,8 @@ defmodule Services.TraceServiceTest do end test "get/2", %{module: module, pid: pid, table: table} do - trace1 = new_trace(1, module, :handle_info, [], pid) - trace2 = new_trace(2, module, :render, [], pid) + trace1 = Fakes.trace(id: 1, module: module, function: :handle_info, args: [], pid: pid) + trace2 = Fakes.trace(id: 2, module: module, function: :render, args: [], pid: pid) :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) @@ -48,8 +48,8 @@ defmodule Services.TraceServiceTest do describe "existing_traces/2" do test "returns traces with default limit", %{module: module, pid: pid, table: table} do - trace1 = new_trace(1, module, :handle_info, [], pid) - trace2 = new_trace(2, module, :render, [], pid) + trace1 = Fakes.trace(id: 1, module: module, function: :handle_info, args: [], pid: pid) + trace2 = Fakes.trace(id: 2, module: module, function: :render, args: [], pid: pid) :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) @@ -60,9 +60,10 @@ defmodule Services.TraceServiceTest do end test "returns traces with limit and continuation", %{module: module, pid: pid, table: table} do - trace1 = new_trace(1, module, :handle_info, [], pid) - trace2 = new_trace(2, module, :render, [], pid) - trace3 = new_trace(3, module, :handle_event, [], pid) + trace1 = Fakes.trace(id: 1, module: module, function: :handle_info, args: [], pid: pid) + trace2 = Fakes.trace(id: 2, module: module, function: :render, args: [], pid: pid) + trace3 = Fakes.trace(id: 3, module: module, function: :handle_event, args: [], pid: pid) + :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) :ets.insert(table, {trace3.id, trace3}) @@ -85,9 +86,9 @@ defmodule Services.TraceServiceTest do end test "returns traces with functions filter", %{module: module, pid: pid, table: table} do - trace1 = new_trace(1, module, :handle_info, [], pid) - trace2 = new_trace(2, module, :render, [], pid) - trace3 = new_trace(3, module, :handle_event, [], pid) + trace1 = Fakes.trace(id: 1, module: module, function: :handle_info, args: [], pid: pid) + trace2 = Fakes.trace(id: 2, module: module, function: :render, args: [], pid: pid) + trace3 = Fakes.trace(id: 3, module: module, function: :handle_event, args: [], pid: pid) :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) :ets.insert(table, {trace3.id, trace3}) @@ -103,9 +104,12 @@ defmodule Services.TraceServiceTest do test "returns traces with node_id filter", %{module: module, pid: pid, table: table} do cid = %Phoenix.LiveComponent.CID{cid: 3} - trace1 = new_trace(1, module, :handle_info, [], pid) - trace2 = new_trace(2, module, :render, [], pid, cid: cid) - trace3 = new_trace(3, module, :handle_event, [], pid, cid: cid) + trace1 = Fakes.trace(id: 1, module: module, function: :handle_info, args: [], pid: pid) + trace2 = Fakes.trace(id: 2, module: module, function: :render, args: [], pid: pid, cid: cid) + + trace3 = + Fakes.trace(id: 3, module: module, function: :handle_event, args: [], pid: pid, cid: cid) + :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) :ets.insert(table, {trace3.id, trace3}) @@ -118,8 +122,8 @@ defmodule Services.TraceServiceTest do end test "returns :end_of_table when no traces match", %{module: module, pid: pid, table: table} do - trace1 = new_trace(1, module, :handle_info, [], pid) - trace2 = new_trace(2, module, :render, [], pid) + trace1 = Fakes.trace(id: 1, module: module, function: :handle_info, args: [], pid: pid) + trace2 = Fakes.trace(id: 2, module: module, function: :render, args: [], pid: pid) :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) @@ -130,8 +134,18 @@ defmodule Services.TraceServiceTest do end test "returns only finished traces", %{module: module, pid: pid, table: table} do - trace1 = new_trace(1, module, :handle_info, [], pid) - trace2 = new_trace(2, module, :render, [], pid, execution_time: nil) + trace1 = Fakes.trace(id: 1, module: module, function: :handle_info, args: [], pid: pid) + + trace2 = + Fakes.trace( + id: 2, + module: module, + function: :render, + args: [], + pid: pid, + execution_time: nil + ) + :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) @@ -145,8 +159,26 @@ defmodule Services.TraceServiceTest do describe "clear_traces/2" do test "clears traces for LiveView or LiveComponent", %{module: module, pid: pid, table: table} do cid = %Phoenix.LiveComponent.CID{cid: 3} - trace1 = new_trace(1, module, :handle_info, [], pid) - trace2 = new_trace(2, module, :render, [], pid, cid: cid) + + trace1 = + Fakes.trace( + id: 1, + module: module, + function: :handle_info, + args: [], + pid: pid + ) + + trace2 = + Fakes.trace( + id: 2, + module: module, + function: :render, + args: [], + pid: pid, + cid: cid + ) + :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) @@ -164,18 +196,4 @@ defmodule Services.TraceServiceTest do assert :end_of_table = TraceService.existing_traces(pid) end end - - defp new_trace(id, module, function, args, pid, opts \\ []) do - %Trace{ - id: id, - module: module, - function: function, - arity: length(args), - args: args, - pid: pid, - cid: Keyword.get(opts, :cid, nil), - timestamp: 1, - execution_time: Keyword.get(opts, :execution_time, 1) - } - end end diff --git a/test/structs/lv_process_test.exs b/test/structs/lv_process_test.exs index 410f5b4e2..8a07f7787 100644 --- a/test/structs/lv_process_test.exs +++ b/test/structs/lv_process_test.exs @@ -36,9 +36,9 @@ defmodule LiveDebugger.Structs.LvProcessTest do describe "new/1" do test "returns nil if the process is not found" do - LiveDebugger.MockProcessService - |> expect(:state, fn _pid -> - {:error, :not_alive} + LiveDebugger.MockStateServer + |> expect(:get, fn _pid -> + {:error, :not_found} end) assert LvProcess.new(self()) == nil @@ -52,8 +52,8 @@ defmodule LiveDebugger.Structs.LvProcessTest do transport_pid = nil module = LiveDebuggerTest.TestView - LiveDebugger.MockProcessService - |> expect(:state, fn _pid -> + LiveDebugger.MockStateServer + |> expect(:get, fn _pid -> {:ok, LiveDebugger.Fakes.state( socket_id: socket_id, @@ -85,8 +85,8 @@ defmodule LiveDebugger.Structs.LvProcessTest do transport_pid = :c.pid(0, 7, 0) module = LiveDebuggerTest.TestView - LiveDebugger.MockProcessService - |> expect(:state, fn _pid -> + LiveDebugger.MockStateServer + |> expect(:get, fn _pid -> {:ok, LiveDebugger.Fakes.state( socket_id: socket_id, @@ -119,8 +119,8 @@ defmodule LiveDebugger.Structs.LvProcessTest do transport_pid = nil module = LiveDebugger.TestView - LiveDebugger.MockProcessService - |> expect(:state, fn _pid -> + LiveDebugger.MockStateServer + |> expect(:get, fn _pid -> {:ok, LiveDebugger.Fakes.state( socket_id: socket_id, @@ -152,8 +152,8 @@ defmodule LiveDebugger.Structs.LvProcessTest do transport_pid = nil module = LiveDebuggerTest.TestView - LiveDebugger.MockProcessService - |> expect(:state, fn _pid -> + LiveDebugger.MockStateServer + |> expect(:get, fn _pid -> {:ok, LiveDebugger.Fakes.state( socket_id: socket_id, @@ -187,8 +187,8 @@ defmodule LiveDebugger.Structs.LvProcessTest do transport_pid = nil module = LiveDebuggerTest.TestView - LiveDebugger.MockProcessService - |> expect(:state, fn ^parent_pid -> + LiveDebugger.MockStateServer + |> expect(:get, fn ^parent_pid -> {:ok, LiveDebugger.Fakes.state( socket_id: socket_id, diff --git a/test/structs/trace_display_test.exs b/test/structs/trace_display_test.exs index ab95602b6..2b0b2bc9c 100644 --- a/test/structs/trace_display_test.exs +++ b/test/structs/trace_display_test.exs @@ -2,23 +2,10 @@ defmodule LiveDebugger.Structs.TraceDisplayTest do use ExUnit.Case, async: true alias LiveDebugger.Structs.TraceDisplay - alias LiveDebugger.Structs.Trace - - @trace %Trace{ - id: 1, - module: LiveDebuggerTest.TestView, - function: :handle_event, - arity: 3, - args: ["event", %{"key" => "value"}, %{}], - socket_id: "socket_id", - transport_pid: self(), - pid: self(), - cid: nil, - timestamp: System.system_time(:millisecond) - } + alias LiveDebugger.Fakes test "from_trace/1 creates a TraceDisplay struct" do - trace = @trace + trace = Fakes.trace() trace_display = TraceDisplay.from_trace(trace) assert %TraceDisplay{id: 1, trace: ^trace, render_body?: false} = trace_display @@ -27,7 +14,7 @@ defmodule LiveDebugger.Structs.TraceDisplayTest do test "render_body/1 sets render_body? to true" do trace_display = %TraceDisplay{ id: 1, - trace: @trace, + trace: Fakes.trace(), render_body?: false, counter: 0 } diff --git a/test/support/fakes.ex b/test/support/fakes.ex index 95a65e5fd..9afbd0355 100644 --- a/test/support/fakes.ex +++ b/test/support/fakes.ex @@ -1,8 +1,26 @@ defmodule LiveDebugger.Fakes do @moduledoc """ - Fake responses from internal services + Fake complex structures """ + def trace(opts \\ []) do + default = [ + id: 1, + module: LiveDebuggerTest.LiveView, + function: :render, + arity: 1, + args: [%{socket_id: "socket_id"}], + socket_id: "socket_id", + pid: :c.pid(0, 1, 0), + timestamp: :erlang.timestamp(), + execution_time: 1 + ] + + fields = Keyword.merge(default, opts) + + Kernel.struct!(LiveDebugger.Structs.Trace, fields) + end + def state(opts \\ []) do socket_id = Keyword.get(opts, :socket_id, "phx-GBsi_6M7paYhySQj") parent_pid = Keyword.get(opts, :parent_pid, nil) diff --git a/test/test_helper.exs b/test/test_helper.exs index d713bffd6..5767f760b 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -20,6 +20,9 @@ else Mox.defmock(LiveDebugger.MockDbg, for: LiveDebugger.Services.System.DbgService) Application.put_env(:live_debugger, :dbg_service, LiveDebugger.MockDbg) + + Mox.defmock(LiveDebugger.MockStateServer, for: LiveDebugger.GenServers.StateServer) + Application.put_env(:live_debugger, :state_server, LiveDebugger.MockStateServer) end ExUnit.start() diff --git a/test/utils/pubsub_test.exs b/test/utils/pubsub_test.exs index 4a01c61b5..4a661dc8b 100644 --- a/test/utils/pubsub_test.exs +++ b/test/utils/pubsub_test.exs @@ -3,7 +3,6 @@ defmodule LiveDebugger.Utils.PubSubTest do import Mox - alias LiveDebugger.Structs.Trace alias LiveDebugger.Utils.PubSub, as: PubSubUtils alias LiveDebugger.MockPubSubUtils @@ -12,49 +11,29 @@ defmodule LiveDebugger.Utils.PubSubTest do PubSubUtils.node_changed_topic("phx-GBsi_6M7paYhySQj") end - test "component_deleted_topic/1" do - trace = %Trace{socket_id: "phx-GBsi_6M7paYhySQj", transport_pid: :c.pid(0, 1, 0)} - - assert "lvdbg/#PID<0.1.0>/phx-GBsi_6M7paYhySQj/component_deleted" = - PubSubUtils.component_deleted_topic(trace) - end - - test "component_deleted_topic/2" do - socket_id = "phx-GBsi_6M7paYhySQj" - transport_pid = :c.pid(0, 1, 0) - - assert "lvdbg/#PID<0.1.0>/phx-GBsi_6M7paYhySQj/component_deleted" = - PubSubUtils.component_deleted_topic(socket_id, transport_pid) + test "component_deleted_topic/0" do + assert "lvdbg/component_deleted" = + PubSubUtils.component_deleted_topic() end - test "process_status_topic/1" do - pid = :c.pid(0, 1, 0) - assert "lvdbg/#PID<0.1.0>/status" = PubSubUtils.process_status_topic(pid) + test "process_status_topic/0" do + assert "lvdbg/process_status" = PubSubUtils.process_status_topic() end - test "tsnf_topic/4" do + test "trace_topic/4" do socket_id = "phx-GBsi_6M7paYhySQj" transport_pid = :c.pid(0, 1, 0) node_id = :c.pid(0, 2, 0) fun = :handle_info assert "#PID<0.1.0>/phx-GBsi_6M7paYhySQj/#PID<0.2.0>/:handle_info/:call" = - PubSubUtils.tsnf_topic(socket_id, transport_pid, node_id, fun) + PubSubUtils.trace_topic(socket_id, transport_pid, node_id, fun) assert "#PID<0.1.0>/phx-GBsi_6M7paYhySQj/#PID<0.2.0>/:handle_info/:call" = - PubSubUtils.tsnf_topic(socket_id, transport_pid, node_id, fun, :call) + PubSubUtils.trace_topic(socket_id, transport_pid, node_id, fun, :call) assert "#PID<0.1.0>/phx-GBsi_6M7paYhySQj/#PID<0.2.0>/:handle_info/:return" = - PubSubUtils.tsnf_topic(socket_id, transport_pid, node_id, fun, :return) - end - - test "ts_f_topic/3" do - socket_id = "phx-GBsi_6M7paYhySQj" - transport_pid = :c.pid(0, 1, 0) - fun = :handle_info - - assert "#PID<0.1.0>/phx-GBsi_6M7paYhySQj/*/:handle_info" = - PubSubUtils.ts_f_topic(socket_id, transport_pid, fun) + PubSubUtils.trace_topic(socket_id, transport_pid, node_id, fun, :return) end describe "mock" do