You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@couchdb.apache.org by ii...@apache.org on 2016/03/22 12:13:07 UTC

[2/4] couchdb-erlang-tests git commit: Initial version

Initial version


Project: http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/commit/a98e66c0
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/tree/a98e66c0
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/diff/a98e66c0

Branch: refs/heads/master
Commit: a98e66c0635f35d369c3ac02b73c2661ac1ace8c
Parents: 2f937ca
Author: ILYA Khlopotov <ii...@ca.ibm.com>
Authored: Thu Mar 17 10:14:27 2016 -0700
Committer: ILYA Khlopotov <ii...@ca.ibm.com>
Committed: Thu Mar 17 10:26:19 2016 -0700

----------------------------------------------------------------------
 include/couch_tests.hrl        |  28 +++++
 rebar.config                   |  20 ++++
 src/couch_tests.app.src        |  18 +++
 src/couch_tests.erl            | 224 ++++++++++++++++++++++++++++++++++++
 test/couch_tests_app_tests.erl | 102 ++++++++++++++++
 5 files changed, 392 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/blob/a98e66c0/include/couch_tests.hrl
----------------------------------------------------------------------
diff --git a/include/couch_tests.hrl b/include/couch_tests.hrl
new file mode 100644
index 0000000..41d7e8d
--- /dev/null
+++ b/include/couch_tests.hrl
@@ -0,0 +1,28 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-record(couch_tests_ctx, {
+    chain = [],
+    args = [],
+    opts = [],
+    started_apps = [],
+    stopped_apps = [],
+    dict = dict:new()
+}).
+
+-record(couch_tests_fixture, {
+    module,
+    id,
+    setup,
+    teardown,
+    apps = []
+}).

http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/blob/a98e66c0/rebar.config
----------------------------------------------------------------------
diff --git a/rebar.config b/rebar.config
new file mode 100644
index 0000000..a08b22f
--- /dev/null
+++ b/rebar.config
@@ -0,0 +1,20 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+{erl_opts, [debug_info,
+    {src_dirs, ["src", "setups"]}]}.
+
+{eunit_opts, [verbose]}.
+
+{cover_enabled, true}.
+
+{cover_print_enabled, true}.

http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/blob/a98e66c0/src/couch_tests.app.src
----------------------------------------------------------------------
diff --git a/src/couch_tests.app.src b/src/couch_tests.app.src
new file mode 100644
index 0000000..ea243eb
--- /dev/null
+++ b/src/couch_tests.app.src
@@ -0,0 +1,18 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+{application, couch_tests, [
+    {description, "Testing infrastructure for Apache CouchDB"},
+    {vsn, git},
+    {registered, []},
+    {applications, [kernel, stdlib]}
+]}.

http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/blob/a98e66c0/src/couch_tests.erl
----------------------------------------------------------------------
diff --git a/src/couch_tests.erl b/src/couch_tests.erl
new file mode 100644
index 0000000..3d53d31
--- /dev/null
+++ b/src/couch_tests.erl
@@ -0,0 +1,224 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_tests).
+
+-export([
+    new/4,
+    setup/3,
+    teardown/1
+]).
+
+-export([
+    start_applications/2,
+    stop_applications/2
+]).
+
+-export([
+    get/2,
+    get_state/2,
+    set_state/3
+]).
+
+-export([
+    validate/1,
+    validate_and_report/1
+]).
+
+-export([
+    validate_fixture/1,
+    validate_fixture/3
+]).
+
+-include_lib("couch_tests/include/couch_tests.hrl").
+
+%% ------------------------------------------------------------------
+%% API functions definitions
+%% ------------------------------------------------------------------
+
+new(Module, FixtureId, Setup, Teardown) ->
+    #couch_tests_fixture{
+        module = Module,
+        id = FixtureId,
+        setup = Setup,
+        teardown = Teardown
+    }.
+
+setup(Chain, Args, Opts) ->
+    Ctx = #couch_tests_ctx{chain = Chain, args = Args, opts = Opts},
+    do_setup(Chain, Ctx, []).
+
+teardown(#couch_tests_ctx{chain = Chain} = Ctx0) ->
+    Ctx1 = lists:foldl(fun do_teardown/2, Ctx0, lists:reverse(Chain)),
+    ToStop = lists:reverse(Ctx1#couch_tests_ctx.started_apps),
+    stop_applications(ToStop, Ctx1).
+
+start_applications(Apps, Ctx) when is_list(Apps) ->
+    #couch_tests_ctx{
+        started_apps = Running
+    } = Ctx,
+    Started = start_applications(Apps),
+    Ctx#couch_tests_ctx{started_apps = Running ++ Started}.
+
+stop_applications(Apps, Ctx) when is_list(Apps) ->
+    #couch_tests_ctx{
+        started_apps = Started,
+        stopped_apps = Stopped
+    } = Ctx,
+    JustStopped = stop_applications(Apps -- Stopped),
+    Ctx#couch_tests_ctx{
+        started_apps = Started -- JustStopped,
+        stopped_apps = remove_duplicates(Stopped ++ JustStopped)
+    }.
+
+get_state(#couch_tests_fixture{module = Module, id = Id}, Ctx) ->
+    dict:fetch({Module, Id}, Ctx#couch_tests_ctx.dict).
+
+set_state(Fixture, Ctx, State) ->
+    #couch_tests_fixture{
+        module = Module,
+        id = Id
+    } = Fixture,
+    Dict = dict:store({Module, Id}, State, Ctx#couch_tests_ctx.dict),
+    Ctx#couch_tests_ctx{dict = Dict}.
+
+get(started_apps, #couch_tests_ctx{started_apps = Started}) ->
+    Started;
+get(stopped_apps, #couch_tests_ctx{stopped_apps = Stopped}) ->
+    Stopped.
+
+validate_fixture(#couch_tests_fixture{} = Fixture) ->
+    validate_fixture(Fixture, [], []).
+
+validate_fixture(#couch_tests_fixture{} = Fixture0, Args, Opts) ->
+    AppsBefore = applications(),
+    #couch_tests_ctx{chain = [Fixture1]} = Ctx0 = setup([Fixture0], Args, Opts),
+    AppsWhile = applications(),
+    Ctx1 = teardown(Ctx0),
+    AppsAfter = applications(),
+    AppsStarted = lists:usort(AppsWhile -- AppsBefore),
+    FixtureApps = lists:usort(Fixture1#couch_tests_fixture.apps),
+    StartedAppsBeforeTeardown = lists:usort(Ctx0#couch_tests_ctx.started_apps),
+    StoppedAppsAfterTeardown = lists:usort(Ctx1#couch_tests_ctx.stopped_apps),
+    StartedAppsAfterTeardown = Ctx1#couch_tests_ctx.started_apps,
+
+    validate_and_report([
+         {equal, "Expected applications before calling fixture (~p) "
+            "to be equal to applications after its calling",
+            AppsBefore, AppsAfter},
+         {equal, "Expected list of started applications (~p) "
+            "to be equal to #couch_tests_fixture.apps (~p)",
+            AppsStarted, FixtureApps},
+         {equal, "Expected list of started applications (~p) "
+            "to be equal to #couch_tests_ctx.started_apps (~p)",
+            AppsStarted, StartedAppsBeforeTeardown},
+         {equal, "Expected list of stopped applications (~p) "
+            "to be equal to #couch_tests_ctx.stopped_apps (~p)",
+            AppsStarted, StoppedAppsAfterTeardown},
+         {equal, "Expected empty list ~i of #couch_tests_ctx.started_apps (~p) "
+            "after teardown", [], StartedAppsAfterTeardown}
+    ]).
+
+validate(Sheet) ->
+    case lists:foldl(fun do_validate/2, [], Sheet) of
+        [] -> true;
+        Errors -> Errors
+    end.
+
+validate_and_report(Sheet) ->
+    case validate(Sheet) of
+        true ->
+            true;
+        Errors ->
+            [io:format(user, "    ~s~n", [Err]) || Err <- Errors],
+            false
+    end.
+
+%% ------------------------------------------------------------------
+%% Helper functions definitions
+%% ------------------------------------------------------------------
+
+
+do_setup([#couch_tests_fixture{setup = Setup} = Fixture | Rest], Ctx0, Acc) ->
+    Ctx1 = Ctx0#couch_tests_ctx{started_apps = []},
+    #couch_tests_ctx{started_apps = Apps} = Ctx2 = Setup(Fixture, Ctx1),
+    Ctx3 = Ctx2#couch_tests_ctx{started_apps = []},
+    do_setup(Rest, Ctx3, [Fixture#couch_tests_fixture{apps = Apps} | Acc]);
+do_setup([], Ctx, Acc) ->
+    Apps = lists:foldl(fun(#couch_tests_fixture{apps = A}, AppsAcc) ->
+        A ++ AppsAcc
+    end, [], Acc),
+    Ctx#couch_tests_ctx{chain = lists:reverse(Acc), started_apps = Apps}.
+
+do_teardown(Fixture, Ctx0) ->
+    #couch_tests_fixture{teardown = Teardown, apps = Apps} = Fixture,
+    #couch_tests_ctx{} = Ctx1 = Teardown(Fixture, Ctx0),
+    stop_applications(lists:reverse(Apps), Ctx1).
+
+start_applications(Apps) ->
+    do_start_applications(Apps, []).
+
+do_start_applications([], Acc) ->
+    lists:reverse(Acc);
+do_start_applications([App | Apps], Acc) ->
+    case application:start(App) of
+    {error, {already_started, _}} ->
+        do_start_applications(Apps, Acc);
+    {error, {not_started, Dep}} ->
+        do_start_applications([Dep, App | Apps], Acc);
+    {error, {not_running, Dep}} ->
+        do_start_applications([Dep, App | Apps], Acc);
+    ok ->
+        do_start_applications(Apps, [App | Acc])
+    end.
+
+stop_applications(Apps) ->
+    do_stop_applications(Apps, []).
+
+do_stop_applications([], Acc) ->
+    lists:reverse(Acc);
+do_stop_applications([App | Apps], Acc) ->
+    case application:stop(App) of
+    {error, _} ->
+        do_stop_applications(Apps, Acc);
+    ok ->
+        do_stop_applications(Apps, [App | Acc])
+    end.
+
+remove_duplicates([])    ->
+    [];
+remove_duplicates([H | T]) ->
+    [H | [X || X <- remove_duplicates(T), X /= H]].
+
+applications() ->
+    lists:usort([App || {App, _, _} <-application:which_applications()]).
+
+do_validate({equal, _Message, Arg, Arg}, Acc) ->
+    Acc;
+do_validate({equal, Message, Arg1, Arg2}, Acc) ->
+    [io_lib:format(Message, [Arg1, Arg2]) | Acc].
+
+
+%% ------------------------------------------------------------------
+%% Tests
+%% ------------------------------------------------------------------
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+validate_test() ->
+    ?assertMatch("1 == 2", lists:flatten(validate([{equal, "~w == ~w", 1, 2}]))),
+    ?assertMatch("2", lists:flatten(validate([{equal, "~i~w", 1, 2}]))),
+    ?assert(validate([{equal, "~w == ~w", 1, 1}])),
+    ok.
+
+-endif.

http://git-wip-us.apache.org/repos/asf/couchdb-erlang-tests/blob/a98e66c0/test/couch_tests_app_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_tests_app_tests.erl b/test/couch_tests_app_tests.erl
new file mode 100644
index 0000000..1acdec7
--- /dev/null
+++ b/test/couch_tests_app_tests.erl
@@ -0,0 +1,102 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_tests_app_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+setup() ->
+    [mock(application)].
+
+teardown(Mocks) ->
+    [unmock(Mock) || Mock <- Mocks].
+
+%% ------------------------------------------------------------------
+%% Test callbacks definitions
+%% ------------------------------------------------------------------
+
+dummy_setup() ->
+    couch_tests:new(?MODULE, dummy_setup,
+        fun(_Fixture, Ctx) -> Ctx end,
+        fun(_Fixture, Ctx) -> Ctx end).
+
+
+setup1(Arg1) ->
+    couch_tests:new(?MODULE, setup1,
+        fun(Fixture, Ctx0) ->
+           Ctx1 = couch_tests:start_applications([asn1], Ctx0),
+           couch_tests:set_state(Fixture, Ctx1, {Arg1})
+        end,
+        fun(_Fixture, Ctx) ->
+           couch_tests:stop_applications([asn1], Ctx)
+        end).
+
+setup2(Arg1, Arg2) ->
+    couch_tests:new(?MODULE, setup2,
+        fun(Fixture, Ctx0) ->
+           Ctx1 = couch_tests:start_applications([public_key], Ctx0),
+           couch_tests:set_state(Fixture, Ctx1, {Arg1, Arg2})
+        end,
+        fun(Fixture, Ctx) ->
+           Ctx
+        end).
+
+
+couch_tests_test_() ->
+    {
+        "couch_tests tests",
+        {
+            foreach, fun setup/0, fun teardown/1,
+            [
+                {"chained setup", fun chained_setup/0}
+            ]
+        }
+    }.
+
+
+chained_setup() ->
+    ?assert(meck:validate(application)),
+    ?assertEqual([], history(application, start)),
+    Ctx0 = couch_tests:setup([
+        setup1(foo),
+        dummy_setup(),
+        setup2(bar, baz)
+    ], [], []),
+
+    ?assertEqual([asn1, public_key], history(application, start)),
+    ?assertEqual([asn1, public_key], couch_tests:get(started_apps, Ctx0)),
+    ?assertEqual([], couch_tests:get(stopped_apps, Ctx0)),
+
+    Ctx1 = couch_tests:teardown(Ctx0),
+
+    ?assertEqual([public_key, asn1], history(application, stop)),
+    ?assertEqual([], couch_tests:get(started_apps, Ctx1)),
+    ?assertEqual([public_key, asn1], couch_tests:get(stopped_apps, Ctx1)),
+
+    ok.
+
+mock(application) ->
+    ok = meck:new(application, [unstick, passthrough]),
+    ok = meck:expect(application, start, fun(_) -> ok end),
+    ok = meck:expect(application, stop, fun(_) -> ok end),
+    meck:validate(application),
+    application.
+
+unmock(application) ->
+    catch meck:unload(application).
+
+history(Module, Function) ->
+    Self = self(),
+    [A || {Pid, {M, F, [A]}, _Result} <- meck:history(Module)
+        , Pid =:= Self
+        , M =:= Module
+        , F =:= Function].