You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2019/11/19 18:21:10 UTC

[couchdb-local] 01/16: first commit

This is an automated email from the ASF dual-hosted git repository.

davisp pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-local.git

commit c3236165de27eca16a9fc6ce178d23736b99b8e7
Author: Takeru Ohta <ph...@gmail.com>
AuthorDate: Fri Sep 19 17:29:46 2014 +0900

    first commit
---
 .gitignore                |  13 +++
 COPYING                   |  21 +++++
 Makefile                  |  42 ++++++++++
 README.md                 |   8 ++
 doc/README.md             |  12 +++
 doc/local.md              | 164 +++++++++++++++++++++++++++++++++++++
 doc/local_name_server.md  |  34 ++++++++
 rebar                     | Bin 0 -> 180896 bytes
 rebar.config              |  32 ++++++++
 src/local.app.src         |  13 +++
 src/local.erl             |  76 +++++++++++++++++
 src/local_app.erl         |  22 +++++
 src/local_lib.erl         |  20 +++++
 src/local_name_server.erl | 139 +++++++++++++++++++++++++++++++
 src/local_sup.erl         |  58 +++++++++++++
 test/echo_gen_server.erl  |  53 ++++++++++++
 test/local_lib_tests.erl  |  48 +++++++++++
 test/local_tests.erl      | 204 ++++++++++++++++++++++++++++++++++++++++++++++
 18 files changed, 959 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4f1eebd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*~
+*.beam
+/.*/
+doc/*
+!doc/*.md
+ebin/
+.dialyzer.plt
+deps/
+log/
+erl
+*.dump
+.eunit
+.rebar
\ No newline at end of file
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..163b2f2
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2014 Takeru Ohta <ph...@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..16142d2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,42 @@
+APP=local
+
+DIALYZER_OPTS=-Werror_handling -Wrace_conditions -Wunmatched_returns
+
+all: compile xref eunit dialyze
+
+init:
+	@eval "if ! [ -f 'src/${APP}.app.src' ]; then ./rebar create-app appid=${APP}; fi"
+	@./rebar prepare-deps
+
+compile:
+	@./rebar -r compile skip_deps=true
+
+refresh:
+	@./rebar refresh-deps
+	@rm -f .dialyzer.plt
+
+xref:
+	@./rebar -r xref skip_deps=true
+
+clean:
+	@./rebar -r clean skip_deps=true
+	@rm -f .dialyzer.plt
+
+distclean:
+	@git clean -d -f -x
+
+eunit:
+	@./rebar -r eunit skip_deps=true
+
+edoc:
+	@./rebar -r doc skip_deps=true
+
+start: compile
+	@erl -pz ebin apps/*/ebin deps/*/ebin -eval 'erlang:display({start_app, $(APP), application:ensure_all_started($(APP))}).'
+
+.dialyzer.plt:
+	touch .dialyzer.plt
+	dialyzer --build_plt --plt .dialyzer.plt --apps erts kernel stdlib crypto compiler
+
+dialyze: compile .dialyzer.plt
+	dialyzer --plt .dialyzer.plt -r ebin $(DIALYZER_OPTS)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7f94bc6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+local
+=====
+
+ローカルスコープ用の名前サーバ
+
+他のアプリケーションに組み込むことも可能
+
+TODO: document
\ No newline at end of file
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 0000000..662992e
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,12 @@
+
+
+# The local application #
+
+
+## Modules ##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="local.md" class="module">local</a></td></tr>
+<tr><td><a href="local_name_server.md" class="module">local_name_server</a></td></tr></table>
+
diff --git a/doc/local.md b/doc/local.md
new file mode 100644
index 0000000..9c60764
--- /dev/null
+++ b/doc/local.md
@@ -0,0 +1,164 @@
+
+
+# Module local #
+* [Data Types](#types)
+* [Function Index](#index)
+* [Function Details](#functions)
+
+Copyright (c) 2013-2014, Takeru Ohta <ph...@gmail.com>
+
+
+<a name="types"></a>
+
+## Data Types ##
+
+
+
+
+### <a name="type-name">name()</a> ###
+
+
+
+<pre><code>
+name() = {<a href="#type-name_server_name">name_server_name()</a>, <a href="#type-process_name">process_name()</a>}
+</code></pre>
+
+
+
+
+
+### <a name="type-name_server_name">name_server_name()</a> ###
+
+
+
+<pre><code>
+name_server_name() = atom()
+</code></pre>
+
+
+
+
+
+### <a name="type-process_name">process_name()</a> ###
+
+
+
+<pre><code>
+process_name() = term()
+</code></pre>
+
+
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#make_name_server_child_spec-1">make_name_server_child_spec/1</a></td><td></td></tr><tr><td valign="top"><a href="#register_name-2">register_name/2</a></td><td></td></tr><tr><td valign="top"><a href="#send-2">send/2</a></td><td></td></tr><tr><td valign="top"><a href="#start_name_server-1">start_name_server/1</a></td><td></td></tr><tr><td valign="top"><a href="#stop_name_s [...]
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="make_name_server_child_spec-1"></a>
+
+### make_name_server_child_spec/1 ###
+
+
+<pre><code>
+make_name_server_child_spec(Name::<a href="#type-name_server_name">name_server_name()</a>) -&gt; <a href="supervisor.md#type-child_spec">supervisor:child_spec()</a>
+</code></pre>
+
+<br></br>
+
+
+
+<a name="register_name-2"></a>
+
+### register_name/2 ###
+
+
+<pre><code>
+register_name(Name::<a href="#type-name">name()</a>, Pid::pid()) -&gt; yes | no
+</code></pre>
+
+<br></br>
+
+
+
+<a name="send-2"></a>
+
+### send/2 ###
+
+
+<pre><code>
+send(Name::<a href="#type-name">name()</a>, Msg::term()) -&gt; pid()
+</code></pre>
+
+<br></br>
+
+
+
+<a name="start_name_server-1"></a>
+
+### start_name_server/1 ###
+
+
+<pre><code>
+start_name_server(Name::<a href="#type-name_server_name">name_server_name()</a>) -&gt; ok | {error, Reason}
+</code></pre>
+
+<ul class="definitions"><li><code>Reason = already_present | {already_started, pid()}</code></li></ul>
+
+
+<a name="stop_name_server-1"></a>
+
+### stop_name_server/1 ###
+
+
+<pre><code>
+stop_name_server(Name::<a href="#type-name_server_name">name_server_name()</a>) -&gt; ok | {error, Reason}
+</code></pre>
+
+<ul class="definitions"><li><code>Reason = not_found</code></li></ul>
+
+
+<a name="unregister_name-1"></a>
+
+### unregister_name/1 ###
+
+
+<pre><code>
+unregister_name(Name::<a href="#type-name">name()</a>) -&gt; ok
+</code></pre>
+
+<br></br>
+
+
+
+<a name="whereis_name-1"></a>
+
+### whereis_name/1 ###
+
+
+<pre><code>
+whereis_name(Name::<a href="#type-name">name()</a>) -&gt; pid() | undefined
+</code></pre>
+
+<br></br>
+
+
+
+<a name="which_name_servers-0"></a>
+
+### which_name_servers/0 ###
+
+
+<pre><code>
+which_name_servers() -&gt; [<a href="#type-name_server_name">name_server_name()</a>]
+</code></pre>
+
+<br></br>
+
+
+
diff --git a/doc/local_name_server.md b/doc/local_name_server.md
new file mode 100644
index 0000000..b14d085
--- /dev/null
+++ b/doc/local_name_server.md
@@ -0,0 +1,34 @@
+
+
+# Module local_name_server #
+* [Function Index](#index)
+* [Function Details](#functions)
+
+Copyright (c) 2014, Takeru Ohta <ph...@gmail.com>
+
+
+__Behaviours:__ [`gen_server`](gen_server.md).
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#start_link-1">start_link/1</a></td><td></td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="start_link-1"></a>
+
+### start_link/1 ###
+
+
+<pre><code>
+start_link(Name::<a href="local.md#type-name_server_name">local:name_server_name()</a>) -&gt; {ok, pid()} | {error, Reason}
+</code></pre>
+
+<ul class="definitions"><li><code>Reason = {already_started, pid()}</code></li></ul>
+
+
diff --git a/rebar b/rebar
new file mode 100755
index 0000000..1f34128
Binary files /dev/null and b/rebar differ
diff --git a/rebar.config b/rebar.config
new file mode 100644
index 0000000..6151883
--- /dev/null
+++ b/rebar.config
@@ -0,0 +1,32 @@
+%% vim: set ft=erlang : -*- erlang -*-
+{require_min_otp_vsn, "R16B03"}.
+
+{erl_opts, [
+            warnings_as_errors,
+            warn_export_all,
+            warn_untyped_record
+           ]}.
+
+{xref_checks, [fail_on_warning, undefined_function_calls]}.
+
+{clean_files, [".eunit/*", "ebin/*.beam"]}.
+
+{cover_enabled, true}.
+
+{edoc_opts, [
+%%             {doclet, edown_doclet},
+             {dialyzer_specs, all},
+             {report_missing_type, true},
+             {report_type_mismatch, true},
+             {pretty_print, erl_pp},
+             {preprocess, true}
+            ]}.
+
+{validate_app_modules, true}.
+
+{deps,
+  [
+   %% {meck, ".*", {git, "git://github.com/eproxus/meck.git", {tag, "0.8.2"}}},
+   %% {reloader, ".*", {git, "git://github.com/sile/reloader.git", {tag, "0.1.0"}}},
+   %% {edown, ".*", {git, "git://github.com/sile/edown.git", {tag, "0.3.2"}}}
+  ]}.
diff --git a/src/local.app.src b/src/local.app.src
new file mode 100644
index 0000000..f95fc60
--- /dev/null
+++ b/src/local.app.src
@@ -0,0 +1,13 @@
+%% vim: set ft=erlang : -*- erlang -*-
+{application, local,
+ [
+  {description, "A Local Name Registration Facility"},
+  {vsn, git},
+  {registered, [local_sup]},
+  {applications, [
+                  kernel,
+                  stdlib
+                 ]},
+  {mod, { local_app, []}},
+  {env, []}
+ ]}.
diff --git a/src/local.erl b/src/local.erl
new file mode 100644
index 0000000..1f4595f
--- /dev/null
+++ b/src/local.erl
@@ -0,0 +1,76 @@
+%% @copyright 2013-2014, Takeru Ohta <ph...@gmail.com>
+%%
+%% TODO: doc
+-module(local).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([start_name_server/1]).
+-export([stop_name_server/1]).
+-export([which_name_servers/0]).
+
+-export([register_name/2]).
+-export([unregister_name/1]).
+-export([whereis_name/1]).
+-export([send/2]).
+
+-export([make_name_server_child_spec/1]).
+
+-export_type([name_server_name/0]).
+-export_type([name/0]).
+-export_type([process_name/0]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Macros & Types
+%%----------------------------------------------------------------------------------------------------------------------
+-type name_server_name() :: atom().
+-type name() :: {name_server_name(), process_name()}.
+-type process_name() :: term().
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported Functions
+%%----------------------------------------------------------------------------------------------------------------------
+-spec start_name_server(name_server_name()) -> ok | {error, Reason} when
+      Reason :: already_present | {already_started, pid()}.
+start_name_server(Name) when is_atom(Name) ->
+    local_sup:start_name_server(Name);
+start_name_server(Name) -> error(badarg, [Name]).
+
+-spec stop_name_server(name_server_name()) -> ok | {error, Reason} when
+      Reason :: not_found.
+stop_name_server(Name) when is_atom(Name) ->
+    local_sup:stop_name_server(Name);
+stop_name_server(Name) -> error(badarg, [Name]).
+
+
+-spec which_name_servers() -> [name_server_name()].
+which_name_servers() ->
+    local_sup:which_name_servers().
+
+-spec register_name(name(), pid()) -> yes | no.
+register_name({Server, Name}, Pid) when is_atom(Server), is_pid(Pid) ->
+    local_name_server:register_name(Server, Name, Pid);
+register_name(Name, Pid) -> error(badarg, [Name, Pid]).
+
+-spec unregister_name(name()) -> ok.
+unregister_name({Server, Name}) when is_atom(Server) ->
+    local_name_server:unregister_name(Server, Name);
+unregister_name(Name) -> error(badarg, [Name]).
+
+-spec whereis_name(name()) -> pid() | undefined.
+whereis_name({Server, Name}) when is_atom(Server) ->
+    local_name_server:whereis_name(Server, Name);
+whereis_name(Name) -> error(badarg, [Name]).
+
+-spec send(name(), term()) -> pid().
+send(Name, Msg) ->
+    case whereis_name(Name) of
+        undefined -> error({badarg, {Name, Msg}});
+        Pid       -> _ = Pid ! Msg, Pid
+    end.
+
+-spec make_name_server_child_spec(name_server_name()) -> supervisor:child_spec().
+make_name_server_child_spec(Name) when is_atom(Name) ->
+    {Name, {local_name_server, start_link, [Name]}, permanent, 5000, worker, [local_name_server]};
+make_name_server_child_spec(Name) -> error(badarg, [Name]).
diff --git a/src/local_app.erl b/src/local_app.erl
new file mode 100644
index 0000000..49cc6eb
--- /dev/null
+++ b/src/local_app.erl
@@ -0,0 +1,22 @@
+%% @copyright 2013-2014, Takeru Ohta <ph...@gmail.com>
+%%
+%% @private
+-module(local_app).
+
+-behaviour(application).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% 'application' Callback API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([start/2, stop/1]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% 'application' Callback Functions
+%%----------------------------------------------------------------------------------------------------------------------
+%% @private
+start(_StartType, _StartArgs) ->
+    local_sup:start_link().
+
+%% @private
+stop(_State) ->
+    ok.
diff --git a/src/local_lib.erl b/src/local_lib.erl
new file mode 100644
index 0000000..ecaf5f3
--- /dev/null
+++ b/src/local_lib.erl
@@ -0,0 +1,20 @@
+%% @copyright 2013-2014, Takeru Ohta <ph...@gmail.com>
+%%
+%% @private
+-module(local_lib).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([unlink_and_flush/1]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported API
+%%----------------------------------------------------------------------------------------------------------------------
+-spec unlink_and_flush(pid()) -> true.
+unlink_and_flush(Pid) ->
+    true = unlink(Pid),
+    receive
+        {'EXIT', Pid, _} -> true
+    after 0 -> true
+    end.
diff --git a/src/local_name_server.erl b/src/local_name_server.erl
new file mode 100644
index 0000000..e6d1677
--- /dev/null
+++ b/src/local_name_server.erl
@@ -0,0 +1,139 @@
+%% @copyright 2014, Takeru Ohta <ph...@gmail.com>
+%%
+%% TODO: doc
+-module(local_name_server).
+
+-behaviour(gen_server).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([start_link/1]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Application Internal API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([register_name/3]).
+-export([unregister_name/2]).
+-export([whereis_name/2]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% 'gen_server' Callback API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Records
+%%----------------------------------------------------------------------------------------------------------------------
+-record(state,
+        {
+          name                           :: local:name_server_name(),
+          pid_to_name = gb_trees:empty() :: gb_trees:tree(pid(), local:process_name())
+        }).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported Functions
+%%----------------------------------------------------------------------------------------------------------------------
+-spec start_link(local:name_server_name()) -> {ok, pid()} | {error, Reason} when
+      Reason :: {already_started, pid()}.
+start_link(Name) ->
+    gen_server:start_link({local, Name}, ?MODULE, [Name], []).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Application Internal Functions
+%%----------------------------------------------------------------------------------------------------------------------
+%% @private
+-spec register_name(local:name_server_name(), local:process_name(), pid()) -> yes | no.
+register_name(Server, Name, Pid) ->
+    gen_server:call(Server, {register_name, {Name, Pid}}).
+
+%% @private
+-spec unregister_name(local:name_server_name(), local:process_name()) -> ok.
+unregister_name(Server, Name) ->
+    gen_server:call(Server, {unregister_name, Name}).
+
+%% @private
+-spec whereis_name(local:name_server_name(), local:process_name()) -> pid() | undefined.
+whereis_name(Server, Name) ->
+    try ets:lookup(Server, Name) of
+        []         -> undefined;
+        [{_, Pid}] -> Pid
+    catch
+        _:_ -> undefined
+    end.
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% 'gen_server' Callback Functions
+%%----------------------------------------------------------------------------------------------------------------------
+%% @private
+init([Name]) ->
+    _ = process_flag(trap_exit, true),
+    State =
+        #state{
+           name = Name
+          },
+    _ = ets:new(Name, [named_table, protected, set, {read_concurrency, true}]),
+    {ok, State}.
+
+%% @private
+handle_call({register_name, Arg}, _From, State) ->
+    handle_register_name(Arg, State);
+handle_call({unregister_name, Arg}, _From, State) ->
+    handle_unregister_name(Arg, State);
+handle_call(_Request, _From, State) ->
+    {noreply, State}.
+
+%% @private
+handle_cast(_Request, State) ->
+    {noreply, State}.
+
+%% @private
+handle_info({'EXIT', Pid, Reason}, State) ->
+    handle_exit(Pid, Reason, State);
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+    ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Internal Functions
+%%----------------------------------------------------------------------------------------------------------------------
+-spec handle_register_name(Arg, #state{}) -> {reply, Result, #state{}} when
+      Arg    :: {local:process_name(), pid()},
+      Result :: yes | no.
+handle_register_name({Name, Pid}, State) ->
+    case ets:lookup(State#state.name, Name) =:= [] andalso not gb_trees:is_defined(Pid, State#state.pid_to_name) of
+        false -> {reply, no, State};
+        true  ->
+            true = ets:insert(State#state.name, {Name, Pid}),
+            true = link(Pid),
+            PidToName = gb_trees:insert(Pid, Name, State#state.pid_to_name),
+            {reply, yes, State#state{pid_to_name = PidToName}}
+    end.
+
+-spec handle_unregister_name(local:process_name(), #state{}) -> {reply, ok, #state{}}.
+handle_unregister_name(Name, State) ->
+    case ets:lookup(State#state.name, Name) of
+        []         -> {reply, ok, State};
+        [{_, Pid}] ->
+            true = local_lib:unlink_and_flush(Pid),
+            true = ets:delete(State#state.name, Name),
+            PidToName = gb_trees:delete(Pid, State#state.pid_to_name),
+            {reply, ok, State#state{pid_to_name = PidToName}}
+    end.
+
+-spec handle_exit(pid(), term(), #state{}) -> {noreply, #state{}} | {stop, term(), #state{}}.
+handle_exit(Pid, Reason, State) ->
+    case gb_trees:lookup(Pid, State#state.pid_to_name) of
+        none          -> {stop, Reason, State};
+        {value, Name} ->
+            true = ets:delete(State#state.name, Name),
+            PidToName = gb_trees:delete(Pid, State#state.pid_to_name),
+            {noreply, State#state{pid_to_name = PidToName}}
+    end.
diff --git a/src/local_sup.erl b/src/local_sup.erl
new file mode 100644
index 0000000..2ead487
--- /dev/null
+++ b/src/local_sup.erl
@@ -0,0 +1,58 @@
+%% @copyright 2013-2014, Takeru Ohta <ph...@gmail.com>
+%%
+%% @private
+-module(local_sup).
+
+-behaviour(supervisor).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([start_link/0]).
+
+-export([start_name_server/1]).
+-export([stop_name_server/1]).
+-export([which_name_servers/0]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% 'supervisor' Callback API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([init/1]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported Functions
+%%----------------------------------------------------------------------------------------------------------------------
+%% @doc Starts root supervisor
+-spec start_link() -> {ok, pid()} | {error, Reason::term()}.
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+-spec start_name_server(local:name_server_name()) -> ok | {error, Reason} when
+      Reason :: already_present | {already_started, pid()}.
+start_name_server(Name) ->
+    Child = local:make_name_server_child_spec(Name),
+    case supervisor:start_child(?MODULE, Child) of
+        {ok, _Pid}                      -> ok;
+        {error, already_present}        -> {error, already_present};
+        {error, {already_started, Pid}} -> {error, {already_started, Pid}};
+        Other                           -> error({unexpected_result, Other}, [Name])
+    end.
+
+-spec stop_name_server(local:name_server_name()) -> ok | {error, Reason} when
+      Reason :: not_found.
+stop_name_server(Name) ->
+    case supervisor:terminate_child(?MODULE, Name) of
+        {error, not_found} -> {error, not_found};
+        ok                 -> ok = supervisor:delete_child(?MODULE, Name)
+    end.
+
+-spec which_name_servers() -> [local:name_server_name()].
+which_name_servers() ->
+    [Id || {Id, _, _, _} <- supervisor:which_children(?MODULE)].
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% 'supervisor' Callback Functions
+%%----------------------------------------------------------------------------------------------------------------------
+%% @private
+init([]) ->
+    {ok, { {one_for_one, 5, 10}, []} }.
diff --git a/test/echo_gen_server.erl b/test/echo_gen_server.erl
new file mode 100644
index 0000000..e2e5a16
--- /dev/null
+++ b/test/echo_gen_server.erl
@@ -0,0 +1,53 @@
+%% @copyright 2014, Takeru Ohta <ph...@gmail.com>
+%%
+%% @doc TODO
+-module(echo_gen_server).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([start_link/1]).
+-export([call/2]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% 'gen_server' Callback API
+%%----------------------------------------------------------------------------------------------------------------------
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Exported Functions
+%%----------------------------------------------------------------------------------------------------------------------
+-spec start_link(term()) -> {ok, pid()} | {error, Reason::term()}.
+start_link(Name) ->
+    gen_server:start_link(Name, ?MODULE, [], []).
+
+-spec call(term(), term()) -> {echo, term()}.
+call(Name, Msg) ->
+    gen_server:call(Name, {call, Msg}).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% 'gen_server' Callback Functions
+%%----------------------------------------------------------------------------------------------------------------------
+%% @private
+init([]) ->
+    {ok, []}.
+
+%% @priate
+handle_call({call, Msg}, _From, State) ->
+    {reply, {echo, Msg}, State}.
+
+%% @private
+handle_cast(_Request, State) ->
+    {noreply, State}.
+
+%% @private
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+    ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
diff --git a/test/local_lib_tests.erl b/test/local_lib_tests.erl
new file mode 100644
index 0000000..4fb5f0f
--- /dev/null
+++ b/test/local_lib_tests.erl
@@ -0,0 +1,48 @@
+%% @copyright 2014, Takeru Ohta <ph...@gmail.com>
+%%
+-module(local_lib_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Unit Tests
+%%----------------------------------------------------------------------------------------------------------------------
+link_and_flush_test_() ->
+    [
+     {"normal unlink",
+      fun () ->
+              Pid = spawn_link(timer, sleep, [infinity]),
+              true = link(Pid),
+              true = local_lib:unlink_and_flush(Pid),
+              exit(Pid, kill),
+              ?assert(true)
+      end},
+     {"flush unlink",
+      fun () ->
+              process_flag(trap_exit, true),
+
+              Pid = spawn_link(timer, sleep, [infinity]),
+              true = link(Pid),
+              exit(Pid, kill),
+              timer:sleep(10),
+              true = local_lib:unlink_and_flush(Pid),
+              receive
+                  _ -> ?assert(false)
+              after 10 -> ?assert(true)
+              end
+      end},
+     {"flush unlink",
+      fun () ->
+              process_flag(trap_exit, true),
+
+              Pid = spawn_link(timer, sleep, [infinity]),
+              true = link(Pid),
+              exit(Pid, kill),
+              timer:sleep(10),
+              true = unlink(Pid),
+              receive
+                  {'EXIT', Pid, killed} -> ?assert(true)
+              after 10 -> ?assert(false)
+              end
+      end}
+    ].
diff --git a/test/local_tests.erl b/test/local_tests.erl
new file mode 100644
index 0000000..292177b
--- /dev/null
+++ b/test/local_tests.erl
@@ -0,0 +1,204 @@
+%% @copyright 2014, Takeru Ohta <ph...@gmail.com>
+%%
+-module(local_tests).
+
+-on_load(init/0).
+-include_lib("eunit/include/eunit.hrl").
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Macros
+%%----------------------------------------------------------------------------------------------------------------------
+-define(NS, test_name_server). % Name Server
+-define(NAME(Name), {?NS, Name}).
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Unit Tests
+%%----------------------------------------------------------------------------------------------------------------------
+start_and_stop_name_server_test_() ->
+    [
+     {"start and stop",
+      fun () ->
+              ?assertEqual([],    local:which_name_servers()),
+              ?assertEqual(ok,    local:start_name_server(?NS)),
+              ?assertEqual([?NS], local:which_name_servers()),
+              ?assertEqual(ok,    local:stop_name_server(?NS)),
+              ?assertEqual([],    local:which_name_servers())
+      end},
+     {"duplicated start",
+      fun () ->
+              ?assertEqual(ok, local:start_name_server(?NS)),
+              ?assertMatch({error, {already_started, _}}, local:start_name_server(?NS)),
+              ?assertEqual(ok, local:stop_name_server(?NS))
+      end},
+     {"delete not-started server",
+      fun () ->
+              ?assertEqual({error, not_found}, local:stop_name_server(?NS))
+      end}
+    ].
+
+register_and_unregister_test_() ->
+    {foreach,
+     fun ()  -> ok = local:start_name_server(?NS) end,
+     fun (_) -> ok = local:stop_name_server(?NS) end,
+     [
+      {"register process name",
+       fun () ->
+               ?assertEqual(yes, local:register_name(?NAME(hoge), self())),
+               ?assertEqual(self(), local:whereis_name(?NAME(hoge)))
+       end},
+      {"duplicated registration",
+       fun () ->
+               yes = local:register_name(?NAME(hoge), self()),
+               ?assertEqual(no, local:register_name(?NAME(hoge), self())),
+               ?assertEqual(no, local:register_name(?NAME(fuga), self()))
+       end},
+      {"unregister process name",
+       fun () ->
+               yes = local:register_name(?NAME(hoge), self()),
+
+               ?assertEqual(ok, local:unregister_name(?NAME(hoge))),
+               ?assertEqual(yes, local:register_name(?NAME(hoge), self())),
+
+               ?assertEqual(ok, local:unregister_name(?NAME(hoge))),
+               ?assertEqual(yes, local:register_name(?NAME(fuga), self()))
+       end},
+      {"unregister not-registered name",
+       fun () ->
+               ?assertEqual(ok, local:unregister_name(?NAME(hoge)))
+       end}
+     ]}.
+
+whereis_test_() ->
+    {foreach,
+     fun ()  -> ok = local:start_name_server(?NS) end,
+     fun (_) -> ok = local:stop_name_server(?NS) end,
+     [
+      {"resolve name",
+       fun () ->
+               yes = local:register_name(?NAME(hoge), self()),
+               ?assertEqual(self(), local:whereis_name(?NAME(hoge)))
+       end},
+      {"resolve not-registered name",
+       fun () ->
+               ?assertEqual(undefined, local:whereis_name(?NAME(hoge)))
+       end},
+      {"resolve unregistered name",
+       fun () ->
+               yes = local:register_name(?NAME(hoge), self()),
+               ok = local:unregister_name(?NAME(hoge)),
+               ?assertEqual(undefined, local:whereis_name(?NAME(hoge)))
+       end}
+     ]}.
+
+send_test_() ->
+    {foreach,
+     fun ()  -> ok = local:start_name_server(?NS) end,
+     fun (_) -> ok = local:stop_name_server(?NS) end,
+     [
+      {"send message to registered process",
+       fun () ->
+               yes = local:register_name(?NAME(hoge), self()),
+               ?assertEqual(self(), local:send(?NAME(hoge), hello)),
+               receive
+                   hello -> ?assert(true)
+               after 10 -> ?assert(timeout)
+               end
+       end},
+      {"send message to not-registered process",
+       fun () ->
+               ?assertError({badarg, _}, local:send(?NAME(hoge), hello))
+       end}
+     ]}.
+
+process_down_test_() ->
+    {foreach,
+     fun ()  -> ok = local:start_name_server(?NS) end,
+     fun (_) -> ok = local:stop_name_server(?NS) end,
+     [
+      {"register downed process",
+       fun () ->
+               process_flag(trap_exit, true),
+
+               Pid = spawn_link(fun () -> ok end),
+               receive {'EXIT', Pid, _} -> ok end,
+
+               yes = local:register_name(?NAME(hoge), Pid),
+               ?assertEqual(undefined, local:whereis_name(?NAME(hoge))),
+
+               yes = local:register_name(?NAME(hoge), self()),
+               ?assertEqual(self(), local:whereis_name(?NAME(hoge)))
+       end},
+      {"register then process down",
+       fun () ->
+               process_flag(trap_exit, true),
+
+               Pid = spawn_link(timer, sleep, [infinity]),
+
+               yes = local:register_name(?NAME(hoge), Pid),
+               ?assertEqual(Pid, local:whereis_name(?NAME(hoge))),
+
+               exit(Pid, shutdown),
+               receive {'EXIT', Pid, _} -> ok end,
+
+               ?assertEqual(undefined, local:whereis_name(?NAME(hoge)))
+       end},
+      {"name server down",
+       fun () ->
+               process_flag(trap_exit, true),
+
+               yes = local:register_name(?NAME(hoge), self()),
+               ?assertEqual(self(), local:whereis_name(?NAME(hoge))),
+
+               Ns = whereis(?NS),
+               exit(Ns, kill),
+
+               receive {'EXIT', Ns, Reason} -> ?assertEqual(killed, Reason) after 50 -> ?assert(timeout) end,
+
+               ?assertEqual(undefined, local:whereis_name(?NAME(hoge))),
+
+               timer:sleep(20), % wait restarting
+
+               yes = local:register_name(?NAME(hoge), self()),
+               ?assertEqual(self(), local:whereis_name(?NAME(hoge)))
+       end},
+      {"other linked process down",
+       fun () ->
+               Ns = whereis(?NS),
+               Monitor = monitor(process, Ns),
+               spawn(fun () ->
+                             link(Ns),
+                             exit(abort)
+                     end),
+               receive
+                   {'DOWN', Monitor, _, Ns, Reason} -> ?assertEqual(abort, Reason)
+               after 50 -> ?assert(timeout)
+               end,
+
+               timer:sleep(20), % wait restarting
+
+               yes = local:register_name(?NAME(hoge), self()),
+               ?assertEqual(self(), local:whereis_name(?NAME(hoge)))
+       end}
+     ]}.
+
+gen_server_test_() ->
+    {foreach,
+     fun ()  -> ok = local:start_name_server(?NS) end,
+     fun (_) -> ok = local:stop_name_server(?NS) end,
+     [
+      {"gen_server via name",
+       fun() ->
+               Result = echo_gen_server:start_link({via, local, ?NAME(hoge)}),
+               ?assertMatch({ok, _}, Result),
+
+               ?assertEqual({echo, hello}, echo_gen_server:call({via, local, ?NAME(hoge)}, hello))
+       end}
+     ]}.
+
+%%----------------------------------------------------------------------------------------------------------------------
+%% Internal Functions
+%%----------------------------------------------------------------------------------------------------------------------
+init() ->
+    {ok, _} = application:ensure_all_started(local),
+    _ = error_logger:tty(false),
+    ok.