1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(couch_task_status_tests).
14
15-include_lib("couch/include/couch_eunit.hrl").
16-include_lib("couch/include/couch_db.hrl").
17
18-define(TIMEOUT, 1000).
19
20
21setup() ->
22    Ctx = test_util:start(?MODULE, [couch_log], [{dont_mock, [config]}]),
23    {ok, TaskStatusPid} = couch_task_status:start_link(),
24    TaskUpdaterPid = spawn(fun() -> loop() end),
25    {TaskStatusPid, TaskUpdaterPid, Ctx}.
26
27
28teardown({TaskStatusPid, _, Ctx})->
29    test_util:stop_sync_throw(TaskStatusPid, fun() ->
30        couch_task_status:stop()
31    end, timeout_error, ?TIMEOUT),
32    test_util:stop(Ctx).
33
34
35couch_task_status_test_() ->
36    {
37        "CouchDB task status updates",
38        {
39            foreach,
40            fun setup/0, fun teardown/1,
41            [
42                fun should_register_task/1,
43                fun should_set_task_startup_time/1,
44                fun should_have_update_time_as_startup_before_any_progress/1,
45                fun should_set_task_type/1,
46                fun should_not_register_multiple_tasks_for_same_pid/1,
47                fun should_set_task_progress/1,
48                fun should_update_task_progress/1,
49                fun should_update_time_changes_on_task_progress/1,
50                %% fun should_control_update_frequency/1,
51                fun should_reset_control_update_frequency/1,
52                fun should_track_multiple_tasks/1,
53                fun should_finish_task/1
54
55            ]
56        }
57    }.
58
59
60should_register_task({_, Pid, _Ctx}) ->
61    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
62    ?_assertEqual(1, length(couch_task_status:all())).
63
64should_set_task_startup_time({_, Pid, _Ctx}) ->
65    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
66    ?_assert(is_integer(get_task_prop(Pid, started_on))).
67
68should_have_update_time_as_startup_before_any_progress({_, Pid, _Ctx}) ->
69    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
70    StartTime = get_task_prop(Pid, started_on),
71    ?_assertEqual(StartTime, get_task_prop(Pid, updated_on)).
72
73should_set_task_type({_, Pid, _Ctx}) ->
74    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
75    ?_assertEqual(replication, get_task_prop(Pid, type)).
76
77should_not_register_multiple_tasks_for_same_pid({_, Pid, _Ctx}) ->
78    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
79    ?_assertEqual({add_task_error, already_registered},
80                  call(Pid, add, [{type, compaction}, {progress, 0}])).
81
82should_set_task_progress({_, Pid, _Ctx}) ->
83    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
84    ?_assertEqual(0, get_task_prop(Pid, progress)).
85
86should_update_task_progress({_, Pid, _Ctx}) ->
87    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
88    call(Pid, update, [{progress, 25}]),
89    ?_assertEqual(25, get_task_prop(Pid, progress)).
90
91should_update_time_changes_on_task_progress({_, Pid, _Ctx}) ->
92    ?_assert(
93        begin
94            ok = call(Pid, add, [{type, replication}, {progress, 0}]),
95            ok = timer:sleep(1000),  % sleep awhile to customize update time
96            call(Pid, update, [{progress, 25}]),
97            get_task_prop(Pid, updated_on) > get_task_prop(Pid, started_on)
98        end).
99
100%%should_control_update_frequency({_, Pid, _Ctx}) ->
101%%    ?_assertEqual(66,
102%%        begin
103%%            ok = call(Pid, add, [{type, replication}, {progress, 0}]),
104%%            call(Pid, update, [{progress, 50}]),
105%%            call(Pid, update_frequency, 500),
106%%            call(Pid, update, [{progress, 66}]),
107%%            call(Pid, update, [{progress, 77}]),
108%%            get_task_prop(Pid, progress)
109%%        end).
110
111should_reset_control_update_frequency({_, Pid, _Ctx}) ->
112    ?_assertEqual(87,
113        begin
114            ok = call(Pid, add, [{type, replication}, {progress, 0}]),
115            call(Pid, update, [{progress, 50}]),
116            call(Pid, update_frequency, 500),
117            call(Pid, update, [{progress, 66}]),
118            call(Pid, update, [{progress, 77}]),
119            call(Pid, update_frequency, 0),
120            call(Pid, update, [{progress, 87}]),
121            get_task_prop(Pid, progress)
122        end).
123
124should_track_multiple_tasks(_) ->
125    ?_assert(run_multiple_tasks()).
126
127should_finish_task({_, Pid, _Ctx}) ->
128    ok = call(Pid, add, [{type, replication}, {progress, 0}]),
129    ?assertEqual(1, length(couch_task_status:all())),
130    ok = call(Pid, done),
131    ?_assertEqual(0, length(couch_task_status:all())).
132
133
134run_multiple_tasks() ->
135    Pid1 = spawn(fun() -> loop() end),
136    Pid2 = spawn(fun() -> loop() end),
137    Pid3 = spawn(fun() -> loop() end),
138    call(Pid1, add, [{type, replication}, {progress, 0}]),
139    call(Pid2, add, [{type, compaction}, {progress, 0}]),
140    call(Pid3, add, [{type, indexer}, {progress, 0}]),
141
142    ?assertEqual(3, length(couch_task_status:all())),
143    ?assertEqual(replication, get_task_prop(Pid1, type)),
144    ?assertEqual(compaction, get_task_prop(Pid2, type)),
145    ?assertEqual(indexer, get_task_prop(Pid3, type)),
146
147    call(Pid2, update, [{progress, 33}]),
148    call(Pid3, update, [{progress, 42}]),
149    call(Pid1, update, [{progress, 11}]),
150    ?assertEqual(42, get_task_prop(Pid3, progress)),
151    call(Pid1, update, [{progress, 72}]),
152    ?assertEqual(72, get_task_prop(Pid1, progress)),
153    ?assertEqual(33, get_task_prop(Pid2, progress)),
154
155    call(Pid1, done),
156    ?assertEqual(2, length(couch_task_status:all())),
157    call(Pid3, done),
158    ?assertEqual(1, length(couch_task_status:all())),
159    call(Pid2, done),
160    ?assertEqual(0, length(couch_task_status:all())),
161
162    true.
163
164
165loop() ->
166    receive
167        {add, Props, From} ->
168            Resp = couch_task_status:add_task(Props),
169            From ! {ok, self(), Resp},
170            loop();
171        {update, Props, From} ->
172            Resp = couch_task_status:update(Props),
173            From ! {ok, self(), Resp},
174            loop();
175        {update_frequency, Msecs, From} ->
176            Resp = couch_task_status:set_update_frequency(Msecs),
177            From ! {ok, self(), Resp},
178            loop();
179        {done, From} ->
180            From ! {ok, self(), ok}
181    end.
182
183call(Pid, done) ->
184    Ref = erlang:monitor(process, Pid),
185    Pid ! {done, self()},
186    Res = wait(Pid),
187    receive
188        {'DOWN', Ref, _Type, Pid, _Info} ->
189            Res
190    after ?TIMEOUT ->
191            throw(timeout_error)
192    end;
193call(Pid, Command) ->
194    Pid ! {Command, self()},
195    wait(Pid).
196
197call(Pid, Command, Arg) ->
198    Pid ! {Command, Arg, self()},
199    wait(Pid).
200
201wait(Pid) ->
202    receive
203        {ok, Pid, Msg} ->
204            Msg
205    after ?TIMEOUT ->
206        throw(timeout_error)
207    end.
208
209get_task_prop(Pid, Prop) ->
210    From = list_to_binary(pid_to_list(Pid)),
211    Element = lists:foldl(
212        fun(PropList, Acc) ->
213            case couch_util:get_value(pid, PropList) of
214                From ->
215                    [PropList | Acc];
216                _ ->
217                    Acc
218            end
219        end,
220        [], couch_task_status:all()
221    ),
222    case couch_util:get_value(Prop, hd(Element), nil) of
223        nil ->
224            erlang:error({assertion_failed,
225                         [{module, ?MODULE},
226                          {line, ?LINE},
227                          {reason, "Could not get property '"
228                                   ++ couch_util:to_list(Prop)
229                                   ++ "' for task "
230                                   ++ pid_to_list(Pid)}]});
231        Value ->
232            Value
233    end.
234