summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am4
-rw-r--r--src/couchdb/couch_config.erl186
-rwxr-xr-xtest/etap/031-doc-to-json.t2
-rwxr-xr-x[-rw-r--r--]test/etap/040-util.t0
-rwxr-xr-x[-rw-r--r--]test/etap/050-stream.t2
-rwxr-xr-x[-rw-r--r--]test/etap/060-kt-merging.t0
-rwxr-xr-x[-rw-r--r--]test/etap/061-kt-missing-leaves.t0
-rwxr-xr-x[-rw-r--r--]test/etap/062-kt-remove-leaves.t0
-rwxr-xr-x[-rw-r--r--]test/etap/063-kt-get-leaves.t0
-rwxr-xr-x[-rw-r--r--]test/etap/064-kt-counting.t0
-rwxr-xr-x[-rw-r--r--]test/etap/065-kt-stemming.t0
-rwxr-xr-x[-rw-r--r--]test/etap/070-couch-db.t0
-rwxr-xr-xtest/etap/080-config-get-set.t116
-rw-r--r--test/etap/081-config-override.1.ini5
-rw-r--r--test/etap/081-config-override.2.ini5
-rwxr-xr-xtest/etap/081-config-override.t200
-rwxr-xr-xtest/etap/082-config-register.t75
-rwxr-xr-xtest/etap/083-config-no-files.t43
18 files changed, 553 insertions, 85 deletions
diff --git a/Makefile.am b/Makefile.am
index bb4b4d24..2ba55310 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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/ \
diff --git a/src/couchdb/couch_config.erl b/src/couchdb/couch_config.erl
index dcdbc9eb..9a2e739a 100644
--- a/src/couchdb/couch_config.erl
+++ b/src/couchdb/couch_config.erl
@@ -10,42 +10,47 @@
% 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).
@@ -53,21 +58,28 @@ get(Section, Key) ->
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()).
@@ -75,54 +87,78 @@ register(Fun) ->
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 =
@@ -130,7 +166,8 @@ parse_ini_file(IniFile) ->
{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,
@@ -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}.
diff --git a/test/etap/031-doc-to-json.t b/test/etap/031-doc-to-json.t
index 37b23848..37ee9946 100755
--- a/test/etap/031-doc-to-json.t
+++ b/test/etap/031-doc-to-json.t
@@ -9,7 +9,7 @@
main(_) ->
code:add_pathz("src/couchdb"),
- etap:plan(unknown),
+ etap:plan(12),
case (catch test()) of
ok ->
etap:end_tests();
diff --git a/test/etap/040-util.t b/test/etap/040-util.t
index a6b7df33..a6b7df33 100644..100755
--- a/test/etap/040-util.t
+++ b/test/etap/040-util.t
diff --git a/test/etap/050-stream.t b/test/etap/050-stream.t
index 41aa9754..dab2d50c 100644..100755
--- a/test/etap/050-stream.t
+++ b/test/etap/050-stream.t
@@ -3,7 +3,7 @@
main(_) ->
code:add_pathz("src/couchdb"),
- etap:plan(unknown),
+ etap:plan(13),
case (catch test()) of
ok ->
etap:end_tests();
diff --git a/test/etap/060-kt-merging.t b/test/etap/060-kt-merging.t
index 004e004f..004e004f 100644..100755
--- a/test/etap/060-kt-merging.t
+++ b/test/etap/060-kt-merging.t
diff --git a/test/etap/061-kt-missing-leaves.t b/test/etap/061-kt-missing-leaves.t
index c5ee1222..c5ee1222 100644..100755
--- a/test/etap/061-kt-missing-leaves.t
+++ b/test/etap/061-kt-missing-leaves.t
diff --git a/test/etap/062-kt-remove-leaves.t b/test/etap/062-kt-remove-leaves.t
index 566efa06..566efa06 100644..100755
--- a/test/etap/062-kt-remove-leaves.t
+++ b/test/etap/062-kt-remove-leaves.t
diff --git a/test/etap/063-kt-get-leaves.t b/test/etap/063-kt-get-leaves.t
index 9e366ff8..9e366ff8 100644..100755
--- a/test/etap/063-kt-get-leaves.t
+++ b/test/etap/063-kt-get-leaves.t
diff --git a/test/etap/064-kt-counting.t b/test/etap/064-kt-counting.t
index dd00cedd..dd00cedd 100644..100755
--- a/test/etap/064-kt-counting.t
+++ b/test/etap/064-kt-counting.t
diff --git a/test/etap/065-kt-stemming.t b/test/etap/065-kt-stemming.t
index 0de69482..0de69482 100644..100755
--- a/test/etap/065-kt-stemming.t
+++ b/test/etap/065-kt-stemming.t
diff --git a/test/etap/070-couch-db.t b/test/etap/070-couch-db.t
index da1f28ab..da1f28ab 100644..100755
--- a/test/etap/070-couch-db.t
+++ b/test/etap/070-couch-db.t
diff --git a/test/etap/080-config-get-set.t b/test/etap/080-config-get-set.t
new file mode 100755
index 00000000..4824b2aa
--- /dev/null
+++ b/test/etap/080-config-get-set.t
@@ -0,0 +1,116 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+default_config() ->
+ "etc/couchdb/default_dev.ini".
+
+main(_) ->
+ code:add_pathz("src/couchdb"),
+ etap:plan(12),
+ case (catch test()) of
+ ok ->
+ etap:end_tests();
+ Other ->
+ etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
+ etap:bail(Other)
+ end,
+ ok.
+
+test() ->
+ % start couch_config with default
+ couch_config:start_link([default_config()]),
+
+
+ % Check that we can get values
+
+
+ etap:fun_is(
+ fun(List) -> length(List) > 0 end,
+ couch_config:all(),
+ "Data was loaded from the INI file."
+ ),
+
+ etap:fun_is(
+ fun(List) -> length(List) > 0 end,
+ couch_config:get("daemons"),
+ "There are settings in the [daemons] section of the INI file."
+ ),
+
+ etap:is(
+ couch_config:get("httpd_design_handlers", "_view"),
+ "{couch_httpd_view, handle_view_req}",
+ "The {httpd_design_handlers, view} is the expected default."
+ ),
+
+ etap:is(
+ couch_config:get("httpd", "foo", "bar"),
+ "bar",
+ "Returns the default when key doesn't exist in config."
+ ),
+
+ etap:is(
+ couch_config:get("httpd", "foo"),
+ undefined,
+ "The default default is the atom 'undefined'."
+ ),
+
+ etap:is(
+ couch_config:get("httpd", "port", "bar"),
+ "5984",
+ "Only returns the default when the config setting does not exist."
+ ),
+
+
+ % Check that setting values works.
+
+
+ ok = couch_config:set("log", "level", "severe", false),
+
+ etap:is(
+ couch_config:get("log", "level"),
+ "severe",
+ "Non persisted changes take effect."
+ ),
+
+ etap:is(
+ couch_config:get("new_section", "bizzle"),
+ undefined,
+ "Section 'new_section' does not exist."
+ ),
+
+ ok = couch_config:set("new_section", "bizzle", "bang", false),
+
+ etap:is(
+ couch_config:get("new_section", "bizzle"),
+ "bang",
+ "New section 'new_section' was created for a new key/value pair."
+ ),
+
+
+ % Check that deleting works
+
+
+ ok = couch_config:delete("new_section", "bizzle", false),
+ etap:is(
+ couch_config:get("new_section", "bizzle"),
+ "",
+ "Deleting sets the value to \"\""
+ ),
+
+
+ % Check ge/set/delete binary strings
+
+ ok = couch_config:set(<<"foo">>, <<"bar">>, <<"baz">>, false),
+ etap:is(
+ couch_config:get(<<"foo">>, <<"bar">>),
+ <<"baz">>,
+ "Can get and set with binary section and key values."
+ ),
+ ok = couch_config:delete(<<"foo">>, <<"bar">>, false),
+ etap:is(
+ couch_config:get(<<"foo">>, <<"bar">>),
+ "",
+ "Deleting with binary section/key pairs sets the value to \"\""
+ ),
+
+ ok.
diff --git a/test/etap/081-config-override.1.ini b/test/etap/081-config-override.1.ini
new file mode 100644
index 00000000..f11da1f3
--- /dev/null
+++ b/test/etap/081-config-override.1.ini
@@ -0,0 +1,5 @@
+[couchdb]
+max_dbs_open=10
+
+[httpd]
+port=4895
diff --git a/test/etap/081-config-override.2.ini b/test/etap/081-config-override.2.ini
new file mode 100644
index 00000000..bb67b629
--- /dev/null
+++ b/test/etap/081-config-override.2.ini
@@ -0,0 +1,5 @@
+[httpd]
+port = 80
+
+[fizbang]
+unicode = normalized \ No newline at end of file
diff --git a/test/etap/081-config-override.t b/test/etap/081-config-override.t
new file mode 100755
index 00000000..1d66b58e
--- /dev/null
+++ b/test/etap/081-config-override.t
@@ -0,0 +1,200 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+default_config() ->
+ "etc/couchdb/default_dev.ini".
+
+local_config_1() ->
+ "test/etap/081-config-override.1.ini".
+
+local_config_2() ->
+ "test/etap/081-config-override.2.ini".
+
+local_config_write() ->
+ "test/etap/temp.081".
+
+% Run tests and wait for the config gen_server to shutdown.
+run_tests(IniFiles, Tests) ->
+ {ok, Pid} = couch_config:start_link(IniFiles),
+ erlang:monitor(process, Pid),
+ Tests(),
+ couch_config:stop(),
+ receive
+ {'DOWN', _, _, Pid, _} -> ok;
+ _Other -> etap:diag("OTHER: ~p~n", [_Other])
+ after
+ 1000 -> throw({timeout_error, config_stop})
+ end.
+
+main(_) ->
+ code:add_pathz("src/couchdb"),
+ etap:plan(17),
+
+ case (catch test()) of
+ ok ->
+ etap:end_tests();
+ Other ->
+ etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
+ etap:bail(Other)
+ end,
+ ok.
+
+test() ->
+
+ CheckStartStop = fun() -> ok end,
+ run_tests([default_config()], CheckStartStop),
+
+ CheckDefaults = fun() ->
+ etap:is(
+ couch_config:get("couchdb", "max_dbs_open"),
+ "100",
+ "{couchdb, max_dbs_open} is 100 by defualt."
+ ),
+
+ etap:is(
+ couch_config:get("httpd","port"),
+ "5984",
+ "{httpd, port} is 5984 by default"
+ ),
+
+ etap:is(
+ couch_config:get("fizbang", "unicode"),
+ undefined,
+ "{fizbang, unicode} is undefined by default"
+ )
+ end,
+
+ run_tests([default_config()], CheckDefaults),
+
+
+ % Check that subsequent files override values appropriately
+
+ CheckOverride = fun() ->
+ etap:is(
+ couch_config:get("couchdb", "max_dbs_open"),
+ "10",
+ "{couchdb, max_dbs_open} was overriden with the value 10"
+ ),
+
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "4895",
+ "{httpd, port} was overriden with the value 4895"
+ )
+ end,
+
+ run_tests([default_config(), local_config_1()], CheckOverride),
+
+
+ % Check that overrides can create new sections
+
+ CheckOverride2 = fun() ->
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "80",
+ "{httpd, port} is overriden with the value 80"
+ ),
+
+ etap:is(
+ couch_config:get("fizbang", "unicode"),
+ "normalized",
+ "{fizbang, unicode} was created by override INI file"
+ )
+ end,
+
+ run_tests([default_config(), local_config_2()], CheckOverride2),
+
+
+ % Check that values can be overriden multiple times
+
+ CheckOverride3 = fun() ->
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "80",
+ "{httpd, port} value was taken from the last specified INI file."
+ )
+ end,
+
+ run_tests(
+ [default_config(), local_config_1(), local_config_2()],
+ CheckOverride3
+ ),
+
+ % Check persistence to last file.
+
+ % Empty the file in case it exists.
+ {ok, Fd} = file:open(local_config_write(), write),
+ ok = file:truncate(Fd),
+ ok = file:close(Fd),
+
+ % Open and write a value
+ CheckCanWrite = fun() ->
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "5984",
+ "{httpd, port} is still 5984 by default"
+ ),
+
+ etap:is(
+ couch_config:set("httpd", "port", "8080"),
+ ok,
+ "Writing {httpd, port} is kosher."
+ ),
+
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "8080",
+ "{httpd, port} was updated to 8080 successfully."
+ ),
+
+ etap:is(
+ couch_config:delete("httpd", "bind_address"),
+ ok,
+ "Deleting {httpd, bind_address} succeeds"
+ ),
+
+ etap:is(
+ couch_config:get("httpd", "bind_address"),
+ "",
+ "{httpd, bind_address} was actually deleted."
+ )
+ end,
+
+ run_tests([default_config(), local_config_write()], CheckCanWrite),
+
+ % Open and check where we don't expect persistence.
+
+ CheckDidntWrite = fun() ->
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "5984",
+ "{httpd, port} was not persisted to the primary INI file."
+ ),
+
+ etap:is(
+ couch_config:get("httpd", "bind_address"),
+ "127.0.0.1",
+ "{httpd, bind_address} was not deleted form the primary INI file."
+ )
+ end,
+
+ run_tests([default_config()], CheckDidntWrite),
+
+ % Open and check we have only the persistence we expect.
+ CheckDidWrite = fun() ->
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "8080",
+ "{httpd, port} is still 8080 after reopening the config."
+ ),
+
+ etap:is(
+ couch_config:get("httpd", "bind_address"),
+ "",
+ "{httpd, bind_address} is still \"\" after reopening."
+ )
+ end,
+
+ run_tests([local_config_write()], CheckDidWrite),
+
+ ok.
diff --git a/test/etap/082-config-register.t b/test/etap/082-config-register.t
new file mode 100755
index 00000000..9f454984
--- /dev/null
+++ b/test/etap/082-config-register.t
@@ -0,0 +1,75 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+default_config() ->
+ "etc/couchdb/default_dev.ini".
+
+main(_) ->
+ code:add_pathz("src/couchdb"),
+ etap:plan(5),
+ case (catch test()) of
+ ok ->
+ etap:end_tests();
+ Other ->
+ etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
+ etap:bail(Other)
+ end,
+ ok.
+
+test() ->
+ couch_config:start_link([default_config()]),
+
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "5984",
+ "{httpd, port} is 5984 by default."
+ ),
+
+ ok = couch_config:set("httpd", "port", "4895", false),
+
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "4895",
+ "{httpd, port} changed to 4895"
+ ),
+
+ SentinelFunc = fun() ->
+ % Ping/Pong to make sure we wait for this
+ % process to die
+ receive {ping, From} -> From ! pong end
+ end,
+ SentinelPid = spawn(SentinelFunc),
+
+ couch_config:register(
+ fun("httpd", "port", Value) ->
+ etap:is(Value, "8080", "Registered function got notification.")
+ end,
+ SentinelPid
+ ),
+
+ ok = couch_config:set("httpd", "port", "8080", false),
+
+ % Implicitly checking that we *don't* call the function
+ etap:is(
+ couch_config:get("httpd", "bind_address"),
+ "127.0.0.1",
+ "{httpd, bind_address} is not '0.0.0.0'"
+ ),
+ ok = couch_config:set("httpd", "bind_address", "0.0.0.0", false),
+
+ % Ping-Pong kill process
+ SentinelPid ! {ping, self()},
+ receive
+ _Any -> ok
+ after 1000 ->
+ throw({timeout_error, registered_pid})
+ end,
+
+ ok = couch_config:set("httpd", "port", "80", false),
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "80",
+ "Implicitly test that the function got de-registered"
+ ),
+
+ ok. \ No newline at end of file
diff --git a/test/etap/083-config-no-files.t b/test/etap/083-config-no-files.t
new file mode 100755
index 00000000..2e4d8e71
--- /dev/null
+++ b/test/etap/083-config-no-files.t
@@ -0,0 +1,43 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+default_config() ->
+ "etc/couchdb/default_dev.ini".
+
+main(_) ->
+ code:add_pathz("src/couchdb"),
+ etap:plan(3),
+ case (catch test()) of
+ ok ->
+ etap:end_tests();
+ Other ->
+ etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
+ etap:bail(Other)
+ end,
+ ok.
+
+test() ->
+ couch_config:start_link([]),
+
+ etap:fun_is(
+ fun(KVPairs) -> length(KVPairs) == 0 end,
+ couch_config:all(),
+ "No INI files specified returns 0 key/value pairs."
+ ),
+
+ ok = couch_config:set("httpd", "port", "80", false),
+
+ etap:is(
+ couch_config:get("httpd", "port"),
+ "80",
+ "Created a new non-persisted k/v pair."
+ ),
+
+ ok = couch_config:set("httpd", "bind_address", "127.0.0.1"),
+ etap:is(
+ couch_config:get("httpd", "bind_address"),
+ "127.0.0.1",
+ "Asking for a persistent key/value pair doesn't choke."
+ ),
+
+ ok. \ No newline at end of file