Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0220bd267 | ||
|
|
6f5cc529d5 | ||
|
|
3d80256852 | ||
|
|
8731ef03d9 | ||
|
|
f8a2d05b57 | ||
|
|
1145dc0168 | ||
|
|
12f93df04f | ||
|
|
47a63a73b6 | ||
|
|
d0ebaa7cdd | ||
|
|
e9ea9dea7f | ||
|
|
1feef9cb34 | ||
|
|
5c5fa57234 | ||
|
|
e891b1a831 | ||
|
|
001a5eb2e8 | ||
|
|
6085ed0288 | ||
|
|
57be943f91 | ||
|
|
99cad598a8 | ||
|
|
ef4c926299 | ||
|
|
d1acad0155 | ||
|
|
4a69a00c71 | ||
|
|
a9ad1f1b2a | ||
|
|
53e3d8f55e | ||
|
|
f53b8c822d | ||
|
|
cdd312b0ca | ||
|
|
0cc53735a1 | ||
|
|
c88ecdec16 | ||
|
|
1b4536bf17 | ||
|
|
ab4d6817ab | ||
|
|
717490a1b0 | ||
|
|
8cb1361e1a | ||
|
|
967f4b412c | ||
|
|
8a876d6c78 | ||
|
|
831b12a116 | ||
|
|
d16497b966 | ||
|
|
7ef6dfc4e4 | ||
|
|
e1158a55a3 | ||
|
|
78cc8bea7a | ||
|
|
5914aba7e4 | ||
|
|
40301963e4 | ||
|
|
248d532ae4 | ||
|
|
74bc9506a9 | ||
|
|
41e218b160 | ||
|
|
94221f2894 | ||
|
|
6eaac2dc72 | ||
|
|
92650c84be | ||
|
|
b30cbb600b | ||
|
|
6092fb4618 |
+6
-9
@@ -27,15 +27,13 @@ test_linux_task:
|
||||
- env:
|
||||
CHECK_POSIX_COMPLIANT: true
|
||||
CHECK_REPRODUCIBLE: true
|
||||
OTP_RELEASE: OTP-22.1
|
||||
OTP_RELEASE: OTP-23.0
|
||||
- env:
|
||||
OTP_RELEASE: OTP-22.3
|
||||
- env:
|
||||
OTP_RELEASE: OTP-22.0
|
||||
- env:
|
||||
OTP_RELEASE: OTP-21.3.8
|
||||
- env:
|
||||
OTP_RELEASE: OTP-21.2
|
||||
- env:
|
||||
OTP_RELEASE: OTP-21.1
|
||||
- env:
|
||||
OTP_RELEASE: OTP-21.0
|
||||
|
||||
@@ -110,7 +108,7 @@ test_windows_task:
|
||||
image: fertapric/elixir-ci:otp-win64-${OTP_RELEASE}
|
||||
os_version: ${OS_VERSION}
|
||||
cpu: 4
|
||||
memory: 3840Mi
|
||||
memory: 6GB
|
||||
|
||||
install_script:
|
||||
- rmdir /s /q .git
|
||||
@@ -130,11 +128,11 @@ test_windows_task:
|
||||
test_freebsd_task:
|
||||
<<: *DEFAULT_TEST_SETTINGS
|
||||
|
||||
name: FreeBSD 12.0
|
||||
name: FreeBSD 12.1
|
||||
alias: FreeBSD Stable
|
||||
|
||||
freebsd_instance:
|
||||
image_family: freebsd-12-0
|
||||
image_family: freebsd-12-1
|
||||
cpu: 8
|
||||
memory: 7424Mi
|
||||
|
||||
@@ -143,7 +141,6 @@ test_freebsd_task:
|
||||
LC_ALL: en_US.UTF-8
|
||||
|
||||
install_script:
|
||||
- sudo pkg update
|
||||
- pkg install -y erlang git gmake
|
||||
- rm -rf .git
|
||||
- gmake compile
|
||||
|
||||
@@ -155,6 +155,58 @@ assert %{"status" => 200, "body" => %{"key" => "foo"}} = json_payload
|
||||
|
||||
Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the `"body"` did not have value of `"foo"`. In previous Elixir versions, if the assertion failed, Elixir would print the right side and let you up to your own devices to figure out what went wrong. In Elixir v1.10, we diff the data structure against the pattern so you can see exactly which parts of the data matched the pattern and which ones did not. Note ExUnit already performed diffing when comparing data types, this new version adds diffing when matching data against a pattern.
|
||||
|
||||
## v1.10.4 (2020-07-04)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Fix a bug where custom types were printed as built-in types
|
||||
* [Kernel] Don't add compile-time dependency on `defdelegate`
|
||||
* [Kernel] Add line numbers to warnings on deprecated imports
|
||||
* [Kernel] Report the correct line number when raising inside a macro
|
||||
* [Task] Include callers in translated Logger metadata for Task
|
||||
* [Task] Fix Task PID and caller in Task Supervisor reports
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Formatter] Avoid crashes when diffing guards when the pattern does not match
|
||||
* [ExUnit.Formatter] Also blame exceptions that come from linked and trapped exits
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Do not crash when printing a type that cannot be code formatted
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix app.start] Fix reading `.app` file located in archives (`.ez` files)
|
||||
* [mix local.hex] Provide more guidance when Hex can't be installed
|
||||
* [mix release] Properly encode config in releases
|
||||
|
||||
## v1.10.3 (2020-04-25)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Return `[{mod, bin}]` from `Code.compile_file/2`, `Code.require_file/2`, `Code.load_file/2`
|
||||
* [Code] Make sure the formatter respects newlines before and after module attributes
|
||||
* [Kernel.ParallelCompiler] Fix a bug where the parallel compiler would raise in long compilation cycles
|
||||
* [Kernel.ParallelCompiler] Fix a bug where the parallel compiler would raise if some of the modules being compiled referred to a module that has been loaded directly to memory
|
||||
* [Module] Fix accidental breaking change where bodiless clauses had their body value on `@on_definition` callbacks set to an empty list instead of `nil`
|
||||
* [String] Undeprecate `String.normalize/2` normalize and fix infinite loop caused by certain invalid strings
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertions] Fix pattern matching diff when matching on pinned variables
|
||||
* [ExUnit.Assertions] Fix pattern matching diff when matching variable struct names
|
||||
* [ExUnit.Assertions] Fix pattern matching diff when matching on the binary concat operator (`<>`) and the left side is not a literal string
|
||||
* [ExUnit.Assertions] Fix pattern matching diff when matching on pseudo-vars (`__MODULE__`, `__DIR__`, etc)
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix release] Respect the `:path` option when creating a `:tar` file for releases
|
||||
|
||||
## v1.10.2 (2020-02-26)
|
||||
|
||||
### 1. Bug fixes
|
||||
@@ -282,6 +334,10 @@ Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the
|
||||
* [URI] Preserve slashes in URIs without authority
|
||||
* [URI] Require a nil or an absolute path on URIs with host or authority
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertions] Fix `assert_receive` and `assert match?` to behave consistently compared `receive` and `match?` when given an invalid macro
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Exit IEx session if the group leader exits
|
||||
@@ -326,6 +382,7 @@ Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the
|
||||
#### Mix
|
||||
|
||||
* [mix compile.xref] This check has been moved into the compiler and has no effect now
|
||||
* [mix xref] `xref` now only tracks dependencies between modules and files, no longer between functions. See "Compilation tracers" to learn more about how to track this information directly
|
||||
* [mix xref deprecations] This check has been moved into the compiler and has no effect now
|
||||
* [mix xref unreachable] This check has been moved into the compiler and has no effect now
|
||||
|
||||
|
||||
@@ -582,6 +582,10 @@ defmodule DateTime do
|
||||
It will return the integer with the given unit,
|
||||
according to `System.convert_time_unit/3`.
|
||||
|
||||
If you want to get the current time in Unix seconds,
|
||||
do not do `DateTime.utc_now() |> DateTime.to_unix()`.
|
||||
Simply call `System.os_time(:second)` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> 1_464_096_368 |> DateTime.from_unix!() |> DateTime.to_unix()
|
||||
|
||||
+10
-1
@@ -43,6 +43,15 @@ defmodule Code do
|
||||
You can configure your list of tracers via `put_compiler_option/2`. The
|
||||
following events are available to tracers:
|
||||
|
||||
* `:start` - (since v1.11.0) invoked whenever the compiler starts to trace
|
||||
a new lexical context, such as a new file. Keep in mind the compiler runs
|
||||
in parallel, so multiple files may invoke `:start` and run at the same
|
||||
time. The value of the `lexical_tracker` of the macro environment, albeit
|
||||
opaque, can be used to uniquely identify the environment.
|
||||
|
||||
* `:stop` - (since v1.11.0) invoked whenever the compiler stops tracing a
|
||||
new lexical context, such as a new file.
|
||||
|
||||
* `{:import, meta, module, opts}` - traced whenever `module` is imported.
|
||||
`meta` is the import AST metadata and `opts` are the import options.
|
||||
|
||||
@@ -1392,6 +1401,6 @@ defmodule Code do
|
||||
defp verify_loaded(loaded) do
|
||||
maps_binaries = Enum.map(loaded, fn {_module, map, binary} -> {map, binary} end)
|
||||
Module.ParallelChecker.verify(maps_binaries, [])
|
||||
Enum.map(loaded, fn {module, map, _binary} -> {module, map} end)
|
||||
Enum.map(loaded, fn {module, _map, binary} -> {module, binary} end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1914,8 +1914,7 @@ defmodule Code.Formatter do
|
||||
{args_docs, comments?, %{state | comments: comments}}
|
||||
end
|
||||
|
||||
defp each_quoted_to_algebra_with_comments(args, acc, max_line, state, comments?, fun) do
|
||||
[arg | args] = args
|
||||
defp each_quoted_to_algebra_with_comments([arg | args], acc, max_line, state, comments?, fun) do
|
||||
{doc_start, doc_end} = traverse_line(arg, {@max_line, @min_line})
|
||||
|
||||
{acc, comments, comments?} =
|
||||
@@ -1926,7 +1925,7 @@ defmodule Code.Formatter do
|
||||
{acc, comments, comments?} =
|
||||
extract_comments_trailing(doc_start, doc_end, acc, state.comments, comments?)
|
||||
|
||||
acc = [doc_triplet | acc]
|
||||
acc = [adjust_trailing_newlines(doc_triplet, doc_end, comments) | acc]
|
||||
state = %{state | comments: comments}
|
||||
each_quoted_to_algebra_with_comments(args, acc, max_line, state, comments?, fun)
|
||||
end
|
||||
@@ -1957,6 +1956,15 @@ defmodule Code.Formatter do
|
||||
{acc, rest, comments?}
|
||||
end
|
||||
|
||||
# If the document is immediately followed by comment which is followed by newlines,
|
||||
# its newlines wouldn't have considerd the comment, so we need to adjust it.
|
||||
defp adjust_trailing_newlines({doc, next_line, newlines}, doc_end, [{line, _, _} | _])
|
||||
when newlines > 1 and line == doc_end + 1 do
|
||||
{doc, next_line, 1}
|
||||
end
|
||||
|
||||
defp adjust_trailing_newlines(doc_triplet, _, _), do: doc_triplet
|
||||
|
||||
defp traverse_line({expr, meta, args}, {min, max}) do
|
||||
acc =
|
||||
case Keyword.fetch(meta, :line) do
|
||||
|
||||
@@ -217,7 +217,8 @@ defmodule Code.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:user_type, line, name, args}) do
|
||||
typespec_to_quoted({:type, line, name, args})
|
||||
args = for arg <- args, do: typespec_to_quoted(arg)
|
||||
{name, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, line, :tuple, :any}) do
|
||||
|
||||
@@ -350,7 +350,7 @@ defmodule Config.Provider do
|
||||
defp write_config!(config, path) do
|
||||
contents = :io_lib.format("%% coding: utf-8~n~tw.~n", [config])
|
||||
|
||||
case File.write(path, contents, [:utf8]) do
|
||||
case File.write(path, IO.chardata_to_string(contents)) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
|
||||
@@ -640,7 +640,7 @@ defmodule GenServer do
|
||||
This callback is optional.
|
||||
"""
|
||||
@callback terminate(reason, state :: term) :: term
|
||||
when reason: :normal | :shutdown | {:shutdown, term}
|
||||
when reason: :normal | :shutdown | {:shutdown, term} | term
|
||||
|
||||
@doc """
|
||||
Invoked to change the state of the `GenServer` when a different version of a
|
||||
|
||||
@@ -317,7 +317,7 @@ defmodule IO do
|
||||
@spec warn(chardata | String.Chars.t(), Exception.stacktrace()) :: :ok
|
||||
def warn(message, []) do
|
||||
message = [to_chardata(message), ?\n]
|
||||
:elixir_errors.io_warn(nil, nil, message, message)
|
||||
:elixir_errors.io_warn(0, nil, message, message)
|
||||
end
|
||||
|
||||
def warn(message, [{_, _, _, opts} | _] = stacktrace) do
|
||||
@@ -327,7 +327,7 @@ defmodule IO do
|
||||
file = opts[:file]
|
||||
|
||||
:elixir_errors.io_warn(
|
||||
line,
|
||||
line || 0,
|
||||
file && List.to_string(file),
|
||||
message,
|
||||
[message, ?\n, " ", formatted_trace, ?\n]
|
||||
|
||||
@@ -4104,7 +4104,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro def(call, expr \\ []) do
|
||||
defmacro def(call, expr \\ nil) do
|
||||
define(:def, call, expr, __CALLER__)
|
||||
end
|
||||
|
||||
@@ -4134,7 +4134,7 @@ defmodule Kernel do
|
||||
** (UndefinedFunctionError) undefined function Foo.sum/2
|
||||
|
||||
"""
|
||||
defmacro defp(call, expr \\ []) do
|
||||
defmacro defp(call, expr \\ nil) do
|
||||
define(:defp, call, expr, __CALLER__)
|
||||
end
|
||||
|
||||
@@ -4162,7 +4162,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro defmacro(call, expr \\ []) do
|
||||
defmacro defmacro(call, expr \\ nil) do
|
||||
define(:defmacro, call, expr, __CALLER__)
|
||||
end
|
||||
|
||||
@@ -4178,7 +4178,7 @@ defmodule Kernel do
|
||||
naming and default arguments.
|
||||
|
||||
"""
|
||||
defmacro defmacrop(call, expr \\ []) do
|
||||
defmacro defmacrop(call, expr \\ nil) do
|
||||
define(:defmacrop, call, expr, __CALLER__)
|
||||
end
|
||||
|
||||
@@ -4673,7 +4673,7 @@ defmodule Kernel do
|
||||
macro_definition =
|
||||
case impls do
|
||||
[] ->
|
||||
define(kind, call, [], env)
|
||||
define(kind, call, nil, env)
|
||||
|
||||
[guard] ->
|
||||
quoted =
|
||||
@@ -4881,6 +4881,18 @@ defmodule Kernel do
|
||||
defmacro defdelegate(funs, opts) do
|
||||
funs = Macro.escape(funs, unquote: true)
|
||||
|
||||
# don't add compile-time dependency on :to
|
||||
opts =
|
||||
with true <- is_list(opts),
|
||||
{:ok, target} <- Keyword.fetch(opts, :to),
|
||||
{:__aliases__, _, _} <- target do
|
||||
target = Macro.expand(target, %{__CALLER__ | function: {:__info__, 1}})
|
||||
Keyword.replace!(opts, :to, target)
|
||||
else
|
||||
_ ->
|
||||
opts
|
||||
end
|
||||
|
||||
quote bind_quoted: [funs: funs, opts: opts] do
|
||||
target =
|
||||
Keyword.get(opts, :to) || raise ArgumentError, "expected to: to be given as argument"
|
||||
|
||||
@@ -239,7 +239,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
defp checker_runtime_modules(modules) do
|
||||
for module <- modules,
|
||||
path = :code.which(module),
|
||||
is_list(path) do
|
||||
is_list(path) and path != [] do
|
||||
{module, File.read!(path)}
|
||||
end
|
||||
end
|
||||
@@ -427,10 +427,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
# The goal of this function is to find leaves in the dependency graph,
|
||||
# i.e. to find code that depends on code that we know is not being defined.
|
||||
# Note that not all files have been compile yet, so they may not be in waiting.
|
||||
defp without_definition(waiting, files) do
|
||||
nillify_empty(
|
||||
for {pid, _, _, _} <- files,
|
||||
{_, ^pid, ref, on, _, _} = List.keyfind(waiting, pid, 1),
|
||||
{_, ^pid, ref, on, _, _} <- List.wrap(List.keyfind(waiting, pid, 1)),
|
||||
not Enum.any?(waiting, fn {_, _, _, _, defining, _} -> on in defining end),
|
||||
do: {ref, :not_found}
|
||||
)
|
||||
@@ -651,6 +652,12 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp get_line(file, _reason, [{_, _, _, [file: 'expanding macro']}, {_, _, _, info} | _]) do
|
||||
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
|
||||
Keyword.get(info, :line)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_line(file, _reason, [{_, _, _, info} | _]) do
|
||||
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
|
||||
Keyword.get(info, :line)
|
||||
|
||||
@@ -14,7 +14,8 @@ defmodule Kernel.SpecialForms do
|
||||
forms used to define tuple and binary data structures respectively.
|
||||
|
||||
This module also documents macros that return information about Elixir's
|
||||
compilation environment, such as (`__ENV__/0`, `__MODULE__/0`, `__DIR__/0` and `__CALLER__/0`).
|
||||
compilation environment, such as (`__ENV__/0`, `__MODULE__/0`, `__DIR__/0`,
|
||||
`__STACKTRACE__/0`, and `__CALLER__/0`).
|
||||
|
||||
Additionally, it documents two special forms, `__block__/1` and
|
||||
`__aliases__/1`, which are not intended to be called directly by the
|
||||
|
||||
@@ -231,7 +231,7 @@ defmodule Module.Checker do
|
||||
|
||||
defp warn(meta, state, warning) do
|
||||
{fun, arity} = state.function
|
||||
location = {state.file, meta[:line], {state.module, fun, arity}}
|
||||
location = {state.file, meta[:line] || 0, {state.module, fun, arity}}
|
||||
%{state | warnings: [{__MODULE__, warning, location} | state.warnings]}
|
||||
end
|
||||
|
||||
@@ -306,7 +306,7 @@ defmodule Module.Checker do
|
||||
|
||||
defp format_location({file, line, {module, fun, arity}}) do
|
||||
file = Path.relative_to_cwd(file)
|
||||
line = if line, do: [Integer.to_string(line), ": "], else: []
|
||||
line = if line > 0, do: [Integer.to_string(line), ": "], else: []
|
||||
mfa = Exception.format_mfa(module, fun, arity)
|
||||
[" ", file, ?:, line, mfa, ?\n]
|
||||
end
|
||||
|
||||
@@ -132,9 +132,9 @@ defmodule Port do
|
||||
While we encourage graceful termination by detecting if stdin/stdout has been
|
||||
closed, we do not always have control over how third-party software terminates.
|
||||
In those cases, you can wrap the application in a script that checks for stdin.
|
||||
Here is such script in `sh`:
|
||||
Here is such script that has been verified to work on bash shells:
|
||||
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Start the program in the background
|
||||
exec "$@" &
|
||||
|
||||
@@ -204,7 +204,7 @@ defmodule Registry do
|
||||
@type guards :: [guard]
|
||||
|
||||
@typedoc "A pattern used to representing the output format part of a match spec"
|
||||
@type body :: [atom | tuple]
|
||||
@type body :: [term]
|
||||
|
||||
@typedoc "A full match spec used when selecting objects in the registry"
|
||||
@type spec :: [{match_pattern, guards, body}]
|
||||
|
||||
+39
-11
@@ -455,13 +455,13 @@ defmodule String do
|
||||
For example, take the grapheme "é" which is made of the characters
|
||||
"e" and the acute accent. The following will split the string into two parts:
|
||||
|
||||
iex> String.split(:unicode.characters_to_nfd_binary("é"), "e")
|
||||
iex> String.split(String.normalize("é", :nfd), "e")
|
||||
["", "́"]
|
||||
|
||||
However, if "é" is represented by the single character "e with acute"
|
||||
accent, then it will split the string into just one part:
|
||||
|
||||
iex> String.split(:unicode.characters_to_nfc_binary("é"), "e")
|
||||
iex> String.split(String.normalize("é", :nfc), "e")
|
||||
["é"]
|
||||
|
||||
"""
|
||||
@@ -650,9 +650,9 @@ defmodule String do
|
||||
|
||||
String.normalize(string1, :nfd) == String.normalize(string2, :nfd)
|
||||
|
||||
Therefore, if you plan to compare multiple strings, multiple times
|
||||
in a row, you may normalize them upfront and compare them directly
|
||||
to avoid multiple normalization passes.
|
||||
If you plan to compare multiple strings, multiple times in a row, you
|
||||
may normalize them upfront and compare them directly to avoid multiple
|
||||
normalization passes.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -674,21 +674,49 @@ defmodule String do
|
||||
normalize(string1, :nfd) == normalize(string2, :nfd)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Use :unicode.characters_to_nfc_binary/1 or :unicode.characters_to_nfd_binary/1 instead"
|
||||
@doc """
|
||||
Converts all characters in `string` to Unicode normalization
|
||||
form identified by `form`.
|
||||
|
||||
Invalid Unicode codepoints are skipped and the remaining of
|
||||
the string is converted. If you want the algorith to stop
|
||||
and return on invalid codepoint, use `:unicode.characters_to_nfd_binary/1`
|
||||
and `:unicode.characters_to_nfc_binary/1` instead.
|
||||
|
||||
## Forms
|
||||
|
||||
The supported forms are:
|
||||
|
||||
* `:nfd` - Normalization Form Canonical Decomposition.
|
||||
Characters are decomposed by canonical equivalence, and
|
||||
multiple combining characters are arranged in a specific
|
||||
order.
|
||||
|
||||
* `:nfc` - Normalization Form Canonical Composition.
|
||||
Characters are decomposed and then recomposed by canonical equivalence.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.normalize("yêṩ", :nfd)
|
||||
"yêṩ"
|
||||
|
||||
iex> String.normalize("leña", :nfc)
|
||||
"leña"
|
||||
|
||||
"""
|
||||
def normalize(string, form)
|
||||
|
||||
def normalize(string, :nfd) do
|
||||
case :unicode.characters_to_nfd_binary(string) do
|
||||
string when is_binary(string) -> string
|
||||
{:error, bad, rest} -> bad <> normalize(rest, :nfd)
|
||||
{:error, good, <<head, rest::binary>>} -> good <> <<head>> <> normalize(rest, :nfd)
|
||||
end
|
||||
end
|
||||
|
||||
def normalize(string, :nfc) do
|
||||
case :unicode.characters_to_nfc_binary(string) do
|
||||
string when is_binary(string) -> string
|
||||
{:error, bad, rest} -> bad <> normalize(rest, :nfc)
|
||||
{:error, good, <<head, rest::binary>>} -> good <> <<head>> <> normalize(rest, :nfc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2174,13 +2202,13 @@ defmodule String do
|
||||
For example, take the grapheme "é" which is made of the characters
|
||||
"e" and the acute accent. The following returns `true`:
|
||||
|
||||
iex> String.contains?(:unicode.characters_to_nfd_binary("é"), "e")
|
||||
iex> String.contains?(String.normalize("é", :nfd), "e")
|
||||
true
|
||||
|
||||
However, if "é" is represented by the single character "e with acute"
|
||||
accent, then it will return `false`:
|
||||
|
||||
iex> String.contains?(:unicode.characters_to_nfc_binary("é"), "e")
|
||||
iex> String.contains?(String.normalize("é", :nfc), "e")
|
||||
false
|
||||
|
||||
"""
|
||||
|
||||
@@ -102,8 +102,8 @@ defmodule Task.Supervised do
|
||||
%{
|
||||
label: {Task.Supervisor, :terminating},
|
||||
report: %{
|
||||
name: get_from(owner),
|
||||
starter: self(),
|
||||
name: self(),
|
||||
starter: get_from(owner),
|
||||
function: fun,
|
||||
args: args,
|
||||
reason: {log_value(kind, value), __STACKTRACE__}
|
||||
@@ -112,7 +112,8 @@ defmodule Task.Supervised do
|
||||
%{
|
||||
domain: [:otp, :elixir],
|
||||
error_logger: %{tag: :error_msg},
|
||||
report_cb: &__MODULE__.format_report/1
|
||||
report_cb: &__MODULE__.format_report/1,
|
||||
callers: Process.get(:"$callers")
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Elixir version | Supported Erlang/OTP versions
|
||||
1.7 | 19 - 22
|
||||
1.8 | 20 - 22
|
||||
1.9 | 20 - 22
|
||||
1.10 | 21 - 22
|
||||
1.10 | 21 - 22 (and Erlang/OTP 23 from v1.10.3)
|
||||
|
||||
While Elixir often adds compatibility to new Erlang/OTP versions on released branches, such as support for Erlang/OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang/OTP release.
|
||||
|
||||
@@ -81,7 +81,6 @@ Version | Deprecated feature | Replaced by (ava
|
||||
[v1.10] | Passing non-chardata to `Logger.log/2` | Explicitly convert to string with `to_string/1` (v1.0)
|
||||
[v1.10] | `:compile_time_purge_level` in `Logger` app environment | `:compile_time_purge_matching` in `Logger` app environment (v1.7)
|
||||
[v1.10] | `Supervisor.Spec.supervise/2` | The new child specs outlined in `Supervisor` (v1.5)
|
||||
[v1.10] | `String.normalize/2` | `:unicode.characters_to_nfc_binary/1` or `:unicode.characters_to_nfd_binary/1` (Erlang/OTP 20)
|
||||
[v1.10] | `:simple_one_for_one` strategy in `Supervisor` | `DynamicSupervisor` (v1.6)
|
||||
[v1.10] | `:restart` and `:shutdown` in `Task.Supervisor.start_link/1` | `:restart` and `:shutdown` in `Task.Supervisor.start_child/3` (v1.6)
|
||||
[v1.9] | Enumerable keys in `Map.drop/2`, `Map.split/2`, and `Map.take/2` | Call `Enum.to_list/1` on the second argument before hand (v1.0)
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
'MACRO-@'(Caller, Tree) ->
|
||||
unless_loaded('MACRO-@', [Caller, Tree], fun() -> nil end).
|
||||
|
||||
'MACRO-def'(Caller, Call) -> 'MACRO-def'(Caller, Call, []).
|
||||
'MACRO-def'(Caller, Call) -> 'MACRO-def'(Caller, Call, nil).
|
||||
'MACRO-def'(Caller, Call, Expr) -> define(Caller, def, Call, Expr).
|
||||
'MACRO-defp'(Caller, Call, Expr) -> define(Caller, defp, Call, Expr).
|
||||
|
||||
'MACRO-defmacro'(Caller, Call) -> 'MACRO-defmacro'(Caller, Call, []).
|
||||
'MACRO-defmacro'(Caller, Call) -> 'MACRO-defmacro'(Caller, Call, nil).
|
||||
'MACRO-defmacro'(Caller, Call, Expr) -> define(Caller, defmacro, Call, Expr).
|
||||
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ run_with_location_change(File, #{file := File} = E, Callback) ->
|
||||
run_with_location_change(File, E, Callback) ->
|
||||
elixir_lexical:with_file(File, E, Callback).
|
||||
|
||||
def_to_clauses(_Kind, Meta, Args, [], [], E) ->
|
||||
def_to_clauses(_Kind, Meta, Args, [], nil, E) ->
|
||||
check_args_for_function_head(Meta, Args, E),
|
||||
[];
|
||||
def_to_clauses(_Kind, Meta, Args, Guards, [{do, Body}], _E) ->
|
||||
|
||||
@@ -92,7 +92,7 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
{ok, Receiver, Quoted} ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ok, Receiver, NewName, NewArgs} ->
|
||||
elixir_expand:expand({{'.', [], [Receiver, NewName]}, Meta, NewArgs}, E);
|
||||
elixir_expand:expand({{'.', Meta, [Receiver, NewName]}, Meta, NewArgs}, E);
|
||||
error ->
|
||||
Callback()
|
||||
end.
|
||||
@@ -328,9 +328,10 @@ get_macros(Receiver, false) ->
|
||||
end;
|
||||
|
||||
get_macros(Receiver, true) ->
|
||||
case is_ensure_loaded(Receiver) of
|
||||
true -> get_info(Receiver, macros);
|
||||
false -> []
|
||||
try
|
||||
Receiver:'__info__'(macros)
|
||||
catch
|
||||
error:_ -> []
|
||||
end.
|
||||
|
||||
%% Kernel deprecations are inlined.
|
||||
|
||||
@@ -48,8 +48,10 @@ compile(Forms, File, Opts) when is_list(Forms), is_list(Opts), is_binary(File) -
|
||||
case erl_to_core(Forms, Opts) of
|
||||
{ok, CoreForms, CoreWarnings} ->
|
||||
format_warnings(Opts, CoreWarnings),
|
||||
CompileOpts = [?NO_SPAWN_COMPILER_PROCESS, from_core, no_core_prepare,
|
||||
no_auto_import, return, {source, Source} | Opts],
|
||||
|
||||
case compile:noenv_forms(CoreForms, [?NO_SPAWN_COMPILER_PROCESS, from_core, no_auto_import, return, {source, Source} | Opts]) of
|
||||
case compile:noenv_forms(CoreForms, CompileOpts) of
|
||||
{ok, Module, Binary, Warnings} when is_binary(Binary) ->
|
||||
format_warnings(Opts, Warnings),
|
||||
{Module, Binary};
|
||||
|
||||
@@ -10,6 +10,7 @@ run(#{tracers := Tracers} = E, ExecutionCallback, AfterExecutionCallback) ->
|
||||
false ->
|
||||
{ok, Pid} = ?tracker:start_link(),
|
||||
LexEnv = E#{lexical_tracker := Pid, tracers := [?MODULE | Tracers]},
|
||||
elixir_env:trace(start, LexEnv),
|
||||
|
||||
try ExecutionCallback(LexEnv) of
|
||||
Res ->
|
||||
@@ -18,6 +19,7 @@ run(#{tracers := Tracers} = E, ExecutionCallback, AfterExecutionCallback) ->
|
||||
AfterExecutionCallback(LexEnv),
|
||||
Res
|
||||
after
|
||||
elixir_env:trace(stop, LexEnv),
|
||||
unlink(Pid),
|
||||
?tracker:stop(Pid)
|
||||
end;
|
||||
|
||||
@@ -102,6 +102,108 @@ defmodule Code.Formatter.CommentsTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "modules attributes" do
|
||||
test "with comments around" do
|
||||
assert_same """
|
||||
defmodule Sample do
|
||||
# Comment 0
|
||||
@moduledoc false
|
||||
# Comment 1
|
||||
|
||||
# Comment 2
|
||||
@attr1 1
|
||||
# Comment 3
|
||||
|
||||
# Comment 4
|
||||
@doc "Doc"
|
||||
# Comment 5
|
||||
@attr2 2
|
||||
# Comment 6
|
||||
def sample, do: :sample
|
||||
end
|
||||
"""
|
||||
end
|
||||
|
||||
test "with comments only after" do
|
||||
assert_same """
|
||||
@moduledoc false
|
||||
# Comment 1
|
||||
|
||||
@attr 1
|
||||
"""
|
||||
end
|
||||
|
||||
test "with too many new lines" do
|
||||
bad = """
|
||||
defmodule Sample do
|
||||
|
||||
# Comment 0
|
||||
|
||||
|
||||
@moduledoc false
|
||||
|
||||
|
||||
# Comment 1
|
||||
|
||||
|
||||
# Comment 2
|
||||
|
||||
|
||||
@attr1 1
|
||||
|
||||
|
||||
# Comment 3
|
||||
|
||||
|
||||
# Comment 4
|
||||
|
||||
|
||||
@doc "Doc"
|
||||
|
||||
|
||||
# Comment 5
|
||||
|
||||
|
||||
@attr2 2
|
||||
|
||||
|
||||
# Comment 6
|
||||
|
||||
|
||||
def sample, do: :sample
|
||||
end
|
||||
"""
|
||||
|
||||
assert_format bad, """
|
||||
defmodule Sample do
|
||||
# Comment 0
|
||||
|
||||
@moduledoc false
|
||||
|
||||
# Comment 1
|
||||
|
||||
# Comment 2
|
||||
|
||||
@attr1 1
|
||||
|
||||
# Comment 3
|
||||
|
||||
# Comment 4
|
||||
|
||||
@doc "Doc"
|
||||
|
||||
# Comment 5
|
||||
|
||||
@attr2 2
|
||||
|
||||
# Comment 6
|
||||
|
||||
def sample, do: :sample
|
||||
end
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
describe "interpolation" do
|
||||
test "with comment outside before, during and after" do
|
||||
assert_same ~S"""
|
||||
|
||||
@@ -103,12 +103,18 @@ defmodule CodeTest do
|
||||
test "compile_file/1" do
|
||||
assert Code.compile_file(fixture_path("code_sample.exs")) == []
|
||||
refute fixture_path("code_sample.exs") in Code.required_files()
|
||||
|
||||
assert [{CompileSample, binary}] = Code.compile_file(fixture_path("compile_sample.ex"))
|
||||
assert is_binary(binary)
|
||||
after
|
||||
:code.purge(CompileSample)
|
||||
:code.delete(CompileSample)
|
||||
end
|
||||
|
||||
test "compile_file/1 also emits checker warnings" do
|
||||
output =
|
||||
ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
Code.compile_file(PathHelpers.fixture_path("checker_warning.exs"))
|
||||
Code.compile_file(fixture_path("checker_warning.exs"))
|
||||
end)
|
||||
|
||||
assert output =~ "incompatible types"
|
||||
@@ -122,8 +128,13 @@ defmodule CodeTest do
|
||||
Code.unrequire_files([fixture_path("code_sample.exs")])
|
||||
refute fixture_path("code_sample.exs") in Code.required_files()
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) != nil
|
||||
|
||||
assert [{CompileSample, binary}] = Code.require_file(fixture_path("compile_sample.ex"))
|
||||
assert is_binary(binary)
|
||||
after
|
||||
Code.unrequire_files([fixture_path("code_sample.exs")])
|
||||
Code.unrequire_files([fixture_path("code_sample.exs"), fixture_path("compile_sample.ex")])
|
||||
:code.purge(CompileSample)
|
||||
:code.delete(CompileSample)
|
||||
end
|
||||
|
||||
describe "string_to_quoted/2" do
|
||||
|
||||
@@ -83,11 +83,11 @@ defmodule Config.ProviderTest do
|
||||
assert config[@config_app] == [config_providers_booted: {:booted, nil}]
|
||||
end
|
||||
|
||||
@tag sys_config: [my_app: [encoding: {:"£", "£", '£'}]]
|
||||
@tag sys_config: [my_app: [encoding: {:time_μs, :"£", "£", '£'}]]
|
||||
test "writes sys_config with encoding" do
|
||||
init_and_assert_boot()
|
||||
config = consult(@sys_config)
|
||||
assert config[:my_app][:encoding] == {:"£", "£", '£'}
|
||||
assert config[:my_app][:encoding] == {:time_μs, :"£", "£", '£'}
|
||||
end
|
||||
|
||||
@tag sys_config: [my_app: [key: :old_value, sys_key: :sys_value, extra_config: :old_value]]
|
||||
@@ -185,6 +185,6 @@ defmodule Config.ProviderTest do
|
||||
end
|
||||
|
||||
defp write_sys_config!(data) do
|
||||
File.write!(@sys_config, :io_lib.format("~tw.~n", [data]), [:utf8])
|
||||
File.write!(@sys_config, IO.chardata_to_string(:io_lib.format("~tw.~n", [data])))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -654,8 +654,8 @@ defmodule Kernel.ExpansionTest do
|
||||
expand(quote(do: [1] ++ 2 ++ [3] = [1, 2, 3]))
|
||||
end
|
||||
|
||||
assert {:=, _, [-1, {{:., [], [:erlang, :-]}, _, [1]}]} = expand(quote(do: -1 = -1))
|
||||
assert {:=, _, [1, {{:., [], [:erlang, :+]}, _, [1]}]} = expand(quote(do: +1 = +1))
|
||||
assert {:=, _, [-1, {{:., _, [:erlang, :-]}, _, [1]}]} = expand(quote(do: -1 = -1))
|
||||
assert {:=, _, [1, {{:., _, [:erlang, :+]}, _, [1]}]} = expand(quote(do: +1 = +1))
|
||||
|
||||
assert {:=, _, [[{:|, _, [1, [{:|, _, [2, 3]}]]}], [1, 2, 3]]} =
|
||||
expand(quote(do: [1] ++ [2] ++ 3 = [1, 2, 3]))
|
||||
|
||||
@@ -126,4 +126,21 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
refute Foo.Bar in runtime
|
||||
refute Foo.Bar in compile
|
||||
end
|
||||
|
||||
test "defdelegate with literal does not add compile dependency" do
|
||||
{{compile, _structs, _runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.Defdelegate do
|
||||
defdelegate a, to: A
|
||||
|
||||
opts = [to: B]
|
||||
defdelegate b, opts
|
||||
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
refute A in compile
|
||||
assert B in compile
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,6 +20,15 @@ defmodule Kernel.TracersTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "traces start and stop" do
|
||||
compile_string("""
|
||||
Foo
|
||||
""")
|
||||
|
||||
assert_receive {:start, %{lexical_tracker: pid}} when is_pid(pid)
|
||||
assert_receive {:stop, %{lexical_tracker: pid}} when is_pid(pid)
|
||||
end
|
||||
|
||||
test "traces alias references" do
|
||||
compile_string("""
|
||||
Foo
|
||||
|
||||
@@ -639,6 +639,31 @@ defmodule Module.CheckerTest do
|
||||
assert_warnings(files, warning)
|
||||
end
|
||||
|
||||
test "reports imported functions" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
defmodule A do
|
||||
@deprecated "oops"
|
||||
def a, do: :ok
|
||||
end
|
||||
""",
|
||||
"b.ex" => """
|
||||
defmodule B do
|
||||
import A
|
||||
def b, do: a()
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
warning = """
|
||||
warning: A.a/0 is deprecated. oops
|
||||
b.ex:3: B.b/0
|
||||
|
||||
"""
|
||||
|
||||
assert_warnings(files, warning)
|
||||
end
|
||||
|
||||
test "reports structs" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
|
||||
@@ -91,8 +91,10 @@ defmodule RegexTest do
|
||||
assert <<0xA0::utf8>> =~ ~r/[[:space:]]/u
|
||||
assert <<0xA0::utf8>> =~ ~r/\s/u
|
||||
|
||||
# Erlang/OTP 23 raises badarg on invalid UTF-8.
|
||||
# Earlier versions simply would not match.
|
||||
assert catch_error(if <<?<, 255, ?>>> =~ ~r/<.>/u, do: flunk("failed"), else: raise("failed"))
|
||||
assert <<?<, 255, ?>>> =~ ~r/<.>/
|
||||
refute <<?<, 255, ?>>> =~ ~r/<.>/u
|
||||
end
|
||||
|
||||
test "ungreedy" do
|
||||
@@ -349,6 +351,6 @@ defmodule RegexTest do
|
||||
end
|
||||
|
||||
defp matches_escaped?(string, match) do
|
||||
Regex.match?(~r/#{Regex.escape(string)}/simxu, match)
|
||||
Regex.match?(~r/#{Regex.escape(string)}/simx, match)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -749,4 +749,79 @@ defmodule StringTest do
|
||||
assert String.myers_difference("abc", "aйbc") == [eq: "a", ins: "й", eq: "bc"]
|
||||
assert String.myers_difference("aйbc", "abc") == [eq: "a", del: "й", eq: "bc"]
|
||||
end
|
||||
|
||||
test "normalize/2" do
|
||||
assert String.normalize("ŝ", :nfd) == "ŝ"
|
||||
assert String.normalize("ḇravô", :nfd) == "ḇravô"
|
||||
assert String.normalize("ṩierra", :nfd) == "ṩierra"
|
||||
assert String.normalize("뢴", :nfd) == "뢴"
|
||||
assert String.normalize("êchǭ", :nfc) == "êchǭ"
|
||||
assert String.normalize("거̄", :nfc) == "거̄"
|
||||
assert String.normalize("뢴", :nfc) == "뢴"
|
||||
|
||||
## Error cases
|
||||
assert String.normalize(<<15, 216>>, :nfc) == <<15, 216>>
|
||||
assert String.normalize(<<15, 216>>, :nfd) == <<15, 216>>
|
||||
assert String.normalize(<<216, 15>>, :nfc) == <<216, 15>>
|
||||
assert String.normalize(<<216, 15>>, :nfd) == <<216, 15>>
|
||||
|
||||
## Cases from NormalizationTest.txt
|
||||
|
||||
# 05B8 05B9 05B1 0591 05C3 05B0 05AC 059F
|
||||
# 05B1 05B8 05B9 0591 05C3 05B0 05AC 059F
|
||||
# HEBREW POINT QAMATS, HEBREW POINT HOLAM, HEBREW POINT HATAF SEGOL,
|
||||
# HEBREW ACCENT ETNAHTA, HEBREW PUNCTUATION SOF PASUQ, HEBREW POINT SHEVA,
|
||||
# HEBREW ACCENT ILUY, HEBREW ACCENT QARNEY PARA
|
||||
assert String.normalize("ֱָֹ֑׃ְ֬֟", :nfc) == "ֱָֹ֑׃ְ֬֟"
|
||||
|
||||
# 095D (exclusion list)
|
||||
# 0922 093C
|
||||
# DEVANAGARI LETTER RHA
|
||||
assert String.normalize("ढ़", :nfc) == "ढ़"
|
||||
|
||||
# 0061 0315 0300 05AE 0340 0062
|
||||
# 00E0 05AE 0300 0315 0062
|
||||
# LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT,
|
||||
# HEBREW ACCENT ZINOR, COMBINING GRAVE TONE MARK, LATIN SMALL LETTER B
|
||||
assert String.normalize("à֮̀̕b", :nfc) == "à֮̀̕b"
|
||||
|
||||
# 0344
|
||||
# 0308 0301
|
||||
# COMBINING GREEK DIALYTIKA TONOS
|
||||
assert String.normalize("\u0344", :nfc) == "\u0308\u0301"
|
||||
|
||||
# 115B9 0334 115AF
|
||||
# 115B9 0334 115AF
|
||||
# SIDDHAM VOWEL SIGN AI, COMBINING TILDE OVERLAY, SIDDHAM VOWEL SIGN AA
|
||||
assert String.normalize("𑖹̴𑖯", :nfc) == "𑖹̴𑖯"
|
||||
# HEBREW ACCENT ETNAHTA, HEBREW PUNCTUATION SOF PASUQ, HEBREW POINT SHEVA,
|
||||
# HEBREW ACCENT ILUY, HEBREW ACCENT QARNEY PARA
|
||||
assert String.normalize("ֱָֹ֑׃ְ֬֟", :nfc) == "ֱָֹ֑׃ְ֬֟"
|
||||
|
||||
# 095D (exclusion list)
|
||||
# HEBREW ACCENT ETNAHTA, HEBREW PUNCTUATION SOF PASUQ, HEBREW POINT SHEVA,
|
||||
# HEBREW ACCENT ILUY, HEBREW ACCENT QARNEY PARA
|
||||
assert String.normalize("ֱָֹ֑׃ְ֬֟", :nfc) == "ֱָֹ֑׃ְ֬֟"
|
||||
|
||||
# 095D (exclusion list)
|
||||
# 0922 093C
|
||||
# DEVANAGARI LETTER RHA
|
||||
assert String.normalize("ढ़", :nfc) == "ढ़"
|
||||
|
||||
# 0061 0315 0300 05AE 0340 0062
|
||||
# 00E0 05AE 0300 0315 0062
|
||||
# LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT,
|
||||
# HEBREW ACCENT ZINOR, COMBINING GRAVE TONE MARK, LATIN SMALL LETTER B
|
||||
assert String.normalize("à֮̀̕b", :nfc) == "à֮̀̕b"
|
||||
|
||||
# 0344
|
||||
# 0308 0301
|
||||
# COMBINING GREEK DIALYTIKA TONOS
|
||||
assert String.normalize("\u0344", :nfc) == "\u0308\u0301"
|
||||
|
||||
# 115B9 0334 115AF
|
||||
# 115B9 0334 115AF
|
||||
# SIDDHAM VOWEL SIGN AI, COMBINING TILDE OVERLAY, SIDDHAM VOWEL SIGN AA
|
||||
assert String.normalize("𑖹̴𑖯", :nfc) == "𑖹̴𑖯"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1183,6 +1183,12 @@ defmodule TypespecTest do
|
||||
quoted =
|
||||
Enum.sort([
|
||||
quote(do: @spec(foo() :: integer())),
|
||||
quote(do: @spec(foo() :: union())),
|
||||
quote(do: @spec(foo() :: union(integer()))),
|
||||
quote(do: @spec(foo() :: truly_union())),
|
||||
quote(do: @spec(foo(union()) :: union())),
|
||||
quote(do: @spec(foo(union(integer())) :: union(integer()))),
|
||||
quote(do: @spec(foo(truly_union()) :: truly_union())),
|
||||
quote(do: @spec(foo(atom()) :: integer() | [{}])),
|
||||
quote(do: @spec(foo(arg) :: integer() when [arg: integer()])),
|
||||
quote(do: @spec(foo(arg) :: arg when [arg: var])),
|
||||
@@ -1191,6 +1197,10 @@ defmodule TypespecTest do
|
||||
|
||||
bytecode =
|
||||
test_module do
|
||||
@type union :: any()
|
||||
@type union(t) :: t
|
||||
@type truly_union :: list | map | union
|
||||
|
||||
def foo(), do: 1
|
||||
def foo(arg), do: arg
|
||||
Module.eval_quoted(__MODULE__, quote(do: (unquote_splicing(quoted))))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ExUnit.start [seed: 0]
|
||||
ExUnit.start(seed: 0)
|
||||
|
||||
defmodule TestOneOfEach do
|
||||
@moduledoc """
|
||||
@@ -10,8 +10,8 @@ defmodule TestOneOfEach do
|
||||
@one 1
|
||||
@two 2
|
||||
|
||||
@long_data_1 [field1: "one", field2: {:two1, :two2}, field3: 'three', field4: [1, 2, 3, 4]]
|
||||
@long_data_2 [field1: "one", field2: {:two1, :two3}, field3: 'three', field4: [1, 2, 3, 4]]
|
||||
@long_data_1 [field1: "one", field2: {:two1, :two2}, field3: 'three', field4: [1, 2, 3, 4]]
|
||||
@long_data_2 [field1: "one", field2: {:two1, :two3}, field3: 'three', field4: [1, 2, 3, 4]]
|
||||
|
||||
setup do
|
||||
{:ok, user_id: 1, post_id: 2, many_ids: Enum.to_list(1..50)}
|
||||
@@ -62,10 +62,10 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "12. assert that a message is received within a timeout" do
|
||||
send self(), {:ok, 1}
|
||||
send self(), :message_in_my_inbox
|
||||
send self(), {:ok, 2}
|
||||
send self(), :another_message
|
||||
send(self(), {:ok, 1})
|
||||
send(self(), :message_in_my_inbox)
|
||||
send(self(), {:ok, 2})
|
||||
send(self(), :another_message)
|
||||
assert_receive :no_message_after_timeout
|
||||
end
|
||||
|
||||
@@ -75,14 +75,14 @@ defmodule TestOneOfEach do
|
||||
|
||||
test "14. assert an exception with a given message is raised" do
|
||||
assert_raise(SomeException, "some message", fn ->
|
||||
raise "other exception"
|
||||
end)
|
||||
raise "other exception"
|
||||
end)
|
||||
end
|
||||
|
||||
test "15. assert an exception with a given message is raised, but the message is wrong" do
|
||||
assert_raise(RuntimeError, "some message", fn ->
|
||||
raise "other error"
|
||||
end)
|
||||
raise "other error"
|
||||
end)
|
||||
end
|
||||
|
||||
test "16. assert an exception is raised" do
|
||||
@@ -90,7 +90,7 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "17. assert two values are within some delta" do
|
||||
assert_in_delta 3.1415926, 22.0/7, 0.001
|
||||
assert_in_delta 3.1415926, 22.0 / 7, 0.001
|
||||
end
|
||||
|
||||
test "18. refute a value with a message" do
|
||||
@@ -98,12 +98,12 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "19. refute a message is received within a timeout" do
|
||||
send self(), {:hello, "Dave"}
|
||||
send(self(), {:hello, "Dave"})
|
||||
refute_receive {:hello, _}, 1000
|
||||
end
|
||||
|
||||
test "20. refute a message is ready to be received" do
|
||||
send self(), :hello_again
|
||||
send(self(), :hello_again)
|
||||
refute_received :hello_again
|
||||
end
|
||||
|
||||
@@ -116,7 +116,7 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "23. flunk" do
|
||||
flunk "we failed. totally"
|
||||
flunk("we failed. totally")
|
||||
end
|
||||
|
||||
test "24. exception raised while running test" do
|
||||
@@ -124,7 +124,8 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
test "25. error due to exit" do
|
||||
spawn_link fn -> raise "oops" end
|
||||
spawn_link(fn -> raise "oops" end)
|
||||
|
||||
receive do
|
||||
end
|
||||
end
|
||||
@@ -133,15 +134,17 @@ defmodule TestOneOfEach do
|
||||
error1 =
|
||||
try do
|
||||
assert [@one] = [@two]
|
||||
rescue e in ExUnit.AssertionError ->
|
||||
{:error, e, System.stacktrace}
|
||||
rescue
|
||||
e in ExUnit.AssertionError ->
|
||||
{:error, e, __STACKTRACE__}
|
||||
end
|
||||
|
||||
error2 =
|
||||
try do
|
||||
assert @one * 4 > @two * 3
|
||||
rescue e in ExUnit.AssertionError ->
|
||||
{:error, e, System.stacktrace}
|
||||
rescue
|
||||
e in ExUnit.AssertionError ->
|
||||
{:error, e, __STACKTRACE__}
|
||||
end
|
||||
|
||||
raise ExUnit.MultiError, errors: [error1, error2]
|
||||
@@ -150,8 +153,8 @@ defmodule TestOneOfEach do
|
||||
@tag capture_log: true
|
||||
test "27. log capturing" do
|
||||
require Logger
|
||||
Logger.debug "this will be logged"
|
||||
flunk "oops"
|
||||
Logger.debug("this will be logged")
|
||||
flunk("oops")
|
||||
end
|
||||
|
||||
test "28. function clause error" do
|
||||
@@ -162,6 +165,28 @@ defmodule TestOneOfEach do
|
||||
assert some_vars(1 + 2, 3 + 4)
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "30. linked assertion error" do
|
||||
Task.async(fn -> assert 1 == 2 end) |> Task.await()
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "31. linked function clause error" do
|
||||
Task.async(fn -> Access.fetch(:foo, :bar) end) |> Task.await()
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "32. trapped assertion error" do
|
||||
Process.flag(:trap_exit, true)
|
||||
Task.async(fn -> assert 1 == 2 end) |> Task.await()
|
||||
end
|
||||
|
||||
@tag :capture_log
|
||||
test "33. trapped function clause error" do
|
||||
Process.flag(:trap_exit, true)
|
||||
Task.async(fn -> Access.fetch(:foo, :bar) end) |> Task.await()
|
||||
end
|
||||
|
||||
defp some_vars(_a, _b) do
|
||||
false
|
||||
end
|
||||
@@ -171,6 +196,6 @@ defmodule TestOneOfEach do
|
||||
end
|
||||
|
||||
defp ignite(val) do
|
||||
1/val
|
||||
1 / val
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,7 +57,8 @@ defmodule ExUnit.Diff do
|
||||
end
|
||||
|
||||
defp diff_quoted({name, _, context} = left, right, env)
|
||||
when is_atom(name) and is_atom(context) do
|
||||
when is_atom(name) and is_atom(context) and
|
||||
name not in [:__MODULE__, :__DIR__, :__STACKTRACE__, :__ENV__, :__CALLER__] do
|
||||
diff_var(left, right, env)
|
||||
end
|
||||
|
||||
@@ -81,20 +82,16 @@ defmodule ExUnit.Diff do
|
||||
diff_tuple(Tuple.to_list(left), Tuple.to_list(right), env)
|
||||
end
|
||||
|
||||
defp diff_quoted({:%, _, [struct, {:%{}, _, kw}]}, %{} = right, env)
|
||||
when is_atom(struct) and is_list(kw) do
|
||||
diff_quoted_struct([__struct__: struct] ++ kw, struct, right, env)
|
||||
defp diff_quoted({:%, _, [struct, {:%{}, _, kw}]}, %{} = right, env) when is_list(kw) do
|
||||
diff_quoted_struct([__struct__: struct] ++ kw, right, env)
|
||||
end
|
||||
|
||||
defp diff_quoted({:%{}, _, items}, %{} = right, env) when is_list(items) do
|
||||
if struct = items[:__struct__] do
|
||||
diff_quoted_struct(items, struct, right, env)
|
||||
else
|
||||
diff_map(items, right, nil, maybe_struct(right), env)
|
||||
end
|
||||
defp diff_quoted({:%{}, _, kw}, %{} = right, env) when is_list(kw) do
|
||||
diff_quoted_struct(kw, right, env)
|
||||
end
|
||||
|
||||
defp diff_quoted({:<>, _, _} = left, right, env) when is_binary(right) do
|
||||
defp diff_quoted({:<>, _, [literal, _]} = left, right, env)
|
||||
when is_binary(literal) and is_binary(right) do
|
||||
diff_string_concat(left, right, env)
|
||||
end
|
||||
|
||||
@@ -183,13 +180,18 @@ defmodule ExUnit.Diff do
|
||||
defp diff_guard({:when, _, [expression, clause]}, right, env) do
|
||||
{diff_expression, post_env} = diff_quoted(expression, right, env)
|
||||
|
||||
bindings = Map.merge(post_env.pins, post_env.current_vars)
|
||||
{diff_clause, clause_equivalent?} = diff_guard_clause(clause, Map.to_list(bindings))
|
||||
{guard_clause, guard_equivalent?} =
|
||||
if diff_expression.equivalent? do
|
||||
bindings = Map.merge(post_env.pins, post_env.current_vars)
|
||||
diff_guard_clause(clause, Map.to_list(bindings))
|
||||
else
|
||||
{clause, false}
|
||||
end
|
||||
|
||||
diff = %__MODULE__{
|
||||
diff_expression
|
||||
| left: {:when, [], [diff_expression.left, diff_clause]},
|
||||
equivalent?: diff_expression.equivalent? and clause_equivalent?
|
||||
| left: {:when, [], [diff_expression.left, guard_clause]},
|
||||
equivalent?: guard_equivalent?
|
||||
}
|
||||
|
||||
{diff, post_env}
|
||||
@@ -225,7 +227,7 @@ defmodule ExUnit.Diff do
|
||||
defp diff_pin({:^, _, [var]} = pin, right, %{pins: pins} = env) do
|
||||
identifier = var_context(var)
|
||||
%{^identifier => pin_value} = pins
|
||||
{diff, post_env} = diff(pin_value, right, env)
|
||||
{diff, post_env} = diff_value(pin_value, right, env)
|
||||
|
||||
diff_left = update_diff_meta(pin, not diff.equivalent?)
|
||||
{%{diff | left: diff_left}, post_env}
|
||||
@@ -626,8 +628,9 @@ defmodule ExUnit.Diff do
|
||||
|
||||
# Structs
|
||||
|
||||
defp diff_quoted_struct(kw, struct1, right, env) do
|
||||
left = load_struct(struct1)
|
||||
defp diff_quoted_struct(kw, right, env) do
|
||||
struct1 = kw[:__struct__]
|
||||
left = load_struct(kw[:__struct__])
|
||||
|
||||
if left && Enum.all?(kw, fn {k, _} -> Map.has_key?(left, k) end) do
|
||||
if Macro.quoted_literal?(kw) do
|
||||
@@ -674,7 +677,8 @@ defmodule ExUnit.Diff do
|
||||
end
|
||||
|
||||
defp load_struct(struct) do
|
||||
if Code.ensure_loaded?(struct) and function_exported?(struct, :__struct__, 0) do
|
||||
if is_atom(struct) and struct != nil and
|
||||
Code.ensure_loaded?(struct) and function_exported?(struct, :__struct__, 0) do
|
||||
struct.__struct__
|
||||
end
|
||||
end
|
||||
@@ -761,11 +765,10 @@ defmodule ExUnit.Diff do
|
||||
String.bag_distance(left, right) > 0.4
|
||||
end
|
||||
|
||||
defp parse_string({:<>, _, [literal, rest]}) do
|
||||
defp parse_string({:<>, _, [literal, rest]}) when is_binary(literal) do
|
||||
{parsed, quoted, indexes, parsed_length} = parse_string(rest)
|
||||
literal_length = String.length(literal)
|
||||
length = literal_length + parsed_length
|
||||
|
||||
{literal <> parsed, quoted, [literal_length | indexes], length}
|
||||
end
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ defmodule ExUnit.Formatter do
|
||||
|
||||
@counter_padding " "
|
||||
@mailbox_label_padding @counter_padding <> " "
|
||||
@formatter_exceptions [ExUnit.AssertionError, FunctionClauseError]
|
||||
@no_value ExUnit.AssertionError.no_value()
|
||||
|
||||
@doc """
|
||||
@@ -136,10 +137,10 @@ defmodule ExUnit.Formatter do
|
||||
|
||||
@doc false
|
||||
def format_assertion_error(%ExUnit.AssertionError{} = struct) do
|
||||
format_assertion_error(%{}, struct, [], :infinity, fn _, msg -> msg end, "")
|
||||
format_exception(%{}, struct, [], :infinity, fn _, msg -> msg end, "") |> elem(0)
|
||||
end
|
||||
|
||||
defp format_assertion_error(test, struct, stack, width, formatter, counter_padding) do
|
||||
defp format_exception(test, %ExUnit.AssertionError{} = struct, stack, width, formatter, pad) do
|
||||
label_padding_size = if has_value?(struct.right), do: 7, else: 6
|
||||
padding_size = label_padding_size + byte_size(@counter_padding)
|
||||
|
||||
@@ -148,16 +149,27 @@ defmodule ExUnit.Formatter do
|
||||
do: &pad_multiline(&1, padding_size),
|
||||
else: &code_multiline(&1, padding_size)
|
||||
|
||||
[
|
||||
note: if_value(struct.message, &format_message(&1, formatter)),
|
||||
doctest: if_value(struct.doctest, &pad_multiline(&1, 2 + byte_size(@counter_padding))),
|
||||
code: if_value(struct.expr, code_multiline),
|
||||
code: unless_value(struct.expr, fn -> get_code(test, stack) || @no_value end),
|
||||
arguments: if_value(struct.args, &format_args(&1, width))
|
||||
]
|
||||
|> Kernel.++(format_context(struct, formatter, padding_size, width))
|
||||
|> format_meta(formatter, counter_padding, label_padding_size)
|
||||
|> IO.iodata_to_binary()
|
||||
formatted =
|
||||
[
|
||||
note: if_value(struct.message, &format_message(&1, formatter)),
|
||||
doctest: if_value(struct.doctest, &pad_multiline(&1, 2 + byte_size(@counter_padding))),
|
||||
code: if_value(struct.expr, code_multiline),
|
||||
code: unless_value(struct.expr, fn -> get_code(test, stack) || @no_value end),
|
||||
arguments: if_value(struct.args, &format_args(&1, width))
|
||||
]
|
||||
|> Kernel.++(format_context(struct, formatter, padding_size, width))
|
||||
|> format_meta(formatter, pad, label_padding_size)
|
||||
|> IO.iodata_to_binary()
|
||||
|
||||
{formatted, stack}
|
||||
end
|
||||
|
||||
defp format_exception(test, %FunctionClauseError{} = struct, stack, _width, formatter, _pad) do
|
||||
{blamed, stack} = Exception.blame(:error, struct, stack)
|
||||
banner = Exception.format_banner(:error, struct)
|
||||
blamed = FunctionClauseError.blame(blamed, &inspect/1, &blame_match(&1, &2, formatter))
|
||||
message = error_info(banner, formatter) <> "\n" <> pad(String.trim_leading(blamed, "\n"))
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -179,30 +191,48 @@ defmodule ExUnit.Formatter do
|
||||
end)
|
||||
end
|
||||
|
||||
defp format_kind_reason(
|
||||
test,
|
||||
:error,
|
||||
%ExUnit.AssertionError{} = struct,
|
||||
stack,
|
||||
width,
|
||||
formatter
|
||||
) do
|
||||
{format_assertion_error(test, struct, stack, width, formatter, @counter_padding), stack}
|
||||
defp format_kind_reason(test, :error, %mod{} = struct, stack, width, formatter)
|
||||
when mod in @formatter_exceptions do
|
||||
format_exception(test, struct, stack, width, formatter, @counter_padding)
|
||||
end
|
||||
|
||||
defp format_kind_reason(test, :error, %FunctionClauseError{} = struct, stack, _width, formatter) do
|
||||
{blamed, stack} = Exception.blame(:error, struct, stack)
|
||||
banner = Exception.format_banner(:error, struct)
|
||||
blamed = FunctionClauseError.blame(blamed, &inspect/1, &blame_match(&1, &2, formatter))
|
||||
message = error_info(banner, formatter) <> "\n" <> pad(String.trim_leading(blamed, "\n"))
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
defp format_kind_reason(test, kind, reason, stack, width, formatter) do
|
||||
case linked_or_trapped_exit(kind, reason) do
|
||||
{header, wrapped_reason, wrapped_stack} ->
|
||||
struct = Exception.normalize(:error, wrapped_reason, wrapped_stack)
|
||||
|
||||
{formatted_reason, _} =
|
||||
format_exception(test, struct, wrapped_stack, width, formatter, @counter_padding)
|
||||
|
||||
formatted_stack = format_stacktrace(wrapped_stack, test.module, test.name, formatter)
|
||||
{error_info(header, formatter) <> pad(formatted_reason <> formatted_stack), stack}
|
||||
|
||||
:error ->
|
||||
{reason, stack} = Exception.blame(kind, reason, stack)
|
||||
message = error_info(Exception.format_banner(kind, reason), formatter)
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
end
|
||||
end
|
||||
|
||||
defp format_kind_reason(test, kind, reason, stack, _width, formatter) do
|
||||
message = error_info(Exception.format_banner(kind, reason), formatter)
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
defp linked_or_trapped_exit({:EXIT, pid}, {reason, [_ | _] = stack})
|
||||
when :erlang.map_get(:__struct__, reason) in @formatter_exceptions
|
||||
when reason == :function_clause do
|
||||
{"** (EXIT from #{inspect(pid)}) an exception was raised:\n", reason, stack}
|
||||
end
|
||||
|
||||
defp linked_or_trapped_exit(:exit, {{reason, [_ | _] = stack}, {mod, fun, args}})
|
||||
when is_atom(mod) and is_atom(fun) and is_list(args) and
|
||||
:erlang.map_get(:__struct__, reason) in @formatter_exceptions
|
||||
when is_atom(mod) and is_atom(fun) and is_list(args) and reason == :function_clause do
|
||||
{
|
||||
"** (exit) exited in: #{Exception.format_mfa(mod, fun, args)}\n ** (EXIT) an exception was raised:",
|
||||
reason,
|
||||
stack
|
||||
}
|
||||
end
|
||||
|
||||
defp linked_or_trapped_exit(_kind, _reason), do: :error
|
||||
|
||||
defp format_code(test, stack, formatter) do
|
||||
if snippet = get_code(test, stack) do
|
||||
" " <> formatter.(:extra_info, "code: ") <> snippet <> "\n"
|
||||
|
||||
@@ -141,6 +141,11 @@ defmodule ExUnit.DiffTest do
|
||||
refute_diff(^b = :a, "-^b-", "+:a+", pins)
|
||||
end
|
||||
|
||||
test "pseudo vars" do
|
||||
assert_diff(__MODULE__ = ExUnit.DiffTest, [])
|
||||
refute_diff(__MODULE__ = SomethingElse, "-__MODULE__-", "+SomethingElse+")
|
||||
end
|
||||
|
||||
test "integers" do
|
||||
assert_diff(123 = 123, [])
|
||||
assert_diff(-123 = -123, [])
|
||||
@@ -471,6 +476,34 @@ defmodule ExUnit.DiffTest do
|
||||
refute_diff(%{a: 1} = :a, "-%{a: 1}-", "+:a+")
|
||||
end
|
||||
|
||||
test "maps as pinned map value" do
|
||||
user = %{"id" => 13, "name" => "john"}
|
||||
|
||||
notification = %{
|
||||
"user" => user,
|
||||
"subtitle" => "foo"
|
||||
}
|
||||
|
||||
assert_diff(
|
||||
%{
|
||||
"user" => ^user,
|
||||
"subtitle" => "foo"
|
||||
} = notification,
|
||||
[],
|
||||
%{{:user, nil} => user}
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
%{
|
||||
"user" => ^user,
|
||||
"subtitle" => "bar"
|
||||
} = notification,
|
||||
~s|%{"subtitle" => "-bar-", "user" => ^user}|,
|
||||
~s|%{"subtitle" => "+foo+", "user" => %{"id" => 13, "name" => "john"}}|,
|
||||
%{{:user, nil} => user}
|
||||
)
|
||||
end
|
||||
|
||||
test "maps outside match context" do
|
||||
assert_diff(%{a: 1} == %{a: 1}, [])
|
||||
assert_diff(%{a: 1, b: 2} == %{a: 1, b: 2}, [])
|
||||
@@ -594,7 +627,7 @@ defmodule ExUnit.DiffTest do
|
||||
)
|
||||
end
|
||||
|
||||
test "structs with inspect difference" do
|
||||
test "structs with same inspect but different" do
|
||||
refute_diff(
|
||||
%Opaque{data: 1} = %Opaque{data: 2},
|
||||
"%ExUnit.DiffTest.Opaque{data: -1-}",
|
||||
@@ -608,7 +641,7 @@ defmodule ExUnit.DiffTest do
|
||||
)
|
||||
end
|
||||
|
||||
test "structs without inspect difference outside match" do
|
||||
test "structs with same inspect but different outside match" do
|
||||
refute_diff(
|
||||
%Opaque{data: 1} == %Opaque{data: 2},
|
||||
"%ExUnit.DiffTest.Opaque{data: -1-}",
|
||||
@@ -630,6 +663,66 @@ defmodule ExUnit.DiffTest do
|
||||
)
|
||||
end
|
||||
|
||||
test "structs with matched type" do
|
||||
pins = %{{:type, nil} => User, {:age, nil} => 33}
|
||||
|
||||
# pin on __struct__
|
||||
assert_diff(
|
||||
%{__struct__: ^type, age: ^age, name: "john"} = %User{name: "john", age: 33},
|
||||
[],
|
||||
pins
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
%{__struct__: ^type, age: ^age, name: "john"} = %User{name: "jane", age: 33},
|
||||
"%{__struct__: ^type, age: ^age, name: \"j-oh-n\"}",
|
||||
"%ExUnit.DiffTest.User{age: 33, name: \"j+a+n+e+\"}",
|
||||
pins
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
%{__struct__: ^type, age: ^age, name: "john"} = %User{name: "john", age: 35},
|
||||
"%{__struct__: ^type, age: -^age-, name: \"john\"}",
|
||||
"%ExUnit.DiffTest.User{age: 3+5+, name: \"john\"}",
|
||||
pins
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
%{__struct__: ^type, age: ^age, name: "john"} = ~D[2020-01-01],
|
||||
"%{__struct__: -^type-, -age: ^age-, -name: \"john\"-}",
|
||||
"%+Date+{calendar: Calendar.ISO, day: 1, month: 1, year: 2020}",
|
||||
pins
|
||||
)
|
||||
|
||||
# pin on %
|
||||
assert_diff(
|
||||
%^type{age: ^age, name: "john"} = %User{name: "john", age: 33},
|
||||
[],
|
||||
pins
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
%^type{age: ^age, name: "john"} = %User{name: "jane", age: 33},
|
||||
"%{__struct__: ^type, age: ^age, name: \"j-oh-n\"}",
|
||||
"%ExUnit.DiffTest.User{age: 33, name: \"j+a+n+e+\"}",
|
||||
pins
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
%^type{age: ^age, name: "john"} = %User{name: "john", age: 35},
|
||||
"%{__struct__: ^type, age: -^age-, name: \"john\"}",
|
||||
"%ExUnit.DiffTest.User{age: 3+5+, name: \"john\"}",
|
||||
pins
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
%^type{age: ^age, name: "john"} = ~D[2020-01-01],
|
||||
"%{__struct__: -^type-, -age: ^age-, -name: \"john\"-}",
|
||||
"%+Date+{calendar: Calendar.ISO, day: 1, month: 1, year: 2020}",
|
||||
pins
|
||||
)
|
||||
end
|
||||
|
||||
test "invalid structs" do
|
||||
refute_diff(
|
||||
%{__struct__: Unknown} = %{},
|
||||
@@ -721,7 +814,7 @@ defmodule ExUnit.DiffTest do
|
||||
)
|
||||
end
|
||||
|
||||
test "concat operator" do
|
||||
test "concat binaries" do
|
||||
assert_diff("fox hops" <> " over the dog" = "fox hops over the dog", [])
|
||||
assert_diff("fox hops " <> "over " <> "the dog" = "fox hops over the dog", [])
|
||||
|
||||
@@ -755,6 +848,10 @@ defmodule ExUnit.DiffTest do
|
||||
~s/"fox hops over the dog"/
|
||||
)
|
||||
|
||||
refute_diff("fox" <> " hops" = :a, ~s/-"fox" <> " hops"-/, "+:a+")
|
||||
end
|
||||
|
||||
test "concat binaries with pin" do
|
||||
pins = %{{:x, nil} => " over the dog"}
|
||||
|
||||
assert_diff("fox hops" <> x = "fox hops over the dog", x: " over the dog")
|
||||
@@ -773,8 +870,16 @@ defmodule ExUnit.DiffTest do
|
||||
~s/"fox hops over +t+he dog"/,
|
||||
pins
|
||||
)
|
||||
end
|
||||
|
||||
refute_diff("fox" <> " hops" = :a, ~s/-"fox" <> " hops"-/, "+:a+")
|
||||
test "concat binaries with specifiers" do
|
||||
input = "foobar"
|
||||
|
||||
refute_diff(
|
||||
<<trap::binary-size(3)>> <> "baz" = input,
|
||||
"-<<trap::binary-size(3)>> <> \"baz\"-",
|
||||
"+\"foobar\"+"
|
||||
)
|
||||
end
|
||||
|
||||
test "underscore" do
|
||||
@@ -809,6 +914,7 @@ defmodule ExUnit.DiffTest do
|
||||
refute_diff((x when x == 1 or x == 2) = 0, "x when -x == 1- or -x == 2-", "0")
|
||||
refute_diff((x when x == 1 when x == 2) = 0, "x when -x == 1- when -x == 2-", "0")
|
||||
refute_diff((x when x in [1, 2]) = 0, "x when -x in [1, 2]-", "0")
|
||||
refute_diff(({:ok, x} when x == 1) = :error, "-{:ok, x}- when x == 1", "+:error+")
|
||||
end
|
||||
|
||||
test "charlists" do
|
||||
|
||||
@@ -69,6 +69,61 @@ defmodule ExUnit.FormatterTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test exits with function clause mfa" do
|
||||
{error, stack} =
|
||||
try do
|
||||
Access.fetch(:foo, :bar)
|
||||
catch
|
||||
:error, error -> {error, __STACKTRACE__}
|
||||
end
|
||||
|
||||
failure = [{:exit, {{error, stack}, {:mod, :fun, []}}, []}]
|
||||
|
||||
assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~
|
||||
"""
|
||||
1) world (Hello)
|
||||
test/ex_unit/formatter_test.exs:1
|
||||
** (exit) exited in: :mod.fun()
|
||||
** (EXIT) an exception was raised:
|
||||
** (FunctionClauseError) no function clause matching in Access.fetch/2
|
||||
|
||||
The following arguments were given to Access.fetch/2:
|
||||
|
||||
# 1
|
||||
:foo
|
||||
|
||||
# 2
|
||||
:bar
|
||||
|
||||
Attempted function clauses (showing 5 out of 5):
|
||||
|
||||
def fetch(%module{} = container, key)
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test exits with assertion mfa" do
|
||||
{error, stack} =
|
||||
try do
|
||||
assert 1 == 2
|
||||
rescue
|
||||
error -> {error, __STACKTRACE__}
|
||||
end
|
||||
|
||||
failure = [{:exit, {{error, stack}, {:mod, :fun, []}}, []}]
|
||||
|
||||
assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~
|
||||
"""
|
||||
1) world (Hello)
|
||||
test/ex_unit/formatter_test.exs:1
|
||||
** (exit) exited in: :mod.fun()
|
||||
** (EXIT) an exception was raised:
|
||||
Assertion with == failed
|
||||
code: assert 1 == 2
|
||||
left: 1
|
||||
right: 2
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test throws" do
|
||||
failure = [{:throw, 1, []}]
|
||||
|
||||
@@ -89,6 +144,61 @@ defmodule ExUnit.FormatterTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test EXITs with function clause errors" do
|
||||
{error, stack} =
|
||||
try do
|
||||
Access.fetch(:foo, :bar)
|
||||
catch
|
||||
:error, error -> {error, __STACKTRACE__}
|
||||
end
|
||||
|
||||
failure = [{{:EXIT, self()}, {error, stack}, []}]
|
||||
|
||||
assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~
|
||||
"""
|
||||
1) world (Hello)
|
||||
test/ex_unit/formatter_test.exs:1
|
||||
** (EXIT from #{inspect(self())}) an exception was raised:
|
||||
|
||||
** (FunctionClauseError) no function clause matching in Access.fetch/2
|
||||
|
||||
The following arguments were given to Access.fetch/2:
|
||||
|
||||
# 1
|
||||
:foo
|
||||
|
||||
# 2
|
||||
:bar
|
||||
|
||||
Attempted function clauses (showing 5 out of 5):
|
||||
|
||||
def fetch(%module{} = container, key)
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test EXITs with assertion errors" do
|
||||
{error, stack} =
|
||||
try do
|
||||
assert 1 == 2
|
||||
rescue
|
||||
error -> {error, __STACKTRACE__}
|
||||
end
|
||||
|
||||
failure = [{{:EXIT, self()}, {error, stack}, []}]
|
||||
|
||||
assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~
|
||||
"""
|
||||
1) world (Hello)
|
||||
test/ex_unit/formatter_test.exs:1
|
||||
** (EXIT from #{inspect(self())}) an exception was raised:
|
||||
|
||||
Assertion with == failed
|
||||
code: assert 1 == 2
|
||||
left: 1
|
||||
right: 2
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats test errors with test_location_relative_path" do
|
||||
Application.put_env(:ex_unit, :test_location_relative_path, "apps/sample")
|
||||
failure = [{:error, catch_error(raise "oops"), []}]
|
||||
|
||||
@@ -735,9 +735,15 @@ defmodule IEx.Introspection do
|
||||
## Helpers
|
||||
|
||||
defp format_typespec(definition, kind, nesting) do
|
||||
"@#{kind} #{Macro.to_string(definition)}"
|
||||
|> Code.format_string!(line_length: IEx.width() - 2 * nesting)
|
||||
|> IO.iodata_to_binary()
|
||||
string = "@#{kind} #{Macro.to_string(definition)}"
|
||||
|
||||
try do
|
||||
string
|
||||
|> Code.format_string!(line_length: IEx.width() - 2 * nesting)
|
||||
|> IO.iodata_to_binary()
|
||||
rescue
|
||||
_ -> string
|
||||
end
|
||||
|> color_prefix_with_line()
|
||||
|> indent(nesting)
|
||||
end
|
||||
|
||||
@@ -382,7 +382,7 @@ defmodule IEx.HelpersTest do
|
||||
"* def left == right\n\n @spec term() == term() :: boolean()\n\nguard: true\n\nReturns `true` if the two terms are equal.\n\n"
|
||||
|
||||
def_h =
|
||||
"* defmacro def(call, expr \\\\ [])\n\nDefines a public function with the given name and body."
|
||||
"* defmacro def(call, expr \\\\ nil)\n\nDefines a public function with the given name and body."
|
||||
|
||||
assert capture_io(fn -> h(IEx.Helpers.pwd() / 0) end) =~ pwd_h
|
||||
assert capture_io(fn -> h(IEx.Helpers.c() / 2) end) =~ c_h
|
||||
@@ -853,6 +853,7 @@ defmodule IEx.HelpersTest do
|
||||
defmodule TypeSample do
|
||||
@typedoc "An ID with description."
|
||||
@type id_with_desc :: {number, String.t}
|
||||
@type unquote(:"?")() :: :question_mark
|
||||
end
|
||||
"""
|
||||
|
||||
@@ -872,6 +873,11 @@ defmodule IEx.HelpersTest do
|
||||
|
||||
An ID with description.
|
||||
"""
|
||||
|
||||
assert capture_io(fn -> t(TypeSample."?"()) end) == """
|
||||
@type ?() :: :question_mark
|
||||
|
||||
"""
|
||||
end)
|
||||
after
|
||||
cleanup_modules([TypeSample])
|
||||
|
||||
@@ -467,8 +467,9 @@ defmodule Logger do
|
||||
|
||||
Erlang/OTP handlers must be listed under your own application:
|
||||
|
||||
config :my_app, :logger,
|
||||
[:handler, :name_of_the_handler, ACustomHandler, configuration = %{}]
|
||||
config :my_app, :logger, [
|
||||
{:handler, :name_of_the_handler, ACustomHandler, configuration = %{}}
|
||||
]
|
||||
|
||||
And then explicitly attached in your `c:Application.start/2` callback:
|
||||
|
||||
|
||||
@@ -306,13 +306,14 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
test "translates Task crashes" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :task, [self()])
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, :go)
|
||||
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
|
||||
end) =~ ~r"""
|
||||
\[error\] Task #PID<\d+\.\d+\.\d+> started from #PID<\d+\.\d+\.\d+> terminating
|
||||
\[error\] Task #{inspect(pid)} started from #{inspect(self())} terminating
|
||||
\*\* \(RuntimeError\) oops
|
||||
.*
|
||||
Function: &Logger.TranslatorTest.task\/1
|
||||
@@ -324,6 +325,7 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert {%RuntimeError{message: "oops"}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%RuntimeError{message: "oops"}, [_ | _]} = process_metadata[:crash_reason]
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
refute Keyword.has_key?(task_metadata, :initial_call)
|
||||
assert process_metadata[:initial_call] == {Logger.TranslatorTest, :task, 1}
|
||||
@@ -332,6 +334,7 @@ defmodule Logger.TranslatorTest do
|
||||
test "translates Task async_stream crashes with neighbour" do
|
||||
fun = fn -> Task.async_stream([:oops], :erlang, :error, []) |> Enum.to_list() end
|
||||
{:ok, pid} = Task.start(__MODULE__, :task, [self(), fun])
|
||||
parent = self()
|
||||
|
||||
assert capture_log(:debug, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
@@ -345,12 +348,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [pid, parent] == task_metadata[:callers]
|
||||
|
||||
assert {:oops, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%ErlangError{original: :oops}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task undef module crash" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(:module_does_not_exist, :undef, [])
|
||||
ref = Process.monitor(pid)
|
||||
@@ -365,12 +371,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%UndefinedFunctionError{function: :undef}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%UndefinedFunctionError{function: :undef}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task undef function crash" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(__MODULE__, :undef, [])
|
||||
ref = Process.monitor(pid)
|
||||
@@ -385,12 +394,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%UndefinedFunctionError{function: :undef}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%UndefinedFunctionError{function: :undef}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task raising ErlangError" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
exception =
|
||||
try do
|
||||
@@ -413,12 +425,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%ErlangError{original: :foo}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%ErlangError{original: :foo}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task raising Erlang badarg error" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(:erlang, :error, [:badarg])
|
||||
ref = Process.monitor(pid)
|
||||
@@ -433,12 +448,15 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%ArgumentError{message: "argument error"}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%ArgumentError{message: "argument error"}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates Task exiting abnormally" do
|
||||
parent = self()
|
||||
|
||||
assert capture_log(fn ->
|
||||
{:ok, pid} = Task.start(:erlang, :exit, [:abnormal])
|
||||
ref = Process.monitor(pid)
|
||||
@@ -453,6 +471,7 @@ defmodule Logger.TranslatorTest do
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert_receive {:error, _pid, {Logger, ["Process " | _], _ts, process_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {:abnormal, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {:abnormal, [_ | _]} = process_metadata[:crash_reason]
|
||||
@@ -625,8 +644,9 @@ defmodule Logger.TranslatorTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "translates :proc_lib crashes on debug" do
|
||||
test "translates :proc_lib+Task crashes on debug" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :task, [self()])
|
||||
parent = self()
|
||||
|
||||
assert capture_log(:debug, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
@@ -652,13 +672,15 @@ defmodule Logger.TranslatorTest do
|
||||
assert process_metadata[:pid] == task_metadata[:pid]
|
||||
assert is_list(process_metadata[:callers])
|
||||
assert is_list(process_metadata[:ancestors])
|
||||
assert [parent] == task_metadata[:callers]
|
||||
|
||||
assert {%RuntimeError{message: "oops"}, [_ | _]} = task_metadata[:crash_reason]
|
||||
assert {%RuntimeError{message: "oops"}, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates :proc_lib crashes with neighbour on debug" do
|
||||
test "translates :proc_lib+Task crashes with neighbour on debug" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :sub_task, [self()])
|
||||
parent = self()
|
||||
|
||||
assert capture_log(:debug, fn ->
|
||||
ref = Process.monitor(pid)
|
||||
@@ -677,6 +699,9 @@ defmodule Logger.TranslatorTest do
|
||||
Current Stacktrace:
|
||||
(lib/logger/)?test/logger/translator_test.exs:\d+: Logger.TranslatorTest.sleep/1
|
||||
"""
|
||||
|
||||
assert_receive {:error, _pid, {Logger, ["Task " <> _ | _], _ts, task_metadata}}
|
||||
assert [parent] == task_metadata[:callers]
|
||||
end
|
||||
|
||||
test "translates Supervisor progress" do
|
||||
|
||||
@@ -66,7 +66,7 @@ defmodule Mix.Dep do
|
||||
scm: Mix.SCM.t(),
|
||||
app: atom,
|
||||
requirement: String.t() | Regex.t() | nil,
|
||||
status: atom,
|
||||
status: {:ok, String.t() | nil} | atom | tuple,
|
||||
opts: keyword,
|
||||
top_level: boolean,
|
||||
manager: :rebar | :rebar3 | :mix | :make | nil,
|
||||
|
||||
@@ -65,6 +65,7 @@ defmodule Mix.Dep.Fetcher do
|
||||
end
|
||||
|
||||
if new do
|
||||
File.touch!(Path.join(opts[:dest], ".fetch"))
|
||||
dep = put_in(dep.opts[:lock], new)
|
||||
{dep, [app | acc], Map.put(lock, app, new)}
|
||||
else
|
||||
@@ -92,22 +93,18 @@ defmodule Mix.Dep.Fetcher do
|
||||
# dependency is missing, it could directly affect one of the
|
||||
# dependencies we are trying to compile, causing the whole thing
|
||||
# to fail.
|
||||
#
|
||||
# If there is any other dependency that is not ok, we include
|
||||
# it for compilation too, this is our best to try to solve the
|
||||
# maximum we can at each deps.get and deps.update.
|
||||
deps =
|
||||
parent_deps =
|
||||
if Enum.all?(all_deps, &available?/1) do
|
||||
Enum.uniq_by(with_depending(deps, all_deps), & &1.app)
|
||||
else
|
||||
deps
|
||||
[]
|
||||
end
|
||||
|
||||
# Merge the new lock on top of the old to guarantee we don't
|
||||
# leave out things that could not be fetched and save it.
|
||||
lock = Map.merge(old_lock, new_lock)
|
||||
Mix.Dep.Lock.write(lock)
|
||||
mark_as_fetched(deps)
|
||||
mark_as_fetched(parent_deps)
|
||||
|
||||
# See if any of the deps diverged and abort.
|
||||
show_diverged!(Enum.filter(all_deps, &Mix.Dep.diverged?/1))
|
||||
|
||||
@@ -161,14 +161,29 @@ defmodule Mix.Local do
|
||||
contents
|
||||
|
||||
{:remote, message} ->
|
||||
Mix.raise("""
|
||||
#{message}
|
||||
Mix.raise(
|
||||
"""
|
||||
#{message}
|
||||
|
||||
Could not install #{name} because Mix could not download metadata at #{path}.
|
||||
""")
|
||||
Could not install #{name} because Mix could not download metadata at #{path}.
|
||||
""" <> suggestions(name)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp suggestions("Hex") do
|
||||
"""
|
||||
|
||||
Alternatively, you can compile and install Hex directly with this command:
|
||||
|
||||
mix archive.install github hexpm/hex branch latest
|
||||
"""
|
||||
end
|
||||
|
||||
defp suggestions(_) do
|
||||
""
|
||||
end
|
||||
|
||||
defp parse_csv(body) do
|
||||
body
|
||||
|> :binary.split("\n", [:global, :trim])
|
||||
|
||||
@@ -402,7 +402,7 @@ defmodule Mix.Release do
|
||||
args = [runtime_config?, sys_config]
|
||||
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~tw.~n"
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
File.write!(path, :io_lib.format(format, args), [:utf8])
|
||||
File.write!(path, IO.chardata_to_string(:io_lib.format(format, args)))
|
||||
|
||||
case :file.consult(path) do
|
||||
{:ok, _} ->
|
||||
@@ -512,7 +512,7 @@ defmodule Mix.Release do
|
||||
:ok | {:error, String.t()}
|
||||
def make_boot_script(release, path, modes, prepend_paths \\ []) do
|
||||
with {:ok, rel_spec} <- build_release_spec(release, modes) do
|
||||
File.write!(path <> ".rel", consultable(rel_spec), [:utf8])
|
||||
File.write!(path <> ".rel", consultable(rel_spec))
|
||||
|
||||
sys_path = String.to_charlist(path)
|
||||
|
||||
@@ -535,7 +535,7 @@ defmodule Mix.Release do
|
||||
|> prepend_paths_to_script(prepend_paths)
|
||||
|
||||
script = {:script, rel_info, instructions}
|
||||
File.write!(script_path, consultable(script), [:utf8])
|
||||
File.write!(script_path, consultable(script))
|
||||
:ok = :systools.script2boot(sys_path)
|
||||
|
||||
{:error, module, info} ->
|
||||
@@ -657,7 +657,7 @@ defmodule Mix.Release do
|
||||
end
|
||||
|
||||
defp consultable(term) do
|
||||
:io_lib.format("%% coding: utf-8~n~tp.~n", [term])
|
||||
IO.chardata_to_string(:io_lib.format("%% coding: utf-8~n~tp.~n", [term]))
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -76,8 +76,12 @@ defmodule Mix.Shell do
|
||||
|
||||
## Options
|
||||
|
||||
* `:cd` - (since v1.11.0) the directory to run the command in
|
||||
|
||||
* `:stderr_to_stdout` - redirects stderr to stdout, defaults to true
|
||||
|
||||
* `:env` - a list of environment variables, defaults to `[]`
|
||||
|
||||
* `:quiet` - overrides the callback to no-op
|
||||
|
||||
"""
|
||||
@@ -98,7 +102,10 @@ defmodule Mix.Shell do
|
||||
[]
|
||||
end
|
||||
|
||||
opts = [:stream, :binary, :exit_status, :hide, :use_stdio, {:env, env} | args]
|
||||
opts =
|
||||
[:stream, :binary, :exit_status, :hide, :use_stdio, {:env, env}] ++
|
||||
args ++ Keyword.take(options, [:cd])
|
||||
|
||||
port = Port.open({:spawn, shell_command(command)}, opts)
|
||||
port_read(port, callback)
|
||||
end
|
||||
|
||||
@@ -162,8 +162,8 @@ defmodule Mix.Tasks.App.Start do
|
||||
{:error, {:file.format_error(:enoent), name}}
|
||||
|
||||
path ->
|
||||
case :file.consult(path) do
|
||||
{:ok, [{:application, _, properties} = application_data]} ->
|
||||
case consult_app_file_maybe_in_archive(path) do
|
||||
{:ok, {:application, _, properties} = application_data} ->
|
||||
with :ok <- :application.load(application_data) do
|
||||
if compile_env = validate_compile_env? && properties[:compile_env] do
|
||||
# Unfortunately we can only check the current app here,
|
||||
@@ -182,6 +182,19 @@ defmodule Mix.Tasks.App.Start do
|
||||
end
|
||||
end
|
||||
|
||||
defp consult_app_file_maybe_in_archive(path) do
|
||||
# The path could be located in .ez archive
|
||||
case :erl_prim_loader.get_file(path) do
|
||||
{:ok, bin, _full_name} ->
|
||||
with {:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
|
||||
:erl_parse.parse_term(tokens)
|
||||
end
|
||||
|
||||
:error ->
|
||||
{:error, :enoent}
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def type(config, opts) do
|
||||
cond do
|
||||
|
||||
@@ -22,6 +22,13 @@ defmodule Mix.Tasks.Cmd do
|
||||
This task is automatically reenabled, so it can be called multiple times
|
||||
with different arguments.
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--app` - limit running the command to the given app. This option
|
||||
may be given multiple times
|
||||
|
||||
* `--cd` - (since v1.11.0) the directory to run the command in
|
||||
|
||||
## Zombie operating system processes
|
||||
|
||||
Beware that the Erlang VM does not terminate child processes
|
||||
@@ -35,12 +42,20 @@ defmodule Mix.Tasks.Cmd do
|
||||
of the `Port` module documentation.
|
||||
"""
|
||||
|
||||
@switches [
|
||||
app: :keep,
|
||||
cd: :string
|
||||
]
|
||||
|
||||
@impl true
|
||||
def run(args) do
|
||||
{args, apps} = parse_apps(args, [])
|
||||
{opts, args} = OptionParser.parse_head!(args, strict: @switches)
|
||||
apps = Enum.map(List.wrap(opts[:app]), &String.to_atom/1)
|
||||
|
||||
if apps == [] or Mix.Project.config()[:app] in apps do
|
||||
case Mix.shell().cmd(Enum.join(args, " ")) do
|
||||
cmd_opts = Keyword.take(opts, [:cd])
|
||||
|
||||
case Mix.shell().cmd(Enum.join(args, " "), cmd_opts) do
|
||||
0 -> :ok
|
||||
status -> exit(status)
|
||||
end
|
||||
@@ -48,14 +63,4 @@ defmodule Mix.Tasks.Cmd do
|
||||
|
||||
Mix.Task.reenable("cmd")
|
||||
end
|
||||
|
||||
defp parse_apps(args, apps) do
|
||||
case args do
|
||||
["--app", app | tail] ->
|
||||
parse_apps(tail, [String.to_atom(app) | apps])
|
||||
|
||||
args ->
|
||||
{args, apps}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,11 @@ defmodule Mix.Tasks.Local.Hex do
|
||||
|
||||
mix local.hex
|
||||
|
||||
If installing a precompiled Hex does not work, you can compile and install
|
||||
Hex directly with this command:
|
||||
|
||||
mix archive.install github hexpm/hex branch latest
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--force` - forces installation without a shell prompt; primarily
|
||||
|
||||
@@ -481,7 +481,9 @@ defmodule Mix.Tasks.Release do
|
||||
will receive a `Mix.Release` struct and must return the same or
|
||||
an updated `Mix.Release` struct. It is also possible to build a tarball
|
||||
of the release by passing the `:tar` step anywhere after `:assemble`.
|
||||
The tarball is created in `_build/MIX_ENV/RELEASE_NAME-RELEASE_VSN.tar.gz`
|
||||
If the release `:path` is not configured, the tarball is created in
|
||||
`_build/MIX_ENV/RELEASE_NAME-RELEASE_VSN.tar.gz` Otherwise it is
|
||||
created inside the configured `:path`.
|
||||
|
||||
See `Mix.Release` for more documentation on the struct and which
|
||||
fields can be modified. Note that `:steps` field itself can be
|
||||
@@ -1065,8 +1067,16 @@ defmodule Mix.Tasks.Release do
|
||||
end
|
||||
|
||||
defp make_tar(release) do
|
||||
tar_filename = "#{release.name}-#{release.version}.tar.gz"
|
||||
out_path = Path.join([release.path, "..", "..", tar_filename]) |> Path.expand()
|
||||
build_path = Mix.Project.build_path()
|
||||
|
||||
dir_path =
|
||||
if release.path == Path.join([build_path, "rel", Atom.to_string(release.name)]) do
|
||||
build_path
|
||||
else
|
||||
release.path
|
||||
end
|
||||
|
||||
out_path = Path.join(dir_path, "#{release.name}-#{release.version}.tar.gz")
|
||||
info(release, [:green, "* building ", :reset, out_path])
|
||||
|
||||
lib_dirs =
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import Config
|
||||
|
||||
config :release_test, :static, :was_set
|
||||
config :release_test, :encoding, {:"£", "£", '£'}
|
||||
config :release_test, :encoding, {:time_μs, :"£", "£", '£'}
|
||||
|
||||
@@ -488,11 +488,15 @@ defmodule Mix.ReleaseTest do
|
||||
end
|
||||
|
||||
test "writes sys_config with encoding" do
|
||||
assert make_sys_config(release([]), [encoding: {:"£", "£", '£'}], "unused/runtime/path") ==
|
||||
assert make_sys_config(
|
||||
release([]),
|
||||
[encoding: {:time_μs, :"£", "£", '£'}],
|
||||
"unused/runtime/path"
|
||||
) ==
|
||||
:ok
|
||||
|
||||
{:ok, contents} = :file.consult(@sys_config)
|
||||
assert contents == [[encoding: {:"£", "£", '£'}]]
|
||||
assert contents == [[encoding: {:time_μs, :"£", "£", '£'}]]
|
||||
end
|
||||
|
||||
test "writes the given sys_config with config providers" do
|
||||
|
||||
@@ -17,4 +17,16 @@ defmodule Mix.ShellTest do
|
||||
after
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
end
|
||||
|
||||
test "with :cd" do
|
||||
Mix.shell(Mix.Shell.IO)
|
||||
tmp_dir = System.tmp_dir()
|
||||
{pwd, 0} = System.cmd("pwd", [], cd: tmp_dir)
|
||||
|
||||
assert ExUnit.CaptureIO.capture_io(fn ->
|
||||
Mix.shell().cmd("pwd", cd: tmp_dir)
|
||||
end) == pwd
|
||||
after
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,4 +36,16 @@ defmodule Mix.Tasks.CmdTest do
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "only runs the cmd for specified apps and in specific directory" do
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
Mix.Task.run("cmd", ["--app", "bar", "--cd", "lib", "pwd"])
|
||||
assert_received {:mix_shell, :info, ["==> bar"]}
|
||||
{pwd, 0} = System.cmd("pwd", [], cd: Path.join(["apps", "bar", "lib"]))
|
||||
assert_received {:mix_shell, :run, [^pwd]}
|
||||
refute_received {:mix_shell, :info, ["==> foo"]}
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -201,6 +201,32 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "compiles dependent changed modules without beam files" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
def a, do: A.__info__(:module)
|
||||
end
|
||||
""")
|
||||
|
||||
Mix.Tasks.Compile.Elixir.run(["--verbose"])
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
|
||||
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam")
|
||||
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.B.beam")
|
||||
|
||||
Code.put_compiler_option(:ignore_module_conflict, true)
|
||||
Code.compile_file("lib/b.ex")
|
||||
File.touch!("lib/a.ex", {{2038, 1, 1}, {0, 0, 0}})
|
||||
|
||||
Mix.Tasks.Compile.Elixir.run(["--verbose"])
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
end)
|
||||
after
|
||||
Code.put_compiler_option(:ignore_module_conflict, false)
|
||||
end
|
||||
|
||||
test "compiles dependent changed modules even on removal" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
File.write!("lib/a.ex", "defmodule A, do: B.module_info()")
|
||||
|
||||
@@ -146,6 +146,39 @@ defmodule Mix.Tasks.CompileTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "calling raise inside a macro returns a diagnostic with a position" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
File.write!("lib/a.ex", """
|
||||
defmodule A do
|
||||
defmacro custom_macro do
|
||||
raise "error"
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
require A
|
||||
A.custom_macro()
|
||||
end
|
||||
""")
|
||||
|
||||
file = Path.absname("lib/b.ex")
|
||||
|
||||
ExUnit.CaptureIO.capture_io(fn ->
|
||||
assert {:error, [diagnostic]} = Mix.Task.run("compile", ["--return-errors"])
|
||||
|
||||
assert %Mix.Task.Compiler.Diagnostic{
|
||||
file: ^file,
|
||||
severity: :error,
|
||||
position: 3,
|
||||
message: "** (RuntimeError) error\n expanding macro: A.custom_macro/0" <> _,
|
||||
compiler_name: "Elixir"
|
||||
} = diagnostic
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "returns syntax error from an Erlang file when --return-errors is set" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
@@ -135,7 +135,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
end
|
||||
|
||||
describe "tar" do
|
||||
test "with ERTS" do
|
||||
test "with default options" do
|
||||
in_fixture("release_test", fn ->
|
||||
config = [releases: [demo: [steps: [:assemble, :tar]]]]
|
||||
|
||||
@@ -186,15 +186,15 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "without ERTS" do
|
||||
test "without ERTS and custom path" do
|
||||
in_fixture("release_test", fn ->
|
||||
config = [releases: [demo: [include_erts: false, steps: [:assemble, :tar]]]]
|
||||
config = [
|
||||
releases: [demo: [include_erts: false, path: "tmp/rel", steps: [:assemble, :tar]]]
|
||||
]
|
||||
|
||||
Mix.Project.in_project(:release_test, ".", config, fn _ ->
|
||||
root = Path.absname("_build/#{Mix.env()}/rel/demo")
|
||||
|
||||
Mix.Task.run("release")
|
||||
tar_path = Path.expand(Path.join([root, "..", "..", "demo-0.1.0.tar.gz"]))
|
||||
tar_path = Path.expand(Path.join(["tmp", "rel", "demo-0.1.0.tar.gz"]))
|
||||
message = "* building #{tar_path}"
|
||||
assert_received {:mix_shell, :info, [^message]}
|
||||
assert File.exists?(tar_path)
|
||||
@@ -281,7 +281,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
assert %{
|
||||
app_dir: app_dir,
|
||||
cookie_env: ^cookie,
|
||||
encoding: {:"£", "£", '£'},
|
||||
encoding: {:time_μs, :"£", "£", '£'},
|
||||
mode: :embedded,
|
||||
node: release_node("release_test"),
|
||||
protocols_consolidated?: true,
|
||||
@@ -322,7 +322,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
File.write!("config/releases.exs", """
|
||||
import Config
|
||||
config :release_test, :runtime, :was_set
|
||||
config :release_test, :encoding, {:runtime, :"£", "£", '£'}
|
||||
config :release_test, :encoding, {:runtime, :time_μs, :"£", "£", '£'}
|
||||
""")
|
||||
|
||||
root = Path.absname("_build/dev/rel/runtime_config")
|
||||
@@ -348,7 +348,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
open_port(Path.join(root, "bin/runtime_config"), ['start'])
|
||||
|
||||
assert %{
|
||||
encoding: {:runtime, :"£", "£", '£'},
|
||||
encoding: {:runtime, :time_μs, :"£", "£", '£'},
|
||||
mode: :embedded,
|
||||
node: release_node("runtime_config"),
|
||||
protocols_consolidated?: true,
|
||||
|
||||
Reference in New Issue
Block a user