Compare commits

...
40 Commits
Author SHA1 Message Date
José Valim 9033b217d9 Support canonical URLs 2016-02-21 21:33:30 +01:00
José Valim 1d9548fd28 Merge pull request #3475 from siscia/fixIntTake
Ensure integer in guards in Enum/Stream.take
2015-07-12 17:58:49 +02:00
siscia 9a6c195880 add test for integer passed as argument in {Enum,Stream}.take #3473 2015-07-12 17:40:16 +02:00
José Valim 3eb938a0ba Release v1.0.5 2015-06-29 09:56:28 +02:00
José Valim 66f1e74e0c Run badkey and badmap only on 18
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 18:01:05 +02:00
José Valim 7c382b94af Ensure we also normalize badmap and badkey in rescue 2015-06-25 16:49:30 +02:00
Boris Mühmer 9297a9167e fix for Erlang R18.0 changes in maps module
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 16:12:34 +02:00
James Fish 0410a1cec9 Fix logger translating process exit 2015-06-25 13:06:38 +01:00
James Fish b545a1e491 Translate emulator logged process crashes in 18.0+
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-25 11:57:49 +01:00
José Valim c08b5b11c6 Move tests to Erlang 18 which has proper logger behaviour
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-25 13:01:04 +02:00
José Valim 8939b39fc2 Update to Erlang 18 2015-06-25 12:47:55 +02:00
José Valim e12a0c561e Backport improvements to <<>>' docs 2015-06-22 13:47:27 +02:00
José Valim 61c3e82123 Improve wording
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:58 +01:00
José Valim d43b41695d Also document exit reasons
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:34 +01:00
José Valim fdca8b2657 Add docs for simple one for one
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-18 16:14:03 +01:00
José Valim dec06d8c82 Update CHANGELOG 2015-06-13 18:47:53 +02:00
José Valim 11c84cbd2e Delegate to :erlang.binary_to_float on Float.parse/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-13 18:45:45 +02:00
José Valim 67501bcddc Avoid non-fixed table errors on autocomplete 2015-06-08 16:56:22 +02:00
José Valim 1f05e7ce73 Rely on loaded_applications instead of running ones
If the application is loaded, it is enough for its
modules to be available for autocompletion. This also
simplifies the code and makes completion faster.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-08 16:08:43 +02:00
James Fish 02c279af12 Really fix race condition in logger translator test
Signed-off-by: James Fish <james@fishcakez.com>
2015-06-08 12:08:26 +01:00
José Valim 2c2fb820ab Update CHANGELOG 2015-06-08 09:45:01 +02:00
James Fish 1ad1d6d6b0 Merge pull request #3382 from lexmag/string_io-fixes
Fix `StringIO` processes leakage in the `ExUnit.CaptureIO`

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-08 09:33:46 +02:00
James Fish 83d8161959 Fix race condition in logger translator test 2015-06-07 22:07:36 +01:00
Eric Meadows-Jönsson b87ea61713 Join pre-release parts with "."
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:13:05 +02:00
Eric Meadows-Jönsson c355ab6482 Fix to_string for Version with numeric pre
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:12:43 +02:00
Eric Meadows-Jönsson f0ed2d48ce Fix Version.pre typespec
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:10:35 +02:00
Eric Meadows-Jönsson d23a46fa9f Add more documentation for ~> requirements
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-06-07 22:10:31 +02:00
Alexei Sholik 6c0cbbd73e Handle :undefined arity in Logger.Translator
We get :undefined when a temporary worker of a simple_one_for_one
supervisor crashes.

Thanks to @fishcakez for feedback
2015-06-07 23:03:54 +03:00
José Valim d58ef57e13 Ensure take/1 does not consume more than required
Kudos to @hamiltop for the idea and initial work on this fix.

Closes #3381 #3379

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-06 19:09:04 +02:00
James Fish 1a9493534e Fix GenEvent detecting module that isn't loaded
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-06 19:07:41 +02:00
Eric Meadows-Jönsson 45fe1cb084 Expand attributes and macros in record extractor
Achieved by using :epp.parse_file/2 instead of
:epp_dodger.quick_parse_file/1.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 22:52:49 +02:00
José Valim 3f0e7c5613 Only consider outer halts in flat_map when the op is not halt itself
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 22:51:36 +02:00
José Valim 49e76833d6 Ensure blocks do not clobber eex buffer
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:17:05 +02:00
José Valim 7963e44a5e Speed up upcase and downcase for large strings
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:16:15 +02:00
José Valim 3ba5e44dae Ensure we properly underscore acronyms followed by paths
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:56 +02:00
José Valim 1d74ddac78 Handle corner cases for small strings in rstrp
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:43 +02:00
José Valim c1e3d4f976 Optimize rstrip
This new implementation is no longer linear without affecting
smaller samples. For a string that is 100 bytes long, it is
25x faster than the previous implementation.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:15:12 +02:00
Michael Sokolnicki b4fc2b35fc Add unit test for IO.binread with :all option
Unit test checks correct handling of 0x0D0A (CR+LF) when reading entire
binary file

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:14:22 +02:00
Michael Sokolnicki 18a5d8affb Read 4K blocks instead of lines in IO.binread
Fix for incorrect handling of 0x0D0A (CR+LF) sequence when reading
binary files with option :all

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-06-05 13:14:15 +02:00
José Valim cd80994a70 Ensure we escape config before injecting into escript
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-04-14 15:23:13 +02:00
41 changed files with 611 additions and 266 deletions
+25 -2
View File
@@ -1,9 +1,32 @@
# Changelog
## v1.0.4
## v1.0.5 (2015-06-29)
* Enhancements
* [Elixir] Support Erlang 17.5 and 18.0
* [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
+4 -2
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
1.0.4
1.0.5
+4 -4
View File
@@ -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
+13 -1
View File
@@ -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 %>
+3 -3
View File
@@ -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
+28 -1
View File
@@ -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
View File
@@ -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`.
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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
+143 -80
View File
@@ -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)
+7 -6
View File
@@ -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
+6 -4
View File
@@ -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]} ->
+10 -4
View File
@@ -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
+72 -7
View File
@@ -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`.
+27 -2
View File
@@ -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
+3
View File
@@ -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,
+10
View File
@@ -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]},
+1 -1
View File
@@ -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]
+4
View File
@@ -0,0 +1,4 @@
LF
CR␍CRLF
LFCR
␍
+6
View File
@@ -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, "")
@@ -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
+10 -2
View File
@@ -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
+2 -2
View File
@@ -123,10 +123,10 @@ defmodule ProtocolTest do
test "protocol defines callbacks" do
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, []}]}]
[{: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, [{:user_type, 16, :t, []}]}, {:type, 16, :term, []}]}]
[{: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, []}]}]
+4 -1
View File
@@ -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()
+16 -4
View File
@@ -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
+2 -7
View File
@@ -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"}
+2
View File
@@ -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"
+2
View File
@@ -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
+49 -32
View File
@@ -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
+30 -13
View File
@@ -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
+20 -6
View File
@@ -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
+3 -7
View File
@@ -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
+11 -4
View File
@@ -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
@@ -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
+10 -10
View File
@@ -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.())
+2 -1
View File
@@ -221,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
@@ -275,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
_ ->
+1 -1
View File
@@ -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
+1 -1
View File
@@ -77,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"]}
+1
View File
@@ -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("/..") == "/.."
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.0.4"},
{vsn, "1.0.5"},
{modules, [elixir]},
{registered, [elixir_config, elixir_code_server]},
{applications, [kernel,stdlib,crypto,compiler,syntax_tools]},