1-module(couch_replicator_test_helper).
2
3-include_lib("couch/include/couch_eunit.hrl").
4-include_lib("couch/include/couch_db.hrl").
5-include_lib("couch_replicator/src/couch_replicator.hrl").
6
7-export([
8    compare_dbs/2,
9    compare_dbs/3,
10    db_url/1,
11    replicate/1,
12    get_pid/1,
13    replicate/2
14]).
15
16
17compare_dbs(Source, Target) ->
18    compare_dbs(Source, Target, []).
19
20
21compare_dbs(Source, Target, ExceptIds) ->
22    {ok, SourceDb} = couch_db:open_int(Source, []),
23    {ok, TargetDb} = couch_db:open_int(Target, []),
24
25    Fun = fun(FullDocInfo, Acc) ->
26        {ok, DocSource} = couch_db:open_doc(SourceDb, FullDocInfo),
27        Id = DocSource#doc.id,
28        case lists:member(Id, ExceptIds) of
29            true ->
30                ?assertEqual(not_found, couch_db:get_doc_info(TargetDb, Id));
31            false ->
32                {ok, TDoc} = couch_db:open_doc(TargetDb, Id),
33                compare_docs(DocSource, TDoc)
34        end,
35        {ok, Acc}
36    end,
37
38    {ok, _} = couch_db:fold_docs(SourceDb, Fun, [], []),
39    ok = couch_db:close(SourceDb),
40    ok = couch_db:close(TargetDb).
41
42
43compare_docs(Doc1, Doc2) ->
44    ?assertEqual(Doc1#doc.body, Doc2#doc.body),
45    #doc{atts = Atts1} = Doc1,
46    #doc{atts = Atts2} = Doc2,
47    ?assertEqual(lists:sort([couch_att:fetch(name, Att) || Att <- Atts1]),
48                 lists:sort([couch_att:fetch(name, Att) || Att <- Atts2])),
49    FunCompareAtts = fun(Att) ->
50        AttName = couch_att:fetch(name, Att),
51        {ok, AttTarget} = find_att(Atts2, AttName),
52        SourceMd5 = att_md5(Att),
53        TargetMd5 = att_md5(AttTarget),
54        case AttName of
55            <<"att1">> ->
56                ?assertEqual(gzip, couch_att:fetch(encoding, Att)),
57                ?assertEqual(gzip, couch_att:fetch(encoding, AttTarget)),
58                DecSourceMd5 = att_decoded_md5(Att),
59                DecTargetMd5 = att_decoded_md5(AttTarget),
60                ?assertEqual(DecSourceMd5, DecTargetMd5);
61            _ ->
62                ?assertEqual(identity, couch_att:fetch(encoding, AttTarget)),
63                ?assertEqual(identity, couch_att:fetch(encoding, AttTarget))
64        end,
65        ?assertEqual(SourceMd5, TargetMd5),
66        ?assert(is_integer(couch_att:fetch(disk_len, Att))),
67        ?assert(is_integer(couch_att:fetch(att_len, Att))),
68        ?assert(is_integer(couch_att:fetch(disk_len, AttTarget))),
69        ?assert(is_integer(couch_att:fetch(att_len, AttTarget))),
70        ?assertEqual(couch_att:fetch(disk_len, Att),
71                     couch_att:fetch(disk_len, AttTarget)),
72        ?assertEqual(couch_att:fetch(att_len, Att),
73                     couch_att:fetch(att_len, AttTarget)),
74        ?assertEqual(couch_att:fetch(type, Att),
75                     couch_att:fetch(type, AttTarget)),
76        ?assertEqual(couch_att:fetch(md5, Att),
77                     couch_att:fetch(md5, AttTarget))
78    end,
79    lists:foreach(FunCompareAtts, Atts1).
80
81
82find_att([], _Name) ->
83    nil;
84find_att([Att | Rest], Name) ->
85    case couch_att:fetch(name, Att) of
86        Name ->
87            {ok, Att};
88        _ ->
89            find_att(Rest, Name)
90    end.
91
92
93att_md5(Att) ->
94    Md50 = couch_att:foldl(
95        Att,
96        fun(Chunk, Acc) -> couch_hash:md5_hash_update(Acc, Chunk) end,
97        couch_hash:md5_hash_init()),
98    couch_hash:md5_hash_final(Md50).
99
100att_decoded_md5(Att) ->
101    Md50 = couch_att:foldl_decode(
102        Att,
103        fun(Chunk, Acc) -> couch_hash:md5_hash_update(Acc, Chunk) end,
104        couch_hash:md5_hash_init()),
105    couch_hash:md5_hash_final(Md50).
106
107db_url(DbName) ->
108    iolist_to_binary([
109        "http://", config:get("httpd", "bind_address", "127.0.0.1"),
110        ":", integer_to_list(mochiweb_socket_server:get(couch_httpd, port)),
111        "/", DbName
112    ]).
113
114get_pid(RepId) ->
115    Pid = global:whereis_name({couch_replicator_scheduler_job,RepId}),
116    ?assert(is_pid(Pid)),
117    Pid.
118
119replicate(Source, Target) ->
120    replicate({[
121        {<<"source">>, Source},
122        {<<"target">>, Target}
123    ]}).
124
125replicate({[_ | _]} = RepObject) ->
126    {ok, Rep} = couch_replicator_utils:parse_rep_doc(RepObject, ?ADMIN_USER),
127    ok = couch_replicator_scheduler:add_job(Rep),
128    couch_replicator_scheduler:reschedule(),
129    Pid = get_pid(Rep#rep.id),
130    MonRef = erlang:monitor(process, Pid),
131    receive
132        {'DOWN', MonRef, process, Pid, _} ->
133            ok
134    end,
135    ok = couch_replicator_scheduler:remove_job(Rep#rep.id).
136