Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6730d669fb | ||
|
|
fb4ccfa656 | ||
|
|
ff2ea69710 | ||
|
|
2146bb449f | ||
|
|
5a583c753b | ||
|
|
ec31cc2331 | ||
|
|
6549d209d9 | ||
|
|
71d27919ad | ||
|
|
a8aea1c763 | ||
|
|
a72f18b38b | ||
|
|
a1a37c67b6 | ||
|
|
ff73830103 | ||
|
|
950bcc0704 | ||
|
|
80564fc954 | ||
|
|
94e0e386ce | ||
|
|
bfa799b31f | ||
|
|
abe2af477b | ||
|
|
2a82c35e39 | ||
|
|
1f14211a5e | ||
|
|
1ae36fa079 | ||
|
|
e3fc947b92 |
@@ -241,6 +241,37 @@ protocol, giving developers more control over the struct representation.
|
||||
See the updated documentation for `Inspect` for a general rundown on
|
||||
the approaches and options available.
|
||||
|
||||
## v1.14.3 (2023-01-14)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Speed up loading of runtime modules in the parallel compiler
|
||||
* [Range] Optimize range membership implementation
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Return values from running doctests and make their order consistent
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Fix handling of negative years in `Calendar.strftime/2`
|
||||
* [Exception] Improve blaming of FunctionClauseError with `is_struct/2` guards
|
||||
* [Kernel] Fix invalid variable scoping in `defguard` expansion
|
||||
* [Kernel] Do not warn on captured underscored vars from `defmodule`
|
||||
* [Kernel] Do not crash for missing line info on type warnings
|
||||
* [Macro] Fix `Macro.to_string/1` for large negative integers
|
||||
* [Macro] Properly type and escape expansion of `__ENV__` in macros
|
||||
* [Path] Make sure `Path.wildcard/2` expands `..` symlinks accordingly
|
||||
* [Range] Address corner cases in `Range.disjoint?/2` implementation
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.DocTest] Remove unnecessary literal quotes from error message on reports
|
||||
|
||||
## v1.14.2 (2022-11-11)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.14.2
|
||||
ELIXIR_VERSION=1.14.3
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
|
||||
set ELIXIR_VERSION=1.14.2
|
||||
set ELIXIR_VERSION=1.14.3
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -791,7 +791,14 @@ defmodule Calendar do
|
||||
|
||||
# Year
|
||||
defp format_modifiers("Y" <> rest, width, pad, datetime, format_options, acc) do
|
||||
result = datetime.year |> Integer.to_string() |> pad_leading(width, pad)
|
||||
{sign, year} =
|
||||
if datetime.year < 0 do
|
||||
{?-, -datetime.year}
|
||||
else
|
||||
{[], datetime.year}
|
||||
end
|
||||
|
||||
result = [sign | year |> Integer.to_string() |> pad_leading(width, pad)]
|
||||
parse(rest, datetime, format_options, [result | acc])
|
||||
end
|
||||
|
||||
|
||||
@@ -1493,6 +1493,13 @@ defmodule DateTime do
|
||||
iex> dt |> DateTime.add(1, :day, FakeTimeZoneDatabase)
|
||||
#DateTime<2019-04-01 02:00:00+02:00 CEST Europe/Copenhagen>
|
||||
|
||||
This operation merges the precision of the naive date time with the given unit:
|
||||
|
||||
iex> result = DateTime.add(~U[2014-10-02 00:29:10Z], 21, :millisecond)
|
||||
~U[2014-10-02 00:29:10.021Z]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
"""
|
||||
@doc since: "1.8.0"
|
||||
@spec add(
|
||||
@@ -1537,6 +1544,7 @@ defmodule DateTime do
|
||||
|
||||
ppd = System.convert_time_unit(86400, :second, unit)
|
||||
total_offset = System.convert_time_unit(utc_offset + std_offset, :second, unit)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
result =
|
||||
datetime
|
||||
|
||||
@@ -247,6 +247,17 @@ defmodule Calendar.ISO do
|
||||
defguardp is_utc_offset(offset) when is_integer(offset)
|
||||
defguardp is_std_offset(offset) when is_integer(offset)
|
||||
|
||||
@doc """
|
||||
Converts a `t:System.time_unit/0` to precision.
|
||||
|
||||
Integer-based time units always get maximum precision.
|
||||
"""
|
||||
def time_unit_to_precision(:nanosecond), do: 6
|
||||
def time_unit_to_precision(:microsecond), do: 6
|
||||
def time_unit_to_precision(:millisecond), do: 3
|
||||
def time_unit_to_precision(:second), do: 0
|
||||
def time_unit_to_precision(_), do: 6
|
||||
|
||||
@doc """
|
||||
Parses a time `string` in the `:extended` format.
|
||||
|
||||
|
||||
@@ -379,7 +379,7 @@ defmodule NaiveDateTime do
|
||||
It can also work with subsecond precisions:
|
||||
|
||||
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10], 2_000, :millisecond)
|
||||
~N[2014-10-02 00:29:12]
|
||||
~N[2014-10-02 00:29:12.000]
|
||||
|
||||
As well as days/hours/minutes:
|
||||
|
||||
@@ -390,16 +390,12 @@ defmodule NaiveDateTime do
|
||||
iex> NaiveDateTime.add(~N[2015-02-28 00:29:10], 60, :minute)
|
||||
~N[2015-02-28 01:29:10]
|
||||
|
||||
This operation keeps the precision of the naive date time:
|
||||
This operation merges the precision of the naive date time with the given unit:
|
||||
|
||||
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10.021], 21, :second)
|
||||
~N[2014-10-02 00:29:31.021]
|
||||
|
||||
And ignores any changes below the precision:
|
||||
|
||||
iex> hidden = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
|
||||
iex> hidden.microsecond # ~N[2014-10-02 00:29:10]
|
||||
{21000, 0}
|
||||
iex> result = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
|
||||
~N[2014-10-02 00:29:10.021]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
Operations on top of gregorian seconds or the Unix epoch are optimized:
|
||||
|
||||
@@ -440,6 +436,7 @@ defmodule NaiveDateTime do
|
||||
)
|
||||
when is_integer(amount_to_add) do
|
||||
ppd = System.convert_time_unit(86400, :second, unit)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
naive_datetime
|
||||
|> to_iso_days()
|
||||
|
||||
@@ -493,6 +493,13 @@ defmodule Time do
|
||||
iex> Time.add(~T[17:10:05], 30, :minute)
|
||||
~T[17:40:05]
|
||||
|
||||
This operation merges the precision of the time with the given unit:
|
||||
|
||||
iex> result = Time.add(~T[00:29:10], 21, :millisecond)
|
||||
~T[00:29:10.021]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec add(Calendar.time(), integer, :hour | :minute | System.time_unit()) :: t
|
||||
@@ -511,6 +518,7 @@ defmodule Time do
|
||||
amount_to_add = System.convert_time_unit(amount_to_add, unit, :microsecond)
|
||||
total = time_to_microseconds(time) + amount_to_add
|
||||
parts = Integer.mod(total, @parts_per_day)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
{hour, minute, second, {microsecond, _}} =
|
||||
calendar.time_from_day_fraction({parts, @parts_per_day})
|
||||
@@ -736,14 +744,14 @@ defmodule Time do
|
||||
hour: hour1,
|
||||
minute: minute1,
|
||||
second: second1,
|
||||
microsecond: {microsecond1, @parts_per_day}
|
||||
microsecond: {microsecond1, _}
|
||||
},
|
||||
%{
|
||||
calendar: Calendar.ISO,
|
||||
hour: hour2,
|
||||
minute: minute2,
|
||||
second: second2,
|
||||
microsecond: {microsecond2, @parts_per_day}
|
||||
microsecond: {microsecond2, _}
|
||||
},
|
||||
unit
|
||||
) do
|
||||
|
||||
@@ -1583,6 +1583,10 @@ defmodule Code.Formatter do
|
||||
insert_underscores(int_part) <> "." <> decimal_part
|
||||
end
|
||||
|
||||
defp insert_underscores("-" <> digits) do
|
||||
"-" <> insert_underscores(digits)
|
||||
end
|
||||
|
||||
defp insert_underscores(digits) do
|
||||
cond do
|
||||
digits =~ "_" ->
|
||||
|
||||
@@ -70,15 +70,15 @@ defmodule Code.Normalizer do
|
||||
# Atoms with interpolations
|
||||
defp do_normalize(
|
||||
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta,
|
||||
[{:<<>>, _, args} = string, :utf8]},
|
||||
[{:<<>>, _, parts} = string, :utf8]},
|
||||
state
|
||||
)
|
||||
when is_list(args) do
|
||||
when is_list(parts) do
|
||||
dot_meta = patch_meta_line(dot_meta, state.parent_meta)
|
||||
call_meta = patch_meta_line(call_meta, dot_meta)
|
||||
|
||||
utf8 =
|
||||
if args == [] or interpolated?(string) do
|
||||
if parts == [] or binary_interpolated?(parts) do
|
||||
# a non-normalized :utf8 atom signals an atom interpolation
|
||||
:utf8
|
||||
else
|
||||
@@ -96,23 +96,27 @@ defmodule Code.Normalizer do
|
||||
end
|
||||
|
||||
# Charlists with interpolations
|
||||
defp do_normalize({{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}, state) do
|
||||
parts =
|
||||
Enum.map(parts, fn
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args} ->
|
||||
args = normalize_args(args, state)
|
||||
defp do_normalize({{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]} = quoted, state) do
|
||||
if list_interpolated?(parts) do
|
||||
parts =
|
||||
Enum.map(parts, fn
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args} ->
|
||||
args = normalize_args(args, state)
|
||||
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args}
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args}
|
||||
|
||||
part ->
|
||||
if state.escape do
|
||||
maybe_escape_literal(part, state)
|
||||
else
|
||||
part
|
||||
end
|
||||
end)
|
||||
part when is_binary(part) ->
|
||||
if state.escape do
|
||||
maybe_escape_literal(part, state)
|
||||
else
|
||||
part
|
||||
end
|
||||
end)
|
||||
|
||||
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
|
||||
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
|
||||
else
|
||||
normalize_call(quoted, state)
|
||||
end
|
||||
end
|
||||
|
||||
# Don't normalize the `Access` atom in access syntax
|
||||
@@ -389,11 +393,11 @@ defmodule Code.Normalizer do
|
||||
defp allow_keyword?(:{}, _), do: false
|
||||
defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity)
|
||||
|
||||
defp normalize_bitstring({:<<>>, meta, parts} = quoted, state, escape_interpolation \\ false) do
|
||||
defp normalize_bitstring({:<<>>, meta, parts}, state, escape_interpolation \\ false) do
|
||||
meta = patch_meta_line(meta, state.parent_meta)
|
||||
|
||||
parts =
|
||||
if interpolated?(quoted) do
|
||||
if binary_interpolated?(parts) do
|
||||
normalize_interpolation_parts(parts, %{state | parent_meta: meta}, escape_interpolation)
|
||||
else
|
||||
state = %{state | parent_meta: meta}
|
||||
@@ -543,8 +547,7 @@ defmodule Code.Normalizer do
|
||||
term
|
||||
end
|
||||
|
||||
# Check if we have an interpolated string.
|
||||
defp interpolated?({:<<>>, _, [_ | _] = parts}) do
|
||||
defp binary_interpolated?(parts) do
|
||||
Enum.all?(parts, fn
|
||||
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
|
||||
binary when is_binary(binary) -> true
|
||||
@@ -552,8 +555,12 @@ defmodule Code.Normalizer do
|
||||
end)
|
||||
end
|
||||
|
||||
defp interpolated?(_) do
|
||||
false
|
||||
defp list_interpolated?(parts) do
|
||||
Enum.all?(parts, fn
|
||||
{{:., _, [Kernel, :to_string]}, _, [_]} -> true
|
||||
binary when is_binary(binary) -> true
|
||||
_ -> false
|
||||
end)
|
||||
end
|
||||
|
||||
defp patch_meta_line(meta, parent_meta) do
|
||||
|
||||
@@ -294,13 +294,11 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp undo_is_struct_guard(
|
||||
{:and, meta, [_, %{node: {_, _, [{_, _, [_, {struct, _, _}]} | optional]}}]}
|
||||
) do
|
||||
defp undo_is_struct_guard({:and, meta, [_, %{node: {_, _, [{_, _, [_, arg]} | optional]}}]}) do
|
||||
args =
|
||||
case optional do
|
||||
[] -> [{struct, meta, nil}]
|
||||
[module] -> [{struct, meta, nil}, module]
|
||||
[] -> [arg]
|
||||
[module] -> [arg, module]
|
||||
end
|
||||
|
||||
%{match?: meta[:value], node: {:is_struct, meta, args}}
|
||||
|
||||
@@ -4806,7 +4806,7 @@ defmodule Kernel do
|
||||
var_meta =
|
||||
case prune do
|
||||
true -> [generated: true, keep_unused: true]
|
||||
false -> [generated: false]
|
||||
false -> [generated: true]
|
||||
end
|
||||
|
||||
module_vars = :lists.map(&module_var(&1, var_meta), :maps.keys(versioned_vars))
|
||||
|
||||
@@ -271,12 +271,6 @@ defmodule Kernel.ParallelCompiler do
|
||||
for {{:module, module}, _} <- result,
|
||||
do: module
|
||||
|
||||
runtime_modules =
|
||||
for module <- runtime_modules,
|
||||
path = :code.which(module),
|
||||
is_list(path) and path != [],
|
||||
do: {module, path}
|
||||
|
||||
profile_checker(profile, compiled_modules, runtime_modules, fn ->
|
||||
Module.ParallelChecker.verify(checker, runtime_modules)
|
||||
end)
|
||||
|
||||
@@ -331,7 +331,7 @@ defmodule Kernel.Utils do
|
||||
quote do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true -> unquote(literal_quote(unquote_every_ref(expr, vars)))
|
||||
false -> unquote(literal_quote(unquote_refs_once(expr, vars)))
|
||||
false -> unquote(literal_quote(unquote_refs_once(expr, vars, env.module)))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -361,7 +361,7 @@ defmodule Kernel.Utils do
|
||||
end
|
||||
|
||||
# Prefaces `guard` with unquoted versions of `refs`.
|
||||
defp unquote_refs_once(guard, refs) do
|
||||
defp unquote_refs_once(guard, refs, module) do
|
||||
{guard, used_refs} =
|
||||
Macro.postwalk(guard, %{}, fn
|
||||
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
@@ -375,7 +375,7 @@ defmodule Kernel.Utils do
|
||||
|
||||
%{} ->
|
||||
generated = String.to_atom("arg" <> Integer.to_string(map_size(acc) + 1))
|
||||
new_var = Macro.unique_var(generated, Elixir)
|
||||
new_var = Macro.unique_var(generated, module)
|
||||
{new_var, Map.put(acc, pair, {new_var, var})}
|
||||
end
|
||||
|
||||
|
||||
+11
-6
@@ -1763,7 +1763,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec expand_once(t(), Macro.Env.t()) :: t()
|
||||
@spec expand_once(input(), Macro.Env.t()) :: output()
|
||||
def expand_once(ast, env) do
|
||||
elem(do_expand_once(ast, env), 0)
|
||||
end
|
||||
@@ -1795,13 +1795,15 @@ defmodule Macro do
|
||||
defp do_expand_once({:__DIR__, _, atom}, env) when is_atom(atom),
|
||||
do: {:filename.dirname(env.file), true}
|
||||
|
||||
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom),
|
||||
do: {{:%{}, [], Map.to_list(env)}, true}
|
||||
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom) do
|
||||
env = update_in(env.versioned_vars, &maybe_escape_map/1)
|
||||
{maybe_escape_map(env), true}
|
||||
end
|
||||
|
||||
defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env)
|
||||
when is_atom(atom) and is_atom(field) do
|
||||
if Map.has_key?(env, field) do
|
||||
{Map.get(env, field), true}
|
||||
{maybe_escape_map(Map.get(env, field)), true}
|
||||
else
|
||||
{original, false}
|
||||
end
|
||||
@@ -1884,6 +1886,9 @@ defmodule Macro do
|
||||
# Anything else is just returned
|
||||
defp do_expand_once(other, _env), do: {other, false}
|
||||
|
||||
defp maybe_escape_map(map) when is_map(map), do: {:%{}, [], Map.to_list(map)}
|
||||
defp maybe_escape_map(other), do: other
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given name and arity is a special form.
|
||||
"""
|
||||
@@ -1995,7 +2000,7 @@ defmodule Macro do
|
||||
when they change.
|
||||
"""
|
||||
@doc since: "1.14.1"
|
||||
@spec expand_literals(t(), Macro.Env.t()) :: t()
|
||||
@spec expand_literals(input(), Macro.Env.t()) :: output()
|
||||
def expand_literals(ast, env) do
|
||||
{ast, :ok} = expand_literals(ast, :ok, fn node, :ok -> {expand(node, env), :ok} end)
|
||||
ast
|
||||
@@ -2075,7 +2080,7 @@ defmodule Macro do
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
it out for more information and examples.
|
||||
"""
|
||||
@spec expand(t(), Macro.Env.t()) :: t()
|
||||
@spec expand(input(), Macro.Env.t()) :: output()
|
||||
def expand(ast, env) do
|
||||
expand_until({ast, true}, env)
|
||||
end
|
||||
|
||||
@@ -62,7 +62,10 @@ defmodule Module.ParallelChecker do
|
||||
if is_map(info) do
|
||||
info
|
||||
else
|
||||
info |> File.read!() |> maybe_module_map(module)
|
||||
case File.read(info) do
|
||||
{:ok, binary} -> maybe_module_map(binary, module)
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
module_map && cache_from_module_map(ets, module_map)
|
||||
@@ -136,7 +139,7 @@ defmodule Module.ParallelChecker do
|
||||
the modules and adds the ExCk chunk to the binaries. Returns the updated
|
||||
list of warnings from the verification.
|
||||
"""
|
||||
@spec verify(pid(), [{module(), binary()}]) :: [warning()]
|
||||
@spec verify(pid(), [{module(), Path.t()}]) :: [warning()]
|
||||
def verify(checker, runtime_files) do
|
||||
for {module, file} <- runtime_files do
|
||||
spawn({self(), checker}, module, file)
|
||||
@@ -324,8 +327,7 @@ defmodule Module.ParallelChecker do
|
||||
|
||||
defp cache_from_chunk(ets, module) do
|
||||
with {^module, binary, _filename} <- :code.get_object_code(module),
|
||||
{:ok, ^module} <- binary |> :beam_lib.info() |> Keyword.fetch(:module),
|
||||
{:ok, {_, [{'ExCk', chunk}]}} <- :beam_lib.chunks(binary, ['ExCk']),
|
||||
{:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]),
|
||||
{:elixir_checker_v1, contents} <- :erlang.binary_to_term(chunk) do
|
||||
cache_chunk(ets, module, contents.exports)
|
||||
true
|
||||
|
||||
@@ -135,8 +135,7 @@ defmodule Module.Types do
|
||||
# Collect relevant information from context and traces to report error
|
||||
def error_to_warning(:unable_apply, {mfa, args, expected, signature, stack}, context) do
|
||||
{fun, arity} = context.function
|
||||
line = get_meta(stack.last_expr)[:line]
|
||||
location = {context.file, line, {context.module, fun, arity}}
|
||||
location = {context.file, get_line(stack), {context.module, fun, arity}}
|
||||
|
||||
traces = type_traces(stack, context)
|
||||
{[signature | args], traces} = lift_all_types([signature | args], traces, context)
|
||||
@@ -146,8 +145,7 @@ defmodule Module.Types do
|
||||
|
||||
def error_to_warning(:unable_unify, {left, right, stack}, context) do
|
||||
{fun, arity} = context.function
|
||||
line = get_meta(stack.last_expr)[:line]
|
||||
location = {context.file, line, {context.module, fun, arity}}
|
||||
location = {context.file, get_line(stack), {context.module, fun, arity}}
|
||||
|
||||
traces = type_traces(stack, context)
|
||||
{[left, right], traces} = lift_all_types([left, right], traces, context)
|
||||
@@ -155,6 +153,8 @@ defmodule Module.Types do
|
||||
{Module.Types, error, location}
|
||||
end
|
||||
|
||||
defp get_line(stack), do: stack.last_expr |> get_meta() |> Keyword.get(:line, 0)
|
||||
|
||||
# Collect relevant traces from context.traces using stack.unify_stack
|
||||
defp type_traces(stack, context) do
|
||||
# TODO: Do we need the unify_stack or is enough to only get the last variable
|
||||
|
||||
@@ -603,6 +603,10 @@ defmodule Path do
|
||||
:file.read_link_info(file)
|
||||
end
|
||||
|
||||
def read_file_info(file) do
|
||||
:file.read_file_info(file)
|
||||
end
|
||||
|
||||
def list_dir(dir) do
|
||||
case :file.list_dir(dir) do
|
||||
{:ok, files} -> {:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
|
||||
+31
-27
@@ -294,22 +294,24 @@ defmodule Range do
|
||||
iex> Range.disjoint?(1..10//2, 2..10//2)
|
||||
true
|
||||
|
||||
# First element in common in all below is 29
|
||||
iex> Range.disjoint?(2..100//3, 9..100//5)
|
||||
false
|
||||
iex> Range.disjoint?(101..2//-3, 99..9//-5)
|
||||
false
|
||||
# First element in common is 29
|
||||
iex> Range.disjoint?(1..100//14, 8..100//21)
|
||||
false
|
||||
iex> Range.disjoint?(57..-1//-14, 8..100//21)
|
||||
false
|
||||
iex> Range.disjoint?(1..100//14, 51..8//-21)
|
||||
iex> Range.disjoint?(1..100//14, 50..8//-21)
|
||||
false
|
||||
|
||||
# If 29 is out of range
|
||||
iex> Range.disjoint?(1..28//14, 8..28//21)
|
||||
true
|
||||
|
||||
# First element in common is 14
|
||||
iex> Range.disjoint?(2..28//3, 9..28//5)
|
||||
false
|
||||
iex> Range.disjoint?(26..2//-3, 29..9//-5)
|
||||
false
|
||||
|
||||
# Starting from the back without alignment
|
||||
iex> Range.disjoint?(27..11//-3, 30..0//-7)
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -334,21 +336,28 @@ defmodule Range do
|
||||
# progressions and see if they belong within the ranges
|
||||
# https://math.stackexchange.com/questions/1656120/formula-to-find-the-first-intersection-of-two-arithmetic-progressions
|
||||
{gcd, u, v} = Integer.extended_gcd(-step1, step2)
|
||||
c = first1 - first2 + step2 - step1
|
||||
t1 = -c / step1 * u
|
||||
t2 = -c / step2 * v
|
||||
t = max(floor(t1) + 1, floor(t2) + 1)
|
||||
x = div(c * u + t * step2, gcd) - 1
|
||||
y = div(c * v + t * step1, gcd) - 1
|
||||
|
||||
x < 0 or first1 + x * step1 > last1 or
|
||||
y < 0 or first2 + y * step2 > last2
|
||||
if rem(first2 - first1, gcd) == 0 do
|
||||
c = first1 - first2 + step2 - step1
|
||||
t1 = -c / step2 * u
|
||||
t2 = -c / step1 * v
|
||||
t = max(floor(t1) + 1, floor(t2) + 1)
|
||||
x = div(c * u + t * step2, gcd) - 1
|
||||
y = div(c * v + t * step1, gcd) - 1
|
||||
|
||||
x < 0 or first1 + x * step1 > last1 or
|
||||
y < 0 or first2 + y * step2 > last2
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@compile inline: [normalize: 3]
|
||||
defp normalize(first, last, step) when first > last, do: {last, first, -step}
|
||||
defp normalize(first, last, step) when first > last,
|
||||
do: {first - abs(div(first - last, step) * step), first, -step}
|
||||
|
||||
defp normalize(first, last, step), do: {first, last, step}
|
||||
|
||||
@doc false
|
||||
@@ -387,16 +396,11 @@ defimpl Enumerable, for: Range do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
def member?(first..last//step = range, value) when is_integer(value) do
|
||||
cond do
|
||||
Range.size(range) == 0 ->
|
||||
{:ok, false}
|
||||
|
||||
first <= last ->
|
||||
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
|
||||
|
||||
true ->
|
||||
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
|
||||
def member?(first..last//step, value) when is_integer(value) do
|
||||
if step > 0 do
|
||||
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
|
||||
else
|
||||
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ translate_into(Meta, Cases, Expr, Opts, S) ->
|
||||
{TInto, SI} =
|
||||
case lists:keyfind(into, 1, Opts) of
|
||||
{into, Into} -> elixir_erl_pass:translate(Into, Ann, S);
|
||||
false -> {{nil, Ann}, S}
|
||||
false -> {false, S}
|
||||
end,
|
||||
|
||||
TUniq = lists:keyfind(uniq, 1, Opts) == {uniq, true},
|
||||
|
||||
@@ -364,5 +364,12 @@ defmodule CalendarTest do
|
||||
Calendar.strftime(~D[2019-08-15], "%D", unknown: "option")
|
||||
end
|
||||
end
|
||||
|
||||
test "zero padding for negative year" do
|
||||
assert Calendar.strftime(Date.new!(-1, 1, 1), "%Y") == "-0001"
|
||||
assert Calendar.strftime(Date.new!(-11, 1, 1), "%Y") == "-0011"
|
||||
assert Calendar.strftime(Date.new!(-111, 1, 1), "%Y") == "-0111"
|
||||
assert Calendar.strftime(Date.new!(-1111, 1, 1), "%Y") == "-1111"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -515,6 +515,17 @@ defmodule Code.Normalizer.QuotedASTTest do
|
||||
test "charlist" do
|
||||
assert quoted_to_string(quote(do: [])) == "[]"
|
||||
assert quoted_to_string(quote(do: 'abc')) == "'abc'"
|
||||
|
||||
# False positive
|
||||
assert quoted_to_string(
|
||||
quote do
|
||||
:"Elixir.List".to_charlist([
|
||||
case var do
|
||||
var -> var
|
||||
end
|
||||
])
|
||||
end
|
||||
) =~ "List.to_charlist([\n case var do\n var -> var\n end\n])"
|
||||
end
|
||||
|
||||
test "string" do
|
||||
|
||||
@@ -426,6 +426,8 @@ defmodule ExceptionTest do
|
||||
when is_binary(url) or (is_struct(url) and is_struct(url, url_module) and false) do
|
||||
url
|
||||
end
|
||||
|
||||
def sub_get!(url) when is_struct(url.sub, URI), do: url.sub
|
||||
end
|
||||
)
|
||||
|
||||
@@ -475,6 +477,19 @@ defmodule ExceptionTest do
|
||||
|
||||
def get!(url) when -is_binary(url)- or is_struct(url) and is_struct(url, URI) and -false-
|
||||
"""
|
||||
|
||||
assert blame_message(Req, & &1.sub_get!(%{})) =~ """
|
||||
no function clause matching in ExceptionTest.Req.sub_get!/1
|
||||
|
||||
The following arguments were given to ExceptionTest.Req.sub_get!/1:
|
||||
|
||||
# 1
|
||||
%{}
|
||||
|
||||
Attempted function clauses (showing 1 out of 1):
|
||||
|
||||
def sub_get!(url) when -is_struct(url.sub, URI)-
|
||||
"""
|
||||
end
|
||||
|
||||
test "annotates badarg on apply" do
|
||||
|
||||
@@ -172,10 +172,18 @@ defmodule MacroTest do
|
||||
end
|
||||
|
||||
test "env" do
|
||||
env = %{__ENV__ | line: 0}
|
||||
assert Macro.expand_once(quote(do: __ENV__), env) == {:%{}, [], Map.to_list(env)}
|
||||
env = %{__ENV__ | line: 0, lexical_tracker: self()}
|
||||
|
||||
expanded = Macro.expand_once(quote(do: __ENV__), env)
|
||||
assert Macro.validate(expanded) == :ok
|
||||
assert Code.eval_quoted(expanded) == {env, []}
|
||||
|
||||
assert Macro.expand_once(quote(do: __ENV__.file), env) == env.file
|
||||
assert Macro.expand_once(quote(do: __ENV__.unknown), env) == quote(do: __ENV__.unknown)
|
||||
|
||||
expanded = Macro.expand_once(quote(do: __ENV__.versioned_vars), env)
|
||||
assert Macro.validate(expanded) == :ok
|
||||
assert Code.eval_quoted(expanded) == {env.versioned_vars, []}
|
||||
end
|
||||
|
||||
defmacro local_macro(), do: raise("ignored")
|
||||
@@ -421,6 +429,16 @@ defmodule MacroTest do
|
||||
test "converts quoted to string" do
|
||||
assert Macro.to_string(quote do: hello(world)) == "hello(world)"
|
||||
end
|
||||
|
||||
test "large number literals" do
|
||||
# with quote
|
||||
assert Macro.to_string(quote do: 576_460_752_303_423_455) == "576_460_752_303_423_455"
|
||||
assert Macro.to_string(quote do: -576_460_752_303_423_455) == "-576_460_752_303_423_455"
|
||||
|
||||
# without quote
|
||||
assert Macro.to_string(576_460_752_303_423_455) == "576_460_752_303_423_455"
|
||||
assert Macro.to_string(-576_460_752_303_423_455) == "-576_460_752_303_423_455"
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_string/2" do
|
||||
|
||||
@@ -562,6 +562,22 @@ defmodule Module.Types.IntegrationTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "regressions" do
|
||||
test "handle missing location info from quoted" do
|
||||
assert capture_io(:stderr, fn ->
|
||||
quote do
|
||||
defmodule X do
|
||||
def f() do
|
||||
x = %{}
|
||||
%{x | key: :value}
|
||||
end
|
||||
end
|
||||
end
|
||||
|> Code.compile_quoted()
|
||||
end) =~ "warning:"
|
||||
end
|
||||
end
|
||||
|
||||
describe "after_verify" do
|
||||
test "reports functions" do
|
||||
files = %{
|
||||
|
||||
@@ -332,6 +332,13 @@ defmodule ModuleTest do
|
||||
assert Elixir.ModuleTest.NonAtomAlias.hello() == :world
|
||||
end
|
||||
|
||||
test "does not warn on captured underscored vars" do
|
||||
_unused = 123
|
||||
|
||||
defmodule __MODULE__.NoVarWarning do
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:no_warn_undefined, ModuleCreateSample}
|
||||
|
||||
test "create" do
|
||||
|
||||
@@ -38,6 +38,21 @@ defmodule PathTest do
|
||||
File.rm_rf(Path.join(config.tmp_dir, "wildcard"))
|
||||
end
|
||||
|
||||
@tag :tmp_dir
|
||||
test "wildcard/2 follows ..", config do
|
||||
hello = Path.join(config.tmp_dir, "wildcard/hello")
|
||||
world = Path.join(config.tmp_dir, "wildcard/world")
|
||||
File.mkdir_p(hello)
|
||||
File.touch(world)
|
||||
|
||||
assert Path.wildcard(Path.join(config.tmp_dir, "wildcard/w*/../h*")) == []
|
||||
|
||||
assert Path.wildcard(Path.join(config.tmp_dir, "wildcard/h*/../w*")) ==
|
||||
[Path.join(config.tmp_dir, "wildcard/hello/../world")]
|
||||
after
|
||||
File.rm_rf(Path.join(config.tmp_dir, "wildcard"))
|
||||
end
|
||||
|
||||
test "wildcard/2 raises on null byte" do
|
||||
assert_raise ArgumentError, ~r/null byte/, fn -> Path.wildcard("foo\0bar") end
|
||||
end
|
||||
|
||||
@@ -238,9 +238,9 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
extract(module)
|
||||
|> filter_by_opts(module, opts)
|
||||
|> Stream.with_index(1)
|
||||
|> Enum.map(fn {test, acc} ->
|
||||
compile_test(test, module, import, acc, file, tags)
|
||||
|> Enum.sort_by(& &1.line)
|
||||
|> Enum.with_index(fn test, index ->
|
||||
compile_test(test, module, import, index + 1, file, tags)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -313,8 +313,8 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
tests =
|
||||
Enum.map(exprs, fn {expr, expected, formatted} ->
|
||||
test_case_content(expr, expected, location, stack, formatted)
|
||||
Enum.map(exprs, fn {expr, expected, doctest} ->
|
||||
test_case_content(expr, expected, location, stack, doctest)
|
||||
end)
|
||||
|
||||
{:__block__, [], test_import(module, do_import) ++ tests}
|
||||
@@ -327,12 +327,11 @@ defmodule ExUnit.DocTest do
|
||||
end) > 1
|
||||
end
|
||||
|
||||
defp test_case_content(expr, :test, location, stack, formatted) do
|
||||
string_to_quoted(location, stack, expr, "\n" <> formatted) |> insert_assertions()
|
||||
defp test_case_content(expr, :test, location, stack, doctest) do
|
||||
string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:test, expected}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
defp test_case_content(expr, {:test, expected}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
expected_ast = string_to_quoted(location, stack, expected, doctest)
|
||||
last_expr = Macro.to_string(last_expr(expr_ast))
|
||||
@@ -340,56 +339,53 @@ defmodule ExUnit.DocTest do
|
||||
quote do
|
||||
value = unquote(expr_ast)
|
||||
expected = unquote(expected_ast)
|
||||
formatted = unquote(formatted)
|
||||
doctest = unquote(doctest)
|
||||
last_expr = unquote(last_expr)
|
||||
expected_expr = unquote(expected)
|
||||
stack = unquote(stack)
|
||||
|
||||
ExUnit.DocTest.__test__(value, expected, formatted, last_expr, expected_expr, stack)
|
||||
ExUnit.DocTest.__test__(value, expected, doctest, last_expr, expected_expr, stack)
|
||||
end
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:inspect, expected}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
defp test_case_content(expr, {:inspect, expected}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
expected_ast = string_to_quoted(location, stack, expected, doctest)
|
||||
last_expr = Macro.to_string(last_expr(expr_ast))
|
||||
|
||||
quote do
|
||||
value = unquote(expr_ast)
|
||||
expected = unquote(expected_ast)
|
||||
formatted = unquote(formatted)
|
||||
expected = unquote(expected)
|
||||
doctest = unquote(doctest)
|
||||
last_expr = unquote(last_expr)
|
||||
expected_expr = unquote(expected)
|
||||
expected_expr = unquote(inspect(expected))
|
||||
stack = unquote(stack)
|
||||
|
||||
ExUnit.DocTest.__inspect__(value, expected, formatted, last_expr, expected_expr, stack)
|
||||
ExUnit.DocTest.__inspect__(value, expected, doctest, last_expr, expected_expr, stack)
|
||||
end
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:error, exception, message}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
defp test_case_content(expr, {:error, exception, message}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest)
|
||||
|
||||
quote do
|
||||
stack = unquote(stack)
|
||||
message = unquote(message)
|
||||
formatted = unquote(formatted)
|
||||
doctest = unquote(doctest)
|
||||
exception = unquote(exception)
|
||||
ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, formatted, stack)
|
||||
ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, doctest, stack)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __test__(value, expected, formatted, last_expr, expected_expr, stack) do
|
||||
def __test__(value, expected, doctest, last_expr, expected_expr, stack) do
|
||||
case value do
|
||||
^expected ->
|
||||
:ok
|
||||
{:ok, value}
|
||||
|
||||
_ ->
|
||||
error = [
|
||||
message: "Doctest failed",
|
||||
doctest: "\n" <> formatted <> "\n" <> expected_expr,
|
||||
doctest: doctest,
|
||||
expr: "#{last_expr} === #{String.trim(expected_expr)}",
|
||||
left: value,
|
||||
right: expected
|
||||
@@ -400,7 +396,7 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __inspect__(value, expected, formatted, last_expr, expected_expr, parent_stack) do
|
||||
def __inspect__(value, expected, doctest, last_expr, expected_expr, parent_stack) do
|
||||
result =
|
||||
try do
|
||||
inspect(value, safe: false)
|
||||
@@ -415,10 +411,9 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
case result do
|
||||
:ok ->
|
||||
:ok
|
||||
{:ok, value}
|
||||
|
||||
{extra, stack} ->
|
||||
doctest = "\n" <> formatted <> "\n" <> expected_expr
|
||||
expr = "inspect(#{last_expr}) === #{String.trim(expected_expr)}"
|
||||
error = [doctest: doctest, expr: expr] ++ extra
|
||||
reraise ExUnit.AssertionError, error, stack ++ parent_stack
|
||||
@@ -426,7 +421,7 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __error__(fun, message, exception, formatted, stack) do
|
||||
def __error__(fun, message, exception, doctest, stack) do
|
||||
try do
|
||||
fun.()
|
||||
rescue
|
||||
@@ -451,12 +446,10 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
if failed do
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
reraise ExUnit.AssertionError, [message: failed, doctest: doctest], stack
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
failed = "Doctest failed: expected exception #{inspect(exception)} but nothing was raised"
|
||||
error = [message: failed, doctest: doctest]
|
||||
reraise ExUnit.AssertionError, error, stack
|
||||
@@ -862,7 +855,8 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
defp add_expr(%{exprs: exprs} = test, expr, expected, formatted) do
|
||||
%{test | exprs: [{expr, tag_expected(expected), formatted} | exprs]}
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
%{test | exprs: [{expr, tag_expected(expected), doctest} | exprs]}
|
||||
end
|
||||
|
||||
defp tag_expected(string) do
|
||||
@@ -876,7 +870,7 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
_ ->
|
||||
if inspectable?(string) do
|
||||
{:inspect, inspect(string)}
|
||||
{:inspect, string}
|
||||
else
|
||||
{:test, string}
|
||||
end
|
||||
|
||||
@@ -577,7 +577,7 @@ defmodule ExUnit.DocTestTest do
|
||||
Doctest failed
|
||||
doctest:
|
||||
iex> :oops
|
||||
"#Inspect<[]>"
|
||||
#Inspect<[]>
|
||||
code: inspect(:oops) === "#Inspect<[]>"
|
||||
left: ":oops"
|
||||
right: "#Inspect<[]>"
|
||||
@@ -600,7 +600,7 @@ defmodule ExUnit.DocTestTest do
|
||||
Doctest failed: expected exception WhatIsThis but got RuntimeError with message "oops"
|
||||
doctest:
|
||||
iex> raise "oops"
|
||||
** (WhatIsThis) "oops"
|
||||
** (WhatIsThis) oops
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 18}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
@@ -615,7 +615,7 @@ defmodule ExUnit.DocTestTest do
|
||||
"oops"
|
||||
doctest:
|
||||
iex> raise "oops"
|
||||
** (RuntimeError) "hello"
|
||||
** (RuntimeError) hello
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 21}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
@@ -635,18 +635,17 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
9) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (9) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
9) doctest ExUnit.DocTestTest.Invalid.b/0 (9) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 57}:5: unexpected token: "`" (column 5, code point U+0060)
|
||||
#{line_placeholder(starting_line + 57)} |
|
||||
#{starting_line + 57} | ```
|
||||
#{line_placeholder(starting_line + 57)} | ^
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 33}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 33)} |
|
||||
#{starting_line + 33} | 1 + * 1
|
||||
#{line_placeholder(starting_line + 33)} | ^
|
||||
doctest:
|
||||
iex> 1 + 2
|
||||
3
|
||||
```
|
||||
iex> 1 + * 1
|
||||
1
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 56}: ExUnit.DocTestTest.Invalid (module)
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 33}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
@@ -680,7 +679,22 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
12) doctest ExUnit.DocTestTest.Invalid.invalid_utf8/0 (12) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
12) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (12) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 57}:5: unexpected token: "`" (column 5, code point U+0060)
|
||||
#{line_placeholder(starting_line + 57)} |
|
||||
#{starting_line + 57} | ```
|
||||
#{line_placeholder(starting_line + 57)} | ^
|
||||
doctest:
|
||||
iex> 1 + 2
|
||||
3
|
||||
```
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 56}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
13) doctest ExUnit.DocTestTest.Invalid.invalid_utf8/0 (13) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (UnicodeConversionError) invalid encoding starting at <<255, 34, 41>>
|
||||
stacktrace:
|
||||
@@ -688,7 +702,7 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
13) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (13) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
14) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (14) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (TokenMissingError) test/ex_unit/doc_test_test.exs:#{starting_line + 69}:7: missing terminator: } (for "{" starting at line #{starting_line + 69})
|
||||
#{line_placeholder(starting_line + 69)} |
|
||||
@@ -702,28 +716,8 @@ defmodule ExUnit.DocTestTest do
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 69}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ "14) doctest ExUnit.DocTestTest.Invalid.raising_inspect/0"
|
||||
assert output =~ "iex> ExUnit.DocTestTest.Haiku.new(:this, :is, {:not, :a, :haiku})"
|
||||
|
||||
assert output =~
|
||||
"test/ex_unit/doc_test_test.exs:#{starting_line + 82}: ExUnit.DocTestTest.Invalid (module)"
|
||||
|
||||
assert output =~ """
|
||||
15) doctest ExUnit.DocTestTest.Invalid.b/0 (15) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 33}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 33)} |
|
||||
#{starting_line + 33} | 1 + * 1
|
||||
#{line_placeholder(starting_line + 33)} | ^
|
||||
doctest:
|
||||
iex> 1 + * 1
|
||||
1
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 33}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
16) doctest ExUnit.DocTestTest.Invalid.t/0 (16) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
15) doctest ExUnit.DocTestTest.Invalid.t/0 (15) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 75}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 75)} |
|
||||
@@ -735,6 +729,12 @@ defmodule ExUnit.DocTestTest do
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 75}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ "16) doctest ExUnit.DocTestTest.Invalid.raising_inspect/0"
|
||||
assert output =~ "iex> ExUnit.DocTestTest.Haiku.new(:this, :is, {:not, :a, :haiku})"
|
||||
|
||||
assert output =~
|
||||
"test/ex_unit/doc_test_test.exs:#{starting_line + 82}: ExUnit.DocTestTest.Invalid (module)"
|
||||
end
|
||||
|
||||
test "pattern matching assertions in doctests" do
|
||||
@@ -900,24 +900,7 @@ defmodule ExUnit.DocTestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "fails when testing single function not found" do
|
||||
message = """
|
||||
test/ex_unit/doc_test_test\.exs: undefined or private function given to doctest:
|
||||
|
||||
ExUnit.DocTestTest.SomewhatGoodModuleWithOnly.three/0
|
||||
|
||||
"""
|
||||
|
||||
assert_raise ExUnit.DocTest.Error, message, fn ->
|
||||
defmodule NeverCompiled do
|
||||
import ExUnit.DocTest
|
||||
|
||||
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithOnly, only: [three: 0], import: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "fails when testing multiple functions not found" do
|
||||
test "fails when testing functions are not found" do
|
||||
message = """
|
||||
test/ex_unit/doc_test_test\.exs: undefined or private functions given to doctest:
|
||||
|
||||
@@ -989,6 +972,16 @@ defmodule ExUnit.DocTestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "doctest direct invocation" do
|
||||
defmodule Direct do
|
||||
use ExUnit.Case, register: false
|
||||
doctest ExUnit.DocTestTest.GoodModule, import: true
|
||||
end
|
||||
|
||||
[head | _] = Direct.__ex_unit__().tests
|
||||
assert apply(Direct, head.name, [%{}]) == {:ok, 2}
|
||||
end
|
||||
|
||||
defp line_placeholder(line_number) do
|
||||
digits =
|
||||
line_number
|
||||
|
||||
@@ -465,7 +465,11 @@ defmodule Mix.Compilers.Elixir do
|
||||
|
||||
modules_set = Map.from_keys(modules, true)
|
||||
{_, runtime_modules} = fixpoint_runtime_modules(sources, modules_set)
|
||||
{:runtime, runtime_modules, warnings}
|
||||
|
||||
runtime_paths =
|
||||
Enum.map(runtime_modules, &{&1, Path.join(compile_path, Atom.to_string(&1) <> ".beam")})
|
||||
|
||||
{:runtime, runtime_paths, warnings}
|
||||
else
|
||||
Mix.Utils.compiling_n(length(changed), :ex)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user