├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── rebar.config ├── rebar.lock ├── src ├── cacherl.app.src ├── cacherl.erl ├── cacherl_cache_owner.erl ├── cacherl_cache_sup.erl ├── cacherl_data_provider.erl ├── cacherl_sup.erl └── cacherl_utils.erl └── test ├── cacherl_SUITE.erl ├── test.config └── test_data_provider.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | _rel 15 | _deps 16 | _plugins 17 | _tdeps 18 | logs 19 | _build 20 | .DS_Store 21 | ._* 22 | _* 23 | *~ 24 | .idea 25 | *.iml 26 | log 27 | logs 28 | data 29 | *.sh 30 | edoc 31 | priv/*.so -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Felipe Ripoll Gisbert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR = $(shell which rebar3 || echo ./rebar3) 2 | 3 | ifdef REBAR_PROFILE 4 | PROFILE = $(REBAR_PROFILE) 5 | else 6 | PROFILE = default 7 | endif 8 | 9 | BUILD_ROOT = ./_build/$(PROFILE)/lib 10 | BUILD_PATH = $(BUILD_ROOT)/*/ebin 11 | 12 | CONFIG ?= test/test.config 13 | 14 | CT_OPTS = -cover test/cover.spec -erl_args -config ${CONFIG} 15 | CT_SUITES = cacherl_SUITE 16 | 17 | .PHONY: all compile clean distclean dialyze tests shell doc 18 | 19 | all: compile 20 | 21 | compile: 22 | $(REBAR) compile 23 | 24 | dist: 25 | REBAR_PROFILE=dist $(REBAR) compile 26 | 27 | clean: 28 | rm -rf ebin/* test/*.beam logs log 29 | $(REBAR) clean 30 | 31 | distclean: clean 32 | $(REBAR) clean --all 33 | rm -rf _build logs log edoc *.dump c_src/*.o priv/*.so 34 | 35 | dialyze: 36 | $(REBAR) dialyzer 37 | 38 | tests: compile 39 | mkdir -p logs 40 | ct_run -dir test -suite $(CT_SUITES) -pa $(BUILD_PATH) -logdir logs $(CT_OPTS) 41 | rm -rf test/*.beam 42 | 43 | shell: compile 44 | erl -pa $(BUILD_PATH) -s cacherl -config ${CONFIG} 45 | 46 | edoc: 47 | $(REBAR) edoc 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Cacherl 3 | 4 | __Authors:__ Felipe Ripoll. ([`ferigis@gmail.com`](mailto:ferigis@gmail.com)). 5 | 6 | Cacherl is a generational cache built on top of [Shards](https://github.com/cabol/shards) 7 | 8 | ## Build 9 | 10 | $ git clone https://github.com/ferigis/cacherl.git 11 | $ cd cacherl 12 | $ make 13 | 14 | ## Tests 15 | 16 | $ make tests 17 | 18 | ## Copyright and License 19 | 20 | Copyright (c) 2016 Felipe Ripoll Gisbert 21 | 22 | **cacherl** source code is licensed under the [MIT License](LICENSE.md). 23 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% == Erlang Compiler == 2 | 3 | {erl_opts, [ 4 | warn_unused_vars, 5 | warn_export_all, 6 | warn_shadow_vars, 7 | warn_unused_import, 8 | warn_unused_function, 9 | warn_bif_clash, 10 | warn_unused_record, 11 | warn_deprecated_function, 12 | warn_obsolete_guard, 13 | strict_validation, 14 | warn_export_vars, 15 | warn_exported_vars, 16 | warn_missing_spec, 17 | warn_untyped_record, 18 | debug_info 19 | ]}. 20 | 21 | %% == Dependencies == 22 | 23 | {deps, [ 24 | {shards, "0.3.1"} 25 | ]}. 26 | 27 | %% == Common Test == 28 | 29 | {ct_compile_opts, [ 30 | warn_unused_vars, 31 | warn_export_all, 32 | warn_shadow_vars, 33 | warn_unused_import, 34 | warn_unused_function, 35 | warn_bif_clash, 36 | warn_unused_record, 37 | warn_deprecated_function, 38 | warn_obsolete_guard, 39 | strict_validation, 40 | warn_export_vars, 41 | warn_exported_vars, 42 | warn_missing_spec, 43 | warn_untyped_record, 44 | debug_info 45 | ]}. 46 | 47 | {ct_opts, [ 48 | {sys_config, ["test/test.config"]} 49 | ]}. 50 | 51 | %% == Cover == 52 | 53 | {cover_enabled, true}. 54 | {cover_excl_mods, [cacherl_data_provider]}. 55 | {cover_opts, [verbose]}. 56 | 57 | %% == EDoc == 58 | 59 | {edoc_opts, [ 60 | {report_missing_types, true}, 61 | {source_path, ["src"]}, 62 | {report_missing_types, true}, 63 | {todo, true}, 64 | {packages, false}, 65 | {subpackages, false} 66 | ]}. 67 | 68 | %% == Dialyzer == 69 | 70 | {dialyzer, [ 71 | {plt_apps, top_level_deps}, 72 | {plt_extra_apps, []}, 73 | {plt_location, local}, 74 | {plt_prefix, "cacherl"}, 75 | {base_plt_location, "."}, 76 | {base_plt_prefix, "cacherl"} 77 | ]}. 78 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.1.0", 2 | [{<<"shards">>,{pkg,<<"shards">>,<<"0.3.1">>},0}]}. 3 | [ 4 | {pkg_hash,[ 5 | {<<"shards">>, <<"E8A116641D517BCF57A1AEBA900DFF5DFD39BCAD4FBE2F213F76C45792DDE9A5">>}]} 6 | ]. 7 | -------------------------------------------------------------------------------- /src/cacherl.app.src: -------------------------------------------------------------------------------- 1 | {application, 'cacherl', 2 | [{description, "Generational Cache on top of Shards"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {mod, {cacherl, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib, 9 | shards 10 | ]}, 11 | {env,[]}, 12 | {modules, []}, 13 | {contributors, []}, 14 | {licenses, []}, 15 | {links, []} 16 | ]}. -------------------------------------------------------------------------------- /src/cacherl.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc 3 | %%% This is the main module, which contains all API functions. 4 | %%% It also implements the application behaviour 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | -module(cacherl). 8 | -behaviour(application). 9 | 10 | %% API 11 | -export([new/2]). 12 | -export([remove/1]). 13 | 14 | %% Application callbacks 15 | -export([start/0, start/2]). 16 | -export([stop/0, stop/1]). 17 | -export([get/2]). 18 | -export([increment_generation/1]). 19 | 20 | -type generation() :: integer(). 21 | 22 | %%%=================================================================== 23 | %%% Application callbacks 24 | %%%=================================================================== 25 | 26 | %% @hidden 27 | -spec(start(StartType :: normal | {takeover, node()} | {failover, node()}, 28 | StartArgs :: term()) -> 29 | {ok, pid()} | 30 | {error, Reason :: term()}). 31 | start(_StartType, _StartArgs) -> 32 | cacherl_sup:start_link(). 33 | 34 | %% @hidden 35 | -spec(stop(State :: term()) -> term()). 36 | stop(_State) -> 37 | ok. 38 | 39 | %% @doc Starts `cacherl' application. 40 | -spec start() -> {ok, _} | {error, term()}. 41 | start() -> application:ensure_all_started(cacherl). 42 | 43 | %% @doc Stops `cacherl' application. 44 | -spec stop() -> ok | {error, term()}. 45 | stop() -> application:stop(cacherl). 46 | 47 | %%%=================================================================== 48 | %%% cacherl API 49 | %%%=================================================================== 50 | 51 | -spec(new(atom(), atom()) -> 52 | {ok, pid()} | {already_exists, pid()}). 53 | new(CacheName, Module) -> 54 | case cacherl_sup:start_child([CacheName, Module]) of 55 | {error,{already_started, Pid}} -> {already_exists, Pid}; 56 | R -> R 57 | end. 58 | 59 | -spec(remove(atom()) -> true). 60 | remove(CacheName) -> 61 | ok = cacherl_sup:terminate_child(cacherl_sup, 62 | whereis(cacherl_cache_sup:supervisor_name(CacheName))), 63 | true. 64 | 65 | -spec(get(atom(), atom()) -> [] | [term()]). 66 | get(CacheName, Key) -> 67 | [{state, #{generation := Generation, module := Module}}] = ets:lookup(CacheName, state), 68 | ShardsName = cacherl_utils:shards_name(CacheName, Generation), 69 | case shards:lookup(ShardsName, Key) of 70 | [{Key, Result}] -> 71 | Result; 72 | [] -> 73 | Result = apply(Module, get, [Key]), 74 | catch shards:insert(ShardsName, {Key, Result}), 75 | Result 76 | end. 77 | 78 | -spec(increment_generation(atom()) -> generation()). 79 | increment_generation(CacheName) -> 80 | cacherl_cache_owner:increment_generation(CacheName). 81 | -------------------------------------------------------------------------------- /src/cacherl_cache_owner.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc 3 | %%% Cache owner. 4 | %%% @end 5 | %%%------------------------------------------------------------------- 6 | -module(cacherl_cache_owner). 7 | 8 | -behaviour(gen_server). 9 | 10 | %% API 11 | -export([start_link/2]). 12 | -export([increment_generation/1]). 13 | 14 | %% gen_server callbacks 15 | -export([init/1, 16 | handle_call/3, 17 | handle_cast/2, 18 | handle_info/2, 19 | terminate/2, 20 | code_change/3]). 21 | 22 | % State 23 | -record(state, { generation :: cacherl:generation() 24 | , cache_name :: atom()}). 25 | 26 | 27 | %%%=================================================================== 28 | %%% API 29 | %%%=================================================================== 30 | 31 | -spec(start_link(atom(), atom()) -> 32 | {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). 33 | start_link(CacheName, Module) -> 34 | gen_server:start_link( {local, CacheName} 35 | , ?MODULE 36 | , [CacheName, Module] 37 | , []). 38 | 39 | -spec(increment_generation(atom()) -> cacherl:generation()). 40 | increment_generation(CacheName) -> 41 | gen_server:call(CacheName, increment_generation). 42 | 43 | %%%=================================================================== 44 | %%% gen_server callbacks 45 | %%%=================================================================== 46 | 47 | %% @hidden 48 | init([CacheName, Module]) -> 49 | process_flag(trap_exit, true), 50 | Generation = 1, 51 | CacheName = ets:new(CacheName, [set, named_table, {read_concurrency, true}]), 52 | State = #{cache_name => CacheName 53 | , generation => Generation 54 | , module => Module}, 55 | true = ets:insert(CacheName, {state, State}), 56 | ShardsName = cacherl_utils:shards_name(CacheName, Generation), 57 | shards:new(ShardsName, [{read_concurrency, true}]), 58 | {ok, #state{generation = 1, cache_name = CacheName}}. 59 | 60 | %% @hidden 61 | handle_call(increment_generation, _From, #state{cache_name = CacheName 62 | , generation = OldGeneration} = State) -> 63 | NewGeneration = OldGeneration + 1, 64 | OldShardsName = cacherl_utils:shards_name(CacheName, OldGeneration), 65 | NewShardsName = cacherl_utils:shards_name(CacheName, NewGeneration), 66 | shards:new(NewShardsName, [{read_concurrency, true}]), 67 | [{state, Metadata}] = ets:lookup(CacheName, state), 68 | Metadata2 = Metadata#{generation := NewGeneration}, 69 | true = ets:insert(CacheName, {state, Metadata2}), 70 | shards:delete(OldShardsName), 71 | {reply, NewGeneration, State#state{generation = NewGeneration}}; 72 | handle_call(_Request, _From, State) -> 73 | {reply, ok, State}. 74 | 75 | %% @hidden 76 | -spec(handle_cast(Request :: term(), State :: #state{}) -> 77 | {noreply, NewState :: #state{}} | 78 | {noreply, NewState :: #state{}, timeout() | hibernate} | 79 | {stop, Reason :: term(), NewState :: #state{}}). 80 | handle_cast(_Request, State) -> 81 | {noreply, State}. 82 | 83 | %% @hidden 84 | -spec(handle_info(Info :: timeout() | term(), State :: #state{}) -> 85 | {noreply, NewState :: #state{}} | 86 | {noreply, NewState :: #state{}, timeout() | hibernate} | 87 | {stop, Reason :: term(), NewState :: #state{}}). 88 | handle_info(_Info, State) -> 89 | {noreply, State}. 90 | 91 | %% @hidden 92 | terminate(_Reason, #state{generation = Generation 93 | , cache_name = CacheName} = _State) -> 94 | ShardsName = cacherl_utils:shards_name(CacheName, Generation), 95 | shards:delete(ShardsName), 96 | ok. 97 | 98 | %% @hidden 99 | code_change(_OldVsn, State, _Extra) -> 100 | {ok, State}. 101 | -------------------------------------------------------------------------------- /src/cacherl_cache_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc 3 | %%% Cache supervisor. 4 | %%% @end 5 | %%%------------------------------------------------------------------- 6 | -module(cacherl_cache_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([start_link/2]). 12 | -export([supervisor_name/1]). 13 | 14 | %% Supervisor callbacks 15 | -export([init/1]). 16 | 17 | %%%=================================================================== 18 | %%% API functions 19 | %%%=================================================================== 20 | 21 | -spec(start_link(atom(), atom()) -> 22 | {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). 23 | start_link(CacheName, Module) -> 24 | supervisor:start_link({local, supervisor_name(CacheName)}, ?MODULE, [CacheName, Module]). 25 | 26 | %%%=================================================================== 27 | %%% Supervisor callbacks 28 | %%%=================================================================== 29 | 30 | %% @hidden 31 | init([CacheName, Module]) -> 32 | RestartStrategy = one_for_one, 33 | MaxRestarts = 1000, 34 | MaxSecondsBetweenRestarts = 3600, 35 | 36 | SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, 37 | 38 | Restart = permanent, 39 | Shutdown = 2000, 40 | Type = worker, 41 | 42 | AChild = {cacherl_cache_owner, {cacherl_cache_owner, start_link, [CacheName, Module]}, 43 | Restart, Shutdown, Type, [cacherl_cache_owner]}, 44 | 45 | {ok, {SupFlags, [AChild]}}. 46 | 47 | %% @doc 48 | %% Provides the supervisor name regarding the cache name. 49 | %% @end 50 | -spec(supervisor_name(atom()) -> atom()). 51 | supervisor_name(CacheName) -> 52 | Bin = <<(atom_to_binary(CacheName, utf8))/binary, "_", 53 | (atom_to_binary(sup, utf8))/binary>>, 54 | binary_to_atom(Bin, utf8). 55 | -------------------------------------------------------------------------------- /src/cacherl_data_provider.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc 3 | %%% This behaviour must be implemented by the modules which provide 4 | %%% access to the data. Basically, where the app will lookup if some 5 | %%% key is not in the cache. 6 | %%% @end 7 | %%%------------------------------------------------------------------- 8 | -module(cacherl_data_provider). 9 | 10 | -callback get(Key :: term()) -> []|list(term()). 11 | -------------------------------------------------------------------------------- /src/cacherl_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc 3 | %%% Main supervisor 4 | %%% @end 5 | %%%------------------------------------------------------------------- 6 | -module(cacherl_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([start_link/0]). 12 | -export([start_child/1]). 13 | -export([terminate_child/2]). 14 | 15 | %% Supervisor callbacks 16 | -export([init/1]). 17 | 18 | %%%=================================================================== 19 | %%% API functions 20 | %%%=================================================================== 21 | 22 | -spec start_link() -> supervisor:startlink_ret(). 23 | start_link() -> 24 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 25 | 26 | -spec start_child([term()]) -> supervisor:startchild_ret(). 27 | start_child(Args) -> 28 | supervisor:start_child(?MODULE, Args). 29 | 30 | -spec terminate_child(SupRef, Id) -> Response when 31 | SupRef :: supervisor:sup_ref(), 32 | Id :: pid() | supervisor:child_id(), 33 | Error :: not_found | simple_one_for_one, 34 | Response :: ok | {error, Error}. 35 | terminate_child(SupRef, Id) -> 36 | supervisor:terminate_child(SupRef, Id). 37 | 38 | %%%=================================================================== 39 | %%% Supervisor callbacks 40 | %%%=================================================================== 41 | 42 | %% @hidden 43 | init([]) -> 44 | ChildSpec = { 45 | ?MODULE, 46 | {cacherl_cache_sup, start_link, []}, 47 | permanent, 48 | infinity, 49 | supervisor, 50 | [cacherl_cache_sup] 51 | }, 52 | {ok, {{simple_one_for_one, 10, 10}, [ChildSpec]}}. 53 | -------------------------------------------------------------------------------- /src/cacherl_utils.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc 3 | %%% Cache Utils 4 | %%% @end 5 | %%%------------------------------------------------------------------- 6 | -module(cacherl_utils). 7 | 8 | %% API 9 | -export([shards_name/2]). 10 | 11 | %% @doc 12 | %% Build the name of the Shard regarding the Cache name and the 13 | %% generation. 14 | %% @end 15 | -spec(shards_name(atom(), cacherl:generation()) -> atom()). 16 | shards_name(CacheName, Generation) -> 17 | Bin = <<(atom_to_binary(cacherl_shards, utf8))/binary, "_", 18 | (atom_to_binary(CacheName, utf8))/binary, "_", 19 | (integer_to_binary(Generation))/binary>>, 20 | binary_to_atom(Bin, utf8). -------------------------------------------------------------------------------- /test/cacherl_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(cacherl_SUITE). 2 | 3 | -include_lib("common_test/include/ct.hrl"). 4 | 5 | %% Common test 6 | -export([all/0]). 7 | -export([init_per_suite/1]). 8 | -export([end_per_suite/1]). 9 | 10 | %% Tests 11 | -export([new_cache/1]). 12 | -export([remove_cache/1]). 13 | -export([common_case/1]). 14 | -export([change_generation/1]). 15 | 16 | %%%=================================================================== 17 | %%% Common Test 18 | %%%=================================================================== 19 | 20 | all() -> 21 | [new_cache, remove_cache, 22 | change_generation, common_case]. 23 | 24 | init_per_suite(Config) -> 25 | cacherl:start(), 26 | Config. 27 | 28 | end_per_suite(Config) -> 29 | ok = cacherl:stop(), 30 | Config. 31 | 32 | %%%=================================================================== 33 | %%% Exported Tests Functions 34 | %%%=================================================================== 35 | 36 | new_cache(_Config) -> 37 | {ok, Pid} = cacherl:new(new_cache, module), 38 | {ok, _Pid2} = cacherl:new(new_cache2, module2), 39 | {already_exists, Pid} = cacherl:new(new_cache, module), 40 | true = is_process_alive(Pid). 41 | 42 | remove_cache(_Config) -> 43 | {already_exists, _} = cacherl:new(new_cache, module), 44 | {already_exists, _} = cacherl:new(new_cache2, module2), 45 | true = cacherl:remove(new_cache), 46 | true = cacherl:remove(new_cache2), 47 | {ok, _} = cacherl:new(new_cache, module2), 48 | true = cacherl:remove(new_cache). 49 | 50 | change_generation(_Config) -> 51 | CacheName = test_cache, 52 | {ok, _Pid} = cacherl:new(CacheName, test_data_provider), 53 | [{state, #{generation := Generation}}] = ets:lookup(CacheName, state), 54 | ShardsName = cacherl_utils:shards_name(CacheName, Generation), 55 | true = is_process_alive(whereis(ShardsName)), 56 | Generation2 = cacherl:increment_generation(CacheName), 57 | undefined = whereis(ShardsName), 58 | [{state, #{generation := Generation2}}] = ets:lookup(CacheName, state), 59 | ShardsName2 = cacherl_utils:shards_name(CacheName, Generation2), 60 | true = is_process_alive(whereis(ShardsName2)), 61 | true = cacherl:remove(CacheName). 62 | 63 | common_case(_Config) -> 64 | CacheName = test_cache, 65 | {ok, _Pid} = cacherl:new(CacheName, test_data_provider), 66 | [{state, #{generation := Generation}}] = ets:lookup(CacheName, state), 67 | ShardsName = cacherl_utils:shards_name(CacheName, Generation), 68 | Key = felipe, 69 | ExpectedValue = {felipe, ripoll}, 70 | [] = shards:lookup(ShardsName, Key), 71 | ExpectedValue = cacherl:get(CacheName, Key), 72 | % Call again in order to fetch if from cacherl 73 | ExpectedValue = cacherl:get(CacheName, Key), 74 | [{Key, ExpectedValue}] = shards:lookup(ShardsName, Key), 75 | [{state, #{generation := Generation}}] = ets:lookup(CacheName, state), 76 | [{Key, ExpectedValue}] = shards:lookup(ShardsName, Key), 77 | Key2 = this_key_doesnt_exists, 78 | ExpectedValue2 = [], 79 | [] = shards:lookup(ShardsName, Key2), 80 | ExpectedValue2 = cacherl:get(CacheName, Key2), 81 | [{state, #{generation := Generation}}] = ets:lookup(CacheName, state), 82 | [{Key2, ExpectedValue2}] = shards:lookup(ShardsName, Key2), 83 | true = cacherl:remove(CacheName). 84 | -------------------------------------------------------------------------------- /test/test.config: -------------------------------------------------------------------------------- 1 | [ 2 | {cacherl, [] 3 | }, 4 | {shards, [ 5 | ]} 6 | ]. 7 | -------------------------------------------------------------------------------- /test/test_data_provider.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc 3 | %%% Implementation of cacherl_data_provider behaviour for testing 4 | %%% @end 5 | %%%------------------------------------------------------------------- 6 | -module(test_data_provider). 7 | 8 | -behaviour(cacherl_data_provider). 9 | 10 | %% API 11 | -export([get/1]). 12 | 13 | get(Key) -> 14 | Map = #{all => [{felipe, ripoll}, {chuck, norris} 15 | , {bruce, wayne}, {peter, parker}] 16 | , felipe => {felipe, ripoll} 17 | , chuck => {chuck, norris} 18 | , batman => {bruce, wayne} 19 | , spiderman => {peter, parker}}, 20 | case catch maps:get(Key, Map) of 21 | {'EXIT', _} -> []; 22 | Result -> Result 23 | end. 24 | --------------------------------------------------------------------------------