[erlang-questions] Test suite for checking concurrency in erlang

Abhishek Ranjan <>
Tue Sep 19 13:24:58 CEST 2017


Hello,

I wanted to write a test case which can show me that when one single code base is used and accessed by two or more individuals at the same time then how does it behaves(I basically want to test for concurrancy). I have chosen the code base as a tic-tac-toe game present on this link https://github.com/bekkopen/erlang-tictactoe/tree/master/src . 

I have changed the code a bit though and have attached the files in this mail.

Now I tried common_test and wrote the following module testing2

-module(testing2).
-include_lib("common_test/include/ct.hrl").
%% ====================================================================
%% API functions
%% ====================================================================
-export([all/0, groups/0, init_per_group/2, end_per_group/2]).
-export([test1/1,test2/1]).
%% ====================================================================
%% Internal functions
%% ====================================================================
all() -> [{group, session}].

groups() -> [{session,
              [],
              [{group, clients}]},
             {clients,
              [parallel, {repeat, 1}],
              [test1, test2]}].

init_per_group(session, Config) ->
   gameserver:start(),
    Config;
init_per_group(_, Config) ->
    Config.

end_per_group(session, _Config) ->
    gameclient:stop();
end_per_group(_, _Config) ->
    ok.


test1(_Config)->
  
Abhishek=gameclient:login("Abhishek"),
Dharun=gameclient:login("Dharun"),
 
 
gameclient:new_game(Abhishek),
 
 timer:sleep(5),
 
 gameclient:make_move(Abhishek, a1),
 timer:sleep(5),
 gameclient:make_move(Dharun, b2),
 timer:sleep(5),
 gameclient:make_move(Abhishek, a2),
 timer:sleep(5), 
 gameclient:make_move(Dharun, c3),
 timer:sleep(5),
 gameclient:make_move(Abhishek, a3).



test2(_Config)->
  
Abhijeet=gameclient:login("Abhijeet"),
Ranjan=gameclient:login("Ranjan"),
 
 
gameclient:new_game(Abhijeet),
 timer:sleep(5),
 
 gameclient:make_move(Abhijeet, a1),
 timer:sleep(5),
 gameclient:make_move(Ranjan, b2),
 timer:sleep(5),
 gameclient:make_move(Abhijeet, a2),
 timer:sleep(5), 
 gameclient:make_move(Ranjan, c3),
 timer:sleep(5),
 gameclient:make_move(Abhijeet, a3).


Now when I see the logs of ct I find the following output:

for test1()->

=== Started at 2017-09-19 16:37:16

“Welcome to tictactoe server!”
“Welcome to tictactoe server!”
“Ready to rumble against Abhishek!”
“Ready to rumble against Dharun!”


=== Ended at 2017-09-19 16:37:16
=== successfully completed test case
=== Returned value: {make_move,a3}

for test2()->



=== Started at 2017-09-19 16:37:16


“Welcome to tictactoe server!”

“Welcome to tictactoe server!”

“Ready to rumble against Ranjan!”

“Ready to rumble against Abhijeet!”

“It is not your turn yet!”

“It is not your turn yet!”



=== Ended at 2017-09-19 16:37:16
=== successfully completed test case
=== Returned value: {make_move,a3}


But I expected the output of both the games in the logs as I am running them in parallel.

Can anyone suggest me what am I missing and how to go about it?

Thank you.

Best Regards,

Abhishek
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20170919/f7ced8bc/attachment.html>
-------------- next part --------------
-module(gameclient).
-compile(export_all).

t() ->
	gameserver:start(),
	Player1 = login("player1"),
	Player2 = login("player2"),
	new_game(Player1),

	make_move(Player1, a1),
	timer:sleep(5),
	make_move(Player1, b1),
	timer:sleep(5),
	make_move(Player2, b2),
	timer:sleep(5),
	make_move(Player1, a2),
	timer:sleep(5),	
	make_move(Player2, a2),
	timer:sleep(5),
	make_move(Player2, c3),
	timer:sleep(5),
	make_move(Player1, a3).


login(Name) ->
	Pid = spawn(gameclient, loop, [ Name ]),
	gameserver:connect(Name, Pid),
	Pid.

new_game(Pid) ->
	Pid ! {new_game}.

make_move(Pid, Move) ->	
	
	Pid ! { make_move, Move }.

stop()->
	self() ! {stop}.

exit(Pid) ->	
	
	Pid ! { exit}.

game_tie(Pid, OpponentName, GameState) ->
	Pid ! { tie, OpponentName, GameState }.

send_message(Pid, Message) ->
	Pid ! { msg, Message}.

loop(Name) ->
	receive
		{ msg, Message } ->
			io:format("~p~n", [Message]),
			loop(Name);
		{ new_game} ->
			gameserver:new_game(Name),
			loop(Name);
		{ make_move, Move } ->
			gameserver:make_move(Name, Move),
			loop(Name);
		{stop}->
			gameserver:stop(),
			loop(Name);
		{exit}->
			gameserver:exit(Name),
			loop(Name);
		{ tie, OpponentName, GameState } ->
			io:format("~p~n", [get_tie_message(OpponentName)]),
			GameState,
			loop(Name)		
	end.

get_win_message(Over) ->
	"You won over " ++ Over ++ " :)".

get_loose_message(For) ->
	"You lost over " ++ For ++ "... :(".

get_tie_message(Opponent) ->
	"It is a tie between you and " ++ Opponent.	
-------------- next part --------------
-module(gameserver).
-compile(export_all).
-define(SERVER, gameserver).

start() ->
    server_util:start(?SERVER, 
    				  { gameserver, game_loop, 
    	  			    [ dict:new(), dict:new() ]}).
stop()->
	server_util:stop(?SERVER).

connect(Name, Pid) ->
	global:send(?SERVER, { connect, Name, Pid }).

new_game(Name) ->
	global:send(?SERVER, { new_game, Name}).

make_move(Name, Move) ->
	global:send(?SERVER, { make_move, Name, Move }).

exit(Name)->
	global:send(?SERVER, { exit,Name}).

game_loop(Players, Games) ->
    process_flag(trap_exit, true),
	receive
		{ connect, Name, Pid } ->
			Pid ! { msg, "Welcome to tictactoe server!" },
			
			game_loop(dict:store(Name, Pid, Players), Games);
        
		{ new_game, Name} ->
			PlayerList=dict:fetch_keys(Players),
			PlayerAndOpponent=lists:partition(fun(A)->Name==A end, PlayerList),
			{_,OpponentNameList}=PlayerAndOpponent,
			OpponentName=list_to_tuple(OpponentNameList),
			case dict:find(Name, Players) of
                { ok, Pid } ->
                    case dict:find(element(1,OpponentName), Players) of
                    	{ ok, OpponentPid} ->
                    		Pid ! { msg, "Ready to rumble against " ++ element(1,OpponentName) ++ "!" },
                    		OpponentPid ! { msg, "Ready to rumble against " ++ Name ++ "!" },
							
                    		GamePid = tictactoe:start({Pid, Name}, {OpponentPid, element(1,OpponentName)}),
							
                    		GameKey = create_game_key(Name, element(1,OpponentName)),
							
							
                            link(GamePid),
                    		game_loop(Players, dict:store(GameKey, GamePid, Games));
                    	error ->
                    		Pid ! { msg, "Did not find opponent " ++ OpponentName ++ "!" },
                    		game_loop(Players, Games)
                    end;
                error ->
                    io:format("Could not find player ~p~n", [ Name ]),
                    game_loop(Players, Games)
            end;
		
		

        { make_move, Name, Move } ->
			
			PlayerList=dict:fetch_keys(Players),
			
			PlayerAndOpponent=lists:partition(fun(A)->Name==A end, PlayerList),
			
			{_,OpponentNameList}=PlayerAndOpponent,
			
			
			OpponentName=list_to_tuple(OpponentNameList),
			case dict:find(create_game_key(Name, element(1,OpponentName)), Games) of
                { ok, GamePid } ->
					
                	tictactoe:make_move(GamePid, Name, Move),
                	game_loop(Players, Games);
                error ->
                	game_loop(Players, Games)
            end;
        {'EXIT', GamePid, _} ->
            io:format("Removing game ~p~n", [GamePid]),
            game_loop(Players, remove_game(GamePid, Games));
		 {exit, Name} ->
			 PlayerList=dict:fetch_keys(Players),
			
			PlayerAndOpponent=lists:partition(fun(A)->Name==A end, PlayerList),
			
			{_,OpponentNameList}=PlayerAndOpponent,
			
			
			OpponentName=list_to_tuple(OpponentNameList),
			 case dict:find(create_game_key(Name, element(1,OpponentName)), Games) of
                { ok, GamePid } ->
            io:format("Exit from game ~p~n", [GamePid]),
            game_loop(Players, remove_game(GamePid, Games));
				   error ->
					   io:format("Iside exit but error"),
                	game_loop(Players, Games)
            end;
	    Oops ->
 			io:format("I don't get ~p~n", [ Oops ]),
 			game_loop(Players, Games)
 	end.

remove_game(GamePid, Games) ->
    dict:filter(fun(_, Value) -> Value =/= GamePid end, Games).

create_game_key(PlayerOne, PlayerTwo) ->
	io:format("Value of Playerone in createGameKey is ~p~n",[PlayerOne]),
	io:format("Value of PlayerTwo in createGameKey is ~p~n",[PlayerTwo]),
	string:join(lists:sort([PlayerOne, PlayerTwo]), "").


-------------- next part --------------
-module(tictactoe).
-compile(export_all).

start(PlayerOne, PlayerTwo) ->
	InitialGame = [a1, a2, a3, b1, b2, b3, c1, c2, c3], 
	spawn(tictactoe, loop, [PlayerOne, PlayerTwo, PlayerOne, InitialGame]).

make_move(GamePid, Player, Move) ->
	GamePid ! { make_move, Player, Move }.

loop(PlayerOne, PlayerTwo, CurrentPlayer, GameState) ->
	{CurrentPid, CurrentName} = CurrentPlayer,
	io:format("Inside tictactoe The value of CurrentPid is ~p~n",[CurrentPid]),
	io:format("Inside tictactoe The value of CurrentName is ~p~n",[CurrentName]),
	{PlayerOnePid, PlayerOneName} = PlayerOne,
	{PlayerTwoPid, PlayerTwoName} = PlayerTwo,
	receive
		{ make_move, CurrentName, Move } ->			
			ValidMove = lists:any(fun(X) -> X =:= Move end, GameState),	
%% 			io:format("Inside tictactoe The value of is ~p~n",[),
            io:format("Inside tictactoe The value of Move is ~p~n",[Move]),
			io:format("Inside tictactoe The value of Gamestate is ~p~n",[GameState]),
			io:format("Inside tictactoe The value of Currentname is ~p~n",[CurrentName]),
			io:format("Inside tictactoe The value of Validmove is ~p~n",[ValidMove]),
			if 
				ValidMove ->
					CurrentPlayerAtom = player_atom(PlayerOne, PlayerTwo, CurrentPlayer),
					UpdateFun = fun(Pos) ->
						value_if_match(Pos, Move, CurrentPlayerAtom)
					end,
					NewGameState = lists:map(UpdateFun, GameState),
					NewCurrentPlayer = change_player(PlayerOne, PlayerTwo, CurrentPlayer),
					io:format("State: ~p -> ~p~n", [GameState, NewGameState]),
						
					case get_game_result(NewGameState) of
						tie ->
							gameclient:game_tie(PlayerOnePid, PlayerTwoName, NewGameState),
							gameclient:game_tie(PlayerTwoPid, PlayerOneName, NewGameState);
						x ->
							gameclient:send_message(PlayerOnePid, get_win_message(PlayerTwoName)),
							gameclient:send_message(PlayerTwoPid, get_loose_message(PlayerOneName));
%% 							gameserver:stop();
						o ->
							gameclient:send_message(PlayerTwoPid, get_win_message(PlayerOneName)),
							gameclient:send_message(PlayerOnePid, get_loose_message(PlayerTwoName));
%% 							gameserver:stop();
						continue ->
							loop(PlayerOne, PlayerTwo, NewCurrentPlayer, NewGameState)
					end;								
				true ->
					gameclient:send_message(CurrentPid, "Position " ++ erlang:atom_to_list(Move) ++ " is not available."),
					loop(PlayerOne, PlayerTwo, CurrentPlayer, GameState)
			end;

		{ make_move, WrongPlayerName, _ } ->
			WrongPlayerPid = get_pid_for_player_name(PlayerOne, PlayerTwo, WrongPlayerName),
			gameclient:send_message(WrongPlayerPid, "It is not your turn yet!"),
			loop(PlayerOne, PlayerTwo, CurrentPlayer, GameState);
		Message -> 
			io:format("PlayerOne: ~p~n PlayerTwo: ~p~n CurrentPlayer: ~p~n Message: ~p~n", [PlayerOne, PlayerTwo, CurrentPlayer, Message]),
			loop(PlayerOne, PlayerTwo, CurrentPlayer, GameState)
	end.

get_win_message(Over) ->
	"You won over " ++ Over ++ " :)".

get_loose_message(For) ->
	"You lost over " ++ For ++ "... :(".

get_game_result(GameState) ->
	case check_for_winner(GameState) of
		undecided ->
			GameOver = lists:all(fun(X) -> (X =:= x) or (X =:= o) end, GameState),
			if
				GameOver ->
					tie;
				true ->
					continue
			end;
		X -> X
	end.

check_for_winner([A1, A2, A3, B1, B2, B3, C1, C2, C3]) ->
	Rows = 		[[A1, A2, A3], [B1, B2, B3], [C1, C2, C3]],
	Columns = 	[[A1, B1, C1], [A2, B2, C2], [A3, B3, C3]],
	Diagonals = [[A1, B2, C3], [A3, B2, C1]],
	get_winner(Rows ++ Columns ++ Diagonals).

get_winner([]) -> undecided;
get_winner([[X, X, X] | _]) -> X;
get_winner([_ | T]) -> get_winner(T).

value_if_match(X, X, NewValue) -> NewValue;
value_if_match(X, _, _) -> X.

player_atom(X, _, X) -> x;
player_atom(_, Y, Y) -> o.

change_player(PlayerOne, PlayerTwo, PlayerOne) -> PlayerTwo;
change_player(PlayerOne, PlayerTwo, PlayerTwo) -> PlayerOne.

get_pid_for_player_name({PlayerOnePid, WrongPlayerName}, _, WrongPlayerName) -> PlayerOnePid;
get_pid_for_player_name(_, {PlayerTwoPid, WrongPlayerName}, WrongPlayerName) -> PlayerTwoPid.


-------------- next part --------------
-module(server_util).

-compile([export_all]).

start(ServerName, {Module, Function, Args}) ->
                            Pid = spawn(Module, Function, Args),
                            global:register_name(ServerName, Pid).

stop(ServerName) ->
		global:unregister_name(ServerName),				
                                global:send(ServerName, shutdown).


More information about the erlang-questions mailing list