From c0f5c666eb6cbedd9783e91f38c67c4ce7aa25d8 Mon Sep 17 00:00:00 2001 From: Kasse-Dembele Date: Wed, 10 Jul 2024 22:56:50 +0200 Subject: [PATCH 1/3] feat: telemetry event for instrumentation --- lib/fun_with_flags.ex | 100 +++++++++++++++++++++++++++++++++++------- mix.exs | 4 +- mix.lock | 1 + 3 files changed, 88 insertions(+), 17 deletions(-) diff --git a/lib/fun_with_flags.ex b/lib/fun_with_flags.ex index 7987875..b186c7e 100644 --- a/lib/fun_with_flags.ex +++ b/lib/fun_with_flags.ex @@ -74,8 +74,11 @@ defmodule FunWithFlags do def enabled?(flag_name, options \\ []) def enabled?(flag_name, []) when is_atom(flag_name) do + start_time = System.monotonic_time() {:ok, flag} = @store.lookup(flag_name) - Flag.enabled?(flag) + result = Flag.enabled?(flag) + emit_telemetry_event(:enabled?, flag_name, start_time, result, %{options: []}) + result end def enabled?(flag_name, [for: nil]) do @@ -83,8 +86,11 @@ defmodule FunWithFlags do end def enabled?(flag_name, [for: item]) when is_atom(flag_name) do + start_time = System.monotonic_time() {:ok, flag} = @store.lookup(flag_name) - Flag.enabled?(flag, for: item) + result = Flag.enabled?(flag, for: item) + emit_telemetry_event(:enabled?, flag_name, start_time, result, %{options: [for: item]}) + result end @@ -178,11 +184,14 @@ defmodule FunWithFlags do def enable(flag_name, options \\ []) def enable(flag_name, []) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:boolean, true) - case @store.put(flag_name, gate) do + result = case @store.put(flag_name, gate) do {:ok, flag} -> verify(flag) error -> error end + emit_telemetry_event(:enable, flag_name, start_time, result, %{options: []}) + result end def enable(flag_name, [for_actor: nil]) do @@ -190,11 +199,14 @@ defmodule FunWithFlags do end def enable(flag_name, [for_actor: actor]) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:actor, actor, true) - case @store.put(flag_name, gate) do + result = case @store.put(flag_name, gate) do {:ok, flag} -> verify(flag, for: actor) error -> error end + emit_telemetry_event(:enable, flag_name, start_time, result, %{options: [for_actor: actor]}) + result end @@ -203,28 +215,37 @@ defmodule FunWithFlags do end def enable(flag_name, [for_group: group_name]) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:group, group_name, true) - case @store.put(flag_name, gate) do + result = case @store.put(flag_name, gate) do {:ok, _flag} -> {:ok, true} error -> error end + emit_telemetry_event(:enable, flag_name, start_time, result, %{options: [for_group: group_name]}) + result end def enable(flag_name, [for_percentage_of: {:time, ratio}]) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:percentage_of_time, ratio) - case @store.put(flag_name, gate) do + result = case @store.put(flag_name, gate) do {:ok, _flag} -> {:ok, true} error -> error end + emit_telemetry_event(:enable, flag_name, start_time, result, %{options: [for_percentage_of: {:time, ratio}]}) + result end def enable(flag_name, [for_percentage_of: {:actors, ratio}]) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:percentage_of_actors, ratio) - case @store.put(flag_name, gate) do + result = case @store.put(flag_name, gate) do {:ok, _flag} -> {:ok, true} error -> error end + emit_telemetry_event(:enable, flag_name, start_time, result, %{options: [for_percentage_of: {:actors, ratio}]}) + result end @@ -310,11 +331,14 @@ defmodule FunWithFlags do def disable(flag_name, options \\ []) def disable(flag_name, []) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:boolean, false) - case @store.put(flag_name, gate) do + result = case @store.put(flag_name, gate) do {:ok, flag} -> verify(flag) error -> error end + emit_telemetry_event(:disable, flag_name, start_time, result, %{options: []}) + result end def disable(flag_name, [for_actor: nil]) do @@ -322,11 +346,14 @@ defmodule FunWithFlags do end def disable(flag_name, [for_actor: actor]) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:actor, actor, false) - case @store.put(flag_name, gate) do + result = case @store.put(flag_name, gate) do {:ok, flag} -> verify(flag, for: actor) error -> error end + emit_telemetry_event(:disable, flag_name, start_time, result, %{options: [for_actor: actor]}) + result end def disable(flag_name, [for_group: nil]) do @@ -334,21 +361,27 @@ defmodule FunWithFlags do end def disable(flag_name, [for_group: group_name]) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:group, group_name, false) - case @store.put(flag_name, gate) do + result = case @store.put(flag_name, gate) do {:ok, _flag} -> {:ok, false} error -> error end + emit_telemetry_event(:disable, flag_name, start_time, result, %{options: [for_group: group_name]}) + result end def disable(flag_name, [for_percentage_of: {type, ratio}]) when is_atom(flag_name) and is_float(ratio) do + start_time = System.monotonic_time() inverted_ratio = 1.0 - ratio - case enable(flag_name, [for_percentage_of: {type, inverted_ratio}]) do + result = case enable(flag_name, [for_percentage_of: {type, inverted_ratio}]) do {:ok, true} -> {:ok, false} error -> error end + emit_telemetry_event(:disable, flag_name, start_time, result, %{options: [for_percentage_of: {type, ratio}]}) + result end @@ -418,15 +451,21 @@ defmodule FunWithFlags do def clear(flag_name, options \\ []) def clear(flag_name, []) when is_atom(flag_name) do - case @store.delete(flag_name) do + start_time = System.monotonic_time() + result = case @store.delete(flag_name) do {:ok, _flag} -> :ok error -> error end + emit_telemetry_event(:clear, flag_name, start_time, result, %{options: []}) + result end def clear(flag_name, [boolean: true]) do + start_time = System.monotonic_time() gate = Gate.new(:boolean, false) # we only care about the gate id - _clear_gate(flag_name, gate) + result = _clear_gate(flag_name, gate) + emit_telemetry_event(:clear, flag_name, start_time, result, %{options: [boolean: true]}) + result end def clear(flag_name, [for_actor: nil]) do @@ -434,8 +473,11 @@ defmodule FunWithFlags do end def clear(flag_name, [for_actor: actor]) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:actor, actor, false) # we only care about the gate id - _clear_gate(flag_name, gate) + result = _clear_gate(flag_name, gate) + emit_telemetry_event(:clear, flag_name, start_time, result, %{options: [for_actor: actor]}) + result end def clear(flag_name, [for_group: nil]) do @@ -443,13 +485,19 @@ defmodule FunWithFlags do end def clear(flag_name, [for_group: group_name]) when is_atom(flag_name) do + start_time = System.monotonic_time() gate = Gate.new(:group, group_name, false) # we only care about the gate id - _clear_gate(flag_name, gate) + result = _clear_gate(flag_name, gate) + emit_telemetry_event(:clear, flag_name, start_time, result, %{options: [for_group: group_name]}) + result end def clear(flag_name, [for_percentage: true]) do + start_time = System.monotonic_time() gate = Gate.new(:percentage_of_time, 0.5) # we only care about the gate id - _clear_gate(flag_name, gate) + result = _clear_gate(flag_name, gate) + emit_telemetry_event(:clear, flag_name, start_time, result, %{options: [for_percentage: true]}) + result end defp _clear_gate(flag_name, gate) do @@ -524,4 +572,24 @@ defmodule FunWithFlags do # @doc false def compiled_store, do: @store + + defp emit_telemetry_event(action, flag_name, start_time, result, metadata) do + end_time = System.monotonic_time() + measurements = %{ + duration: end_time - start_time + } + + event_metadata = Map.merge(metadata, %{ + flag_name: flag_name, + result: result, + operation: action, + }) + + :telemetry.execute( + [:fun_with_flags, :flag_operation], + measurements, + event_metadata + ) + end + end diff --git a/mix.exs b/mix.exs index 26b9165..e11ec22 100644 --- a/mix.exs +++ b/mix.exs @@ -80,7 +80,9 @@ defmodule FunWithFlags.Mixfile do {:ex_doc, "~> 0.21", only: :dev, runtime: false}, {:credo, "~> 1.7", only: :dev, runtime: false}, - {:dialyxir, "~> 1.0", only: :dev, runtime: false} + {:dialyxir, "~> 1.0", only: :dev, runtime: false}, + {:telemetry_test, "~> 0.1", only: :test, runtime: false}, + {:telemetry, "~> 1.0", optional: true}, ] end diff --git a/mix.lock b/mix.lock index 67f61ee..a255d77 100644 --- a/mix.lock +++ b/mix.lock @@ -29,4 +29,5 @@ "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "redix": {:hex, :redix, "1.3.0", "f4121163ff9d73bf72157539ff23b13e38422284520bb58c05e014b19d6f0577", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "60d483d320c77329c8cbd3df73007e51b23f3fae75b7693bc31120d83ab26131"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry_test": {:hex, :telemetry_test, "0.1.2", "122d927567c563cf57773105fa8104ae4299718ec2cbdddcf6776562c7488072", [:mix], [{:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bd41a49ecfd33ecd82d2c7edae19a5736f0d2150206d0ee290dcf3885d0e14d"}, } From 97a8025f4ffab2f68e475cfb469e28b9342a9c46 Mon Sep 17 00:00:00 2001 From: Kasse-Dembele Date: Wed, 10 Jul 2024 22:57:41 +0200 Subject: [PATCH 2/3] test: telemetry event emit --- test/fun_with_flags/telemetry_test.exs | 490 +++++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 test/fun_with_flags/telemetry_test.exs diff --git a/test/fun_with_flags/telemetry_test.exs b/test/fun_with_flags/telemetry_test.exs new file mode 100644 index 0000000..765608b --- /dev/null +++ b/test/fun_with_flags/telemetry_test.exs @@ -0,0 +1,490 @@ +defmodule FunWithFlags.TelemetryTest do + use FunWithFlags.TestCase, async: false + import FunWithFlags.TestUtils + import TelemetryTest + import Mock + + setup [:telemetry_listen] + + describe ":enable telemetry" do + setup do + scrooge = %FunWithFlags.TestUser{id: 1, email: "scrooge@mcduck.pdp", groups: [:ducks, :billionaires]} + donald = %FunWithFlags.TestUser{id: 2, email: "donald@duck.db", groups: [:ducks, :super_heroes]} + {:ok, scrooge: scrooge, donald: donald, flag_name: unique_atom()} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when enabling a flag", %{flag_name: flag_name} do + FunWithFlags.enable(flag_name) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when enabling a flag for an actor", %{flag_name: flag_name, scrooge: scrooge} do + FunWithFlags.enable(flag_name, for_actor: scrooge) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_actor: ^scrooge], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when enabling a flag for a group", %{flag_name: flag_name} do + group_name = :test_group + FunWithFlags.enable(flag_name, for_group: group_name) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_group: ^group_name], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when enabling a flag for percentage of time", %{flag_name: flag_name} do + ratio = 0.5 + FunWithFlags.enable(flag_name, for_percentage_of: {:time, ratio}) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_percentage_of: {:time, ^ratio}], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when enabling a flag for percentage of actors", %{flag_name: flag_name} do + ratio = 0.5 + FunWithFlags.enable(flag_name, for_percentage_of: {:actors, ratio}) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_percentage_of: {:actors, ^ratio}], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + }} + end + end + + describe "enabled? telemetry" do + setup do + scrooge = %FunWithFlags.TestUser{id: 1, email: "scrooge@mcduck.pdp", groups: [:ducks, :billionaires]} + donald = %FunWithFlags.TestUser{id: 2, email: "donald@duck.db", groups: [:ducks, :super_heroes]} + flag_name = unique_atom() + {:ok, scrooge: scrooge, donald: donald, flag_name: flag_name} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when checking an enabled flag", %{flag_name: flag_name} do + FunWithFlags.enable(flag_name) + + + FunWithFlags.enabled?(flag_name) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [], + result: true, + flag_name: ^flag_name, + operation: :enabled? + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when checking a disabled flag", %{flag_name: flag_name} do + FunWithFlags.disable(flag_name) + + + FunWithFlags.enabled?(flag_name) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [], + result: false, + flag_name: ^flag_name, + operation: :enabled? + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when checking an enabled flag for a specific actor", %{flag_name: flag_name, scrooge: scrooge} do + FunWithFlags.enable(flag_name, for_actor: scrooge) + + FunWithFlags.enabled?(flag_name, for: scrooge) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for: ^scrooge], + result: true, + flag_name: ^flag_name, + operation: :enabled? + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when checking a disabled flag for a specific actor", %{flag_name: flag_name, donald: donald} do + FunWithFlags.enable(flag_name) + FunWithFlags.disable(flag_name, for_actor: donald) + FunWithFlags.enabled?(flag_name, for: donald) + + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for: ^donald], + result: false, + flag_name: ^flag_name, + operation: :enabled? + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when checking an enabled flag for a group", %{flag_name: flag_name, scrooge: scrooge} do + group = :billionaires + FunWithFlags.enable(flag_name, for_group: group) + + + result = FunWithFlags.enabled?(flag_name, for: scrooge) + + assert result == true + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for: ^scrooge], + result: true, + flag_name: ^flag_name, + operation: :enabled? + } + }} + end + end + + describe "disable telemetry" do + setup do + scrooge = %FunWithFlags.TestUser{id: 1, email: "scrooge@mcduck.pdp", groups: [:ducks, :billionaires]} + donald = %FunWithFlags.TestUser{id: 2, email: "donald@duck.db", groups: [:ducks, :super_heroes]} + flag_name = unique_atom() + {:ok, scrooge: scrooge, donald: donald, flag_name: flag_name} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when disabling a flag globally", %{flag_name: flag_name} do + FunWithFlags.enable(flag_name) + + + result = FunWithFlags.disable(flag_name) + + assert result == {:ok, false} + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [], + result: {:ok, false}, + flag_name: ^flag_name, + operation: :disable + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when disabling a flag for an actor", %{flag_name: flag_name, scrooge: scrooge} do + FunWithFlags.enable(flag_name, for_actor: scrooge) + + + result = FunWithFlags.disable(flag_name, for_actor: scrooge) + + assert result == {:ok, false} + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_actor: ^scrooge], + result: {:ok, false}, + flag_name: ^flag_name, + operation: :disable + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when disabling a flag for a group", %{flag_name: flag_name} do + group = :billionaires + FunWithFlags.enable(flag_name, for_group: group) + + + result = FunWithFlags.disable(flag_name, for_group: group) + + assert result == {:ok, false} + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_group: ^group], + result: {:ok, false}, + flag_name: ^flag_name, + operation: :disable + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when disabling a flag for a percentage of time", %{flag_name: flag_name} do + ratio = 0.5 + FunWithFlags.enable(flag_name, for_percentage_of: {:time, ratio}) + + + result = FunWithFlags.disable(flag_name, for_percentage_of: {:time, ratio}) + + assert result == {:ok, false} + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_percentage_of: {:time, ^ratio}], + result: {:ok, false}, + flag_name: ^flag_name, + operation: :disable + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when disabling a flag for a percentage of actors", %{flag_name: flag_name} do + ratio = 0.5 + FunWithFlags.enable(flag_name, for_percentage_of: {:actors, ratio}) + + + result = FunWithFlags.disable(flag_name, for_percentage_of: {:actors, ratio}) + + assert result == {:ok, false} + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_percentage_of: {:actors, ^ratio}], + result: {:ok, false}, + flag_name: ^flag_name, + operation: :disable + } + }} + end + end + + describe "clear telemetry" do + setup do + scrooge = %FunWithFlags.TestUser{id: 1, email: "scrooge@mcduck.pdp", groups: [:ducks, :billionaires]} + donald = %FunWithFlags.TestUser{id: 2, email: "donald@duck.db", groups: [:ducks, :super_heroes]} + flag_name = unique_atom() + {:ok, scrooge: scrooge, donald: donald, flag_name: flag_name} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when clearing a flag globally", %{flag_name: flag_name} do + FunWithFlags.enable(flag_name) + + + result = FunWithFlags.clear(flag_name) + + assert result == :ok + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [], + result: :ok, + flag_name: ^flag_name, + operation: :clear + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when clearing a boolean gate", %{flag_name: flag_name} do + FunWithFlags.enable(flag_name) + + + result = FunWithFlags.clear(flag_name, boolean: true) + + assert result == :ok + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [boolean: true], + result: :ok, + flag_name: ^flag_name, + operation: :clear + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when clearing a flag for an actor", %{flag_name: flag_name, scrooge: scrooge} do + FunWithFlags.enable(flag_name, for_actor: scrooge) + + + result = FunWithFlags.clear(flag_name, for_actor: scrooge) + + assert result == :ok + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_actor: ^scrooge], + result: :ok, + flag_name: ^flag_name, + operation: :clear + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when clearing a flag for a group", %{flag_name: flag_name} do + group = :billionaires + FunWithFlags.enable(flag_name, for_group: group) + + + result = FunWithFlags.clear(flag_name, for_group: group) + + assert result == :ok + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_group: ^group], + result: :ok, + flag_name: ^flag_name, + operation: :clear + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when clearing a percentage gate", %{flag_name: flag_name} do + FunWithFlags.enable(flag_name, for_percentage_of: {:time, 0.5}) + + + result = FunWithFlags.clear(flag_name, for_percentage: true) + + assert result == :ok + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_percentage: true], + result: :ok, + flag_name: ^flag_name, + operation: :clear + } + }} + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when clearing fails globally", %{flag_name: flag_name} do + error_reason = {:error, :test_error} + + with_mock FunWithFlags.Store, [:passthrough], [delete: fn(_) -> error_reason end] do + result = FunWithFlags.clear(flag_name) + + assert result == error_reason + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [], + result: ^error_reason, + flag_name: ^flag_name, + operation: :clear + } + }} + end + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when clearing fails for an actor", %{flag_name: flag_name, scrooge: scrooge} do + error_reason = {:error, :test_error} + + with_mock FunWithFlags.Store, [:passthrough], [delete: fn(_, _) -> error_reason end] do + result = FunWithFlags.clear(flag_name, for_actor: scrooge) + + assert result == error_reason + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_actor: ^scrooge], + result: ^error_reason, + flag_name: ^flag_name, + operation: :clear + } + }} + end + end + + @tag telemetry_listen: [:fun_with_flags, :flag_operation] + test "emits telemetry event when clearing fails for a group", %{flag_name: flag_name} do + error_reason = {:error, :test_error} + group = :billionaires + + with_mock FunWithFlags.Store, [:passthrough], [delete: fn(_, _) -> error_reason end] do + result = FunWithFlags.clear(flag_name, for_group: group) + + assert result == error_reason + assert_received {:telemetry_event, %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_group: ^group], + result: ^error_reason, + flag_name: ^flag_name, + operation: :clear + } + }} + end + end + end + +end From 5aa7fd5ed658c92f33b6cc3bb4b710dad28f983d Mon Sep 17 00:00:00 2001 From: Kasse-Dembele Date: Wed, 7 Aug 2024 23:44:12 +0000 Subject: [PATCH 3/3] test: telemetry event emit without using telemetry_test package --- mix.exs | 3 +- test/fun_with_flags/telemetry_test.exs | 420 ++++++++++++++----------- 2 files changed, 245 insertions(+), 178 deletions(-) diff --git a/mix.exs b/mix.exs index e11ec22..7803297 100644 --- a/mix.exs +++ b/mix.exs @@ -81,8 +81,7 @@ defmodule FunWithFlags.Mixfile do {:ex_doc, "~> 0.21", only: :dev, runtime: false}, {:credo, "~> 1.7", only: :dev, runtime: false}, {:dialyxir, "~> 1.0", only: :dev, runtime: false}, - {:telemetry_test, "~> 0.1", only: :test, runtime: false}, - {:telemetry, "~> 1.0", optional: true}, + {:telemetry, "~> 1.0", optional: true} ] end diff --git a/test/fun_with_flags/telemetry_test.exs b/test/fun_with_flags/telemetry_test.exs index 765608b..3f7b1a2 100644 --- a/test/fun_with_flags/telemetry_test.exs +++ b/test/fun_with_flags/telemetry_test.exs @@ -1,137 +1,156 @@ defmodule FunWithFlags.TelemetryTest do use FunWithFlags.TestCase, async: false import FunWithFlags.TestUtils - import TelemetryTest import Mock - setup [:telemetry_listen] - describe ":enable telemetry" do setup do - scrooge = %FunWithFlags.TestUser{id: 1, email: "scrooge@mcduck.pdp", groups: [:ducks, :billionaires]} - donald = %FunWithFlags.TestUser{id: 2, email: "donald@duck.db", groups: [:ducks, :super_heroes]} - {:ok, scrooge: scrooge, donald: donald, flag_name: unique_atom()} - end - - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when enabling a flag", %{flag_name: flag_name} do - FunWithFlags.enable(flag_name) - - assert_received {:telemetry_event, %{ - measurements: %{duration: _}, - event: [:fun_with_flags, :flag_operation], - metadata: %{ - options: [], - result: {:ok, true}, - flag_name: ^flag_name, - operation: :enable - } - }} - end - - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when enabling a flag for an actor", %{flag_name: flag_name, scrooge: scrooge} do - FunWithFlags.enable(flag_name, for_actor: scrooge) - - assert_received {:telemetry_event, %{ - measurements: %{duration: _}, - event: [:fun_with_flags, :flag_operation], - metadata: %{ - options: [for_actor: ^scrooge], - result: {:ok, true}, - flag_name: ^flag_name, - operation: :enable - } - }} - end - - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when enabling a flag for a group", %{flag_name: flag_name} do - group_name = :test_group - FunWithFlags.enable(flag_name, for_group: group_name) - - assert_received {:telemetry_event, %{ - measurements: %{duration: _}, - event: [:fun_with_flags, :flag_operation], - metadata: %{ - options: [for_group: ^group_name], - result: {:ok, true}, - flag_name: ^flag_name, - operation: :enable - } - }} - end - - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when enabling a flag for percentage of time", %{flag_name: flag_name} do - ratio = 0.5 - FunWithFlags.enable(flag_name, for_percentage_of: {:time, ratio}) - - assert_received {:telemetry_event, %{ - measurements: %{duration: _}, - event: [:fun_with_flags, :flag_operation], - metadata: %{ - options: [for_percentage_of: {:time, ^ratio}], - result: {:ok, true}, - flag_name: ^flag_name, - operation: :enable - } - }} - end - - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when enabling a flag for percentage of actors", %{flag_name: flag_name} do - ratio = 0.5 - FunWithFlags.enable(flag_name, for_percentage_of: {:actors, ratio}) - - assert_received {:telemetry_event, %{ - measurements: %{duration: _}, - event: [:fun_with_flags, :flag_operation], - metadata: %{ - options: [for_percentage_of: {:actors, ^ratio}], - result: {:ok, true}, - flag_name: ^flag_name, - operation: :enable - } - }} - end + attach_telemetry() + + scrooge = %FunWithFlags.TestUser{ + id: 1, + email: "scrooge@mcduck.pdp", + groups: [:ducks, :billionaires] + } + + donald = %FunWithFlags.TestUser{ + id: 2, + email: "donald@duck.db", + groups: [:ducks, :super_heroes] + } + + {:ok, scrooge: scrooge, donald: donald, flag_name: unique_atom()} + end + + test "emits telemetry event when enabling a flag", %{flag_name: flag_name} do + FunWithFlags.enable(flag_name) + + assert_receive %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + } + end + + test "emits telemetry event when enabling a flag for an actor", %{ + flag_name: flag_name, + scrooge: scrooge + } do + FunWithFlags.enable(flag_name, for_actor: scrooge) + + assert_receive %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_actor: ^scrooge], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + } + end + + test "emits telemetry event when enabling a flag for a group", %{flag_name: flag_name} do + group_name = :test_group + FunWithFlags.enable(flag_name, for_group: group_name) + + assert_receive %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_group: ^group_name], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + } + end + + test "emits telemetry event when enabling a flag for percentage of time", %{ + flag_name: flag_name + } do + ratio = 0.5 + FunWithFlags.enable(flag_name, for_percentage_of: {:time, ratio}) + + assert_receive %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_percentage_of: {:time, ^ratio}], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + } + end + + test "emits telemetry event when enabling a flag for percentage of actors", %{ + flag_name: flag_name + } do + ratio = 0.5 + FunWithFlags.enable(flag_name, for_percentage_of: {:actors, ratio}) + + assert_receive %{ + measurements: %{duration: _}, + event: [:fun_with_flags, :flag_operation], + metadata: %{ + options: [for_percentage_of: {:actors, ^ratio}], + result: {:ok, true}, + flag_name: ^flag_name, + operation: :enable + } + } + end end describe "enabled? telemetry" do setup do - scrooge = %FunWithFlags.TestUser{id: 1, email: "scrooge@mcduck.pdp", groups: [:ducks, :billionaires]} - donald = %FunWithFlags.TestUser{id: 2, email: "donald@duck.db", groups: [:ducks, :super_heroes]} + attach_telemetry() + + scrooge = %FunWithFlags.TestUser{ + id: 1, + email: "scrooge@mcduck.pdp", + groups: [:ducks, :billionaires] + } + + donald = %FunWithFlags.TestUser{ + id: 2, + email: "donald@duck.db", + groups: [:ducks, :super_heroes] + } + flag_name = unique_atom() {:ok, scrooge: scrooge, donald: donald, flag_name: flag_name} end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when checking an enabled flag", %{flag_name: flag_name} do FunWithFlags.enable(flag_name) - FunWithFlags.enabled?(flag_name) - assert_received {:telemetry_event, %{ - measurements: %{duration: _}, + assert_receive %{ event: [:fun_with_flags, :flag_operation], + measurements: %{duration: _}, metadata: %{ options: [], result: true, flag_name: ^flag_name, operation: :enabled? } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when checking a disabled flag", %{flag_name: flag_name} do FunWithFlags.disable(flag_name) - FunWithFlags.enabled?(flag_name) - assert_received {:telemetry_event, %{ + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -140,16 +159,18 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :enabled? } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when checking an enabled flag for a specific actor", %{flag_name: flag_name, scrooge: scrooge} do + test "emits telemetry event when checking an enabled flag for a specific actor", %{ + flag_name: flag_name, + scrooge: scrooge + } do FunWithFlags.enable(flag_name, for_actor: scrooge) FunWithFlags.enabled?(flag_name, for: scrooge) - assert_received {:telemetry_event, %{ + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -158,16 +179,18 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :enabled? } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when checking a disabled flag for a specific actor", %{flag_name: flag_name, donald: donald} do + test "emits telemetry event when checking a disabled flag for a specific actor", %{ + flag_name: flag_name, + donald: donald + } do FunWithFlags.enable(flag_name) FunWithFlags.disable(flag_name, for_actor: donald) FunWithFlags.enabled?(flag_name, for: donald) - assert_received {:telemetry_event, %{ + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -176,19 +199,21 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :enabled? } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when checking an enabled flag for a group", %{flag_name: flag_name, scrooge: scrooge} do + test "emits telemetry event when checking an enabled flag for a group", %{ + flag_name: flag_name, + scrooge: scrooge + } do group = :billionaires FunWithFlags.enable(flag_name, for_group: group) - result = FunWithFlags.enabled?(flag_name, for: scrooge) assert result == true - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -197,27 +222,38 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :enabled? } - }} + } end end describe "disable telemetry" do setup do - scrooge = %FunWithFlags.TestUser{id: 1, email: "scrooge@mcduck.pdp", groups: [:ducks, :billionaires]} - donald = %FunWithFlags.TestUser{id: 2, email: "donald@duck.db", groups: [:ducks, :super_heroes]} + attach_telemetry() + + scrooge = %FunWithFlags.TestUser{ + id: 1, + email: "scrooge@mcduck.pdp", + groups: [:ducks, :billionaires] + } + + donald = %FunWithFlags.TestUser{ + id: 2, + email: "donald@duck.db", + groups: [:ducks, :super_heroes] + } + flag_name = unique_atom() {:ok, scrooge: scrooge, donald: donald, flag_name: flag_name} end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when disabling a flag globally", %{flag_name: flag_name} do FunWithFlags.enable(flag_name) - result = FunWithFlags.disable(flag_name) assert result == {:ok, false} - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -226,18 +262,20 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :disable } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when disabling a flag for an actor", %{flag_name: flag_name, scrooge: scrooge} do + test "emits telemetry event when disabling a flag for an actor", %{ + flag_name: flag_name, + scrooge: scrooge + } do FunWithFlags.enable(flag_name, for_actor: scrooge) - result = FunWithFlags.disable(flag_name, for_actor: scrooge) assert result == {:ok, false} - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -246,19 +284,18 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :disable } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when disabling a flag for a group", %{flag_name: flag_name} do group = :billionaires FunWithFlags.enable(flag_name, for_group: group) - result = FunWithFlags.disable(flag_name, for_group: group) assert result == {:ok, false} - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -267,19 +304,20 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :disable } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when disabling a flag for a percentage of time", %{flag_name: flag_name} do + test "emits telemetry event when disabling a flag for a percentage of time", %{ + flag_name: flag_name + } do ratio = 0.5 FunWithFlags.enable(flag_name, for_percentage_of: {:time, ratio}) - result = FunWithFlags.disable(flag_name, for_percentage_of: {:time, ratio}) assert result == {:ok, false} - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -288,19 +326,20 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :disable } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when disabling a flag for a percentage of actors", %{flag_name: flag_name} do + test "emits telemetry event when disabling a flag for a percentage of actors", %{ + flag_name: flag_name + } do ratio = 0.5 FunWithFlags.enable(flag_name, for_percentage_of: {:actors, ratio}) - result = FunWithFlags.disable(flag_name, for_percentage_of: {:actors, ratio}) assert result == {:ok, false} - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -309,27 +348,38 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :disable } - }} + } end end describe "clear telemetry" do setup do - scrooge = %FunWithFlags.TestUser{id: 1, email: "scrooge@mcduck.pdp", groups: [:ducks, :billionaires]} - donald = %FunWithFlags.TestUser{id: 2, email: "donald@duck.db", groups: [:ducks, :super_heroes]} + attach_telemetry() + + scrooge = %FunWithFlags.TestUser{ + id: 1, + email: "scrooge@mcduck.pdp", + groups: [:ducks, :billionaires] + } + + donald = %FunWithFlags.TestUser{ + id: 2, + email: "donald@duck.db", + groups: [:ducks, :super_heroes] + } + flag_name = unique_atom() {:ok, scrooge: scrooge, donald: donald, flag_name: flag_name} end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when clearing a flag globally", %{flag_name: flag_name} do FunWithFlags.enable(flag_name) - result = FunWithFlags.clear(flag_name) assert result == :ok - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -338,18 +388,17 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :clear } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when clearing a boolean gate", %{flag_name: flag_name} do FunWithFlags.enable(flag_name) - result = FunWithFlags.clear(flag_name, boolean: true) assert result == :ok - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -358,18 +407,20 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :clear } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when clearing a flag for an actor", %{flag_name: flag_name, scrooge: scrooge} do + test "emits telemetry event when clearing a flag for an actor", %{ + flag_name: flag_name, + scrooge: scrooge + } do FunWithFlags.enable(flag_name, for_actor: scrooge) - result = FunWithFlags.clear(flag_name, for_actor: scrooge) assert result == :ok - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -378,19 +429,18 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :clear } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when clearing a flag for a group", %{flag_name: flag_name} do group = :billionaires FunWithFlags.enable(flag_name, for_group: group) - result = FunWithFlags.clear(flag_name, for_group: group) assert result == :ok - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -399,18 +449,17 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :clear } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when clearing a percentage gate", %{flag_name: flag_name} do FunWithFlags.enable(flag_name, for_percentage_of: {:time, 0.5}) - result = FunWithFlags.clear(flag_name, for_percentage: true) assert result == :ok - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -419,18 +468,18 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :clear } - }} + } end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when clearing fails globally", %{flag_name: flag_name} do error_reason = {:error, :test_error} - with_mock FunWithFlags.Store, [:passthrough], [delete: fn(_) -> error_reason end] do + with_mock FunWithFlags.Store, [:passthrough], delete: fn _ -> error_reason end do result = FunWithFlags.clear(flag_name) assert result == error_reason - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -439,19 +488,22 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :clear } - }} + } end end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] - test "emits telemetry event when clearing fails for an actor", %{flag_name: flag_name, scrooge: scrooge} do + test "emits telemetry event when clearing fails for an actor", %{ + flag_name: flag_name, + scrooge: scrooge + } do error_reason = {:error, :test_error} - with_mock FunWithFlags.Store, [:passthrough], [delete: fn(_, _) -> error_reason end] do + with_mock FunWithFlags.Store, [:passthrough], delete: fn _, _ -> error_reason end do result = FunWithFlags.clear(flag_name, for_actor: scrooge) assert result == error_reason - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -460,20 +512,20 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :clear } - }} + } end end - @tag telemetry_listen: [:fun_with_flags, :flag_operation] test "emits telemetry event when clearing fails for a group", %{flag_name: flag_name} do error_reason = {:error, :test_error} group = :billionaires - with_mock FunWithFlags.Store, [:passthrough], [delete: fn(_, _) -> error_reason end] do + with_mock FunWithFlags.Store, [:passthrough], delete: fn _, _ -> error_reason end do result = FunWithFlags.clear(flag_name, for_group: group) assert result == error_reason - assert_received {:telemetry_event, %{ + + assert_receive %{ measurements: %{duration: _}, event: [:fun_with_flags, :flag_operation], metadata: %{ @@ -482,9 +534,25 @@ defmodule FunWithFlags.TelemetryTest do flag_name: ^flag_name, operation: :clear } - }} + } end end - end + defp attach_telemetry do + :telemetry.attach_many( + "test-handler", + [ + [:fun_with_flags, :flag_operation] + ], + fn event_name, measurements, metadata, reply_to -> + send(reply_to, %{event: event_name, measurements: measurements, metadata: metadata}) + end, + self() + ) + + on_exit(fn -> + :telemetry.detach("test-handler") + end) + end + end end