Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions lib/ex_unit/lib/ex_unit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,18 @@ defmodule ExUnit do
* `:time` - the time to run the test
* `:tags` - the test tags
* `:logs` - the captured logs
* `:type` - the test type

"""
defstruct [:name, :case, :state, time: 0, tags: %{}, logs: ""]
defstruct [:name, :case, :state, time: 0, tags: %{}, logs: "", type: :test]

@type t :: %__MODULE__{
name: atom,
case: module,
state: ExUnit.state,
time: non_neg_integer,
tags: map}
tags: map,
type: atom}
end

defmodule TestCase do
Expand Down Expand Up @@ -211,6 +213,9 @@ defmodule ExUnit do
on formatting and reporters (defaults to 20)

* `:timeout` - set the timeout for the tests (default 60_000ms)

* `:plural_rules` - See `ExUnit.plural_rule/1` and `ExUnit.plural_rule/2`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this from here since you are not supposed to set it directly anyway.

You should not set this option directly.
"""
def configure(options) do
Enum.each options, fn {k, v} ->
Expand All @@ -225,6 +230,42 @@ defmodule ExUnit do
Application.get_all_env(:ex_unit)
end

@doc """
Returns the pluralization for `word`.

If one is not registered, returns `"\#{word}s"`.
"""
@spec plural_rule(binary) :: binary
def plural_rule(word) when not is_binary(word),
do: raise_plural_argument_error("word")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have a single clause that checks for binaries only.

def plural_rule(word) do
configuration()
|> Keyword.get(:plural_rules, %{})
|> Map.get(word, "#{word}s")
end

@doc """
Registers a `pluralization` for `word`.

If one is already registered, it is replaced.
"""
@spec plural_rule(binary, binary) :: :ok
def plural_rule(word, _pluralization) when not is_binary(word),
do: raise_plural_argument_error("word")
def plural_rule(_word, pluralization) when not is_binary(pluralization),
do: raise_plural_argument_error("pluralization")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just use regular guards. No need for the "when not".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, let's have a single clause that checks they are binary and that's it.

def plural_rule(word, pluralization) do
plural_rules =
configuration()
|> Keyword.get(:plural_rules, %{})
|> Map.put(word, pluralization)

configure(plural_rules: plural_rules)
end

defp raise_plural_argument_error(argument_name),
do: raise ArgumentError, message: "`#{argument_name}` must be a binary"

@doc """
API used to run the tests. It is invoked automatically
if ExUnit is started via `ExUnit.start/1`.
Expand Down
15 changes: 15 additions & 0 deletions lib/ex_unit/lib/ex_unit/case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ defmodule ExUnit.Case do
* `:timeout` - customizes the test timeout in milliseconds (defaults to 60000)
* `:report` - include the given tags and context keys on error reports,
see the "Reporting tags" section
* `:type` - customizes the test's type in reports (defaults to `:test`). The
test type will be converted to a string and pluralized for display. You
can use `ExUnit.plural_rule/2` to set a custom pluralization.

### Reporting tags

Expand Down Expand Up @@ -324,6 +327,14 @@ defmodule ExUnit.Case do
|> Map.merge(%{line: line, file: file, registered: registered})

test = %ExUnit.Test{name: name, case: mod, tags: tags}

test =
if tags[:type] do
%{test | type: tags[:type]}
else
test
end

Module.put_attribute(mod, :ex_unit_tests, test)

Enum.each [:tag | registered_attributes], fn(attribute) ->
Expand Down Expand Up @@ -363,6 +374,10 @@ defmodule ExUnit.Case do
Map.has_key?(tags, tag) do
raise "cannot set tag #{inspect tag} because it is reserved by ExUnit"
end

unless is_atom(tags[:type]),
do: raise "value for tag `:type` must be an atom"

tags
end

Expand Down
27 changes: 20 additions & 7 deletions lib/ex_unit/lib/ex_unit/cli_formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule ExUnit.CLIFormatter do
trace: opts[:trace],
colors: Keyword.put_new(opts[:colors], :enabled, IO.ANSI.enabled?),
width: get_terminal_width(),
tests_counter: 0,
tests_counter: %{},
failures_counter: 0,
skipped_counter: 0,
invalids_counter: 0
Expand Down Expand Up @@ -43,12 +43,12 @@ defmodule ExUnit.CLIFormatter do
else
IO.write success(".", config)
end
{:ok, %{config | tests_counter: config.tests_counter + 1}}
{:ok, %{config | tests_counter: update_tests_counter(config.tests_counter, test)}}
end

def handle_event({:test_finished, %ExUnit.Test{state: {:skip, _}} = test}, config) do
if config.trace, do: IO.puts trace_test_skip(test)
{:ok, %{config | tests_counter: config.tests_counter + 1,
{:ok, %{config | tests_counter: update_tests_counter(config.tests_counter, test),
skipped_counter: config.skipped_counter + 1}}
end

Expand All @@ -59,7 +59,7 @@ defmodule ExUnit.CLIFormatter do
IO.write invalid("?", config)
end

{:ok, %{config | tests_counter: config.tests_counter + 1,
{:ok, %{config | tests_counter: update_tests_counter(config.tests_counter, test),
invalids_counter: config.invalids_counter + 1}}
end

Expand All @@ -73,7 +73,7 @@ defmodule ExUnit.CLIFormatter do
print_failure(formatted, config)
print_logs(test.logs)

{:ok, %{config | tests_counter: config.tests_counter + 1,
{:ok, %{config | tests_counter: update_tests_counter(config.tests_counter, test),
failures_counter: config.failures_counter + 1}}
end

Expand Down Expand Up @@ -120,18 +120,23 @@ defmodule ExUnit.CLIFormatter do
end
end

defp update_tests_counter(tests_counter, %{type: type} = _test) do
Map.update(tests_counter, type, 1, &(&1 + 1))
end

## Printing

defp print_suite(config, run_us, load_us) do
IO.write "\n\n"
IO.puts format_time(run_us, load_us)

# singular/plural
test_pl = pluralize(config.tests_counter, "test", "tests")
failure_pl = pluralize(config.failures_counter, "failure", "failures")

test_type_counts = format_test_type_counts(config)

message =
"#{config.tests_counter} #{test_pl}, #{config.failures_counter} #{failure_pl}"
"#{test_type_counts} #{config.failures_counter} #{failure_pl}"
|> if_true(config.skipped_counter > 0, & &1 <> ", #{config.skipped_counter} skipped")
|> if_true(config.invalids_counter > 0, & &1 <> ", #{config.invalids_counter} invalid")

Expand Down Expand Up @@ -166,6 +171,14 @@ defmodule ExUnit.CLIFormatter do
IO.puts formatted
end

defp format_test_type_counts(%{tests_counter: tests_counter} = _config) do
Enum.map_join tests_counter, " ", fn {type, count} ->
type_pluralized = pluralize(count, type, ExUnit.plural_rule(type |> to_string()))

"#{count} #{type_pluralized},"
end
end

# Color styles

defp colorize(escape, string, %{colors: colors}) do
Expand Down
110 changes: 110 additions & 0 deletions lib/ex_unit/test/ex_unit_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,116 @@ defmodule ExUnitTest do
end)
end

test "plural rules" do
on_exit fn ->
ExUnit.configure(plural_rules: %{})
end

word = "property"
pluralization = "properties"

assert ExUnit.plural_rule(word) == word <> "s"

assert ExUnit.plural_rule(word, pluralization) == :ok

assert ExUnit.plural_rule(word) == pluralization

invalid_word =
fn ->
ExUnit.plural_rule(:atom)
end

assert_raise ArgumentError, "`word` must be a binary", invalid_word

invalid_word =
fn ->
ExUnit.plural_rule(:atom, "atoms")
end

assert_raise ArgumentError, "`word` must be a binary", invalid_word

both_invalid =
fn ->
ExUnit.plural_rule(:atom, :atoms)
end

assert_raise ArgumentError, "`word` must be a binary", both_invalid

invalid_pluralization =
fn ->
ExUnit.plural_rule("atom", :atoms)
end

assert_raise ArgumentError, "`pluralization` must be a binary", invalid_pluralization
end

test "singular test types" do
on_exit fn ->
ExUnit.configure(plural_rules: %{})
end

ExUnit.plural_rule("property", "properties")

defmodule SingularTestTypeCase do
use ExUnit.Case

@tag type: :property
test "property is true" do
assert succeed()
end

test "test true" do
assert succeed()
end

defp succeed, do: true
end

ExUnit.Server.cases_loaded()

assert capture_io(fn ->
assert ExUnit.run == %{failures: 0, skipped: 0, total: 2}
end) =~ "1 property, 1 test, 0 failures"
end

test "plural test types" do
on_exit fn ->
ExUnit.configure(plural_rules: %{})
end

ExUnit.plural_rule("property", "properties")

defmodule PluralTestTypeCase do
use ExUnit.Case

@tag type: :property
test "property is true" do
assert succeed()
end

@tag type: :property
test "property is also true" do
assert succeed()
end

test "test true" do
assert succeed()
end

test "test true also" do
assert succeed()
end

defp succeed, do: true
end

ExUnit.Server.cases_loaded()

assert capture_io(fn ->
assert ExUnit.run == %{failures: 0, skipped: 0, total: 4}
end) =~ "2 properties, 2 tests, 0 failures"
end

defp run_with_filter(filters, cases) do
Enum.each(cases, &ExUnit.Server.add_sync_case/1)
ExUnit.Server.cases_loaded()
Expand Down
2 changes: 1 addition & 1 deletion lib/iex/test/iex/helpers_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ defmodule IEx.HelpersTest do
end

test "s helper" do
assert capture_io(fn -> s ExUnit end) == "No specification for ExUnit was found\n"
assert capture_io(fn -> s IEx.Remsh end) == "No specification for IEx.Remsh was found\n"

# Test that it shows at least two specs
assert Enum.count(capture_io(fn -> s Enum end) |> String.split("\n"), fn line ->
Expand Down