You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2008/08/20 16:18:06 UTC

svn commit: r687339 - in /incubator/couchdb/trunk: etc/couchdb/ src/couchdb/ test/

Author: jan
Date: Wed Aug 20 07:18:05 2008
New Revision: 687339

URL: http://svn.apache.org/viewvc?rev=687339&view=rev
Log:
add missing files

Added:
    incubator/couchdb/trunk/etc/couchdb/default.ini.tpl.in
    incubator/couchdb/trunk/etc/couchdb/local.ini
    incubator/couchdb/trunk/src/couchdb/couch_config.erl
    incubator/couchdb/trunk/src/couchdb/couch_config_writer.erl
    incubator/couchdb/trunk/src/couchdb/couch_db_update_notifier_sup.erl
    incubator/couchdb/trunk/test/
    incubator/couchdb/trunk/test/Makefile.am
    incubator/couchdb/trunk/test/couch_config_test.erl
    incubator/couchdb/trunk/test/couch_config_writer_test.erl
    incubator/couchdb/trunk/test/runner.erl
    incubator/couchdb/trunk/test/runner.sh

Added: incubator/couchdb/trunk/etc/couchdb/default.ini.tpl.in
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/etc/couchdb/default.ini.tpl.in?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/etc/couchdb/default.ini.tpl.in (added)
+++ incubator/couchdb/trunk/etc/couchdb/default.ini.tpl.in Wed Aug 20 07:18:05 2008
@@ -0,0 +1,23 @@
+; @configure_input@
+
+; Upgrading CouchDB will overwrite this file.
+
+[CouchDB]
+RootDirectory=%localstatelibdir%
+UtilDriverDir=%couchprivlibdir%
+MaximumDocumentSize=4294967296 ; 4 GB
+
+[HTTPd]
+Port=5984
+BindAddress=127.0.0.1
+DocumentRoot=%localdatadir%/www
+
+[Log]
+File=%localstatelogdir%/couch.log
+Level=info
+
+[CouchDB Query Servers]
+javascript=%bindir%/%couchjs_command_name% %localdatadir%/server/main.js
+
+[CouchDB Query Server Options]
+QueryTimeout=5000 ; 5 seconds

Added: incubator/couchdb/trunk/etc/couchdb/local.ini
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/etc/couchdb/local.ini?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/etc/couchdb/local.ini (added)
+++ incubator/couchdb/trunk/etc/couchdb/local.ini Wed Aug 20 07:18:05 2008
@@ -0,0 +1,20 @@
+; CouchDB Configuration Settings
+
+; Custom settings should be made in this file. They will override settings
+; in default.ini, but unlike changes made to default.ini, this file won't be
+; overwritten on server upgrade.
+
+[CouchDB]
+;MaximumDocumentSize=4294967296 ; bytes
+
+[HTTPd]
+;Port=5984
+;BindAddress=127.0.0.1
+
+[Log]
+;Level=info
+
+[Update Notification]
+;unique notifier name=/full/path/to/exe -with "cmd line arg"
+
+

Added: incubator/couchdb/trunk/src/couchdb/couch_config.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_config.erl?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_config.erl (added)
+++ incubator/couchdb/trunk/src/couchdb/couch_config.erl Wed Aug 20 07:18:05 2008
@@ -0,0 +1,195 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License.  You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+%% @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.
+
+-module(couch_config).
+-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([store/2, register/1, register/2, 
+    get/1, get/2,
+    lookup_match/1, lookup_match/2,
+    all/0, unset/1, load_ini_file/1]).
+    
+-record(config,
+    {notify_funs=[],
+    writeback_filename=""
+    }).
+
+%% Public API %%
+
+%% @type etstable() = integer().
+
+start_link(IniFiles) -> gen_server:start_link({local, ?MODULE}, ?MODULE, IniFiles, []).  
+
+%% @spec store(Key::any(), Value::any()) -> {ok, Tab::etsatable()}
+%% @doc Public API function that triggers storage of a Key/Value pair into the
+%%      local ets table and writes it to the storage ini file.
+store(Key, Value) -> gen_server:call(?MODULE, {store, [{Key, Value}]}).
+
+%% @spec get(Key::any()) -> Value::any() | undefined
+%% @doc Returns the value that is stored under key::any() or undefined::atom() if no
+%%      such Key exists.
+get(Key) ->
+    ?MODULE:get(Key, undefined).
+
+%% @spec get(Key::any(), Default::any()) -> Value::any() | Default
+%% @doc Returns the value that is stored under key::any() or Default::any() if
+%%      no such Key exists.
+get(Key, Default) ->
+    fix_lookup_result(ets:lookup(?MODULE, Key), Default).
+
+%% @spec lookup_match(Key::any()) -> Value::any() | undefined:atom()
+%% @doc Lets you look for a Key's Value specifying a pattern that gets passed 
+%%      to ets::match(). Returns undefined::atom() if no Key is found.
+lookup_match(Key) -> gen_server:call(?MODULE, {lookup_match, Key}).
+
+%% @spec lookup_match(Key::any(), Default::any()) -> Value::any() | Default
+%% @doc Lets you look for a Key's Value specifying a pattern that gets passed 
+%%      to ets::match(). Returns Default::any() if no Key is found
+lookup_match(Key, Default) -> gen_server:call(?MODULE, {lookup_match, Key, Default}).
+
+all() -> gen_server:call(?MODULE, all).
+
+register(Fun) -> gen_server:call(?MODULE, {register, Fun, self()}).
+
+
+register(Fun, Pid) -> gen_server:call(?MODULE, {register, Fun, Pid}).
+
+%% @spec unset(Key::any) -> ok
+%% @doc Public API call to remove the configuration entry from the internal 
+%%      ets table. This change is _not_ written to the storage ini file.
+unset(Key) -> gen_server:call(?MODULE, {unset, Key}).
+
+%% 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]),
+    [ok = load_ini_file(IniFile) || IniFile <- IniFiles],
+    {ok, #config{writeback_filename=lists:last(IniFiles)}}.
+
+%% @doc see store/2
+handle_call({store, KVs}, _From, Config) ->
+    [ok = insert_and_commit(Config, KV) || KV <- KVs],
+    {reply, ok, Config};
+
+
+%% @doc See init_value/2
+handle_call({init_value, Key, Value}, _From, Config) ->
+    Reply = ets:insert(?MODULE, {Key, Value}),
+    {reply, Reply, Config};
+
+%% @doc See unset/1
+handle_call({unset, Key}, _From, Config) ->
+    ets:delete(?MODULE, Key),
+    {reply, ok, Config};
+
+
+%% @doc See lookup_match/2
+handle_call({lookup_match, Key, Default}, _From, Config) ->
+    {reply, fix_lookup_result(ets:match(?MODULE, Key), Default), Config};
+
+handle_call(all, _From, Config) ->
+    {reply, lists:sort(ets:tab2list(?MODULE)), Config};
+
+%% @doc See register/2
+handle_call({register, Fun, Pid}, _From, #config{notify_funs=PidFuns}=Config) ->
+    erlang:monitor(process, Pid),
+    {reply, ok, Config#config{notify_funs=[{Pid, Fun}|PidFuns]}}.
+    
+
+fix_lookup_result([{_Key, Value}], _Default) ->
+    Value;
+fix_lookup_result([], Default) ->
+    Default;
+fix_lookup_result(Values, _Default) ->
+    [list_to_tuple(Value) || Value <- Values].
+
+%% @spec insert_and_commit(Tab::etstable(), Config::any()) -> ok
+%% @doc Inserts a Key/Value pair into the ets table, writes it to the storage 
+%%      ini file and calls all registered callback functions for Key.
+insert_and_commit(Config, KV) ->
+    true = ets:insert(?MODULE, KV),
+    % notify funs
+    %[catch Fun(KV) || {_Pid, Fun} <- Config#config.notify_funs],
+    couch_config_writer:save_to_file(KV, Config#config.writeback_filename).
+
+%% @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) ->
+    IniFilename = couch_util:abs_pathname(IniFile),
+    IniBin =
+    case file:read_file(IniFilename) of
+        {ok, IniBin0} ->
+           IniBin0;
+        {error, enoent} ->
+           Msg = io_lib:format("Couldn't find server configuration file ~s.", [IniFilename]),
+           io:format("~s~n", [Msg]),
+           throw({startup_error, Msg})
+    end,
+    
+    {ok, Lines} = regexp:split(binary_to_list(IniBin), "\r\n|\n|\r|\032"),
+    {_, ParsedIniValues} =
+    lists:foldl(fun(Line, {AccSectionName, AccValues}) ->
+            case string:strip(Line) of
+            "[" ++ Rest ->
+                case regexp:split(Rest, "\\]") of
+                {ok, [NewSectionName, ""]} ->
+                    {NewSectionName, AccValues};
+                _Else -> % end bracket not at end, ignore this line
+                    {AccSectionName, AccValues}
+                end;
+            ";" ++ _Comment ->
+                {AccSectionName, AccValues};
+            Line2 ->
+                case regexp:split(Line2, "=") of
+                {ok, [_SingleElement]} -> % no "=" found, ignore this line
+                    {AccSectionName, AccValues};
+                {ok, [""|_LineValues]} -> % line begins with "=", ignore
+                    {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]}
+                end
+            end
+        end, {"", []}, Lines),
+        
+        [ets:insert(?MODULE, {Key, Value}) || {Key, Value} <- ParsedIniValues],
+    ok.
+
+% 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}.
\ No newline at end of file

Added: incubator/couchdb/trunk/src/couchdb/couch_config_writer.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_config_writer.erl?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_config_writer.erl (added)
+++ incubator/couchdb/trunk/src/couchdb/couch_config_writer.erl Wed Aug 20 07:18:05 2008
@@ -0,0 +1,147 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License.  You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+%% @doc Saves a Key/Value pair to a ini file. The Key consists of a Module
+%%      and Variable combination. If that combination is found in the ini file
+%%      the new value replaces the old value. If only the Module is found the
+%%      Variable and value combination is appended to the Module. If the Module
+%%      does not yet exist in the ini file, it is added and the Variable/Value
+%%      pair is appended.
+%% @see couch_config
+
+-module(couch_config_writer).
+-include("couch_db.hrl").
+
+-export([save_to_file/2]).
+
+%% @spec save_to_file(
+%%           Config::{{Module::string(), Variable::string()}, Value::string()}, 
+%%           File::filename()) -> ok
+%% @doc Saves a Module/Key/Value triple to the ini file File::filename()
+save_to_file({{Module, Variable}, Value}, File) ->
+    
+    ?LOG_DEBUG("saving to file '~s', Congif: '~p'", [File, {{Module, Variable}, Value}]),
+    
+    % open file and create a list of lines
+    {ok, Stream} = file:read_file(File),
+    OldFileContents = binary_to_list(Stream),
+    {ok, Lines} = regexp:split(OldFileContents, "\r\n|\n|\r|\032"),
+    
+    % prepare input variables
+    ModuleName = "[" ++ Module ++ "]",
+    VariableList = Variable,
+    
+    % produce the contents for the config file
+    NewFileContents = 
+    case NewFileContents2 = save_loop({{ModuleName, VariableList}, Value}, Lines, "", "", []) of
+        % we didn't change anything, that means we couldn't find a matching
+        % [ini section] in which case we just append a new one.
+        OldFileContents ->
+            append_new_ini_section({{ModuleName, VariableList}, Value}, OldFileContents);
+        _ ->
+            NewFileContents2
+    end,
+    
+    % do the save, close the config file and get out
+    save_file(File, NewFileContents),
+    file:close(Stream),
+    ok.
+
+%% @doc Iterates over the lines of an ini file and replaces or adds a new
+%%      configuration directive.
+save_loop({{Module, Variable}, Value}, [Line|Rest], OldCurrentModule, Contents, DoneVariables) ->
+
+    % if we find a new [ini section] (Module), save that for reference
+    NewCurrentModule = parse_module(Line, OldCurrentModule),
+
+    % if the current Module is the one we want to change, try to match
+    % each line with the Variable
+    NewContents = case Module of
+        NewCurrentModule ->
+            % see if the current line matches the variable we want to substitute
+            case parse_variable(Line, Variable, Value) of
+                % nope, return original line
+                nomatch ->
+                    DoneVariables2 = DoneVariables,
+                    Line;
+                % got em! return new line
+                NewLine ->
+                    DoneVariables2 = [Variable|DoneVariables],
+                    NewLine
+            end;
+        % if the variable we want to change couldn't be replaced, we append it
+        % in the proper module section
+        OldCurrentModule ->
+            case lists:member(Variable, DoneVariables) of
+                false ->
+                    DoneVariables2 = [Variable|DoneVariables],
+                    Variable ++ "=" ++ Value ++ "\n" ++ Line;
+                true ->
+                    DoneVariables2 = DoneVariables,
+                    Line
+            end;
+        % otherwise we just print out the original line
+        _ ->
+            DoneVariables2 = DoneVariables,
+            Line
+        end,
+    % clumsy way to only append a newline character
+    % if the line is not empty. We need this to not
+    % avoid haveing a newline inserted at the top
+    % of the target file each time we save it.
+    Contents2 = case Contents of "" -> ""; _ -> Contents ++ "\n" end,
+
+    % go to next line
+    save_loop({{Module, Variable}, Value}, Rest, NewCurrentModule, Contents2 ++ NewContents, DoneVariables2);
+    
+save_loop(_Config, [], _OldModule, NewFileContents, _DoneVariable) ->
+    % we're out of new lines, just return the new file's contents
+    NewFileContents.
+
+append_new_ini_section({{ModuleName, Variable}, Value}, OldFileContents) ->
+    OldFileContents ++ "\n\n" ++ ModuleName ++ "\n" ++  Variable ++ "=" ++ Value ++ "\n".
+
+%% @spec parse_module(Lins::string(), OldModule::string()) -> string()
+%% @doc Tries to match a line against a pattern specifying a ini module or 
+%%      section ("[Module]"). Returns OldModule if no match is found.
+parse_module(Line, OldModule) ->
+    case regexp:match(Line, "^\\[([a-zA-Z0-9_-]*)\\]$") of
+        nomatch ->
+            OldModule;
+        {error, Error} ->
+            io:format("ini file regex error module: '~s'~n", [Error]),
+            OldModule;
+        {match, Start, Length} ->
+            string:substr(Line, Start, Length)
+    end.
+
+%% @spec parse_variable(Line::string(), Variable::string(), Value::string()) ->
+%%         string() | nomatch
+%% @doc Tries to match a variable assignment in Line. Returns nomatch if the
+%%      Variable is not found. Returns a new line composed of the Variable and
+%%      Value otherwise.
+parse_variable(Line, Variable, Value) ->
+    case regexp:match(Line, "^" ++ Variable ++ "=") of
+        nomatch ->
+            nomatch;
+        {error, Error}->
+            io:format("ini file regex error variable: '~s'~n", [Error]),
+            nomatch;
+        {match, _Start, _Length} ->
+            Variable ++ "=" ++ Value
+    end.
+
+%% @spec save_file(File::filename(), Contents::string()) -> 
+%%           ok | {error, Reason::string()}
+%% @doc Writes Contents to File
+save_file(File, Contents) ->
+    file:write_file(File, list_to_binary(Contents)).
\ No newline at end of file

Added: incubator/couchdb/trunk/src/couchdb/couch_db_update_notifier_sup.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_db_update_notifier_sup.erl?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_db_update_notifier_sup.erl (added)
+++ incubator/couchdb/trunk/src/couchdb/couch_db_update_notifier_sup.erl Wed Aug 20 07:18:05 2008
@@ -0,0 +1,50 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License.  You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+%
+% This causes an OS process to spawned and it is notified every time a database
+% is updated.
+%
+% The notifications are in the form of a the database name sent as a line of
+% text to the OS processes stdout.
+%
+
+-module(couch_db_update_notifier_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0,init/1]).
+
+start_link() ->
+    supervisor:start_link({local, couch_db_update_notifier_sup},
+        couch_db_update_notifier_sup, []).
+
+init([]) ->
+    Self = self(),
+    ok = couch_config:register(
+        fun({"Update Notification", _}) ->
+            exit(Self, reload_config)
+        end),
+    
+    UpdateNotifierExes = couch_config:lookup_match(
+            {{"Update Notification", '$1'}, '$2'}, []),
+
+    {ok,
+        {{one_for_one, 10, 3600}, 
+            lists:map(fun({Name, UpdateNotifierExe}) ->
+                {Name,
+                {couch_db_update_notifier, start_link, [UpdateNotifierExe]},
+                    permanent,
+                    1000,
+                    supervisor,
+                    [couch_db_update_notifier]}
+                end, UpdateNotifierExes)}}.
\ No newline at end of file

Added: incubator/couchdb/trunk/test/Makefile.am
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/test/Makefile.am?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/test/Makefile.am (added)
+++ incubator/couchdb/trunk/test/Makefile.am Wed Aug 20 07:18:05 2008
@@ -0,0 +1,20 @@
+## Licensed under the Apache License, Version 2.0 (the "License"); you may not
+## use this file except in compliance with the License.  You may obtain a copy
+## of the License at
+##
+##   http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+## License for the specific language governing permissions and limitations
+## under the License.
+
+TESTS = runner.sh
+
+check_PROGRAMS = runner.beam
+
+CLEANFILES = test.ini
+
+runner.beam: runner.erl
+	$(ERLC) $<

Added: incubator/couchdb/trunk/test/couch_config_test.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/test/couch_config_test.erl?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/test/couch_config_test.erl (added)
+++ incubator/couchdb/trunk/test/couch_config_test.erl Wed Aug 20 07:18:05 2008
@@ -0,0 +1,43 @@
+% couch_config module test suote
+
+% Set up test suite
+% ?MODULE_test() returns a list of functions 
+% that run the actual tests.
+couch_config_test() ->
+    [
+        fun() -> store_tuples() end, 
+        fun() -> store_strings() end,
+        fun() -> store_numbers() end,
+        fun() -> store_tuple_key() end
+    ].
+
+
+% test functions
+
+% test storing different types and see if they come back
+% the same way there put in.
+store_tuples() ->
+    store(key, value).
+  
+store_strings() ->
+    store("key", "value").
+
+store_numbers() ->
+    store("number_key", 12345).
+
+store_tuple_key() ->
+    store({key, subkey}, value).
+
+    
+store(Key2, Value2) ->
+    Key = binary_to_list(term_to_binary(Key2)),
+    Value = binary_to_list(term_to_binary(Value2)),
+
+    couch_config:start_link(["couch.ini"]),
+
+    couch_config:store({"test_module", Key}, Value),
+    Result = couch_config:get({"test_module", Key}),
+    couch_config:unset(Key),
+
+    couch_config:terminate(end_of_test, ok),
+    Value = Result.
\ No newline at end of file

Added: incubator/couchdb/trunk/test/couch_config_writer_test.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/test/couch_config_writer_test.erl?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/test/couch_config_writer_test.erl (added)
+++ incubator/couchdb/trunk/test/couch_config_writer_test.erl Wed Aug 20 07:18:05 2008
@@ -0,0 +1,181 @@
+% couch_config_writer module test suote
+
+% Set up test suite
+% ?MODULE_test() returns a list of functions 
+% that run the actual tests.
+couch_config_writer_test() ->
+    [
+        fun() -> replace_existing_variable() end,
+        fun() -> append_new_variable() end,
+        fun() -> append_new_module() end
+    ].
+
+
+% test functions
+replace_existing_variable() ->
+    % create file
+    Contents = "; etc/couchdb/couch.ini.tpl.  Generated from couch.ini.tpl.in by configure.
+
+[CouchDB]
+RootDirectory=/Users/jan/Work/runcouch/conf9/var/lib/couchdb
+UtilDriverDir=/Users/jan/Work/runcouch/conf9/lib/couchdb/erlang/lib/couch-0.7.3a663206/priv/lib
+MaximumDocumentSize=4294967296 ; 4 GB
+
+[HTTPd]
+Port=5984
+BindAddress=127.0.0.1
+DocumentRoot=/Users/jan/Work/runcouch/conf9/share/couchdb/www
+
+[Log]
+File=/Users/jan/Work/runcouch/conf9/var/log/couchdb/couch.log
+Level=info
+
+[CouchDB Query Servers]
+javascript=/Users/jan/Work/runcouch/conf9/bin/couchjs /Users/jan/Work/runcouch/conf9/share/couchdb/server/main.js
+
+[CouchDB Query Server Options]
+QueryTimeout=5000 ; 5 seconds
+",
+
+    Expect = "; etc/couchdb/couch.ini.tpl.  Generated from couch.ini.tpl.in by configure.
+
+[CouchDB]
+RootDirectory=/Users/jan/Work/runcouch/conf9/var/lib/couchdb
+UtilDriverDir=/Users/jan/Work/runcouch/conf9/lib/couchdb/erlang/lib/couch-0.7.3a663206/priv/lib
+MaximumDocumentSize=4294967296 ; 4 GB
+
+[HTTPd]
+Port=5985
+BindAddress=127.0.0.1
+DocumentRoot=/Users/jan/Work/runcouch/conf9/share/couchdb/www
+
+[Log]
+File=/Users/jan/Work/runcouch/conf9/var/log/couchdb/couch.log
+Level=info
+
+[CouchDB Query Servers]
+javascript=/Users/jan/Work/runcouch/conf9/bin/couchjs /Users/jan/Work/runcouch/conf9/share/couchdb/server/main.js
+
+[CouchDB Query Server Options]
+QueryTimeout=5000 ; 5 seconds
+",
+    run_operation_and_compare_results(Contents, Expect, {{"HTTPd", "Port"}, "5985"}).
+
+
+append_new_variable() ->
+    % create file
+    Contents = "; etc/couchdb/couch.ini.tpl.  Generated from couch.ini.tpl.in by configure.
+
+[CouchDB]
+RootDirectory=/Users/jan/Work/runcouch/conf9/var/lib/couchdb
+UtilDriverDir=/Users/jan/Work/runcouch/conf9/lib/couchdb/erlang/lib/couch-0.7.3a663206/priv/lib
+MaximumDocumentSize=4294967296 ; 4 GB
+
+[HTTPd]
+Port=5984
+BindAddress=127.0.0.1
+DocumentRoot=/Users/jan/Work/runcouch/conf9/share/couchdb/www
+
+[Log]
+File=/Users/jan/Work/runcouch/conf9/var/log/couchdb/couch.log
+Level=info
+
+[CouchDB Query Servers]
+javascript=/Users/jan/Work/runcouch/conf9/bin/couchjs /Users/jan/Work/runcouch/conf9/share/couchdb/server/main.js
+
+[CouchDB Query Server Options]
+QueryTimeout=5000 ; 5 seconds
+",
+
+    Expect = "; etc/couchdb/couch.ini.tpl.  Generated from couch.ini.tpl.in by configure.
+
+[CouchDB]
+RootDirectory=/Users/jan/Work/runcouch/conf9/var/lib/couchdb
+UtilDriverDir=/Users/jan/Work/runcouch/conf9/lib/couchdb/erlang/lib/couch-0.7.3a663206/priv/lib
+MaximumDocumentSize=4294967296 ; 4 GB
+
+[HTTPd]
+Port=5984
+BindAddress=127.0.0.1
+DocumentRoot=/Users/jan/Work/runcouch/conf9/share/couchdb/www
+
+FantasyConfiguration=Citation Needed
+[Log]
+File=/Users/jan/Work/runcouch/conf9/var/log/couchdb/couch.log
+Level=info
+
+[CouchDB Query Servers]
+javascript=/Users/jan/Work/runcouch/conf9/bin/couchjs /Users/jan/Work/runcouch/conf9/share/couchdb/server/main.js
+
+[CouchDB Query Server Options]
+QueryTimeout=5000 ; 5 seconds
+",
+    run_operation_and_compare_results(Contents, Expect, {{"HTTPd", "FantasyConfiguration"}, "Citation Needed"}).
+
+
+append_new_module() ->
+    % create file
+    Contents = "; etc/couchdb/couch.ini.tpl.  Generated from couch.ini.tpl.in by configure.
+
+[CouchDB]
+RootDirectory=/Users/jan/Work/runcouch/conf9/var/lib/couchdb
+UtilDriverDir=/Users/jan/Work/runcouch/conf9/lib/couchdb/erlang/lib/couch-0.7.3a663206/priv/lib
+MaximumDocumentSize=4294967296 ; 4 GB
+
+[HTTPd]
+Port=5984
+BindAddress=127.0.0.1
+DocumentRoot=/Users/jan/Work/runcouch/conf9/share/couchdb/www
+
+[Log]
+File=/Users/jan/Work/runcouch/conf9/var/log/couchdb/couch.log
+Level=info
+
+[CouchDB Query Servers]
+javascript=/Users/jan/Work/runcouch/conf9/bin/couchjs /Users/jan/Work/runcouch/conf9/share/couchdb/server/main.js
+
+[CouchDB Query Server Options]
+QueryTimeout=5000 ; 5 seconds",
+
+    Expect = "; etc/couchdb/couch.ini.tpl.  Generated from couch.ini.tpl.in by configure.
+
+[CouchDB]
+RootDirectory=/Users/jan/Work/runcouch/conf9/var/lib/couchdb
+UtilDriverDir=/Users/jan/Work/runcouch/conf9/lib/couchdb/erlang/lib/couch-0.7.3a663206/priv/lib
+MaximumDocumentSize=4294967296 ; 4 GB
+
+[HTTPd]
+Port=5984
+BindAddress=127.0.0.1
+DocumentRoot=/Users/jan/Work/runcouch/conf9/share/couchdb/www
+
+[Log]
+File=/Users/jan/Work/runcouch/conf9/var/log/couchdb/couch.log
+Level=info
+
+[CouchDB Query Servers]
+javascript=/Users/jan/Work/runcouch/conf9/bin/couchjs /Users/jan/Work/runcouch/conf9/share/couchdb/server/main.js
+
+[CouchDB Query Server Options]
+QueryTimeout=5000 ; 5 seconds
+
+[Erlang]
+Option=Value
+",
+    run_operation_and_compare_results(Contents, Expect, {{"Erlang", "Option"}, "Value"}).
+  
+run_operation_and_compare_results(Contents, Expect, Config) ->
+    Filename = "couch.ini",
+    file:write_file(Filename, Contents),
+
+    % call replace function
+    couch_config_writer:save_to_file(Config, Filename),
+    
+    % compare new file with expected file
+    {ok, Result_} = file:read_file(Filename),
+    Result = binary_to_list(Result_),
+
+    % clean up
+    % file:delete(Filename),
+    
+    Result = Expect.

Added: incubator/couchdb/trunk/test/runner.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/test/runner.erl?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/test/runner.erl (added)
+++ incubator/couchdb/trunk/test/runner.erl Wed Aug 20 07:18:05 2008
@@ -0,0 +1,67 @@
+-module(runner).
+
+-export([run/0]).
+-include("couch_config_test.erl").
+-include("couch_config_writer_test.erl").
+
+%% Test Runner
+run() ->
+    % mochiweb tests
+    % case mochiweb:test() of
+    %     ok ->
+    %         io:format("Mochiweb Tests PASSED~n");
+    %     _ ->
+    %         io:format("Mochiweb Tests FAILED~n")
+    % end,
+
+    % CouchDB tests
+    Tests = lists:flatten([
+        couch_config_test(),
+        couch_config_writer_test()
+    ]),
+    run_tests(Tests),
+
+    % we're done, get out of here
+    halt().
+
+run_test(TestFun) ->
+    io:format("  ~s        ", [proplists:get_value(
+                                 name,
+                                 erlang:fun_info(TestFun))]),
+    try TestFun() of
+        _ ->
+            io:format("[PASSED]~n", []),
+            passed
+    catch
+        _:{skipped, Reason} ->
+            io:format("[SKIPPED]~n", []),
+            io:format("      reason: ~s~n", [Reason]),
+            skipped;
+        _:X ->
+            io:format("[FAILED]~n", []),
+            io:format("ERROR: ~n======~n~p ~n======~n~n",
+                      [{X, erlang:get_stacktrace()}]),
+            failed
+    end.
+
+run_tests(List) ->
+    io:format("Running ~p tests...~n", [lists:flatlength(List)]),
+
+    Results = lists:map(fun run_test/1, List),
+
+    Passed = lists:filter(
+               fun (Result) -> Result =:= passed end,
+               Results),
+
+    Failed = lists:filter(
+               fun (Result) -> Result =:= failed end,
+               Results),
+
+    Skipped = lists:filter(
+                fun(Result) -> Result =:= skipped end,
+                Results),
+
+    io:format("PASSED: ~p, FAILED: ~p, SKIPPED: ~p ~n",
+              [lists:flatlength(Passed),
+               lists:flatlength(Failed),
+               lists:flatlength(Skipped)]).

Added: incubator/couchdb/trunk/test/runner.sh
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/test/runner.sh?rev=687339&view=auto
==============================================================================
--- incubator/couchdb/trunk/test/runner.sh (added)
+++ incubator/couchdb/trunk/test/runner.sh Wed Aug 20 07:18:05 2008
@@ -0,0 +1,3 @@
+#!/bin/sh -e
+
+erl -noshell -pa ../src/couchdb -pa ../src/mochiweb -eval "runner:run()"