You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2009/06/24 07:51:41 UTC

svn commit: r787914 - in /couchdb/trunk: ./ src/couchdb/ test/etap/

Author: davisp
Date: Wed Jun 24 05:51:41 2009
New Revision: 787914

URL: http://svn.apache.org/viewvc?rev=787914&view=rev
Log:
Lots of tests for couch_config.erl
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).


Added:
    couchdb/trunk/test/etap/080-config-get-set.t   (with props)
    couchdb/trunk/test/etap/081-config-override.1.ini
    couchdb/trunk/test/etap/081-config-override.2.ini
    couchdb/trunk/test/etap/081-config-override.t   (with props)
    couchdb/trunk/test/etap/082-config-register.t   (with props)
    couchdb/trunk/test/etap/083-config-no-files.t   (with props)
Modified:
    couchdb/trunk/Makefile.am
    couchdb/trunk/src/couchdb/couch_config.erl
    couchdb/trunk/test/etap/031-doc-to-json.t
    couchdb/trunk/test/etap/040-util.t   (props changed)
    couchdb/trunk/test/etap/050-stream.t   (contents, props changed)
    couchdb/trunk/test/etap/060-kt-merging.t   (props changed)
    couchdb/trunk/test/etap/061-kt-missing-leaves.t   (props changed)
    couchdb/trunk/test/etap/062-kt-remove-leaves.t   (props changed)
    couchdb/trunk/test/etap/063-kt-get-leaves.t   (props changed)
    couchdb/trunk/test/etap/064-kt-counting.t   (props changed)
    couchdb/trunk/test/etap/065-kt-stemming.t   (props changed)
    couchdb/trunk/test/etap/070-couch-db.t   (props changed)

Modified: couchdb/trunk/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/Makefile.am?rev=787914&r1=787913&r2=787914&view=diff
==============================================================================
--- couchdb/trunk/Makefile.am (original)
+++ couchdb/trunk/Makefile.am Wed Jun 24 05:51:41 2009
@@ -36,10 +36,10 @@
 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/ \

Modified: couchdb/trunk/src/couchdb/couch_config.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_config.erl?rev=787914&r1=787913&r2=787914&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_config.erl (original)
+++ couchdb/trunk/src/couchdb/couch_config.erl Wed Jun 24 05:51:41 2009
@@ -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=""
-    }).
 
-%% Public API %%
+-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]).
+
+-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, 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, 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)}}.
+    WriteFile = case length(IniFiles) > 0 of
+        true -> lists:last(IniFiles);
+        _ -> undefined
+    end,
+    {ok, #config{write_filename=WriteFile}}.
 
-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};
 
+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 @@
         {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 @@
                     {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}.

Modified: couchdb/trunk/test/etap/031-doc-to-json.t
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/031-doc-to-json.t?rev=787914&r1=787913&r2=787914&view=diff
==============================================================================
--- couchdb/trunk/test/etap/031-doc-to-json.t (original)
+++ couchdb/trunk/test/etap/031-doc-to-json.t Wed Jun 24 05:51:41 2009
@@ -9,7 +9,7 @@
 
 main(_) ->
     code:add_pathz("src/couchdb"),
-    etap:plan(unknown),
+    etap:plan(12),
     case (catch test()) of
         ok ->
             etap:end_tests();

Propchange: couchdb/trunk/test/etap/040-util.t
------------------------------------------------------------------------------
    svn:executable = *

Modified: couchdb/trunk/test/etap/050-stream.t
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/050-stream.t?rev=787914&r1=787913&r2=787914&view=diff
==============================================================================
--- couchdb/trunk/test/etap/050-stream.t (original)
+++ couchdb/trunk/test/etap/050-stream.t Wed Jun 24 05:51:41 2009
@@ -3,7 +3,7 @@
 
 main(_) ->
     code:add_pathz("src/couchdb"),
-    etap:plan(unknown),
+    etap:plan(13),
     case (catch test()) of
         ok ->
             etap:end_tests();

Propchange: couchdb/trunk/test/etap/050-stream.t
------------------------------------------------------------------------------
    svn:executable = *

Propchange: couchdb/trunk/test/etap/060-kt-merging.t
------------------------------------------------------------------------------
    svn:executable = *

Propchange: couchdb/trunk/test/etap/061-kt-missing-leaves.t
------------------------------------------------------------------------------
    svn:executable = *

Propchange: couchdb/trunk/test/etap/062-kt-remove-leaves.t
------------------------------------------------------------------------------
    svn:executable = *

Propchange: couchdb/trunk/test/etap/063-kt-get-leaves.t
------------------------------------------------------------------------------
    svn:executable = *

Propchange: couchdb/trunk/test/etap/064-kt-counting.t
------------------------------------------------------------------------------
    svn:executable = *

Propchange: couchdb/trunk/test/etap/065-kt-stemming.t
------------------------------------------------------------------------------
    svn:executable = *

Propchange: couchdb/trunk/test/etap/070-couch-db.t
------------------------------------------------------------------------------
    svn:executable = *

Added: couchdb/trunk/test/etap/080-config-get-set.t
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/080-config-get-set.t?rev=787914&view=auto
==============================================================================
--- couchdb/trunk/test/etap/080-config-get-set.t (added)
+++ couchdb/trunk/test/etap/080-config-get-set.t Wed Jun 24 05:51:41 2009
@@ -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.

Propchange: couchdb/trunk/test/etap/080-config-get-set.t
------------------------------------------------------------------------------
    svn:executable = *

Added: couchdb/trunk/test/etap/081-config-override.1.ini
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/081-config-override.1.ini?rev=787914&view=auto
==============================================================================
--- couchdb/trunk/test/etap/081-config-override.1.ini (added)
+++ couchdb/trunk/test/etap/081-config-override.1.ini Wed Jun 24 05:51:41 2009
@@ -0,0 +1,5 @@
+[couchdb]
+max_dbs_open=10
+
+[httpd]
+port=4895

Added: couchdb/trunk/test/etap/081-config-override.2.ini
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/081-config-override.2.ini?rev=787914&view=auto
==============================================================================
--- couchdb/trunk/test/etap/081-config-override.2.ini (added)
+++ couchdb/trunk/test/etap/081-config-override.2.ini Wed Jun 24 05:51:41 2009
@@ -0,0 +1,5 @@
+[httpd]
+port = 80
+
+[fizbang]
+unicode = normalized
\ No newline at end of file

Added: couchdb/trunk/test/etap/081-config-override.t
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/081-config-override.t?rev=787914&view=auto
==============================================================================
--- couchdb/trunk/test/etap/081-config-override.t (added)
+++ couchdb/trunk/test/etap/081-config-override.t Wed Jun 24 05:51:41 2009
@@ -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.

Propchange: couchdb/trunk/test/etap/081-config-override.t
------------------------------------------------------------------------------
    svn:executable = *

Added: couchdb/trunk/test/etap/082-config-register.t
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/082-config-register.t?rev=787914&view=auto
==============================================================================
--- couchdb/trunk/test/etap/082-config-register.t (added)
+++ couchdb/trunk/test/etap/082-config-register.t Wed Jun 24 05:51:41 2009
@@ -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

Propchange: couchdb/trunk/test/etap/082-config-register.t
------------------------------------------------------------------------------
    svn:executable = *

Added: couchdb/trunk/test/etap/083-config-no-files.t
URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/083-config-no-files.t?rev=787914&view=auto
==============================================================================
--- couchdb/trunk/test/etap/083-config-no-files.t (added)
+++ couchdb/trunk/test/etap/083-config-no-files.t Wed Jun 24 05:51:41 2009
@@ -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

Propchange: couchdb/trunk/test/etap/083-config-no-files.t
------------------------------------------------------------------------------
    svn:executable = *



Re: svn commit: r787914 - in /couchdb/trunk: ./ src/couchdb/ test/etap/

Posted by Paul Davis <pa...@gmail.com>.
I totally forgot to give the apropriate kudos to Bob Dionne for all
the help he's been giving me on getting tests written. He's blazing
alot of the trail in writing these new tests while I try and keep up.

Paul Davis

On Wed, Jun 24, 2009 at 1:51 AM, <da...@apache.org> wrote:
> Author: davisp
> Date: Wed Jun 24 05:51:41 2009
> New Revision: 787914
>
> URL: http://svn.apache.org/viewvc?rev=787914&view=rev
> Log:
> Lots of tests for couch_config.erl
> 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).
>
>
> Added:
>    couchdb/trunk/test/etap/080-config-get-set.t   (with props)
>    couchdb/trunk/test/etap/081-config-override.1.ini
>    couchdb/trunk/test/etap/081-config-override.2.ini
>    couchdb/trunk/test/etap/081-config-override.t   (with props)
>    couchdb/trunk/test/etap/082-config-register.t   (with props)
>    couchdb/trunk/test/etap/083-config-no-files.t   (with props)
> Modified:
>    couchdb/trunk/Makefile.am
>    couchdb/trunk/src/couchdb/couch_config.erl
>    couchdb/trunk/test/etap/031-doc-to-json.t
>    couchdb/trunk/test/etap/040-util.t   (props changed)
>    couchdb/trunk/test/etap/050-stream.t   (contents, props changed)
>    couchdb/trunk/test/etap/060-kt-merging.t   (props changed)
>    couchdb/trunk/test/etap/061-kt-missing-leaves.t   (props changed)
>    couchdb/trunk/test/etap/062-kt-remove-leaves.t   (props changed)
>    couchdb/trunk/test/etap/063-kt-get-leaves.t   (props changed)
>    couchdb/trunk/test/etap/064-kt-counting.t   (props changed)
>    couchdb/trunk/test/etap/065-kt-stemming.t   (props changed)
>    couchdb/trunk/test/etap/070-couch-db.t   (props changed)
>
> Modified: couchdb/trunk/Makefile.am
> URL: http://svn.apache.org/viewvc/couchdb/trunk/Makefile.am?rev=787914&r1=787913&r2=787914&view=diff
> ==============================================================================
> --- couchdb/trunk/Makefile.am (original)
> +++ couchdb/trunk/Makefile.am Wed Jun 24 05:51:41 2009
> @@ -36,10 +36,10 @@
>  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/ \
>
> Modified: couchdb/trunk/src/couchdb/couch_config.erl
> URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_config.erl?rev=787914&r1=787913&r2=787914&view=diff
> ==============================================================================
> --- couchdb/trunk/src/couchdb/couch_config.erl (original)
> +++ couchdb/trunk/src/couchdb/couch_config.erl Wed Jun 24 05:51:41 2009
> @@ -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=""
> -    }).
>
> -%% Public API %%
> +-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]).
> +
> +-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, 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, 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)}}.
> +    WriteFile = case length(IniFiles) > 0 of
> +        true -> lists:last(IniFiles);
> +        _ -> undefined
> +    end,
> +    {ok, #config{write_filename=WriteFile}}.
>
> -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};
>
> +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 @@
>         {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 @@
>                     {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}.
>
> Modified: couchdb/trunk/test/etap/031-doc-to-json.t
> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/031-doc-to-json.t?rev=787914&r1=787913&r2=787914&view=diff
> ==============================================================================
> --- couchdb/trunk/test/etap/031-doc-to-json.t (original)
> +++ couchdb/trunk/test/etap/031-doc-to-json.t Wed Jun 24 05:51:41 2009
> @@ -9,7 +9,7 @@
>
>  main(_) ->
>     code:add_pathz("src/couchdb"),
> -    etap:plan(unknown),
> +    etap:plan(12),
>     case (catch test()) of
>         ok ->
>             etap:end_tests();
>
> Propchange: couchdb/trunk/test/etap/040-util.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Modified: couchdb/trunk/test/etap/050-stream.t
> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/050-stream.t?rev=787914&r1=787913&r2=787914&view=diff
> ==============================================================================
> --- couchdb/trunk/test/etap/050-stream.t (original)
> +++ couchdb/trunk/test/etap/050-stream.t Wed Jun 24 05:51:41 2009
> @@ -3,7 +3,7 @@
>
>  main(_) ->
>     code:add_pathz("src/couchdb"),
> -    etap:plan(unknown),
> +    etap:plan(13),
>     case (catch test()) of
>         ok ->
>             etap:end_tests();
>
> Propchange: couchdb/trunk/test/etap/050-stream.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Propchange: couchdb/trunk/test/etap/060-kt-merging.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Propchange: couchdb/trunk/test/etap/061-kt-missing-leaves.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Propchange: couchdb/trunk/test/etap/062-kt-remove-leaves.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Propchange: couchdb/trunk/test/etap/063-kt-get-leaves.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Propchange: couchdb/trunk/test/etap/064-kt-counting.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Propchange: couchdb/trunk/test/etap/065-kt-stemming.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Propchange: couchdb/trunk/test/etap/070-couch-db.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Added: couchdb/trunk/test/etap/080-config-get-set.t
> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/080-config-get-set.t?rev=787914&view=auto
> ==============================================================================
> --- couchdb/trunk/test/etap/080-config-get-set.t (added)
> +++ couchdb/trunk/test/etap/080-config-get-set.t Wed Jun 24 05:51:41 2009
> @@ -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.
>
> Propchange: couchdb/trunk/test/etap/080-config-get-set.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Added: couchdb/trunk/test/etap/081-config-override.1.ini
> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/081-config-override.1.ini?rev=787914&view=auto
> ==============================================================================
> --- couchdb/trunk/test/etap/081-config-override.1.ini (added)
> +++ couchdb/trunk/test/etap/081-config-override.1.ini Wed Jun 24 05:51:41 2009
> @@ -0,0 +1,5 @@
> +[couchdb]
> +max_dbs_open=10
> +
> +[httpd]
> +port=4895
>
> Added: couchdb/trunk/test/etap/081-config-override.2.ini
> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/081-config-override.2.ini?rev=787914&view=auto
> ==============================================================================
> --- couchdb/trunk/test/etap/081-config-override.2.ini (added)
> +++ couchdb/trunk/test/etap/081-config-override.2.ini Wed Jun 24 05:51:41 2009
> @@ -0,0 +1,5 @@
> +[httpd]
> +port = 80
> +
> +[fizbang]
> +unicode = normalized
> \ No newline at end of file
>
> Added: couchdb/trunk/test/etap/081-config-override.t
> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/081-config-override.t?rev=787914&view=auto
> ==============================================================================
> --- couchdb/trunk/test/etap/081-config-override.t (added)
> +++ couchdb/trunk/test/etap/081-config-override.t Wed Jun 24 05:51:41 2009
> @@ -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.
>
> Propchange: couchdb/trunk/test/etap/081-config-override.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Added: couchdb/trunk/test/etap/082-config-register.t
> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/082-config-register.t?rev=787914&view=auto
> ==============================================================================
> --- couchdb/trunk/test/etap/082-config-register.t (added)
> +++ couchdb/trunk/test/etap/082-config-register.t Wed Jun 24 05:51:41 2009
> @@ -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
>
> Propchange: couchdb/trunk/test/etap/082-config-register.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
> Added: couchdb/trunk/test/etap/083-config-no-files.t
> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/083-config-no-files.t?rev=787914&view=auto
> ==============================================================================
> --- couchdb/trunk/test/etap/083-config-no-files.t (added)
> +++ couchdb/trunk/test/etap/083-config-no-files.t Wed Jun 24 05:51:41 2009
> @@ -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
>
> Propchange: couchdb/trunk/test/etap/083-config-no-files.t
> ------------------------------------------------------------------------------
>    svn:executable = *
>
>
>