%% @author Arjan Scherpenisse <arjan@miraclethings.nl>
%% @copyright 2016 Arjan Scherpenisse
%%
%% @doc Run site-specific tests in an isolated database schema
%% @end
%% Copyright 2016 Arjan Scherpenisse <arjan@miraclethings.nl>
%%
%% 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(z_sitetest).
-author("Arjan Scherpenisse <arjan@miraclethings.nl>").
-export([run/1, run/2, watch/1, unwatch/1, is_watching/1]).
-include_lib("../../include/zotonic.hrl").
-include_lib("epgsql/include/epgsql.hrl").
%% @doc Run all *_sitetest.erl tests for the given site.
run(Site) when is_atom(Site) ->
run(Site, find_sitetest_modules(Site)).
%% @doc Run the given _sitetest eunit modules for the given site.
run(Site, Modules) when is_atom(Site), is_list(Modules) ->
%% Stop the site
ok = maybe_stop_site(Site, z_sites_manager:get_site_status(Site)),
timer:sleep(500),
%% Override the site config to set the test schema
ok = configure_test_schema(Site),
%% And make sure its not there yet
ok = ensure_drop_test_schema(Site),
%% Now start the site and wait for it
ok = start_site(Site),
%% Run the tests
Result = eunit:test(Modules, [verbose]),
%% Start the site with the regular schema again
ok = unconfigure_test_schema(Site),
z_sites_manager:restart(Site),
Result.
maybe_stop_site(Site, {ok, running}) ->
ok = z_sites_manager:stop(Site);
maybe_stop_site(_, {ok, stopped}) ->
ok.
%% @doc Configure the site config override to set the test schema.
configure_test_schema(Site) ->
Schema = "z_sitetest",
z_sites_manager:put_site_config_overrides(Site, [{dbschema, Schema}]).
%% @doc Remove the site config overrides.
unconfigure_test_schema(Site) ->
z_sites_manager:put_site_config_overrides(Site, []).
%% @doc Start watching the given site for .erl file changes. As soon
%% as any Erlang module inside the watched site is recompiled, all
%% sitetests are run.
watch(Site) ->
application:set_env(zotonic_core, sitetest_watched,
sets:to_list(sets:from_list(watches() ++ [Site]))).
%% @doc Stop the sitetests from being run when Erlang modules in the
%% site are recompiled.
unwatch(Site) ->
application:set_env(zotonic_core, sitetest_watched, [W || W <- watches(), W =/= Site]).
%% @doc Returns whether the given site is being watched for sitetest runs.
is_watching(Site) ->
lists:member(Site, watches()).
watches() ->
application:get_env(zotonic_core, sitetest_watched, []).
%% @doc Drop the site's datbase schema
ensure_drop_test_schema(Site) ->
{ok, Config} = z_sites_manager:get_site_config(Site),
DbConfig = z_db_pool:database_options(Site, Config),
Database = proplists:get_value(dbdatabase, DbConfig),
Schema = proplists:get_value(dbschema, DbConfig),
{ok, Conn} = open_connection(Database, DbConfig),
ok = drop_schema(Site, Conn, Schema),
close_connection(Conn).
%% @doc Drop a schema
-spec drop_schema(atom(), epgsql:connection(), string()) -> ok | {error, term()}.
drop_schema(_Site, Connection, Schema) ->
case epgsql:equery(
Connection,
"DROP SCHEMA \"" ++ Schema ++ "\" CASCADE"
) of
{ok, _, _} ->
ok;
{error, #error{ codename = invalid_schema_name }} ->
ok;
{error, Reason} = Error ->
?LOG_ERROR(#{
text => <<"z_sitetest: error while dropping schema">>,
in => zotonic_core,
schema => Schema,
result => error,
reason => Reason
}),
Error
end.
open_connection(DatabaseName, Options) ->
epgsql:connect(
proplists:get_value(dbhost, Options),
proplists:get_value(dbuser, Options),
proplists:get_value(dbpassword, Options),
[
{port, proplists:get_value(dbport, Options)},
{database, DatabaseName}
]
).
close_connection(Connection) ->
epgsql:close(Connection).
%% @doc Start the site, and wait for it to be fully booted.
start_site(Site) ->
ok = z_sites_manager:start(Site),
z_sites_manager:await_startup(Site).
%% @doc Filter the list of beam files to find all sitetest modules
%% given a certain site atom.
find_sitetest_modules(Site) when is_atom(Site) ->
SiteStr = z_convert:to_list(Site),
lists:flatten(
[
[ z_convert:to_atom(filename:basename(File, ".beam"))
|| File <- filelib:wildcard(Path ++ "/" ++ SiteStr ++ "_*_sitetest.beam")
]
|| Path <- code:get_path()]).