Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Valid Solutions #9

Merged
merged 1 commit into from
Jul 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/bo_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ init(noargs) -> {ok, #{}}.
({task, bo_players:name()}, {pid(), term()}, state()) ->
{reply, {ok, bo_task:task()} | {error, forbidden | notfound}, state()};
({submit, bo_players:name(), term()}, {pid(), term()}, state()) ->
{reply, bo_task:result() | {error, forbidden | notfound}, state()}.
{reply, {ok, bo_task:task()} | the_end
| {error, invalid | timeout | forbidden | notfound}
| {failures, [term()]}, state()}.
handle_call({signup, PlayerName}, {From, _}, State) ->
Node = node(From),
try bo_players_repo:signup(PlayerName, Node) of
Expand Down Expand Up @@ -65,7 +67,7 @@ handle_call({submit, PlayerName, Solution}, {From, _}, State) ->
Player ->
case bo_players:node(Player) of
Node ->
{reply, bo_task:test(bo_players:task(Player), Solution), State};
{reply, test(Player, Solution), State};
NotNode ->
error_logger:warning_msg(
"~p trying to access from ~p but registered at ~p",
Expand All @@ -90,3 +92,16 @@ handle_info(_, State) -> {noreply, State}.
%%% Internals
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
task(Player) -> bo_task:describe(bo_players:task(Player)).

test(Player, Solution) ->
case bo_task:test(bo_players:task(Player), Solution) of
ok -> next_task(Player);
NOK -> NOK
end.

next_task(Player) ->
NewPlayer = bo_players_repo:advance(Player),
case bo_players:task(NewPlayer) of
undefined -> the_end;
Task -> {ok, bo_task:describe(Task)}
end.
21 changes: 20 additions & 1 deletion src/models/bo_players.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
-opaque player() ::
#{ name := name()
, node := node()
, task := module()
, task := module() | undefined
, done := [module()]
, created_at => calendar:datetime()
}.

Expand All @@ -27,6 +28,9 @@
[ new/2
, node/1
, task/1
, done/1
, finish/1
, task/2
]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand All @@ -40,6 +44,7 @@ sumo_schema() ->
[ sumo:new_field(name, string, [id, unique])
, sumo:new_field(node, custom, [not_null])
, sumo:new_field(task, custom, [])
, sumo:new_field(done, custom, [not_null])
, sumo:new_field(created_at, datetime, [not_null])
]).

Expand All @@ -57,6 +62,7 @@ new(Name, Node) ->
#{ name => Name
, node => Node
, task => bo_tasks:first()
, done => []
, created_at => calendar:universal_time()
}.

Expand All @@ -65,3 +71,16 @@ node(#{node := Node}) -> Node.

-spec task(player()) -> module().
task(#{task := Task}) -> Task.

-spec done(player()) -> [module()].
done(#{done := Done}) -> Done.

-spec finish(player()) -> player().
finish(Player) ->
#{task := Task, done := Done} = Player,
Player#{task := undefined, done := [Task|Done]}.

-spec task(player(), module()) -> player().
task(Player, NextTask) ->
#{task := Task, done := Done} = Player,
Player#{task := NextTask, done := [Task|Done]}.
13 changes: 12 additions & 1 deletion src/repos/bo_players_repo.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(bo_players_repo).

-export([signup/2, fetch/1]).
-export([signup/2, fetch/1, advance/1]).

%% @throws conflict
-spec signup(bo_players:name(), node()) -> bo_players:player().
Expand All @@ -16,3 +16,14 @@ signup(PlayerName, Node) ->

-spec fetch(bo_players:name()) -> bo_players:player() | notfound.
fetch(PlayerName) -> sumo:find(bo_players, PlayerName).

-spec advance(bo_players:player()) -> bo_players:player().
advance(Player) ->
DoneTasks = [bo_players:task(Player) | bo_players:done(Player)],
case bo_tasks:all() -- DoneTasks of
[] -> sumo:persist(bo_players, bo_players:finish(Player));
RemainingTasks ->
NextTask =
lists:nth(rand:uniform(length(RemainingTasks)), RemainingTasks),
sumo:persist(bo_players, bo_players:task(Player, NextTask))
end.
3 changes: 2 additions & 1 deletion src/tasks/bo_task.erl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ do_test(Task, Fun) ->
ok -> false;
{error, Msg} -> {true, Msg}
catch
_:Exception -> {true, {Exception, erlang:get_stacktrace()}}
_:Exception ->
{true, #{error => Exception, stack => erlang:get_stacktrace()}}
end
end, Task:tests()).
16 changes: 13 additions & 3 deletions test/bo_invalid_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
, invalid_user/1
, test_timeout/1
, test_fails/1
, test_error/1
]).

-type config() :: proplists:proplist().

-spec all() -> [atom()].
all() -> [invalid_input, invalid_user, test_timeout, test_fails].
all() -> [invalid_input, invalid_user, test_timeout, test_fails, test_error].

-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
Expand Down Expand Up @@ -80,9 +81,18 @@ test_fails(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),

ct:comment("Providing a function that fails the tests returns errors"),
{failures, Failures} = submit(Client, fun(X) -> {'not', X} end),
{failures, [_|_]} = submit(Client, fun(X) -> {'not', X} end),

ct:log("~p", [Failures]),
{comment, ""}.

-spec test_error(config()) -> {comment, string()}.
test_error(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),

ct:comment("Providing a function that errors the tests returns errors"),
{failures, Failures} = submit(Client, fun(X) -> X / 2 end),

[_|_] = [Err || #{error := Err, stack := _} <- Failures],

{comment, ""}.

Expand Down
9 changes: 4 additions & 5 deletions test/bo_test_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
-export([signup/2, task/2, submit/3]).
-export([gen_call/2]).

-type task() :: #{ name := module()
, desc := binary()
, test := fun((fun()) -> term())
}.
-type task() :: bo_task:task().
-type player_name() :: binary().

-spec start(atom()) -> {ok, node()}.
Expand All @@ -32,7 +29,9 @@ signup(Node, Player) -> call(Node, {signup, Player}).
task(Node, Player) -> call(Node, {task, Player}).

-spec submit(node(), player_name(), term()) ->
bo_task:result() | {error, forbidden | notfound}.
{ok, bo_task:task()} | the_end
| {error, invalid | timeout | forbidden | notfound}
| {failures, [term()]}.
submit(Node, Player, Solution) -> call(Node, {submit, Player, Solution}).

call(Node, Msg) ->
Expand Down
109 changes: 109 additions & 0 deletions test/bo_valid_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
-module(bo_valid_SUITE).
-author('elbrujohalcon@inaka.net').

-export([all/0]).
-export([init_per_suite/1, end_per_suite/1]).
-export([ next_task/1
, next_task_is_random/1
, no_more_tasks/1
]).

-type config() :: proplists:proplist().

-spec all() -> [atom()].
all() -> [next_task, next_task_is_random, no_more_tasks].

-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
case net_kernel:start(['bo_test@127.0.0.1']) of
{ok, _} -> ok;
{error, {already_started, _}} -> ok;
{error, Error} -> throw(Error)
end,
_ = application:load(beam_olympics),
application:set_env(
beam_olympics, all_tasks, [bo_first_task, simple_task1, simple_task2]),
{ok, _} = bo:start(),
_ = sumo:delete_all(bo_players),
{ok, Client} = bo_test_client:start(invalid_suite),
[{client, Client} | Config].

-spec end_per_suite(config()) -> config().
end_per_suite(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),
ok = bo_test_client:stop(Client),
_ = sumo:delete_all(bo_players),
application:unset_env(beam_olympics, all_tasks),
ok = bo:stop(),
Config.

-spec next_task(config()) -> {comment, string()}.
next_task(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),
{ok, FirstTask} = bo_test_client:signup(Client, <<"next_task">>),

ct:comment("The initial task can be solved"),
#{name := bo_first_task} = FirstTask,
{ok, NextTask} = bo_test_client:submit(Client, <<"next_task">>, fun id/1),

ct:comment("A new task is provided"),
case NextTask of
FirstTask -> ct:fail("Different task expected");
NextTask -> ok
end,

{comment, ""}.

-spec next_task_is_random(config()) -> {comment, string()}.
next_task_is_random(Config) ->
Players = [<<Char>> || Char <- lists:seq($a, $z)],
{client, Client} = lists:keyfind(client, 1, Config),

ct:comment("The initial task is the same for all players"),
[{ok, FirstTask}] =
lists:usort([bo_test_client:signup(Client, Player) || Player <- Players]),
#{name := bo_first_task} = FirstTask,

ct:comment("Every player can solve that task"),
[{ok, NextTask1}, {ok, NextTask2}] =
lists:usort(
[bo_test_client:submit(Client, Player, fun id/1) || Player <- Players]),

ct:comment("Tasks come from the list"),
case {NextTask1, NextTask2} of
{#{name := simple_task1}, #{name := simple_task2}} -> ok;
{#{name := simple_task2}, #{name := simple_task1}} -> ok;
{NextTask1, NextTask2} ->
ct:fail("Unexpected tasks: ~p", [{NextTask1, NextTask2}])
end,

{comment, ""}.

-spec no_more_tasks(config()) -> {comment, string()}.
no_more_tasks(Config) ->
{client, Client} = lists:keyfind(client, 1, Config),
{ok, FirstTask} = bo_test_client:signup(Client, <<"nmt">>),

ct:comment("The initial task can be solved"),
#{name := bo_first_task} = FirstTask,
{ok, #{name := NextTask}} =
bo_test_client:submit(Client, <<"nmt">>, fun id/1),

ct:comment("The next task can be solved"),
ExpectedTask =
case NextTask of
simple_task1 -> simple_task2;
simple_task2 -> simple_task1
end,
{ok, #{name := ExpectedTask}} =
bo_test_client:submit(Client, <<"nmt">>, fun NextTask:solution/1),

ct:comment("Solving the final task user gets the end message"),
the_end =
bo_test_client:submit(Client, <<"nmt">>, fun ExpectedTask:solution/1),

{comment, ""}.

id(X) ->
ct:log("id evaluated for ~p", [X]),
X.
31 changes: 31 additions & 0 deletions test/simple_task1.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-module(simple_task1).

-behaviour(bo_task).

-export([description/0, expected_arity/0, timeout/0, tests/0, solution/1]).

-spec description() -> binary().
description() -> <<"Echo: Always return 1">>.

-spec expected_arity() -> 1.
expected_arity() -> 1.

-spec timeout() -> 1000.
timeout() -> 1000.

-spec tests() -> [bo_task:test()].
tests() -> [build_test(Input) || Input <- [a, 1, 2.14, #{}]].

build_test(Something) ->
fun(Fun) ->
case Fun(Something) of
1 -> ok;
SomethingElse -> {error, #{ input => Something
, output => SomethingElse
, expected => 1
}}
end
end.

-spec solution(_) -> 1.
solution(_) -> 1.
31 changes: 31 additions & 0 deletions test/simple_task2.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-module(simple_task2).

-behaviour(bo_task).

-export([description/0, expected_arity/0, timeout/0, tests/0, solution/1]).

-spec description() -> binary().
description() -> <<"Echo: Always return 2">>.

-spec expected_arity() -> 1.
expected_arity() -> 1.

-spec timeout() -> 1000.
timeout() -> 1000.

-spec tests() -> [bo_task:test()].
tests() -> [build_test(Input) || Input <- [a, 1, 2.14, #{}]].

build_test(Something) ->
fun(Fun) ->
case Fun(Something) of
2 -> ok;
SomethingElse -> {error, #{ input => Something
, output => SomethingElse
, expected => 2
}}
end
end.

-spec solution(_) -> 2.
solution(_) -> 2.