- add encoding with test unit
- add readme and license - add decoding feature with test unit - add rebar files
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
.rebar3
|
||||
_*
|
||||
.eunit
|
||||
*.o
|
||||
*.beam
|
||||
*.plt
|
||||
*.swp
|
||||
*.swo
|
||||
.erlang.cookie
|
||||
ebin
|
||||
log
|
||||
erl_crash.dump
|
||||
.rebar
|
||||
logs
|
||||
_build
|
||||
.idea
|
||||
rebar3.crashdump
|
||||
**~
|
||||
14
LICENSE
Normal file
14
LICENSE
Normal file
@@ -0,0 +1,14 @@
|
||||
Copyright (c) 2021 Mathieu Kerjouan <contact [at] steepath [dot] eu>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||||
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
74
README.md
Normal file
74
README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# redis
|
||||
|
||||
`redis` is an application and library implementing redis protocol in
|
||||
Erlang.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
rebar3 shell
|
||||
```
|
||||
|
||||
### Serializer
|
||||
|
||||
Encoding Erlang terms in Redis data format using `redis:encode/1`
|
||||
function:
|
||||
|
||||
```erlang
|
||||
% integer
|
||||
redis:encode(1).
|
||||
|
||||
% simple string
|
||||
redis:encode(<<"test">>).
|
||||
|
||||
% bulk string
|
||||
redis:encode({bulk_string, <<"test">>).
|
||||
|
||||
% array
|
||||
redis:encode([1,2,3,<<"test">>, {bulk_string, <<"test">>}]).
|
||||
|
||||
% error
|
||||
redis:encode({error, <<"my message">>}).
|
||||
```
|
||||
|
||||
Decoding Redis data in Erlang term with `redis:decode/1` function:
|
||||
|
||||
```erlang
|
||||
% simple string
|
||||
redis:decode(<<"+OK\r\n">>).
|
||||
|
||||
% integer
|
||||
redis:decode(<<":1\r\n">>).
|
||||
|
||||
% bulk string
|
||||
redis:decode(<<"$3\r\nfoo\r\n">>).
|
||||
|
||||
% array
|
||||
redis:decode(<<"*0\r\n\r\n">>).
|
||||
|
||||
% error
|
||||
redis:decode(<<"-Message\r\n">>).
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
wip.
|
||||
|
||||
### Server
|
||||
|
||||
wip.
|
||||
|
||||
## Test
|
||||
|
||||
```sh
|
||||
rebar3 eunit
|
||||
```
|
||||
|
||||
# Resources and References
|
||||
|
||||
* https://redis.io/topics/protocol
|
||||
|
||||
# About
|
||||
|
||||
Made with <3 by Mathieu Kerjouan with [Erlang](erlang.org/) and
|
||||
[rebar3](https://www.rebar3.org).
|
||||
7
rebar.config
Normal file
7
rebar.config
Normal file
@@ -0,0 +1,7 @@
|
||||
{erl_opts, [debug_info]}.
|
||||
{deps, []}.
|
||||
|
||||
{shell, [
|
||||
% {config, "config/sys.config"},
|
||||
{apps, [redis]}
|
||||
]}.
|
||||
1
rebar.lock
Normal file
1
rebar.lock
Normal file
@@ -0,0 +1 @@
|
||||
[].
|
||||
306
src/redis.erl
306
src/redis.erl
@@ -1,45 +1,275 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(redis).
|
||||
-compile(export_all).
|
||||
-export([encode/1, decode/1]).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-type resp_type() :: simple_string | error | integer | bulk_string | array.
|
||||
-type resp_simple_string() :: {simple_string, bitstring()}.
|
||||
-type resp_error() :: {error, bitstring()}.
|
||||
-type resp_integer() :: {integer, integer()}.
|
||||
-type resp_bulk_string() :: {bulk_string, bitstring()}.
|
||||
-type resp_types() :: resp_simple_string() |
|
||||
resp_integer() |
|
||||
resp_bulk_string().
|
||||
-type encode_error() :: {error, bitstring() | binary()}.
|
||||
-type encode_integer() :: integer().
|
||||
-type encode_string() :: bitstring() | binary().
|
||||
-type encode_bulk_string() :: {bulk_string, bitstring()}.
|
||||
-type encode_array() :: [encode_integer() |
|
||||
encode_string() |
|
||||
encode_bulk_string()].
|
||||
-type encode_types() :: encode_integer() |
|
||||
encode_string() |
|
||||
encode_bulk_string() |
|
||||
encode_array().
|
||||
-type encode_return_ok() :: bitstring() | binary().
|
||||
-type encode_return_error() :: {error, atom()}.
|
||||
|
||||
-type resp_array() :: {array, [resp_types()]}.
|
||||
|
||||
|
||||
-spec resp(Rest) -> Return when
|
||||
Rest :: resp(),
|
||||
Return :: bitstring().
|
||||
resp(simple_string) -> <<"+">>;
|
||||
resp(error) -> <<"-">>;
|
||||
resp(integer) -> <<":">>;
|
||||
resp(bulk_string) -> <<"$">>;
|
||||
resp(array) -> <<"*">>.
|
||||
|
||||
|
||||
|
||||
ok() ->
|
||||
<<"+OK\r\n">>.
|
||||
|
||||
error(Message) ->
|
||||
<<"-ERR ", Message, "\r\n">>.
|
||||
|
||||
wrongtype(Message) ->
|
||||
<<"-WRONGTYPE ", Message, "\r\n">>.
|
||||
|
||||
string(String) ->
|
||||
Length = erlang:size(String),
|
||||
LengthStr = erlang:integer_to_binary(Length),
|
||||
case Length of
|
||||
0 -> <<"$0\r\n\r\n">>;
|
||||
_ -> <<"$", LengthStr, "\r\n", String, "\r\n">>
|
||||
%%-------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%-------------------------------------------------------------------
|
||||
-spec encode(Data) -> Return when
|
||||
Data :: encode_types(),
|
||||
Return :: encode_return_ok() | encode_return_error().
|
||||
encode(Integer)
|
||||
when is_integer(Integer) ->
|
||||
Value = erlang:integer_to_binary(Integer),
|
||||
<<":", Value/bitstring, "\r\n">>;
|
||||
encode(Bitstring)
|
||||
when is_bitstring(Bitstring) ->
|
||||
case Return = encode_valid_char(Bitstring) of
|
||||
{ok, Valid} -> <<"+", Valid/bitstring, "\r\n">>;
|
||||
Return -> Return
|
||||
end;
|
||||
encode({bulk_string, null}) ->
|
||||
<<"$-1\r\n">>;
|
||||
encode({bulk_string, Bitstring})
|
||||
when is_bitstring(Bitstring) ->
|
||||
Length = erlang:integer_to_binary(erlang:size(Bitstring)),
|
||||
<<"$", Length/bitstring, "\r\n", Bitstring/bitstring, "\r\n">>;
|
||||
encode([]) ->
|
||||
<<"*0\r\n">>;
|
||||
encode(Array)
|
||||
when is_list(Array) ->
|
||||
{ok, Elements, Counter} = encode_array(Array, <<>>, 0),
|
||||
C = erlang:integer_to_binary(Counter),
|
||||
<<"*", C/bitstring, "\r\n", Elements/bitstring>>;
|
||||
encode({error, Bitstring})
|
||||
when is_bitstring(Bitstring) ->
|
||||
case Return = encode_valid_char(Bitstring) of
|
||||
{ok, Valid} -> <<"-", Valid/bitstring, "\r\n">>;
|
||||
Return -> Return
|
||||
end.
|
||||
|
||||
encode_test() ->
|
||||
[{"encode bitstring", [?assertEqual(<<"+OK\r\n">>
|
||||
,encode(<<"OK">>))
|
||||
,?assertEqual({error, badchar}
|
||||
,encode(<<"OK\r">>))
|
||||
,?assertEqual({error, badchar}
|
||||
,encode(<<"OK\n">>))
|
||||
,?assertEqual({error, badchar}
|
||||
,encode(<<"OK\r\n">>))
|
||||
]}
|
||||
,{"encode integer", [?assertEqual(<<":0\r\n">>
|
||||
,encode(0))
|
||||
,?assertEqual(<<":10000\r\n">>
|
||||
,encode(10000))
|
||||
]}
|
||||
,{"encode bulk string", [?assertEqual(<<"$-1\r\n">>
|
||||
,encode({bulk_string, null}))
|
||||
,?assertEqual(<<"$0\r\n\r\n">>
|
||||
,encode({bulk_string, <<>>}))
|
||||
,?assertEqual(<<"$6\r\nfoobar\r\n">>
|
||||
,encode({bulk_string, <<"foobar">>}))
|
||||
]}
|
||||
,{"encode array", [?assertEqual(<<"*0\r\n">>, encode([]))
|
||||
,?assertEqual(<<"*3\r\n:1\r\n:2\r\n:3\r\n">>
|
||||
,encode([1,2,3]))
|
||||
,?assertEqual(<<"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n">>
|
||||
,encode([{bulk_string, <<"foo">>}
|
||||
,{bulk_string, <<"bar">>}]))
|
||||
,?assertEqual(<<"*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$6\r\nfoobar\r\n">>
|
||||
,encode([1,2,3,4,{bulk_string, <<"foobar">>}]))
|
||||
,?assertEqual(<<"*1\r\n*0\r\n">>
|
||||
,encode([[]]))
|
||||
,?assertEqual(<<"*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n">>
|
||||
,encode([[1,2,3]]))
|
||||
]}
|
||||
,{"encode error", [?assertEqual(<<"-Error message\r\n">>
|
||||
,encode({error, <<"Error message">>}))
|
||||
,?assertEqual(<<"-ERR unknown command 'foobar'\r\n">>
|
||||
,encode({error, <<"ERR unknown command 'foobar'">>}))
|
||||
,?assertEqual(<<"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n">>
|
||||
,encode({error, <<"WRONGTYPE Operation against a key holding the wrong kind of value">>}))
|
||||
]}
|
||||
].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec encode_valid_char(Bitstring) -> Return when
|
||||
Bitstring :: bitstring() | binary(),
|
||||
Return :: {ok, bitstring() | binary()} |
|
||||
{error, badchar}.
|
||||
encode_valid_char(Bitstring)
|
||||
when is_bitstring(Bitstring) ->
|
||||
try
|
||||
nomatch = binary:match(Bitstring, <<"\r">>),
|
||||
nomatch = binary:match(Bitstring, <<"\n">>)
|
||||
of
|
||||
_ -> {ok, Bitstring}
|
||||
catch
|
||||
error:_ -> {error, badchar}
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec encode_array(List, Buffer, Counter) -> Return when
|
||||
List :: list(),
|
||||
Buffer :: bitstring() | binary(),
|
||||
Counter :: integer(),
|
||||
Return :: bitstring() | binary().
|
||||
encode_array([], Buffer, Counter) ->
|
||||
{ok, Buffer, Counter};
|
||||
encode_array([H|T], Buffer, Counter) ->
|
||||
Data = encode(H),
|
||||
Return = <<Buffer/bitstring, Data/bitstring>>,
|
||||
encode_array(T, Return, Counter+1).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
decode(<<"+", Rest/bitstring>>) ->
|
||||
decode_string(Rest);
|
||||
decode(<<":", Rest/bitstring>>) ->
|
||||
decode_integer(Rest);
|
||||
decode(<<"$", Rest/bitstring>>) ->
|
||||
decode_bulk_string(Rest);
|
||||
decode(<<"*", Rest/bitstring>>) ->
|
||||
decode_array(Rest);
|
||||
decode(<<"-", Rest/bitstring>>) ->
|
||||
decode_error(Rest).
|
||||
|
||||
decode_test() ->
|
||||
[{"decode integer", [?assertEqual({ok, 0}
|
||||
,decode(<<":0\r\n">>))
|
||||
,?assertEqual({ok, 1}
|
||||
,decode(<<":1\r\n">>))
|
||||
,?assertEqual({ok, 999999}
|
||||
,decode(<<":999999\r\n">>))
|
||||
]}
|
||||
,{"decode simple string", [?assertEqual({ok, <<>>}
|
||||
,decode(<<"+\r\n">>))
|
||||
,?assertEqual({ok, <<"test">>}
|
||||
,decode(<<"+test\r\n">>))
|
||||
]}
|
||||
,{"decode bulk string", [?assertEqual({ok, <<"test">>}
|
||||
,decode(<<"$4\r\ntest\r\n">>))
|
||||
]}
|
||||
,{"decode array", [?assertEqual({ok, []}
|
||||
,decode(<<"*0\r\n">>))
|
||||
,?assertEqual({ok, [1,2,3]}
|
||||
,decode(<<"*3\r\n:1\r\n:2\r\n:3\r\n">>))
|
||||
]}
|
||||
,{"decode error", [?assertEqual({ok, {error, <<"ERR">>}}
|
||||
,decode(<<"-ERR\r\n">>))
|
||||
]}
|
||||
].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
decode_string(Bitstring) ->
|
||||
decode_string(Bitstring, <<>>).
|
||||
|
||||
decode_string(<<"\r\n">>, Buffer) ->
|
||||
{ok, Buffer};
|
||||
decode_string(<<"\r\n", Rest/bitstring>>, Buffer) ->
|
||||
{ok, Buffer, Rest};
|
||||
decode_string(<<"\r", _/bitstring>>, _) ->
|
||||
{error, badchar};
|
||||
decode_string(<<"\n", _/bitstring>>, _) ->
|
||||
{error, badchar};
|
||||
decode_string(<<Char, Rest/bitstring>>, Buffer) ->
|
||||
decode_string(Rest, <<Buffer/bitstring, Char>>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
decode_integer(Integer) ->
|
||||
decode_integer(Integer, <<>>).
|
||||
|
||||
decode_integer(<<"\r\n">>, Buffer) ->
|
||||
{ok, erlang:binary_to_integer(Buffer)};
|
||||
decode_integer(<<"\r\n", Rest/bitstring>>, Buffer) ->
|
||||
{ok, erlang:binary_to_integer(Buffer), Rest};
|
||||
decode_integer(<<Char, Rest/bitstring>>, Buffer)
|
||||
when Char >= $0 andalso Char =< $9 ->
|
||||
decode_integer(Rest, <<Buffer/bitstring, Char>>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
decode_separator(Bitstring) ->
|
||||
case binary:split(Bitstring, <<"\r\n">>) of
|
||||
[<<>>] -> {ok, <<>>};
|
||||
[<<>>,<<>>] -> {ok, <<>>};
|
||||
[Value,Rest] -> {ok, Value, Rest}
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
decode_bulk_string(<<"-1\r\n">>) ->
|
||||
{ok, nil};
|
||||
decode_bulk_string(<<"-1\r\n", Rest/bitstring>>) ->
|
||||
{ok, nil, Rest};
|
||||
decode_bulk_string(Bitstring) ->
|
||||
{ok, Size, Rest} = decode_integer(Bitstring),
|
||||
Length = Size*8,
|
||||
<<String:Length/bitstring, "\r\n", R/bitstring>> = Rest,
|
||||
case R of
|
||||
<<>> -> {ok, String};
|
||||
_ -> {ok, String, R}
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
decode_array(<<"0\r\n">>) ->
|
||||
{ok, []};
|
||||
decode_array(<<"0\r\n", Rest/bitstring>>) ->
|
||||
{ok, [], Rest};
|
||||
decode_array(Bitstring) ->
|
||||
{ok, Size, Rest} = decode_integer(Bitstring),
|
||||
decode_array(Rest, Size, []).
|
||||
|
||||
decode_array(<<>>, 0, Buffer) ->
|
||||
{ok, lists:reverse(Buffer)};
|
||||
decode_array(Rest, 0, Buffer) ->
|
||||
{ok, lists:reverse(Buffer), Rest};
|
||||
decode_array(Bitstring, Size, Buffer) ->
|
||||
case decode(Bitstring) of
|
||||
{ok, Result} ->
|
||||
decode_array(<<>>, Size-1, [Result|Buffer]);
|
||||
{ok, Result, Rest} ->
|
||||
decode_array(Rest, Size-1, [Result|Buffer])
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
decode_error(Bitstring) ->
|
||||
decode_error(Bitstring, <<>>).
|
||||
|
||||
decode_error(<<"\r\n">>, Buffer) ->
|
||||
{ok, {error, Buffer}};
|
||||
decode_error(<<"\r", Rest/bitstring>>, _) ->
|
||||
{error, badchar};
|
||||
decode_error(<<"\n", Rest/bitstring>>, _) ->
|
||||
{error, badchar};
|
||||
decode_error(<<Char, Rest/bitstring>>, Buffer) ->
|
||||
decode_error(Rest, <<Buffer/bitstring, Char>>).
|
||||
|
||||
|
||||
|
||||
141
test/redis_SUITE.erl
Normal file
141
test/redis_SUITE.erl
Normal file
@@ -0,0 +1,141 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Mathieu Kerjouan <contact [at] steepath [dot] eu>
|
||||
%%% @copyright 2021 (c) Mathieu Kerjouan
|
||||
%%%
|
||||
%%% @doc
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(redis_SUITE).
|
||||
-compile(export_all).
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec suite() -> Return when
|
||||
Return :: [tuple()].
|
||||
suite() ->
|
||||
[{timetrap,{seconds,30}}].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec init_per_suite(Config) -> Return when
|
||||
Config :: [tuple()],
|
||||
Reason :: term(),
|
||||
Return :: Config | {skip,Reason} | {skip_and_save,Reason,Config}.
|
||||
init_per_suite(Config) ->
|
||||
Config.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec end_per_suite(Config) -> Return when
|
||||
Config :: [tuple()],
|
||||
Return :: term() | {save_config,Config}.
|
||||
end_per_suite(_Config) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec init_per_group(GroupName, Config) -> Return when
|
||||
GroupName :: atom(),
|
||||
Config :: [tuple()],
|
||||
Reason :: term(),
|
||||
Return :: Config | {skip,Reason} | {skip_and_save,Reason,Config}.
|
||||
init_per_group(_GroupName, Config) ->
|
||||
Config.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec end_per_group(GroupName, Config) -> Return when
|
||||
GroupName :: atom(),
|
||||
Config :: [tuple()],
|
||||
Return :: term() | {save_config,Config}.
|
||||
end_per_group(_GroupName, _Config) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec init_per_testcase(TestCase, Config) -> Return when
|
||||
TestCase :: atom(),
|
||||
Config :: [tuple()],
|
||||
Reason :: term(),
|
||||
Return :: Config | {skip,Reason} | {skip_and_save,Reason,Config}.
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
Config.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec end_per_testcase(TestCase, Config) -> Return when
|
||||
TestCase :: atom(),
|
||||
Config :: [tuple()],
|
||||
Reason :: term(),
|
||||
Return :: term() | {save_config,Config} | {fail,Reason}.
|
||||
end_per_testcase(_TestCase, _Config) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec groups() -> Return when
|
||||
Group :: {GroupName,Properties,GroupsAndTestCases},
|
||||
GroupName :: atom(),
|
||||
Properties :: [parallel | sequence | Shuffle | {RepeatType,N}],
|
||||
GroupsAndTestCases :: [Group | {group,GroupName} | TestCase],
|
||||
TestCase :: atom(),
|
||||
Shuffle :: shuffle | {shuffle,{integer(),integer(),integer()}},
|
||||
RepeatType :: repeat | repeat_until_all_ok | repeat_until_all_fail |
|
||||
repeat_until_any_ok | repeat_until_any_fail,
|
||||
N :: integer() | forever,
|
||||
Return :: [Group].
|
||||
groups() ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec all() -> Return when
|
||||
GroupsAndTestCases :: [{group,GroupName} | TestCase],
|
||||
GroupName :: atom(),
|
||||
TestCase :: atom(),
|
||||
Reason :: term(),
|
||||
Return :: GroupsAndTestCases | {skip,Reason}.
|
||||
all() ->
|
||||
[my_test_case].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec my_test_case() -> Return when
|
||||
Return :: [tuple()].
|
||||
my_test_case() ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec my_test_case(Config) -> Return when
|
||||
Config :: [tuple()],
|
||||
Reason :: term(),
|
||||
Comment :: term(),
|
||||
Return :: ok | erlang:exit() | {skip,Reason} | {comment,Comment} |
|
||||
{save_config,Config} | {skip_and_save,Reason,Config}.
|
||||
my_test_case(_Config) ->
|
||||
ok.
|
||||
|
||||
Reference in New Issue
Block a user