Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
Initial version
  • Loading branch information
iilyak committed Mar 17, 2016
1 parent 2f937ca commit a98e66c0635f35d369c3ac02b73c2661ac1ace8c
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 0 deletions.
@@ -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 = []
}).
@@ -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}.
@@ -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]}
]}.
@@ -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.
@@ -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].

0 comments on commit a98e66c

Please sign in to comment.