Add note about migrating to the main repo
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 3164eee..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-.rebar
-ebin/
-*~
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 6a3648b..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-language: erlang
-
-otp_release:
- - 21.2
- - 20.3
- - 19.3
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index f6cd2bc..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- 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.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 058a7ec..0000000
--- a/Makefile
+++ /dev/null
@@ -1,41 +0,0 @@
-REBAR?=rebar
-
-
-.PHONY: all
-# target: all - Makes everything
-all: build
-
-
-.PHONY: build
-# target: build - Builds the project
-build:
- $(REBAR) compile
-
-
-.PHONY: check
-# target: check - Checks if project builds and passes all the tests
-check: build eunit
-
-
-.PHONY: clean
-# target: clean - Removes build artifacts
-clean:
- $(REBAR) clean
-
-
-.PHONY: distclean
-# target: distclean - Removes all unversioned files
-distclean: clean
- git clean -fxd
-
-
-.PHONY: eunit
-# target: eunit - Runs eunit test suite
-eunit:
- $(REBAR) eunit
-
-
-.PHONY: help
-# target: help - Prints this help
-help:
- @egrep "^# target:" Makefile | sed -e 's/^# target: //g' | sort
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c75fbcd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+## NOTE ##
+
+Application was moved to the [main repository](https://github.com/apache/couchdb.git)
diff --git a/src/ets_lru.app.src b/src/ets_lru.app.src
deleted file mode 100644
index c81ce11..0000000
--- a/src/ets_lru.app.src
+++ /dev/null
@@ -1,21 +0,0 @@
-% 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.
-
-{application, ets_lru, [
- {description, "ETS Base LRU Cache"},
- {vsn, git},
- {registered, []},
- {applications, [
- kernel,
- stdlib
- ]}
-]}.
diff --git a/src/ets_lru.erl b/src/ets_lru.erl
deleted file mode 100644
index 0f6fdb2..0000000
--- a/src/ets_lru.erl
+++ /dev/null
@@ -1,364 +0,0 @@
-% 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(ets_lru).
--behaviour(gen_server).
--vsn(2).
-
-
--export([
- start_link/2,
- stop/1,
-
- insert/3,
- lookup/2,
- match/3,
- match_object/3,
- remove/2,
- clear/1,
-
- % Dirty functions read straight from
- % the ETS tables which means there are
- % race conditions with concurrent access.
- lookup_d/2
-]).
-
--export([
- init/1,
- terminate/2,
-
- handle_call/3,
- handle_cast/2,
- handle_info/2,
-
- code_change/3
-]).
-
-
--define(DEFAULT_TIME_UNIT, millisecond).
-
--type time_value() :: integer().
--type strict_monotonic_time() :: {time_value(), integer()}.
-
--record(entry, {
- key :: term(),
- val :: term(),
- atime :: strict_monotonic_time(),
- ctime :: strict_monotonic_time()
-}).
-
--record(st, {
- objects,
- atimes,
- ctimes,
-
- max_objs :: non_neg_integer() | undefined,
- max_size :: non_neg_integer() | undefined,
- max_lifetime :: non_neg_integer() | undefined,
- time_unit = ?DEFAULT_TIME_UNIT :: atom()
-}).
-
-
-start_link(Name, Options) when is_atom(Name) ->
- gen_server:start_link({local, Name}, ?MODULE, {Name, Options}, []).
-
-
-stop(LRU) ->
- gen_server:cast(LRU, stop).
-
-
-lookup(LRU, Key) ->
- gen_server:call(LRU, {lookup, Key}).
-
-
-insert(LRU, Key, Val) ->
- gen_server:call(LRU, {insert, Key, Val}).
-
-
-remove(LRU, Key) ->
- gen_server:call(LRU, {remove, Key}).
-
-%% @doc match/3 provides an efficient way to retrieve parts of the
-%% keys and values without copying or requiring circumvention of the
-%% ets_lru API. The KeySpec and ValueSpec parameters are used as part
-%% of one larger match spec so keep in mind that all capturing
-%% placeholders will be aliased between the key and value parts.
--spec match(atom() | pid(), term(), term()) -> [[any()]].
-match(LRU, KeySpec, ValueSpec) ->
- gen_server:call(LRU, {match, KeySpec, ValueSpec}).
-
-%% @doc match_object/3 provides an efficient way to retrieve multiple
-%% values using match conditions. The KeySpec and ValueSpec parameters
-%% are used as part of one larger match spec so keep in mind that all
-%% capturing placeholders will be aliased between the key and value
-%% parts.
--spec match_object(atom() | pid(), term(), term()) -> [any()].
-match_object(Name, KeySpec, ValueSpec) when is_atom(Name) ->
- Pattern = #entry{key=KeySpec, val=ValueSpec, _='_'},
- Entries = ets:match_object(obj_table(Name), Pattern),
- lists:map(fun(#entry{key=Key,val=Val}) ->
- gen_server:cast(Name, {accessed, Key}),
- Val
- end, Entries);
-match_object(LRU, KeySpec, ValueSpec) ->
- gen_server:call(LRU, {match_object, KeySpec, ValueSpec}).
-
-clear(LRU) ->
- gen_server:call(LRU, clear).
-
-
-lookup_d(Name, Key) when is_atom(Name) ->
- case ets:lookup(obj_table(Name), Key) of
- [#entry{val=Val}] ->
- gen_server:cast(Name, {accessed, Key}),
- {ok, Val};
- [] ->
- not_found
- end.
-
-
-init({Name, Options}) ->
- St = set_options(#st{}, Options),
- ObjOpts = [set, named_table, protected, {keypos, #entry.key}],
- TimeOpts = [ordered_set, named_table, protected],
-
- {ok, St#st{
- objects = ets:new(obj_table(Name), ObjOpts),
- atimes = ets:new(at_table(Name), TimeOpts),
- ctimes = ets:new(ct_table(Name), TimeOpts)
- }}.
-
-
-terminate(_Reason, St) ->
- true = ets:delete(St#st.objects),
- true = ets:delete(St#st.atimes),
- true = ets:delete(St#st.ctimes),
- ok.
-
-
-handle_call({lookup, Key}, _From, St) ->
- Reply = case ets:lookup(St#st.objects, Key) of
- [#entry{val=Val} | _] ->
- accessed(Key, St),
- {ok, Val};
- [] ->
- not_found
- end,
- {reply, Reply, St, 0};
-
-handle_call({match_object, KeySpec, ValueSpec}, _From, St) ->
- Pattern = #entry{key=KeySpec, val=ValueSpec, _='_'},
- Entries = ets:match_object(St#st.objects, Pattern),
- Values = lists:map(fun(#entry{key=Key,val=Val}) ->
- accessed(Key, St),
- Val
- end, Entries),
- {reply, Values, St, 0};
-
-handle_call({match, KeySpec, ValueSpec}, _From, St) ->
- Pattern = #entry{key=KeySpec, val=ValueSpec, _='_'},
- Values = ets:match(St#st.objects, Pattern),
- {reply, Values, St, 0};
-
-handle_call({insert, Key, Val}, _From, St) ->
- NewATime = strict_monotonic_time(St#st.time_unit),
- Pattern = #entry{key=Key, atime='$1', _='_'},
- case ets:match(St#st.objects, Pattern) of
- [[ATime]] ->
- Update = {#entry.val, Val},
- true = ets:update_element(St#st.objects, Key, Update),
- true = ets:delete(St#st.atimes, ATime),
- true = ets:insert(St#st.atimes, {NewATime, Key});
- [] ->
- Entry = #entry{key=Key, val=Val, atime=NewATime, ctime=NewATime},
- true = ets:insert(St#st.objects, Entry),
- true = ets:insert(St#st.atimes, {NewATime, Key}),
- true = ets:insert(St#st.ctimes, {NewATime, Key})
- end,
- {reply, ok, St, 0};
-
-handle_call({remove, Key}, _From, St) ->
- Pattern = #entry{key=Key, atime='$1', ctime='$2', _='_'},
- Reply = case ets:match(St#st.objects, Pattern) of
- [[ATime, CTime]] ->
- true = ets:delete(St#st.objects, Key),
- true = ets:delete(St#st.atimes, ATime),
- true = ets:delete(St#st.ctimes, CTime),
- ok;
- [] ->
- not_found
- end,
- {reply, Reply, St, 0};
-
-handle_call(clear, _From, St) ->
- true = ets:delete_all_objects(St#st.objects),
- true = ets:delete_all_objects(St#st.atimes),
- true = ets:delete_all_objects(St#st.ctimes),
- % No need to timeout here and evict cache
- % entries because its now empty.
- {reply, ok, St};
-
-
-handle_call(Msg, _From, St) ->
- {stop, {invalid_call, Msg}, {invalid_call, Msg}, St}.
-
-
-handle_cast({accessed, Key}, St) ->
- accessed(Key, St),
- {noreply, St, 0};
-
-handle_cast(stop, St) ->
- {stop, normal, St};
-
-handle_cast(Msg, St) ->
- {stop, {invalid_cast, Msg}, St}.
-
-
-handle_info(timeout, St) ->
- trim(St),
- {noreply, St, next_timeout(St)};
-
-handle_info(Msg, St) ->
- {stop, {invalid_info, Msg}, St}.
-
-
-code_change(_OldVsn, St, _Extra) ->
- {ok, St}.
-
-
-accessed(Key, St) ->
- Pattern = #entry{key=Key, atime='$1', _='_'},
- case ets:match(St#st.objects, Pattern) of
- [[ATime]] ->
- NewATime = strict_monotonic_time(St#st.time_unit),
- Update = {#entry.atime, NewATime},
- true = ets:update_element(St#st.objects, Key, Update),
- true = ets:delete(St#st.atimes, ATime),
- true = ets:insert(St#st.atimes, {NewATime, Key}),
- ok;
- [] ->
- ok
- end.
-
-
-trim(St) ->
- trim_count(St),
- trim_size(St),
- trim_lifetime(St).
-
-
-trim_count(#st{max_objs=undefined}) ->
- ok;
-trim_count(#st{max_objs=Max}=St) ->
- case ets:info(St#st.objects, size) > Max of
- true ->
- drop_lru(St, fun trim_count/1);
- false ->
- ok
- end.
-
-
-trim_size(#st{max_size=undefined}) ->
- ok;
-trim_size(#st{max_size=Max}=St) ->
- case ets:info(St#st.objects, memory) > Max of
- true ->
- drop_lru(St, fun trim_size/1);
- false ->
- ok
- end.
-
-
-trim_lifetime(#st{max_lifetime=undefined}) ->
- ok;
-trim_lifetime(#st{max_lifetime=Max}=St) ->
- Now = erlang:monotonic_time(St#st.time_unit),
- case ets:first(St#st.ctimes) of
- '$end_of_table' ->
- ok;
- CTime = {Time, _} ->
- case Now - Time > Max of
- true ->
- [{CTime, Key}] = ets:lookup(St#st.ctimes, CTime),
- Pattern = #entry{key=Key, atime='$1', _='_'},
- [[ATime]] = ets:match(St#st.objects, Pattern),
- true = ets:delete(St#st.objects, Key),
- true = ets:delete(St#st.atimes, ATime),
- true = ets:delete(St#st.ctimes, CTime),
- trim_lifetime(St);
- false ->
- ok
- end
- end.
-
-
-drop_lru(St, Continue) ->
- case ets:first(St#st.atimes) of
- '$end_of_table' ->
- empty;
- ATime ->
- [{ATime, Key}] = ets:lookup(St#st.atimes, ATime),
- Pattern = #entry{key=Key, ctime='$1', _='_'},
- [[CTime]] = ets:match(St#st.objects, Pattern),
- true = ets:delete(St#st.objects, Key),
- true = ets:delete(St#st.atimes, ATime),
- true = ets:delete(St#st.ctimes, CTime),
- Continue(St)
- end.
-
-
-next_timeout(#st{max_lifetime=undefined}) ->
- infinity;
-next_timeout(St) ->
- case ets:first(St#st.ctimes) of
- '$end_of_table' ->
- infinity;
- {Time, _} ->
- Now = erlang:monotonic_time(St#st.time_unit),
- TimeDiff = Now - Time,
- erlang:max(St#st.max_lifetime - TimeDiff, 0)
- end.
-
-
-set_options(St, []) ->
- St;
-set_options(St, [{max_objects, N} | Rest]) when is_integer(N), N >= 0 ->
- set_options(St#st{max_objs=N}, Rest);
-set_options(St, [{max_size, N} | Rest]) when is_integer(N), N >= 0 ->
- set_options(St#st{max_size=N}, Rest);
-set_options(St, [{max_lifetime, N} | Rest]) when is_integer(N), N >= 0 ->
- set_options(St#st{max_lifetime=N}, Rest);
-set_options(St, [{time_unit, T} | Rest]) when is_atom(T) ->
- set_options(St#st{time_unit=T}, Rest);
-set_options(_, [Opt | _]) ->
- throw({invalid_option, Opt}).
-
-
-obj_table(Name) ->
- table_name(Name, "_objects").
-
-
-at_table(Name) ->
- table_name(Name, "_atimes").
-
-
-ct_table(Name) ->
- table_name(Name, "_ctimes").
-
-
-table_name(Name, Ext) ->
- list_to_atom(atom_to_list(Name) ++ Ext).
-
-
--spec strict_monotonic_time(atom()) -> strict_monotonic_time().
-strict_monotonic_time(TimeUnit) ->
- {erlang:monotonic_time(TimeUnit), erlang:unique_integer([monotonic])}.
diff --git a/test/ets_lru_test.erl b/test/ets_lru_test.erl
deleted file mode 100644
index f54299a..0000000
--- a/test/ets_lru_test.erl
+++ /dev/null
@@ -1,340 +0,0 @@
--module(ets_lru_test).
-
-
--include_lib("eunit/include/eunit.hrl").
-
-lifecyle_test_() ->
- {
- "Test LRU lifecycle",
- {setup,
- fun() -> ets_lru:start_link(?MODULE, []) end,
- fun({ok, LRU}) -> is_process_alive(LRU) == false end,
- fun({ok, LRU}) ->
- [
- {
- "ets_lru:start_link/2 returned an LRU",
- ?_assert(is_pid(LRU))
- },
- {
- "Destroyed the LRU ok",
- ?_assertEqual(ok, ets_lru:stop(LRU))
- }
- ]
- end
- }
- }.
-
-table_names_test_() ->
- {
- "Test tables names",
- {setup,
- fun() -> ets_lru:start_link(foo, []) end,
- fun({ok, LRU}) ->
- [
- {
- "foo_objects exists",
- ?_assertEqual(0, ets:info(foo_objects, size))
- },
- {
- "foo_atimes exists",
- ?_assertEqual(0, ets:info(foo_atimes, size))
- },
- {
- "foo_ctimes exists",
- ?_assertEqual(0, ets:info(foo_ctimes, size))
- },
- {
- "LRU stopped normally",
- ?_test(begin
- Reason = stop_lru({ok, LRU}),
- ?assertEqual(normal, Reason)
- end)
- },
- {
- "foo_objects doesn't exists",
- ?_assertEqual(undefined, ets:info(foo_objects, size))
- },
- {
- "foo_atimes doesn't exists",
- ?_assertEqual(undefined, ets:info(foo_atimes, size))
- },
- {
- "foo_ctimes doesn't exists",
- ?_assertEqual(undefined, ets:info(foo_ctimes, size))
- }
- ]
- end
- }
- }.
-
-basic_behavior_test_() ->
- {
- "Test basic behavior",
- {foreach,
- fun() ->
- {ok, LRU} = ets_lru:start_link(test_lru, []),
- ok = ets_lru:insert(LRU, foo, bar),
- {ok, LRU}
- end,
- fun stop_lru/1,
- [
- fun({ok, LRU}) ->
- {
- "Lookup returned the inserted value",
- ?_assertEqual({ok, bar}, ets_lru:lookup(LRU, foo))
- }
- end,
- fun({ok, _LRU}) ->
- {
- "Dirty lookup returned the inserted value",
- ?_assertEqual({ok, bar},
- ets_lru:lookup_d(test_lru, foo))
- }
- end,
- fun({ok, LRU}) ->
- [{
- "Lookup returned the inserted value",
- ?_assertEqual({ok, bar}, ets_lru:lookup(LRU, foo))
- },
- {
- "Insert new value",
- ?_assertEqual(ok, ets_lru:insert(LRU, foo, bam))
- },
- {
- "Lookup returned the newly inserted value",
- ?_assertEqual({ok, bam}, ets_lru:lookup(LRU, foo))
- }]
- end,
- fun({ok, LRU}) ->
- [{
- "Lookup returned the inserted value",
- ?_assertEqual({ok, bar}, ets_lru:lookup(LRU, foo))
- },
- {
- "Remove value",
- ?_assertEqual(ok, ets_lru:remove(LRU, foo))
- },
- {
- "Lookup returned not_found for removed value",
- ?_assertEqual(not_found, ets_lru:lookup(LRU, foo))
- }]
- end,
- fun({ok, LRU}) ->
- [{
- "Lookup returned the inserted value",
- ?_assertEqual({ok, bar}, ets_lru:lookup(LRU, foo))
- },
- {
- "Clear LRU",
- ?_assertEqual(ok, ets_lru:clear(LRU))
- },
- {
- "Lookup returned not_found after a clear",
- ?_assertEqual(not_found, ets_lru:lookup(LRU, foo))
- }]
- end
- ]
- }
- }.
-
-lru_good_options_test_() ->
- {
- "Test good LRU options",
- {foreachx,
- fun(Opts) ->
- process_flag(trap_exit,true),
- ets_lru:start_link(?MODULE, Opts)
- end,
- fun(_, Cfg) -> stop_lru(Cfg) end,
- [
- {[{max_objects, 1}], fun test_good_opts/2},
- {[{max_objects, 5}], fun test_good_opts/2},
- {[{max_objects, 923928342098203942}], fun test_good_opts/2},
- {[{max_size, 1}], fun test_good_opts/2},
- {[{max_size, 5}], fun test_good_opts/2},
- {[{max_size, 2342923423942309423094}], fun test_good_opts/2},
- {[{max_lifetime, 1}], fun test_good_opts/2},
- {[{max_lifetime, 5}], fun test_good_opts/2},
- {[{max_lifetime, 1244209909180928348}], fun test_good_opts/2}
- ]
- }
- }.
-
-lru_bad_options_test_() ->
- {
- "Test bad LRU options",
- {foreachx,
- fun(Opts) ->
- process_flag(trap_exit,true),
- ets_lru:start_link(?MODULE, Opts)
- end,
- fun(_, _) ->
- case whereis(?MODULE) of
- Pid when is_pid(Pid) ->
- stop_lru({ok, Pid});
- undefined ->
- ok
- end
- end,
- [
- {[{bingo, bango}], fun test_bad_opts/2},
- {[12], fun test_bad_opts/2},
- {[true], fun test_bad_opts/2}
- ]
- }
- }.
-
-lru_limits_test_() ->
- {
- "Test LRU limits",
- {foreachx,
- fun(Opts) -> ets_lru:start_link(lru, Opts) end,
- fun(_, Cfg) -> stop_lru(Cfg) end,
- [
- {[{max_objects, 25}], fun test_limits/2},
- {[{max_size, 1024}], fun test_limits/2},
- {[{max_lifetime, 100}], fun test_limits/2}
- ]
- }
- }.
-
-lru_match_test_() ->
- {
- "Test match functions",
- {foreach,
- fun() -> ets_lru:start_link(test_lru, []) end,
- fun stop_lru/1,
- [
- fun({ok, LRU}) ->
- {
- "Empty match",
- ?_assertEqual([], ets_lru:match(LRU, a, '$1'))
- }
- end,
- fun({ok, LRU}) ->
- ets_lru:insert(LRU, b, {x, y}),
- {
- "Single match",
- ?_assertEqual([[x, y]],
- ets_lru:match(LRU, b, {'$1', '$2'}))
- }
- end,
- fun({ok, LRU}) ->
- ets_lru:insert(LRU, boston, {state, "MA"}),
- ets_lru:insert(LRU, new_york, {state, "NY"}),
- Values = ets_lru:match(LRU, '_', {state, '$1'}),
- {
- "Multiple match",
- ?_assertEqual([["MA"],["NY"]], lists:sort(Values))
- }
- end,
- fun({ok, LRU}) ->
- {
- "Empty match_object",
- ?_assertEqual([], ets_lru:match_object(LRU, a, '$1'))
- }
- end,
- fun({ok, LRU}) ->
- ets_lru:insert(LRU, ans, 42),
- [{
- "Single match_object (registered)",
- ?_assertEqual([42],
- ets_lru:match_object(test_lru, ans, '$1'))
- },
- {
- "Single match_object (pid)",
- ?_assertEqual([42],
- ets_lru:match_object(LRU, ans, '$1'))
- }]
- end,
- fun({ok, LRU}) ->
- ets_lru:insert(LRU, {color, blue}, a),
- ets_lru:insert(LRU, {color, red}, b),
- Values = ets_lru:match_object(LRU, {color, '_'}, '_'),
- {
- "Multiple match_object",
- ?_assertEqual(lists:sort(Values), [a, b])
- }
- end
- ]
- }
- }.
-
-
-test_good_opts(Opts, {ok, LRU}) ->
- Msg = io_lib:format("LRU created ok with options: ~w", [Opts]),
- {lists:flatten(Msg), ?_assert(is_pid(LRU))};
-test_good_opts(Opts, ErrorMsg) ->
- Msg = io_lib:format("LRU created ok with options: ~w", [Opts]),
- {lists:flatten(Msg), ?_assertEqual(ok, ErrorMsg)}.
-
-test_bad_opts([Opts], {error,{bad_return_value,{invalid_option,Opts2}}}) ->
- Msg = io_lib:format("LRU failed with bad options: ~w", [Opts]),
- {lists:flatten(Msg), ?_assertEqual(Opts, Opts2)}.
-
-test_limits([{max_objects, N}], {ok, LRU}) ->
- {
- "Max object count ok",
- ?_assert(insert_kvs(size, LRU, 100 * N, N))
- };
-test_limits([{max_size, N}], {ok, LRU}) ->
- {
- "Max size ok",
- ?_assert(insert_kvs(memory, LRU, 10 * N, N))
- };
-test_limits([{max_lifetime, N}], {ok, LRU}) ->
- [
- {
- "Expire leaves new entries",
- ?_test(begin
- ets_lru:insert(LRU, foo, bar),
- ?assertEqual({ok, bar}, ets_lru:lookup(LRU, foo))
- end)
- },
- {
- "Entry was expired",
- ?_test(begin
- timer:sleep(round(N * 1.5)),
- ?assertEqual(not_found, ets_lru:lookup(LRU, foo))
- end)
- }
- ].
-
-
-insert_kvs(_, _, 0, _) ->
- true;
-insert_kvs(Info, LRU, Count, Limit) ->
- ets_lru:insert(LRU, Count, 1.5234),
- case ets:info(lru_objects, Info) > Limit of
- true ->
- % Retry again as eviction statistics
- % returned by ets:info() can be delayed.
- timer:sleep(1),
- case ets:info(lru_objects, Info) > Limit of
- true ->
- erlang:error(exceeded_limit);
- false -> true
- end;
- false -> true
- end,
- insert_kvs(Info, LRU, Count - 1, Limit).
-
-stop_lru({ok, LRU}) ->
- Ref = erlang:monitor(process, LRU),
- ets_lru:stop(LRU),
- receive {'DOWN', Ref, process, LRU, Reason} -> Reason end;
-stop_lru({error, _}) ->
- ok.
-
-valid_parameterized_time_unit_test() ->
- Opts = [{time_unit, microsecond}],
- {ok, LRU} = ets_lru:start_link(lru_test, Opts),
- ?assert(is_process_alive(LRU)),
- ok = ets_lru:insert(LRU, foo, bar),
- ?assertEqual({ok, bar}, ets_lru:lookup(LRU, foo)),
- ?assertEqual(ok, ets_lru:stop(LRU)).
-
-invalid_parameterized_time_unit_test() ->
- Opts = [{time_unit, invalid}],
- {ok, LRU} = ets_lru:start_link(lru_test, Opts),
- ?assertExit(_, ets_lru:insert(LRU, foo, bar)).