Compare commits

...
10 Commits
21 changed files with 329 additions and 116 deletions
+28
View File
@@ -234,6 +234,34 @@ These additions offer greater transparency into the components and licenses of e
This work was performed by [Jonatan Männchen](https://maennchen.dev) and sponsored by the [Erlang Ecosystem Foundation](https://erlef.org).
## v1.19.3 (2025-11-13)
### 1. Enhancements
#### Elixir
* [Kernel] Support /E modifier for regular expressions in config files
#### Mix
* [mix compile] Allow forcing specific compilers, such as `--force-elixir`, `--force-app`, etc
* [mix help app:APP] Support showing helps for apps in Elixir and Erlang standard libraries
### 2. Bug fixes
#### Elixir
* [IO] Fix dialyzer warning on `IO.inspect :label`
#### ExUnit
* [ExUnit.Case] Fix crash when formatting errors caused by a linked/trapped exit during `setup_all`
#### Mix
* [mix compile.app] Ensure functions in the format `&Mod.fun/arity` can be written to .app files
* [mix compile.app] Ensure strings with Unicode characters can be written to .app files
## v1.19.2 (2025-11-02)
### 1. Enhancements
+1 -1
View File
@@ -1 +1 @@
1.19.2
1.19.3
+1 -1
View File
@@ -6,7 +6,7 @@
set -e
ELIXIR_VERSION=1.19.2
ELIXIR_VERSION=1.19.3
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
+1 -1
View File
@@ -4,7 +4,7 @@
:: SPDX-FileCopyrightText: 2021 The Elixir Team
:: SPDX-FileCopyrightText: 2012 Plataformatec
set ELIXIR_VERSION=1.19.2
set ELIXIR_VERSION=1.19.3
if ""%1""=="""" if ""%2""=="""" goto documentation
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
+1
View File
@@ -569,6 +569,7 @@ defimpl Inspect, for: Regex do
defp translate_options([:firstline | t], acc), do: translate_options(t, [?f | acc])
defp translate_options([:ungreedy | t], acc), do: translate_options(t, [?U | acc])
defp translate_options([:multiline | t], acc), do: translate_options(t, [?m | acc])
defp translate_options([:export | t], acc), do: translate_options(t, [?E | acc])
defp translate_options([], acc), do: acc
defp translate_options(_t, _acc), do: :error
+31 -5
View File
@@ -101,6 +101,13 @@ defmodule Regex do
* `:ungreedy` (U) - inverts the "greediness" of the regexp
(the previous `r` option is deprecated in favor of `U`)
* `:export` (E) (since Elixir 1.20) - uses an exported pattern
which can be shared across nodes or through config, at the cost of a runtime
overhead every time to re-import it every time it is executed.
This modifier only has an effect starting on Erlang/OTP 28, and it is ignored
on older versions (i.e. `~r/foo/E == ~r/foo/`). This is because patterns cannot
and do not need to be exported in order to be shared in these versions.
## Captures
Many functions in this module handle what to capture in a regex
@@ -251,7 +258,7 @@ defmodule Regex do
This checks the version stored in the regular expression
and recompiles the regex in case of version mismatch.
"""
# Remove me on Elixir v1.22
# TODO: Deprecate on Elixir v1.22
@doc deprecated: "It can be removed and it has no effect"
@doc since: "1.4.0"
def recompile(%Regex{} = regex) do
@@ -261,7 +268,7 @@ defmodule Regex do
@doc """
Recompiles the existing regular expression and raises `Regex.CompileError` in case of errors.
"""
# Remove me on Elixir v1.22
# TODO: Deprecate on Elixir v1.22
@doc deprecated: "It can be removed and it has no effect"
@doc since: "1.4.0"
def recompile!(regex) do
@@ -271,7 +278,7 @@ defmodule Regex do
@doc """
Returns the version of the underlying Regex engine.
"""
# Remove me on Elixir v1.22
# TODO: Deprecate on Elixir v1.22
@doc deprecated: "Use :re.version() instead"
@doc since: "1.4.0"
def version do
@@ -515,7 +522,7 @@ defmodule Regex do
"""
@spec names(t) :: [String.t()]
def names(%Regex{re_pattern: re_pattern}) do
{:namelist, names} = :re.inspect(re_pattern, :namelist)
{:namelist, names} = :re.inspect(maybe_import_pattern(re_pattern), :namelist)
names
end
@@ -585,10 +592,16 @@ defmodule Regex do
%Regex{source: source, opts: compile_opts} = regex
:re.run(string, source, compile_opts ++ options)
else
_ -> :re.run(string, re_pattern, options)
_ -> :re.run(string, maybe_import_pattern(re_pattern), options)
end
end
@compile {:inline, maybe_import_pattern: 1}
defp maybe_import_pattern({:re_exported_pattern, _, _, _, _} = exported),
do: :re.import(exported)
defp maybe_import_pattern(pattern), do: pattern
@typedoc """
Options for regex functions that capture matches.
"""
@@ -1007,6 +1020,16 @@ defmodule Regex do
translate_options(t, [:ungreedy | acc])
end
defp translate_options(<<?E, t::binary>>, acc) do
# on OTP 27-, the E modifier is a no-op since the feature doesn't exist but isn't needed
# (regexes aren't using references and can be shared across nodes or stored in config)
# TODO: remove this check on Erlang/OTP 28+ and update docs
case Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) do
true -> translate_options(t, [:export | acc])
false -> translate_options(t, acc)
end
end
defp translate_options(<<>>, acc), do: acc
defp translate_options(t, _acc), do: {:error, t}
@@ -1022,6 +1045,9 @@ defmodule Regex do
:erlang.system_info(:otp_release) < [?2, ?8] ->
Macro.escape(regex.re_pattern)
:lists.member(:export, regex.opts) ->
Macro.escape(regex.re_pattern)
# OTP 28.1+ introduced the ability to export and import regexes from compiled binaries
Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) ->
{:ok, exported} = :re.compile(regex.source, [:export] ++ regex.opts)
+5
View File
@@ -906,6 +906,11 @@ defmodule Inspect.OthersTest do
assert inspect(Regex.compile!("foo", [:ucp])) == ~S'Regex.compile!("foo", [:ucp])'
end
@tag :re_import
test "exported regex" do
assert inspect(~r/foo/E) == "~r/foo/E"
end
test "inspect_fun" do
fun = fn
integer, _opts when is_integer(integer) ->
+14
View File
@@ -52,6 +52,20 @@ defmodule RegexTest do
assert Regex.match?(~r/^b$/m, "a\nb\nc")
end
@tag :re_import
test "export" do
# exported patterns have no structs, so these are structurally equal
assert ~r/foo/E == Regex.compile!("foo", [:export])
assert Regex.match?(~r/foo/E, "foo")
refute Regex.match?(~r/foo/E, "Foo")
assert Regex.run(~r/c(d)/E, "abcd") == ["cd", "d"]
assert Regex.run(~r/e/E, "abcd") == nil
assert Regex.names(~r/(?<FOO>foo)/E) == ["FOO"]
end
test "precedence" do
assert {"aa", :unknown} |> elem(0) =~ ~r/(a)\1/
end
+13 -6
View File
@@ -338,7 +338,8 @@ defmodule ExUnit.Formatter do
{formatted_reason, wrapped_stack} =
format_exception(test, struct, wrapped_stack, width, formatter, @counter_padding)
formatted_stack = format_stacktrace(wrapped_stack, test.module, test.name, formatter)
formatted_stack = format_stacktrace(wrapped_stack, test, formatter)
{error_info(header, formatter) <> pad(formatted_reason <> formatted_stack), stack}
:error ->
@@ -672,22 +673,28 @@ defmodule ExUnit.Formatter do
|> Algebra.format(width)
end
defp format_stacktrace([], _case, _test, _color) do
defp format_stacktrace(stack, %{module: module, name: name}, color),
do: format_stacktrace(stack, module, name, color)
defp format_stacktrace(stack, %{name: name}, color),
do: format_stacktrace(stack, name, nil, color)
defp format_stacktrace([], _module, _test, _color) do
""
end
defp format_stacktrace(stacktrace, test_case, test, color) do
defp format_stacktrace(stacktrace, module, test, color) do
extra_info("stacktrace:", color) <>
Enum.map_join(stacktrace, fn entry ->
stacktrace_info(format_stacktrace_entry(entry, test_case, test), color)
stacktrace_info(format_stacktrace_entry(entry, module, test), color)
end)
end
defp format_stacktrace_entry({test_case, test, _, location}, test_case, test) do
defp format_stacktrace_entry({module, test, _, location}, module, test) do
format_file_line(location[:file], location[:line], " (test)")
end
defp format_stacktrace_entry(entry, _test_case, _test) do
defp format_stacktrace_entry(entry, _module, _test) do
format_stacktrace_entry(entry)
end
@@ -465,6 +465,21 @@ defmodule ExUnit.FormatterTest do
"""
end
test "formats setup_all exit stacktraces using module context" do
stacktrace = [
{Hello, :setup_all, [], [file: __ENV__.file, line: 123]},
{Hello, :some_fun, [], [file: __ENV__.file, line: 456]}
]
failure = [{{:EXIT, self()}, {:function_clause, stacktrace}, []}]
formatted =
format_test_all_failure(test_module(), failure, 1, 80, &formatter/2)
assert formatted =~ "Hello.setup_all/0"
assert formatted =~ "Hello.some_fun()"
end
test "formats assertions with operators with no limit" do
failure = [{:error, catch_assertion(assert [1, 2, 3] == [4, 5, 6]), []}]
+35 -38
View File
@@ -468,51 +468,48 @@ defmodule Mix.Release do
{sys_config, runtime_config?} =
merge_provider_config(release, sys_config, config_provider_path)
path = Path.join(release.version_path, "sys.config")
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.chardata_to_string(:io_lib.format(format, args)))
case :file.consult(path) do
{:ok, _} ->
case consultable_sys_config(sys_config) do
{:ok, contents} ->
path = Path.join(release.version_path, "sys.config")
args = [runtime_config?, contents]
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~ts.~n"
File.mkdir_p!(Path.dirname(path))
File.write!(path, IO.chardata_to_string(:io_lib.format(format, args)))
:ok
{:error, reason} ->
invalid =
for {app, kv} <- sys_config,
{key, value} <- kv,
not valid_config?(value),
do: """
Application: #{inspect(app)}
Key: #{inspect(key)}
Value: #{inspect(value)}
"""
message =
case invalid do
[] ->
"Could not read configuration file. Reason: #{inspect(reason)}"
_ ->
"Could not read configuration file. It has invalid configuration terms " <>
"such as functions, references, and pids. Please make sure your configuration " <>
"is made of numbers, atoms, strings, maps, tuples and lists. The following entries " <>
"are wrong:\n#{Enum.join(invalid)}"
end
{:error, message} ->
{:error, message}
end
end
defp valid_config?(m) when is_map(m),
do: Enum.all?(Map.delete(m, :__struct__), &valid_config?/1)
defp consultable_sys_config(sys_config) do
contents =
Enum.map_intersperse(sys_config, ?,, fn {app, kv} ->
kv =
Enum.map_intersperse(kv, ?,, fn {key, value} ->
case Mix.Utils.consultable(value) do
{:ok, value} -> [?{, :io_lib.print(key), ?,, value, ?}]
{:error, term, reason} -> throw({:error, app, key, term, reason})
end
end)
defp valid_config?(l) when is_list(l), do: Enum.all?(l, &valid_config?/1)
defp valid_config?(t) when is_tuple(t), do: Enum.all?(Tuple.to_list(t), &valid_config?/1)
defp valid_config?(o), do: is_number(o) or is_atom(o) or is_binary(o)
[?{, :io_lib.print(app), ?,, ?[, kv, ?], ?}]
end)
{:ok, [?[, contents, ?]]}
catch
{:error, app, key, value, reason} ->
message = """
Could not write configuration file because it has invalid terms
Application: #{inspect(app)}
Key: #{inspect(key)}
Invalid value: #{inspect(value)}
Reason: #{reason}
"""
{:error, message}
end
defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false}
+15 -1
View File
@@ -295,10 +295,24 @@ defmodule Mix.Task.Compiler do
end
defp run_compiler(compiler, args) do
result = normalize(Mix.Task.run("compile.#{compiler}", args), compiler)
args_maybe_force = maybe_prepend_force(args, compiler)
result = normalize(Mix.Task.run("compile.#{compiler}", args_maybe_force), compiler)
Enum.reduce(Mix.ProjectStack.pop_after_compiler(compiler), result, & &1.(&2))
end
defp maybe_prepend_force(args, compiler) do
args
|> Enum.any?(fn
"--force-" <> rest -> rest == compiler |> to_string() |> String.replace("_", "-")
_ -> false
end)
|> if do
["--force" | args]
else
args
end
end
# Normalize the compiler result to a diagnostic tuple
defp normalize(result, name) do
case result do
+12 -49
View File
@@ -187,12 +187,22 @@ defmodule Mix.Tasks.Compile.App do
|> add_compile_env(current_properties)
|> add_modules(modules, compile_path)
contents = to_erl_term({:application, app, properties})
contents =
case Mix.Utils.consultable({:application, app, properties}) do
{:ok, contents} ->
contents
{:error, term, reason} ->
Mix.raise(
"\"def application\" has a term which cannot be written to .app files: #{inspect(term)} (#{reason})"
)
end
:application.unload(app)
:application.load({:application, app, properties})
Mix.Project.ensure_structure()
File.write!(target, [contents, ?.])
File.write!(target, IO.chardata_to_string([contents, ?.]))
File.touch!(target, new_mtime)
# If we just created the .app file, it will have touched
@@ -209,53 +219,6 @@ defmodule Mix.Tasks.Compile.App do
end
end
defp to_erl_term(tuple) when is_tuple(tuple) do
[?{, tuple |> Tuple.to_list() |> to_erl_head(), ?}]
end
defp to_erl_term(list) when is_list(list) do
[?[, to_erl_head(list), ?]]
end
defp to_erl_term(map) when is_map(map) do
inner =
Enum.map_intersperse(
:maps.to_list(:maps.iterator(map, :reversed)),
?,,
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
)
[?#, ?{, inner, ?}]
end
defp to_erl_term(map) when is_map(map) do
inner =
Enum.map_intersperse(
:maps.to_list(:maps.iterator(map, :reversed)),
?,,
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
)
[?#, ?{, inner, ?}]
end
defp to_erl_term(term) when is_function(term) or is_reference(term) or is_pid(term) do
Mix.raise(
"\"def application\" has a term which cannot be written to .app files: #{inspect(term)}"
)
end
defp to_erl_term(term) do
:io_lib.print(term)
end
defp to_erl_head([]), do: []
defp to_erl_head([h | t]), do: [to_erl_term(h) | to_erl_tail(t)]
defp to_erl_tail([h | t]), do: [?,, to_erl_term(h) | to_erl_tail(t)]
defp to_erl_tail([]), do: []
defp to_erl_tail(other), do: [?|, to_erl_term(other)]
defp current_app_properties(target) do
case :file.consult(target) do
{:ok, [{:application, _app, properties}]} -> properties
+4 -2
View File
@@ -7,7 +7,7 @@ defmodule Mix.Tasks.Compile do
@shortdoc "Compiles source files"
@moduledoc """
@moduledoc ~S"""
The main entry point to compile source files.
It simply runs the compilers registered in your project and returns
@@ -59,7 +59,9 @@ defmodule Mix.Tasks.Compile do
* `--all-warnings` (`--no-all-warnings`) - prints all warnings, including previous compilations
(default is true except on errors)
* `--erl-config` - path to an Erlang term file that will be loaded as Mix config
* `--force` - forces compilation
* `--force` - forces compilation. You can also specify `--force-#{compiler}` for each compiler
invoked to force specifically that compiler to run (it requires the compiler to respect the
`--force` option, which is advised)
* `--list` - lists all enabled compilers
* `--listeners` - starts Mix listeners (they are started by default,
unless `--no-listeners` or `--no-deps-check` are given)
+10 -1
View File
@@ -114,10 +114,19 @@ defmodule Mix.Tasks.Help do
loadpaths!()
app = String.to_atom(app)
# If the application is not available, attempt to load it from Erlang/Elixir
if is_nil(Application.spec(app, :vsn)) do
try do
Mix.ensure_application!(app)
rescue
_ -> :ok
end
end
if modules = Application.spec(app, :modules) do
for module <- modules,
not (module |> Atom.to_string() |> String.starts_with?("Elixir.Mix.Tasks.")),
{:docs_v1, _, :elixir, "text/markdown", %{"en" => <<doc::binary>>}, _, _} <-
{:docs_v1, _, _, "text/markdown", %{"en" => <<doc::binary>>}, _, _} <-
[Code.fetch_docs(module)] do
leading = doc |> String.split(["\n\n", "\r\n\r\n"], parts: 2) |> hd()
"# #{inspect(module)}\n#{leading}\n"
+73
View File
@@ -907,4 +907,77 @@ defmodule Mix.Utils do
uid
end
end
@doc """
Convert the given terms to a consultable Erlang term.
"""
def consultable(term) do
{:ok, to_erl_term(term)}
catch
{:error, term, reason} -> {:error, term, reason}
end
defp to_erl_term(tuple) when is_tuple(tuple) do
[?{, tuple |> Tuple.to_list() |> to_erl_head(), ?}]
end
defp to_erl_term(list) when is_list(list) do
if List.ascii_printable?(list) do
:io_lib.print(list)
else
[?[, to_erl_head(list), ?]]
end
end
defp to_erl_term(%Regex{re_pattern: {:re_pattern, _, _, _, ref}} = regex)
when is_reference(ref) do
throw({:error, regex, "you must use the /E modifier to store regexes"})
end
defp to_erl_term(map) when is_map(map) do
inner =
Enum.map_intersperse(
:maps.to_list(:maps.iterator(map, :reversed)),
?,,
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
)
[?#, ?{, inner, ?}]
end
defp to_erl_term(map) when is_map(map) do
inner =
Enum.map_intersperse(
:maps.to_list(:maps.iterator(map, :reversed)),
?,,
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
)
[?#, ?{, inner, ?}]
end
defp to_erl_term(function) when is_function(function) do
fun_info = Function.info(function)
if fun_info[:type] == :external and fun_info[:env] == [] do
:io_lib.print(function)
else
throw({:error, function, "only functions in the form &Mod.fun/arity are allowed"})
end
end
defp to_erl_term(term) when is_reference(term) or is_pid(term) do
throw({:error, term, "PIDs and References are not supported"})
end
defp to_erl_term(term) do
:io_lib.print(term)
end
defp to_erl_head([]), do: []
defp to_erl_head([h | t]), do: [to_erl_term(h) | to_erl_tail(t)]
defp to_erl_tail([h | t]), do: [?,, to_erl_term(h) | to_erl_tail(t)]
defp to_erl_tail([]), do: []
defp to_erl_tail(other), do: [?|, to_erl_term(other)]
end
+6 -5
View File
@@ -592,19 +592,20 @@ defmodule Mix.ReleaseTest do
assert make_sys_config(release([]), [foo: [bar: :baz]], "unused/runtime/path") == :ok
contents = File.read!(@sys_config)
assert contents =~ "%% RUNTIME_CONFIG=false"
assert contents =~ "[{foo,[{bar,baz}]}]."
{:ok, contents} = :file.consult(@sys_config)
assert contents == [[foo: [bar: :baz]]]
end
test "writes sys_config with encoding" do
assert make_sys_config(
release([]),
[encoding: {:_μ, :"£", "£", ~c"£"}],
[encoding: [key: {:_μ, :"£", "£", ~c"£"}]],
"unused/runtime/path"
) ==
:ok
{:ok, contents} = :file.consult(@sys_config)
assert contents == [[encoding: {:_μ, :"£", "£", ~c"£"}]]
assert contents == [[encoding: [key: {:_μ, :"£", "£", ~c"£"}]]]
end
test "writes the given sys_config with config providers" do
@@ -649,12 +650,12 @@ defmodule Mix.ReleaseTest do
end
test "errors on bad config" do
assert {:error, "Could not read configuration file." <> _} =
assert {:error, "Could not write configuration file " <> _} =
make_sys_config(release([]), [foo: [bar: self()]], "unused/runtime/path")
env = %{__ENV__ | lexical_tracker: self()}
assert {:error, "Could not read configuration file." <> _} =
assert {:error, "Could not write configuration file " <> _} =
make_sys_config(release([]), [foo: [bar: env]], "unused/runtime/path")
end
end
+24 -2
View File
@@ -110,7 +110,7 @@ defmodule Mix.Tasks.Compile.AppTest do
test "uses custom application settings" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(CustomProject)
env = [foo: [:one, "two", 3, 4], bar: [{} | %{foo: :bar}]]
env = [foo: [:one, "two", 3, 4, "฿", &List.flatten/1], bar: [{} | %{foo: :bar}]]
Process.put(:application,
maxT: :infinity,
@@ -132,7 +132,10 @@ defmodule Mix.Tasks.Compile.AppTest do
assert properties[:applications] ==
[:kernel, :stdlib, :elixir, :logger, :ex_unit, :example_app, :mix]
assert properties[:env] == [foo: [:one, "two", 3, 4], bar: [{} | %{foo: :bar}]]
assert properties[:env] == [
foo: [:one, "two", 3, 4, "฿", &List.flatten/1],
bar: [{} | %{foo: :bar}]
]
refute Keyword.has_key?(properties, :extra_applications)
end)
@@ -260,6 +263,25 @@ defmodule Mix.Tasks.Compile.AppTest do
end)
end
@tag :re_import
test "accepts only regexes without a reference" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(CustomProject)
Process.put(:application, env: [regex: ~r/foo/])
assert_raise Mix.Error, ~r/you must use the \/E modifier to store regexes/, fn ->
Mix.Tasks.Compile.App.run([])
end
Process.put(:application, env: [exported: ~r/foo/E])
Mix.Tasks.Compile.Elixir.run([])
Mix.Tasks.Compile.App.run([])
properties = parse_resource_file(:custom_project)
assert properties[:env] == [exported: ~r/foo/E]
end)
end
test ".app contains description and registered (as required by systools)" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
@@ -1253,6 +1253,23 @@ defmodule Mix.Tasks.Compile.ElixirTest do
end)
end
test "recompiles with --force-elixir" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
assert Mix.Task.run("compile") == {:ok, []}
purge([A, B])
# Now we have a noop
Mix.Task.clear()
assert Mix.Task.run("compile") == {:noop, []}
# --force-elixir
Mix.Task.clear()
assert Mix.Task.run("compile", ["--verbose", "--force-elixir"]) == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
end)
end
test "compiles files with autoload disabled" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
+12 -3
View File
@@ -199,14 +199,23 @@ defmodule Mix.Tasks.HelpTest do
in_tmp(context.test, fn ->
output =
capture_io(fn ->
Mix.Tasks.Help.run(["app:mix"])
Mix.Tasks.Help.run(["app:iex"])
end)
assert output =~ "# Mix\n\nMix is a build tool"
assert output =~ "# IEx\n\nElixir's interactive shell."
if System.otp_release() >= "27" do
output =
capture_io(fn ->
Mix.Tasks.Help.run(["app:parsetools"])
end)
assert output =~ "# :leex"
end
end)
end
test "help unknown app:APP", context do
test "help app:UNKNOWN", context do
in_tmp(context.test, fn ->
Mix.Tasks.Help.run(["app:foobar"])
assert_received {:mix_shell, :error, ["Application foobar does not exist or is not loaded"]}
+11 -1
View File
@@ -43,12 +43,22 @@ cover_exclude =
[]
end
# OTP 28.1+
re_import_exclude =
if Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) do
[]
else
[:re_import]
end
Code.require_file("../../elixir/scripts/cover_record.exs", __DIR__)
CoverageRecorder.maybe_record("mix")
ExUnit.start(
trace: !!System.get_env("TRACE"),
exclude: epmd_exclude ++ os_exclude ++ git_exclude ++ line_exclude ++ cover_exclude,
exclude:
epmd_exclude ++
os_exclude ++ git_exclude ++ line_exclude ++ cover_exclude ++ re_import_exclude,
include: line_include,
assert_receive_timeout: String.to_integer(System.get_env("ELIXIR_ASSERT_TIMEOUT", "300"))
)