Eunit test won't wait for receive - erlang

Eunit won't wait for the receive, is there something special to eunit.
-module (test_account).
-include_lib ("eunit/include/eunit.hrl").
-compile (export_all).
login_test() ->
{ok, Socket} = gen_tcp:connect("localhost", 5678,
[binary, {packet, 4}]),
RoleName = <<"abc">>,
LenRoleName = byte_size(RoleName),
Password = <<"def">>,
LenPassword = byte_size(Password),
LoginBin = <<11001:16, LenRoleName:16, RoleName/binary,
LenPassword:16, Password/binary>>,
gen_tcp:send(Socket, LoginBin),
print(Socket).
print(Socket) ->
receive
{tcp, Socket, Data} ->
io:format("Data=~p~n", [Data])
end.
If I call test_account:login_test(). directly, it can receive the response.
Thank you.

My guess is there's something wrong on the listening side, like missing {packet, 4} parameter or something. I started a listening socket on the required port manually, and the test did work.
EUnit isn't really supposed to run integration tests out-of-the-box (though there are some libraries that make it somewhat convenient for integration tests as well). What you are realy supposed to do here is something like that:
main_test_() ->
{setup,
fun setup/0,
fun teardown/1,
[{"login", fun login_test/0}]
}.
setup() ->
process_flag(trap_exit, true),
{ok, Pid} = my_tcp_server:start_link(),
Pid.
teardown(Pid) ->
exit(Pid, shutdown).
So, basically, you shouldn't rely on a separately running server when using just EUnit. Instead, you should either start it explicitly from your code or, if it's external, mock it (or just some of its parts, if the entire server code is complicated).
P.S. Don't forget the underscore at the end of 'main_test_', it's a contract for {setup, ...} and {foreach, ...} tests.

Related

How to test gen_server internal state with eunit

Is it possible to inspect the internal state of a gen_server after a callback function has been called? I would rather not change the API of my server here.
You could use sys:get_state/1 which works nicely with all gen's.
Maybe, you would found useful another approach to unit-testing gen_servers.
Instead of running gen_server process and testing its behaviour, you can directly test gen_server callbacks and then inspect its state transitions.
For example:
-module(foo_server).
%% Some code skipped
handle_call({do_stuf, Arg}, _From, State) ->
NewState = modify_state(
{reply, {stuf_done, Arg}, NewState}.
%% Some code skipped
-ifdef(TEST)
do_stuf_test_() ->
{setup,
fun() ->
{ok, InitState} = foo_server:init(SomeInitParams),
InitState
end,
fun(State) ->
ok = foo_server:terminate(shutdown, State)
end,
fun(State) ->
Result = foo_server:handle_call({do_stuf, hello}, undefined, State),
[
?_assertMatch({reply, {stuf_done, hello}, _}, Result)
]
end
}
}.
-endif.
See discussion of this approach here
Also, if you dealing with realy complex states and state transitions, maybe you would be found proper helpful.

how this kind of erlang gen_server should be tested?

I have a gen_server which exports a function like this:
my_function(Param) ->
gen_server:cast(?SERVER, {forward, Param}).
and has an handle_cast like this:
handle_cast({forward, Param}, #state{peer=Socket} = State) ->
gen_tcp:send(Socket, Param),
{noreply, State}.
In most of cases the peer that is connected using gen_tcp will reply with one among different messages, and I handle the reply in the handle_info
handle_info({tcp, Socket, Data}) ->
io:format("Received : ~p~n", [Data]),
{noreply, State}.
Is there any recommended way to test this kind of scenario in erlang?
You can use setup/teardown functions in order to do something like that:
my_test_() -> {
setup,
fun() -> my_server:start_link() end,
fun(_) -> my_server:stop() end,
fun() ->
%% here you do all the message sending and such
end
}.
You also need to export a stop/0 function which will be responsible for call/cast the gen_server in order to stop it.
You can find out more infos on fixtures here
HTH,
Alin

Best way to declare N number of workers in a supervisor, without knowing N beforehand

I'm developing an application with 1 supervisor and several workers. Each one of those workers will just open a tcp socket, execute a listen, and then accept connections, spawning a process for each client as they arrive (I dont mind supervising these).
I'd like to make the listening addresses configurable in the application configuration, so I can have as many addresses to listen as needed (at least 1 address is needed, but any number can be specified). Each address is an ip (or hostname) and a port address. So far, nothing new.
My question is about how to declare, start, and supervise an unknown number of workers. My solution was to dynamically generate (at runtime) the result of the init/1 function in the supervisor code, like this:
-define(
CHILD(Name, Args),
{Name, {
?MODULE, start_listen, Args
}, permanent, 5000, worker, [?MODULE]
}
).
init([]) ->
{ok, Addresses} = application:get_env(listen),
Children = lists:map(
fun(Address) ->
{X1,X2,X3} = os:timestamp(),
ChildName = string:join([
"Listener-",
integer_to_list(X1),
integer_to_list(X2),
integer_to_list(X3)
], ""),
?CHILD(ChildName, [Address])
end,
Addresses
),
{ok, { {one_for_one, 5, 10}, Children }}.
This allows me to dynamically generate a name for each worker and also generate the worker definitions. So:
Is this solution acceptable? It seems to me that it's not that elegant. Is there any standard solution (or best practice, etc) for this kind of use cases?
I know about the "simple_one_for_one" strategy, that allows to dynamically add workers to a supervisor. But can it also be used to dynamically generate the worker's names? Is it better (in any way) to use "simple_one_for_one" instead of my own solution that uses "one_for_one"? (again, for this particular situation).
Thanks in advance, and sorry for the long post! :)
With simple_one_for_one :
in the init function of your workers, you can register them in a table to associate a name with their PID, so you will be able to get the pid from the name.
You can use the global module (or gproc !) to associate a name with a pid. When a process dies, the name is automatically deleted by global or gproc, so when the supervisor restarts the child, the name is available.
You would pass the names in the arguments list (2nd param) of supervisor:start_child/2
Using simple_one_for_one will allow you to dynamically add more listeners after your supervisor initialization.
If you need this feature, simple_one_for_one is the good solution.
If you stick with your solution of a dynamic content inside the init function of your sup, you could clean the code like this, it may, or may not, seem more elegant :
-define(
CHILD(Name, Args),
{Name, {
?MODULE, start_listen, Args
}, permanent, 5000, worker, [?MODULE]
}
).
generate_names(Adresses) -> generate_names(Adresses, 1, []).
generate_names([], _, Acc) -> Acc;
generate_names([Addr|Addresses], ID, Acc) -> generate_names(Addresses, ID+1, [{id_name(ID), Addr}|Acc]).
id_name(ID) -> "listener-" ++ integer_to_list(ID).
init([]]) ->
{ok, Addresses} = application:get_env(listen),
Children = [?CHILD(Name, Address) || {Name, Address} <- generate_names(Addresses)],
{ok, { {one_for_one, 5, 10}, Children }}.
Or use a lists:foldl instead of all theese little functions to keep the code short.
But anyway i would pass the Adresses in the Args list of init and not call get_env inside init to keep it pure.
From your code, I know that you want to get the number of children from environment.
In the source code of worker_pool_sup.erl of rabbitmq open source project, I have read the almost similar requirement code, and the code is much elegant, I think it is helpful to you.
3 files are related, worker_pool_sup.erl, worker_pool_worker.erl, work_pool.erl.
The following code is from worker_pool_sup.erl.
init([WCount]) ->
{ok, {{one_for_one, 10, 10},
[{worker_pool, {worker_pool, start_link, []}, transient,
16#ffffffff, worker, [worker_pool]} |
[{N, {worker_pool_worker, start_link, [N]}, transient, 16#ffffffff,
worker, [worker_pool_worker]} || N <- lists:seq(1, WCount)]]}}.
For how to use it, the following code is from worker_pool.erl
get_worker_pid(WId) ->
[{WId, Pid, _Type, _Modules} | _] =
lists:dropwhile(fun ({Id, _Pid, _Type, _Modules})
when Id =:= WId -> false;
(_) -> true
end,
supervisor:which_children(worker_pool_sup)),
Pid.

How to use gen_tcp:recv correctly

I writing simple http client, and faced following problem, i copypasted that do_recv from offficial doc, but it works strange way:
do_recv(Sock, Bs) ->
case gen_tcp:recv(Sock, 0, ?TIMEOUT) of
{ok, B} ->
gen_tcp:shutdown(Sock, write), % <-- this appears to fix the problem!
do_recv(Sock, [Bs, B]);
{error, closed} ->
{ok, list_to_binary(Bs)}
end.
The chat sequence is following:
{ok, S} = gen_tcp:connect(Ip, Port, [inet, binary,
{packet, 0},
{active, false},
{nodelay, true},
{reuseaddr, true}], 2000),
Req = io_lib:format("GET ~s HTTP/1.1\r\nHost: ~s\r\n\r\n", [Url, UrlHost]),
ok = gen_tcp:send(S, list_to_binary(Req)) of
do_recv(S, []);
And final call to do_recv sometimes works as expected and returns server
respose, but sometimes it hangs and timeouts, i guess because server not closing socket
on its own.
So the second case with timeout is something i want to avoid, any ideas
how to cope with that behaviour ?
UPD:
I am added gen_tcp:shutdown call to do_recv function (see comment in code sample),
and this seem to resolve the issue. Question is pretty noobish i know, and solution
is pretty like guess, maybe someone still can explain what happens here and
how they typically solve this kind of problem.
Your code has some problems.
If you receive 0, you can get half of the GET string, or you can get more than the whole GET string, depending on how the kernel handles the stream. TCP is stream oriented, so you need to eat data from the socket until you have enough. Also, you can easily end up with a {error, timeout} triggering, so you have to handle that problem as well. Otherwise it won't work as expected. Basically you need a loop which gathers up data until you have enough data to parse the GET. Timeouts will happen in that loop before you have all data.
Something along the lines of:
do_recv(Sock, Gathered) ->
case gen_tcp:recv(Sock, 0, ?TIMEOUT) of
{ok, Bin} ->
Remaining = try_decode(Sock, <<Gathered/binary, Bin/binary>>),
do_recv(Sock, Remaining);
{error, timeout} ->
do_recv(Sock, Remaining);
{error, Reason} ->
exit(Reason)
end.
try_decode(Sock, Gathered) ->
case decode(Gathered) of
{ok, Data, Rest} ->
processor ! Data,
try_decode(Sock, Rest);
need_more_data ->
do_recv(Sock, Gathered)
end.
Here assuming a couple of things
decode/1 is a function that tries to decode data and it might fail to do so and request more data.
processor is a process to which we can send the message once we have something decoded. This could also be a function call that does something with the Data we just decoded.

How to create global variables in Erlang

I am writing an ejabberd module to filter packets. I need to get the hostname to pull some configs using gen_mod:get_module_opt().
I have 4 important functions :
start(Host, _Opt) : This is an ejabberd function to load my module. I get the Host atom here
filter_packet({From, To, XML}): This is my packet filter hook. I cannot pass custom params to this function, as it is a hook in ejabberd.
get_translation(XmlData): filter_packet() calls get_translation() in a loop
fetch_translation(XmlData): called recursively from get_translation(). This is where I am calling gen_mod:get_module_opt(), and hence need the Host.
My question is, how can I take Host from start() and put it in a global variable, so that fetch_translation can access it?
The "easiest way" is to create a named ets table, and put it in there.
start(Host, _Opt) ->
ets:new(my_table, [named_table, protected, set, {keypos, 1}]),
ets:insert(my_table, {host, Host}),
...
fetch_translation(XmlData) ->
[{_, Host}] = ets:lookup(my_table, host),
...
Note that this is a "general" solution. Ejabberd might provide facilities for what you want, but I cannot help you with that.
It may sound as an overkill but you may consider implementing a very basic gen_server. It contains a state that is available to its callbacks and the data can be kept there. For your case you can write a module similar to this one:
-module(your_module_name).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([start/2, filter_loop/1]).
start(Host, Opt) ->
%% start the named gen server
gen_server:start({local, ?MODULE}, ?MODULE, Host, []).
filter_packet({From, To, XML}) ->
%% do your thing
gen_server:call(?MODULE, {fetch_translation, XmlData}).
%% this will be called by gen_server:start - just pass the Host
init(Host) ->
{ok, Host}.
handle_call({fetch_translation, XmlData}, _From, Host) ->
%% do your thing
{reply, ok, Host}.
%% you can ignore the rest - they are needed to be present
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
You define your global variable on your module top...like below
-define (Your Variable, "your host name here").
eg.
-define (RelayHost, "smtp.gmail.com").
and you can use this Global variable in all your method in your module.
io:fwrite("Global Value ~p", [?RelayHost]).
-AjAy
You could start a new message filtering process and register it using erlang:register/2, then route all filter_packet/1 requests through it (a potential bottleneck).
-define(?SERVER, msg_filter).
start(Host, Opt) ->
{ok, Pid} = spawn(?MODULE, filter_loop, [Host, Opt]),
register(?SERVER, Pid).
filter_loop(Host, Opt) ->
receive
{Pid, filter_packet, {_From, _To, XML}} ->
Trans = get_translation(XML, Host),
Pid ! {?SERVER, translation, Trans},
filter_loop(Host, Opt)
end.
filter_packet(Pack) ->
?SERVER ! {self(), filter_packet, Pack}
receive
{?SERVER, translation, Trans} ->
% wrap translation
UpdatedPacket
end.
Say you are filtering incoming packets, then To#jid.lserver might be your host.
guessing for your description than you are in a single-domain ejabberd deployment (no virtual hosts),
yo can get the local XMPP domain using the ?MYNAME macro (see ejabberd.hrl for the definition).
Try use persistent_term:
1> persistent_term:put(hello, <<"world">>).
ok
2> persistent_term:get(hello).
<<"world">>
3> persistent_term:erase(hello).
true
4> persistent_term:get(hello).
** exception error: bad argument
in function persistent_term:get/1
called as persistent_term:get(hello)
You cannot create global variable but you can define a record outside your functions and create an instance of that record with properties then pass it down to the methods you call. Therefore, you can only share one record via method parameter.

Resources