Skip to content

Commit 8b4e8e8

Browse files
committed
Split Benchee integration tests
1 parent e453aaf commit 8b4e8e8

10 files changed

Lines changed: 662 additions & 588 deletions

test/benchee/escript_test.exs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
defmodule Benchee.EscriptTest do
2+
use ExUnit.Case, async: false
3+
4+
import Benchee.IntegrationHelpers
5+
6+
describe "escript building" do
7+
@working_directory File.cwd!()
8+
@sample_project_directory Path.expand("../fixtures/escript", __DIR__)
9+
test "benchee can be built into and used as an escript" do
10+
File.cd!(@sample_project_directory)
11+
# we don't match the exit_status right now to get better error messages potentially
12+
{output, exit_status} = System.cmd("bash", ["test.sh"])
13+
14+
readme_sample_asserts(output)
15+
assert exit_status == 0
16+
after
17+
File.cd!(@working_directory)
18+
end
19+
end
20+
end
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
defmodule Benchee.EvaluationWarningTest do
2+
use ExUnit.Case, async: true
3+
4+
describe "warn when functions are evaluated" do
5+
test "warns when run in iex" do
6+
# test env to avoid repeated compilation on CI
7+
port = Port.open({:spawn, "iex -S mix"}, [:binary, env: [{~c"MIX_ENV", ~c"test"}]])
8+
9+
try do
10+
# wait for startup
11+
# timeout huge because of CI
12+
assert_receive {^port, {:data, "iex(1)> "}}, 20_000
13+
14+
send(
15+
port,
16+
{self(),
17+
{:command, "Benchee.run(%{\"test\" => fn -> 1 end}, time: 0.001, warmup: 0)\n"}}
18+
)
19+
20+
assert_receive {^port, {:data, "Warning: " <> message}}, 20_000
21+
assert message =~ ~r/test.+evaluated.+slower.+compiled.+module.+/is
22+
23+
# waiting for iex to be ready for input again/the benchmark to be finished
24+
# sometimes we get "iex(2)>" as a separate message, sometimes it's attached to the
25+
# previous output - hence we gotta doe out own `receive` checking.
26+
assert :ok = wait_for_benchmark_finished(port)
27+
after
28+
# https://elixirforum.com/t/starting-shutting-down-iex-with-a-port-gracefully/60388/2?u=pragtob
29+
send(port, {self(), {:command, "\a"}})
30+
send(port, {self(), {:command, "q\n"}})
31+
end
32+
end
33+
34+
defp wait_for_benchmark_finished(port) do
35+
receive do
36+
{^port, {:data, output}} ->
37+
if String.contains?(output, "iex(2)>") do
38+
:ok
39+
else
40+
wait_for_benchmark_finished(port)
41+
end
42+
after
43+
20_000 -> raise RuntimeError, "Waited too long for iex benchmark to finish/send iex(2)>"
44+
end
45+
end
46+
end
47+
end
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
defmodule Benchee.FunctionOverheadAndOutliersTest do
2+
use ExUnit.Case, async: false
3+
4+
import ExUnit.CaptureIO
5+
import Benchee.IntegrationHelpers
6+
import Benchee.TestHelpers
7+
@test_configuration Benchee.IntegrationHelpers.test_configuration()
8+
9+
describe "function call overhead measurement" do
10+
@overhead_output_regex ~r/function call overhead.*\d+/i
11+
test "by default it is off" do
12+
output =
13+
capture_io(fn ->
14+
Benchee.run(
15+
%{"sleeps" => fn -> sleep_safe_time() end},
16+
@test_configuration
17+
)
18+
end)
19+
20+
refute output =~ @overhead_output_regex
21+
end
22+
23+
test "can be turned on" do
24+
output =
25+
capture_io(fn ->
26+
Benchee.run(
27+
%{"sleeps" => fn -> sleep_safe_time() end},
28+
Keyword.merge(@test_configuration, measure_function_call_overhead: true)
29+
)
30+
end)
31+
32+
assert output =~ @overhead_output_regex
33+
end
34+
end
35+
36+
describe "exclude_outliers" do
37+
test "even with it the high level README example still passes its asserts" do
38+
output =
39+
capture_io(fn ->
40+
list = Enum.to_list(1..10_000)
41+
map_fun = fn i -> [i, i * i] end
42+
43+
Benchee.run(
44+
%{
45+
"flat_map" => fn -> Enum.flat_map(list, map_fun) end,
46+
"map.flatten" => fn -> list |> Enum.map(map_fun) |> List.flatten() end
47+
},
48+
Keyword.merge(@test_configuration, exclude_outliers: true)
49+
)
50+
end)
51+
52+
readme_sample_asserts(output)
53+
end
54+
55+
# The easiest way to create an outlier is to just run something once
56+
# and then take a "stable" measurement like reductions or memory
57+
test "removes outliers" do
58+
{:ok, agent} = Agent.start(fn -> 0 end)
59+
60+
output =
61+
capture_io(fn ->
62+
suite =
63+
Benchee.run(
64+
%{
65+
"flawed" => fn ->
66+
# Produce some garbage but only once
67+
# can't use process dictionary as it's a different process every time
68+
if Agent.get(agent, & &1) < 1 do
69+
Enum.reduce(1..10_000, 0, &+/2)
70+
Agent.update(agent, &(&1 + 1))
71+
end
72+
end
73+
},
74+
time: 0,
75+
warmup: 0,
76+
reduction_time: 0.05,
77+
exclude_outliers: true
78+
)
79+
80+
%{scenarios: [%{reductions_data: %{samples: samples, statistics: stats}}]} = suite
81+
82+
assert [outlier] = stats.outliers
83+
assert outlier >= stats.upper_outlier_bound
84+
# since the outlier is removed, all values are the same
85+
assert stats.std_dev == 0
86+
assert stats.minimum == stats.maximum
87+
88+
# It's a big outlier!
89+
assert 10 * stats.average < outlier
90+
91+
# The outlier is only removed from the stats, but not from the samples
92+
assert outlier in samples
93+
end)
94+
95+
# As the outlier is removed, all measurements are the same
96+
assert output =~ ~r/all.*reduction.*same/i
97+
assert output =~ ~r/exclud.*outlier.*true/i
98+
end
99+
end
100+
end

test/benchee/hooks_test.exs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
defmodule Benchee.HooksTest do
2+
use ExUnit.Case, async: true
3+
4+
import ExUnit.CaptureIO
5+
import Benchee.TestHelpers
6+
@test_configuration Benchee.IntegrationHelpers.test_configuration()
7+
8+
describe "hooks" do
9+
test "it runs all of them" do
10+
capture_io(fn ->
11+
myself = self()
12+
13+
Benchee.run(
14+
%{
15+
"sleeper" =>
16+
{fn -> sleep_safe_time() end,
17+
before_each: fn input ->
18+
send(myself, :local_before)
19+
input
20+
end,
21+
after_each: fn _ -> send(myself, :local_after) end,
22+
before_scenario: fn input ->
23+
send(myself, :local_before_scenario)
24+
input
25+
end,
26+
after_scenario: fn _ -> send(myself, :local_after_scenario) end},
27+
"sleeper 2" => fn -> sleep_safe_time() end
28+
},
29+
Keyword.merge(
30+
@test_configuration,
31+
time: 0.0001,
32+
warmup: 0,
33+
before_each: fn input ->
34+
send(myself, :global_before)
35+
input
36+
end,
37+
after_each: fn _ -> send(myself, :global_after) end,
38+
before_scenario: fn input ->
39+
send(myself, :global_before_scenario)
40+
input
41+
end,
42+
after_scenario: fn _ -> send(myself, :global_after_scenario) end
43+
)
44+
)
45+
end)
46+
47+
assert_received_exactly([
48+
# first job with all those local hooks
49+
:global_before_scenario,
50+
:local_before_scenario,
51+
:global_before,
52+
:local_before,
53+
:local_after,
54+
:global_after,
55+
:local_after_scenario,
56+
:global_after_scenario,
57+
# second job that only runs global hooks
58+
:global_before_scenario,
59+
:global_before,
60+
:global_after,
61+
:global_after_scenario
62+
])
63+
end
64+
end
65+
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
defmodule Benchee.MaxSampleSizeTest do
2+
use ExUnit.Case, async: true
3+
4+
import ExUnit.CaptureIO
5+
import Benchee.Configuration
6+
7+
alias Benchee.{Configuration, Suite}
8+
9+
@default_max_sample_size 1_000_000
10+
11+
test "max_sample_size defaults to 1 million samples" do
12+
assert %Suite{configuration: %Configuration{max_sample_size: 1_000_000}} = init()
13+
end
14+
15+
# Normally we do not test defaults this way, but this covers the full process
16+
# using the default `max_sample_size` with formatters enabled.
17+
@tag :performance
18+
test "max_sample_size by default is set to 1 Million" do
19+
capture_io(fn ->
20+
suite =
21+
Benchee.run(
22+
%{
23+
"fast" => fn -> :fast end
24+
},
25+
time: 1,
26+
warmup: 0
27+
)
28+
29+
Enum.each(suite.scenarios, fn scenario ->
30+
assert scenario.run_time_data.statistics.sample_size <= @default_max_sample_size
31+
end)
32+
end)
33+
end
34+
35+
test "max_sample_size can be disabled" do
36+
assert %Suite{configuration: %Configuration{max_sample_size: nil}} =
37+
init(max_sample_size: nil)
38+
end
39+
end
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
defmodule Benchee.MeasurementIntegrationTest do
2+
use ExUnit.Case, async: true
3+
4+
import ExUnit.CaptureIO
5+
6+
@test_configuration Benchee.IntegrationHelpers.test_configuration()
7+
8+
describe "memory measurement" do
9+
@describetag :memory_measure
10+
11+
test "measures memory usage when instructed to do so" do
12+
output =
13+
capture_io(fn ->
14+
Benchee.run(
15+
%{"To List" => fn -> Enum.to_list(1..100) end},
16+
Keyword.merge(
17+
@test_configuration,
18+
memory_time: 0.001
19+
)
20+
)
21+
end)
22+
23+
assert output =~ ~r/Memory usage statistics:/
24+
assert output =~ ~r/To List\s+[0-9.]{3,} K*B{1}/
25+
end
26+
27+
test "does not blow up when only measuring memory times" do
28+
output =
29+
capture_io(fn ->
30+
Benchee.run(
31+
%{
32+
"something" => fn -> Enum.map(1..100, fn i -> i + 1 end) end
33+
},
34+
Keyword.merge(
35+
@test_configuration,
36+
time: 0,
37+
warmup: 0,
38+
memory_time: 0.001
39+
)
40+
)
41+
end)
42+
43+
# no runtime statistics displayed
44+
refute output =~ ~r/ips/i
45+
assert output =~ ~r/memory.+statistics/i
46+
end
47+
48+
test "the micro keyword list code from Michal does not break memory measurements #213" do
49+
benches = %{
50+
"delete old" => fn {kv, key} -> BenchKeyword.delete_v0(kv, key) end,
51+
"delete reverse" => fn {kv, key} -> BenchKeyword.delete_v2(kv, key) end,
52+
"delete keymember reverse" => fn {kv, key} -> BenchKeyword.delete_v3(kv, key) end,
53+
"delete throw" => fn {kv, key} -> BenchKeyword.delete_v1(kv, key) end
54+
}
55+
56+
inputs = %{
57+
"large miss" => {Enum.map(1..100, &{:"k#{&1}", &1}), :k101},
58+
"large hit" => {Enum.map(1..100, &{:"k#{&1}", &1}), :k100},
59+
"small miss" => {Enum.map(1..10, &{:"k#{&1}", &1}), :k11}
60+
}
61+
62+
output =
63+
capture_io(fn ->
64+
Benchee.run(
65+
benches,
66+
Keyword.merge(
67+
@test_configuration,
68+
inputs: inputs,
69+
print: [fast_warning: false],
70+
memory_time: 0.001,
71+
warmup: 0,
72+
time: 0
73+
)
74+
)
75+
end)
76+
77+
refute output =~ "N/A"
78+
refute output =~ ~r/warning/i
79+
assert output =~ "large hit"
80+
# Byte
81+
assert output =~ "B"
82+
83+
assert output =~ ~r/1\.0\dx memory/
84+
85+
assert output =~ "∞ x memo"
86+
end
87+
end
88+
89+
describe "reduction measurement" do
90+
test "measures reduction count when instructed to do so" do
91+
output =
92+
capture_io(fn ->
93+
Benchee.run(
94+
%{"To List" => fn -> Enum.to_list(1..100) end},
95+
Keyword.merge(
96+
@test_configuration,
97+
reduction_time: 0.1
98+
)
99+
)
100+
end)
101+
102+
assert output =~ ~r/Reduction count statistics:/
103+
end
104+
end
105+
end

0 commit comments

Comments
 (0)