Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0220bd267 | ||
|
|
6f5cc529d5 | ||
|
|
3d80256852 | ||
|
|
8731ef03d9 | ||
|
|
f8a2d05b57 | ||
|
|
1145dc0168 | ||
|
|
12f93df04f | ||
|
|
47a63a73b6 | ||
|
|
d0ebaa7cdd | ||
|
|
e9ea9dea7f | ||
|
|
1feef9cb34 | ||
|
|
5c5fa57234 | ||
|
|
e891b1a831 | ||
|
|
001a5eb2e8 | ||
|
|
6085ed0288 | ||
|
|
57be943f91 | ||
|
|
99cad598a8 | ||
|
|
ef4c926299 | ||
|
|
d1acad0155 | ||
|
|
4a69a00c71 | ||
|
|
a9ad1f1b2a | ||
|
|
53e3d8f55e | ||
|
|
f53b8c822d | ||
|
|
cdd312b0ca | ||
|
|
0cc53735a1 |
+4
-7
@@ -27,15 +27,13 @@ test_linux_task:
|
||||
- env:
|
||||
CHECK_POSIX_COMPLIANT: true
|
||||
CHECK_REPRODUCIBLE: true
|
||||
OTP_RELEASE: OTP-22.1
|
||||
OTP_RELEASE: OTP-23.0
|
||||
- env:
|
||||
OTP_RELEASE: OTP-22.3
|
||||
- env:
|
||||
OTP_RELEASE: OTP-22.0
|
||||
- env:
|
||||
OTP_RELEASE: OTP-21.3.8
|
||||
- env:
|
||||
OTP_RELEASE: OTP-21.2
|
||||
- env:
|
||||
OTP_RELEASE: OTP-21.1
|
||||
- env:
|
||||
OTP_RELEASE: OTP-21.0
|
||||
|
||||
@@ -110,7 +108,7 @@ test_windows_task:
|
||||
image: fertapric/elixir-ci:otp-win64-${OTP_RELEASE}
|
||||
os_version: ${OS_VERSION}
|
||||
cpu: 4
|
||||
memory: 3840Mi
|
||||
memory: 6GB
|
||||
|
||||
install_script:
|
||||
- rmdir /s /q .git
|
||||
@@ -143,7 +141,6 @@ test_freebsd_task:
|
||||
LC_ALL: en_US.UTF-8
|
||||
|
||||
install_script:
|
||||
- sudo pkg update
|
||||
- pkg install -y erlang git gmake
|
||||
- rm -rf .git
|
||||
- gmake compile
|
||||
|
||||
@@ -155,6 +155,34 @@ assert %{"status" => 200, "body" => %{"key" => "foo"}} = json_payload
|
||||
|
||||
Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the `"body"` did not have value of `"foo"`. In previous Elixir versions, if the assertion failed, Elixir would print the right side and let you up to your own devices to figure out what went wrong. In Elixir v1.10, we diff the data structure against the pattern so you can see exactly which parts of the data matched the pattern and which ones did not. Note ExUnit already performed diffing when comparing data types, this new version adds diffing when matching data against a pattern.
|
||||
|
||||
## v1.10.4 (2020-07-04)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Fix a bug where custom types were printed as built-in types
|
||||
* [Kernel] Don't add compile-time dependency on `defdelegate`
|
||||
* [Kernel] Add line numbers to warnings on deprecated imports
|
||||
* [Kernel] Report the correct line number when raising inside a macro
|
||||
* [Task] Include callers in translated Logger metadata for Task
|
||||
* [Task] Fix Task PID and caller in Task Supervisor reports
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Formatter] Avoid crashes when diffing guards when the pattern does not match
|
||||
* [ExUnit.Formatter] Also blame exceptions that come from linked and trapped exits
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Do not crash when printing a type that cannot be code formatted
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix app.start] Fix reading `.app` file located in archives (`.ez` files)
|
||||
* [mix local.hex] Provide more guidance when Hex can't be installed
|
||||
* [mix release] Properly encode config in releases
|
||||
|
||||
## v1.10.3 (2020-04-25)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
@@ -582,6 +582,10 @@ defmodule DateTime do
|
||||
It will return the integer with the given unit,
|
||||
according to `System.convert_time_unit/3`.
|
||||
|
||||
If you want to get the current time in Unix seconds,
|
||||
do not do `DateTime.utc_now() |> DateTime.to_unix()`.
|
||||
Simply call `System.os_time(:second)` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> 1_464_096_368 |> DateTime.from_unix!() |> DateTime.to_unix()
|
||||
|
||||
@@ -217,7 +217,8 @@ defmodule Code.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:user_type, line, name, args}) do
|
||||
typespec_to_quoted({:type, line, name, args})
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
{name, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :tuple, :any}) do
|
||||
|
||||
@@ -350,7 +350,7 @@ defmodule Config.Provider do
|
||||
defp write_config!(config, path) do
|
||||
contents = :io_lib.format("%% coding: utf-8~n~tw.~n", [config])
|
||||
|
||||
case File.write(path, contents, [:utf8]) do
|
||||
case File.write(path, IO.chardata_to_string(contents)) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ defmodule IO do
|
||||
@spec warn(chardata | String.Chars.t(), Exception.stacktrace()) :: :ok
|
||||
def warn(message, []) do
|
||||
message = [to_chardata(message), ?\n]
|
||||
:elixir_errors.io_warn(nil, nil, message, message)
|
||||
:elixir_errors.io_warn(0, nil, message, message)
|
||||
end
|
||||
|
||||
def warn(message, [{_, _, _, opts} | _] = stacktrace) do
|
||||
@@ -327,7 +327,7 @@ defmodule IO do
|
||||
file = opts[:file]
|
||||
|
||||
:elixir_errors.io_warn(
|
||||
line,
|
||||
line || 0,
|
||||
file && List.to_string(file),
|
||||
message,
|
||||
[message, ?\n, " ", formatted_trace, ?\n]
|
||||
|
||||
@@ -4881,6 +4881,18 @@ defmodule Kernel do
|
||||
defmacro defdelegate(funs, opts) do
|
||||
funs = Macro.escape(funs, unquote: true)
|
||||
|
||||
# don't add compile-time dependency on :to
|
||||
opts =
|
||||
with true <- is_list(opts),
|
||||
{:ok, target} <- Keyword.fetch(opts, :to),
|
||||
{:__aliases__, _, _} <- target do
|
||||
target = Macro.expand(target, %{__CALLER__ | function: {:__info__, 1}})
|
||||
Keyword.replace!(opts, :to, target)
|
||||
else
|
||||
_ ->
|
||||
opts
|
||||
end
|
||||
|
||||
quote bind_quoted: [funs: funs, opts: opts] do
|
||||
target =
|
||||
Keyword.get(opts, :to) || raise ArgumentError, "expected to: to be given as argument"
|
||||
|
||||
@@ -652,6 +652,12 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp get_line(file, _reason, [{_, _, _, [file: 'expanding macro']}, {_, _, _, info} | _]) do
|
||||
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
|
||||
Keyword.get(info, :line)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_line(file, _reason, [{_, _, _, info} | _]) do
|
||||
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
|
||||
Keyword.get(info, :line)
|
||||
|
||||
@@ -231,7 +231,7 @@ defmodule Module.Checker do
|
||||
|
||||
defp warn(meta, state, warning) do
|
||||
{fun, arity} = state.function
|
||||
location = {state.file, meta[:line], {state.module, fun, arity}}
|
||||
location = {state.file, meta[:line] || 0, {state.module, fun, arity}}
|
||||
%{state | warnings: [{__MODULE__, warning, location} | state.warnings]}
|
||||
end
|
||||
|
||||
@@ -306,7 +306,7 @@ defmodule Module.Checker do
|
||||
|
||||
defp format_location({file, line, {module, fun, arity}}) do
|
||||
file = Path.relative_to_cwd(file)
|
||||
line = if line, do: [Integer.to_string(line), ": "], else: []
|
||||
line = if line > 0, do: [Integer.to_string(line), ": "], else: []
|
||||
mfa = Exception.format_mfa(module, fun, arity)
|
||||
[" ", file, ?:, line, mfa, ?\n]
|
||||
end
|
||||
|
||||
@@ -204,7 +204,7 @@ defmodule Registry do
|
||||
@type guards :: [guard]
|
||||
|
||||
@typedoc "A pattern used to representing the output format part of a match spec"
|
||||
@type body :: [atom | tuple]
|
||||
@type body :: [term]
|
||||
|
||||
@typedoc "A full match spec used when selecting objects in the registry"
|
||||
@type spec :: [{match_pattern, guards, body}]
|
||||
|
||||
@@ -102,8 +102,8 @@ defmodule Task.Supervised do
|
||||
%{
|
||||
label: {Task.Supervisor, :terminating},
|
||||
report: %{
|
||||
name: get_from(owner),
|
||||
starter: self(),
|
||||
name: self(),
|
||||
starter: get_from(owner),
|
||||
function: fun,
|
||||
args: args,
|
||||
reason: {log_value(kind, value), __STACKTRACE__}
|
||||
@@ -112,7 +112,8 @@ defmodule Task.Supervised do
|
||||
%{
|
||||
domain: [:otp, :elixir],
|
||||
error_logger: %{tag: :error_msg},
|
||||
report_cb: &__MODULE__.format_report/1
|
||||
report_cb: &__MODULE__.format_report/1,
|
||||
callers: Process.get(:"$callers")
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Elixir version | Supported Erlang/OTP versions
|
||||
1.7 | 19 - 22
|
||||
1.8 | 20 - 22
|
||||
1.9 | 20 - 22
|
||||
1.10 | 21 - 22
|
||||
1.10 | 21 - 22 (and Erlang/OTP 23 from v1.10.3)
|
||||
|
||||
While Elixir often adds compatibility to new Erlang/OTP versions on released branches, such as support for Erlang/OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang/OTP release.
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
{ok, Receiver, Quoted} ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ok, Receiver, NewName, NewArgs} ->
|
||||
elixir_expand:expand({{'.', [], [Receiver, NewName]}, Meta, NewArgs}, E);
|
||||
elixir_expand:expand({{'.', Meta, [Receiver, NewName]}, Meta, NewArgs}, E);
|
||||
error ->
|
||||
Callback()
|
||||
end.
|
||||
@@ -328,9 +328,10 @@ get_macros(Receiver, false) ->
|
||||
end;
|
||||
|
||||
get_macros(Receiver, true) ->
|
||||
case is_ensure_loaded(Receiver) of
|
||||
true -> get_info(Receiver, macros);
|
||||
false -> []
|
||||
try
|
||||
Receiver:'__info__'(macros)
|
||||
catch
|
||||
error:_ -> []
|
||||
end.
|
||||
|
||||
%% Kernel deprecations are inlined.
|
||||
|
||||
@@ -83,11 +83,11 @@ defmodule Config.ProviderTest do
|
||||
assert config[@config_app] == [config_providers_booted: {:booted, nil}]
|
||||
end
|
||||
|
||||
@tag sys_config: [my_app: [encoding: {:"£", "£", '£'}]]
|
||||
@tag sys_config: [my_app: [encoding: {:time_μs, :"£", "£", '£'}]]
|
||||
test "writes sys_config with encoding" do
|
||||
init_and_assert_boot()
|
||||
config = consult(@sys_config)
|
||||
assert config[:my_app][:encoding] == {:"£", "£", '£'}
|
||||
assert config[:my_app][:encoding] == {:time_μs, :"£", "£", '£'}
|
||||
end
|
||||
|
||||
@tag sys_config: [my_app: [key: :old_value, sys_key: :sys_value, extra_config: :old_value]]
|
||||
@@ -185,6 +185,6 @@ defmodule Config.ProviderTest do
|
||||
end
|
||||
|
||||
defp write_sys_config!(data) do
|
||||
File.write!(@sys_config, :io_lib.format("~tw.~n", [data]), [:utf8])
|
||||
File.write!(@sys_config, IO.chardata_to_string(:io_lib.format("~tw.~n", [data])))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -654,8 +654,8 @@ defmodule Kernel.ExpansionTest do
|
||||
expand(quote(do: [1] ++ 2 ++ [3] = [1, 2, 3]))
|
||||
end
|
||||
|
||||
assert {:=, _, [-1, {{:., [], [:erlang, :-]}, _, [1]}]} = expand(quote(do: -1 = -1))
|
||||
assert {:=, _, [1, {{:., [], [:erlang, :+]}, _, [1]}]} = expand(quote(do: +1 = +1))
|
||||
assert {:=, _, [-1, {{:., _, [:erlang, :-]}, _, [1]}]} = expand(quote(do: -1 = -1))
|
||||
assert {:=, _, [1, {{:., _, [:erlang, :+]}, _, [1]}]} = expand(quote(do: +1 = +1))
|
||||
|
||||
assert {:=, _, [[{:|, _, [1, [{:|, _, [2, 3]}]]}], [1, 2, 3]]} =
|
||||
expand(quote(do: [1] ++ [2] ++ 3 = [1, 2, 3]))
|
||||
|
||||
@@ -126,4 +126,21 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
refute Foo.Bar in runtime
|
||||
refute Foo.Bar in compile
|
||||
end
|
||||
|
||||
test "defdelegate with literal does not add compile dependency" do
|
||||
{{compile, _structs, _runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.Defdelegate do
|
||||
defdelegate a, to: A
|
||||
|
||||
opts = [to: B]
|
||||
defdelegate b, opts
|
||||
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
refute A in compile
|
||||
assert B in compile
|
||||
end
|
||||
end
|
||||
|
||||
@@ -639,6 +639,31 @@ defmodule Module.CheckerTest do
|
||||
assert_warnings(files, warning)
|
||||
end
|
||||
|
||||
test "reports imported functions" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
defmodule A do
|
||||
@deprecated "oops"
|
||||
def a, do: :ok
|
||||
end
|
||||
""",
|
||||
"b.ex" => """
|
||||
defmodule B do
|
||||
import A
|
||||
def b, do: a()
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
warning = """
|
||||
warning: A.a/0 is deprecated. oops
|
||||
b.ex:3: B.b/0
|
||||
|
||||
"""
|
||||
|
||||
assert_warnings(files, warning)
|
||||
end
|
||||
|
||||
test "reports structs" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
|
||||
@@ -1183,6 +1183,12 @@ defmodule TypespecTest do
|
||||
quoted =
|
||||
Enum.sort([
|
||||
quote(do: @spec(foo() :: integer())),
|
||||
quote(do: @spec(foo() :: union())),
|
||||
quote(do: @spec(foo() :: union(integer()))),
|
||||
quote(do: @spec(foo() :: truly_union())),
|
||||
quote(do: @spec(foo(union()) :: union())),
|
||||
quote(do: @spec(foo(union(integer())) :: union(integer()))),
|
||||
quote(do: @spec(foo(truly_union()) :: truly_union())),
|
||||
quote(do: @spec(foo(atom()) :: integer() | [{}])),
|
||||
quote(do: @spec(foo(arg) :: integer() when [arg: integer()])),
|
||||
quote(do: @spec(foo(arg) :: arg when [arg: var])),
|
||||
@@ -1191,6 +1197,10 @@ defmodule TypespecTest do
|
||||
|
||||
bytecode =
|
||||
test_module do
|
||||
@type union :: any()
|
||||
@type union(t) :: t
|
||||
@type truly_union :: list | map | union
|
||||
|
||||
def foo(), do: 1
|
||||
def foo(arg), do: arg
|
||||
Module.eval_quoted(__MODULE__, quote(do: (unquote_splicing(quoted))))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ExUnit.start [seed: 0]
|
||||
ExUnit.start(seed: 0)
|
||||
|
||||
defmodule TestOneOfEach do
|
||||
@moduledoc """
|
||||
@@ -10,8 +10,8 @@ defmodule TestOneOfEach do
|
||||
@one 1
|
||||
@two 2
|
||||
|
||||
@long_data_1 [field1: "one", field2: {:two1, :two2}, field3: 'three', field4: [1, 2, 3, 4]]
|
||||
@long_data_2 [field1: "one", field2: {:two1, :two3}, field3: 'three', field4: [1, 2, 3, 4]]
|
||||
@long_data_1 [field1: "one", field2: {:two1, :two2}, field3: 'three', field4: [1, 2, 3, 4]]
|
||||
@long_data_2 [field1: "one", field2: {:two1, :two3}, field3: 'three', field4: [1, 2, 3, 4]]
|
||||
|
||||
setup do
|
||||
{:ok, user_id: 1, post_id: 2, many_ids: Enum.to_list(1..50)}
|
||||
@@ -62,10 +62,10 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "12. assert that a message is received within a timeout" do
|
||||
send self(), {:ok, 1}
|
||||
send self(), :message_in_my_inbox
|
||||
send self(), {:ok, 2}
|
||||
send self(), :another_message
|
||||
send(self(), {:ok, 1})
|
||||
send(self(), :message_in_my_inbox)
|
||||
send(self(), {:ok, 2})
|
||||
send(self(), :another_message)
|
||||
assert_receive :no_message_after_timeout
|
||||
end
|
||||
|
||||
@@ -75,14 +75,14 @@ defmodule TestOneOfEach do
|
||||
|
||||
test "14. assert an exception with a given message is raised" do
|
||||
assert_raise(SomeException, "some message", fn ->
|
||||
raise "other exception"
|
||||
end)
|
||||
raise "other exception"
|
||||
end)
|
||||
end
|
||||
|
||||
test "15. assert an exception with a given message is raised, but the message is wrong" do
|
||||
assert_raise(RuntimeError, "some message", fn ->
|
||||
raise "other error"
|
||||
end)
|
||||
raise "other error"
|
||||
end)
|
||||
end
|
||||
|
||||
test "16. assert an exception is raised" do
|
||||
@@ -90,7 +90,7 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "17. assert two values are within some delta" do
|
||||
assert_in_delta 3.1415926, 22.0/7, 0.001
|
||||
assert_in_delta 3.1415926, 22.0 / 7, 0.001
|
||||
end
|
||||
|
||||
test "18. refute a value with a message" do
|
||||
@@ -98,12 +98,12 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "19. refute a message is received within a timeout" do
|
||||
send self(), {:hello, "Dave"}
|
||||
send(self(), {:hello, "Dave"})
|
||||
refute_receive {:hello, _}, 1000
|
||||
end
|
||||
|
||||
test "20. refute a message is ready to be received" do
|
||||
send self(), :hello_again
|
||||
send(self(), :hello_again)
|
||||
refute_received :hello_again
|
||||
end
|
||||
|
||||
@@ -116,7 +116,7 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "23. flunk" do
|
||||
flunk "we failed. totally"
|
||||
flunk("we failed. totally")
|
||||
end
|
||||
|
||||
test "24. exception raised while running test" do
|
||||
@@ -124,7 +124,8 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "25. error due to exit" do
|
||||
spawn_link fn -> raise "oops" end
|
||||
spawn_link(fn -> raise "oops" end)
|
||||
|
||||
receive do
|
||||
end
|
||||
end
|
||||
@@ -133,15 +134,17 @@ defmodule TestOneOfEach do
|
||||
error1 =
|
||||
try do
|
||||
assert [@one] = [@two]
|
||||
rescue e in ExUnit.AssertionError ->
|
||||
{:error, e, System.stacktrace}
|
||||
rescue
|
||||
e in ExUnit.AssertionError ->
|
||||
{:error, e, __STACKTRACE__}
|
||||
end
|
||||
|
||||
error2 =
|
||||
try do
|
||||
assert @one * 4 > @two * 3
|
||||
rescue e in ExUnit.AssertionError ->
|
||||
{:error, e, System.stacktrace}
|
||||
rescue
|
||||
e in ExUnit.AssertionError ->
|
||||
{:error, e, __STACKTRACE__}
|
||||
end
|
||||
|
||||
raise ExUnit.MultiError, errors: [error1, error2]
|
||||
@@ -150,8 +153,8 @@ defmodule TestOneOfEach do
|
||||
@tag capture_log: true
|
||||
test "27. log capturing" do
|
||||
require Logger
|
||||
Logger.debug "this will be logged"
|
||||
flunk "oops"
|
||||
Logger.debug("this will be logged")
|
||||
flunk("oops")
|
||||
end
|
||||
|
||||
test "28. function clause error" do
|
||||
@@ -162,6 +165,28 @@ defmodule TestOneOfEach do
|
||||
assert some_vars(1 + 2, 3 + 4)
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "30. linked assertion error" do
|
||||
Task.async(fn -> assert 1 == 2 end) |> Task.await()
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "31. linked function clause error" do
|
||||
Task.async(fn -> Access.fetch(:foo, :bar) end) |> Task.await()
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "32. trapped assertion error" do
|
||||
Process.flag(:trap_exit, true)
|
||||
Task.async(fn -> assert 1 == 2 end) |> Task.await()
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "33. trapped function clause error" do
|
||||
Process.flag(:trap_exit, true)
|
||||
Task.async(fn -> Access.fetch(:foo, :bar) end) |> Task.await()
|
||||
end
|
||||
|
||||
defp some_vars(_a, _b) do
|
||||
false
|
||||
end
|
||||
@@ -171,6 +196,6 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
defp ignite(val) do
|
||||
1/val
|
||||
1 / val
|
||||
end
|
||||
end
|
||||
|
||||
@@ -180,13 +180,18 @@ defmodule ExUnit.Diff do
|
||||
defp diff_guard({:when, _, [expression, clause]}, right, env) do
|
||||
{diff_expression, post_env} = diff_quoted(expression, right, env)
|
||||
|
||||
bindings = Map.merge(post_env.pins, post_env.current_vars)
|
||||
{diff_clause, clause_equivalent?} = diff_guard_clause(clause, Map.to_list(bindings))
|
||||
{guard_clause, guard_equivalent?} =
|
||||
if diff_expression.equivalent? do
|
||||
bindings = Map.merge(post_env.pins, post_env.current_vars)
|
||||
diff_guard_clause(clause, Map.to_list(bindings))
|
||||
else
|
||||
{clause, false}
|
||||
end
|
||||
|
||||
diff = %__MODULE__{
|
||||
diff_expression
|
||||
| left: {:when, [], [diff_expression.left, diff_clause]},
|
||||
equivalent?: diff_expression.equivalent? and clause_equivalent?
|
||||
| left: {:when, [], [diff_expression.left, guard_clause]},
|
||||
equivalent?: guard_equivalent?
|
||||
}
|
||||
|
||||
{diff, post_env}
|
||||
|
||||
@@ -54,6 +54,7 @@ defmodule ExUnit.Formatter do
|
||||
|
||||
@counter_padding " "
|
||||
@mailbox_label_padding @counter_padding <> " "
|
||||
@formatter_exceptions [ExUnit.AssertionError, FunctionClauseError]
|
||||
@no_value ExUnit.AssertionError.no_value()
|
||||
|
||||
@doc """
|
||||
@@ -136,10 +137,10 @@ defmodule ExUnit.Formatter do
|
||||
|
||||
@doc false
|
||||
def format_assertion_error(%ExUnit.AssertionError{} = struct) do
|
||||
format_assertion_error(%{}, struct, [], :infinity, fn _, msg -> msg end, "")
|
||||
format_exception(%{}, struct, [], :infinity, fn _, msg -> msg end, "") |> elem(0)
|
||||
end
|
||||
|
||||
defp format_assertion_error(test, struct, stack, width, formatter, counter_padding) do
|
||||
defp format_exception(test, %ExUnit.AssertionError{} = struct, stack, width, formatter, pad) do
|
||||
label_padding_size = if has_value?(struct.right), do: 7, else: 6
|
||||
padding_size = label_padding_size + byte_size(@counter_padding)
|
||||
|
||||
@@ -148,16 +149,27 @@ defmodule ExUnit.Formatter do
|
||||
do: &pad_multiline(&1, padding_size),
|
||||
else: &code_multiline(&1, padding_size)
|
||||
|
||||
[
|
||||
note: if_value(struct.message, &format_message(&1, formatter)),
|
||||
doctest: if_value(struct.doctest, &pad_multiline(&1, 2 + byte_size(@counter_padding))),
|
||||
code: if_value(struct.expr, code_multiline),
|
||||
code: unless_value(struct.expr, fn -> get_code(test, stack) || @no_value end),
|
||||
arguments: if_value(struct.args, &format_args(&1, width))
|
||||
]
|
||||
|> Kernel.++(format_context(struct, formatter, padding_size, width))
|
||||
|> format_meta(formatter, counter_padding, label_padding_size)
|
||||
|> IO.iodata_to_binary()
|
||||
formatted =
|
||||
[
|
||||
note: if_value(struct.message, &format_message(&1, formatter)),
|
||||
doctest: if_value(struct.doctest, &pad_multiline(&1, 2 + byte_size(@counter_padding))),
|
||||
code: if_value(struct.expr, code_multiline),
|
||||
code: unless_value(struct.expr, fn -> get_code(test, stack) || @no_value end),
|
||||
arguments: if_value(struct.args, &format_args(&1, width))
|
||||
]
|
||||
|> Kernel.++(format_context(struct, formatter, padding_size, width))
|
||||
|> format_meta(formatter, pad, label_padding_size)
|
||||
|> IO.iodata_to_binary()
|
||||
|
||||
{formatted, stack}
|
||||
end
|
||||
|
||||
defp format_exception(test, %FunctionClauseError{} = struct, stack, _width, formatter, _pad) do
|
||||
{blamed, stack} = Exception.blame(:error, struct, stack)
|
||||
banner = Exception.format_banner(:error, struct)
|
||||
blamed = FunctionClauseError.blame(blamed, &inspect/1, &blame_match(&1, &2, formatter))
|
||||
message = error_info(banner, formatter) <> "\n" <> pad(String.trim_leading(blamed, "\n"))
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -179,30 +191,48 @@ defmodule ExUnit.Formatter do
|
||||
end)
|
||||
end
|
||||
|
||||
defp format_kind_reason(
|
||||
test,
|
||||
:error,
|
||||
%ExUnit.AssertionError{} = struct,
|
||||
stack,
|
||||
width,
|
||||
formatter
|
||||
) do
|
||||
{format_assertion_error(test, struct, stack, width, formatter, @counter_padding), stack}
|
||||
defp format_kind_reason(test, :error, %mod{} = struct, stack, width, formatter)
|
||||
when mod in @formatter_exceptions do
|
||||
format_exception(test, struct, stack, width, formatter, @counter_padding)
|
||||
end
|
||||
|
||||
defp format_kind_reason(test, :error, %FunctionClauseError{} = struct, stack, _width, formatter) do
|
||||
{blamed, stack} = Exception.blame(:error, struct, stack)
|
||||
banner = Exception.format_banner(:error, struct)
|
||||
blamed = FunctionClauseError.blame(blamed, &inspect/1, &blame_match(&1, &2, formatter))
|
||||
message = error_info(banner, formatter) <> "\n" <> pad(String.trim_leading(blamed, "\n"))
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
defp format_kind_reason(test, kind, reason, stack, width, formatter) do
|
||||
case linked_or_trapped_exit(kind, reason) do
|
||||
{header, wrapped_reason, wrapped_stack} ->
|
||||
struct = Exception.normalize(:error, wrapped_reason, wrapped_stack)
|
||||
|
||||
{formatted_reason, _} =
|
||||
format_exception(test, struct, wrapped_stack, width, formatter, @counter_padding)
|
||||
|
||||
formatted_stack = format_stacktrace(wrapped_stack, test.module, test.name, formatter)
|
||||
{error_info(header, formatter) <> pad(formatted_reason <> formatted_stack), stack}
|
||||
|
||||
:error ->
|
||||
{reason, stack} = Exception.blame(kind, reason, stack)
|
||||
message = error_info(Exception.format_banner(kind, reason), formatter)
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
end
|
||||
end
|
||||
|
||||
defp format_kind_reason(test, kind, reason, stack, _width, formatter) do
|
||||
message = error_info(Exception.format_banner(kind, reason), formatter)
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
defp linked_or_trapped_exit({:EXIT, pid}, {reason, [_ | _] = stack})
|
||||
when :erlang.map_get(:__struct__, reason) in @formatter_exceptions
|
||||
when reason == :function_clause do
|
||||
{"** (EXIT from #{inspect(pid)}) an exception was raised:\n", reason, stack}
|
||||
end
|
||||
|
||||
defp linked_or_trapped_exit(:exit, {{reason, [_ | _] = stack}, {mod, fun, args}})
|
||||
when is_atom(mod) and is_atom(fun) and is_list(args) and
|
||||
:erlang.map_get(:__struct__, reason) in @formatter_exceptions
|
||||
when is_atom(mod) and is_atom(fun) and is_list(args) and reason == :function_clause do
|
||||
{
|
||||
"** (exit) exited in: #{Exception.format_mfa(mod, fun, args)}\n ** (EXIT) an exception was raised:",
|
||||
reason,
|
||||
stack
|
||||
}
|
||||
end
|
||||
|
||||
defp linked_or_trapped_exit(_kind, _reason), do: :error
|
||||
|
||||
defp format_code(test, stack, formatter) do
|
||||
if snippet = get_code(test, stack) do
|
||||
" " <> formatter.(:extra_info, "code: ") <> snippet <> "\n"
|
||||
|
||||
@@ -914,6 +914,7 @@ defmodule ExUnit.DiffTest do
|
||||
refute_diff((x when x == 1 or x == 2) = 0, "x when -x == 1- or -x == 2-", "0")
|
||||
refute_diff((x when x == 1 when x == 2) = 0, "x when -x == 1- when -x == 2-", "0")
|
||||
refute_diff((x when x in [1, 2]) = 0, "x when -x in [1, 2]-", "0")
|
||||
refute_diff(({:ok, x} when x == 1) = :error, "-{:ok, x}- when x == 1", "+:error+")
|
||||
end
|
||||
|
||||
test "charlists" do
|
||||
|
||||
@@ -69,6 +69,61 @@ defmodule ExUnit.FormatterTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test exits with function clause mfa" do
|
||||
{error, stack} =
|
||||
try do
|
||||
Access.fetch(:foo, :bar)
|
||||
catch
|
||||
:error, error -> {error, __STACKTRACE__}
|
||||
end
|
||||
|
||||
failure = [{:exit, {{error, stack}, {:mod, :fun, []}}, []}]
|
||||
|
||||
assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~
|
||||
"""
|
||||
1) world (Hello)
|
||||
test/ex_unit/formatter_test.exs:1
|
||||
** (exit) exited in: :mod.fun()
|
||||
** (EXIT) an exception was raised:
|
||||
** (FunctionClauseError) no function clause matching in Access.fetch/2
|
||||
|
||||
The following arguments were given to Access.fetch/2:
|
||||
|
||||
# 1
|
||||
:foo
|
||||
|
||||
# 2
|
||||
:bar
|
||||
|
||||
Attempted function clauses (showing 5 out of 5):
|
||||
|
||||
def fetch(%module{} = container, key)
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test exits with assertion mfa" do
|
||||
{error, stack} =
|
||||
try do
|
||||
assert 1 == 2
|
||||
rescue
|
||||
error -> {error, __STACKTRACE__}
|
||||
end
|
||||
|
||||
failure = [{:exit, {{error, stack}, {:mod, :fun, []}}, []}]
|
||||
|
||||
assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~
|
||||
"""
|
||||
1) world (Hello)
|
||||
test/ex_unit/formatter_test.exs:1
|
||||
** (exit) exited in: :mod.fun()
|
||||
** (EXIT) an exception was raised:
|
||||
Assertion with == failed
|
||||
code: assert 1 == 2
|
||||
left: 1
|
||||
right: 2
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test throws" do
|
||||
failure = [{:throw, 1, []}]
|
||||
|
||||
@@ -89,6 +144,61 @@ defmodule ExUnit.FormatterTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test EXITs with function clause errors" do
|
||||
{error, stack} =
|
||||
try do
|
||||
Access.fetch(:foo, :bar)
|
||||
catch
|
||||
:error, error -> {error, __STACKTRACE__}
|
||||
end
|
||||
|
||||
failure = [{{:EXIT, self()}, {error, stack}, []}]
|
||||
|
||||
assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~
|
||||
"""
|
||||
1) world (Hello)
|
||||
test/ex_unit/formatter_test.exs:1
|
||||
** (EXIT from #{inspect(self())}) an exception was raised:
|
||||
|
||||
** (FunctionClauseError) no function clause matching in Access.fetch/2
|
||||
|
||||
The following arguments were given to Access.fetch/2:
|
||||
|
||||
# 1
|
||||
:foo
|
||||
|
||||
# 2
|
||||
:bar
|
||||
|
||||
Attempted function clauses (showing 5 out of 5):
|
||||
|
||||
def fetch(%module{} = container, key)
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test EXITs with assertion errors" do
|
||||
{error, stack} =
|
||||
try do
|
||||
assert 1 == 2
|
||||
rescue
|
||||
error -> {error, __STACKTRACE__}
|
||||
end
|
||||
|
||||
failure = [{{:EXIT, self()}, {error, stack}, []}]
|
||||
|
||||
assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~
|
||||
"""
|
||||
1) world (Hello)
|
||||
test/ex_unit/formatter_test.exs:1
|
||||
** (EXIT from #{inspect(self())}) an exception was raised:
|
||||
|
||||
Assertion with == failed
|
||||
code: assert 1 == 2
|
||||
left: 1
|
||||
right: 2
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test errors with test_location_relative_path" do
|
||||
Application.put_env(:ex_unit, :test_location_relative_path, "apps/sample")
|
||||
failure = [{:error, catch_error(raise "oops"), []}]
|
||||
|
||||
@@ -735,9 +735,15 @@ defmodule IEx.Introspection do
|
||||
## Helpers
|
||||
|
||||
defp format_typespec(definition, kind, nesting) do
|
||||
"@#{kind} #{Macro.to_string(definition)}"
|
||||
|> Code.format_string!(line_length: IEx.width() - 2 * nesting)
|
||||
|> IO.iodata_to_binary()
|
||||
string = "@#{kind} #{Macro.to_string(definition)}"
|
||||
|
||||
try do
|
||||
string
|
||||
|> Code.format_string!(line_length: IEx.width() - 2 * nesting)
|
||||
|> IO.iodata_to_binary()
|
||||
rescue
|
||||
_ -> string
|
||||
end
|
||||
|> color_prefix_with_line()
|
||||
|> indent(nesting)
|
||||
end
|
||||
|
||||
@@ -853,6 +853,7 @@ defmodule IEx.HelpersTest do
|
||||
defmodule TypeSample do
|
||||
@typedoc "An ID with description."
|
||||
@type id_with_desc :: {number, String.t}
|
||||
@type unquote(:"?")() :: :question_mark
|
||||
end
|
||||
"""
|
||||
|
||||
@@ -872,6 +873,11 @@ defmodule IEx.HelpersTest do
|
||||
|
||||
An ID with description.
|
||||
"""
|
||||
|
||||
assert capture_io(fn -> t(TypeSample."?"()) end) == """
|
||||
@type ?() :: :question_mark
|
||||
|
||||
"""
|
||||
end)
|
||||
after
|
||||
cleanup_modules([TypeSample])
|
||||
|
||||
@@ -467,8 +467,9 @@ defmodule Logger do
|
||||
|
||||
Erlang/OTP handlers must be listed under your own application:
|
||||
|
||||
config :my_app, :logger,
|
||||
[:handler, :name_of_the_handler, ACustomHandler, configuration = %{}]
|
||||
config :my_app, :logger, [
|
||||
{:handler, :name_of_the_handler, ACustomHandler, configuration = %{}}
|
||||
]
|
||||
|
||||
And then explicitly attached in your `c:Application.start/2` callback:
|
||||
|
||||
|
||||
@@ -306,13 +306,14 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
test "translates Task crashes" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :task, [self()])
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, :go)
|
||||
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
|
||||
end) =~ ~r"""
|
||||
\[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
|
||||
\[error\] Task #{inspect(pid)} started from #{inspect(self())} terminating
|
||||
\*\* \(RuntimeError\) oops
|
||||
.*
|
||||
Function: &Logger.TranslatorTest.task\/1
|
||||
@@ -324,6 +325,7 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert {%RuntimeError{message: "oops"}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%RuntimeError{message: "oops"}, [_ | _]} = process_metadata[:crash_reason]
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
refute Keyword.has_key?(task_metadata, :initial_call)
|
||||
assert process_metadata[:initial_call] == {Logger.TranslatorTest, :task, 1}
|
||||
@@ -332,6 +334,7 @@ defmodule Logger.TranslatorTest do
|
||||
test "translates Task async_stream crashes with neighbour" do
|
||||
fun = fn -> Task.async_stream([:oops], :erlang, :error, []) |> Enum.to_list() end
|
||||
{:ok, pid} = Task.start(__MODULE__, :task, [self(), fun])
|
||||
parent = self()
|
||||
|
||||
assert capture_log(:debug, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
@@ -345,12 +348,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [pid, parent] == task_metadata[:callers]
|
||||
|
||||
assert {:oops, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%ErlangError{original: :oops}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task undef module crash" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(:module_does_not_exist, :undef, [])
|
||||
ref = Process.monitor(pid)
|
||||
@@ -365,12 +371,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%UndefinedFunctionError{function: :undef}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%UndefinedFunctionError{function: :undef}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task undef function crash" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(__MODULE__, :undef, [])
|
||||
ref = Process.monitor(pid)
|
||||
@@ -385,12 +394,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%UndefinedFunctionError{function: :undef}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%UndefinedFunctionError{function: :undef}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task raising ErlangError" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
exception =
|
||||
try do
|
||||
@@ -413,12 +425,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%ErlangError{original: :foo}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%ErlangError{original: :foo}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task raising Erlang badarg error" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(:erlang, :error, [:badarg])
|
||||
ref = Process.monitor(pid)
|
||||
@@ -433,12 +448,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%ArgumentError{message: "argument error"}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%ArgumentError{message: "argument error"}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task exiting abnormally" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(:erlang, :exit, [:abnormal])
|
||||
ref = Process.monitor(pid)
|
||||
@@ -453,6 +471,7 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {:abnormal, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {:abnormal, [_ | _]} = process_metadata[:crash_reason]
|
||||
@@ -625,8 +644,9 @@ defmodule Logger.TranslatorTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "translates :proc_lib crashes on debug" do
|
||||
test "translates :proc_lib+Task crashes on debug" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :task, [self()])
|
||||
parent = self()
|
||||
|
||||
assert capture_log(:debug, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
@@ -652,13 +672,15 @@ defmodule Logger.TranslatorTest do
|
||||
assert process_metadata[:pid] == task_metadata[:pid]
|
||||
assert is_list(process_metadata[:callers])
|
||||
assert is_list(process_metadata[:ancestors])
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%RuntimeError{message: "oops"}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%RuntimeError{message: "oops"}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates :proc_lib crashes with neighbour on debug" do
|
||||
test "translates :proc_lib+Task crashes with neighbour on debug" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :sub_task, [self()])
|
||||
parent = self()
|
||||
|
||||
assert capture_log(:debug, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
@@ -677,6 +699,9 @@ defmodule Logger.TranslatorTest do
|
||||
Current Stacktrace:
|
||||
(lib/logger/)?test/logger/translator_test.exs:\d+: Logger.TranslatorTest.sleep/1
|
||||
"""
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
end
|
||||
|
||||
test "translates Supervisor progress" do
|
||||
|
||||
@@ -161,14 +161,29 @@ defmodule Mix.Local do
|
||||
contents
|
||||
|
||||
{:remote, message} ->
|
||||
Mix.raise("""
|
||||
#{message}
|
||||
Mix.raise(
|
||||
"""
|
||||
#{message}
|
||||
|
||||
Could not install #{name} because Mix could not download metadata at #{path}.
|
||||
""")
|
||||
Could not install #{name} because Mix could not download metadata at #{path}.
|
||||
""" <> suggestions(name)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp suggestions("Hex") do
|
||||
"""
|
||||
|
||||
Alternatively, you can compile and install Hex directly with this command:
|
||||
|
||||
mix archive.install github hexpm/hex branch latest
|
||||
"""
|
||||
end
|
||||
|
||||
defp suggestions(_) do
|
||||
""
|
||||
end
|
||||
|
||||
defp parse_csv(body) do
|
||||
body
|
||||
|> :binary.split("\n", [:global, :trim])
|
||||
|
||||
@@ -402,7 +402,7 @@ defmodule Mix.Release do
|
||||
args = [runtime_config?, sys_config]
|
||||
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~tw.~n"
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
File.write!(path, :io_lib.format(format, args), [:utf8])
|
||||
File.write!(path, IO.chardata_to_string(:io_lib.format(format, args)))
|
||||
|
||||
case :file.consult(path) do
|
||||
{:ok, _} ->
|
||||
@@ -512,7 +512,7 @@ defmodule Mix.Release do
|
||||
:ok | {:error, String.t()}
|
||||
def make_boot_script(release, path, modes, prepend_paths \\ []) do
|
||||
with {:ok, rel_spec} <- build_release_spec(release, modes) do
|
||||
File.write!(path <> ".rel", consultable(rel_spec), [:utf8])
|
||||
File.write!(path <> ".rel", consultable(rel_spec))
|
||||
|
||||
sys_path = String.to_charlist(path)
|
||||
|
||||
@@ -535,7 +535,7 @@ defmodule Mix.Release do
|
||||
|> prepend_paths_to_script(prepend_paths)
|
||||
|
||||
script = {:script, rel_info, instructions}
|
||||
File.write!(script_path, consultable(script), [:utf8])
|
||||
File.write!(script_path, consultable(script))
|
||||
:ok = :systools.script2boot(sys_path)
|
||||
|
||||
{:error, module, info} ->
|
||||
@@ -657,7 +657,7 @@ defmodule Mix.Release do
|
||||
end
|
||||
|
||||
defp consultable(term) do
|
||||
:io_lib.format("%% coding: utf-8~n~tp.~n", [term])
|
||||
IO.chardata_to_string(:io_lib.format("%% coding: utf-8~n~tp.~n", [term]))
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -76,8 +76,12 @@ defmodule Mix.Shell do
|
||||
|
||||
## Options
|
||||
|
||||
* `:cd` - (since v1.11.0) the directory to run the command in
|
||||
|
||||
* `:stderr_to_stdout` - redirects stderr to stdout, defaults to true
|
||||
|
||||
* `:env` - a list of environment variables, defaults to `[]`
|
||||
|
||||
* `:quiet` - overrides the callback to no-op
|
||||
|
||||
"""
|
||||
@@ -98,7 +102,10 @@ defmodule Mix.Shell do
|
||||
[]
|
||||
end
|
||||
|
||||
opts = [:stream, :binary, :exit_status, :hide, :use_stdio, {:env, env} | args]
|
||||
opts =
|
||||
[:stream, :binary, :exit_status, :hide, :use_stdio, {:env, env}] ++
|
||||
args ++ Keyword.take(options, [:cd])
|
||||
|
||||
port = Port.open({:spawn, shell_command(command)}, opts)
|
||||
port_read(port, callback)
|
||||
end
|
||||
|
||||
@@ -162,8 +162,8 @@ defmodule Mix.Tasks.App.Start do
|
||||
{:error, {:file.format_error(:enoent), name}}
|
||||
|
||||
path ->
|
||||
case :file.consult(path) do
|
||||
{:ok, [{:application, _, properties} = application_data]} ->
|
||||
case consult_app_file_maybe_in_archive(path) do
|
||||
{:ok, {:application, _, properties} = application_data} ->
|
||||
with :ok <- :application.load(application_data) do
|
||||
if compile_env = validate_compile_env? && properties[:compile_env] do
|
||||
# Unfortunately we can only check the current app here,
|
||||
@@ -182,6 +182,19 @@ defmodule Mix.Tasks.App.Start do
|
||||
end
|
||||
end
|
||||
|
||||
defp consult_app_file_maybe_in_archive(path) do
|
||||
# The path could be located in .ez archive
|
||||
case :erl_prim_loader.get_file(path) do
|
||||
{:ok, bin, _full_name} ->
|
||||
with {:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
|
||||
:erl_parse.parse_term(tokens)
|
||||
end
|
||||
|
||||
:error ->
|
||||
{:error, :enoent}
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def type(config, opts) do
|
||||
cond do
|
||||
|
||||
@@ -22,6 +22,13 @@ defmodule Mix.Tasks.Cmd do
|
||||
This task is automatically reenabled, so it can be called multiple times
|
||||
with different arguments.
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--app` - limit running the command to the given app. This option
|
||||
may be given multiple times
|
||||
|
||||
* `--cd` - (since v1.11.0) the directory to run the command in
|
||||
|
||||
## Zombie operating system processes
|
||||
|
||||
Beware that the Erlang VM does not terminate child processes
|
||||
@@ -35,12 +42,20 @@ defmodule Mix.Tasks.Cmd do
|
||||
of the `Port` module documentation.
|
||||
"""
|
||||
|
||||
@switches [
|
||||
app: :keep,
|
||||
cd: :string
|
||||
]
|
||||
|
||||
@impl true
|
||||
def run(args) do
|
||||
{args, apps} = parse_apps(args, [])
|
||||
{opts, args} = OptionParser.parse_head!(args, strict: @switches)
|
||||
apps = Enum.map(List.wrap(opts[:app]), &String.to_atom/1)
|
||||
|
||||
if apps == [] or Mix.Project.config()[:app] in apps do
|
||||
case Mix.shell().cmd(Enum.join(args, " ")) do
|
||||
cmd_opts = Keyword.take(opts, [:cd])
|
||||
|
||||
case Mix.shell().cmd(Enum.join(args, " "), cmd_opts) do
|
||||
0 -> :ok
|
||||
status -> exit(status)
|
||||
end
|
||||
@@ -48,14 +63,4 @@ defmodule Mix.Tasks.Cmd do
|
||||
|
||||
Mix.Task.reenable("cmd")
|
||||
end
|
||||
|
||||
defp parse_apps(args, apps) do
|
||||
case args do
|
||||
["--app", app | tail] ->
|
||||
parse_apps(tail, [String.to_atom(app) | apps])
|
||||
|
||||
args ->
|
||||
{args, apps}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,11 @@ defmodule Mix.Tasks.Local.Hex do
|
||||
|
||||
mix local.hex
|
||||
|
||||
If installing a precompiled Hex does not work, you can compile and install
|
||||
Hex directly with this command:
|
||||
|
||||
mix archive.install github hexpm/hex branch latest
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--force` - forces installation without a shell prompt; primarily
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import Config
|
||||
|
||||
config :release_test, :static, :was_set
|
||||
config :release_test, :encoding, {:"£", "£", '£'}
|
||||
config :release_test, :encoding, {:time_μs, :"£", "£", '£'}
|
||||
|
||||
@@ -488,11 +488,15 @@ defmodule Mix.ReleaseTest do
|
||||
end
|
||||
|
||||
test "writes sys_config with encoding" do
|
||||
assert make_sys_config(release([]), [encoding: {:"£", "£", '£'}], "unused/runtime/path") ==
|
||||
assert make_sys_config(
|
||||
release([]),
|
||||
[encoding: {:time_μs, :"£", "£", '£'}],
|
||||
"unused/runtime/path"
|
||||
) ==
|
||||
:ok
|
||||
|
||||
{:ok, contents} = :file.consult(@sys_config)
|
||||
assert contents == [[encoding: {:"£", "£", '£'}]]
|
||||
assert contents == [[encoding: {:time_μs, :"£", "£", '£'}]]
|
||||
end
|
||||
|
||||
test "writes the given sys_config with config providers" do
|
||||
|
||||
@@ -17,4 +17,16 @@ defmodule Mix.ShellTest do
|
||||
after
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
end
|
||||
|
||||
test "with :cd" do
|
||||
Mix.shell(Mix.Shell.IO)
|
||||
tmp_dir = System.tmp_dir()
|
||||
{pwd, 0} = System.cmd("pwd", [], cd: tmp_dir)
|
||||
|
||||
assert ExUnit.CaptureIO.capture_io(fn ->
|
||||
Mix.shell().cmd("pwd", cd: tmp_dir)
|
||||
end) == pwd
|
||||
after
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,4 +36,16 @@ defmodule Mix.Tasks.CmdTest do
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "only runs the cmd for specified apps and in specific directory" do
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
Mix.Task.run("cmd", ["--app", "bar", "--cd", "lib", "pwd"])
|
||||
assert_received {:mix_shell, :info, ["==> bar"]}
|
||||
{pwd, 0} = System.cmd("pwd", [], cd: Path.join(["apps", "bar", "lib"]))
|
||||
assert_received {:mix_shell, :run, [^pwd]}
|
||||
refute_received {:mix_shell, :info, ["==> foo"]}
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -146,6 +146,39 @@ defmodule Mix.Tasks.CompileTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "calling raise inside a macro returns a diagnostic with a position" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
File.write!("lib/a.ex", """
|
||||
defmodule A do
|
||||
defmacro custom_macro do
|
||||
raise "error"
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
require A
|
||||
A.custom_macro()
|
||||
end
|
||||
""")
|
||||
|
||||
file = Path.absname("lib/b.ex")
|
||||
|
||||
ExUnit.CaptureIO.capture_io(fn ->
|
||||
assert {:error, [diagnostic]} = Mix.Task.run("compile", ["--return-errors"])
|
||||
|
||||
assert %Mix.Task.Compiler.Diagnostic{
|
||||
file: ^file,
|
||||
severity: :error,
|
||||
position: 3,
|
||||
message: "** (RuntimeError) error\n expanding macro: A.custom_macro/0" <> _,
|
||||
compiler_name: "Elixir"
|
||||
} = diagnostic
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "returns syntax error from an Erlang file when --return-errors is set" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
@@ -281,7 +281,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
assert %{
|
||||
app_dir: app_dir,
|
||||
cookie_env: ^cookie,
|
||||
encoding: {:"£", "£", '£'},
|
||||
encoding: {:time_μs, :"£", "£", '£'},
|
||||
mode: :embedded,
|
||||
node: release_node("release_test"),
|
||||
protocols_consolidated?: true,
|
||||
@@ -322,7 +322,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
File.write!("config/releases.exs", """
|
||||
import Config
|
||||
config :release_test, :runtime, :was_set
|
||||
config :release_test, :encoding, {:runtime, :"£", "£", '£'}
|
||||
config :release_test, :encoding, {:runtime, :time_μs, :"£", "£", '£'}
|
||||
""")
|
||||
|
||||
root = Path.absname("_build/dev/rel/runtime_config")
|
||||
@@ -348,7 +348,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
open_port(Path.join(root, "bin/runtime_config"), ['start'])
|
||||
|
||||
assert %{
|
||||
encoding: {:runtime, :"£", "£", '£'},
|
||||
encoding: {:runtime, :time_μs, :"£", "£", '£'},
|
||||
mode: :embedded,
|
||||
node: release_node("runtime_config"),
|
||||
protocols_consolidated?: true,
|
||||
|
||||
Reference in New Issue
Block a user