From 3a503c9e3c381f456c0cb047d781dcbae1472b51 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Tue, 22 Apr 2025 13:45:02 +0200 Subject: [PATCH 01/14] Add ModuleDiscoveryService.all_modules test --- .../module_discovery_service_test.exs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/services/module_discovery_service_test.exs b/test/services/module_discovery_service_test.exs index 35d728286..e29cff1f2 100644 --- a/test/services/module_discovery_service_test.exs +++ b/test/services/module_discovery_service_test.exs @@ -5,6 +5,27 @@ defmodule LiveDebugger.Services.ModuleDiscoveryServiceTest do alias LiveDebugger.Services.ModuleDiscoveryService + describe "all_modules/0" do + test "returns all modules" do + modules = [ + :"CoolApp.LiveViews.UserDashboard", + :"CoolApp.Service.UserService", + :"CoolApp.LiveComponent.UserElement" + ] + + LiveDebugger.MockModuleService + |> expect(:all, 1, fn -> + [ + {~c"CoolApp.LiveViews.UserDashboard", "", false}, + {~c"CoolApp.Service.UserService", "", false}, + {~c"CoolApp.LiveComponent.UserElement", "", false} + ] + end) + + assert modules == ModuleDiscoveryService.all_modules() + end + end + describe "live_view_modules/1" do test "filters LiveView modules correctly" do modules = [ From 67333ac92cbb641cf765f65c09c602f2baa3ef9d Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Tue, 22 Apr 2025 13:45:08 +0200 Subject: [PATCH 02/14] wip --- .../callback_tracing_server_test.exs | 100 ++++++++++++++++++ test/services/trace_service_test.exs | 33 ++++++ 2 files changed, 133 insertions(+) create mode 100644 test/gen_servers/callback_tracing_server_test.exs create mode 100644 test/services/trace_service_test.exs diff --git a/test/gen_servers/callback_tracing_server_test.exs b/test/gen_servers/callback_tracing_server_test.exs new file mode 100644 index 000000000..de0522d19 --- /dev/null +++ b/test/gen_servers/callback_tracing_server_test.exs @@ -0,0 +1,100 @@ +defmodule LiveDebugger.GenServers.CallbackTracingServerTest do + @moduledoc false + use ExUnit.Case, async: true + + import Mox + + alias LiveDebugger.GenServers.CallbackTracingServer + + setup_all do + LiveDebugger.MockModuleService + |> stub(:all, fn -> [] end) + + allow(LiveDebugger.MockModuleService, self(), fn -> + GenServer.whereis(CallbackTracingServer) + end) + + CallbackTracingServer.start_link() + :ok + end + + describe "table!/1" do + test "creates and remembers table for given pid" do + pid = + spawn(fn -> + receive do + :stop -> + :ok + end + end) + + ref1 = CallbackTracingServer.table!(pid) + + assert ref1 == CallbackTracingServer.table!(pid) + assert [] == :ets.tab2list(ref1) + + send(pid, :stop) + end + + test "creates different tables for different pids" do + [pid1, pid2] = + for _ <- 1..2 do + spawn(fn -> + receive do + :stop -> + :ok + end + end) + end + + ref1 = CallbackTracingServer.table!(pid1) + ref2 = CallbackTracingServer.table!(pid2) + + assert ref1 != ref2 + assert ref1 == CallbackTracingServer.table!(pid1) + assert ref2 == CallbackTracingServer.table!(pid2) + assert [] == :ets.tab2list(ref1) + assert [] == :ets.tab2list(ref2) + + send(pid1, :stop) + send(pid2, :stop) + end + + test "removes table after process exits" do + pid = + spawn(fn -> + receive do + :stop -> + :ok + end + end) + + ref = CallbackTracingServer.table!(pid) + + send(pid, :stop) + + Process.sleep(1000) + + assert_raise ArgumentError, fn -> :ets.tab2list(ref) end + assert ref != CallbackTracingServer.table!(pid) + end + end + + test "delete_table!/1" do + pid = + spawn(fn -> + receive do + :stop -> + :ok + end + end) + + ref = CallbackTracingServer.table!(pid) + + assert :ok == CallbackTracingServer.delete_table!(pid) + assert_raise ArgumentError, fn -> :ets.tab2list(ref) end + assert ref != CallbackTracingServer.table!(pid) + + send(pid, :stop) + end +end diff --git a/test/services/trace_service_test.exs b/test/services/trace_service_test.exs new file mode 100644 index 000000000..85508fb14 --- /dev/null +++ b/test/services/trace_service_test.exs @@ -0,0 +1,33 @@ +defmodule Services.TraceServiceTest do + use ExUnit.Case, async: true + + import Mox + + alias LiveDebugger.Structs.Trace + alias LiveDebugger.Services.TraceService + + setup_all do + LiveDebugger.MockModuleService + |> stub(:all, fn -> [] end) + + allow(LiveDebugger.MockModuleService, self(), fn -> + GenServer.whereis(LiveDebugger.GenServers.CallbackTracingServer) + end) + + LiveDebugger.GenServers.CallbackTracingServer.start_link() + + %{ + module: CoolApp.LiveViews.UserDashboard + } + end + + test "a", %{module: module} do + pid = spawn(fn -> :ok end) + + trace = Trace.new(1, module, :render, [], pid) + + assert true == TraceService.insert(trace) + + send(pid, :stop) + end +end From 268e21cfc245918dd4b5f3af3fed143786d0b054 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Tue, 22 Apr 2025 15:32:15 +0200 Subject: [PATCH 03/14] Add tests for TraceService --- test/services/trace_service_test.exs | 132 +++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 8 deletions(-) diff --git a/test/services/trace_service_test.exs b/test/services/trace_service_test.exs index 85508fb14..225f99e42 100644 --- a/test/services/trace_service_test.exs +++ b/test/services/trace_service_test.exs @@ -5,29 +5,145 @@ defmodule Services.TraceServiceTest do alias LiveDebugger.Structs.Trace alias LiveDebugger.Services.TraceService + alias LiveDebugger.GenServers.CallbackTracingServer setup_all do LiveDebugger.MockModuleService |> stub(:all, fn -> [] end) allow(LiveDebugger.MockModuleService, self(), fn -> - GenServer.whereis(LiveDebugger.GenServers.CallbackTracingServer) + GenServer.whereis(CallbackTracingServer) end) - LiveDebugger.GenServers.CallbackTracingServer.start_link() + start_supervised(CallbackTracingServer) - %{ - module: CoolApp.LiveViews.UserDashboard - } + %{module: CoolApp.LiveViews.UserDashboard} end - test "a", %{module: module} do - pid = spawn(fn -> :ok end) + setup context do + pid = + spawn(fn -> + receive do + :stop -> + :ok + end + end) + on_exit(fn -> send(pid, :stop) end) + + Map.put(context, :pid, pid) + end + + test "insert/1", %{module: module, pid: pid} do trace = Trace.new(1, module, :render, [], pid) assert true == TraceService.insert(trace) + end + + test "get/2", %{module: module, pid: pid} do + trace1 = Trace.new(1, module, :handle_info, [], pid) + trace2 = Trace.new(2, module, :render, [], pid) + + TraceService.insert(trace1) + TraceService.insert(trace2) + + assert trace1 == TraceService.get(pid, trace1.id) + assert trace2 == TraceService.get(pid, trace2.id) + assert nil == TraceService.get(pid, 99) + end + + describe "existing_traces/2" do + test "returns traces with default limit", %{module: module, pid: pid} do + trace1 = Trace.new(1, module, :handle_info, [], pid) + trace2 = Trace.new(2, module, :render, [], pid) + + TraceService.insert(trace1) + TraceService.insert(trace2) + + assert {[^trace1, ^trace2], _} = TraceService.existing_traces(pid) + end + + test "returns traces with limit and continuation", %{module: module, pid: pid} do + trace1 = Trace.new(1, module, :handle_info, [], pid) + trace2 = Trace.new(2, module, :render, [], pid) + trace3 = Trace.new(3, module, :handle_event, [], pid) + + TraceService.insert(trace1) + TraceService.insert(trace2) + TraceService.insert(trace3) + + {traces1, cont} = TraceService.existing_traces(pid, limit: 2) + {traces2, cont} = TraceService.existing_traces(pid, cont: cont) + + assert [trace1, trace2] == traces1 + assert [trace3] == traces2 + assert cont == :end_of_table + assert :end_of_table == TraceService.existing_traces(pid, cont: :end_of_table) + end + + test "raise ArgumentError when limit is less than 1", %{pid: pid} do + assert_raise ArgumentError, fn -> TraceService.existing_traces(pid, limit: 0) end + assert_raise ArgumentError, fn -> TraceService.existing_traces(pid, limit: -23) end + end + + test "returns traces with functions filter", %{module: module, pid: pid} do + trace1 = Trace.new(1, module, :handle_info, [], pid) + trace2 = Trace.new(2, module, :render, [], pid) + trace3 = Trace.new(3, module, :handle_event, [], pid) + + TraceService.insert(trace1) + TraceService.insert(trace2) + TraceService.insert(trace3) + + assert {[^trace1], _} = TraceService.existing_traces(pid, functions: [:handle_info]) + + assert {[^trace1, ^trace2], _} = + TraceService.existing_traces(pid, functions: [:handle_info, :render, :mount]) + end + + test "returns traces with node_id filter", %{module: module, pid: pid} do + cid = %Phoenix.LiveComponent.CID{cid: 3} + trace1 = Trace.new(1, module, :handle_info, [], pid) + trace2 = Trace.new(2, module, :render, [], pid, cid: cid) + trace3 = Trace.new(3, module, :handle_event, [], pid, cid: cid) + + TraceService.insert(trace1) + TraceService.insert(trace2) + TraceService.insert(trace3) + + assert {[^trace1], _} = TraceService.existing_traces(pid, node_id: pid) + assert {[^trace2, ^trace3], _} = TraceService.existing_traces(pid, node_id: cid) + end + + test "returns :end_of_table when no traces match", %{module: module, pid: pid} do + trace1 = Trace.new(1, module, :handle_info, [], pid) + trace2 = Trace.new(2, module, :render, [], pid) + + TraceService.insert(trace1) + TraceService.insert(trace2) + + assert :end_of_table = TraceService.existing_traces(pid, functions: [:non_existent]) + end + end + + describe "clear_traces/2" do + test "clears traces for LiveView or LiveComponent", %{module: module, pid: pid} do + cid = %Phoenix.LiveComponent.CID{cid: 3} + trace1 = Trace.new(1, module, :handle_info, [], pid) + trace2 = Trace.new(2, module, :render, [], pid, cid: cid) + + TraceService.insert(trace1) + TraceService.insert(trace2) + + assert {[^trace1, ^trace2], _} = TraceService.existing_traces(pid) + + TraceService.clear_traces(pid, trace1.pid) + + assert {[^trace2], _} = TraceService.existing_traces(pid) + + TraceService.clear_traces(pid, trace2.cid) - send(pid, :stop) + assert :end_of_table = TraceService.existing_traces(pid) + end end end From 1d330274b477e45fc85988bce071894fb7568fb7 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Wed, 23 Apr 2025 11:23:29 +0200 Subject: [PATCH 04/14] Refactor --- test/services/module_discovery_service_test.exs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/services/module_discovery_service_test.exs b/test/services/module_discovery_service_test.exs index e29cff1f2..7f924728c 100644 --- a/test/services/module_discovery_service_test.exs +++ b/test/services/module_discovery_service_test.exs @@ -15,11 +15,7 @@ defmodule LiveDebugger.Services.ModuleDiscoveryServiceTest do LiveDebugger.MockModuleService |> expect(:all, 1, fn -> - [ - {~c"CoolApp.LiveViews.UserDashboard", "", false}, - {~c"CoolApp.Service.UserService", "", false}, - {~c"CoolApp.LiveComponent.UserElement", "", false} - ] + modules |> Enum.map(&{to_charlist(&1), "", false}) end) assert modules == ModuleDiscoveryService.all_modules() From 0458a074231014e86bc5207cc38378eaa5dcbb79 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Wed, 23 Apr 2025 11:32:46 +0200 Subject: [PATCH 05/14] Add tests for CallbackTracingServer --- .../callback_tracing_server_test.exs | 80 +++++++++---------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/test/gen_servers/callback_tracing_server_test.exs b/test/gen_servers/callback_tracing_server_test.exs index de0522d19..73ec0edb9 100644 --- a/test/gen_servers/callback_tracing_server_test.exs +++ b/test/gen_servers/callback_tracing_server_test.exs @@ -14,38 +14,47 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do GenServer.whereis(CallbackTracingServer) end) - CallbackTracingServer.start_link() + start_supervised(CallbackTracingServer) + :ok end - describe "table!/1" do - test "creates and remembers table for given pid" do - pid = - spawn(fn -> - receive do - :stop -> - :ok - end - end) + setup do + pid = + spawn(fn -> + receive do + :stop -> + :ok + end + end) + + on_exit(fn -> send(pid, :stop) end) + + %{pid: pid} + end + test "gen server is started" do + pid = GenServer.whereis(CallbackTracingServer) + assert {:error, {:already_started, ^pid}} = CallbackTracingServer.start_link() + assert is_pid(pid) + end + + describe "table!/1" do + test "creates and remembers table for given pid", %{pid: pid} do ref1 = CallbackTracingServer.table!(pid) assert ref1 == CallbackTracingServer.table!(pid) assert [] == :ets.tab2list(ref1) - - send(pid, :stop) end - test "creates different tables for different pids" do - [pid1, pid2] = - for _ <- 1..2 do - spawn(fn -> - receive do - :stop -> - :ok - end - end) - end + test "creates different tables for different pids", %{pid: pid1} do + pid2 = + spawn(fn -> + receive do + :stop -> + :ok + end + end) ref1 = CallbackTracingServer.table!(pid1) ref2 = CallbackTracingServer.table!(pid2) @@ -56,45 +65,30 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do assert [] == :ets.tab2list(ref1) assert [] == :ets.tab2list(ref2) - send(pid1, :stop) send(pid2, :stop) end - test "removes table after process exits" do - pid = - spawn(fn -> - receive do - :stop -> - :ok - end - end) - + test "removes table after process exits", %{pid: pid} do ref = CallbackTracingServer.table!(pid) send(pid, :stop) - Process.sleep(1000) + Process.sleep(200) assert_raise ArgumentError, fn -> :ets.tab2list(ref) end assert ref != CallbackTracingServer.table!(pid) end end - test "delete_table!/1" do - pid = - spawn(fn -> - receive do - :stop -> - :ok - end - end) - + test "delete_table!/1", %{pid: pid} do ref = CallbackTracingServer.table!(pid) assert :ok == CallbackTracingServer.delete_table!(pid) assert_raise ArgumentError, fn -> :ets.tab2list(ref) end assert ref != CallbackTracingServer.table!(pid) + end - send(pid, :stop) + test "ping!/1" do + assert :ok == CallbackTracingServer.ping!() end end From 00b2118baab2a0c6ee9661f337452c3f67844367 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Wed, 23 Apr 2025 11:41:22 +0200 Subject: [PATCH 06/14] Change receive/send to sleep/exit --- test/gen_servers/callback_tracing_server_test.exs | 12 +++--------- test/services/trace_service_test.exs | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/test/gen_servers/callback_tracing_server_test.exs b/test/gen_servers/callback_tracing_server_test.exs index 73ec0edb9..4fee93b5c 100644 --- a/test/gen_servers/callback_tracing_server_test.exs +++ b/test/gen_servers/callback_tracing_server_test.exs @@ -20,15 +20,9 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do end setup do - pid = - spawn(fn -> - receive do - :stop -> - :ok - end - end) + pid = spawn(fn -> Process.sleep(:infinity) end) - on_exit(fn -> send(pid, :stop) end) + on_exit(fn -> Process.exit(pid, :kill) end) %{pid: pid} end @@ -71,7 +65,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do test "removes table after process exits", %{pid: pid} do ref = CallbackTracingServer.table!(pid) - send(pid, :stop) + Process.exit(pid, :kill) Process.sleep(200) diff --git a/test/services/trace_service_test.exs b/test/services/trace_service_test.exs index 225f99e42..d7fafc2f9 100644 --- a/test/services/trace_service_test.exs +++ b/test/services/trace_service_test.exs @@ -21,15 +21,9 @@ defmodule Services.TraceServiceTest do end setup context do - pid = - spawn(fn -> - receive do - :stop -> - :ok - end - end) - - on_exit(fn -> send(pid, :stop) end) + pid = spawn(fn -> Process.sleep(:infinity) end) + + on_exit(fn -> Process.exit(pid, :kill) end) Map.put(context, :pid, pid) end From 8a7485c2087725678ecd090995ef0dde58b9a2d3 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Thu, 24 Apr 2025 12:11:47 +0200 Subject: [PATCH 07/14] Ignore LiveDebuggerDev in test coverage --- mix.exs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index dced7f73a..9d38bb083 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,10 @@ defmodule LiveDebugger.MixProject do name: "LiveDebugger", source_url: "https://github.com/software-mansion/live-debugger", description: "Tool for debugging LiveView applications", - docs: docs() + docs: docs(), + test_coverage: [ + ignore_modules: [~r/^LiveDebuggerDev\./, DevWeb] + ] ] end From f1b24bc758564969ab9896c58e1f4bd9bc929252 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Thu, 24 Apr 2025 12:27:24 +0200 Subject: [PATCH 08/14] Add tracing mechanism tests --- .../callback_tracing_server_test.exs | 42 ++++++++++++++++++- test/services/trace_service_test.exs | 2 +- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/test/gen_servers/callback_tracing_server_test.exs b/test/gen_servers/callback_tracing_server_test.exs index 4fee93b5c..acf08ea24 100644 --- a/test/gen_servers/callback_tracing_server_test.exs +++ b/test/gen_servers/callback_tracing_server_test.exs @@ -1,6 +1,6 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do @moduledoc false - use ExUnit.Case, async: true + use ExUnit.Case, async: false import Mox @@ -8,7 +8,9 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do setup_all do LiveDebugger.MockModuleService - |> stub(:all, fn -> [] end) + |> stub(:all, fn -> [{to_charlist(CoolApp.Dashboard), "", false}] end) + |> stub(:loaded?, fn _module -> true end) + |> stub(:behaviours, fn _module -> [Phoenix.LiveView] end) allow(LiveDebugger.MockModuleService, self(), fn -> GenServer.whereis(CallbackTracingServer) @@ -85,4 +87,40 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do test "ping!/1" do assert :ok == CallbackTracingServer.ping!() end + + describe "tracing mechanism" do + test "properly tracing callback call" do + Process.sleep(200) + CoolApp.Dashboard.handle_info(:msg, %{transport_pid: self()}) + + assert [{0, trace}] = CallbackTracingServer.table!(self()) |> :ets.tab2list() + + assert trace.id == 0 + assert trace.module == CoolApp.Dashboard + assert trace.function == :handle_info + assert trace.arity == 2 + assert trace.args == [:msg, %{transport_pid: self()}] + assert trace.socket_id == nil + assert trace.transport_pid == self() + assert trace.pid == self() + assert trace.cid == nil + end + + test "ignoring non-traced callbacks" do + Process.sleep(200) + CoolApp.Dashboard.non_traced_function(:arg) + + assert [] == CallbackTracingServer.table!(self()) |> :ets.tab2list() + end + end +end + +defmodule CoolApp.Dashboard do + def handle_info(_msg, state) do + {:noreply, state} + end + + def non_traced_function(_arg) do + :ok + end end diff --git a/test/services/trace_service_test.exs b/test/services/trace_service_test.exs index d7fafc2f9..e31972ff2 100644 --- a/test/services/trace_service_test.exs +++ b/test/services/trace_service_test.exs @@ -1,5 +1,5 @@ defmodule Services.TraceServiceTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false import Mox From 6964b8a33e26979bef270473d000ea1d6f981cb3 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Tue, 29 Apr 2025 10:03:24 +0200 Subject: [PATCH 09/14] Remove duplicate test --- test/services/module_discovery_service_test.exs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/services/module_discovery_service_test.exs b/test/services/module_discovery_service_test.exs index 429a9a552..303744db0 100644 --- a/test/services/module_discovery_service_test.exs +++ b/test/services/module_discovery_service_test.exs @@ -28,23 +28,6 @@ defmodule LiveDebugger.Services.ModuleDiscoveryServiceTest do assert modules == ModuleDiscoveryService.all_modules() end - describe "all_modules/0" do - test "returns all modules" do - modules = [ - :"CoolApp.LiveViews.UserDashboard", - :"CoolApp.Service.UserService", - :"CoolApp.LiveComponent.UserElement" - ] - - LiveDebugger.MockModuleService - |> expect(:all, 1, fn -> - modules |> Enum.map(&{to_charlist(&1), "", false}) - end) - - assert modules == ModuleDiscoveryService.all_modules() - end - end - describe "live_view_modules/1" do test "filters LiveView modules correctly" do modules = @modules From c150ba8f48d66125abd85b421d3492ad57fea883 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Tue, 29 Apr 2025 11:25:30 +0200 Subject: [PATCH 10/14] Update TraceService test --- test/services/trace_service_test.exs | 91 +++++++++++++++------------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/test/services/trace_service_test.exs b/test/services/trace_service_test.exs index e31972ff2..3b0522bcd 100644 --- a/test/services/trace_service_test.exs +++ b/test/services/trace_service_test.exs @@ -1,45 +1,42 @@ defmodule Services.TraceServiceTest do - use ExUnit.Case, async: false + use ExUnit.Case, async: true import Mox alias LiveDebugger.Structs.Trace alias LiveDebugger.Services.TraceService - alias LiveDebugger.GenServers.CallbackTracingServer - setup_all do - LiveDebugger.MockModuleService - |> stub(:all, fn -> [] end) - - allow(LiveDebugger.MockModuleService, self(), fn -> - GenServer.whereis(CallbackTracingServer) - end) - - start_supervised(CallbackTracingServer) + @module CoolApp.LiveViews.UserDashboard + @pid :c.pid(0, 0, 1) - %{module: CoolApp.LiveViews.UserDashboard} + setup_all do + %{module: @module, pid: @pid} end setup context do - pid = spawn(fn -> Process.sleep(:infinity) end) - - on_exit(fn -> Process.exit(pid, :kill) end) + table = :ets.new(:trace_table, [:ordered_set, :public]) - Map.put(context, :pid, pid) + Map.put(context, :table, table) end - test "insert/1", %{module: module, pid: pid} do + test "insert/1", %{module: module, pid: pid, table: table} do trace = Trace.new(1, module, :render, [], pid) + LiveDebugger.MockEtsTableServer + |> expect(:table!, fn ^pid -> table end) + assert true == TraceService.insert(trace) + assert [{trace.id, trace}] == :ets.lookup(table, trace.id) end - test "get/2", %{module: module, pid: pid} do + test "get/2", %{module: module, pid: pid, table: table} do trace1 = Trace.new(1, module, :handle_info, [], pid) trace2 = Trace.new(2, module, :render, [], pid) + :ets.insert(table, {trace1.id, trace1}) + :ets.insert(table, {trace2.id, trace2}) - TraceService.insert(trace1) - TraceService.insert(trace2) + LiveDebugger.MockEtsTableServer + |> expect(:table!, 3, fn ^pid -> table end) assert trace1 == TraceService.get(pid, trace1.id) assert trace2 == TraceService.get(pid, trace2.id) @@ -47,24 +44,28 @@ defmodule Services.TraceServiceTest do end describe "existing_traces/2" do - test "returns traces with default limit", %{module: module, pid: pid} do + test "returns traces with default limit", %{module: module, pid: pid, table: table} do trace1 = Trace.new(1, module, :handle_info, [], pid) trace2 = Trace.new(2, module, :render, [], pid) + :ets.insert(table, {trace1.id, trace1}) + :ets.insert(table, {trace2.id, trace2}) - TraceService.insert(trace1) - TraceService.insert(trace2) + LiveDebugger.MockEtsTableServer + |> expect(:table!, fn ^pid -> table end) assert {[^trace1, ^trace2], _} = TraceService.existing_traces(pid) end - test "returns traces with limit and continuation", %{module: module, pid: pid} do + test "returns traces with limit and continuation", %{module: module, pid: pid, table: table} do trace1 = Trace.new(1, module, :handle_info, [], pid) trace2 = Trace.new(2, module, :render, [], pid) trace3 = Trace.new(3, module, :handle_event, [], pid) + :ets.insert(table, {trace1.id, trace1}) + :ets.insert(table, {trace2.id, trace2}) + :ets.insert(table, {trace3.id, trace3}) - TraceService.insert(trace1) - TraceService.insert(trace2) - TraceService.insert(trace3) + LiveDebugger.MockEtsTableServer + |> expect(:table!, 3, fn ^pid -> table end) {traces1, cont} = TraceService.existing_traces(pid, limit: 2) {traces2, cont} = TraceService.existing_traces(pid, cont: cont) @@ -80,14 +81,16 @@ defmodule Services.TraceServiceTest do assert_raise ArgumentError, fn -> TraceService.existing_traces(pid, limit: -23) end end - test "returns traces with functions filter", %{module: module, pid: pid} do + test "returns traces with functions filter", %{module: module, pid: pid, table: table} do trace1 = Trace.new(1, module, :handle_info, [], pid) trace2 = Trace.new(2, module, :render, [], pid) trace3 = Trace.new(3, module, :handle_event, [], pid) + :ets.insert(table, {trace1.id, trace1}) + :ets.insert(table, {trace2.id, trace2}) + :ets.insert(table, {trace3.id, trace3}) - TraceService.insert(trace1) - TraceService.insert(trace2) - TraceService.insert(trace3) + LiveDebugger.MockEtsTableServer + |> expect(:table!, 2, fn ^pid -> table end) assert {[^trace1], _} = TraceService.existing_traces(pid, functions: [:handle_info]) @@ -95,39 +98,45 @@ defmodule Services.TraceServiceTest do TraceService.existing_traces(pid, functions: [:handle_info, :render, :mount]) end - test "returns traces with node_id filter", %{module: module, pid: pid} do + test "returns traces with node_id filter", %{module: module, pid: pid, table: table} do cid = %Phoenix.LiveComponent.CID{cid: 3} trace1 = Trace.new(1, module, :handle_info, [], pid) trace2 = Trace.new(2, module, :render, [], pid, cid: cid) trace3 = Trace.new(3, module, :handle_event, [], pid, cid: cid) + :ets.insert(table, {trace1.id, trace1}) + :ets.insert(table, {trace2.id, trace2}) + :ets.insert(table, {trace3.id, trace3}) - TraceService.insert(trace1) - TraceService.insert(trace2) - TraceService.insert(trace3) + LiveDebugger.MockEtsTableServer + |> expect(:table!, 2, fn ^pid -> table end) assert {[^trace1], _} = TraceService.existing_traces(pid, node_id: pid) assert {[^trace2, ^trace3], _} = TraceService.existing_traces(pid, node_id: cid) end - test "returns :end_of_table when no traces match", %{module: module, pid: pid} do + test "returns :end_of_table when no traces match", %{module: module, pid: pid, table: table} do trace1 = Trace.new(1, module, :handle_info, [], pid) trace2 = Trace.new(2, module, :render, [], pid) + :ets.insert(table, {trace1.id, trace1}) + :ets.insert(table, {trace2.id, trace2}) - TraceService.insert(trace1) - TraceService.insert(trace2) + LiveDebugger.MockEtsTableServer + |> expect(:table!, fn ^pid -> table end) assert :end_of_table = TraceService.existing_traces(pid, functions: [:non_existent]) end end describe "clear_traces/2" do - test "clears traces for LiveView or LiveComponent", %{module: module, pid: pid} do + test "clears traces for LiveView or LiveComponent", %{module: module, pid: pid, table: table} do cid = %Phoenix.LiveComponent.CID{cid: 3} trace1 = Trace.new(1, module, :handle_info, [], pid) trace2 = Trace.new(2, module, :render, [], pid, cid: cid) + :ets.insert(table, {trace1.id, trace1}) + :ets.insert(table, {trace2.id, trace2}) - TraceService.insert(trace1) - TraceService.insert(trace2) + LiveDebugger.MockEtsTableServer + |> expect(:table!, 5, fn ^pid -> table end) assert {[^trace1, ^trace2], _} = TraceService.existing_traces(pid) From edff413c4581f3e73e29a954f0c4e622bd597f02 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Tue, 29 Apr 2025 14:57:32 +0200 Subject: [PATCH 11/14] Update CallbackTracingServer tests --- .../callback_tracing_server_test.exs | 285 ++++++++++++------ test/services/trace_service_test.exs | 25 +- test/test_helper.exs | 3 + 3 files changed, 207 insertions(+), 106 deletions(-) diff --git a/test/gen_servers/callback_tracing_server_test.exs b/test/gen_servers/callback_tracing_server_test.exs index acf08ea24..ac13ad4c2 100644 --- a/test/gen_servers/callback_tracing_server_test.exs +++ b/test/gen_servers/callback_tracing_server_test.exs @@ -4,123 +4,220 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do import Mox + alias LiveDebugger.Structs.Trace alias LiveDebugger.GenServers.CallbackTracingServer + alias LiveDebugger.Utils.PubSub, as: PubSubUtils + alias LiveDebugger.MockModuleService + alias LiveDebugger.MockDbg + alias LiveDebugger.MockEtsTableServer + alias LiveDebugger.MockPubSubUtils + alias LiveDebugger.MockProcessService + + @modules [ + CoolApp.LiveViews.UserDashboard, + CoolApp.Service.UserService, + CoolApp.LiveComponent.UserElement + ] + + test "init/1" do + assert {:ok, %{}} = CallbackTracingServer.init([]) + assert_receive :setup_tracing, 1000 + end - setup_all do - LiveDebugger.MockModuleService - |> stub(:all, fn -> [{to_charlist(CoolApp.Dashboard), "", false}] end) - |> stub(:loaded?, fn _module -> true end) - |> stub(:behaviours, fn _module -> [Phoenix.LiveView] end) - - allow(LiveDebugger.MockModuleService, self(), fn -> - GenServer.whereis(CallbackTracingServer) - end) - - start_supervised(CallbackTracingServer) - - :ok + test "handle_call/3" do + assert {:reply, :ok, %{}} == CallbackTracingServer.handle_call(:ping, self(), %{}) end - setup do - pid = spawn(fn -> Process.sleep(:infinity) end) + describe "tracing mechanism" do + test "proper tracing setup" do + MockModuleService + |> expect(:all, fn -> + Enum.map(@modules, fn module -> {to_charlist(module), ~c"", false} end) + end) + |> expect(:loaded?, 6, fn _module -> true end) + |> expect(:behaviours, 6, fn module -> get_behaviours(module) end) - on_exit(fn -> Process.exit(pid, :kill) end) + MockDbg + |> expect(:tracer, fn :process, {_handler, 0} -> :ok end) + |> expect(:p, fn :all, :c -> :ok end) - %{pid: pid} - end + get_live_view_callbacks(CoolApp.LiveViews.UserDashboard) + |> Enum.each(&expect(MockDbg, :tp, fn &1, [] -> :ok end)) - test "gen server is started" do - pid = GenServer.whereis(CallbackTracingServer) - assert {:error, {:already_started, ^pid}} = CallbackTracingServer.start_link() - assert is_pid(pid) - end + get_live_component_callbacks(CoolApp.LiveComponent.UserElement) + |> Enum.each(&expect(MockDbg, :tp, fn &1, [] -> :ok end)) - describe "table!/1" do - test "creates and remembers table for given pid", %{pid: pid} do - ref1 = CallbackTracingServer.table!(pid) + MockDbg + |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) - assert ref1 == CallbackTracingServer.table!(pid) - assert [] == :ets.tab2list(ref1) + assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) end - test "creates different tables for different pids", %{pid: pid1} do - pid2 = - spawn(fn -> - receive do - :stop -> - :ok - end - end) - - ref1 = CallbackTracingServer.table!(pid1) - ref2 = CallbackTracingServer.table!(pid2) - - assert ref1 != ref2 - assert ref1 == CallbackTracingServer.table!(pid1) - assert ref2 == CallbackTracingServer.table!(pid2) - assert [] == :ets.tab2list(ref1) - assert [] == :ets.tab2list(ref2) - - send(pid2, :stop) + test "handle delete component trace" do + transport_pid = :c.pid(0, 0, 1) + pid = :c.pid(0, 0, 2) + cid = 3 + socket_id = "phx-GDrDzLLr4USWzwBC" + module = Phoenix.LiveView.Diff + function = :delete_component + args = [cid, %{}] + + MockModuleService + |> expect(:all, fn -> [] end) + + MockProcessService + |> expect(:state, fn ^pid -> + {:ok, LiveDebugger.Fakes.state(transport_pid: transport_pid, socket_id: socket_id)} + end) + + MockPubSubUtils + |> expect(:broadcast, fn topic, {:new_trace, trace} -> + assert PubSubUtils.component_deleted_topic(trace) == topic + + assert %Trace{ + id: 0, + module: ^module, + function: ^function, + arity: 2, + args: ^args, + socket_id: ^socket_id, + transport_pid: ^transport_pid, + pid: ^pid, + cid: %Phoenix.LiveComponent.CID{cid: ^cid} + } = trace + end) + |> expect(:broadcast, fn topic, {:new_trace, trace} -> + assert PubSubUtils.component_deleted_topic(trace) == topic + + assert %Trace{ + id: 0, + module: ^module, + function: ^function, + arity: 2, + args: ^args, + socket_id: ^socket_id, + transport_pid: ^transport_pid, + pid: ^pid, + cid: %Phoenix.LiveComponent.CID{cid: ^cid} + } = trace + end) + + MockDbg + |> expect(:tracer, fn :process, {handle_trace, 0} -> + assert 0 == handle_trace.({:trace, pid, :call, {module, function, args}}, 0) + end) + |> expect(:p, fn :all, :c -> :ok end) + |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) + + assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) + + # Test needs to wait for async task to finish + Process.sleep(100) end - test "removes table after process exits", %{pid: pid} do - ref = CallbackTracingServer.table!(pid) - - Process.exit(pid, :kill) - - Process.sleep(200) - - assert_raise ArgumentError, fn -> :ets.tab2list(ref) end - assert ref != CallbackTracingServer.table!(pid) + test "handle standard live view trace" do + transport_pid = :c.pid(0, 0, 1) + pid = :c.pid(0, 0, 2) + socket_id = "phx-GDrDzLLr4USWzwBC" + module = CoolApp.LiveViews.UserDashboard + function = :handle_info + + args = [ + :msg, + %{ + transport_pid: transport_pid, + socket: %Phoenix.LiveView.Socket{id: socket_id} + } + ] + + table = :ets.new(:test_table, [:ordered_set, :public]) + + MockModuleService + |> expect(:all, fn -> [] end) + + MockEtsTableServer + |> expect(:table!, 2, fn ^pid -> table end) + + MockPubSubUtils + |> expect(:broadcast, fn topic, {:new_trace, _trace} -> + assert PubSubUtils.tsnf_topic(socket_id, transport_pid, pid, function) == + topic + end) + |> expect(:broadcast, fn topic, {:new_trace, _trace} -> + assert PubSubUtils.ts_f_topic(socket_id, transport_pid, function) == + topic + end) + + MockDbg + |> expect(:tracer, fn :process, {handle_trace, 0} -> + assert -1 == handle_trace.({:trace, pid, :call, {module, function, args}}, 0) + end) + |> expect(:p, fn :all, :c -> :ok end) + |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) + + assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) + + assert [{0, trace}] = :ets.tab2list(table) + + assert %Trace{ + id: 0, + module: ^module, + function: ^function, + arity: 2, + args: ^args, + socket_id: ^socket_id, + transport_pid: ^transport_pid, + pid: ^pid, + cid: nil + } = trace end - end - test "delete_table!/1", %{pid: pid} do - ref = CallbackTracingServer.table!(pid) + test "handle unexpected trace" do + MockModuleService + |> expect(:all, fn -> [] end) - assert :ok == CallbackTracingServer.delete_table!(pid) - assert_raise ArgumentError, fn -> :ets.tab2list(ref) end - assert ref != CallbackTracingServer.table!(pid) - end - - test "ping!/1" do - assert :ok == CallbackTracingServer.ping!() - end + MockDbg + |> expect(:tracer, fn :process, {handle_trace, 0} -> + assert 0 == handle_trace.({:_, :c.pid(0, 0, 1), :_, {SomeModule, :some_func, []}}, 0) + end) + |> expect(:p, fn :all, :c -> :ok end) + |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) - describe "tracing mechanism" do - test "properly tracing callback call" do - Process.sleep(200) - CoolApp.Dashboard.handle_info(:msg, %{transport_pid: self()}) - - assert [{0, trace}] = CallbackTracingServer.table!(self()) |> :ets.tab2list() - - assert trace.id == 0 - assert trace.module == CoolApp.Dashboard - assert trace.function == :handle_info - assert trace.arity == 2 - assert trace.args == [:msg, %{transport_pid: self()}] - assert trace.socket_id == nil - assert trace.transport_pid == self() - assert trace.pid == self() - assert trace.cid == nil + assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) end + end - test "ignoring non-traced callbacks" do - Process.sleep(200) - CoolApp.Dashboard.non_traced_function(:arg) - - assert [] == CallbackTracingServer.table!(self()) |> :ets.tab2list() + defp get_behaviours(module) do + case module do + CoolApp.LiveViews.UserDashboard -> [Phoenix.LiveView] + CoolApp.Service.UserService -> [] + CoolApp.LiveComponent.UserElement -> [Phoenix.LiveComponent] end end -end -defmodule CoolApp.Dashboard do - def handle_info(_msg, state) do - {:noreply, state} + defp get_live_view_callbacks(module) do + [ + {module, :mount, 2}, + {module, :mount, 3}, + {module, :handle_params, 3}, + {module, :handle_info, 2}, + {module, :handle_call, 3}, + {module, :handle_cast, 2}, + {module, :terminate, 2}, + {module, :render, 1}, + {module, :handle_event, 3}, + {module, :handle_async, 3} + ] end - def non_traced_function(_arg) do - :ok + defp get_live_component_callbacks(module) do + [ + {module, :mount, 1}, + {module, :update, 2}, + {module, :update_many, 1}, + {module, :render, 1}, + {module, :handle_event, 3}, + {module, :handle_async, 3} + ] end end diff --git a/test/services/trace_service_test.exs b/test/services/trace_service_test.exs index 3b0522bcd..90b5262a4 100644 --- a/test/services/trace_service_test.exs +++ b/test/services/trace_service_test.exs @@ -5,12 +5,13 @@ defmodule Services.TraceServiceTest do alias LiveDebugger.Structs.Trace alias LiveDebugger.Services.TraceService - - @module CoolApp.LiveViews.UserDashboard - @pid :c.pid(0, 0, 1) + alias LiveDebugger.MockEtsTableServer setup_all do - %{module: @module, pid: @pid} + %{ + module: CoolApp.LiveViews.UserDashboard, + pid: :c.pid(0, 0, 1) + } end setup context do @@ -22,7 +23,7 @@ defmodule Services.TraceServiceTest do test "insert/1", %{module: module, pid: pid, table: table} do trace = Trace.new(1, module, :render, [], pid) - LiveDebugger.MockEtsTableServer + MockEtsTableServer |> expect(:table!, fn ^pid -> table end) assert true == TraceService.insert(trace) @@ -35,7 +36,7 @@ defmodule Services.TraceServiceTest do :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) - LiveDebugger.MockEtsTableServer + MockEtsTableServer |> expect(:table!, 3, fn ^pid -> table end) assert trace1 == TraceService.get(pid, trace1.id) @@ -50,7 +51,7 @@ defmodule Services.TraceServiceTest do :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) - LiveDebugger.MockEtsTableServer + MockEtsTableServer |> expect(:table!, fn ^pid -> table end) assert {[^trace1, ^trace2], _} = TraceService.existing_traces(pid) @@ -64,7 +65,7 @@ defmodule Services.TraceServiceTest do :ets.insert(table, {trace2.id, trace2}) :ets.insert(table, {trace3.id, trace3}) - LiveDebugger.MockEtsTableServer + MockEtsTableServer |> expect(:table!, 3, fn ^pid -> table end) {traces1, cont} = TraceService.existing_traces(pid, limit: 2) @@ -89,7 +90,7 @@ defmodule Services.TraceServiceTest do :ets.insert(table, {trace2.id, trace2}) :ets.insert(table, {trace3.id, trace3}) - LiveDebugger.MockEtsTableServer + MockEtsTableServer |> expect(:table!, 2, fn ^pid -> table end) assert {[^trace1], _} = TraceService.existing_traces(pid, functions: [:handle_info]) @@ -107,7 +108,7 @@ defmodule Services.TraceServiceTest do :ets.insert(table, {trace2.id, trace2}) :ets.insert(table, {trace3.id, trace3}) - LiveDebugger.MockEtsTableServer + MockEtsTableServer |> expect(:table!, 2, fn ^pid -> table end) assert {[^trace1], _} = TraceService.existing_traces(pid, node_id: pid) @@ -120,7 +121,7 @@ defmodule Services.TraceServiceTest do :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) - LiveDebugger.MockEtsTableServer + MockEtsTableServer |> expect(:table!, fn ^pid -> table end) assert :end_of_table = TraceService.existing_traces(pid, functions: [:non_existent]) @@ -135,7 +136,7 @@ defmodule Services.TraceServiceTest do :ets.insert(table, {trace1.id, trace1}) :ets.insert(table, {trace2.id, trace2}) - LiveDebugger.MockEtsTableServer + MockEtsTableServer |> expect(:table!, 5, fn ^pid -> table end) assert {[^trace1, ^trace2], _} = TraceService.existing_traces(pid) diff --git a/test/test_helper.exs b/test/test_helper.exs index 9faf5ea83..d713bffd6 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -17,6 +17,9 @@ else Mox.defmock(LiveDebugger.MockEtsTableServer, for: LiveDebugger.GenServers.EtsTableServer) Application.put_env(:live_debugger, :ets_table_server, LiveDebugger.MockEtsTableServer) + + Mox.defmock(LiveDebugger.MockDbg, for: LiveDebugger.Services.System.DbgService) + Application.put_env(:live_debugger, :dbg_service, LiveDebugger.MockDbg) end ExUnit.start() From 667ed382c6518c2ad14bc29f46cf1c0d20844899 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Tue, 29 Apr 2025 15:14:20 +0200 Subject: [PATCH 12/14] Change test to async --- test/gen_servers/callback_tracing_server_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gen_servers/callback_tracing_server_test.exs b/test/gen_servers/callback_tracing_server_test.exs index ac13ad4c2..2069e8e7f 100644 --- a/test/gen_servers/callback_tracing_server_test.exs +++ b/test/gen_servers/callback_tracing_server_test.exs @@ -1,6 +1,6 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do @moduledoc false - use ExUnit.Case, async: false + use ExUnit.Case, async: true import Mox From 14e06013ce28ea3b763b32b6823ce212b6889f5f Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Wed, 30 Apr 2025 15:21:47 +0200 Subject: [PATCH 13/14] Refactor --- .../callback_tracing_server_test.exs | 154 +++++++----------- test/gen_servers/ets_table_server_test.exs | 10 +- 2 files changed, 70 insertions(+), 94 deletions(-) diff --git a/test/gen_servers/callback_tracing_server_test.exs b/test/gen_servers/callback_tracing_server_test.exs index 2069e8e7f..04b532fa9 100644 --- a/test/gen_servers/callback_tracing_server_test.exs +++ b/test/gen_servers/callback_tracing_server_test.exs @@ -19,41 +19,58 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do CoolApp.LiveComponent.UserElement ] + setup :verify_on_exit! + test "init/1" do assert {:ok, %{}} = CallbackTracingServer.init([]) - assert_receive :setup_tracing, 1000 + assert_receive :setup_tracing end test "handle_call/3" do assert {:reply, :ok, %{}} == CallbackTracingServer.handle_call(:ping, self(), %{}) end - describe "tracing mechanism" do - test "proper tracing setup" do - MockModuleService - |> expect(:all, fn -> - Enum.map(@modules, fn module -> {to_charlist(module), ~c"", false} end) - end) - |> expect(:loaded?, 6, fn _module -> true end) - |> expect(:behaviours, 6, fn module -> get_behaviours(module) end) + test "proper tracing setup" do + MockModuleService + |> expect(:all, fn -> + Enum.map(@modules, fn module -> {to_charlist(module), ~c"", false} end) + end) + |> expect(:loaded?, 6, fn _module -> true end) + |> expect(:behaviours, 6, fn module -> get_behaviours(module) end) - MockDbg - |> expect(:tracer, fn :process, {_handler, 0} -> :ok end) - |> expect(:p, fn :all, :c -> :ok end) + MockDbg + |> expect(:tracer, fn :process, {_handler, 0} -> :ok end) + |> expect(:p, fn :all, :c -> :ok end) + + get_live_view_callbacks(CoolApp.LiveViews.UserDashboard) + |> Enum.each(&expect(MockDbg, :tp, fn &1, [] -> :ok end)) - get_live_view_callbacks(CoolApp.LiveViews.UserDashboard) - |> Enum.each(&expect(MockDbg, :tp, fn &1, [] -> :ok end)) + get_live_component_callbacks(CoolApp.LiveComponent.UserElement) + |> Enum.each(&expect(MockDbg, :tp, fn &1, [] -> :ok end)) - get_live_component_callbacks(CoolApp.LiveComponent.UserElement) - |> Enum.each(&expect(MockDbg, :tp, fn &1, [] -> :ok end)) + MockDbg + |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) + + assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) + end + + describe "tracing mechanism" do + setup do + parent = self() + + MockModuleService + |> expect(:all, fn -> [] end) MockDbg + |> expect(:p, fn :all, :c -> :ok end) |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) + |> expect(:tracer, fn :process, {handle_trace, 0} -> send(parent, handle_trace) end) - assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) + :ok end test "handle delete component trace" do + parent = self() transport_pid = :c.pid(0, 0, 1) pid = :c.pid(0, 0, 2) cid = 3 @@ -62,8 +79,8 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do function = :delete_component args = [cid, %{}] - MockModuleService - |> expect(:all, fn -> [] end) + expected_topic = + PubSubUtils.component_deleted_topic(%{socket_id: socket_id, transport_pid: transport_pid}) MockProcessService |> expect(:state, fn ^pid -> @@ -71,48 +88,27 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do end) MockPubSubUtils - |> expect(:broadcast, fn topic, {:new_trace, trace} -> - assert PubSubUtils.component_deleted_topic(trace) == topic - - assert %Trace{ - id: 0, - module: ^module, - function: ^function, - arity: 2, - args: ^args, - socket_id: ^socket_id, - transport_pid: ^transport_pid, - pid: ^pid, - cid: %Phoenix.LiveComponent.CID{cid: ^cid} - } = trace - end) - |> expect(:broadcast, fn topic, {:new_trace, trace} -> - assert PubSubUtils.component_deleted_topic(trace) == topic - - assert %Trace{ - id: 0, - module: ^module, - function: ^function, - arity: 2, - args: ^args, - socket_id: ^socket_id, - transport_pid: ^transport_pid, - pid: ^pid, - cid: %Phoenix.LiveComponent.CID{cid: ^cid} - } = trace + |> expect(:broadcast, fn ^expected_topic, {:new_trace, trace} -> + send(parent, {:trace, trace}) end) - MockDbg - |> expect(:tracer, fn :process, {handle_trace, 0} -> - assert 0 == handle_trace.({:trace, pid, :call, {module, function, args}}, 0) - end) - |> expect(:p, fn :all, :c -> :ok end) - |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) - assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) - # Test needs to wait for async task to finish - Process.sleep(100) + assert_receive handle_trace + assert 0 == handle_trace.({:trace, pid, :call, {module, function, args}}, 0) + assert_receive {:trace, trace} + + assert %Trace{ + id: 0, + module: ^module, + function: ^function, + arity: 2, + args: ^args, + socket_id: ^socket_id, + transport_pid: ^transport_pid, + pid: ^pid, + cid: %Phoenix.LiveComponent.CID{cid: ^cid} + } = trace end test "handle standard live view trace" do @@ -124,39 +120,24 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do args = [ :msg, - %{ - transport_pid: transport_pid, - socket: %Phoenix.LiveView.Socket{id: socket_id} - } + %{transport_pid: transport_pid, socket: %Phoenix.LiveView.Socket{id: socket_id}} ] table = :ets.new(:test_table, [:ordered_set, :public]) - MockModuleService - |> expect(:all, fn -> [] end) + expected_tsnf_topic = PubSubUtils.tsnf_topic(socket_id, transport_pid, pid, function) + expected_ts_f_topic = PubSubUtils.ts_f_topic(socket_id, transport_pid, function) MockEtsTableServer - |> expect(:table!, 2, fn ^pid -> table end) + |> expect(:table!, fn ^pid -> table end) MockPubSubUtils - |> expect(:broadcast, fn topic, {:new_trace, _trace} -> - assert PubSubUtils.tsnf_topic(socket_id, transport_pid, pid, function) == - topic - end) - |> expect(:broadcast, fn topic, {:new_trace, _trace} -> - assert PubSubUtils.ts_f_topic(socket_id, transport_pid, function) == - topic - end) - - MockDbg - |> expect(:tracer, fn :process, {handle_trace, 0} -> - assert -1 == handle_trace.({:trace, pid, :call, {module, function, args}}, 0) - end) - |> expect(:p, fn :all, :c -> :ok end) - |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) + |> expect(:broadcast, fn ^expected_tsnf_topic, {:new_trace, _trace} -> :ok end) + |> expect(:broadcast, fn ^expected_ts_f_topic, {:new_trace, _trace} -> :ok end) assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) - + assert_receive handle_trace + assert -1 == handle_trace.({:trace, pid, :call, {module, function, args}}, 0) assert [{0, trace}] = :ets.tab2list(table) assert %Trace{ @@ -173,17 +154,9 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do end test "handle unexpected trace" do - MockModuleService - |> expect(:all, fn -> [] end) - - MockDbg - |> expect(:tracer, fn :process, {handle_trace, 0} -> - assert 0 == handle_trace.({:_, :c.pid(0, 0, 1), :_, {SomeModule, :some_func, []}}, 0) - end) - |> expect(:p, fn :all, :c -> :ok end) - |> expect(:tp, fn {Phoenix.LiveView.Diff, :delete_component, 2}, [] -> :ok end) - assert {:noreply, %{}} = CallbackTracingServer.handle_info(:setup_tracing, %{}) + assert_receive handle_trace + assert 0 == handle_trace.({:_, :c.pid(0, 0, 1), :_, {SomeModule, :some_func, []}}, 0) end end @@ -197,7 +170,6 @@ defmodule LiveDebugger.GenServers.CallbackTracingServerTest do defp get_live_view_callbacks(module) do [ - {module, :mount, 2}, {module, :mount, 3}, {module, :handle_params, 3}, {module, :handle_info, 2}, diff --git a/test/gen_servers/ets_table_server_test.exs b/test/gen_servers/ets_table_server_test.exs index 7b00800d6..768af3411 100644 --- a/test/gen_servers/ets_table_server_test.exs +++ b/test/gen_servers/ets_table_server_test.exs @@ -2,9 +2,13 @@ defmodule LiveDebugger.GenServers.EtsTableServerTest do @moduledoc false use ExUnit.Case, async: true + import Mox + alias LiveDebugger.Utils.PubSub, as: PubSubUtils alias LiveDebugger.GenServers.EtsTableServer + setup :verify_on_exit! + test "start_link/1" do assert {:ok, _pid} = EtsTableServer.start_link() GenServer.stop(EtsTableServer) @@ -19,7 +23,7 @@ defmodule LiveDebugger.GenServers.EtsTableServerTest do pid = :c.pid(0, 0, 1) LiveDebugger.MockEtsTableServer - |> Mox.expect(:table!, fn ^pid -> :some_ref end) + |> expect(:table!, fn ^pid -> :some_ref end) assert :some_ref = EtsTableServer.table!(pid) end @@ -28,7 +32,7 @@ defmodule LiveDebugger.GenServers.EtsTableServerTest do pid = :c.pid(0, 0, 1) LiveDebugger.MockEtsTableServer - |> Mox.expect(:delete_table!, fn ^pid -> :ok end) + |> expect(:delete_table!, fn ^pid -> :ok end) assert :ok = EtsTableServer.delete_table!(pid) end @@ -47,7 +51,7 @@ defmodule LiveDebugger.GenServers.EtsTableServerTest do topic = PubSubUtils.process_status_topic(pid) LiveDebugger.MockPubSubUtils - |> Mox.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) From c4edf5a1821df3342550307cf357db0f7837dfe7 Mon Sep 17 00:00:00 2001 From: Hubert Kasprzycki Date: Wed, 30 Apr 2025 15:28:09 +0200 Subject: [PATCH 14/14] Add verify_on_exit --- test/services/trace_service_test.exs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/services/trace_service_test.exs b/test/services/trace_service_test.exs index 90b5262a4..f189533e6 100644 --- a/test/services/trace_service_test.exs +++ b/test/services/trace_service_test.exs @@ -7,6 +7,8 @@ defmodule Services.TraceServiceTest do alias LiveDebugger.Services.TraceService alias LiveDebugger.MockEtsTableServer + setup :verify_on_exit! + setup_all do %{ module: CoolApp.LiveViews.UserDashboard, @@ -66,7 +68,7 @@ defmodule Services.TraceServiceTest do :ets.insert(table, {trace3.id, trace3}) MockEtsTableServer - |> expect(:table!, 3, fn ^pid -> table end) + |> expect(:table!, fn ^pid -> table end) {traces1, cont} = TraceService.existing_traces(pid, limit: 2) {traces2, cont} = TraceService.existing_traces(pid, cont: cont)