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/initial_version Commit: a98e66c0635f35d369c3ac02b73c2661ac1ace8c Parents: 2f937ca Author: ILYA Khlopotov <[email protected]> Authored: Thu Mar 17 10:14:27 2016 -0700 Committer: ILYA Khlopotov <[email protected]> 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].
