diff options
Diffstat (limited to 'learn-you-some-erlang/processquest/apps/processquest-1.1.0/test/pq_player_tests.erl')
-rw-r--r-- | learn-you-some-erlang/processquest/apps/processquest-1.1.0/test/pq_player_tests.erl | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/learn-you-some-erlang/processquest/apps/processquest-1.1.0/test/pq_player_tests.erl b/learn-you-some-erlang/processquest/apps/processquest-1.1.0/test/pq_player_tests.erl new file mode 100644 index 0000000..d0f9c62 --- /dev/null +++ b/learn-you-some-erlang/processquest/apps/processquest-1.1.0/test/pq_player_tests.erl @@ -0,0 +1,395 @@ +-module(pq_player_tests). +-include_lib("eunit/include/eunit.hrl"). +-record(state, {name, stats, exp=0, lvlexp=1000, lvl=1, % copied from pq_player.erl + equip=[], money=0, loot=[], bought=[], + time=0, quest}). + +-define(setup(Name, T), {setup, fun() -> start(Name) end, fun stop/1, fun T/1}). +-define(setup(T), ?setup(make_ref(), T)). + +%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% TESTS DESCRIPTIONS %%% +%%%%%%%%%%%%%%%%%%%%%%%%%% +new_player_test_() -> + [{"A player holds its own name in its state", + ?setup(initial_name)}, + {"A new player has stats randomly rolled", + ?setup(initial_roll)}, + {"A new player becomes a registered process", + ?setup(initial_register)}, + {"A player has a counter for experience and a counter for " + "the next level (which is higher than the current exp)", + ?setup(initial_lvl)}, + {"A player has a basic equipment (empty) when first going", + ?setup(initial_equipment)}, + {"A new player has no money", + ?setup(initial_money)}, + {"A new player has no loot", + ?setup(initial_loot)}, + {"A new player has a quest given to him", + ?setup(initial_quest)}, + {"The state of a player can be overriden using the info " + "arguments to the init function", + ?setup(override_init)}]. + +market_test_() -> + [{"A player with N items will sell all of them to the market " + "and end with equivalent money, before switching to the " + "buying state", + ?setup(sell_all)}, + {"A player with nearly infinite money will buy items available " + "for his money, higher than his level", + ?setup(buy_items)}, + {"A player with no money or no items available for the price " + "range leaves for the killing fields.", + ?setup(buy_none)}, + {"Receiving the kill message just forwards to the killing state", + ?setup(to_killing)}]. + +killing_fields_test_() -> + [{"Kill enemies until the loot limit is hit. Loot is 2x Strength", + ?setup(loot_limit)}, + {"Killing enemies raises XP until someone levels up", + ?setup(kill_xp)}, + {"Leveling up boosts stats. The sum is higher by at least as " + "many points as there are fields, but not more than 6 times. " + "Moreover, the rolling is random.", + ?setup(lvl_stats)}, + {"receiving the market message just forwards to the market state", + ?setup(to_market)}]. + +quest_test_() -> + [{"A player who kills enough enemies to complete a quest obtains " + "a new, different one", + ?setup(quest_change)}, + {"A player who kills an enemy sees the quest kill count change", + ?setup(quest_kill_count)}, + {"A player who completes a quest obtains XP in exchange", + ?setup(quest_exp)}]. + +code_change_test_() -> + [{"Updating", update()}, + {"Downgrading", downgrade()}]. + +%%%%%%%%%%%%%%%%%%%%%%% +%%% SETUP FUNCTIONS %%% +%%%%%%%%%%%%%%%%%%%%%%% +start(Name) -> + application:start(crypto), + application:start(regis), + Pid = spawn(fun() -> timer:sleep(infinity) end), + regis:register({events, Name}, Pid), + Name. + +stop(Name) -> + exit(regis:whereis({events, Name}), kill), + application:stop(regis), + application:stop(crypto). + +%%%%%%%%%%%%%%%%%%%% +%%% ACTUAL TESTS %%% +%%%%%%%%%%%%%%%%%%%% + +%% Initial State +initial_name(Name) -> + {ok, market, S} = pq_player:init({Name, []}), + M = read_event(), + [?_assertEqual(Name, S#state.name), + ?_assertEqual(M, kill)]. + +initial_roll(Name) -> + {ok, _, S} = pq_player:init({Name, []}), + _ = read_event(), + ?_assertMatch([{charisma,_}, {constitution, _}, {dexterity, _}, + {intelligence, _}, {strength, _}, {wisdom, _}], + lists:sort(S#state.stats)). + +initial_register(Ref) -> + {ok, _, _} = pq_player:init({Ref, []}), + _ = read_event(), + Pid = regis:whereis(Ref), + Ret = pq_player:init({Ref, []}), + _ = read_event(), + [?_assert(undefined =/= Pid), + ?_assert(is_pid(Pid)), + ?_assertEqual({stop, name_taken}, Ret)]. + +initial_lvl(Name) -> + {ok, _, S} = pq_player:init({Name, []}), + _ = read_event(), + [?_assert(is_integer(S#state.lvlexp)), + ?_assert(S#state.lvlexp > 0), + ?_assert(S#state.exp =:= 0)]. % start at 0 exp + +initial_equipment(Name) -> + {ok, _, S} = pq_player:init({Name, []}), + _ = read_event(), + [?_assert(is_list(S#state.equip)), + ?_assertEqual(undefined, proplists:get_value(armor, S#state.equip)), + ?_assertEqual(undefined, proplists:get_value(helmet, S#state.equip)), + ?_assertEqual(undefined, proplists:get_value(weapon, S#state.equip)), + ?_assertEqual(undefined, proplists:get_value(shield, S#state.equip))]. + +initial_money(Name) -> + {ok, _, S} = pq_player:init({Name, []}), + _ = read_event(), + ?_assertEqual(0, S#state.money). + +initial_loot(Name) -> + {ok, _, S} = pq_player:init({Name, []}), + _ = read_event(), + ?_assertEqual([], S#state.loot). + +initial_quest(Name) -> + {ok, _, S} = pq_player:init({Name, []}), + _ = read_event(), + ?_assertMatch({_QuestName, _Props}, S#state.quest). + +override_init(Name) -> + {ok, _, Partial} = pq_player:init({Name, [ + {stats, [{charisma,1}, {constitution,1}, {dexterity,1}, + {intelligence,1}, {strength,1}, {wisdom,1}]}, + {lvlexp,1}]}), + regis:unregister(Name), + _ = read_event(), + {ok, _, Complete} = pq_player:init({Name, [ + {stats, [{charisma,1}, {constitution,1}, {dexterity,1}, + {intelligence,1}, {strength,1}, {wisdom,1}]}, + {exp, 1}, {lvlexp,1}, {lvl,9}, + {equip, [{weapon,{<<"plastic knife">>, -1, 1, 2}}]}, + {money,1}, {loot, [{<<"Bacon">>, 1}]}, {bought, [helmet]}, + {time, 1}, + {quest, {<<"A">>, [{experience, 1},{kills,1}]}} + ]}), + _ = read_event(), + [?_assertMatch(#state{stats=[{_,1},{_,1},{_,1},{_,1},{_,1},{_,1}], + lvlexp=1, lvl=1, exp=0}, + Partial), + ?_assertMatch(#state{stats=[{_,1},{_,1},{_,1},{_,1},{_,1},{_,1}], + exp=1, lvlexp=1, lvl=9, equip=[{weapon,_}], + money=1, loot=[{_,_}], bought=[_], time=1, + quest={<<"A">>, [_|_]}}, + Complete)]. + +%% Market +sell_all(Name) -> + Loot = [proplists:get_value(drop, element(2, pq_enemy:fetch())) + || _ <- lists:seq(1,5)], + [{_, LV1}, {_, LV2}, {_, LV3}, {_, LV4}, {_, LV5}] = Loot, + LootVals = [LV1, LV2*2, LV3*3, LV4*4, LV5*5], + {[Sum1, Sum2, Sum3, Sum4, Sum5], _} = lists:mapfoldl( + fun(X, Sum) -> {X+Sum, X+Sum} end, + 0, + LootVals + ), + undefined = read_event(), + S0 = #state{name=Name, loot=Loot, money=0}, + {next_state, market, S1} = pq_player:market(sell, S0), + M1 = read_event(), + {next_state, market, S2} = pq_player:market(sell, S1#state{lvl=2}), + M2 = read_event(), + {next_state, market, S3} = pq_player:market(sell, S2#state{lvl=3}), + M3 = read_event(), + {next_state, market, S4} = pq_player:market(sell, S3#state{lvl=4}), + M4 = read_event(), + {next_state, market, S5} = pq_player:market(sell, S4#state{lvl=5}), + M5 = read_event(), + {next_state, market, S6} = pq_player:market(sell, S5#state{lvl=6}), + M6 = read_event(), + [?_assertMatch(#state{money=Sum1, loot=[_,_,_,_]}, S1), + ?_assertMatch(#state{money=Sum2, loot=[_,_,_]}, S2), + ?_assertMatch(#state{money=Sum3, loot=[_,_]}, S3), + ?_assertMatch(#state{money=Sum4, loot=[_]}, S4), + ?_assertMatch(#state{money=Sum5, loot=[]}, S5), + ?_assertMatch(#state{money=Sum5, loot=[]}, S6), + ?_assertEqual([sell, sell, sell, sell, sell, buy], + [M1,M2,M3,M4,M5,M6])]. + +buy_items(Name) -> + %% 4 different pieces of equipment to buy + S0 = #state{name=Name, equip=[], money=999999999999}, + {next_state, market, S1} = pq_player:market(buy, S0), + M1 = read_event(), + {next_state, market, S2} = pq_player:market(buy, S1), + M2 = read_event(), + {next_state, market, S3} = pq_player:market(buy, S2), + M3 = read_event(), + {next_state, market, S4} = pq_player:market(buy, S3), + M4 = read_event(), + %% All slots bought. Implicit requirement: not buying for the + %% same slot twice. + {next_state, market, S5} = pq_player:market(buy, S4), + M5 = read_event(), + [?_assertEqual([S5#state.money, S4#state.money, S3#state.money, + S2#state.money, S1#state.money, S0#state.money], + lists:sort([S5#state.money, S4#state.money, S3#state.money, + S2#state.money, S1#state.money, S0#state.money])), + ?_assertEqual([1,2,3,4,4], + [length(L) || L <- [S1#state.equip, S2#state.equip, + S3#state.equip, S4#state.equip, + S5#state.equip]]), + ?_assertEqual([buy, buy, buy, buy, kill], + [M1, M2, M3, M4, M5])]. + +buy_none(Name) -> + S0 = #state{name=Name, equip=[], money=0}, + %% one try per part of the equipment + {next_state, market, S1} = pq_player:market(buy, S0), + _ = read_event(), + {next_state, market, S2} = pq_player:market(buy, S1), + _ = read_event(), + {next_state, market, S3} = pq_player:market(buy, S2), + _ = read_event(), + {next_state, market, S4} = pq_player:market(buy, S3), + M = read_event(), + [?_assertEqual(S0, S4), + ?_assertEqual(kill, M)]. + +to_killing(Name) -> + S = #state{name=Name}, + Res = pq_player:market(kill, S), + M = read_event(), + [?_assertMatch({next_state, killing, S}, Res), + ?_assertEqual(kill, M)]. + +%% Killing fields tests +loot_limit(Name) -> + S0 = #state{name=Name, stats=[{strength, 2}], loot=[], + quest={<<1>>,[{experience,0},{kills,10000}]}}, + {next_state, killing, S1 = #state{loot=L1}} = pq_player:killing(kill, S0), + M1 = read_event(), + {next_state, killing, S2 = #state{loot=L2}} = pq_player:killing(kill, S1), + M2 = read_event(), + {next_state, killing, S3 = #state{loot=L3}} = pq_player:killing(kill, S2), + M3 = read_event(), + {next_state, killing, #state{loot=L4}} = pq_player:killing(kill, S3), + M4 = read_event(), + %% Group identical drops with a counter? + [?_assertEqual([1,2,3,4], [length(L) || L <- [L1, L2, L3, L4]]), + ?_assertEqual([kill, kill, kill, market], [M1, M2, M3, M4])]. + +kill_xp(Name) -> + S0 = #state{name=Name, stats=[{strength, 999}|pq_stats:initial_roll()], + lvl=1, exp=0, lvlexp=5, + quest={<<1>>,[{experience,0},{kills,10000}]}}, + %% between 1 and 5 kills required to lvl up. + {next_state, NS1, S1} = pq_player:killing(kill, S0), + M1 = read_event(), + {next_state, NS2, S2} = pq_player:NS1(M1, S1), + M2 = read_event(), + {next_state, NS3, S3} = pq_player:NS2(M2, S2), + M3 = read_event(), + {next_state, NS4, S4} = pq_player:NS3(M3, S3), + M4 = read_event(), + {next_state, NS5, S5} = pq_player:NS4(M4, S4), + M5 = read_event(), + {next_state, NS6, S6} = pq_player:NS5(M5, S5), + M6 = read_event(), + [?_assert(lists:any(fun(#state{lvl=L}) -> L > 1 end, [S1,S2,S3,S4,S5,S6])), + ?_assert(lists:any(fun(#state{lvlexp=L}) -> L >= 10 end, [S1,S2,S3,S4,S5,S6])), + ?_assert(lists:any(fun(#state{exp=E}) -> E >= 5 end, [S1,S2,S3,S4,S5,S6])), + ?_assert(lists:any(fun(FSMState) -> FSMState =:= killing end, + [NS1, NS2, NS3, NS4, NS5, NS6])), + ?_assert(lists:any(fun(Msg) -> Msg =:= kill end, + [M1, M2, M3, M4, M5, M6])), + ?_assert(lists:any(fun(Msg) -> Msg =:= lvl_up end, + [M1, M2, M3, M4, M5, M6]))]. + +lvl_stats(Name) -> + {ok, _, S0} = pq_player:init({Name, []}), + _ = read_event(), + TotalStats = length(S0#state.stats), + {next_state, killing, S1} = pq_player:killing(lvl_up, S0), + _ = read_event(), + {next_state, killing, S2} = pq_player:killing(lvl_up, S0), + _ = read_event(), + SumInit = lists:sum([Pts || {_,Pts} <- S0#state.stats]), + SumS1 = lists:sum([Pts || {_,Pts} <- S1#state.stats]), + SumS2 = lists:sum([Pts || {_,Pts} <- S2#state.stats]), + [?_assert(SumS1 >= TotalStats+SumInit), + ?_assert(SumS2 >= TotalStats+SumInit), + ?_assert(SumS1 =< TotalStats*6 + SumInit), + ?_assert(SumS2 =< TotalStats*6 + SumInit), + ?_assert(S1#state.stats =/= S2#state.stats)]. + +to_market(Name) -> + S = #state{name=Name}, + Res = pq_player:killing(market, S), + M = read_event(), + [?_assertMatch({next_state, market, S}, Res), + ?_assertEqual(sell, M)]. + +%% Quests +quest_change(Name) -> + {ok, _, S0=#state{quest={N, Props}}} = pq_player:init({Name, []}), + _ = read_event(), + S = S0#state{quest={N, [{kills,1}|Props]}}, + States = [element(3, pq_player:killing(kill, S)) || _ <- lists:seq(1,10)], + [read_event() || _ <- lists:seq(1,10)], + [?_assert(lists:all(fun(#state{quest={QName, _}}) -> QName =/= N end, + States))]. + +quest_kill_count(Name) -> + {ok, _, S0=#state{quest={N, Props}}} = pq_player:init({Name, []}), + _ = read_event(), + S1 = S0#state{quest={N, [{kills,3}|Props]}}, + {next_state, _, S2} = pq_player:killing(kill, S1), + _ = read_event(), + [?_assertEqual(2, proplists:get_value(kills,element(2,S2#state.quest)))]. + +quest_exp(Name) -> + {ok, _, S0=#state{quest={N, _Props}}} = pq_player:init({Name, [{lvl,0},{exp,0}]}), + _ = read_event(), + S1 = S0#state{quest={N, [{kills,2},{experience, 100000}]}}, + {next_state, _, S2 = #state{exp=E1}} = pq_player:killing(kill, S1), + _ = read_event(), + {next_state, _, #state{exp=E2}} = pq_player:killing(kill, S2), + _ = read_event(), + [?_assert(E1 < 100000), + ?_assert(E2 > 100000)]. + +%% Update/Downgrade + +update() -> + Stats = [{charisma,1}, {constitution,1}, {dexterity,1}, + {intelligence,1}, {strength,1}, {wisdom,1}], + OldState = {state, joe, + Stats, + 1, % exp + 2, % lvlexp + 3, % lvl + [{weapon,{<<"a">>, -1, 1, 2}}], % equip + 4, % money + [{<<"b">>, 1}], % loot + [helmet], % bought + 5 % time + }, + ?_assertMatch({ok, + market, + #state{name=joe, stats=Stats, exp=1, lvlexp=2, + lvl=3, equip=[{weapon,{<<"a">>,-1,1,2}}], + money=4, loot=[{<<"b">>,1}], bought=[helmet], + quest={_, [_|_]}}}, + pq_player:code_change("some version hash", market, OldState, none)). + +downgrade() -> + Stats = [{charisma,1}, {constitution,1}, {dexterity,1}, + {intelligence,1}, {strength,1}, {wisdom,1}], + State = #state{name=joe, stats=Stats, exp=1, lvlexp=2, + lvl=3, equip=[{weapon,{<<"a">>,-1,1,2}}], + money=4, loot=[{<<"b">>,1}], bought=[helmet], + time=5, quest={<<"this goes">>, []}}, + NewState = {state, joe, Stats, 1, 2, 3, [{weapon,{<<"a">>, -1, 1, 2}}], + 4, [{<<"b">>, 1}], [helmet], 5}, + ?_assertMatch({ok, market, NewState}, + pq_player:code_change({down, "old vsn hash"}, market, State, none)). + +%%%%%%%%%%%%%%%%%%%%%%%% +%%% HELPER FUNCTIONS %%% +%%%%%%%%%%%%%%%%%%%%%%%% +read_event() -> + receive + {'$gen_event', Msg} -> Msg + after 0 -> + undefined + end. |