diff --git a/lib/benchee/formatters/tagged_save.ex b/lib/benchee/formatters/tagged_save.ex index a65c92a3..2c5fe0ea 100644 --- a/lib/benchee/formatters/tagged_save.ex +++ b/lib/benchee/formatters/tagged_save.ex @@ -14,26 +14,56 @@ defmodule Benchee.Formatters.TaggedSave do alias Benchee.Benchmark.Scenario alias Benchee.Utility.FileCreation - @spec format(Suite.t) :: {binary, String.t} + @spec format(Suite.t()) :: {binary, String.t()} def format(suite = %Suite{configuration: config, scenarios: scenarios}) do formatter_config = config.formatter_options.tagged_save - tagged_scenarios = tag_scenarios(scenarios, formatter_config) + tag = determine_tag(scenarios, formatter_config) + tagged_scenarios = tag_scenarios(scenarios, tag) tagged_suite = %Suite{suite | scenarios: tagged_scenarios} {:erlang.term_to_binary(tagged_suite), formatter_config.path} end - defp tag_scenarios(scenarios, %{tag: tag}) do - Enum.map scenarios, fn(scenario) -> + defp determine_tag(scenarios, %{tag: desired_tag}) do + scenarios + |> Enum.map(fn scenario -> scenario.tag end) + |> Enum.uniq() + |> Enum.filter(fn tag -> + tag != nil && tag =~ ~r/#{Regex.escape(desired_tag)}/ + end) + |> choose_tag(desired_tag) + end + + defp choose_tag([], desired_tag), do: desired_tag + + defp choose_tag(tags, desired_tag) do + if Enum.all?(tags, fn tag -> tag == desired_tag end) do + "#{desired_tag}-2" + else + max = get_maximum_tag_increaser(tags, desired_tag) + "#{desired_tag}-#{max + 1}" + end + end + + defp get_maximum_tag_increaser(tags, desired_tag) do + tags + |> Enum.map(fn tag -> String.replace(tag, desired_tag <> "-", "") end) + |> Enum.map(&String.to_integer/1) + |> Enum.max() + end + + defp tag_scenarios(scenarios, tag) do + Enum.map(scenarios, fn scenario -> scenario |> tagged_scenario(tag) |> update_name - end + end) end defp tagged_scenario(scenario = %Scenario{tag: nil}, desired_tag) do %Scenario{scenario | tag: desired_tag} end + defp tagged_scenario(scenario, _desired_tag) do scenario end @@ -42,12 +72,12 @@ defmodule Benchee.Formatters.TaggedSave do %Scenario{scenario | name: Scenario.display_name(scenario)} end - @spec write({binary, String.t}) :: :ok + @spec write({binary, String.t()}) :: :ok def write({term_binary, filename}) do FileCreation.ensure_directory_exists(filename) return_value = File.write(filename, term_binary) - IO.puts "Suite saved in external term format at #{filename}" + IO.puts("Suite saved in external term format at #{filename}") return_value end diff --git a/test/benchee/formatters/tagged_save_test.exs b/test/benchee/formatters/tagged_save_test.exs index c243cfb1..de3d0dbc 100644 --- a/test/benchee/formatters/tagged_save_test.exs +++ b/test/benchee/formatters/tagged_save_test.exs @@ -31,12 +31,12 @@ defmodule Benchee.Formatters.TaggedSaveTest do input_name: no_input(), input: no_input(), run_time_statistics: %Statistics{ - average: 100.0, - ips: 10_000.0, - std_dev_ratio: 0.1, - median: 90.0, - percentiles: %{99 => 300.1} - } + average: 100.0, + ips: 10_000.0, + std_dev_ratio: 0.1, + median: 90.0, + percentiles: %{99 => 300.1} + } } ], configuration: %Benchee.Configuration{ @@ -53,9 +53,10 @@ defmodule Benchee.Formatters.TaggedSaveTest do test "able to restore the original just fine" do {binary, path} = format(@suite) - loaded_suite = binary - |> :erlang.binary_to_term - |> suite_without_scenario_tags + loaded_suite = + binary + |> :erlang.binary_to_term() + |> suite_without_scenario_tags assert loaded_suite == @suite assert path == @filename @@ -66,38 +67,85 @@ defmodule Benchee.Formatters.TaggedSaveTest do loaded_suite = :erlang.binary_to_term(binary) - Enum.each loaded_suite.scenarios, fn(scenario) -> + Enum.each(loaded_suite.scenarios, fn scenario -> assert scenario.tag == @benchee_tag assert scenario.name =~ ~r/#{@benchee_tag}/ - end + end) end - test "doesn't tag scenarios that already had a tag" do + test "doesn't tag scenarios that already have a tag" do tagged_scenario = %Scenario{tag: "some-tag"} suite = %Suite{@suite | scenarios: [tagged_scenario | @suite.scenarios]} + tags = + suite + |> scenarios_from_formatted + |> sorted_tags + + assert tags == [@benchee_tag, "some-tag"] + end + + test "when duplicating tags for the same job the second gets -2" do + tagged_scenario = %Scenario{job_name: "foo", tag: @benchee_tag} + scenario = %Scenario{job_name: "foo"} + suite = %Suite{@suite | scenarios: [scenario, tagged_scenario]} + + scenarios = scenarios_from_formatted(suite) + tags = sorted_tags(scenarios) + names = sorted_names(scenarios) + + assert tags == [@benchee_tag, @benchee_tag <> "-2"] + assert names == ["foo (#{@benchee_tag})", "foo (#{@benchee_tag}-2)"] + end + + test "when there's already a -2 and -3 tag we end up with -4" do + scenario_1 = %Scenario{job_name: "foo", tag: "#{@benchee_tag}-2"} + scenario_2 = %Scenario{job_name: "foo", tag: "#{@benchee_tag}-3"} + new_scenario = %Scenario{job_name: "foo"} + + suite = %Suite{@suite | scenarios: [scenario_1, new_scenario, scenario_2]} + + scenarios = scenarios_from_formatted(suite) + tags = sorted_tags(scenarios) + names = sorted_names(scenarios) + + assert tags == [@benchee_tag <> "-2", @benchee_tag <> "-3", @benchee_tag <> "-4"] + + assert names == + ["foo (#{@benchee_tag}-2)", "foo (#{@benchee_tag}-3)", "foo (#{@benchee_tag}-4)"] + end + + defp scenarios_from_formatted(suite) do {binary, _path} = format(suite) loaded_suite = :erlang.binary_to_term(binary) - loaded_scenarios = loaded_suite.scenarios + loaded_suite.scenarios + end - tags = loaded_scenarios - |> Enum.map(fn(scenario) -> scenario.tag end) - |> Enum.uniq - |> Enum.sort + defp sorted_tags(scenarios) do + scenarios + |> Enum.map(fn scenario -> scenario.tag end) + |> Enum.uniq() + |> Enum.sort() + end - assert tags == [@benchee_tag, "some-tag"] + defp sorted_names(scenarios) do + scenarios + |> Enum.map(fn scenario -> scenario.name end) + |> Enum.uniq() + |> Enum.sort() end end describe ".output/1" do test "able to restore fully from file" do - capture_io fn -> output(@suite) end + capture_io(fn -> output(@suite) end) etf_data = File.read!(@filename) - loaded_suite = etf_data - |> :erlang.binary_to_term - |> suite_without_scenario_tags + loaded_suite = + etf_data + |> :erlang.binary_to_term() + |> suite_without_scenario_tags assert loaded_suite == @suite after