Merge remote-tracking branch 'github/pr/15'
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..236bcb5
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,34 @@
+language: erlang
+
+otp_release:
+  - 18.1
+  - 17.5
+  - R16B03-1
+
+matrix:
+  allow_failures:
+    - otp_release: R16B03-1
+
+sudo: false
+
+addons:
+  apt:
+    packages:
+      - libmozjs185-dev
+
+before_install:
+  - git clone https://github.com/apache/couchdb
+
+before_script:
+  - cd couchdb
+  - ./configure --disable-docs --disable-fauxton
+  - cp -r ../!(couchdb) ./src/couch_epi
+  - make
+
+script:
+  - ./bin/rebar setup_eunit
+  - BUILDDIR=`pwd` ./bin/rebar -r eunit apps=couch_epi skip_deps=couch_log
+  - ./bin/rebar -r build-plt apps=couch_epi skip_deps=couch_log
+  - ./bin/rebar -r dialyze apps=couch_epi skip_deps=couch_log
+
+cache: apt
diff --git a/README.md b/README.md
index 88390d0..5b5223f 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,10 @@
 Notes:
 
   - `concurrent` is incompatible with `pipe`
+  - if there are multiple plugins providing same service they will be called in the order
+    they listed in application:get_env(couch_epi, plugins)
+  - if the same plugin provides multiple implementations of the same service
+    the order is as defined in providers callback
 
 ## decide functionality
 
diff --git a/src/couch_epi.erl b/src/couch_epi.erl
index 0ccef90..ddb3c48 100644
--- a/src/couch_epi.erl
+++ b/src/couch_epi.erl
@@ -37,7 +37,8 @@
     key/0,
     handle/0,
     plugin_id/0,
-    data_spec/0
+    data_spec/0,
+    apply_opts/0
 ]).
 
 -type app() :: atom().
@@ -128,7 +129,7 @@
     couch_epi_data_gen:subscribers(Handle).
 
 -spec apply(Handle :: handle(), ServiceId :: atom(), Function :: atom(),
-    Args :: [term()], Opts :: apply_opts()) -> ok.
+    Args :: [term()], Opts :: apply_opts()) -> [any()].
 
 apply(Handle, ServiceId, Function, Args, Opts) when Handle /= undefined ->
     couch_epi_functions_gen:apply(Handle, ServiceId, Function, Args, Opts).
diff --git a/src/couch_epi_functions.erl b/src/couch_epi_functions.erl
index 34d1a06..ac93739 100644
--- a/src/couch_epi_functions.erl
+++ b/src/couch_epi_functions.erl
@@ -43,6 +43,7 @@
     [{M, M:module_info(exports) -- Blacklist} || M <- Modules].
 
 group(KV) ->
-    dict:to_list(lists:foldr(fun({K,V}, D) ->
+    Dict = lists:foldr(fun({K,V}, D) ->
         dict:append_list(K, V, D)
-    end, dict:new(), KV)).
+    end, dict:new(), KV),
+    [{K, lists:reverse(V)} || {K, V} <- dict:to_list(Dict)].
diff --git a/src/couch_epi_functions_gen.erl b/src/couch_epi_functions_gen.erl
index df1d916..7408593 100644
--- a/src/couch_epi_functions_gen.erl
+++ b/src/couch_epi_functions_gen.erl
@@ -46,7 +46,7 @@
     apply(get_handle(ServiceId), ServiceId, Function, Args, Opts).
 
 -spec apply(Handle :: atom(), ServiceId :: atom(), Function :: atom(),
-    Args :: [term()], Opts :: couch_epi:apply_opts()) -> ok.
+    Args :: [term()], Opts :: couch_epi:apply_opts()) -> [any()].
 
 apply(Handle, _ServiceId, Function, Args, Opts) ->
     DispatchOpts = parse_opts(Opts),
diff --git a/src/couch_epi_plugin.erl b/src/couch_epi_plugin.erl
index 05aa591..0d76465 100644
--- a/src/couch_epi_plugin.erl
+++ b/src/couch_epi_plugin.erl
@@ -109,9 +109,10 @@
         [{{kind(), key()}, [{couch_epi:app(), #couch_epi_spec{}}]}].
 
 group_specs(Specs) ->
-    group(
+    Grouped = group(
         [{{Kind, Key}, group([{App, Spec}])}
-            || #couch_epi_spec{kind = Kind, key = Key, app = App} = Spec <- Specs]).
+            || #couch_epi_spec{kind = Kind, key = Key, app = App} = Spec <- Specs]),
+    [{K, lists:reverse(V)} || {K, V} <- Grouped].
 
 
 group(KV) ->
@@ -168,7 +169,8 @@
         providers() ->
             [
                 {chttpd_handlers, foo_provider},
-                {bar_handlers, bar_provider}
+                {bar_handlers, bar_provider1},
+                {bar_handlers, bar_provider2}
             ].
 
         services() ->
@@ -228,6 +230,35 @@
 generate_modules(Kind, Providers) ->
     [generate_module(P, Kind(P)) || P <- Providers].
 
+provider_modules_order_test() ->
+    [ok,ok] = generate_modules(fun plugin_module/1, [foo_epi, bar_epi]),
+    ok = application:set_env(couch_epi, plugins, [foo_epi, bar_epi]),
+    Expected = [
+        {foo, bar_provider1},
+        {foo, bar_provider2},
+        {bar, bar_provider}
+    ],
+
+    Defs = definitions(providers, bar_handlers),
+    Result = [{App, V} || {App, #couch_epi_spec{value = V}} <- Defs],
+    Tests = lists:zip(Expected, Result),
+    [?assertEqual(Expect, Result) || {Expect, Result} <- Tests],
+    ok.
+
+providers_order_test() ->
+    [ok,ok] = generate_modules(fun plugin_module/1, [foo_epi, bar_epi]),
+    Expected = [
+        {foo, bar_provider1},
+        {foo, bar_provider2},
+        {bar, bar_provider}
+    ],
+    AllDefs = grouped_definitions([foo_epi, bar_epi]),
+    {_, Defs} = lists:keyfind({providers, bar_handlers}, 1, AllDefs),
+    Result = [{App, V} || {App, #couch_epi_spec{value = V}} <- Defs],
+    Tests = lists:zip(Expected, Result),
+    [?assertEqual(Expect, Result) || {Expect, Result} <- Tests],
+    ok.
+
 definitions_test() ->
     Expected = lists:sort([
         #couch_epi_spec{
@@ -275,7 +306,17 @@
             kind = providers,
             options = [],
             key = bar_handlers,
-            value = bar_provider,
+            value = bar_provider1,
+            codegen = couch_epi_functions_gen,
+            type = couch_epi_functions
+        },
+        #couch_epi_spec{
+            behaviour = foo_epi,
+            app = foo,
+            kind = providers,
+            options = [],
+            key = bar_handlers,
+            value = bar_provider2,
             codegen = couch_epi_functions_gen,
             type = couch_epi_functions
         },
diff --git a/test/couch_epi_tests.erl b/test/couch_epi_tests.erl
index fc2daa8..56733b1 100644
--- a/test/couch_epi_tests.erl
+++ b/test/couch_epi_tests.erl
@@ -86,7 +86,7 @@
 %% couch_epi_plugin behaviour
 %% ------------------------------------------------------------------
 
-plugin_module([KV, Spec]) ->
+plugin_module([KV, Spec]) when is_tuple(Spec) ->
     SpecStr = io_lib:format("~w", [Spec]),
     KVStr = "'" ++ atom_to_list(KV) ++ "'",
     "
@@ -114,7 +114,7 @@
         notify(Key, OldData, Data) ->
             couch_epi_tests:notify_cb(Key, OldData, Data, " ++ KVStr ++ ").
     ";
-plugin_module([KV]) ->
+plugin_module([KV, Provider]) when is_atom(Provider) ->
     KVStr = "'" ++ atom_to_list(KV) ++ "'",
     "
         -compile([export_all]).
@@ -122,13 +122,12 @@
         app() -> test_app.
         providers() ->
             [
-                {my_service, provider1},
-                {my_service, provider2}
+                {my_service, " ++ atom_to_list(Provider) ++ "}
             ].
 
         services() ->
             [
-                {my_service, provider1}
+                {my_service, " ++ atom_to_list(Provider) ++ "}
             ].
 
         data_providers() ->
@@ -203,7 +202,10 @@
 
     KV = start_state_storage(),
 
-    ok = start_epi([{provider_epi, plugin_module([KV])}]),
+    ok = start_epi([
+        {provider_epi1, plugin_module([KV, provider1])},
+        {provider_epi2, plugin_module([KV, provider2])}
+    ]),
 
     Pid = whereis(couch_epi:get_handle(Key)),
     Handle = couch_epi:get_handle(Key),
@@ -285,6 +287,20 @@
         }
     }.
 
+epi_providers_order_test_() ->
+    {
+        "epi providers' order test",
+        {
+            foreach,
+            fun() -> setup(functions) end,
+            fun teardown/1,
+            [
+                fun check_providers_order/1
+            ]
+        }
+    }.
+
+
 epi_reload_test_() ->
     Cases = [
         data_file,
@@ -525,6 +541,14 @@
         ?assertEqual(error, get(Ctx, is_called))
     end).
 
+check_providers_order(#ctx{handle = Handle, kv = KV, key = Key} = Ctx) ->
+    ?_test(begin
+        Result = couch_epi:apply(Handle, Key, inc, [KV, 2], [pipe]),
+        ?assertMatch([KV, 4], Result),
+        Order = [element(2, get(Ctx, K)) || K <- [inc1, inc2]],
+        ?assertEqual(Order, [3, 4]),
+        ok
+    end).
 
 %% ------------------------------------------------------------------
 %% Internal Function Definitions