Compare commits

...
53 Commits
Author SHA1 Message Date
José Valim c107a2fe26 Release v1.6.4 2018-03-16 12:10:21 +01:00
José Valim 17853e63d8 Ensure bitstring comprehensions work on OTP 20.3 (#7453)
Closes #7452

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-14 21:45:47 +01:00
José Valim 338e242a59 Avoid protocol dispatch and multiple lists conversion in the formatter
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-14 10:12:53 +01:00
José Valim 2de4680c6e Promote stats format first
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-14 10:12:21 +01:00
Damien Krotkine 1cffb06981 Remove comma from operators table (#7450)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-14 10:12:02 +01:00
José Valim e0f1ba28dd Do not escape quoted keywords on formatting, closes #7451
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-14 10:11:59 +01:00
José Valim 45c7f828ef Release v1.6.3 2018-03-09 08:57:25 +01:00
Johan Suleiko Allansson 9169b6e302 Properly cancel timeout on compilation error in ParallelCompiler #7428 (#7429)
The :timed_out message was not cancelled properly when compilation failed in ParallelCompiler. This meant the timeout message would be delivered even after compilation completed. This could be problematic when invoking compilation programmatically via e.g. Mix.Task.run("compile", ["--return-errors"]) since it means that the calling process, depending on the implemenation, might crash unexpectedly or get its inbox filled with messages.

The fix simply calls the cancel_waiting_timer when a compilation error has ocurred in the same way it is called in other similar cases.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-09 08:55:46 +01:00
José Valim c2bdc66b9e Update CHANGELOG 2018-03-07 12:31:46 +01:00
José Valim d16f354a3b Ensure proper reports from named DynamicSupervisor, closes #7425 2018-03-07 12:29:46 +01:00
José Valim dd30768354 Consider commas when breaking groups, closes #7406
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-03 00:21:12 +01:00
José Valim bf1d993b2f Ensure proper precedence between & and operators, closes #7412
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-03 00:20:48 +01:00
José Valim 755639f538 Consider .formatter.exs when formatting stdin
Closes #7411

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-03-03 00:20:22 +01:00
José Valim 97e0b1e1ff Support comments in the middle of pipelines and type expressions
Closes #7231

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-28 11:35:25 +01:00
José Valim c2a9c93f02 Release v1.6.2 2018-02-28 10:35:19 +01:00
José Valim d896f541f1 Allow subdirectories in .formatter.exs (#7398)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-28 10:24:23 +01:00
José Valim f033672fa8 Update CHANGELOG 2018-02-27 22:54:33 +01:00
José Valim 717c72e71b Ensure module is loaded before function exported check
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-24 12:33:01 +01:00
José Valim 9ac830f56b Include soft deprecations in the CHANGELOG 2018-02-24 11:54:34 +01:00
Unai Esteibar 0ded67dd60 Don't remove docs for previous function declaration on @impl true (#7383)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-23 20:32:15 +01:00
José Valim abbf173da7 Do not talk about tuple child_spec before they are introduced
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-21 09:27:02 +01:00
Jens Fischer fc1c303481 Fix DynamicSupervisor error report when restarting child (#7377)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-21 09:27:00 +01:00
José Valim 5e1e2e6a83 Inject less code when reading @attribute 2018-02-20 19:42:29 +01:00
Florian Ebeling 77d708e1bc Improve documentation for ExUnit.Callbacks (#7338)
The change refines and extends the documentation for the ExUnit
callbacks for test setup, "setup" and "setup_all".

* refer to the introductory Context section in function documentation
* name various ways to define setup code using atom naming unary
  function, list of atoms naming unary functions and block.
* clarify that multiple setup callbacks can be used
* reduce two overly similar paragraphs about return values into a
  single one

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-20 12:35:09 +01:00
José Valim cb0f900b1c Document defoverridable behaviour regarding compilation callbacks
Closes #7340

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-20 12:35:08 +01:00
jvf 815420ce0f Fix DynamicSupervisor handling of extra arguments on child restart (#7371)
Fixes #7369

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-02-20 12:34:24 +01:00
José Valim cba11d6941 Ensure -> does not pass line lengths, closes #7279 2018-02-10 20:38:46 +01:00
José Valim 966f8f74ae Bring add_doc/6 back, closes #7335 2018-02-10 11:07:30 +01:00
Michał Muskała 55241d92e2 Clarify behaviour of failing gaurds (#7334)
[ci skip]
2018-02-09 18:38:11 +01:00
José Valim 11086ac450 Include the environment in missing dep error message, closes #7331 2018-02-09 18:38:07 +01:00
José Valim 0c81857622 Consistently raise for missing do option in definitions
Closes #7333.
2018-02-09 17:07:12 +01:00
Michał Muskała bd0a2e8a12 Bring back Mix.Shell.cmd/2
It was not brought back when Mix.Shell.cmd/3 was "undeprecated" in b2e5b69.

Signed-off-by: Michał Muskała <michal@muskala.eu>
2018-02-08 23:33:21 +01:00
José Valim df50dca83a Use tags on windows/unix and allow failures on appveyor (#7320) 2018-02-08 20:24:43 +01:00
José Valim f0969f0aa0 Only cache deps opts when the root formatter is used 2018-02-08 20:24:39 +01:00
José Valim ad489d7d80 Improve the docs for mix format
Closes #7327.
2018-02-08 19:12:08 +01:00
José Valim f8afbf3321 Publicly document child_spec/1 2018-02-08 14:51:37 +01:00
Pavel Platto 3b5f10874f Add is_integer/1 guard for argument "n" in Stream.drop/2 (#7315) 2018-02-06 17:58:57 +01:00
Raphael Costa e17b2470c6 Fixed eprof to work with multiple processes (#7317) 2018-02-06 16:37:49 +01:00
Bram Verburg 8a1c5c38d0 Fix DynamicSupervisor error report 2018-02-06 16:36:41 +01:00
José Valim 00deab98bf Update CHANGELOG 2018-02-03 01:40:12 -02:00
Ross Kaffenberger 47f4721c0b Update doc for DynamicSupervisor.start_child (#7299)
The previous example made it seem like the arguments to
`def start_child` where being dropped by `spec = MyWorker`.
2018-02-01 22:14:08 -02:00
José Valim beb69232c4 Properly format warning_test.exs 2018-02-01 14:08:19 -02:00
José Valim 4b43c7ae20 Do not change code invocation semantics in guards 2018-02-01 11:38:29 -02:00
José Valim ca49a53ab2 Consistently preserve the user choice in regards to parens, closes #7286 2018-01-31 13:50:30 -02:00
Paweł Chmielowski 1ff0e1ac7a Teach Mix erlang compiler alternative spelling for -behavior declaration (#7290)
Erlang recognizes both -behaviour and -behavior, but mix was only able to
process one, which made erlang compiler complain about missing behaviour
when source file was using the other one
2018-01-31 13:41:51 -02:00
José Valim ae66125f2e Update dynamic_supervisor.ex
Closes #7291
2018-01-31 13:36:17 -02:00
José Valim 3025cc759b Improve docs for dynamic and task supervisors 2018-01-31 13:35:49 -02:00
José Valim 8f18a5f4ff Use Supervisor.init/2 instead of supervise/2 with the new child specs 2018-01-31 13:35:41 -02:00
Benjamin Tan Wei Hao 1626f9659b Use the new child specification format in DynamicSupervisor docs
Since `Supervisor.Spec.worker/3` is deprecated (#7244)
2018-01-31 13:35:36 -02:00
Frank Hunleth 9083937177 Add missing & in port zombie script (#7287)
This is needed to background the processing being run so that control
could move on to the read loop.
2018-01-31 07:26:02 -02:00
Xavier Noria da90bb741e Defines the prep_stop/1 optional callback for applications (#7289) 2018-01-31 07:25:58 -02:00
Daniel Kempkens 51d56274de Re-add missing Supervisor behaviour (#7288)
Closes #7285.
2018-01-31 07:25:55 -02:00
José Valim 79fcebb407 Fix CHANGELOG 2018-01-29 18:19:44 -02:00
59 changed files with 1657 additions and 618 deletions
+3 -3
View File
@@ -11,8 +11,8 @@ test_script:
environment:
ELIXIR_ASSERT_TIMEOUT: 2000
configuration: Test
matrix:
allow_failures:
- configuration: Test
- platform: x86
- platform: x64
- platform: Any CPU
+68 -3
View File
@@ -123,10 +123,66 @@ Those improvements will help developers better understand the relationship betwe
Other improvements in Mix include better compiler diagnostics for editor integration, support for the `--slowest N` flag in `mix test` that shows the slowest tests in your suite, and a new `mix profile.eprof` task that provides time based profiling, complementing the existing `mix profile.cprof` (count based) and `mix profile.fprof` (flame based).
## v1.6.4 (2018-03-16)
### 1. Bug fixes
#### Elixir
* [Code.Formatter] Do not double escape quoted keyword list identifiers
* [Kernel] Properly support `into: binary` in Erlang/OTP 20.3
## v1.6.3 (2018-03-09)
### 1. Enhancements
#### Elixir
* [Code.Formatter] Support comments in the middle of pipelines, `when` and `|` expressions
### 2. Bug fixes
#### Elixir
* [Code.Formatter] Consider commas when breaking groups
* [Code.Formatter] Ensure proper precedence between `&` and operators
* [Code.Formatter] Consider `.formatter.exs` when formatting stdin
#### Logger
* [Logger.Translator] Ensure logger doesn't crash when reporting named `DynamicSupervisor`
## v1.6.2 (2018-02-28)
### 1. Enhancements
#### Mix
* [mix compile.erlang] Teach Mix erlang compiler alternative spelling for `-behavior` declaration
* [mix format] Support the `:subdirectories` configuration that points to other directories with their own `.formatter.exs` file. This is useful in umbrella applications. `mix new --umbrella` has also been changed to use this new configuration by default
* [mix format] Include the current environment for missing dependency errors
### 2. Bug fixes
#### Elixir
* [Code.Formatter] Ensure `->` does not exceed line length
* [DynamicSupervisor] Properly tag error reports generated by dynamic supervisors so they can be properly translated by `Logger`
* [DynamicSupervisor] Consider extra arguments during child restart
* [Kernel] Ensure arguments given to a guard defined with `defguard` are evaluated in the correct order
* [Module] Do not remove docs for previous function declaration when `@impl true` is used
* [Supervisor] Ensure `use Supervisor` properly adds the `@behaviour Supervisor` annotation
#### Mix
* [Mix.Shell] Bring back `Mix.Shell.cmd/2` - this arity was defined via a default argument that was accidentally removed
## v1.6.1 (2018-01-29)
### 1. Enhancements
#### Elixir
* [DynamicSupervisor] Implement `child_spec/1` for DynamicSupervisor
* [Kernel] Raise better error messages on invalid map syntax
@@ -135,13 +191,13 @@ Other improvements in Mix include better compiler diagnostics for editor integra
#### Elixir
* [Code.Formatter] Only rearrange `not in` operator if explicitly opted-in
* [Code.Formatter] Ensure do blocks do not exceed line length on calls with a single argument
* [Code.Formatter] Ensure `do` blocks do not exceed line length on calls with a single argument
* [Collectable] Support bitstrings in Collectable and for-comprehensions (regression in v1.6.0)
* [GenServer] Do not override user own `@opts` attribute
* [Enum] Reintroduce zipping of any enumerable of enumerables in `Enum.zip/1` (regression in v1.6.0)
* [Macro] Reorder kw blocks in `Macro.to_string/1` to avoid warnings
* [Protocol] Fix protocol consolidation when some chunks may be missing
* [Stream] Reintroduce zipping of any enumerable of enumerables in `Enum.zip/1` (regression in v1.6.0)
* [Stream] Reintroduce zipping of any enumerable of enumerables in `Stream.zip/1` (regression in v1.6.0)
* [Supervisor] Do not override user own `@opts` attribute
* [Supervisor] Add `@spec` to second clause of `start_link/2`
@@ -235,6 +291,7 @@ Other improvements in Mix include better compiler diagnostics for editor integra
* [Kernel] Solve a precedence issue between `&` and `|`, such as `[&Foo.bar/1 | &Baz.bat/2]`
* [Kernel] Do not load dynamic Elixir modules as `:in_memory` as this value is not officially supported by the code server. Instead, use an empty list, which is the same value used by Erlang.
* [Kernel] Validate variable struct name is atom when used in pattern matching
* [Kernel] No longer generate documentation for `defdelegate` functions automatically to avoid overriding previously specified `@doc`
* [Macro] Fix `Macro.to_string/2` for tuple calls, such as `alias Foo.{Bar, Baz}`
* [MapSet] Return valid MapSet when unioning a legacy MapSet
* [Regex] Return a leading empty space when splitting on empty pattern. This makes the `split` operation consistent with the other operations in the `Regex` module
@@ -255,15 +312,23 @@ Other improvements in Mix include better compiler diagnostics for editor integra
#### Elixir
* [GenServer] Warn if `init/1` is not defined in `GenServer`. This brings GenServer closer to the implementation in OTP and aligns all behaviours to require the `init/1` callback
* [Inspect.Algebra] `surround/3` and `surround_many/6` are deprecated in favor of `container_doc/6`
* [Kernel] Specifying map types with variable keys without defining the type as required/optional is deprecated
* [Kernel.ParallelCompiler] `files/2` and `files_to_path/3` are deprecated in favor of `compile/2` and `compile_to_path/3`
* [Kernel.ParallelRequire] `files/2` is deprecated in favor of `Kernel.ParallelCompiler.require/2`
* [GenServer] Warn if `init/1` is not defined in `GenServer`. This brings GenServer closer to the implementation in OTP and aligns all behaviours to require the `init/1` callback
* [Supervisor] The `:simple_one_for_one` strategy is deprecated in favor of `DynamicSupervisor`
* [Supervisor] Passing a list of args to `Supervisor.start_child/2` is deprecated in favor of `DynamicSupervisor`
* [Task.Supervisor] Passing `:restart` and `:shutdown` to `Task.Supervisor.start_link/2` is deprecated (it should be passed on start child instead)
#### ExUnit
* [ExUnit.Formatter] `:case_started` and `:case_finished` events are deprecated in favor of `:module_started` and `:module_finished`
#### Mix
* [Mix.Compilers.Erlang] Returning `{:ok, val} | :error` from custom Erlang compilers is deprecated in favor of `{:ok, val, warnings} | {:error, errors, warnings}`
### 4. Deprecations
#### Elixir
-2
View File
@@ -2,8 +2,6 @@
=========
[![Travis build](https://secure.travis-ci.org/elixir-lang/elixir.svg?branch=master
"Build Status")](https://travis-ci.org/elixir-lang/elixir)
[![Windows build](https://ci.appveyor.com/api/projects/status/macwuxq7aiiv61g1?svg=true)](https://ci.appveyor.com/project/josevalim/elixir)
Elixir is a dynamic, functional language designed for building scalable and maintainable applications.
+1 -1
View File
@@ -1 +1 @@
1.6.1
1.6.4
+11 -2
View File
@@ -139,7 +139,12 @@ defmodule Agent do
@typedoc "The agent state"
@type state :: term
@doc false
@doc """
Returns a specification to start an agent under a supervisor.
See `Supervisor`.
"""
@since "1.5.0"
def child_spec(arg) do
%{
id: Agent,
@@ -150,7 +155,11 @@ defmodule Agent do
@doc false
defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
@doc false
@doc """
Returns a specification to start this module under a supervisor.
See `Supervisor`.
"""
def child_spec(arg) do
default = %{
id: __MODULE__,
+27 -8
View File
@@ -79,10 +79,19 @@ defmodule Application do
* [`:application` module](http://www.erlang.org/doc/man/application.html)
* [Applications – OTP Design Principles](http://www.erlang.org/doc/design_principles/applications.html)
A developer may also implement the `stop/1` callback (automatically defined
by `use Application`) which does any application cleanup. It receives the
application state and can return any value. Note that shutting down the
supervisor is automatically handled by the VM.
When an application is shutting down, its `c:stop/1` callback is called after
the supervision tree has been stopped by the runtime. This callback allows the
application to do any final cleanup. The argument is the state returned by
`c:start/2`, if it did, or `[]` otherwise. The return value of `c:stop/1` is
ignored.
By using `Application`, modules get a default implementation of `c:stop/1`
that ignores its argument and returns `:ok`, but it can be overridden.
Application callback modules may also implement the optional callback
`c:prep_stop/1`. If present, `c:prep_stop/1` is invoked before the supervision
tree is terminated. Its argument is the state returned by `c:start/2`, if it did,
or `[]` otherwise, and its return value is passed to `c:stop/1`.
An application without a supervision tree doesn't define an application
module callback in the application definition in `mix.exs` file. Even though
@@ -185,16 +194,26 @@ defmodule Application do
| {:ok, pid, state}
| {:error, reason :: term}
@doc """
Called before stopping the application.
This function is called before the top-level supervisor is terminated. It
receives the state returned by `c:start/2`, if it did, or `[]` otherwise.
The return value is later passed to `c:stop/1`.
"""
@callback prep_stop(state) :: state
@doc """
Called after an application has been stopped.
This function is called after an application has been stopped, i.e., after its
supervision tree has been stopped. It should do the opposite of what the
`start/2` callback did, and should perform any necessary cleanup. The return
`c:start/2` callback did, and should perform any necessary cleanup. The return
value of this callback is ignored.
`state` is the return value of the `start/2` callback or the return value of
the `prep_stop/1` function if the application module defines such a function.
`state` is the state returned by `c:start/2`, if it did, or `[]` otherwise.
If the optional callback `c:prep_stop/1` is present, `state` is its return
value instead.
`use Application` defines a default implementation of this function which does
nothing and just returns `:ok`.
@@ -212,7 +231,7 @@ defmodule Application do
@callback start_phase(phase :: term, start_type, phase_args :: term) ::
:ok | {:error, reason :: term}
@optional_callbacks start_phase: 3
@optional_callbacks start_phase: 3, prep_stop: 1
@doc false
defmacro __using__(_) do
+198 -99
View File
@@ -10,6 +10,7 @@ defmodule Code.Formatter do
@min_line 0
@max_line 9_999_999
@empty empty()
@ampersand_prec Code.Identifier.unary_op(:&) |> elem(1)
# Operators that do not have space between operands
@no_space_binary_operators [:..]
@@ -17,8 +18,8 @@ defmodule Code.Formatter do
# Operators that do not have newline between operands (as well as => and keywords)
@no_newline_binary_operators [:\\, :in]
# Left associative operators that start on the next line in case of breaks
@left_new_line_before_binary_operators [:|>, :~>>, :<<~, :~>, :<~, :<~>, :<|>]
# Left associative operators that start on the next line in case of breaks (always pipes)
@pipeline_operators [:|>, :~>>, :<<~, :~>, :<~, :<~>, :<|>]
# Right associative operators that start on the next line in case of breaks
@right_new_line_before_binary_operators [:|, :when]
@@ -253,6 +254,7 @@ defmodule Code.Formatter do
|> Keyword.get(:locals_without_parens, [])
|> MapSet.new()
|> MapSet.union(@locals_without_parens)
|> MapSet.to_list()
%{
locals_without_parens: locals_without_parens,
@@ -325,12 +327,12 @@ defmodule Code.Formatter do
# Special AST nodes from compiler feedback.
defp quoted_to_algebra({:special, :clause_args, [args]}, _context, state) do
defp quoted_to_algebra({{:special, :clause_args}, _meta, [args]}, _context, state) do
{doc, state} = clause_args_to_algebra(args, state)
{group(doc), state}
end
defp quoted_to_algebra({:special, :bitstring_segment, [arg, last]}, _context, state) do
defp quoted_to_algebra({{:special, :bitstring_segment}, _meta, [arg, last]}, _context, state) do
bitstring_segment_to_algebra({arg, -1}, state, last)
end
@@ -553,7 +555,16 @@ defmodule Code.Formatter do
{left, state} =
case left_arg do
{:__block__, _, [atom]} when is_atom(atom) ->
{atom |> Code.Identifier.inspect_as_key() |> string(), state}
key =
case Code.Identifier.classify(atom) do
type when type in [:callable_local, :callable_operator, :not_callable] ->
IO.iodata_to_binary([Atom.to_string(atom), ?:])
_ ->
IO.iodata_to_binary([?", Atom.to_string(atom), ?", ?:])
end
{string(key), state}
{{:., _, [:erlang, :binary_to_atom]}, _, [{:<<>>, _, entries}, :utf8]} ->
interpolation_to_algebra(entries, @double_quote, state, "\"", "\":")
@@ -670,23 +681,75 @@ defmodule Code.Formatter do
# strict or flex mode around.
defp binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state) do
%{operand_nesting: nesting} = state
binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state, nil, nesting)
binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state, nesting)
end
defp binary_op_to_algebra(
op,
op_string,
meta,
left_arg,
right_arg,
context,
state,
parent_info,
nesting
) do
defp binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state, _nesting)
when op in @right_new_line_before_binary_operators do
op_info = Code.Identifier.binary_op(op)
left_context = force_many_args_or_operand(context, :parens_arg)
right_context = force_many_args_or_operand(context, :operand)
op_string = op_string <> " "
left_context = left_op_context(context)
right_context = right_op_context(context)
min_line =
case left_arg do
{_, left_meta, _} -> line(left_meta)
_ -> line(meta)
end
{operands, max_line} =
unwrap_right(right_arg, op, meta, right_context, [{{:root, left_context}, left_arg}])
operand_to_algebra = fn
{{:root, context}, arg}, _args, newlines, state ->
{doc, state} = binary_operand_to_algebra(arg, context, state, op, op_info, :left, 2)
{doc, @empty, newlines, state}
{{kind, context}, arg}, _args, newlines, state ->
{doc, state} = binary_operand_to_algebra(arg, context, state, op, op_info, kind, 0)
doc = doc |> nest_by_length(op_string) |> force_keyword(arg)
{concat(op_string, doc), @empty, newlines, state}
end
operand_to_algebra_with_comments(
operands,
meta,
min_line,
max_line,
state,
operand_to_algebra
)
end
defp binary_op_to_algebra(op, _, meta, left_arg, right_arg, context, state, _nesting)
when op in @pipeline_operators do
op_info = Code.Identifier.binary_op(op)
left_context = left_op_context(context)
right_context = right_op_context(context)
max_line = line(meta)
{pipes, min_line} =
unwrap_pipes(left_arg, meta, left_context, [{{op, right_context}, right_arg}])
operand_to_algebra = fn
{{:root, context}, arg}, _args, newlines, state ->
{doc, state} = binary_operand_to_algebra(arg, context, state, op, op_info, :left, 2)
{doc, @empty, newlines, state}
{{op, context}, arg}, _args, newlines, state ->
op_info = Code.Identifier.binary_op(op)
op_string = Atom.to_string(op) <> " "
{doc, state} = binary_operand_to_algebra(arg, context, state, op, op_info, :right, 0)
{concat(op_string, doc), @empty, newlines, state}
end
operand_to_algebra_with_comments(pipes, meta, min_line, max_line, state, operand_to_algebra)
end
defp binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state, nesting) do
op_info = Code.Identifier.binary_op(op)
left_context = left_op_context(context)
right_context = right_op_context(context)
{left, state} =
binary_operand_to_algebra(left_arg, left_context, state, op, op_info, :left, 2)
@@ -703,39 +766,6 @@ defmodule Code.Formatter do
op_string = " " <> op_string <> " "
concat(concat(group(left), op_string), group(right))
op in @left_new_line_before_binary_operators ->
op_string = op_string <> " "
# If the parent is of the same type (computed via same precedence),
# we cannot group the left side yet.
left = if op_info == parent_info, do: left, else: group(left)
doc = glue(left, concat(op_string, group(right)))
if Keyword.get(meta, :eol, false), do: force_unfit(doc), else: doc
op in @right_new_line_before_binary_operators ->
op_string = op_string <> " "
# If the parent is of the same type (computed via same precedence),
# we need to nest the left side because of the associativity.
left =
if op_info == parent_info do
nest_by_length(left, op_string)
else
group(left)
end
# If the right side is of the same type, we will keep recursing
# and do the nesting on the left side later on (as written above).
right =
case right_arg do
{^op, _, [_, _]} -> right
_ -> right |> nest_by_length(op_string) |> force_keyword(right_arg) |> group()
end
doc = glue(left, concat(op_string, right))
if Keyword.get(meta, :eol, false), do: force_unfit(doc), else: doc
true ->
next_break_fits? =
op in @next_break_fits_operators and next_break_fits?(right_arg, state) and
@@ -769,10 +799,11 @@ defmodule Code.Formatter do
end
defp binary_operand_to_algebra(operand, context, state, parent_op, parent_info, side, nesting) do
{parent_assoc, parent_prec} = parent_info
with {op, meta, [left, right]} <- operand,
op_info = Code.Identifier.binary_op(op),
{_assoc, prec} <- op_info do
{parent_assoc, parent_prec} = parent_info
op_string = Atom.to_string(op)
cond do
@@ -780,17 +811,7 @@ defmodule Code.Formatter do
# the correct side, we respect the nesting rule to avoid multiple
# nestings. This only applies for left associativity or same operator.
parent_prec == prec and parent_assoc == side and (side == :left or op == parent_op) ->
binary_op_to_algebra(
op,
op_string,
meta,
left,
right,
context,
state,
parent_info,
nesting
)
binary_op_to_algebra(op, op_string, meta, left, right, context, state, nesting)
# If the parent requires parens or the precedence is inverted or
# it is in the wrong side, then we *need* parenthesis.
@@ -799,16 +820,18 @@ defmodule Code.Formatter do
parent_op in @required_parens_logical_binary_operands) or parent_prec > prec or
(parent_prec == prec and parent_assoc != side) ->
{operand, state} =
binary_op_to_algebra(op, op_string, meta, left, right, context, state, parent_info, 2)
binary_op_to_algebra(op, op_string, meta, left, right, context, state, 2)
{wrap_in_parens(operand), state}
# Otherwise, we rely on precedence but also nest.
true ->
binary_op_to_algebra(op, op_string, meta, left, right, context, state, parent_info, 2)
binary_op_to_algebra(op, op_string, meta, left, right, context, state, 2)
end
else
{:&, _, [arg]} when not is_integer(arg) and side == :left ->
{:&, _, [arg]}
when not is_integer(arg) and side == :left
when not is_integer(arg) and parent_assoc == :left and parent_prec > @ampersand_prec ->
{doc, state} = quoted_to_algebra(operand, context, state)
{wrap_in_parens(doc), state}
@@ -817,6 +840,45 @@ defmodule Code.Formatter do
end
end
defp unwrap_pipes({op, meta, [left, right]}, _meta, context, acc)
when op in @pipeline_operators do
left_context = left_op_context(context)
right_context = right_op_context(context)
unwrap_pipes(left, meta, left_context, [{{op, right_context}, right} | acc])
end
defp unwrap_pipes(left, meta, context, acc) do
min_line =
case left do
{_, meta, _} -> line(meta)
_ -> line(meta)
end
{[{{:root, context}, left} | acc], min_line}
end
defp unwrap_right({op, meta, [left, right]}, op, _meta, context, acc) do
left_context = left_op_context(context)
right_context = right_op_context(context)
unwrap_right(right, op, meta, right_context, [{{:left, left_context}, left} | acc])
end
defp unwrap_right(right, _op, meta, context, acc) do
acc = [{{:right, context}, right} | acc]
{Enum.reverse(acc), line(meta)}
end
defp operand_to_algebra_with_comments(operands, meta, min_line, max_line, state, fun) do
{docs, comments?, state} =
quoted_to_algebra_with_comments(operands, [], min_line, max_line, 1, state, fun)
if comments? or Keyword.get(meta, :eol, false) do
{docs |> Enum.reduce(&line(&2, &1)) |> force_unfit(), state}
else
{docs |> Enum.reduce(&glue(&2, &1)), state}
end
end
## Module attributes
# @Foo
@@ -998,7 +1060,11 @@ defmodule Code.Formatter do
# function(arguments)
defp local_to_algebra(fun, meta, args, context, state) when is_atom(fun) do
skip_parens =
if skip_parens?(fun, meta, args, state), do: :skip_unless_many_args, else: :skip_if_do_end
cond do
not Keyword.get(meta, :no_parens, false) -> :required
local_without_parens?(fun, args, state) -> :skip_unless_many_args
true -> :skip_if_do_end
end
{{call_doc, state}, wrap_in_parens?} =
call_args_to_algebra(args, meta, context, skip_parens, true, state)
@@ -1021,7 +1087,7 @@ defmodule Code.Formatter do
#
defp call_args_to_algebra([], meta, _context, _parens, _list_to_keyword?, state) do
{args_doc, _join, state} =
args_to_algebra_with_comments([], meta, false, false, :glue, state, &{&1, &2})
args_to_algebra_with_comments([], meta, false, :none, :glue, state, &{&1, &2})
{{surround("(", args_doc, ")"), state}, false}
end
@@ -1069,6 +1135,7 @@ defmodule Code.Formatter do
call_args_to_algebra_with_no_parens_keywords(meta, left, right, context, extra, state)
else
next_break_fits? = next_break_fits?(right, state)
last_arg_mode = if next_break_fits?, do: :next_break_fits, else: :none
force_keyword? = keyword? and force_keyword?(right)
non_empty_eol? = left != [] and not next_break_fits? and Keyword.get(meta, :eol, false)
join = if generators_count > 1 or force_keyword? or non_empty_eol?, do: :line, else: :glue
@@ -1079,7 +1146,7 @@ defmodule Code.Formatter do
args,
meta,
skip_parens?,
next_break_fits?,
last_arg_mode,
join,
state,
&quoted_to_algebra(&1, context, &2)
@@ -1115,12 +1182,12 @@ defmodule Code.Formatter do
to_algebra_fun = &quoted_to_algebra(&1, context, &2)
{left_doc, _join, state} =
args_to_algebra_with_comments(left, meta, true, false, :glue, state, to_algebra_fun)
args_to_algebra_with_comments(left, meta, true, :force_comma, :glue, state, to_algebra_fun)
{right_doc, _join, state} =
args_to_algebra_with_comments(right, meta, false, false, :glue, state, to_algebra_fun)
args_to_algebra_with_comments(right, meta, false, :none, :glue, state, to_algebra_fun)
right_doc = "," |> glue(right_doc) |> force_keyword(right) |> group(:inherit)
right_doc = break() |> concat(right_doc) |> force_keyword(right) |> group(:inherit)
doc =
with_next_break_fits(true, right_doc, fn right_doc ->
@@ -1136,10 +1203,10 @@ defmodule Code.Formatter do
{doc, state}
end
defp skip_parens?(fun, meta, args, %{locals_without_parens: locals_without_parens}) do
defp local_without_parens?(fun, args, %{locals_without_parens: locals_without_parens}) do
length = length(args)
length > 0 and Keyword.get(meta, :no_parens, false) and
length > 0 and
Enum.any?(locals_without_parens, fn {key, val} ->
key == fun and (val == :* or val == length)
end)
@@ -1264,7 +1331,7 @@ defmodule Code.Formatter do
{args_doc, join, state} =
args
|> Enum.with_index()
|> args_to_algebra_with_comments(meta, false, false, join, state, to_algebra_fun)
|> args_to_algebra_with_comments(meta, false, :none, join, state, to_algebra_fun)
if join == :flex_glue do
{"<<" |> concat(args_doc) |> nest(2) |> concat(">>") |> group(), state}
@@ -1274,7 +1341,7 @@ defmodule Code.Formatter do
end
defp bitstring_segment_to_algebra({{:<-, meta, [left, right]}, i}, state, last) do
left = {:special, :bitstring_segment, [left, last]}
left = {{:special, :bitstring_segment}, meta, [left, last]}
{doc, state} = quoted_to_algebra({:<-, meta, [left, right]}, :parens_arg, state)
{bitstring_wrap_parens(doc, i, last), state}
end
@@ -1323,7 +1390,7 @@ defmodule Code.Formatter do
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, _join, state} =
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
args_to_algebra_with_comments(args, meta, false, :none, join, state, fun)
{surround("[", args_doc, "]"), state}
end
@@ -1334,7 +1401,7 @@ defmodule Code.Formatter do
{left_doc, state} = fun.(left, state)
{right_doc, _join, state} =
args_to_algebra_with_comments(right, meta, false, false, join, state, fun)
args_to_algebra_with_comments(right, meta, false, :none, join, state, fun)
args_doc =
left_doc
@@ -1350,7 +1417,7 @@ defmodule Code.Formatter do
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, _join, state} =
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
args_to_algebra_with_comments(args, meta, false, :none, join, state, fun)
name_doc = "%" |> concat(name_doc) |> concat("{")
{surround(name_doc, args_doc, "}"), state}
@@ -1361,7 +1428,7 @@ defmodule Code.Formatter do
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, join, state} =
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
args_to_algebra_with_comments(args, meta, false, :none, join, state, fun)
if join == :flex_glue do
{"{" |> concat(args_doc) |> nest(1) |> concat("}") |> group(), state}
@@ -1471,7 +1538,7 @@ defmodule Code.Formatter do
defp heredoc_line(["", _ | _]), do: nest(line(), :reset)
defp heredoc_line(_), do: line()
defp args_to_algebra_with_comments(args, meta, skip_parens?, next_break_fits?, join, state, fun) do
defp args_to_algebra_with_comments(args, meta, skip_parens?, last_arg_mode, join, state, fun) do
min_line = line(meta)
max_line = end_line(meta)
@@ -1479,10 +1546,11 @@ defmodule Code.Formatter do
{doc, state} = fun.(arg, state)
doc =
cond do
args != [] -> concat(doc, ",")
next_break_fits? -> next_break_fits(doc, :enabled)
true -> doc
case args do
[_ | _] -> concat_to_last_group(doc, ",")
[] when last_arg_mode == :force_comma -> concat_to_last_group(doc, ",")
[] when last_arg_mode == :next_break_fits -> next_break_fits(doc, :enabled)
[] when last_arg_mode == :none -> doc
end
{doc, @empty, newlines, state}
@@ -1545,10 +1613,16 @@ defmodule Code.Formatter do
{args_doc, state} = clause_args_to_algebra(args, min_line, state)
{body_doc, state} = block_to_algebra(body, min_line, max_line, state)
head =
args_doc
|> ungroup_if_group()
|> concat(" ->")
|> nest(:cursor)
|> group()
doc =
"fn "
|> concat(group(nest(args_doc, :cursor)))
|> concat(" ->")
|> concat(head)
|> glue(body_doc)
|> nest(2)
|> glue("end")
@@ -1594,15 +1668,12 @@ defmodule Code.Formatter do
{args_doc, state} = clause_args_to_algebra(args, min_line, state)
{body_doc, state} = block_to_algebra(body, min_line, max_line, state)
clause_doc =
" ->"
|> glue(body_doc)
|> nest(2)
doc =
args_doc
|> ungroup_if_group()
|> concat(" ->")
|> group()
|> concat(clause_doc)
|> concat(break() |> concat(body_doc) |> nest(2))
|> wrap_in_parens()
|> maybe_force_clauses(clauses)
|> group()
@@ -1670,7 +1741,15 @@ defmodule Code.Formatter do
state = %{state | operand_nesting: nesting}
{body_doc, state} = block_to_algebra(body, min_line, end_line(meta), state)
{concat(args_doc, " ->" |> glue(body_doc) |> nest(2)), state}
doc =
args_doc
|> ungroup_if_group()
|> concat(" ->")
|> group()
|> concat(break() |> concat(body_doc) |> nest(2))
{doc, state}
end
defp add_max_line_to_last_clause([{op, meta, args}], max_line) do
@@ -1686,7 +1765,7 @@ defmodule Code.Formatter do
fun = &clause_args_to_algebra/2
{args_docs, _join, state} =
args_to_algebra_with_comments([args], meta, false, false, :glue, state, fun)
args_to_algebra_with_comments([args], meta, false, :none, :glue, state, fun)
{args_docs, state}
end
@@ -1694,7 +1773,7 @@ defmodule Code.Formatter do
# fn a, b, c when d -> e end
defp clause_args_to_algebra([{:when, meta, args}], state) do
{args, right} = split_last(args)
left = {:special, :clause_args, [args]}
left = {{:special, :clause_args}, meta, [args]}
binary_op_to_algebra(:when, "when", meta, left, right, :no_parens_arg, state)
end
@@ -1824,6 +1903,9 @@ defmodule Code.Formatter do
## Quoted helpers
defp left_op_context(context), do: force_many_args_or_operand(context, :parens_arg)
defp right_op_context(context), do: force_many_args_or_operand(context, :operand)
defp force_many_args_or_operand(:no_parens_one_arg, _choice), do: :no_parens_arg
defp force_many_args_or_operand(:parens_one_arg, _choice), do: :parens_arg
defp force_many_args_or_operand(:no_parens_arg, _choice), do: :no_parens_arg
@@ -1985,26 +2067,29 @@ defmodule Code.Formatter do
)
end
# A literal list is a keyword or (... -> ...)
defp last_arg_to_keyword([_ | _] = arg, _list_to_keyword?) do
{keyword?(arg), arg}
end
# This is a list of tuples, it can be converted to keywords.
defp last_arg_to_keyword({:__block__, _, [[_ | _] = arg]} = block, true) do
if keyword?(arg), do: {true, arg}, else: {false, block}
end
# Otherwise we don't have a keyword.
defp last_arg_to_keyword(arg, _list_to_keyword?) do
{false, arg}
end
defp force_keyword?(keyword) do
match?([_, _ | _], keyword) and force_keyword?(keyword, MapSet.new())
match?([{_, _}, _ | _], keyword) and force_keyword?(keyword, MapSet.new())
end
defp force_keyword?([{{_, meta, _}, _} | keyword], lines) do
line = line(meta)
if line in lines do
if MapSet.member?(lines, line) do
false
else
force_keyword?(keyword, MapSet.put(lines, line))
@@ -2049,6 +2134,20 @@ defmodule Code.Formatter do
## Algebra helpers
# Relying on the inner document is brittle and error prone.
# It would be best if we had a mechanism to apply this.
defp concat_to_last_group({:doc_cons, left, right}, concat) do
{:doc_cons, left, concat_to_last_group(right, concat)}
end
defp concat_to_last_group({:doc_group, group, mode}, concat) do
{:doc_group, {:doc_cons, group, concat}, mode}
end
defp concat_to_last_group(other, concat) do
{:doc_cons, other, concat}
end
defp ungroup_if_group({:doc_group, group, _mode}), do: group
defp ungroup_if_group(other), do: other
+3
View File
@@ -101,6 +101,9 @@ defimpl Collectable, for: BitString do
acc, {:cont, x} when is_bitstring(x) ->
<<IO.iodata_to_binary(acc)::bitstring, x::bitstring>>
acc, :done when is_bitstring(acc) ->
acc
acc, :done ->
IO.iodata_to_binary(acc)
+49 -24
View File
@@ -11,21 +11,27 @@ defmodule DynamicSupervisor do
## Examples
A dynamic supervisor is started with no children, only with the
supervision strategy (the only strategy currently supported is
`:one_for_one`):
A dynamic supervisor is started with no children, often under a
supervisor with the supervision strategy (the only strategy currently
supported is `:one_for_one`) and a name:
{:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)
children = [
{DynamicSupervisor, strategy: :one_for_one, name: MyApp.DynamicSupervisor}
]
Supervisor.start_link(strategy: :one_for_one)
The options given in the child specification are documented in `start_link/1`.
Once the dynamic supervisor is running, we can start children
with `start_child/2`, which receives a child specification:
{:ok, agent1} = DynamicSupervisor.start_child(sup, {Agent, fn -> %{} end})
{:ok, agent1} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Agent, fn -> %{} end})
Agent.update(agent1, &Map.put(&1, :key, "value"))
Agent.get(agent1, & &1)
#=> %{key: "value"}
{:ok, agent2} = DynamicSupervisor.start_child(sup, {Agent, fn -> %{} end})
{:ok, agent2} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Agent, fn -> %{} end})
Agent.get(agent2, & &1)
#=> %{}
@@ -74,16 +80,17 @@ defmodule DynamicSupervisor do
end
def start_child(foo, bar, baz) do
# This will start child by calling MyWorker.start_link(implicit_arg, foo, bar, baz)
# This will start child by calling MyWorker.start_link(initial_arg, foo, bar, baz)
Supervisor.start_child(__MODULE__, [foo, bar, baz])
end
def init(implicit_arg) do
def init(initial_arg) do
children = [
worker(MyWorker, [implicit_arg])
# Or the deprecated: worker(MyWorker, [initial_arg])
%{id: MyWorker, start: {MyWorker, :start_link, [initial_arg]})
]
supervise(children, strategy: :simple_one_for_one)
Supervisor.init(children, strategy: :simple_one_for_one)
end
end
@@ -97,22 +104,23 @@ defmodule DynamicSupervisor do
end
def start_child(foo, bar, baz) do
# This will start child by calling MyWorker.start_link(implicit_arg, foo, bar, baz)
spec = Supervisor.Spec.worker(MyWorker, [foo, bar, baz])
# If MyWorker is not using the new child specs, we need to pass a map:
# spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}}
spec = {MyWorker, foo: foo, bar: bar, baz: baz}
DynamicSupervisor.start_child(__MODULE__, spec)
end
def init(implicit_arg) do
def init(initial_arg) do
DynamicSupervisor.init(
strategy: :one_for_one,
extra_arguments: [implicit_arg]
extra_arguments: [initial_arg]
)
end
end
The difference is that the `DynamicSupervisor` expects the child specification
at the moment `start_child/2` is called, and no longer on the init callback.
If there are any implicit arguments given on initialization, such as `[implicit_arg]`,
If there are any initial arguments given on initialization, such as `[initial_arg]`,
it can be given in the `:extra_arguments` flag on `DynamicSupervisor.init/1`.
"""
@@ -165,7 +173,12 @@ defmodule DynamicSupervisor do
restarts: []
]
@doc false
@doc """
Returns a specification to start a dynamic supervisor under a supervisor.
See `Supervisor`.
"""
@since "1.6.1"
def child_spec(arg) do
%{
id: DynamicSupervisor,
@@ -179,7 +192,11 @@ defmodule DynamicSupervisor do
quote location: :keep, bind_quoted: [opts: opts] do
@behaviour DynamicSupervisor
@doc false
@doc """
Returns a specification to start this module under a supervisor.
See `Supervisor`.
"""
def child_spec(arg) do
default = %{
id: __MODULE__,
@@ -478,7 +495,14 @@ defmodule DynamicSupervisor do
case mod.init(args) do
{:ok, flags} when is_map(flags) ->
state = %DynamicSupervisor{mod: mod, args: args, name: name || {self(), mod}}
name =
cond do
is_nil(name) -> {self(), mod}
is_atom(name) -> {:local, name}
is_tuple(name) -> name
end
state = %DynamicSupervisor{mod: mod, args: args, name: name}
case init(state, flags) do
{:ok, state} -> {:ok, state}
@@ -902,8 +926,9 @@ defmodule DynamicSupervisor do
defp restart_child(:one_for_one, current_pid, child, state) do
{{m, f, args} = mfa, restart, shutdown, type, modules} = child
%{extra_arguments: extra} = state
case start_child(m, f, args) do
case start_child(m, f, extra ++ args) do
{:ok, pid, _} ->
state = delete_child(current_pid, state)
{:ok, save_child(pid, mfa, restart, shutdown, type, modules, state)}
@@ -922,21 +947,21 @@ defmodule DynamicSupervisor do
end
end
defp report_error(error, reason, pid, child, %{name: name}) do
defp report_error(error, reason, pid, child, %{name: name, extra_arguments: extra}) do
:error_logger.error_report(
:supervision_report,
:supervisor_report,
supervisor: name,
errorContext: error,
reason: reason,
offender: extract_child(pid, child)
offender: extract_child(pid, child, extra)
)
end
defp extract_child(pid, {mfa, restart, shutdown, type, _modules}) do
defp extract_child(pid, {{m, f, args}, restart, shutdown, type, _modules}, extra) do
[
pid: pid,
id: :undefined,
mfargs: mfa,
mfargs: {m, f, extra ++ args},
restart_type: restart,
shutdown: shutdown,
child_type: type
+2 -1
View File
@@ -161,7 +161,8 @@ defimpl Enumerable, for: GenEvent.Stream do
defp flush_events(ref) do
receive do
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
{_from, {_pid, ^ref}, {notify, _event}}
when notify in [:notify, :ack_notify, :sync_notify] ->
flush_events(ref)
after
0 -> :ok
+5 -1
View File
@@ -570,7 +570,11 @@ defmodule GenServer do
quote location: :keep, bind_quoted: [opts: opts] do
@behaviour GenServer
@doc false
@doc """
Returns a specification to start this module under a supervisor.
See `Supervisor`.
"""
def child_spec(arg) do
default = %{
id: __MODULE__,
+33 -14
View File
@@ -2709,7 +2709,10 @@ defmodule Kernel do
case function? do
true ->
value =
with {_, doc} when doc_attr? <- Module.get_attribute(env.module, name, stack), do: doc
case Module.get_attribute(env.module, name, stack) do
{_, doc} when doc_attr? -> doc
other -> other
end
try do
:elixir_quote.escape(value, false)
@@ -2722,13 +2725,21 @@ defmodule Kernel do
{val, _} -> val
end
false when doc_attr? ->
{escaped, _} = :elixir_quote.escape(stack, false)
quote do
case Module.get_attribute(__MODULE__, unquote(name), unquote(escaped)) do
{_, doc} -> doc
other -> other
end
end
false ->
{escaped, _} = :elixir_quote.escape(stack, false)
quote do
with {_, doc} when unquote(doc_attr?) <-
Module.get_attribute(__MODULE__, unquote(name), unquote(escaped)),
do: doc
Module.get_attribute(__MODULE__, unquote(name), unquote(escaped))
end
end
end
@@ -4570,25 +4581,33 @@ defmodule Kernel do
defp define_guard(kind, guard, env) do
case :elixir_utils.extract_guards(guard) do
{call, impl} when length(impl) < 2 ->
{call, [_, _ | _]} ->
raise ArgumentError,
"invalid syntax in defguard #{Macro.to_string(call)}, " <>
"only a single when clause is allowed"
{call, impls} ->
case Macro.decompose_call(call) do
{_name, args} ->
validate_variable_only_args!(call, args)
quoted =
quote do
require Kernel.Utils
Kernel.Utils.defguard(unquote(args), unquote(impl))
end
case impls do
[] ->
define(kind, call, nil, env)
define(kind, call, [do: quoted], env)
[guard] ->
quoted =
quote do
require Kernel.Utils
Kernel.Utils.defguard(unquote(args), unquote(guard))
end
define(kind, call, [do: quoted], env)
end
_invalid_definition ->
raise ArgumentError, "invalid syntax in defguard #{Macro.to_string(call)}"
end
{call, _multiple_impls} ->
raise ArgumentError, "invalid syntax in defguard #{Macro.to_string(call)}"
end
end
@@ -356,6 +356,7 @@ defmodule Kernel.ParallelCompiler do
{:file_done, child_pid, file, {kind, reason, stack}} ->
discard_down(child_pid)
print_error(file, kind, reason, stack)
cancel_waiting_timer(queued, child_pid)
terminate(queued)
{:error, [to_error(file, kind, reason, stack)], warnings}
+15 -12
View File
@@ -208,9 +208,9 @@ defmodule Kernel.Utils do
end)
end
# Finds every reference to `refs` in `expr` and wraps them in an unquote.
defp unquote_every_ref(expr, refs) do
Macro.postwalk(expr, fn
# Finds every reference to `refs` in `guard` and wraps them in an unquote.
defp unquote_every_ref(guard, refs) do
Macro.postwalk(guard, fn
{ref, _meta, context} = var when is_atom(ref) and is_atom(context) ->
case {ref, context} in refs do
true -> literal_unquote(var)
@@ -222,10 +222,10 @@ defmodule Kernel.Utils do
end)
end
# Prefaces `expr` with unquoted versions of `refs`.
defp unquote_refs_once(expr, refs) do
{^expr, used_refs} =
Macro.postwalk(expr, [], fn
# Prefaces `guard` with unquoted versions of `refs`.
defp unquote_refs_once(guard, refs) do
{_, used_refs} =
Macro.postwalk(guard, [], fn
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
case {ref, context} in refs and {ref, context} not in acc do
true -> {var, [{ref, context} | acc]}
@@ -236,14 +236,17 @@ defmodule Kernel.Utils do
{node, acc}
end)
for {ref, context} <- :lists.reverse(used_refs) do
var = {ref, [], context}
quote do: unquote(var) = unquote(literal_unquote(var))
end ++ List.wrap(expr)
vars = for {ref, context} <- :lists.reverse(used_refs), do: {ref, [], context}
exprs = for var <- vars, do: literal_unquote(var)
quote do
{unquote_splicing(vars)} = {unquote_splicing(exprs)}
unquote(guard)
end
end
defp literal_quote(ast) do
{:quote, [], [[do: {:__block__, [], List.wrap(ast)}]]}
{:quote, [], [[do: ast]]}
end
defp literal_unquote(ast) do
+50 -34
View File
@@ -297,6 +297,8 @@ defmodule Module do
When just a module is provided, the function is assumed to be
`__after_compile__/2`.
Callbacks registered first will run last.
#### Example
defmodule MyModule do
@@ -312,13 +314,18 @@ defmodule Module do
A hook that will be invoked before the module is compiled.
Accepts a module or a `{module, function_or_macro_name}` tuple. The
function/macro must take one argument: the module environment. If it's a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
function/macro must take one argument: the module environment. If
it's a macro, its returned value will be injected at the end of the
module definition before the compilation starts.
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
Callbacks registered first will run last. Any overridable definition
will be made concrete before the first callback runs. A definition may
be made overridable again in another before compile callback and it
will be made concrete one last time after after all callbacks run.
*Note*: unlike `@after_compile`, the callback function/macro must
be placed in a separate module (because when the callback is invoked,
the current module does not yet exist).
@@ -1195,6 +1202,20 @@ defmodule Module do
raise ArgumentError, "expected an Elixir module, got: #{inspect(original)}"
end
@doc false
# TODO: Remove in 2.0 - deprecated.
def add_doc(module, line, kind, function_tuple, signature \\ [], doc) do
assert_not_compiled!(:add_doc, module)
if kind in [:defp, :defmacrop, :typep] do
if doc, do: {:error, :private_doc}, else: :ok
else
table = data_table_for(module)
compile_doc(table, line, kind, function_tuple, signature, doc, __ENV__, false)
:ok
end
end
@doc false
# Used internally to compile documentation.
# This function is private and must be used only internally.
@@ -1205,22 +1226,17 @@ defmodule Module do
pair = {name, arity}
impl = compile_impl(table, name, env, kind, args)
compile_doc(table, pair, env, kind, args, impl)
compile_deprecated(table, pair)
_deprecated = compile_deprecated(table, pair)
_since = compile_since(table)
# TODO: Store @since and @deprecated alongside the docs
{line, doc} = get_doc_info(table, env)
compile_doc(table, line, kind, pair, args, doc, env, impl)
:ok
end
defp compile_doc(table, pair, env, kind, args, impl) do
{line, doc} = get_doc_info(table, env, impl)
# TODO: Store @since alongside the docs
_ = get_since_info(table)
add_doc(table, line, kind, pair, args, doc, env)
end
defp add_doc(_table, line, kind, {name, arity}, _args, doc, env)
defp compile_doc(_table, line, kind, {name, arity}, _args, doc, env, _impl)
when kind in [:defp, :defmacrop] do
if doc do
error_message =
@@ -1231,23 +1247,37 @@ defmodule Module do
end
end
defp add_doc(table, line, kind, pair, args, doc, env) do
defp compile_doc(table, line, kind, pair, args, doc, env, impl) do
signature = build_signature(args, env)
case :ets.lookup(table, {:doc, pair}) do
[] ->
doc = if is_nil(doc) && impl, do: false, else: doc
:ets.insert(table, {{:doc, pair}, line, kind, signature, doc})
[{doc_tuple, line, _current_kind, current_sign, current_doc}] ->
signature = merge_signatures(current_sign, signature, 1)
doc = if is_nil(doc), do: current_doc, else: doc
doc = if is_nil(doc) && impl, do: false, else: doc
:ets.insert(table, {doc_tuple, line, kind, signature, doc})
end
end
defp compile_since(table) do
case :ets.take(table, :since) do
[{:since, since, _, _}] when is_binary(since) -> since
_ -> nil
end
end
defp compile_deprecated(table, pair) do
if reason = get_deprecated_info(table) do
:ets.insert(table, {{:deprecated, pair}, reason})
case :ets.take(table, :deprecated) do
[{:deprecated, reason, _, _}] when is_binary(reason) ->
:ets.insert(table, {{:deprecated, pair}, reason})
reason
_ ->
nil
end
end
@@ -1723,27 +1753,13 @@ defmodule Module do
value
end
defp get_doc_info(table, env, impl) do
defp get_doc_info(table, env) do
case :ets.take(table, :doc) do
[{:doc, {_, _} = pair, _, _}] ->
pair
[] when impl == false ->
{env.line, nil}
[] ->
{env.line, false}
end
end
defp get_since_info(table) do
:ets.take(table, :since)
end
defp get_deprecated_info(table) do
case :ets.take(table, :deprecated) do
[{:deprecated, reason, _, _}] -> reason
[] -> nil
{env.line, nil}
end
end
+1 -1
View File
@@ -132,7 +132,7 @@ defmodule Port do
script in bash:
#!/bin/sh
"$@"
"$@" &
pid=$!
while read line ; do
:
+6 -1
View File
@@ -321,7 +321,12 @@ defmodule Registry do
start_link([keys: keys, name: name] ++ options)
end
@doc false
@doc """
Returns a specification to start a registry under a supervisor.
See `Supervisor`.
"""
@since "1.5.0"
def child_spec(opts) do
%{
id: Keyword.get(opts, :name, Registry),
+2 -2
View File
@@ -318,11 +318,11 @@ defmodule Stream do
"""
@spec drop(Enumerable.t(), non_neg_integer) :: Enumerable.t()
def drop(enum, n) when n >= 0 do
def drop(enum, n) when is_integer(n) and n >= 0 do
lazy(enum, n, fn f1 -> R.drop(f1) end)
end
def drop(enum, n) when n < 0 do
def drop(enum, n) when is_integer(n) and n < 0 do
n = abs(n)
lazy(enum, {0, [], []}, fn f1 ->
+13 -9
View File
@@ -101,13 +101,10 @@ defmodule Supervisor do
## Start and shutdown
When the supervisor starts, it traverses all children and retrieves
each child specification. It is at this moment `{Stack, [:hello]}`
becomes a child specification by calling `Stack.child_spec([:hello])`.
Then the supervisor starts each child in the order they are defined.
This is done by calling the function defined under the `:start` key
in the child specification and typically defaults to `start_link/1`.
When the supervisor starts, it traverses all child specifications and
then starts each child in the order they are defined. This is done by
calling the function defined under the `:start` key in the child
specification and typically defaults to `start_link/1`.
The `start_link/1` (or a custom) is then called for each child process.
The `start_link/1` function must return `{:ok, pid}` where `pid` is the
@@ -301,7 +298,9 @@ defmodule Supervisor do
function.
You may also completely override the `child_spec/1` function in the Stack module
and return your own child specification.
and return your own child specification. Note there is no guarantee the `child_spec/1`
function will be called by the Supervisor process, as other processes may invoke
it to retrieve the child specification before reaching the supervisor.
## Exit reasons and restarts
@@ -459,8 +458,13 @@ defmodule Supervisor do
defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
import Supervisor.Spec
@behaviour Supervisor
@doc false
@doc """
Returns a specification to start this module under a supervisor.
See `Supervisor`.
"""
def child_spec(arg) do
default = %{
id: __MODULE__,
+12 -2
View File
@@ -173,7 +173,12 @@ defmodule Task do
@type t :: %__MODULE__{}
@doc false
@doc """
Returns a specification to start a task under a supervisor.
See `Supervisor`.
"""
@since "1.5.0"
def child_spec(arg) do
%{
id: Task,
@@ -185,7 +190,12 @@ defmodule Task do
@doc false
defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
@doc false
@doc """
Returns a specification to start this module under a supervisor.
See `Supervisor`.
"""
@since "1.5.0"
def child_spec(arg) do
default = %{
id: __MODULE__,
+31 -5
View File
@@ -5,8 +5,18 @@ defmodule Task.Supervisor do
This module defines a supervisor which can be used to dynamically
supervise tasks.
`start_link/1` can be used to start the supervisor. See the `Task`
module for more examples.
A task supervisor is started with no children, often under a
supervisor and a name:
children = [
{Task.Supervisor, name: MyApp.TaskSupervisor}
]
Supervisor.start_link(strategy: :one_for_one)
The options given in the child specification are documented in `start_link/1`.
See the `Task` module for more examples.
## Name registration
@@ -32,17 +42,33 @@ defmodule Task.Supervisor do
@doc """
Starts a new supervisor.
The supported options are:
## Examples
A task supervisor is typically started under a supervision tree using
the tuple format:
{Task.Supervisor, name: MyApp.TaskSupervisor}
You can also start it by calling `start_link/1` directly:
Task.Supervisor.start_link(name: MyApp.TaskSupervisor)
But this is recommended only for scripting and should be avoided in
production code. Generally speaking, processes should always be started
inside supervision trees.
## Options
* `:name` - used to register a supervisor name, the supported values are
described under the `Name Registration` section in the `GenServer` module
docs;
* `:max_restarts`, `:max_seconds` and `:max_children` - as specified in `DynamicSupervisor`;
* `:max_restarts`, `:max_seconds` and `:max_children` - as specified in
`DynamicSupervisor`;
This function could also receive `:restart` and `:shutdown` as options
but those two options have been deprecated and it is now preferred to
give them directly to `start_child` and `async` when supported.
give them directly to `start_child` and `async`.
"""
@spec start_link([option]) :: Supervisor.on_start()
# TODO: Deprecate passing restart and shutdown here on Elixir v1.8.
@@ -35,6 +35,7 @@ Deprecated feature | Deprecated in | Replaced by (
`Enum.partition/2` | [v1.6] | `Enum.split_with/2` (v1.4)
`Keyword.replace/3` | [v1.6] | Use `Keyword.fetch/2` + `Keyword.put/3` (v1.0)
`Macro.unescape_tokens/1` and `Macro.unescape_tokens/2` | [v1.6] | Use `Enum.map/2` to traverse over the arguments (v1.0)
`Module.add_doc/6` | [v1.6] | Use `@doc` instead
`Map.replace/3` | [v1.6] | Use `Map.fetch/2` + `Map.put/3` (v1.0)
`Range.range?/1` | [v1.6] | Pattern match on `_.._` instead (v1.0)
`Atom.to_char_list/1` | [v1.5] | `Atom.to_charlist/1` (v1.3)
+2 -1
View File
@@ -109,7 +109,8 @@ Other constructs are `for`, `with`, `try`/`rescue`/`catch`/`else`/, and the `mat
## Failing guards
Errors in guards do not result in runtime errors, but in guards failing. For example, the `length/1` function only works with lists. If we use it with anything else, a runtime error is raised:
In guards, when functions would normally raise exceptions, they cause the guard to fail instead.
For example, the `length/1` function only works with lists. If we use it with anything else, a runtime error is raised:
```elixir
iex> length("hello")
+1 -1
View File
@@ -26,7 +26,7 @@ Operator
`\|` | Right to left
`::` | Right to left
`when` | Right to left
`<-`, `\\` | Left to right
`<-` `\\` | Left to right
## Comparison operators
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.6.1"},
{vsn, "1.6.4"},
{modules, [
elixir
]},
+7 -4
View File
@@ -190,12 +190,15 @@ run_with_location_change(File, E, Callback) ->
def_to_clauses(_Kind, Meta, Args, [], nil, E) ->
check_args_for_bodiless_clause(Meta, Args, E),
[];
def_to_clauses(Kind, Meta, _Args, _Guards, nil, E) ->
elixir_errors:form_error(Meta, ?key(E, file), elixir_expand, {missing_option, Kind, [do]});
def_to_clauses(_Kind, Meta, Args, Guards, [{do, Body}], _E) ->
[{Meta, Args, Guards, Body}];
def_to_clauses(Kind, Meta, Args, Guards, Body, _E) ->
[{Meta, Args, Guards, {'try', [{origin, Kind} | Meta], [Body]}}].
def_to_clauses(Kind, Meta, Args, Guards, Body, E) ->
case is_list(Body) andalso lists:keyfind(do, 1, Body) of
{do, _} ->
[{Meta, Args, Guards, {'try', [{origin, Kind} | Meta], [Body]}}];
false ->
elixir_errors:form_error(Meta, ?key(E, file), elixir_expand, {missing_option, Kind, [do]})
end.
run_on_definition_callbacks(Kind, Module, Name, Args, Guards, Body, E) ->
Callbacks = ets:lookup_element(elixir_module:data_table(Module), on_definition, 2),
+4
View File
@@ -397,6 +397,10 @@ deprecation('Elixir.Macro', unescape_tokens, 1) ->
"instead traverse over the arguments using Enum.map/2";
deprecation('Elixir.Macro', unescape_tokens, 2) ->
"instead traverse over the arguments using Enum.map/2";
deprecation('Elixir.Module', add_doc, 5) ->
"use @doc instead";
deprecation('Elixir.Module', add_doc, 6) ->
"use @doc instead";
deprecation('Elixir.Range', 'range?', 1) ->
"instead pattern match on left..right";
deprecation('Elixir.Stream', filter_map, 3) ->
+17 -7
View File
@@ -21,7 +21,7 @@ translate(Meta, Args, Return, S) ->
case comprehension_expr(TInto, TExpr) of
{inline, TIntoExpr} ->
{build_inline(Ann, TCases, TIntoExpr, TInto, TUniq, SF), SF};
build_inline(Ann, TCases, TIntoExpr, TInto, TUniq, SF);
{into, TIntoExpr} ->
build_into(Ann, TCases, TIntoExpr, TInto, TUniq, SF)
end.
@@ -91,17 +91,17 @@ collect_filters([], Acc) ->
build_inline(Ann, Clauses, Expr, Into, Uniq, S) ->
case not Uniq and lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
true -> build_comprehension(Ann, Clauses, Expr, Into);
true -> {build_comprehension(Ann, Clauses, Expr, Into), S};
false -> build_inline_each(Ann, Clauses, Expr, Into, Uniq, S)
end.
build_inline_each(Ann, Clauses, Expr, false, Uniq, S) ->
InnerFun = fun(InnerExpr, _InnerAcc) -> InnerExpr end,
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S);
{build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S), S};
build_inline_each(Ann, Clauses, Expr, {nil, _} = Into, Uniq, S) ->
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerExpr, InnerAcc} end,
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
elixir_erl:remote(Ann, lists, reverse, [ReduceExpr]);
{elixir_erl:remote(Ann, lists, reverse, [ReduceExpr]), S};
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
{InnerValue, SV} = build_var(Ann, S),
@@ -135,11 +135,21 @@ build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
end,
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
elixir_erl:remote(Ann, erlang, iolist_to_binary, [ReduceExpr]).
{{'case', Ann, ReduceExpr, [
{clause, Ann,
[InnerValue],
[[elixir_erl:remote(Ann, erlang, is_bitstring, [InnerValue])]],
[InnerValue]},
{clause, Ann,
[InnerValue],
[],
[elixir_erl:remote(Ann, erlang, iolist_to_binary, [InnerValue])]}
]}, SV}.
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, S) ->
ReduceExpr = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
{elixir_erl:remote(Ann, maps, from_list, [ReduceExpr]), S};
{ReduceExpr, SR} = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
{elixir_erl:remote(Ann, maps, from_list, [ReduceExpr]), SR};
build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
{Fun, SF} = build_var(Ann, S),
{Acc, SA} = build_var(Ann, SF),
@@ -201,6 +201,21 @@ defmodule Code.Formatter.CallsTest do
assert_format bad, good, @short_length
end
test "with arguments on comma limit" do
bad = """
import(foo(abc, cde), :next)
"""
good = """
import(
foo(abc, cde),
:next
)
"""
assert_format bad, good, @medium_length
end
test "with keyword lists" do
assert_same "foo(foo: 1, bar: 2)"
@@ -263,6 +278,22 @@ defmodule Code.Formatter.CallsTest do
assert_format bad, good, @short_length
end
test "without parens on comma limit" do
bad = """
import foo(abc, cde), :next
"""
good = """
import foo(
abc,
cde
),
:next
"""
assert_format bad, good, @medium_length
end
test "without parens and with keyword lists preserves multiline" do
assert_same """
defstruct foo: 1,
@@ -276,6 +307,22 @@ defmodule Code.Formatter.CallsTest do
"""
end
test "without parens and with keyword lists on comma limit" do
bad = """
import foo(abc, cde), opts: :next
"""
good = """
import foo(
abc,
cde
),
opts: :next
"""
assert_format bad, good, @medium_length
end
test "without parens and with keyword lists on line limit" do
assert_same "import :atom, opts: [foo: :bar]"
@@ -434,9 +481,32 @@ defmodule Code.Formatter.CallsTest do
)
"""
assert_same """
call(
:hello,
:foo,
:bar
) do
1 + 2
end
"""
# Doesn't preserve this because only the beginning has a newline
assert_format "call(\nfoo, bar, baz)", "call(foo, bar, baz)"
# Doesn't preserve because there are no args
bad = """
call() do
1 + 2
end
"""
assert_format bad, """
call do
1 + 2
end
"""
# Doesn't preserve because we have a single argument with next break fits
bad = """
call(
@@ -56,36 +56,19 @@ defmodule Code.Formatter.CommentsTest do
assert_format bad, good
bad = """
foo # this is foo
|> bar # this is bar
|> baz # this is baz
"""
good = """
# this is foo
# this is bar
# this is baz
foo
|> bar
|> baz
"""
assert_format bad, good, @short_length
bad = """
foo # this is foo
| bar # this is bar
| baz # this is baz
++ bar # this is bar
++ baz # this is baz
"""
good = """
# this is foo
# this is bar
# this is baz
foo
| bar
| baz
foo ++
bar ++
baz
"""
assert_format bad, good, @short_length
@@ -721,6 +704,118 @@ defmodule Code.Formatter.CommentsTest do
end
end
describe "operators" do
test "with comment before, during and after uniform pipelines" do
assert_same """
foo
# |> bar
# |> baz
|> bat
"""
bad = """
# before
foo # this is foo
|> bar # this is bar
|> baz # this is baz
# after
"""
good = """
# before
# this is foo
foo
# this is bar
|> bar
# this is baz
|> baz
# after
"""
assert_format bad, good, @short_length
end
test "with comment before, during and after mixed pipelines" do
assert_same """
foo
# |> bar
# |> baz
~> bat
"""
bad = """
# before
foo # this is foo
~> bar # this is bar
<|> baz # this is baz
# after
"""
good = """
# before
# this is foo
foo
# this is bar
~> bar
# this is baz
<|> baz
# after
"""
assert_format bad, good, @short_length
end
test "with comment before, during and after uniform right" do
assert_same """
foo
# | bar
# | baz
| bat
"""
bad = """
# before
foo # this is foo
| bar # this is bar
| baz # this is baz
# after
"""
good = """
# before
# this is foo
foo
# this is bar
| bar
# this is baz
| baz
# after
"""
assert_format bad, good, @short_length
end
test "with comment before, during and after mixed right" do
assert_same """
one
# when two
# when three
when four
# | five
| six
"""
end
test "handles nodes without meta info" do
assert_same "(a -> b) |> (c -> d)"
assert_same "(a -> b) when c: d"
assert_same "(a -> b) when (c -> d)"
end
end
describe "containers" do
test "with comment outside before, during and after" do
assert_same ~S"""
@@ -178,9 +178,30 @@ defmodule Code.Formatter.ContainersTest do
assert_same keyword, @short_length
end
test "with keyword lists on comma line limit" do
bad = """
[
foooo: 1,
barrr: 2
]
"""
good = """
[
foooo:
1,
barrr: 2
]
"""
assert_format bad, good, @short_length
end
test "with quoted keyword lists" do
assert_same ~S(["with spaces": 1])
assert_same ~S(["one #{two} three": 1])
assert_same ~S(["\w": 1, "\\w": 2])
assert_same ~S(["Elixir.Foo": 1, "Elixir.Bar": 2])
assert_format ~S(["Foo": 1, "Bar": 2]), ~S([Foo: 1, Bar: 2])
end
@@ -455,7 +476,7 @@ defmodule Code.Formatter.ContainersTest do
}
"""
assert_format bad, good, @short_length
assert_format bad, good, line_length: 11
end
test "removes trailing comma" do
@@ -559,7 +580,7 @@ defmodule Code.Formatter.ContainersTest do
}
"""
assert_format bad, good, @short_length
assert_format bad, good, line_length: 11
end
test "removes trailing comma" do
@@ -329,6 +329,46 @@ defmodule Code.Formatter.GeneralTest do
end
"""
end
test "with -> on line limit" do
bad = """
fn ab, cd ->
ab + cd
end
"""
good = """
fn ab,
cd ->
ab + cd
end
"""
assert_format bad, good, @short_length
bad = """
fn
ab, cd ->
1
xy, zw ->
2
end
"""
good = """
fn
ab,
cd ->
1
xy,
zw ->
2
end
"""
assert_format bad, good, @short_length
end
end
describe "anonymous functions types" do
@@ -348,17 +348,17 @@ defmodule Code.Formatter.IntegrationTest do
test "no parens keywords at the end of the line" do
bad = """
defmodule Mod do
defp token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char), do: token_list_downcase(rest, acc)
defp token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33), do: token_list_downcase(rest, acc)
def token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char), do: token_list_downcase(rest, acc)
def token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33), do: token_list_downcase(rest, acc)
end
"""
assert_format bad, """
defmodule Mod do
defp token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char),
def token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char),
do: token_list_downcase(rest, acc)
defp token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33),
def token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33),
do: token_list_downcase(rest, acc)
end
"""
@@ -436,8 +436,8 @@ defmodule Code.Formatter.IntegrationTest do
"""
end
test "first argument in a call without parens" do
bad = """
test "first argument in a call without parens with comments" do
assert_same """
with bar ::
:ok
| :invalid
@@ -445,28 +445,58 @@ defmodule Code.Formatter.IntegrationTest do
| :other
"""
assert_format bad, """
# | :unknown
with bar ::
:ok
| :invalid
| :other
"""
bad = """
assert_same """
@spec bar ::
:ok
| :invalid
# | :unknown
| :other
"""
end
test "capture with operators" do
assert_same """
"this works" |> (&String.upcase/1) |> (&String.downcase/1)
"""
assert_same """
"this works" || (&String.upcase/1) || (&String.downcase/1)
"""
assert_same """
"this works" == (&String.upcase/1) == (&String.downcase/1)
"""
bad = """
"this works" = (&String.upcase/1) = (&String.downcase/1)
"""
assert_format bad, """
# | :unknown
@spec bar ::
:ok
| :invalid
| :other
"this works" = (&String.upcase/1) = &String.downcase/1
"""
bad = """
"this works" ++ (&String.upcase/1) ++ (&String.downcase/1)
"""
assert_format bad, """
"this works" ++ (&String.upcase/1) ++ &String.downcase/1
"""
bad = """
"this works" | (&String.upcase/1) | (&String.downcase/1)
"""
assert_format bad, """
"this works" | (&String.upcase/1) | &String.downcase/1
"""
bad = ~S"""
"this works" \\ (&String.upcase/1) \\ (&String.downcase/1)
"""
assert_format bad, ~S"""
"this works" \\ &String.upcase/1 \\ &String.downcase/1
"""
end
end
@@ -812,9 +812,9 @@ defmodule Code.Formatter.OperatorsTest do
end
test "with operators outside" do
assert_same "(& &1) == & &2"
assert_same "(& &1) and & &2"
assert_same "(&foo/1) and &bar/1"
assert_same "(& &1) == (& &2)"
assert_same "(& &1) and (& &2)"
assert_same "(&foo/1) and (&bar/1)"
assert_same "[(&IO.puts/1) | &IO.puts/2]"
end
@@ -353,6 +353,26 @@ defmodule DynamicSupervisorTest do
assert {:error, :max_children} = DynamicSupervisor.start_child(pid, child)
end
test "restarting a child with extra_args successfully restarts child" do
parent = self()
fun = fn ->
send(parent, :from_child)
:timer.sleep(:infinity)
end
{:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one, extra_arguments: [fun])
child = %{id: Task, restart: :transient, start: {Task, :start_link, []}}
assert {:ok, child} = DynamicSupervisor.start_child(sup, child)
assert is_pid(child)
assert_receive :from_child
assert %{active: 1, workers: 1} = DynamicSupervisor.count_children(sup)
assert_kill(child, :oops)
assert_receive :from_child
assert %{workers: 1, active: 1} = DynamicSupervisor.count_children(sup)
end
test "child is restarted when trying again" do
child = current_module_worker([:try_again, self()], restart: :permanent)
{:ok, pid} = DynamicSupervisor.start_link(strategy: :one_for_one, max_restarts: 2)
@@ -178,6 +178,9 @@ defmodule Kernel.DocsTest do
@doc false
def qux(true), do: false
Module.add_doc(__MODULE__, __ENV__.line, :def, {:nullary, 0}, [], "add_doc")
def nullary, do: 0
end
)
@@ -191,6 +194,7 @@ defmodule Kernel.DocsTest do
{{:bar, 1}, _, :def, [{:arg, _, nil}], "Multiple bodiless clause doc"},
{{:baz, 1}, _, :def, [{:arg, _, nil}], "Multiple bodiless clause and docs"},
{{:foo, 1}, _, :def, [{:arg, _, nil}], "Function doc"},
{{:nullary, 0}, _, :def, [], "add_doc"},
{{:qux, 1}, _, :def, [{:bool, _, Elixir}], false}
] = docs[:docs]
@@ -207,4 +211,42 @@ defmodule Kernel.DocsTest do
] = docs[:callback_docs]
end
end
test "@impl true doesn't set @doc false if previous implementation has docs" do
write_beam(
defmodule Docs do
defmodule SampleBehaviour do
@callback foo(any()) :: any()
@callback bar() :: any()
@callback baz() :: any()
end
@behaviour SampleBehaviour
@doc "Foo docs"
def foo(nil), do: nil
@impl true
def foo(_), do: false
@impl true
def bar(), do: true
@doc "Baz docs"
@impl true
def baz(), do: true
def fuz(), do: true
end
)
docs = Code.get_docs(Docs, :all)
assert [
{{:bar, 0}, _, :def, [], false},
{{:baz, 0}, _, :def, [], "Baz docs"},
{{:foo, 1}, _, :def, [{:arg1, [], _}], "Foo docs"},
{{:fuz, 0}, _, :def, [], nil}
] = docs[:docs]
end
end
@@ -701,6 +701,12 @@ defmodule Kernel.ErrorsTest do
def foo(n) when is_number(n)
end
'''
assert_eval_raise CompileError, "nofile:2: missing :do option in \"def\"", '''
defmodule Kernel.ErrorsTest.BodyessFunctionWithGuard do
def foo(n) when is_number(n), true
end
'''
end
test "invalid args for bodyless clause" do
+3 -3
View File
@@ -58,7 +58,7 @@ defmodule Kernel.FnTest do
assert (&List.flatten/1).([[0]]) == [0]
assert (&List.flatten/1).([[0]]) == [0]
assert (&List.flatten(&1)).([[0]]) == [0]
assert (&List.flatten(&1)) == &List.flatten/1
assert (&List.flatten(&1)) == (&List.flatten/1)
end
test "capture local" do
@@ -77,7 +77,7 @@ defmodule Kernel.FnTest do
assert (&is_atom/1).(:a)
assert (&is_atom/1).(:a)
assert (&is_atom(&1)).(:a)
assert (&is_atom(&1)) == &is_atom/1
assert (&is_atom(&1)) == (&is_atom/1)
end
test "capture macro" do
@@ -102,7 +102,7 @@ defmodule Kernel.FnTest do
mod = List
assert (&mod.flatten(&1)).([1, [2], 3]) == [1, 2, 3]
assert (&mod.flatten/1).([1, [2], 3]) == [1, 2, 3]
assert (&mod.flatten/1) == &List.flatten/1
assert (&mod.flatten/1) == (&List.flatten/1)
end
test "local partial application" do
+11 -8
View File
@@ -21,6 +21,7 @@ defmodule Kernel.GuardTest do
defmodule Guards.In.Funs do
defguard is_foo(atom) when atom == :foo
defguard is_equal(foo, bar) when foo == bar
def is_foobar(atom) when is_foo(atom) do
is_foo(atom)
@@ -33,6 +34,13 @@ defmodule Kernel.GuardTest do
refute Guards.In.Funs.is_foo(:bar)
end
test "guards do not change code evaluation semantics" do
require Guards.In.Funs
x = 1
assert Guards.In.Funs.is_equal(x = 2, x) == false
assert x == 2
end
defmodule Macros.In.Guards do
defmacro is_foo(atom) do
quote do
@@ -365,9 +373,7 @@ defmodule Kernel.GuardTest do
:erlang.+(:erlang.+(unquote(foo), unquote(bar)), unquote(baz))
end
false -> quote do
foo = unquote(foo)
bar = unquote(bar)
baz = unquote(baz)
{foo, bar, baz} = {unquote(foo), unquote(bar), unquote(baz)}
:erlang.+(:erlang.+(foo, bar), baz)
end
end
@@ -387,8 +393,7 @@ defmodule Kernel.GuardTest do
:erlang.+(unquote(foo), unquote(bar))
end
false -> quote do
foo = unquote(foo)
bar = unquote(bar)
{foo, bar} = {unquote(foo), unquote(bar)}
:erlang.+(foo, bar)
end
end
@@ -410,9 +415,7 @@ defmodule Kernel.GuardTest do
end
false ->
quote() do
foo = unquote(foo)
bar = unquote(bar)
baz = unquote(baz)
{foo, bar, baz} = {unquote(foo), unquote(bar), unquote(baz)}
:erlang.+(:erlang.+(:erlang.+(foo, foo), bar), baz)
end
end
@@ -835,8 +835,12 @@ defmodule Kernel.TypespecTest do
quote(do: @type(literal_keyword_list_type_key() :: [{binary(), integer()}])),
quote(do: @type(literal_empty_map() :: %{})),
quote(do: @type(literal_map_with_key() :: %{key: integer()})),
quote(do: @type(literal_map_with_required_key() :: %{required(bitstring()) => integer()})),
quote(do: @type(literal_map_with_optional_key() :: %{optional(bitstring()) => integer()})),
quote(
do: @type(literal_map_with_required_key() :: %{required(bitstring()) => integer()})
),
quote(
do: @type(literal_map_with_optional_key() :: %{optional(bitstring()) => integer()})
),
quote(do: @type(literal_struct_all_fields_any_type() :: %SomeStruct{})),
quote(do: @type(literal_struct_all_fields_key_type() :: %SomeStruct{key: integer()})),
quote(do: @type(literal_empty_tuple() :: {})),
@@ -863,7 +867,9 @@ defmodule Kernel.TypespecTest do
quote(do: @type(builtin_list() :: list())),
quote(do: @type(builtin_nonempty_list() :: nonempty_list())),
quote(do: @type(builtin_maybe_improper_list() :: maybe_improper_list())),
quote(do: @type(builtin_nonempty_maybe_improper_list() :: nonempty_maybe_improper_list())),
quote(
do: @type(builtin_nonempty_maybe_improper_list() :: nonempty_maybe_improper_list())
),
quote(do: @type(builtin_mfa() :: mfa())),
quote(do: @type(builtin_module() :: module())),
quote(do: @type(builtin_no_return() :: no_return())),
+30 -56
View File
@@ -1092,13 +1092,13 @@ defmodule Kernel.WarningTest do
end
test "unused variable in defguard" do
capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defguard foo(bar, baz) when bar
end
""")
end) =~ "variable \"baz\" is unused"
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defguard foo(bar, baz) when bar
end
""")
end) =~ "variable \"baz\" is unused"
after
purge(Sample)
end
@@ -1129,65 +1129,39 @@ defmodule Kernel.WarningTest do
end
test "defguard overriding defmacro" do
capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defmacro foo(bar), do: bar == :bar
defguard foo(baz) when baz == :baz
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defmacro foo(bar), do: bar == :bar
defguard foo(baz) when baz == :baz
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
after
purge(Sample)
end
test "defmacro overriding defguard" do
capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defguard foo(baz) when baz == :baz
defmacro foo(bar), do: bar == :bar
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
after
purge(Sample)
end
test "defguardp overriding defmacrop" do
capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defmacrop foo(bar), do: bar == :bar
defguardp foo(baz) when baz == :baz
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
after
purge(Sample)
end
test "defmacrop overriding defguardp" do
capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defguardp foo(baz) when baz == :baz
defmacrop foo(bar), do: bar == :bar
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defguard foo(baz) when baz == :baz
defmacro foo(bar), do: bar == :bar
end
""")
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
after
purge(Sample)
end
test "defguard needs an implementation" do
capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defguard foo(bar)
end
""")
end) =~ "implementation not provided for predefined defguard"
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defguard foo(bar)
end
""")
end) =~ "implementation not provided for predefined defmacro foo/1"
after
purge(Sample)
end
+1
View File
@@ -5,6 +5,7 @@ defmodule NodeTest do
doctest Node
@tag :unix
test "start/3 and stop/0" do
assert Node.stop() == {:error, :not_found}
assert {:ok, _} = Node.start(:hello, :shortnames, 15000)
+57 -55
View File
@@ -36,67 +36,69 @@ defmodule PathTest do
assert_raise ArgumentError, ~r/null byte/, fn -> Path.wildcard("foo\0bar") end
end
if windows?() do
describe "Windows" do
test "relative/1" do
assert Path.relative("C:/usr/local/bin") == "usr/local/bin"
assert Path.relative("C:\\usr\\local\\bin") == "usr\\local\\bin"
assert Path.relative("C:usr\\local\\bin") == "usr\\local\\bin"
describe "Windows" do
@describetag :windows
assert Path.relative("/usr/local/bin") == "usr/local/bin"
assert Path.relative("usr/local/bin") == "usr/local/bin"
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
end
test "relative/1" do
assert Path.relative("C:/usr/local/bin") == "usr/local/bin"
assert Path.relative("C:\\usr\\local\\bin") == "usr\\local\\bin"
assert Path.relative("C:usr\\local\\bin") == "usr\\local\\bin"
test "relative_to/2" do
assert Path.relative_to("D:/usr/local/foo", "D:/usr/") == "local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:/usr/") == "local/foo"
assert Path.relative_to("d:/usr/local/foo", "D:/usr/") == "local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:/") == "usr/local/foo"
assert Path.relative_to("D:/usr/local/foo", "D:/") == "usr/local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:") == "D:/usr/local/foo"
assert Path.relative_to("D:/usr/local/foo", "D:") == "D:/usr/local/foo"
end
test "type/1" do
assert Path.type("C:/usr/local/bin") == :absolute
assert Path.type('C:\\usr\\local\\bin') == :absolute
assert Path.type("C:usr\\local\\bin") == :volumerelative
assert Path.type("/usr/local/bin") == :volumerelative
assert Path.type('usr/local/bin') == :relative
assert Path.type("../usr/local/bin") == :relative
end
test "split/1" do
assert Path.split("C:\\foo\\bar") == ["c:/", "foo", "bar"]
assert Path.split("C:/foo/bar") == ["c:/", "foo", "bar"]
end
assert Path.relative("/usr/local/bin") == "usr/local/bin"
assert Path.relative("usr/local/bin") == "usr/local/bin"
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
end
else
describe "Unix" do
test "relative/1" do
assert Path.relative("/usr/local/bin") == "usr/local/bin"
assert Path.relative("usr/local/bin") == "usr/local/bin"
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
assert Path.relative("/") == "."
assert Path.relative('/') == "."
assert Path.relative(['/usr', ?/, "local/bin"]) == "usr/local/bin"
end
test "type/1" do
assert Path.type("/usr/local/bin") == :absolute
assert Path.type("usr/local/bin") == :relative
assert Path.type("../usr/local/bin") == :relative
test "relative_to/2" do
assert Path.relative_to("D:/usr/local/foo", "D:/usr/") == "local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:/usr/") == "local/foo"
assert Path.relative_to("d:/usr/local/foo", "D:/usr/") == "local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:/") == "usr/local/foo"
assert Path.relative_to("D:/usr/local/foo", "D:/") == "usr/local/foo"
assert Path.relative_to("D:/usr/local/foo", "d:") == "D:/usr/local/foo"
assert Path.relative_to("D:/usr/local/foo", "D:") == "D:/usr/local/foo"
end
assert Path.type('/usr/local/bin') == :absolute
assert Path.type('usr/local/bin') == :relative
assert Path.type('../usr/local/bin') == :relative
test "type/1" do
assert Path.type("C:/usr/local/bin") == :absolute
assert Path.type('C:\\usr\\local\\bin') == :absolute
assert Path.type("C:usr\\local\\bin") == :volumerelative
assert Path.type(['/usr/', 'local/bin']) == :absolute
assert Path.type(['usr/', 'local/bin']) == :relative
assert Path.type(['../usr', '/local/bin']) == :relative
end
assert Path.type("/usr/local/bin") == :volumerelative
assert Path.type('usr/local/bin') == :relative
assert Path.type("../usr/local/bin") == :relative
end
test "split/1" do
assert Path.split("C:\\foo\\bar") == ["c:/", "foo", "bar"]
assert Path.split("C:/foo/bar") == ["c:/", "foo", "bar"]
end
end
describe "Unix" do
@describetag :unix
test "relative/1" do
assert Path.relative("/usr/local/bin") == "usr/local/bin"
assert Path.relative("usr/local/bin") == "usr/local/bin"
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
assert Path.relative("/") == "."
assert Path.relative('/') == "."
assert Path.relative(['/usr', ?/, "local/bin"]) == "usr/local/bin"
end
test "type/1" do
assert Path.type("/usr/local/bin") == :absolute
assert Path.type("usr/local/bin") == :relative
assert Path.type("../usr/local/bin") == :relative
assert Path.type('/usr/local/bin') == :absolute
assert Path.type('usr/local/bin') == :relative
assert Path.type('../usr/local/bin') == :relative
assert Path.type(['/usr/', 'local/bin']) == :absolute
assert Path.type(['usr/', 'local/bin']) == :relative
assert Path.type(['../usr', '/local/bin']) == :relative
end
end
+14 -8
View File
@@ -92,12 +92,14 @@ defmodule SystemTest do
end
end
if windows?() do
test "cmd/2 win" do
describe "Windows" do
@describetag :windows
test "cmd/2" do
assert {"hello\r\n", 0} = System.cmd("cmd", ~w[/c echo hello])
end
test "cmd/3 (with options) win" do
test "cmd/3 (with options)" do
assert {["hello\r\n"], 0} =
System.cmd(
"cmd",
@@ -113,7 +115,7 @@ defmodule SystemTest do
@echo "echo-elixir-test"
test "cmd/2 with absolute and relative Windows paths" do
test "cmd/2 with absolute and relative paths" do
echo = tmp_path(@echo)
File.mkdir_p!(Path.dirname(echo))
File.cp!(System.find_executable("cmd"), echo)
@@ -132,12 +134,16 @@ defmodule SystemTest do
after
File.rm_rf!(Path.dirname(tmp_path(@echo)))
end
else
test "cmd/2 unix" do
end
describe "Unix" do
@describetag :unix
test "cmd/2" do
assert {"hello\n", 0} = System.cmd("echo", ["hello"])
end
test "cmd/3 (with options) unix" do
test "cmd/3 (with options)" do
opts = [
into: [],
cd: System.cwd!(),
@@ -152,7 +158,7 @@ defmodule SystemTest do
@echo "echo-elixir-test"
test "cmd/2 with absolute and relative Unix paths" do
test "cmd/2 with absolute and relative paths" do
echo = tmp_path(@echo)
File.mkdir_p!(Path.dirname(echo))
File.cp!(System.find_executable("echo"), echo)
+9 -3
View File
@@ -1,6 +1,3 @@
assert_timeout = String.to_integer(System.get_env("ELIXIR_ASSERT_TIMEOUT") || "500")
ExUnit.start(trace: "--trace" in System.argv(), assert_receive_timeout: assert_timeout)
# Beam files compiled on demand
path = Path.expand("../../tmp/beams", __DIR__)
File.rm_rf!(path)
@@ -87,3 +84,12 @@ defmodule CodeFormatterHelpers do
end
end
end
assert_timeout = String.to_integer(System.get_env("ELIXIR_ASSERT_TIMEOUT") || "500")
exclude = if PathHelpers.windows?(), do: [unix: true], else: [windows: true]
ExUnit.start(
trace: "--trace" in System.argv(),
assert_receive_timeout: assert_timeout,
exclude: exclude
)
+60 -11
View File
@@ -5,15 +5,25 @@ defmodule ExUnit.Callbacks do
This module defines both `setup` and `setup_all` callbacks, as well as
the `on_exit/2`, `start_supervised/2` and `stop_supervised/1` functions.
The setup callbacks are defined via macros and each one can optionally
receive a map with metadata, usually referred to as `context`. The context
to be used in the tests can be optionally extended by the callbacks by
returning a properly structured value (see below).
The setup callbacks are defined via macros and each one can
optionally receive a map with test state and metadata, usually
referred to as `context`. The context to be used in the tests can be
optionally extended by the setup callbacks by returning a properly
structured value (see below).
The `setup_all` callbacks are invoked only once per module, before any
test runs. All `setup` callbacks are run before each test. No callback
runs if the test case has no tests or all tests have been filtered out.
Both `setup` and `setup_all` can be defined by a block, by passing
an atom naming a unary function, or by passing a list of such
atoms. Both can opt to receive the current context by specifying it
as parameter if defined by a block. Functions used to define a test
setup must accept the context as single argument.
A test module can define mutiple `setup` and `setup_all` callbacks,
and they are invoked in order of appearance.
`start_supervised/2` is used to start processes under a supervisor. The
supervisor is linked to the current test process. The supervisor as well
as all child processes are guaranteed to terminate before any `on_exit/2`
@@ -43,13 +53,10 @@ defmodule ExUnit.Callbacks do
## Context
If `setup_all` returns a keyword list, a map, or `{:ok, keywords | map}`,
the keyword list/map will be merged into the current context and will be
available in all subsequent `setup_all`, `setup`, and the `test` itself.
Similarly, if `setup` returns a keyword list, map, or `{:ok, keywords | map}`,
the returned keyword list/map will be merged into the current context and will
be available in all subsequent `setup` and the `test` itself.
If `setup_all` or `setup` return a keyword list, a map, or `{:ok,
keywords | map}`, the keyword list or map will be merged into the
current context and will be available in all subsequent `setup_all`,
`setup`, and the `test` itself.
Returning `:ok` leaves the context unchanged (both in `setup` and `setup_all`
callbacks).
@@ -132,10 +139,25 @@ defmodule ExUnit.Callbacks do
@doc """
Defines a callback to be run before each test in a case.
Pass a block or name of a unary function as atom, or list of such
atoms.
Can return values to be merged into the context, to set up state for
tests. See section Context above for details.
## Examples
def clean_up_tmp_directory(context) do
# perform setup
:ok
end
setup :clean_up_tmp_directory
setup do
[conn: Plug.Conn.build_conn()]
end
"""
defmacro setup(block) do
if Keyword.keyword?(block) do
@@ -151,6 +173,12 @@ defmodule ExUnit.Callbacks do
@doc """
Defines a callback to be run before each test in a case.
Pass a block or name of a unary function as atom, or list of such
atoms.
Can return values to be merged into the context, to set up state for
tests. See section Context above for details.
## Examples
setup context do
@@ -173,10 +201,25 @@ defmodule ExUnit.Callbacks do
@doc """
Defines a callback to be run before all tests in a case.
Pass a block or name of a unary function as atom, or list of such
atoms.
Can return values to be merged into the context, to set up state for
tests. See section Context above for details.
## Examples
def clean_up_tmp_directory(context) do
# perform setup
:ok
end
setup_all :clean_up_tmp_directory
setup_all do
[conn: Plug.Conn.build_conn()]
end
"""
defmacro setup_all(block) do
if Keyword.keyword?(block) do
@@ -196,6 +239,12 @@ defmodule ExUnit.Callbacks do
@doc """
Defines a callback to be run before all tests in a case.
Pass a block or name of a unary function as atom, or list of such
atoms.
Can return values to be merged into the context, to set up state for
tests. See section Context above for details.
## Examples
setup_all context do
+2 -2
View File
@@ -55,10 +55,10 @@ defmodule IEx.Autocomplete do
@doc false
def exports(mod) do
if function_exported?(mod, :__info__, 1) do
if Code.ensure_loaded?(mod) and function_exported?(mod, :__info__, 1) do
mod.__info__(:macros) ++ (mod.__info__(:functions) -- [__info__: 1])
else
mod.module_info(:exports)
mod.module_info(:exports) -- [module_info: 0, module_info: 1]
end
end
@@ -694,6 +694,60 @@ defmodule Logger.TranslatorTest do
"""
end
test "translates DynamicSupervisor reports abnormal shutdown" do
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
child = %{id: __MODULE__, start: {__MODULE__, :abnormal, []}}
{:ok, pid} = DynamicSupervisor.start_link(strategy: :one_for_one)
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
Process.exit(pid, :normal)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child :undefined of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shutdown abnormally
\*\* \(exit\) :stop
Pid: #PID<\d+\.\d+\.\d+>
Start Call: Logger.TranslatorTest.abnormal\(\)
"""
end
test "translates DynamicSupervisor reports extra_arguments in in abnormal shutdown" do
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
{:ok, pid} =
DynamicSupervisor.start_link(strategy: :one_for_one, extra_arguments: [:extra])
child = %{id: __MODULE__, start: {__MODULE__, :abnormal2, [:args]}}
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
Process.exit(pid, :normal)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child :undefined of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shutdown abnormally
\*\* \(exit\) :stop
Pid: #PID<\d+\.\d+\.\d+>
Start Call: Logger.TranslatorTest.abnormal2\(:extra, :args\)
"""
end
test "translates named DynamicSupervisor reports abnormal shutdown" do
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
child = %{id: __MODULE__, start: {__MODULE__, :abnormal, []}}
{:ok, pid} = DynamicSupervisor.start_link(strategy: :one_for_one, name: __MODULE__)
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
Process.exit(pid, :normal)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child :undefined of Supervisor Logger.TranslatorTest shutdown abnormally
\*\* \(exit\) :stop
Pid: #PID<\d+\.\d+\.\d+>
Start Call: Logger.TranslatorTest.abnormal\(\)
"""
end
test "translates :supervisor_bridge progress" do
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
@@ -787,6 +841,10 @@ defmodule Logger.TranslatorTest do
:proc_lib.start_link(__MODULE__, :abnormal_init, [])
end
def abnormal2(:extra, :args) do
:proc_lib.start_link(__MODULE__, :abnormal_init, [])
end
def abnormal_init() do
Process.flag(:trap_exit, true)
:proc_lib.init_ack({:ok, self()})
+1 -1
View File
@@ -81,7 +81,7 @@ defmodule Mix.Shell do
* `:quiet` - overrides the callback to no-op
"""
def cmd(command, options, callback) when is_function(callback, 1) do
def cmd(command, options \\ [], callback) when is_function(callback, 1) do
callback =
if Keyword.get(options, :quiet, false) do
fn x -> x end
+3
View File
@@ -165,6 +165,9 @@ defmodule Mix.Tasks.Compile.Erlang do
{:attribute, _, :behaviour, behaviour} ->
%{erl | behaviours: [behaviour | erl.behaviours]}
{:attribute, _, :behavior, behaviour} ->
%{erl | behaviours: [behaviour | erl.behaviours]}
{:attribute, _, :compile, value} when is_list(value) ->
%{erl | compile: value ++ erl.compile}
+209 -125
View File
@@ -13,58 +13,62 @@ defmodule Mix.Tasks.Format do
## Formatting options
Formatting is done with the `Code.format_string!/2` function.
For complete list of formatting options please refer to its
description.
A `.formatter.exs` file can also be defined for customizing input
files and the formatter itself.
## Task-specific options
* `--check-formatted` - check that the file is already formatted.
This is useful in pre-commit hooks and CI scripts if you want to
reject contributions with unformatted code. However, keep in mind,
that the formatting output may differ between Elixir versions as
improvements and fixes are applied to the formatter.
* `--check-equivalent` - check if the file after formatting has the
same AST. If the ASTs are not equivalent, it is a bug in the code
formatter. This option is recommended if you are automatically
formatting files.
* `--dry-run` - do not save files after formatting.
* `--dot-formatter` - the file with formatter configuration.
Defaults to `.formatter.exs` if one is available, see next section.
If any of the `--check-*` flags are given and a check fails, the formatted
contents won't be written to disk nor printed to stdout.
## `.formatter.exs`
The formatter will read a `.formatter.exs` in the current directory for
formatter configuration. It should return a keyword list with any of the
options supported by `Code.format_string!/2`.
formatter configuration. Evaluating this file should return a keyword list
with any of the options supported by `Code.format_string!/2`.
The `.formatter.exs` also supports other options:
* `:inputs` (a list of paths and patterns) - specifies the default inputs
to be used by this task. For example, `["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]`.
* `:subdirectories` (a list of paths and patterns) - specifies subdirectories
that have their own formatting rules. Each subdirectory should have a
`.formatter.exs` that configures how entries in that subdirectory should be
formatted as. Configuration between `.formatter.exs` are not shared nor
inherited. If a `.formatter.exs` lists "lib/app" as a subdirectory, the rules
in `.formatter.exs` won't be available in `lib/app/.formatter.exs`.
Note that the parent `.formatter.exs` must not specify files inside the "lib/app"
subdirectory in its `:inputs` configuration. If this happens, the behaviour of
which formatter configuration will be picked is unspecified.
* `:import_deps` (a list of dependencies as atoms) - specifies a list
of dependencies whose formatter configuration will be imported.
See the "Importing dependencies configuration" section below for more
information.
When specified, the formatter should run in the same directory as
the `mix.exs` file that defines those depednencies. See the "Importing
dependencies configuration" section below for more information.
* `:export` (a keyword list) - specifies formatter configuration to be exported. See the
"Importing dependencies configuration" section below.
* `:export` (a keyword list) - specifies formatter configuration to be exported.
See the "Importing dependencies configuration" section below.
## Task-specific options
* `--check-formatted` - check that the file is already formatted.
This is useful in pre-commit hooks and CI scripts if you want to
reject contributions with unformatted code. However keep in mind
that the formatted output may differ between Elixir versions as
improvements and fixes are applied to the formatter.
* `--check-equivalent` - check if the files after formatting have the
same AST as before formatting. If the ASTs are not equivalent,
it is a bug in the code formatter. This option is recommended if you
are automatically formatting files.
* `--dry-run` - do not save files after formatting.
* `--dot-formatter` - path to the file with formatter configuration.
Defaults to `.formatter.exs` if one is available. See the "`.formatter.exs`"
section for more information.
If any of the `--check-*` flags are given and a check fails, the formatted
contents won't be written to disk nor printed to standard output.
## When to format code
We recommend developers to format code directly in their editors. Either
automatically on save or via an explicit command/key binding. If such option
is not yet available in your editor of choice, adding the required integration
is relatively simple as it is a matter of invoking
We recommend developers to format code directly in their editors, either
automatically when saving a file or via an explicit command or key binding. If
such option is not yet available in your editor of choice, adding the required
integration is usually a matter of invoking:
cd $project && mix format $file
@@ -72,7 +76,7 @@ defmodule Mix.Tasks.Format do
project.
It is also possible to format code across the whole project by passing a list
of patterns and files to `mix format`, as showed at the top of this task
of patterns and files to `mix format`, as shown at the top of this task
documentation. This list can also be set in the `.formatter.exs` under the
`:inputs` key.
@@ -80,15 +84,15 @@ defmodule Mix.Tasks.Format do
This task supports importing formatter configuration from dependencies.
A dependency that wants to export formatter configuration needs to have a `.formatter.exs` file
at the root of the project. In this file, the dependency can export a `:export` option with
configuration to export. For now, only one option is supported under `:export`:
`:locals_without_parens` (whose value has the same shape as the value of the
`:locals_without_parens` in `Code.format_string!/2`).
A dependency that wants to export formatter configuration needs to have a
`.formatter.exs` file at the root of the project. In this file, the dependency
can export a `:export` option with configuration to export. For now, only one
option is supported under `:export`: `:locals_without_parens` (whose value has
the same shape as the value of the `:locals_without_parens` in `Code.format_string!/2`).
The functions listed under `:locals_without_parens` in the `:export` option of a dependency
can be imported in a project by listing that dependency in the `:import_deps`
option of the formatter configuration file of the project.
The functions listed under `:locals_without_parens` in the `:export` option of
a dependency can be imported in a project by listing that dependency in the
`:import_deps` option of the formatter configuration file of the project.
For example, consider I have a project `my_app` that depends on `my_dep`.
`my_dep` wants to export some configuration, so `my_dep/.formatter.exs`
@@ -121,94 +125,140 @@ defmodule Mix.Tasks.Format do
dry_run: :boolean
]
@deps_manifest "cached_formatter_deps"
@manifest "cached_dot_formatter"
@manifest_vsn 1
def run(args) do
{opts, args} = OptionParser.parse!(args, strict: @switches)
formatter_opts = eval_dot_formatter(opts)
formatter_opts = fetch_deps_opts(formatter_opts)
{dot_formatter, formatter_opts} = eval_dot_formatter(opts)
{formatter_opts_and_subs, _sources} =
eval_deps_and_subdirectories(dot_formatter, [], formatter_opts, [dot_formatter])
args
|> expand_args(formatter_opts)
|> Task.async_stream(&format_file(&1, opts, formatter_opts), ordered: false, timeout: 30000)
|> expand_args(dot_formatter, formatter_opts_and_subs)
|> Task.async_stream(&format_file(&1, opts), ordered: false, timeout: 30000)
|> Enum.reduce({[], [], []}, &collect_status/2)
|> check!()
end
defp eval_dot_formatter(opts) do
case dot_formatter(opts) do
{:ok, dot_formatter} -> eval_file_with_keyword_list(dot_formatter)
:error -> []
end
end
defp dot_formatter(opts) do
cond do
dot_formatter = opts[:dot_formatter] -> {:ok, dot_formatter}
File.regular?(".formatter.exs") -> {:ok, ".formatter.exs"}
true -> :error
end
end
dot_formatter = opts[:dot_formatter] ->
{dot_formatter, eval_file_with_keyword_list(dot_formatter)}
# This function reads exported configuration from the imported dependencies and deals with
# caching the result of reading such configuration in a manifest file.
defp fetch_deps_opts(formatter_opts) do
deps = Keyword.get(formatter_opts, :import_deps, [])
cond do
deps == [] ->
formatter_opts
is_list(deps) ->
# Since we have dependencies listed, we write the manifest even if those dependencies
# don't export anything so that we avoid lookups everytime.
deps_manifest = Path.join(Mix.Project.manifest_path(), @deps_manifest)
dep_parenless_calls =
if deps_dot_formatters_stale?(deps_manifest) do
dep_parenless_calls = eval_deps_opts(deps)
write_deps_manifest(deps_manifest, dep_parenless_calls)
dep_parenless_calls
else
read_deps_manifest(deps_manifest)
end
Keyword.update(
formatter_opts,
:locals_without_parens,
dep_parenless_calls,
&(&1 ++ dep_parenless_calls)
)
File.regular?(".formatter.exs") ->
{".formatter.exs", eval_file_with_keyword_list(".formatter.exs")}
true ->
Mix.raise("Expected :import_deps to return a list of dependencies, got: #{inspect(deps)}")
{".formatter.exs", []}
end
end
defp deps_dot_formatters_stale?(deps_manifest) do
Mix.Utils.stale?([".formatter.exs" | Mix.Project.config_files()], [deps_manifest])
# This function reads exported configuration from the imported
# dependencies and subdirectories and deals with caching the result
# of reading such configuration in a manifest file.
defp eval_deps_and_subdirectories(dot_formatter, prefix, formatter_opts, sources) do
deps = Keyword.get(formatter_opts, :import_deps, [])
subs = Keyword.get(formatter_opts, :subdirectories, [])
if not is_list(deps) do
Mix.raise("Expected :import_deps to return a list of dependencies, got: #{inspect(deps)}")
end
if not is_list(subs) do
Mix.raise("Expected :subdirectories to return a list of directories, got: #{inspect(subs)}")
end
if deps == [] and subs == [] do
{{formatter_opts, []}, sources}
else
manifest = Path.join(Mix.Project.manifest_path(), @manifest)
maybe_cache_in_manifest(dot_formatter, manifest, fn ->
{subdirectories, sources} = eval_subs_opts(subs, prefix, sources)
{{eval_deps_opts(formatter_opts, deps), subdirectories}, sources}
end)
end
end
defp read_deps_manifest(deps_manifest) do
deps_manifest |> File.read!() |> :erlang.binary_to_term()
defp maybe_cache_in_manifest(dot_formatter, manifest, fun) do
cond do
is_nil(Mix.Project.get()) or dot_formatter != ".formatter.exs" -> fun.()
entry = read_manifest(manifest) -> entry
true -> write_manifest!(manifest, fun.())
end
end
defp write_deps_manifest(deps_manifest, parenless_calls) do
File.mkdir_p!(Path.dirname(deps_manifest))
File.write!(deps_manifest, :erlang.term_to_binary(parenless_calls))
def read_manifest(manifest) do
with {:ok, binary} <- File.read(manifest),
{:ok, {@manifest_vsn, entry, sources}} <- safe_binary_to_term(binary),
expanded_sources = Enum.flat_map(sources, &Path.wildcard(&1, match_dot: true)),
false <- Mix.Utils.stale?(Mix.Project.config_files() ++ expanded_sources, [manifest]) do
{entry, sources}
else
_ -> nil
end
end
defp eval_deps_opts(deps) do
defp safe_binary_to_term(binary) do
{:ok, :erlang.binary_to_term(binary)}
rescue
_ -> :error
end
defp write_manifest!(manifest, {entry, sources}) do
File.mkdir_p!(Path.dirname(manifest))
File.write!(manifest, :erlang.term_to_binary({@manifest_vsn, entry, sources}))
{entry, sources}
end
defp eval_deps_opts(formatter_opts, []) do
formatter_opts
end
defp eval_deps_opts(formatter_opts, deps) do
deps_paths = Mix.Project.deps_paths()
for dep <- deps,
dep_path = assert_valid_dep_and_fetch_path(dep, deps_paths),
dep_dot_formatter = Path.join(dep_path, ".formatter.exs"),
File.regular?(dep_dot_formatter),
dep_opts = eval_file_with_keyword_list(dep_dot_formatter),
parenless_call <- dep_opts[:export][:locals_without_parens] || [],
uniq: true,
do: parenless_call
parenless_calls =
for dep <- deps,
dep_path = assert_valid_dep_and_fetch_path(dep, deps_paths),
dep_dot_formatter = Path.join(dep_path, ".formatter.exs"),
File.regular?(dep_dot_formatter),
dep_opts = eval_file_with_keyword_list(dep_dot_formatter),
parenless_call <- dep_opts[:export][:locals_without_parens] || [],
uniq: true,
do: parenless_call
Keyword.update(
formatter_opts,
:locals_without_parens,
parenless_calls,
&(&1 ++ parenless_calls)
)
end
defp eval_subs_opts(subs, prefix, sources) do
{subs, sources} =
Enum.flat_map_reduce(subs, sources, fn sub, sources ->
prefix = Path.join(prefix ++ [sub])
{Path.wildcard(prefix), [Path.join(prefix, ".formatter.exs") | sources]}
end)
Enum.flat_map_reduce(subs, sources, fn sub, sources ->
sub_formatter = Path.join(sub, ".formatter.exs")
if File.exists?(sub_formatter) do
formatter_opts = eval_file_with_keyword_list(sub_formatter)
{formatter_opts_and_subs, sources} =
eval_deps_and_subdirectories(:in_memory, [sub], formatter_opts, sources)
{[{sub, formatter_opts_and_subs}], sources}
else
{[], sources}
end
end)
end
defp assert_valid_dep_and_fetch_path(dep, deps_paths) when is_atom(dep) do
@@ -226,7 +276,7 @@ defmodule Mix.Tasks.Format do
:error ->
Mix.raise(
"Unknown dependency #{inspect(dep)} given to :import_deps in the formatter configuration. " <>
"The dependency is not listed in your mix.exs file"
"The dependency is not listed in your mix.exs for environment #{inspect(Mix.env())}"
)
end
end
@@ -245,22 +295,20 @@ defmodule Mix.Tasks.Format do
opts
end
defp expand_args([], formatter_opts) do
if inputs = formatter_opts[:inputs] do
expand_files_and_patterns(List.wrap(inputs), ".formatter.exs")
else
defp expand_args([], dot_formatter, formatter_opts_and_subs) do
if no_entries_in_formatter_opts?(formatter_opts_and_subs) do
Mix.raise(
"Expected one or more files/patterns to be given to mix format " <>
"or for a .formatter.exs to exist with an :inputs key"
"or for a .formatter.exs to exist with an :inputs or :subdirectories key"
)
end
dot_formatter
|> expand_dot_inputs([], formatter_opts_and_subs, %{})
|> Enum.uniq()
end
defp expand_args(files_and_patterns, _formatter_opts) do
expand_files_and_patterns(files_and_patterns, "command line")
end
defp expand_files_and_patterns(files_and_patterns, context) do
defp expand_args(files_and_patterns, _dot_formatter, {formatter_opts, subs}) do
files =
for file_or_pattern <- files_and_patterns,
file <- stdin_or_wildcard(file_or_pattern),
@@ -269,12 +317,48 @@ defmodule Mix.Tasks.Format do
if files == [] do
Mix.raise(
"Could not find a file to format. The files/patterns from #{context} " <>
"Could not find a file to format. The files/patterns given to command line " <>
"did not point to any existing file. Got: #{inspect(files_and_patterns)}"
)
end
files
for file <- files do
if file == :stdin do
{file, formatter_opts}
else
split = file |> Path.relative_to_cwd() |> Path.split()
{file, find_formatter_opts_for_file(split, {formatter_opts, subs})}
end
end
end
defp expand_dot_inputs(dot_formatter, prefix, {formatter_opts, subs}, acc) do
if no_entries_in_formatter_opts?({formatter_opts, subs}) do
Mix.raise("Expected :inputs or :subdirectories key in #{dot_formatter}")
end
map =
for input <- List.wrap(formatter_opts[:inputs]),
file <- Path.wildcard(Path.join(prefix ++ [input])),
do: {file, formatter_opts},
into: %{}
Enum.reduce(subs, Map.merge(acc, map), fn {sub, formatter_opts_and_subs}, acc ->
sub_formatter = Path.join(sub, ".formatter.exs")
expand_dot_inputs(sub_formatter, [sub], formatter_opts_and_subs, acc)
end)
end
defp find_formatter_opts_for_file(split, {formatter_opts, subs}) do
Enum.find_value(subs, formatter_opts, fn {sub, formatter_opts_and_subs} ->
if List.starts_with?(split, Path.split(sub)) do
find_formatter_opts_for_file(split, formatter_opts_and_subs)
end
end)
end
defp no_entries_in_formatter_opts?({formatter_opts, subs}) do
is_nil(formatter_opts[:inputs]) and subs == []
end
defp stdin_or_wildcard("-"), do: [:stdin]
@@ -288,7 +372,7 @@ defmodule Mix.Tasks.Format do
{File.read!(file), file: file}
end
defp format_file(file, task_opts, formatter_opts) do
defp format_file({file, formatter_opts}, task_opts) do
{input, extra_opts} = read_file(file)
output = IO.iodata_to_binary([Code.format_string!(input, extra_opts ++ formatter_opts), ?\n])
+2 -1
View File
@@ -255,7 +255,8 @@ defmodule Mix.Tasks.New do
embed_template(:formatter_umbrella, """
# Used by "mix format"
[
inputs: ["mix.exs", "apps/*/mix.exs", "apps/*/{config,lib,test}/**/*.{ex,exs}"]
inputs: ["mix.exs", "config/*.exs"],
subdirectories: ["apps/*"]
]
""")
+25 -11
View File
@@ -152,7 +152,7 @@ defmodule Mix.Tasks.Profile.Eprof do
def profile(fun, opts) do
fun
|> profile_and_analyse(opts)
|> print_output
|> print_output()
end
defp profile_and_analyse(fun, opts) do
@@ -165,11 +165,15 @@ defmodule Mix.Tasks.Profile.Eprof do
:eprof.profile([], fun, matching_pattern(opts))
results =
:eprof.dump()
|> extract_results
|> filter_results(opts)
|> sort_results(opts)
|> add_totals
Enum.map(:eprof.dump(), fn {pid, call_results} ->
parsed_calls =
call_results
|> filter_results(opts)
|> sort_results(opts)
|> add_totals()
{pid, parsed_calls}
end)
:eprof.stop()
@@ -191,9 +195,6 @@ defmodule Mix.Tasks.Profile.Eprof do
end
end
defp extract_results([]), do: []
defp extract_results([{_pid, call_results}]), do: call_results
defp filter_results(call_results, opts) do
calls_opt = Keyword.get(opts, :calls, 0)
time_opt = Keyword.get(opts, :time, 0)
@@ -227,14 +228,23 @@ defmodule Mix.Tasks.Profile.Eprof do
@header ["#", "CALLS", "%", "TIME", "µS/CALL"]
defp print_output({0, _, _, _}), do: print_function_count(0)
defp print_output([]) do
print_function_count(0)
end
defp print_output({function_count, call_results, call_count, total_time}) do
defp print_output(results) do
Enum.each(results, &print_result/1)
end
defp print_result({pid, {function_count, call_results, call_count, total_time}}) do
formatted_rows = Enum.map(call_results, &format_row(&1, total_time))
formatted_total = format_total(total_time, call_count)
column_lengths = column_lengths(@header, formatted_rows)
IO.puts("")
print_pid_row(pid)
print_row(@header, column_lengths)
print_row(formatted_total, column_lengths)
Enum.each(formatted_rows, &print_row(&1, column_lengths))
@@ -244,6 +254,10 @@ defmodule Mix.Tasks.Profile.Eprof do
print_function_count(function_count)
end
defp print_pid_row(pid) do
IO.puts("Profile results of #{inspect(pid)}")
end
defp format_row({{module, function, arity}, {count, time}}, total_time) do
mfa = Exception.format_mfa(module, function, arity)
time_percentage = :erlang.float_to_binary(100 * divide(time, total_time), [{:decimals, 2}])
+1 -1
View File
@@ -63,7 +63,7 @@ defmodule Mix.Tasks.Xref do
Prints a file dependency graph where an edge from `A` to `B` indicates
that `A` depends on `B`.
mix xref graph --format dot
mix xref graph --format stats
The following options are accepted:
+6
View File
@@ -249,6 +249,9 @@ defmodule Mix.RebarTest do
end
end
# We run only on Unix because Windows has a hard time
# removing the rebar executable after executed.
@tag [unix: true]
test "applies variables from :system_env option when compiling dependencies for Rebar" do
Mix.Project.push(RebarAsDepWithEnv)
@@ -294,6 +297,9 @@ defmodule Mix.RebarTest do
end
end
# We run only on Unix because Windows has a hard time
# removing the rebar executable after executed.
@tag [unix: true]
test "applies variables from :system_env option when compiling dependencies for rebar3" do
Mix.Project.push(Rebar3AsDep)
+197 -28
View File
@@ -16,7 +16,7 @@ defmodule Mix.Tasks.FormatTest do
end
test "formats the given files", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("a.ex", """
foo bar
""")
@@ -26,11 +26,11 @@ defmodule Mix.Tasks.FormatTest do
assert File.read!("a.ex") == """
foo(bar)
"""
end
end)
end
test "formats the given pattern", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("a.ex", """
foo bar
""")
@@ -40,11 +40,11 @@ defmodule Mix.Tasks.FormatTest do
assert File.read!("a.ex") == """
foo(bar)
"""
end
end)
end
test "is a no-op if the file is already formatted", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("a.ex", """
foo(bar)
""")
@@ -52,11 +52,11 @@ defmodule Mix.Tasks.FormatTest do
File.touch!("a.ex", {{2000, 1, 1}, {0, 0, 0}})
Mix.Tasks.Format.run(["a.ex"])
assert File.stat!("a.ex").mtime == {{2000, 1, 1}, {0, 0, 0}}
end
end)
end
test "does not write file to disk on dry-run", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("a.ex", """
foo bar
""")
@@ -66,11 +66,11 @@ defmodule Mix.Tasks.FormatTest do
assert File.read!("a.ex") == """
foo bar
"""
end
end)
end
test "reads file from stdin and prints to stdout", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("a.ex", """
foo bar
""")
@@ -87,11 +87,28 @@ defmodule Mix.Tasks.FormatTest do
assert File.read!("a.ex") == """
foo(bar)
"""
end
end)
end
test "reads file from stdin and prints to stdout with formatter", context do
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[locals_without_parens: [foo: 1]]
""")
output =
capture_io("foo :bar", fn ->
Mix.Tasks.Format.run(["-"])
end)
assert output == """
foo :bar
"""
end)
end
test "checks if file is formatted with --check-formatted", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("a.ex", """
foo bar
""")
@@ -110,7 +127,7 @@ defmodule Mix.Tasks.FormatTest do
assert File.read!("a.ex") == """
foo(bar)
"""
end
end)
end
test "checks if stdin is formatted with --check-formatted" do
@@ -129,7 +146,7 @@ defmodule Mix.Tasks.FormatTest do
end
test "checks if file is equivalent with --check-equivalent", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("a.ex", """
foo bar
""")
@@ -139,11 +156,11 @@ defmodule Mix.Tasks.FormatTest do
assert File.read!("a.ex") == """
foo(bar)
"""
end
end)
end
test "uses inputs and configuration from .formatter.exs", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[
inputs: ["a.ex"],
@@ -160,11 +177,11 @@ defmodule Mix.Tasks.FormatTest do
assert File.read!("a.ex") == """
foo bar(baz)
"""
end
end)
end
test "uses inputs and configuration from --dot-formatter", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("custom_formatter.exs", """
[
inputs: ["a.ex"],
@@ -181,13 +198,62 @@ defmodule Mix.Tasks.FormatTest do
assert File.read!("a.ex") == """
foo bar(baz)
"""
end
end)
end
test "can read exported configuration from subdirectories", context do
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[subdirectories: ["lib"]]
""")
File.mkdir_p!("lib")
File.write!("lib/.formatter.exs", """
[inputs: "a.ex", locals_without_parens: [my_fun: 2]]
""")
File.write!("lib/a.ex", """
my_fun :foo, :bar
other_fun :baz
""")
Mix.Tasks.Format.run([])
assert File.read!("lib/a.ex") == """
my_fun :foo, :bar
other_fun(:baz)
"""
Mix.Tasks.Format.run(["lib/a.ex"])
assert File.read!("lib/a.ex") == """
my_fun :foo, :bar
other_fun(:baz)
"""
# No caching without a project
manifest_path = Path.join(Mix.Project.manifest_path(), "cached_dot_formatter")
refute File.regular?(manifest_path)
# Caching with a project
Mix.Project.push(__MODULE__.FormatWithDepsApp)
Mix.Tasks.Format.run(["lib/a.ex"])
manifest_path = Path.join(Mix.Project.manifest_path(), "cached_dot_formatter")
assert File.regular?(manifest_path)
# Let's check that the manifest gets updated if it's stale.
File.touch!(manifest_path, {{1970, 1, 1}, {0, 0, 0}})
Mix.Tasks.Format.run(["lib/a.ex"])
assert File.stat!(manifest_path).mtime > {{1970, 1, 1}, {0, 0, 0}}
end)
end
test "can read exported configuration from dependencies", context do
Mix.Project.push(__MODULE__.FormatWithDepsApp)
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[import_deps: [:my_dep]]
""")
@@ -208,7 +274,7 @@ defmodule Mix.Tasks.FormatTest do
my_fun :foo, :bar
"""
manifest_path = Path.join(Mix.Project.manifest_path(), "cached_formatter_deps")
manifest_path = Path.join(Mix.Project.manifest_path(), "cached_dot_formatter")
assert File.regular?(manifest_path)
# Let's check that the manifest gets updated if it's stale.
@@ -216,13 +282,116 @@ defmodule Mix.Tasks.FormatTest do
Mix.Tasks.Format.run(["a.ex"])
assert File.stat!(manifest_path).mtime > {{1970, 1, 1}, {0, 0, 0}}
end
end)
end
test "can read exported configuration from dependencies and subdirectories", context do
Mix.Project.push(__MODULE__.FormatWithDepsApp)
in_tmp(context.test, fn ->
File.mkdir_p!("deps/my_dep/")
File.write!("deps/my_dep/.formatter.exs", """
[export: [locals_without_parens: [my_fun: 2]]]
""")
File.mkdir_p!("lib/sub")
File.mkdir_p!("lib/not_used_and_wont_raise")
File.write!(".formatter.exs", """
[subdirectories: ["lib"]]
""")
File.write!("lib/.formatter.exs", """
[subdirectories: ["*"]]
""")
File.write!("lib/sub/.formatter.exs", """
[inputs: "a.ex", import_deps: [:my_dep]]
""")
File.write!("lib/sub/a.ex", """
my_fun :foo, :bar
other_fun :baz
""")
Mix.Tasks.Format.run([])
assert File.read!("lib/sub/a.ex") == """
my_fun :foo, :bar
other_fun(:baz)
"""
Mix.Tasks.Format.run(["lib/sub/a.ex"])
assert File.read!("lib/sub/a.ex") == """
my_fun :foo, :bar
other_fun(:baz)
"""
# Update .formatter.exs, check that file is updated
File.write!("lib/sub/.formatter.exs", """
[inputs: "a.ex"]
""")
File.touch!("lib/sub/.formatter.exs", {{2030, 1, 1}, {0, 0, 0}})
Mix.Tasks.Format.run([])
assert File.read!("lib/sub/a.ex") == """
my_fun(:foo, :bar)
other_fun(:baz)
"""
# Add a new entry to "lib" and it also gets picked.
File.mkdir_p!("lib/extra")
File.write!("lib/extra/.formatter.exs", """
[inputs: "a.ex", locals_without_parens: [other_fun: 1]]
""")
File.write!("lib/extra/a.ex", """
my_fun :foo, :bar
other_fun :baz
""")
File.touch!("lib/extra/.formatter.exs", {{2030, 1, 1}, {0, 0, 0}})
Mix.Tasks.Format.run([])
assert File.read!("lib/extra/a.ex") == """
my_fun(:foo, :bar)
other_fun :baz
"""
end)
end
test "validates subdirectories in :subdirectories", context do
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[subdirectories: "oops"]
""")
message = "Expected :subdirectories to return a list of directories, got: \"oops\""
assert_raise Mix.Error, message, fn -> Mix.Tasks.Format.run([]) end
File.write!(".formatter.exs", """
[subdirectories: ["lib"]]
""")
File.mkdir_p!("lib")
File.write!("lib/.formatter.exs", """
[]
""")
message = "Expected :inputs or :subdirectories key in lib/.formatter.exs"
assert_raise Mix.Error, message, fn -> Mix.Tasks.Format.run([]) end
end)
end
test "validates dependencies in :import_deps", context do
Mix.Project.push(__MODULE__.FormatWithDepsApp)
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[import_deps: [:my_dep]]
""")
@@ -239,14 +408,14 @@ defmodule Mix.Tasks.FormatTest do
message =
"Unknown dependency :nonexistent_dep given to :import_deps in the formatter configuration. " <>
"The dependency is not listed in your mix.exs file"
"The dependency is not listed in your mix.exs for environment :dev"
assert_raise Mix.Error, message, fn -> Mix.Tasks.Format.run([]) end
end
end)
end
test "raises on invalid arguments", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
assert_raise Mix.Error, ~r"Expected one or more files\/patterns to be given", fn ->
Mix.Tasks.Format.run([])
end
@@ -254,11 +423,11 @@ defmodule Mix.Tasks.FormatTest do
assert_raise Mix.Error, ~r"Could not find a file to format", fn ->
Mix.Tasks.Format.run(["unknown.whatever"])
end
end
end)
end
test "raises SyntaxError when parsing invalid source file", context do
in_tmp context.test, fn ->
in_tmp(context.test, fn ->
File.write!("a.ex", """
defmodule <%= module %>.Bar do end
""")
@@ -268,6 +437,6 @@ defmodule Mix.Tasks.FormatTest do
end
assert_received {:mix_shell, :error, ["mix format failed for file: a.ex"]}
end
end)
end
end
@@ -18,6 +18,14 @@ defmodule Mix.Tasks.Profile.EprofTest do
end
end
test "profiles evaluated expression in multiple processes", context do
in_tmp context.test, fn ->
assert capture_io(fn ->
Eprof.run(["-e", "spawn(fn -> #{@expr} end)"])
end) =~ ~r(String\.Chars\.Integer\.to_string\/1\s+\d)
end
end
test "profiles the script", context do
in_tmp context.test, fn ->
profile_script_name = "profile_script.ex"
+13 -16
View File
@@ -48,34 +48,31 @@ defmodule Mix.UtilsTest do
end
test "symlink or copy" do
in_fixture "archive", fn ->
in_fixture("archive", fn ->
File.mkdir_p!("_build/archive")
result = Mix.Utils.symlink_or_copy(Path.expand("ebin"), Path.expand("_build/archive/ebin"))
assert_ebin_symlinked_or_copied(result)
end
end)
end
test "symlink or copy removes previous directories" do
in_fixture "archive", fn ->
in_fixture("archive", fn ->
File.mkdir_p!("_build/archive/ebin")
result = Mix.Utils.symlink_or_copy(Path.expand("ebin"), Path.expand("_build/archive/ebin"))
assert_ebin_symlinked_or_copied(result)
end
end)
end
@windows? match?({:win32, _}, :os.type())
unless @windows? do
test "symlink or copy erases wrong symlinks" do
in_fixture "archive", fn ->
File.mkdir_p!("_build/archive")
Mix.Utils.symlink_or_copy(Path.expand("priv"), Path.expand("_build/archive/ebin"))
@tag unix: true
test "symlink or copy erases wrong symlinks" do
in_fixture("archive", fn ->
File.mkdir_p!("_build/archive")
build_ebin = Path.expand("_build/archive/ebin")
Mix.Utils.symlink_or_copy(Path.expand("priv"), build_ebin)
result =
Mix.Utils.symlink_or_copy(Path.expand("ebin"), Path.expand("_build/archive/ebin"))
assert_ebin_symlinked_or_copied(result)
end
end
result = Mix.Utils.symlink_or_copy(Path.expand("ebin"), build_ebin)
assert_ebin_symlinked_or_copied(result)
end)
end
test "proxy_config reads from env and returns credentials" do
+3 -1
View File
@@ -1,7 +1,9 @@
Mix.start()
Mix.shell(Mix.Shell.Process)
Application.put_env(:mix, :colors, enabled: false)
ExUnit.start(trace: "--trace" in System.argv())
exclude = if match?({:win32, _}, :os.type()), do: [unix: true], else: [windows: true]
ExUnit.start(trace: "--trace" in System.argv(), exclude: exclude)
unless {1, 7, 4} <= Mix.SCM.Git.git_version() do
IO.puts(:stderr, "Skipping tests with git sparse checkouts...")