diff --git a/lib/deployex/tracer.ex b/lib/deployex/tracer.ex index a581035..3e0f8d4 100644 --- a/lib/deployex/tracer.ex +++ b/lib/deployex/tracer.ex @@ -39,8 +39,14 @@ defmodule Deployex.Tracer do def get_modules(node \\ Node.self()) do :rpc.call(node, :code, :all_loaded, [], :infinity) |> Enum.map(fn - {module, _} -> module - module -> module + {module, _} -> + module + + # coveralls-ignore-start + module -> + module + + # coveralls-ignore-stop end) end @@ -117,6 +123,7 @@ defmodule Deployex.Tracer do ### ========================================================================== ### Private functions ### ========================================================================== + # coveralls-ignore-start defp regular_functions?(function) do String.contains?(function, "-anonymous-") or String.contains?(function, "-fun-") or @@ -125,4 +132,6 @@ defmodule Deployex.Tracer do String.contains?(function, "-lc") or String.contains?(function, "-lbc") end + + # coveralls-ignore-stop end diff --git a/lib/deployex/tracer/server.ex b/lib/deployex/tracer/server.ex index b04abb1..18f267e 100644 --- a/lib/deployex/tracer/server.ex +++ b/lib/deployex/tracer/server.ex @@ -29,14 +29,6 @@ defmodule Deployex.Tracer.Server do end @impl true - def handle_call( - {:start_trace, _functions, _session_timeout_ms}, - _from, - %DeployexT{status: :running} = state - ) do - {:reply, {:error, :already_started}, state} - end - def handle_call({:stop_tracing, rcv_session_id}, _from, %DeployexT{ session_id: session_id }) @@ -56,6 +48,14 @@ defmodule Deployex.Tracer.Server do {:reply, state, state} end + def handle_call( + {:start_trace, _new_state}, + _from, + %DeployexT{status: :running} = state + ) do + {:reply, {:error, :already_started}, state} + end + # credo:disable-for-lines:1 def handle_call( {:start_trace, @@ -71,9 +71,7 @@ defmodule Deployex.Tracer.Server do ) do Process.monitor(request_pid) - Logger.info( - "New trace requested session: #{session_id} functions: #{inspect(functions_by_node)}" - ) + Logger.info("New Trace Session: #{session_id} functions: #{inspect(functions_by_node)}") tracer_pid = self() # The local node (deployex) is always present in the trace list of nodes. @@ -96,10 +94,13 @@ defmodule Deployex.Tracer.Server do Enum.each(functions_by_node, fn {node, functions} -> # Add node to tracing process (exclude local node since it is added by default) + # coveralls-ignore-start if node != Node.self() do :dbg.n(node) end + # coveralls-ignore-stop + # Add functions to be traced # credo:disable-for-lines:12 Enum.each(functions, fn function -> @@ -108,7 +109,9 @@ defmodule Deployex.Tracer.Server do atom_spec = String.to_existing_atom(spec) case Map.get(default_functions_matchspecs, atom_spec) do + # coveralls-ignore-start nil -> acc + # coveralls-ignore-stop %{pattern: pattern} -> acc ++ pattern end end) @@ -226,7 +229,9 @@ defmodule Deployex.Tracer.Server do def handle_trace(_trace_message, {%{max_messages: max_messages} = session_info, index}) when index > max_messages do + # coveralls-ignore-start :dbg.stop() + # coveralls-ignore-stop {session_info, index} end @@ -268,12 +273,14 @@ defmodule Deployex.Tracer.Server do {pid, type, "[#{y}-#{mm}-#{d} #{h}:#{m}:#{s}] (#{inspect(pid)}) #{inspect(module)}.#{fun}/#{arity}}) exception_value: #{inspect(exception_value)}"} + # coveralls-ignore-start trace_msg -> Logger.warning( "Not able to decode trace_mg: #{inspect(trace_msg)} session_index: #{inspect(index)}" ) {nil, nil, nil} + # coveralls-ignore-stop end node = origin_pid && :erlang.node(origin_pid) diff --git a/test/deployex/tracer_test.exs b/test/deployex/tracer_test.exs new file mode 100644 index 0000000..84e9b8b --- /dev/null +++ b/test/deployex/tracer_test.exs @@ -0,0 +1,235 @@ +defmodule Deployex.TracerTest do + use ExUnit.Case, async: true + + import Mox + + alias Deployex.Tracer, as: DeployexT + alias Deployex.TracerFixtures + + setup :verify_on_exit! + + test "get_modules/1" do + assert list = DeployexT.get_modules(Node.self()) + assert Enum.member?(list, :kernel) + end + + test "get_module_functions_info/2" do + assert %{ + functions: %{"config_change/3" => %{arity: 3, name: :config_change}}, + module: :kernel, + node: :nonode@nohost + } = DeployexT.get_module_functions_info(Node.self(), :kernel) + + assert %{ + functions: _, + module: :erlang, + node: :nonode@nohost + } = DeployexT.get_module_functions_info(Node.self(), :erlang) + end + + test "get_default_functions_matchspecs/0" do + assert %{ + return_trace: %{pattern: [{:_, [], [{:return_trace}]}]}, + exception_trace: %{pattern: [{:_, [], [{:exception_trace}]}]}, + caller: %{pattern: [{:_, [], [{:message, {:caller}}]}]}, + process_dump: %{pattern: [{:_, [], [{:message, {:process_dump}}]}]} + } = DeployexT.get_default_functions_matchspecs() + end + + describe "start_trace/2" do + test "Only one session is allowed for Tracer" do + functions = [ + %{ + arity: :_, + function: :_, + match_spec: [], + module: TracerFixtures, + node: Node.self() + } + ] + + assert {:ok, %{session_id: session_id}} = + DeployexT.start_trace(functions, %{max_messages: 1}) + + assert {:error, :already_started} = + DeployexT.start_trace(functions, %{max_messages: 1}) + + assert :ok == DeployexT.stop_trace(session_id) + + :timer.sleep(50) + end + + test "Success requesting only module" do + node = Node.self() + + functions = [ + %{ + arity: :_, + function: :_, + match_spec: [], + module: TracerFixtures, + node: node + } + ] + + assert {:ok, %{session_id: session_id} = state} = + DeployexT.start_trace(functions, %{max_messages: 1}) + + assert state == DeployexT.state() + + TracerFixtures.testing_fun([true, true, true]) + + assert_receive {:new_trace_message, ^session_id, ^node, _index, :call, msg}, 1_000 + + assert msg =~ "TracerFixtures" + + assert :ok == DeployexT.stop_trace(session_id) + + :timer.sleep(50) + end + + test "Success requesting module/function" do + node = Node.self() + + functions = [ + %{ + arity: 2, + function: :testing_adding_fun, + match_spec: [], + module: TracerFixtures, + node: node + } + ] + + assert {:ok, %{session_id: session_id}} = + DeployexT.start_trace(functions, %{max_messages: 1}) + + TracerFixtures.testing_adding_fun(50, 50) + + assert_receive {:new_trace_message, ^session_id, ^node, _index, :call, msg}, 1_000 + + assert msg =~ "TracerFixtures" + assert msg =~ "testing_adding_fun" + + assert :ok == DeployexT.stop_trace(session_id) + + :timer.sleep(50) + end + + test "Success requesting module/function with match_spec [return_trace]" do + node = Node.self() + + functions = [ + %{ + arity: 2, + function: :testing_adding_fun, + match_spec: ["return_trace"], + module: TracerFixtures, + node: node + } + ] + + assert {:ok, %{session_id: session_id}} = + DeployexT.start_trace(functions, %{max_messages: 2}) + + TracerFixtures.testing_adding_fun(253, 200) + + assert_receive {:new_trace_message, ^session_id, ^node, _index, :return_from, msg}, 1_000 + + assert msg =~ "TracerFixtures" + assert msg =~ "testing_adding_fun" + assert msg =~ "453" + + assert :ok == DeployexT.stop_trace(session_id) + + :timer.sleep(50) + end + + test "Success requesting module/function with match_spec [caller]" do + node = Node.self() + + functions = [ + %{ + arity: 2, + function: :testing_adding_fun, + match_spec: ["caller"], + module: TracerFixtures, + node: node + } + ] + + assert {:ok, %{session_id: session_id}} = + DeployexT.start_trace(functions, %{max_messages: 2}) + + TracerFixtures.testing_adding_fun(255, 200) + + assert_receive {:new_trace_message, ^session_id, ^node, _index, :call, msg}, 1_000 + + assert msg =~ "TracerFixtures" + assert msg =~ "testing_adding_fun" + assert msg =~ "caller: {Deployex.TracerTest" + + assert :ok == DeployexT.stop_trace(session_id) + + :timer.sleep(50) + end + + @tag :capture_log + test "Success requesting module/function with match_spec [exception_trace]" do + node = Node.self() + + functions = [ + %{ + arity: 1, + function: :testing_exception_fun, + match_spec: ["exception_trace"], + module: TracerFixtures, + node: node + } + ] + + assert {:ok, %{session_id: session_id}} = + DeployexT.start_trace(functions, %{max_messages: 2}) + + spawn(fn -> + TracerFixtures.testing_exception_fun(0) + end) + + assert_receive {:new_trace_message, ^session_id, ^node, _index, :exception_from, msg}, 1_000 + + assert msg =~ "TracerFixtures" + assert msg =~ "testing_exception_fun" + assert msg =~ "exception_value: {:error, :badarith}" + + assert :ok == DeployexT.stop_trace(session_id) + + :timer.sleep(50) + end + end + + describe "stop_trace/2" do + test "Ignore stop_trace with invalid session_id" do + functions = [ + %{ + arity: :_, + function: :_, + match_spec: [], + module: TracerFixtures, + node: Node.self() + } + ] + + assert {:ok, %{session_id: session_id}} = + DeployexT.start_trace(functions, %{max_messages: 1}) + + assert %DeployexT{status: :running} = DeployexT.state() + + assert :ok == DeployexT.stop_trace("123456789") + + assert :ok == DeployexT.stop_trace(session_id) + + assert %DeployexT{status: :idle} = DeployexT.state() + :timer.sleep(50) + end + end +end diff --git a/test/support/fixtures/tracer.ex b/test/support/fixtures/tracer.ex new file mode 100644 index 0000000..6b49489 --- /dev/null +++ b/test/support/fixtures/tracer.ex @@ -0,0 +1,21 @@ +defmodule Deployex.TracerFixtures do + @moduledoc """ + This module will handle the tracer fixture + """ + + def testing_fun(_arg1) do + :ok + end + + def testing_adding_fun(arg1, arg2) do + arg1 + arg2 + end + + def testing_caller_fun(arg1, arg2) do + testing_adding_fun(arg1, arg2) + end + + def testing_exception_fun(arg) do + 1 / arg + end +end