Skip to content

Commit

Permalink
#57 Randomized with seed.
Browse files Browse the repository at this point in the history
  • Loading branch information
Anton Mishchuk committed Nov 5, 2015
1 parent 4f029df commit b05539f
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 34 deletions.
1 change: 1 addition & 0 deletions lib/espec/configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule ESpec.Configuration do
only: "Run only tests that match the filter",
exclude: "Exclude tests that match the filter",
string: "Run only examples whose full nested descriptions contain string",
seed: "Seeds the random number generator used to randomize tests order",
test: "For test purpose",
start_loading_time: "Starts loading files",
finish_loading_time: "Finished loading",
Expand Down
28 changes: 19 additions & 9 deletions lib/espec/output/doc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule ESpec.Output.Doc do
@status_colors [success: @green, failure: @red, pending: @yellow]
@status_symbols [success: ".", failure: "F", pending: "*"]

alias ESpec.Example
alias ESpec.Example

@doc "Formats the final result."
def format_result(examples, times, _opts) do
Expand All @@ -22,9 +22,10 @@ defmodule ESpec.Output.Doc do
failed = Example.failure(examples)
if Enum.any?(failed), do: string = string <> format_failed(failed)
string = string <> format_footer(examples, failed, pending)
string <> format_times(times, failed, pending)
string = string <> format_times(times, failed, pending)
string <> format_seed
end

@doc "Formats an example result."
def format_example(example, opts) do
color = color_for_status(example.status)
Expand All @@ -38,26 +39,26 @@ defmodule ESpec.Output.Doc do

defp format_failed(failed) do
res = failed |> Enum.with_index
|> Enum.map fn({example, index}) ->
|> Enum.map fn({example, index}) ->
do_format_example(example, example.error.message, index)
end
Enum.join(res, "\n")
end

defp format_pending(pending) do
res = pending |> Enum.with_index
|> Enum.map fn({example, index}) ->
|> Enum.map fn({example, index}) ->
do_format_example(example, example.result, index)
end
Enum.join(res, "\n")
end

defp do_format_example(example, info, index) do
color = color_for_status(example.status)
decription = one_line_description(example)
description = one_line_description(example)
[
"\n",
"\t#{index + 1}) #{decription}",
"\t#{index + 1}) #{description}",
"\t#{@cyan}#{example.file}:#{example.line}#{@reset}",
"\t#{color}#{info}#{@reset}",
]
Expand All @@ -79,6 +80,15 @@ defmodule ESpec.Output.Doc do
<> " (#{us_to_sec(load_time)}s on load, #{us_to_sec(spec_time)}s on specs)#{@reset}\n\n"
end

defp format_seed do
if ESpec.Configuration.get(:order) do
""
else
seed = ESpec.Configuration.get(:seed)
"\tRandomized with seed #{seed}\n\n"
end
end

defp us_to_sec(us), do: div(us, 10000) / 100

defp get_color(failed, pending) do
Expand All @@ -97,7 +107,7 @@ defmodule ESpec.Output.Doc do

defp trace_description(example) do
color = color_for_status(example.status)
ex_desc = if String.length(example.description) > 0 do
ex_desc = if String.length(example.description) > 0 do
"#{color}#{example.description}#{@reset}"
else
if example.status == :failure do
Expand All @@ -110,7 +120,7 @@ defmodule ESpec.Output.Doc do
{result, _} = Enum.reduce(array, {"", ""}, fn(description, acc) ->
{d, w} = acc
{d <> w <> "#{description}" <> "\n", w <> " "}
end)
end)
result
end

Expand Down
28 changes: 19 additions & 9 deletions lib/espec/output/html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ defmodule ESpec.Output.Html do
string = EEx.eval_file(template_path, [examples: html, summary: summary])
String.replace(string, "\n", "")
end

@doc "Formats an example result."
def format_example(_example, _opts), do: ""

defp template_path, do: Path.join(Path.dirname(__ENV__.file), "templates/html.html.eex")

defp context_tree(examples) do
examples
|> Enum.reduce({HashDict.new, []}, fn(ex, acc) ->
Expand All @@ -29,7 +29,7 @@ defmodule ESpec.Output.Html do
d = case HashDict.get(dict, el) do
{inner, vals} -> put_deep({inner, vals}, tl, value)
nil -> put_deep({HashDict.new, []}, tl, value)
end
end
{HashDict.put(dict, el, d), values}
end

Expand All @@ -39,14 +39,14 @@ defmodule ESpec.Output.Html do
nil -> HashDict.put(dict, el, {HashDict.new, [value]})
end
{new_dict, values}
end
end

defp put_deep({dict, values}, [], value) do
{dict, [value | values]}
end

defp make_html({dict, values}, top? \\ false, firstli? \\ false) do
lis = Enum.reduce(values, "", fn(ex, acc) ->
lis = Enum.reduce(values, "", fn(ex, acc) ->
acc <> "<li class='#{li_class(ex)}'>#{ex_desc(ex)}</li>"
end)
if String.length(lis) > 0 do
Expand All @@ -56,7 +56,7 @@ defmodule ESpec.Output.Html do
if top? do
acc <> "<section class='context'><h3>#{key.description}</h3>" <> make_html(d, false, true) <> "</section>"
else
mainli = "<li><h4>#{key.description}</h4>"
mainli = "<li><h4>#{key.description}</h4>"
if firstli? do
acc <> "<ul class='tree'>#{mainli}" <> make_html(d) <> "</li></ul>"
else
Expand All @@ -68,12 +68,12 @@ defmodule ESpec.Output.Html do
end

defp ex_desc(ex) do
res = if String.length(ex.description) > 0 do
res = if String.length(ex.description) > 0 do
ex.description
else
if ex.status == :failure, do: ex.error.message, else: ex.result
end
res = "#{res} (#{ex.duration} ms)"
res = "#{inspect res} (#{ex.duration} ms)"
String.replace(res, "\"", "'")
end

Expand All @@ -84,11 +84,21 @@ defmodule ESpec.Output.Html do
failed = Example.failure(examples)
load_time = :timer.now_diff(finish_loading_time, start_loading_time)
spec_time = :timer.now_diff(finish_specs_time, finish_loading_time)
seed = get_seed
{
Enum.count(examples), Enum.count(failed), Enum.count(pending),
us_to_sec(load_time + spec_time), us_to_sec(load_time), us_to_sec(spec_time)
us_to_sec(load_time + spec_time), us_to_sec(load_time), us_to_sec(spec_time),
seed
}
end

defp us_to_sec(us), do: div(us, 10000) / 100

defp get_seed do
if ESpec.Configuration.get(:order) do
false
else
seed = ESpec.Configuration.get(:seed)
end
end
end
22 changes: 16 additions & 6 deletions lib/espec/output/json.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,38 @@ defmodule ESpec.Output.Json do
def format_result(examples, times, _opts) do
pending = Example.pendings(examples)
failed = Example.failure(examples)
list = [
list = [
format_pending(pending), format_failed(failed), format_success(Example.success(examples))
] |> List.flatten
summary = format_summary(examples, pending, failed, times)
string = EEx.eval_file(template_path, [examples: list, summary: summary])
String.replace(string, "\n", "")
end

@doc "Formats an example result."
def format_example(_example, _opts), do: ""

defp template_path, do: Path.join(Path.dirname(__ENV__.file), "templates/json.json.eex")

defp format_failed(examples), do: Enum.map(examples, &(do_format_example(&1, &1.error.message)))

defp format_pending(examples), do: Enum.map(examples, &(do_format_example(&1, &1.result)))

def format_success(examples), do: Enum.map(examples, &(do_format_example(&1, &1.result)))

defp do_format_example(example, info) do
decription = one_line_description(example)
{decription, "#{example.file}:#{example.line}", example.status, String.replace("#{info}", "\"", "'"), example.duration}
description = one_line_description(example)
{description, "#{example.file}:#{example.line}", example.status, String.replace("#{inspect info}", "\"", "'"), example.duration}
end

def format_summary(examples, pending, failed, {start_loading_time, finish_loading_time, finish_specs_time}) do
load_time = :timer.now_diff(finish_loading_time, start_loading_time)
spec_time = :timer.now_diff(finish_specs_time, finish_loading_time)
seed = get_seed
{
Enum.count(examples), Enum.count(failed), Enum.count(pending),
us_to_sec(load_time + spec_time), us_to_sec(load_time), us_to_sec(spec_time)
us_to_sec(load_time + spec_time), us_to_sec(load_time), us_to_sec(spec_time),
seed
}
end

Expand All @@ -47,4 +49,12 @@ defmodule ESpec.Output.Json do
[ module | ESpec.Example.context_descriptions(example)] ++ [example.description]
|> Enum.join(" ") |> String.rstrip
end

defp get_seed do
if ESpec.Configuration.get(:order) do
false
else
seed = ESpec.Configuration.get(:seed)
end
end
end
7 changes: 5 additions & 2 deletions lib/espec/output/templates/html.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
<header>
<h2>ESpec results</h2>
<aside>
<% {example_count, failure_count, pending_count, duration, load_time, spec_time} = summary %>
<% {example_count, failure_count, pending_count, duration, load_time, spec_time, seed} = summary %>
<p><%= example_count %> examples, <%= failure_count %> failures, <%= pending_count %> pending</p>
<p>Finished in <%= duration %> seconds (<%= load_time %>s on load, <%= spec_time %>s on specs)</p>
<%= if seed do %>
<p>Randomized with seed <%= seed %></p>
<% end %>
</aside>
<nav>
<label for="checkbox-success" class="checkbox">
Expand Down Expand Up @@ -51,4 +54,4 @@
function ready(a){"use strict";"loading"!==document.readyState?a():document.addEventListener("DOMContentLoaded",a)}ready(function(){"use strict";function a(){document.querySelector("main").style.paddingTop=1.5*document.querySelector("header").offsetHeight+"px"}function b(a,b){Array.prototype.forEach.call(a,b)}function c(a){a.style.opacity=0,a.style.display="",g();var b=+new Date,c=function(){a.style.opacity=+a.style.opacity+(new Date-b)/10,b=+new Date,+a.style.opacity<1&&(window.requestAnimationFrame&&requestAnimationFrame(c)||setTimeout(c,16))};c()}function d(a){a.style.opacity=1;var b=+new Date,c=function(){a.style.opacity=a.style.opacity-(new Date-b)/200,b=+new Date,+a.style.opacity>0?window.requestAnimationFrame&&requestAnimationFrame(c)||setTimeout(c,16):(a.style.display="none",g())};c()}function e(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function f(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function g(){var a,c,d=document.querySelectorAll("ul");b(d,function(d){c=d.children,a=null,b(c,function(b){f(b,"last-child"),"none"!==b.style.display&&(a=b)}),a&&e(a,"last-child")})}function h(a){if("checkbox"===a.target.type){var e=document.getElementById(a.target.id),f=document.querySelectorAll("."+e.getAttribute("data-selector"));b(f,function(b){a.target.checked?c(b):d(b)})}}document.addEventListener("click",h),window.addEventListener("resize",a),a(),g()});
</script>
</body>
</html>
</html>
11 changes: 7 additions & 4 deletions lib/espec/output/templates/json.json.eex
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
"examples":
[
<%= for example <- examples do %>
<% {decription, file_line, status, info, duration} = example %>
{"decription":"<%= decription %>","file_line":"<%= file_line %>","status":"<%= status %>","info":"<%= info %>","duration":<%= duration %>}
<% {description, file_line, status, info, duration} = example %>
{"description":"<%= description %>","file_line":"<%= file_line %>","status":"<%= status %>","info":"<%= info %>","duration":<%= duration %>}
<%= unless example == last_example, do: "," %>
<% end %>
],
<% {example_count, failure_count, pending_count, duration, load_time, spec_time} = summary %>
<% {example_count, failure_count, pending_count, duration, load_time, spec_time, seed} = summary %>
"summary":
{
"example_count":<%= example_count %>,
Expand All @@ -17,5 +17,8 @@
"duration":<%= duration %>,
"load_time":<%= load_time %>,
"spec_time":<%= spec_time %>
<%= if seed do %>
,"Randomized with seed":<%= seed %>
<% end %>
}
}
}
21 changes: 17 additions & 4 deletions lib/espec/runner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule ESpec.Runner do
Uses GenServer behavior.
"""
use GenServer
alias ESpec.Configuration

@doc "Starts the `ESpec.Runner` server"
def start do
Expand All @@ -12,7 +13,7 @@ defmodule ESpec.Runner do

@doc "Initiate the `ESpec.Runner` server with specs and options"
def init(_args) do
state = %{specs: ESpec.specs, opts: ESpec.Configuration.all}
state = %{specs: ESpec.specs, opts: Configuration.all}
{:ok, state}
end

Expand All @@ -34,20 +35,21 @@ defmodule ESpec.Runner do
end

defp do_run(specs, opts) do
if ESpec.Configuration.get(:order) do
if Configuration.get(:order) do
examples = run_in_order(specs, opts)
else
seed_random!
examples = run_in_random(specs, opts)
end
ESpec.Configuration.add([finish_specs_time: :os.timestamp])
Configuration.add([finish_specs_time: :os.timestamp])
ESpec.Output.print_result(examples)
!Enum.any?(ESpec.Example.failure(examples))
end

defp run_in_order(specs, opts) do
specs |> Enum.reverse
|> Enum.map(fn(module) ->
filter(module.examples, opts)
filter(module.examples |> Enum.reverse, opts)
|> run_examples
end)
|> List.flatten
Expand Down Expand Up @@ -177,4 +179,15 @@ defmodule ESpec.Runner do
[key_value, false]
end
end

defp seed_random! do
conf_seed = Configuration.get(:seed)
if conf_seed do
seed = String.to_integer(conf_seed)
else
seed = :os.timestamp |> elem(2)
Configuration.add(seed: seed)
end
:random.seed({3172, 9814, seed})
end
end
1 change: 1 addition & 0 deletions lib/mix/tasks/espec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ defmodule Mix.Tasks.Espec do
* `--only` - run only tests that match the filter `--only some:tag`
* `--exclude` - exclude tests that match the filter `--exclude some:tag`
* `--string` - run only examples whose full nested descriptions contain string `--string 'only this'`
* `--seed` - seeds the random number generator used to randomize tests order
## Configuration
* `:spec_paths` - list of paths containing spec files, defaults to `["spec"]`.
Expand Down

0 comments on commit b05539f

Please sign in to comment.