Compare commits

...
30 Commits
Author SHA1 Message Date
José Valim 07450832e7 Release v1.10.2 2020-02-26 12:53:01 +01:00
José Valim 34adbcb95a Fix unused variable warning 2020-02-26 12:17:23 +01:00
José Valim 286d310ac3 Fixes for Erlang/OTP 23 2020-02-26 12:05:47 +01:00
José Valim 2fc916088a Rewrite stacktraces for compatibility with upcoming OTP version 2020-02-24 21:36:51 +01:00
José Valim 9a428e9cdd Fix bootstrap 2020-02-13 12:16:15 +01:00
José Valim 090dddcf00 Only escape contents outside of interpolation, closes #9812 2020-02-12 20:51:58 +01:00
José Valim 089c470e13 Only print metadata of known types, closes #9814 2020-02-12 19:03:59 +01:00
José Valim 68481b7377 Do not crash on fake down messages on stream monitor, closes #9803 2020-02-12 18:31:25 +01:00
José Valim e2dc9ae72e Do not write files to disk if warnings as errors was triggered, closes #9808 2020-02-12 18:00:50 +01:00
José Valim 51c95b6000 Release v1.10.1 2020-02-10 12:42:26 +01:00
José Valim 12917b00e5 Unpack extra guards from with matches, closes #9801 2020-02-09 18:15:43 +01:00
Łukasz Jan Niemier ab9ecf2d7f fix(logger): use correct return value from report_cb/2 (#9798)
Earlier it assumed that the return type is the same as in `report_cb/1`
which is not true. `report_cb/1` returns format string and data while
`report_cb/2` returns already formatted string.
2020-02-09 09:35:02 +01:00
José Valim eca809285e Ensure assert_received still produces valid exceptions, closes #9799 2020-02-09 09:32:40 +01:00
José Valim 8e71e65aae Fix typo 2020-02-08 08:31:09 +01:00
José Valim c3a27be073 Do not imply nil is caused by Access 2020-02-08 01:58:44 +01:00
José Valim e10f9fb6fe Revert "Emit warnings for unused match vars in assertions, closes #9787"
Revert it as we may have false positives in v1.10.
We are still keeping it on master.

This reverts commit 696bad8de1.
2020-02-06 20:26:29 +01:00
José Valim f66ffd0733 Add missing :erlang.size/1 guard, closes #9789 2020-02-06 18:45:01 +01:00
José Valim 696bad8de1 Emit warnings for unused match vars in assertions, closes #9787 2020-02-06 18:35:21 +01:00
Joel C 96c8e31410 Quote variables in cli_bat_text install section (#9780)
This allows the release batch script to be run in a path that
includes spaces.
2020-02-04 14:45:46 +01:00
José Valim 70be440359 Only warn for commas at the end of words 2020-02-04 13:24:50 +01:00
José Valim c6d6683225 Add tests for timer and interval with infinity 2020-02-03 12:41:54 +01:00
José Valim db2504eca6 Do not emit invalid code when formatting assocs in maps, closes #9771 2020-02-01 11:18:42 +01:00
José Valim 98c3f5c602 Do not validate line in quote (moved to v1.11) 2020-01-31 11:23:25 +01:00
Akash Hiremath db4684b2f5 Fix logger truncate for multi-byte characters (#9767) 2020-01-31 10:44:45 +01:00
José Valim f3f65e4cd1 No longer rebroadcast messages as this is taken care by Erlang's logger
Closes #9765.
2020-01-30 17:02:43 +01:00
Piotr Szmielew 5331174934 Allow using remote or rpc with compile_env
Signed-off-by: José Valim <jose.valim@dashbit.co>
2020-01-30 12:05:47 +01:00
Devon Estes f4f2be67d7 Fix docs for compiler tracing events (#9760)
The `local_function` event is a tuple with four elements, not five.
2020-01-30 11:44:12 +01:00
Theo Dowling 686daadaaa Fix typo in CHANGELOG (#9757) 2020-01-28 07:37:53 +01:00
José Valim 6e8563e930 Add space to quote error message 2020-01-27 12:50:23 +01:00
José Valim 65be24a8c2 CHANGELOG fixes 2020-01-27 10:24:23 +01:00
43 changed files with 441 additions and 297 deletions
+50 -4
View File
@@ -74,7 +74,7 @@ iex> Enum.sort([~D[2019-12-31], ~D[2020-01-01]], {:desc, Date})
These API improvements make the code more concise and readable and they have also been added to `Enum.sort_by`, `Enum.min_by`, `Enum.max_by`, and friends.
### Tracking of compile-time configuration
## Tracking of compile-time configuration
In Elixir, we organize our code in applications. Libraries, your dependencies, and your own project are all separate applications. All applications in Elixir also come with an application environment.
@@ -124,7 +124,7 @@ By using `compile_env/3`, Elixir will store the values used during compilation a
In future versions, we will deprecate the use `Application.get_env` at compile-time with a clear message pointing users to configuration best practices, effectively addressing the scenario where users read from the application environment at compile time unaware of its pitfalls.
### Compiler tracing
## Compiler tracing
This release brings enhancements to the Elixir compiler and adds new capabilities for developers to listen to compilation events.
@@ -141,7 +141,7 @@ Elixir itself is using the new compiler tracing to provide new functionality. On
Previously, this information had to be added to the overall project configuration, which was far away from where the optional call effectively happened.
### Other enhancements
## Other enhancements
Elixir's calendar data types got many improvements, such as sigil support for third-party calendars, as well as the additions of `DateTime.now!/2`, `DateTime.shift_zone!/3`, and `NaiveDateTime.local_now/0`.
@@ -153,7 +153,51 @@ ExUnit, our test framework, ships two small but important improvements: `ExUnit.
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 agaainst a pattern.
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.2 (2020-02-26)
### 1. Bug fixes
#### Elixir
* [Macro] Fix a bug where `Macro.to_string/1` would emit invalid code for sigils
* [Task] Do not crash `async_stream` monitor if it receives spurious DOWN messages
#### Logger
* [Logger] Fix a bug where the Logger formatter would fail when handling unknown metadata values
#### Mix
* [mix compile] Do not write files to disk if `--warnings-as-errors` was given and warnings were emitted
## v1.10.1 (2020-02-10)
### 1. Bug fixes
#### Elixir
* [Code] Do not emit invalid code when formatting `nil`, `false`, and `true` keys in maps
* [Kernel] Ensure `with` clauses properly unpack "implicit guards" (such as matching on the struct name)
* [Kernel] Do not warn if commas are used by themselves in `~w`/`~W` sigils
* [Kernel] Do not validate the `:line` option in quote (the validation has been moved to v1.11 to give users more time to update their code)
* [Module] Ensure the code verifier handles the `:erlang.size/1` guard properly
#### Logger
* [Logger] Properly handle the `report_cb/2` option from Erlang
* [Logger] Fix truncation for multi-byte characters
* [Logger] Do not rebroadcast messages from remote nodes as this is now taken care by Erlang's logger
#### ExUnit
* [ExUnit] Ensure `assert_receive` produces valid exception messages in case of errors
#### Mix
* [mix release] Make sure the install command (Window specific) works on paths with spaces in the name
* [mix release] Allow using `remote` and `rpc` commands with `Application.compile_env/3`
## v1.10.0 (2020-01-27)
@@ -179,6 +223,7 @@ Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the
* [Kernel] Warn when function head comes immediately after the implementation instead of before the implementation
* [Kernel] Warn if duplicate key is found in struct declaration
* [Kernel] Print all undefined functions as warnings and then raise. This allows users to see all undefined calls at once, when it would otherwise require them to compile the code multiple times
* [Kernel] Allow file, line and context to be dynamically set on `quote`
* [Keyword] Add `Keyword.pop!/2` and `Keyword.pop_values/2`
* [Map] Add `Map.pop!/2`
* [MapSet] Optimize multiple operations
@@ -227,6 +272,7 @@ Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the
* [Enum] Allow positive range slices on infinite streams given to `Enum.slice/2`
* [Kernel] Raise error on functions/guards without implementation
* [Kernel] Do not expand expressions inside interpolation twice
* [Keyword] Ensure keyword replace and update preserve order
* [Module] Raise instead of silently failing when performing a write module operation during after-compile
* [Module] Fix `@macrocallback` definitions with a `when` clause
+1 -1
View File
@@ -1 +1 @@
1.10.0
1.10.2
+2 -2
View File
@@ -75,8 +75,8 @@ defmodule Code do
function or macro is referenced. `meta` is the call AST metadata, `module`
is the invoked module, followed by the `name` and `arity`.
* `{:local_function, meta, module, name, arity}` and
`{:local_macro, meta, module, name, arity}` - traced whenever a local
* `{:local_function, meta, name, arity}` and
`{:local_macro, meta, name, arity}` - traced whenever a local
function or macro is referenced. `meta` is the call AST metadata, `module`
is the invoked module, followed by the `name` and `arity`.
+2 -2
View File
@@ -2239,10 +2239,10 @@ defmodule Code.Formatter do
defp keyword?(rest), do: rest == []
defp keyword_key?({:__block__, meta, [atom]}) when is_atom(atom),
do: meta[:delimiter] != ":"
do: meta[:format] == :keyword
defp keyword_key?({{:., _, [:erlang, :binary_to_atom]}, meta, [{:<<>>, _, _}, :utf8]}),
do: meta[:delimiter] != ":"
do: meta[:format] == :keyword
defp keyword_key?(_),
do: false
+65 -44
View File
@@ -110,7 +110,8 @@ defmodule Config.Provider do
:config_path,
extra_config: [],
prune_after_boot: false,
reboot_after_config: true
reboot_after_config: true,
validate_compile_env: false
]
@doc """
@@ -165,54 +166,74 @@ defmodule Config.Provider do
# before we go around running Elixir code.
{:ok, _} = :application.ensure_all_started(:elixir)
case :application.get_env(app, key) do
{:ok, %Config.Provider{} = provider} ->
path = resolve_config_path!(provider.config_path)
validate_no_cyclic_boot!(path)
loaded_applications = :application.loaded_applications()
original_config = read_config!(path)
# The key we store if the system already booted
booted_key = :"#{key}_booted"
config =
original_config
|> Config.__merge__(provider.extra_config)
|> run_providers(provider)
case :application.get_env(app, booted_key) do
{:ok, {:booted, path}} ->
path && File.rm(path)
if provider.reboot_after_config do
config
|> Config.__merge__([{app, [{key, booted_key(provider, path)}]}])
|> write_config!(path)
restart_fun.()
else
for {app, _, _} <- loaded_applications, config[app] != original_config[app] do
abort("""
Cannot configure #{inspect(app)} because :reboot_after_config has been set \
to false and #{inspect(app)} has already been loaded, meaning any further \
configuration won't have an effect.
The configuration for #{inspect(app)} before config providers was:
#{inspect(original_config[app])}
The configuration for #{inspect(app)} after config providers was:
#{inspect(config[app])}
""")
end
_ = Application.put_all_env(config, persistent: true)
:ok
with {:ok, %Config.Provider{} = provider} <- :application.get_env(app, key) do
maybe_validate_compile_env(provider)
end
{:ok, {:booted, path}} ->
File.rm(path)
:booted
{:ok, :booted} ->
:booted
_ ->
:skip
case :application.get_env(app, key) do
{:ok, %Config.Provider{} = provider} ->
path = resolve_config_path!(provider.config_path)
reboot_config = [{app, [{booted_key, booted_value(provider, path)}]}]
boot_providers(path, provider, reboot_config, restart_fun)
_ ->
:skip
end
end
end
defp boot_providers(path, provider, reboot_config, restart_fun) do
validate_no_cyclic_boot!(path)
loaded_applications = :application.loaded_applications()
original_config = read_config!(path)
config =
original_config
|> Config.__merge__(provider.extra_config)
|> run_providers(provider)
if provider.reboot_after_config do
config
|> Config.__merge__(reboot_config)
|> write_config!(path)
restart_fun.()
else
for {app, _, _} <- loaded_applications, config[app] != original_config[app] do
abort("""
Cannot configure #{inspect(app)} because :reboot_after_config has been set \
to false and #{inspect(app)} has already been loaded, meaning any further \
configuration won't have an effect.
The configuration for #{inspect(app)} before config providers was:
#{inspect(original_config[app])}
The configuration for #{inspect(app)} after config providers was:
#{inspect(config[app])}
""")
end
_ = Application.put_all_env(config, persistent: true)
maybe_validate_compile_env(provider)
:ok
end
end
defp maybe_validate_compile_env(provider) do
with [_ | _] = compile_env <- provider.validate_compile_env do
validate_compile_env(compile_env)
end
end
@@ -283,8 +304,8 @@ defmodule Config.Provider do
Process.sleep(:infinity)
end
defp booted_key(%{prune_after_boot: true}, path), do: {:booted, path}
defp booted_key(%{prune_after_boot: false}, _path), do: :booted
defp booted_value(%{prune_after_boot: true}, path), do: {:booted, path}
defp booted_value(%{prune_after_boot: false}, _path), do: {:booted, nil}
defp validate_no_cyclic_boot!(path) do
if System.get_env("ELIXIR_CONFIG_PROVIDER_BOOTED") do
+3 -6
View File
@@ -2263,8 +2263,7 @@ defmodule Kernel do
iex> get_in(users, ["john", :age])
27
In case any of the entries in the middle returns `nil`, `nil` will
be returned as per the `Access` module:
In case any of the keys returns `nil`, `nil` will be returned:
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["unknown", :age])
@@ -2280,9 +2279,6 @@ defmodule Kernel do
* a function to be invoked next
This means `get_in/2` can be extended to provide custom lookups.
The downside is that functions cannot be stored as keys in the accessed
data structures.
In the example below, we use a function to get all the maps inside
a list:
@@ -5388,7 +5384,8 @@ defmodule Kernel do
true ->
parts = String.split(string)
parts_with_trailing_comma = :lists.filter(&(:binary.last(&1) == ?,), parts)
parts_with_trailing_comma =
:lists.filter(&(byte_size(&1) > 1 and :binary.last(&1) == ?,), parts)
if parts_with_trailing_comma != [] do
stacktrace = Macro.Env.stacktrace(caller)
+78 -72
View File
@@ -139,15 +139,15 @@ defmodule Kernel.ParallelCompiler do
compiler_pid = self()
:elixir_code_server.cast({:reset_warnings, compiler_pid})
schedulers = max(:erlang.system_info(:schedulers_online), 2)
beam_timestamp = Keyword.get(options, :beam_timestamp)
result =
outcome =
spawn_workers(files, 0, [], [], %{}, [], %{
dest: Keyword.get(options, :dest),
each_cycle: Keyword.get(options, :each_cycle, fn -> {:runtime, []} end),
each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
each_long_compilation: Keyword.get(options, :each_long_compilation, fn _file -> :ok end),
each_module: Keyword.get(options, :each_module, fn _file, _module, _binary -> :ok end),
beam_timestamp: Keyword.get(options, :beam_timestamp),
long_compilation_threshold: Keyword.get(options, :long_compilation_threshold, 15),
profile: Keyword.get(options, :profile),
cycle_start: System.monotonic_time(),
@@ -160,7 +160,7 @@ defmodule Kernel.ParallelCompiler do
# compilation status will be set to error.
compilation_status = :elixir_code_server.call({:compilation_status, compiler_pid})
case {result, compilation_status} do
case {outcome, compilation_status} do
{{:ok, _, warnings}, :error} ->
message = "Compilation failed due to warnings while using the --warnings-as-errors option"
IO.puts(:stderr, message)
@@ -169,8 +169,11 @@ defmodule Kernel.ParallelCompiler do
{{:error, errors, warnings}, :error} ->
{:error, errors ++ warnings, []}
_ ->
result
{{:ok, outcome, warnings}, _} ->
{:ok, write_module_binaries(outcome, output, beam_timestamp), warnings}
{{:error, errors, warnings}, _} ->
{:error, errors, warnings}
end
end
@@ -187,6 +190,74 @@ defmodule Kernel.ParallelCompiler do
end
end
defp write_module_binaries(result, {:compile, path}, timestamp) do
Enum.flat_map(result, fn
{{:module, module}, {binary, _map}} ->
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
File.write!(full_path, binary)
if timestamp, do: File.touch!(full_path, timestamp)
[module]
_ ->
[]
end)
end
defp write_module_binaries(result, _output, _timestamp) do
for {{:module, module}, _} <- result, do: module
end
## Verification
defp verify_modules(result, warnings, dependent_modules, state) do
checker_warnings = maybe_check_modules(result, dependent_modules, state)
warnings = Enum.reverse(warnings, checker_warnings)
{:ok, result, warnings}
end
defp maybe_check_modules(result, runtime_modules, state) do
%{schedulers: schedulers, profile: profile} = state
if :elixir_config.get(:bootstrap) do
[]
else
compiled_modules = checker_compiled_modules(result)
runtime_modules = checker_runtime_modules(runtime_modules)
profile_checker(profile, compiled_modules, runtime_modules, fn ->
Module.ParallelChecker.verify(compiled_modules, runtime_modules, schedulers)
end)
end
end
defp checker_compiled_modules(result) do
for {{:module, _module}, {binary, module_map}} <- result do
{module_map, binary}
end
end
defp checker_runtime_modules(modules) do
for module <- modules,
path = :code.which(module),
is_list(path) do
{module, File.read!(path)}
end
end
defp profile_checker(_profile = :time, compiled_modules, runtime_modules, fun) do
{time, result} = :timer.tc(fun)
time = div(time, 1000)
num_modules = length(compiled_modules) + length(runtime_modules)
IO.puts(:stderr, "[profile] Finished group pass check of #{num_modules} modules in #{time}ms")
result
end
defp profile_checker(_profile = nil, _compiled_modules, _runtime_modules, fun) do
fun.()
end
## Compiler worker spawning
# We already have n=schedulers currently running, don't spawn new ones
defp spawn_workers(
queue,
@@ -253,10 +324,10 @@ defmodule Kernel.ParallelCompiler do
case each_cycle_return(state.each_cycle.()) do
{:runtime, dependent_modules} ->
write_and_verify_modules(result, warnings, dependent_modules, state)
verify_modules(result, warnings, dependent_modules, state)
{:compile, []} ->
write_and_verify_modules(result, warnings, [], state)
verify_modules(result, warnings, [], state)
{:compile, more} ->
spawn_workers(more, 0, [], [], result, warnings, state)
@@ -354,71 +425,6 @@ defmodule Kernel.ParallelCompiler do
defp each_cycle_return(modules) when is_list(modules), do: {:compile, modules}
defp each_cycle_return(other), do: other
defp write_and_verify_modules(result, warnings, dependent_modules, state) do
modules = write_module_binaries(result, state)
checker_warnings = maybe_check_modules(result, dependent_modules, state)
warnings = Enum.reverse(warnings, checker_warnings)
{:ok, modules, warnings}
end
defp write_module_binaries(result, %{output: {:compile, path}, beam_timestamp: timestamp}) do
Enum.flat_map(result, fn
{{:module, module}, {binary, _map}} ->
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
File.write!(full_path, binary)
if timestamp, do: File.touch!(full_path, timestamp)
[module]
_ ->
[]
end)
end
defp write_module_binaries(result, _state) do
for {{:module, module}, _} <- result, do: module
end
defp maybe_check_modules(result, runtime_modules, state) do
%{schedulers: schedulers, profile: profile} = state
if :elixir_config.get(:bootstrap) do
[]
else
compiled_modules = checker_compiled_modules(result)
runtime_modules = checker_runtime_modules(runtime_modules)
profile_checker(profile, compiled_modules, runtime_modules, fn ->
Module.ParallelChecker.verify(compiled_modules, runtime_modules, schedulers)
end)
end
end
defp checker_compiled_modules(result) do
for {{:module, _module}, {binary, module_map}} <- result do
{module_map, binary}
end
end
defp checker_runtime_modules(modules) do
for module <- modules,
path = :code.which(module),
is_list(path) do
{module, File.read!(path)}
end
end
defp profile_checker(_profile = :time, compiled_modules, runtime_modules, fun) do
{time, result} = :timer.tc(fun)
time = div(time, 1000)
num_modules = length(compiled_modules) + length(runtime_modules)
IO.puts(:stderr, "[profile] Finished group pass check of #{num_modules} modules in #{time}ms")
result
end
defp profile_checker(_profile = nil, _compiled_modules, _runtime_modules, fun) do
fun.()
end
# The goal of this function is to find leaves in the dependency graph,
# i.e. to find code that depends on code that we know is not being defined.
defp without_definition(waiting, files) do
+6 -5
View File
@@ -163,8 +163,9 @@ defmodule Macro do
with parens. The `:closing` does not delimit the end of expression if
there are `:do` and `:end` metadata (when `:token_metadata` is true)
* `:column` - the column number of the AST node (when `:columns` is true)
* `:delimiter` - contains the opening delimiter for sigils, strings, atoms,
and charlists as a string (such as `"{"`, `"/"`, `":"`, and the like)
* `:delimiter` - contains the opening delimiter for sigils, strings,
and charlists as a string (such as `"{"`, `"/"`, `"'"`, and the like)
* `:format` - set to `:keyword` when an atom is defined as a keyword
* `:do` - contains metadata about the `do` location in a function call with
`do/end` blocks (when `:token_metadata` is true)
* `:end` - contains metadata about the `end` location in a function call with
@@ -1021,11 +1022,11 @@ defmodule Macro do
binary when is_binary(binary) ->
binary = inspect_no_limit(binary)
binary_part(binary, 1, byte_size(binary) - 2)
binary = binary_part(binary, 1, byte_size(binary) - 2)
escape_sigil(binary, left)
end)
escaped = escape_sigil(parts, left)
<<left::binary, escaped::binary, right::binary>>
<<left::binary, parts::binary, right::binary>>
end
defp escape_sigil(parts, "("), do: String.replace(parts, ")", ~S"\)")
-2
View File
@@ -265,8 +265,6 @@ defmodule Map do
iex> Map.fetch!(%{a: 1}, :a)
1
iex> Map.fetch!(%{a: 1}, :b)
** (KeyError) key :b not found in: %{a: 1}
"""
@spec fetch!(map, key) :: value
+1
View File
@@ -275,6 +275,7 @@ defmodule Module.Types.Pattern do
{:binary_part, 3} => {[:binary, :integer, :integer], :binary},
{:bit_size, 1} => {[:binary], :integer},
{:byte_size, 1} => {[:binary], :integer},
{:size, 1} => {[{:union, [:binary, :tuple]}], :boolean},
{:div, 2} => {[:integer, :integer], :integer},
{:rem, 2} => {[:integer, :integer], :integer},
{:node, 0} => {[], :atom},
+9 -4
View File
@@ -107,6 +107,7 @@ defmodule Stream do
@type index :: non_neg_integer
@type default :: any
@type timer :: non_neg_integer | :infinity
# Require Stream.Reducers and its callbacks
require Stream.Reducers, as: R
@@ -505,8 +506,10 @@ defmodule Stream do
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
"""
@spec interval(non_neg_integer) :: Enumerable.t()
def interval(n) when is_integer(n) and n >= 0 do
@spec interval(timer()) :: Enumerable.t()
def interval(n)
when is_integer(n) and n >= 0
when n == :infinity do
unfold(0, fn count ->
Process.sleep(n)
{count, count + 1}
@@ -787,8 +790,10 @@ defmodule Stream do
[0]
"""
@spec timer(non_neg_integer) :: Enumerable.t()
def timer(n) when is_integer(n) and n >= 0 do
@spec timer(timer()) :: Enumerable.t()
def timer(n)
when is_integer(n) and n >= 0
when n == :infinity do
take(interval(n), 1)
end
+1 -1
View File
@@ -539,7 +539,7 @@ defmodule Task.Supervised do
# One of the spawned processes went down. We inform the parent process of
# this and keep going.
{:DOWN, ref, _, _, reason} ->
{:DOWN, ref, _, _, reason} when is_map_key(running_tasks, ref) ->
{task, running_tasks} = Map.pop(running_tasks, ref)
%{position: position, timer_ref: timer_ref, timed_out?: timed_out?} = task
+14 -12
View File
@@ -97,6 +97,20 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
Callback()
end.
dispatch_require(Meta, 'Elixir.System', stacktrace, [], #{contextual_vars := Vars} = E, Callback) ->
case lists:member('__STACKTRACE__', Vars) of
true ->
{{'__STACKTRACE__', [], nil}, E};
false ->
Message =
"System.stacktrace/0 outside of rescue/catch clauses is deprecated. "
"If you want to support only Elixir v1.7+, you must access __STACKTRACE__ "
"inside a rescue/catch. If you want to support earlier Elixir versions, "
"move System.stacktrace/0 inside a rescue/catch",
elixir_errors:erl_warn(?line(Meta), ?key(E, file), Message),
Callback('Elixir.System', stacktrace, [])
end;
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
Arity = length(Args),
@@ -353,18 +367,6 @@ check_deprecated(_, erlang, _, _, _) ->
ok;
check_deprecated(_, _, _, _, #{module := 'Elixir.HashDict'}) ->
ok;
check_deprecated(Meta, 'Elixir.System', stacktrace, 0, #{contextual_vars := Vars} = E) ->
case lists:member('__STACKTRACE__', Vars) of
true ->
ok;
false ->
Message =
"System.stacktrace/0 outside of rescue/catch clauses is deprecated. "
"If you want to support only Elixir v1.7+, you must access __STACKTRACE__ "
"inside a rescue/catch. If you want to support earlier Elixir versions, "
"move System.stacktrace/0 inside a rescue/catch",
elixir_errors:erl_warn(?line(Meta), ?key(E, file), Message)
end;
check_deprecated(Meta, Receiver, Name, Arity, E) ->
case (?key(E, function) == nil) andalso is_ensure_loaded(Receiver) of
true ->
+2 -7
View File
@@ -54,13 +54,8 @@ definition_to_anonymous(Module, Kind, Meta, Clauses) ->
{value, Result, _Binding} = erl_eval:expr(Fun, [], {value, LocalHandler}),
Result.
invoke_local(Module, RawName, Args) ->
%% If we have a macro, its arity in the table is
%% actually one less than in the function call
{Name, Arity} = case atom_to_list(RawName) of
"MACRO-" ++ Rest -> {list_to_atom(Rest), length(Args) - 1};
_ -> {RawName, length(Args)}
end,
invoke_local(Module, ErlName, Args) ->
{Name, Arity} = elixir_utils:erl_fa_to_elixir_fa(ErlName, length(Args)),
case elixir_def:local_for(Module, Name, Arity, all) of
false ->
+8
View File
@@ -134,6 +134,14 @@ custom_format(sys_core_fold, {eval_failure, Error}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
["this expression will fail with ", elixir_aliases:inspect(Struct)];
custom_format(sys_core_fold, {nomatch_shadow,Line,{ErlName,ErlArity}}) ->
{Name, Arity} = elixir_utils:erl_fa_to_elixir_fa(ErlName, ErlArity),
io_lib:format(
"this clause for ~ts/~B cannot match because a previous clause at line ~B always matches",
[Name, Arity, Line]
);
custom_format([], Desc) ->
io_lib:format("~p", [Desc]);
+2 -2
View File
@@ -409,8 +409,8 @@ translate_with_do([{'<-', Meta, [Left, Expr]} | Rest], Do, Else, S) ->
{Args, Guards} = elixir_utils:extract_guards(Left),
{TExpr, SR} = elixir_erl_pass:translate(Expr, S),
{TArgs, SA} = elixir_erl_clauses:match(fun elixir_erl_pass:translate/2, Args, SR),
TGuards = elixir_erl_clauses:guards(Guards, [], SA),
{TBody, SB} = translate_with_do(Rest, Do, Else, SA),
TGuards = elixir_erl_clauses:guards(Guards, SA#elixir_erl.extra_guards, SA),
{TBody, SB} = translate_with_do(Rest, Do, Else, SA#elixir_erl{extra_guards=[]}),
Clause = {clause, ?ann(Meta), [TArgs], TGuards, unblock(TBody)},
{{'case', ?ann(?generated(Meta)), TExpr, [Clause, Else]}, SB};
+9 -9
View File
@@ -259,9 +259,9 @@ access_expr -> bin_heredoc : build_bin_heredoc('$1').
access_expr -> list_heredoc : build_list_heredoc('$1').
access_expr -> bit_string : '$1'.
access_expr -> sigil : build_sigil('$1').
access_expr -> atom : handle_literal(?exprs('$1'), '$1', delimiter(<<$:>>)).
access_expr -> atom_safe : build_quoted_atom('$1', true, delimiter(<<$:>>)).
access_expr -> atom_unsafe : build_quoted_atom('$1', false, delimiter(<<$:>>)).
access_expr -> atom : handle_literal(?exprs('$1'), '$1').
access_expr -> atom_safe : build_quoted_atom('$1', true, []).
access_expr -> atom_unsafe : build_quoted_atom('$1', false, []).
access_expr -> dot_alias : '$1'.
access_expr -> parens_call : '$1'.
@@ -526,12 +526,12 @@ call_args_parens -> open_paren call_args_parens_base ',' kw_base ',' close_paren
% KV
kw_eol -> kw_identifier : handle_literal(?exprs('$1'), '$1').
kw_eol -> kw_identifier eol : handle_literal(?exprs('$1'), '$1').
kw_eol -> kw_identifier_safe : build_quoted_atom('$1', true, []).
kw_eol -> kw_identifier_safe eol : build_quoted_atom('$1', true, []).
kw_eol -> kw_identifier_unsafe : build_quoted_atom('$1', false, []).
kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false, []).
kw_eol -> kw_identifier : handle_literal(?exprs('$1'), '$1', [{format, keyword}]).
kw_eol -> kw_identifier eol : handle_literal(?exprs('$1'), '$1', [{format, keyword}]).
kw_eol -> kw_identifier_safe : build_quoted_atom('$1', true, [{format, keyword}]).
kw_eol -> kw_identifier_safe eol : build_quoted_atom('$1', true, [{format, keyword}]).
kw_eol -> kw_identifier_unsafe : build_quoted_atom('$1', false, [{format, keyword}]).
kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false, [{format, keyword}]).
kw_base -> kw_eol container_expr : [{'$1', '$2'}].
kw_base -> kw_base ',' kw_eol container_expr : [{'$3', '$4'} | '$1'].
+5 -3
View File
@@ -36,6 +36,8 @@ build(Meta, Line, File, Context, Unquote, Generated) ->
{Q, Acc3}.
validate_compile(_Meta, file, nil, Acc) ->
{nil, Acc};
validate_compile(Meta, Key, Value, Acc) ->
case is_valid(Key, Value) of
true ->
@@ -55,13 +57,13 @@ validate_runtime(Key, Value) ->
erlang:error(
'Elixir.ArgumentError':exception(
<<"invalid value for option :", (erlang:atom_to_binary(Key, utf8))/binary,
"in quote, got: ", ('Elixir.Kernel':inspect(Value))/binary>>
" in quote, got: ", ('Elixir.Kernel':inspect(Value))/binary>>
)
)
end.
is_valid(line, Line) -> is_integer(Line) orelse is_boolean(Line);
is_valid(file, File) -> is_binary(File) orelse (File == nil);
is_valid(line, _Line) -> true;
is_valid(file, File) -> is_binary(File);
is_valid(context, Context) -> is_atom(Context) andalso (Context /= nil);
is_valid(generated, Generated) -> is_boolean(Generated);
is_valid(unquote, Unquote) -> is_boolean(Unquote).
+7 -3
View File
@@ -7,15 +7,19 @@
read_file_type/1, read_file_type/2, read_link_type/1, read_posix_mtime_and_size/1,
change_posix_time/2, change_universal_time/2,
guard_op/2, extract_splat_guards/1, extract_guards/1,
erlang_comparison_op_to_elixir/1]).
erlang_comparison_op_to_elixir/1, erl_fa_to_elixir_fa/2]).
-include("elixir.hrl").
-include_lib("kernel/include/file.hrl").
% Builds the macro name
macro_name(Macro) ->
list_to_atom("MACRO-" ++ atom_to_list(Macro)).
erl_fa_to_elixir_fa(Name, Arity) ->
case atom_to_list(Name) of
"MACRO-" ++ Rest -> {list_to_atom(Rest), Arity - 1};
_ -> {Name, Arity}
end.
guard_op('andalso', 2) ->
true;
guard_op('orelse', 2) ->
@@ -412,6 +412,11 @@ defmodule Code.Formatter.ContainersTest do
assert_same map, @medium_length
end
test "preserves user choice in regards to =>" do
assert_same "%{:hello => 1, :world => 2}"
assert_format "%{:true => 1, :false => 2}", "%{true => 1, false => 2}"
end
test "preserves user choice even when it fits" do
assert_same """
%{
+21 -20
View File
@@ -338,20 +338,22 @@ defmodule CodeTest do
assert string_to_quoted.("0xEF") == {:__block__, [token: "0xEF", line: 1], [239]}
assert string_to_quoted.("12.3") == {:__block__, [token: "12.3", line: 1], [12.3]}
assert string_to_quoted.("nil") == {:__block__, [line: 1], [nil]}
assert string_to_quoted.(":one") == {:__block__, [delimiter: ":", line: 1], [:one]}
assert string_to_quoted.(":one") == {:__block__, [line: 1], [:one]}
args = [[{:__block__, [token: "1", line: 1], [1]}]]
assert string_to_quoted.("[one: :two]") == {
:__block__,
[{:closing, [line: 1]}, {:line, 1}],
[
[
{{:__block__, [format: :keyword, line: 1], [:one]},
{:__block__, [line: 1], [:two]}}
]
]
}
assert string_to_quoted.("[1]") ==
{:__block__, [closing: [line: 1], line: 1], args}
args = [
{{:__block__, [delimiter: ":", line: 1], [:ok]},
{:__block__, [delimiter: ":", line: 1], [:test]}}
]
assert string_to_quoted.("{:ok, :test}") ==
{:__block__, [closing: [line: 1], line: 1], args}
{:__block__, [closing: [line: 1], line: 1],
[[{:__block__, [token: "1", line: 1], [1]}]]}
assert string_to_quoted.(~s("""\nhello\n""")) ==
{:__block__, [delimiter: ~s["""], line: 1], ["hello\n"]}
@@ -359,16 +361,15 @@ defmodule CodeTest do
assert string_to_quoted.("'''\nhello\n'''") ==
{:__block__, [delimiter: ~s['''], line: 1], ['hello\n']}
args = [
{:->, [line: 1],
[
[{:__block__, [token: "1", line: 1, closing: [line: 1], line: 1], [1]}],
{:__block__, [delimiter: "\"", line: 1], ["hello"]}
]}
]
assert string_to_quoted.(~s[fn (1) -> "hello" end]) ==
{:fn, [closing: [line: 1], line: 1], args}
{:fn, [closing: [line: 1], line: 1],
[
{:->, [line: 1],
[
[{:__block__, [token: "1", line: 1, closing: [line: 1], line: 1], [1]}],
{:__block__, [delimiter: "\"", line: 1], ["hello"]}
]}
]}
end
test "raises on bad literal" do
@@ -20,6 +20,7 @@ defmodule Config.ProviderTest do
on_exit(fn ->
Application.delete_env(@config_app, :config_providers)
Application.delete_env(@config_app, :config_providers_booted)
System.delete_env(@env_var)
end)
end
@@ -79,7 +80,7 @@ defmodule Config.ProviderTest do
init_and_assert_boot()
config = consult(@sys_config)
assert config[:my_app] == [key: :value]
assert config[@config_app] == [config_providers: :booted]
assert config[@config_app] == [config_providers_booted: {:booted, nil}]
end
@tag sys_config: [my_app: [encoding: {:"£", "£", '£'}]]
@@ -113,6 +114,18 @@ defmodule Config.ProviderTest do
refute File.exists?(@sys_config)
end
test "returns :booted if already booted and runs validate_compile_env" do
init_and_assert_boot(
prune_after_boot: true,
validate_compile_env: [{:elixir, [:unknown], {:ok, :value}}]
)
Application.put_all_env(Keyword.take(consult(@sys_config), [@config_app]))
assert capture_abort(fn -> boot() end) =~
"the application :elixir has a different value set for key :unknown"
end
test "raises if booting twice in a row" do
init_and_assert_boot()
@@ -278,14 +278,17 @@ defmodule Kernel.ParallelCompilerTest do
"""
)
output = tmp_path("not_to_be_used")
try do
Code.compiler_options(warnings_as_errors: true)
msg =
capture_io(:stderr, fn ->
assert {:error, [error], []} = Kernel.ParallelCompiler.compile([fixture])
msg = "this clause cannot match because a previous clause at line 2 always matches"
assert error == {fixture, 3, msg}
assert {:error, [error], []} =
Kernel.ParallelCompiler.compile_to_path([fixture], output)
assert {^fixture, 3, "this clause " <> _} = error
end)
assert msg =~
@@ -294,6 +297,8 @@ defmodule Kernel.ParallelCompilerTest do
Code.compiler_options(warnings_as_errors: warnings_as_errors)
purge([WarningsSample])
end
refute File.exists?(output)
end
test "does not use incorrect line number when error originates in another file" do
@@ -428,10 +433,7 @@ defmodule Kernel.ParallelCompilerTest do
capture_io(:stderr, fn ->
assert {:error, [error], []} = Kernel.ParallelCompiler.require([fixture])
message =
"this clause cannot match because a previous clause at line 2 always matches"
assert error == {fixture, 3, message}
assert {^fixture, 3, "this clause " <> _} = error
end)
assert msg =~
@@ -25,11 +25,6 @@ defmodule Kernel.QuoteTest do
test "quote line var" do
line = __ENV__.line
assert quote(line: line, do: bar(1, 2, 3)) == {:bar, [line: line], [1, 2, 3]}
assert_raise ArgumentError, fn ->
line = "oops"
quote(line: line, do: bar(1, 2, 3))
end
end
test "quote context var" do
@@ -66,6 +66,7 @@ bar) in ["foo\\\nbar", "foo\\\r\nbar"]
test "sigil w" do
assert ~w() == []
assert ~w([ , ]) == ["[", ",", "]"]
assert ~w(foo bar baz) == ["foo", "bar", "baz"]
assert ~w(foo #{:bar} baz) == ["foo", "bar", "baz"]
@@ -100,6 +101,7 @@ bar) in ["foo\\\nbar", "foo\\\r\nbar"]
test "sigil W" do
assert ~W() == []
assert ~W([ , ]) == ["[", ",", "]"]
assert ~W(foo #{bar} baz) == ["foo", "\#{bar}", "baz"]
assert ~W(foo\ bar) == ["foo\\", "bar"]
+8 -22
View File
@@ -661,24 +661,6 @@ defmodule Kernel.WarningTest do
purge(Sample)
end
test "previous clause always matches" do
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
def binary_cond do
v = "bc"
cond do
is_binary(v) -> :bin
true -> :ok
end
end
end
""")
end) =~ "this clause cannot match because a previous clause at line 5 always matches"
after
purge(Sample)
end
test "late function heads" do
assert capture_err(fn ->
Code.eval_string("""
@@ -740,7 +722,8 @@ defmodule Kernel.WarningTest do
def hello, do: nil
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
end) =~
~r"this clause( for hello/0)? cannot match because a previous clause at line 2 always matches"
after
purge(Sample)
end
@@ -760,7 +743,8 @@ defmodule Kernel.WarningTest do
use Sample
end
""")
end) =~ "this clause cannot match because a previous clause at line 10 always matches"
end) =~
~r"this clause( for hello/0)? cannot match because a previous clause at line 10 always matches"
after
purge(Sample)
purge(UseSample)
@@ -1628,7 +1612,8 @@ defmodule Kernel.WarningTest do
defguard foo(baz) when baz == :baz
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
end) =~
~r"this clause( for foo/1)? cannot match because a previous clause at line 2 always matches"
after
purge(Sample)
end
@@ -1641,7 +1626,8 @@ defmodule Kernel.WarningTest do
defmacro foo(bar), do: bar == :bar
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
end) =~
~r"this clause( for foo/1)? cannot match because a previous clause at line 2 always matches"
after
purge(Sample)
end
@@ -73,6 +73,16 @@ defmodule Kernel.WithTest do
assert state == 1
end
test "with extra guards" do
var =
with %_{} = a <- struct(URI),
%_{} <- a do
:ok
end
assert var == :ok
end
test "errors in with" do
assert_raise RuntimeError, fn ->
with({:ok, res} <- oops(), do: res)
+7
View File
@@ -344,6 +344,7 @@ defmodule MacroTest do
assert Macro.to_string(quote(do: ~R"\n123")) == ~S/~R"\n123"/
assert Macro.to_string(quote(do: ~S["'(123)'"])) == ~S/~S["'(123)'"]/
assert Macro.to_string(quote(do: ~s"#{"foo"}")) == ~S/~s"#{"foo"}"/
assert Macro.to_string(
quote do
@@ -704,6 +705,12 @@ defmodule MacroTest do
assert Macro.to_string(quote(do: 'abc')) == "'abc'"
end
test "string" do
assert Macro.to_string(quote(do: "")) == ~S/""/
assert Macro.to_string(quote(do: "abc")) == ~S/"abc"/
assert Macro.to_string(quote(do: "#{"abc"}")) == ~S/"#{"abc"}"/
end
test "last arg keyword list" do
assert Macro.to_string(quote(do: foo([]))) == "foo([])"
assert Macro.to_string(quote(do: foo(x: y))) == "foo(x: y)"
@@ -161,6 +161,11 @@ defmodule Module.TypesTest do
quoted_head([x], [is_tuple(is_atom(x))])
end
test "erlang-only guards" do
assert quoted_head([x], [:erlang.size(x)]) ==
{:ok, [{:union, [:binary, :tuple]}]}
end
test "failing guard functions" do
assert quoted_head([x], [length([])]) == {:ok, [{:var, 0}]}
+10 -1
View File
@@ -499,13 +499,17 @@ defmodule StreamTest do
test "interval/1" do
stream = Stream.interval(10)
{time_us, value} = :timer.tc(fn -> Enum.take(stream, 5) end)
assert value == [0, 1, 2, 3, 4]
assert time_us >= 50000
end
test "interval/1 with infinity" do
stream = Stream.interval(:infinity)
spawn(Stream, :run, [stream])
end
test "into/2 and run/1" do
Process.put(:stream_cont, [])
Process.put(:stream_done, false)
@@ -1119,6 +1123,11 @@ defmodule StreamTest do
assert time_us >= 5000
end
test "timer/1 with infinity" do
stream = Stream.timer(:infinity)
spawn(Stream, :run, [stream])
end
test "unfold/2" do
stream = Stream.unfold(10, fn x -> if x > 0, do: {x, x - 1} end)
assert Enum.take(stream, 5) == [10, 9, 8, 7, 6]
+15
View File
@@ -584,6 +584,21 @@ defmodule TaskTest do
[ok: :ok] = Task.async_stream([1], fn _ -> :ok end, timeout: :infinity) |> Enum.to_list()
end
test "streams with fake down messages on the inbox" do
parent = self()
assert Task.async_stream([:ok], fn :ok ->
{:links, links} = Process.info(self(), :links)
for link <- links do
send(link, {:DOWN, make_ref(), :process, parent, :oops})
end
:ok
end)
|> Enum.to_list() == [ok: :ok]
end
test "with $callers" do
grandparent = self()
+1 -1
View File
@@ -338,7 +338,7 @@ defmodule ExUnit.Formatter do
{:match, pins},
formatter,
padding_size + 5,
width - 5
width
)
[
@@ -375,6 +375,7 @@ defmodule ExUnit.AssertionsTest do
"assert_received({:status, ^status, ^status})" = Macro.to_string(error.expr)
"{:status, ^status, ^status}" = Macro.to_string(error.left)
"\n\nAssertion failed" <> _ = Exception.message(error)
end
end
-8
View File
@@ -32,14 +32,6 @@ defmodule Logger.Config do
Application.fetch_env!(:logger, :discard_threshold_periodic_check)}
end
def handle_event({_type, gl, _msg} = event, state) when node(gl) != node() do
# Cross node messages are always async which also
# means this handler won't crash in case Logger
# is not installed in the other node.
:gen_event.notify({Logger, node(gl)}, event)
{:ok, update_counter(state, false)}
end
def handle_event(_event, state) do
{:ok, update_counter(state, false)}
end
+30 -27
View File
@@ -168,22 +168,42 @@ defmodule Logger.Formatter do
defp levelpad(:warn), do: " "
defp levelpad(:error), do: ""
defp metadata([{:report_cb, _} | metadata]), do: metadata(metadata)
defp metadata([{:time, _} | metadata]), do: metadata(metadata)
defp metadata([{:gl, _} | metadata]), do: metadata(metadata)
defp metadata([{:crash_reason, _} | metadata]), do: metadata(metadata)
defp metadata([{:ancestors, _} | metadata]), do: metadata(metadata)
defp metadata([{:callers, _} | metadata]), do: metadata(metadata)
defp metadata([{_, nil} | metadata]), do: metadata(metadata)
defp metadata([{key, value} | metadata]) do
[to_string(key), ?=, metadata(key, value), ?\s | metadata(metadata)]
if formatted = metadata(key, value) do
[to_string(key), ?=, formatted, ?\s | metadata(metadata)]
else
metadata(metadata)
end
end
defp metadata([]) do
[]
end
defp metadata(:time, _), do: nil
defp metadata(:gl, _), do: nil
defp metadata(_, nil), do: nil
defp metadata(_, string) when is_binary(string), do: string
defp metadata(_, integer) when is_integer(integer), do: Integer.to_string(integer)
defp metadata(_, float) when is_float(float), do: Float.to_string(float)
defp metadata(_, pid) when is_pid(pid), do: :erlang.pid_to_list(pid)
defp metadata(_, atom) when is_atom(atom) do
case Atom.to_string(atom) do
"Elixir." <> rest -> rest
"nil" -> ""
binary -> binary
end
end
defp metadata(_, ref) when is_reference(ref) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
rest
end
defp metadata(:file, file) when is_list(file), do: file
defp metadata(:domain, [head | tail]) when is_atom(head) do
Enum.map_intersperse([head | tail], ?., &Atom.to_string/1)
end
@@ -198,22 +218,5 @@ defmodule Logger.Formatter do
Exception.format_mfa(mod, fun, arity)
end
defp metadata(_, pid) when is_pid(pid) do
:erlang.pid_to_list(pid)
end
defp metadata(_, ref) when is_reference(ref) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
rest
end
defp metadata(_, atom) when is_atom(atom) do
case Atom.to_string(atom) do
"Elixir." <> rest -> rest
"nil" -> ""
binary -> binary
end
end
defp metadata(_, other), do: to_string(other)
defp metadata(_, _), do: nil
end
+2 -2
View File
@@ -230,7 +230,7 @@ defmodule Logger.Handler do
translate_fallback(:format, callback.(data), meta, truncate)
end
defp translate_fallback(:report, {:logger, data}, %{report_cb: callback} = meta, truncate)
defp translate_fallback(:report, {:logger, data}, %{report_cb: callback}, _truncate)
when is_function(callback, 2) do
translator_opts =
struct(Inspect.Opts, Application.fetch_env!(:logger, :translator_inspect_opts))
@@ -241,7 +241,7 @@ defmodule Logger.Handler do
single_line: false
}
translate_fallback(:format, callback.(data, opts), meta, truncate)
callback.(data, opts)
end
defp translate_fallback(:format, {format, args}, _meta, truncate) do
+1 -1
View File
@@ -81,7 +81,7 @@ defmodule Logger.Utils do
end
defp fix_binary(<<h, t::binary>>, acc) do
fix_binary(t, <<h, acc::binary>>)
fix_binary(t, <<acc::binary, h>>)
end
defp fix_binary(<<>>, _acc) do
+2 -2
View File
@@ -77,9 +77,9 @@ defmodule Logger.FormatterTest do
"2014-12-30 12:06:30.100"
end
test "format discards callers/ancestors/crash_reason" do
test "format discards unknown formats" do
compiled = compile("$metadata $message")
metadata = [callers: :discard, ancestors: :discard, crash_reason: :discard, foo: :bar]
metadata = [ancestors: [self()], crash_reason: {:some, :tuple}, foo: :bar]
assert format(compiled, :error, "hello", nil, metadata) ==
[["foo", 61, "bar", 32], " ", "hello"]
+1 -1
View File
@@ -147,6 +147,6 @@ defmodule Logger.HandlerTest do
defp format_report(report, opts) do
send(self(), {:format, report, opts})
{'~p', [report]}
inspect(report)
end
end
+1
View File
@@ -23,6 +23,7 @@ defmodule Logger.UtilsTest do
assert truncate("olá", 4) == "olá"
assert truncate("ááááá:", 10) == ["ááááá", " (truncated)"]
assert truncate("áááááá:", 10) == ["ááááá", " (truncated)"]
assert truncate("𠜎𠜱𠝹𠱓", 15) == ["𠜎𠜱𠝹", " (truncated)"]
# Charlists
assert truncate('olá', 2) == ['olá', " (truncated)"]
+18 -18
View File
@@ -425,13 +425,29 @@ defmodule Mix.Release do
opts = [
extra_config: initial_config,
prune_after_boot: prune_after_boot,
reboot_after_config: reboot?
reboot_after_config: reboot?,
validate_compile_env: validate_compile_env(release)
]
init = Config.Provider.init(release.config_providers, config_path, opts)
{Config.Reader.merge(sys_config, [elixir: [config_providers: init]] ++ extra_config), reboot?}
end
defp validate_compile_env(release) do
with true <- Keyword.get(release.options, :validate_compile_env, true),
[_ | _] = compile_env <- compile_env(release) do
compile_env
else
_ -> false
end
end
defp compile_env(release) do
for {_, properties} <- release.applications,
triplet <- Keyword.get(properties, :compile_env, []),
do: triplet
end
defp start_distribution(%{options: opts}) do
reboot? = Keyword.get(opts, :reboot_system_after_config, true)
early_distribution? = Keyword.get(opts, :start_distribution_during_config, false)
@@ -613,8 +629,7 @@ defmodule Mix.Release do
&(not match?({:apply, {:application, :start_boot, [:stdlib, _]}}, &1))
)
pre ++
[stdlib] ++ config_provider_apply(release) ++ validate_compile_env_apply(release) ++ post
pre ++ [stdlib] ++ config_provider_apply(release) ++ post
end
defp config_provider_apply(%{config_providers: []}),
@@ -623,21 +638,6 @@ defmodule Mix.Release do
defp config_provider_apply(_),
do: [{:apply, {Config.Provider, :boot, [:elixir, :config_providers]}}]
defp validate_compile_env_apply(release) do
with true <- Keyword.get(release.options, :validate_compile_env, true),
[_ | _] = compile_env <- compile_env(release) do
[{:apply, {Config.Provider, :validate_compile_env, [compile_env]}}]
else
_ -> []
end
end
defp compile_env(release) do
for {_, properties} <- release.applications,
triplet <- Keyword.get(properties, :compile_env, []),
do: triplet
end
defp prepend_paths_to_script(instructions, []), do: instructions
defp prepend_paths_to_script(instructions, prepend_paths) do
+2 -2
View File
@@ -416,9 +416,9 @@ defmodule Mix.Tasks.Release.Init do
goto end
)
!ERLSRV! add !RELEASE_NAME!_!RELEASE_NAME! ^
"!ERLSRV!" add "!RELEASE_NAME!_!RELEASE_NAME!" ^
-!RELEASE_DISTRIBUTION! "!RELEASE_NODE!" ^
-env RELEASE_ROOT=!RELEASE_ROOT! -env RELEASE_NAME=!RELEASE_NAME! -env RELEASE_VSN=!RELEASE_VSN! -env RELEASE_COOKIE=!RELEASE_COOKIE! -env RELEASE_NODE=!RELEASE_NODE! -env RELEASE_VM_ARGS=!RELEASE_VM_ARGS! -env RELEASE_TMP=!RELEASE_TMP! -env RELEASE_SYS_CONFIG=!RELEASE_SYS_CONFIG! ^
-env RELEASE_ROOT="!RELEASE_ROOT!" -env RELEASE_NAME="!RELEASE_NAME!" -env RELEASE_VSN="!RELEASE_VSN!" -env RELEASE_COOKIE="!RELEASE_COOKIE!" -env RELEASE_NODE="!RELEASE_NODE!" -env RELEASE_VM_ARGS="!RELEASE_VM_ARGS!" -env RELEASE_TMP="!RELEASE_TMP!" -env RELEASE_SYS_CONFIG="!RELEASE_SYS_CONFIG!" ^
-args "-setcookie !RELEASE_COOKIE! -config !RELEASE_SYS_CONFIG! -mode !RELEASE_MODE! -boot !REL_VSN_DIR!\start -boot_var RELEASE_LIB !RELEASE_ROOT!\lib -args_file !REL_VSN_DIR!\vm.args"
if %ERRORLEVEL% EQU 0 (
+11
View File
@@ -514,7 +514,18 @@ defmodule Mix.Tasks.ReleaseTest do
in_fixture("release_test", fn ->
config = [releases: [permanent1: [include_erts: false]]]
# We write the compile env to guarantee rpc still works
File.write!("lib/compile_env.ex", """
_ = Application.compile_env(:release_test, :static)
""")
Mix.Project.in_project(:release_test, ".", config, fn _ ->
Mix.Task.run("loadconfig", [])
File.write!("config/releases.exs", """
import Config
""")
root = Path.absname("_build/dev/rel/permanent1")
Mix.Task.run("release")
script = Path.join(root, "bin/permanent1")