summaryrefslogtreecommitdiff
path: root/apps/couch/test/etap/011-file-headers.t
diff options
context:
space:
mode:
Diffstat (limited to 'apps/couch/test/etap/011-file-headers.t')
-rwxr-xr-xapps/couch/test/etap/011-file-headers.t145
1 files changed, 145 insertions, 0 deletions
diff --git a/apps/couch/test/etap/011-file-headers.t b/apps/couch/test/etap/011-file-headers.t
new file mode 100755
index 00000000..4705f629
--- /dev/null
+++ b/apps/couch/test/etap/011-file-headers.t
@@ -0,0 +1,145 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%! -pa ./src/couchdb -sasl errlog_type error -boot start_sasl -noshell
+
+% 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.
+
+filename() -> test_util:build_file("test/etap/temp.011").
+sizeblock() -> 4096. % Need to keep this in sync with couch_file.erl
+
+main(_) ->
+ test_util:init_code_path(),
+ {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(filename(), [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(filename(), [create,overwrite]),
+ {ok, RawFd} = file:open(filename(), [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 sizeblock()) * sizeblock()};
+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).
+