diff options
-rw-r--r-- | etc/couchdb/default.ini.tpl.in | 23 | ||||
-rw-r--r-- | etc/couchdb/local.ini | 20 | ||||
-rw-r--r-- | src/couchdb/couch_config.erl | 195 | ||||
-rw-r--r-- | src/couchdb/couch_config_writer.erl | 147 | ||||
-rw-r--r-- | src/couchdb/couch_db_update_notifier_sup.erl | 50 | ||||
-rw-r--r-- | test/Makefile.am | 20 | ||||
-rw-r--r-- | test/couch_config_test.erl | 43 | ||||
-rw-r--r-- | test/couch_config_writer_test.erl | 181 | ||||
-rw-r--r-- | test/runner.erl | 67 | ||||
-rw-r--r-- | test/runner.sh | 3 |
10 files changed, 749 insertions, 0 deletions
diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in new file mode 100644 index 00000000..07d2a9a3 --- /dev/null +++ b/etc/couchdb/default.ini.tpl.in @@ -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 diff --git a/etc/couchdb/local.ini b/etc/couchdb/local.ini new file mode 100644 index 00000000..eb4510d6 --- /dev/null +++ b/etc/couchdb/local.ini @@ -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" + + diff --git a/src/couchdb/couch_config.erl b/src/couchdb/couch_config.erl new file mode 100644 index 00000000..07d47dff --- /dev/null +++ b/src/couchdb/couch_config.erl @@ -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 diff --git a/src/couchdb/couch_config_writer.erl b/src/couchdb/couch_config_writer.erl new file mode 100644 index 00000000..6e93c131 --- /dev/null +++ b/src/couchdb/couch_config_writer.erl @@ -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 diff --git a/src/couchdb/couch_db_update_notifier_sup.erl b/src/couchdb/couch_db_update_notifier_sup.erl new file mode 100644 index 00000000..8902498d --- /dev/null +++ b/src/couchdb/couch_db_update_notifier_sup.erl @@ -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 diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 00000000..47227fc8 --- /dev/null +++ b/test/Makefile.am @@ -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) $< diff --git a/test/couch_config_test.erl b/test/couch_config_test.erl new file mode 100644 index 00000000..b9dc71ad --- /dev/null +++ b/test/couch_config_test.erl @@ -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 diff --git a/test/couch_config_writer_test.erl b/test/couch_config_writer_test.erl new file mode 100644 index 00000000..b093edff --- /dev/null +++ b/test/couch_config_writer_test.erl @@ -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. diff --git a/test/runner.erl b/test/runner.erl new file mode 100644 index 00000000..5cf559f3 --- /dev/null +++ b/test/runner.erl @@ -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)]). diff --git a/test/runner.sh b/test/runner.sh new file mode 100644 index 00000000..ae0f1ef8 --- /dev/null +++ b/test/runner.sh @@ -0,0 +1,3 @@ +#!/bin/sh -e + +erl -noshell -pa ../src/couchdb -pa ../src/mochiweb -eval "runner:run()" |