From 5a9cdd3cc89507d4d74f8bded56ce5e037b3b56e Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Fri, 23 Feb 2024 07:08:18 +0100 Subject: wip --- learn-you-some-erlang/reminder/Emakefile | 4 + .../ebin/.this-file-intentionally-left-blank | 1 + .../include/.this-file-intentionally-left-blank | 1 + .../priv/.this-file-intentionally-left-blank | 1 + .../src/.this-file-intentionally-left-blank | 1 + learn-you-some-erlang/reminder/src/dev_event.erl | 82 +++++++++ learn-you-some-erlang/reminder/src/event.erl | 61 +++++++ learn-you-some-erlang/reminder/src/evserv.erl | 168 +++++++++++++++++ learn-you-some-erlang/reminder/src/sup.erl | 22 +++ .../reminder/src/tests/dev_event_tests.erl | 41 +++++ .../reminder/src/tests/event_tests.erl | 43 +++++ .../reminder/src/tests/evserv_tests.erl | 203 +++++++++++++++++++++ .../reminder/src/tests/sup_tests.erl | 25 +++ 13 files changed, 653 insertions(+) create mode 100644 learn-you-some-erlang/reminder/Emakefile create mode 100644 learn-you-some-erlang/reminder/ebin/.this-file-intentionally-left-blank create mode 100644 learn-you-some-erlang/reminder/include/.this-file-intentionally-left-blank create mode 100644 learn-you-some-erlang/reminder/priv/.this-file-intentionally-left-blank create mode 100644 learn-you-some-erlang/reminder/src/.this-file-intentionally-left-blank create mode 100644 learn-you-some-erlang/reminder/src/dev_event.erl create mode 100644 learn-you-some-erlang/reminder/src/event.erl create mode 100644 learn-you-some-erlang/reminder/src/evserv.erl create mode 100644 learn-you-some-erlang/reminder/src/sup.erl create mode 100644 learn-you-some-erlang/reminder/src/tests/dev_event_tests.erl create mode 100644 learn-you-some-erlang/reminder/src/tests/event_tests.erl create mode 100644 learn-you-some-erlang/reminder/src/tests/evserv_tests.erl create mode 100644 learn-you-some-erlang/reminder/src/tests/sup_tests.erl (limited to 'learn-you-some-erlang/reminder') diff --git a/learn-you-some-erlang/reminder/Emakefile b/learn-you-some-erlang/reminder/Emakefile new file mode 100644 index 0000000..fbde9cb --- /dev/null +++ b/learn-you-some-erlang/reminder/Emakefile @@ -0,0 +1,4 @@ +{'src/*', [debug_info, + {i, "src"}, + {i, "include"}, + {outdir, "ebin"}]}. diff --git a/learn-you-some-erlang/reminder/ebin/.this-file-intentionally-left-blank b/learn-you-some-erlang/reminder/ebin/.this-file-intentionally-left-blank new file mode 100644 index 0000000..758f532 --- /dev/null +++ b/learn-you-some-erlang/reminder/ebin/.this-file-intentionally-left-blank @@ -0,0 +1 @@ +Thank you, Mercurial! I still love you... diff --git a/learn-you-some-erlang/reminder/include/.this-file-intentionally-left-blank b/learn-you-some-erlang/reminder/include/.this-file-intentionally-left-blank new file mode 100644 index 0000000..758f532 --- /dev/null +++ b/learn-you-some-erlang/reminder/include/.this-file-intentionally-left-blank @@ -0,0 +1 @@ +Thank you, Mercurial! I still love you... diff --git a/learn-you-some-erlang/reminder/priv/.this-file-intentionally-left-blank b/learn-you-some-erlang/reminder/priv/.this-file-intentionally-left-blank new file mode 100644 index 0000000..758f532 --- /dev/null +++ b/learn-you-some-erlang/reminder/priv/.this-file-intentionally-left-blank @@ -0,0 +1 @@ +Thank you, Mercurial! I still love you... diff --git a/learn-you-some-erlang/reminder/src/.this-file-intentionally-left-blank b/learn-you-some-erlang/reminder/src/.this-file-intentionally-left-blank new file mode 100644 index 0000000..758f532 --- /dev/null +++ b/learn-you-some-erlang/reminder/src/.this-file-intentionally-left-blank @@ -0,0 +1 @@ +Thank you, Mercurial! I still love you... diff --git a/learn-you-some-erlang/reminder/src/dev_event.erl b/learn-you-some-erlang/reminder/src/dev_event.erl new file mode 100644 index 0000000..5b752ac --- /dev/null +++ b/learn-you-some-erlang/reminder/src/dev_event.erl @@ -0,0 +1,82 @@ +%%% This module is there to test the incomplete loops and constructs +%%% that are presented in the text, but are not the final result. +-module(dev_event). +-compile(export_all). +-record(state, {server, + name="", + to_go=0}). + +start1(EventName, Delay) -> + spawn(?MODULE, init1, [self(), EventName, Delay]). + +start_link1(EventName, Delay) -> + spawn_link(?MODULE, init1, [self(), EventName, Delay]). + +start2(EventName, Delay) -> + spawn(?MODULE, init2, [self(), EventName, Delay]). + +start_link2(EventName, Delay) -> + spawn_link(?MODULE, init2, [self(), EventName, Delay]). + +cancel(Pid) -> + %% Monitor in case the process is already dead + Ref = erlang:monitor(process, Pid), + Pid ! {self(), Ref, cancel}, + receive + {Ref, ok} -> + erlang:demonitor(Ref, [flush]), + ok; + {'DOWN', Ref, process, Pid, _Reason} -> + ok + end. + + + +%%% Event's innards +init1(Server, EventName, Delay) -> + loop2(#state{server=Server, + name=EventName, + to_go=normalize(Delay)}). + +init2(Server, EventName, DateTime) -> + loop2(#state{server=Server, + name=EventName, + to_go=time_to_go(DateTime)}). + +loop1(S = #state{server=Server}) -> + receive + {Server, Ref, cancel} -> + Server ! {Ref, ok} + after S#state.to_go * 1000 -> + Server ! {done, S#state.name} + end. + +%% Loop uses a list for times in order to go around the ~49 days limit +%% on timeouts. +loop2(S = #state{server=Server, to_go=[T|Next]}) -> + receive + {Server, Ref, cancel} -> + Server ! {Ref, ok} + after T*1000 -> + if Next =:= [] -> + Server ! {done, S#state.name}; + Next =/= [] -> + loop2(S#state{to_go=Next}) + end + end. + + +time_to_go(TimeOut={{_,_,_}, {_,_,_}}) -> + Now = calendar:local_time(), + ToGo = calendar:datetime_to_gregorian_seconds(TimeOut) - + calendar:datetime_to_gregorian_seconds(Now), + Secs = if ToGo > 0 -> ToGo; + ToGo =< 0 -> 0 + end, + normalize(Secs). + +%% Because Erlang is limited to about 49 days (49*24*60*60*1000) in +%% milliseconds, the following function is used +normalize(N) -> + Limit = 49*24*60*60, + [N rem Limit | lists:duplicate(N div Limit, Limit)]. diff --git a/learn-you-some-erlang/reminder/src/event.erl b/learn-you-some-erlang/reminder/src/event.erl new file mode 100644 index 0000000..58b5b57 --- /dev/null +++ b/learn-you-some-erlang/reminder/src/event.erl @@ -0,0 +1,61 @@ +-module(event). +-export([start/2, start_link/2, cancel/1]). +-export([init/3, loop/1]). +-record(state, {server, + name="", + to_go=0}). + +%%% Public interface +start(EventName, DateTime) -> + spawn(?MODULE, init, [self(), EventName, DateTime]). + +start_link(EventName, DateTime) -> + spawn_link(?MODULE, init, [self(), EventName, DateTime]). + +cancel(Pid) -> + %% Monitor in case the process is already dead + Ref = erlang:monitor(process, Pid), + Pid ! {self(), Ref, cancel}, + receive + {Ref, ok} -> + erlang:demonitor(Ref, [flush]), + ok; + {'DOWN', Ref, process, Pid, _Reason} -> + ok + end. + +%%% Event's innards +init(Server, EventName, DateTime) -> + loop(#state{server=Server, + name=EventName, + to_go=time_to_go(DateTime)}). + +%% Loop uses a list for times in order to go around the ~49 days limit +%% on timeouts. +loop(S = #state{server=Server, to_go=[T|Next]}) -> + receive + {Server, Ref, cancel} -> + Server ! {Ref, ok} + after T*1000 -> + if Next =:= [] -> + Server ! {done, S#state.name}; + Next =/= [] -> + loop(S#state{to_go=Next}) + end + end. + +%%% private functions +time_to_go(TimeOut={{_,_,_}, {_,_,_}}) -> + Now = calendar:local_time(), + ToGo = calendar:datetime_to_gregorian_seconds(TimeOut) - + calendar:datetime_to_gregorian_seconds(Now), + Secs = if ToGo > 0 -> ToGo; + ToGo =< 0 -> 0 + end, + normalize(Secs). + +%% Because Erlang is limited to about 49 days (49*24*60*60*1000) in +%% milliseconds, the following function is used +normalize(N) -> + Limit = 49*24*60*60, + [N rem Limit | lists:duplicate(N div Limit, Limit)]. diff --git a/learn-you-some-erlang/reminder/src/evserv.erl b/learn-you-some-erlang/reminder/src/evserv.erl new file mode 100644 index 0000000..1087376 --- /dev/null +++ b/learn-you-some-erlang/reminder/src/evserv.erl @@ -0,0 +1,168 @@ +%% Event server +-module(evserv). +-compile(export_all). + +-record(state, {events, %% list of #event{} records + clients}). %% list of Pids + +-record(event, {name="", + description="", + pid, + timeout={{1970,1,1},{0,0,0}}}). + +%%% User Interface + +start() -> + register(?MODULE, Pid=spawn(?MODULE, init, [])), + Pid. + +start_link() -> + register(?MODULE, Pid=spawn_link(?MODULE, init, [])), + Pid. + +terminate() -> + ?MODULE ! shutdown. + +init() -> + %% Loading events from a static file could be done here. + %% You would need to pass an argument to init (maybe change the functions + %% start/0 and start_link/0 to start/1 and start_link/1) telling where the + %% resource to find the events is. Then load it from here. + %% Another option is to just pass the event straight to the server + %% through this function. + loop(#state{events=orddict:new(), + clients=orddict:new()}). + +subscribe(Pid) -> + Ref = erlang:monitor(process, whereis(?MODULE)), + ?MODULE ! {self(), Ref, {subscribe, Pid}}, + receive + {Ref, ok} -> + {ok, Ref}; + {'DOWN', Ref, process, _Pid, Reason} -> + {error, Reason} + after 5000 -> + {error, timeout} + end. + +add_event(Name, Description, TimeOut) -> + Ref = make_ref(), + ?MODULE ! {self(), Ref, {add, Name, Description, TimeOut}}, + receive + {Ref, Msg} -> Msg + after 5000 -> + {error, timeout} + end. + +add_event2(Name, Description, TimeOut) -> + Ref = make_ref(), + ?MODULE ! {self(), Ref, {add, Name, Description, TimeOut}}, + receive + {Ref, {error, Reason}} -> erlang:error(Reason); + {Ref, Msg} -> Msg + after 5000 -> + {error, timeout} + end. + +cancel(Name) -> + Ref = make_ref(), + ?MODULE ! {self(), Ref, {cancel, Name}}, + receive + {Ref, ok} -> ok + after 5000 -> + {error, timeout} + end. + +listen(Delay) -> + receive + M = {done, _Name, _Description} -> + [M | listen(0)] + after Delay*1000 -> + [] + end. + +%%% The Server itself + +loop(S=#state{}) -> + receive + {Pid, MsgRef, {subscribe, Client}} -> + Ref = erlang:monitor(process, Client), + NewClients = orddict:store(Ref, Client, S#state.clients), + Pid ! {MsgRef, ok}, + loop(S#state{clients=NewClients}); + {Pid, MsgRef, {add, Name, Description, TimeOut}} -> + case valid_datetime(TimeOut) of + true -> + EventPid = event:start_link(Name, TimeOut), + NewEvents = orddict:store(Name, + #event{name=Name, + description=Description, + pid=EventPid, + timeout=TimeOut}, + S#state.events), + Pid ! {MsgRef, ok}, + loop(S#state{events=NewEvents}); + false -> + Pid ! {MsgRef, {error, bad_timeout}}, + loop(S) + end; + {Pid, MsgRef, {cancel, Name}} -> + Events = case orddict:find(Name, S#state.events) of + {ok, E} -> + event:cancel(E#event.pid), + orddict:erase(Name, S#state.events); + error -> + S#state.events + end, + Pid ! {MsgRef, ok}, + loop(S#state{events=Events}); + {done, Name} -> + case orddict:find(Name, S#state.events) of + {ok, E} -> + send_to_clients({done, E#event.name, E#event.description}, + S#state.clients), + NewEvents = orddict:erase(Name, S#state.events), + loop(S#state{events=NewEvents}); + error -> + %% This may happen if we cancel an event and + %% it fires at the same time + loop(S) + end; + shutdown -> + exit(shutdown); + {'DOWN', Ref, process, _Pid, _Reason} -> + loop(S#state{clients=orddict:erase(Ref, S#state.clients)}); + code_change -> + ?MODULE:loop(S); + {Pid, debug} -> %% used as a hack to let me do some unit testing + Pid ! S, + loop(S); + Unknown -> + io:format("Unknown message: ~p~n",[Unknown]), + loop(S) + end. + + +%%% Internal Functions +send_to_clients(Msg, ClientDict) -> + orddict:map(fun(_Ref, Pid) -> Pid ! Msg end, ClientDict). + +valid_datetime({Date,Time}) -> + try + calendar:valid_date(Date) andalso valid_time(Time) + catch + error:function_clause -> %% not in {{Y,M,D},{H,Min,S}} format + false + end; +valid_datetime(_) -> + false. + +%% calendar has valid_date, but nothing for days. +%% This function is based on its interface. +%% Ugly, but ugh. +valid_time({H,M,S}) -> valid_time(H,M,S). + +valid_time(H,M,S) when H >= 0, H < 24, + M >= 0, M < 60, + S >= 0, S < 60 -> true; +valid_time(_,_,_) -> false. diff --git a/learn-you-some-erlang/reminder/src/sup.erl b/learn-you-some-erlang/reminder/src/sup.erl new file mode 100644 index 0000000..71a463b --- /dev/null +++ b/learn-you-some-erlang/reminder/src/sup.erl @@ -0,0 +1,22 @@ +-module(sup). +-export([start/2, start_link/2, init/1, loop/1]). + +start(Mod, Args) -> + spawn(?MODULE, init, [{Mod, Args}]). + +start_link(Mod,Args) -> + spawn_link(?MODULE, init, [{Mod, Args}]). + +init({Mod,Args}) -> + process_flag(trap_exit, true), + loop({Mod,start_link,Args}). + +loop({M,F,A}) -> + Pid = apply(M,F,A), + receive + {'EXIT', _From, shutdown} -> + exit(shutdown); % will kill the child too + {'EXIT', Pid, Reason} -> + io:format("Process ~p exited for reason ~p~n",[Pid,Reason]), + loop({M,F,A}) + end. diff --git a/learn-you-some-erlang/reminder/src/tests/dev_event_tests.erl b/learn-you-some-erlang/reminder/src/tests/dev_event_tests.erl new file mode 100644 index 0000000..5de18d1 --- /dev/null +++ b/learn-you-some-erlang/reminder/src/tests/dev_event_tests.erl @@ -0,0 +1,41 @@ +-module(dev_event_tests). +-include_lib("eunit/include/eunit.hrl"). +%%% Very minimal tests, only verifying that the two implementations of +%%% timeouts work the same in their final and primitive versions +%%% as in the final module. The rest is rather cruft to emulate the normal +%%% event.erl module. Little is tested, because little needs to be tested. + + +-record(state, {server, + name="", + to_go=[0]}). + +timeout_test_() -> + {inorder, + [fun() -> timeout(loop1, 2) end, + fun() -> timeout(loop2, [2]) end]}. + +timeout(AtomFun, T) -> + S = self(), + spawn_link(dev_event, AtomFun, [#state{server=S, name="test", to_go=T}]), + timer:sleep(1000), + M1 = receive A -> A after 0 -> timeout end, + timer:sleep(1500), + M2 = receive B -> B after 0 -> timeout end, + M3 = receive C -> C after 0 -> timeout end, + ?assertEqual(timeout, M1), + ?assertEqual({done, "test"}, M2), + ?assertEqual(timeout, M3). + +cancel_msg_test_() -> + {inorder, + [fun() -> cancel_msg(loop1, 2) end, + fun() -> cancel_msg(loop2, [2]) end]}. + +cancel_msg(AtomFun, T) -> + S = self(), + R = make_ref(), + Pid = spawn_link(dev_event, AtomFun, [#state{server=S, name="test", to_go=T}]), + Pid ! {S, R, cancel}, + M = receive A -> A after 500 -> timeout end, + ?assertEqual({R, ok}, M). diff --git a/learn-you-some-erlang/reminder/src/tests/event_tests.erl b/learn-you-some-erlang/reminder/src/tests/event_tests.erl new file mode 100644 index 0000000..80b3a44 --- /dev/null +++ b/learn-you-some-erlang/reminder/src/tests/event_tests.erl @@ -0,0 +1,43 @@ +-module(event_tests). +-include_lib("eunit/include/eunit.hrl"). +-test_warnings([start/0, start_link/1, init/0, time_to_go/1]). +%% defined in event.erl +-record(state, {server, + name="", + to_go=[0]}). + +timeout_test_() -> + S = self(), + spawn_link(event, loop, [#state{server=S, name="test", to_go=[2]}]), + timer:sleep(1000), + M1 = receive A -> A after 0 -> timeout end, + timer:sleep(1500), + M2 = receive B -> B after 0 -> timeout end, + M3 = receive C -> C after 0 -> timeout end, + [?_assertEqual(timeout, M1), + ?_assertEqual({done, "test"}, M2), + ?_assertEqual(timeout, M3)]. + +cancel_msg_test_() -> + S = self(), + R = make_ref(), + Pid = spawn_link(event, loop, [#state{server=S, name="test", to_go=[2]}]), + Pid ! {S, R, cancel}, + M = receive A -> A after 500 -> timeout end, + [?_assertEqual({R, ok}, M)]. + +cancel_fn_test_() -> + S = self(), + Pid = spawn_link(event, loop, [#state{server=S, name="test", to_go=[2]}]), + [?_assertEqual(ok, event:cancel(Pid)), + %% calling cancel again should fail, but still return ok. + ?_assertEqual(ok, event:cancel(Pid))]. + +normalize_test_() -> + [?_assertEqual([0], event:normalize(0)), + ?_assertEqual([2], event:normalize(2)), + %% special cases w/ remainders + ?_assertEqual(1, length(event:normalize(49*24*60*59))), + ?_assertEqual(2, length(event:normalize(49*24*60*60))), + ?_assertEqual(2, length(event:normalize(49*24*60*60+1))), + ?_assertEqual(1000*24*60*60, lists:sum(event:normalize(1000*24*60*60)))]. diff --git a/learn-you-some-erlang/reminder/src/tests/evserv_tests.erl b/learn-you-some-erlang/reminder/src/tests/evserv_tests.erl new file mode 100644 index 0000000..b093a58 --- /dev/null +++ b/learn-you-some-erlang/reminder/src/tests/evserv_tests.erl @@ -0,0 +1,203 @@ +-module(evserv_tests). +-include_lib("eunit/include/eunit.hrl"). +-define(FUTURE_DATE, {{2100,1,1},{0,0,0}}). +%% all the tests in this module would be much easier with property-based testing. +%% see Triq, Quvic Quickcheck or Proper for libraries to download that let you +%% do this. +-test_warnings([start/0]). + +valid_time_test_() -> + [?_assert(evserv:valid_time({0,0,0})), + ?_assert(evserv:valid_time({23,59,59})), + ?_assert(not evserv:valid_time({23,59,60})), + ?_assert(not evserv:valid_time({23,60,59})), + ?_assert(not evserv:valid_time({24,59,59})), + ?_assert(not evserv:valid_time({-1,0,0})), + ?_assert(not evserv:valid_time({0,-1,0})), + ?_assert(not evserv:valid_time({0,0,-1}))]. + +valid_datetime_test_() -> + [?_assert(evserv:valid_datetime({{0,1,1},{0,0,0}})), + ?_assert(evserv:valid_datetime({{2004,2,29},{23,59,59}})), + ?_assert(evserv:valid_datetime({{2004,12,31},{23,59,59}})), + ?_assert(not evserv:valid_datetime({{2004,12,31},{23,60,60}})), + ?_assert(not evserv:valid_datetime({{2003,2,29},{23,59,59}})), + ?_assert(not evserv:valid_datetime({{0,0,0},{0,0,0}})), + ?_assert(not evserv:valid_datetime(1209312303))]. + +loop_test_() -> + {"Testing all server events on a protocol level", + [{"Subscribe Tests", + [{spawn, + {setup, + fun evserv:start_link/0, + fun terminate/1, + fun subscribe/1}}, + {spawn, + {setup, + fun evserv:start_link/0, + fun terminate/1, + fun subscribe2/1}}, + {spawn, + {setup, + fun evserv:start_link/0, + fun terminate/1, + fun down/1}}]}, + {"Adding event integration tests", + [{spawn, + {setup, + fun evserv:start_link/0, + fun terminate/1, + fun add/1}}, + {spawn, + {setup, + fun evserv:start_link/0, + fun terminate/1, + fun addremove/1}}, + {spawn, + {setup, + fun evserv:start_link/0, + fun terminate/1, + fun done/1}}]}]}. + +interface_test_() -> + {"Testing all server events via the interface functions", + [{spawn, + {setup, + fun evserv:start_link/0, + fun terminate/1, + fun interface/1}}]}. + +subscribe(Pid) -> + Ref = make_ref(), + S = self(), + Pid ! {S, Ref, {subscribe, S}}, + {ok, M1} = read(), + Pid ! {S, debug}, + {ok, M2} = read(), + [?_assertEqual({Ref, ok}, M1), + ?_assertMatch({state,[], [{_, S}]}, M2)]. + +subscribe2(Pid) -> + Ref1 = make_ref(), + Ref2 = make_ref(), + S = self(), + Pid ! {S, Ref1, {subscribe, S}}, + {ok, M1} = read(), + Pid ! {S, Ref2, {subscribe, S}}, + {ok, M2} = read(), + Pid ! {S, debug}, + {ok, M3} = read(), + [?_assertEqual({Ref1, ok}, M1), + ?_assertEqual({Ref2, ok}, M2), + ?_assertMatch({state,[], [{_,S},{_,S}]}, M3)]. + +down(Pid) -> + Ref = make_ref(), + ClientPid = spawn(fun() -> timer:sleep(50000) end), + Pid ! {self(), debug}, + {ok, S1} = read(), + Pid ! {self(), Ref, {subscribe, ClientPid}}, + {ok, M1} = read(), + Pid ! {self(), debug}, + {ok, S2} = read(), + exit(ClientPid, testkill), + timer:sleep(100), + Pid ! {self(), debug}, + {ok, S3} = read(), + [?_assertMatch({state, _, []}, S1), + ?_assertEqual({Ref, ok}, M1), + ?_assertMatch({state, _, [{_,ClientPid}]}, S2), + ?_assertEqual(S1, S3)]. + +add(Pid) -> + Ref1 = make_ref(), + Ref2 = make_ref(), + Pid ! {self(), Ref1, {add, "test", "a test event", ?FUTURE_DATE}}, + {ok, M1} = read(), + Pid ! {self(), Ref2, {add, "test", "a test event2", wrong_date}}, + {ok, M2} = read(), + [?_assertEqual({Ref1, ok}, M1), + ?_assertEqual({Ref2, {error, bad_timeout}}, M2)]. + +addremove(Pid) -> + Ref1 = make_ref(), + Ref2 = make_ref(), + Ref3 = make_ref(), + %% ask for useless deletion, check the state + Pid ! {self(), Ref1, {cancel, "nonexist"}}, + {ok, M1} = read(), + Pid ! {self(), debug}, + {ok, State1} = read(), + %% add an event, check the state + Pid ! {self(), Ref2, {add, "test", "a test event", ?FUTURE_DATE}}, + {ok, M2} = read(), + Pid ! {self(), debug}, + {ok, State2} = read(), + %% remove the event, check the state + Pid ! {self(), Ref3, {cancel, "test"}}, + {ok, M3} = read(), + Pid ! {self(), debug}, + {ok, State3} = read(), + [?_assertEqual({Ref1, ok}, M1), + ?_assertEqual({Ref2, ok}, M2), + ?_assertEqual({Ref3, ok}, M3), + ?_assertEqual(State1, State3), + ?_assert(State2 =/= State3), + ?_assertMatch({state,[{"test", {event,"test",_,_,?FUTURE_DATE}}],_}, State2)]. + +done(Pid) -> + Ref = make_ref(), + {ok, _} = evserv:subscribe(self()), + Pid ! {self(), debug}, + {ok, S1} = read(), + {Date,{H,Min,S}} = calendar:local_time(), + DateTime = {Date,{H,Min,S+1}}, + Pid ! {self(), Ref, {add, "test", "a test event", DateTime}}, + {ok, M1} = read(), + Pid ! {self(), debug}, + {ok, S2} = read(), + X = read(), + timer:sleep(750), + {ok, M2} = read(), + Pid ! {self(), debug}, + {ok, S3} = read(), + [?_assertMatch({state, [], _}, S1), + ?_assertEqual({Ref, ok}, M1), + ?_assertMatch({state, [{"test",_}], _}, S2), + ?_assertEqual(timeout, X), + ?_assertEqual({done, "test", "a test event"}, M2), + ?_assertEqual(S1,S3)]. + +interface(_Pid) -> + Ref = evserv:subscribe(self()), + {Date,{H,Min,S}} = calendar:local_time(), + M1 = evserv:add_event("test", "desc", {Date,{H,Min,S+1}}), + M2 = evserv:cancel("test"), + M3 = evserv:add_event("test1", "desc1", calendar:local_time()), + M4 = evserv:add_event2("test2", "desc2", calendar:local_time()), + timer:sleep(100), + M5 = evserv:listen(2), + M6 = evserv:add_event("test3", "desc3", some_atom), + M7 = (catch evserv:add_event2("test4", "desc4", some_atom)), + [?_assertMatch({ok, _}, Ref), + ?_assert(is_reference(element(2, Ref))), + ?_assertEqual(ok, M1), + ?_assertEqual(ok, M2), + ?_assertEqual(ok, M3), + ?_assertEqual(ok, M4), + ?_assertEqual([{done,"test1","desc1"},{done,"test2","desc2"}], M5), + ?_assertEqual({error, bad_timeout}, M6), + ?_assertMatch({'EXIT', {bad_timeout,_}}, M7)]. + + +%% helpers +terminate(Pid) -> Pid ! shutdown. + +read() -> + receive + M -> {ok, M} + after 500 -> + timeout + end. + diff --git a/learn-you-some-erlang/reminder/src/tests/sup_tests.erl b/learn-you-some-erlang/reminder/src/tests/sup_tests.erl new file mode 100644 index 0000000..28d7fa9 --- /dev/null +++ b/learn-you-some-erlang/reminder/src/tests/sup_tests.erl @@ -0,0 +1,25 @@ +-module(sup_tests). +-include_lib("eunit/include/eunit.hrl"). + +restart_test_() -> + {"Test that everything restarts until a kill", + {setup, + fun() -> sup:start(evserv, []) end, + fun(_) -> ok end, + fun restart/1}}. + +restart(_SupPid) -> + timer:sleep(100), + A = is_pid(whereis(evserv)), + catch exit(whereis(evserv), die), + timer:sleep(100), + B = is_pid(whereis(evserv)), + catch exit(whereis(evserv), die), + timer:sleep(100), + C = is_pid(whereis(evserv)), + catch exit(whereis(evserv), shutdown), + timer:sleep(500), + D = is_pid(whereis(evserv)), + ?_assertEqual([true,true,true,false], + [A,B,C,D]). + -- cgit v1.2.3