Compare commits
182
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8931f34582 | ||
|
|
843706e36e | ||
|
|
172bfdb128 | ||
|
|
2c741ec6ae | ||
|
|
12a457829f | ||
|
|
0ff6850237 | ||
|
|
63f6088476 | ||
|
|
206f6413f8 | ||
|
|
a2c0424b37 | ||
|
|
6ea7d3cef6 | ||
|
|
2cafd8c060 | ||
|
|
7271dfebfb | ||
|
|
2eff3590c1 | ||
|
|
b578f45060 | ||
|
|
a34ccfe129 | ||
|
|
8f4442f2df | ||
|
|
598e42e00b | ||
|
|
b93912b9dc | ||
|
|
71eca8a5d6 | ||
|
|
f0bb0e1f7f | ||
|
|
2cb0639208 | ||
|
|
081e3b36ec | ||
|
|
d521d7927f | ||
|
|
7313d26720 | ||
|
|
577a07d47f | ||
|
|
f6093d40d7 | ||
|
|
1dd034d4aa | ||
|
|
2417b196a4 | ||
|
|
50bb712e22 | ||
|
|
7b0747c907 | ||
|
|
b878e414de | ||
|
|
c447868e0f | ||
|
|
681d5aa9c4 | ||
|
|
0263dcc621 | ||
|
|
1ae24c5c5d | ||
|
|
6c02ebe4af | ||
|
|
a08e44c5a9 | ||
|
|
08f30098db | ||
|
|
5dbd91e4fa | ||
|
|
5b2eb54794 | ||
|
|
2c17007693 | ||
|
|
2770c32f3d | ||
|
|
8c8f6d8346 | ||
|
|
3950130e73 | ||
|
|
c8bcd89c7c | ||
|
|
7f7aaa115f | ||
|
|
53aeb26407 | ||
|
|
bba0d12510 | ||
|
|
035d8227a5 | ||
|
|
003a4da87e | ||
|
|
1977631b7f | ||
|
|
c9a0ec000a | ||
|
|
ba842ba113 | ||
|
|
6304919d0d | ||
|
|
d2d27e96e5 | ||
|
|
fc6f7c532c | ||
|
|
1bee85e518 | ||
|
|
66b211987d | ||
|
|
15fdae4de6 | ||
|
|
5b4497fa6b | ||
|
|
612e124ec9 | ||
|
|
abd2b35738 | ||
|
|
2afb875099 | ||
|
|
88507f65f8 | ||
|
|
275d1f5494 | ||
|
|
e6420c8d13 | ||
|
|
60bcf1cadb | ||
|
|
d86ca5826c | ||
|
|
2af00f4c33 | ||
|
|
0fe86e77c1 | ||
|
|
1ca451f779 | ||
|
|
ebe0c8cf8e | ||
|
|
de9feb26d5 | ||
|
|
b8528f6c8e | ||
|
|
c9fbbac391 | ||
|
|
98e1b4ccf8 | ||
|
|
ae19491620 | ||
|
|
64f3760b19 | ||
|
|
0b38bc050f | ||
|
|
9431338654 | ||
|
|
88be750a32 | ||
|
|
032c8b8291 | ||
|
|
2d90d8ac60 | ||
|
|
a8b2cb99ee | ||
|
|
68274311fb | ||
|
|
88f84f80b5 | ||
|
|
2bf898e8fe | ||
|
|
d45f451943 | ||
|
|
c0a075724f | ||
|
|
f1a39375c9 | ||
|
|
b9c1b22163 | ||
|
|
be75e3be17 | ||
|
|
9793caa1ef | ||
|
|
0792fe2c52 | ||
|
|
4408aa3450 | ||
|
|
e4ba8d5d21 | ||
|
|
689f14a867 | ||
|
|
aa5b414e8e | ||
|
|
07467a733b | ||
|
|
74391b793c | ||
|
|
f0b8bc1756 | ||
|
|
3e54b95bf2 | ||
|
|
313d26cb3b | ||
|
|
44b85be014 | ||
|
|
e344b1f9d8 | ||
|
|
f5f4205969 | ||
|
|
538c717ded | ||
|
|
8dd81e661a | ||
|
|
26b61b98f9 | ||
|
|
ab9450d92f | ||
|
|
a4a23b9a93 | ||
|
|
2f83ea5a7f | ||
|
|
7940d223ec | ||
|
|
c6fa951520 | ||
|
|
e5c8194a70 | ||
|
|
4a7e3dd11f | ||
|
|
638fccb79d | ||
|
|
e9d84ec43b | ||
|
|
bb35b9afb1 | ||
|
|
1496c04592 | ||
|
|
3e9725a5a8 | ||
|
|
1565ec7658 | ||
|
|
f1aa2f01c7 | ||
|
|
7284542116 | ||
|
|
4f17e8fd1c | ||
|
|
f536507b9d | ||
|
|
592300a0f0 | ||
|
|
28b1bd16de | ||
|
|
19fdd84f80 | ||
|
|
631e2a051a | ||
|
|
df0f7b5f95 | ||
|
|
a0dfc4160e | ||
|
|
ee8331023e | ||
|
|
a2ee71f5ab | ||
|
|
60163194e2 | ||
|
|
ba37682300 | ||
|
|
15265b9352 | ||
|
|
0a929310de | ||
|
|
a8670c1cfa | ||
|
|
ebd18d45fa | ||
|
|
ccbfec611a | ||
|
|
98a0999c2e | ||
|
|
e504142d3a | ||
|
|
c04e13169f | ||
|
|
96a4bff67b | ||
|
|
46de5ea462 | ||
|
|
22257b8342 | ||
|
|
a0be9fe272 | ||
|
|
1fd5a2a38e | ||
|
|
13aec18bd5 | ||
|
|
a85938d092 | ||
|
|
0d4af57abe | ||
|
|
e00db53112 | ||
|
|
b941618a10 | ||
|
|
d895c762a1 | ||
|
|
427c4b01c9 | ||
|
|
9d5a728cb8 | ||
|
|
1ed99d4ac7 | ||
|
|
ceb17fac7d | ||
|
|
c638f45542 | ||
|
|
c09905d537 | ||
|
|
117a428030 | ||
|
|
2ada7c40d7 | ||
|
|
cd162bf3d6 | ||
|
|
e6e71675ac | ||
|
|
c54e0caf6f | ||
|
|
b64552780e | ||
|
|
add8c66e82 | ||
|
|
aecbd2c16a | ||
|
|
32a649c1cd | ||
|
|
a1e657cb75 | ||
|
|
6bb4c4de03 | ||
|
|
ea5b56d434 | ||
|
|
a4082748cf | ||
|
|
4d763846c3 | ||
|
|
d56cc88f5f | ||
|
|
5e27b7b72b | ||
|
|
260d6d16ab | ||
|
|
1a82f126a4 | ||
|
|
3cf3eee2ea | ||
|
|
9e8a41b2d3 | ||
|
|
5f853f8e71 |
@@ -1,5 +1,67 @@
|
||||
# Changelog
|
||||
|
||||
## v1.0.0-rc2 (2014-09-07)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Print process mailbox on failing `assert_receive`
|
||||
* [ExUnit] Tag all doctests with the `:doctest` tag
|
||||
* [Kernel] Friendlier `unquote_splicing` error on inline quote
|
||||
* [Kernel] Add `<~`, `~>`, `<<~`, `~>>`, `<~>`, `<|>` as operators
|
||||
* [Mix] `mix deps.unlock` warns when given a missing dependency
|
||||
* [Mix] Fetch hex using HTTPS
|
||||
|
||||
* Bug fixes
|
||||
* [File] Ensure `File.cwd` generates lowercase drive letters on Windows
|
||||
* [GenEvent] Ensure the event manager does not crash on `:sys.replace_state/3`
|
||||
* [GenEvent] Do not consider swap handlers to be failures in a stream
|
||||
* [Logger] Fix `Logger.log/2` not to raise on Logger exit
|
||||
* [Logger] Recompute sync/async mode on Logger configuration
|
||||
* [System] Ensure `System.cwd` generates lowercase drive letters on Windows
|
||||
|
||||
* Deprecations
|
||||
* [GenEvent] The `:monitor` option in `add_handler/4` and `swap_handler/6` is deprecated in favor of `add_mon_handler/3` and `swap_mon_handler/5`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Supervisor] Make `max_restarts` default to 3 (closer to upcoming Erlang defaults)
|
||||
|
||||
## v1.0.0-rc1 (2014-08-30)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Add timeouts to tests (which is configurable via tags)
|
||||
* [GenEvent] Add support for `GenEvent.ack_notify/2`
|
||||
* [GenEvent] Add support for monitored handlers to `GenEvent.add_handler/4` that removes the drawbacks in the previous linked handlers
|
||||
* [Logger] Allow any value that implements `String.Chars` to be logger
|
||||
* [Logger] Allow `Logger.log/3` timeout to be configured
|
||||
* [Mix] Add `--force` option to `mix local.rebar`
|
||||
* [Mix] Fix `--no-color`/`--color` option for `mix test`
|
||||
* [Mix] Add a `:language` option to improve the usability of Erlang projects using Mix
|
||||
* [Mix] Accept wildcards on `Mix.Config.import_config/1`
|
||||
* [Mix] Support protocol consolidation on escripts
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Do not print the underlying type for `@opaque` in the `t` helper
|
||||
* [GenEvent] Do not allow a handler to be added more than once
|
||||
* [GenServer] Do not treat bad arguments as exits in `GenServer.call/2`
|
||||
* [GenServer] Do not deliver out of order messages on `GenServer.cast/2` on distributed mode
|
||||
* [Mix] Do not pre-compile a Mix project if an alias was found
|
||||
* [Mix] Properly handle compilation errors in the Erlang compiler
|
||||
* [Mix] Always try to compile project if task cannot be found
|
||||
* [Mix] Purge in memory modules before consolidating
|
||||
* [Mix] Always compile a project if a task cannot be found
|
||||
|
||||
* Deprecations
|
||||
* [Collectable] Deprecate `Collectable.empty/1` and `Enum.traverse/2`
|
||||
* [Integer] `odd?/1` and `even?/1` are deprecated in favor of `is_odd/1` and `is_even/1`
|
||||
* [Kernel] `nil?/1` is deprecated in favor of `is_nil/1`
|
||||
* [Kernel] `x.Alias` is deprecated in favor of an explicit `Module.concat/2`
|
||||
* [Record] `record?/1` and `record?/2` are deprecated in favor of `is_record/1` and `is_record/2`
|
||||
* [Stream] Returning `{item, acc} | nil` from `Stream.resource/2` is deprecated, instead return `{[item], acc} | {:halt, acc}` (similar to `Stream.transform/3`)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.cancel_stream/1` no longer is
|
||||
* [GenEvent] The `:mode` and `:duration` options are deprecated and no longer supported in `GenEvent.stream/2` (the mode is now a property of the sender)
|
||||
* [GenEvent] The `:linked` option is deprecated and no longer supported in `GenEvent.add_handler/4` (check monitored handlers instead)
|
||||
|
||||
## v0.15.1 (2014-08-10)
|
||||
|
||||
* Enhancements
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ Use the issues tracker for:
|
||||
* [bug reports](#bugs-reports)
|
||||
* [submitting pull requests](#pull-requests)
|
||||
|
||||
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
|
||||
Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
|
||||
|
||||
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
|
||||
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
|
||||
@@ -18,7 +18,7 @@ Please **do not** use the issues tracker for personal support requests nor featu
|
||||
|
||||
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
|
||||
|
||||
We do our best to keep the issues tracker tidy and organized, making it useful
|
||||
We do our best to keep the issue tracker tidy and organized, making it useful
|
||||
for everyone. For example, we classify open issues per application and perceived
|
||||
difficulty of the issue, making it easier for developers to
|
||||
[contribute to Elixir](#contributing).
|
||||
|
||||
@@ -142,7 +142,7 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../docs
|
||||
@@ -183,7 +183,7 @@ test_stdlib: compile
|
||||
|
||||
#==> Dialyzer tasks
|
||||
|
||||
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wrace_conditions -Wunderspecs
|
||||
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
|
||||
PLT = .elixir.plt
|
||||
|
||||
$(PLT):
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ done
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m $SCRIPT_PATH); fi
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "SCRIPT_PATH"); fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
|
||||
|
||||
# Check for terminal support
|
||||
|
||||
@@ -201,7 +201,7 @@ defmodule Application do
|
||||
`:applications` in the `.app` file in case they were not previously
|
||||
started.
|
||||
"""
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
|
||||
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_all_started(app, type)
|
||||
end
|
||||
|
||||
@@ -8,12 +8,6 @@ defprotocol Collectable do
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
|
||||
The `Enumerable` protocol is useful to take values out of a collection.
|
||||
@@ -27,25 +21,13 @@ defprotocol Collectable do
|
||||
shape where just the range limits are stored.
|
||||
|
||||
The `Collectable` module was designed to fill the gap left by the
|
||||
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
|
||||
|
||||
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
|
||||
`Enumerable` is about taking values out, `Collectable.into/1` is about
|
||||
collecting those values into a structure.
|
||||
|
||||
`empty/1` receives a collectable and returns an empty version of the
|
||||
same collectable. By combining the enumerable functionality with `into/1`
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
`Enumerable` protocol. `into/1` can be seen as the opposite of
|
||||
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
|
||||
`Collectable.into/1` is about collecting those values into a structure.
|
||||
"""
|
||||
|
||||
@type command :: {:cont, term} | :done | :halt
|
||||
|
||||
@doc """
|
||||
Receives a collectable structure and returns an empty one.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(collectable)
|
||||
|
||||
@doc """
|
||||
Returns a function that collects values alongside
|
||||
the initial accumulation value.
|
||||
@@ -65,10 +47,6 @@ defprotocol Collectable do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: List do
|
||||
def empty(_list) do
|
||||
[]
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{[], fn
|
||||
list, {:cont, x} -> [x|list]
|
||||
@@ -79,10 +57,6 @@ defimpl Collectable, for: List do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def empty(_bitstring) do
|
||||
""
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
|
||||
@@ -92,22 +66,7 @@ defimpl Collectable, for: BitString do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Function do
|
||||
def empty(function) do
|
||||
function
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
IO.write :stderr, "warning: passing a function as Collectable is deprecated\n#{Exception.format_stacktrace}"
|
||||
{function, function}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def empty(_map) do
|
||||
%{}
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
map, {:cont, {k, v}} -> :maps.put(k, v, map)
|
||||
|
||||
+1
-22
@@ -341,7 +341,7 @@ defmodule Enum do
|
||||
{_, {acc, {buffer, i}}} =
|
||||
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
|
||||
|
||||
if nil?(pad) || i == 0 do
|
||||
if is_nil(pad) || i == 0 do
|
||||
:lists.reverse(acc)
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
|
||||
@@ -1798,27 +1798,6 @@ defmodule Enum do
|
||||
reverse(collection) |> :lists.reverse
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Traverses the given enumerable keeping its shape.
|
||||
|
||||
It also expects the enumerable to implement the `Collectable` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
"""
|
||||
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
|
||||
def traverse(collection, transform) when is_list(collection) do
|
||||
:lists.map(transform, collection)
|
||||
end
|
||||
|
||||
def traverse(collection, transform) do
|
||||
into(collection, Collectable.empty(collection), transform)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enumerates the collection, removing all duplicated items.
|
||||
|
||||
|
||||
@@ -613,12 +613,17 @@ defmodule BadArityError do
|
||||
end
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
defexception [module: nil, function: nil, arity: nil, self: false]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
def message(%{function: function, module: module, arity: arity, self: self}) do
|
||||
if function do
|
||||
formatted = Exception.format_mfa module, function, arity
|
||||
suffix = if self or is_nil(module) or :code.is_loaded(module) do
|
||||
""
|
||||
else
|
||||
" (module #{inspect module} is not available)"
|
||||
end
|
||||
"undefined function: #{formatted}" <> suffix
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
@@ -772,8 +777,9 @@ defmodule ErlangError do
|
||||
end
|
||||
|
||||
def normalize(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
|
||||
stacktrace = stacktrace || :erlang.get_stacktrace
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
|
||||
end
|
||||
|
||||
def normalize(:function_clause, stacktrace) do
|
||||
@@ -800,4 +806,7 @@ defmodule ErlangError do
|
||||
defp from_stacktrace(_) do
|
||||
{nil, nil, nil}
|
||||
end
|
||||
|
||||
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
|
||||
defp from_self(_), do: false
|
||||
end
|
||||
|
||||
+31
-15
@@ -75,6 +75,12 @@ defmodule File do
|
||||
@type posix :: :file.posix()
|
||||
@type io_device :: :file.io_device()
|
||||
@type stat_options :: [time: :local | :universal | :posix]
|
||||
@type mode :: :append | :binary | :compressed | :delayed_write | :exclusive |
|
||||
:raw | :read | :read_ahead | :sync | :write |
|
||||
{:encoding , :latin1 | :unicode | :utf16 | :utf32 | :utf8 |
|
||||
{:utf16, :big | :little} | {:utf32, :big | :little}} |
|
||||
{:read_ahead, pos_integer} |
|
||||
{:delayed_write, non_neg_integer, non_neg_integer}
|
||||
|
||||
@doc """
|
||||
Returns `true` if the path is a regular file.
|
||||
@@ -612,7 +618,7 @@ defmodule File do
|
||||
|
||||
Check `File.open/2` for other available options.
|
||||
"""
|
||||
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
|
||||
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(IO.chardata_to_string(path), content, modes)
|
||||
end
|
||||
@@ -620,7 +626,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
|
||||
"""
|
||||
@spec write!(Path.t, iodata, list) :: :ok | no_return
|
||||
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
|
||||
def write!(path, content, modes \\ []) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.write_file(path, content, modes) do
|
||||
@@ -902,7 +908,8 @@ defmodule File do
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
|
||||
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
|
||||
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
@@ -935,7 +942,7 @@ defmodule File do
|
||||
end)
|
||||
|
||||
"""
|
||||
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
def open(path, modes, function) do
|
||||
case open(path, modes) do
|
||||
{:ok, device} ->
|
||||
@@ -953,7 +960,7 @@ defmodule File do
|
||||
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list) :: io_device | no_return
|
||||
@spec open!(Path.t, [mode]) :: io_device | no_return
|
||||
def open!(path, modes \\ []) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes) do
|
||||
@@ -968,7 +975,7 @@ defmodule File do
|
||||
|
||||
Returns the function result otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
|
||||
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
|
||||
def open!(path, modes, function) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes, function) do
|
||||
@@ -989,18 +996,27 @@ defmodule File do
|
||||
@spec cwd() :: {:ok, binary} | {:error, posix}
|
||||
def cwd() do
|
||||
case F.get_cwd do
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
|
||||
case :os.type() do
|
||||
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
|
||||
_ -> original
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter(original), do: original
|
||||
|
||||
@doc """
|
||||
The same as `cwd/0`, but raises an exception if it fails.
|
||||
"""
|
||||
@spec cwd!() :: binary | no_return
|
||||
def cwd!() do
|
||||
case F.get_cwd do
|
||||
{:ok, cwd} -> IO.chardata_to_string(cwd)
|
||||
case cwd() do
|
||||
{:ok, cwd} -> cwd
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "get current working directory"
|
||||
end
|
||||
@@ -1124,7 +1140,7 @@ defmodule File do
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
"""
|
||||
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chmod(path, mode) do
|
||||
F.change_mode(IO.chardata_to_string(path), mode)
|
||||
end
|
||||
@@ -1132,7 +1148,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chmod!(Path.t, integer) :: :ok | no_return
|
||||
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chmod!(path, mode) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chmod(path, mode) do
|
||||
@@ -1147,7 +1163,7 @@ defmodule File do
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chgrp(path, gid) do
|
||||
F.change_group(IO.chardata_to_string(path), gid)
|
||||
end
|
||||
@@ -1155,7 +1171,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chgrp!(Path.t, integer) :: :ok | no_return
|
||||
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chgrp!(path, gid) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chgrp(path, gid) do
|
||||
@@ -1170,7 +1186,7 @@ defmodule File do
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
@spec chown(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chown(path, uid) do
|
||||
F.change_owner(IO.chardata_to_string(path), uid)
|
||||
end
|
||||
@@ -1178,7 +1194,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chown!(Path.t, integer) :: :ok | no_return
|
||||
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chown!(path, uid) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chown(path, uid) do
|
||||
|
||||
+705
-302
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,173 @@
|
||||
defmodule GenEvent.Stream do
|
||||
@moduledoc """
|
||||
Defines a `GenEvent` stream.
|
||||
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
|
||||
"""
|
||||
defstruct manager: nil, timeout: :infinity
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
manager: GenEvent.manager,
|
||||
timeout: timeout}
|
||||
|
||||
@doc false
|
||||
def init({_pid, _ref} = state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(event, _state) do
|
||||
exit({:bad_event, event})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(msg, _state) do
|
||||
exit({:bad_call, msg})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: GenEvent.Stream do
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _item) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp wrap_reducer(fun) do
|
||||
fn
|
||||
{:ack, manager, ref, event}, acc ->
|
||||
send manager, {ref, :ok}
|
||||
fun.(event, acc)
|
||||
{:async, _manager, _ref, event}, acc ->
|
||||
fun.(event, acc)
|
||||
{:sync, manager, ref, event}, acc ->
|
||||
try do
|
||||
fun.(event, acc)
|
||||
after
|
||||
send manager, {ref, :ok}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start(%{manager: manager} = stream) do
|
||||
try do
|
||||
{:ok, {pid, ref}} = :gen.call(manager, self(), {:add_process_handler, self(), true}, :infinity)
|
||||
mon_ref = Process.monitor(pid)
|
||||
{pid, ref, mon_ref}
|
||||
catch
|
||||
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp next(%{timeout: timeout} = stream, {pid, ref, mon_ref} = acc) do
|
||||
self = self()
|
||||
|
||||
receive do
|
||||
# The handler was removed. Stop iteration, resolve the
|
||||
# event later. We need to demonitor now, otherwise DOWN
|
||||
# appears with higher priority in the shutdown process.
|
||||
{:gen_event_EXIT, ^self, _reason} = event ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
send(self, event)
|
||||
{:halt, {:removed, acc}}
|
||||
|
||||
# The manager died. Stop iteration, resolve the event later.
|
||||
{:DOWN, ^mon_ref, _, _, _} = event ->
|
||||
send(self, event)
|
||||
{:halt, {:removed, acc}}
|
||||
|
||||
# Got an async event.
|
||||
{_from, {^pid, ^ref}, {:notify, event}} ->
|
||||
{[{:async, pid, ref, event}], acc}
|
||||
|
||||
# Got a sync event.
|
||||
{_from, {^pid, ^ref}, {:sync_notify, event}} ->
|
||||
{[{:sync, pid, ref, event}], acc}
|
||||
|
||||
# Got an ack event.
|
||||
{_from, {^pid, ^ref}, {:ack_notify, event}} ->
|
||||
{[{:ack, pid, ref, event}], acc}
|
||||
after
|
||||
timeout ->
|
||||
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, we know the handler was already
|
||||
# removed, so we don't trigger a request for doing so.
|
||||
defp stop(stream, {:removed, {pid, ref, mon_ref} = acc}) do
|
||||
case wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
:ok ->
|
||||
flush_events(ref)
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :stop, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, the handler was not removed yet,
|
||||
# so we trigger a request for doing so.
|
||||
defp stop(stream, {pid, _, _} = acc) do
|
||||
parent = self()
|
||||
_ = Task.start(fn -> GenEvent.remove_handler(pid, parent, :shutdown) end)
|
||||
stop(stream, {:removed, acc})
|
||||
end
|
||||
|
||||
defp wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
self = self()
|
||||
|
||||
receive do
|
||||
{_from, {^pid, ^ref}, {notify, _event}} when notify in [:ack_notify, :sync_notify] ->
|
||||
send pid, {ref, :done}
|
||||
wait_for_handler_removal(pid, ref, mon_ref)
|
||||
{:gen_event_EXIT, ^self, reason}
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 3 and elem(reason, 0) == :swapped ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
:ok
|
||||
{:gen_event_EXIT, ^self, reason} ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
{:error, reason}
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp flush_events(ref) do
|
||||
receive do
|
||||
{_from, {_pid, ^ref}, {:notify, _event}} ->
|
||||
flush_events(ref)
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -349,18 +349,57 @@ defmodule GenServer do
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
:gen_server.call(server, request, timeout)
|
||||
try do
|
||||
:gen.call(server, :"$gen_call", request, timeout)
|
||||
catch
|
||||
:exit, reason ->
|
||||
exit({reason, {__MODULE__, :call, [server, request, timeout]}})
|
||||
else
|
||||
{:ok, res} -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an asynchronous request to the `server`.
|
||||
|
||||
This function returns `:ok` immediately, regardless of whether the
|
||||
destination node or server does exists. `handle_cast/2` will be called on the
|
||||
server to handle the request.
|
||||
This function returns `:ok` immediately, regardless of
|
||||
whether the destination node or server does exists, unless
|
||||
the server is specified as an atom.
|
||||
|
||||
`handle_cast/2` will be called on the server to handle
|
||||
the request. In case the server is a node which is not
|
||||
yet connected to the caller one, the call is going to
|
||||
block until a connection happens. This is different than
|
||||
the behaviour in OTP's `:gen_server` where the message
|
||||
would be sent by another process, which could cause
|
||||
messages to arrive out of order.
|
||||
"""
|
||||
@spec cast(server, term) :: :ok
|
||||
defdelegate cast(server, request), to: :gen_server
|
||||
def cast(server, request)
|
||||
|
||||
def cast({:global, name}, request) do
|
||||
try do
|
||||
:global.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({:via, mod, name}, request) do
|
||||
try do
|
||||
mod.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({name, node}, request) when is_atom(name) and is_atom(node),
|
||||
do: do_send({name, node}, cast_msg(request))
|
||||
|
||||
def cast(dest, request) when is_atom(dest) or is_pid(dest),
|
||||
do: do_send(dest, cast_msg(request))
|
||||
|
||||
@doc """
|
||||
Casts all servers locally registered as `name` at the specified nodes.
|
||||
@@ -371,8 +410,19 @@ defmodule GenServer do
|
||||
See `multi_call/4` for more information.
|
||||
"""
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ nodes(), name, request) do
|
||||
:gen_server.abcast(nodes, name, request)
|
||||
def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
|
||||
msg = cast_msg(request)
|
||||
_ = for node <- nodes, do: do_send({name, node}, msg)
|
||||
:abcast
|
||||
end
|
||||
|
||||
defp cast_msg(req) do
|
||||
{:"$gen_cast", req}
|
||||
end
|
||||
|
||||
defp do_send(dest, msg) do
|
||||
send(dest, msg)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -440,11 +440,11 @@ defmodule Inspect.Algebra do
|
||||
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
|
||||
end
|
||||
|
||||
def do_surround_many(left, [], right, _, _opts, _fun, _) do
|
||||
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def do_surround_many(left, docs, right, limit, _opts, fun, sep) do
|
||||
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
|
||||
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
|
||||
end
|
||||
|
||||
@@ -482,7 +482,7 @@ defmodule Inspect.Algebra do
|
||||
and returns an IO data representation of the best layout for the
|
||||
document to fit in the given width.
|
||||
"""
|
||||
@spec format(t, non_neg_integer | :infinity) :: binary
|
||||
@spec format(t, non_neg_integer | :infinity) :: iodata
|
||||
def format(d, w) do
|
||||
format(w, 0, [{0, default_mode(w), doc_group(d)}])
|
||||
end
|
||||
|
||||
@@ -9,9 +9,10 @@ defmodule Integer do
|
||||
Determines if an integer is odd.
|
||||
|
||||
Returns `true` if `n` is an odd number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro odd?(n) do
|
||||
defmacro is_odd(n) do
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
end
|
||||
|
||||
@@ -19,9 +20,10 @@ defmodule Integer do
|
||||
Determines if an integer is even.
|
||||
|
||||
Returns `true` if `n` is an even number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro even?(n) do
|
||||
defmacro is_even(n) do
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
end
|
||||
|
||||
@@ -99,7 +101,7 @@ defmodule Integer do
|
||||
"64"
|
||||
|
||||
"""
|
||||
@spec to_string(integer, pos_integer) :: String.t
|
||||
@spec to_string(integer, 2..36) :: String.t
|
||||
def to_string(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
@@ -132,7 +134,7 @@ defmodule Integer do
|
||||
'3FF'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer, pos_integer) :: char_list
|
||||
@spec to_char_list(integer, 2..36) :: char_list
|
||||
def to_char_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
|
||||
@@ -394,11 +394,11 @@ defmodule IO do
|
||||
def each_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -406,11 +406,11 @@ defmodule IO do
|
||||
def each_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ defmodule IO.Stream do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
|
||||
@@ -1881,20 +1881,20 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> nil?(1)
|
||||
iex> is_nil(1)
|
||||
false
|
||||
|
||||
iex> nil?(nil)
|
||||
iex> is_nil(nil)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro nil?(x) do
|
||||
defmacro is_nil(x) do
|
||||
quote do: unquote(x) == nil
|
||||
end
|
||||
|
||||
@doc """
|
||||
A convenient macro that checks if the right side matches
|
||||
the left side. The left side is allowed to be a match pattern.
|
||||
A convenience macro that checks if the right side (an expression)
|
||||
matches the left side (a pattern).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2379,7 +2379,7 @@ defmodule Kernel do
|
||||
it is not loaded. Check `Code.ensure_loaded/1` for more
|
||||
information.
|
||||
"""
|
||||
@spec function_exported?(atom | tuple, atom, integer) :: boolean
|
||||
@spec function_exported?(atom | tuple, atom, arity) :: boolean
|
||||
def function_exported?(module, function, arity) do
|
||||
:erlang.function_exported(module, function, arity)
|
||||
end
|
||||
|
||||
@@ -408,7 +408,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp wrapper(fun) do
|
||||
fun.()
|
||||
_ = fun.()
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
is set to `true` and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
This function receives a set of callbacks as options:
|
||||
This function accepts the following options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
@@ -23,32 +23,36 @@ defmodule Kernel.ParallelCompiler do
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
* `:dest` - the destination directory for the beam files. When using `files/2`,
|
||||
this information is only used to properly annotate the beam files before
|
||||
they are loaded into memory. If you want a file to actually be writen to
|
||||
`dest`, use `files_to_path/3` instead.
|
||||
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks \\ [])
|
||||
def files(files, options \\ [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
def files(files, options) when is_list(options) do
|
||||
spawn_compilers(files, nil, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callbacks \\ [])
|
||||
def files_to_path(files, path, options \\ [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
|
||||
spawn_compilers(files, path, options)
|
||||
end
|
||||
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
defp spawn_compilers(files, path, options) do
|
||||
true = Code.ensure_loaded?(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
|
||||
result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error and we fail with CompileError
|
||||
@@ -59,21 +63,21 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
|
||||
send h, {ref, :ready}
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(t, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} =
|
||||
@@ -90,7 +94,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
_ = if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
:elixir_compiler.file(h, Keyword.get(options, :dest))
|
||||
end
|
||||
{:compiled, h}
|
||||
catch
|
||||
@@ -99,31 +103,31 @@ defmodule Kernel.ParallelCompiler do
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
spawn_compilers(t, original, output, options, waiting,
|
||||
[{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
defp spawn_compilers([], _original, _output, _options, [], [], _schedulers, result) do
|
||||
for {:module, mod} <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
|
||||
send child, {ref, :release}
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, but queue and waiting are not full or do not match
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{:struct_available, module} ->
|
||||
available = for {:struct, pid, _, waiting_module} <- waiting,
|
||||
@@ -131,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:struct, module}|result])
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
if callback = Keyword.get(options, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
@@ -147,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:module, module}|result])
|
||||
|
||||
{:waiting, kind, child, ref, on} ->
|
||||
@@ -160,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
waiting = [{kind, child, ref, on}|waiting]
|
||||
end
|
||||
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
|
||||
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
if callback = Keyword.get(options, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
@@ -172,11 +176,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
new_entries = List.delete(entries, down_pid)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
spawn_compilers(new_entries, original, output, options, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
handle_failure(down_ref, reason, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -343,28 +343,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
|
||||
This choice was done so every time single- or double-quotes are used, we have
|
||||
a remote call irregardless of the quote contents. This decision is also reflected
|
||||
a remote call regardless of the quote contents. This decision is also reflected
|
||||
in the quoted expressions discussed below.
|
||||
|
||||
## Runtime (dynamic) behaviour
|
||||
|
||||
The result returned by `.` is always specified by the right-side:
|
||||
|
||||
iex> x = String
|
||||
iex> x.downcase("FOO")
|
||||
"foo"
|
||||
iex> x.Sample
|
||||
String.Sample
|
||||
|
||||
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
|
||||
at runtime via `apply/3` and `Module.concat/2`:
|
||||
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
## Quoted expression
|
||||
|
||||
When `.` is used, the quoted expression may take two distinct
|
||||
@@ -1112,7 +1093,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine the situation you have a variable `name` and
|
||||
Imagine the situation you have a variable `value` and
|
||||
you want to inject it inside some quote. The first attempt
|
||||
would be:
|
||||
|
||||
@@ -1327,22 +1308,13 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
1. The head element of aliases can be any term.
|
||||
1. The head element of aliases can be any term that must expand to
|
||||
an atom at compilation time.
|
||||
|
||||
2. The tail elements of aliases are guaranteed to always be atoms.
|
||||
|
||||
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
|
||||
|
||||
4. When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
Module.concat [some_var, Foo]
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
|
||||
|
||||
@@ -998,7 +998,7 @@ defmodule Kernel.Typespec do
|
||||
typespec({:list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
defp typespec(list, vars, caller) when is_list(list) do
|
||||
[h|t] = :lists.reverse(list)
|
||||
union = :lists.foldl(fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
@@ -1006,6 +1006,10 @@ defmodule Kernel.Typespec do
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(other, _vars, caller) do
|
||||
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
|
||||
@@ -136,7 +136,7 @@ defmodule Keyword do
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: {:ok, value}
|
||||
@spec fetch(t, key) :: {:ok, value} | :error
|
||||
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{^key, value} -> {:ok, value}
|
||||
|
||||
@@ -247,7 +247,7 @@ defmodule List do
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
end
|
||||
@@ -532,7 +532,7 @@ defmodule List do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(char_list, non_neg_integer) :: integer
|
||||
@spec to_integer(char_list, 2..36) :: integer
|
||||
def to_integer(char_list, base) do
|
||||
:erlang.list_to_integer(char_list, base)
|
||||
end
|
||||
|
||||
@@ -17,6 +17,7 @@ defmodule Macro do
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :when, :in,
|
||||
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
@@ -39,7 +40,8 @@ defmodule Macro do
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
|
||||
o when o in [:|>, :<<<, :>>>] -> {:left, 170}
|
||||
o when o in [:|>, :<<<, :>>>, :<~, :~>,
|
||||
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
|
||||
:in -> {:left, 180}
|
||||
o when o in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
@@ -937,7 +939,7 @@ defmodule Macro do
|
||||
be expanded.
|
||||
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
`expand_once/2` for more information and exmaples.
|
||||
`expand_once/2` for more information and examples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({tree, true}, env)
|
||||
|
||||
@@ -4,7 +4,22 @@ defmodule Macro.Env do
|
||||
|
||||
The current environment can be accessed at any time as
|
||||
`__ENV__`. Inside macros, the caller environment can be
|
||||
accessed as `__CALLER__`. It contains the following fields:
|
||||
accessed as `__CALLER__`.
|
||||
|
||||
An instance of `Macro.Env` must not be modified by hand. If you need to
|
||||
create a custom environment to pass to `Code.eval_quoted/3`, use the
|
||||
following trick:
|
||||
|
||||
def make_custom_env do
|
||||
import SomeModule, only: [some_function: 2]
|
||||
alias A.B.C
|
||||
__ENV__
|
||||
end
|
||||
|
||||
You may then call `make_custom_env()` to get a struct with the desired
|
||||
imports and aliases included.
|
||||
|
||||
It contains the following fields:
|
||||
|
||||
* `module` - the current module name
|
||||
* `file` - the current file name as a binary
|
||||
@@ -105,9 +120,9 @@ defmodule Macro.Env do
|
||||
"""
|
||||
def stacktrace(%{__struct__: Macro.Env} = env) do
|
||||
cond do
|
||||
nil?(env.module) ->
|
||||
is_nil(env.module) ->
|
||||
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
|
||||
nil?(env.function) ->
|
||||
is_nil(env.function) ->
|
||||
[{env.module, :__MODULE__, 0, relative_location(env)}]
|
||||
true ->
|
||||
{name, arity} = env.function
|
||||
|
||||
@@ -85,6 +85,9 @@ defmodule Module do
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
Several uses of `@compile` will accumulate instead of overriding
|
||||
previous ones.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@@ -476,11 +479,6 @@ defmodule Module do
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def function(mod, fun, arity) do
|
||||
:erlang.make_fun(mod, fun, arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
@@ -522,7 +520,7 @@ defmodule Module do
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
if(is_nil(doc), do: old_doc, else: doc)
|
||||
})
|
||||
:ok
|
||||
end
|
||||
@@ -896,7 +894,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
|
||||
@@ -360,7 +360,7 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
defp replace_edge!(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
||||
end
|
||||
:ok
|
||||
|
||||
@@ -30,7 +30,7 @@ defmodule Node do
|
||||
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
|
||||
local node is not alive.
|
||||
"""
|
||||
@spec stop() :: :ok | {:error, term}
|
||||
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
|
||||
def stop() do
|
||||
:net_kernel.stop()
|
||||
end
|
||||
|
||||
@@ -345,7 +345,7 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
|
||||
"""
|
||||
@spec flag(pid, process_flag, term) :: term
|
||||
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
|
||||
def flag(pid, flag, value) do
|
||||
:erlang.process_flag(pid, flag, value)
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Record do
|
||||
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
iex> Record.record? {User, "john", 27}
|
||||
iex> Record.is_record {User, "john", 27}
|
||||
true
|
||||
|
||||
This module provides conveniences for working with records at
|
||||
@@ -64,11 +64,11 @@ defmodule Record do
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.record?(record, User)
|
||||
iex> Record.is_record(record, User)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro record?(data, kind) do
|
||||
defmacro is_record(data, kind) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -92,14 +92,14 @@ defmodule Record do
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.record?(record)
|
||||
iex> Record.is_record(record)
|
||||
true
|
||||
iex> tuple = {}
|
||||
iex> Record.record?(tuple)
|
||||
iex> Record.is_record(tuple)
|
||||
false
|
||||
|
||||
"""
|
||||
defmacro record?(data) do
|
||||
defmacro is_record(data) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -319,7 +319,7 @@ defmodule Record do
|
||||
# Returns a keyword list of the record
|
||||
@doc false
|
||||
def __keyword__(atom, fields, record) do
|
||||
if record?(record, atom) do
|
||||
if is_record(record, atom) do
|
||||
[_tag|values] = Tuple.to_list(record)
|
||||
join_keyword(fields, values, [])
|
||||
else
|
||||
|
||||
+14
-2
@@ -78,7 +78,7 @@ defmodule Regex do
|
||||
|
||||
defstruct re_pattern: nil, source: "", opts: ""
|
||||
|
||||
@opaque t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
|
||||
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
|
||||
|
||||
defmodule CompileError do
|
||||
defexception message: "regex could not be compiled"
|
||||
@@ -133,6 +133,7 @@ defmodule Regex do
|
||||
Compiles the regular expression according to the given options.
|
||||
Fails with `Regex.CompileError` if the regex cannot be compiled.
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: t
|
||||
def compile!(source, options \\ "") do
|
||||
case compile(source, options) do
|
||||
{:ok, regex} -> regex
|
||||
@@ -152,6 +153,7 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, String.t) :: boolean
|
||||
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{:capture, :none}]) == :match
|
||||
end
|
||||
@@ -168,6 +170,8 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec regex?(t) :: true
|
||||
@spec regex?(any) :: false
|
||||
def regex?(%Regex{}), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@@ -193,6 +197,7 @@ defmodule Regex do
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
@@ -223,6 +228,7 @@ defmodule Regex do
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec named_captures(t, String.t, [term]) :: map | nil
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
names = names(regex)
|
||||
options = Keyword.put(options, :capture, names)
|
||||
@@ -233,6 +239,7 @@ defmodule Regex do
|
||||
@doc """
|
||||
Returns the underlying `re_pattern` in the regular expression.
|
||||
"""
|
||||
@spec re_pattern(t) :: term
|
||||
def re_pattern(%Regex{re_pattern: compiled}) do
|
||||
compiled
|
||||
end
|
||||
@@ -246,6 +253,7 @@ defmodule Regex do
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@spec source(t) :: String.t
|
||||
def source(%Regex{source: source}) do
|
||||
source
|
||||
end
|
||||
@@ -259,6 +267,7 @@ defmodule Regex do
|
||||
"m"
|
||||
|
||||
"""
|
||||
@spec opts(t) :: String.t
|
||||
def opts(%Regex{opts: opts}) do
|
||||
opts
|
||||
end
|
||||
@@ -272,6 +281,7 @@ defmodule Regex do
|
||||
["foo"]
|
||||
|
||||
"""
|
||||
@spec names(t) :: [String.t]
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
names
|
||||
@@ -301,6 +311,7 @@ defmodule Regex do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec scan(t, String.t, [term]) :: [[String.t]]
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
@@ -353,7 +364,7 @@ defmodule Regex do
|
||||
["a", "c"]
|
||||
|
||||
"""
|
||||
|
||||
@spec split(t, String.t, [term]) :: [String.t]
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(%Regex{}, "", _opts), do: [""]
|
||||
@@ -439,6 +450,7 @@ defmodule Regex do
|
||||
"[b][d]"
|
||||
|
||||
"""
|
||||
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex, string, replacement, options) when is_binary(replacement) do
|
||||
|
||||
@@ -198,7 +198,7 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
|
||||
target1.reduce(set1, {:cont, target1.new}, fn v, acc ->
|
||||
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
+112
-14
@@ -162,7 +162,7 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
|
||||
if nil?(pad) || count == 0 do
|
||||
if is_nil(pad) || count == 0 do
|
||||
{:cont, acc}
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
@@ -338,6 +338,25 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a value every `n` milliseconds.
|
||||
|
||||
The values emitted are an increasing counter starting at `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.interval(10) |> Enum.take(10)
|
||||
[0,1,2,3,4,5,6,7,8,9]
|
||||
|
||||
"""
|
||||
@spec interval(non_neg_integer) :: Enumerable.t
|
||||
def interval(n) do
|
||||
unfold 0, fn (count) ->
|
||||
:timer.sleep(n)
|
||||
{count, count + 1}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Injects the stream values into the given collectable as a side-effect.
|
||||
|
||||
@@ -563,6 +582,22 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a single value after `n` milliseconds.
|
||||
|
||||
The value emitted is `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.timer(10) |> Enum.to_list
|
||||
[0]
|
||||
|
||||
"""
|
||||
@spec timer(non_neg_integer) :: Enumerable.t
|
||||
def timer(n) do
|
||||
take(interval(n), 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
@@ -847,6 +882,15 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: Enumerable.t
|
||||
def cycle(enumerable)
|
||||
|
||||
def cycle(enumerable) when is_list(enumerable) do
|
||||
unfold {enumerable, enumerable}, fn
|
||||
{source, [h | t]} -> {h, {source, t}}
|
||||
{source = [h | t], []} -> {h, {source, t}}
|
||||
end
|
||||
end
|
||||
|
||||
def cycle(enumerable) do
|
||||
fn acc, fun ->
|
||||
inner = &do_cycle_each(&1, &2, fun)
|
||||
@@ -934,14 +978,15 @@ defmodule Stream do
|
||||
@doc """
|
||||
Emits a sequence of values for the given resource.
|
||||
|
||||
Similar to `unfold/2` but the initial value is computed lazily via
|
||||
`start_fun` and executes an `after_fun` at the end of enumeration
|
||||
(both in cases of success and failure).
|
||||
Similar to `transform/2` but the initial accumulated value is
|
||||
computed lazily via `start_fun` and executes an `after_fun` at
|
||||
the end of enumeration (both in cases of success and failure).
|
||||
|
||||
Successive values are generated by calling `next_fun` with the
|
||||
previous accumulator (the initial value being the result returned
|
||||
by `start_fun`) and it must return a tuple with the current and
|
||||
next accumulator. The enumeration finishes if it returns `nil`.
|
||||
by `start_fun`) and it must return a tuple containing a list
|
||||
of items to be emitted and the next accumulator. The enumeration
|
||||
finishes if it returns `{:halt, acc}`.
|
||||
|
||||
As the name says, this function is useful to stream values from
|
||||
resources.
|
||||
@@ -951,8 +996,8 @@ defmodule Stream do
|
||||
Stream.resource(fn -> File.open!("sample") end,
|
||||
fn file ->
|
||||
case IO.read(file, :line) do
|
||||
data when is_binary(data) -> {data, file}
|
||||
_ -> nil
|
||||
data when is_binary(data) -> {[data], file}
|
||||
_ -> {:halt, file}
|
||||
end
|
||||
end,
|
||||
fn file -> File.close(file) end)
|
||||
@@ -974,9 +1019,11 @@ defmodule Stream do
|
||||
|
||||
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
|
||||
try do
|
||||
# Optimize the most common cases
|
||||
case next_fun.(next_acc) do
|
||||
nil -> nil
|
||||
{v, next_acc} -> {fun.(v, acc), next_acc}
|
||||
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
|
||||
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
|
||||
{_, _} = other -> other
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -984,11 +1031,62 @@ defmodule Stream do
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
{:done, acc}
|
||||
{acc, next_acc} ->
|
||||
{:opt, acc, next_acc} ->
|
||||
do_resource(next_acc, next_fun, acc, fun, after_fun)
|
||||
{:halt, next_acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{list, next_acc} when is_list(list) ->
|
||||
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.List.reduce(list, &1, fun))
|
||||
{enum, next_acc} ->
|
||||
inner = &do_resource_each(&1, &2, fun)
|
||||
do_enum_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.reduce(enum, &1, inner))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_list_resource(next_acc, next_fun, acc, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.(acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_list_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_enum_resource(next_acc, next_fun, {op, acc}, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.({op, [:outer|acc]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:halted, [:outer|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, [:inner|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:done, [_|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:suspended, [_|acc], c} ->
|
||||
{:suspended, acc, &do_enum_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_resource_each(x, [:outer|acc], f) do
|
||||
case f.(x, acc) do
|
||||
{:halt, res} -> {:halt, [:inner|res]}
|
||||
{op, res} -> {op, [:outer|res]}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+81
-21
@@ -4,28 +4,12 @@ defmodule String do
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
The functions in this module act according to the
|
||||
Unicode Standard, version 6.3.0. For example,
|
||||
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
|
||||
module.
|
||||
|
||||
In addition to this module, Elixir provides more low-level
|
||||
operations that work directly with binaries. Some
|
||||
of those can be found in the `Kernel` module, as:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
As per the Unicode Standard, a codepoint is an Unicode
|
||||
Character, which may be represented by one or more bytes.
|
||||
For example, the character "é" is represented with two
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 6.3.0.As per the standard, a codepoint is
|
||||
an Unicode Character, which may be represented by one or more
|
||||
bytes. For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> byte_size("é")
|
||||
@@ -66,6 +50,82 @@ defmodule String do
|
||||
This current Elixir version implements Extended Grapheme Cluster
|
||||
algorithm.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
To act accordingly to the Unicode Standard, many functions
|
||||
in this module runs in linear time, as it needs to traverse
|
||||
the whole string considering the proper Unicode codepoints.
|
||||
|
||||
For example, `String.length/1` is going to take longer as
|
||||
the input grows. On the other hand, `byte_size/1` always runs
|
||||
in constant time (i.e. regardless of the input size).
|
||||
|
||||
This means often there are performance costs in using the
|
||||
functions in this module, compared to the more low-level
|
||||
operations that work directly with binaries:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
There are many situations where using the `String` module can
|
||||
be avoided in favor of binary functions or pattern matching.
|
||||
For example, imagine you have a string `prefix` and you want to
|
||||
remove this prefix from another string named `full`.
|
||||
|
||||
One may be tempted to write:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base, String.length(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Although the function above works, it performs poorly. To
|
||||
calculate the length of the string, we need to traverse it
|
||||
fully, so we traverse both `prefix` and `full` strings, then
|
||||
slice the `full` one, traversing it again.
|
||||
|
||||
A first attempting at improving it could be with ranges:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base..-1)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
While this is much better (we don't traverse `full` twice),
|
||||
it could still be improved. In this case, since we want to
|
||||
extract a substring from a string, we can use `byte_size/1`
|
||||
and `binary_part/3` as there is no chance we will slice in
|
||||
the middle of a codepoint made of more than one byte:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> binary_part(full, base, byte_size(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Or simply used pattern matching:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> <<_ :: binary-size(base), rest :: binary>> = full
|
||||
...> rest
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
On the other hand, if you want to dynamically slice a string
|
||||
based on an integer value, then using `String.slice/3` is the
|
||||
best option as it guarantees we won't incorrectly split a valid
|
||||
codepoint in multiple bytes.
|
||||
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
@@ -1363,7 +1423,7 @@ defmodule String do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t, pos_integer) :: integer
|
||||
@spec to_integer(String.t, 2..36) :: integer
|
||||
def to_integer(string, base) do
|
||||
:erlang.binary_to_integer(string, base)
|
||||
end
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: {:ok, pid}
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, {string, options}, [])
|
||||
GenServer.start_link(__MODULE__, {string, options}, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -52,7 +52,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec contents(pid) :: {binary, binary}
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
GenServer.call(pid, :contents)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -68,7 +68,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec close(pid) :: {:ok, {binary, binary}}
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
GenServer.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
|
||||
@@ -132,7 +132,7 @@ defmodule Supervisor do
|
||||
|
||||
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one children. Many functions
|
||||
supervisor specification to contain only one child. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used.
|
||||
|
||||
@@ -268,7 +268,9 @@ defmodule Supervisor do
|
||||
and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
|
||||
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
|
||||
def start_child(supervisor, child_spec_or_args) do
|
||||
call(supervisor, {:start_child, child_spec_or_args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given pid or child id.
|
||||
@@ -290,7 +292,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
|
||||
def terminate_child(supervisor, pid_or_child_id) do
|
||||
call(supervisor, {:terminate_child, pid_or_child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the child specification identified by `child_id`.
|
||||
@@ -306,7 +310,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting
|
||||
defdelegate delete_child(supervisor, child_id), to: :supervisor
|
||||
def delete_child(supervisor, child_id) do
|
||||
call(supervisor, {:delete_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Restarts a child process identified by `child_id`.
|
||||
@@ -335,7 +341,9 @@ defmodule Supervisor do
|
||||
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
|
||||
{:ok, child} | {:ok, child, term} | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting | term
|
||||
defdelegate restart_child(supervisor, child_id), to: :supervisor
|
||||
def restart_child(supervisor, child_id) do
|
||||
call(supervisor, {:restart_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children.
|
||||
@@ -361,7 +369,9 @@ defmodule Supervisor do
|
||||
child | :restarting,
|
||||
Supervisor.Spec.worker,
|
||||
Supervisor.Spec.modules}]
|
||||
defdelegate which_children(supervisor), to: :supervisor
|
||||
def which_children(supervisor) do
|
||||
call(supervisor, :which_children)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a map containing count values for the supervisor.
|
||||
@@ -381,9 +391,15 @@ defmodule Supervisor do
|
||||
|
||||
"""
|
||||
@spec count_children(supervisor) ::
|
||||
[specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer]
|
||||
%{specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer}
|
||||
def count_children(supervisor) do
|
||||
:supervisor.count_children(supervisor) |> :maps.from_list
|
||||
call(supervisor, :count_children) |> :maps.from_list
|
||||
end
|
||||
|
||||
@compile {:inline, call: 2}
|
||||
|
||||
defp call(supervisor, req) do
|
||||
GenServer.call(supervisor, req, :infinity)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -145,12 +145,12 @@ defmodule Supervisor.Spec do
|
||||
in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5.
|
||||
a time frame. Defaults to 3.
|
||||
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
Defaults to 5.
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
The `:strategy` option is required and by default maximum 3 restarts
|
||||
are allowed within 5 seconds. Please check the `Supervisor` module for
|
||||
a complete description of the available strategies.
|
||||
"""
|
||||
@@ -162,7 +162,7 @@ defmodule Supervisor.Spec do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxR = Keyword.get(options, :max_restarts, 3)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
|
||||
|
||||
@@ -94,11 +94,20 @@ defmodule System do
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> IO.chardata_to_string(base)
|
||||
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
|
||||
case :os.type() do
|
||||
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
|
||||
_ -> original
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter(original), do: original
|
||||
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
|
||||
@@ -119,6 +119,30 @@ defmodule Task do
|
||||
Task.Supervised.start_link(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(fun) :: {:ok, pid}
|
||||
def start(fun) do
|
||||
start(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(module, atom, [term]) :: {:ok, pid}
|
||||
def start(mod, fun, args) do
|
||||
Task.Supervised.start(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
def start(info, fun) do
|
||||
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(info, fun) do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
@@ -309,7 +309,7 @@ defmodule URI do
|
||||
|
||||
scheme = normalize_scheme(scheme)
|
||||
|
||||
if nil?(port) and not nil?(scheme) do
|
||||
if is_nil(port) and not is_nil(scheme) do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ defmodule Version do
|
||||
patch = nillify(patch)
|
||||
pre = nillify(pre)
|
||||
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
if is_nil(minor) or (is_nil(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
major = String.to_integer(major)
|
||||
|
||||
@@ -41,28 +41,29 @@
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
%% Numbers
|
||||
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
|
||||
-define(is_bin(S), S >= $0 andalso S =< $1).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
|
||||
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
|
||||
-define(is_bin(S), (S >= $0 andalso S =< $1)).
|
||||
-define(is_octal(S), (S >= $0 andalso S =< $7)).
|
||||
-define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
|
||||
|
||||
%% Digits and letters
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
-define(is_upcase(S), S >= $A andalso S =< $Z).
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
-define(is_digit(S), (S >= $0 andalso S =< $9)).
|
||||
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
|
||||
-define(is_downcase(S), (S >= $a andalso S =< $z)).
|
||||
|
||||
%% Atoms
|
||||
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
-define(is_atom_start(S), (?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_atom(S), (?is_identifier(S) orelse (S == $@))).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
|
||||
-define(is_identifier(S), (?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_sigil(S), ((S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
-define(is_quote(S), (S == $" orelse S == $')).
|
||||
|
||||
%% Spaces
|
||||
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
|
||||
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
|
||||
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
|
||||
-define(is_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
|
||||
-define(is_vertical_space(S), ((S == $\r) orelse (S == $\n))).
|
||||
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
|
||||
-define(is_invalid_space(S), (S == 16#A0)).
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file/2, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -42,12 +42,15 @@ file(Relative) when is_binary(Relative) ->
|
||||
file(Relative, Dest) ->
|
||||
File = filename:absname(Relative),
|
||||
{ok, Bin} = file:read_file(File),
|
||||
string(elixir_utils:characters_to_list(Bin), File, Dest).
|
||||
string(elixir_utils:characters_to_list(Bin), File, case Dest of
|
||||
nil -> Dest;
|
||||
_ -> filename:absname(Dest)
|
||||
end).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
Dest = filename:absname(Path),
|
||||
file_to_path(File, Dest) when is_binary(File), is_binary(Dest) ->
|
||||
Comp = file(File, Dest),
|
||||
_ = [binary_to_path(X, Dest) || X <- Comp],
|
||||
Abs = filename:absname(Dest),
|
||||
_ = [binary_to_path(X, Abs) || X <- Comp],
|
||||
Comp.
|
||||
|
||||
%% Evaluation
|
||||
|
||||
@@ -82,6 +82,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
LinifyBody = elixir_quote:linify(Line, Key, Body),
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, E),
|
||||
assert_valid_name(Line, Kind, Name, Args, E),
|
||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
|
||||
|
||||
@@ -175,7 +176,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
|
||||
Arity = length(Args),
|
||||
|
||||
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
|
||||
|
||||
@@ -404,8 +405,12 @@ invalid_arg(_) ->
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
|
||||
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
assert_valid_name(Line, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {is_record, Kind});
|
||||
assert_valid_name(_Line, _Kind, _Name, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
%% Format errors
|
||||
@@ -447,5 +452,9 @@ format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
format_error(invalid_args_for_bodyless_clause) ->
|
||||
"can use only variables and \\\\ as arguments of bodyless clause";
|
||||
|
||||
format_error({is_record, Kind}) ->
|
||||
io_lib:format("cannot define function named ~ts is_record/2 due to compability "
|
||||
"issues with the Erlang compiler (it is a known bug)", [Kind]);
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
expand(Args, E) ->
|
||||
NoContext = E#{context := nil},
|
||||
lists:mapfoldl(fun
|
||||
({'\\\\', Meta, [Left, Right]}, Acc) ->
|
||||
{ELeft, EL} = elixir_exp:expand(Left, Acc),
|
||||
{ERight, _} = elixir_exp:expand(Right, Acc#{context := nil}),
|
||||
{ERight, _} = elixir_exp:expand(Right, NoContext),
|
||||
{{'\\\\', Meta, [ELeft, ERight]}, EL};
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
|
||||
@@ -522,7 +522,5 @@ deprecation_message(Warning, Message) ->
|
||||
Message -> Warning ++ ", " ++ Message
|
||||
end.
|
||||
|
||||
deprecation('Elixir.Module', 'function', 3) ->
|
||||
"use :erlang.make_fun/3";
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-spec warn(non_neg_integer(), binary(), iolist()) -> ok.
|
||||
-spec warn(non_neg_integer(), binary(), unicode:chardata()) -> ok.
|
||||
|
||||
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
|
||||
warn(file_format(Line, File), Warning).
|
||||
|
||||
-spec warn(iolist(), iolist()) -> ok.
|
||||
-spec warn(unicode:chardata(), unicode:chardata()) -> ok.
|
||||
|
||||
warn(Caller, Warning) ->
|
||||
do_warn([Caller, "warning: ", Warning, $\n]).
|
||||
@@ -31,8 +31,8 @@ form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
|
||||
|
||||
%% Compilation error.
|
||||
|
||||
-spec compile_error(list(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
|
||||
-spec compile_error(list(), binary(), unicode:charlist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), string(), list()) -> no_return().
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
@@ -241,4 +241,4 @@ do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_b
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
@@ -66,6 +66,8 @@ expand({'__aliases__', _, _} = Alias, E) ->
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
{Receiver, EA};
|
||||
false ->
|
||||
elixir_errors:warn(?m(E, line), ?m(E, file), io_lib:format("an alias must expand to an atom "
|
||||
"at compilation time but got ~ts", ['Elixir.Macro':to_string(hd(EAliases))])),
|
||||
{{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA}
|
||||
end
|
||||
end;
|
||||
|
||||
@@ -101,11 +101,11 @@ load_struct(Meta, Name, S) ->
|
||||
error:undef ->
|
||||
Inspected = elixir_aliases:inspect(Name),
|
||||
|
||||
case Context of
|
||||
case Context andalso (S#elixir_scope.function == nil) of
|
||||
true ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot access struct ~ts in the same context that defines it as "
|
||||
"the struct fields are not yet accessible", [Inspected]);
|
||||
"cannot access struct ~ts, the struct was not yet defined or the struct is being "
|
||||
"accessed in the same context that defines it", [Inspected]);
|
||||
false ->
|
||||
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
|
||||
"cannot expand struct ~ts", [Inspected, Inspected])
|
||||
|
||||
@@ -7,7 +7,7 @@ Nonterminals
|
||||
add_op_eol mult_op_eol hat_op_eol two_op_eol pipe_op_eol stab_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
|
||||
type_op_eol rel_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_paren close_paren empty_paren eoe
|
||||
list list_args open_bracket close_bracket
|
||||
tuple open_curly close_curly
|
||||
bit_string open_bit close_bit
|
||||
@@ -16,11 +16,11 @@ Nonterminals
|
||||
container_args_base container_args call_args_parens_base call_args_parens parens_call
|
||||
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
|
||||
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
|
||||
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
|
||||
stab stab_eoe stab_expr stab_maybe_expr stab_parens_many
|
||||
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
|
||||
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_bracket_identifier
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list
|
||||
.
|
||||
|
||||
Terminals
|
||||
@@ -32,13 +32,13 @@ Terminals
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
|
||||
type_op dual_op add_op mult_op hat_op two_op pipe_op stab_op when_op assoc_op
|
||||
capture_op rel_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.'
|
||||
'true' 'false' 'nil' 'do' eol ';' ',' '.'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
|
||||
.
|
||||
|
||||
Rootsymbol grammar.
|
||||
|
||||
%% There are two shift/reduce conflicts coming from call_args_parens.
|
||||
%% Two shift/reduce conflicts coming from call_args_parens.
|
||||
Expect 2.
|
||||
|
||||
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
|
||||
@@ -58,12 +58,12 @@ Left 130 or_op_eol. %% ||, |||, or
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% ==, !=, =~, ===, !==
|
||||
Left 160 rel_op_eol. %% <, >, <=, >=
|
||||
Left 170 arrow_op_eol. %% < (op), (op) > (e.g |>, <<<, >>>)
|
||||
Left 170 arrow_op_eol. %% < (op), (op) > (|>, <<<, >>>, ~>>, <<~, ~>, <~, <~>, <|>)
|
||||
Left 180 in_op_eol. %% in
|
||||
Right 200 two_op_eol. %% ++, --, .., <>
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Left 250 hat_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Left 250 hat_op_eol. %% ^ (op) (^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op. %% .
|
||||
@@ -72,16 +72,16 @@ Nonassoc 330 dot_identifier.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
|
||||
grammar -> eol : nil.
|
||||
grammar -> eoe : nil.
|
||||
grammar -> expr_list : to_block('$1').
|
||||
grammar -> eol expr_list : to_block('$2').
|
||||
grammar -> expr_list eol : to_block('$1').
|
||||
grammar -> eol expr_list eol : to_block('$2').
|
||||
grammar -> eoe expr_list : to_block('$2').
|
||||
grammar -> expr_list eoe : to_block('$1').
|
||||
grammar -> eoe expr_list eoe : to_block('$2').
|
||||
grammar -> '$empty' : nil.
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
expr_list -> expr_list eol expr : ['$3'|'$1'].
|
||||
expr_list -> expr_list eoe expr : ['$3'|'$1'].
|
||||
|
||||
expr -> empty_paren : nil.
|
||||
expr -> matched_expr : '$1'.
|
||||
@@ -202,8 +202,12 @@ access_expr -> bracket_expr : '$1'.
|
||||
access_expr -> at_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> fn_eoe stab end_eoe : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> open_paren stab ';' close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> open_paren ';' stab ';' close_paren : build_stab(reverse('$3')).
|
||||
access_expr -> open_paren ';' stab close_paren : build_stab(reverse('$3')).
|
||||
access_expr -> open_paren ';' close_paren : build_stab([]).
|
||||
access_expr -> number : ?exprs('$1').
|
||||
access_expr -> signed_number : {element(4, '$1'), meta('$1'), ?exprs('$1')}.
|
||||
access_expr -> list : element(1, '$1').
|
||||
@@ -240,28 +244,32 @@ bracket_at_expr -> at_op_eol access_expr bracket_arg :
|
||||
|
||||
%% Blocks
|
||||
|
||||
do_block -> do_eol 'end' : [[{do,nil}]].
|
||||
do_block -> do_eol stab end_eol : [[{do, build_stab(reverse('$2'))}]].
|
||||
do_block -> do_eol block_list 'end' : [[{do, nil}|'$2']].
|
||||
do_block -> do_eol stab_eol block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
|
||||
do_block -> do_eoe 'end' : [[{do,nil}]].
|
||||
do_block -> do_eoe stab end_eoe : [[{do, build_stab(reverse('$2'))}]].
|
||||
do_block -> do_eoe block_list 'end' : [[{do, nil}|'$2']].
|
||||
do_block -> do_eoe stab_eoe block_list 'end' : [[{do, build_stab(reverse('$2'))}|'$3']].
|
||||
|
||||
fn_eol -> 'fn' : '$1'.
|
||||
fn_eol -> 'fn' eol : '$1'.
|
||||
eoe -> eol : '$1'.
|
||||
eoe -> ';' : '$1'.
|
||||
eoe -> eol ';' : '$1'.
|
||||
|
||||
do_eol -> 'do' : '$1'.
|
||||
do_eol -> 'do' eol : '$1'.
|
||||
fn_eoe -> 'fn' : '$1'.
|
||||
fn_eoe -> 'fn' eoe : '$1'.
|
||||
|
||||
end_eol -> 'end' : '$1'.
|
||||
end_eol -> eol 'end' : '$2'.
|
||||
do_eoe -> 'do' : '$1'.
|
||||
do_eoe -> 'do' eoe : '$1'.
|
||||
|
||||
block_eol -> block_identifier : '$1'.
|
||||
block_eol -> block_identifier eol : '$1'.
|
||||
end_eoe -> 'end' : '$1'.
|
||||
end_eoe -> eoe 'end' : '$2'.
|
||||
|
||||
block_eoe -> block_identifier : '$1'.
|
||||
block_eoe -> block_identifier eoe : '$1'.
|
||||
|
||||
stab -> stab_expr : ['$1'].
|
||||
stab -> stab eol stab_expr : ['$3'|'$1'].
|
||||
stab -> stab eoe stab_expr : ['$3'|'$1'].
|
||||
|
||||
stab_eol -> stab : '$1'.
|
||||
stab_eol -> stab eol : '$1'.
|
||||
stab_eoe -> stab : '$1'.
|
||||
stab_eoe -> stab eoe : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
|
||||
@@ -275,8 +283,8 @@ stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
|
||||
block_item -> block_eol stab_eol : {?exprs('$1'), build_stab(reverse('$2'))}.
|
||||
block_item -> block_eol : {?exprs('$1'), nil}.
|
||||
block_item -> block_eoe stab_eoe : {?exprs('$1'), build_stab(reverse('$2'))}.
|
||||
block_item -> block_eoe : {?exprs('$1'), nil}.
|
||||
|
||||
block_list -> block_item : ['$1'].
|
||||
block_list -> block_item block_list : ['$1'|'$2'].
|
||||
|
||||
@@ -151,6 +151,10 @@ escape(Expr, Unquote) ->
|
||||
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
quote({unquote_splicing, _, [_]}, _Binding, #elixir_quote{unquote=true}, _) ->
|
||||
argument_error(<<"unquote_splicing only works inside arguments and block contexts, "
|
||||
"wrap it in parens if you want it to work with one-liners">>);
|
||||
|
||||
quote(Expr, nil, Q, E) ->
|
||||
do_quote(Expr, Q, E);
|
||||
|
||||
|
||||
@@ -33,10 +33,16 @@
|
||||
|
||||
-define(arrow_op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>).
|
||||
T1 == $>, T2 == $>, T3 == $>;
|
||||
T1 == $~, T2 == $>, T3 == $>;
|
||||
T1 == $<, T2 == $<, T3 == $~;
|
||||
T1 == $<, T2 == $~, T3 == $>;
|
||||
T1 == $<, T2 == $|, T3 == $>).
|
||||
|
||||
-define(arrow_op(T1, T2),
|
||||
T1 == $|, T2 == $>).
|
||||
T1 == $|, T2 == $>;
|
||||
T1 == $~, T2 == $>;
|
||||
T1 == $<, T2 == $~).
|
||||
|
||||
-define(rel_op(T),
|
||||
T == $<;
|
||||
@@ -299,10 +305,19 @@ tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
% End of line
|
||||
|
||||
tokenize(";" ++ Rest, Line, Scope, []) ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, ';', []));
|
||||
tokenize(Rest, Line, Scope, [{';', Line}]);
|
||||
|
||||
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, ';', [Top|Tokens]));
|
||||
tokenize(";" ++ Rest, Line, Scope, [Top|_] = Tokens) when element(1, Top) /= ';' ->
|
||||
tokenize(Rest, Line, Scope, [{';', Line}|Tokens]);
|
||||
|
||||
tokenize("\\" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\n" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\r\n" = Original, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid escape \\ at end of file", []}, Original, Tokens};
|
||||
|
||||
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
@@ -311,10 +326,10 @@ tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
|
||||
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
|
||||
|
||||
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, newline, Tokens));
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, Tokens));
|
||||
|
||||
% Stand-alone tokens
|
||||
|
||||
@@ -458,6 +473,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
handle_space_sensitive_tokens(strip_horizontal_space(Rest), Line, Scope, Tokens);
|
||||
tokenize([T|Rest] = Original, Line, _Scope, Tokens) when ?is_invalid_space(T) ->
|
||||
Message = io_lib:format("invalid space character U+~.16B before: ", [T]),
|
||||
{error, {Line, lists:flatten(Message), until_eol(Rest)}, Original, Tokens};
|
||||
|
||||
tokenize(T, Line, _Scope, Tokens) ->
|
||||
{error, {Line, "invalid token: ", until_eol(T)}, T, Tokens}.
|
||||
|
||||
@@ -598,9 +617,10 @@ handle_space_sensitive_tokens(String, Line, Scope, Tokens) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
|
||||
eol(_Line, [{';',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, [{eol,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Tokens) -> [{eol,Line}|Tokens].
|
||||
|
||||
unsafe_to_atom(Part, Line, #elixir_tokenizer{}) when
|
||||
is_binary(Part) andalso size(Part) > 255;
|
||||
@@ -827,7 +847,7 @@ check_call_identifier(_Kind, Line, Atom, [$(|_]) -> {paren_identifier, Line, Ato
|
||||
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> {bracket_identifier, Line, Atom};
|
||||
check_call_identifier(Kind, Line, Atom, _Rest) -> {Kind, Line, Atom}.
|
||||
|
||||
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, [{eol,_}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, T) -> [Left|T].
|
||||
|
||||
%% Error handling
|
||||
|
||||
@@ -381,8 +381,6 @@ defmodule DictTest.Common do
|
||||
dict = new_dict([{1}, {2}, {3}], fn {x} -> {<<x + 64>>, x} end)
|
||||
assert Dict.size(dict) == 3
|
||||
assert Enum.sort(dict) == [{"A", 1}, {"B", 2}, {"C", 3}]
|
||||
|
||||
assert Collectable.empty(new_dict) == new_dict([])
|
||||
end
|
||||
|
||||
test "is zippable" do
|
||||
|
||||
@@ -363,11 +363,6 @@ defmodule EnumTest.List do
|
||||
assert Enum.to_list(1 .. 3) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :traverse do
|
||||
assert Enum.traverse([1, 2, 3], &(&1 * &1)) == [1, 4, 9]
|
||||
assert Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v*2} end) == %{a: 2, b: 4}
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq([1, 2, 3, 2, 1]) == [1, 2, 3]
|
||||
assert Enum.uniq([1, 2, 3, 2, 1], fn x -> x end) == [1, 2, 3]
|
||||
|
||||
@@ -308,8 +308,10 @@ defmodule Kernel.ExceptionTest do
|
||||
|
||||
test "undefined function message" do
|
||||
assert %UndefinedFunctionError{} |> message == "undefined function"
|
||||
assert %UndefinedFunctionError{module: Kernel, function: :bar, arity: 1} |> message ==
|
||||
"undefined function: Kernel.bar/1"
|
||||
assert %UndefinedFunctionError{module: Foo, function: :bar, arity: 1} |> message ==
|
||||
"undefined function: Foo.bar/1"
|
||||
"undefined function: Foo.bar/1 (module Foo is not available)"
|
||||
assert %UndefinedFunctionError{module: nil, function: :bar, arity: 0} |> message ==
|
||||
"undefined function: nil.bar/0"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenEvent.StreamTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defmodule SlowHandler do
|
||||
use GenEvent
|
||||
|
||||
def handle_event(_event, _state) do
|
||||
:timer.sleep(100)
|
||||
:remove_handler
|
||||
end
|
||||
end
|
||||
|
||||
defp notify(:async, manager, event), do: GenEvent.notify(manager, event)
|
||||
defp notify(:sync, manager, event), do: GenEvent.sync_notify(manager, event)
|
||||
defp notify(:ack, manager, event), do: GenEvent.ack_notify(manager, event)
|
||||
|
||||
@receive_timeout 1000
|
||||
|
||||
test "ack stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.ack_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
spawn_link fn ->
|
||||
GenEvent.ack_notify(pid, i)
|
||||
end
|
||||
end
|
||||
|
||||
# Note the length is 4 compared to 5 n the sync case.
|
||||
wait_for_queue_length(pid, 4)
|
||||
end
|
||||
|
||||
test "sync stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
spawn_link fn ->
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_queue_length(pid, 5)
|
||||
end
|
||||
|
||||
test "async stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 1)
|
||||
end
|
||||
|
||||
test "stream/2 with timeout" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
child = spawn_link fn ->
|
||||
Enum.take(GenEvent.stream(pid, timeout: 50), 5)
|
||||
end
|
||||
|
||||
assert_receive {:EXIT, ^child,
|
||||
{:timeout, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "stream/2 with error/timeout on subscription" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
child = spawn fn -> Enum.to_list(GenEvent.stream(pid)) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.exit(child, :kill)
|
||||
wait_for_handlers(pid, 0)
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "stream/2 with manager killed and trap_exit" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
Process.exit(pid, :kill)
|
||||
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
|
||||
assert_receive ({:EXIT, ^child, {stat, {Enumerable.GenEvent.Stream, :stop, [_,_]}}}
|
||||
when stat in [:killed, :noproc]), @receive_timeout
|
||||
end
|
||||
|
||||
test "stream/2 with manager not alive" do
|
||||
stream = GenEvent.stream(:does_not_exit)
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
assert_receive {:EXIT, ^child,
|
||||
{:noproc, {Enumerable.GenEvent.Stream, :start, [_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
Enum.each [:sync, :async, :ack], fn mode ->
|
||||
@mode mode
|
||||
|
||||
test "#{mode} stream/2 with parallel use (and first finishing first)" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:take3, Enum.take(stream, 3)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
spawn_link fn -> send parent, {:take5, Enum.take(stream, 5)} end
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
# Notify the events for both handlers
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
assert_receive {:take3, [1, 2, 3]}, @receive_timeout
|
||||
|
||||
# Notify the events for to_list stream handler
|
||||
for i <- 4..5 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
assert_receive {:take5, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager stop" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
parent = self()
|
||||
child = spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid), 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:EXIT, ^child, :normal}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with swap_handler" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref())
|
||||
|
||||
parent = self()
|
||||
spawn_link(fn -> send parent, Enum.take(stream, 5) end)
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
[handler] = GenEvent.which_handlers(pid)
|
||||
GenEvent.swap_handler(pid, handler, :swap_handler, UnknownHandler, [])
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager unregistered" do
|
||||
{:ok, pid} = GenEvent.start_link(name: :unreg)
|
||||
stream = GenEvent.stream(:unreg)
|
||||
|
||||
parent = self()
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(stream, 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
# Unregister the process
|
||||
Process.unregister(:unreg)
|
||||
|
||||
# Notify the remaining events
|
||||
for i <- 4..5 do
|
||||
notify(@mode, pid, i)
|
||||
end
|
||||
|
||||
# We should have gotten the message and all handlers were removed
|
||||
assert_receive [1, 2, 3, 4, 5], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 flushes events on abort" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
spawn_link fn ->
|
||||
wait_for_handlers(pid, 2)
|
||||
notify(@mode, pid, 1)
|
||||
notify(@mode, pid, 2)
|
||||
notify(@mode, pid, 3)
|
||||
end
|
||||
|
||||
GenEvent.add_handler(pid, SlowHandler, [])
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
try do
|
||||
Enum.each stream, fn _ -> throw :done end
|
||||
catch
|
||||
:done -> :ok
|
||||
end
|
||||
|
||||
# Wait for the slow handler to be removed
|
||||
# so all events have been handled
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# Check no messages leaked.
|
||||
refute_received _any
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_handlers(pid, count) do
|
||||
unless length(GenEvent.which_handlers(pid)) == count do
|
||||
wait_for_handlers(pid, count)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_queue_length(pid, count) do
|
||||
{:message_queue_len, n} = Process.info(pid, :message_queue_len)
|
||||
unless n == count do
|
||||
wait_for_queue_length(pid, count)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,438 +1,529 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenEventTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case
|
||||
|
||||
defmodule LoggerHandler do
|
||||
defmodule ReplyHandler do
|
||||
use GenEvent
|
||||
|
||||
def handle_event({:log, x}, messages) do
|
||||
{:ok, [x|messages]}
|
||||
def init(:raise) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_call(:messages, messages) do
|
||||
{:ok, Enum.reverse(messages), []}
|
||||
def init({:throw, process}) do
|
||||
{:ok, process}
|
||||
end
|
||||
|
||||
def handle_call(call, state) do
|
||||
super(call, state)
|
||||
def init({:raise, _}) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def init({:swap, {:error, :not_found}}) do
|
||||
{:error, :not_found_on_swap}
|
||||
end
|
||||
|
||||
def init({:swap, parent}) when is_pid(parent) do
|
||||
send parent, :swapped
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def init({:custom, return}) do
|
||||
return
|
||||
end
|
||||
|
||||
def init({parent, :hibernate}) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def init({parent, trap}) when is_pid(parent) and is_boolean(trap) do
|
||||
Process.flag(:trap_exit, trap)
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def handle_event(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_event(:hibernate, parent) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_event({:custom, reply}, _parent) do
|
||||
reply
|
||||
end
|
||||
|
||||
def handle_event(event, parent) do
|
||||
send parent, {:event, event}
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def handle_call(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def handle_call(:hibernate, parent) do
|
||||
{:ok, :ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_call({:custom, reply}, _parent) do
|
||||
reply
|
||||
end
|
||||
|
||||
def handle_call(event, parent) do
|
||||
send parent, {:call, event}
|
||||
{:ok, :ok, parent}
|
||||
end
|
||||
|
||||
def handle_info(:hibernate, parent) do
|
||||
{:ok, parent, :hibernate}
|
||||
end
|
||||
|
||||
def handle_info(event, parent) do
|
||||
send parent, {:info, event}
|
||||
{:ok, parent}
|
||||
end
|
||||
|
||||
def terminate(:raise, _parent) do
|
||||
raise "oops"
|
||||
end
|
||||
|
||||
def terminate(:swapped, parent) do
|
||||
send parent, {:terminate, :swapped}
|
||||
parent
|
||||
end
|
||||
|
||||
def terminate(arg, parent) do
|
||||
send parent, {:terminate, arg}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule SlowHandler do
|
||||
defmodule DefaultHandler do
|
||||
use GenEvent
|
||||
end
|
||||
|
||||
def handle_event(_event, _state) do
|
||||
:timer.sleep(100)
|
||||
:remove_handler
|
||||
defmodule Via do
|
||||
def register_name(name, pid) do
|
||||
Process.register(pid, name)
|
||||
:yes
|
||||
end
|
||||
|
||||
def whereis_name(name) do
|
||||
Process.whereis(name) || :undefined
|
||||
end
|
||||
end
|
||||
|
||||
@receive_timeout 1000
|
||||
|
||||
test "start_link/2 and handler workflow" do
|
||||
test "start/1" do
|
||||
assert {:ok, pid} = GenEvent.start()
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start(name: :my_gen_event_name)
|
||||
assert GenEvent.which_handlers(:my_gen_event_name) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(:my_gen_event_name) == :ok
|
||||
end
|
||||
|
||||
test "start_link/1" do
|
||||
assert {:ok, pid} = GenEvent.start_link()
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
|
||||
assert GenEvent.which_handlers(:my_gen_event_name) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(:my_gen_event_name) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: {:global, :my_gen_event_name})
|
||||
assert GenEvent.which_handlers({:global, :my_gen_event_name}) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop({:global, :my_gen_event_name}) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: {:via, Via, :my_gen_event_name})
|
||||
assert GenEvent.which_handlers({:via, Via, :my_gen_event_name}) == []
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop({:via, Via, :my_gen_event_name}) == :ok
|
||||
|
||||
assert {:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
|
||||
assert GenEvent.start_link(name: :my_gen_event_name) ==
|
||||
{:error, {:already_started, pid}}
|
||||
end
|
||||
|
||||
test "handles exit signals" do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
# Terminates on signal from parent when not trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
Process.exit(pid, :shutdown)
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
refute_received {:terminate, _}
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
# Terminates on signal from parent when trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), true})
|
||||
Process.exit(pid, :shutdown)
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
assert_receive {:terminate, :stop}
|
||||
|
||||
assert GenEvent.notify(pid, {:log, 0}) == :ok
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
|
||||
assert GenEvent.notify(pid, {:log, 1}) == :ok
|
||||
assert GenEvent.notify(pid, {:log, 2}) == :ok
|
||||
# Terminates on signal not from parent when not trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
spawn fn -> Process.exit(pid, :shutdown) end
|
||||
assert_receive {:EXIT, ^pid, :shutdown}
|
||||
refute_received {:terminate, _}
|
||||
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == [1, 2]
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == []
|
||||
|
||||
assert GenEvent.remove_handler(pid, LoggerHandler, []) == :ok
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
# Does not terminate on signal not from parent when trapping exits
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), true})
|
||||
terminator = spawn fn -> Process.exit(pid, :shutdown) end
|
||||
assert_receive {:info, {:EXIT, ^terminator, :shutdown}}
|
||||
refute_received {:terminate, _}
|
||||
end
|
||||
|
||||
test "start/2 with linked handler" do
|
||||
test "hibernates" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self(), :hibernate})
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
wake_up(pid)
|
||||
refute hibernating?(pid)
|
||||
|
||||
:ok = GenEvent.call(pid, ReplyHandler, :hibernate)
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
wake_up(pid)
|
||||
:ok = GenEvent.sync_notify(pid, :hibernate)
|
||||
wait_until fn -> hibernating?(pid) end
|
||||
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "add_handler/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, {:error, :my_error}}) ==
|
||||
{:error, :my_error}
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:custom, :oops}) ==
|
||||
{:error, {:bad_return_value, :oops}}
|
||||
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, [], link: true) == :ok
|
||||
assert {:error, {%RuntimeError{}, _}} =
|
||||
GenEvent.add_handler(pid, ReplyHandler, :raise)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [ReplyHandler]
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {:throw, self()}) == {:error, :already_present}
|
||||
|
||||
assert GenEvent.notify(pid, {:log, 1}) == :ok
|
||||
assert GenEvent.sync_notify(pid, {:log, 2}) == :ok
|
||||
|
||||
assert GenEvent.call(pid, LoggerHandler, :messages) == [1, 2]
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
|
||||
end
|
||||
|
||||
test "start/2 with linked swap" do
|
||||
test "add_mon_handler/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
parent = self()
|
||||
|
||||
{mon_pid, mon_ref} = spawn_monitor(fn ->
|
||||
assert GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false}) == :ok
|
||||
send parent, :ok
|
||||
receive after: (:infinity -> :ok)
|
||||
end)
|
||||
|
||||
assert_receive :ok
|
||||
assert GenEvent.add_handler(pid, {ReplyHandler, self()}, {self(), false}) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}, ReplyHandler]
|
||||
|
||||
# A regular monitor message is passed forward
|
||||
send pid, {:DOWN, make_ref(), :process, self(), :oops}
|
||||
assert_receive {:info, {:DOWN, _, :process, _, :oops}}
|
||||
|
||||
# Killing the monitor though is not passed forward
|
||||
Process.exit(mon_pid, :oops)
|
||||
assert_receive {:DOWN, ^mon_ref, :process, ^mon_pid, :oops}
|
||||
refute_received {:info, {:DOWN, _, :process, _, :oops}}
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
|
||||
end
|
||||
|
||||
test "add_mon_handler/3 with notifications" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
self = self()
|
||||
|
||||
GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
|
||||
GenEvent.remove_handler(pid, ReplyHandler, :ok)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, :normal}
|
||||
|
||||
:ok = GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
|
||||
:ok = GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, nil}}
|
||||
|
||||
:ok = GenEvent.swap_mon_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
|
||||
:ok = GenEvent.swap_mon_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, {:swapped, ReplyHandler, ^self}}
|
||||
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:gen_event_EXIT, ReplyHandler, :shutdown}
|
||||
end
|
||||
|
||||
test "remove_handler/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
|
||||
GenEvent.add_mon_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :ok) ==
|
||||
{:error, :not_found}
|
||||
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
|
||||
{:terminate, :ok}
|
||||
assert_receive {:terminate, :ok}
|
||||
|
||||
assert GenEvent.swap_handler(pid, LoggerHandler, [], LoggerHandler, [], link: true) == :ok
|
||||
GenEvent.add_mon_handler(pid, {ReplyHandler, self()}, {self(), false})
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
assert GenEvent.remove_handler(pid, ReplyHandler, :ok) ==
|
||||
{:error, :not_found}
|
||||
assert {:error, {%RuntimeError{}, _}} =
|
||||
GenEvent.remove_handler(pid, {ReplyHandler, self()}, :raise)
|
||||
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
end
|
||||
|
||||
test "start/2 with registered name" do
|
||||
{:ok, _} = GenEvent.start(name: :logger)
|
||||
assert GenEvent.stop(:logger) == :ok
|
||||
test "swap_handler/5" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
|
||||
{ReplyHandler, self()}, :swap) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.add_handler(pid, ReplyHandler, {self(), false}) == :ok
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped,
|
||||
{ReplyHandler, self()}, :swap) == {:error, :already_present}
|
||||
assert GenEvent.which_handlers(pid) == [{ReplyHandler, self()}]
|
||||
|
||||
assert GenEvent.remove_handler(pid, {ReplyHandler, self()}, :remove_handler) ==
|
||||
{:terminate, :remove_handler}
|
||||
|
||||
# The handler is initialized even when the module does not exist
|
||||
# on swap. However, in this case, we are returning an error on init.
|
||||
assert GenEvent.swap_handler(pid, ReplyHandler, :swapped, ReplyHandler, :swap) ==
|
||||
{:error, :not_found_on_swap}
|
||||
end
|
||||
|
||||
test "bad calls" do
|
||||
test "notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.notify(pid, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
|
||||
assert GenEvent.notify(pid, msg) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.notify(pid, {:custom, :oops}) == :ok
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.notify(pid, :raise) == :ok
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "notify/2 with bad args" do
|
||||
assert GenEvent.notify({:global, :foo}, :bar) == :ok
|
||||
assert GenEvent.notify({:foo, :bar}, :bar) == :ok
|
||||
assert GenEvent.notify(self, :bar) == :ok
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
GenEvent.notify(:foo, :bar)
|
||||
end
|
||||
end
|
||||
|
||||
test "ack_notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.ack_notify(pid, :hello) == :ok
|
||||
assert_receive {:event, :hello}
|
||||
|
||||
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
|
||||
assert GenEvent.ack_notify(pid, msg) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.ack_notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.ack_notify(pid, {:custom, :oops}) == :ok
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.ack_notify(pid, :raise) == :ok
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "sync_notify/2" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.sync_notify(pid, :hello) == :ok
|
||||
assert_received {:event, :hello}
|
||||
|
||||
msg = {:custom, {:swap_handler, :swapped, self(), ReplyHandler, :swap}}
|
||||
assert GenEvent.sync_notify(pid, msg) == :ok
|
||||
assert_received {:terminate, :swapped}
|
||||
assert_received :swapped
|
||||
|
||||
assert GenEvent.sync_notify(pid, {:custom, :remove_handler}) == :ok
|
||||
assert_received {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.sync_notify(pid, {:custom, :oops}) == :ok
|
||||
assert_received {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert GenEvent.sync_notify(pid, :raise) == :ok
|
||||
assert_received {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "call/3" do
|
||||
{:ok, pid} = GenEvent.start()
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
|
||||
assert GenEvent.call(pid, ReplyHandler, :hello) == :ok
|
||||
assert_receive {:call, :hello}
|
||||
|
||||
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :swap}}
|
||||
assert GenEvent.call(pid, ReplyHandler, msg) == :ok
|
||||
assert_receive {:terminate, :swapped}
|
||||
assert_receive :swapped
|
||||
|
||||
assert GenEvent.call(pid, ReplyHandler, {:custom, {:remove_handler, :ok}}) == :ok
|
||||
assert_receive {:terminate, :remove_handler}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :raise}}
|
||||
assert GenEvent.call(pid, ReplyHandler, msg) == :ok
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
Logger.remove_backend(:console)
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
GenEvent.add_handler(pid, {ReplyHandler, self}, {self(), false})
|
||||
|
||||
msg = {:custom, {:swap_handler, :ok, :swapped, self(), ReplyHandler, :raise}}
|
||||
assert GenEvent.call(pid, {ReplyHandler, self()}, msg) == :ok
|
||||
assert GenEvent.which_handlers(pid) == [ReplyHandler]
|
||||
|
||||
assert {:error, {:bad_return_value, :oops}} =
|
||||
GenEvent.call(pid, ReplyHandler, {:custom, :oops})
|
||||
assert_receive {:terminate, {:error, {:bad_return_value, :oops}}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
|
||||
GenEvent.add_handler(pid, ReplyHandler, {self(), false})
|
||||
assert {:error, {%RuntimeError{}, _}} = GenEvent.call(pid, ReplyHandler, :raise)
|
||||
assert_receive {:terminate, {:error, {%RuntimeError{}, _}}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "call/2 with bad args" do
|
||||
Logger.remove_backend(:console)
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
assert GenEvent.add_handler(pid, LoggerHandler, []) == :ok
|
||||
|
||||
assert GenEvent.add_handler(pid, DefaultHandler, []) == :ok
|
||||
assert GenEvent.call(pid, UnknownHandler, :messages) ==
|
||||
{:error, :bad_module}
|
||||
assert GenEvent.call(pid, LoggerHandler, :whatever) ==
|
||||
{:error, {:EXIT, {:bad_call, :whatever}}}
|
||||
{:error, :not_found}
|
||||
assert GenEvent.call(pid, DefaultHandler, :whatever) ==
|
||||
{:error, {:bad_call, :whatever}}
|
||||
assert GenEvent.which_handlers(pid) == []
|
||||
assert GenEvent.stop(pid) == :ok
|
||||
after
|
||||
Logger.add_backend(:console, flush: true)
|
||||
end
|
||||
|
||||
test "ack stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
test ":sys.get_status/2" do
|
||||
{:ok, pid} = GenEvent.start(name: :my_gen_event_name)
|
||||
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self(), true})
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: :ack), 3)
|
||||
end
|
||||
self = self()
|
||||
status = :sys.get_status(pid)
|
||||
GenEvent.stop(pid)
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Receive one of the results
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid, mode: :ack), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
GenEvent.notify(pid, i)
|
||||
end
|
||||
|
||||
# The length is 4 instead of 5 because we wait for
|
||||
# message acks and not consumptions acks.
|
||||
wait_for_queue_length(pid, 4)
|
||||
assert {:status, ^pid, {:module, GenEvent},
|
||||
[pdict, _, ^pid, [], data]} = status
|
||||
assert pdict[:"$ancestors"] == [self()]
|
||||
assert pdict[:"$initial_call"] == {GenEvent, :init_it, 6}
|
||||
assert {'Installed handlers', [
|
||||
{:handler, ReplyHandler, {ReplyHandler, :ok}, ^self, nil, nil}]} = data[:items]
|
||||
end
|
||||
|
||||
test "sync stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
test ":sys.get_state/1 and :sys.replace_state/2" do
|
||||
{:ok, pid} = GenEvent.start_link(name: :my_gen_event_name)
|
||||
self = self()
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: :sync), 3)
|
||||
end
|
||||
assert [] = :sys.get_state(pid)
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
:ok = GenEvent.add_handler(pid, ReplyHandler, {self, true})
|
||||
assert [{ReplyHandler, ReplyHandler, ^self}] = :sys.get_state(pid)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
replacer = fn {ReplyHandler, ReplyHandler, _} -> {ReplyHandler, ReplyHandler, :unknown} end
|
||||
:sys.replace_state(pid, replacer)
|
||||
assert [{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
|
||||
|
||||
# Receive one of the results
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
# Fail while replacing does not cause a crash
|
||||
:sys.replace_state(pid, fn _ -> exit(:fail) end)
|
||||
assert [{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid, mode: :sync), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
:ok = GenEvent.add_handler(pid, {ReplyHandler, :ok}, {self, true})
|
||||
assert [{ReplyHandler, {ReplyHandler, :ok}, ^self},
|
||||
{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
|
||||
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
for i <- 1..6 do
|
||||
GenEvent.notify(pid, i)
|
||||
end
|
||||
|
||||
wait_for_queue_length(pid, 5)
|
||||
:ok = :sys.suspend(pid)
|
||||
assert [{ReplyHandler, {ReplyHandler, :ok}, ^self},
|
||||
{ReplyHandler, ReplyHandler, :unknown}] = :sys.get_state(pid)
|
||||
:ok = :sys.resume(pid)
|
||||
end
|
||||
|
||||
test "async stream/2" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
parent = self()
|
||||
|
||||
spawn_link fn ->
|
||||
Enum.each(GenEvent.stream(pid, mode: :async), fn _ ->
|
||||
:timer.sleep(:infinity)
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: :async), 3)
|
||||
end
|
||||
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Receive one of the results
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
|
||||
# One of the subscriptions are gone
|
||||
wait_for_handlers(pid, 1)
|
||||
defp hibernating?(pid) do
|
||||
Process.info(pid, :current_function) ==
|
||||
{:current_function,{:erlang,:hibernate,3}}
|
||||
end
|
||||
|
||||
Enum.each [:sync, :async], fn mode ->
|
||||
test "#{mode} stream/2 with parallel use (and first finishing first)" do
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, duration: 200, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:take, Enum.take(stream, 3)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
spawn_link fn -> send parent, {:to_list, Enum.to_list(stream)} end
|
||||
wait_for_handlers(pid, 2)
|
||||
|
||||
# Notify the events for both handlers
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
assert_receive {:take, [1, 2, 3]}, @receive_timeout
|
||||
|
||||
# Notify the events for to_list stream handler
|
||||
for i <- 4..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
assert_receive {:to_list, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with timeout" do
|
||||
# Start a manager
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
pid = spawn_link fn ->
|
||||
Enum.take(GenEvent.stream(pid, timeout: 50, mode: unquote(mode)), 5)
|
||||
end
|
||||
|
||||
assert_receive {:EXIT, ^pid,
|
||||
{:timeout, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with error/timeout on subscription" do
|
||||
# Start a manager
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
# Start a subscriber with timeout
|
||||
child = spawn fn -> Enum.to_list(GenEvent.stream(pid, mode: unquote(mode))) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Kill and wait until we have 0 handlers
|
||||
Process.exit(child, :kill)
|
||||
wait_for_handlers(pid, 0)
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager stop" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.take(GenEvent.stream(pid, mode: unquote(mode)), 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.stop(pid)
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:shutdown, {Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with cancel streams" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref(), mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, Enum.take(stream, 5) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
GenEvent.cancel_streams(stream)
|
||||
assert_receive [1, 2, 3], @receive_timeout
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with swap_handler" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, id: make_ref(), mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn -> send parent, Enum.take(stream, 5) end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
[handler] = GenEvent.which_handlers(pid)
|
||||
Process.flag(:trap_exit, true)
|
||||
GenEvent.swap_handler(pid, handler, :swap_handler, LogHandler, [])
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{{:swapped, LogHandler, _},
|
||||
{Enumerable.GenEvent.Stream, :next, [_, _]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with duration" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, duration: 200, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn -> send parent, {:duration, Enum.take(stream, 10)} end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Wait until the handler is gone
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# The stream is not complete but terminated anyway due to duration
|
||||
assert_receive {:duration, [1, 2, 3, 4, 5]}, @receive_timeout
|
||||
|
||||
GenEvent.stop(pid)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager killed and trap_exit" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
stream = GenEvent.stream(pid, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
Process.exit(pid, :kill)
|
||||
assert_receive {:EXIT, ^pid, :killed}, @receive_timeout
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:killed, {Enumerable.GenEvent.Stream, :next, [_,_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager not alive" do
|
||||
# Start a manager and subscribers
|
||||
stream = GenEvent.stream(:does_not_exit, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
stream_pid = spawn_link fn ->
|
||||
send parent, Enum.to_list(stream)
|
||||
end
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
assert_receive {:EXIT, ^stream_pid,
|
||||
{:noproc, {Enumerable.GenEvent.Stream, :start, [_]}}}, @receive_timeout
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 with manager unregistered" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link(name: :unreg)
|
||||
stream = GenEvent.stream(:unreg, mode: unquote(mode))
|
||||
|
||||
parent = self()
|
||||
spawn_link fn ->
|
||||
send parent, Enum.take(stream, 5)
|
||||
end
|
||||
wait_for_handlers(pid, 1)
|
||||
|
||||
# Notify the events
|
||||
for i <- 1..3 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# Unregister the process
|
||||
Process.unregister(:unreg)
|
||||
|
||||
# Notify the remaining events
|
||||
for i <- 4..5 do
|
||||
GenEvent.sync_notify(pid, i)
|
||||
end
|
||||
|
||||
# We should have gotten the message and all handlers were removed
|
||||
assert_receive [1, 2, 3, 4, 5], @receive_timeout
|
||||
wait_for_handlers(pid, 0)
|
||||
end
|
||||
|
||||
test "#{mode} stream/2 flushes events on abort" do
|
||||
# Start a manager and subscribers
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
spawn_link fn ->
|
||||
wait_for_handlers(pid, 2)
|
||||
GenEvent.notify(pid, 1)
|
||||
GenEvent.notify(pid, 2)
|
||||
GenEvent.notify(pid, 3)
|
||||
end
|
||||
|
||||
GenEvent.add_handler(pid, SlowHandler, [])
|
||||
stream = GenEvent.stream(pid, mode: unquote(mode))
|
||||
|
||||
try do
|
||||
Enum.each stream, fn _ -> throw :done end
|
||||
catch
|
||||
:done -> :ok
|
||||
end
|
||||
|
||||
# Wait for the slow handler to be removed
|
||||
# so all events have been handled
|
||||
wait_for_handlers(pid, 0)
|
||||
|
||||
# Check no messages leaked.
|
||||
refute_received _any
|
||||
defp wait_until(fun, counter \\ 0) do
|
||||
cond do
|
||||
counter > 100 ->
|
||||
flunk "Waited for 1s, but #{inspect fun} never returned true"
|
||||
fun.() ->
|
||||
true
|
||||
true ->
|
||||
receive after: (10 -> wait_until(fun, counter + 1))
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_handlers(pid, count) do
|
||||
unless length(GenEvent.which_handlers(pid)) == count do
|
||||
wait_for_handlers(pid, count)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_queue_length(pid, count) do
|
||||
{:message_queue_len, n} = Process.info(pid, :message_queue_len)
|
||||
unless n == count do
|
||||
wait_for_queue_length(pid, count)
|
||||
end
|
||||
defp wake_up(pid) do
|
||||
send pid, :wake
|
||||
assert_receive {:info, :wake}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,6 +49,12 @@ defmodule GenServerTest do
|
||||
assert GenServer.cast(pid, {:push, :world}) == :ok
|
||||
assert GenServer.call(pid, :pop) == :world
|
||||
assert GenServer.call(pid, :stop) == :ok
|
||||
|
||||
assert GenServer.cast({:global, :foo}, {:push, :world}) == :ok
|
||||
assert GenServer.cast({:via, :foo, :bar}, {:push, :world}) == :ok
|
||||
assert_raise ArgumentError, fn ->
|
||||
GenServer.cast(:foo, {:push, :world})
|
||||
end
|
||||
end
|
||||
|
||||
test "nil name" do
|
||||
|
||||
@@ -41,8 +41,14 @@ defmodule Inspect.AtomTest do
|
||||
|
||||
test :op do
|
||||
assert inspect(:+) == ":+"
|
||||
assert inspect(:<~) == ":<~"
|
||||
assert inspect(:~>) == ":~>"
|
||||
assert inspect(:&&&) == ":&&&"
|
||||
assert inspect(:~~~) == ":~~~"
|
||||
assert inspect(:<<~) == ":<<~"
|
||||
assert inspect(:~>>) == ":~>>"
|
||||
assert inspect(:<~>) == ":<~>"
|
||||
assert inspect(:<|>) == ":<|>"
|
||||
end
|
||||
|
||||
test :... do
|
||||
|
||||
@@ -5,23 +5,23 @@ defmodule IntegerTest do
|
||||
require Integer
|
||||
|
||||
test :odd? do
|
||||
assert Integer.odd?(0) == false
|
||||
assert Integer.odd?(1) == true
|
||||
assert Integer.odd?(2) == false
|
||||
assert Integer.odd?(3) == true
|
||||
assert Integer.odd?(-1) == true
|
||||
assert Integer.odd?(-2) == false
|
||||
assert Integer.odd?(-3) == true
|
||||
assert Integer.is_odd(0) == false
|
||||
assert Integer.is_odd(1) == true
|
||||
assert Integer.is_odd(2) == false
|
||||
assert Integer.is_odd(3) == true
|
||||
assert Integer.is_odd(-1) == true
|
||||
assert Integer.is_odd(-2) == false
|
||||
assert Integer.is_odd(-3) == true
|
||||
end
|
||||
|
||||
test :even? do
|
||||
assert Integer.even?(0) == true
|
||||
assert Integer.even?(1) == false
|
||||
assert Integer.even?(2) == true
|
||||
assert Integer.even?(3) == false
|
||||
assert Integer.even?(-1) == false
|
||||
assert Integer.even?(-2) == true
|
||||
assert Integer.even?(-3) == false
|
||||
assert Integer.is_even(0) == true
|
||||
assert Integer.is_even(1) == false
|
||||
assert Integer.is_even(2) == true
|
||||
assert Integer.is_even(3) == false
|
||||
assert Integer.is_even(-1) == false
|
||||
assert Integer.is_even(-2) == true
|
||||
assert Integer.is_even(-3) == false
|
||||
end
|
||||
|
||||
test :parse do
|
||||
|
||||
@@ -133,7 +133,7 @@ defmodule Kernel.ComprehensionTest do
|
||||
test "for comprehension with into, generators and filters" do
|
||||
Process.put(:into_cont, [])
|
||||
|
||||
for x <- 1..3, Integer.odd?(x), << y <- "hello" >>, into: %PDict{} do
|
||||
for x <- 1..3, Integer.is_odd(x), << y <- "hello" >>, into: %PDict{} do
|
||||
x + y
|
||||
end
|
||||
|
||||
|
||||
@@ -150,6 +150,15 @@ defmodule Kernel.ErrorsTest do
|
||||
def hello(arg), do: arg
|
||||
end
|
||||
'''
|
||||
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: function foo/0 undefined",
|
||||
~C'''
|
||||
defmodule ErrorsTest do
|
||||
def hello(foo, bar \\ foo)
|
||||
def hello(foo, bar), do: foo + bar
|
||||
end
|
||||
'''
|
||||
end
|
||||
|
||||
test :invalid_match_pattern do
|
||||
@@ -232,8 +241,8 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :struct_access_on_body do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:3: cannot access struct TZ in the same context that defines it " <>
|
||||
"as the struct fields are not yet accessible",
|
||||
"nofile:3: cannot access struct TZ, the struct was not yet defined or the struct " <>
|
||||
"is being accessed in the same context that defines it",
|
||||
'''
|
||||
defmodule TZ do
|
||||
defstruct %{name: "Brasilia"}
|
||||
@@ -290,6 +299,23 @@ defmodule Kernel.ErrorsTest do
|
||||
'unquote 1'
|
||||
end
|
||||
|
||||
test :invalid_unquote_splicing_in_oneliners do
|
||||
assert_compile_fail ArgumentError,
|
||||
"unquote_splicing only works inside arguments and block contexts, " <>
|
||||
"wrap it in parens if you want it to work with one-liners",
|
||||
'''
|
||||
defmodule Foo do
|
||||
defmacro oneliner2 do
|
||||
quote do: unquote_splicing 1
|
||||
end
|
||||
|
||||
def callme do
|
||||
oneliner2
|
||||
end
|
||||
end
|
||||
'''
|
||||
end
|
||||
|
||||
test :invalid_quote_args do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: invalid arguments for quote",
|
||||
@@ -340,6 +366,12 @@ defmodule Kernel.ErrorsTest do
|
||||
'fn 1'
|
||||
end
|
||||
|
||||
test :invalid_escape do
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:1: invalid escape \\ at end of file",
|
||||
'1 \\'
|
||||
end
|
||||
|
||||
test :function_local_conflict do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: imported Kernel.&&/2 conflicts with local function",
|
||||
@@ -590,9 +622,47 @@ defmodule Kernel.ErrorsTest do
|
||||
'fn x -> x; x, y -> x + y end'
|
||||
end
|
||||
|
||||
test :end_of_expression do
|
||||
# All valid examples
|
||||
Code.eval_quoted '''
|
||||
1;
|
||||
2;
|
||||
3
|
||||
|
||||
(;)
|
||||
(;1)
|
||||
(1;)
|
||||
(1; 2)
|
||||
|
||||
fn -> 1; 2 end
|
||||
fn -> ; end
|
||||
|
||||
if true do
|
||||
;
|
||||
end
|
||||
|
||||
try do
|
||||
;
|
||||
catch
|
||||
_, _ -> ;
|
||||
after
|
||||
;
|
||||
end
|
||||
'''
|
||||
|
||||
# All invalid examples
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: ';'",
|
||||
'1+;\n2'
|
||||
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:1: syntax error before: ';'",
|
||||
'max(1,;2)'
|
||||
end
|
||||
|
||||
test :new_line_error do
|
||||
assert_compile_fail SyntaxError,
|
||||
"nofile:3: syntax error before: newline",
|
||||
"nofile:3: syntax error before: eol",
|
||||
'if true do\n foo = [],\n baz\nend'
|
||||
end
|
||||
|
||||
|
||||
@@ -55,11 +55,6 @@ defmodule Kernel.ExpansionTest do
|
||||
assert expand_env(quote(do: World), __ENV__) |> elem(0) == :"Elixir.Source"
|
||||
end
|
||||
|
||||
test "__aliases__: expands to elixir_aliases on runtime" do
|
||||
assert expand(quote do: hello.World) ==
|
||||
quote do: :elixir_aliases.concat([hello(), :World])
|
||||
end
|
||||
|
||||
## =
|
||||
|
||||
test "=: sets context to match" do
|
||||
|
||||
@@ -71,7 +71,7 @@ defmodule Kernel.FnTest do
|
||||
|
||||
test "imported partial application" do
|
||||
import Record
|
||||
assert (&record?(&1, :sample)).({:sample, 1})
|
||||
assert (&is_record(&1, :sample)).({:sample, 1})
|
||||
end
|
||||
|
||||
test "remote partial application" do
|
||||
|
||||
@@ -226,7 +226,7 @@ defmodule Kernel.RaiseTest do
|
||||
x in [UndefinedFunctionError] -> Exception.message(x)
|
||||
end
|
||||
|
||||
assert result == "undefined function: DoNotExist.for_sure/0"
|
||||
assert result == "undefined function: DoNotExist.for_sure/0 (module DoNotExist is not available)"
|
||||
end
|
||||
|
||||
test :function_clause_error do
|
||||
@@ -352,7 +352,7 @@ defmodule Kernel.RaiseTest do
|
||||
x in [ErlangError] -> Exception.message(x)
|
||||
end
|
||||
|
||||
assert result == "undefined function: DoNotExist.for_sure/0"
|
||||
assert result == "undefined function: DoNotExist.for_sure/0 (module DoNotExist is not available)"
|
||||
end
|
||||
|
||||
defmacrop exceptions do
|
||||
@@ -366,7 +366,7 @@ defmodule Kernel.RaiseTest do
|
||||
x in exceptions -> Exception.message(x)
|
||||
end
|
||||
|
||||
assert result == "undefined function: DoNotExist.for_sure/0"
|
||||
assert result == "undefined function: DoNotExist.for_sure/0 (module DoNotExist is not available)"
|
||||
end
|
||||
|
||||
defp zero(0), do: 0
|
||||
|
||||
@@ -47,6 +47,14 @@ defmodule Kernel.TypespecTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "unexpected expression in typespec" do
|
||||
assert_raise CompileError, ~r"unexpected expression in typespec: \"foobar\"", fn ->
|
||||
test_module do
|
||||
@type mytype :: "foobar"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "invalid function specification" do
|
||||
assert_raise CompileError, ~r"invalid function type specification: myfun = 1", fn ->
|
||||
test_module do
|
||||
|
||||
@@ -26,8 +26,6 @@ defmodule Kernel.WarningTest do
|
||||
{_arg, _arg} = {1, 1}
|
||||
"""
|
||||
end) =~ "warning: the underscored variable \"_arg\" appears more than once in a match"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :unused_function do
|
||||
@@ -160,7 +158,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "warning: unused import :lists"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :unused_alias do
|
||||
@@ -173,7 +171,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "warning: unused alias List"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :unused_inside_dynamic_module do
|
||||
@@ -189,7 +187,7 @@ defmodule Kernel.WarningTest do
|
||||
end
|
||||
end) =~ "warning: unused import String"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :unused_guard do
|
||||
@@ -233,7 +231,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "warning: bodyless clause provided for nonexistent def hello/0"
|
||||
after
|
||||
purge [Sample1]
|
||||
purge Sample1
|
||||
end
|
||||
|
||||
test :used_import_via_alias do
|
||||
@@ -420,7 +418,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "warning: @behavior attribute is not supported, please use @behaviour instead"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :undefined_macro_for_protocol do
|
||||
@@ -449,7 +447,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "nofile:4: warning: clauses for the same def should be grouped together, def foo/2 was previously defined (nofile:2)"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :warning_with_overridden_file do
|
||||
@@ -462,15 +460,13 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "sample:3: warning: variable x is unused"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :warning_on_codepoint_escape do
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string "? "
|
||||
end) =~ "nofile:1: warning: found ? followed by codepoint 0x20 (space), please use \\s instead"
|
||||
after
|
||||
purge [Sample]
|
||||
end
|
||||
|
||||
test :typedoc_on_typep do
|
||||
@@ -485,7 +481,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "nofile:3: warning: type priv/0 is private, @typedoc's are always discarded for private types"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :typedoc_with_no_type do
|
||||
@@ -497,7 +493,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "nofile:1: warning: @typedoc provided but no type follows it"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :doc_with_no_function do
|
||||
@@ -509,7 +505,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "nofile:1: warning: @doc provided but no definition follows it"
|
||||
after
|
||||
purge [Sample]
|
||||
purge Sample
|
||||
end
|
||||
|
||||
defp purge(list) when is_list(list) do
|
||||
|
||||
@@ -41,12 +41,6 @@ defmodule KernelTest do
|
||||
assert binding() == [a: 0]
|
||||
end
|
||||
|
||||
test "nil?/1" do
|
||||
assert nil?(nil) == true
|
||||
assert nil?(0) == false
|
||||
assert nil?(false) == false
|
||||
end
|
||||
|
||||
test "in/2" do
|
||||
assert 2 in [1, 2, 3]
|
||||
assert 2 in 1..3
|
||||
@@ -96,7 +90,7 @@ defmodule KernelTest do
|
||||
end
|
||||
|
||||
test "paren as nil" do
|
||||
assert nil?(()) == true
|
||||
assert is_nil(()) == true
|
||||
assert ((); ();) == nil
|
||||
assert [ 1, (), 3 ] == [1, nil, 3 ]
|
||||
assert [do: ()] == [do: nil]
|
||||
|
||||
@@ -38,6 +38,12 @@ defmodule PathTest do
|
||||
assert Path.relative("/usr/local/bin") == "usr/local/bin"
|
||||
assert Path.relative("usr/local/bin") == "usr/local/bin"
|
||||
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
|
||||
|
||||
assert Path.relative_to("D:/usr/local/foo", "D:/usr/") == "local/foo"
|
||||
assert Path.relative_to("D:/usr/local/foo", "d:/usr/") == "local/foo"
|
||||
assert Path.relative_to("d:/usr/local/foo", "D:/usr/") == "local/foo"
|
||||
assert Path.relative_to("D:/usr/local/foo", "d:") == "usr/local/foo"
|
||||
assert Path.relative_to("D:/usr/local/foo", "D:") == "usr/local/foo"
|
||||
end
|
||||
|
||||
test :type_win do
|
||||
|
||||
@@ -64,20 +64,20 @@ defmodule ProtocolTest do
|
||||
end
|
||||
|
||||
test "protocol implementations without any" do
|
||||
assert nil? Sample.impl_for(:foo)
|
||||
assert nil? Sample.impl_for(fn(x) -> x end)
|
||||
assert nil? Sample.impl_for(1)
|
||||
assert nil? Sample.impl_for(1.1)
|
||||
assert nil? Sample.impl_for([])
|
||||
assert nil? Sample.impl_for([1, 2, 3])
|
||||
assert nil? Sample.impl_for({})
|
||||
assert nil? Sample.impl_for({1, 2, 3})
|
||||
assert nil? Sample.impl_for("foo")
|
||||
assert nil? Sample.impl_for(<<1>>)
|
||||
assert nil? Sample.impl_for(%{})
|
||||
assert nil? Sample.impl_for(self)
|
||||
assert nil? Sample.impl_for(hd(:erlang.ports))
|
||||
assert nil? Sample.impl_for(make_ref)
|
||||
assert is_nil Sample.impl_for(:foo)
|
||||
assert is_nil Sample.impl_for(fn(x) -> x end)
|
||||
assert is_nil Sample.impl_for(1)
|
||||
assert is_nil Sample.impl_for(1.1)
|
||||
assert is_nil Sample.impl_for([])
|
||||
assert is_nil Sample.impl_for([1, 2, 3])
|
||||
assert is_nil Sample.impl_for({})
|
||||
assert is_nil Sample.impl_for({1, 2, 3})
|
||||
assert is_nil Sample.impl_for("foo")
|
||||
assert is_nil Sample.impl_for(<<1>>)
|
||||
assert is_nil Sample.impl_for(%{})
|
||||
assert is_nil Sample.impl_for(self)
|
||||
assert is_nil Sample.impl_for(hd(:erlang.ports))
|
||||
assert is_nil Sample.impl_for(make_ref)
|
||||
|
||||
assert Sample.impl_for(%ImplStruct{}) ==
|
||||
Sample.ProtocolTest.ImplStruct
|
||||
@@ -302,20 +302,20 @@ defmodule Protocol.ConsolidationTest do
|
||||
end
|
||||
|
||||
test "consolidated implementations without any" do
|
||||
assert nil? Sample.impl_for(:foo)
|
||||
assert nil? Sample.impl_for(fn(x) -> x end)
|
||||
assert nil? Sample.impl_for(1)
|
||||
assert nil? Sample.impl_for(1.1)
|
||||
assert nil? Sample.impl_for([])
|
||||
assert nil? Sample.impl_for([1, 2, 3])
|
||||
assert nil? Sample.impl_for({})
|
||||
assert nil? Sample.impl_for({1, 2, 3})
|
||||
assert nil? Sample.impl_for("foo")
|
||||
assert nil? Sample.impl_for(<<1>>)
|
||||
assert nil? Sample.impl_for(self)
|
||||
assert nil? Sample.impl_for(%{})
|
||||
assert nil? Sample.impl_for(hd(:erlang.ports))
|
||||
assert nil? Sample.impl_for(make_ref)
|
||||
assert is_nil Sample.impl_for(:foo)
|
||||
assert is_nil Sample.impl_for(fn(x) -> x end)
|
||||
assert is_nil Sample.impl_for(1)
|
||||
assert is_nil Sample.impl_for(1.1)
|
||||
assert is_nil Sample.impl_for([])
|
||||
assert is_nil Sample.impl_for([1, 2, 3])
|
||||
assert is_nil Sample.impl_for({})
|
||||
assert is_nil Sample.impl_for({1, 2, 3})
|
||||
assert is_nil Sample.impl_for("foo")
|
||||
assert is_nil Sample.impl_for(<<1>>)
|
||||
assert is_nil Sample.impl_for(self)
|
||||
assert is_nil Sample.impl_for(%{})
|
||||
assert is_nil Sample.impl_for(hd(:erlang.ports))
|
||||
assert is_nil Sample.impl_for(make_ref)
|
||||
|
||||
assert Sample.impl_for(%ImplStruct{}) ==
|
||||
Sample.Protocol.ConsolidationTest.ImplStruct
|
||||
|
||||
@@ -30,10 +30,10 @@ defmodule RecordTest do
|
||||
|
||||
# We need indirection to avoid warnings
|
||||
defp record?(data, kind) do
|
||||
Record.record?(data, kind)
|
||||
Record.is_record(data, kind)
|
||||
end
|
||||
|
||||
test "record?/2" do
|
||||
test "is_record/2" do
|
||||
assert record?({User, "meg", 27}, User)
|
||||
refute record?({User, "meg", 27}, Author)
|
||||
refute record?(13, Author)
|
||||
@@ -41,10 +41,10 @@ defmodule RecordTest do
|
||||
|
||||
# We need indirection to avoid warnings
|
||||
defp record?(data) do
|
||||
Record.record?(data)
|
||||
Record.is_record(data)
|
||||
end
|
||||
|
||||
test "record?/1" do
|
||||
test "is_record/1" do
|
||||
assert record?({User, "john", 27})
|
||||
refute record?({"john", 27})
|
||||
refute record?(13)
|
||||
|
||||
@@ -158,7 +158,6 @@ defmodule SetTest.Common do
|
||||
test "is collectable" do
|
||||
assert Set.equal?(new_set([1, 1, 2, 3, 3, 3]), new_set([1, 2, 3]))
|
||||
assert Set.equal?(new_set([1, 1, 2, 3, 3, 3], &(&1 * 2)), new_set([2, 4, 6]))
|
||||
assert Collectable.empty(new_set([1, 2, 3])) == new_set
|
||||
end
|
||||
|
||||
test "is zippable" do
|
||||
|
||||
@@ -270,7 +270,7 @@ defmodule StreamTest do
|
||||
stream = Stream.flat_map [1,2,3],
|
||||
fn i ->
|
||||
Stream.resource(fn -> i end,
|
||||
fn acc -> {acc, acc + 1} end,
|
||||
fn acc -> {[acc], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_flat_map, true) end)
|
||||
end
|
||||
|
||||
@@ -281,7 +281,7 @@ defmodule StreamTest do
|
||||
|
||||
test "flat_map/2 does not leave outer stream suspended" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {acc, acc + 1} end,
|
||||
fn acc -> {[acc], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_flat_map, true) end)
|
||||
stream = Stream.flat_map(stream, fn i -> [i, i + 1, i + 2] end)
|
||||
|
||||
@@ -292,7 +292,7 @@ defmodule StreamTest do
|
||||
|
||||
test "flat_map/2 closes on error" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {acc, acc + 1} end,
|
||||
fn acc -> {[acc], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_flat_map, true) end)
|
||||
stream = Stream.flat_map(stream, fn _ -> throw(:error) end)
|
||||
|
||||
@@ -311,6 +311,13 @@ defmodule StreamTest do
|
||||
assert Enum.take(stream, 5) == [1, 2, 3, 4, 3]
|
||||
end
|
||||
|
||||
test "interval/1" do
|
||||
stream = Stream.interval(10)
|
||||
now = :erlang.now
|
||||
assert Enum.take(stream, 5) == [0, 1, 2, 3, 4]
|
||||
assert :timer.now_diff(:erlang.now, now) > 50000
|
||||
end
|
||||
|
||||
test "into/2 and run/1" do
|
||||
Process.put(:stream_cont, [])
|
||||
Process.put(:stream_done, false)
|
||||
@@ -368,7 +375,7 @@ defmodule StreamTest do
|
||||
|
||||
test "transform/3 with halt" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {acc, acc + 1} end,
|
||||
fn acc -> {[acc], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_transform, true) end)
|
||||
stream = Stream.transform(stream, 0, fn i, acc -> if acc < 3, do: {[i], acc + 1}, else: {:halt, acc} end)
|
||||
|
||||
@@ -417,7 +424,7 @@ defmodule StreamTest do
|
||||
|
||||
test "resource/3 closes on errors" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {acc, acc + 1} end,
|
||||
fn acc -> {[acc], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_resource, true) end)
|
||||
|
||||
Process.put(:stream_resource, false)
|
||||
@@ -428,8 +435,76 @@ defmodule StreamTest do
|
||||
|
||||
test "resource/3 is zippable" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn 10 -> nil
|
||||
acc -> {acc, acc + 1}
|
||||
fn 10 -> {:halt, 10}
|
||||
acc -> {[acc], acc + 1}
|
||||
end,
|
||||
fn _ -> Process.put(:stream_resource, true) end)
|
||||
|
||||
list = Enum.to_list(stream)
|
||||
Process.put(:stream_resource, false)
|
||||
assert Enum.zip(list, list) == Enum.zip(stream, stream)
|
||||
assert Process.get(:stream_resource)
|
||||
end
|
||||
|
||||
test "resource/3 halts with inner list" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {[acc, acc+1, acc+2], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_resource, true) end)
|
||||
|
||||
Process.put(:stream_resource, false)
|
||||
assert Enum.take(stream, 5) == [1, 2, 3, 2, 3]
|
||||
assert Process.get(:stream_resource)
|
||||
end
|
||||
|
||||
test "resource/3 closes on errors with inner list" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {[acc, acc+1, acc+2], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_resource, true) end)
|
||||
|
||||
Process.put(:stream_resource, false)
|
||||
stream = Stream.map(stream, fn x -> if x > 2, do: throw(:error), else: x end)
|
||||
assert catch_throw(Enum.to_list(stream)) == :error
|
||||
assert Process.get(:stream_resource)
|
||||
end
|
||||
|
||||
test "resource/3 is zippable with inner list" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn 10 -> {:halt, 10}
|
||||
acc -> {[acc, acc+1, acc+2], acc + 1}
|
||||
end,
|
||||
fn _ -> Process.put(:stream_resource, true) end)
|
||||
|
||||
list = Enum.to_list(stream)
|
||||
Process.put(:stream_resource, false)
|
||||
assert Enum.zip(list, list) == Enum.zip(stream, stream)
|
||||
assert Process.get(:stream_resource)
|
||||
end
|
||||
|
||||
test "resource/3 halts with inner enum" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {acc..acc+2, acc + 1} end,
|
||||
fn _ -> Process.put(:stream_resource, true) end)
|
||||
|
||||
Process.put(:stream_resource, false)
|
||||
assert Enum.take(stream, 5) == [1, 2, 3, 2, 3]
|
||||
assert Process.get(:stream_resource)
|
||||
end
|
||||
|
||||
test "resource/3 closes on errors with inner enum" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {acc..acc+2, acc + 1} end,
|
||||
fn _ -> Process.put(:stream_resource, true) end)
|
||||
|
||||
Process.put(:stream_resource, false)
|
||||
stream = Stream.map(stream, fn x -> if x > 2, do: throw(:error), else: x end)
|
||||
assert catch_throw(Enum.to_list(stream)) == :error
|
||||
assert Process.get(:stream_resource)
|
||||
end
|
||||
|
||||
test "resource/3 is zippable with inner enum" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn 10 -> {:halt, 10}
|
||||
acc -> {acc..acc+2, acc + 1}
|
||||
end,
|
||||
fn _ -> Process.put(:stream_resource, true) end)
|
||||
|
||||
@@ -518,6 +593,13 @@ defmodule StreamTest do
|
||||
assert Stream.take_while(stream, &(&1 < 11)) |> Enum.to_list == [6,7,8,9,10]
|
||||
end
|
||||
|
||||
test "timer/1" do
|
||||
stream = Stream.timer(10)
|
||||
now = :erlang.now
|
||||
assert Enum.to_list(stream) == [0]
|
||||
assert :timer.now_diff(:erlang.now, now) > 10000
|
||||
end
|
||||
|
||||
test "unfold/2" do
|
||||
stream = Stream.unfold(10, fn x -> if x > 0, do: {x, x-1}, else: nil end)
|
||||
assert Enum.take(stream, 5) == [10, 9, 8, 7, 6]
|
||||
@@ -556,7 +638,7 @@ defmodule StreamTest do
|
||||
|
||||
test "zip/2 does not leave streams suspended" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {acc, acc + 1} end,
|
||||
fn acc -> {[acc], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_zip, true) end)
|
||||
|
||||
Process.put(:stream_zip, false)
|
||||
@@ -570,7 +652,7 @@ defmodule StreamTest do
|
||||
|
||||
test "zip/2 does not leave streams suspended on halt" do
|
||||
stream = Stream.resource(fn -> 1 end,
|
||||
fn acc -> {acc, acc + 1} end,
|
||||
fn acc -> {[acc], acc + 1} end,
|
||||
fn _ -> Process.put(:stream_zip, :done) end)
|
||||
|
||||
assert Stream.zip([:a, :b, :c, :d, :e], stream) |> Enum.take(3) ==
|
||||
|
||||
@@ -64,7 +64,7 @@ defmodule Supervisor.SpecTest do
|
||||
|
||||
test "supervise/2" do
|
||||
assert supervise([], strategy: :one_for_one) == {
|
||||
:ok, {{:one_for_one, 5, 5}, []}
|
||||
:ok, {{:one_for_one, 3, 5}, []}
|
||||
}
|
||||
|
||||
children = [worker(GenEvent, [])]
|
||||
|
||||
@@ -6,9 +6,9 @@ defmodule SystemTest do
|
||||
|
||||
test "build_info/0" do
|
||||
assert is_map System.build_info
|
||||
assert not nil?(System.build_info[:version])
|
||||
assert not nil?(System.build_info[:tag])
|
||||
assert not nil?(System.build_info[:date])
|
||||
assert not is_nil(System.build_info[:version])
|
||||
assert not is_nil(System.build_info[:tag])
|
||||
assert not is_nil(System.build_info[:date])
|
||||
end
|
||||
|
||||
test "cwd/0" do
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Task.SupervisorTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case
|
||||
|
||||
setup do
|
||||
{:ok, pid} = Task.Supervisor.start_link()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule TaskTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case
|
||||
|
||||
setup do
|
||||
Logger.remove_backend(:console)
|
||||
@@ -61,6 +61,37 @@ defmodule TaskTest do
|
||||
assert_receive :done
|
||||
end
|
||||
|
||||
test "start/1" do
|
||||
parent = self()
|
||||
fun = fn -> wait_and_send(parent, :done) end
|
||||
{:ok, pid} = Task.start(fun)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
|
||||
{:name, fun_name} = :erlang.fun_info(fun, :name)
|
||||
assert {__MODULE__, fun_name, 0} === :proc_lib.translate_initial_call(pid)
|
||||
|
||||
send pid, true
|
||||
assert_receive :done
|
||||
end
|
||||
|
||||
test "start/3" do
|
||||
{:ok, pid} = Task.start(__MODULE__, :wait_and_send, [self(), :done])
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
|
||||
assert {__MODULE__, :wait_and_send, 2} === :proc_lib.translate_initial_call(pid)
|
||||
|
||||
send pid, true
|
||||
assert_receive :done
|
||||
end
|
||||
|
||||
test "start_link/1" do
|
||||
parent = self()
|
||||
fun = fn -> wait_and_send(parent, :done) end
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
-module(atom_test).
|
||||
-export([kv/1]).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
eval(Content) ->
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
-module(control_test).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
eval(Content) ->
|
||||
@@ -286,4 +285,4 @@ optimized_oror_test() ->
|
||||
} = to_erl("is_list([]) || :done").
|
||||
|
||||
no_after_in_try_test() ->
|
||||
{'try', _, [_], [_], _, []} = to_erl("try do :foo.bar() else _ -> :ok end").
|
||||
{'try', _, [_], [_], _, []} = to_erl("try do :foo.bar() else _ -> :ok end").
|
||||
|
||||
@@ -90,9 +90,6 @@ nesting_test() ->
|
||||
dot_alias_test() ->
|
||||
{'Elixir.Foo.Bar.Baz', _} = eval("Foo.Bar.Baz").
|
||||
|
||||
dot_dyn_alias_test() ->
|
||||
{'Elixir.Foo.Bar.Baz', _} = eval("a = Foo.Bar; a.Baz").
|
||||
|
||||
single_ref_test() ->
|
||||
{'Elixir.Foo', _} = eval("Foo"),
|
||||
{'Elixir.Foo', _} = eval("Elixir.Foo").
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(string_test).
|
||||
-include("elixir.hrl").
|
||||
-include("../../src/elixir.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
eval(Content) ->
|
||||
@@ -39,7 +39,7 @@ extract_interpolations_with_tuple_inside_interpolation_test() ->
|
||||
|
||||
extract_interpolations_with_many_expressions_inside_interpolation_test() ->
|
||||
[<<"f">>,
|
||||
{1,[{number,1,1},{eol,1,newline},{number,2,2}]},
|
||||
{1,[{number,1,1},{eol,1},{number,2,2}]},
|
||||
<<"o">>] = extract_interpolations("f#{1\n2}o").
|
||||
|
||||
extract_interpolations_with_right_curly_inside_string_inside_interpolation_test() ->
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
-module(test_helper).
|
||||
-include("elixir.hrl").
|
||||
-export([test/0, run_and_remove/2, throw_elixir/1, throw_erlang/1]).
|
||||
-define(TESTS, [
|
||||
atom_test,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
-module(tokenizer_test).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
tokenize(String) ->
|
||||
@@ -57,18 +56,18 @@ kw_test() ->
|
||||
|
||||
integer_test() ->
|
||||
[{number, 1, 123}] = tokenize("123"),
|
||||
[{number, 1, 123},{eol, 1, ';'}] = tokenize("123;"),
|
||||
[{eol, 1, newline}, {number, 3, 123}] = tokenize("\n\n123"),
|
||||
[{number, 1, 123},{';', 1}] = tokenize("123;"),
|
||||
[{eol, 1}, {number, 3, 123}] = tokenize("\n\n123"),
|
||||
[{number, 1, 123}, {number, 1, 234}] = tokenize(" 123 234 ").
|
||||
|
||||
float_test() ->
|
||||
[{number, 1, 12.3}] = tokenize("12.3"),
|
||||
[{number, 1, 12.3},{eol, 1, ';'}] = tokenize("12.3;"),
|
||||
[{eol, 1, newline}, {number, 3, 12.3}] = tokenize("\n\n12.3"),
|
||||
[{number, 1, 12.3},{';', 1}] = tokenize("12.3;"),
|
||||
[{eol, 1}, {number, 3, 12.3}] = tokenize("\n\n12.3"),
|
||||
[{number, 1, 12.3}, {number, 1, 23.4}] = tokenize(" 12.3 23.4 ").
|
||||
|
||||
comments_test() ->
|
||||
[{number, 1, 1},{eol, 1, newline},{number,2,2}] = tokenize("1 # Comment\n2").
|
||||
[{number, 1, 1},{eol, 1},{number,2,2}] = tokenize("1 # Comment\n2").
|
||||
|
||||
identifier_test() ->
|
||||
[{identifier,1,abc}] = tokenize("abc "),
|
||||
@@ -128,6 +127,9 @@ space_test() ->
|
||||
[{op_identifier,1,foo},{dual_op,1,'-'},{number,1,2}] = tokenize("foo -2"),
|
||||
[{op_identifier,1,foo},{dual_op,1,'-'},{number,1,2}] = tokenize("foo -2").
|
||||
|
||||
invalid_space_test() ->
|
||||
{1, "invalid space character U+A0 before: ", "-2"} = tokenize_error("foo" ++ [16#A0] ++"-2").
|
||||
|
||||
chars_test() ->
|
||||
[{number,1,97}] = tokenize("?a"),
|
||||
[{number,1,99}] = tokenize("?c"),
|
||||
|
||||
@@ -109,6 +109,15 @@ defmodule ExUnit do
|
||||
tests: [ExUnit.Test.t]}
|
||||
end
|
||||
|
||||
defmodule TimeoutError do
|
||||
defexception [:timeout]
|
||||
|
||||
def message(%{timeout: timeout}) do
|
||||
"test timed out after #{timeout}ms (you can change the test timeout " <>
|
||||
"by setting \"@tag timeout: x\" where x is an integer in miliseconds)"
|
||||
end
|
||||
end
|
||||
|
||||
use Application
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -273,19 +273,50 @@ defmodule ExUnit.Assertions do
|
||||
|
||||
defp do_assert_receive(expected, timeout, message) do
|
||||
binary = Macro.to_string(expected)
|
||||
message = message || "No message matching #{binary}"
|
||||
|
||||
pattern =
|
||||
case expected do
|
||||
{:when, meta, [left, right]} ->
|
||||
{:when, meta, [quote(do: unquote(left) = received), right]}
|
||||
left ->
|
||||
quote(do: unquote(left) = received)
|
||||
end
|
||||
|
||||
{:receive, meta, args} =
|
||||
quote do
|
||||
receive do
|
||||
unquote(expected) = received -> received
|
||||
unquote(pattern) ->
|
||||
received
|
||||
after
|
||||
unquote(timeout) ->
|
||||
flunk unquote(message)
|
||||
timeout ->
|
||||
message = unquote(message) || "No message matching #{unquote(binary)} after #{timeout}ms"
|
||||
flunk(message <> ExUnit.Assertions.__mailbox__(self()))
|
||||
end
|
||||
end
|
||||
|
||||
{:receive, [{:export_head, true}|meta], args}
|
||||
quote do
|
||||
timeout = unquote(timeout)
|
||||
unquote({:receive, [{:export_head, true}|meta], args})
|
||||
end
|
||||
end
|
||||
|
||||
@max_mailbox_length 10
|
||||
|
||||
@doc false
|
||||
def __mailbox__(pid) do
|
||||
{:messages, messages} = Process.info(pid, :messages)
|
||||
length = length(messages)
|
||||
mailbox = Enum.take(messages, @max_mailbox_length) |> Enum.map_join("\n", &inspect/1)
|
||||
mailbox_message(length, mailbox)
|
||||
end
|
||||
|
||||
defp mailbox_message(0, _mailbox), do: ". The process mailbox is empty."
|
||||
defp mailbox_message(length, mailbox) when length > 10 do
|
||||
". Process mailbox:\n" <> mailbox
|
||||
<> "\nShowing only #{@max_mailbox_length} of #{length} messages."
|
||||
end
|
||||
defp mailbox_message(_length, mailbox) do
|
||||
". Process mailbox:\n" <> mailbox
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -109,7 +109,7 @@ defmodule ExUnit.Case do
|
||||
If the same key is set via `@tag`, the `@tag` value has higher
|
||||
precedence.
|
||||
|
||||
### Reserved tags
|
||||
### Known tags
|
||||
|
||||
The following tags are set automatically by ExUnit and are
|
||||
therefore reserved:
|
||||
@@ -119,6 +119,10 @@ defmodule ExUnit.Case do
|
||||
* `:line` - the line on which the test was defined
|
||||
* `:file` - the file on which the test was defined
|
||||
|
||||
The following tags customize how tests behaves:
|
||||
|
||||
* `:timeout` - customizes the test timeout in miliseconds (defaults to 30000)
|
||||
|
||||
## Filters
|
||||
|
||||
Tags can also be used to identify specific tests, which can then
|
||||
|
||||
@@ -152,6 +152,7 @@ defmodule ExUnit.DocTest do
|
||||
tests = quote bind_quoted: binding do
|
||||
file = "(for doctest at) " <> Path.relative_to_cwd(mod.__info__(:compile)[:source])
|
||||
for {name, test} <- ExUnit.DocTest.__doctests__(mod, opts) do
|
||||
@tag :doctest
|
||||
@file file
|
||||
test name, do: unquote(test)
|
||||
end
|
||||
@@ -348,9 +349,9 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
moduledocs = extract_from_moduledoc(all_docs[:moduledoc])
|
||||
|
||||
docs = for doc <- all_docs[:docs] do
|
||||
extract_from_doc(doc)
|
||||
end |> Enum.concat
|
||||
docs = for doc <- all_docs[:docs],
|
||||
doc <- extract_from_doc(doc),
|
||||
do: doc
|
||||
|
||||
moduledocs ++ docs
|
||||
end
|
||||
|
||||
@@ -171,6 +171,7 @@ defmodule ExUnit.Formatter do
|
||||
end
|
||||
|
||||
defp format_banner(value, formatter) do
|
||||
value = String.replace(value, "\n", "\n" <> @counter_padding)
|
||||
formatter.(:error_info, value)
|
||||
end
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ defmodule ExUnit.Runner do
|
||||
{test_case, pending} =
|
||||
receive do
|
||||
{^case_pid, :case_finished, test_case, tests} ->
|
||||
Process.demonitor(case_ref, [:flush])
|
||||
{test_case, tests}
|
||||
{:DOWN, ^case_ref, :process, ^case_pid, error} ->
|
||||
test_case = %{test_case | state: {:failed, {{:EXIT, case_pid}, error, []}}}
|
||||
@@ -180,7 +181,7 @@ defmodule ExUnit.Runner do
|
||||
defp run_test(config, test, context) do
|
||||
EM.test_started(config.manager, test)
|
||||
|
||||
if nil?(test.state) do
|
||||
if is_nil(test.state) do
|
||||
test = spawn_test(config, test, Map.merge(test.tags, context))
|
||||
end
|
||||
|
||||
@@ -208,12 +209,20 @@ defmodule ExUnit.Runner do
|
||||
exit(:shutdown)
|
||||
end)
|
||||
|
||||
timeout = Map.get(test.tags, :timeout, 30_000)
|
||||
|
||||
test =
|
||||
receive do
|
||||
{^test_pid, :test_finished, test} ->
|
||||
Process.demonitor(test_ref, [:flush])
|
||||
test
|
||||
{:DOWN, ^test_ref, :process, ^test_pid, error} ->
|
||||
%{test | state: {:failed, {{:EXIT, test_pid}, error, []}}}
|
||||
after
|
||||
timeout ->
|
||||
Process.exit(test_pid, :kill)
|
||||
Process.demonitor(test_ref, [:flush])
|
||||
%{test | state: {:failed, {:error, %ExUnit.TimeoutError{timeout: timeout}, []}}}
|
||||
end
|
||||
|
||||
exec_on_exit(test, test_pid)
|
||||
|
||||
@@ -5,37 +5,37 @@ defmodule ExUnit.Server do
|
||||
use GenServer
|
||||
|
||||
def start_link() do
|
||||
:gen_server.start_link({:local, __MODULE__}, __MODULE__, :ok, [])
|
||||
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
|
||||
end
|
||||
|
||||
## Before run API
|
||||
|
||||
def start_load() do
|
||||
:gen_server.cast(__MODULE__, :start_load)
|
||||
GenServer.cast(__MODULE__, :start_load)
|
||||
end
|
||||
|
||||
def add_async_case(name) do
|
||||
:gen_server.cast(__MODULE__, {:add_async_case, name})
|
||||
GenServer.cast(__MODULE__, {:add_async_case, name})
|
||||
end
|
||||
|
||||
def add_sync_case(name) do
|
||||
:gen_server.cast(__MODULE__, {:add_sync_case, name})
|
||||
GenServer.cast(__MODULE__, {:add_sync_case, name})
|
||||
end
|
||||
|
||||
## After run API
|
||||
|
||||
def start_run() do
|
||||
:gen_server.call(__MODULE__, :start_run, @timeout)
|
||||
GenServer.call(__MODULE__, :start_run, @timeout)
|
||||
end
|
||||
|
||||
## Capture Device API
|
||||
|
||||
def add_device(device) do
|
||||
:gen_server.call(__MODULE__, {:add_device, device})
|
||||
GenServer.call(__MODULE__, {:add_device, device})
|
||||
end
|
||||
|
||||
def remove_device(device) do
|
||||
:gen_server.call(__MODULE__, {:remove_device, device})
|
||||
GenServer.call(__MODULE__, {:remove_device, device})
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
@@ -86,12 +86,35 @@ defmodule ExUnit.AssertionsTest do
|
||||
:hello = assert_received :hello
|
||||
end
|
||||
|
||||
test "assert received when different" do
|
||||
test "assert received when empty mailbox" do
|
||||
try do
|
||||
"This should never be tested" = assert_received :hello
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"No message matching :hello" = error.message
|
||||
"No message matching :hello after 0ms. The process mailbox is empty." = error.message
|
||||
end
|
||||
end
|
||||
|
||||
test "assert received when different message" do
|
||||
send self, {:message, :not_expected, :at_all}
|
||||
try do
|
||||
"This should never be tested" = assert_received :hello
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"No message matching :hello after 0ms. Process mailbox:\n{:message, :not_expected, :at_all}" = error.message
|
||||
end
|
||||
end
|
||||
|
||||
test "assert received when different message having more than 10 on mailbox" do
|
||||
for i <- 1..11, do: send(self, {:message, i})
|
||||
try do
|
||||
"This should never be tested" = assert_received x when x == :hello
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"No message matching x when x == :hello after 0ms. Process mailbox:\n" <>
|
||||
"{:message, 1}\n{:message, 2}\n{:message, 3}\n{:message, 4}\n" <>
|
||||
"{:message, 5}\n{:message, 6}\n{:message, 7}\n{:message, 8}\n" <>
|
||||
"{:message, 9}\n{:message, 10}\nShowing only 10 of 11 messages." = error.message
|
||||
end
|
||||
end
|
||||
|
||||
@@ -226,7 +249,8 @@ defmodule ExUnit.AssertionsTest do
|
||||
end
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"Expected exception ArgumentError but got UndefinedFunctionError (undefined function: Not.Defined.function/3)" = error.message
|
||||
"Expected exception ArgumentError but got UndefinedFunctionError " <>
|
||||
"(undefined function: Not.Defined.function/3 (module Not.Defined is not available))" = error.message
|
||||
end
|
||||
|
||||
test "assert raise with erlang error" do
|
||||
|
||||
@@ -24,8 +24,8 @@ defmodule ExUnit.CaseTest do
|
||||
end
|
||||
|
||||
test "reset tags", context do
|
||||
assert nil?(context[:hello])
|
||||
assert nil?(context[:world])
|
||||
assert is_nil(context[:hello])
|
||||
assert is_nil(context[:world])
|
||||
end
|
||||
|
||||
test "module tags", context do
|
||||
|
||||
@@ -239,7 +239,7 @@ defmodule ExUnit.DocTestTest do
|
||||
assert output =~ """
|
||||
4) test moduledoc at ExUnit.DocTestTest.Invalid (4) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:204
|
||||
Doctest failed: got UndefinedFunctionError with message undefined function: Hello.world/0
|
||||
Doctest failed: got UndefinedFunctionError with message undefined function: Hello.world/0 (module Hello is not available)
|
||||
code: Hello.world
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:112: ExUnit.DocTestTest.Invalid (module)
|
||||
@@ -264,6 +264,20 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "tags tests as doctests" do
|
||||
defmodule DoctestTag do
|
||||
use ExUnit.Case
|
||||
doctest ExUnit.DocTestTest.NoImport
|
||||
|
||||
setup test do
|
||||
assert test.doctest
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
assert capture_io(fn -> ExUnit.run end) =~ "1 tests, 0 failures"
|
||||
end
|
||||
|
||||
test "multiple exceptions in one test case is not supported" do
|
||||
assert_raise ExUnit.DocTest.Error, ~r"multiple exceptions in one doctest case are not supported", fn ->
|
||||
defmodule NeverCompiled do
|
||||
|
||||
@@ -133,4 +133,15 @@ defmodule ExUnit.FormatterTest do
|
||||
6]
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats assertions with message with multiple lines" do
|
||||
message = "Some meaningful error:\nuseful info\nanother useful info"
|
||||
failure = {:error, catch_assertion(assert(false, message)), []}
|
||||
assert format_test_case_failure(case(), failure, 1, :infinity, &formatter/2) =~ """
|
||||
1) Hello: failure on setup_all callback, tests invalidated
|
||||
Some meaningful error:
|
||||
useful info
|
||||
another useful info
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,6 +40,20 @@ defmodule ExUnitTest do
|
||||
end) =~ "1 tests, 1 failures"
|
||||
end
|
||||
|
||||
test "it supports timeouts" do
|
||||
defmodule TimeoutTest do
|
||||
use ExUnit.Case
|
||||
|
||||
@tag timeout: 10
|
||||
test "ok" do
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
assert capture_io(fn -> ExUnit.run end) =~
|
||||
"** (ExUnit.TimeoutError) test timed out after 10ms"
|
||||
end
|
||||
|
||||
test "filtering cases with tags" do
|
||||
defmodule ParityTest do
|
||||
use ExUnit.Case
|
||||
|
||||
+1
-1
@@ -291,7 +291,7 @@ defmodule IEx do
|
||||
end
|
||||
|
||||
defp configure(k, v) do
|
||||
raise ArgumentError, "invalid value #{inspect v} for configuration #{inspect k}"
|
||||
raise ArgumentError, "invalid configuration or value for pair #{inspect k} - #{inspect v}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule IEx.Evaluator do
|
||||
|
||||
path = Enum.find candidates, &File.regular?/1
|
||||
|
||||
if nil?(path) do
|
||||
if is_nil(path) do
|
||||
config
|
||||
else
|
||||
eval_dot_iex(config, path)
|
||||
|
||||
@@ -40,6 +40,11 @@ defmodule IEx.Helpers do
|
||||
|
||||
h(Enum)
|
||||
h(Enum.reverse/1)
|
||||
|
||||
To discover all available functions for a module, type the module name
|
||||
follow by a dot, then press tab to trigger autocomplete. For example:
|
||||
|
||||
Enum.
|
||||
|
||||
To learn more about IEx as a whole, just type `h(IEx)`.
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user