Compare commits

...
25 Commits
Author SHA1 Message Date
José Valim e0220bd267 Revert "Add branch docs publishing"
This reverts commit 6f5cc529d5.

Latest ExDoc no longer works with this version.
2023-05-30 11:49:19 +02:00
José Valim 6f5cc529d5 Add branch docs publishing 2023-05-30 11:43:21 +02:00
Alberto Sartori 3d80256852 Fix invalid example for using erlang logger handler (#10432) 2020-10-16 12:54:08 +02:00
tarreeandSamuel Smith 8731ef03d9 Adjust @type body for Registry to match erlang ets match_spec Result (#10017)
Co-authored-by: Samuel Smith <samuel.smith@se.com>
2020-07-14 12:33:39 +02:00
José Valim f8a2d05b57 Clarify to_unix docs 2020-07-14 12:33:25 +02:00
José Valim 1145dc0168 Release v1.10.4 2020-07-04 13:10:47 +02:00
José Valim 12f93df04f Revert "Add --repo to archive.install and escript.install (#10121)"
This reverts commit e9ea9dea7f.
2020-07-01 15:40:55 +02:00
José Valim 47a63a73b6 Update CHANGELOG 2020-07-01 15:40:51 +02:00
Cleidiano Oliveira d0ebaa7cdd Allow user to define a type named union (#10055) 2020-07-01 15:26:12 +02:00
Wojtek Mach e9ea9dea7f Add --repo to archive.install and escript.install (#10121) 2020-06-22 17:12:45 +02:00
Mitchell Henke 1feef9cb34 include callers in translated Logger metadata for Task (#10111) 2020-06-19 20:51:21 +02:00
Eksperimental 5c5fa57234 Update .cirrus.yml to latest Erlang/OTP versions and changes from master (#10101) 2020-06-15 19:36:55 +02:00
Wojtek Mach e891b1a831 Don't add compile-time dependency on defdelegate (#10093)
The dependency doesn't seem needed in this case.

We go through extra hoops to maintain dynamic invocations where
`opts` is not a compile-time list and :to is not a literal.

An easy way to see this optimization is:

    defmodule A do
      defdelegate f(), to: B
    end

    defmodule B do
      def f() do
        :ok
      end
    end

    $ touch lib/b.ex && mix compile --verbose
    Compiling 1 file (.ex)
    Compiled lib/b.ex

vs:

    defmodule A do
      opts = [to: B]
      defdelegate f(), opts
    end

    defmodule B do
      def f() do
        :ok
      end
    end

    $ touch lib/b.ex && mix compile --verbose
    Compiling 2 files (.ex)
    Compiled lib/b.ex
    Compiled lib/a.ex
2020-06-11 16:02:19 +02:00
José Valim 001a5eb2e8 Add line numbers to deprecated imports 2020-06-11 16:01:46 +02:00
José Valim 6085ed0288 Do not diff guards if pattern does not match 2020-06-04 20:15:35 +02:00
José Valim 57be943f91 Do not raise on foreign types, closes #10077 2020-06-02 15:06:29 +02:00
Waldemar Rachwał 99cad598a8 Fix read of app file located in archive .ez file (#10062) 2020-05-27 17:59:14 +02:00
José Valim ef4c926299 Provide exception blaming to linked and trapped exits in ExUnit 2020-05-22 11:00:46 +02:00
José Valim d1acad0155 Add extra suggestions when Hex can't be installed, closes #10039 2020-05-18 10:05:09 +02:00
Dallin Osmun 4a69a00c71 Report the correct line number when raising inside a macro (#10040) 2020-05-16 09:33:15 +02:00
José Valim a9ad1f1b2a v1.10.3 is compatible with Erlang/OTP 23 2020-05-13 12:54:37 +02:00
Wojtek Mach 53e3d8f55e Add :cd option to Mix.Shell.cmd/2 and --cd to mix cmd (#10029) 2020-05-13 09:48:29 +02:00
Ryan Zidago f53b8c822d Fix Task PID and caller in Task Supervisor reports (#10025)
Closes #10024
2020-05-09 01:14:29 +02:00
José Valim cdd312b0ca Support proper encoding in releases config (#10012) 2020-05-01 20:14:18 +02:00
José Valim 0cc53735a1 Force modules to be loaded when they are required 2020-04-26 11:02:46 +02:00
40 changed files with 539 additions and 122 deletions
+4 -7
View File
@@ -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
+28
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
1.10.3
1.10.4
+4
View File
@@ -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()
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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]
+12
View File
@@ -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)
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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}]
+4 -3
View File
@@ -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.
+5 -4
View File
@@ -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" => """
+10
View File
@@ -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))))
+48 -23
View File
@@ -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
+9 -4
View File
@@ -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}
+60 -30
View File
@@ -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"
+1
View File
@@ -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
+110
View File
@@ -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"), []}]
+9 -3
View File
@@ -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
+6
View File
@@ -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])
+3 -2
View File
@@ -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:
+28 -3
View File
@@ -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
+19 -4
View File
@@ -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])
+4 -4
View File
@@ -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 """
+8 -1
View File
@@ -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
+15 -2
View File
@@ -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
+17 -12
View File
@@ -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
+5
View File
@@ -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
View File
@@ -1,4 +1,4 @@
import Config
config :release_test, :static, :was_set
config :release_test, :encoding, {:"£", "£", '£'}
config :release_test, :encoding, {:time_μs, :"£", "£", '£'}
+6 -2
View File
@@ -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
+12
View File
@@ -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
+12
View File
@@ -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
+33
View File
@@ -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
+3 -3
View File
@@ -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,