diff options
Diffstat (limited to '1.1.x/test/etap/011-file-headers.t')
-rwxr-xr-x | 1.1.x/test/etap/011-file-headers.t | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/1.1.x/test/etap/011-file-headers.t b/1.1.x/test/etap/011-file-headers.t new file mode 100755 index 00000000..4705f629 --- /dev/null +++ b/1.1.x/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). + |