Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2a9c93f02 | ||
|
|
d896f541f1 | ||
|
|
f033672fa8 | ||
|
|
717c72e71b | ||
|
|
9ac830f56b | ||
|
|
0ded67dd60 | ||
|
|
abbf173da7 | ||
|
|
fc1c303481 | ||
|
|
5e1e2e6a83 | ||
|
|
77d708e1bc | ||
|
|
cb0f900b1c | ||
|
|
815420ce0f | ||
|
|
cba11d6941 | ||
|
|
966f8f74ae | ||
|
|
55241d92e2 | ||
|
|
11086ac450 | ||
|
|
0c81857622 | ||
|
|
bd0a2e8a12 | ||
|
|
df50dca83a | ||
|
|
f0969f0aa0 | ||
|
|
ad489d7d80 | ||
|
|
f8afbf3321 | ||
|
|
3b5f10874f | ||
|
|
e17b2470c6 | ||
|
|
8a1c5c38d0 | ||
|
|
00deab98bf | ||
|
|
47f4721c0b | ||
|
|
beb69232c4 | ||
|
|
4b43c7ae20 | ||
|
|
ca49a53ab2 | ||
|
|
1ff0e1ac7a | ||
|
|
ae66125f2e | ||
|
|
3025cc759b | ||
|
|
8f18a5f4ff | ||
|
|
1626f9659b | ||
|
|
9083937177 | ||
|
|
da90bb741e | ||
|
|
51d56274de | ||
|
|
79fcebb407 |
+3
-3
@@ -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
|
||||
|
||||
+39
-3
@@ -123,10 +123,37 @@ 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.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 +162,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 +262,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 +283,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,8 +2,6 @@
|
||||
=========
|
||||
[](https://travis-ci.org/elixir-lang/elixir)
|
||||
[](https://ci.appveyor.com/project/josevalim/elixir)
|
||||
|
||||
|
||||
Elixir is a dynamic, functional language designed for building scalable and maintainable applications.
|
||||
|
||||
|
||||
+11
-2
@@ -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__,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -998,7 +998,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)
|
||||
@@ -1136,10 +1140,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)
|
||||
@@ -1545,10 +1549,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 +1604,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 +1677,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
|
||||
|
||||
@@ -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__,
|
||||
@@ -902,8 +919,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 +940,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ defmodule Port do
|
||||
script in bash:
|
||||
|
||||
#!/bin/sh
|
||||
"$@"
|
||||
"$@" &
|
||||
pid=$!
|
||||
while read line ; do
|
||||
:
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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
@@ -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__,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.6.1"},
|
||||
{vsn, "1.6.2"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -434,9 +434,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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,43 @@ 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 :supervisor_bridge progress" do
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
@@ -787,6 +824,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()})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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_and_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, []}
|
||||
else
|
||||
split = file |> Path.relative_to_cwd() |> Path.split()
|
||||
{file, find_formatter_opts_for_file(split, formatter_opts_and_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])
|
||||
|
||||
|
||||
@@ -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/*"]
|
||||
]
|
||||
""")
|
||||
|
||||
|
||||
@@ -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}])
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,11 @@ defmodule Mix.Tasks.FormatTest do
|
||||
assert File.read!("a.ex") == """
|
||||
foo(bar)
|
||||
"""
|
||||
end
|
||||
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 +110,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 +129,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 +139,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 +160,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 +181,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 +257,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 +265,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 +391,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 +406,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 +420,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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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...")
|
||||
|
||||
Reference in New Issue
Block a user