I have some issue with distributed tests under rebar.
Rebar starts node with name nonode#nohost. After it I call help function make_distrib which provide normal node name and starts distribution work.
After starting the slave node I couldn't sent to it any lambda. I had error:
=ERROR REPORT==== 27-Jul-2013::22:48:02 ===
Error in process on node 'test1#just' with exit value: {{badfun,#Fun<msg_proxy_tests.2.117197241>},[{error_handler,undefined_lambda,3,[{file,"error_handler.erl"},{line,64}]}]}
But! If I run it test with simple way - all works good:
$erl
1> c(msg_proxy_tests).
{ok,msg_proxy_tests}
2> eunit:test({module, msg_proxy_tests},[verbose]).
======================== EUnit ========================
module 'msg_proxy_tests'msg_proxy_tests: distrib_mode_test_ (distribute mode test for nodes)...
msg_proxy_tests.erl:14:<0.48.0>: nodes ( ) = [test1#just]
msg_proxy_tests.erl:15:<9999.39.0>: node ( ) = test1#just
msg_proxy_tests.erl:17:<0.48.0>: nodes ( ) = [test1#just]
[0.238 s] ok
How can I fix this?
Module source:
-module(msg_proxy_tests).
-include_lib("eunit/include/eunit.hrl").
distrib_mode_test_()->
{"distribute mode test for nodes", timeout, 60,
fun() ->
{ok, Host} = inet:gethostname(),
make_distrib("tests#"++Host, shortnames),
slave:start(Host, test1),
?debugVal(nodes()),
spawn(list_to_atom("test1#"++Host), fun()-> ?debugVal(node()) end),
timer:sleep(100),
?debugVal(nodes()),
stop_distrib(),
ok
end}.
-spec make_distrib( NodeName::string()|atom(), NodeType::shortnames | longnames) ->
{ok, ActualNodeName::atom} | {error, Reason::term()}.
make_distrib(NodeName, NodeType) when is_list(NodeName) ->
make_distrib(erlang:list_to_atom(NodeName), NodeType);
make_distrib(NodeName, NodeType) ->
case node() of
'nonode#nohost' ->
[] = os:cmd("epmd -daemon"),
case net_kernel:start([NodeName, NodeType]) of
{ok, _Pid} -> node()
end;
CurrNode -> CurrNode
end.
stop_distrib()->
net_kernel:stop().
That is because of module hash on both nodes is different. That happens because eunit compiles code before it run and module contains -ifdef(TEST) macroses, which will definitely change it's hash.
Thats why anonymous function #Fun<msg_proxy_tests.2.117197241 could not be called and error occurred. (look at function name and you will notice funny numbers, this is module hash, it will be different on both nodes).
If you want to avoid this, you should call funs by it fully qualified name: module:fun_name/arity.
Related
I got a set of tests that the program should pass and all the local tests works just fine with my server it's when I try to run the remote tests that the server crashes.
The crash message is the following:
=ERROR REPORT==== 23-Jul-2015::23:59:17 === Error in process <0.39.0> on
node 'nodeS#127.0.0.1' with exit value:
{undef,[{genserver,start,[server, {server_st,[],[]},#Fun<server.loop.2>],[]}]}
My start-up function looks as following:
loop(St, {From, Nick, connection_wanted}) ->
case lists:keymember(Nick, 2, St#server_st.users) of
false -> {ok, St#server_st{users = St#server_st.users ++ [{From, Nick}]}};
true -> {{user_already_connected, St}, St}
end;
With the record "server_st" is defined as:
-record(server_st, {users = [], channels = []}).
Finally the genserver start&loop function is:
start(Name, State, F) ->
Pid = spawn(fun() -> loop(State, F) end),
register(Name, Pid),
Pid.
loop(State, F) ->
receive
{request, From, Ref, Data} ->
case catch(F(State, Data)) of
{'EXIT', Reason} ->
From!{exit, Ref, Reason},
loop(State, F);
{R, NewState} ->
From!{result, Ref, R},
loop(NewState, F)
end;
{update, From, Ref, NewF} ->
From ! {ok, Ref},
loop(State, NewF);
stop ->
true
end.
Then genserver functions I'm not allowed to change. If needed I can post the whole testsuite too.
Edit
Digging a bit further into the test cases and I'm unsure if it really is the server that's causing the issue, my remote connect function looks as following:
loop(St, {connect, {_Server, _Machine}}) ->
ServerPID = {list_to_atom(_Server), list_to_atom(_Machine)},
case genserver:request(ServerPID, {self(), St#cl_st.nick, connection_wanted}) of
ok -> {ok, St#cl_st{connected_to = ServerPID}};
_ -> {{error, user_already_connected, "A user with the nick " ++ St#cl_st.nick ++ "is already connected to" ++ _Server}, St}
end;
Edit 2
Found the specific row inside the testsuite that's causing the error:
-define(HOST, '127.0.0.1').
new_client(Nick, GUIName) ->
ClientName = test_client:find_unique_name("client_"),
ClientAtom = list_to_atom(ClientName),
% Row below is causing the error
Result = slave:start(?HOST, ClientAtom),
assert_ok("start client node "++ClientName, element(1,Result)),
ClientNode = element(2,Result),
InitState = client:initial_state(Nick, GUIName),
Result2 = spawn(ClientNode, genserver, start, [ClientAtom, InitState, fun client:loop/2]),
assert("client startup "++ClientName, is_pid(Result2)),
{Nick, ClientAtom, ClientNode}.
Your function genserver:start/3 is most probably not exported or module genserver is not available at the node where you run code which calls it.
Solved it, was in a completely unrelated part where the client is communicating with other users. Still used the whereis command to locate other users from an older version of the program.
I'm trying to write and compile a custom behaviour in Erlang.
I cannot find any clear documentation on how to compile this behaviour.
-module(bla).
-export([start_link/0,behaviour_info/1]).
behaviour_info(callbacks)->
[{init,1}];
behaviour_info(_Other)->
undefined.
%% -callback init(Args :: term()) ->
%% {ok, State :: term()} | {ok, State :: term(), timeout()} |
%% {stop, Reason :: term()} | ignore.
start_link()->
init([]).
my command for comiling is :
erlc.exe .\src\bla.erl
resulting:
bla.erl:24: function init/1 undefined
Anyone an idea on writing and compiling behaviours in erlang, please? any links?
Defining behaviour callbacks results in an obligation towards your implementation of callback module. In erlang, modules are just function containers, not classes nor interfaces. Behaviours are based on runtime module name resolution Mod:fun(). OTP gen_server (check it) keeps its callback module name after you pass it in: gen_server:start_link(CallbackModuleName, Args, Opts) and here is code for applying callback init/1:
init_it(Starter, Parent, Name0, Mod, Args, Options) ->
Name = name(Name0),
Debug = debug_options(Name, Options),
case catch Mod:init(Args) of
{ok, State} ->
proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, State, Mod, infinity, Debug);
{ok, State, Timeout} ->
proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, State, Mod, Timeout, Debug);
...
It's applying init/1 of passed callback module kept in Mod parameter, gets its last value, do what you want and keep going (or not, depends on that last value).
Assume we have module bla_impl which looks like this:
-module(bla_impl).
-behaviour(bla).
-export([init/1, start_link/0]).
start_link() -> bla:start_link(?MODULE). %% macro ?MODULE is resolved to bla_impl
init(Args) -> ... .
And now you need to say in bla which module you use by:
-module(bla).
-export([start_link/1]).
start_link(Mod) -> Mod:init([]).
or maybe better solution is to read it from configuration:
-module(bla).
-export([start_link/0]).
start_link() ->
Mod = application:get_env(bla_app, callback_module),
Mod:init([]),
...
There is many ways for doing so.
As you see there is no magic here. This would work even without -behaviour(bla) nor specified callback with -callback. This is just an information for compiler, tools and documentation.
From erlang documentation: Behaviours
And btw. start_link function should spawn another process and link to it.
start_link(Mod) ->
spawn_link(Mod, init, [[]]).
I'm having some trouble with an Erlang module. Here is the one that I wrote:
-module(basic_gen_server).
-export([start/1, call/2, cast/2]).
start(Module) ->
register(server, spawn(basic_gen_server,gen_server_loop,[Module, Module:init()])), server.
call(Pid,Request) ->
Pid ! {call, self(), Request},
receive
Reply -> Reply
end.
cast(Pid,Request) ->
Pid ! {cast, self(), Request},
receive
_ -> ok
end.
gen_server_loop(Module, CurrentState) ->
io:fwrite("gen_server_loop~n", []),
receive
{call, CallPid, Request} ->
{reply, Reply, NewState} = Module:handle_call(Request,self(),CurrentState),
CallPid ! Reply,
gen_server_loop(Module, NewState);
{cast, CastPid, Request} ->
{noReply, NewState} = Module:handle_cast(Request, CurrentState),
CastPid ! noReply,
gen_server_loop(Module, NewState)
end.
And here is the callback module that was defined:
% Written by Caleb Helbling
% Last updated Oct 10, 2014
-module(name_server).
-export([init/0, add/3, whereis/2, handle_cast/2,
handle_call/3, handle_swap_code/1]).
%% client routines
add(ServerPid, Person, Place) ->
basic_gen_server:cast(ServerPid, {add, Person, Place}).
whereis(ServerPid, Person) ->
basic_gen_server:call(ServerPid, {whereis, Person}).
%% callback routines
init() ->
maps:new().
handle_cast({add, Person, Place}, State) ->
NewState = maps:put(Person, Place, State),
{noreply, NewState}.
handle_call({whereis, Person}, _From, State) ->
Reply = case maps:find(Person, State) of
{ok, Place} -> Place;
error -> error
end,
NewState = State,
{reply, Reply, NewState}.
handle_swap_code(State) ->
{ok, State}.
Upon trying to initialize the server with the following command:
MyServer = basic_gen_server:start(name_server).
I get the following debug output:
=ERROR REPORT==== 29-Oct-2014::12:41:42 ===
Error in process <0.70.0> with exit value: {undef,[{basic_gen_server,gen_server_loop,[name_server,#{}],[]}]}
Conceptually, I understand the notion of making serial code into a basic server system, but I believe that I have a syntax error that I haven't been able to find using either syntax highlighting or Google. Thanks in advance for the help!
Function gen_server_loop is not exported. So you can not call basic_gen_server:gen_server_loop(Module, Module:init()), which is what is happening inside spawn(basic_gen_server,gen_server_loop,[Module, Module:init()]).
If you read your error message it tells you that function you are trying to call in undefined (trougn undef atom). Function being {basic_gen_server,gen_server_loop,[name_server,#{}],[]}, or where you have {Module, Function, ListOfArgs, ...}. You always should check that
there are no types module or function name
function arity match number of arguments in call (List in error message)
function is exported
All local calls (like loop(SomeArgs), without module specified) will not compile if function is not defined. And you can do local call dynamically (FuntionName(SomeArgs) again without module name).
EDIT after comment about need of local calls.
You actually could use lambda for this. There is spawn/1 funciton, which takes lambda (or fun if you like), so you can call spawn( fun local_functino/0).. Only issue with that is fact that your fun can not take any arguments, but there is a way around it, with use of closures.
spawn(fun () ->
gen_server_loop(Module, Module:init())
end).
And gen_serve_loop stays local call.
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.
I am new to erlang and have a bit of a headache with the following scenario:
Take this code:
-module (so).
-export ( [foo/0] ).
bar () ->
receive
die -> ok;
Msg -> io:format ("I say ~p.~n", [Msg] )
end.
bar (Name) ->
receive
die -> ok;
Msg -> io:format ("~s says ~p.~n", [Name, Msg] )
end.
foo () ->
Bar = spawn (fun bar/0),
Bar ! "Hello",
Bar ! die,
Baz = spawn (?MODULE, bar, ["Alice"] ), % bar/1 not exported
Baz ! "Hello",
Baz ! die.
The process spawned with spawn/1 works fine, but the second process spawned with spawn/3 fails as expected because I haven't exported bar/1 and hence I get {undef,[{so,bar,["Alice"]}]}. Both spawn functions that take a parameter list (spawn/3 and spawn/4) also take a module name. But I don't want to export bar/1, because it is only used internally and never needs to be invoked from outside the module.
How can I spawn a function with arguments without exporting it?
You can just put the call inside another fun like this:
spawn(fun() -> bar("alice") end)