summaryrefslogtreecommitdiff
path: root/src/couch_expiring_cache/test/couch_expiring_cache_tests.erl
blob: 0780b8847a0d5cfb3c1b414900a097c7cc4c1e7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
% 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.

-module(couch_expiring_cache_tests).


-include_lib("couch/include/couch_eunit.hrl").

-include_lib("couch_expiring_cache/include/couch_expiring_cache.hrl").


-define(CACHE_NAME, atom_to_binary(?MODULE, utf8)).

-define(FOREVER, 576460752303423488). % max int 64 bit


couch_expiring_cache_basic_test_() ->
    {
        "Test expiring cache basics",
        {
            setup,
            fun setup_couch/0, fun teardown_couch/1,
            {
                foreach,
                fun setup/0, fun teardown/1,
                [
                    fun simple_lifecycle/1
                ]
            }
        }
    }.


setup_couch() ->
    test_util:start_couch([fabric, couch_jobs]).


teardown_couch(Ctx) ->
    test_util:stop_couch(Ctx).


setup() ->
    Opts = #{
        cache_name => ?CACHE_NAME,
        period => 10,
        max_jitter => 0},
    {ok, Pid} = couch_expiring_cache_server:start_link(?MODULE, Opts),
    true = unlink(Pid),
    #{pid => Pid}.


teardown(#{pid := Pid}) ->
    exit(Pid, kill).


simple_lifecycle(_) ->
    % The entire test is racing against FDB being faster than timeout seconds
    {timeout, 20, ?_test(begin
        Start = couch_expiring_cache_server:now_ts(),
        % Race Alert!
        % We're betting on FDB returning a lookup faster than these:
        Stale = 500,
        Expires = 1000,
        Timeout = 5000,
        Interval = 5,

        StaleTS = Start + Stale,
        ExpiresTS = Start + Expires,
        Name = ?CACHE_NAME,
        Key = <<"key">>,
        Val = <<"val">>,

        ?assertEqual(ok, couch_expiring_cache_fdb:clear_all(Name)),
        ?assertEqual(not_found, couch_expiring_cache:lookup(Name, Key)),
        ?assertEqual([], entries(Name)),
        ?assertEqual(ok, couch_expiring_cache:insert(Name, Key, Val,
            StaleTS, ExpiresTS)),
        ok = attempt_fresh_and_stale_lookups(Name, Key, Timeout, Interval),

        % Refresh the existing key with updated timestamps
        Refresh = couch_expiring_cache_server:now_ts(),
        ?assertEqual(ok, couch_expiring_cache:insert(Name, Key, Val,
            Refresh + Stale, Refresh + Expires)),
        ok = attempt_fresh_and_stale_lookups(Name, Key, Timeout, Interval),
        ?assertEqual(1, length(entries(Name))),
        % These last 2 are also races, betting on FDB to be reasonably
        % fast on the home stretch
        ok = wait_lookup(Name, Key, expired, Timeout, Interval),
        ok = wait_lookup(Name, Key, not_found, Timeout, Interval),
        ?assertEqual([], entries(Name))
    end)}.


% In this race we're betting on FDB to take less than `Stale` and then
% `Expired` milliseconds to respond
attempt_fresh_and_stale_lookups(Name, Key, Timeout, Interval) ->
    case couch_expiring_cache:lookup(Name, Key) of
        {fresh, Val} ->
            % We won that race, let's bet on another!
            ok = wait_lookup(Name, Key, {stale, Val}, Timeout, Interval);
        _ ->
            % Unlucky! But don't fail the test just yet...
            ok
    end.


entries(Name) ->
    couch_expiring_cache_fdb:get_range_to(Name, ?FOREVER, _Limit=100).


% This lookup races against Timeout
wait_lookup(Name, Key, Expect, Timeout, Interval) ->
    wait(fun() ->
        case couch_expiring_cache:lookup(Name, Key) of
            Expect -> ok;
            _ -> wait
        end
    end, Timeout, Interval).


wait(Fun, Timeout, Delay) ->
    Now = couch_expiring_cache_server:now_ts(),
    wait(Fun, Timeout, Delay, Now, Now).


wait(_Fun, Timeout, _Delay, Started, Prev) when Prev - Started > Timeout ->
    timeout;

wait(Fun, Timeout, Delay, Started, _Prev) ->
    case Fun() of
        wait ->
            % http://erlang.org/doc/man/timer.html#sleep-1
            ok = timer:sleep(Delay), % always millisecond
            wait(Fun, Timeout, Delay, Started,
                couch_expiring_cache_server:now_ts());
        Else ->
            Else
    end.