-
-
Save sgobotta/08a1dc72b3f6c240c9db6cab2eb4c861 to your computer and use it in GitHub Desktop.
| -module(server). | |
| -author("Santiago Botta <santiago@camba.coop>"). | |
| -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 aspalindromeoutput 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 ;)