Compare commits

...
14 Commits
Author SHA1 Message Date
José Valim 9ad182986c Release v1.13.2 2022-01-13 10:51:17 +01:00
José Valim 16292c65ba Update CHANGELOG 2022-01-12 14:43:11 +01:00
José Valim d25ce48d5d Bring back filter/reject on Map and Keyword
They are already being used in projects, which
means it is too late for a deprecation. The docs
instead discuss when to use them.
2022-01-12 14:29:13 +01:00
Aaron Gunderson 66b4ab19ae Mix.Tasks.Test.Coverage warns on failure (#11457)
Adds an explicit warning when exiting with an error code because of a
failed test coverage threshold.

Example:
```
-----------|--------------------------
    62.35% | Total

Coverage test failed, threshold not met:
        Coverage:   62.35%
        Threshold: 100.00%
```
2022-01-02 19:58:06 +01:00
Aleksei Matiushkin 01a4889e39 Fix the nested uniq: acc name clash by preserving a scope (#11532) 2022-01-01 09:56:42 +01:00
José Valim d5e7446399 Add missing Logger CHANGELOG entry 2021-12-31 11:12:22 +01:00
Thiago Santos ab1da111bf Warn on zero arity callbacks inside protocols (#11519) 2021-12-22 22:01:33 +01:00
José Valim 6549b00eba Ensure context modules are handled in optimized defmodule 2021-12-22 12:22:22 +01:00
José Valim 4431ac63e2 Do not use type exclusive to Erlang/OTP 24+ 2021-12-22 10:18:02 +01:00
Thales Macedo Garitezi 280215c24f Allow bypassing application mode validation in release spec (#11506)
Today, there is a mode validation check when doing a Mix Release that
prevents a parent application that has an application mode of
`:permanent`, for example, while a child application has mode `:load`,
as it might be unsafe.

However, some complex applications may need more control over the
application load/start order.  For such cases, the user would need a
way to tell Mix.Release to don't be strict while constructing the
`.rel` file.

To allow for better control over the mode validation check instead of
simply disabling the check completely, we introduce the
`:skip_mode_validation_for` to allow users to specify a list of
applications for which the strict application mode validations should
not be enforced.
2021-12-20 12:48:11 +01:00
Marc-André Lafortune 071285403d Check plugins first so they can format .ex and .exs files (#11507) 2021-12-20 08:51:59 +01:00
José Valim 3d3453c1e2 Do not emit warnings on Cursor.Fragment.container_cursor_to_quoted/2 2021-12-16 16:44:10 +01:00
José Valim 2dc3fd26b8 Update CHANGELOG 2021-12-16 15:39:52 +01:00
José Valim e61ed47066 Deprecate map/filter/reject in Map and Keyword 2021-12-16 15:33:41 +01:00
23 changed files with 165 additions and 94 deletions
+22
View File
@@ -128,6 +128,27 @@ Now any application can use your formatter as follows:
Finally, the `Code` module has also been augmented with two functions: `Code.string_to_quoted_with_comments/2` and `Code.quoted_to_algebra/2`. Those functions allow someone to retrieve the Elixir AST with their original source code comments, and then convert this AST to formatted code. In other words, those functions provide a wrapper around the Elixir Code Formatter, supporting developers who wish to create tools that directly manipulate and custom format Elixir source code.
## v1.13.2 (2022-01-13)
### 1. Enhancements
#### Mix
* [mix format] Allow plugins to also format `.ex` and `.exs` files
* [mix release] Allow bypassing application mode validation in release spec
* [mix test] Print a message when the suite fails due to the coverage threshold
### 2. Bug fixes
#### Elixir
* [Code] Do not emit warnings on `Code.Fragment.container_cursor_to_quoted/2`
* [Kernel] Fix a crash when a for-comprehension with `:uniq` was used inside another comprehension with `:uniq`
* [Kernel] Ensure `env.context_modules` is properly set inside optimized `defmodule`
* [Keyword] Deprecate the recently added `Keyword.map/2` as it is equivalent to `Keyword.new/2`
* [Map] Deprecate the recently added `Map.map/2` as it is equivalent to `Map.new/2`
* [Protocol] Warn on zero arity callbacks inside protocols
## v1.13.1 (2021-12-14)
### 1. Bug fixes
@@ -210,6 +231,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
#### Logger
* [Logger] Add `Logger.put_application_level/2`
* [Logger] Print all log levels in accordance to Erlang/OTP. This also means `[warn]` is now shown as `[warning]`
#### Mix
+1 -1
View File
@@ -1 +1 @@
1.13.1
1.13.2
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.13.1
ELIXIR_VERSION=1.13.2
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
+1 -1
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.13.1
set ELIXIR_VERSION=1.13.2
setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
+1 -1
View File
@@ -827,7 +827,7 @@ defmodule Code.Fragment do
case :elixir_tokenizer.tokenize(fragment, line, column, tokenizer_opts) do
{:ok, _, _, _warnings, tokens} ->
:elixir.tokens_to_quoted(tokens, file, columns: columns, token_metadata: token_metadata)
:elixir.tokens_to_quoted(tokens, nil, columns: columns, token_metadata: token_metadata)
{:error, {line, column, {prefix, suffix}, token}, _rest, _warnings, _so_far} ->
location = [line: line, column: column]
+4 -22
View File
@@ -1374,27 +1374,9 @@ defmodule Keyword do
end
end
@doc """
Maps the function `fun` over all key-value pairs in `keywords`,
returning a keyword list with all the values replaced with
the result of the function.
## Examples
iex> Keyword.map([one: 1, two: 2, three: 3], fn {_key, val} -> to_string(val) end)
[one: "1", two: "2", three: "3"]
"""
@doc since: "1.13.0"
@spec map(t, ({key, value} -> value)) :: t
def map(keywords, fun) when is_list(keywords) and is_function(fun, 1) do
do_map(keywords, fun)
end
defp do_map([], _fun), do: []
defp do_map([{key, value} | rest], fun) do
new_value = fun.({key, value})
[{key, new_value} | do_map(rest, fun)]
@doc false
@deprecated "Use Keyword.new/2 instead"
def map(keywords, fun) when is_list(keywords) do
Enum.map(keywords, fn {k, v} -> {k, fun.({k, v})} end)
end
end
+6 -32
View File
@@ -982,8 +982,8 @@ defmodule Map do
See also `reject/2` which discards all elements where the
function returns a truthy value.
> Note: if you find yourself doing multiple calls to `Map.map/2`
> and `Map.filter/2` in a pipeline, it is likely more efficient
> Note: if you find yourself doing multiple calls to `Map.filter/2`
> and `Map.reject/2` in a pipeline, it is likely more efficient
> to use `Enum.map/2` and `Enum.filter/2` instead and convert to
> a map at the end using `Map.new/1`.
@@ -1041,35 +1041,9 @@ defmodule Map do
end
end
@doc """
Maps the function `fun` over all key-value pairs in `map`
It returns a map with all the values replaced with the result
of the function.
> Note: if you find yourself doing multiple calls to `Map.map/2`
> and `Map.filter/2` in a pipeline, it is likely more efficient
> to use `Enum.map/2` and `Enum.filter/2` instead and convert to
> a map at the end using `Map.new/1`.
## Examples
iex> Map.map(%{1 => "joe", 2 => "mike", 3 => "robert"}, fn {_key, val} -> String.capitalize(val) end)
%{1 => "Joe", 2 => "Mike", 3 => "Robert"}
"""
@doc since: "1.13.0"
@spec map(map, ({key, value} -> value)) :: map
def map(map, fun) when is_map(map) and is_function(fun, 1) do
iter = :maps.iterator(map)
next = :maps.next(iter)
:maps.from_list(do_map(next, fun))
end
defp do_map(:none, _fun), do: []
defp do_map({key, value, iter}, fun) do
new_value = fun.({key, value})
[{key, new_value} | do_map(:maps.next(iter), fun)]
@doc false
@deprecated "Use Map.new/2 instead"
def map(map, fun) when is_map(map) do
new(map, fn {k, v} -> {k, fun.({k, v})} end)
end
end
+5 -1
View File
@@ -383,7 +383,7 @@ defmodule Process do
@type spawn_opt ::
:link
| :monitor
| {:monitor, :erlang.monitor_option()}
| {:monitor, monitor_option()}
| {:priority, :low | :normal | :high}
| {:fullsweep_after, non_neg_integer}
| {:min_heap_size, non_neg_integer}
@@ -392,6 +392,10 @@ defmodule Process do
| {:message_queue_data, :off_heap | :on_heap}
@type spawn_opts :: [spawn_opt]
# TODO: Use :erlang.monitor_option() on Erlang/OTP 24+
@typep monitor_option ::
[alias: :explicit_unalias | :demonitor | :reply_demonitor, tag: term()]
@doc """
Spawns the given function according to the given options.
+2 -2
View File
@@ -723,14 +723,14 @@ defmodule Protocol do
defp callback_ast_to_fa({kind, {:"::", meta, [{name, _, args}, _return]}, _pos})
when kind in [:callback, :macrocallback] do
[{{name, length(args)}, meta}]
[{{name, length(List.wrap(args))}, meta}]
end
defp callback_ast_to_fa(
{kind, {:when, _, [{:"::", meta, [{name, _, args}, _return]}, _vars]}, _pos}
)
when kind in [:callback, :macrocallback] do
[{{name, length(args)}, meta}]
[{{name, length(List.wrap(args))}, meta}]
end
defp callback_ast_to_fa({kind, _, _pos}) when kind in [:callback, :macrocallback] do
+5 -5
View File
@@ -359,8 +359,8 @@ string_to_tokens(String, StartLine, StartColumn, File, Opts) when is_integer(Sta
{error, {Location, to_binary(Error), to_binary(Token)}}
end.
tokens_to_quoted(Tokens, File, Opts) ->
handle_parsing_opts(File, Opts),
tokens_to_quoted(Tokens, WarningFile, Opts) ->
handle_parsing_opts(WarningFile, Opts),
try elixir_parser:parse(Tokens) of
{ok, Forms} ->
@@ -370,7 +370,7 @@ tokens_to_quoted(Tokens, File, Opts) ->
{error, {Line, _, [Error, Token]}} ->
{error, {parser_location(Line), to_binary(Error), to_binary(Token)}}
after
erase(elixir_parser_file),
erase(elixir_parser_warning_file),
erase(elixir_parser_columns),
erase(elixir_token_metadata),
erase(elixir_literal_encoder)
@@ -406,7 +406,7 @@ parser_location(Meta) ->
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
handle_parsing_opts(File, Opts) ->
handle_parsing_opts(WarningFile, Opts) ->
LiteralEncoder =
case lists:keyfind(literal_encoder, 1, Opts) of
{literal_encoder, Fun} -> Fun;
@@ -414,7 +414,7 @@ handle_parsing_opts(File, Opts) ->
end,
TokenMetadata = lists:keyfind(token_metadata, 1, Opts) == {token_metadata, true},
Columns = lists:keyfind(columns, 1, Opts) == {columns, true},
put(elixir_parser_file, File),
put(elixir_parser_warning_file, WarningFile),
put(elixir_parser_columns, Columns),
put(elixir_token_metadata, TokenMetadata),
put(elixir_literal_encoder, LiteralEncoder).
+2 -1
View File
@@ -133,7 +133,8 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
'Elixir.Macro':expand(Mod, E)
end,
elixir_module:compile(Expanded, Block, [], E).
ContextModules = [Expanded | ?key(E, context_modules)],
elixir_module:compile(Expanded, Block, [], E#{context_modules := ContextModules}).
%% Bootstrapper
+10 -10
View File
@@ -21,7 +21,7 @@ translate_reduce(Meta, Cases, Expr, Reduce, S) ->
({'case', CaseAnn, _, CaseBlock}, InnerAcc) -> {'case', CaseAnn, InnerAcc, CaseBlock}
end,
{build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE), SE}.
build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE).
translate_into(Meta, Cases, Expr, Opts, Return, S) ->
Ann = ?ann(Meta),
@@ -115,11 +115,11 @@ build_inline(Ann, Clauses, Expr, Into, Uniq, S) ->
build_inline_each(Ann, Clauses, Expr, false, Uniq, S) ->
InnerFun = fun(InnerExpr, _InnerAcc) -> InnerExpr end,
{build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S), S};
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S);
build_inline_each(Ann, Clauses, Expr, {nil, _} = Into, Uniq, S) ->
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerExpr, InnerAcc} end,
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
{?remote(Ann, lists, reverse, [ReduceExpr]), S};
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
{?remote(Ann, lists, reverse, [ReduceExpr]), SR};
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
{InnerValue, SV} = build_var(Ann, S),
Generated = erl_anno:set_generated(true, Ann),
@@ -138,8 +138,8 @@ build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
]}
end,
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SV}.
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SR}.
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, S) ->
{ReduceExpr, SR} = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
@@ -161,7 +161,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
?remote(Ann, 'Elixir.Collectable', into, [Into])
},
IntoReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
{IntoReduceExpr, SN} = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
TryExpr =
{'try', Ann,
@@ -173,7 +173,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
[stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack)],
[]},
{{block, Ann, [MatchExpr, TryExpr]}, SD}.
{{block, Ann, [MatchExpr, TryExpr]}, SN}.
stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
{clause, Ann,
@@ -186,7 +186,7 @@ stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
build_reduce(Ann, Clauses, InnerFun, Expr, Into, false, S) ->
{Acc, SA} = build_var(Ann, S),
build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA);
{build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA), SA};
build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
%% Those variables are used only inside the anonymous function
%% so we don't need to worry about returning the scope.
@@ -209,7 +209,7 @@ build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
]},
EnumReduceCall = build_reduce_each(Clauses, InnerExpr, NewInto, Acc, SU),
?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]).
{?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]), SU}.
build_reduce_each([{enum, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) ->
Ann = ?ann(Meta),
+21 -11
View File
@@ -640,7 +640,6 @@ map -> struct_op struct_expr eol map_args : {'%', meta_from_token('$1'), ['$2',
Erlang code.
-define(file(), get(elixir_parser_file)).
-define(columns(), get(elixir_parser_columns)).
-define(token_metadata(), get(elixir_token_metadata)).
@@ -729,10 +728,12 @@ build_op(AST, {_Kind, Location, '//'}, Right) ->
build_op({UOp, _, [Left]}, {_Kind, {Line, Column, _} = Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
%% TODO: Remove "not left in right" rearrangement on v2.0
elixir_errors:erl_warn({Line, Column}, ?file(),
warn(
{Line, Column},
"\"not expr1 in expr2\" is deprecated. "
"Instead use \"expr1 not in expr2\" if you require Elixir v1.5+, "
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"),
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"
),
Meta = meta_from_location(Location),
{UOp, Meta, [{'in', Meta, [Left, Right]}]};
@@ -1169,21 +1170,22 @@ error_invalid_kw_identifier({_, Location, KW}) ->
%% TODO: Make this an error on v2.0
warn_trailing_comma({',', {Line, Column, _}}) ->
elixir_errors:erl_warn({Line, Column}, ?file(),
"trailing commas are not allowed inside function/macro call arguments"
).
warn({Line, Column}, "trailing commas are not allowed inside function/macro call arguments").
%% TODO: Make this an error on v2.0
warn_empty_paren({_, {Line, Column, _}}) ->
elixir_errors:erl_warn({Line, Column}, ?file(),
warn(
{Line, Column},
"invalid expression (). "
"If you want to invoke or define a function, make sure there are "
"no spaces between the function name and its arguments. If you wanted "
"to pass an empty block or code, pass a value instead, such as a nil or an atom").
"to pass an empty block or code, pass a value instead, such as a nil or an atom"
).
%% TODO: Make this an error on v2.0
warn_pipe({arrow_op, {Line, Column, _}, Op}, {_, [_ | _], [_ | _]}) ->
elixir_errors:erl_warn({Line, Column}, ?file(),
warn(
{Line, Column},
io_lib:format(
"parentheses are required when piping into a function call. For example:\n\n"
" foo 1 ~ts bar 2 ~ts baz 3\n\n"
@@ -1197,6 +1199,14 @@ warn_pipe(_Token, _) ->
ok.
warn_empty_stab_clause({stab_op, {Line, Column, _}, '->'}) ->
elixir_errors:erl_warn({Line, Column}, ?file(),
warn(
{Line, Column},
"an expression is always required on the right side of ->. "
"Please provide a value after ->").
"Please provide a value after ->"
).
warn(LineColumn, Message) ->
case get(elixir_parser_warning_file) of
nil -> ok;
File -> elixir_errors:erl_warn(LineColumn, File, Message)
end.
@@ -915,6 +915,11 @@ defmodule CodeFragmentTest do
assert cc2q("foo(123, ~r/") == s2q("foo(123, __cursor__())")
end
test "no warnings" do
assert cc2q(~s"?\\ ") == s2q("__cursor__()")
assert cc2q(~s"{fn -> end, ") == s2q("{fn -> nil end, __cursor__()}")
end
test "options" do
opts = [columns: true]
assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
@@ -98,6 +98,19 @@ defmodule Kernel.ComprehensionTest do
assert Process.get(:into_halt)
end
test "nested for comprehensions with unique values" do
assert for(x <- [1, 1, 2], uniq: true, do: for(y <- [3, 3], uniq: true, do: x * y)) == [
[3],
[6]
]
assert for(<<x <- "abcabc">>,
uniq: true,
into: "",
do: for(<<y <- "zz">>, uniq: true, into: "", do: to_bin(x) <> to_bin(y))
) == "azbzcz"
end
test "for comprehensions with nilly filters" do
assert for(x <- 1..3, nilly(), do: x * 2) == []
end
@@ -1163,9 +1163,11 @@ defmodule Kernel.WarningTest do
def without_specs(term, options \\ [])
@callback foo :: {:ok, term}
@callback foo(term) :: {:ok, term}
@callback foo(term, keyword) :: {:ok, term, keyword}
@callback foo_when :: {:ok, x} when x: term
@callback foo_when(x) :: {:ok, x} when x: term
@callback foo_when(x, opts) :: {:ok, x, opts} when x: term, opts: keyword
@@ -1178,12 +1180,18 @@ defmodule Kernel.WarningTest do
""")
end)
assert message =~
"cannot define @callback foo/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
assert message =~
"cannot define @callback foo/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
assert message =~
"cannot define @callback foo/2 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
assert message =~
"cannot define @callback foo_when/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
assert message =~
"cannot define @callback foo_when/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
+8
View File
@@ -810,6 +810,14 @@ defmodule MacroTest do
defmodule Foo.Bar do
assert __MODULE__ in __ENV__.context_modules
end
assert Foo.Bar in __ENV__.context_modules
Code.compile_string("""
defmodule Foo.Bar.Compiled do
true = __MODULE__ in __ENV__.context_modules
end
""")
end
test "to_match/1" do
+13 -2
View File
@@ -609,13 +609,24 @@ defmodule Mix.Release do
end
defp build_release_spec(release, modes) do
%{name: name, version: version, erts_version: erts_version, applications: apps} = release
%{
name: name,
version: version,
erts_version: erts_version,
applications: apps,
options: options
} = release
skip_mode_validation_for =
options
|> Keyword.get(:skip_mode_validation_for, [])
|> MapSet.new()
rel_apps =
for {app, mode} <- modes do
properties = Map.get(apps, app) || throw({:error, "Unknown application #{inspect(app)}"})
children = Keyword.get(properties, :applications, [])
validate_mode!(app, mode, modes, children)
app in skip_mode_validation_for || validate_mode!(app, mode, modes, children)
build_app_for_release(app, mode, properties)
end
+3 -3
View File
@@ -495,12 +495,12 @@ defmodule Mix.Tasks.Format do
ext = Path.extname(file)
cond do
ext in ~w(.ex .exs) ->
&elixir_format(&1, [file: file] ++ formatter_opts)
plugin = find_plugin_for_extension(formatter_opts, ext) ->
&plugin.format(&1, [extension: ext] ++ formatter_opts)
ext in ~w(.ex .exs) ->
&elixir_format(&1, [file: file] ++ formatter_opts)
true ->
& &1
end
+8
View File
@@ -455,6 +455,14 @@ defmodule Mix.Tasks.Release do
* `:steps` - a list of steps to execute when assembling the release. See
the "Steps" section for more information.
* `:skip_mode_validation_for` - a list of application names
(atoms) specifying applications to skip strict validation of
"unsafe" modes. An "unsafe" case is when a parent application
mode is `:permanent` but one of the applications it depends on
is set to `:load`. Use this with care, as a release with
invalid modes may no longer boot without additional tweaks.
Defaults to `[]`.
Note each release definition can be given as an anonymous function. This
is useful if some release attributes are expensive to compute:
+11
View File
@@ -299,6 +299,7 @@ defmodule Mix.Tasks.Test.Coverage do
print_summary(module_results, totals, summary_opts)
if totals < get_threshold(summary_opts) do
print_failed_threshold(totals, get_threshold(summary_opts))
System.at_exit(fn _ -> exit({:shutdown, 3}) end)
end
@@ -353,6 +354,13 @@ defmodule Mix.Tasks.Test.Coverage do
Mix.shell().info("")
end
defp print_failed_threshold(totals, threshold) do
Mix.shell().info("Coverage test failed, threshold not met:\n")
Mix.shell().info(" Coverage: #{format_number(totals, 6)}%")
Mix.shell().info(" Threshold: #{format_number(threshold, 6)}%")
Mix.shell().info("")
end
defp display({percentage, name}, threshold) do
Mix.shell().info([
color(percentage, threshold),
@@ -369,6 +377,9 @@ defmodule Mix.Tasks.Test.Coverage do
defp color(percentage, threshold) when percentage >= threshold, do: :green
defp color(_, _), do: :red
defp format_number(number, length) when is_integer(number),
do: format_number(number / 1, length)
defp format_number(number, length), do: :io_lib.format("~#{length}.2f", [number])
defp format_name(name) when is_binary(name), do: name
+7
View File
@@ -479,6 +479,13 @@ defmodule Mix.ReleaseTest do
assert message =~
"Application :mix has mode :permanent but it depends on :elixir which is set to :none"
end
test "does not raise on child unsafe mode if parent is in `skip_mode_validation_for`" do
# `:mix` is a parent that depends on `:elixir`
release = release(applications: [elixir: :load], skip_mode_validation_for: [:mix])
assert make_boot_script(release, @boot_script_path, release.boot_scripts.start) == :ok
assert make_boot_script(release, @boot_script_path, release.boot_scripts.start_clean) == :ok
end
end
describe "make_cookie/1" do
+8 -1
View File
@@ -168,7 +168,14 @@ defmodule Mix.Tasks.TestTest do
# We skip a test in bar to force coverage below the default threshold
# which should result in an exit status of 1.
assert {_, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"])
assert {output, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"])
assert output =~ """
Coverage test failed, threshold not met:
Coverage: 0.00%
Threshold: 90.00%
"""
unless windows?() do
assert code == 3