Compare commits

...
45 Commits
Author SHA1 Message Date
José Valim 327063cc84 Release v1.16.3 2024-05-21 00:14:25 +02:00
José Valim 795d0a9583 More fixes 2024-05-20 16:51:13 +02:00
José Valim 1150ebad03 Update CHANGELOG 2024-05-06 12:50:25 +02:00
José Valim 18feec4cd8 Add brackets around keyword lists when formatting with when, closes #13503 2024-05-06 12:44:54 +02:00
José Valim 7d5920206b Check that docs are not hidden in suite, closes #13516 2024-05-02 01:26:48 +02:00
José Valim a694447601 Ensure translators are persisted across logger restarts 2024-05-01 11:40:12 +02:00
José Valim 19ae388dda Add .bat/.com disclaimers to System.cmd and Port 2024-04-17 11:29:02 +02:00
José Valim 6b5edea5f1 Fix --dbg handling in bin/elixir, closes #13482 2024-04-08 09:31:20 +02:00
Arne Bedurftig b736c23af9 Fix typo in basic types (#13481)
The exclamation mark was missing in the result of the concatenation.
2024-04-07 20:58:08 +02:00
Theodor Fiedler 63f0000ace Translate :undefined URI port to nil (#13464)
Resolves #13462

When a url string is schema less and the host is followed by a colon
without setting the actual port, `:uri_string.parse/1` returns the
port to be `:undefined`. As long as the url string is parseable,
we should translate `:undefined` to `nil`, in order to ensure we return
a valid URI struct.
2024-04-04 12:43:16 +02:00
José Valim 5e8b187d08 Ensure compile paths are available during compilation, closes #13458 2024-04-01 12:18:01 +02:00
Jean Klingler aea1a47b8d Only infer size in pinned variable when needed (#13423) 2024-03-20 19:00:17 +09:00
Jean Klingler eeba8e992d Fix Enum.slide/3 example in cheatsheet (#13054) 2024-03-16 17:09:52 +01:00
Steven C dd42dd8620 Fix typo around Enum.slide/3 in the Enum cheatsheet (#13053) 2024-03-16 17:09:45 +01:00
José Valim ed1ddfd338 Add parens to private macro example, closes #13411 2024-03-13 12:28:36 +01:00
José Valim d17df054e2 Skip tests if Erlang was compiled without docs, closes #13322 2024-03-10 19:07:22 +01:00
José Valim 41f6ee3f2e Release v1.16.2 2024-03-10 11:54:45 +01:00
José Valim 9d31768f42 Update CHANGELOG 2024-03-07 11:25:06 +01:00
José Valim 558e0ba6a6 Discard mermaid fenced blocks from ansi docs 2024-03-07 11:06:07 +01:00
Jean Klingler ab1e3448f0 Replace deprecated :code.lib_dir/2 usages (#13395) 2024-03-06 13:00:19 +01:00
Jean Klingler 17cd103a28 Logger handles :process_label from OTP27 (#13392) 2024-03-06 13:00:19 +01:00
José Valim 60e6ceae10 Correct task link in docs, closes #13388 2024-03-06 12:59:20 +01:00
José Valim f64ccd0871 Improve capture_log docs with latest Logger 2024-03-05 18:37:17 +01:00
Jonatan Kłosko 6c463b2310 Ensure install-related functions do not crash when Mix is not started (#13391) 2024-03-04 20:45:03 +01:00
H e310dd91d6 Remove duplicate line in formatter_callback doc (#13387) 2024-03-04 15:46:47 +09:00
Jonatan Kłosko 29ef147a22 Remove consolidated when restoring Mix.install/2 dir (#13382) 2024-03-01 09:34:26 +01:00
Jonatan Kłosko 74ab38439e Fix Mix.install_project_dir/0 spec (#13381) 2024-03-01 09:34:26 +01:00
José Valim 5654752026 Add @doc since to install_project_dir 2024-02-29 20:30:48 +01:00
Jonatan Kłosko e78538a42b Add environment variable for reusing Mix.install/2 installation (#13378) 2024-02-29 19:20:17 +01:00
Jonatan Kłosko 079b92e98e Support recompiling local Mix.install/2 dependencies (#13375) 2024-02-29 08:41:11 +01:00
José Valim 325877a224 Clarify the meaning of --overwrite, closes #13371 2024-02-26 20:16:27 +01:00
José Valim 097c4cf672 Emit defmodule tracing event 2024-02-26 18:28:47 +01:00
Łukasz Samson fe16fd3442 Add documentation to Exception callbacks (#13367) 2024-02-24 16:23:16 +01:00
Jean Klingler e520e8ce96 Fix unexpected rounding signs on OPT26- (#13365) 2024-02-24 16:05:09 +01:00
Jean Klingler dd004539c6 Fixes to support 0TP27 (#13351)
* Fix non-deterministic key-value tests

* Fix non-deterministic Enum tests

* Fix :only option when deriving Inspect

* Float.ceil and Float.floor return -0.0 for negative numbers

* Fix non-deterministic Registry doctest

* Simplify check
2024-02-24 16:03:58 +01:00
Jean Klingler 2512cbaff0 Fix doctest example since Inspect.MapSet changed (#13366) 2024-02-24 22:21:18 +09:00
sabiwara a99a9cf47a Fix test due to backport 2024-02-24 00:11:43 +09:00
Jean Klingler 677ad618e9 Fix charlist formatting issue on '\"' (#13364) 2024-02-24 00:02:38 +09:00
Philip Munksgaard ff309747b7 Escape pinned values when computing diff (#13354)
Take these two tests:

```elixir
  test "correctly colored" do
    assert [{:foo}] = [{:bar}]
  end

  test "incorrectly colored" do
    val = [{:foo}]
    assert ^val = [{:bar}]
  end
```

That's because when diffing a pin, we were not converting the underlying
diff context from match to ===.

This fixes #13348
2024-02-17 12:51:27 +01:00
José Valim e5ec5ce7f6 Add API for deleting SCM
This is used by Hex when its application is stopped
to revert its changes to Mix state.
2024-02-15 12:22:53 +01:00
Jean Klingler f3e669c471 Fix :trim_doc example in doc (#13340) 2024-02-13 07:39:31 +09:00
Jean Klingler bf915aa2e7 Fix <> match test (#13327) 2024-02-09 17:03:42 +09:00
Jean KlinglerandJosé Valim ef3e2796ef Update argument error message when matching with <> (#13325)
* Update argument error message when matching with <>

* Update lib/elixir/lib/kernel.ex

Co-authored-by: José Valim <jose.valim@gmail.com>

---------

Co-authored-by: José Valim <jose.valim@gmail.com>
2024-02-09 16:50:08 +09:00
Mitchell Hanberg d8cc841ab0 Include from_brackets metadata in all cases (#13317) 2024-02-05 09:49:40 +01:00
José Valim 7098ac8a16 Preserve . semantics in Path.relative_to, closes #13310 2024-02-01 12:41:26 +01:00
59 changed files with 908 additions and 281 deletions
+4 -1
View File
@@ -13,7 +13,10 @@
assert_same: 2,
# Errors tests
assert_eval_raise: 3
assert_eval_raise: 3,
# Float tests
float_assert: 1
],
normalize_bitstring_modifiers: false
]
+50
View File
@@ -78,6 +78,56 @@ Another [ExDoc](https://github.com/elixir-lang/ex_doc) feature we have incorpora
Finally, we have started enriching our documentation with [Mermaid.js](https://mermaid.js.org/) diagrams. You can find examples in the [GenServer](https://hexdocs.pm/elixir/main/GenServer.html) and [Supervisor](https://hexdocs.pm/elixir/main/Supervisor.html) docs.
## v1.16.3 (2024-05-21)
### 1. Bug fixes
#### Elixir
* [bin/elixir] Properly handle the `--dbg` flag in Elixir's CLI
* [Code.Formatter] Add brackets around keyword lists when formatting the left-hand side of `when`
* [Kernel] Only infer size in pinned variable in binary strings when needed
* [System] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows via `System.cmd/3`
* [Port] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows
* [URI] Ensure `:undefined` fields are properly converted to `nil` when invoking Erlang's API
#### Logger
* [Logger] Ensure translators are persisted across logger restarts
#### Mix
* [mix compile] Ensure compile paths are accessible during compilation
## v1.16.2 (2024-03-10)
### 1. Enhancements
#### Elixir
* [Code] Emit `:defmodule` tracing event on module definition
#### Mix
* [Mix] Add `Mix.install_project_dir/0`
* [Mix] Add environment variable for reusing `Mix.install/2` installation
* [Mix.SCM] Add `Mix.SCM.delete/1`
### 2. Bug fixes
#### Elixir
* [Code] Fix charlist formatting issue when a single-quoted charlist escapes a double-quote character
* [Path] Fix regression on how `Path.relative_to/2` dealt with "." as input
#### IEx
* [IEx.Helpers] Discard mermaid fenced blocks from ansi docs
#### ExUnit
* [ExUnit] Properly compared pinned values when building diffs
## v1.16.1 (2024-01-31)
### 1. Bug fixes
+1 -1
View File
@@ -1 +1 @@
1.16.1
1.16.3
+3 -3
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.16.1
ELIXIR_VERSION=1.16.3
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
@@ -112,10 +112,10 @@ while [ $I -le $LENGTH ]; do
C=1
MODE="iex"
;;
-v|--no-halt|--dbg)
-v|--no-halt)
C=1
;;
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex)
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
C=2
;;
--rpc-eval)
+1 -1
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.16.1
set ELIXIR_VERSION=1.16.3
setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
+4
View File
@@ -158,6 +158,10 @@ defmodule Code do
of keys to traverse in the application environment and `return` is either
`{:ok, value}` or `:error`.
* `:defmodule` - (since v1.16.2) traced as soon as the definition of a module
starts. This is invoked early on in the module life-cycle, `Module.open?/1`
still returns `false` for such traces
* `{:on_module, bytecode, _ignore}` - (since v1.13.0) traced whenever a module
is defined. This is equivalent to the `@after_compile` callback and invoked
after any `@after_compile` in the given module. The third element is currently
+29 -15
View File
@@ -6,7 +6,8 @@ defmodule Code.Formatter do
@double_heredoc "\"\"\""
@single_quote "'"
@single_heredoc "'''"
@sigil_c "~c\""
@sigil_c_double "~c\""
@sigil_c_single "~c'"
@sigil_c_heredoc "~c\"\"\""
@newlines 2
@min_line 0
@@ -299,7 +300,7 @@ defmodule Code.Formatter do
remote_to_algebra(quoted, context, state)
meta[:delimiter] == ~s['''] ->
{opener, quotes} = get_charlist_quotes(true, state)
{opener, quotes} = get_charlist_quotes(:heredoc, state)
{doc, state} =
entries
@@ -309,7 +310,7 @@ defmodule Code.Formatter do
{force_unfit(doc), state}
true ->
{opener, quotes} = get_charlist_quotes(false, state)
{opener, quotes} = get_charlist_quotes({:regular, entries}, state)
list_interpolation_to_algebra(entries, quotes, state, opener, quotes)
end
end
@@ -368,13 +369,14 @@ defmodule Code.Formatter do
defp quoted_to_algebra({:__block__, meta, [list]}, _context, state) when is_list(list) do
case meta[:delimiter] do
~s['''] ->
{opener, quotes} = get_charlist_quotes(true, state)
{opener, quotes} = get_charlist_quotes(:heredoc, state)
string = list |> List.to_string() |> escape_heredoc(quotes)
{opener |> concat(string) |> concat(quotes) |> force_unfit(), state}
~s['] ->
{opener, quotes} = get_charlist_quotes(false, state)
string = list |> List.to_string() |> escape_string(quotes)
string = list |> List.to_string()
{opener, quotes} = get_charlist_quotes({:regular, [string]}, state)
string = escape_string(string, quotes)
{opener |> concat(string) |> concat(quotes), state}
_other ->
@@ -1957,6 +1959,14 @@ defmodule Code.Formatter do
# fn a, b, c when d -> e end
defp clause_args_to_algebra([{:when, meta, args}], state) do
{args, right} = split_last(args)
# If there are any keywords, wrap them in lists
args =
Enum.map(args, fn
[_ | _] = keyword -> {:__block__, [], [keyword]}
other -> other
end)
left = {{:special, :clause_args}, meta, [args]}
binary_op_to_algebra(:when, "when", meta, left, right, :no_parens_arg, state)
end
@@ -2410,19 +2420,23 @@ defmodule Code.Formatter do
{left, right}
end
defp get_charlist_quotes(_heredoc = false, state) do
if state.normalize_charlists_as_sigils do
{@sigil_c, @double_quote}
else
{@single_quote, @single_quote}
end
end
defp get_charlist_quotes(_heredoc = true, state) do
defp get_charlist_quotes(:heredoc, state) do
if state.normalize_charlists_as_sigils do
{@sigil_c_heredoc, @double_heredoc}
else
{@single_heredoc, @single_heredoc}
end
end
defp get_charlist_quotes({:regular, chunks}, state) do
cond do
!state.normalize_charlists_as_sigils -> {@single_quote, @single_quote}
Enum.any?(chunks, &has_double_quote?/1) -> {@sigil_c_single, @single_quote}
true -> {@sigil_c_double, @double_quote}
end
end
defp has_double_quote?(chunk) do
is_binary(chunk) and chunk =~ @double_quote
end
end
+1 -4
View File
@@ -3793,9 +3793,6 @@ defmodule Enum do
iex> Enum.unzip([{:a, 1}, {:b, 2}, {:c, 3}])
{[:a, :b, :c], [1, 2, 3]}
iex> Enum.unzip(%{a: 1, b: 2})
{[:a, :b], [1, 2]}
"""
@spec unzip(t) :: {[element], [element]}
@@ -4051,7 +4048,7 @@ defmodule Enum do
...> end)
[{1, 2, 3}, {1, 2, 3}]
iex> enums = [[1, 2], %{a: 3, b: 4}, [5, 6]]
iex> enums = [[1, 2], [a: 3, b: 4], [5, 6]]
...> Enum.zip_reduce(enums, [], fn elements, acc ->
...> [List.to_tuple(elements) | acc]
...> end)
+14
View File
@@ -31,7 +31,21 @@ defmodule Exception do
@type arity_or_args :: non_neg_integer | list
@type location :: keyword
@doc """
Receives the arguments given to `raise/2` and returns the exception struct.
The default implementation accepts either a set of keyword arguments
that is merged into the struct or a string to be used as the exception's message.
"""
@callback exception(term) :: t
@doc """
Receives the exception struct and must return its message.
Most commonly exceptions have a message field which by default is accessed
by this function. However, if an exception does not have a message field,
this function must be explicitly implemented.
"""
@callback message(t) :: String.t()
@doc """
+1 -1
View File
@@ -1764,7 +1764,7 @@ defmodule File do
## Examples
# Read a utf8 text file which may include BOM
File.stream!("./test/test.txt", encoding: :utf8, trim_bom: true)
File.stream!("./test/test.txt", [:trim_bom, encoding: :utf8])
# Read in 2048 byte chunks rather than lines
File.stream!("./test/test.data", 2048)
+20 -1
View File
@@ -275,6 +275,8 @@ defmodule Float do
-56.0
iex> Float.ceil(34.251, 2)
34.26
iex> Float.ceil(-0.01)
-0.0
"""
@spec ceil(float, precision_range) :: float
@@ -332,6 +334,8 @@ defmodule Float do
-6.0
iex> Float.round(12.341444444444441, 15)
12.341444444444441
iex> Float.round(-0.01)
-0.0
"""
@spec round(float, precision_range) :: float
@@ -340,8 +344,13 @@ defmodule Float do
# and could be implemented in the future.
def round(float, precision \\ 0)
def round(float, 0) when float == 0.0, do: float
def round(float, 0) when is_float(float) do
float |> :erlang.round() |> :erlang.float()
case float |> :erlang.round() |> :erlang.float() do
zero when zero == 0.0 and float < 0.0 -> -0.0
rounded -> rounded
end
end
def round(float, precision) when is_float(float) and precision in @precision_range do
@@ -365,6 +374,8 @@ defmodule Float do
case rounding do
:ceil when sign === 0 -> 1 / power_of_10(precision)
:floor when sign === 1 -> -1 / power_of_10(precision)
:ceil when sign === 1 -> minus_zero()
:half_up when sign === 1 -> minus_zero()
_ -> 0.0
end
@@ -394,6 +405,9 @@ defmodule Float do
boundary = den <<< 52
cond do
num == 0 and sign == 1 ->
minus_zero()
num == 0 ->
0.0
@@ -408,6 +422,11 @@ defmodule Float do
end
end
# TODO remove once we require Erlang/OTP 27+
# This function tricks the compiler to avoid this bug in previous versions:
# https://github.com/elixir-lang/elixir/blob/main/lib/elixir/lib/float.ex#L408-L412
defp minus_zero, do: -0.0
defp decompose(significant, initial) do
decompose(significant, 1, 0, initial)
end
+3 -2
View File
@@ -535,7 +535,8 @@ end
defimpl Inspect, for: Any do
defmacro __deriving__(module, struct, options) do
fields = Map.keys(struct) -- [:__exception__, :__struct__]
fields = Enum.sort(Map.keys(struct) -- [:__exception__, :__struct__])
only = Keyword.get(options, :only, fields)
except = Keyword.get(options, :except, [])
optional = Keyword.get(options, :optional, [])
@@ -545,7 +546,7 @@ defimpl Inspect, for: Any do
:ok = validate_option(:optional, optional, fields, module)
inspect_module =
if fields == only and except == [] do
if fields == Enum.sort(only) and except == [] do
Inspect.Map
else
Inspect.Any
+10 -5
View File
@@ -176,12 +176,17 @@ defmodule IO.ANSI.Docs do
process_code(rest, [line], indent, options)
end
defp process(["```" <> _line | rest], text, indent, options) do
process_fenced_code_block(rest, text, indent, options, _delimiter = "```")
defp process(["```mermaid" <> _line | rest], text, indent, options) do
write_text(text, indent, options)
rest
|> Enum.drop_while(&(&1 != "```"))
|> Enum.drop(1)
|> process([], indent, options)
end
defp process(["~~~" <> _line | rest], text, indent, options) do
process_fenced_code_block(rest, text, indent, options, _delimiter = "~~~")
defp process(["```" <> _line | rest], text, indent, options) do
process_fenced_code_block(rest, text, indent, options, _delimiter = "```")
end
defp process(["<!--" <> line | rest], text, indent, options) do
@@ -388,7 +393,7 @@ defmodule IO.ANSI.Docs do
end
defp process_fenced_code([line | rest], code, indent, options, delimiter) do
if line === delimiter do
if line == delimiter do
process_code(rest, code, indent, options)
else
process_fenced_code(rest, [line | code], indent, options, delimiter)
+3 -2
View File
@@ -2076,8 +2076,9 @@ defmodule Kernel do
defp invalid_concat_left_argument_error(arg) do
:erlang.error(
ArgumentError.exception(
"the left argument of <> operator inside a match should always be a literal " <>
"binary because its size can't be verified. Got: #{arg}"
"cannot perform prefix match because the left operand of <> has unknown size. " <>
"The left operand of <> inside a match should either be a literal binary or " <>
"an existing variable with the pin operator (such as ^some_var). Got: #{arg}"
)
)
end
+2 -2
View File
@@ -147,7 +147,7 @@ defmodule Map do
## Examples
iex> Map.keys(%{a: 1, b: 2})
Map.keys(%{a: 1, b: 2})
[:a, :b]
"""
@@ -161,7 +161,7 @@ defmodule Map do
## Examples
iex> Map.values(%{a: 1, b: 2})
Map.values(%{a: 1, b: 2})
[1, 2]
"""
+2
View File
@@ -424,6 +424,8 @@ defmodule Path do
defp relative_to_unforced(_, _, original), do: join(original)
defp relative_to_forced(path, path, _original), do: "."
defp relative_to_forced(["."], _path, _original), do: "."
defp relative_to_forced(path, ["."], _original), do: join(path)
defp relative_to_forced([h | t1], [h | t2], original), do: relative_to_forced(t1, t2, original)
# this should only happen if we have two paths on different drives on windows
+21
View File
@@ -79,6 +79,27 @@ defmodule Port do
are for advanced usage within the VM. Also consider using `System.cmd/3`
if all you want is to execute a program and retrieve its return value.
> #### Windows argument splitting and untrusted arguments {: .warning}
>
> On Unix systems, arguments are passed to a new operating system
> process as an array of strings but on Windows it is up to the child
> process to parse them and some Windows programs may apply their own
> rules, which are inconsistent with the standard C runtime `argv` parsing
>
> This is particularly troublesome when invoking `.bat` or `.com` files
> as these run implicitly through `cmd.exe`, whose argument parsing is
> vulnerable to malicious input and can be used to run arbitrary shell
> commands.
>
> Therefore, if you are running on Windows and you execute batch
> files or `.com` applications, you must not pass untrusted input as
> arguments to the program. You may avoid accidentally executing them
> by explicitly passing the extension of the program you want to run,
> such as `.exe`, and double check the program is indeed not a batch
> file or `.com` application.
>
> This affects both `spawn` and `spawn_executable`.
### spawn
The `:spawn` tuple receives a binary that is going to be executed as a
+2 -2
View File
@@ -448,7 +448,7 @@ defmodule Protocol do
## Examples
# Get Elixir's ebin directory path and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> path = Application.app_dir(:elixir, "ebin")
iex> mods = Protocol.extract_protocols([path])
iex> Enumerable in mods
true
@@ -477,7 +477,7 @@ defmodule Protocol do
## Examples
# Get Elixir's ebin directory path and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> path = Application.app_dir(:elixir, "ebin")
iex> mods = Protocol.extract_impls(Enumerable, [path])
iex> List in mods
true
+4 -4
View File
@@ -1301,16 +1301,16 @@ defmodule Registry do
iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}])
[{"world", self(), :value}, {"hello", self(), :value}]
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}]) |> Enum.sort()
[{"hello", self(), :value}, {"world", self(), :value}]
Get all keys in the registry:
iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :_, :_}, [], [:"$1"]}])
["world", "hello"]
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :_, :_}, [], [:"$1"]}]) |> Enum.sort()
["hello", "world"]
"""
@doc since: "1.9.0"
+19
View File
@@ -1005,6 +1005,25 @@ defmodule System do
`Port` module describes this problem and possible solutions under
the "Zombie processes" section.
> #### Windows argument splitting and untrusted arguments {: .warning}
>
> On Unix systems, arguments are passed to a new operating system
> process as an array of strings but on Windows it is up to the child
> process to parse them and some Windows programs may apply their own
> rules, which are inconsistent with the standard C runtime `argv` parsing
>
> This is particularly troublesome when invoking `.bat` or `.com` files
> as these run implicitly through `cmd.exe`, whose argument parsing is
> vulnerable to malicious input and can be used to run arbitrary shell
> commands.
>
> Therefore, if you are running on Windows and you execute batch
> files or `.com` applications, you must not pass untrusted input as
> arguments to the program. You may avoid accidentally executing them
> by explicitly passing the extension of the program you want to run,
> such as `.exe`, and double check the program is indeed not a batch
> file or `.com` application.
## Examples
iex> System.cmd("echo", ["hello"])
+1 -1
View File
@@ -40,7 +40,7 @@ defmodule Task do
as they are *always* sent. If you are not expecting a reply,
consider using `Task.start_link/1` as detailed below.
2. async tasks link the caller and the spawned process. This
2. Async tasks link the caller and the spawned process. This
means that, if the caller crashes, the task will crash
too and vice-versa. This is on purpose: if the process
meant to receive the result no longer exists, there is
+4 -4
View File
@@ -152,7 +152,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Supervisor`.
The task will still be linked to the caller, see `Task.async/3` for
The task will still be linked to the caller, see `Task.async/1` for
more information and `async_nolink/3` for a non-linked variant.
Raises an error if `supervisor` has reached the maximum number of
@@ -174,7 +174,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Supervisor`.
The task will still be linked to the caller, see `Task.async/3` for
The task will still be linked to the caller, see `Task.async/1` for
more information and `async_nolink/3` for a non-linked variant.
Raises an error if `supervisor` has reached the maximum number of
@@ -196,7 +196,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Supervisor`.
The task won't be linked to the caller, see `Task.async/3` for
The task won't be linked to the caller, see `Task.async/1` for
more information.
Raises an error if `supervisor` has reached the maximum number of
@@ -283,7 +283,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Supervisor`.
The task won't be linked to the caller, see `Task.async/3` for
The task won't be linked to the caller, see `Task.async/1` for
more information.
Raises an error if `supervisor` has reached the maximum number of
+5 -5
View File
@@ -656,16 +656,16 @@ defmodule URI do
scheme = String.downcase(scheme, :ascii)
case map do
%{port: port} when port != :undefined ->
%{port: port} when is_integer(port) ->
%{uri | scheme: scheme}
%{} ->
case default_port(scheme) do
nil -> %{uri | scheme: scheme}
port -> %{uri | scheme: scheme, port: port}
end
%{uri | scheme: scheme, port: default_port(scheme)}
end
%{port: :undefined} ->
%{uri | port: nil}
%{} ->
uri
end
@@ -707,11 +707,11 @@ fruits = ["apple", "banana", "grape", "orange", "pear"]
iex> Enum.slide(fruits, 2, 0)
["grape", "apple", "banana", "orange", "pear"]
iex> Enum.slide(fruits, 2, 4)
["apple", "banana", "orange", "pear", "grape", ]
["apple", "banana", "orange", "pear", "grape"]
iex> Enum.slide(fruits, 1..3, 0)
["banana", "grape", "orange", "apple", "pear"]
iex> Enum.slide(fruits, 1..3, 4)
["banana", "pear", "grape", "orange", "apple"]
["apple", "pear", "banana", "grape", "orange"]
```
## Reversing
@@ -227,7 +227,7 @@ Elixir also supports string interpolation:
```elixir
iex> string = "world"
iex> "hello #{string}!"
"hello world"
"hello world!"
```
String concatenation requires both sides to be strings but interpolation supports any data type that may be converted to a string:
+1 -1
View File
@@ -223,7 +223,7 @@ It is important that a macro is defined before its usage. Failing to define a ma
```elixir
iex> defmodule Sample do
...> def four, do: two + two
...> def four, do: two() + two()
...> defmacrop two, do: 2
...> end
** (CompileError) iex:2: function two/0 undefined
+2 -2
View File
@@ -36,9 +36,9 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, S, E, Alignment,
MatchOrRequireSize = RequireSize or is_match_size(T, EL),
EType = expr_type(ELeft),
ExpectSize = case ELeft of
_ when not MatchOrRequireSize -> optional;
{'^', _, [{_, _, _}]} -> {infer, ELeft};
_ when MatchOrRequireSize -> required;
_ -> optional
_ -> required
end,
{ERight, EAlignment, SS, ES} = expand_specs(EType, Meta, Right, SL, OriginalS, EL, ExpectSize),
+1
View File
@@ -118,6 +118,7 @@ invalid_module_name(Module) ->
compile(Line, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
File = ?key(E, file),
check_module_availability(Module, Line, E),
elixir_env:trace(defmodule, E),
CompilerModules = compiler_modules(),
{Tables, Ref} = build(Module, Line, File, E),
+3 -3
View File
@@ -292,13 +292,13 @@ bracket_arg -> open_bracket container_expr close_bracket : build_access_arg('$1'
bracket_arg -> open_bracket container_expr ',' close_bracket : build_access_arg('$1', '$2', '$4').
bracket_arg -> open_bracket container_expr ',' container_args close_bracket : error_too_many_access_syntax('$3').
bracket_expr -> dot_bracket_identifier bracket_arg : build_access(build_no_parens('$1', nil), '$2').
bracket_expr -> dot_bracket_identifier bracket_arg : build_access(build_no_parens('$1', nil), meta_with_from_brackets('$2')).
bracket_expr -> access_expr bracket_arg : build_access('$1', meta_with_from_brackets('$2')).
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_arg :
build_access(build_unary_op('$1', build_no_parens('$2', nil)), '$3').
build_access(build_unary_op('$1', build_no_parens('$2', nil)), meta_with_from_brackets('$3')).
bracket_at_expr -> at_op_eol access_expr bracket_arg :
build_access(build_unary_op('$1', '$2'), '$3').
build_access(build_unary_op('$1', '$2'), meta_with_from_brackets('$3')).
%% Blocks
@@ -296,6 +296,28 @@ defmodule Code.Formatter.GeneralTest do
assert_same code, @short_length
end
test "keeps parens if argument includes keyword list" do
assert_same """
fn [] when is_integer(x) ->
x + 42
end
"""
bad = """
fn (input: x) when is_integer(x) ->
x + 42
end
"""
good = """
fn [input: x] when is_integer(x) ->
x + 42
end
"""
assert_format bad, good
end
test "with a single clause, followed by a newline, and can fit in one line" do
assert_same """
fn
@@ -210,10 +210,15 @@ defmodule Code.Formatter.LiteralsTest do
test "with escapes" do
assert_format ~S['f\a\b\ro'], ~S[~c"f\a\b\ro"]
assert_format ~S['single \' quote'], ~S[~c"single ' quote"]
assert_format ~S['double " quote'], ~S[~c"double \" quote"]
assert_format ~S['double " quote'], ~S[~c'double " quote']
assert_format ~S['escaped \" quote'], ~S[~c'escaped \" quote']
assert_format ~S['\\"'], ~S[~c'\\"']
assert_same ~S['f\a\b\ro'], @keep_charlists
assert_same ~S['single \' quote'], @keep_charlists
assert_same ~S['double " quote'], @keep_charlists
assert_same ~S['escaped \" quote'], @keep_charlists
assert_same ~S['\\"'], @keep_charlists
end
test "keeps literal new lines" do
@@ -235,13 +240,15 @@ defmodule Code.Formatter.LiteralsTest do
test "with interpolation" do
assert_format ~S['one #{2} three'], ~S[~c"one #{2} three"]
assert_format ~S['#{1}\n \\ " \"'], ~S[~c'#{1}\n \\ " \"']
assert_same ~S['one #{2} three'], @keep_charlists
assert_same ~S['#{1}\n \\ " \"'], @keep_charlists
end
test "with escape and interpolation" do
assert_format ~S['one\n\'#{2}\'\nthree'], ~S[~c"one\n'#{2}'\nthree"]
assert_format ~S['one\n"#{2}"\nthree'], ~S[~c"one\n\"#{2}\"\nthree"]
assert_format ~S['one\n"#{2}"\nthree'], ~S[~c'one\n"#{2}"\nthree']
assert_same ~S['one\n\'#{2}\'\nthree'], @keep_charlists
end
@@ -708,10 +708,10 @@ defmodule Code.Normalizer.QuotedASTTest do
~S[~c"one\n'#{2}'\nthree"]
assert Code.string_to_quoted!(~S/'one\n"#{2}"\nthree'/) |> quoted_to_string(escape: false) ==
~s[~c"one\n\\"\#{2}\\"\nthree"]
~s[~c'one\n"\#{2}"\nthree']
assert Code.string_to_quoted!(~S/'one\n"#{2}"\nthree'/) |> quoted_to_string() ==
~S[~c"one\n\"#{2}\"\nthree"]
~S[~c'one\n"#{2}"\nthree']
end
test "atoms" do
+4 -4
View File
@@ -56,9 +56,9 @@ defmodule EnumTest do
end
test "mix and match" do
enums = [[1, 2], %{a: 3, b: 4}, [5, 6]]
enums = [[1, 2], 3..4, [5, 6]]
result = Enum.zip_reduce(enums, [], fn elements, acc -> [List.to_tuple(elements) | acc] end)
assert result == [{2, {:b, 4}, 6}, {1, {:a, 3}, 5}]
assert result == [{2, 4, 6}, {1, 3, 5}]
end
end
@@ -412,7 +412,7 @@ defmodule EnumTest do
assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
assert Enum.into(MapSet.new(a: 1, b: 2), %{}) == %{a: 1, b: 2}
assert Enum.into(MapSet.new(a: 1, b: 2), %{c: 3}) == %{a: 1, b: 2, c: 3}
assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
assert Enum.into(%{a: 1, b: 2}, []) |> Enum.sort() == [a: 1, b: 2]
assert Enum.into(1..3, []) == [1, 2, 3]
assert Enum.into(["H", "i"], "") == "Hi"
end
@@ -1430,7 +1430,7 @@ defmodule EnumTest do
test "unzip/1" do
assert Enum.unzip([{:a, 1}, {:b, 2}, {:c, 3}]) == {[:a, :b, :c], [1, 2, 3]}
assert Enum.unzip([]) == {[], []}
assert Enum.unzip(%{a: 1, b: 2}) == {[:a, :b], [1, 2]}
assert Enum.unzip(%{a: 1}) == {[:a], [1]}
assert Enum.unzip(foo: "a", bar: "b") == {[:foo, :bar], ["a", "b"]}
assert_raise FunctionClauseError, fn -> Enum.unzip([{:a, 1}, {:b, 2, "foo"}]) end
+91 -64
View File
@@ -5,6 +5,17 @@ defmodule FloatTest do
doctest Float
# TODO remove and replace by assert once we require Erlang/OTP 27+
# We can't easily distinguish between -0.0 and +0.0 on previous version
defmacrop float_assert({:===, _, [left, right]}) do
quote do
# note: these are pure functions so no need to use bind_quoted
# we favor a useful error message instead
assert unquote(left) === unquote(right)
assert to_string(unquote(left)) === to_string(unquote(right))
end
end
test "parse/1" do
assert Float.parse("12") === {12.0, ""}
assert Float.parse("-12") === {-12.0, ""}
@@ -45,39 +56,39 @@ defmodule FloatTest do
end
test "floor/1" do
assert Float.floor(12.524235) === 12.0
assert Float.floor(-12.5) === -13.0
assert Float.floor(-12.524235) === -13.0
assert Float.floor(7.5e3) === 7500.0
assert Float.floor(7.5432e3) === 7543.0
assert Float.floor(7.5e-3) === 0.0
assert Float.floor(-12.32453e4) === -123_246.0
assert Float.floor(-12.32453e-10) === -1.0
assert Float.floor(0.32453e-10) === 0.0
assert Float.floor(-0.32453e-10) === -1.0
assert Float.floor(1.32453e-10) === 0.0
float_assert Float.floor(12.524235) === 12.0
float_assert Float.floor(-12.5) === -13.0
float_assert Float.floor(-12.524235) === -13.0
float_assert Float.floor(7.5e3) === 7500.0
float_assert Float.floor(7.5432e3) === 7543.0
float_assert Float.floor(7.5e-3) === 0.0
float_assert Float.floor(-12.32453e4) === -123_246.0
float_assert Float.floor(-12.32453e-10) === -1.0
float_assert Float.floor(0.32453e-10) === 0.0
float_assert Float.floor(-0.32453e-10) === -1.0
float_assert Float.floor(1.32453e-10) === 0.0
end
describe "floor/2" do
test "with 0.0" do
for precision <- 0..15 do
assert Float.floor(0.0, precision) === 0.0
assert Float.floor(-0.0, precision) === -0.0
float_assert Float.floor(0.0, precision) === 0.0
float_assert Float.floor(-0.0, precision) === -0.0
end
end
test "floor/2 with precision" do
assert Float.floor(12.524235, 0) === 12.0
assert Float.floor(-12.524235, 0) === -13.0
float_assert Float.floor(12.524235, 0) === 12.0
float_assert Float.floor(-12.524235, 0) === -13.0
assert Float.floor(12.52, 2) === 12.51
assert Float.floor(-12.52, 2) === -12.52
float_assert Float.floor(12.52, 2) === 12.51
float_assert Float.floor(-12.52, 2) === -12.52
assert Float.floor(12.524235, 2) === 12.52
assert Float.floor(-12.524235, 3) === -12.525
float_assert Float.floor(12.524235, 2) === 12.52
float_assert Float.floor(-12.524235, 3) === -12.525
assert Float.floor(12.32453e-20, 2) === 0.0
assert Float.floor(-12.32453e-20, 2) === -0.01
float_assert Float.floor(12.32453e-20, 2) === 0.0
float_assert Float.floor(-12.32453e-20, 2) === -0.01
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.floor(1.1, 16)
@@ -85,68 +96,73 @@ defmodule FloatTest do
end
test "with subnormal floats" do
assert Float.floor(-5.0e-324, 0) === -1.0
assert Float.floor(-5.0e-324, 1) === -0.1
assert Float.floor(-5.0e-324, 2) === -0.01
assert Float.floor(-5.0e-324, 15) === -0.000000000000001
float_assert Float.floor(-5.0e-324, 0) === -1.0
float_assert Float.floor(-5.0e-324, 1) === -0.1
float_assert Float.floor(-5.0e-324, 2) === -0.01
float_assert Float.floor(-5.0e-324, 15) === -0.000000000000001
for precision <- 0..15 do
assert Float.floor(5.0e-324, precision) === 0.0
float_assert Float.floor(5.0e-324, precision) === 0.0
end
end
end
test "ceil/1" do
assert Float.ceil(12.524235) === 13.0
assert Float.ceil(-12.5) === -12.0
assert Float.ceil(-12.524235) === -12.0
assert Float.ceil(7.5e3) === 7500.0
assert Float.ceil(7.5432e3) === 7544.0
assert Float.ceil(7.5e-3) === 1.0
assert Float.ceil(-12.32453e4) === -123_245.0
assert Float.ceil(-12.32453e-10) === 0.0
assert Float.ceil(0.32453e-10) === 1.0
assert Float.ceil(-0.32453e-10) === 0.0
assert Float.ceil(1.32453e-10) === 1.0
assert Float.ceil(0.0) === 0.0
float_assert Float.ceil(12.524235) === 13.0
float_assert Float.ceil(-12.5) === -12.0
float_assert Float.ceil(-12.524235) === -12.0
float_assert Float.ceil(7.5e3) === 7500.0
float_assert Float.ceil(7.5432e3) === 7544.0
float_assert Float.ceil(7.5e-3) === 1.0
float_assert Float.ceil(-12.32453e4) === -123_245.0
float_assert Float.ceil(-12.32453e-10) === -0.0
float_assert Float.ceil(0.32453e-10) === 1.0
float_assert Float.ceil(-0.32453e-10) === -0.0
float_assert Float.ceil(1.32453e-10) === 1.0
float_assert Float.ceil(0.0) === 0.0
end
describe "ceil/2" do
test "with 0.0" do
for precision <- 0..15 do
assert Float.ceil(0.0, precision) === 0.0
assert Float.ceil(-0.0, precision) === -0.0
float_assert Float.ceil(0.0, precision) === 0.0
float_assert Float.ceil(-0.0, precision) === -0.0
end
end
test "with regular floats" do
assert Float.ceil(12.524235, 0) === 13.0
assert Float.ceil(-12.524235, 0) === -12.0
float_assert Float.ceil(12.524235, 0) === 13.0
float_assert Float.ceil(-12.524235, 0) === -12.0
assert Float.ceil(12.52, 2) === 12.52
assert Float.ceil(-12.52, 2) === -12.51
float_assert Float.ceil(12.52, 2) === 12.52
float_assert Float.ceil(-12.52, 2) === -12.51
assert Float.ceil(12.524235, 2) === 12.53
assert Float.ceil(-12.524235, 3) === -12.524
float_assert Float.ceil(12.524235, 2) === 12.53
float_assert Float.ceil(-12.524235, 3) === -12.524
assert Float.ceil(12.32453e-20, 2) === 0.01
assert Float.ceil(-12.32453e-20, 2) === 0.0
float_assert Float.ceil(12.32453e-20, 2) === 0.01
float_assert Float.ceil(-12.32453e-20, 2) === -0.0
assert Float.ceil(0.0, 2) === 0.0
float_assert Float.ceil(0.0, 2) === 0.0
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.ceil(1.1, 16)
end
end
test "with small floats rounded up to -0.0" do
float_assert Float.ceil(-0.1, 0) === -0.0
float_assert Float.ceil(-0.01, 1) === -0.0
end
test "with subnormal floats" do
assert Float.ceil(5.0e-324, 0) === 1.0
assert Float.ceil(5.0e-324, 1) === 0.1
assert Float.ceil(5.0e-324, 2) === 0.01
assert Float.ceil(5.0e-324, 15) === 0.000000000000001
float_assert Float.ceil(5.0e-324, 0) === 1.0
float_assert Float.ceil(5.0e-324, 1) === 0.1
float_assert Float.ceil(5.0e-324, 2) === 0.01
float_assert Float.ceil(5.0e-324, 15) === 0.000000000000001
for precision <- 0..15 do
assert Float.ceil(-5.0e-324, precision) === -0.0
float_assert Float.ceil(-5.0e-324, precision) === -0.0
end
end
end
@@ -154,28 +170,39 @@ defmodule FloatTest do
describe "round/2" do
test "with 0.0" do
for precision <- 0..15 do
assert Float.round(0.0, precision) === 0.0
assert Float.round(-0.0, precision) === -0.0
float_assert Float.round(0.0, precision) === 0.0
float_assert Float.round(-0.0, precision) === -0.0
end
end
test "with regular floats" do
assert Float.round(5.5675, 3) === 5.567
assert Float.round(-5.5674, 3) === -5.567
assert Float.round(5.5, 3) === 5.5
assert Float.round(5.5e-10, 10) === 5.0e-10
assert Float.round(5.5e-10, 8) === 0.0
assert Float.round(5.0, 0) === 5.0
float_assert Float.round(5.5675, 3) === 5.567
float_assert Float.round(-5.5674, 3) === -5.567
float_assert Float.round(5.5, 3) === 5.5
float_assert Float.round(5.5e-10, 10) === 5.0e-10
float_assert Float.round(5.5e-10, 8) === 0.0
float_assert Float.round(5.0, 0) === 5.0
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.round(1.1, 16)
end
end
test "with small floats rounded to +0.0 / -0.0" do
float_assert Float.round(0.01, 0) === 0.0
float_assert Float.round(0.01, 1) === 0.0
float_assert Float.round(-0.01, 0) === -0.0
float_assert Float.round(-0.01, 1) === -0.0
float_assert Float.round(-0.49999, 0) === -0.0
float_assert Float.round(-0.049999, 1) === -0.0
end
test "with subnormal floats" do
for precision <- 0..15 do
assert Float.round(5.0e-324, precision) === 0.0
assert Float.round(-5.0e-324, precision) === -0.0
float_assert Float.round(5.0e-324, precision) === 0.0
float_assert Float.round(-5.0e-324, precision) === -0.0
end
end
end
+5 -2
View File
@@ -187,8 +187,11 @@ defmodule IO.ANSI.DocsTest do
assert result == "line\n\e[0m\n\e[36m code\n code2\e[0m\n\e[0m\nline2\n\e[0m"
result = format_markdown("line\n```elixir\ncode\ncode2\n```\nline2\n")
assert result == "line\n\e[0m\n\e[36m code\n code2\e[0m\n\e[0m\nline2\n\e[0m"
result = format_markdown("line\n~~~elixir\ncode\n```\n~~~\nline2\n")
assert result == "line\n\e[0m\n\e[36m code\n ```\e[0m\n\e[0m\nline2\n\e[0m"
end
test "mermaid fenced code block is discarded" do
result = format_markdown("line\n```mermaid\ncode\ncode2\n```\nline2\n")
assert result == "line\n\e[0m\nline2\n\e[0m"
end
test "* list is converted" do
@@ -119,7 +119,7 @@ defmodule Kernel.BinaryTest do
Code.eval_string(~s["foo" <> 1])
end
message = ~r"left argument of <> operator inside a match"
message = ~r"cannot perform prefix match because the left operand of <> has unknown size."
assert_raise ArgumentError, message, fn ->
Code.eval_string(~s[a <> "b" = "ab"])
@@ -255,6 +255,12 @@ defmodule Kernel.BinaryTest do
assert <<1::size((^foo).bar)>> = <<1::5>>
end
test "bitsyntax size with pinned integer" do
a = 1
b = <<2, 3>>
assert <<^a, ^b::binary>> = <<1, 2, 3>>
end
test "automatic size computation of matched bitsyntax variable" do
var = "foo"
<<^var::binary, rest::binary>> = "foobar"
@@ -201,6 +201,19 @@ defmodule Kernel.TracersTest do
end
""")
assert_receive {:defmodule, %{module: Sample, function: nil}}
assert_receive {{:on_module, <<_::binary>>, :none}, %{module: Sample, function: nil}}
after
:code.purge(Sample)
:code.delete(Sample)
end
test "traces dynamic modules" do
compile_string("""
Module.create(Sample, :ok, __ENV__)
""")
assert_receive {:defmodule, %{module: Sample, function: nil}}
assert_receive {{:on_module, <<_::binary>>, :none}, %{module: Sample, function: nil}}
after
:code.purge(Sample)
@@ -217,6 +230,35 @@ defmodule Kernel.TracersTest do
assert meta[:from_interpolation]
end
test "traces bracket access" do
compile_string("""
foo = %{bar: 3}
foo[:bar]
""")
assert_receive {{:remote_function, meta, Access, :get, 2}, _env}
assert meta[:from_brackets]
compile_string("""
defmodule Foo do
@foo %{bar: 3}
def a() do
@foo[:bar]
end
end
""")
assert_receive {{:remote_function, meta, Access, :get, 2}, _env}
assert meta[:from_brackets]
compile_string("""
%{bar: 3}[:bar]
""")
assert_receive {{:remote_function, meta, Access, :get, 2}, _env}
assert meta[:from_brackets]
end
"""
# Make sure this module is compiled with column information
defmodule MacroWithColumn do
+2
View File
@@ -316,6 +316,8 @@ defmodule PathTest do
assert Path.relative_to("./foo/../bar/..", File.cwd!()) == "."
# both relative
assert Path.relative_to("usr/local/foo", ".") == "usr/local/foo"
assert Path.relative_to(".", "usr/local/foo") == "."
assert Path.relative_to("usr/local/foo", "usr/local") == "foo"
assert Path.relative_to("usr/local/foo", "etc") == "../usr/local/foo"
assert Path.relative_to(~c"usr/local/foo", "etc") == "../usr/local/foo"
@@ -161,13 +161,15 @@ defmodule Protocol.ConsolidationTest do
end
test "consolidation extracts protocols" do
protos = Protocol.extract_protocols([:code.lib_dir(:elixir, :ebin)])
protos = Protocol.extract_protocols([Application.app_dir(:elixir, "ebin")])
assert Enumerable in protos
assert Inspect in protos
end
test "consolidation extracts implementations with charlist path" do
protos = Protocol.extract_impls(Enumerable, [:code.lib_dir(:elixir, :ebin)])
protos =
Protocol.extract_impls(Enumerable, [to_charlist(Application.app_dir(:elixir, "ebin"))])
assert List in protos
assert Function in protos
end
+26
View File
@@ -277,6 +277,32 @@ defmodule URITest do
test "preserves an empty query" do
assert URI.new!("http://foo.com/?").query == ""
end
test "without scheme, undefined port after host translates to nil" do
assert URI.new!("//https://www.example.com") ==
%URI{
scheme: nil,
userinfo: nil,
host: "https",
port: nil,
path: "//www.example.com",
query: nil,
fragment: nil
}
end
test "with scheme, undefined port after host translates to nil" do
assert URI.new!("myscheme://myhost:/path/info") ==
%URI{
scheme: "myscheme",
userinfo: nil,
host: "myhost",
port: nil,
path: "/path/info",
query: nil,
fragment: nil
}
end
end
test "http://http://http://@http://http://?http://#http://" do
+9 -9
View File
@@ -42,16 +42,20 @@ defmodule ExUnit.CaptureLog do
@doc """
Captures Logger messages generated when evaluating `fun`.
Returns the binary which is the captured output.
Returns the binary which is the captured output. The captured log
messages will be formatted using `Logger.default_formatter/1`. Any
option, besides the `:level`, will be forwarded as an override to
the default formatter.
This function mutes the `:console` backend and captures any log
This function mutes the default logger handler and captures any log
messages sent to Logger from the calling processes. It is possible
to ensure explicit log messages from other processes are captured
by waiting for their exit or monitor signal.
Note that when the `async` is set to `true`, the messages from another
test might be captured. This is OK as long you consider such cases in
your assertions.
Note that when the `async` is set to `true` on `use ExUnit.Case`,
messages from other tests might be captured. This is OK as long
you consider such cases in your assertions, typically by using
the `=~/2` operator to perform partial matches.
It is possible to configure the level to capture with `:level`,
which will set the capturing level for the duration of the
@@ -63,10 +67,6 @@ defmodule ExUnit.CaptureLog do
configured in this function, no message will be captured.
The behaviour is undetermined if async tests change Logger level.
The format, metadata and colors can be configured with `:format`,
`:metadata` and `:colors` respectively. These three options
defaults to the `:console` backend configuration parameters.
To get the result of the evaluation along with the captured log,
use `with_log/2`.
"""
+2 -2
View File
@@ -286,10 +286,10 @@ 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_value(pin_value, right, env)
{diff, post_env} = diff_value(pin_value, right, %{env | context: :===})
diff_left = update_diff_meta(pin, not diff.equivalent?)
{%{diff | left: diff_left}, post_env}
{%{diff | left: diff_left}, %{post_env | context: :match}}
end
# Vars
+9 -9
View File
@@ -96,9 +96,9 @@ defmodule ExUnit.DocTest do
values are treated as comments in Elixir code due to the leading
`#` sign, they require special care when being used in doctests.
Imagine you have a map that contains a MapSet and is printed as:
Imagine you have a map that contains a `DateTime` and is printed as:
%{users: #MapSet<[:foo, :bar]>}
%{datetime: #DateTime<2023-06-26 09:30:00+09:00 JST Asia/Tokyo>}
If you try to match on such an expression, `doctest` will fail to compile.
There are two ways to resolve this.
@@ -106,20 +106,20 @@ defmodule ExUnit.DocTest do
The first is to rely on the fact that doctest can compare internal
structures as long as they are at the root. So one could write:
iex> map = %{users: Enum.into([:foo, :bar], MapSet.new())}
iex> map.users
#MapSet<[:foo, :bar]>
iex> map = %{datetime: DateTime.from_naive!(~N[2023-06-26T09:30:00], "Asia/Tokyo")}
iex> map.datetime
#DateTime<2023-06-26 09:30:00+09:00 JST Asia/Tokyo>
Whenever a doctest starts with "#Name<", `doctest` will perform a string
comparison. For example, the above test will perform the following match:
inspect(map.users) == "#MapSet<[:foo, :bar]>"
inspect(map.datetime) == "#DateTime<2023-06-26 09:30:00+09:00 JST Asia/Tokyo>"
Alternatively, since doctest results are actually evaluated, you can have
the MapSet building expression as the doctest result:
the `DateTime` building expression as the doctest result:
iex> %{users: Enum.into([:foo, :bar], MapSet.new())}
%{users: Enum.into([:foo, :bar], MapSet.new())}
iex> %{datetime: DateTime.from_naive!(~N[2023-06-26T09:30:00], "Asia/Tokyo")}
%{datetime: DateTime.from_naive!(~N[2023-06-26T09:30:00], "Asia/Tokyo")}
The downside of this approach is that the doctest result is not really
what users would see in the terminal.
-2
View File
@@ -120,8 +120,6 @@ defmodule ExUnit.Formatter do
* `:error_info` - Should format error information.
* `:error_info` - Should format error information.
* `:test_module_info` - Should format test module information. The message returned
when this key is passed precedes messages such as `"failure on setup_all callback [...]"`.
+8 -1
View File
@@ -216,7 +216,14 @@ defmodule ExUnit.CaptureIOTest do
end)
capture_io("\"a", fn ->
assert :io.scan_erl_form(~c">") == {:error, {1, :erl_scan, {:string, 34, ~c"a"}}, 1}
error =
if System.otp_release() >= "27" do
{1, :erl_scan, {:unterminated, :string, ~c"a"}}
else
{1, :erl_scan, {:string, 34, ~c"a"}}
end
assert :io.scan_erl_form(~c">") == {:error, error, 1}
assert :io.scan_erl_form(~c">") == {:eof, 1}
end)
+9 -1
View File
@@ -251,7 +251,12 @@ defmodule ExUnit.DiffTest do
refute_diff([:a, [:c, :b]] = [:a, [:b, :c]], "[:a, [-:c-, :b]]", "[:a, [:b, +:c+]]")
refute_diff(:a = [:a, [:b, :c]], "-:a-", "+[:a, [:b, :c]]+")
pins = %{{:a, nil} => :a, {:b, nil} => :b, {:list_ab, nil} => [:a, :b]}
pins = %{
{:a, nil} => :a,
{:b, nil} => :b,
{:list_ab, nil} => [:a, :b],
{:list_tuple, nil} => [{:foo}]
}
assert_diff(x = [], [x: []], pins)
assert_diff(x = [:a, :b], [x: [:a, :b]], pins)
@@ -282,6 +287,9 @@ defmodule ExUnit.DiffTest do
refute_diff([:a, :b] = :a, "-[:a, :b]-", "+:a+")
refute_diff([:foo] = [:foo, {:a, :b, :c}], "[:foo]", "[:foo, +{:a, :b, :c}+]")
refute_diff([{:foo}] = [{:bar}], "[{-:foo-}]", "[{+:bar+}]")
refute_diff(^list_tuple = [{:bar}], "-^list_tuple-", "[{+:bar+}]", pins)
end
test "improper lists" do
+49 -21
View File
@@ -69,17 +69,22 @@ defmodule IEx.Helpers do
import IEx, only: [dont_display_result: 0]
@doc """
Recompiles the current Mix project.
Recompiles the current Mix project or Mix install
dependencies.
This helper only works when IEx is started with a Mix
project, for example, `iex -S mix`. Note this function
simply recompiles Elixir modules, without reloading
configuration, recompiling dependencies, or restarting
applications.
This helper requires either `Mix.install/2` to have been
called within the current IEx session or for IEx to be
started alongside, for example, `iex -S mix`.
Therefore, any long running process may crash on recompilation,
as changed modules will be temporarily removed and recompiled,
without going through the proper code change callback.
In the `Mix.install/1` case, it will recompile any outdated
path dependency declared during install. Within a project,
it will recompile any outdated module.
Note this function simply recompiles Elixir modules, without
reloading configuration or restarting applications. This means
any long running process may crash on recompilation, as changed
modules will be temporarily removed and recompiled, without
going through the proper code change callback.
If you want to reload a single module, consider using
`r(ModuleName)` instead.
@@ -93,23 +98,46 @@ defmodule IEx.Helpers do
"""
def recompile(options \\ []) do
if mix_started?() do
config = Mix.Project.config()
consolidation = Mix.Project.consolidation_path(config)
reenable_tasks(config)
cond do
not mix_started?() ->
IO.puts(IEx.color(:eval_error, "Mix is not running. Please start IEx with: iex -S mix"))
:error
force? = Keyword.get(options, :force, false)
args = ["--purge-consolidation-path-if-stale", "--return-errors", consolidation]
args = if force?, do: ["--force" | args], else: args
Mix.installed?() ->
Mix.in_install_project(fn ->
# TODO: remove this once Mix requires Hex with the fix from
# https://github.com/hexpm/hex/pull/1015
# Context: Mix.install/1 starts :hex if necessary and stops
# it afterwards. Calling compile here may require hex to be
# started and that should happen automatically, but because
# of a bug it is not (fixed in the linked PR).
_ = Application.ensure_all_started(:hex)
{result, _} = Mix.Task.run("compile", args)
result
else
IO.puts(IEx.color(:eval_error, "Mix is not running. Please start IEx with: iex -S mix"))
:error
do_recompile(options)
# Just as with Mix.install/2 we clear all task invocations,
# so that we can recompile the dependencies again next time
Mix.Task.clear()
:ok
end)
true ->
do_recompile(options)
end
end
defp do_recompile(options) do
config = Mix.Project.config()
consolidation = Mix.Project.consolidation_path(config)
reenable_tasks(config)
force? = Keyword.get(options, :force, false)
args = ["--purge-consolidation-path-if-stale", "--return-errors", consolidation]
args = if force?, do: ["--force" | args], else: args
{result, _} = Mix.Task.run("compile", args)
result
end
defp mix_started? do
List.keyfind(Application.started_applications(), :mix, 0) != nil
end
+43 -10
View File
@@ -144,7 +144,7 @@ defmodule IEx.HelpersTest do
describe "open" do
@iex_helpers "iex/lib/iex/helpers.ex"
@elixir_erl "elixir/src/elixir.erl"
@lists_erl "#{:code.lib_dir(:stdlib, :src)}/lists.erl"
@lists_erl Application.app_dir(:stdlib, "src/lists.erl")
@httpc_erl "src/http_client/httpc.erl"
@editor System.get_env("ELIXIR_EDITOR")
@@ -332,17 +332,26 @@ defmodule IEx.HelpersTest do
assert help =~ "Welcome to Interactive Elixir"
end
@tag :erlang_doc
test "prints Erlang module documentation" do
captured = capture_io(fn -> h(:timer) end)
assert captured =~ "This module provides useful functions related to time."
end
@tag :erlang_doc
test "prints Erlang module function specs" do
captured = capture_io(fn -> h(:timer.sleep() / 1) end)
assert captured =~ ":timer.sleep/1"
assert captured =~ "-spec sleep(Time) -> ok when Time :: timeout()."
# TODO Fix for OTP 27 once specs are available
if System.otp_release() < "27" do
assert captured =~ "-spec sleep(Time) -> ok when Time :: timeout()."
else
assert captured =~ "sleep(Time)"
end
end
@tag :erlang_doc
test "handles non-existing Erlang module function" do
captured = capture_io(fn -> h(:timer.baz() / 1) end)
assert captured =~ "No documentation for :timer.baz was found"
@@ -802,7 +811,7 @@ defmodule IEx.HelpersTest do
assert capture_io(fn -> b(NoMix.run()) end) == "Could not load module NoMix, got: nofile\n"
assert capture_io(fn -> b(Exception.message() / 1) end) ==
assert capture_io(fn -> b(Exception.message() / 1) end) =~
"@callback message(t()) :: String.t()\n\n"
assert capture_io(fn -> b(:gen_server.handle_cast() / 2) end) =~
@@ -1008,23 +1017,47 @@ defmodule IEx.HelpersTest do
cleanup_modules([TypeSample])
end
test "prints all types in erlang module" do
@tag :erlang_doc
test "prints all types in Erlang module" do
captured = capture_io(fn -> t(:queue) end)
assert captured =~ "-type queue() :: queue(_)"
assert captured =~ "-opaque queue(Item)"
# TODO Fix for OTP 27 once specs are available
if System.otp_release() < "27" do
assert captured =~ "-type queue() :: queue(_)"
assert captured =~ "-opaque queue(Item)"
else
assert captured =~ "queue()"
assert captured =~ "queue(Item)"
end
end
test "prints single type from erlang module" do
@tag :erlang_doc
test "prints single type from Erlang module" do
captured = capture_io(fn -> t(:erlang.iovec()) end)
assert captured =~ "-type iovec() :: [binary()]"
# TODO Fix for OTP 27 once specs are available
if System.otp_release() < "27" do
assert captured =~ "-type iovec() :: [binary()]"
else
assert captured =~ "iovec()"
end
assert captured =~ "A list of binaries."
captured = capture_io(fn -> t(:erlang.iovec() / 0) end)
assert captured =~ "-type iovec() :: [binary()]"
# TODO Fix for OTP 27 once specs are available
if System.otp_release() < "27" do
assert captured =~ "-type iovec() :: [binary()]"
else
assert captured =~ "iovec()"
end
assert captured =~ "A list of binaries."
end
test "handles non-existing types from erlang module" do
@tag :erlang_doc
test "handles non-existing types from Erlang module" do
captured = capture_io(fn -> t(:erlang.foo()) end)
assert captured =~ "No type information for :erlang.foo was found or :erlang.foo is private"
+9 -1
View File
@@ -7,11 +7,19 @@ IEx.configure(colors: [enabled: false])
{line_exclude, line_include} =
if line = System.get_env("LINE"), do: {[:test], [line: line]}, else: {[], []}
erlang_doc_exclude =
if match?({:docs_v1, _, _, _, %{}, _, _}, Code.fetch_docs(:array)) do
[]
else
IO.puts("Erlang/OTP compiled without docs, some tests are excluded...")
[:erlang_doc]
end
ExUnit.start(
assert_receive_timeout: assert_timeout,
trace: !!System.get_env("TRACE"),
include: line_include,
exclude: line_exclude
exclude: line_exclude ++ erlang_doc_exclude
)
defmodule IEx.Case do
+4 -1
View File
@@ -867,9 +867,12 @@ defmodule Logger do
defp update_translators(updater) do
:elixir_config.serial(fn ->
translators = updater.(Application.fetch_env!(:logger, :translators))
Application.put_env(:logger, :translators, translators)
with %{filters: filters} <- :logger.get_primary_config(),
{{_, {fun, config}}, filters} <- List.keytake(filters, :logger_translator, 0) do
config = update_in(config.translators, updater)
config = %{config | translators: translators}
:ok = :logger.set_primary_config(:filters, filters ++ [logger_translator: {fun, config}])
end
end)
+7 -5
View File
@@ -404,11 +404,9 @@ defmodule Logger.Translator do
end
defp report_crash(min_level, crashed, extra, linked) do
[
{:pid, pid},
{:registered_name, name},
{:error_info, {kind, reason, stack}} | crashed
] = crashed
{pid, crashed} = Keyword.pop_first(crashed, :pid)
{name, crashed} = Keyword.pop_first(crashed, :registered_name)
{{kind, reason, stack}, crashed} = Keyword.pop_first(crashed, :error_info)
dictionary = crashed[:dictionary]
reason = Exception.normalize(kind, reason, stack)
@@ -459,6 +457,10 @@ defmodule Logger.Translator do
[prefix, "Ancestors: ", inspect(ancestors) | crash_info(min_level, debug, prefix)]
end
defp crash_info(min_level, [{:process_label, _} | info], prefix) do
crash_info(min_level, info, prefix)
end
defp crash_info(:debug, debug, prefix) do
for {key, value} <- debug do
crash_debug(key, value, prefix)
@@ -58,7 +58,9 @@ defmodule Logger.Backends.HandlerTest do
end
test "add_translator/1 and remove_translator/1 for logger formats" do
refute {CustomTranslator, :t} in Application.fetch_env!(:logger, :translators)
assert Logger.add_translator({CustomTranslator, :t})
assert {CustomTranslator, :t} in Application.fetch_env!(:logger, :translators)
assert capture_log(fn ->
:logger.info(~c"hello: ~p", [:ok])
+140 -30
View File
@@ -352,8 +352,6 @@ defmodule Mix do
* `MIX_INSTALL_DIR` - (since v1.12.0) specifies directory where `Mix.install/2` keeps
install cache
* `MIX_INSTALL_FORCE` - (since v1.13.0) runs `Mix.install/2` with empty install cache
* `MIX_PATH` - appends extra code paths
* `MIX_PROFILE` - a list of comma-separated Mix tasks to profile the time spent on
@@ -379,6 +377,8 @@ defmodule Mix do
"""
@mix_install_project __MODULE__.InstallProject
@mix_install_app :mix_install
@mix_install_app_string Atom.to_string(@mix_install_app)
use Application
@@ -661,9 +661,6 @@ defmodule Mix do
This function can only be called outside of a Mix project and only with the
same dependencies in the given VM.
**Note:** this feature is currently experimental and it may change
in future releases.
## Options
* `:force` - if `true`, runs with empty install cache. This is useful when you want
@@ -779,6 +776,21 @@ defmodule Mix do
The contents inside `defmodule` will only be expanded and executed
after `Mix.install/2` runs, which means that any struct, macros,
and imports will be correctly handled.
## Environment variables
The `MIX_INSTALL_DIR` environment variable configures the directory that
caches all `Mix.install/2`.
The `MIX_INSTALL_FORCE` is available since Elixir v1.13.0 and forces
`Mix.install/2` to discard any previously cached entry of the current install.
The `MIX_INSTALL_RESTORE_PROJECT_DIR` environment variable may be specified
since Elixir v1.16.2. It should point to a previous installation directory,
which can be obtained with `Mix.install_project_dir/0` (after calling `Mix.install/2`).
Using a restore dir may speed up the installation, since matching dependencies
do not need be refetched nor recompiled. This environment variable is ignored
if `:force` is enabled.
"""
@doc since: "1.12.0"
def install(deps, opts \\ [])
@@ -848,50 +860,49 @@ defmodule Mix do
Application.put_all_env(config, persistent: true)
System.put_env(system_env)
install_dir = install_dir(id)
install_project_dir = install_project_dir(id)
if Keyword.fetch!(opts, :verbose) do
Mix.shell().info("Mix.install/2 using #{install_dir}")
Mix.shell().info("Mix.install/2 using #{install_project_dir}")
end
if force? do
File.rm_rf!(install_dir)
File.rm_rf!(install_project_dir)
end
config = [
version: "0.1.0",
build_embedded: false,
build_per_environment: true,
build_path: "_build",
lockfile: "mix.lock",
deps_path: "deps",
dynamic_config = [
deps: deps,
app: :mix_install,
erlc_paths: [],
elixirc_paths: [],
compilers: [],
consolidate_protocols: consolidate_protocols?,
config_path: config_path,
prune_code_paths: false
config_path: config_path
]
config = install_project_config(dynamic_config)
started_apps = Application.started_applications()
:ok = Mix.ProjectStack.push(@mix_install_project, config, "nofile")
build_dir = Path.join(install_dir, "_build")
build_dir = Path.join(install_project_dir, "_build")
external_lockfile = expand_path(opts[:lockfile], deps, :lockfile, "mix.lock")
try do
first_build? = not File.dir?(build_dir)
File.mkdir_p!(install_dir)
File.cd!(install_dir, fn ->
restore_dir = System.get_env("MIX_INSTALL_RESTORE_PROJECT_DIR")
if first_build? and restore_dir != nil and not force? do
File.cp_r(restore_dir, install_project_dir)
remove_dep(install_project_dir, @mix_install_app_string)
end
File.mkdir_p!(install_project_dir)
File.cd!(install_project_dir, fn ->
if config_path do
Mix.Task.rerun("loadconfig")
end
cond do
external_lockfile ->
md5_path = Path.join(install_dir, "merge.lock.md5")
md5_path = Path.join(install_project_dir, "merge.lock.md5")
old_md5 =
case File.read(md5_path) do
@@ -902,7 +913,7 @@ defmodule Mix do
new_md5 = external_lockfile |> File.read!() |> :erlang.md5()
if old_md5 != new_md5 do
lockfile = Path.join(install_dir, "mix.lock")
lockfile = Path.join(install_project_dir, "mix.lock")
old_lock = Mix.Dep.Lock.read(lockfile)
new_lock = Mix.Dep.Lock.read(external_lockfile)
Mix.Dep.Lock.write(Map.merge(old_lock, new_lock), file: lockfile)
@@ -940,13 +951,22 @@ defmodule Mix do
end
end
Mix.State.put(:installed, id)
if restore_dir do
remove_leftover_deps(install_project_dir)
end
Mix.State.put(:installed, {id, dynamic_config})
:ok
after
Mix.ProjectStack.pop()
# Clear all tasks invoked during installation, since there
# is no reason to keep this in memory. Additionally this
# allows us to rerun tasks for the dependencies later on,
# such as recompilation
Mix.Task.clear()
end
^id when not force? ->
{^id, _dynamic_config} when not force? ->
:ok
_ ->
@@ -972,7 +992,35 @@ defmodule Mix do
Path.join(app_dir, relative_path)
end
defp install_dir(cache_id) do
defp remove_leftover_deps(install_project_dir) do
build_lib_dir = Path.join([install_project_dir, "_build", "dev", "lib"])
deps = File.ls!(build_lib_dir)
loaded_deps =
for {app, _description, _version} <- Application.loaded_applications(),
into: MapSet.new(),
do: Atom.to_string(app)
# We want to keep :mix_install, but it has no application
loaded_deps = MapSet.put(loaded_deps, @mix_install_app_string)
for dep <- deps, not MapSet.member?(loaded_deps, dep) do
remove_dep(install_project_dir, dep)
end
end
defp remove_dep(install_project_dir, dep) do
build_lib_dir = Path.join([install_project_dir, "_build", "dev", "lib"])
deps_dir = Path.join(install_project_dir, "deps")
build_path = Path.join(build_lib_dir, dep)
File.rm_rf(build_path)
dep_path = Path.join(deps_dir, dep)
File.rm_rf(dep_path)
end
defp install_project_dir(cache_id) do
install_root =
System.get_env("MIX_INSTALL_DIR") ||
Path.join(Mix.Utils.mix_cache(), "installs")
@@ -981,12 +1029,74 @@ defmodule Mix do
Path.join([install_root, version, cache_id])
end
defp install_project_config(dynamic_config) do
[
version: "0.1.0",
build_embedded: false,
build_per_environment: true,
build_path: "_build",
lockfile: "mix.lock",
deps_path: "deps",
app: @mix_install_app,
erlc_paths: [],
elixirc_paths: [],
compilers: [],
prune_code_paths: false
] ++ dynamic_config
end
@doc false
def in_install_project(fun) do
case safe_get_installed() do
{id, dynamic_config} ->
config = install_project_config(dynamic_config)
install_project_dir = install_project_dir(id)
File.cd!(install_project_dir, fn ->
:ok = Mix.ProjectStack.push(@mix_install_project, config, "nofile")
try do
fun.()
after
Mix.ProjectStack.pop()
end
end)
nil ->
Mix.raise("trying to call Mix.in_install_project/1, but Mix.install/2 was never called")
end
end
@doc """
Returns the directory where the current `Mix.install/2` project
resides.
"""
@doc since: "1.16.2"
@spec install_project_dir() :: Path.t() | nil
def install_project_dir() do
case safe_get_installed() do
{id, _dynamic_config} -> install_project_dir(id)
nil -> nil
end
end
@doc """
Returns whether `Mix.install/2` was called in the current node.
"""
@doc since: "1.13.0"
def installed? do
Mix.State.get(:installed) != nil
safe_get_installed() != nil
end
defp safe_get_installed() do
if mix_started?() do
Mix.State.get(:installed)
end
end
defp mix_started?() do
Process.whereis(Mix.State) != nil
end
defp stop_apps([]), do: :ok
+8
View File
@@ -131,6 +131,14 @@ defmodule Mix.SCM do
Mix.State.get(:scm)
end
@doc """
Deletes the given SCM from the list of available SCMs.
"""
@doc since: "1.16.2"
def delete(mod) when is_atom(mod) do
Mix.State.update(:scm, &List.delete(&1, mod))
end
@doc """
Prepends the given SCM module to the list of available SCMs.
"""
+5 -7
View File
@@ -43,7 +43,11 @@ defmodule Mix.Tasks.Compile.All do
Code.delete_paths(current_paths -- loaded_paths)
end
Code.prepend_paths(loaded_paths -- current_paths, cache: true)
# Add the current compilation path. compile.elixir and compile.erlang
# will also add this path, but only if they run, so we always add it
# here too. Furthermore, we don't cache it as we may still write to it.
compile_path = to_charlist(Mix.Project.compile_path())
Code.prepend_paths([compile_path | loaded_paths -- current_paths], cache: true)
result =
if "--no-compile" in args do
@@ -64,12 +68,6 @@ defmodule Mix.Tasks.Compile.All do
Mix.AppLoader.write_cache(app_cache, Map.new(loaded_modules))
end
# Add the current compilation path. compile.elixir and compile.erlang
# will also add this path, but only if they run, so we always add it
# here too. Furthermore, we don't cache it as we may still write to it.
compile_path = to_charlist(Mix.Project.compile_path())
_ = Code.prepend_path(compile_path)
unless "--no-app-loading" in args do
app = config[:app]
+1 -1
View File
@@ -1021,7 +1021,7 @@ defmodule Mix.Tasks.Release do
* `--no-deps-check` - does not check dependencies
* `--no-elixir-version-check` - does not check Elixir version
* `--no-compile` - does not compile before assembling the release
* `--overwrite` - if there is an existing release version, overwrite it
* `--overwrite` - overwrite existing files instead of prompting the user for action
* `--path` - the path of the release
* `--quiet` - does not write progress to the standard output
* `--version` - the version of the release
+4
View File
@@ -12,10 +12,14 @@ defmodule Mix.SCMTest do
test "prepends an SCM" do
Mix.SCM.prepend(Hello)
assert Enum.at(Mix.SCM.available(), 0) == Hello
Mix.SCM.delete(Hello)
assert Hello not in Mix.SCM.available()
end
test "appends an SCM" do
Mix.SCM.append(Hello)
assert Enum.at(Mix.SCM.available(), -1) == Hello
Mix.SCM.delete(Hello)
assert Hello not in Mix.SCM.available()
end
end
+23 -7
View File
@@ -19,6 +19,14 @@ defmodule Mix.Tasks.Compile.ElixirTest do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", """
defmodule A, do: :ok
# Also make sure that we access the ebin directory during compilation
true = to_charlist(Mix.Project.compile_path()) in :code.get_path()
""")
Mix.Tasks.Compile.Elixir.run(["--verbose"])
assert File.regular?("_build/shared/lib/sample/ebin/Elixir.A.beam")
@@ -32,6 +40,14 @@ defmodule Mix.Tasks.Compile.ElixirTest do
test "compiles a project with per environment build" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", """
defmodule A, do: :ok
# Also make sure that we access the ebin directory during compilation
true = to_charlist(Mix.Project.compile_path()) in :code.get_path()
""")
Mix.Tasks.Compile.Elixir.run(["--verbose"])
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam")
@@ -772,7 +788,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles mtime changed files if content changed but not length" do
test "recompiles mtime changed files if content changed but not length" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
@@ -872,7 +888,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles size changed files" do
test "recompiles size changed files" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
past = @old_time
@@ -894,7 +910,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles dependent changed modules" do
test "recompiles dependent changed modules" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", "defmodule A, do: B.module_info()")
@@ -914,7 +930,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles dependent changed modules without beam files" do
test "recompiles dependent changed modules without beam files" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
@@ -943,7 +959,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
Code.put_compiler_option(:ignore_module_conflict, false)
end
test "compiles dependent changed modules even on removal" do
test "recompiles dependent changed modules even on removal" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", "defmodule A, do: B.module_info()")
@@ -964,7 +980,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles dependent changed on conflict" do
test "recompiles dependent changed on conflict" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
@@ -991,7 +1007,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "compiles dependent changed external resources" do
test "recompiles dependent changed external resources" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
tmp = tmp_path("c.eex")
+148 -34
View File
@@ -39,7 +39,7 @@ defmodule MixTest do
assert Protocol.consolidated?(InstallTest.Protocol)
assert_received {:mix_shell, :info, ["==> install_test"]}
assert_received {:mix_shell, :info, ["Compiling 1 file (.ex)"]}
assert_received {:mix_shell, :info, ["Compiling 2 files (.ex)"]}
assert_received {:mix_shell, :info, ["Generated install_test app"]}
refute_received _
@@ -67,7 +67,7 @@ defmodule MixTest do
assert File.dir?(Path.join(tmp_dir, "installs"))
assert_received {:mix_shell, :info, ["==> install_test"]}
assert_received {:mix_shell, :info, ["Compiling 1 file (.ex)"]}
assert_received {:mix_shell, :info, ["Compiling 2 files (.ex)"]}
assert_received {:mix_shell, :info, ["Generated install_test app"]}
refute_received _
@@ -263,30 +263,26 @@ defmodule MixTest do
[
{:git_repo, git: fixture_path("git_repo")}
],
lockfile: lockfile,
verbose: true
lockfile: lockfile
)
assert_received {:mix_shell, :info, ["* Getting git_repo " <> _]}
assert_received {:mix_shell, :info, ["Mix.install/2 using " <> install_dir]}
assert File.read!(Path.join(install_dir, "mix.lock")) =~ rev
after
purge([GitRepo, GitRepo.MixProject])
install_project_dir = Mix.install_project_dir()
assert File.read!(Path.join(install_project_dir, "mix.lock")) =~ rev
end
test ":lockfile merging", %{tmp_dir: tmp_dir} do
[rev1, rev2 | _] = get_git_repo_revs("git_repo")
Mix.install(
[
{:git_repo, git: fixture_path("git_repo")}
],
verbose: true
)
Mix.install([
{:git_repo, git: fixture_path("git_repo")}
])
assert_received {:mix_shell, :info, ["* Getting git_repo " <> _]}
assert_received {:mix_shell, :info, ["Mix.install/2 using " <> install_dir]}
assert File.read!(Path.join(install_dir, "mix.lock")) =~ rev1
install_project_dir = Mix.install_project_dir()
assert File.read!(Path.join(install_project_dir, "mix.lock")) =~ rev1
Mix.Project.push(GitApp)
lockfile = Path.join(tmp_dir, "lock")
@@ -300,9 +296,7 @@ defmodule MixTest do
lockfile: lockfile
)
assert File.read!(Path.join(install_dir, "mix.lock")) =~ rev1
after
purge([GitRepo, GitRepo.MixProject])
assert File.read!(Path.join(install_project_dir, "mix.lock")) =~ rev1
end
test ":lockfile with application name", %{tmp_dir: tmp_dir} do
@@ -318,15 +312,12 @@ defmodule MixTest do
{:install_test, path: Path.join(tmp_dir, "install_test")},
{:git_repo, git: fixture_path("git_repo")}
],
lockfile: :install_test,
verbose: true
lockfile: :install_test
)
assert_received {:mix_shell, :info, ["* Getting git_repo " <> _]}
assert_received {:mix_shell, :info, ["Mix.install/2 using " <> install_dir]}
assert File.read!(Path.join(install_dir, "mix.lock")) =~ rev
after
purge([GitRepo, GitRepo.MixProject])
install_project_dir = Mix.install_project_dir()
assert File.read!(Path.join(install_project_dir, "mix.lock")) =~ rev
end
test ":lockfile that does not exist" do
@@ -335,6 +326,73 @@ defmodule MixTest do
end
end
test "restore dir", %{tmp_dir: tmp_dir} do
with_cleanup(fn ->
Mix.install([
{:git_repo, git: fixture_path("git_repo")}
])
assert_received {:mix_shell, :info, ["* Getting git_repo " <> _]}
assert_received {:mix_shell, :info, ["==> git_repo"]}
assert_received {:mix_shell, :info, ["Compiling 1 file (.ex)"]}
assert_received {:mix_shell, :info, ["Generated git_repo app"]}
refute_received _
install_project_dir = Mix.install_project_dir()
build_lib_path = Path.join([install_project_dir, "_build", "dev", "lib"])
deps_path = Path.join([install_project_dir, "deps"])
assert File.ls!(build_lib_path) |> Enum.sort() == ["git_repo", "mix_install"]
assert File.ls!(deps_path) == ["git_repo"]
System.put_env("MIX_INSTALL_RESTORE_PROJECT_DIR", install_project_dir)
end)
# Adding a dependency
with_cleanup(fn ->
Mix.install([
{:git_repo, git: fixture_path("git_repo")},
{:install_test, path: Path.join(tmp_dir, "install_test")}
])
assert_received {:mix_shell, :info, ["==> install_test"]}
assert_received {:mix_shell, :info, ["Compiling 2 files (.ex)"]}
assert_received {:mix_shell, :info, ["Generated install_test app"]}
refute_received _
install_project_dir = Mix.install_project_dir()
build_lib_path = Path.join([install_project_dir, "_build", "dev", "lib"])
deps_path = Path.join([install_project_dir, "deps"])
assert File.ls!(build_lib_path) |> Enum.sort() ==
["git_repo", "install_test", "mix_install"]
assert File.ls!(deps_path) == ["git_repo"]
System.put_env("MIX_INSTALL_RESTORE_PROJECT_DIR", install_project_dir)
end)
# Removing a dependency
with_cleanup(fn ->
Mix.install([
{:install_test, path: Path.join(tmp_dir, "install_test")}
])
refute_received _
install_project_dir = Mix.install_project_dir()
build_lib_path = Path.join([install_project_dir, "_build", "dev", "lib"])
deps_path = Path.join([install_project_dir, "deps"])
assert File.ls!(build_lib_path) |> Enum.sort() == ["install_test", "mix_install"]
assert File.ls!(deps_path) == []
end)
after
System.delete_env("MIX_INSTALL_RESTORE_PROJECT_DIR")
end
test "installed?", %{tmp_dir: tmp_dir} do
refute Mix.installed?()
@@ -345,20 +403,42 @@ defmodule MixTest do
assert Mix.installed?()
end
test "in_install_project", %{tmp_dir: tmp_dir} do
Mix.install([
{:install_test, path: Path.join(tmp_dir, "install_test")}
])
Mix.in_install_project(fn ->
config = Mix.Project.config()
assert [{:install_test, [path: _]}] = config[:deps]
end)
end
test "in_install_project recompile", %{tmp_dir: tmp_dir} do
Mix.install([
{:install_test, path: Path.join(tmp_dir, "install_test")}
])
File.write!("#{tmp_dir}/install_test/lib/install_test.ex", """
defmodule InstallTest do
def hello do
:universe
end
end
""")
Mix.in_install_project(fn ->
Mix.Task.run("compile")
assert apply(InstallTest, :hello, []) == :universe
end)
end
defp test_project(%{tmp_dir: tmp_dir}) do
path = :code.get_path()
on_exit(fn ->
:code.set_path(path)
purge([InstallTest, InstallTest.MixProject, InstallTest.Protocol])
ExUnit.CaptureLog.capture_log(fn ->
Application.stop(:git_repo)
Application.unload(:git_repo)
Application.stop(:install_test)
Application.unload(:install_test)
end)
cleanup_deps()
end)
Mix.State.put(:installed, nil)
@@ -384,7 +464,9 @@ defmodule MixTest do
:world
end
end
""")
File.write!("#{tmp_dir}/install_test/lib/install_test_protocol.ex", """
defprotocol InstallTest.Protocol do
def foo(x)
end
@@ -392,5 +474,37 @@ defmodule MixTest do
[tmp_dir: tmp_dir]
end
defp with_cleanup(fun) do
path = :code.get_path()
try do
fun.()
after
:code.set_path(path)
cleanup_deps()
Mix.State.clear_cache()
Mix.State.put(:installed, nil)
end
end
defp cleanup_deps() do
purge([
GitRepo,
GitRepo.MixProject,
InstallTest,
InstallTest.MixProject,
InstallTest.Protocol
])
ExUnit.CaptureLog.capture_log(fn ->
Application.stop(:git_repo)
Application.unload(:git_repo)
Application.stop(:install_test)
Application.unload(:install_test)
end)
end
end
end