From ed2d2ca07436289ad4ab62ef71b74e0740b3ec05 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 5 Sep 2024 23:44:01 +0200 Subject: [PATCH] evaluate conditional breakpoints, hit counts and log points in Macro.Env obtained from metadata --- .../lib/debug_adapter/breakpoint_condition.ex | 101 ++++++--- .../debug_adapter/lib/debug_adapter/server.ex | 98 +++++---- .../test/breakpoint_condition_test.exs | 205 ++++++++++++++---- apps/debug_adapter/test/debugger_test.exs | 41 ++-- 4 files changed, 296 insertions(+), 149 deletions(-) diff --git a/apps/debug_adapter/lib/debug_adapter/breakpoint_condition.ex b/apps/debug_adapter/lib/debug_adapter/breakpoint_condition.ex index cd514238c..ca7d0cc51 100644 --- a/apps/debug_adapter/lib/debug_adapter/breakpoint_condition.ex +++ b/apps/debug_adapter/lib/debug_adapter/breakpoint_condition.ex @@ -16,27 +16,27 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do @spec register_condition( module, module, - [non_neg_integer], + non_neg_integer, String.t(), String.t() | nil, - non_neg_integer + String.t() ) :: {:ok, {module, atom}} | {:error, :limit_reached} - def register_condition(name \\ __MODULE__, module, lines, condition, log_message, hit_count) do + def register_condition(name \\ __MODULE__, module, line, env, condition, log_message, hit_count) do GenServer.call( name, - {:register_condition, {module, lines}, condition, log_message, hit_count} + {:register_condition, {module, line}, env, condition, log_message, hit_count} ) end - @spec unregister_condition(module, module, [non_neg_integer]) :: :ok - def unregister_condition(name \\ __MODULE__, module, lines) do - GenServer.cast(name, {:unregister_condition, {module, lines}}) + @spec unregister_condition(module, module, non_neg_integer) :: :ok + def unregister_condition(name \\ __MODULE__, module, line) do + GenServer.cast(name, {:unregister_condition, {module, line}}) end - @spec has_condition?(module, module, [non_neg_integer]) :: boolean - def has_condition?(name \\ __MODULE__, module, lines) do - GenServer.call(name, {:has_condition?, {module, lines}}) + @spec has_condition?(module, module, non_neg_integer) :: boolean + def has_condition?(name \\ __MODULE__, module, line) do + GenServer.call(name, {:has_condition?, {module, line}}) end @spec get_condition(module, non_neg_integer) :: @@ -96,7 +96,7 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do @impl GenServer def handle_call( - {:register_condition, key, condition, log_message, hit_count}, + {:register_condition, key, env, condition, log_message, hit_count}, _from, %{free: free, conditions: conditions} = state ) do @@ -111,7 +111,7 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do state | free: rest, conditions: - conditions |> Map.put(key, {number, {condition, log_message, hit_count}}) + conditions |> Map.put(key, {number, {env, condition, log_message, hit_count}}) } {:reply, {:ok, {__MODULE__, :"check_#{number}"}}, state} @@ -120,7 +120,8 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do {number, _old_condition} -> state = %{ state - | conditions: conditions |> Map.put(key, {number, {condition, log_message, hit_count}}) + | conditions: + conditions |> Map.put(key, {number, {env, condition, log_message, hit_count}}) } {:reply, {:ok, {__MODULE__, :"check_#{number}"}}, state} @@ -132,11 +133,11 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do end def handle_call({:get_condition, number}, _from, %{conditions: conditions, hits: hits} = state) do - {condition, log_message, hit_count} = + {env, condition, log_message, hit_count} = conditions |> Map.values() |> Enum.find(fn {n, _c} -> n == number end) |> elem(1) hits = hits |> Map.get(number, 0) - {:reply, {condition, log_message, hit_count, hits}, state} + {:reply, {env, condition, log_message, hit_count, hits}, state} end def handle_call(:clear, _from, _state) do @@ -177,14 +178,20 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do for i <- @range do @spec unquote(:"check_#{i}")(term) :: boolean def unquote(:"check_#{i}")(binding) do - {condition, log_message, hit_count, hits} = get_condition(unquote(i)) + {env, condition, log_message, hit_count_condition, hits} = get_condition(unquote(i)) elixir_binding = binding |> ElixirLS.DebugAdapter.Binding.to_elixir_variable_names() - result = eval_condition(condition, elixir_binding) + result = eval_condition(condition, elixir_binding, env) result = if result do register_hit(unquote(i)) # do not break if hit count not reached + # the spec requires: + # If both this property and `condition` are specified, `hitCondition` should + # be evaluated only if the `condition` is met, and the debug adapter should + # stop only if both conditions are met. + + hit_count = eval_hit_condition(hit_count_condition, elixir_binding, env) hits + 1 > hit_count else result @@ -194,7 +201,9 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do # Debug Adapter Protocol: # If this attribute exists and is non-empty, the backend must not 'break' (stop) # but log the message instead. Expressions within {} are interpolated. - Output.debugger_console(interpolate(log_message, elixir_binding)) + # If either `hitCondition` or `condition` is specified, then the message + # should only be logged if those conditions are met. + Output.debugger_console(interpolate(log_message, elixir_binding, env)) false else result @@ -202,12 +211,12 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do end end - @spec eval_condition(String.t(), keyword) :: boolean - def eval_condition("true", _binding), do: true + @spec eval_condition(String.t(), keyword, Macro.Env.t()) :: boolean + def eval_condition("true", _binding, _env), do: true - def eval_condition(condition, elixir_binding) do + def eval_condition(condition, elixir_binding, env) do try do - {term, _bindings} = Code.eval_string(condition, elixir_binding) + {term, _bindings} = Code.eval_string(condition, elixir_binding, env) if term, do: true, else: false catch kind, error -> @@ -219,9 +228,29 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do end end - def eval_string(expression, elixir_binding) do + @spec eval_hit_condition(String.t(), keyword, Macro.Env.t()) :: number + def eval_hit_condition("0", _binding, _env), do: 0 + + def eval_hit_condition(condition, elixir_binding, env) do + try do + {term, _bindings} = Code.eval_string(condition, elixir_binding, env) + + if is_number(term) do + term + else + raise "Hit count evaluated to non number #{inspect(term)}" + end + catch + kind, error -> + Output.debugger_important("Error in hit count: " <> Exception.format_banner(kind, error)) + + 0 + end + end + + def eval_string(expression, elixir_binding, env) do try do - {term, _bindings} = Code.eval_string(expression, elixir_binding) + {term, _bindings} = Code.eval_string(expression, elixir_binding, env) to_string(term) catch kind, error -> @@ -233,21 +262,21 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do end end - def interpolate(format_string, elixir_binding) do - interpolate(format_string, [], elixir_binding) + def interpolate(format_string, elixir_binding, env) do + interpolate(format_string, [], elixir_binding, env) |> Enum.reverse() |> IO.iodata_to_binary() end - def interpolate(<<>>, acc, _elixir_binding), do: acc + def interpolate(<<>>, acc, _elixir_binding, _env), do: acc - def interpolate(<<"\\{", rest::binary>>, acc, elixir_binding), - do: interpolate(rest, ["{" | acc], elixir_binding) + def interpolate(<<"\\{", rest::binary>>, acc, elixir_binding, env), + do: interpolate(rest, ["{" | acc], elixir_binding, env) - def interpolate(<<"\\}", rest::binary>>, acc, elixir_binding), - do: interpolate(rest, ["}" | acc], elixir_binding) + def interpolate(<<"\\}", rest::binary>>, acc, elixir_binding, env), + do: interpolate(rest, ["}" | acc], elixir_binding, env) - def interpolate(<<"{", rest::binary>>, acc, elixir_binding) do + def interpolate(<<"{", rest::binary>>, acc, elixir_binding, env) do case parse_expression(rest, []) do {:ok, expression_iolist, expression_rest} -> expression = @@ -255,8 +284,8 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do |> Enum.reverse() |> IO.iodata_to_binary() - eval_result = eval_string(expression, elixir_binding) - interpolate(expression_rest, [eval_result | acc], elixir_binding) + eval_result = eval_string(expression, elixir_binding, env) + interpolate(expression_rest, [eval_result | acc], elixir_binding, env) :error -> Output.debugger_important("Log message has unpaired or nested `{}`") @@ -264,8 +293,8 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do end end - def interpolate(<>, acc, elixir_binding), - do: interpolate(rest, [char | acc], elixir_binding) + def interpolate(<>, acc, elixir_binding, env), + do: interpolate(rest, [char | acc], elixir_binding, env) def parse_expression(<<>>, _acc), do: :error def parse_expression(<<"\\{", rest::binary>>, acc), do: parse_expression(rest, ["{" | acc]) diff --git a/apps/debug_adapter/lib/debug_adapter/server.ex b/apps/debug_adapter/lib/debug_adapter/server.ex index a5786d428..e373a04ea 100644 --- a/apps/debug_adapter/lib/debug_adapter/server.ex +++ b/apps/debug_adapter/lib/debug_adapter/server.ex @@ -973,7 +973,9 @@ defmodule ElixirLS.DebugAdapter.Server do for {{m, f, a}, lines} <- state.function_breakpoints, not Map.has_key?(parsed_mfas_conditions, {m, f, a}) do - BreakpointCondition.unregister_condition(m, lines) + for line <- lines do + BreakpointCondition.unregister_condition(m, line) + end case :int.del_break_in(m, f, a) do :ok -> @@ -993,6 +995,14 @@ defmodule ElixirLS.DebugAdapter.Server do into: %{}, do: ( + path = + try do + module_info = ModuleInfoCache.get(m) || m.module_info() + Path.expand(to_string(module_info[:compile][:source])) + rescue + _ -> "nofile" + end + result = case current[{m, f, a}] do nil -> @@ -1011,6 +1021,7 @@ defmodule ElixirLS.DebugAdapter.Server do # pass nil as log_message - not supported on function breakpoints as of DAP 1.63 update_break_condition( + path, m, lines, condition, @@ -1038,7 +1049,15 @@ defmodule ElixirLS.DebugAdapter.Server do lines -> # pass nil as log_message - not supported on function breakpoints as of DAP 1.51 - update_break_condition(m, lines, condition, nil, hit_count, state.config) + update_break_condition( + path, + m, + lines, + condition, + nil, + hit_count, + state.config + ) {:ok, lines} end @@ -2461,7 +2480,7 @@ defmodule ElixirLS.DebugAdapter.Server do Output.debugger_console("Setting breakpoint in #{inspect(module)} #{path}:#{line}") # no need to handle errors here, it can fail only with {:error, :break_exists} :int.break(module, line) - update_break_condition(module, line, condition, log_message, hit_count, config) + update_break_condition(path, module, line, condition, log_message, hit_count, config) [module | added] @@ -2524,38 +2543,49 @@ defmodule ElixirLS.DebugAdapter.Server do end end - def update_break_condition(module, lines, condition, log_message, hit_count, config) do + def update_break_condition(path, module, lines, condition, log_message, hit_count, config) do lines = List.wrap(lines) - condition = parse_condition(condition) + condition = parse_condition(condition, "true") - hit_count = eval_hit_count(hit_count) + hit_count = parse_condition(hit_count, "0") log_message = if log_message not in ["", nil], do: log_message - register_break_condition(module, lines, condition, log_message, hit_count, config) + register_break_condition(path, module, lines, condition, log_message, hit_count, config) end - defp register_break_condition(module, lines, condition, log_message, hit_count, %{ + defp register_break_condition(file, module, lines, condition, log_message, hit_count, %{ "request" => "launch" }) do - case BreakpointCondition.register_condition(module, lines, condition, log_message, hit_count) do - {:ok, mf} -> - for line <- lines do + for line <- lines do + {_metadata, _env, macro_env_or_opts} = parse_file(file, line) + # TODO use Code.env_for_eval when we require elixir 1.14 + env = ElixirLS.DebugAdapter.Code.env_for_eval(macro_env_or_opts) + + case BreakpointCondition.register_condition( + module, + line, + env, + condition, + log_message, + hit_count + ) do + {:ok, mf} -> :int.test_at_break(module, line, mf) - end - {:error, reason} -> - Output.debugger_important( - "Unable to set condition on a breakpoint in #{module}:#{inspect(lines)}: #{inspect(reason)}" - ) + {:error, reason} -> + Output.debugger_important( + "Unable to set condition on a breakpoint in #{module}:#{inspect(line)}: #{inspect(reason)}" + ) + end end end - defp register_break_condition(_module, _lines, condition, log_message, hit_count, %{ + defp register_break_condition(_file, _module, _lines, condition, log_message, hit_count, %{ "request" => "attach" }) do - if condition != "true" || log_message || hit_count != 0 do + if condition != "true" || log_message || hit_count != "0" do # Module passed to :int.test_at_break has to be available on remote nodes. Otherwise break condition will # always evaluate to false. We cannot easily distribute BreakpointCondition to remote nodes. Output.debugger_important( @@ -2564,39 +2594,16 @@ defmodule ElixirLS.DebugAdapter.Server do end end - defp parse_condition(condition) when condition in [nil, ""], do: "true" + defp parse_condition(condition, default) when condition in [nil, ""], do: default - defp parse_condition(condition) do + defp parse_condition(condition, default) do case Code.string_to_quoted(condition) do {:ok, _} -> condition {:error, reason} -> Output.debugger_important("Cannot parse breakpoint condition: #{inspect(reason)}") - "true" - end - end - - defp eval_hit_count(hit_count) when hit_count in [nil, ""], do: 0 - - defp eval_hit_count(hit_count) do - try do - # TODO binding? - {term, _bindings} = Code.eval_string(hit_count, []) - - if is_integer(term) do - term - else - Output.debugger_important("Hit condition must evaluate to integer") - 0 - end - catch - kind, error -> - Output.debugger_important( - "Error while evaluating hit condition: " <> Exception.format_banner(kind, error) - ) - - 0 + default end end @@ -2920,8 +2927,7 @@ defmodule ElixirLS.DebugAdapter.Server do buffer_file_metadata = ElixirSense.Core.Parser.parse_string(code, false, true, {line, 1}) env = ElixirSense.Core.Metadata.get_env(buffer_file_metadata, {line, 1}) - # TODO env_for_eval? - # should we clear versioned_vars? + {buffer_file_metadata, env, ElixirSense.Core.State.Env.to_macro_env(env, file, line)} else # do not try to parse non elixir files diff --git a/apps/debug_adapter/test/breakpoint_condition_test.exs b/apps/debug_adapter/test/breakpoint_condition_test.exs index 8523e30f4..c8638c4f8 100644 --- a/apps/debug_adapter/test/breakpoint_condition_test.exs +++ b/apps/debug_adapter/test/breakpoint_condition_test.exs @@ -22,44 +22,93 @@ defmodule ElixirLS.DebugAdapter.BreakpointConditionTest do describe "register" do test "basic" do assert {:ok, {BreakpointCondition, :check_0}} == - BreakpointCondition.register_condition(@name, Some, [123], "a == b", nil, 0) + BreakpointCondition.register_condition( + @name, + Some, + 123, + __ENV__, + "a == b", + nil, + "0" + ) assert {:ok, {BreakpointCondition, :check_1}} == - BreakpointCondition.register_condition(@name, Some, [124], "c == d", "asd", 1) + BreakpointCondition.register_condition( + @name, + Some, + 124, + __ENV__, + "c == d", + "asd", + "1" + ) assert {:ok, {BreakpointCondition, :check_2}} == - BreakpointCondition.register_condition(@name, Other, [124], "c == d", nil, 2) + BreakpointCondition.register_condition( + @name, + Other, + 124, + __ENV__, + "c == d", + nil, + "2" + ) state = :sys.get_state(Process.whereis(@name)) - assert state.conditions == %{ - {Other, [124]} => {2, {"c == d", nil, 2}}, - {Some, [123]} => {0, {"a == b", nil, 0}}, - {Some, [124]} => {1, {"c == d", "asd", 1}} - } + assert %{ + {Other, 124} => {2, {_, "c == d", nil, "2"}}, + {Some, 123} => {0, {_, "a == b", nil, "0"}}, + {Some, 124} => {1, {_, "c == d", "asd", "1"}} + } = state.conditions assert state.free == 3..99 |> Enum.to_list() end test "limit" do for i <- 0..99 do - {:ok, _} = BreakpointCondition.register_condition(@name, Some, [i], "c == d", nil, 1) + {:ok, _} = + BreakpointCondition.register_condition(@name, Some, i, __ENV__, "c == d", nil, "1") end assert {:error, :limit_reached} == - BreakpointCondition.register_condition(@name, Some, [100], "c == d", nil, 1) + BreakpointCondition.register_condition( + @name, + Some, + 100, + __ENV__, + "c == d", + nil, + "1" + ) end test "update" do assert {:ok, {BreakpointCondition, :check_0}} == - BreakpointCondition.register_condition(@name, Some, [123], "a == b", nil, 2) + BreakpointCondition.register_condition( + @name, + Some, + 123, + __ENV__, + "a == b", + nil, + "2" + ) assert {:ok, {BreakpointCondition, :check_0}} == - BreakpointCondition.register_condition(@name, Some, [123], "c == b", "xxx", 3) + BreakpointCondition.register_condition( + @name, + Some, + 123, + __ENV__, + "c == b", + "xxx", + "3" + ) state = :sys.get_state(Process.whereis(@name)) - assert state.conditions == %{{Some, [123]} => {0, {"c == b", "xxx", 3}}} + assert %{{Some, 123} => {0, {_, "c == b", "xxx", "3"}}} = state.conditions assert state.free == 1..99 |> Enum.to_list() end end @@ -67,11 +116,19 @@ defmodule ElixirLS.DebugAdapter.BreakpointConditionTest do describe "unregister" do test "basic" do assert {:ok, {BreakpointCondition, :check_0}} == - BreakpointCondition.register_condition(@name, Some, [123], "a == b", nil, 0) + BreakpointCondition.register_condition( + @name, + Some, + 123, + __ENV__, + "a == b", + nil, + "0" + ) BreakpointCondition.register_hit(@name, 0) - assert :ok == BreakpointCondition.unregister_condition(@name, Some, [123]) + assert :ok == BreakpointCondition.unregister_condition(@name, Some, 123) state = :sys.get_state(Process.whereis(@name)) @@ -82,10 +139,18 @@ defmodule ElixirLS.DebugAdapter.BreakpointConditionTest do test "idempotency" do assert {:ok, {BreakpointCondition, :check_0}} == - BreakpointCondition.register_condition(@name, Some, [123], "a == b", nil, 1) - - assert :ok == BreakpointCondition.unregister_condition(@name, Some, [123]) - assert :ok == BreakpointCondition.unregister_condition(@name, Some, [123]) + BreakpointCondition.register_condition( + @name, + Some, + 123, + __ENV__, + "a == b", + nil, + "1" + ) + + assert :ok == BreakpointCondition.unregister_condition(@name, Some, 123) + assert :ok == BreakpointCondition.unregister_condition(@name, Some, 123) state = :sys.get_state(Process.whereis(@name)) @@ -96,30 +161,38 @@ defmodule ElixirLS.DebugAdapter.BreakpointConditionTest do test "has_condition?" do assert {:ok, {BreakpointCondition, :check_0}} == - BreakpointCondition.register_condition(@name, Some, [123], "a == b", nil, 1) + BreakpointCondition.register_condition(@name, Some, 123, __ENV__, "a == b", nil, "1") - assert BreakpointCondition.has_condition?(@name, Some, [123]) + assert BreakpointCondition.has_condition?(@name, Some, 123) - refute BreakpointCondition.has_condition?(@name, Some, [124]) - refute BreakpointCondition.has_condition?(@name, Other, [123]) + refute BreakpointCondition.has_condition?(@name, Some, 124) + refute BreakpointCondition.has_condition?(@name, Other, 123) end test "get_condition" do assert {:ok, {BreakpointCondition, :check_0}} == - BreakpointCondition.register_condition(@name, Some, [123], "a == b", nil, 1) + BreakpointCondition.register_condition(@name, Some, 123, __ENV__, "a == b", nil, "1") assert {:ok, {BreakpointCondition, :check_1}} == - BreakpointCondition.register_condition(@name, Some, [124], "c == d", "xxx", 2) + BreakpointCondition.register_condition( + @name, + Some, + 124, + __ENV__, + "c == d", + "xxx", + "2" + ) BreakpointCondition.register_hit(@name, 1) - assert {"a == b", nil, 1, 0} == BreakpointCondition.get_condition(@name, 0) - assert {"c == d", "xxx", 2, 1} == BreakpointCondition.get_condition(@name, 1) + assert {_, "a == b", nil, "1", 0} = BreakpointCondition.get_condition(@name, 0) + assert {_, "c == d", "xxx", "2", 1} = BreakpointCondition.get_condition(@name, 1) end test "register_hit" do assert {:ok, {BreakpointCondition, :check_0}} == - BreakpointCondition.register_condition(@name, Some, [123], "a == b", nil, 1) + BreakpointCondition.register_condition(@name, Some, 123, __ENV__, "a == b", nil, "1") BreakpointCondition.register_hit(@name, 0) assert :sys.get_state(Process.whereis(@name)).hits == %{0 => 1} @@ -130,75 +203,113 @@ defmodule ElixirLS.DebugAdapter.BreakpointConditionTest do describe "evel_condition" do test "evals to true" do binding = [{:a, 1}, {:b, 1}] - assert BreakpointCondition.eval_condition("a == b", binding) == true + assert BreakpointCondition.eval_condition("a == b", binding, __ENV__) == true end test "evals to truthy" do binding = [{:a, 1}] - assert BreakpointCondition.eval_condition("a", binding) == true + assert BreakpointCondition.eval_condition("a", binding, __ENV__) == true end test "evals to false" do binding = [{:a, 1}, {:b, 2}] - assert BreakpointCondition.eval_condition("a == b", binding) == false + assert BreakpointCondition.eval_condition("a == b", binding, __ENV__) == false end test "evals to falsy" do binding = [{:a, nil}] - assert BreakpointCondition.eval_condition("a", binding) == false + assert BreakpointCondition.eval_condition("a", binding, __ENV__) == false + end + + test "handles raise" do + capture_io(:standard_error, fn -> + assert BreakpointCondition.eval_condition("raise ArgumentError", [], __ENV__) == false + end) + end + + test "handles throw" do + capture_io(:standard_error, fn -> + assert BreakpointCondition.eval_condition("throw :asd", [], __ENV__) == false + end) + end + + test "handles exit" do + capture_io(:standard_error, fn -> + assert BreakpointCondition.eval_condition("exit(:normal)", [], __ENV__) == false + end) + end + end + + describe "eval_hit_condition" do + test "evals to number" do + binding = [{:a, 1}] + assert BreakpointCondition.eval_hit_condition("1 + 2.5", binding, __ENV__) == 3.5 + end + + test "defaults to 0" do + binding = [{:a, 1}, {:b, 2}] + assert BreakpointCondition.eval_hit_condition("false", binding, __ENV__) == 0 end test "handles raise" do capture_io(:standard_error, fn -> - assert BreakpointCondition.eval_condition("raise ArgumentError", []) == false + assert BreakpointCondition.eval_hit_condition("raise ArgumentError", [], __ENV__) == 0 end) end test "handles throw" do capture_io(:standard_error, fn -> - assert BreakpointCondition.eval_condition("throw :asd", []) == false + assert BreakpointCondition.eval_hit_condition("throw :asd", [], __ENV__) == 0 end) end test "handles exit" do capture_io(:standard_error, fn -> - assert BreakpointCondition.eval_condition("exit(:normal)", []) == false + assert BreakpointCondition.eval_hit_condition("exit(:normal)", [], __ENV__) == 0 end) end end describe "interpolate" do test "basic" do - assert BreakpointCondition.interpolate("", []) == "" - assert BreakpointCondition.interpolate("abc", []) == "abc" + assert BreakpointCondition.interpolate("", [], __ENV__) == "" + assert BreakpointCondition.interpolate("abc", [], __ENV__) == "abc" end test "escape sequences" do - assert BreakpointCondition.interpolate("\\{", []) == "{" - assert BreakpointCondition.interpolate("\\}", []) == "}" + assert BreakpointCondition.interpolate("\\{", [], __ENV__) == "{" + assert BreakpointCondition.interpolate("\\}", [], __ENV__) == "}" end test "substitute variable" do - assert BreakpointCondition.interpolate("abc{myvar}cde", myvar: "123") == "abc123cde" - assert BreakpointCondition.interpolate("abc{myvar}cde", myvar: 123) == "abc123cde" + assert BreakpointCondition.interpolate("abc{myvar}cde", [myvar: "123"], __ENV__) == + "abc123cde" + + assert BreakpointCondition.interpolate("abc{myvar}cde", [myvar: 123], __ENV__) == + "abc123cde" end test "escape sequence within substitution" do - assert BreakpointCondition.interpolate("abc{inspect(%\\{\\})}cde", []) == "abc%{}cde" + assert BreakpointCondition.interpolate("abc{inspect(%\\{\\})}cde", [], __ENV__) == + "abc%{}cde" end test "invalid" do capture_io(:standard_error, fn -> - assert BreakpointCondition.interpolate("abc{myvar{cde", []) == "abc" - assert BreakpointCondition.interpolate("abc{myvarcde", myvar: 123) == "abc" + assert BreakpointCondition.interpolate("abc{myvar{cde", [], __ENV__) == "abc" + assert BreakpointCondition.interpolate("abc{myvarcde", [myvar: 123], __ENV__) == "abc" end) end test "error in substitution" do capture_io(:standard_error, fn -> - assert BreakpointCondition.interpolate("abc{myvar}cde", []) == "abccde" - assert BreakpointCondition.interpolate("abc{self()}cde", myvar: 123) == "abccde" - assert BreakpointCondition.interpolate("abc{throw :error}cde", myvar: 123) == "abccde" + assert BreakpointCondition.interpolate("abc{myvar}cde", [], __ENV__) == "abccde" + + assert BreakpointCondition.interpolate("abc{self()}cde", [myvar: 123], __ENV__) == + "abccde" + + assert BreakpointCondition.interpolate("abc{throw :error}cde", [myvar: 123], __ENV__) == + "abccde" end) end end diff --git a/apps/debug_adapter/test/debugger_test.exs b/apps/debug_adapter/test/debugger_test.exs index cd8db5b3d..563a0a89a 100644 --- a/apps/debug_adapter/test/debugger_test.exs +++ b/apps/debug_adapter/test/debugger_test.exs @@ -1579,9 +1579,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{^abs_path => [{[MixProject], 3}]} = :sys.get_state(server).breakpoints - assert BreakpointCondition.has_condition?(MixProject, [3]) + assert BreakpointCondition.has_condition?(MixProject, 3) - assert BreakpointCondition.get_condition(0) == {"a == b", nil, 0, 0} + assert {%Macro.Env{}, "a == b", nil, "0", 0} = BreakpointCondition.get_condition(0) # modify @@ -1604,9 +1604,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{^abs_path => [{[MixProject], 3}]} = :sys.get_state(server).breakpoints - assert BreakpointCondition.has_condition?(MixProject, [3]) + assert BreakpointCondition.has_condition?(MixProject, 3) - assert BreakpointCondition.get_condition(0) == {"x == y", nil, 0, 0} + assert {%Macro.Env{}, "x == y", nil, "0", 0} = BreakpointCondition.get_condition(0) # unset @@ -1674,9 +1674,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{^abs_path => [{[MixProject], 3}]} = :sys.get_state(server).breakpoints - assert BreakpointCondition.has_condition?(MixProject, [3]) + assert BreakpointCondition.has_condition?(MixProject, 3) - assert BreakpointCondition.get_condition(0) == {"true", nil, 25, 0} + assert {%Macro.Env{}, "true", nil, "25", 0} = BreakpointCondition.get_condition(0) # modify @@ -1699,9 +1699,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{^abs_path => [{[MixProject], 3}]} = :sys.get_state(server).breakpoints - assert BreakpointCondition.has_condition?(MixProject, [3]) + assert BreakpointCondition.has_condition?(MixProject, 3) - assert BreakpointCondition.get_condition(0) == {"true", nil, 55, 0} + assert {%Macro.Env{}, "true", nil, "55", 0} = BreakpointCondition.get_condition(0) # unset @@ -1769,9 +1769,10 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{^abs_path => [{[MixProject], 3}]} = :sys.get_state(server).breakpoints - assert BreakpointCondition.has_condition?(MixProject, [3]) + assert BreakpointCondition.has_condition?(MixProject, 3) - assert BreakpointCondition.get_condition(0) == {"true", "breakpoint hit", 0, 0} + assert {%Macro.Env{}, "true", "breakpoint hit", "0", 0} = + BreakpointCondition.get_condition(0) # modify @@ -1794,9 +1795,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{^abs_path => [{[MixProject], 3}]} = :sys.get_state(server).breakpoints - assert BreakpointCondition.has_condition?(MixProject, [3]) + assert BreakpointCondition.has_condition?(MixProject, 3) - assert BreakpointCondition.get_condition(0) == {"true", "abc", 0, 0} + assert {%Macro.Env{}, "true", "abc", "0", 0} = BreakpointCondition.get_condition(0) # unset @@ -2207,9 +2208,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{{:hello, :hello_world, 0} => [5]} = :sys.get_state(server).function_breakpoints - assert BreakpointCondition.has_condition?(:hello, [5]) + assert BreakpointCondition.has_condition?(:hello, 5) - assert BreakpointCondition.get_condition(0) == {"a == b", nil, 0, 0} + assert {%Macro.Env{}, "a == b", nil, "0", 0} = BreakpointCondition.get_condition(0) # update @@ -2233,9 +2234,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{{:hello, :hello_world, 0} => [5]} = :sys.get_state(server).function_breakpoints - assert BreakpointCondition.has_condition?(:hello, [5]) + assert BreakpointCondition.has_condition?(:hello, 5) - assert BreakpointCondition.get_condition(0) == {"x == y", nil, 0, 0} + assert {%Macro.Env{}, "x == y", nil, "0", 0} = BreakpointCondition.get_condition(0) # unset @@ -2300,9 +2301,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{{:hello, :hello_world, 0} => [5]} = :sys.get_state(server).function_breakpoints - assert BreakpointCondition.has_condition?(:hello, [5]) + assert BreakpointCondition.has_condition?(:hello, 5) - assert BreakpointCondition.get_condition(0) == {"true", nil, 25, 0} + assert {%Macro.Env{}, "true", nil, "25", 0} = BreakpointCondition.get_condition(0) # update @@ -2326,9 +2327,9 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert %{{:hello, :hello_world, 0} => [5]} = :sys.get_state(server).function_breakpoints - assert BreakpointCondition.has_condition?(:hello, [5]) + assert BreakpointCondition.has_condition?(:hello, 5) - assert BreakpointCondition.get_condition(0) == {"true", nil, 55, 0} + assert {%Macro.Env{}, "true", nil, "55", 0} = BreakpointCondition.get_condition(0) # unset