HG-253: Sequence service created (#1)

This commit is contained in:
Natalia Pulina 2017-09-07 17:26:47 +04:00 committed by GitHub
parent 06c06f2830
commit 5bc3c65824
19 changed files with 850 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
# general
log
/_build/
/_checkouts/
*~
erl_crash.dump
.tags*
*.sublime-workspace
.DS_Store
Dockerfile
docker-compose.yml
/.idea/
*.beam

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "build_utils"]
path = build_utils
url = git@github.com:rbkmoney/build_utils.git
branch = master

25
Dockerfile.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
cat <<EOF
FROM $BASE_IMAGE
MAINTAINER Natalia Pulina <n.pulina@rbkmoney.com>
COPY ./_build/prod/rel/sequences /opt/sequences
CMD /opt/sequences/bin/sequences foreground
EXPOSE 8022
LABEL base_image_tag=$BASE_IMAGE_TAG
LABEL build_image_tag=$BUILD_IMAGE_TAG
# A bit of magic to get a proper branch name
# even when the HEAD is detached (Hey Jenkins!
# BRANCH_NAME is available in Jenkins env).
LABEL branch=$( \
if [ "HEAD" != $(git rev-parse --abbrev-ref HEAD) ]; then \
echo $(git rev-parse --abbrev-ref HEAD); \
elif [ -n "$BRANCH_NAME" ]; then \
echo $BRANCH_NAME; \
else \
echo $(git name-rev --name-only HEAD); \
fi)
LABEL commit=$(git rev-parse HEAD)
LABEL commit_number=$(git rev-list --count HEAD)
WORKDIR /opt/sequences
EOF

64
Jenkinsfile vendored
View File

@ -1 +1,65 @@
#!groovy
// -*- mode: groovy -*-
def finalHook = {
runStage('store CT logs') {
archive '_build/test/logs/'
}
}
build('sequences', 'docker-host', finalHook) {
checkoutRepo()
loadBuildUtils()
def pipeDefault
def withWsCache
runStage('load pipeline') {
env.JENKINS_LIB = "build_utils/jenkins_lib"
pipeDefault = load("${env.JENKINS_LIB}/pipeDefault.groovy")
withWsCache = load("${env.JENKINS_LIB}/withWsCache.groovy")
}
pipeDefault() {
if (env.BRANCH_NAME != 'master') {
runStage('compile') {
withGithubPrivkey {
sh 'make wc_compile'
}
}
runStage('lint') {
sh 'make wc_lint'
}
runStage('xref') {
sh 'make wc_xref'
}
runStage('dialyze') {
withWsCache("_build/default/rebar3_19.1_plt") {
sh 'make wc_dialyze'
}
}
runStage('test') {
sh "make wdeps_test"
}
}
runStage('make release') {
withGithubPrivkey {
sh "make wc_release"
}
}
runStage('build image') {
sh "make build_image"
}
try {
if (env.BRANCH_NAME == 'master' || env.BRANCH_NAME.startsWith('epic')) {
runStage('push image') {
sh "make push_image"
}
}
} finally {
runStage('rm local image') {
sh 'make rm_local_image'
}
}
}
}

73
Makefile Normal file
View File

@ -0,0 +1,73 @@
REBAR := $(shell which rebar3 2>/dev/null || which ./rebar3)
SUBMODULES = build_utils
SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES))
UTILS_PATH := build_utils
TEMPLATES_PATH := .
# Name of the service
SERVICE_NAME := sequences
# Service image default tag
SERVICE_IMAGE_TAG ?= $(shell git rev-parse HEAD)
# The tag for service image to be pushed with
SERVICE_IMAGE_PUSH_TAG ?= $(SERVICE_IMAGE_TAG)
# Base image for the service
BASE_IMAGE_NAME := service_erlang
BASE_IMAGE_TAG := 13454a94990acb72f753623ec13599a9f6f4f852
# Build image tag to be used
BUILD_IMAGE_TAG := 317d28640a5dd2ec6e732d81283628d8ad3f3f52
CALL_ANYWHERE := all submodules rebar-update compile xref lint dialyze start devrel release clean distclean
CALL_W_CONTAINER := $(CALL_ANYWHERE) test
all: compile
-include $(UTILS_PATH)/make_lib/utils_container.mk
-include $(UTILS_PATH)/make_lib/utils_image.mk
.PHONY: $(CALL_W_CONTAINER)
# CALL_ANYWHERE
$(SUBTARGETS): %/.git: %
git submodule update --init $<
touch $@
submodules: $(SUBTARGETS)
rebar-update:
$(REBAR) update
compile: submodules rebar-update
$(REBAR) compile
xref: submodules
$(REBAR) xref
lint:
elvis rock
dialyze:
$(REBAR) dialyzer
start: submodules
$(REBAR) run
devrel: submodules
$(REBAR) release
release: distclean
$(REBAR) as prod release
clean:
$(REBAR) clean
distclean:
$(REBAR) clean
rm -rf _build
# CALL_W_CONTAINER
test: submodules
$(REBAR) ct

1
build_utils Submodule

@ -0,0 +1 @@
Subproject commit 8dd1d30e97e4db20c9da98a5ddb261b199402bc0

21
config/sys.config Normal file
View File

@ -0,0 +1,21 @@
[
{sequences, [
{automaton_service_url, "http://machinegun:8022/v1/automaton"},
{ip, "::"},
{port, 8022},
{net_opts, [
{timeout, 60000}
]}
]},
{lager, [
{error_logger_redirect, true},
{log_root, "/var/log/sequences"},
{handlers, [
{lager_file_backend, [
{file, "log.json"},
{level, debug},
{formatter, lager_logstash_formatter}
]}
]}
]}
].

6
config/vm.args Normal file
View File

@ -0,0 +1,6 @@
-sname sequences
-setcookie sequences_cookie
+K true
+A 10

35
docker-compose.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
cat <<EOF
version: '2.1'
services:
${SERVICE_NAME}:
image: ${BUILD_IMAGE}
volumes:
- .:$PWD
- $HOME/.cache:/home/$UNAME/.cache
working_dir: $PWD
command: /sbin/init
depends_on:
machinegun:
condition: service_healthy
machinegun:
image: dr.rbkmoney.com/rbkmoney/machinegun:1844dff663c24acdcd32f30ae3ea208f5d05a008
command: /opt/machinegun/bin/machinegun foreground
volumes:
- ./test/machinegun/config.yaml:/opt/machinegun/etc/config.yaml
healthcheck:
test: "curl http://localhost:8022/"
interval: 5s
timeout: 1s
retries: 12
networks:
default:
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "true"
com.docker.network.bridge.enable_ip_masquerade: "true"
EOF

89
elvis.config Normal file
View File

@ -0,0 +1,89 @@
[
{elvis, [
{config, [
#{
dirs => ["src"],
filter => "*.erl",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace},
{elvis_style, macro_module_names},
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
{elvis_style, nesting_level, #{level => 3}},
{elvis_style, god_modules, #{limit => 25}},
{elvis_style, no_if_expression},
{elvis_style, invalid_dynamic_call, #{ignore => [elvis]}},
{elvis_style, used_ignored_variable},
{elvis_style, no_behavior_info},
{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}},
{elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}},
{elvis_style, state_record_and_type},
{elvis_style, no_spec_with_records},
{elvis_style, dont_repeat_yourself, #{min_complexity => 10}},
{elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}}
]
},
#{
dirs => ["test"],
filter => "*.erl",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace},
{elvis_style, macro_module_names},
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
{elvis_style, nesting_level, #{level => 3}},
{elvis_style, god_modules, #{limit => 25}},
{elvis_style, no_if_expression},
{elvis_style, invalid_dynamic_call, #{ignore => [elvis]}},
{elvis_style, used_ignored_variable},
{elvis_style, no_behavior_info},
{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}},
{elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}},
{elvis_style, state_record_and_type},
{elvis_style, no_spec_with_records},
{elvis_style, dont_repeat_yourself, #{min_complexity => 10}},
{elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}}
]
},
#{
dirs => ["."],
filter => "Makefile",
ruleset => makefiles
},
#{
dirs => ["."],
filter => "elvis.config",
ruleset => elvis_config
},
#{
dirs => ["."],
filter => "rebar.config",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace}
]
},
#{
dirs => ["."],
filter => "rebar.config",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace}
]
},
#{
dirs => ["src"],
filter => "*.app.src",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace}
]
}
]}
]}
].

108
rebar.config Normal file
View File

@ -0,0 +1,108 @@
%% Common project erlang options.
{erl_opts, [
{parse_transform, lager_transform},
% mandatory
debug_info,
warnings_as_errors,
warn_export_all,
warn_missing_spec,
warn_untyped_record,
warn_export_vars,
% by default
warn_unused_record,
warn_bif_clash,
warn_obsolete_guard,
warn_unused_vars,
warn_shadow_vars,
warn_unused_import,
warn_unused_function,
warn_deprecated_function
% at will
% bin_opt_info
% no_auto_import
% warn_missing_spec_all
]}.
%% Common project dependencies.
{deps, [
{woody , {git, "git@github.com:rbkmoney/woody_erlang.git", {branch, "master"}}},
{dmsl , {git, "git@github.com:rbkmoney/damsel.git", {branch, "release/erlang/master"}}},
{mg_proto , {git, "git@github.com:rbkmoney/machinegun_proto.git", {branch, master}}},
{lager , "3.2.1"},
{lager_logstash_formatter, {git, "git@github.com:rbkmoney/lager_logstash_formatter.git", {branch, "master"}}}
]}.
%% XRef checks
{xref_checks, [
undefined_function_calls,
undefined_functions,
deprecated_functions_calls,
deprecated_functions
]}.
% at will
% {xref_warnings, true}.
%% Tests
{cover_enabled, true}.
%% Relx configuration
{relx, [
{release, {sequences, "0.1.0"}, [
sequences
]},
{sys_config, "./config/sys.config"},
{vm_args, "./config/vm.args"},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}
]}.
%% Dialyzer static analyzing
{dialyzer, [
{warnings, [
% mandatory
unmatched_returns,
error_handling,
race_conditions
% unknown %% need fix
]},
{plt_apps, all_deps}
]}.
{profiles, [
{test, [
{deps, [
]}
]},
{prod, [
{deps, [
% for introspection on production
{recon, "2.3.2"}
]},
{relx, [
{release, {sequences, "0.1.0"}, [
{recon , load }, % tools for introspection
{runtime_tools, load }, % debugger
{tools , load }, % profiler
sasl,
sequences
]},
{dev_mode, false},
{include_erts, true}
]}
]}
]}.
{plugins, [
rebar3_run
]}.

58
rebar.lock Normal file
View File

@ -0,0 +1,58 @@
{"1.1.0",
[{<<"certifi">>,{pkg,<<"certifi">>,<<"0.7.0">>},2},
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"1.0.4">>},1},
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},2},
{<<"dmsl">>,
{git,"git@github.com:rbkmoney/damsel.git",
{ref,"66d5da21a29df00dc2b7b2a5700e41d70228c9f4"}},
0},
{<<"genlib">>,
{git,"https://github.com/rbkmoney/genlib.git",
{ref,"82ff16f4314fc406dd90752467a08fe401b009ef"}},
1},
{<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.8">>},1},
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.6.2">>},1},
{<<"idna">>,{pkg,<<"idna">>,<<"1.2.0">>},2},
{<<"jsx">>,{pkg,<<"jsx">>,<<"2.8.2">>},1},
{<<"lager">>,{pkg,<<"lager">>,<<"3.2.1">>},0},
{<<"lager_logstash_formatter">>,
{git,"git@github.com:rbkmoney/lager_logstash_formatter.git",
{ref,"d7370337d4d55b37915a2c3202f5c39047674bb3"}},
0},
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2},
{<<"mg_proto">>,
{git,"git@github.com:rbkmoney/machinegun_proto.git",
{ref,"35a23af91ee4245b6faffda4ed66a926df087bdf"}},
0},
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.0.2">>},2},
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.4.0">>},2},
{<<"rfc3339">>,{pkg,<<"rfc3339">>,<<"0.9.0">>},1},
{<<"snowflake">>,
{git,"https://github.com/rbkmoney/snowflake.git",
{ref,"0a598108f6582affe3b4ae550fc5b9f2062e318a"}},
1},
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.1">>},2},
{<<"thrift">>,
{git,"https://github.com/rbkmoney/thrift_erlang.git",
{ref,"240bbc842f6e9b90d01bd07838778cf48752b510"}},
1},
{<<"woody">>,
{git,"git@github.com:rbkmoney/woody_erlang.git",
{ref,"2d00bda10454534e230d452b7338debafaf0a869"}},
0}]}.
[
{pkg_hash,[
{<<"certifi">>, <<"861A57F3808F7EB0C2D1802AFEAAE0FA5DE813B0DF0979153CBAFCD853ABABAF">>},
{<<"cowboy">>, <<"A324A8DF9F2316C833A470D918AAF73AE894278B8AA6226CE7A9BF699388F878">>},
{<<"cowlib">>, <<"9D769A1D062C9C3AC753096F868CA121E2730B9A377DE23DEC0F7E08B1DF84EE">>},
{<<"goldrush">>, <<"2024BA375CEEA47E27EA70E14D2C483B2D8610101B4E852EF7F89163CDB6E649">>},
{<<"hackney">>, <<"96A0A5E7E65B7ACAD8031D231965718CC70A9B4131A8B033B7543BBD673B8210">>},
{<<"idna">>, <<"AC62EE99DA068F43C50DC69ACF700E03A62A348360126260E87F2B54ECED86B2">>},
{<<"jsx">>, <<"7ACC7D785B5ABE8A6E9ADBDE926A24E481F29956DD8B4DF49E3E4E7BCC92A018">>},
{<<"lager">>, <<"EEF4E18B39E4195D37606D9088EA05BF1B745986CF8EC84F01D332456FE88D17">>},
{<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>},
{<<"mimerl">>, <<"993F9B0E084083405ED8252B99460C4F0563E41729AB42D9074FD5E52439BE88">>},
{<<"ranch">>, <<"10272F95DA79340FA7E8774BA7930B901713D272905D0012B06CA6D994F8826B">>},
{<<"rfc3339">>, <<"2075653DC9407541C84B1E15F8BDA2ABE95FB17C9694025E079583F2D19C1060">>},
{<<"ssl_verify_fun">>, <<"28A4D65B7F59893BC2C7DE786DEC1E1555BD742D336043FE644AE956C3497FBE">>}]}
].

20
src/seq_handler.erl Normal file
View File

@ -0,0 +1,20 @@
-module(seq_handler).
%% Woody handler
-behaviour(woody_server_thrift_handler).
-export([handle_function/4]).
%%
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) ->
{ok, woody:result()}.
handle_function('GetCurrent', [SequenceId], Context, _Opts) ->
Value = seq_machine:get_current(SequenceId, Context),
{ok, Value};
handle_function('GetNext', [SequenceId], Context, _Opts) ->
Value = seq_machine:get_next(SequenceId, Context),
{ok, Value}.

121
src/seq_machine.erl Normal file
View File

@ -0,0 +1,121 @@
-module(seq_machine).
-include_lib("mg_proto/include/mg_proto_state_processing_thrift.hrl").
-export([get_current/2]).
-export([get_next/2]).
%% State processor
-behaviour(woody_server_thrift_handler).
-export([handle_function/4]).
%%
-define(NS, <<"sequences">>).
-define(NIL, {nl, #msgpack_Nil{}}).
-define(INIT, 0).
-type id() :: mg_proto_base_thrift:'ID'().
-type context() :: woody_context:ctx().
%%
-spec get_next(id(), context()) ->
integer().
get_next(Id, Context) ->
{ok, AuxState} = call_automaton_with_lazy_start('Call', Id, [?NIL], Context),
get_sequence_value(AuxState).
-spec get_current(id(), context()) ->
integer().
get_current(Id, Context) ->
{ok, #'Machine'{aux_state = AuxState}} = call_automaton_with_lazy_start('GetMachine', Id, Context),
get_sequence_value(AuxState).
get_sequence_value(AuxState) ->
unmarshal(AuxState).
%%
ensure_started(Id, Context) ->
case call_automaton('Start', [?NS, Id, ?NIL], Context) of
{ok, _} ->
ok;
{exception, #'MachineAlreadyExists'{}} ->
ok
end.
call_automaton(Function, Id, Args, Context) ->
Descriptor = construct_descriptor({id, Id}),
call_automaton(Function, [Descriptor|Args], Context).
call_automaton(Function, Args, Context) ->
Request = {{mg_proto_state_processing_thrift, 'Automaton'}, Function, Args},
{ok, URL} = application:get_env(sequences, automaton_service_url),
Opts = #{url => URL, event_handler => {woody_event_handler_default, undefined}},
woody_client:call(Request, Opts, Context).
call_automaton_with_lazy_start(Function, Id, Context) ->
call_automaton_with_lazy_start(Function, Id, [], Context).
call_automaton_with_lazy_start(Function, Id, Args, Context) ->
case call_automaton(Function, Id, Args, Context) of
{ok, _} = Ok ->
Ok;
{exception, #'MachineNotFound'{}} ->
ok = ensure_started(Id, Context),
call_automaton(Function, Id, Args, Context)
end.
construct_descriptor(Ref) ->
#'MachineDescriptor'{
ns = ?NS,
ref = Ref,
range = #'HistoryRange'{}
}.
%%
-type func() :: 'ProcessSignal' | 'ProcessCall'.
-spec handle_function(func(), woody:args(), context(), woody:options()) ->
{ok, term()}.
handle_function('ProcessSignal', [#'SignalArgs'{signal = {init, _}}], _Context, _Opts) ->
{ok, #'SignalResult'{
change = construct_change(init()),
action = #'ComplexAction'{}
}};
handle_function('ProcessCall', [#'CallArgs'{machine = #'Machine'{aux_state = CurrentAuxState}}], _Context, _Opts) ->
NextAuxState = process_call(CurrentAuxState),
{ok, #'CallResult'{
change = construct_change(NextAuxState),
action = #'ComplexAction'{},
response = NextAuxState
}}.
construct_change(State) ->
#'MachineStateChange'{
events = [],
aux_state = State
}.
init() ->
marshal(?INIT).
process_call(CurrentValue) ->
NextValue = unmarshal(CurrentValue) + 1,
marshal(NextValue).
%% Marshalling
marshal(Int) when is_integer(Int) ->
{arr, [{i, 1}, {i, Int}]}.
unmarshal({arr, [{i, 1}, {i, Int}]}) ->
Int.

16
src/sequences.app.src Normal file
View File

@ -0,0 +1,16 @@
{application, sequences, [
{description, "Sequence service"},
{vsn, "0.1.0"},
{registered, []},
{applications, [
kernel,
stdlib,
lager,
lager_logstash_formatter,
woody,
dmsl,
mg_proto
]},
{mod, {sequences, []}},
{env, []}
]}.

76
src/sequences.erl Normal file
View File

@ -0,0 +1,76 @@
-module(sequences).
-behaviour(supervisor).
-behaviour(application).
%% API
-export([start/0]).
-export([stop/0]).
%% Supervisor callbacks
-export([init/1]).
%% Application callbacks
-export([start/2]).
-export([stop/1]).
%% API
-spec start() ->
{ok, _}.
start() ->
application:ensure_all_started(?MODULE).
-spec stop() ->
ok.
stop() ->
application:stop(?MODULE).
%% Supervisor callbacks
-spec init([]) ->
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
{ok, Ip} = inet:parse_address(genlib_app:env(?MODULE, ip, "::")),
ChildSpec = woody_server:child_spec(
?MODULE,
#{
ip => Ip,
port => genlib_app:env(?MODULE, port, 8022),
net_opts => genlib_app:env(?MODULE, net_opts, []),
event_handler => woody_event_handler_default,
handlers => [
get_handler_spec(sequences),
get_handler_spec(state_processor)
]
}
),
{ok, {
#{strategy => one_for_all, intensity => 6, period => 30},
[ChildSpec]
}}.
get_handler_spec(sequences) ->
{"/v1/sequences", {
{dmsl_sequences_thrift, 'Sequences'},
seq_handler
}};
get_handler_spec(state_processor) ->
{"/v1/stateproc", {
{mg_proto_state_processing_thrift, 'Processor'},
seq_machine
}}.
%% Application callbacks
-spec start(normal, any()) ->
{ok, pid()} | {error, any()}.
start(_StartType, _StartArgs) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec stop(any()) ->
ok.
stop(_State) ->
ok.

View File

@ -0,0 +1,6 @@
namespaces:
sequences:
processor:
url: http://sequences:8022/v1/stateproc
storage:
type: memory

35
test/seq_client.erl Normal file
View File

@ -0,0 +1,35 @@
-module(seq_client).
-export([new/0]).
-export([get_current/2]).
-export([get_next/2]).
-type client() :: woody_context:ctx().
-spec get_current(binary(), client()) -> integer().
get_current(SeqId, Client) ->
call('GetCurrent', [SeqId], Client).
-spec get_next(binary(), client()) -> integer().
get_next(SeqId, Client) ->
call('GetNext', [SeqId], Client).
-spec new() -> client().
new() ->
woody_context:new().
call(Function, Args, Client) ->
Call = {{dmsl_sequences_thrift, 'Sequences'}, Function, Args},
Opts = #{
url => <<"http://sequences:8022/v1/sequences">>,
event_handler => {woody_event_handler_default, undefined}
},
case woody_client:call(Call, Opts, Client) of
{ok, Response} ->
Response;
{exception, Exception} ->
throw(Exception)
end.

View File

@ -0,0 +1,79 @@
-module(sequences_tests_SUITE).
-include_lib("common_test/include/ct.hrl").
-export([all/0]).
-export([init_per_suite/1]).
-export([end_per_suite/1]).
-export([init_per_testcase/2]).
-export([end_per_testcase/2]).
-export([get_current/1]).
-export([get_next/1]).
%% tests descriptions
-type config() :: [{atom(), term()}].
-define(config(Key, C), (element(2, lists:keyfind(Key, 1, C)))).
-spec all() -> [atom()].
all() ->
[
get_current,
get_next
].
%% starting/stopping
-spec init_per_suite(config()) -> config().
init_per_suite(C) ->
Apps = genlib_app:start_application_with(lager, [
{async_threshold, 1},
{async_threshold_window, 0},
{error_logger_hwm, 600},
{suppress_application_start_stop, true},
{handlers, [
{lager_common_test_backend, warning}
]}
]) ++ genlib_app:start_application_with(sequences, [
{automaton_service_url, "http://machinegun:8022/v1/automaton"}
]),
[{suite_apps, Apps} | C].
-spec end_per_suite(config()) -> term().
end_per_suite(C) ->
genlib_app:stop_unload_applications(?config(suite_apps, C)).
%% tests
-spec init_per_testcase(atom(), config()) -> config().
init_per_testcase(_Name, C) ->
Client = seq_client:new(),
[{client, Client} | C].
-spec end_per_testcase(atom(), config()) -> config().
end_per_testcase(_Name, _C) ->
ok.
-spec get_current(term()) -> term().
get_current(C) ->
Client = proplists:get_value(client, C),
SeqId = get_sequence_id(),
0 = seq_client:get_current(SeqId, Client),
0 = seq_client:get_current(SeqId, Client),
_ = seq_client:get_next(SeqId, Client),
1 = seq_client:get_current(SeqId, Client).
-spec get_next(term()) -> term().
get_next(C) ->
Client = proplists:get_value(client, C),
SeqId = get_sequence_id(),
1 = seq_client:get_next(SeqId, Client),
2 = seq_client:get_next(SeqId, Client).
%%
get_sequence_id() ->
integer_to_binary(erlang:system_time(micro_seconds)).