| % 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/1, |
| 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) -> |
| setup(Chain, [], []). |
| |
| 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. |