Skip to content

Commit

Permalink
Adding Erlang unit tests.
Browse files Browse the repository at this point in the history
To run these tests:

    $ git clone git://github.com/ngerakines/etap.git
    $ cd etap
    $ sudo make install
    $ cd /path/to/couchdb
    $ ./bootstrap && ./configure && make check

So far I've worked through most of couch_file.erl, couch_btree.erl, and couch_doc.erl. Tomorrow I'll be adding coverage reporting so that we can see what code we're actually testing.



git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@780197 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
davisp committed May 30, 2009
1 parent fe63c51 commit 519ba57
Show file tree
Hide file tree
Showing 8 changed files with 959 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Makefile.am
Expand Up @@ -36,6 +36,9 @@ README.gz: $(top_srcdir)/README
THANKS.gz: $(top_srcdir)/THANKS
-gzip -9 < $< > $@

check: all
prove t/*.t

dev: all
@echo "This command is intended for developers to use;"
@echo "it creates development ini files as well as a"
Expand Down
58 changes: 58 additions & 0 deletions t/001-load.t
@@ -0,0 +1,58 @@
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -pa src/couchdb -sasl errlog_type error -boot start_sasl -noshell

% Test that we can load each module.

main(_) ->
etap:plan(39),
Modules = [
couch_batch_save,
couch_batch_save_sup,
couch_btree,
couch_config,
couch_config_writer,
couch_db,
couch_db_update_notifier,
couch_db_update_notifier_sup,
couch_db_updater,
couch_doc,
couch_event_sup,
couch_external_manager,
couch_external_server,
couch_file,
couch_httpd,
couch_httpd_db,
couch_httpd_external,
couch_httpd_misc_handlers,
couch_httpd_show,
couch_httpd_stats_handlers,
couch_httpd_view,
couch_key_tree,
couch_log,
couch_os_process,
couch_query_servers,
couch_ref_counter,
couch_rep,
couch_rep_sup,
couch_server,
couch_server_sup,
couch_stats_aggregator,
couch_stats_collector,
couch_stream,
couch_task_status,
couch_util,
couch_view,
couch_view_compactor,
couch_view_group,
couch_view_updater
],

lists:foreach(
fun(Module) ->
etap_can:loaded_ok(
Module,
lists:concat(["Loaded: ", Module])
)
end, Modules),
etap:end_tests().
83 changes: 83 additions & 0 deletions t/010-file-basics.t
@@ -0,0 +1,83 @@
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -pa ./src/couchdb -sasl errlog_type error -boot start_sasl -noshell

-define(FILE_NAME, "./t/temp.010").

main(_) ->
etap:plan(16),
case (catch test()) of
ok ->
etap:end_tests();
Other ->
etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
etap:bail()
end,
ok.

test() ->
etap:is({error, enoent}, couch_file:open("not a real file"),
"Opening a non-existant file should return an enoent error."),

etap:fun_is(
fun({ok, _}) -> true; (_) -> false end,
couch_file:open(?FILE_NAME ++ ".1", [create, invalid_option]),
"Invalid flags to open are ignored."
),

{ok, Fd} = couch_file:open(?FILE_NAME ++ ".0", [create, overwrite]),
etap:ok(is_pid(Fd),
"Returned file descriptor is a Pid"),

etap:is({ok, 0}, couch_file:bytes(Fd),
"Newly created files have 0 bytes."),

etap:is({ok, 0}, couch_file:append_term(Fd, foo),
"Appending a term returns the previous end of file position."),

{ok, Size} = couch_file:bytes(Fd),
etap:is_greater(Size, 0,
"Writing a term increased the file size."),

etap:is({ok, Size}, couch_file:append_binary(Fd, <<"fancy!">>),
"Appending a binary returns the current file size."),

etap:is({ok, foo}, couch_file:pread_term(Fd, 0),
"Reading the first term returns what we wrote: foo"),

etap:is({ok, <<"fancy!">>}, couch_file:pread_binary(Fd, Size),
"Reading back the binary returns what we wrote: <<\"fancy\">>."),

etap:is({ok, <<131, 100, 0, 3, 102, 111, 111>>},
couch_file:pread_binary(Fd, 0),
"Reading a binary at a term position returns the term as binary."
),

{ok, BinPos} = couch_file:append_binary(Fd, <<131,100,0,3,102,111,111>>),
etap:is({ok, foo}, couch_file:pread_term(Fd, BinPos),
"Reading a term from a written binary term representation succeeds."),

% append_binary == append_iolist?
% Possible bug in pread_iolist or iolist() -> append_binary
{ok, IOLPos} = couch_file:append_binary(Fd, ["foo", $m, <<"bam">>]),
etap:is({ok, [<<"foombam">>]}, couch_file:pread_iolist(Fd, IOLPos),
"Reading an results in a binary form of the written iolist()"),

% XXX: How does on test fsync?
etap:is(ok, couch_file:sync(Fd),
"Syncing does not cause an error."),

etap:is(ok, couch_file:truncate(Fd, Size),
"Truncating a file succeeds."),

%etap:is(eof, (catch couch_file:pread_binary(Fd, Size)),
% "Reading data that was truncated fails.")
etap:skip(fun() -> ok end,
"No idea how to test reading beyond EOF"),

etap:is({ok, foo}, couch_file:pread_term(Fd, 0),
"Truncating does not affect data located before the truncation mark."),

etap:is(ok, couch_file:close(Fd),
"Files close properly."),
ok.
133 changes: 133 additions & 0 deletions t/011-file-headers.t
@@ -0,0 +1,133 @@
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -pa ./src/couchdb -sasl errlog_type error -boot start_sasl -noshell

-define(FILE_NAME, "./t/temp.011").
-define(SIZE_BLOCK, 4096). % Need to keep this in sync with couch_file.erl

main(_) ->
{S1, S2, S3} = now(),
random:seed(S1, S2, S3),

etap:plan(17),
case (catch test()) of
ok ->
etap:end_tests();
Other ->
etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
etap:bail()
end,
ok.

test() ->
{ok, Fd} = couch_file:open(?FILE_NAME, [create,overwrite]),

etap:is({ok, 0}, couch_file:bytes(Fd),
"File should be initialized to contain zero bytes."),

etap:is(ok, couch_file:write_header(Fd, {<<"some_data">>, 32}),
"Writing a header succeeds."),

{ok, Size1} = couch_file:bytes(Fd),
etap:is_greater(Size1, 0,
"Writing a header allocates space in the file."),

etap:is({ok, {<<"some_data">>, 32}}, couch_file:read_header(Fd),
"Reading the header returns what we wrote."),

etap:is(ok, couch_file:write_header(Fd, [foo, <<"more">>]),
"Writing a second header succeeds."),

{ok, Size2} = couch_file:bytes(Fd),
etap:is_greater(Size2, Size1,
"Writing a second header allocates more space."),

etap:is({ok, [foo, <<"more">>]}, couch_file:read_header(Fd),
"Reading the second header does not return the first header."),

% Delete the second header.
ok = couch_file:truncate(Fd, Size1),

etap:is({ok, {<<"some_data">>, 32}}, couch_file:read_header(Fd),
"Reading the header after a truncation returns a previous header."),

couch_file:write_header(Fd, [foo, <<"more">>]),
etap:is({ok, Size2}, couch_file:bytes(Fd),
"Rewriting the same second header returns the same second size."),

ok = couch_file:close(Fd),

% Now for the fun stuff. Try corrupting the second header and see
% if we recover properly.

% Destroy the 0x1 byte that marks a header
check_header_recovery(fun(CouchFd, RawFd, Expect, HeaderPos) ->
etap:isnt(Expect, couch_file:read_header(CouchFd),
"Should return a different header before corruption."),
file:pwrite(RawFd, HeaderPos, <<0>>),
etap:is(Expect, couch_file:read_header(CouchFd),
"Corrupting the byte marker should read the previous header.")
end),

% Corrupt the size.
check_header_recovery(fun(CouchFd, RawFd, Expect, HeaderPos) ->
etap:isnt(Expect, couch_file:read_header(CouchFd),
"Should return a different header before corruption."),
% +1 for 0x1 byte marker
file:pwrite(RawFd, HeaderPos+1, <<10/integer>>),
etap:is(Expect, couch_file:read_header(CouchFd),
"Corrupting the size should read the previous header.")
end),

% Corrupt the MD5 signature
check_header_recovery(fun(CouchFd, RawFd, Expect, HeaderPos) ->
etap:isnt(Expect, couch_file:read_header(CouchFd),
"Should return a different header before corruption."),
% +5 = +1 for 0x1 byte and +4 for term size.
file:pwrite(RawFd, HeaderPos+5, <<"F01034F88D320B22">>),
etap:is(Expect, couch_file:read_header(CouchFd),
"Corrupting the MD5 signature should read the previous header.")
end),

% Corrupt the data
check_header_recovery(fun(CouchFd, RawFd, Expect, HeaderPos) ->
etap:isnt(Expect, couch_file:read_header(CouchFd),
"Should return a different header before corruption."),
% +21 = +1 for 0x1 byte, +4 for term size and +16 for MD5 sig
file:pwrite(RawFd, HeaderPos+21, <<"some data goes here!">>),
etap:is(Expect, couch_file:read_header(CouchFd),
"Corrupting the header data should read the previous header.")
end),

ok.

check_header_recovery(CheckFun) ->
{ok, Fd} = couch_file:open(?FILE_NAME, [create,overwrite]),
{ok, RawFd} = file:open(?FILE_NAME, [read, write, raw, binary]),

{ok, _} = write_random_data(Fd),
ExpectHeader = {some_atom, <<"a binary">>, 756},
ok = couch_file:write_header(Fd, ExpectHeader),

{ok, HeaderPos} = write_random_data(Fd),
ok = couch_file:write_header(Fd, {2342, <<"corruption! greed!">>}),

CheckFun(Fd, RawFd, {ok, ExpectHeader}, HeaderPos),

ok = file:close(RawFd),
ok = couch_file:close(Fd),
ok.

write_random_data(Fd) ->
write_random_data(Fd, 100 + random:uniform(1000)).

write_random_data(Fd, 0) ->
{ok, Bytes} = couch_file:bytes(Fd),
{ok, (1 + Bytes div ?SIZE_BLOCK) * ?SIZE_BLOCK};
write_random_data(Fd, N) ->
Choices = [foo, bar, <<"bizzingle">>, "bank", ["rough", stuff]],
Term = lists:nth(random:uniform(4) + 1, Choices),
{ok, _} = couch_file:append_term(Fd, Term),
write_random_data(Fd, N-1).


0 comments on commit 519ba57

Please sign in to comment.