Compare commits

..
182 Commits
Author SHA1 Message Date
José Valim 8931f34582 Release v1.0.0-rc2 2014-09-07 12:49:56 +02:00
José Valim 843706e36e Add free operators
<<~, ~>>, <~, ~>, <~>, <|>
2014-09-07 12:42:10 +02:00
José Valim 172bfdb128 Revert "Merge pull request #2702 from alexrp/master"
<~ is already a valid expression in Elixir.

This reverts commit 2c741ec6ae, reversing
changes made to 12a457829f.
2014-09-07 12:12:35 +02:00
José Valim 2c741ec6ae Merge pull request #2702 from alexrp/master
Introduce new operators intended for use by parser combinator libraries.
2014-09-07 12:06:29 +02:00
Eric Meadows-Jönsson 12a457829f Return the proper exit code from powershell 2014-09-06 21:10:48 +02:00
José Valim 0ff6850237 Raise if status code != 0 and use non-verbose modes 2014-09-06 20:33:02 +02:00
Eric Meadows-Jönsson 63f6088476 Bump Hex requirement 2014-09-06 20:13:37 +02:00
Eric Meadows-Jönsson 206f6413f8 Merge pull request #2756 from elixir-lang/emj-archive-shell
Fetch Hex with wget or curl
2014-09-06 20:12:49 +02:00
Eric Meadows-Jönsson a2c0424b37 Add file exists check on windows when installing archive 2014-09-06 19:14:36 +02:00
Eric Meadows-Jönsson 6ea7d3cef6 Fetch Hex with wget or curl
This way we can use https and do peer verification without bundling
certificates with elixir.
2014-09-06 16:37:57 +02:00
José Valim 2cafd8c060 Ensure we don't look up inexisting variables for default args 2014-09-05 17:59:45 +02:00
José Valim 7271dfebfb Update CHANGELOG 2014-09-05 16:52:26 +02:00
José Valim 2eff3590c1 Lowercase drive letters from cwd, closes #2747 2014-09-05 16:43:55 +02:00
José Valim b578f45060 Fix IEx test 2014-09-05 16:35:30 +02:00
José Valim a34ccfe129 Make the regex type non-opaque 2014-09-05 16:27:20 +02:00
José Valim 8f4442f2df Test :sys functions with GenEvent 2014-09-05 16:15:28 +02:00
José Valim 598e42e00b Tidy up GenEvent docs and test suite 2014-09-05 13:34:26 +02:00
José Valim b93912b9dc Deprecate monitor option in GenEvent 2014-09-05 13:20:40 +02:00
José Valim 71eca8a5d6 Move from already_added to already_present 2014-09-05 12:50:42 +02:00
José Valim f0bb0e1f7f Do not wrap process handlers in a tuple 2014-09-05 12:26:59 +02:00
José Valim 2cb0639208 Move from module_not_found to the more generic handler_not_found 2014-09-05 11:59:53 +02:00
José Valim 081e3b36ec Allow only one process handler per process 2014-09-05 11:51:33 +02:00
José Valim d521d7927f Leave clause in case the handler can fail 2014-09-04 19:21:37 +02:00
José Valim 7313d26720 Remove deprecated branches 2014-09-04 13:58:58 +02:00
José Valim 577a07d47f Do not consider handler swap to be failures 2014-09-04 13:24:17 +02:00
José Valim f6093d40d7 Tag all doctest tests 2014-09-04 12:10:35 +02:00
José Valim 1dd034d4aa Merge pull request #2752 from fishcakez/dialyzer_warns
Fix some dialyzer warnings
2014-09-03 21:05:29 +02:00
James Fish 2417b196a4 Fix elixir dialyzer warnings 2014-09-03 19:29:01 +01:00
James Fish 50bb712e22 Fix iex dialyzer warnings 2014-09-03 19:24:32 +01:00
James Fish 7b0747c907 Fix :sys.replace_state/2,3 for GenEvent 2014-09-03 19:24:01 +01:00
James Fish b878e414de Fix mix dialyzer warnings and improve task specs 2014-09-03 19:23:29 +01:00
James Fish c447868e0f Fix Mix lock files to use maps 2014-09-03 19:22:42 +01:00
James Fish 681d5aa9c4 Fix logger dialyzer warnings 2014-09-03 19:20:09 +01:00
James Fish 0263dcc621 Don't check for race conditions with dialyzer
For more information:
http://erlang.org/pipermail/erlang-bugs/2014-July/004518.html
2014-09-03 19:13:39 +01:00
José Valim 1ae24c5c5d Update CHANGELOG 2014-09-03 14:38:28 +02:00
José Valim 6c02ebe4af Merge pull request #2720 from edgurgel/print-mailbox-on-failing-assert-receive
Print process mailbox on failing assert_receive
2014-09-03 14:24:16 +02:00
José Valim a08e44c5a9 Default max_restarts to 3 (closer to Erlang) 2014-09-03 14:23:03 +02:00
José Valim 08f30098db Improve iex error message 2014-09-03 14:22:16 +02:00
José Valim 5dbd91e4fa Merge pull request #2746 from styx/master
Fixes #2744
2014-09-03 13:46:23 +02:00
Mikhail S. Pobolovets 5b2eb54794 #2744: Ensure Path.relative_to is case insensitive on the drive letter
Signed-off-by: Mikhail S. Pobolovets <styx.mp@gmail.com>
2014-09-03 14:40:03 +03:00
José Valim 2c17007693 Merge pull request #2749 from alco/macro-env-doc
Document how to obtain a custom instance of Macro.Env
2014-09-03 13:13:15 +02:00
Alexei Sholik 2770c32f3d Document how to obtain a custom instance of Macro.Env 2014-09-03 11:10:07 +03:00
José Valim 8c8f6d8346 Merge pull request #2743 from fishcakez/loggertest_exit_signal_rc
Fix exit signal race condition in logger tests
2014-09-02 12:22:22 +02:00
James Fish 3950130e73 Fix exit signal race condition in logger tests
Test process must wait for exit signal before untrapping exits,
otherwise exit signal may cause it to exit.
2014-09-02 11:07:18 +01:00
Alex Rønne Petersen c8bcd89c7c Introduce new operators intended for use by parser combinator libraries.
The expected definitions (which are by no means required) in
parser combinator speak are:

* `p  ~>> f`   =  `bind(p, f)`
* `p1 ~>  p2`  =  `pair_right(p1, p2)`
* `p1 <~  p2`  =  `pair_left(p1, p2)`
* `p1 <~> p2`  =  `pair_both(p1, p2)`
* `p  |~> f`   =  `map(p, f)`
* `1p <|> p2`  =  `either(p1, p2)`
2014-09-02 01:57:50 +02:00
José Valim 7f7aaa115f Merge pull request #2736 from fishcakez/logger_stopped_exit
Fix Logger.log/2 not to raise on Logger exit
2014-09-01 19:33:11 -04:00
José Valim 53aeb26407 Merge pull request #2741 from styx/master
Fixes #2737
2014-09-01 19:31:55 -04:00
Mikhail S. Pobolovets bba0d12510 #2737: deps.unlock should warn when given missing dependency
Signed-off-by: Mikhail S. Pobolovets <styx.mp@gmail.com>
2014-09-01 17:53:03 +03:00
Eric Meadows-Jönsson 035d8227a5 Merge pull request #2740 from styx/patch-1
Typo fix
2014-09-01 13:36:43 +02:00
Eric Meadows-Jönsson 003a4da87e Merge pull request #2738 from mgwidmann/master
Correct documentation for proper grammer.
2014-09-01 13:36:17 +02:00
Mikhail S. Pobolovets 1977631b7f Typo fix 2014-09-01 11:58:09 +03:00
matt c9a0ec000a Correct documentation for proper grammer. 2014-08-31 23:55:41 -04:00
Eric Meadows-Jönsson ba842ba113 No commas in deps.unlock 2014-08-31 19:49:50 +02:00
James Fish 6304919d0d Update Logger mode after configuration
Compute mode after configure as possible mode should change.
2014-08-31 17:04:23 +01:00
James Fish d2d27e96e5 Fix Logger.log/2 not to raise on Logger exit 2014-08-31 16:40:57 +01:00
Eric Meadows-Jönsson fc6f7c532c Fix unused warnings 2014-08-31 09:01:00 +02:00
José Valim 1bee85e518 Merge pull request #2734 from gjaldon/clearer-unquote-splicing-error
Friendlier unquote_splicing error with hints
2014-08-30 14:58:30 -04:00
Gabriel Jaldon 66b211987d Friendlier unquote_splicing error with hints 2014-08-31 02:01:17 +08:00
José Valim 15fdae4de6 Start v1.0.0-rc2 2014-08-30 13:38:53 -04:00
José Valim 5b4497fa6b Release v1.0.0-rc1 2014-08-30 13:33:02 -04:00
José Valim 612e124ec9 Add tests for sync_threshold 2014-08-29 08:13:53 -04:00
José Valim abd2b35738 Get rid of Logger timeout config, closes #2724 2014-08-29 08:01:03 -04:00
José Valim 2afb875099 Merge pull request #2725 from MSch/document-compile-attr-accumulation
Document that @compile accumulates.
2014-08-28 14:10:41 -04:00
Martin Schürrer 88507f65f8 Document that @compile accumulates. 2014-08-28 20:05:56 +02:00
José Valim 275d1f5494 Straight up and unify impls of clean, deps.clean and deps.unlock 2014-08-28 13:00:36 -04:00
José Valim e6420c8d13 Merge pull request #2715 from nurugger07/remove_unused_deps
Add an unused flag to deps.clean & deps.unlock
2014-08-28 11:38:46 -04:00
Eduardo Gurgel 60bcf1cadb Print process mailbox on failing assert_receive 2014-08-29 00:33:48 +12:00
Eduardo Gurgel d86ca5826c Format multiline AssertionError message with correct padding 2014-08-29 00:33:25 +12:00
José Valim 2af00f4c33 Merge pull request #2723 from edgurgel/fix-private-function-on-algebra
Make do_surround_many private on Inspect.Algebra
2014-08-27 16:08:55 -04:00
Eduardo Gurgel 0fe86e77c1 Make do_surround_many private on Inspect.Algebra 2014-08-28 08:01:32 +12:00
José Valim 1ca451f779 Normalize is_guard checks 2014-08-27 15:28:31 -04:00
José Valim ebe0c8cf8e Update CHANGELOG 2014-08-27 12:21:12 -04:00
José Valim de9feb26d5 Deprecate x.Alias syntax 2014-08-27 12:17:15 -04:00
José Valim b8528f6c8e Remove warnings from the test suite 2014-08-25 14:42:24 -04:00
Alexei Sholik c9fbbac391 Add version checks and proper error formatting to escripts. Closes #2709
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-08-25 14:26:54 -04:00
José Valim 98e1b4ccf8 Ensure we tag the destination when compiling through mix
Closes #2533
2014-08-25 14:26:10 -04:00
Alexei Sholik ae19491620 Merge pull request #2701 from alco/escript-protocol-consolidation
Escript protocol consolidation
2014-08-23 23:24:48 +03:00
Alexei Sholik 64f3760b19 Add missing fixtures for escript test 2014-08-23 23:16:29 +03:00
José Valim 0b38bc050f Demonitor and flush when appropriate 2014-08-23 15:56:38 -04:00
José Valim 9431338654 Add timeouts to ExUnit, closes #2660 2014-08-23 15:55:33 -04:00
José Valim 88be750a32 import_config/1 now accepts wildcards 2014-08-23 15:14:12 -04:00
José Valim 032c8b8291 Update CHANGELOG 2014-08-23 14:42:52 -04:00
José Valim 2d90d8ac60 Remove code duplication on watcher 2014-08-23 14:38:28 -04:00
José Valim a8b2cb99ee Use to_string when possible on Logger.log 2014-08-23 14:34:51 -04:00
José Valim 68274311fb Merge pull request #2712 from fishcakez/logger_rc
Fix logger watcher race condition
2014-08-23 14:34:05 -04:00
José Valim 88f84f80b5 Merge pull request #2713 from fishcakez/logger_eh_racecon
Fix race condition in Logger.ErrorHandlerTest
2014-08-23 14:32:48 -04:00
James Fish 2bf898e8fe Fix race condition in Logger.ErrorHandlerTest
When crashing the error logger handler, the handler's watcher could log
its message before Logger was flushed, causing the watcher's log
message to appear in the first, rather than second, capture_log.
2014-08-23 19:20:06 +01:00
José Valim d45f451943 Allow configurable timeout and catch exits on logger 2014-08-23 14:09:11 -04:00
James Fish c0a075724f Fix logger watcher race condition
Previously a backend could be removed from logger and the watcher would
not have exited before the remove_backend/1 call returned. This meant
that a new watcher could not always be started because the child
(watcher) might still exist but be about to die.
2014-08-23 18:59:13 +01:00
José Valim f1a39375c9 Do not crash the caller when Logger is not available
Instead, return :ok | {:error, :not_available} and leave it up
to the caller to decide.
2014-08-23 13:43:58 -04:00
Alexei Sholik b9c1b22163 Add protocol consolidation support to escript.build. Closes #2699 2014-08-23 20:40:56 +03:00
José Valim be75e3be17 Purge in memory in modules before consolidating, closes #2700 2014-08-23 13:32:19 -04:00
José Valim 9793caa1ef Ensure we automatically load deps on compile 2014-08-23 12:22:38 -04:00
José Valim 0792fe2c52 Merge pull request #2708 from alco/typo
Fix a typo in Mix.Project.compile_path doc
2014-08-22 12:02:31 -04:00
Alexei Sholik 4408aa3450 Fix a typo in Mix.Project.compile_path doc 2014-08-22 18:33:24 +03:00
Eric Meadows-Jönsson e4ba8d5d21 Mix should not depend directly on Logger 2014-08-22 15:56:25 +02:00
Eric Meadows-Jönsson 689f14a867 Always compile project if task cannot be found
Closes #2704.
2014-08-21 15:26:58 +02:00
Eric Meadows-Jönsson aa5b414e8e Merge pull request #2703 from Kosmas/master
Update CONTRIBUTING.md
2014-08-21 12:19:11 +02:00
Kosmas Chatzimichalis 07467a733b Update CONTRIBUTING.md
Corrected 'should be send' to 'should be sent'
2014-08-21 11:34:34 +02:00
Alexei Sholik 74391b793c Merge pull request #2687 from alco/mix-language-setting
Mix language setting
2014-08-20 20:56:20 +03:00
Alexei Sholik f0b8bc1756 Use common code for Elixir and Erlang escripts 2014-08-20 20:38:24 +03:00
José Valim 3e54b95bf2 Fix the build 2014-08-20 11:30:30 -04:00
José Valim 313d26cb3b Use task start to get better reports 2014-08-20 10:28:11 -04:00
José Valim 44b85be014 Add Task.start/1 and Task.start/3 2014-08-20 10:27:23 -04:00
José Valim e344b1f9d8 Add a small note to UndefinedFunctionError when the module is not available 2014-08-20 10:22:36 -04:00
Eric Meadows-Jönsson f5f4205969 Merge pull request #2698 from Kosmas/master
Update CONTRIBUTING.md
2014-08-20 15:08:20 +02:00
Kosmas Chatzimichalis 538c717ded Update CONTRIBUTING.md
renamed 'issues tracker' to 'issue tracker' for consistency
2014-08-20 14:54:50 +02:00
Eric Meadows-Jönsson 8dd81e661a Merge pull request #2697 from hamiltop/patch-1
Fix docs (missing indentation in examples)
2014-08-20 11:24:32 +02:00
hamiltop 26b61b98f9 Fix docs (missing indentation in examples) 2014-08-19 20:28:24 -07:00
Eric Meadows-Jönsson ab9450d92f Don't print the underlying type for @opaque
Closes #2612
2014-08-19 17:14:30 +02:00
Eric Meadows-Jönsson a4a23b9a93 Fix Enum.traverse deprecated warning doctest 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 2f83ea5a7f Wrap erlang macros in () 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson 7940d223ec Move elixir.hrl to src/ 2014-08-19 17:00:58 +02:00
Eric Meadows-Jönsson c6fa951520 Better error message on nbsp 2014-08-19 17:00:58 +02:00
José Valim e5c8194a70 Rename :add_stream_handler to :add_process_handler 2014-08-19 09:55:26 -04:00
Eric Meadows-Jönsson 4a7e3dd11f Document alias arguments 2014-08-19 14:16:19 +02:00
José Valim 638fccb79d Remove duration from GenEvent.Stream 2014-08-19 07:34:49 -04:00
José Valim e9d84ec43b Allow gen_event_EXIT notifications to be disabled for streams 2014-08-19 07:07:23 -04:00
Eric Meadows-Jönsson bb35b9afb1 Include makefile in precompiled zip 2014-08-18 23:11:27 +02:00
Eric Meadows-Jönsson 1496c04592 Improve error message for invalid typespec 2014-08-18 23:08:26 +02:00
José Valim 3e9725a5a8 Make tests that remove the logging backend sync 2014-08-18 16:51:38 -04:00
José Valim 1565ec7658 Update CHANGELOG 2014-08-18 16:43:17 -04:00
José Valim f1aa2f01c7 Merge pull request #2695 from elixir-lang/jv-ge
GenEvent++
2014-08-18 16:33:11 -04:00
José Valim 7284542116 Split GenEvent.Stream into its own file 2014-08-18 16:28:25 -04:00
José Valim 4f17e8fd1c Also tag notify messages 2014-08-18 16:20:25 -04:00
José Valim f536507b9d Make the mode a property of the sender 2014-08-18 15:38:55 -04:00
José Valim 592300a0f0 Ensure Logger watcher monitors event manager 2014-08-18 09:18:28 -04:00
Alexei Sholik 28b1bd16de Document the :language setting 2014-08-18 16:12:15 +03:00
José Valim 19fdd84f80 Update docs 2014-08-17 19:48:50 -04:00
José Valim 631e2a051a Document lack of guaranteed on monitored handlers 2014-08-17 18:44:06 -04:00
José Valim df0f7b5f95 Fix failures in logger 2014-08-17 18:36:59 -04:00
José Valim a0dfc4160e Ensure proper whereis expansion 2014-08-17 18:28:14 -04:00
José Valim ee8331023e Do not add duplicated handlers 2014-08-17 16:55:11 -04:00
José Valim a2ee71f5ab Implement some of fishcakez feedback 2014-08-17 13:18:55 -04:00
José Valim 60163194e2 Improve coverage for GenEvent 2014-08-17 09:20:02 -04:00
José Valim ba37682300 Merge pull request #2691 from herenowcoder/patch-1
fix doc typo in Macro.expand/2
2014-08-17 08:57:53 -04:00
Wojciech Kaczmarek 15265b9352 fix doc typo in Macro.expand/2 2014-08-17 14:55:25 +02:00
Alexei Sholik 0a929310de Support building Erlang-based escripts in mix escript.build 2014-08-17 15:19:42 +03:00
José Valim a8670c1cfa Do not pre-compile a project if an alias was found
Closes #2689
2014-08-17 06:10:20 -04:00
Alexei Sholik ebd18d45fa Only add :elixir application for Elixir projects in compile.app task 2014-08-17 01:22:33 +03:00
José Valim ccbfec611a Merge pull request #2684 from alco/mix-compile-erlang
Properly handle compilation errors in Erlang compiler
2014-08-16 17:05:00 -04:00
Alexei Sholik 98a0999c2e Properly handle compilation errors in Erlang compiler 2014-08-16 23:45:44 +03:00
José Valim e504142d3a Merge pull request #2686 from billgathen/patch-1
Docs: Using autocomplete for function discovery in IEx
2014-08-16 15:58:01 -04:00
José Valim c04e13169f Deprecate Collectable.empty and Enum.traverse
The current implementation has three issues:

1. It is slow as we can't reuse the data structure but rather
   build a new one from scratch

2. Due to 1., trees can't be traversed

3. empty/1 is unable to deal with resources
2014-08-16 15:46:58 +02:00
José Valim 96a4bff67b Use the new resource acc on GenEvent.stream/2 2014-08-16 15:41:30 +02:00
José Valim 46de5ea462 Make Stream.resource/3 more like Stream.transform/2
With this change, we allow the accumulator to be passed
to after_fun once iteration is done.

This, however, introduces a deprecation on the old
format used by Stream.resource/3.
2014-08-16 15:35:56 +02:00
Bill Gathen 22257b8342 Using autocomplete for function discovery
I had to go to the mailing list to discover this trick. Knowing it's autocomplete supplying the information makes it obvious in hindsight, but I had it in my head that it would be part of the h() command. It might also be worth adding a mention of autocomplete to the h(IEx) page, as well, but since this is likely first help page a new user would come across, this may be enough.
2014-08-16 08:59:18 -04:00
José Valim a0be9fe272 Improve Logger formatter defaults
* Make colors in the docs consistent with the ones in the code
* Add new lines before and after each message
2014-08-15 18:30:30 +02:00
José Valim 1fd5a2a38e Use monitored handlers instead of linked ones 2014-08-15 18:02:47 +02:00
José Valim 13aec18bd5 Update docs 2014-08-15 13:57:22 +02:00
José Valim a85938d092 Fix the build 2014-08-15 13:54:08 +02:00
José Valim 0d4af57abe Port GenEvent to Elixir for upcoming extensions 2014-08-15 13:16:14 +02:00
José Valim e00db53112 Talk about correctness first, performance later 2014-08-14 22:09:00 +02:00
José Valim b941618a10 Port the :gen_event client API to Elixir
The only difference is that notify is made similar to
GenServer's cast where an unknown atom name does not
trigger an error.
2014-08-13 18:20:10 +02:00
Eric Meadows-Jönsson d895c762a1 Add --force option to 'mix local.rebar' 2014-08-13 17:11:28 +02:00
Eric Meadows-Jönsson 427c4b01c9 Ensure load_tasks return no duplicates 2014-08-13 17:10:40 +02:00
Eric Meadows-Jönsson 9d5a728cb8 Output mix version on 'mix -v' 2014-08-13 17:10:40 +02:00
José Valim 1ed99d4ac7 Improve error message for undefined struct 2014-08-13 09:19:26 +02:00
José Valim ceb17fac7d Use GenServer when possible 2014-08-12 23:22:51 +02:00
José Valim c638f45542 Merge pull request #2674 from koudelka/unquote-doc-fix
Minor unquote/1 doc fix.
2014-08-12 21:40:02 +02:00
Michael Shapiro c09905d537 Minor unquote/1 doc fix. 2014-08-12 12:23:08 -07:00
José Valim 117a428030 Merge pull request #2668 from reset/fix-nocolor
fix --no-color / --color option for `mix test`
2014-08-12 19:32:33 +02:00
Jamie Winsor 2ada7c40d7 fix --no-color / --color option for mix test 2014-08-12 10:32:10 -07:00
José Valim cd162bf3d6 Do not inherit bugs on GenServer calls and casts
Prior to this change, we were inheriting OTP bug where
bad arguments in call were treated as exits.

We also fixed the bug in cast where messages could be
delivered out of order. It is preferrable to wait on
connection.
2014-08-12 17:30:25 +02:00
José Valim e6e71675ac Make a distinction between eol and ; 2014-08-12 16:27:45 +02:00
José Valim c54e0caf6f Merge pull request #2670 from chyndman/cygpath-quotes
Use quotes to avoid space-in-path issues with cygpath
2014-08-12 09:34:41 +02:00
José Valim b64552780e Merge pull request #2671 from lexmag/no-purge
No need in some purges in `Kernel.WarningTest`
2014-08-12 09:34:25 +02:00
Eric Meadows-Jönsson add8c66e82 Merge pull request #2673 from alexrp/master
Clarify match?/2 docs.
2014-08-12 02:02:35 +02:00
Alex Rønne Petersen aecbd2c16a Clarify match?/2 docs. 2014-08-12 01:54:08 +02:00
Aleksei Magusev 32a649c1cd No need for Stream. prefix 2014-08-12 03:31:03 +04:00
Aleksei Magusev a1e657cb75 No need in some purges in Kernel.WarningTest 2014-08-12 03:06:48 +04:00
Chris Hyndman 6bb4c4de03 Use quotes to avoid space-in-path issues with cygpath 2014-08-11 15:13:50 -07:00
José Valim ea5b56d434 Merge pull request #2667 from MSch/fix-new-config-exs
Fix example in config/config.exs that's created for new projects.
2014-08-11 20:51:03 +02:00
Martin Schürrer a4082748cf Fix example in config/config.exs that's created for new projects. 2014-08-11 20:43:36 +02:00
José Valim 4d763846c3 Ensure code escape \ works in IEx 2014-08-11 10:05:24 +02:00
José Valim d56cc88f5f Start v0.15.2-dev 2014-08-11 10:05:24 +02:00
José Valim 5e27b7b72b Merge pull request #2666 from hamiltop/stream_cycle_performance
Add special case for lists in Stream.cycle
2014-08-11 09:53:38 +02:00
Peter Hamilton 260d6d16ab Add special case for lists in Stream.cycle 2014-08-11 00:38:53 -07:00
José Valim 1a82f126a4 Merge pull request #2664 from hamiltop/add_timer_and_interval_streams
Add Stream.interval/1 and Stream.timer/1
2014-08-11 08:16:51 +02:00
Peter Hamilton 3cf3eee2ea Clean up interval/1 and timer/1 docs 2014-08-10 16:03:43 -07:00
Peter Hamilton 9e8a41b2d3 Add Stream.interval/1 and Stream.timer/1 2014-08-10 11:25:53 -07:00
Johnny Winn 5f853f8e71 Implement flag to clean unused dependencies
This addresses issues #2569
https://github.com/elixir-lang/elixir/issues/2569
2014-08-03 13:42:51 -04:00
189 changed files with 3708 additions and 1519 deletions
+62
View File
@@ -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
View File
@@ -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).
+2 -2
View File
@@ -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
View File
@@ -1 +1 @@
0.15.1
1.0.0-rc2
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -44
View File
@@ -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
View File
@@ -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.
+16 -7
View File
@@ -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
View File
@@ -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
File diff suppressed because it is too large Load Diff
+173
View File
@@ -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
+57 -7
View File
@@ -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 """
+3 -3
View File
@@ -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
+8 -6
View File
@@ -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
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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
+6 -6
View File
@@ -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
+1 -1
View File
@@ -408,7 +408,7 @@ defmodule Kernel.CLI do
end
defp wrapper(fun) do
fun.()
_ = fun.()
:ok
end
+34 -30
View File
@@ -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
+4 -32
View File
@@ -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)
+5 -1
View File
@@ -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
+1 -1
View File
@@ -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}
+2 -2
View File
@@ -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
+4 -2
View File
@@ -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)
+18 -3
View File
@@ -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
+5 -7
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+7 -7
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -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
+25 -9
View File
@@ -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
+3 -3
View File
@@ -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)))
+10 -1
View File
@@ -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.
+24
View File
@@ -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.
+4
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)).
+8 -5
View File
@@ -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
+11 -2
View File
@@ -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]).
+2 -1
View File
@@ -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)
-2
View File
@@ -522,7 +522,5 @@ deprecation_message(Warning, Message) ->
Message -> Warning ++ ", " ++ Message
end.
deprecation('Elixir.Module', 'function', 3) ->
"use :erlang.make_fun/3";
deprecation(_, _, _) ->
false.
+5 -5
View File
@@ -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)).
+2
View File
@@ -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;
+3 -3
View File
@@ -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])
+38 -30
View File
@@ -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'].
+4
View File
@@ -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);
+31 -11
View File
@@ -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
-2
View File
@@ -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
-5
View File
@@ -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]
+3 -1
View File
@@ -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
+458 -367
View File
@@ -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
+6
View File
@@ -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
+14 -14
View File
@@ -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
+73 -3
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
+10 -14
View File
@@ -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
+1 -7
View File
@@ -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]
+6
View File
@@ -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
+28 -28
View File
@@ -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
+4 -4
View File
@@ -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)
-1
View File
@@ -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
+91 -9
View File
@@ -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, [])]
+3 -3
View File
@@ -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()
+32 -1
View File
@@ -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
View File
@@ -1,6 +1,5 @@
-module(atom_test).
-export([kv/1]).
-include("elixir.hrl").
-include_lib("eunit/include/eunit.hrl").
eval(Content) ->
+1 -2
View File
@@ -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").
-3
View File
@@ -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").
+2 -2
View File
@@ -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
View File
@@ -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,
+8 -6
View File
@@ -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"),
+9
View File
@@ -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
+36 -5
View File
@@ -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 """
+5 -1
View File
@@ -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
+4 -3
View File
@@ -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
+1
View File
@@ -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
+10 -1
View File
@@ -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)
+7 -7
View File
@@ -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
+27 -3
View File
@@ -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
+2 -2
View File
@@ -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
+15 -1
View File
@@ -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
+14
View File
@@ -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
View File
@@ -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 """
+1 -1
View File
@@ -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)
+5
View File
@@ -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