-
-
Save sgobotta/08a1dc72b3f6c240c9db6cab2eb4c861 to your computer and use it in GitHub Desktop.
-module(server). | |
-author("Santiago Botta <[email protected]>"). | |
-include_lib("eunit/include/eunit.hrl"). | |
-export([server/0, server/1, proxy/1]). | |
-export([start/1, check/2, stop/1]). | |
% Testing purpose | |
-export([send_multiple_requests/3]). | |
%%%------------------------------------------------------------------- | |
%% @doc Server tests | |
%% @end | |
%%%------------------------------------------------------------------- | |
server_test() -> | |
Assertions = [ | |
{"Adam", {result, "Adam is not a palindrome."}}, | |
{"Madam Im Adam", {result, "Madam Im Adam is a palindrome."}} | |
], | |
Proxy = start(4), | |
ok = lists:foreach( | |
fun ({Request, _Response}) -> | |
mock_check_request(self(), Proxy, Request) | |
end, | |
Assertions | |
), | |
ok = lists:foreach( | |
fun ({Request, Response}) -> | |
?assertEqual(Response, mock_check_response(Request)) | |
end, | |
Assertions | |
), | |
stop = server:stop(Proxy), | |
ok. | |
mock_check_request(From, Server, Request) -> | |
spawn(fun () -> | |
Response = server:check(Server, Request), | |
From ! {Request, Response} end | |
). | |
mock_check_response(Request) -> | |
receive | |
{Request, Response} -> | |
Response | |
end. | |
%% @doc Given a process id, listens to palindrome requests to return a processed | |
%% result. | |
%% Usage: | |
%% ServerPid = spawn(server, server, [self()]). | |
%% ServerPid ! {check, "MadamImAdam"}. | |
%% flush(). | |
%% @end | |
server(From) -> | |
receive | |
{check, String} -> | |
IsPalindromeResult = is_palindrome(String), | |
From ! {result, String ++ IsPalindromeResult}, | |
server(From); | |
stop -> | |
ok | |
end. | |
%% @doc Takes requests from multiple clients | |
%% Usage: | |
%% ServerPid = spawn(server, server, []). | |
%% ServerPid ! {check, "MadamImAdam", self()}. | |
%% flush(). | |
%% @end | |
server() -> | |
receive | |
{check, String, From} -> | |
IsPalindromeResult = is_palindrome(String), | |
io:format("~p ::: checks palindrome ~s from: ~p~n", [self(), String, From]), | |
From ! {result, String ++ IsPalindromeResult}, | |
server(); | |
stop -> | |
ok | |
end. | |
%% Replicating the server | |
%% @doc Given a list of server pids, calls a function that accepts a request to | |
%% one of them and distributes the next requests to the rest of the servers | |
%% indefinately. | |
%% Usage: | |
%% Server1 = spawn(server, server, []). | |
%% Server2 = spawn(server, server, []). | |
%% Server3 = spawn(server, server, []). | |
%% Proxy = spawn(server, proxy, [[Server1, Server2, Server3]]). | |
%% @end | |
proxy(Servers) -> | |
proxy(Servers, Servers). | |
%% @doc Given a list of server pids and a pid accumulator listens to requests | |
%% and delegates future requests to the next pid in the servers list | |
%% indefinately. | |
proxy([], Servers) -> | |
proxy(Servers, Servers); | |
proxy([S|Svrs], Servers) -> | |
receive | |
stop -> | |
lists:foreach( | |
fun (Server) -> | |
Server ! stop, | |
io:format("Terminating ~p...~n", [Server]) | |
end, | |
Servers | |
), | |
ok; | |
{check, String, From} -> | |
S ! {check, String, From}, | |
proxy(Svrs, Servers) | |
end. | |
%% @doc Given a list of servers, sends a stop message to each one. | |
stop(Server) -> | |
io:format("Terminating ~p...~n", [Server]), | |
Server ! stop. | |
%%%------------------------------------------------------------------- | |
%% @doc server API | |
%% @end | |
%%%------------------------------------------------------------------- | |
%% @doc Given an integer, spawns a proxy server with N servers as argument. | |
start(N) -> | |
start(N, []). | |
%% @doc Starts N servers to return a tuple where the first component is the | |
%% proxy pid and the second component the list of spawned server pids. | |
start(0, Servers) -> | |
spawn(?MODULE, proxy, [Servers]); | |
start(N, Servers) -> | |
Server = spawn(?MODULE, server, []), | |
io:format("Starting... ~p~n", [Server]), | |
start(N-1, [Server | Servers]). | |
%% @doc Given a server pid() and a string sends a request to the server to | |
%% return an evaluated expression for a palindrome query. | |
-spec check(pid(), string()) -> {{atom(), string()}}. | |
check(Server, String) -> | |
Server ! {check, String, self()}, | |
receive | |
Response -> Response | |
end. | |
%% @doc Given a server pid, a client pid and a number of requests, sends N | |
%% similar requests to the server pid. | |
send_multiple_requests(_ServerPid, _From, 0) -> | |
ok; | |
send_multiple_requests(ServerPid, From, N) -> | |
From ! check(ServerPid, "Madam Im Adam"), | |
send_multiple_requests(ServerPid, From, N-1). | |
%%%------------------------------------------------------------------- | |
%% @doc Palindrome Auxiliary functions | |
%% @end | |
%%%------------------------------------------------------------------- | |
%% @doc Given a string, returns a string telling whether it's a palindrome or not. | |
-spec is_palindrome(string()) -> string(). | |
is_palindrome(String) -> | |
IsPalindrome = palin:palindrome(String), | |
case IsPalindrome of | |
true -> " is a palindrome."; | |
false -> " is not a palindrome." | |
end. | |
is_palindrome_test() -> | |
IsPalindrome = " is a palindrome.", | |
IsNotPalindrome = " is not a palindrome.", | |
?assertEqual(IsPalindrome, is_palindrome("Madam I'm Adam")), | |
?assertEqual(IsNotPalindrome, is_palindrome("Madam I'm Adams")). |
Hey @elbrujohalcon! Nice to hear from you again!
Nice code! You're only missing a good set of tests for your servers, proxy, etc…
Yeah, I felt bad for not submitting those. Do you know if there's a unit framework with support for receive
statements? Maybe I just can define a function that accepts a message and returns a value to implement assertions. What do you think?
and maybe a couple of API functions to allow clients to call
server:check(Server, ThisString)
instead of writing the whole message sending and receiving logic there ;)
Ohh, you mean defining separate functions to be called from each case in the receive cases?
receive
{check, String, From} ->
From ! server:check(Server, String)
You don't need a framework… and hopefully this answers your other concerns, too…
Check what I did: https://gist.github.com/elbrujohalcon/8d3366fe1765c63a3e48d6d5589f1391#file-palin-multi-server-erl
I basically created an API (with functions like start, check, stop, etc…) so that I hide the fact that those things run in different processes. Then, I tested those API functions.
You don't need a framework… and hopefully this answers your other concerns, too…
Check what I did: https://gist.github.com/elbrujohalcon/8d3366fe1765c63a3e48d6d5589f1391#file-palin-multi-server-erl
I basically created an API (with functions like start, check, stop, etc…) so that I hide the fact that those things run in different processes. Then, I tested those API functions.
Thank you! I've been looking at your examples and I'll definitely apply those strategies.
@elbrujohalcon, I particularly liked the way you make use of lists compehension funcions. I had to take a look at the documentation. I've seen them before but had no idea what they were supposed to do, haa!
Well, it took me a little while but I see now how you didn't have to use eunit but just did the assertion evaluation the {I, O} = ...
expression. Nice one. It feels so much better using booleans as palindrome
output instead of "it is a palin... it isnt't a p...", haha
So, something got me thinking quite a bit. Trying to figure out how every process is receiving a response during the second iteration in your test: is it possible that it's done 'cause you're matching with a particular message in the mailbox? I mean when you do:
check_test_client(Input) ->
receive
{Input, Output} ->
Output
end.
specifically matching the Input
value.
Yeah, exactly! I'm doing a selective receive there, matching only on messages that are 2-sized tuples starting with Input
.
Well, it took me a little while but I see now how you didn't have to use eunit but just did the assertion evaluation the
{I, O} = ...
expression. Nice one. It feels so much better using booleans aspalindrome
output instead of "it is a palin... it isnt't a p...", haha
Oh, yeah… I ignored the whole stringifying nonsense with the goal of highlighting the important pieces. 🙄
Nice code! You're only missing a good set of tests for your servers, proxy, etc… and maybe a couple of API functions to allow clients to call
server:check(Server, ThisString)
instead of writing the whole message sending and receiving logic there ;)