Compare commits

...
33 Commits
Author SHA1 Message Date
José Valim eb5679bfcc Release v1.7.4 2018-10-24 20:11:49 +02:00
Fernando Tapia Rico e38ae98c3a Allow typespec attrs to be used as module attrs (#8327)
Port of: https://github.com/elixir-lang/elixir/commit/c190f09d5cbb1e79b510ea4332c65a7717aa9eb8
2018-10-24 13:05:53 +02:00
Eric Meadows-Jönsson 71a537b950 Add --no-load-deps flag to deps.loadpaths task (#8256) 2018-10-09 22:44:58 +02:00
José Valim 7087149bfa Run the formatter 2018-10-09 20:56:25 +02:00
José Valim b4b1947a22 Ensure eval_quoted properly resets last definition, closes #8261
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-10-09 20:49:41 +02:00
Po Chen a33d9116ab Expand paths so mix format path\for\windows.ex works (#8147) 2018-10-09 13:32:43 +02:00
Gabe Jackson 7d744ebc84 Ensure that mix --cover displays correct coverage in an umbrella app (#8176)
`:cover.start/0` initializes the internal database where coverage data
on all compiled modules is stored. Prior to this change, the database
wasn't cleared out between sub-apps in an umbrella app. When
`:cover.analyse/2` is called, it runs against the entire contents of the
internal database. In an umbrella app, the coverage report for App1
would show App1, the report for App2 would show App1 and App2, App3
would have App1, App2, and App3, and so on.

Closes #8086
2018-10-09 13:32:23 +02:00
Paul Wilson 3e6c6986f2 Fix for converting from negative iso days on New Year in a leap year (#8145) 2018-10-09 13:31:49 +02:00
Eric Meadows-Jönsson f167716f53 Unload previous archive versions before building (#8253)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-10-07 17:03:58 +02:00
José Valim 0cdf6d28b1 Consider that Macro may have not been bootstrapped 2018-09-27 14:50:59 +02:00
José Valim e4c1e3d262 Format kernel accordingly 2018-09-27 14:18:51 +02:00
José Valim 3f6313906d Avoid range creation in Calendar.ISO.valid_date?/3 2018-09-27 13:08:52 +02:00
Saša Jurić 0089a40a33 Fix typespec (#8207) 2018-09-20 11:58:46 +02:00
Tobiasz Małecki 549745fa5e Inspect.Algebra typespecs improvement (#8047)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-09-20 11:07:15 +02:00
Mike Schutte ac41358b5a Fix small curly typo in tuple docs (#8162) 2018-09-03 16:54:56 +02:00
As-har Dweedar 0db2ad6953 Fix warning for unused variable (#8158) 2018-09-01 20:42:48 +02:00
José Valim 8ca85ed2a8 Release v1.7.3 2018-08-24 20:46:27 +02:00
José Valim af64fe7e98 Consider :runtime and :app from mix.exs (#8109)
Since those values are not stored on Hex, we should not
expect them to be available on Mix.Dep.cached. Instead
we traverse the deps in `mix.exs` to lift this information.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-24 20:13:26 +02:00
Fernando Tapia Rico 28cf40fe03 Fix arity of try/1 in :elixir_import.special_form (#8079)
Fixes #8078

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-14 12:57:12 +02:00
José Valim d38a189907 Do not use protocols while consolidating
This avoids a race condition in tests.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-06 07:47:22 +02:00
José Valim 2b338092b6 Release v1.7.2 2018-08-05 09:01:26 +02:00
Scott Riley 557b779bd4 Move parentheses in System.cmd() docs (#8046) 2018-08-04 18:44:11 +02:00
José Valim ece40480f6 Update CHANGELOG 2018-08-04 16:12:28 +02:00
José Valim a77445c46b Revert "Fix errors with ref/4 IEx helper (#8016)"
This reverts commit b9d6f4f9c1.
2018-08-04 15:37:15 +02:00
José Valim 4d55fa6814 Accordingly mark top level optional dependencies from child deps (#8035)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-04 14:08:33 +02:00
Fernando Tapia Rico b9d6f4f9c1 Fix errors with ref/4 IEx helper (#8016)
* Fix errors with ref/4 IEx helper

In addition, include documentation for that function.

* Update helpers.ex

* Add IEx.Helpers.ref/* tests

* Use examples that do not return ArgumentError

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-04 14:07:37 +02:00
Fernando Tapia Rico b0cf76c74c Fix year in negative datetimes with offset (#8038) 2018-08-04 13:18:41 +02:00
José Valim 7841fbb8e0 Do not fail suite if there are no tests to run
This matters in umbrella apps where some apps may not
have Elixir tests.

Closes #8040

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-04 00:46:04 +02:00
José Valim f5773fed42 Take negative years into account in DateTime (#8033)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-03 11:39:30 +02:00
José Valim e2b8ab9a0d Unify alias doc handling
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-31 23:20:35 +02:00
Dave Lucia 1dc6d10f30 mix help when theres a task and alias (#8020)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-31 23:20:30 +02:00
José Valim e7138fb2c6 Avoid Mix.ProjectStack deadlock, closes #8024
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-31 21:29:45 +02:00
José Valim 04ecc05adc Remove warnings for docs in the same clause
Unfortunately this leads to false positives when trying
to override the docs of an overridable definition.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-28 21:33:46 +02:00
47 changed files with 832 additions and 170 deletions
+54
View File
@@ -116,6 +116,60 @@ Percentage | Module
77.19% | Total
```
## v1.7.4 (2018-10-24)
### 1. Enhancements
#### Elixir
* [Kernel] Expand `left..right` at compile time in more cases, which leads to improved performance under different scenarios, especially on `x in left..right` expressions
#### Mix
* [mix deps.loadpaths] Add `--no-load-deps` flag. This is useful for Rebar 3 compatibility
### 2. Bug fixes
#### Elixir
* [Calendar] Fix for converting from negative iso days on New Year in a leap year
* [Kernel] Ensure `@spec`, `@callback`, `@type` and friends can be read accordingly
* [Module] Avoid warnings when using Module.eval_quoted in the middle of existing definitions
#### Mix
* [mix archive.build] Unload previous archive versions before building
* [mix format] Expand paths so mix format `path\for\windows.ex` works
* [mix test] Ensure that `--cover` displays correct coverage in an umbrella app
## v1.7.3 (2018-08-24)
### 1. Bug fixes
#### ExUnit
* [ExUnit.Assertions] Do not attempt to expand `try/1` as it is a special form
#### Mix
* [mix compile.app] Do not include applications with `runtime: false` as a runtime dependency for applications coming from Hex
## v1.7.2 (2018-08-05)
### 1. Bug fixes
#### Elixir
* [DateTime] Take negative years into account in `DateTime.from_iso8601/1`
* [Kernel] Do not emit warnings for repeated docs over different clauses due to false positives
#### Mix
* [mix compile] Properly mark top-level dependencies as optional and as runtime. This fixes a bug where Mix attempted to start optional dependencies of a package when those optional dependencies were not available
* [mix compile] Avoid deadlock when a config has a timestamp later than current time
* [mix help] Show task and alias help when both are available
* [mix test] Do not fail suite if there are no test files
## v1.7.1 (2018-07-26)
### 1. Bug fixes
+1 -1
View File
@@ -1 +1 @@
1.7.1
1.7.4
+19 -1
View File
@@ -485,6 +485,14 @@ defmodule DateTime do
iex> datetime
#DateTime<2015-01-23 21:20:07.123Z>
iex> {:ok, datetime, 0} = DateTime.from_iso8601("-2015-01-23T23:50:07Z")
iex> datetime
#DateTime<-2015-01-23 23:50:07Z>
iex> {:ok, datetime, 9000} = DateTime.from_iso8601("-2015-01-23T23:50:07,123+02:30")
iex> datetime
#DateTime<-2015-01-23 21:20:07.123Z>
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
{:error, :invalid_format}
iex> DateTime.from_iso8601("2015-01-23 23:50:07A")
@@ -505,18 +513,28 @@ defmodule DateTime do
@doc since: "1.4.0"
@spec from_iso8601(String.t(), Calendar.calendar()) ::
{:ok, t, Calendar.utc_offset()} | {:error, atom}
def from_iso8601(string, calendar \\ Calendar.ISO)
def from_iso8601(<<?-, rest::binary>>, calendar) do
raw_from_iso8601(rest, calendar, true)
end
def from_iso8601(<<rest::binary>>, calendar) do
raw_from_iso8601(rest, calendar, false)
end
@sep [?\s, ?T]
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
def from_iso8601(string, calendar \\ Calendar.ISO) when is_binary(string) do
defp raw_from_iso8601(string, calendar, is_negative_datetime) do
with <<unquote(match_date), sep, unquote(match_time), rest::binary>> <- string,
true <- unquote(guard_date) and sep in @sep and unquote(guard_time),
{microsecond, rest} <- Calendar.ISO.parse_microsecond(rest),
{offset, ""} <- Calendar.ISO.parse_offset(rest) do
{year, month, day} = unquote(read_date)
{hour, minute, second} = unquote(read_time)
year = if is_negative_datetime, do: -year, else: year
cond do
not calendar.valid_date?(year, month, day) ->
+32 -16
View File
@@ -28,6 +28,7 @@ defmodule Calendar.ISO do
@seconds_per_hour 60 * 60
# Note that this does _not_ handle leap seconds.
@seconds_per_day 24 * 60 * 60
@last_second_of_the_day @seconds_per_day - 1
@microseconds_per_second 1_000_000
@parts_per_day @seconds_per_day * @microseconds_per_second
@@ -207,23 +208,13 @@ defmodule Calendar.ISO do
# Converts count of days since 0000-01-01 to {year, month, day} tuple.
@doc false
@doc since: "1.5.0"
def date_from_iso_days(days) when days in 0..3_652_424 do
def date_from_iso_days(days) when days in -3_652_059..3_652_424 do
{year, day_of_year} = days_to_year(days)
extra_day = if leap_year?(year), do: 1, else: 0
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
{year, month, day_in_month + 1}
end
def date_from_iso_days(days) when days in -3_652_059..-1 do
{year, day_of_year} = days_to_year(-days)
previous_extra_day = if leap_year?(year), do: 1, else: 0
extra_day = if leap_year?(year + 1), do: 1, else: 0
day_of_year = @days_per_nonleap_year + extra_day - day_of_year
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
{-year - 1, month, day_in_month + previous_extra_day}
end
defp div_mod(int1, int2) do
div = div(int1, int2)
rem = int1 - div * int2
@@ -499,7 +490,8 @@ defmodule Calendar.ISO do
@impl true
@spec valid_date?(year, month, day) :: boolean
def valid_date?(year, month, day) do
month in 1..12 and year in -9999..9999 and day in 1..days_in_month(year, month)
month in 1..12 and year in -9999..9999 and
(is_integer(day) and day >= 1 and day <= days_in_month(year, month))
end
@doc """
@@ -759,13 +751,30 @@ defmodule Calendar.ISO do
if leap_year?(year), do: 1, else: 0
end
defp days_to_year(days) when days < 0 do
year_estimate = -div(-days, @days_per_nonleap_year) - 1
{year, days_before_year} =
days_to_year(year_estimate, days, days_to_end_of_epoch(year_estimate))
leap_year_pad = if leap_year?(year), do: 1, else: 0
{year, leap_year_pad + @days_per_nonleap_year + days - days_before_year}
end
defp days_to_year(days) do
year = Integer.floor_div(days, @days_per_nonleap_year)
{year, days_before_year} = days_to_year(year, days, days_in_previous_years(year))
year_estimate = div(days, @days_per_nonleap_year)
{year, days_before_year} =
days_to_year(year_estimate, days, days_in_previous_years(year_estimate))
{year, days - days_before_year}
end
defp days_to_year(year, days1, days2) when days1 < days2 do
defp days_to_year(year, days1, days2) when year < 0 and days1 >= days2 do
days_to_year(year + 1, days1, days_to_end_of_epoch(year + 1))
end
defp days_to_year(year, days1, days2) when year >= 0 and days1 < days2 do
days_to_year(year - 1, days1, days_in_previous_years(year - 1))
end
@@ -773,6 +782,13 @@ defmodule Calendar.ISO do
{year, days2}
end
defp days_to_end_of_epoch(year) when year < 0 do
previous_year = year + 1
div(previous_year, 4) - div(previous_year, 100) + div(previous_year, 400) +
previous_year * @days_per_nonleap_year
end
defp days_in_previous_years(0), do: 0
defp days_in_previous_years(year) do
@@ -840,7 +856,7 @@ defmodule Calendar.ISO do
{date, time}
end
defp seconds_to_time(seconds) when seconds in 0..(@seconds_per_day - 1) do
defp seconds_to_time(seconds) when seconds in 0..@last_second_of_the_day do
{hour, rest_seconds} = div_mod(seconds, @seconds_per_hour)
{minute, second} = div_mod(rest_seconds, @seconds_per_minute)
+3 -3
View File
@@ -538,7 +538,7 @@ defmodule Inspect.Algebra do
["hello", "\n ", "world"]
"""
@spec nest(t, non_neg_integer) :: doc_nest
@spec nest(t, non_neg_integer | :cursor | :reset, :always | :break) :: doc_nest
def nest(doc, level, mode \\ :always)
def nest(doc, :cursor, mode) when is_doc(doc) and mode in [:always, :break] do
@@ -640,7 +640,7 @@ defmodule Inspect.Algebra do
"""
@doc since: "1.6.0"
@spec next_break_fits(t) :: doc_fits
@spec next_break_fits(t, :enabled | :disabled) :: doc_fits
def next_break_fits(doc, mode \\ @next_break_fits)
when is_doc(doc) and mode in [:enabled, :disabled] do
doc_fits(doc, mode)
@@ -757,7 +757,7 @@ defmodule Inspect.Algebra do
["Hello,", "\n", "A", "\n", "B"]
"""
@spec group(t) :: doc_group
@spec group(t, :self | :inherit) :: doc_group
def group(doc, mode \\ :self) when is_doc(doc) do
doc_group(doc, mode)
end
+21 -11
View File
@@ -3027,26 +3027,36 @@ defmodule Kernel do
true
"""
defmacro first..last when is_integer(first) and is_integer(last) do
defmacro first..last do
case bootstrapped?(Macro) do
true ->
first = Macro.expand(first, __CALLER__)
last = Macro.expand(last, __CALLER__)
range(__CALLER__.context, first, last)
false ->
range(__CALLER__.context, first, last)
end
end
defp range(_context, first, last) when is_integer(first) and is_integer(last) do
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
end
defmacro first..last
when is_float(first) or is_float(last) or is_atom(first) or is_atom(last) or
is_binary(first) or is_binary(last) or is_list(first) or is_list(last) do
defp range(_context, first, last)
when is_float(first) or is_float(last) or is_atom(first) or is_atom(last) or
is_binary(first) or is_binary(last) or is_list(first) or is_list(last) do
raise ArgumentError,
"ranges (first..last) expect both sides to be integers, " <>
"got: #{Macro.to_string({:.., [], [first, last]})}"
end
defmacro first..last do
case __CALLER__.context do
nil ->
quote(do: Elixir.Range.new(unquote(first), unquote(last)))
defp range(nil, first, last) do
quote(do: Elixir.Range.new(unquote(first), unquote(last)))
end
_ ->
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
end
defp range(_, first, last) do
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
end
@doc """
+38 -25
View File
@@ -71,24 +71,24 @@ defmodule Kernel.Typespec do
type_to_signature(expr) == signature
end
:lists.any(finder, get_typespec(bag, :type))
:lists.any(finder, get_typespecs(bag, [:type, :opaque, :typep]))
end
def spec_to_callback(module, {name, arity} = signature)
when is_atom(module) and is_atom(name) and arity in 0..255 do
{_set, bag} = :elixir_module.data_tables(module)
filter = fn {expr, _} = object ->
filter = fn {:spec, expr, pos} ->
if spec_to_signature(expr) == signature do
:ets.delete_object(bag, {:spec, object})
store_typespec(bag, :callback, object)
delete_typespec(bag, :spec, expr, pos)
store_typespec(bag, :callback, expr, pos)
true
else
false
end
end
:lists.filter(filter, get_typespec(bag, :spec)) != []
:lists.filter(filter, get_typespecs(bag, :spec)) != []
end
## Typespec definition and storage
@@ -100,7 +100,7 @@ defmodule Kernel.Typespec do
"""
def deftypespec(:spec, expr, _line, _file, module, pos) do
{_set, bag} = :elixir_module.data_tables(module)
store_typespec(bag, :spec, {expr, pos})
store_typespec(bag, :spec, expr, pos)
end
def deftypespec(kind, expr, line, _file, module, pos)
@@ -116,7 +116,7 @@ defmodule Kernel.Typespec do
:error
end
store_typespec(bag, kind, {expr, pos})
store_typespec(bag, kind, expr, pos)
end
def deftypespec(kind, expr, line, file, module, pos)
@@ -143,17 +143,34 @@ defmodule Kernel.Typespec do
:error
end
store_typespec(bag, :type, {kind, expr, pos})
store_typespec(bag, kind, expr, pos)
end
defp get_typespec(bag, key) do
:ets.lookup_element(bag, key, 2)
defp get_typespecs(bag, keys) when is_list(keys) do
:lists.flatmap(&get_typespecs(bag, &1), keys)
end
defp get_typespecs(bag, key) do
:ets.lookup_element(bag, {:accumulate, key}, 2)
catch
:error, :badarg -> []
end
defp store_typespec(bag, key, value) do
:ets.insert(bag, {key, value})
defp take_typespecs(bag, keys) when is_list(keys) do
:lists.flatmap(&take_typespecs(bag, &1), keys)
end
defp take_typespecs(bag, key) do
:lists.map(&elem(&1, 1), :ets.take(bag, {:accumulate, key}))
end
defp store_typespec(bag, key, expr, pos) do
:ets.insert(bag, {{:accumulate, key}, {key, expr, pos}})
:ok
end
defp delete_typespec(bag, key, expr, pos) do
:ets.delete_object(bag, {{:accumulate, key}, {key, expr, pos}})
:ok
end
@@ -192,19 +209,15 @@ defmodule Kernel.Typespec do
@doc false
def translate_typespecs_for_module(_set, bag) do
types = Enum.map(take_typespec(bag, :type), &translate_type/1)
specs = Enum.map(take_typespec(bag, :spec), &translate_spec/1)
callbacks = Enum.map(take_typespec(bag, :callback), &translate_spec/1)
macrocallbacks = Enum.map(take_typespec(bag, :macrocallback), &translate_spec/1)
optional_callbacks = List.flatten(get_typespec(bag, {:accumulate, :optional_callbacks}))
types = Enum.map(take_typespecs(bag, [:type, :opaque, :typep]), &translate_type/1)
specs = Enum.map(take_typespecs(bag, :spec), &translate_spec/1)
callbacks = Enum.map(take_typespecs(bag, :callback), &translate_spec/1)
macrocallbacks = Enum.map(take_typespecs(bag, :macrocallback), &translate_spec/1)
optional_callbacks = List.flatten(get_typespecs(bag, :optional_callbacks))
{types, specs, callbacks, macrocallbacks, optional_callbacks}
end
defp take_typespec(bag, key) do
:ets.take(bag, key)
end
defp translate_type({_, {kind, {:::, _, [{name, _, args}, definition]}, pos}})
defp translate_type({kind, {:::, _, [{name, _, args}, definition]}, pos})
when is_atom(name) and name != ::: do
caller = :elixir_locals.get_cached_env(pos)
@@ -247,7 +260,7 @@ defmodule Kernel.Typespec do
{kind, {name, arity}, caller.line, type, export}
end
defp translate_type({_, {_kind, other, pos}}) do
defp translate_type({_kind, other, pos}) do
caller = :elixir_locals.get_cached_env(pos)
type_spec = Macro.to_string(other)
compile_error(caller, "invalid type specification: #{type_spec}")
@@ -259,12 +272,12 @@ defmodule Kernel.Typespec do
defp valid_variable_ast?(_), do: false
defp translate_spec({kind, {{:when, _meta, [spec, guard]}, pos}}) do
defp translate_spec({kind, {:when, _meta, [spec, guard]}, pos}) do
caller = :elixir_locals.get_cached_env(pos)
translate_spec(kind, spec, guard, caller)
end
defp translate_spec({kind, {spec, pos}}) do
defp translate_spec({kind, spec, pos}) do
caller = :elixir_locals.get_cached_env(pos)
translate_spec(kind, spec, [], caller)
end
+6
View File
@@ -1255,6 +1255,12 @@ defmodule Macro do
next = :erlang.unique_integer()
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
{:ok, Kernel, op, [arg]} when op in [:+, :-] ->
case expand_once(arg, env) do
integer when is_integer(integer) -> {apply(Kernel, op, [integer]), true}
_ -> {original, false}
end
{:ok, _receiver, _name, _args} ->
{original, false}
+4 -14
View File
@@ -466,7 +466,7 @@ defmodule Module do
of the corresponding setting in `Code.compiler_options/1`
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
name/arity pairs. Inlining is applied locally, calls from another
name/arity pairs. Inlining is applied locally, calls from another
module are not affected by this option
* `@compile {:autoload, false}` - disables automatic loading of
@@ -1320,7 +1320,7 @@ defmodule Module do
end
end
defp compile_doc(table, line, kind, name, arity, args, body, doc, doc_meta, env, impl) do
defp compile_doc(table, line, kind, name, arity, args, _body, doc, doc_meta, env, impl) do
key = {doc_key(kind), name, arity}
signature = build_signature(args, env)
@@ -1331,16 +1331,6 @@ defmodule Module do
[{_, current_line, current_sign, current_doc, current_doc_meta}] ->
signature = merge_signatures(current_sign, signature, 1)
if is_binary(doc) and is_binary(current_doc) and not is_nil(body) do
message =
"redefining @doc attribute previously set at line #{current_line}. " <>
"If you want to redefine a previously specified doc, " <>
"use a definition without a body after the original documentation"
:elixir_errors.warn(line, env.file, message)
end
doc = if is_nil(doc), do: current_doc, else: doc
doc = if is_nil(doc) && impl, do: false, else: doc
doc_meta = Map.merge(current_doc_meta, doc_meta)
@@ -1874,9 +1864,9 @@ defmodule Module do
do: {atom, :__on_definition__}
defp preprocess_attribute(key, _value)
when key in [:type, :typep, :export_type, :opaque, :callback, :macrocallback] do
when key in [:type, :typep, :export_type, :opaque, :spec, :callback, :macrocallback] do
raise ArgumentError,
"attributes type, typep, export_type, opaque, callback, and macrocallback" <>
"attributes type, typep, export_type, opaque, spec, callback, and macrocallback " <>
"must be set directly via the @ notation"
end
+2 -2
View File
@@ -564,10 +564,10 @@ defmodule System do
iex> System.cmd("echo", ["hello"])
{"hello\n", 0}
iex> System.cmd("echo", ["hello"]), env: [{"MIX_ENV", "test"}]
iex> System.cmd("echo", ["hello"], env: [{"MIX_ENV", "test"}])
{"hello\n", 0}
iex> System.cmd("echo", ["hello"]), into: IO.stream(:stdio, :line)
iex> System.cmd("echo", ["hello"], into: IO.stream(:stdio, :line))
hello
{%IO.Stream{}, 0}
+1 -1
View File
@@ -36,7 +36,7 @@ defmodule Tuple do
tuple = {:ok, :example}
# Avoid
Tuple.insert_at(tuple, 2, %{}}
Tuple.insert_at(tuple, 2, %{})
# Prefer
{:ok, atom} = tuple
+3 -1
View File
@@ -94,7 +94,9 @@ defmodule Version do
@type patch :: non_neg_integer | nil
@type pre :: [String.t() | non_neg_integer]
@type build :: String.t() | nil
@type matchable :: {major :: major, minor :: minor, patch :: patch, pre :: pre}
@type matchable ::
{major :: major, minor :: minor, patch :: patch, pre :: pre,
build_parts :: [String.t()]}
@type t :: %__MODULE__{major: major, minor: minor, patch: patch, pre: pre, build: build}
defmodule Requirement do
+1 -1
View File
@@ -11,7 +11,7 @@ setup(DataTables) ->
ok.
reset_last({DataSet, _DataBag}) ->
ets:insert(DataSet, {?last_def, []});
ets:insert(DataSet, {?last_def, none});
reset_last(Module) when is_atom(Module) ->
reset_last(elixir_module:data_tables(Module)).
+1 -1
View File
@@ -239,6 +239,6 @@ special_form('for', _) -> true;
special_form('with', _) -> true;
special_form('cond', 1) -> true;
special_form('case', 2) -> true;
special_form('try', 2) -> true;
special_form('try', 1) -> true;
special_form('receive', 1) -> true;
special_form(_, _) -> false.
+7 -2
View File
@@ -211,7 +211,7 @@ build(Line, File, Module, Lexical) ->
%% In the bag table we store:
%%
%% * {{accumulate, Attribute}, ...}
%% * {{accumulate, Attribute}, ...} (includes typespecs)
%% * {attributes, ...}
%% * {impls, ...}
%% * {deprecated, ...}
@@ -221,7 +221,6 @@ build(Line, File, Module, Lexical) ->
%% * {{clauses, Tuple}, ...} (from elixir_def)
%% * {reattach, ...} (from elixir_local)
%% * {{local, Tuple}, ...} (from elixir_local)
%% * {spec, ...}, {type, ...}, {callback, ...}, {macrocallback, ...}
%%
DataBag = ets:new(Module, [duplicate_bag, public]),
@@ -238,6 +237,12 @@ build(Line, File, Module, Lexical) ->
{dialyzer, [], accumulate},
{external_resource, [], accumulate},
{on_definition, [], accumulate},
{type, [], accumulate},
{opaque, [], accumulate},
{typep, [], accumulate},
{spec, [], accumulate},
{callback, [], accumulate},
{macrocallback, [], accumulate},
{on_load, [], accumulate},
{optional_callbacks, [], accumulate},
@@ -3,4 +3,40 @@ Code.require_file("../test_helper.exs", __DIR__)
defmodule Calendar.ISOTest do
use ExUnit.Case, async: true
doctest Calendar.ISO
describe "date_from_iso_days" do
test "with positive dates" do
assert {0, 1, 1} == iso_day_roundtrip(0, 1, 1)
assert {0, 12, 31} == iso_day_roundtrip(0, 12, 31)
assert {1, 12, 31} == iso_day_roundtrip(1, 12, 31)
assert {4, 1, 1} == iso_day_roundtrip(4, 1, 1)
assert {4, 12, 31} == iso_day_roundtrip(4, 12, 31)
assert {9999, 12, 31} == iso_day_roundtrip(9999, 12, 31)
assert {9999, 1, 1} == iso_day_roundtrip(9999, 1, 1)
assert {9996, 12, 31} == iso_day_roundtrip(9996, 12, 31)
assert {9996, 1, 1} == iso_day_roundtrip(9996, 1, 1)
end
test "with negative dates" do
assert {-1, 1, 1} == iso_day_roundtrip(-1, 1, 1)
assert {-1, 12, 31} == iso_day_roundtrip(-1, 12, 31)
assert {-1, 12, 31} == iso_day_roundtrip(-1, 12, 31)
assert {-2, 1, 1} == iso_day_roundtrip(-2, 1, 1)
assert {-5, 12, 31} == iso_day_roundtrip(-5, 12, 31)
assert {-4, 1, 1} == iso_day_roundtrip(-4, 1, 1)
assert {-4, 12, 31} == iso_day_roundtrip(-4, 12, 31)
assert {-9999, 12, 31} == iso_day_roundtrip(-9999, 12, 31)
assert {-9996, 12, 31} == iso_day_roundtrip(-9996, 12, 31)
assert {-9996, 12, 31} == iso_day_roundtrip(-9996, 12, 31)
assert {-9996, 1, 1} == iso_day_roundtrip(-9996, 1, 1)
end
end
defp iso_day_roundtrip(year, month, day) do
iso_days = Calendar.ISO.date_to_iso_days(year, month, day)
Calendar.ISO.date_from_iso_days(iso_days)
end
end
+94 -4
View File
@@ -293,8 +293,7 @@ defmodule DateTimeTest do
end
test "from_iso8601/1 handles positive and negative offsets" do
assert DateTime.from_iso8601("2015-01-24T09:50:07-10:00")
|> elem(1) ==
assert DateTime.from_iso8601("2015-01-24T09:50:07-10:00") |> elem(1) ==
%DateTime{
microsecond: {0, 0},
month: 1,
@@ -309,8 +308,7 @@ defmodule DateTimeTest do
second: 7
}
assert DateTime.from_iso8601("2015-01-24T09:50:07+10:00")
|> elem(1) ==
assert DateTime.from_iso8601("2015-01-24T09:50:07+10:00") |> elem(1) ==
%DateTime{
microsecond: {0, 0},
month: 1,
@@ -324,6 +322,98 @@ defmodule DateTimeTest do
minute: 50,
second: 7
}
assert DateTime.from_iso8601("0000-01-01T01:22:07+10:30") |> elem(1) ==
%DateTime{
microsecond: {0, 0},
month: 12,
std_offset: 0,
time_zone: "Etc/UTC",
utc_offset: 0,
year: -1,
zone_abbr: "UTC",
day: 31,
hour: 14,
minute: 52,
second: 7
}
end
test "from_iso8601/1 handles negative dates" do
assert DateTime.from_iso8601("-2015-01-24T09:50:07-10:00") |> elem(1) ==
%DateTime{
microsecond: {0, 0},
month: 1,
std_offset: 0,
time_zone: "Etc/UTC",
utc_offset: 0,
year: -2015,
zone_abbr: "UTC",
day: 24,
hour: 19,
minute: 50,
second: 7
}
assert DateTime.from_iso8601("-2015-01-24T09:50:07+10:00") |> elem(1) ==
%DateTime{
microsecond: {0, 0},
month: 1,
std_offset: 0,
time_zone: "Etc/UTC",
utc_offset: 0,
year: -2015,
zone_abbr: "UTC",
day: 23,
hour: 23,
minute: 50,
second: 7
}
assert DateTime.from_iso8601("-0001-01-01T01:22:07+10:30") |> elem(1) ==
%DateTime{
microsecond: {0, 0},
month: 12,
std_offset: 0,
time_zone: "Etc/UTC",
utc_offset: 0,
year: -2,
zone_abbr: "UTC",
day: 31,
hour: 14,
minute: 52,
second: 7
}
assert DateTime.from_iso8601("-0001-01-01T01:22:07-10:30") |> elem(1) ==
%DateTime{
microsecond: {0, 0},
month: 1,
std_offset: 0,
time_zone: "Etc/UTC",
utc_offset: 0,
year: -1,
zone_abbr: "UTC",
day: 1,
hour: 11,
minute: 52,
second: 7
}
assert DateTime.from_iso8601("-0001-12-31T23:22:07-10:30") |> elem(1) ==
%DateTime{
microsecond: {0, 0},
month: 1,
std_offset: 0,
time_zone: "Etc/UTC",
utc_offset: 0,
year: 0,
zone_abbr: "UTC",
day: 1,
hour: 9,
minute: 52,
second: 7
}
end
test "from_unix/2" do
@@ -503,6 +503,22 @@ defmodule Kernel.ErrorsTest do
end
end
test "typespec attributes set via Module.put_attribute/4" do
message =
"attributes type, typep, export_type, opaque, spec, callback, and macrocallback " <>
"must be set directly via the @ notation"
for kind <- [:type, :typep, :opaque, :spec, :callback, :macrocallback] do
assert_eval_raise ArgumentError,
message,
"""
defmodule PutTypespecAttribute do
Module.put_attribute(__MODULE__, #{inspect(kind)}, {})
end
"""
end
end
test "invalid struct field value" do
msg = ~r"invalid value for struct field baz, cannot escape "
@@ -1109,34 +1109,6 @@ defmodule Kernel.WarningTest do
purge(Sample)
end
test "duplicated docs across multiple clauses" do
output =
capture_err(fn ->
Code.eval_string("""
defmodule Sample do
@doc "Something"
def foo(a)
@doc "Something"
def foo(1), do: :ok
@doc "Something"
def foo(2), do: :ok
@doc "Something"
def foo(a)
end
""")
end)
assert output =~ "redefining @doc attribute previously set at line 2"
assert output =~ "nofile:5"
assert output =~ "nofile:8"
refute output =~ "nofile:11"
after
purge(Sample)
end
test "reserved doc metadata keys" do
output =
capture_err(fn ->
+11 -3
View File
@@ -440,9 +440,7 @@ defmodule KernelTest do
end
end
test "is optimized" do
assert expand_to_string(quote(do: foo in [])) == "Enum.member?([], foo)"
test "hoists variables" do
result = expand_to_string(quote(do: rand() in 1..2))
assert result =~ "var = rand()"
@@ -463,6 +461,16 @@ defmodule KernelTest do
assert result =~ ":erlang.orelse(:erlang.\"=:=\"(var, 1), :lists.member(var, var0))"
end
test "is optimized" do
assert expand_to_string(quote(do: foo in [])) == "Enum.member?([], foo)"
assert expand_to_string(quote(do: foo in 0..1)) ==
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, 0), :erlang.\"=<\"(foo, 1)))"
assert expand_to_string(quote(do: foo in -1..0)) ==
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, -1), :erlang.\"=<\"(foo, 0)))"
end
defp expand_to_string(ast) do
ast
|> Macro.prewalk(&Macro.expand(&1, __ENV__))
+17 -1
View File
@@ -85,7 +85,23 @@ defmodule ModuleTest do
assert eval_quoted_info() == {ModuleTest, "sample.ex", 13}
end
test "retrieves line from macros" do
test "resets last definition information on eval" do
# This should not emit any warning
defmodule LastDefinition do
def foo(0), do: 0
Module.eval_quoted(
__ENV__,
quote do
def bar, do: :ok
end
)
def foo(1), do: 1
end
end
test "retrieves line from use callsite" do
assert ModuleTest.ToUse.line() == 40
end
+130
View File
@@ -747,6 +747,136 @@ defmodule TypespecTest do
assert [{:atom, _, :is_subtype}, [{:var, _, :y}, {:var, _, :x}]] = constraint_type
end
test "@type, @opaque, and @typep as module attributes" do
defmodule TypeModuleAttributes do
@type type1 :: boolean
@opaque opaque1 :: boolean
@typep typep1 :: boolean
def type1, do: @type
def opaque1, do: @opaque
def typep1, do: @typep
@type type2 :: atom
@type type3 :: pid
@opaque opaque2 :: atom
@opaque opaque3 :: pid
@typep typep2 :: atom
def type2, do: @type
def opaque2, do: @opaque
def typep2, do: @typep
# Avoid unused warnings
@spec foo(typep1) :: typep2
def foo(_x), do: :ok
end
assert [
{:type, {:::, _, [{:type1, _, _}, {:boolean, _, _}]},
{TypespecTest.TypeModuleAttributes, _}}
] = TypeModuleAttributes.type1()
assert [
{:type, {:::, _, [{:type3, _, _}, {:pid, _, _}]},
{TypespecTest.TypeModuleAttributes, _}},
{:type, {:::, _, [{:type2, _, _}, {:atom, _, _}]},
{TypespecTest.TypeModuleAttributes, _}},
{:type, {:::, _, [{:type1, _, _}, {:boolean, _, _}]},
{TypespecTest.TypeModuleAttributes, _}}
] = TypeModuleAttributes.type2()
assert [
{:opaque, {:::, _, [{:opaque1, _, _}, {:boolean, _, _}]},
{TypespecTest.TypeModuleAttributes, _}}
] = TypeModuleAttributes.opaque1()
assert [
{:opaque, {:::, _, [{:opaque3, _, _}, {:pid, _, _}]},
{TypespecTest.TypeModuleAttributes, _}},
{:opaque, {:::, _, [{:opaque2, _, _}, {:atom, _, _}]},
{TypespecTest.TypeModuleAttributes, _}},
{:opaque, {:::, _, [{:opaque1, _, _}, {:boolean, _, _}]},
{TypespecTest.TypeModuleAttributes, _}}
] = TypeModuleAttributes.opaque2()
assert [
{:typep, {:::, _, [{:typep1, _, _}, {:boolean, _, _}]},
{TypespecTest.TypeModuleAttributes, _}}
] = TypeModuleAttributes.typep1()
assert [
{:typep, {:::, _, [{:typep2, _, _}, {:atom, _, _}]},
{TypespecTest.TypeModuleAttributes, _}},
{:typep, {:::, _, [{:typep1, _, _}, {:boolean, _, _}]},
{TypespecTest.TypeModuleAttributes, _}}
] = TypeModuleAttributes.typep2()
after
:code.delete(TypeModuleAttributes)
:code.purge(TypeModuleAttributes)
end
test "@spec, @callback, and @macrocallback as module attributes" do
defmodule SpecModuleAttributes do
@callback callback1 :: integer
@macrocallback macrocallback1 :: integer
@spec spec1 :: boolean
def spec1, do: @spec
@callback callback2 :: boolean
@macrocallback macrocallback2 :: boolean
@spec spec2 :: atom
def spec2, do: @spec
@spec spec3 :: pid
def spec3, do: :ok
def spec4, do: @spec
def callback, do: @callback
def macrocallback, do: @macrocallback
end
assert [
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
{TypespecTest.SpecModuleAttributes, _}}
] = SpecModuleAttributes.spec1()
assert [
{:spec, {:::, _, [{:spec2, _, _}, {:atom, _, _}]},
{TypespecTest.SpecModuleAttributes, _}},
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
{TypespecTest.SpecModuleAttributes, _}}
] = SpecModuleAttributes.spec2()
assert [
{:spec, {:::, _, [{:spec3, _, _}, {:pid, _, _}]},
{TypespecTest.SpecModuleAttributes, _}},
{:spec, {:::, _, [{:spec2, _, _}, {:atom, _, _}]},
{TypespecTest.SpecModuleAttributes, _}},
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
{TypespecTest.SpecModuleAttributes, _}}
] = SpecModuleAttributes.spec4()
assert [
{:callback, {:::, _, [{:callback2, _, _}, {:boolean, _, _}]},
{TypespecTest.SpecModuleAttributes, _}},
{:callback, {:::, _, [{:callback1, _, _}, {:integer, _, _}]},
{TypespecTest.SpecModuleAttributes, _}}
] = SpecModuleAttributes.callback()
assert [
{:macrocallback, {:::, _, [{:macrocallback2, _, _}, {:boolean, _, _}]},
{TypespecTest.SpecModuleAttributes, _}},
{:macrocallback, {:::, _, [{:macrocallback1, _, _}, {:integer, _, _}]},
{TypespecTest.SpecModuleAttributes, _}}
] = SpecModuleAttributes.macrocallback()
after
:code.delete(SpecModuleAttributes)
:code.purge(SpecModuleAttributes)
end
test "@callback(callback)" do
bytecode =
test_module do
+19 -3
View File
@@ -124,11 +124,27 @@ defmodule Mix.Dep do
defp load_and_cache(config, _top, bottom, _env) do
{_, deps} = Mix.ProjectStack.read_cache({:cached_deps, bottom})
app = Keyword.fetch!(config, :app)
top_level = for dep <- deps, dep.app == app, child <- dep.deps, do: child.app
seen = populate_seen(MapSet.new(), [app])
children = get_deps(deps, tl(Enum.uniq(get_children(deps, seen, [app]))))
Enum.map(children, &%{&1 | top_level: &1.app in top_level})
top_level =
for dep <- deps,
dep.app == app,
child <- dep.deps,
do: {child.app, Keyword.get(child.opts, :optional, false)},
into: %{}
Enum.map(children, fn %{app: app, opts: opts} = dep ->
# optional only matters at the top level. Any non-top level dependency
# that is optional and is still available means it has been fulfilled.
case top_level do
%{^app => optional} ->
%{dep | top_level: true, opts: Keyword.put(opts, :optional, optional)}
%{} ->
%{dep | top_level: false, opts: Keyword.delete(opts, :optional)}
end
end)
end
defp read_cached_deps(project, env) do
+18 -12
View File
@@ -99,19 +99,25 @@ defmodule Mix.ProjectStack do
@spec config_mtime() :: [integer]
def config_mtime() do
get_and_update(fn state ->
get_and_update_in(state.stack, fn
[%{config_mtime: nil, config_files: files} = h | t] ->
mtime = files |> Enum.map(&Mix.Utils.last_modified/1) |> Enum.max()
{mtime, [%{h | config_mtime: mtime} | t]}
[%{config_mtime: mtime} | _] = stack ->
{mtime, stack}
[] ->
{0, []}
mtime_or_files =
get(fn
%{stack: [%{config_mtime: nil, config_files: files} | _]} -> files
%{stack: [%{config_mtime: mtime} | _]} -> mtime
%{stack: []} -> 0
end)
end)
if is_list(mtime_or_files) do
mtime = mtime_or_files |> Enum.map(&Mix.Utils.last_modified/1) |> Enum.max()
cast(fn state ->
update_in(state.stack, fn [h | t] -> [%{h | config_mtime: mtime} | t] end)
end)
mtime
else
mtime = mtime_or_files
mtime
end
end
@spec config_apps() :: [atom]
+8 -1
View File
@@ -113,8 +113,15 @@ defmodule Mix.Tasks.Archive.Install do
end
def build(_install_spec, _opts) do
src = Mix.Local.name_for(:archive, Mix.Project.config())
previous = find_previous_versions(src)
Enum.each(previous, fn path ->
Code.delete_path(Mix.Local.archive_ebin(path))
end)
Mix.Task.run("archive.build", [])
Mix.Local.name_for(:archive, Mix.Project.config())
src
end
### Private helpers
+19 -4
View File
@@ -208,7 +208,7 @@ defmodule Mix.Tasks.Compile.App do
apps =
properties
|> Keyword.get(:applications)
|> Kernel.||(apps_from_prod_non_optional_deps(properties))
|> Kernel.||(apps_from_prod_non_optional_deps(properties, config))
|> normalize_apps(extra, config)
Keyword.put(properties, :applications, apps)
@@ -313,17 +313,32 @@ defmodule Mix.Tasks.Compile.App do
end)
end
defp apps_from_prod_non_optional_deps(properties) do
defp apps_from_prod_non_optional_deps(properties, config) do
included_applications = Keyword.get(properties, :included_applications, [])
non_runtime_deps = non_runtime_deps(config)
for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached(),
Keyword.get(opts, :app, true),
Keyword.get(opts, :runtime, true),
not Keyword.get(opts, :optional, false),
not Map.has_key?(non_runtime_deps, app),
app not in included_applications,
do: app
end
defp non_runtime_deps(config) do
for config_dep <- Keyword.get(config, :deps, []),
not runtime_dep?(config_dep),
do: {elem(config_dep, 0), true},
into: %{}
end
defp runtime_dep?({_app, opts}) when is_list(opts), do: runtime_opts?(opts)
defp runtime_dep?({_app, _req, opts}) when is_list(opts), do: runtime_opts?(opts)
defp runtime_dep?(_), do: true
defp runtime_opts?(opts) do
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true)
end
defp normalize_apps(apps, extra, config) do
Enum.uniq([:kernel, :stdlib] ++ language_app(config) ++ extra ++ apps)
end
+10 -4
View File
@@ -145,10 +145,10 @@ defmodule Mix.Tasks.Compile.Protocols do
case Protocol.consolidate(protocol, impls) do
{:ok, binary} ->
File.write!(Path.join(output, "#{protocol}.beam"), binary)
File.write!(Path.join(output, "#{Atom.to_string(protocol)}.beam"), binary)
if opts[:verbose] do
Mix.shell().info("Consolidated #{inspect(protocol)}")
Mix.shell().info("Consolidated #{inspect_protocol(protocol)}")
end
# If we remove a dependency and we have implemented one of its
@@ -161,11 +161,17 @@ defmodule Mix.Tasks.Compile.Protocols do
remove_consolidated(protocol, output)
if opts[:verbose] do
Mix.shell().info("Unavailable #{inspect(protocol)}")
Mix.shell().info("Unavailable #{inspect_protocol(protocol)}")
end
end
end
# We cannot use the inspect protocol while consolidating
# since inspect may not be available.
defp inspect_protocol(protocol) do
Code.Identifier.inspect_as_atom(protocol)
end
defp reload(module) do
:code.purge(module)
:code.delete(module)
@@ -227,6 +233,6 @@ defmodule Mix.Tasks.Compile.Protocols do
end
defp remove_consolidated(protocol, output) do
File.rm(Path.join(output, "#{protocol}.beam"))
File.rm(Path.join(output, "#{Atom.to_string(protocol)}.beam"))
end
end
+6 -2
View File
@@ -93,8 +93,12 @@ defmodule Mix.Tasks.Deps do
and override it by setting the `:override` option in a top-level project.
* `:runtime` - whether the dependency is part of runtime applications.
Defaults to `true` which automatically adds the application to the list
of apps that are started automatically and included in releases
If the `:applications` key is not provided in `def application` in your
mix.exs file, Mix will automatically included all dependencies as a runtime
application, except if `runtime: false` is given. Defaults to true.
* `:system_env` - an enumerable of key-value tuples of binaries to be set
as environment variables when loading or compiling the dependency
### Git options (`:git`)
+10 -7
View File
@@ -16,6 +16,7 @@ defmodule Mix.Tasks.Deps.Loadpaths do
* `--no-deps-check` - does not check or compile deps, only load available ones
* `--no-compile` - does not compile dependencies
* `--no-load-deps` - do not load deps from the code path
"""
@@ -26,14 +27,16 @@ defmodule Mix.Tasks.Deps.Loadpaths do
deps_check(all, "--no-compile" in args)
end
load_paths =
for dep <- all,
path <- Mix.Dep.load_paths(dep) do
_ = Code.prepend_path(path)
path
end
unless "--no-load-deps" in args do
load_paths =
for dep <- all,
path <- Mix.Dep.load_paths(dep) do
_ = Code.prepend_path(path)
path
end
prune_deps(load_paths, "--no-deps-check" in args)
prune_deps(load_paths, "--no-deps-check" in args)
end
end
# If the build is per environment, we should be able to look
+2 -2
View File
@@ -382,7 +382,7 @@ defmodule Mix.Tasks.Format do
end
defp stdin_or_wildcard("-"), do: [:stdin]
defp stdin_or_wildcard(path), do: Path.wildcard(path, match_dot: true)
defp stdin_or_wildcard(path), do: path |> Path.expand() |> Path.wildcard(match_dot: true)
defp read_file(:stdin) do
{IO.stream(:stdio, :line) |> Enum.to_list() |> IO.iodata_to_binary(), file: "stdin"}
@@ -447,7 +447,7 @@ defmodule Mix.Tasks.Format do
end
defp check!({[{:exit, file, exception, stacktrace} | _], _not_equivalent, _not_formatted}) do
Mix.shell().error("mix format failed for file: #{file}")
Mix.shell().error("mix format failed for file: #{Path.relative_to_cwd(file)}")
reraise exception, stacktrace
end
+47 -19
View File
@@ -75,7 +75,6 @@ defmodule Mix.Tasks.Help do
aliases = Enum.filter(load_aliases(), fn {name, _} -> String.contains?(name, pattern) end)
{docs, max} = build_doc_list(modules, aliases)
display_doc_list(docs, max)
end
@@ -86,25 +85,39 @@ defmodule Mix.Tasks.Help do
def run([task]) do
loadpaths!()
{doc, location} = verbose_doc(task)
opts = Application.get_env(:mix, :colors)
if ansi_docs?(opts) do
opts = [width: width()] ++ opts
IO.ANSI.Docs.print_heading("mix #{task}", opts)
IO.ANSI.Docs.print(doc, opts)
else
IO.puts("# mix #{task}\n")
IO.puts(doc)
end
opts =
if ansi_docs?(opts) do
[width: width()] ++ opts
else
opts
end
IO.puts("Location: #{location}")
for doc <- verbose_doc(task) do
print_doc(task, doc, opts)
end
end
def run(_) do
Mix.raise("Unexpected arguments, expected \"mix help\" or \"mix help TASK\"")
end
defp print_doc(task, {doc, location, note}, opts) do
if ansi_docs?(opts) do
opts = [width: width()] ++ opts
IO.ANSI.Docs.print_heading("mix #{task}", opts)
IO.ANSI.Docs.print(doc, opts)
IO.puts("Location: #{location}")
note && IO.puts("") && IO.ANSI.Docs.print(note, opts)
else
IO.puts("# mix #{task}\n")
IO.puts(doc)
IO.puts("\nLocation: #{location}")
note && IO.puts([?\n, note, ?\n, ?\n])
end
end
# Loadpaths without checks because tasks may be defined in deps.
defp loadpaths! do
args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
@@ -194,14 +207,29 @@ defmodule Mix.Tasks.Help do
defp verbose_doc(task) do
aliases = load_aliases()
if Map.has_key?(aliases, task) do
task_name = aliases[task]
doc = "Alias for " <> inspect(task_name)
{doc, "mix.exs"}
else
module = Mix.Task.get!(task)
doc = Mix.Task.moduledoc(module) || "There is no documentation for this task"
{doc, where_is_file(module)}
has_alias? = Map.has_key?(aliases, task)
has_task? = Mix.Task.get(task)
cond do
has_alias? and has_task? ->
note = "There is also a task named \"#{task}\". The documentation is shown next."
[alias_doc(aliases[task], note), task_doc(task)]
has_alias? ->
[alias_doc(aliases[task], nil)]
true ->
[task_doc(task)]
end
end
defp alias_doc(task_name, note) do
{"Alias for " <> inspect(task_name), "mix.exs", note}
end
defp task_doc(task) do
module = Mix.Task.get!(task)
doc = Mix.Task.moduledoc(module) || "There is no documentation for this task"
{doc, where_is_file(module), nil}
end
end
+2 -1
View File
@@ -6,6 +6,7 @@ defmodule Mix.Tasks.Test do
def start(compile_path, opts) do
Mix.shell().info("Cover compiling modules ...")
_ = :cover.stop()
_ = :cover.start()
case :cover.compile_beam_directory(compile_path |> to_charlist) do
@@ -407,7 +408,7 @@ defmodule Mix.Tasks.Test do
Mix.shell().info("No stale tests")
files == [] ->
raise_or_error_at_exit("There are no tests to run", opts)
Mix.shell().info("There are no tests to run")
true ->
message = "Paths given to `mix test` did not match any directory/file: "
@@ -0,0 +1,5 @@
defmodule Bar do
def hello do
:world
end
end
+13
View File
@@ -0,0 +1,13 @@
defmodule Bar.MixProject do
use Mix.Project
def project do
[
app: :bar,
version: "0.1.0",
# Choose something besides *_test.exs so that these test files don't
# get accidentally swept up into the actual Mix test suite.
test_pattern: "*_tests.exs"
]
end
end
@@ -0,0 +1,7 @@
defmodule BarTest do
use ExUnit.Case
test "greets the world" do
assert Foo.hello() == :world
end
end
@@ -0,0 +1 @@
ExUnit.start()
@@ -0,0 +1,5 @@
defmodule Foo do
def hello do
:world
end
end
+13
View File
@@ -0,0 +1,13 @@
defmodule Foo.MixProject do
use Mix.Project
def project do
[
app: :foo,
version: "0.1.0",
# Choose something besides *_test.exs so that these test files don't
# get accidentally swept up into the actual Mix test suite.
test_pattern: "*_tests.exs"
]
end
end
@@ -0,0 +1,7 @@
defmodule FooTest do
use ExUnit.Case
test "greets the world" do
assert Foo.hello() == :world
end
end
@@ -0,0 +1 @@
ExUnit.start()
+9
View File
@@ -0,0 +1,9 @@
defmodule UmbrellaCover.MixProject do
use Mix.Project
def project do
[
apps_path: "apps"
]
end
end
+45
View File
@@ -509,6 +509,51 @@ defmodule Mix.DepTest do
end)
end
test "nested deps with optional matching" do
Process.put(:custom_deps_git_repo_opts, optional: true)
# deps_repo brings git_repo but it is optional
deps = [
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}
]
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
File.mkdir_p!("custom/deps_repo/lib")
File.write!("custom/deps_repo/lib/a.ex", """
# Check that the child dependency is top_level and optional
[%Mix.Dep{app: :git_repo, top_level: true, opts: opts}] = Mix.Dep.cached()
true = Keyword.fetch!(opts, :optional)
""")
Mix.Tasks.Deps.Get.run([])
Mix.Tasks.Deps.Compile.run([])
end)
end)
end
test "nested deps considers runtime from current app" do
Process.put(:custom_deps_git_repo_opts, runtime: false)
deps = [
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}
]
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
Mix.Tasks.Deps.Compile.run([])
{:ok, [{:application, :deps_repo, opts}]} =
:file.consult("_build/dev/lib/deps_repo/ebin/deps_repo.app")
assert :git_repo not in Keyword.get(opts, :applications)
end)
end)
end
test "nested deps with overrides" do
# deps_repo brings git_repo but it is overriden
deps = [
+4
View File
@@ -183,6 +183,10 @@ defmodule Mix.Tasks.ArchiveTest do
send(self(), {:mix_shell_input, :yes?, true})
Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
# Check old paths are unloaded
paths = Enum.map(:code.get_path(), &List.to_string/1)
refute tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin") in paths
end)
end
+30
View File
@@ -242,6 +242,36 @@ defmodule Mix.Tasks.DepsTest do
end)
end
test "does not load or prune builds with --no-load-deps" do
Mix.Project.push(SuccessfulDepsApp)
in_fixture("deps_status", fn ->
# Start from scratch!
File.rm_rf("_build")
Mix.Tasks.Deps.Compile.run([])
Mix.Tasks.Deps.Loadpaths.run([])
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
assert File.exists?("_build/dev/lib/ok/priv/sample")
Mix.Tasks.Compile.run([])
assert to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path()
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
# Remove the deps without build_path
Mix.ProjectStack.post_config(deps: [])
Mix.ProjectStack.clear_cache()
Mix.Project.pop()
Mix.Project.push(SuccessfulDepsApp)
Code.delete_path("_build/dev/lib/ok/ebin")
Mix.Tasks.Deps.Loadpaths.run(["--no-load-deps"])
refute to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path()
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
end)
end
## deps.unlock
test "unlocks all deps", context do
+27
View File
@@ -136,6 +136,33 @@ defmodule Mix.Tasks.HelpTest do
end)
end
defmodule ShadowedAliases do
def project do
[aliases: [compile: "compile"]]
end
end
test "help TASK && ALIAS", context do
Mix.Project.push(ShadowedAliases)
in_tmp(context.test, fn ->
output =
capture_io(fn ->
Mix.Tasks.Help.run(["compile"])
end)
assert output =~ "# mix compile\n\n"
assert output =~ "Alias for \"compile\"\n"
assert output =~ ~r/^Location: mix.exs/m
assert output =~
"\nThere is also a task named \"compile\". The documentation is shown next.\n"
assert output =~ "## Command line options"
assert output =~ ~r/^Location:/m
end)
end
test "help --search PATTERN", context do
in_tmp(context.test, fn ->
Mix.Tasks.Help.run(["--search", "deps"])
@@ -61,6 +61,12 @@ defmodule Mix.Tasks.LoadconfigTest do
Mix.Task.run("loadconfig", [config])
assert config in Mix.Project.config_files()
assert Mix.Project.config_mtime() > mtime
# Touching it should not have any deadlocks
File.touch!(config, {{2030, 1, 1}, {0, 0, 0}})
Mix.Task.run("loadconfig", [config])
assert config in Mix.Project.config_files()
assert Mix.Project.config_mtime() > mtime
end)
end
+21
View File
@@ -123,6 +123,27 @@ defmodule Mix.Tasks.TestTest do
end)
end
test "--cover: in an umbrella application, reports on the coverage of each app's modules exactly once" do
in_fixture("umbrella_cover", fn ->
output = mix(["test", "--cover"])
occurrences_of_bar_coverage =
output
|> String.split("100.00% | Bar")
|> length()
|> (&(&1 - 1)).()
occurrences_of_foo_coverage =
output
|> String.split("100.00% | Foo")
|> length()
|> (&(&1 - 1)).()
assert occurrences_of_bar_coverage == 1
assert occurrences_of_foo_coverage == 1
end)
end
test "--failed: loads only files with failures and runs just the failures" do
in_fixture("test_failed", fn ->
loading_only_passing_test_msg = "loading OnlyPassingTest"