Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9033b217d9 | ||
|
|
1d9548fd28 | ||
|
|
9a6c195880 | ||
|
|
3eb938a0ba | ||
|
|
66f1e74e0c | ||
|
|
7c382b94af | ||
|
|
9297a9167e | ||
|
|
0410a1cec9 | ||
|
|
b545a1e491 | ||
|
|
c08b5b11c6 | ||
|
|
8939b39fc2 | ||
|
|
e12a0c561e | ||
|
|
61c3e82123 | ||
|
|
d43b41695d | ||
|
|
fdca8b2657 | ||
|
|
dec06d8c82 | ||
|
|
11c84cbd2e | ||
|
|
67501bcddc | ||
|
|
1f05e7ce73 | ||
|
|
02c279af12 | ||
|
|
2c2fb820ab | ||
|
|
1ad1d6d6b0 | ||
|
|
83d8161959 | ||
|
|
b87ea61713 | ||
|
|
c355ab6482 | ||
|
|
f0ed2d48ce | ||
|
|
d23a46fa9f | ||
|
|
6c0cbbd73e | ||
|
|
d58ef57e13 | ||
|
|
1a9493534e | ||
|
|
45fe1cb084 | ||
|
|
3f0e7c5613 | ||
|
|
49e76833d6 | ||
|
|
7963e44a5e | ||
|
|
3ba5e44dae | ||
|
|
1d74ddac78 | ||
|
|
c1e3d4f976 | ||
|
|
b4fc2b35fc | ||
|
|
18a5d8affb | ||
|
|
cd80994a70 | ||
|
|
e02d7bf913 | ||
|
|
fe5ff41514 | ||
|
|
e9887a760e | ||
|
|
0559fe5927 | ||
|
|
1a05bff45d | ||
|
|
4d6670342b | ||
|
|
193e85882e | ||
|
|
a58c007e0c | ||
|
|
ce6f4e6d15 | ||
|
|
321096665a | ||
|
|
b93aae9f86 | ||
|
|
a503069479 | ||
|
|
120ea0fc8a | ||
|
|
d725c66d93 | ||
|
|
fab917dba3 | ||
|
|
23fb92f444 | ||
|
|
db61109495 | ||
|
|
c4e8ad0d65 | ||
|
|
ce77fa54b9 | ||
|
|
eee921bf7b | ||
|
|
73c6a0be8b | ||
|
|
7644bd87a5 | ||
|
|
9e2c5a29a4 | ||
|
|
09498422d0 | ||
|
|
a697bab7f2 | ||
|
|
36e81d587e | ||
|
|
17a0abd57a | ||
|
|
e6cfc20556 | ||
|
|
906f03102b | ||
|
|
6a594e2bba | ||
|
|
10dd8f843b | ||
|
|
53ecea8b03 | ||
|
|
efba8d8bfa | ||
|
|
5a201065dc |
@@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## v1.0.5 (2015-06-29)
|
||||
|
||||
* Enhancements
|
||||
* [Elixir] Support 18.0
|
||||
* [IEx] Rely only on loaded applications for autocompletion on IEx
|
||||
* [Record] Expand attributes and macros in record extractor
|
||||
* [String] `Optimize String.rstrip/1`
|
||||
* [String] `Optimize String.downcase/1`
|
||||
* [String] `Optimize String.upcase/1`
|
||||
|
||||
* Bug fixes
|
||||
* [EEx] Ensure blocks do not clobber EEx buffers
|
||||
* [Enum] Ensure `Enum.take/2` does not consume one extra item when halting on the last emittable item
|
||||
* [ExUnit] Fix `StringIO` processes leakage in the `ExUnit.CaptureIO` when there are errors inside the `capture_io` block
|
||||
* [Float] Avoid rounding errors on `Float.parse/1`
|
||||
* [GenEvent] Fix `GenEvent` detection of modules that aren't loaded
|
||||
* [IO] Read 4K blocks instead of lines in `IO.binread/2`. This fixes a bug where CRLF were being ignored and is also going to improve performance
|
||||
* [Logger] Handle `:undefined` arity in `Logger.Translator` (we get :undefined when a temporary worker of a `simple_one_for_one` supervisor crashes)
|
||||
* [Mix] Ensure config is escaped before being injected into escripts. This fixes a bug where escripts failed to build when containing values like maps in config files
|
||||
* [Mix] Ensure we properly underscore acronyms followed by paths, for example, `HTTP.Foo`
|
||||
* [Stream] Ensure `Stream.flat_map/2` does not consume more items than necessary when piped to another `Stream.flat_map/2` that halts in the inner stream
|
||||
* [Version] Fix `to_string` for versions with numeric pre releases
|
||||
|
||||
## v1.0.4 (2015-04-07)
|
||||
|
||||
* Enhancements
|
||||
* [Elixir] Support Erlang 17.5 and 18.0-rc
|
||||
* [Mix] Support "--search PATTERN" in "mix help"
|
||||
* [Mix] Support `:start_permanent` that starts the application as permanent
|
||||
* [Mix] Support `:build_embedded` that compile protocols, avoid symlinks and ensure protocols are loaded on boot
|
||||
* [Mix] Make "mix app.start" public and support "--permanent" and "--temporary" options
|
||||
* [URI] Speed decoding operations
|
||||
|
||||
* Bug fixes
|
||||
* [Elixir] Produce clearer error messages for syntax errors before a sigil
|
||||
* [Elixir] Return value when matching on `_` in the last line of a block
|
||||
* [Elixir] Ensure changes in child deps forces the parent to recompile
|
||||
* [Kernel] Fix a bug where `=~/2` returned false when matching on an empty string. The current behaviour was changed to match the behaviour of `String.contains?/2`
|
||||
* [Mix] Store relative paths in erlang manifests
|
||||
* [Mix] Fix httpc proxy option profile for downloading hex and other safe packges
|
||||
* [Mix] Fix missing behaviour warning when compiling Erlang modules
|
||||
|
||||
## v1.0.3 (2015-02-12)
|
||||
|
||||
* Enhancements
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
REBAR := rebar
|
||||
DOCS := v1.0
|
||||
CANONICAL := stable
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -125,8 +126,9 @@ clean_exbeam:
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o doc/$(2) -p http://elixir-lang.org/docs.html
|
||||
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
|
||||
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}\c")
|
||||
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "v$(VERSION)" $(call LOGO_PATH) -o doc/$(2) -a http://elixir-lang.org/docs/$(CANONICAL)/$(2)/ -p http://elixir-lang.org/docs.html
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
$(Q) rm -rf doc
|
||||
|
||||
+4
-4
@@ -28,13 +28,13 @@ This document simply outlines the release process:
|
||||
|
||||
## New vMAJOR.MINOR releases
|
||||
|
||||
11. Create a new branch "vMAJOR.MINOR"
|
||||
12. Create a new branch "vMAJOR.MINOR"
|
||||
|
||||
12. Move docs generation to `docs/vOLD-MAJOR.OLD-MINOR` and copy them to `docs/stable`
|
||||
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
|
||||
|
||||
13. Bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
||||
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
||||
|
||||
14. `make release_docs` and push it to `elixir-lang/docs`
|
||||
15. `make release_docs` and push it to `elixir-lang/docs`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
@@ -98,16 +98,16 @@ defmodule EEx.Engine do
|
||||
"""
|
||||
def handle_expr(buffer, "=", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
tmp <> String.Chars.to_string(unquote(expr))
|
||||
tmp1 = unquote(buffer)
|
||||
tmp1 <> String.Chars.to_string(unquote(expr))
|
||||
end
|
||||
end
|
||||
|
||||
def handle_expr(buffer, "", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
tmp2 = unquote(buffer)
|
||||
unquote(expr)
|
||||
tmp
|
||||
tmp2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -252,7 +252,7 @@ Number <%= x %>
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "do not consider already finished functions" do
|
||||
test "properly handle functions on the left side of clauses" do
|
||||
expected = """
|
||||
foo
|
||||
|
||||
@@ -287,6 +287,18 @@ foo
|
||||
assert_eval "\n\n Good\n \n", string
|
||||
end
|
||||
|
||||
test "evaluates expressions with buffers" do
|
||||
string = """
|
||||
<%= 123 %>
|
||||
<% if true do %>
|
||||
<%= 456 %>
|
||||
<% end %>
|
||||
<%= 789 %>
|
||||
"""
|
||||
|
||||
assert_eval "123\n\n789\n", string
|
||||
end
|
||||
|
||||
test "for comprehensions" do
|
||||
string = """
|
||||
<%= for _name <- packages || [] do %>
|
||||
|
||||
+1
-10
@@ -51,16 +51,7 @@ defmodule Code do
|
||||
:code.del_path(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Re-add paths given to the command line to keep their position
|
||||
on the overall code path.
|
||||
|
||||
Some tools may change the code path by prepending new items but
|
||||
still want the paths given by the user to have higher priority.
|
||||
Calling this function guarantees the paths are re-added on
|
||||
top of the user given ones.
|
||||
"""
|
||||
@spec readd_paths() :: :ok
|
||||
@doc false
|
||||
def readd_paths() do
|
||||
{pa, pz} = :elixir_code_server.call(:paths)
|
||||
:code.add_pathsa(pa)
|
||||
|
||||
@@ -1731,11 +1731,11 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
def take(collection, count) when is_list(collection) and count > 0 do
|
||||
def take(collection, count) when is_list(collection) and is_integer(count) and count > 0 do
|
||||
do_take(collection, count)
|
||||
end
|
||||
|
||||
def take(collection, count) when count > 0 do
|
||||
def take(collection, count) when count > 0 and is_integer(count) do
|
||||
{_, {res, _}} =
|
||||
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
|
||||
case count do
|
||||
@@ -1747,7 +1747,7 @@ defmodule Enum do
|
||||
:lists.reverse(res)
|
||||
end
|
||||
|
||||
def take(collection, count) when count < 0 do
|
||||
def take(collection, count) when count < 0 and is_integer(count) do
|
||||
Stream.take(collection, count).({:cont, []}, &{:cont, [&1|&2]})
|
||||
|> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@@ -564,6 +564,14 @@ defmodule BadStructError do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadMapError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"expected a map, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule MatchError do
|
||||
defexception [term: nil]
|
||||
|
||||
@@ -669,7 +677,12 @@ defmodule KeyError do
|
||||
defexception key: nil, term: nil
|
||||
|
||||
def message(exception) do
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
msg = "key #{inspect exception.key} not found"
|
||||
if exception.term != nil do
|
||||
msg <> " in: #{inspect exception.term}"
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -768,6 +781,20 @@ defmodule ErlangError do
|
||||
%MatchError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badmap, term}, _stacktrace) do
|
||||
%BadMapError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badkey, key}, stacktrace) do
|
||||
term =
|
||||
case stacktrace || :erlang.get_stacktrace do
|
||||
[{:maps, :update, [_, _, map], _}|_] -> map
|
||||
[{:maps, :get, [_, map], _}|_] -> map
|
||||
_ -> nil
|
||||
end
|
||||
%KeyError{key: key, term: term}
|
||||
end
|
||||
|
||||
def normalize({:case_clause, term}, _stacktrace) do
|
||||
%CaseClauseError{term: term}
|
||||
end
|
||||
|
||||
+18
-54
@@ -28,75 +28,39 @@ defmodule Float do
|
||||
"""
|
||||
@spec parse(binary) :: {float, binary} | :error
|
||||
def parse("-" <> binary) do
|
||||
case parse_unsign(binary) do
|
||||
case parse_unsigned(binary) do
|
||||
:error -> :error
|
||||
{number, remainder} -> {-number, remainder}
|
||||
end
|
||||
end
|
||||
|
||||
def parse(binary) do
|
||||
parse_unsign(binary)
|
||||
parse_unsigned(binary)
|
||||
end
|
||||
|
||||
defp parse_unsign("-" <> _), do: :error
|
||||
defp parse_unsign(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{integer_part, after_integer} -> parse_unsign after_integer, integer_part
|
||||
end
|
||||
end
|
||||
defp parse_unsigned(<<char, rest::binary>>) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, false, false, <<char>>)
|
||||
|
||||
# Dot followed by digit is required afterwards or we are done
|
||||
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse_unsign(rest, char - ?0, 1, int)
|
||||
end
|
||||
defp parse_unsigned(binary) when is_binary(binary), do:
|
||||
:error
|
||||
|
||||
defp parse_unsign(rest, int) do
|
||||
{:erlang.float(int), rest}
|
||||
end
|
||||
defp parse_unsigned(<<char, rest :: binary>>, dot?, e?, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, dot?, e?, <<acc::binary, char>>)
|
||||
|
||||
# Handle decimal points
|
||||
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
end
|
||||
defp parse_unsigned(<<?., char, rest :: binary>>, false, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, false, <<acc::binary, ?., char>>)
|
||||
|
||||
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
case Integer.parse after_e do
|
||||
:error ->
|
||||
# Note we rebuild the binary here instead of breaking it apart at
|
||||
# the function clause because the current approach copies a binary
|
||||
# just on this branch. If we broke it apart in the function clause,
|
||||
# the copy would happen when calling Integer.parse/1.
|
||||
{floatify(int, float, decimal), << ?e, after_e :: binary >>}
|
||||
{exponential, after_exponential} ->
|
||||
{floatify(int, float, decimal, exponential), after_exponential}
|
||||
end
|
||||
end
|
||||
defp parse_unsigned(<<?e, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, char>>)
|
||||
|
||||
defp parse_unsign(bitstring, float, decimal, int) do
|
||||
{floatify(int, float, decimal), bitstring}
|
||||
end
|
||||
defp parse_unsigned(<<?e, ?-, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, ?-, char>>)
|
||||
|
||||
defp floatify(int, float, decimal, exponential \\ 0) do
|
||||
multiplier = if int < 0, do: -1.0, else: 1.0
|
||||
defp parse_unsigned(rest, dot?, _e?, acc), do:
|
||||
{:erlang.binary_to_float(add_dot(acc, dot?)), rest}
|
||||
|
||||
# Try to ensure the minimum amount of rounding errors
|
||||
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
|
||||
|
||||
# Try avoiding stuff like this:
|
||||
# iex(1)> 0.0001 * 75
|
||||
# 0.007500000000000001
|
||||
# Due to IEEE 754 floating point standard
|
||||
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
|
||||
final_decimal_places = decimal - exponential
|
||||
if final_decimal_places > 0 do
|
||||
decimal_power_round = :math.pow(10, final_decimal_places)
|
||||
trunc(result * decimal_power_round) / decimal_power_round
|
||||
else
|
||||
result
|
||||
end
|
||||
end
|
||||
defp add_dot(acc, true), do: acc
|
||||
defp add_dot(acc, false), do: acc <> ".0"
|
||||
|
||||
@doc """
|
||||
Rounds a float to the largest integer less than or equal to `num`.
|
||||
|
||||
@@ -1015,7 +1015,7 @@ defmodule GenEvent do
|
||||
case reason do
|
||||
{:undef, [{m,f,a,_}|_]=mfas} ->
|
||||
cond do
|
||||
:code.is_loaded(m) ->
|
||||
:code.is_loaded(m) === false ->
|
||||
{:"module could not be loaded", mfas}
|
||||
function_exported?(m, f, length(a)) ->
|
||||
reason
|
||||
|
||||
@@ -123,8 +123,9 @@ defmodule IO do
|
||||
end
|
||||
end
|
||||
|
||||
@read_all_size 4096
|
||||
defp do_binread_all(mapped_dev, acc) do
|
||||
case :file.read_line(mapped_dev) do
|
||||
case :file.read(mapped_dev, @read_all_size) do
|
||||
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
|
||||
:eof -> acc
|
||||
other -> other
|
||||
|
||||
@@ -408,7 +408,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
# Characters that can mark the beginning or the end of a word.
|
||||
# Only support the most common ones at this moment.
|
||||
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>]
|
||||
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
|
||||
|
||||
# Inline start
|
||||
|
||||
|
||||
@@ -1386,7 +1386,14 @@ defmodule Kernel do
|
||||
iex> "abcd" =~ "ad"
|
||||
false
|
||||
|
||||
iex> "abcd" =~ ""
|
||||
true
|
||||
|
||||
"""
|
||||
|
||||
@spec (String.t =~ (String.t | Regex.t)) :: boolean
|
||||
def left =~ "" when is_binary(left), do: true
|
||||
|
||||
def left =~ right when is_binary(left) and is_binary(right) do
|
||||
:binary.match(left, right) != :nomatch
|
||||
end
|
||||
|
||||
@@ -172,10 +172,22 @@ defmodule Kernel.SpecialForms do
|
||||
iex> << 1, 2, 3 >>
|
||||
<< 1, 2, 3 >>
|
||||
|
||||
## Bitstring types
|
||||
## Types
|
||||
|
||||
A bitstring is made of many segments. Each segment has a
|
||||
type, which defaults to integer:
|
||||
A bitstring is made of many segments and each segment has a
|
||||
type. There are 9 types used in bitstrings:
|
||||
|
||||
- `integer`
|
||||
- `float`
|
||||
- `bits` (alias for bitstring)
|
||||
- `bitstring`
|
||||
- `binary`
|
||||
- `bytes` (alias for binary)
|
||||
- `utf8`
|
||||
- `utf16`
|
||||
- `utf32`
|
||||
|
||||
When no type is specified, the default is `integer`:
|
||||
|
||||
iex> <<1, 2, 3>>
|
||||
<<1, 2, 3>>
|
||||
@@ -186,14 +198,7 @@ defmodule Kernel.SpecialForms do
|
||||
iex> <<0, "foo">>
|
||||
<<0, 102, 111, 111>>
|
||||
|
||||
Any other type needs to be explicitly tagged. For example,
|
||||
in order to store a float type in the binary, one has to do:
|
||||
|
||||
iex> <<3.14 :: float>>
|
||||
<<64, 9, 30, 184, 81, 235, 133, 31>>
|
||||
|
||||
This also means that variables need to be explicitly tagged,
|
||||
otherwise Elixir defaults to integer:
|
||||
Variables or any other type need to be explicitly tagged:
|
||||
|
||||
iex> rest = "oo"
|
||||
iex> <<102, rest>>
|
||||
@@ -201,103 +206,161 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
We can solve this by explicitly tagging it as a binary:
|
||||
|
||||
<<102, rest :: binary>>
|
||||
|
||||
The type can be integer, float, bitstring/bits, binary/bytes,
|
||||
utf8, utf16 or utf32, e.g.:
|
||||
|
||||
<<102 :: float, rest :: binary>>
|
||||
|
||||
An integer can be any arbitrary precision integer. A float is an
|
||||
IEEE 754 binary32 or binary64 floating point number. A bitstring
|
||||
is an arbitrary series of bits. A binary is a special case of
|
||||
bitstring that has a total size divisible by 8.
|
||||
iex> rest = "oo"
|
||||
iex> <<102, rest :: binary>>
|
||||
"foo"
|
||||
|
||||
The utf8, utf16, and utf32 types are for unicode codepoints. They
|
||||
can also be applied to literal strings and char lists:
|
||||
|
||||
iex> <<"foo" :: utf16>>
|
||||
<<0,102,0,111,0,111>>
|
||||
<<0, 102, 0, 111, 0, 111>>
|
||||
iex> <<"foo" :: utf32>>
|
||||
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
|
||||
|
||||
The bits type is an alias for bitstring. The bytes type is an
|
||||
alias for binary.
|
||||
## Options
|
||||
|
||||
The signedness can also be given as signed or unsigned. The
|
||||
signedness only matters for matching and relevant only for
|
||||
integers. If unspecified, it defaults to unsigned. Example:
|
||||
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156,102,111,111>>
|
||||
|
||||
This match would have failed if we did not specify that the
|
||||
value -100 is signed. If we're matching into a variable instead
|
||||
of a value, the signedness won't be checked; rather, the number
|
||||
will simply be interpreted as having the given (or implied)
|
||||
signedness, e.g.:
|
||||
|
||||
iex> <<val, _rest :: binary>> = <<-100, "foo">>
|
||||
iex> val
|
||||
156
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
|
||||
The endianness of a segment can be big, little or native (the
|
||||
latter meaning it will be resolved at VM load time). Many options
|
||||
can be given by using `-` as separator:
|
||||
Many options can be given by using `-` as separator. Order is
|
||||
arbitrary, so the following are all equivalent:
|
||||
|
||||
<<102 :: integer-native, rest :: binary>>
|
||||
|
||||
Or:
|
||||
|
||||
<<102 :: native-integer, rest :: binary>>
|
||||
<<102 :: unsigned-big-integer, rest :: binary>>
|
||||
<<102 :: unsigned-big-integer-size(8), rest :: binary>>
|
||||
<<102 :: unsigned-big-integer-8, rest :: binary>>
|
||||
<<102 :: 8-integer-big-unsigned, rest :: binary>>
|
||||
<<102, rest :: binary>>
|
||||
|
||||
And so on.
|
||||
### Unit and Size
|
||||
|
||||
Endianness only makes sense for integers and some UTF code
|
||||
point types (utf16 and utf32).
|
||||
The length of the match is equal to the `unit` (a number of bits) times the
|
||||
`size` (the number of repeated segnments of length `unit`).
|
||||
|
||||
Finally, we can also specify size and unit for each segment. The
|
||||
unit is multiplied by the size to give the effective size of
|
||||
the segment in bits. The default unit for integers, floats,
|
||||
and bitstrings is 1. For binaries, it is 8.
|
||||
Type | Default Unit
|
||||
--------- | ------------
|
||||
`integer` | 1 bit
|
||||
`float` | 1 bit
|
||||
`binary` | 8 bits
|
||||
|
||||
Since integers are default, the default unit is 1. The example below
|
||||
matches because the string "foo" takes 24 bits and we match it
|
||||
against a segment of 24 bits, 8 of which are taken by the integer
|
||||
102 and the remaining 16 bits are specified on the rest.
|
||||
Sizes for types are a bit more nuanced. The default size for integers is 8.
|
||||
|
||||
iex> <<102, _rest :: size(16)>> = "foo"
|
||||
"foo"
|
||||
For floats, it is 64. For floats, `size * unit` must result in 32 or 64,
|
||||
corresponding to [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)
|
||||
binary32 and binary64, respectively.
|
||||
|
||||
We can also match by specifying size and unit explicitly:
|
||||
For binaries, the default is the size of the binary. Only the last binary in a
|
||||
match can use the default size. All others must have their size specified
|
||||
explicitly, even if the match is unambiguous. For example:
|
||||
|
||||
iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
|
||||
"foo"
|
||||
iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
|
||||
"Frank the Walrus"
|
||||
iex> {name, species}
|
||||
{"Frank", "Walrus"}
|
||||
|
||||
However, if we expect a size of 32, it won't match:
|
||||
Failing to specify the size for the non-last causes compilation to fail:
|
||||
|
||||
iex> <<102, _rest :: size(32)>> = "foo"
|
||||
** (MatchError) no match of right hand side value: "foo"
|
||||
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
|
||||
** (CompileError): a binary field without size is only allowed at the end of a binary pattern
|
||||
|
||||
Size and unit are not applicable to utf8, utf16, and utf32.
|
||||
|
||||
The default size for integers is 8. For floats, it is 64. For
|
||||
binaries, it is the size of the binary. Only the last binary
|
||||
in a binary match can use the default size (all others must
|
||||
have their size specified explicitly).
|
||||
#### Shortcut Syntax
|
||||
|
||||
Size and unit can also be specified using a syntax shortcut
|
||||
when passing integer values:
|
||||
|
||||
<< x :: 8 >> == << x :: size(8) >>
|
||||
<< x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
|
||||
<< x :: _ * 4 >> == << x :: unit(4) >>
|
||||
iex> x = 1
|
||||
iex> << x :: 8 >> == << x :: size(8) >>
|
||||
true
|
||||
iex> << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
|
||||
true
|
||||
|
||||
This syntax reflects the fact the effective size is given by
|
||||
multiplying the size by the unit.
|
||||
|
||||
For floats, `size * unit` must result in 32 or 64, corresponding
|
||||
to binary32 and binary64, respectively.
|
||||
### Modifiers
|
||||
|
||||
Some types have associated modifiers to clear up ambiguity in byte
|
||||
representation.
|
||||
|
||||
Modifier | Relevant Type(s)
|
||||
-------------------- | ----------------
|
||||
`signed` | `integer`
|
||||
`unsigned` (default) | `integer`
|
||||
`little` | `integer`, `utf16`, `utf32`
|
||||
`big` (default) | `integer`, `utf16`, `utf32`
|
||||
`native` | `integer`, `utf16`, `utf32`
|
||||
|
||||
### Sign
|
||||
|
||||
Integers can be `signed` or `unsigned`, defaulting to `unsigned`.
|
||||
|
||||
iex> <<int::integer>> = <<-100>>
|
||||
<<156>>
|
||||
iex> int
|
||||
156
|
||||
iex> <<int::integer-signed>> = <<-100>>
|
||||
<<156>>
|
||||
iex> int
|
||||
-100
|
||||
|
||||
`signed` and `unsigned` are only used for matching binaries (see below) and
|
||||
are only used for integers.
|
||||
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156, 102, 111, 111>>
|
||||
|
||||
### Endianness
|
||||
|
||||
Elixir has three options for endianness: `big`, `little`, and `native`.
|
||||
The default is `big`. `native` is determined by the VM at startup.
|
||||
|
||||
iex> <<number::little-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
256
|
||||
iex> <<number::big-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
1
|
||||
iex> <<number::native-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
256
|
||||
|
||||
## Binary/Bitstring Matching
|
||||
|
||||
Binary matching is a powerful feature in Elixir that is useful for extracting
|
||||
information from binaries as well as pattern matching.
|
||||
|
||||
Binary matching can be used by itself to extract information from binaries:
|
||||
|
||||
iex> <<"Hello, ", place::binary>> = "Hello, World"
|
||||
"Hello, World"
|
||||
iex> place
|
||||
"World"
|
||||
|
||||
Or as a part of function definitions to pattern match:
|
||||
|
||||
defmodule ImageTyper
|
||||
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
|
||||
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
|
||||
@jpg_signature <<255::size(8), 216::size(8)>>
|
||||
|
||||
def type(<<@png_signature, rest::binary>>), do: :png
|
||||
def type(<<@jpg_signature, rest::binary>>), do: :jpg
|
||||
def type(_), do :unknown
|
||||
end
|
||||
|
||||
### Performance & Optimizations
|
||||
|
||||
The Erlang compiler can provide a number of optimizations on binary creation
|
||||
and matching. To see optimization output, set the `bin_opt_info` compiler
|
||||
option:
|
||||
|
||||
ERL_COMPILER_OPTIONS=bin_opt_info mix compile
|
||||
|
||||
To learn more about specific optimizations and performance considerations,
|
||||
check out
|
||||
[Erlang's Efficiency Guide on handling binaries](http://www.erlang.org/doc/efficiency_guide/binaryhandling.html).
|
||||
"""
|
||||
defmacro unquote(:<<>>)(args)
|
||||
|
||||
|
||||
@@ -644,6 +644,10 @@ defmodule Kernel.Typespec do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:user_type, line, name, args}) do
|
||||
typespec_to_ast({:type, line, name, args})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :tuple, :any}) do
|
||||
{:tuple, [line: line], []}
|
||||
end
|
||||
@@ -677,8 +681,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :map, fields}) do
|
||||
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
fields = Enum.map fields, fn
|
||||
# OTP 18
|
||||
{:type, _, :map_field_assoc, [k, v]} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
# OTP 17
|
||||
{:type, _, :map_field_assoc, k, v} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
end
|
||||
|
||||
{struct, fields} = Keyword.pop(fields, :__struct__)
|
||||
@@ -821,9 +830,17 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = :lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end, fields)
|
||||
fields =
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
:lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
|
||||
end, fields)
|
||||
else
|
||||
:lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end, fields)
|
||||
end
|
||||
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
@@ -884,7 +901,8 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:.., meta, args}, vars, caller) do
|
||||
typespec({:range, meta, args}, vars, caller)
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :range, args}
|
||||
end
|
||||
|
||||
# Handle special forms
|
||||
@@ -968,9 +986,21 @@ defmodule Kernel.Typespec do
|
||||
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:fun, meta, args}, vars, caller) do
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :fun, args}
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), name, arguments}
|
||||
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
arity = length(arguments)
|
||||
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
|
||||
{type, line(meta), name, arguments}
|
||||
else
|
||||
{:type, line(meta), name, arguments}
|
||||
end
|
||||
end
|
||||
|
||||
# Handle literals
|
||||
|
||||
@@ -45,10 +45,11 @@ defmodule Record.Extractor do
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
# includes record and other preprocessor modules. This is done
|
||||
# by using Erlang's epp_dodger.
|
||||
# includes record but with macros and other attributes expanded,
|
||||
# such as `-include(...)` and `-include_lib(...)`. This is done
|
||||
# by using Erlang's epp.
|
||||
defp read_file(file) do
|
||||
case :epp_dodger.quick_parse_file(file) do
|
||||
case :epp.parse_file(file, []) do
|
||||
{:ok, form} ->
|
||||
form
|
||||
other ->
|
||||
@@ -80,11 +81,11 @@ defmodule Record.Extractor do
|
||||
|
||||
defp eval_record(cons, form) do
|
||||
form = form ++
|
||||
[ {:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ cons ]} ]} ]
|
||||
[{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [cons]}]}]
|
||||
|
||||
{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
|
||||
{:clause, 0, [], [], [record_ast]}]} = :erl_expand_records.module(form, []) |> List.last
|
||||
|
||||
{:value, record, _} = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
|
||||
@@ -518,11 +518,11 @@ defmodule Stream do
|
||||
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
|
||||
def take(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
def take(enum, n) when n > 0 do
|
||||
def take(enum, n) when n > 0 and is_integer(n) do
|
||||
lazy enum, n, fn(f1) -> R.take(f1) end
|
||||
end
|
||||
|
||||
def take(enum, n) when n < 0 do
|
||||
def take(enum, n) when n < 0 and is_integer(n) do
|
||||
&do_take(enum, abs(n), &1, &2)
|
||||
end
|
||||
|
||||
@@ -699,9 +699,11 @@ defmodule Stream do
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:halted, [:outer|acc]} ->
|
||||
# Only take into account outer halts when the op is not halt itself.
|
||||
# Otherwise, we were the ones wishing to halt, so we should just stop.
|
||||
{:halted, [:outer|acc]} when op != :halt ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:halted, [:inner|acc]} ->
|
||||
{:halted, [_|acc]} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, acc}
|
||||
{:done, [_|acc]} ->
|
||||
|
||||
@@ -132,10 +132,16 @@ defmodule Stream.Reducers do
|
||||
defmacro take(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, n, t) = orig) ->
|
||||
if n >= 1 do
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
else
|
||||
{:halt, orig}
|
||||
case n do
|
||||
0 ->
|
||||
{:halt, orig}
|
||||
1 ->
|
||||
case cont_with_acc(unquote(f), entry, h, n-1, t) do
|
||||
{:cont, acc} -> {:halt, acc}
|
||||
reason -> reason
|
||||
end
|
||||
_ ->
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,8 +20,8 @@ defmodule Supervisor do
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
def start_link(state) do
|
||||
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
|
||||
def start_link(state, opts) do
|
||||
GenServer.start_link(__MODULE__, state, opts)
|
||||
end
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
@@ -38,16 +38,17 @@ defmodule Supervisor do
|
||||
# Import helpers for defining supervisors
|
||||
import Supervisor.Spec
|
||||
|
||||
# We are going to supervise the Stack server which will
|
||||
# be started with a single argument [:hello]
|
||||
# We are going to supervise the Stack server which
|
||||
# will be started with a single argument [:hello]
|
||||
# and the default name of :sup_stack.
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
worker(Stack, [[:hello], [name: :sup_stack]])
|
||||
]
|
||||
|
||||
# Start the supervisor with our one child
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
Notice that when starting the GenServer, we have registered it
|
||||
Notice that when starting the GenServer, we are registering it
|
||||
with name `:sup_stack`, which allows us to call it directly and
|
||||
get what is on the stack:
|
||||
|
||||
@@ -65,7 +66,7 @@ defmodule Supervisor do
|
||||
Let's try it:
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
=ERROR REPORT====
|
||||
** (exit) exited in: GenServer.call(:sup_stack, :pop, 5000)
|
||||
|
||||
Luckily, since the server is being supervised by a supervisor, the
|
||||
supervisor will automatically start a new one, with the default stack
|
||||
@@ -136,6 +137,70 @@ defmodule Supervisor do
|
||||
in this module behave slightly differently when this strategy is
|
||||
used.
|
||||
|
||||
## Simple one for one
|
||||
|
||||
The simple one for one supervisor is useful when you want to dynamically
|
||||
start and stop supervisor children. For example, imagine you want to
|
||||
dynamically create multiple stacks. We can do so by defining a simple one
|
||||
for one supervisor:
|
||||
|
||||
# Import helpers for defining supervisors
|
||||
import Supervisor.Spec
|
||||
|
||||
# This time, we don't pass any argument because
|
||||
# the argument will be given when we start the child
|
||||
children = [
|
||||
worker(Stack, [], restart: :transient)
|
||||
]
|
||||
|
||||
# Start the supervisor with our one child
|
||||
{:ok, sup_pid} = Supervisor.start_link(children, strategy: :simple_one_for_one)
|
||||
|
||||
There are a couple differences here:
|
||||
|
||||
* The simple one for one specification can define only one child which
|
||||
works as a template for when we call `start_child/2`
|
||||
|
||||
* We have define the child to have restart strategy of transient. This
|
||||
means that, if the child process exits due to a `:normal`, `:shutdown`
|
||||
or `{:shutdown, term}` reason, it won't be restarted. This is useful
|
||||
as it allows our workers to politely shutdown and be removed from the
|
||||
simple one for one supervisor, without being restarted. You can find
|
||||
more information about restart strategies on `Supervisor.Spec`
|
||||
|
||||
With the supervisor defined, let's dynamically start stacks:
|
||||
|
||||
{:ok, pid} = Supervisor.start_child(sup_pid, [[:hello, :world], []])
|
||||
GenServer.call(pid, :pop) #=> :hello
|
||||
GenServer.call(pid, :pop) #=> :world
|
||||
|
||||
{:ok, pid} = Supervisor.start_child(sup_pid, [[:something, :else], []])
|
||||
GenServer.call(pid, :pop) #=> :something
|
||||
GenServer.call(pid, :pop) #=> :else
|
||||
|
||||
Supervisor.count_children(sup_pid)
|
||||
#=> %{active: 2, specs: 1, supervisors: 0, workers: 2}
|
||||
|
||||
## Exit reasons
|
||||
|
||||
From the example above, you may have noticed that the transient restart
|
||||
strategy for the worker does not restart the child in case it crashes with
|
||||
reason `:normal`, `:shutdown` or `{:shutdown, term}`.
|
||||
|
||||
So one may ask: which exit reason should I choose when existing my worker?
|
||||
There are three options:
|
||||
|
||||
* `:normal` - on such cases, the exit won't be logged, there is no restart
|
||||
on transient mode and linked processes do not exit
|
||||
|
||||
* `:shutdown` or `{:shutdown, term}` - on such cases, the exit won't be
|
||||
logged, there is no restart on transient mode and linked processes exit
|
||||
with the same reason unless trapping exits
|
||||
|
||||
* any other term - on such cases, the exit will be logged, there are
|
||||
restarts on transient mode and linked processes exit with the same reason
|
||||
unless trapping exits
|
||||
|
||||
## Name Registration
|
||||
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
|
||||
+21
-16
@@ -95,7 +95,10 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode_query(q, dict \\ %{}) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
|
||||
case do_decode_query(q) do
|
||||
nil -> dict
|
||||
{{k, v}, q} -> decode_query(q, Dict.put(dict, k, v))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -104,19 +107,19 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map(&(&1))
|
||||
[{"foo", "1"}, {"bar", "2"}]
|
||||
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
Stream.unfold(q, &do_decoder/1)
|
||||
Stream.unfold(q, &do_decode_query/1)
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
defp do_decode_query("") do
|
||||
nil
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
defp do_decode_query(q) do
|
||||
{first, next} =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> {first, rest}
|
||||
@@ -231,7 +234,7 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode(uri) do
|
||||
unpercent(uri)
|
||||
unpercent(uri, "", false)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
@@ -247,23 +250,25 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode_www_form(str) do
|
||||
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
|
||||
unpercent(str, "", true)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect str}"
|
||||
end
|
||||
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
|
||||
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
|
||||
end
|
||||
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
|
||||
defp unpercent(<<?%>>), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail :: binary>>) do
|
||||
<<head>> <> unpercent(tail)
|
||||
defp unpercent(<<?+, tail::binary>>, acc, spaces = true) do
|
||||
unpercent(tail, <<acc::binary, ?\s>>, spaces)
|
||||
end
|
||||
|
||||
defp unpercent(<<>>), do: <<>>
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail::binary>>, acc, spaces) do
|
||||
unpercent(tail, <<acc::binary, bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>>, spaces)
|
||||
end
|
||||
defp unpercent(<<?%, _::binary>>, _acc, _spaces), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail::binary>>, acc, spaces) do
|
||||
unpercent(tail, <<acc::binary, head>>, spaces)
|
||||
end
|
||||
defp unpercent(<<>>, acc, _spaces), do: acc
|
||||
|
||||
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
|
||||
@@ -51,6 +51,19 @@ defmodule Version do
|
||||
|
||||
"~> 2.0.0"
|
||||
|
||||
`~>` will never include pre-release versions of its upper bound.
|
||||
It can also be used to set an upper bound on only the major
|
||||
version part. See the table below for `~>` requirements and
|
||||
their corresponding translation.
|
||||
|
||||
`~>` | Translation
|
||||
:------------- | :---------------------
|
||||
`~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
|
||||
`~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
|
||||
`~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
|
||||
`~> 2.0` | `>= 2.0.0 and < 3.0.0`
|
||||
`~> 2.1` | `>= 2.1.0 and < 3.0.0`
|
||||
|
||||
"""
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
@@ -61,7 +74,7 @@ defmodule Version do
|
||||
@type major :: String.t | non_neg_integer
|
||||
@type minor :: non_neg_integer | nil
|
||||
@type patch :: non_neg_integer | nil
|
||||
@type pre :: [String.t]
|
||||
@type pre :: [String.t | non_neg_integer]
|
||||
@type build :: String.t | nil
|
||||
@type matchable :: {major :: major,
|
||||
minor :: minor,
|
||||
@@ -506,10 +519,22 @@ end
|
||||
|
||||
defimpl String.Chars, for: Version do
|
||||
def to_string(version) do
|
||||
pre = unless Enum.empty?(pre = version.pre), do: "-#{pre}"
|
||||
pre = pre(version.pre)
|
||||
build = if build = version.build, do: "+#{build}"
|
||||
"#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
|
||||
end
|
||||
|
||||
defp pre([]) do
|
||||
""
|
||||
end
|
||||
|
||||
defp pre(pre) do
|
||||
"-" <>
|
||||
Enum.map_join(pre, ".", fn
|
||||
int when is_integer(int) -> Integer.to_string(int)
|
||||
string when is_binary(string) -> string
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version do
|
||||
|
||||
@@ -29,9 +29,12 @@ start(_Type, _Args) ->
|
||||
end,
|
||||
|
||||
ok = io:setopts(standard_io, Opts),
|
||||
%% Must use undocument {unicode, true} to set unicode on standard_error, more
|
||||
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
|
||||
ok = io:setopts(standard_error, [{unicode,true}]),
|
||||
|
||||
ok = case io:setopts(standard_error, [{encoding,utf8}]) of
|
||||
ok -> ok;
|
||||
{error, _} -> io:setopts(standard_error, [{unicode,true}]) %% R17.3 and earlier
|
||||
end,
|
||||
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 1000). %% 1 second
|
||||
-define(limit, 4294967296). %% 2^32
|
||||
-define(timeout, 30000). %% 30 seconds
|
||||
-define(limit, 4294967295). %% 2^32 - 1
|
||||
|
||||
next() ->
|
||||
gen_server:call(?MODULE, next, ?timeout).
|
||||
|
||||
@@ -215,7 +215,7 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
||||
end.
|
||||
|
||||
caller(Line, #{module := nil} = E) ->
|
||||
{elixir_compiler, '__FILE__', 2, location(Line, E)};
|
||||
{elixir_compiler_0, '__FILE__', 1, location(Line, E)};
|
||||
caller(Line, #{module := Module, function := nil} = E) ->
|
||||
{Module, '__MODULE__', 0, location(Line, E)};
|
||||
caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
|
||||
|
||||
@@ -55,6 +55,13 @@ parse_error(Line, File, Error, <<>>) ->
|
||||
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
%% Produce a human-readable message for errors before a sigil
|
||||
parse_error(Line, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) ->
|
||||
{ok, Tokens, _} = erl_scan:string(binary_to_list(Full)),
|
||||
{ok, {sigil, _, Sigil, [Content], _}} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
|
||||
Message = <<"syntax error before: sigil ~", Sigil," with content '", Content/binary, "'">>,
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', Message);
|
||||
|
||||
%% Aliases are wrapped in ['']
|
||||
parse_error(Line, File, Error, <<"['", Token/binary>>) when is_binary(Error) ->
|
||||
Rest =
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
translate({'=', Meta, [Left, Right]}, S) ->
|
||||
Return = case Left of
|
||||
{'_', _, Atom} when is_atom(Atom) -> false;
|
||||
{'_', _, Atom} when is_atom(Atom) -> S#elixir_scope.return;
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
@@ -257,6 +257,9 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
|
||||
%% the is_map/1 guard instead of matching on map. Hopefully
|
||||
%% we can use a match on 17.1.
|
||||
%%
|
||||
%% In the future, we could also use maps:get/2 instead
|
||||
%% of pattern match, reducing the AST footprint.
|
||||
%%
|
||||
%% http://erlang.org/pipermail/erlang-bugs/2014-April/004338.html
|
||||
{{'case', -1, TLeft, [
|
||||
{clause, -1,
|
||||
|
||||
@@ -156,6 +156,16 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.BadStructError') ->
|
||||
erl_tuple_size(Meta, Var, 3),
|
||||
erl_record_compare(Meta, Var, badstruct));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadMapError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_record_compare(Meta, Var, badmap));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.KeyError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_record_compare(Meta, Var, badkey));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
|
||||
erl_or(Meta,
|
||||
{erl(Meta, '=='), Meta, [Var, badarg]},
|
||||
|
||||
@@ -291,7 +291,7 @@ defmodule Kernel.ExceptionTest do
|
||||
|
||||
test "format_fa" do
|
||||
assert Exception.format_fa(fn -> end, 1) =~
|
||||
~r"#Function<\d\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
|
||||
~r"#Function<\d+\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
|
||||
end
|
||||
|
||||
import Exception, only: [message: 1]
|
||||
@@ -327,4 +327,20 @@ defmodule Kernel.ExceptionTest do
|
||||
assert %ErlangError{original: :sample} |> message ==
|
||||
"erlang error: :sample"
|
||||
end
|
||||
|
||||
test "format_stacktrace from file" do
|
||||
assert_raise ArgumentError, fn ->
|
||||
Code.eval_string("def foo do end", [], file: "myfile")
|
||||
end
|
||||
|
||||
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (file)"
|
||||
end
|
||||
|
||||
test "format_stacktrace from module" do
|
||||
assert_raise ArgumentError, fn ->
|
||||
Code.eval_string("defmodule Foo do raise ArgumentError, ~s(oops) end", [], file: "myfile")
|
||||
end
|
||||
|
||||
assert Exception.format_stacktrace(System.stacktrace) =~ "myfile:1: (module)"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
LF
|
||||
CR
CRLF
|
||||
LFCR
|
||||
|
||||
@@ -103,6 +103,9 @@ defmodule IO.ANSI.DocsTest do
|
||||
result = format("*world*")
|
||||
assert result == "\e[1mworld\e[0m\n\e[0m"
|
||||
|
||||
result = format("*world*.")
|
||||
assert result == "\e[1mworld\e[0m.\n\e[0m"
|
||||
|
||||
result = format("**world**")
|
||||
assert result == "\e[1mworld\e[0m\n\e[0m"
|
||||
|
||||
|
||||
@@ -22,6 +22,12 @@ defmodule IOTest do
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :binread_all do
|
||||
{:ok, file} = File.open(Path.expand('fixtures/file.bin', __DIR__))
|
||||
assert "LF\nCR\rCRLF\r\nLFCR\n\r" == IO.binread(file, :all)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :getn do
|
||||
{:ok, file} = File.open(Path.expand('fixtures/file.txt', __DIR__))
|
||||
assert "F" == IO.getn(file, "")
|
||||
|
||||
@@ -151,6 +151,12 @@ defmodule Kernel.ComprehensionTest do
|
||||
assert for({_,x} <- [1, 2, a: 3, b: 4, c: 5], do: x * 2) == [6, 8, 10]
|
||||
end
|
||||
|
||||
test "list for comprehension matched to '_' on last line of block" do
|
||||
assert (if true do
|
||||
_ = for x <- [1, 2, 3], do: x * 2
|
||||
end) == [2, 4, 6]
|
||||
end
|
||||
|
||||
test "list for comprehensions with filters" do
|
||||
assert for(x <- [1, 2, 3], x > 1, x < 3, do: x * 2) == [4]
|
||||
end
|
||||
|
||||
@@ -89,6 +89,12 @@ defmodule Kernel.ErrorsTest do
|
||||
'+.foo'
|
||||
end
|
||||
|
||||
test :syntax_error_before_sigil do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: sigil ~s with content 'bar baz'",
|
||||
'~s(foo) ~s(bar baz)'
|
||||
end
|
||||
|
||||
test :compile_error_on_op_ambiguity do
|
||||
msg = "nofile:1: \"a -1\" looks like a function call but there is a variable named \"a\", " <>
|
||||
"please use explicit parenthesis or even spaces"
|
||||
@@ -750,8 +756,13 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
'''
|
||||
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: spec for undefined function ErrorsTest.omg/0",
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
message = "nofile:2: spec for undefined function omg/0"
|
||||
else
|
||||
message = "nofile:2: spec for undefined function ErrorsTest.omg/0"
|
||||
end
|
||||
|
||||
assert_compile_fail CompileError, message,
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
@spec omg :: atom
|
||||
|
||||
@@ -304,6 +304,36 @@ defmodule Kernel.RaiseTest do
|
||||
assert result == "no match of right hand side value: 0"
|
||||
end
|
||||
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
test :bad_key_error do
|
||||
result = try do
|
||||
%{%{} | foo: :bar}
|
||||
rescue
|
||||
x in [KeyError] -> Exception.message(x)
|
||||
end
|
||||
|
||||
assert result == "key :foo not found"
|
||||
|
||||
result = try do
|
||||
%{}.foo
|
||||
rescue
|
||||
x in [KeyError] -> Exception.message(x)
|
||||
end
|
||||
|
||||
assert result == "key :foo not found in: %{}"
|
||||
end
|
||||
|
||||
test :bad_map_error do
|
||||
result = try do
|
||||
%{zero(0) | foo: :bar}
|
||||
rescue
|
||||
x in [BadMapError] -> Exception.message(x)
|
||||
end
|
||||
|
||||
assert result == "expected a map, got: 0"
|
||||
end
|
||||
end
|
||||
|
||||
test :case_clause_error do
|
||||
x = :example
|
||||
result = try do
|
||||
|
||||
@@ -154,15 +154,6 @@ defmodule Kernel.TypespecTest do
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a range" do
|
||||
module = test_module do
|
||||
@type mytype :: range(1, 10)
|
||||
end
|
||||
|
||||
assert [type: {:mytype, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a range op" do
|
||||
module = test_module do
|
||||
@type mytype :: 1..10
|
||||
@@ -177,11 +168,19 @@ defmodule Kernel.TypespecTest do
|
||||
@type mytype :: %{hello: :world}
|
||||
end
|
||||
|
||||
assert [type: {:mytype,
|
||||
{:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
|
||||
]},
|
||||
[]}] = types(module)
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
assert [type: {:mytype,
|
||||
{:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, [{:atom, _, :hello}, {:atom, _, :world}]}
|
||||
]},
|
||||
[]}] = types(module)
|
||||
else
|
||||
assert [type: {:mytype,
|
||||
{:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
|
||||
]},
|
||||
[]}] = types(module)
|
||||
end
|
||||
end
|
||||
|
||||
test "@type with a struct" do
|
||||
@@ -190,13 +189,23 @@ defmodule Kernel.TypespecTest do
|
||||
@type mytype :: %TestTypespec{hello: :world}
|
||||
end
|
||||
|
||||
assert [type: {:mytype,
|
||||
{:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :other}, {:type, _, :term, []}}
|
||||
]},
|
||||
[]}] = types(module)
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
assert [type: {:mytype,
|
||||
{:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, [{:atom, _, :__struct__}, {:atom, _, TestTypespec}]},
|
||||
{:type, _, :map_field_assoc, [{:atom, _, :hello}, {:atom, _, :world}]},
|
||||
{:type, _, :map_field_assoc, [{:atom, _, :other}, {:type, _, :term, []}]}
|
||||
]},
|
||||
[]}] = types(module)
|
||||
else
|
||||
assert [type: {:mytype,
|
||||
{:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :other}, {:type, _, :term, []}}
|
||||
]},
|
||||
[]}] = types(module)
|
||||
end
|
||||
end
|
||||
|
||||
test "@type with undefined struct" do
|
||||
|
||||
@@ -6,11 +6,25 @@ defmodule KernelTest do
|
||||
test "=~/2" do
|
||||
assert ("abcd" =~ ~r/c(d)/) == true
|
||||
assert ("abcd" =~ ~r/e/) == false
|
||||
assert ("abcd" =~ ~R/c(d)/) == true
|
||||
assert ("abcd" =~ ~R/e/) == false
|
||||
|
||||
string = "^ab+cd*$"
|
||||
assert (string =~ "ab+") == true
|
||||
assert (string =~ "bb") == false
|
||||
|
||||
assert ("abcd" =~ ~r//) == true
|
||||
assert ("abcd" =~ ~R//) == true
|
||||
assert ("abcd" =~ "") == true
|
||||
|
||||
assert ("" =~ ~r//) == true
|
||||
assert ("" =~ ~R//) == true
|
||||
assert ("" =~ "") == true
|
||||
|
||||
assert ("" =~ "abcd") == false
|
||||
assert ("" =~ ~r/abcd/) == false
|
||||
assert ("" =~ ~R/abcd/) == false
|
||||
|
||||
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
|
||||
1234 =~ "hello"
|
||||
end
|
||||
@@ -18,6 +32,34 @@ defmodule KernelTest do
|
||||
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
|
||||
1234 =~ ~r"hello"
|
||||
end
|
||||
|
||||
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
|
||||
1234 =~ ~R"hello"
|
||||
end
|
||||
|
||||
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
|
||||
~r"hello" =~ "hello"
|
||||
end
|
||||
|
||||
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
|
||||
~r"hello" =~ ~r"hello"
|
||||
end
|
||||
|
||||
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
|
||||
:abcd =~ ~r//
|
||||
end
|
||||
|
||||
assert_raise FunctionClauseError, "no function clause matching in Kernel.=~/2", fn ->
|
||||
:abcd =~ ""
|
||||
end
|
||||
|
||||
assert_raise FunctionClauseError, "no function clause matching in Regex.match?/2", fn ->
|
||||
"abcd" =~ nil
|
||||
end
|
||||
|
||||
assert_raise FunctionClauseError, "no function clause matching in Regex.match?/2", fn ->
|
||||
"abcd" =~ :abcd
|
||||
end
|
||||
end
|
||||
|
||||
test "^" do
|
||||
|
||||
@@ -85,8 +85,16 @@ defmodule MapTest do
|
||||
test "update maps" do
|
||||
assert %{two_items_map | a: 3} == %{a: 3, b: 2}
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
%{two_items_map | c: 3}
|
||||
# TODO: proper handling of API changes in different Erlang/OTP releases
|
||||
case :erlang.system_info(:otp_release) do
|
||||
'17' ->
|
||||
assert_raise ArgumentError, fn ->
|
||||
%{two_items_map | c: 3}
|
||||
end
|
||||
_ ->
|
||||
assert_raise KeyError, fn ->
|
||||
%{two_items_map | c: 3}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -121,11 +121,19 @@ defmodule ProtocolTest do
|
||||
end
|
||||
|
||||
test "protocol defines callbacks" do
|
||||
assert get_callbacks(Sample, :ok, 1) ==
|
||||
[{:type, 9, :fun, [{:type, 9, :product, [{:type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
assert get_callbacks(Sample, :ok, 1) ==
|
||||
[{:type, [9], :fun, [{:type, [9], :product, [{:user_type, [9], :t, []}]}, {:type, [9], :boolean, []}]}]
|
||||
|
||||
assert get_callbacks(WithAny, :ok, 1) ==
|
||||
[{:type, 16, :fun, [{:type, 16, :product, [{:type, 16, :t, []}]}, {:type, 16, :term, []}]}]
|
||||
assert get_callbacks(WithAny, :ok, 1) ==
|
||||
[{:type, [16], :fun, [{:type, [16], :product, [{:user_type, [16], :t, []}]}, {:type, [16], :term, []}]}]
|
||||
else
|
||||
assert get_callbacks(Sample, :ok, 1) ==
|
||||
[{:type, 9, :fun, [{:type, 9, :product, [{:type, 9, :t, []}]}, {:type, 9, :boolean, []}]}]
|
||||
|
||||
assert get_callbacks(WithAny, :ok, 1) ==
|
||||
[{:type, 16, :fun, [{:type, 16, :product, [{:type, 16, :t, []}]}, {:type, 16, :term, []}]}]
|
||||
end
|
||||
end
|
||||
|
||||
test "protocol defines attributes" do
|
||||
|
||||
@@ -52,7 +52,10 @@ defmodule RecordTest do
|
||||
|
||||
Record.defrecord :timestamp, [:date, :time]
|
||||
Record.defrecord :user, __MODULE__, name: "john", age: 25
|
||||
Record.defrecordp :file_info, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
Record.defrecordp :file_info,
|
||||
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
Record.defrecordp :certificate, :OTPCertificate,
|
||||
Record.extract(:OTPCertificate, from_lib: "public_key/include/public_key.hrl")
|
||||
|
||||
test "records generates macros that generates tuples" do
|
||||
record = user()
|
||||
|
||||
@@ -326,11 +326,23 @@ defmodule StreamTest do
|
||||
assert Enum.take(stream, 5) == [1, 2, 3, 4, 3]
|
||||
end
|
||||
|
||||
test "flat_map/2 properly halts both inner and outer stream when inner stream is halted" do
|
||||
# Fixes a bug that, when the inner stream was done,
|
||||
# sending it a halt would cause it to return the
|
||||
# inner stream was halted, forcing flat_map to get
|
||||
# the next value from the outer stream, evaluate it,
|
||||
# get annother inner stream, just to halt it.
|
||||
assert [1, 2] # 2 should never be used
|
||||
|> Stream.flat_map(fn 1 -> Stream.repeatedly(fn -> 1 end) end)
|
||||
|> Stream.flat_map(fn 1 -> Stream.repeatedly(fn -> 1 end) end)
|
||||
|> Enum.take(1) == [1]
|
||||
end
|
||||
|
||||
test "interval/1" do
|
||||
stream = Stream.interval(10)
|
||||
now = :erlang.now
|
||||
now = :os.timestamp
|
||||
assert Enum.take(stream, 5) == [0, 1, 2, 3, 4]
|
||||
assert :timer.now_diff(:erlang.now, now) > 50000
|
||||
assert :timer.now_diff(:os.timestamp, now) > 50000
|
||||
end
|
||||
|
||||
test "into/2 and run/1" do
|
||||
@@ -610,9 +622,9 @@ defmodule StreamTest do
|
||||
|
||||
test "timer/1" do
|
||||
stream = Stream.timer(10)
|
||||
now = :erlang.now
|
||||
now = :os.timestamp
|
||||
assert Enum.to_list(stream) == [0]
|
||||
assert :timer.now_diff(:erlang.now, now) > 10000
|
||||
assert :timer.now_diff(:os.timestamp, now) > 10000
|
||||
end
|
||||
|
||||
test "unfold/2" do
|
||||
|
||||
@@ -3,17 +3,12 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule StringIOTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "start and stop" do
|
||||
test "open and close" do
|
||||
{:ok, pid} = StringIO.open("")
|
||||
assert StringIO.close(pid) == {:ok, {"", ""}}
|
||||
end
|
||||
|
||||
test "start_link and stop" do
|
||||
{:ok, pid} = StringIO.open("")
|
||||
assert StringIO.close(pid) == {:ok, {"", ""}}
|
||||
end
|
||||
|
||||
test "peek" do
|
||||
test "contents" do
|
||||
{:ok, pid} = StringIO.open("abc")
|
||||
IO.write(pid, "edf")
|
||||
assert StringIO.contents(pid) == {"abc", "edf"}
|
||||
|
||||
@@ -135,6 +135,8 @@ defmodule StringTest do
|
||||
|
||||
test :rstrip do
|
||||
assert String.rstrip("") == ""
|
||||
assert String.rstrip("1\n") == "1"
|
||||
assert String.rstrip("\r\n") == ""
|
||||
assert String.rstrip(" abc ") == " abc"
|
||||
assert String.rstrip(" abc a") == " abc a"
|
||||
assert String.rstrip("a abc a\n\n") == "a abc a"
|
||||
|
||||
@@ -72,6 +72,8 @@ defmodule VersionTest do
|
||||
assert V.parse("1.0.0-dev") |> elem(1) |> to_string == "1.0.0-dev"
|
||||
assert V.parse("1.0.0+lol") |> elem(1) |> to_string == "1.0.0+lol"
|
||||
assert V.parse("1.0.0-dev+lol") |> elem(1) |> to_string == "1.0.0-dev+lol"
|
||||
assert V.parse("1.0.0-0") |> elem(1) |> to_string == "1.0.0-0"
|
||||
assert V.parse("1.0.0-rc.0") |> elem(1) |> to_string == "1.0.0-rc.0"
|
||||
end
|
||||
|
||||
test "invalid match" do
|
||||
|
||||
@@ -15,11 +15,11 @@ defmodule String.Unicode do
|
||||
data_path = Path.join(__DIR__, "UnicodeData.txt")
|
||||
|
||||
{codes, whitespace} = Enum.reduce File.stream!(data_path), {[], []}, fn(line, {cacc, wacc}) ->
|
||||
[ codepoint, _name, _category,
|
||||
_class, bidi, _decomposition,
|
||||
_numeric_1, _numeric_2, _numeric_3,
|
||||
_bidi_mirror, _unicode_1, _iso,
|
||||
upper, lower, title ] = :binary.split(line, ";", [:global])
|
||||
[codepoint, _name, _category,
|
||||
_class, bidi, _decomposition,
|
||||
_numeric_1, _numeric_2, _numeric_3,
|
||||
_bidi_mirror, _unicode_1, _iso,
|
||||
upper, lower, title] = :binary.split(line, ";", [:global])
|
||||
|
||||
title = :binary.part(title, 0, byte_size(title) - 1)
|
||||
|
||||
@@ -36,42 +36,42 @@ defmodule String.Unicode do
|
||||
special_path = Path.join(__DIR__, "SpecialCasing.txt")
|
||||
|
||||
codes = Enum.reduce File.stream!(special_path), codes, fn(line, acc) ->
|
||||
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
|
||||
[codepoint, lower, title, upper, _comment] = :binary.split(line, "; ", [:global])
|
||||
key = to_binary.(codepoint)
|
||||
:lists.keystore(key, 1, acc, {key, to_binary.(upper), to_binary.(lower), to_binary.(title)})
|
||||
end
|
||||
|
||||
# Downcase
|
||||
|
||||
def downcase(string), do: do_downcase(string) |> IO.iodata_to_binary
|
||||
def downcase(string), do: downcase(string, "")
|
||||
|
||||
for {codepoint, _upper, lower, _title} <- codes, lower && lower != codepoint do
|
||||
defp do_downcase(unquote(codepoint) <> rest) do
|
||||
unquote(:binary.bin_to_list(lower)) ++ downcase(rest)
|
||||
defp downcase(unquote(codepoint) <> rest, acc) do
|
||||
downcase(rest, acc <> unquote(lower))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_downcase(<< char, rest :: binary >>) do
|
||||
[char|do_downcase(rest)]
|
||||
defp downcase(<<char, rest :: binary>>, acc) do
|
||||
downcase(rest, <<acc::binary, char>>)
|
||||
end
|
||||
|
||||
defp do_downcase(""), do: []
|
||||
defp downcase("", acc), do: acc
|
||||
|
||||
# Upcase
|
||||
|
||||
def upcase(string), do: do_upcase(string) |> IO.iodata_to_binary
|
||||
def upcase(string), do: upcase(string, "")
|
||||
|
||||
for {codepoint, upper, _lower, _title} <- codes, upper && upper != codepoint do
|
||||
defp do_upcase(unquote(codepoint) <> rest) do
|
||||
unquote(:binary.bin_to_list(upper)) ++ do_upcase(rest)
|
||||
defp upcase(unquote(codepoint) <> rest, acc) do
|
||||
upcase(rest, acc <> unquote(upper))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_upcase(<< char, rest :: binary >>) do
|
||||
[char|do_upcase(rest)]
|
||||
defp upcase(<<char, rest :: binary>>, acc) do
|
||||
upcase(rest, <<acc::binary, char>>)
|
||||
end
|
||||
|
||||
defp do_upcase(""), do: []
|
||||
defp upcase("", acc), do: acc
|
||||
|
||||
# Titlecase once
|
||||
|
||||
@@ -88,7 +88,6 @@ defmodule String.Unicode do
|
||||
end
|
||||
|
||||
# Strip
|
||||
|
||||
def lstrip(""), do: ""
|
||||
|
||||
for codepoint <- whitespace do
|
||||
@@ -99,27 +98,45 @@ defmodule String.Unicode do
|
||||
|
||||
def lstrip(other) when is_binary(other), do: other
|
||||
|
||||
def rstrip(string) when is_binary(string) do
|
||||
do_rstrip(string, [], [])
|
||||
end
|
||||
|
||||
@whitespace_max_size 3
|
||||
for codepoint <- whitespace do
|
||||
c = :binary.bin_to_list(codepoint) |> :lists.reverse
|
||||
# We need to increment @whitespace_max_size as well
|
||||
# as the small table (_s) if we add a new entry here.
|
||||
case byte_size(codepoint) do
|
||||
3 ->
|
||||
defp do_rstrip_l(unquote(codepoint)), do: -3
|
||||
2 ->
|
||||
defp do_rstrip_l(<<_, unquote(codepoint)>>), do: -2
|
||||
|
||||
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
|
||||
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
|
||||
defp do_rstrip_s(unquote(codepoint)), do: <<>>
|
||||
1 ->
|
||||
defp do_rstrip_l(<<unquote(codepoint), unquote(codepoint), unquote(codepoint)>>), do: -3
|
||||
defp do_rstrip_l(<<_, unquote(codepoint), unquote(codepoint)>>), do: -2
|
||||
defp do_rstrip_l(<<_, _, unquote(codepoint)>>), do: -1
|
||||
|
||||
defp do_rstrip_s(<<x, unquote(codepoint)>>), do: do_rstrip_s(<<x>>)
|
||||
defp do_rstrip_s(unquote(codepoint)), do: <<>>
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rstrip(<< char, rest :: binary >>, nil, acc2) do
|
||||
do_rstrip(rest, nil, [char|acc2])
|
||||
defp do_rstrip_l(_), do: 0
|
||||
defp do_rstrip_s(o), do: o
|
||||
|
||||
def rstrip(string) when is_binary(string) do
|
||||
rstrip(string, byte_size(string))
|
||||
end
|
||||
|
||||
defp do_rstrip(<< char, rest :: binary >>, acc1, _acc2) do
|
||||
do_rstrip(rest, nil, [char|acc1])
|
||||
defp rstrip(string, size) when size < @whitespace_max_size do
|
||||
do_rstrip_s(string)
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> IO.iodata_to_binary
|
||||
defp rstrip(string, size) do
|
||||
trail = binary_part(string, size, -@whitespace_max_size)
|
||||
case do_rstrip_l(trail) do
|
||||
0 -> string
|
||||
x -> rstrip(binary_part(string, 0, size + x), size + x)
|
||||
end
|
||||
end
|
||||
|
||||
# Split
|
||||
|
||||
@@ -190,7 +207,7 @@ defmodule String.Graphemes do
|
||||
end
|
||||
|
||||
cluster = Enum.reduce File.stream!(cluster_path), HashDict.new, fn(line, dict) ->
|
||||
[ _full, first, last, class ] = Regex.run(regex, line)
|
||||
[_full, first, last, class] = Regex.run(regex, line)
|
||||
|
||||
# Skip surrogates
|
||||
if first == "D800" and last == "DFFF" do
|
||||
|
||||
@@ -207,26 +207,16 @@ defmodule ExUnit.Assertions do
|
||||
true
|
||||
end
|
||||
|
||||
@doc """
|
||||
Asserts `value` is true.
|
||||
If it fails, it raises an expectation error
|
||||
using the given `left` and `right` values.
|
||||
|
||||
You probably don't need to use this—the regular `assert` function
|
||||
handles this for you.
|
||||
|
||||
## Examples
|
||||
|
||||
assert this > that, this, that, "more than"
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def assert(value, left, right, message) when is_binary(message) do
|
||||
assert(value, left: left, right: right, message: message)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Asserts a message was or is going to be received. Unlike
|
||||
`assert_received`, it has a default timeout of 100 milliseconds.
|
||||
Asserts a message was or is going to be received.
|
||||
|
||||
Unlike `assert_received`, it has a default timeout
|
||||
of 100 milliseconds.
|
||||
|
||||
The `expected` argument is a pattern.
|
||||
|
||||
@@ -460,8 +450,6 @@ defmodule ExUnit.Assertions do
|
||||
end
|
||||
|
||||
@doc """
|
||||
refute_receive message, timeout \\ 100, message \\ nil
|
||||
|
||||
Asserts `message` was not received (and won't be received) within
|
||||
the `timeout` period.
|
||||
|
||||
|
||||
@@ -9,13 +9,17 @@ defmodule ExUnit.CaptureIO do
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
test :example do
|
||||
assert capture_io(fn ->
|
||||
IO.puts "a"
|
||||
end) == "a\n"
|
||||
test "checking the return value and the IO output" do
|
||||
fun = fn ->
|
||||
assert Enum.each(["some", "example"], &(IO.puts &1)) == :ok
|
||||
end
|
||||
|
||||
assert capture_io(fun) == "some\nexample\n"
|
||||
|
||||
# Or use only: `capture_io(fun)` to silence the
|
||||
# IO output (so only assert the return value)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -91,15 +95,13 @@ defmodule ExUnit.CaptureIO do
|
||||
prompt_config = Keyword.get(options, :capture_prompt, true)
|
||||
input = Keyword.get(options, :input, "")
|
||||
|
||||
original_gl = :erlang.group_leader
|
||||
original_gl = Process.group_leader()
|
||||
{:ok, capture_gl} = StringIO.open(input, capture_prompt: prompt_config)
|
||||
:erlang.group_leader(capture_gl, self)
|
||||
|
||||
try do
|
||||
fun.()
|
||||
StringIO.close(capture_gl) |> elem(1) |> elem(1)
|
||||
Process.group_leader(self(), capture_gl)
|
||||
do_capture_io(capture_gl, fun)
|
||||
after
|
||||
:erlang.group_leader(original_gl, self)
|
||||
Process.group_leader(self(), original_gl)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -119,8 +121,7 @@ defmodule ExUnit.CaptureIO do
|
||||
Process.register(capture_io, device)
|
||||
|
||||
try do
|
||||
fun.()
|
||||
StringIO.close(capture_io) |> elem(1) |> elem(1)
|
||||
do_capture_io(capture_io, fun)
|
||||
after
|
||||
try do
|
||||
Process.unregister(device)
|
||||
@@ -131,4 +132,20 @@ defmodule ExUnit.CaptureIO do
|
||||
ExUnit.Server.remove_device(device)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_capture_io(string_io, fun) do
|
||||
try do
|
||||
_ = fun.()
|
||||
:ok
|
||||
catch
|
||||
kind, reason ->
|
||||
stack = System.stacktrace()
|
||||
_ = StringIO.close(string_io)
|
||||
:erlang.raise(kind, reason, stack)
|
||||
else
|
||||
:ok ->
|
||||
{:ok, output} = StringIO.close(string_io)
|
||||
elem(output, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -162,15 +162,17 @@ defmodule ExUnit.Case do
|
||||
|
||||
quote do
|
||||
unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
|
||||
if unquote(async) do
|
||||
ExUnit.Server.add_async_case(__MODULE__)
|
||||
else
|
||||
ExUnit.Server.add_sync_case(__MODULE__)
|
||||
end
|
||||
|
||||
Enum.each [:ex_unit_tests, :tag, :moduletag],
|
||||
&Module.register_attribute(__MODULE__, &1, accumulate: true)
|
||||
|
||||
if unquote(async) do
|
||||
@moduletag async: true
|
||||
ExUnit.Server.add_async_case(__MODULE__)
|
||||
else
|
||||
@moduletag async: false
|
||||
ExUnit.Server.add_sync_case(__MODULE__)
|
||||
end
|
||||
|
||||
@before_compile ExUnit.Case
|
||||
use ExUnit.Callbacks
|
||||
end
|
||||
|
||||
@@ -9,23 +9,23 @@ defmodule ExUnit.Formatter do
|
||||
The following events are possible:
|
||||
|
||||
* `{:suite_started, opts}` -
|
||||
the suite has started with the specified options to the runner.
|
||||
the suite has started with the specified options to the runner.
|
||||
|
||||
* `{:suite_finished, run_us, load_us}` -
|
||||
the suite has finished. `run_us` and `load_us` are the run and load
|
||||
times in microseconds respectively.
|
||||
the suite has finished. `run_us` and `load_us` are the run and load
|
||||
times in microseconds respectively.
|
||||
|
||||
* `{:case_started, test_case}` -
|
||||
a test case has started. See `ExUnit.TestCase` for details.
|
||||
a test case has started. See `ExUnit.TestCase` for details.
|
||||
|
||||
* `{:case_finished, test_case}` -
|
||||
a test case has finished. See `ExUnit.TestCase` for details.
|
||||
a test case has finished. See `ExUnit.TestCase` for details.
|
||||
|
||||
* `{:test_started, test_case}` -
|
||||
a test case has started. See `ExUnit.Test` for details.
|
||||
a test case has started. See `ExUnit.Test` for details.
|
||||
|
||||
* `{:test_finished, test_case}` -
|
||||
a test case has finished. See `ExUnit.Test` for details.
|
||||
a test case has finished. See `ExUnit.Test` for details.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule ExUnit.Runner do
|
||||
|> Keyword.put(:exclude, exclude)
|
||||
|> Keyword.put(:include, include)
|
||||
|> Keyword.put_new(:max_cases, :erlang.system_info(:schedulers_online))
|
||||
|> Keyword.put_new(:seed, :erlang.now |> elem(2))
|
||||
|> Keyword.put_new(:seed, :os.timestamp |> elem(2))
|
||||
end
|
||||
|
||||
defp loop(config) do
|
||||
@@ -159,7 +159,9 @@ defmodule ExUnit.Runner do
|
||||
{test_case, pending} =
|
||||
receive do
|
||||
{^case_pid, :case_finished, test_case, tests} ->
|
||||
Process.demonitor(case_ref, [:flush])
|
||||
receive do
|
||||
{:DOWN, ^case_ref, :process, ^case_pid, _} -> :ok
|
||||
end
|
||||
{test_case, tests}
|
||||
{:DOWN, ^case_ref, :process, ^case_pid, error} ->
|
||||
test_case = %{test_case | state: {:failed, {{:EXIT, case_pid}, error, []}}}
|
||||
@@ -214,7 +216,9 @@ defmodule ExUnit.Runner do
|
||||
test =
|
||||
receive do
|
||||
{^test_pid, :test_finished, test} ->
|
||||
Process.demonitor(test_ref, [:flush])
|
||||
receive do
|
||||
{:DOWN, ^test_ref, :process, ^test_pid, _} -> :ok
|
||||
end
|
||||
test
|
||||
{:DOWN, ^test_ref, :process, ^test_pid, error} ->
|
||||
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
|
||||
|
||||
@@ -28,6 +28,25 @@ defmodule ExUnit.CaptureIOTest do
|
||||
import ExUnit.CaptureIO
|
||||
doctest ExUnit.CaptureIO, import: true
|
||||
|
||||
test "no leakage on failures" do
|
||||
group_leader = Process.group_leader()
|
||||
|
||||
test = self()
|
||||
assert_raise ArgumentError, fn ->
|
||||
capture_io(fn ->
|
||||
send(test, {:string_io, Process.group_leader()})
|
||||
raise ArgumentError
|
||||
end)
|
||||
end
|
||||
|
||||
receive do
|
||||
{:string_io, pid} ->
|
||||
ref = Process.monitor(pid)
|
||||
assert_receive {:DOWN, ^ref, _, _, _}
|
||||
end
|
||||
assert Process.group_leader() == group_leader
|
||||
end
|
||||
|
||||
test "with no output" do
|
||||
assert capture_io(fn ->
|
||||
end) == ""
|
||||
@@ -290,19 +309,14 @@ defmodule ExUnit.CaptureIOTest do
|
||||
end
|
||||
|
||||
test "with assert inside" do
|
||||
group_leader = :erlang.group_leader
|
||||
|
||||
try do
|
||||
capture_io(fn ->
|
||||
assert false
|
||||
end)
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"Expected truthy, got false" = error.message
|
||||
assert error.message == "Expected truthy, got false"
|
||||
end
|
||||
|
||||
# Ensure no leakage on failures
|
||||
assert group_leader == :erlang.group_leader
|
||||
end
|
||||
|
||||
test "capture :stderr by two processes" do
|
||||
|
||||
@@ -19,6 +19,7 @@ defmodule ExUnit.CaseTest do
|
||||
assert context[:case] == __MODULE__
|
||||
assert context[:test] == __ENV__.function |> elem(0)
|
||||
assert context[:line] == line
|
||||
assert context[:async] == true
|
||||
assert context[:hello] == true
|
||||
assert context[:world] == :good
|
||||
end
|
||||
|
||||
+33
-17
@@ -391,27 +391,43 @@ defmodule IEx do
|
||||
Setting variables or importing modules in IEx does not
|
||||
affect the caller the environment (hence it is called `pry`).
|
||||
"""
|
||||
defmacro pry(timeout \\ 1000) do
|
||||
defmacro pry(timeout \\ 5000) do
|
||||
quote do
|
||||
env = __ENV__
|
||||
meta = "#{inspect self} at #{Path.relative_to_cwd(env.file)}:#{env.line}"
|
||||
opts = [binding: binding, dot_iex_path: "", env: env, prefix: "pry"]
|
||||
res = IEx.Server.take_over("Request to pry #{meta}", opts, unquote(timeout))
|
||||
|
||||
# We cannot use colors because IEx may be off.
|
||||
case res do
|
||||
{:error, :self} = err ->
|
||||
IO.puts :stdio, "IEx cannot pry itself."
|
||||
{:error, :no_iex} = err ->
|
||||
IO.puts :stdio, "Cannot pry #{meta}. Is an IEx shell running?"
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
res
|
||||
IEx.pry(binding, __ENV__, unquote(timeout))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback for `IEx.pry/1`.
|
||||
|
||||
You can invoke this function directly when you are not able to invoke
|
||||
`IEx.pry/1` as a macro. This function expects the binding (from
|
||||
`Kernel.binding/0`), the environment (from `__ENV__`) and the timeout
|
||||
(a sensible default is 5000).
|
||||
"""
|
||||
def pry(binding, env, timeout) do
|
||||
meta = "#{inspect self} at #{Path.relative_to_cwd(env.file)}:#{env.line}"
|
||||
opts = [binding: binding, dot_iex_path: "", env: env, prefix: "pry"]
|
||||
res = IEx.Server.take_over("Request to pry #{meta}", opts, timeout)
|
||||
|
||||
# We cannot use colors because IEx may be off.
|
||||
case res do
|
||||
{:error, :self} ->
|
||||
IO.puts :stdio, "IEx cannot pry the shell itself."
|
||||
{:error, :no_iex} ->
|
||||
extra =
|
||||
case :os.type do
|
||||
{:win32, _} -> " If you are Windows, you may need to start IEx with the --werl flag."
|
||||
_ -> ""
|
||||
end
|
||||
IO.puts :stdio, "Cannot pry #{meta}. Is an IEx shell running?" <> extra
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
# This is a callback invoked by Erlang shell utilities
|
||||
|
||||
@@ -177,19 +177,15 @@ defmodule IEx.Autocomplete do
|
||||
end
|
||||
|
||||
defp get_modules_from_applications do
|
||||
for {app, _, _} <- which_applications(),
|
||||
for [app] <- loaded_applications(),
|
||||
{_, modules} = :application.get_key(app, :modules),
|
||||
module <- modules do
|
||||
Atom.to_string(module)
|
||||
end
|
||||
end
|
||||
|
||||
defp which_applications() do
|
||||
try do
|
||||
:application.which_applications(5000)
|
||||
catch
|
||||
:exit, {:timeout, _} -> [:elixir, :iex]
|
||||
end
|
||||
defp loaded_applications do
|
||||
:ets.match(:ac_tab, {{:loaded, :"$1"}, :_})
|
||||
end
|
||||
|
||||
defp match_module_funs(mod, hint) do
|
||||
|
||||
@@ -53,6 +53,11 @@ defmodule Logger.Translator do
|
||||
"** (exit) " <> Exception.format_exit(reason)
|
||||
{:ok, msg}
|
||||
|
||||
{'Error in process ' ++ _, [pid, {reason, stack}]} ->
|
||||
msg = ["Process ", inspect(pid), " raised an exception\n" |
|
||||
Exception.format(:error, reason, stack)]
|
||||
{:ok, msg}
|
||||
|
||||
_ ->
|
||||
:none
|
||||
end
|
||||
@@ -82,8 +87,8 @@ defmodule Logger.Translator do
|
||||
defp translate_supervisor(min_level,
|
||||
[supervisor: sup, errorContext: context,
|
||||
reason: reason,
|
||||
offender: [{:pid, pid}, {:name, name} | offender]])
|
||||
when is_pid(pid) and context !== :shutdown do
|
||||
offender: [{:pid, pid}, {name_or_id, name} | offender]])
|
||||
when is_pid(pid) and context !== :shutdown and name_or_id in [:name, :id] do
|
||||
{:ok, ["Child ", inspect(name), " of Supervisor ",
|
||||
sup_name(sup), ?\s, sup_context(context), ?\n,
|
||||
"Pid: ", inspect(pid), ?\n,
|
||||
@@ -95,7 +100,7 @@ defmodule Logger.Translator do
|
||||
[supervisor: sup, errorContext: context,
|
||||
reason: reason,
|
||||
offender: [{:pid, _pid},
|
||||
{:name, name} | offender]]) do
|
||||
{name_or_id, name} | offender]]) when name_or_id in [:name, :id] do
|
||||
{:ok, ["Child ", inspect(name), " of Supervisor ",
|
||||
sup_name(sup), ?\s, sup_context(context), ?\n,
|
||||
child_info(min_level, offender), ?\n,
|
||||
@@ -117,7 +122,7 @@ defmodule Logger.Translator do
|
||||
[supervisor: sup, errorContext: context,
|
||||
reason: reason,
|
||||
offender: [{:nb_children, n},
|
||||
{:name, name} | offender]]) do
|
||||
{name_or_id, name} | offender]]) when name_or_id in [:name, :id] do
|
||||
{:ok, ["Children ", inspect(name), " of Supervisor ",
|
||||
sup_name(sup), ?\s, sup_context(context), ?\n,
|
||||
"Number: ", inspect(n), ?\n,
|
||||
@@ -134,7 +139,7 @@ defmodule Logger.Translator do
|
||||
|
||||
defp translate_progress(min_level,
|
||||
[supervisor: sup,
|
||||
started: [{:pid, pid}, {:name, name} | started]]) do
|
||||
started: [{:pid, pid}, {name_or_id, name} | started]]) when name_or_id in [:name, :id] do
|
||||
{:ok, ["Child ", inspect(name), " of Supervisor ",
|
||||
sup_name(sup), " started\n",
|
||||
"Pid: ", inspect(pid), ?\n |
|
||||
@@ -162,12 +167,12 @@ defmodule Logger.Translator do
|
||||
defp sup_context(:shutdown_error), do: "shutdown abnormally"
|
||||
|
||||
defp child_info(min_level, [{:mfargs, {mod, fun, args}} | debug]) do
|
||||
["Start Call: ", Exception.format_mfa(mod, fun, args) |
|
||||
["Start Call: ", format_mfa(mod, fun, args) |
|
||||
child_debug(min_level, debug)]
|
||||
end
|
||||
|
||||
defp child_info(min_level, [{:mfa, {mod, fun, args}} | debug]) do
|
||||
["Start Call: ", Exception.format_mfa(mod, fun, args) |
|
||||
["Start Call: ", format_mfa(mod, fun, args) |
|
||||
child_debug(min_level, debug)]
|
||||
end
|
||||
|
||||
@@ -248,11 +253,11 @@ defmodule Logger.Translator do
|
||||
end
|
||||
|
||||
defp crash_call(mod, fun, arity) when is_integer(arity) do
|
||||
Exception.format_mfa(mod, fun, arity)
|
||||
format_mfa(mod, fun, arity)
|
||||
end
|
||||
|
||||
defp crash_call(mod, fun, args) do
|
||||
Exception.format_mfa(mod, fun, length(args))
|
||||
format_mfa(mod, fun, length(args))
|
||||
end
|
||||
|
||||
defp crash_debug(:debug,
|
||||
@@ -295,4 +300,6 @@ defmodule Logger.Translator do
|
||||
crash_info(min_level, info, [prefix | prefix])]
|
||||
end
|
||||
|
||||
defp format_mfa(mod, fun, :undefined), do: [inspect(mod), ?., to_string(fun) | "/?"]
|
||||
defp format_mfa(mod, fun, args), do: Exception.format_mfa(mod, fun, args)
|
||||
end
|
||||
|
||||
@@ -39,17 +39,17 @@ defmodule Logger.ErrorHandlerTest do
|
||||
assert error_log(:error_report, :special, :hello) == ""
|
||||
end
|
||||
|
||||
test "formats error_logger warning message" do
|
||||
# Warnings by default are logged as errors by Erlang
|
||||
assert error_log(:warning_msg, "hello", []) =~ msg("[error] hello")
|
||||
assert error_log(:warning_msg, "~p~n", [:hello]) =~ msg("[error] :hello")
|
||||
end
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
test "formats error_logger warning message" do
|
||||
assert error_log(:warning_msg, "hello", []) =~ msg("[warn] hello")
|
||||
assert error_log(:warning_msg, "~p~n", [:hello]) =~ msg("[warn] :hello")
|
||||
end
|
||||
|
||||
test "formats error_logger warning report" do
|
||||
# Warnings by default are logged as errors by Erlang
|
||||
assert error_log(:warning_report, "hello") =~ msg("[error] \"hello\"")
|
||||
assert error_log(:warning_report, :hello) =~ msg("[error] :hello")
|
||||
assert error_log(:warning_report, :special, :hello) == ""
|
||||
test "formats error_logger warning report" do
|
||||
assert error_log(:warning_report, "hello") =~ msg("[warn] \"hello\"")
|
||||
assert error_log(:warning_report, :hello) =~ msg("[warn] :hello")
|
||||
assert error_log(:warning_report, :special, :hello) == ""
|
||||
end
|
||||
end
|
||||
|
||||
defp error_log(fun, format) do
|
||||
|
||||
@@ -154,6 +154,21 @@ defmodule Logger.TranslatorTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "translates Process crashes" do
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
assert capture_log(:info, fn ->
|
||||
{_, ref} = spawn_monitor(fn() -> raise "oops" end)
|
||||
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
|
||||
# Even though the monitor has been received the emulator may not have
|
||||
# sent the message to the error logger
|
||||
:timer.sleep(200)
|
||||
end) =~ ~r"""
|
||||
\[error\] Process #PID<\d+\.\d+\.\d+>\ raised an exception
|
||||
\*\* \(RuntimeError\) oops
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
test "translates :proc_lib crashes with name" do
|
||||
{:ok, pid} = Task.start_link(__MODULE__, :task,
|
||||
[self(), fn() ->
|
||||
@@ -476,6 +491,17 @@ defmodule Logger.TranslatorTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "handles :undefined MFA properly" do
|
||||
children = [Supervisor.Spec.worker(GenServer, [], restart: :temporary)]
|
||||
opts = [strategy: :simple_one_for_one]
|
||||
{:ok, sup} = Supervisor.start_link(children, opts)
|
||||
assert capture_log(:info, fn ->
|
||||
{:ok, pid} = Supervisor.start_child(sup, [MyGenServer, []])
|
||||
catch_exit(GenServer.call(pid, :error))
|
||||
[] = Supervisor.which_children(sup)
|
||||
end) =~ "Start Call: GenServer.start_link/?"
|
||||
end
|
||||
|
||||
def task(parent, fun \\ (fn() -> raise "oops" end)) do
|
||||
Process.unlink(parent)
|
||||
receive do: (:go -> fun.())
|
||||
|
||||
@@ -99,6 +99,8 @@ defmodule Mix.Compilers.Elixir do
|
||||
|
||||
defp compile_manifest(manifest, entries, stale, dest, on_start) do
|
||||
Mix.Project.build_structure
|
||||
true = Code.prepend_path(dest)
|
||||
|
||||
on_start.()
|
||||
cwd = File.cwd!
|
||||
|
||||
|
||||
@@ -72,6 +72,11 @@ defmodule Mix.Compilers.Erlang do
|
||||
# Build the project structure so we can write down compiled files.
|
||||
Mix.Project.build_structure
|
||||
|
||||
# Let's prepend the newly created path so compiled files
|
||||
# can be accessed still during compilation (for behaviours
|
||||
# and what not).
|
||||
Code.prepend_path(Mix.Project.compile_path)
|
||||
|
||||
# Remove manifest entries with no source
|
||||
Enum.each(removed, &File.rm/1)
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ defmodule Mix.Config do
|
||||
config
|
||||
rescue
|
||||
e in [LoadError] -> reraise(e, System.stacktrace)
|
||||
e -> raise LoadError, file: file, error: e
|
||||
e -> reraise(LoadError, [file: file, error: e], System.stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ defmodule Mix.Dep.Fetcher do
|
||||
dep_names = Enum.map(deps, fn dep -> dep.app end)
|
||||
|
||||
parents = Enum.filter all_deps, fn dep ->
|
||||
Enum.any?(dep.deps, &(&1 in dep_names))
|
||||
Enum.any?(dep.deps, &(&1.app in dep_names))
|
||||
end
|
||||
|
||||
do_with_depending(parents, all_deps) ++ parents
|
||||
|
||||
@@ -80,8 +80,10 @@ defmodule Mix.Project do
|
||||
# The configuration that is pushed down to dependencies.
|
||||
@doc false
|
||||
def deps_config(config \\ config()) do
|
||||
[build_path: build_path(config),
|
||||
[build_embedded: config[:build_embedded],
|
||||
build_path: build_path(config),
|
||||
build_per_environment: config[:build_per_environment],
|
||||
consolidate_protocols: false,
|
||||
deps_path: deps_path(config)]
|
||||
end
|
||||
|
||||
@@ -320,7 +322,7 @@ defmodule Mix.Project do
|
||||
|
||||
_ = cond do
|
||||
opts[:symlink_ebin] ->
|
||||
_ = Mix.Utils.symlink_or_copy(source, target)
|
||||
_ = symlink_or_copy(config, source, target)
|
||||
match?({:ok, _}, :file.read_link(target)) ->
|
||||
_ = File.rm_rf!(target)
|
||||
File.mkdir_p!(target)
|
||||
@@ -328,11 +330,22 @@ defmodule Mix.Project do
|
||||
File.mkdir_p!(target)
|
||||
end
|
||||
|
||||
_ = Mix.Utils.symlink_or_copy(Path.expand("include"), Path.join(app, "include"))
|
||||
_ = Mix.Utils.symlink_or_copy(Path.expand("priv"), Path.join(app, "priv"))
|
||||
_ = symlink_or_copy(config, Path.expand("include"), Path.join(app, "include"))
|
||||
_ = symlink_or_copy(config, Path.expand("priv"), Path.join(app, "priv"))
|
||||
:ok
|
||||
end
|
||||
|
||||
defp symlink_or_copy(config, source, target) do
|
||||
if config[:build_embedded] do
|
||||
if File.exists?(source) do
|
||||
File.rm_rf!(target)
|
||||
File.cp_r!(source, target)
|
||||
end
|
||||
else
|
||||
Mix.Utils.symlink_or_copy(source, target)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all load paths for this project.
|
||||
"""
|
||||
@@ -378,6 +391,7 @@ defmodule Mix.Project do
|
||||
defp default_config do
|
||||
[aliases: [],
|
||||
build_per_environment: true,
|
||||
build_embedded: false,
|
||||
default_task: "run",
|
||||
deps: [],
|
||||
deps_path: "deps",
|
||||
@@ -386,7 +400,8 @@ defmodule Mix.Project do
|
||||
erlc_include_path: "include",
|
||||
erlc_options: [:debug_info],
|
||||
lockfile: "mix.lock",
|
||||
preferred_cli_env: []]
|
||||
preferred_cli_env: [],
|
||||
start_permanent: false]
|
||||
end
|
||||
|
||||
defp get_project_config(nil), do: []
|
||||
|
||||
@@ -1,29 +1,53 @@
|
||||
defmodule Mix.Tasks.App.Start do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Start all registered apps"
|
||||
@recursive true
|
||||
|
||||
@moduledoc """
|
||||
Starts all registered apps. If no apps key exists,
|
||||
it starts the current application.
|
||||
Starts all registered apps.
|
||||
|
||||
The application is started by default as temporary. In case
|
||||
`:start_permanent` is set to true in your prject configuration
|
||||
or the `--permanent` flag is given, it is started as permanent,
|
||||
which guarantee the node will shutdown in case the application
|
||||
crashes permanently.
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--force` - force compilation regardless of compilation times
|
||||
* `--no-compile` - do not compile even if files require compilation
|
||||
* `--force` - force compilation regardless of compilation times
|
||||
* `--temporary` - start the application as temporary
|
||||
* `--permanent` - start the application as permanent
|
||||
* `--no-compile` - do not compile even if files require compilation
|
||||
* `--no-protocols` - do not load consolidated protocols
|
||||
* `--no-deps-check` - do not check dependencies
|
||||
* `--no-elixir-version-check`
|
||||
- do not check elixir version
|
||||
* `--no-start` - do not start applications after compilation
|
||||
* `--no-elixir-version-check` - do not check elixir version
|
||||
* `--no-start` - do not start applications after compilation
|
||||
|
||||
"""
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
def run(args) do
|
||||
Mix.Project.get!
|
||||
Mix.Task.run "loadpaths", ["--no-readd"|args]
|
||||
|
||||
{opts, _, _} = OptionParser.parse args, switches: [permanent: :boolean, temporary: :boolean]
|
||||
Mix.Task.run "loadpaths", args
|
||||
|
||||
unless "--no-compile" in args do
|
||||
Mix.Task.run "compile", ["--no-readd"|args]
|
||||
Mix.Task.run "compile", args
|
||||
end
|
||||
|
||||
unless "--no-protocols" in args do
|
||||
path = Path.join(Mix.Project.build_path, "consolidated")
|
||||
|
||||
if File.dir?(path) do
|
||||
Code.prepend_path(path)
|
||||
|
||||
Enum.each(File.ls!(path), fn file ->
|
||||
module = file |> Path.rootname() |> String.to_atom()
|
||||
:code.purge(module)
|
||||
:code.delete(module)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
unless "--no-start" in args do
|
||||
@@ -32,17 +56,15 @@ defmodule Mix.Tasks.App.Start do
|
||||
# or not.
|
||||
#
|
||||
# Mix should not depend directly on Logger so check that it's loaded.
|
||||
if Code.ensure_loaded?(Logger), do: Logger.App.stop()
|
||||
start(Mix.Project.config[:app])
|
||||
if Process.whereis(Logger), do: Logger.App.stop()
|
||||
start(Mix.Project.config, opts)
|
||||
end
|
||||
|
||||
Code.readd_paths()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start(app) do
|
||||
if app do
|
||||
case Application.ensure_all_started(app) do
|
||||
def start(config, opts) do
|
||||
if app = config[:app] do
|
||||
case Application.ensure_all_started(app, type(config, opts)) do
|
||||
{:ok, _} -> :ok
|
||||
{:error, {app, reason}} ->
|
||||
Mix.raise "Could not start application #{app}: " <>
|
||||
@@ -52,4 +74,14 @@ defmodule Mix.Tasks.App.Start do
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def type(config, opts) do
|
||||
cond do
|
||||
opts[:temporary] -> :temporary
|
||||
opts[:permanent] -> :permanent
|
||||
config[:start_permanent] -> :permanent
|
||||
true -> :temporary
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
defmodule Mix.Tasks.Compile.All do
|
||||
use Mix.Task
|
||||
|
||||
@moduledoc false
|
||||
@recursive true
|
||||
|
||||
# This is an internal task used by mix compile which
|
||||
# is meant to be recursive and be invoked for each child
|
||||
# project.
|
||||
|
||||
def run(args) do
|
||||
Mix.Project.get!
|
||||
|
||||
res =
|
||||
Enum.map(Mix.Tasks.Compile.compilers(), fn(compiler) ->
|
||||
Mix.Task.run("compile.#{compiler}", args)
|
||||
end)
|
||||
|
||||
true = Code.prepend_path(Mix.Project.compile_path)
|
||||
if :ok in res, do: :ok, else: :noop
|
||||
end
|
||||
end
|
||||
@@ -104,7 +104,7 @@ defmodule Mix.Tasks.Compile.App do
|
||||
if mods == [], do: Mix.Project.build_structure(config)
|
||||
File.write!(target, :io_lib.format("~p.", [contents]))
|
||||
|
||||
Mix.shell.info "Generated #{app}.app"
|
||||
Mix.shell.info "Generated #{app} app"
|
||||
:ok
|
||||
else
|
||||
:noop
|
||||
|
||||
@@ -65,7 +65,6 @@ defmodule Mix.Tasks.Compile.Elixir do
|
||||
|| Mix.Utils.stale?(configs, [manifest])
|
||||
|
||||
Mix.Compilers.Elixir.compile(manifest, srcs, skip, [:ex], dest, force, fn ->
|
||||
true = Code.prepend_path(dest)
|
||||
set_compiler_opts(project, opts, [])
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -65,6 +65,8 @@ defmodule Mix.Tasks.Compile.Erlang do
|
||||
opt
|
||||
end
|
||||
|
||||
compile_path = Path.relative_to(compile_path, File.cwd!)
|
||||
|
||||
tuples = files
|
||||
|> scan_sources(include_path, source_paths)
|
||||
|> sort_dependencies
|
||||
@@ -165,6 +167,6 @@ defmodule Mix.Tasks.Compile.Erlang do
|
||||
end
|
||||
|
||||
defp module_from_artifact(artifact) do
|
||||
artifact |> Path.basename |> Path.rootname
|
||||
artifact |> Path.basename |> Path.rootname |> String.to_atom
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,12 @@ defmodule Mix.Tasks.Compile do
|
||||
* `:compilers` - compilers to run, defaults to:
|
||||
`[:leex, :yeec, :erlang, :elixir, :app]`
|
||||
|
||||
* `:consolidate_protocols` - when true, runs protocol
|
||||
consolidation via the `compile.protocols` task
|
||||
|
||||
* `:build_embedded` - when true, activates protocol
|
||||
consolidation and does not generate symlinks in builds
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--list` - list all enabled compilers
|
||||
@@ -47,21 +53,22 @@ defmodule Mix.Tasks.Compile do
|
||||
shell.info format('mix ~-#{max}s # ~ts', [task, doc])
|
||||
end
|
||||
|
||||
shell.info "\nEnabled compilers: #{Enum.join compilers(), ", "}"
|
||||
compilers = compilers() ++ if(consolidate_protocols?(), do: [:protocols], else: [])
|
||||
shell.info "\nEnabled compilers: #{Enum.join compilers, ", "}"
|
||||
end
|
||||
|
||||
def run(args) do
|
||||
Mix.Project.get!
|
||||
Mix.Task.run "loadpaths", ["--no-readd"|args]
|
||||
Mix.Task.run "loadpaths", args
|
||||
|
||||
res =
|
||||
Enum.map(compilers(), fn(compiler) ->
|
||||
List.wrap Mix.Task.run("compile.#{compiler}", args)
|
||||
end)
|
||||
res = Mix.Task.run "compile.all", args
|
||||
res = if :ok in List.wrap(res), do: :ok, else: :noop
|
||||
|
||||
true = Code.prepend_path(Mix.Project.compile_path)
|
||||
unless "--no-readd" in args, do: Code.readd_paths()
|
||||
if Enum.any?(res, &(:ok in &1)), do: :ok, else: :noop
|
||||
if res == :ok && consolidate_protocols?() do
|
||||
Mix.Task.run "compile.protocols", args
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
# Loadpaths without checks because compilers may be defined in deps.
|
||||
@@ -70,6 +77,11 @@ defmodule Mix.Tasks.Compile do
|
||||
Mix.Task.reenable "loadpaths"
|
||||
end
|
||||
|
||||
defp consolidate_protocols? do
|
||||
config = Mix.Project.config
|
||||
Keyword.get(config, :consolidate_protocols, config[:build_embedded])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all compilers.
|
||||
"""
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
defmodule Mix.Tasks.Compile.Protocols do
|
||||
use Mix.Task
|
||||
|
||||
@recursive true
|
||||
|
||||
@shortdoc "Consolidates all protocols in all paths"
|
||||
|
||||
@moduledoc ~S"""
|
||||
Consolidates all protocols in all paths.
|
||||
|
||||
This module consolidates all protocols in the code path
|
||||
and output the new binary files to the given directory
|
||||
(defaults to "consolidated").
|
||||
This task is automatically invoked whenever the project
|
||||
enables `:consolidate_protocols` or `:build_embedded` in
|
||||
its configuration.
|
||||
|
||||
A new directory will be created with the consolidated
|
||||
protocol versions in the build directory for the given
|
||||
environment. Simply add it to your loadpath to make use
|
||||
of it according to your MIX_ENV:
|
||||
## Consolidation
|
||||
|
||||
Protocol consolidation is useful in production when no
|
||||
dynamic code loading will happen, effectively optimizing
|
||||
protocol dispatches by not accounting for code loading.
|
||||
|
||||
This task consolidates all protocols in the code path
|
||||
and output the new binary files to the given directory
|
||||
(defaults to "_build/MIX_ENV/consolidated").
|
||||
|
||||
In case you are manually compiling protocols or building
|
||||
releases, you need to take the generated protocols into
|
||||
account. This can be done with:
|
||||
|
||||
$ elixir -pa _build/MIX_ENV/consolidated -S mix run
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ defmodule Mix.Tasks.Deps.Check do
|
||||
to_prune = Enum.reduce(all, paths, &(&2 -- Mix.Dep.load_paths(&1)))
|
||||
|
||||
Enum.map(to_prune, fn path ->
|
||||
# Path may not be in code path
|
||||
# Path cannot be in code path when deleting
|
||||
_ = Code.delete_path(path)
|
||||
File.rm_rf!(path |> Path.dirname)
|
||||
end)
|
||||
|
||||
@@ -102,7 +102,7 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
end
|
||||
|
||||
try do
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-elixir-version-check", "--no-readd"])
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-elixir-version-check"])
|
||||
:ok in List.wrap(res)
|
||||
catch
|
||||
kind, reason ->
|
||||
|
||||
@@ -48,11 +48,6 @@ defmodule Mix.Tasks.Escript.Build do
|
||||
|
||||
Defaults to `true` for Elixir projects.
|
||||
|
||||
* `:consolidate_protocols` - if `true`, all protocols will be consolidated
|
||||
before being embedded into the escript.
|
||||
|
||||
Defaults to `true` for Elixir projects.
|
||||
|
||||
* `:shebang` - shebang interpreter directive used to execute the escript.
|
||||
Defaults to `"#! /usr/bin/env escript\n"`.
|
||||
|
||||
@@ -93,19 +88,13 @@ defmodule Mix.Tasks.Escript.Build do
|
||||
Mix.Task.run :compile, args
|
||||
end
|
||||
|
||||
project = Mix.Project.config
|
||||
project = Mix.Project.config
|
||||
language = Keyword.get(project, :language, :elixir)
|
||||
should_consolidate =
|
||||
Keyword.get(project, :consolidate_protocols, language == :elixir)
|
||||
|
||||
if should_consolidate do
|
||||
Mix.Task.run "compile.protocols", []
|
||||
end
|
||||
|
||||
escriptize(project, language, opts[:force], should_consolidate)
|
||||
escriptize(project, language, opts[:force])
|
||||
end
|
||||
|
||||
defp escriptize(project, language, force, should_consolidate) do
|
||||
defp escriptize(project, language, force) do
|
||||
escript_opts = project[:escript] || []
|
||||
|
||||
script_name = to_string(escript_opts[:name] || project[:app])
|
||||
@@ -131,7 +120,7 @@ defmodule Mix.Tasks.Escript.Build do
|
||||
|> Stream.concat
|
||||
|> prepare_beam_paths
|
||||
|
||||
if should_consolidate do
|
||||
if Keyword.get(project, :consolidate_protocols, project[:build_embedded]) do
|
||||
beam_paths =
|
||||
Path.wildcard(consolidated_path <> "/*")
|
||||
|> prepare_beam_paths(beam_paths)
|
||||
@@ -232,7 +221,7 @@ defmodule Mix.Tasks.Escript.Build do
|
||||
defp gen_main(name, module, app, language) do
|
||||
config =
|
||||
if File.regular?("config/config.exs") do
|
||||
Mix.Config.read!("config/config.exs")
|
||||
Macro.escape Mix.Config.read!("config/config.exs")
|
||||
else
|
||||
[]
|
||||
end
|
||||
@@ -286,6 +275,7 @@ defmodule Mix.Tasks.Escript.Build do
|
||||
defp main_body_for(:elixir) do
|
||||
quote do
|
||||
erl_version = :erlang.system_info(:otp_release)
|
||||
|
||||
case :string.to_integer(erl_version) do
|
||||
{num, _} when num >= 17 -> nil
|
||||
_ ->
|
||||
|
||||
@@ -8,10 +8,11 @@ defmodule Mix.Tasks.Help do
|
||||
|
||||
## Arguments
|
||||
|
||||
mix help - prints all tasks and their shortdoc
|
||||
mix help TASK - prints full docs for the given task
|
||||
mix help --names - prints all task names and aliases
|
||||
(useful for autocompleting)
|
||||
mix help - prints all tasks and their shortdoc
|
||||
mix help TASK - prints full docs for the given task
|
||||
mix help --search PATTERN - prints all tasks that contain PATTERN in the name
|
||||
mix help --names - prints all task names and aliases
|
||||
(useful for autocompleting)
|
||||
|
||||
## Colors
|
||||
|
||||
@@ -40,24 +41,12 @@ defmodule Mix.Tasks.Help do
|
||||
def run([]) do
|
||||
loadpaths!
|
||||
|
||||
shell = Mix.shell
|
||||
modules = Mix.Task.load_all()
|
||||
|
||||
docs = for module <- modules,
|
||||
doc = Mix.Task.shortdoc(module) do
|
||||
{"mix " <> Mix.Task.task_name(module), doc}
|
||||
end
|
||||
|
||||
max = Enum.reduce docs, 0, fn({task, _}, acc) ->
|
||||
max(byte_size(task), acc)
|
||||
end
|
||||
{docs, max} = build_task_doc_list(modules)
|
||||
|
||||
display_default_task_doc(max)
|
||||
|
||||
Enum.each Enum.sort(docs), fn({task, doc}) ->
|
||||
shell.info format_task(task, max, doc)
|
||||
end
|
||||
|
||||
display_task_doc_list(docs, max)
|
||||
display_iex_task_doc(max)
|
||||
end
|
||||
|
||||
@@ -78,6 +67,22 @@ defmodule Mix.Tasks.Help do
|
||||
end
|
||||
end
|
||||
|
||||
def run(["--search", pattern]) do
|
||||
loadpaths!
|
||||
|
||||
modules =
|
||||
Mix.Task.load_all()
|
||||
|> Enum.filter(&(String.contains?(Mix.Task.task_name(&1), pattern)))
|
||||
|
||||
{docs, max} = build_task_doc_list(modules)
|
||||
|
||||
display_task_doc_list(docs, max)
|
||||
end
|
||||
|
||||
def run(["--search"]) do
|
||||
Mix.raise "Unexpected arguments, expected `mix help --search PATTERN`"
|
||||
end
|
||||
|
||||
def run([task]) do
|
||||
loadpaths!
|
||||
|
||||
@@ -147,4 +152,22 @@ defmodule Mix.Tasks.Help do
|
||||
Mix.shell.info format_task("iex -S mix", max,
|
||||
"Start IEx and run the default task")
|
||||
end
|
||||
|
||||
defp display_task_doc_list(docs, max) do
|
||||
Enum.each Enum.sort(docs), fn({task, doc}) ->
|
||||
Mix.shell.info format_task(task, max, doc)
|
||||
end
|
||||
end
|
||||
|
||||
defp build_task_doc_list(modules) do
|
||||
Enum.reduce modules, {[], 0}, fn module, {docs, max} ->
|
||||
doc = Mix.Task.shortdoc(module)
|
||||
if doc do
|
||||
task = "mix " <> Mix.Task.task_name(module)
|
||||
docs = [{task, doc} | docs]
|
||||
max = max(byte_size(task), max)
|
||||
end
|
||||
{docs, max}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,10 +29,6 @@ defmodule Mix.Tasks.Loadpaths do
|
||||
load_project(config, args)
|
||||
end
|
||||
|
||||
unless "--no-readd" in args do
|
||||
Code.readd_paths()
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
@@ -207,6 +207,8 @@ defmodule Mix.Tasks.New do
|
||||
[app: :<%= @app %>,
|
||||
version: "0.0.1",
|
||||
elixir: "~> <%= @version %>",
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps: deps]
|
||||
end
|
||||
|
||||
@@ -242,6 +244,8 @@ defmodule Mix.Tasks.New do
|
||||
deps_path: "../../deps",
|
||||
lockfile: "../../mix.lock",
|
||||
elixir: "~> <%= @version %>",
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps: deps]
|
||||
end
|
||||
|
||||
@@ -277,6 +281,8 @@ defmodule Mix.Tasks.New do
|
||||
|
||||
def project do
|
||||
[apps_path: "apps",
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps: deps]
|
||||
end
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ defmodule Mix.Utils do
|
||||
<<to_lower_char(h)>> <> do_underscore(t, h)
|
||||
end
|
||||
|
||||
defp do_underscore(<<h, t, rest :: binary>>, _) when h in ?A..?Z and not t in ?A..?Z do
|
||||
defp do_underscore(<<h, t, rest :: binary>>, _) when h in ?A..?Z and not (t in ?A..?Z or t == ?.) do
|
||||
<<?_, to_lower_char(h), t>> <> do_underscore(rest, t)
|
||||
end
|
||||
|
||||
@@ -434,7 +434,7 @@ defmodule Mix.Utils do
|
||||
|
||||
# Starting a http client profile allows us to scope
|
||||
# the effects of using a http proxy to this function
|
||||
{:ok, pid} = :inets.start(:httpc, [{:profile, :mix}])
|
||||
{:ok, _pid} = :inets.start(:httpc, [{:profile, :mix}])
|
||||
|
||||
headers = [{'user-agent', 'Mix/#{System.version}'}]
|
||||
request = {:binary.bin_to_list(path), headers}
|
||||
@@ -476,7 +476,7 @@ defmodule Mix.Utils do
|
||||
|
||||
if uri.host && uri.port do
|
||||
host = String.to_char_list(uri.host)
|
||||
:httpc.set_options([{proxy_scheme(uri.scheme), {{host, uri.port}, []}}], :hex)
|
||||
:httpc.set_options([{proxy_scheme(uri.scheme), {{host, uri.port}, []}}], :mix)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
-module(b).
|
||||
-compile(export_all).
|
||||
|
||||
-callback c() -> term().
|
||||
-record(br, {cell=undefined}).
|
||||
|
||||
b() -> #br{cell=specified}.
|
||||
b() -> #br{cell=specified}.
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
-compile(export_all).
|
||||
|
||||
-include("r.hrl").
|
||||
-behaviour(b).
|
||||
|
||||
c() -> #r{cell=specified}.
|
||||
@@ -1,3 +0,0 @@
|
||||
defprotocol DepProto do
|
||||
def hello(x)
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
defmodule DepProto.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[ app: :depproto,
|
||||
version: "0.1.0" ]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,38 +3,6 @@ Code.require_file "../test_helper.exs", __DIR__
|
||||
defmodule Mix.CLITest do
|
||||
use MixTest.Case
|
||||
|
||||
test "env and path configs" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
File.write! "custom.exs", """
|
||||
defmodule P do
|
||||
use Mix.Project
|
||||
def project, do: [app: :p, version: "0.1.0"]
|
||||
end
|
||||
"""
|
||||
|
||||
System.put_env("MIX_ENV", "prod")
|
||||
System.put_env("MIX_EXS", "custom.exs")
|
||||
|
||||
# Emulate protocol consolidation
|
||||
File.mkdir_p!("_build/prod/consolidated")
|
||||
|
||||
{output, _status} =
|
||||
System.cmd elixir_executable,
|
||||
["-pa", "_build/prod/consolidated",
|
||||
mix_executable, "run",
|
||||
"-e", "IO.inspect {Mix.env, System.argv}",
|
||||
"-e", "IO.inspect hd(:code.get_path)",
|
||||
"--", "1", "2", "3"]
|
||||
|
||||
System.delete_env("MIX_ENV")
|
||||
System.delete_env("MIX_EXS")
|
||||
|
||||
assert output =~ "Compiled lib/a.ex"
|
||||
assert output =~ ~s({:prod, ["1", "2", "3"]})
|
||||
assert output =~ ~s('#{Path.expand("_build/prod/consolidated")}')
|
||||
end
|
||||
end
|
||||
|
||||
test "default task" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
File.write! "mix.exs", """
|
||||
@@ -49,20 +17,6 @@ defmodule Mix.CLITest do
|
||||
end
|
||||
end
|
||||
|
||||
test "custom mix.exs" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
File.write! "mix.exs", """
|
||||
defmodule P do
|
||||
use Mix.Project
|
||||
def project, do: [app: :p]
|
||||
end
|
||||
"""
|
||||
output = mix ~w[]
|
||||
assert File.regular?("_build/dev/lib/p/ebin/Elixir.A.beam")
|
||||
assert output =~ "Compiled lib/a.ex"
|
||||
end
|
||||
end
|
||||
|
||||
test "compiles and invokes simple task from CLI" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
File.mkdir_p!("lib")
|
||||
@@ -121,6 +75,29 @@ defmodule Mix.CLITest do
|
||||
end
|
||||
end
|
||||
|
||||
test "env config" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
File.write! "custom.exs", """
|
||||
defmodule P do
|
||||
use Mix.Project
|
||||
def project, do: [app: :p, version: "0.1.0"]
|
||||
end
|
||||
"""
|
||||
|
||||
System.put_env("MIX_ENV", "prod")
|
||||
System.put_env("MIX_EXS", "custom.exs")
|
||||
|
||||
output = mix ["run", "-e", "IO.inspect {Mix.env, System.argv}",
|
||||
"--", "1", "2", "3"]
|
||||
|
||||
assert output =~ "Compiled lib/a.ex"
|
||||
assert output =~ ~s({:prod, ["1", "2", "3"]})
|
||||
end
|
||||
after
|
||||
System.delete_env("MIX_ENV")
|
||||
System.delete_env("MIX_EXS")
|
||||
end
|
||||
|
||||
test "new with tests" do
|
||||
in_tmp "new_with_tests", fn ->
|
||||
output = mix ~w[new .]
|
||||
|
||||
@@ -63,7 +63,16 @@ defmodule Mix.ProjectTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "builds the project structure with ebin symlink" do
|
||||
test "builds the project structure without symlinks" do
|
||||
in_fixture "archive", fn ->
|
||||
config = [app_path: Path.expand("_build/archive"), build_embedded: true]
|
||||
assert Mix.Project.build_structure(config) == :ok
|
||||
assert File.dir?("_build/archive/ebin")
|
||||
assert {:error, _} = :file.read_link("_build/archive/ebin")
|
||||
end
|
||||
end
|
||||
|
||||
test "builds the project structure with symlinks" do
|
||||
in_fixture "archive", fn ->
|
||||
config = [app_path: Path.expand("_build/archive")]
|
||||
File.mkdir_p!("include")
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule Mix.Tasks.App.StartTest do
|
||||
|
||||
defmodule AppStartSample do
|
||||
def project do
|
||||
[app: :app_start_sample, version: "0.1.0"]
|
||||
[app: :app_start_sample, version: "0.1.0", start_permanent: true]
|
||||
end
|
||||
|
||||
def application do
|
||||
@@ -89,7 +89,14 @@ defmodule Mix.Tasks.App.StartTest do
|
||||
end
|
||||
|
||||
test "start does nothing if app is nil" do
|
||||
assert Mix.Tasks.App.Start.start(nil) == :error
|
||||
assert Mix.Tasks.App.Start.start([app: nil], []) == :error
|
||||
end
|
||||
|
||||
test "allows type to be configured" do
|
||||
assert Mix.Tasks.App.Start.type([], [permanent: true]) == :permanent
|
||||
assert Mix.Tasks.App.Start.type([], [temporary: true]) == :temporary
|
||||
assert Mix.Tasks.App.Start.type([start_permanent: true], []) == :permanent
|
||||
assert Mix.Tasks.App.Start.type([], []) == :temporary
|
||||
end
|
||||
|
||||
defmodule ReturnSample do
|
||||
@@ -115,9 +122,12 @@ defmodule Mix.Tasks.App.StartTest do
|
||||
Process.put(:application_definition, mod: {ReturnApp, {:error, :bye}})
|
||||
Mix.Tasks.Compile.run []
|
||||
|
||||
assert_raise Mix.Error, "Could not start application return_sample: Mix.Tasks.App.StartTest.ReturnApp.start(:normal, {:error, :bye}) returned an error: :bye",
|
||||
fn ->
|
||||
Mix.Tasks.App.Start.start(:return_sample)
|
||||
message = "Could not start application return_sample: " <>
|
||||
"Mix.Tasks.App.StartTest.ReturnApp.start(:normal, {:error, :bye}) " <>
|
||||
"returned an error: :bye"
|
||||
|
||||
assert_raise Mix.Error, message, fn ->
|
||||
Mix.Tasks.App.Start.start([app: :return_sample], [])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -130,10 +140,14 @@ defmodule Mix.Tasks.App.StartTest do
|
||||
{:badarg, [{ReturnApp, :start, 2, []}] }}})
|
||||
Mix.Tasks.Compile.run []
|
||||
|
||||
assert_raise Mix.Error, "Could not start application return_sample: Mix.Tasks.App.StartTest.ReturnApp.start(:normal, {:error, {:badarg, [{Mix.Tasks.App.StartTest.ReturnApp, :start, 2, []}]}}) returned an error: an exception was raised:\n" <>
|
||||
" ** (ArgumentError) argument error\n" <>
|
||||
" Mix.Tasks.App.StartTest.ReturnApp.start/2", fn ->
|
||||
Mix.Tasks.App.Start.start(:return_sample)
|
||||
message = "Could not start application return_sample: " <>
|
||||
"Mix.Tasks.App.StartTest.ReturnApp.start(:normal, {:error, {:badarg, [{Mix.Tasks.App.StartTest.ReturnApp, :start, 2, []}]}}) " <>
|
||||
"returned an error: an exception was raised:\n" <>
|
||||
" ** (ArgumentError) argument error\n" <>
|
||||
" Mix.Tasks.App.StartTest.ReturnApp.start/2"
|
||||
|
||||
assert_raise Mix.Error, message, fn ->
|
||||
Mix.Tasks.App.Start.start([app: :return_sample], [])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -145,9 +159,12 @@ defmodule Mix.Tasks.App.StartTest do
|
||||
Process.put(:application_definition, mod: {ReturnApp, :bad})
|
||||
Mix.Tasks.Compile.run []
|
||||
|
||||
assert_raise Mix.Error, "Could not start application return_sample: Mix.Tasks.App.StartTest.ReturnApp.start(:normal, :bad) returned a bad value: :bad",
|
||||
fn ->
|
||||
Mix.Tasks.App.Start.start(:return_sample)
|
||||
message = "Could not start application return_sample: " <>
|
||||
"Mix.Tasks.App.StartTest.ReturnApp.start(:normal, :bad) " <>
|
||||
"returned a bad value: :bad"
|
||||
|
||||
assert_raise Mix.Error, message, fn ->
|
||||
Mix.Tasks.App.Start.start([app: :return_sample], [])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -175,10 +192,12 @@ defmodule Mix.Tasks.App.StartTest do
|
||||
Process.put(:application_definition, mod: {ExitApp, :bye})
|
||||
Mix.Tasks.Compile.run []
|
||||
|
||||
assert_raise Mix.Error, "Could not start application exit_sample: exited in: Mix.Tasks.App.StartTest.ExitApp.start(:normal, :bye)\n" <>
|
||||
" ** (EXIT) :bye",
|
||||
fn ->
|
||||
Mix.Tasks.App.Start.start(:exit_sample)
|
||||
message = "Could not start application exit_sample: exited in: " <>
|
||||
"Mix.Tasks.App.StartTest.ExitApp.start(:normal, :bye)\n" <>
|
||||
" ** (EXIT) :bye"
|
||||
|
||||
assert_raise Mix.Error, message, fn ->
|
||||
Mix.Tasks.App.Start.start([app: :exit_sample], [])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -190,10 +209,12 @@ defmodule Mix.Tasks.App.StartTest do
|
||||
Process.put(:application_definition, mod: {ExitApp, :normal})
|
||||
Mix.Tasks.Compile.run []
|
||||
|
||||
assert_raise Mix.Error, "Could not start application exit_sample: exited in: Mix.Tasks.App.StartTest.ExitApp.start(:normal, :normal)\n" <>
|
||||
" ** (EXIT) normal",
|
||||
fn ->
|
||||
Mix.Tasks.App.Start.start(:exit_sample)
|
||||
message = "Could not start application exit_sample: exited in: " <>
|
||||
"Mix.Tasks.App.StartTest.ExitApp.start(:normal, :normal)\n" <>
|
||||
" ** (EXIT) normal"
|
||||
|
||||
assert_raise Mix.Error, message, fn ->
|
||||
Mix.Tasks.App.Start.start([app: :exit_sample], [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
:ok
|
||||
end
|
||||
|
||||
test "compiles a project" do
|
||||
test "compiles a project without per environment build" do
|
||||
Mix.Project.pop
|
||||
Mix.ProjectStack.post_config [build_per_environment: false]
|
||||
Mix.Project.push MixTest.Case.Sample
|
||||
@@ -40,6 +40,24 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "recompiles project if elixir version changed" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
Mix.Tasks.Compile.run []
|
||||
purge [A, B, C]
|
||||
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
assert System.version == Mix.Dep.Lock.elixir_vsn
|
||||
|
||||
Mix.Task.clear
|
||||
File.write!("_build/dev/lib/sample/.compile.lock", "the_past")
|
||||
File.touch!("_build/dev/lib/sample/.compile.lock", {{2010, 1, 1}, {0, 0, 0}})
|
||||
|
||||
Mix.Tasks.Compile.run []
|
||||
assert System.version == Mix.Dep.Lock.elixir_vsn
|
||||
assert File.stat!("_build/dev/lib/sample/.compile.lock").mtime > {{2010, 1, 1}, {0, 0, 0}}
|
||||
end
|
||||
end
|
||||
|
||||
test "does not write beam down on failures" do
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ defmodule Mix.Tasks.Compile.ErlangTest do
|
||||
assert File.regular?("_build/dev/lib/sample/ebin/b.beam")
|
||||
assert File.regular?("_build/dev/lib/sample/ebin/c.beam")
|
||||
|
||||
assert File.read!("_build/dev/lib/sample/.compile.erlang") ==
|
||||
"_build/dev/lib/sample/ebin/b.beam\n" <>
|
||||
"_build/dev/lib/sample/ebin/c.beam"
|
||||
|
||||
assert Mix.Tasks.Compile.Erlang.run([]) == :noop
|
||||
refute_received {:mix_shell, :info, ["Compiled src/b.erl"]}
|
||||
|
||||
|
||||
@@ -9,18 +9,26 @@ defmodule Mix.Tasks.CompileTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Consolidation do
|
||||
def project do
|
||||
[app: :consolidation,
|
||||
version: "0.1.0",
|
||||
consolidate_protocols: true]
|
||||
end
|
||||
end
|
||||
|
||||
setup do
|
||||
Mix.Project.push MixTest.Case.Sample
|
||||
:ok
|
||||
end
|
||||
|
||||
test "mix compile --list with mixfile" do
|
||||
test "compile --list with mixfile" do
|
||||
Mix.Tasks.Compile.run ["--list"]
|
||||
assert_received {:mix_shell, :info, ["\nEnabled compilers: yecc, leex, erlang, elixir, app"]}
|
||||
assert_received {:mix_shell, :info, ["mix compile.elixir # " <> _]}
|
||||
end
|
||||
|
||||
test "mix compile --list with custom mixfile" do
|
||||
test "compile --list with custom mixfile" do
|
||||
Mix.Project.push CustomCompilers
|
||||
Mix.Tasks.Compile.run ["--list"]
|
||||
assert_received {:mix_shell, :info, ["\nEnabled compilers: elixir, app, custom"]}
|
||||
@@ -28,14 +36,32 @@ defmodule Mix.Tasks.CompileTest do
|
||||
|
||||
test "compile a project with mixfile" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
Mix.Tasks.Compile.run []
|
||||
assert Mix.Tasks.Compile.run([]) == :ok
|
||||
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam")
|
||||
assert File.regular?("_build/dev/lib/sample/ebin/sample.app")
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Generated sample.app"]}
|
||||
assert_received {:mix_shell, :info, ["Generated sample app"]}
|
||||
end
|
||||
end
|
||||
|
||||
test "compile a project with protocol consolidation" do
|
||||
Mix.Project.push Consolidation
|
||||
in_fixture "no_mixfile", fn ->
|
||||
assert Mix.Tasks.Compile.run([]) == :ok
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Consolidated Enumerable"]}
|
||||
assert File.regular? "_build/dev/consolidated/Elixir.Enumerable.beam"
|
||||
|
||||
assert Mix.Tasks.Compile.run([]) == :noop
|
||||
refute_received {:mix_shell, :info, ["Consolidated Enumerable"]}
|
||||
|
||||
assert Mix.Tasks.App.Start.run([]) == :ok
|
||||
assert Protocol.consolidated?(Enumerable)
|
||||
end
|
||||
after
|
||||
purge [Enumerable]
|
||||
end
|
||||
|
||||
test "compile a project with multiple compilers and a syntax error in an erlang file" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
import ExUnit.CaptureIO
|
||||
@@ -56,22 +82,4 @@ defmodule Mix.Tasks.CompileTest do
|
||||
refute File.regular?("ebin/Elixir.C.beam")
|
||||
end
|
||||
end
|
||||
|
||||
test "recompiles project if elixir version changed" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
Mix.Tasks.Compile.run []
|
||||
purge [A, B, C]
|
||||
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
assert System.version == Mix.Dep.Lock.elixir_vsn
|
||||
|
||||
Mix.Task.clear
|
||||
File.write!("_build/dev/lib/sample/.compile.lock", "the_past")
|
||||
File.touch!("_build/dev/lib/sample/.compile.lock", {{2010, 1, 1}, {0, 0, 0}})
|
||||
|
||||
Mix.Tasks.Compile.run []
|
||||
assert System.version == Mix.Dep.Lock.elixir_vsn
|
||||
assert File.stat!("_build/dev/lib/sample/.compile.lock").mtime > {{2010, 1, 1}, {0, 0, 0}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -100,7 +100,11 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
assert File.exists?("deps/git_repo/mix.exs")
|
||||
assert File.rm("deps/git_repo/.fetch") == :ok
|
||||
|
||||
Mix.Tasks.Deps.Update.run ["deps_on_git_repo"]
|
||||
# Compile the dependencies
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
|
||||
# Now update children and make sure it propagates
|
||||
Mix.Tasks.Deps.Update.run ["git_repo"]
|
||||
assert File.exists?("deps/deps_on_git_repo/.fetch")
|
||||
assert File.exists?("deps/git_repo/.fetch")
|
||||
|
||||
@@ -109,7 +113,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
assert File.exists?("_build/dev/lib/git_repo/ebin")
|
||||
Code.delete_path("_build/dev/lib/git_repo/ebin")
|
||||
|
||||
# Deps on git repo loads it automatically on compile.
|
||||
# Deps on git repo loads it automatically on compile
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
Mix.Tasks.Deps.Compile.run ["deps_on_git_repo"]
|
||||
assert File.exists?("_build/dev/lib/deps_on_git_repo/ebin")
|
||||
|
||||
@@ -34,10 +34,7 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
def project do
|
||||
[ app: :escripttestwithdeps,
|
||||
version: "0.0.1",
|
||||
escript: [
|
||||
main_module: Escripttest,
|
||||
path: Path.join("ebin", "escripttestwithdeps"),
|
||||
],
|
||||
escript: [main_module: Escripttest],
|
||||
deps: [{:ok, path: fixture_path("deps_status/deps/ok")}] ]
|
||||
end
|
||||
end
|
||||
@@ -47,10 +44,7 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
[ app: :escripttesterlangwithdeps,
|
||||
version: "0.0.1",
|
||||
language: :erlang,
|
||||
escript: [
|
||||
main_module: :escripttest,
|
||||
path: Path.join("ebin", "escripttesterlangwithdeps"),
|
||||
],
|
||||
escript: [main_module: :escripttest],
|
||||
deps: [{:ok, path: fixture_path("deps_status/deps/ok")}] ]
|
||||
end
|
||||
end
|
||||
@@ -58,12 +52,9 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
defmodule EscriptConsolidated do
|
||||
def project do
|
||||
[ app: :escripttestconsolidated,
|
||||
build_embedded: true,
|
||||
version: "0.0.1",
|
||||
escript: [
|
||||
main_module: Escripttest,
|
||||
path: Path.join("ebin", "escripttestconsolidated"),
|
||||
],
|
||||
deps: [{:depproto, path: fixture_path("dep_with_protocol")}] ]
|
||||
escript: [main_module: Escripttest] ]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,7 +77,7 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
in_fixture "escripttest", fn ->
|
||||
File.mkdir_p! "config"
|
||||
File.write! "config/config.exs", """
|
||||
[foobar: [value: "FROM CONFIG"]]
|
||||
[foobar: [value: "FROM CONFIG", other: %{}]]
|
||||
"""
|
||||
Mix.Tasks.Escript.Build.run []
|
||||
assert_received {:mix_shell, :info, ["Generated escript escriptest with MIX_ENV=dev"]}
|
||||
@@ -109,20 +100,20 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
|
||||
in_fixture "escripttest", fn ->
|
||||
Mix.Tasks.Escript.Build.run []
|
||||
assert_received {:mix_shell, :info, ["Generated escript ebin/escripttestwithdeps with MIX_ENV=dev"]}
|
||||
assert System.cmd("escript", ["ebin/escripttestwithdeps"]) == {"TEST\n", 0}
|
||||
assert_received {:mix_shell, :info, ["Generated escript escripttestwithdeps with MIX_ENV=dev"]}
|
||||
assert System.cmd("escript", ["escripttestwithdeps"]) == {"TEST\n", 0}
|
||||
end
|
||||
after
|
||||
purge [Ok.Mixfile]
|
||||
end
|
||||
|
||||
test "generate Erlang escript with config and deps" do
|
||||
test "generate escript with erlang and deps" do
|
||||
Mix.Project.push EscriptErlangWithDeps
|
||||
|
||||
in_fixture "escripttest", fn ->
|
||||
Mix.Tasks.Escript.Build.run []
|
||||
assert_received {:mix_shell, :info, ["Generated escript ebin/escripttesterlangwithdeps with MIX_ENV=dev"]}
|
||||
assert System.cmd("escript", ["ebin/escripttesterlangwithdeps"]) == {"Erlang value", 0}
|
||||
assert_received {:mix_shell, :info, ["Generated escript escripttesterlangwithdeps with MIX_ENV=dev"]}
|
||||
assert System.cmd("escript", ["escripttesterlangwithdeps"]) == {"Erlang value", 0}
|
||||
end
|
||||
after
|
||||
purge [Ok.Mixfile]
|
||||
@@ -133,11 +124,8 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
|
||||
in_fixture "escripttest_protocols", fn ->
|
||||
Mix.Tasks.Escript.Build.run []
|
||||
assert_received {:mix_shell, :info, ["Generated escript ebin/escripttestconsolidated with MIX_ENV=dev"]}
|
||||
assert System.cmd("escript", ["ebin/escripttestconsolidated", "Enumerable"]) == {"true\n", 0}
|
||||
assert System.cmd("escript", ["ebin/escripttestconsolidated", "DepProto"]) == {"true\n", 0}
|
||||
assert_received {:mix_shell, :info, ["Generated escript escripttestconsolidated with MIX_ENV=dev"]}
|
||||
assert System.cmd("escript", ["escripttestconsolidated", "Enumerable"]) == {"true\n", 0}
|
||||
end
|
||||
after
|
||||
purge [DepProto.Mixfile]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -56,6 +56,31 @@ defmodule Mix.Tasks.HelpTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "help --search PATTERN" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
Mix.Tasks.Help.run ["--search", "deps"]
|
||||
assert_received {:mix_shell, :info, ["mix deps" <> _]}
|
||||
assert_received {:mix_shell, :info, ["mix deps.clean" <> _]}
|
||||
end
|
||||
end
|
||||
|
||||
test "help --search without pattern" do
|
||||
assert_raise Mix.Error, "Unexpected arguments, expected `mix help --search PATTERN`", fn ->
|
||||
Mix.Tasks.Help.run ["--search"]
|
||||
end
|
||||
end
|
||||
|
||||
test "help --search without results" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
output =
|
||||
capture_io fn ->
|
||||
Mix.Tasks.Help.run ["--search", "foo"]
|
||||
end
|
||||
|
||||
assert output == ""
|
||||
end
|
||||
end
|
||||
|
||||
test "bad arguments" do
|
||||
assert_raise Mix.Error, "Unexpected arguments, expected `mix help` or `mix help TASK`", fn ->
|
||||
Mix.Tasks.Help.run ["foo", "bar"]
|
||||
|
||||
@@ -46,8 +46,6 @@ defmodule Mix.Tasks.RunTest do
|
||||
end
|
||||
|
||||
test "run errors on missing files" do
|
||||
git_repo = fixture_path("git_repo/lib/git_repo.ex")
|
||||
|
||||
in_fixture "no_mixfile", fn ->
|
||||
assert_raise Mix.Error, "No files matched pattern \"non-existent\" given to --require", fn ->
|
||||
Mix.Tasks.Run.run ["-r", "non-existent"]
|
||||
|
||||
@@ -17,10 +17,10 @@ defmodule Mix.UmbrellaTest do
|
||||
|
||||
assert_received {:mix_shell, :info, ["==> bar"]}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Generated bar.app"]}
|
||||
assert_received {:mix_shell, :info, ["Generated bar app"]}
|
||||
assert_received {:mix_shell, :info, ["==> foo"]}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/foo.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Generated foo.app"]}
|
||||
assert_received {:mix_shell, :info, ["Generated foo app"]}
|
||||
|
||||
# Ensure foo was loaded and in the same env as Mix.env
|
||||
assert_received {:mix_shell, :info, [":foo env is dev"]}
|
||||
|
||||
@@ -37,6 +37,7 @@ defmodule Mix.UtilsTest do
|
||||
assert Mix.Utils.underscore("FoBaZa") == "fo_ba_za"
|
||||
assert Mix.Utils.underscore("Foo.Bar") == "foo/bar"
|
||||
assert Mix.Utils.underscore(Foo.Bar) == "foo/bar"
|
||||
assert Mix.Utils.underscore("API.V1.User") == "api/v1/user"
|
||||
assert Mix.Utils.underscore("") == ""
|
||||
assert Mix.Utils.underscore("..") == ".."
|
||||
assert Mix.Utils.underscore("/..") == "/.."
|
||||
|
||||
+2
-4
@@ -1,9 +1,7 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.0.3"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
{vsn, "1.0.5"},
|
||||
{modules, [elixir]},
|
||||
{registered, [elixir_config, elixir_code_server]},
|
||||
{applications, [kernel,stdlib,crypto,compiler,syntax_tools]},
|
||||
{mod, {elixir,[]}},
|
||||
|
||||
Reference in New Issue
Block a user