Skip to content

Commit

Permalink
Lots of tests for couch_config.erl
Browse files Browse the repository at this point in the history
Refactored couch_config.erl to resolve COUCHDB-384
Tweaked the main Makefile.am to make the cover and check targets depend on the dev target instead of the all target.
Added the executable property to all test files to make them easily runnable as standalone tests (as in not via prove).



git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@787914 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
davisp committed Jun 24, 2009
1 parent e05e90e commit 8a19831
Show file tree
Hide file tree
Showing 18 changed files with 553 additions and 85 deletions.
4 changes: 2 additions & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ README.gz: $(top_srcdir)/README
THANKS.gz: $(top_srcdir)/THANKS
-gzip -9 < $< > $@

check: all
check: dev
prove test/etap/*.t

cover: all
cover: dev
rm -f cover/*.coverdata
COVER=1 COVER_BIN=./src/couchdb/ prove test/etap/*.t
SRC=./src/couchdb/ \
Expand Down
186 changes: 105 additions & 81 deletions src/couchdb/couch_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,127 +10,164 @@
% License for the specific language governing permissions and limitations under
% the License.

%% @doc Reads CouchDB's ini file and gets queried for configuration parameters.
%% This module is initialized with a list of ini files that it
%% consecutively reads Key/Value pairs from and saves them in an ets
%% table. If more an one ini file is specified, the last one is used to
%% write changes that are made with store/2 back to that ini file.
% Reads CouchDB's ini file and gets queried for configuration parameters.
% This module is initialized with a list of ini files that it consecutively
% reads Key/Value pairs from and saves them in an ets table. If more an one
% ini file is specified, the last one is used to write changes that are made
% with store/2 back to that ini file.

-module(couch_config).
-behaviour(gen_server).

-include("couch_db.hrl").

-behaviour(gen_server).
-export([start_link/1, init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([all/0, get/1, get/2, get/3, delete/2, set/3, set/4, register/1,
register/2, load_ini_file/1]).

-record(config,
{notify_funs=[],
write_filename=""
}).
-export([start_link/1, stop/0]).
-export([all/0, get/1, get/2, get/3, set/3, set/4, delete/2, delete/3]).
-export([register/1, register/2]).
-export([parse_ini_file/1]).

%% Public API %%
-export([init/1, terminate/2, code_change/3]).
-export([handle_call/3, handle_cast/2, handle_info/2]).

-record(config, {
notify_funs=[],
write_filename=undefined
}).

%% @type etstable() = integer().

start_link(IniFiles) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, IniFiles, []).

stop() ->
gen_server:cast(?MODULE, stop).


all() ->
lists:sort(ets:tab2list(?MODULE)).
lists:sort(gen_server:call(?MODULE, all)).


get(Section) when is_binary(Section) ->
?MODULE:get(?b2l(Section));
get(Section) ->
Matches = ets:match(?MODULE, {{Section, '$1'}, '$2'}),
[{Key, Value} || [Key, Value] <- Matches].

gen_server:call(?MODULE, {get, Section}).

get(Section, Key) ->
?MODULE:get(Section, Key, undefined).

get(Section, Key, Default) when is_binary(Section) and is_binary(Key) ->
?MODULE:get(?b2l(Section), ?b2l(Key), Default);
get(Section, Key, Default) ->
case ets:lookup(?MODULE, {Section, Key}) of
[] -> Default;
[{_,Result}] -> Result
end.
gen_server:call(?MODULE, {get, Section, Key, Default}).


set(Section, Key, Value) ->
set(Section, Key, Value, true).
?MODULE:set(Section, Key, Value, true).

set(Section, Key, Value, Persist) when is_binary(Section) and is_binary(Key) ->
set(?b2l(Section), ?b2l(Key), Value, Persist);
?MODULE:set(?b2l(Section), ?b2l(Key), Value, Persist);
set(Section, Key, Value, Persist) ->
gen_server:call(?MODULE, {set, [{{Section, Key}, Value}], Persist}).
gen_server:call(?MODULE, {set, Section, Key, Value, Persist}).


delete(Section, Key) when is_binary(Section) and is_binary(Key) ->
delete(?b2l(Section), ?b2l(Key));
delete(Section, Key) ->
set(Section, Key, "").
delete(Section, Key, true).

delete(Section, Key, Persist) when is_binary(Section) and is_binary(Key) ->
delete(?b2l(Section), ?b2l(Key), Persist);
delete(Section, Key, Persist) ->
?MODULE:set(Section, Key, "", Persist).


register(Fun) ->
?MODULE:register(Fun, self()).

register(Fun, Pid) ->
gen_server:call(?MODULE, {register, Fun, Pid}).

%% Private API %%

%% @spec init(List::list([])) -> {ok, Tab::etsatable()}
%% @doc Creates a new ets table of the type "set".
init(IniFiles) ->
ets:new(?MODULE, [named_table, set, protected]),
ets:new(?MODULE, [named_table, set, private]),
lists:map(fun(IniFile) ->
{ok, ParsedIniValues} = parse_ini_file(IniFile),
ets:insert(?MODULE, ParsedIniValues)
end, IniFiles),
{ok, #config{write_filename=lists:last(IniFiles)}}.

handle_call({set, KVs, Persist}, _From, Config) ->
lists:map(
fun({{Section, Key}, Value}=KV) ->
true = ets:insert(?MODULE, KV),
if Persist ->
ok = couch_config_writer:save_to_file(KV,
Config#config.write_filename);
true -> ok
end,
[catch F(Section, Key, Value)
|| {_Pid, F} <- Config#config.notify_funs]
end, KVs),
{reply, ok, Config};
WriteFile = case length(IniFiles) > 0 of
true -> lists:last(IniFiles);
_ -> undefined
end,
{ok, #config{write_filename=WriteFile}}.


terminate(_Reason, _State) ->
ok.


handle_call(all, _From, Config) ->
Resp = lists:sort((ets:tab2list(?MODULE))),
{reply, Resp, Config};
handle_call({get, Section}, _From, Config) ->
Matches = ets:match(?MODULE, {{Section, '$1'}, '$2'}),
Resp = [{Key, Value} || [Key, Value] <- Matches],
{reply, Resp, Config};
handle_call({get, Section, Key, Default}, _From, Config) ->
Resp = case ets:lookup(?MODULE, {Section, Key}) of
[] -> Default;
[{_, Match}] -> Match
end,
{reply, Resp, Config};
handle_call({set, Sec, Key, Val, Persist}, _From, Config) ->
true = ets:insert(?MODULE, {{Sec, Key}, Val}),
case {Persist, Config#config.write_filename} of
{true, undefined} ->
ok;
{true, FileName} ->
couch_config_writer:save_to_file({{Sec, Key}, Val}, FileName);
_ ->
ok
end,
[catch F(Sec, Key, Val) || {_Pid, F} <- Config#config.notify_funs],
{reply, ok, Config};
handle_call({register, Fun, Pid}, _From, #config{notify_funs=PidFuns}=Config) ->
erlang:monitor(process, Pid),
% convert 1 and 2 arity to 3 arity
Fun2 =
if is_function(Fun, 1) ->
fun(Section, _Key, _Value) -> Fun(Section) end;
is_function(Fun, 2) ->
fun(Section, Key, _Value) -> Fun(Section, Key) end;
is_function(Fun, 3) ->
Fun
Fun2 =
case Fun of
_ when is_function(Fun, 1) ->
fun(Section, _Key, _Value) -> Fun(Section) end;
_ when is_function(Fun, 2) ->
fun(Section, Key, _Value) -> Fun(Section, Key) end;
_ when is_function(Fun, 3) ->
Fun
end,
{reply, ok, Config#config{notify_funs=[{Pid, Fun2}|PidFuns]}}.
{reply, ok, Config#config{notify_funs=[{Pid, Fun2} | PidFuns]}}.


handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.

handle_info({'DOWN', _, _, DownPid, _}, #config{notify_funs=PidFuns}=Config) ->
% remove any funs registered by the downed process
FilteredPidFuns = [{Pid,Fun} || {Pid,Fun} <- PidFuns, Pid /= DownPid],
{noreply, Config#config{notify_funs=FilteredPidFuns}}.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%% @spec load_ini_file(IniFile::filename()) -> ok
%% @doc Parses an ini file and stores Key/Value Pairs into the ets table.
load_ini_file(IniFile) ->
{ok, KVs} = parse_ini_file(IniFile),
gen_server:call(?MODULE, {set, KVs, false}).

%% @spec load_ini_file(IniFile::filename()) -> {ok, [KV]}
%% KV = {{Section::string(), Key::string()}, Value::String()}
%% @throws {startup_error, Msg::string()}
parse_ini_file(IniFile) ->
IniFilename = couch_util:abs_pathname(IniFile),
IniBin =
case file:read_file(IniFilename) of
{ok, IniBin0} ->
IniBin0;
{error, enoent} ->
Msg = ?l2b(io_lib:format("Couldn't find server configuration file ~s.", [IniFilename])),
Fmt = "Couldn't find server configuration file ~s.",
Msg = ?l2b(io_lib:format(Fmt, [IniFilename])),
?LOG_ERROR("~s~n", [Msg]),
throw({startup_error, Msg})
end,
Expand All @@ -156,25 +193,12 @@ parse_ini_file(IniFile) ->
{AccSectionName, AccValues};
{ok, [ValueName|LineValues]} -> % yeehaw, got a line!
RemainingLine = couch_util:implode(LineValues, "="),
{ok, [LineValue | _Rest]} = regexp:split(RemainingLine, " ;|\t;"), % removes comments
{AccSectionName, [{{AccSectionName, ValueName}, LineValue} | AccValues]}
{ok, [LineValue | _Rest]} =
regexp:split(RemainingLine, " ;|\t;"), % removes comments
{AccSectionName,
[{{AccSectionName, ValueName}, LineValue} | AccValues]}
end
end
end, {"", []}, Lines),
{ok, ParsedIniValues}.

% Unused gen_server behaviour API functions that we need to declare.

%% @doc Unused
handle_cast(foo, State) -> {noreply, State}.

handle_info({'DOWN', _, _, DownPid, _}, #config{notify_funs=PidFuns}=Config) ->
% remove any funs registered by the downed process
FilteredPidFuns = [{Pid,Fun} || {Pid,Fun} <- PidFuns, Pid /= DownPid],
{noreply, Config#config{notify_funs=FilteredPidFuns}}.

%% @doc Unused
terminate(_Reason, _State) -> ok.

%% @doc Unused
code_change(_OldVersion, State, _Extra) -> {ok, State}.
2 changes: 1 addition & 1 deletion test/etap/031-doc-to-json.t
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

main(_) ->
code:add_pathz("src/couchdb"),
etap:plan(unknown),
etap:plan(12),
case (catch test()) of
ok ->
etap:end_tests();
Expand Down
Empty file modified test/etap/040-util.t
100644 → 100755
Empty file.
2 changes: 1 addition & 1 deletion test/etap/050-stream.t
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

main(_) ->
code:add_pathz("src/couchdb"),
etap:plan(unknown),
etap:plan(13),
case (catch test()) of
ok ->
etap:end_tests();
Expand Down
Empty file modified test/etap/060-kt-merging.t
100644 → 100755
Empty file.
Empty file modified test/etap/061-kt-missing-leaves.t
100644 → 100755
Empty file.
Empty file modified test/etap/062-kt-remove-leaves.t
100644 → 100755
Empty file.
Empty file modified test/etap/063-kt-get-leaves.t
100644 → 100755
Empty file.
Empty file modified test/etap/064-kt-counting.t
100644 → 100755
Empty file.
Empty file modified test/etap/065-kt-stemming.t
100644 → 100755
Empty file.
Empty file modified test/etap/070-couch-db.t
100644 → 100755
Empty file.
Loading

0 comments on commit 8a19831

Please sign in to comment.