1%% Licensed under the Apache License, Version 2.0 (the "License"); you may
2%% not use this file except in compliance with the License. You may obtain
3%% a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>
4%%
5%% Unless required by applicable law or agreed to in writing, software
6%% distributed under the License is distributed on an "AS IS" BASIS,
7%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8%% See the License for the specific language governing permissions and
9%% limitations under the License.
10%%
11%% Alternatively, you may use this file under the terms of the GNU Lesser
12%% General Public License (the "LGPL") as published by the Free Software
13%% Foundation; either version 2.1, or (at your option) any later version.
14%% If you wish to allow use of your version of this file only under the
15%% terms of the LGPL, you should delete the provisions above and replace
16%% them with the notice and other provisions required by the LGPL; see
17%% <http://www.gnu.org/licenses/>. If you do not delete the provisions
18%% above, a recipient may use your version of this file under the terms of
19%% either the Apache License or the LGPL.
20%%
21%% @author Richard Carlsson <carlsson.richard@gmail.com>
22%% @copyright 2006 Richard Carlsson
23%% @private
24%% @see eunit
25%% @doc Test runner process tree functions
26
27-module(eunit_proc).
28
29-include("eunit.hrl").
30-include("eunit_internal.hrl").
31
32-export([start/4]).
33
34%% This must be exported; see new_group_leader/1 for details.
35-export([group_leader_process/1]).
36
37-record(procstate, {ref, id, super, insulator, parent, order}).
38
39
40%% Spawns test process and returns the process Pid; sends {done,
41%% Reference, Pid} to caller when finished. See the function
42%% wait_for_task/2 for details about the need for the reference.
43%%
44%% The `Super' process receives a stream of status messages; see
45%% message_super/3 for details.
46
47start(Tests, Order, Super, Reference)
48  when is_pid(Super), is_reference(Reference) ->
49    St = #procstate{ref = Reference,
50		    id = [],
51		    super = Super,
52		    order = Order},
53    spawn_group(local, #group{tests = Tests}, St).
54
55
56%% Status messages sent to the supervisor process. (A supervisor does
57%% not have to act on these messages - it can e.g. just log them, or
58%% even discard them.) Each status message has the following form:
59%%
60%%   {status, Id, Info}
61%%
62%% where Id identifies the item that the message pertains to, and the
63%% Info part can be one of:
64%%
65%%   {progress, 'begin', {test | group, Data}}
66%%       indicates that the item has been entered, and what type it is;
67%%       Data is [{desc,binary()}, {source,Source}, {line,integer()}] for
68%%       a test, and [{desc,binary()}, {spawn,SpawnType},
69%%       {order,OrderType}] for a group.
70%%
71%%   {progress, 'end', {Status, Data}}
72%%       Status = 'ok' | {error, Exception} | {skipped, Cause} | integer()
73%%       Data = [{time,integer()}, {output,binary()}]
74%%
75%%       where Time is measured in milliseconds and Output is the data
76%%       written to the standard output stream during the test; if
77%%       Status is {skipped, Cause}, then Cause is a term thrown from
78%%       eunit_test:run_testfun/1. For a group item, the Status field is
79%%       the number of immediate subitems of the group; this helps the
80%%       collation of results. Failure for groups is always signalled
81%%       through a cancel message, not through the Status field.
82%%
83%%   {cancel, Descriptor}
84%%       where Descriptor can be:
85%%           timeout            a timeout occurred
86%%           {blame, Id}        forced to terminate because of item `Id'
87%%           {abort, Cause}     the test or group failed to execute
88%%           {exit, Reason}     the test process terminated unexpectedly
89%%           {startup, Reason}  failed to start a remote test process
90%%
91%%       where Cause is a term thrown from eunit_data:enter_context/4 or
92%%       from eunit_data:iter_next/2, and Reason is an exit term from a
93%%       crashed process
94%%
95%% Note that due to concurrent (and possibly distributed) execution,
96%% there are *no* strict ordering guarantees on the status messages,
97%% with one exception: a 'begin' message will always arrive before its
98%% corresponding 'end' message.
99
100message_super(Id, Info, St) ->
101    St#procstate.super ! {status, Id, Info}.
102
103
104%% @TODO implement synchronized mode for insulator/child execution
105
106%% Ideas for synchronized mode:
107%%
108%% * At each "program point", i.e., before entering a test, entering a
109%% group, or leaving a group, the child will synchronize with the
110%% insulator to make sure it is ok to proceed.
111%%
112%% * The insulator can receive controlling messages from higher up in
113%% the hierarchy, telling it to pause, resume, single-step, repeat, etc.
114%%
115%% * Synchronization on entering/leaving groups is necessary in order to
116%% get control over things such as subprocess creation/termination and
117%% setup/cleanup, making it possible to, e.g., repeat all the tests
118%% within a particular subprocess without terminating and restarting it,
119%% or repeating tests without repeating the setup/cleanup.
120%%
121%% * Some tests that depend on state will not be possible to repeat, but
122%% require a fresh context setup. There is nothing that can be done
123%% about this, and the many tests that are repeatable should not be
124%% punished because of it. The user must decide which level to restart.
125%%
126%% * Question: How propagate control messages down the hierarchy
127%% (preferably only to the correct insulator process)? An insulator does
128%% not currenctly know whether its child process has spawned subtasks.
129%% (The "supervisor" process does not know the Pids of the controlling
130%% insulator processes in the tree, and it probably should not be
131%% responsible for this anyway.)
132
133
134%% ---------------------------------------------------------------------
135%% Process tree primitives
136
137%% A "task" consists of an insulator process and a child process which
138%% handles the actual work. When the child terminates, the insulator
139%% process sends {done, Reference, self()} to the process which started
140%% the task (the "parent"). The child process is given a State record
141%% which contains the process id:s of the parent, the insulator, and the
142%% supervisor.
143
144%% @spec (Type, (#procstate{}) -> () -> term(), #procstate{}) -> pid()
145%%   Type = local | {remote, Node::atom()}
146
147start_task(Type, Fun, St0) ->
148    St = St0#procstate{parent = self()},
149    %% (note: the link here is mainly to propagate signals *downwards*,
150    %% so that the insulator can detect if the process that started the
151    %% task dies before the task is done)
152    F = fun () -> insulator_process(Type, Fun, St) end,
153    case Type of
154	local ->
155	    %% we assume (at least for now) that local spawns can never
156	    %% fail in such a way that the process does not start, so a
157	    %% new local insulator does not need to synchronize here
158	    spawn_link(F);
159	{remote, Node} ->
160	    Pid = spawn_link(Node, F),
161	    %% See below for the need for the {ok, Reference, Pid}
162	    %% message.
163	    Reference = St#procstate.ref,
164	    Monitor = erlang:monitor(process, Pid),
165	    %% (the DOWN message is guaranteed to arrive after any
166	    %% messages sent by the process itself)
167	    receive
168		{ok, Reference, Pid} ->
169		    Pid;
170		{'DOWN', Monitor, process, Pid, Reason} ->
171		    %% send messages as if the insulator process was
172		    %% started, but terminated on its own accord
173		    Msg = {startup, Reason},
174		    message_super(St#procstate.id, {cancel, Msg}, St),
175		    self() ! {done, Reference, Pid}
176	    end,
177	    erlang:demonitor(Monitor, [flush]),
178	    Pid
179    end.
180
181%% Relatively simple, and hopefully failure-proof insulator process
182%% (This is cleaner than temporarily setting up the caller to trap
183%% signals, and does not affect the caller's mailbox or other state.)
184%%
185%% We assume that nobody does a 'kill' on an insulator process - if that
186%% should happen, the test framework will hang since the insulator will
187%% never send a reply; see below for more.
188%%
189%% Note that even if the insulator process itself never fails, it is
190%% still possible that it does not start properly, if it is spawned
191%% remotely (e.g., if the remote node is down). Therefore, remote
192%% insulators must always immediately send an {ok, Reference, self()}
193%% message to the parent as soon as it is spawned.
194
195%% @spec (Type, Fun::() -> term(), St::#procstate{}) -> ok
196%%  Type = local | {remote, Node::atom()}
197
198insulator_process(Type, Fun, St0) ->
199    process_flag(trap_exit, true),
200    Parent = St0#procstate.parent,
201    if Type =:= local -> ok;
202       true -> Parent ! {ok, St0#procstate.ref, self()}
203    end,
204    St = St0#procstate{insulator = self()},
205    Child = spawn_link(fun () -> child_process(Fun(St), St) end),
206    insulator_wait(Child, Parent, [], St).
207
208%% Normally, child processes exit with the reason 'normal' even if the
209%% executed tests failed (by throwing exceptions), since the tests are
210%% executed within a try-block. Child processes can terminate abnormally
211%% by the following reasons:
212%%   1) an error in the processing of the test descriptors (a malformed
213%%      descriptor, failure in a setup, cleanup or initialization, a
214%%      missing module or function, or a failing generator function);
215%%   2) an internal error in the test running framework itself;
216%%   3) receiving a non-trapped error signal as a consequence of running
217%%      test code.
218%% Those under point 1 are "expected errors", handled specially in the
219%% protocol, while the other two are unexpected errors. (Since alt. 3
220%% implies that the test neither reported success nor failure, it can
221%% never be considered "proper" behaviour of a test.) Abnormal
222%% termination is reported to the supervisor process but otherwise does
223%% not affect the insulator compared to normal termination. Child
224%% processes can also be killed abruptly by their insulators, in case of
225%% a timeout or if a parent process dies.
226%%
227%% The insulator is the group leader for the child process, and gets all
228%% of its standard I/O. The output is buffered and associated with the
229%% currently active test or group, and is sent along with the 'end'
230%% progress message when the test or group has finished.
231
232insulator_wait(Child, Parent, Buf, St) ->
233    receive
234	{child, Child, Id, {'begin', Type, Data}} ->
235	    message_super(Id, {progress, 'begin', {Type, Data}}, St),
236	    insulator_wait(Child, Parent, [[] | Buf], St);
237	{child, Child, Id, {'end', Status, Time}} ->
238	    Data = [{time, Time}, {output, lists:reverse(hd(Buf))}],
239	    message_super(Id, {progress, 'end', {Status, Data}}, St),
240	    insulator_wait(Child, Parent, tl(Buf), St);
241	{child, Child, Id, {skipped, Reason}} ->
242	    %% this happens when a subgroup fails to enter the context
243	    message_super(Id, {cancel, {abort, Reason}}, St),
244	    insulator_wait(Child, Parent, Buf, St);
245	{child, Child, Id, {abort, Cause}} ->
246	    %% this happens when the child code threw an internal
247	    %% eunit_abort; the child process has already exited
248	    exit_messages(Id, {abort, Cause}, St),
249	    %% no need to wait for the {'EXIT',Child,_} message
250	    terminate_insulator(St);
251	{io_request, Child, ReplyAs, Req} ->
252	    %% we only collect output from the child process itself, not
253	    %% from secondary processes, otherwise we get race problems;
254	    %% however, each test runs its personal group leader that
255	    %% funnels all output - see the run_test() function
256	    Buf1 = io_request(Child, ReplyAs, Req, hd(Buf)),
257	    insulator_wait(Child, Parent, [Buf1 | tl(Buf)], St);
258	{io_request, From, ReplyAs, Req} when is_pid(From) ->
259	    %% (this shouldn't happen anymore, but we keep it safe)
260	    %% just ensure the sender gets a reply; ignore the data
261	    io_request(From, ReplyAs, Req, []),
262	    insulator_wait(Child, Parent, Buf, St);
263	{timeout, Child, Id} ->
264	    exit_messages(Id, timeout, St),
265	    kill_task(Child, St);
266	{'EXIT', Child, normal} ->
267	    terminate_insulator(St);
268	{'EXIT', Child, Reason} ->
269	    exit_messages(St#procstate.id, {exit, Reason}, St),
270	    terminate_insulator(St);
271	{'EXIT', Parent, _} ->
272	    %% make sure child processes are cleaned up recursively
273	    kill_task(Child, St)
274    end.
275
276-spec kill_task(_, _) -> no_return().
277
278kill_task(Child, St) ->
279    exit(Child, kill),
280    terminate_insulator(St).
281
282%% Unlinking before exit avoids polluting the parent process with exit
283%% signals from the insulator. The child process is already dead here.
284
285terminate_insulator(St) ->
286    %% messaging/unlinking is ok even if the parent is already dead
287    Parent = St#procstate.parent,
288    Parent ! {done, St#procstate.ref, self()},
289    unlink(Parent),
290    exit(normal).
291
292%% send cancel messages for the Id of the "causing" item, and also for
293%% the Id of the insulator itself, if they are different
294exit_messages(Id, Cause, St) ->
295    %% the message for the most specific Id is always sent first
296    message_super(Id, {cancel, Cause}, St),
297    case St#procstate.id of
298	Id -> ok;
299	Id1 -> message_super(Id1, {cancel, {blame, Id}}, St)
300    end.
301
302%% Child processes send all messages via the insulator to ensure proper
303%% sequencing with timeouts and exit signals.
304
305message_insulator(Data, St) ->
306    St#procstate.insulator ! {child, self(), St#procstate.id, Data}.
307
308%% Timeout handling
309
310set_timeout(Time, St) ->
311    erlang:send_after(Time, St#procstate.insulator,
312		      {timeout, self(), St#procstate.id}).
313
314clear_timeout(Ref) ->
315    erlang:cancel_timer(Ref).
316
317with_timeout(undefined, Default, F, St) ->
318    with_timeout(Default, F, St);
319with_timeout(Time, _Default, F, St) ->
320    with_timeout(Time, F, St).
321
322with_timeout(infinity, F, _St) ->
323    %% don't start timers unnecessarily
324    {T0, _} = statistics(wall_clock),
325    Value = F(),
326    {T1, _} = statistics(wall_clock),
327    {Value, T1 - T0};
328with_timeout(Time, F, St) when is_integer(Time), Time > 16#FFFFffff ->
329    with_timeout(16#FFFFffff, F, St);
330with_timeout(Time, F, St) when is_integer(Time), Time < 0 ->
331    with_timeout(0, F, St);
332with_timeout(Time, F, St) when is_integer(Time) ->
333    Ref = set_timeout(Time, St),
334    {T0, _} = statistics(wall_clock),
335    try F() of
336	Value ->
337	    %% we could also read the timer, but this is simpler
338	    {T1, _} = statistics(wall_clock),
339	    {Value, T1 - T0}
340    after
341	clear_timeout(Ref)
342    end.
343
344%% The normal behaviour of a child process is not to trap exit
345%% signals. The testing framework is not dependent on this, however, so
346%% the test code is allowed to enable signal trapping as it pleases.
347%% Note that I/O is redirected to the insulator process.
348
349%% @spec (() -> term(), #procstate{}) -> ok
350
351child_process(Fun, St) ->
352    group_leader(St#procstate.insulator, self()),
353    try Fun() of
354	_ -> ok
355    catch
356	%% the only "normal" way for a child process to bail out (e.g,
357	%% when not being able to parse the test descriptor) is to throw
358	%% an {eunit_abort, Reason} exception; any other exception will
359	%% be reported as an unexpected termination of the test
360	{eunit_abort, Cause} ->
361	    message_insulator({abort, Cause}, St),
362	    exit(aborted)
363    end.
364
365-ifdef(TEST).
366child_test_() ->
367    [{"test processes do not trap exit signals",
368      ?_assertMatch(false, process_flag(trap_exit, false))}].
369-endif.
370
371%% @throws abortException()
372%% @type abortException() = {eunit_abort, Cause::term()}
373
374abort_task(Cause) ->
375    throw({eunit_abort, Cause}).
376
377%% Typically, the process that executes this code is not trapping
378%% signals, but it might be - it is outside of our control, since test
379%% code can enable or disable trapping at will. That we cannot rely on
380%% process links here, is why the insulator process of a task must be
381%% guaranteed to always send a reply before it terminates.
382%%
383%% The unique reference guarantees that we don't extract any message
384%% from the mailbox unless it belongs to the test framework (and not to
385%% the running tests) - it is not possible to use selective receive to
386%% match only messages that are tagged with some pid out of a
387%% dynamically varying set of pids. When the wait-loop terminates, no
388%% such message should remain in the mailbox.
389
390wait_for_task(Pid, St) ->
391    wait_for_tasks(sets:from_list([Pid]), St).
392
393wait_for_tasks(PidSet, St) ->
394    case sets:size(PidSet) of
395	0 ->
396	    ok;
397	_ ->
398	    %% (note that when we receive this message for some task, we
399	    %% are guaranteed that the insulator process of the task has
400	    %% already informed the supervisor about any anomalies)
401	    Reference = St#procstate.ref,
402	    receive
403		{done, Reference, Pid} ->
404		    %% (if Pid is not in the set, del_element has no
405		    %% effect, so this is always safe)
406		    Rest = sets:del_element(Pid, PidSet),
407		    wait_for_tasks(Rest, St)
408	    end
409    end.
410
411%% ---------------------------------------------------------------------
412%% Separate testing process
413
414%% TODO: Ability to stop after N failures.
415%% TODO: Flow control, starting new job as soon as slot is available
416
417tests(T, St) ->
418    I = eunit_data:iter_init(T, St#procstate.id),
419    case St#procstate.order of
420	inorder -> tests_inorder(I, St);
421	inparallel -> tests_inparallel(I, 0, St);
422	{inparallel, N} when is_integer(N), N >= 0 ->
423	    tests_inparallel(I, N, St)
424    end.
425
426set_id(I, St) ->
427    St#procstate{id = eunit_data:iter_id(I)}.
428
429tests_inorder(I, St) ->
430    tests_inorder(I, 0, St).
431
432tests_inorder(I, N, St) ->
433    case get_next_item(I) of
434	{T, I1} ->
435	    handle_item(T, set_id(I1, St)),
436	    tests_inorder(I1, N+1, St);
437	none ->
438	    N    % the return status of a group is the subtest count
439    end.
440
441tests_inparallel(I, K0, St) ->
442    tests_inparallel(I, 0, St, K0, K0, sets:new()).
443
444tests_inparallel(I, N, St, K, K0, Children) when K =< 0, K0 > 0 ->
445    wait_for_tasks(Children, St),
446    tests_inparallel(I, N, St, K0, K0, sets:new());
447tests_inparallel(I, N, St, K, K0, Children) ->
448    case get_next_item(I) of
449	{T, I1} ->
450	    Child = spawn_item(T, set_id(I1, St)),
451	    tests_inparallel(I1, N+1, St, K - 1, K0,
452			     sets:add_element(Child, Children));
453	none ->
454	    wait_for_tasks(Children, St),
455	    N    % the return status of a group is the subtest count
456    end.
457
458%% this starts a new separate task for an inparallel-item (which might
459%% be a group and in that case might cause yet another spawn in the
460%% handle_group() function, but it might also be just a single test)
461spawn_item(T, St0) ->
462    Fun = fun (St) ->
463		  fun () -> handle_item(T, St) end
464	  end,
465    %% inparallel-items are always spawned locally
466    start_task(local, Fun, St0).
467
468get_next_item(I) ->
469    try eunit_data:iter_next(I)
470    catch
471	Term -> abort_task(Term)
472    end.
473
474handle_item(T, St) ->
475    case T of
476	#test{} -> handle_test(T, St);
477	#group{} -> handle_group(T, St)
478    end.
479
480handle_test(T, St) ->
481    Data = [{desc, T#test.desc}, {source, T#test.location},
482	    {line, T#test.line}],
483    message_insulator({'begin', test, Data}, St),
484
485    %% each test case runs under a fresh group leader process
486    G0 = group_leader(),
487    Runner = self(),
488    G1 = new_group_leader(Runner),
489    group_leader(G1, self()),
490
491    %% run the actual test, handling timeouts and getting the total run
492    %% time of the test code (and nothing else)
493    {Status, Time} = with_timeout(T#test.timeout, ?DEFAULT_TEST_TIMEOUT,
494				  fun () -> run_test(T) end, St),
495
496    %% restore group leader, get the collected output, and re-emit it so
497    %% that it all seems to come from this process, and always comes
498    %% before the 'end' message for this test
499    group_leader(G0, self()),
500    Output = group_leader_sync(G1),
501    io:put_chars(Output),
502
503    message_insulator({'end', Status, Time}, St),
504    ok.
505
506%% @spec (#test{}) -> ok | {error, eunit_lib:exception()}
507%%                  | {skipped, eunit_test:wrapperError()}
508
509run_test(#test{f = F}) ->
510    try eunit_test:run_testfun(F) of
511	{ok, _Value} ->
512	    %% just discard the return value
513	    ok;
514	{error, Exception} ->
515	    {error, Exception}
516    catch
517	throw:WrapperError -> {skipped, WrapperError}
518    end.
519
520set_group_order(#group{order = undefined}, St) ->
521    St;
522set_group_order(#group{order = Order}, St) ->
523    St#procstate{order = Order}.
524
525handle_group(T, St0) ->
526    St = set_group_order(T, St0),
527    case T#group.spawn of
528	undefined ->
529	    run_group(T, St);
530	Type ->
531	    Child = spawn_group(Type, T, St),
532	    wait_for_task(Child, St)
533    end.
534
535spawn_group(Type, T, St0) ->
536    Fun = fun (St) ->
537		  fun () -> run_group(T, St) end
538	  end,
539    start_task(Type, Fun, St0).
540
541run_group(T, St) ->
542    %% note that the setup/cleanup is outside the group timeout; if the
543    %% setup fails, we do not start any timers
544    Timeout = T#group.timeout,
545    Data = [{desc, T#group.desc}, {spawn, T#group.spawn},
546	    {order, T#group.order}],
547    message_insulator({'begin', group, Data}, St),
548    F = fun (G) -> enter_group(G, Timeout, St) end,
549    try with_context(T, F) of
550	{Status, Time} ->
551	    message_insulator({'end', Status, Time}, St)
552    catch
553	%% a throw here can come from eunit_data:enter_context/4 or from
554	%% get_next_item/1; for context errors, report group as aborted,
555	%% but continue processing tests
556	{context_error, Why, Trace} ->
557	    message_insulator({skipped, {Why, Trace}}, St)
558    end,
559    ok.
560
561enter_group(T, Timeout, St) ->
562    with_timeout(Timeout, ?DEFAULT_GROUP_TIMEOUT,
563		 fun () -> tests(T, St) end, St).
564
565with_context(#group{context = undefined, tests = T}, F) ->
566    F(T);
567with_context(#group{context = #context{} = C, tests = I}, F) ->
568    eunit_data:enter_context(C, I, F).
569
570%% Group leader process for test cases - collects I/O output requests.
571
572new_group_leader(Runner) ->
573    %% We must use spawn/3 here (with explicit module and function
574    %% name), because the 'current function' status of the group leader
575    %% is used by the UNDER_EUNIT macro (in eunit.hrl). If we spawn
576    %% using a fun, the current function will be 'erlang:apply/2' during
577    %% early process startup, which will fool the macro.
578    spawn_link(?MODULE, group_leader_process, [Runner]).
579
580group_leader_process(Runner) ->
581    group_leader_loop(Runner, infinity, []).
582
583group_leader_loop(Runner, Wait, Buf) ->
584    receive
585	{io_request, From, ReplyAs, Req} ->
586	    P = process_flag(priority, normal),
587	    %% run this part under normal priority always
588	    Buf1 = io_request(From, ReplyAs, Req, Buf),
589	    process_flag(priority, P),
590	    group_leader_loop(Runner, Wait, Buf1);
591	stop ->
592	    %% quitting time: make a minimal pause, go low on priority,
593	    %% set receive-timeout to zero and schedule out again
594	    receive after 2 -> ok end,
595	    process_flag(priority, low),
596	    group_leader_loop(Runner, 0, Buf);
597	_ ->
598	    %% discard any other messages
599	    group_leader_loop(Runner, Wait, Buf)
600    after Wait ->
601	    %% no more messages and nothing to wait for; we ought to
602	    %% have collected all immediately pending output now
603	    process_flag(priority, normal),
604	    Runner ! {self(), lists:reverse(Buf)}
605    end.
606
607group_leader_sync(G) ->
608    G ! stop,
609    receive
610	{G, Buf} -> Buf
611    end.
612
613%% Implementation of buffering I/O for group leader processes. (Note that
614%% each batch of characters is just pushed on the buffer, so it needs to
615%% be reversed when it is flushed.)
616
617io_request(From, ReplyAs, Req, Buf) ->
618    {Reply, Buf1} = io_request(Req, Buf),
619    io_reply(From, ReplyAs, Reply),
620    Buf1.
621
622io_reply(From, ReplyAs, Reply) ->
623    From ! {io_reply, ReplyAs, Reply}.
624
625io_request({put_chars, Chars}, Buf) ->
626    {ok, [Chars | Buf]};
627io_request({put_chars, M, F, As}, Buf) ->
628    try apply(M, F, As) of
629	Chars -> {ok, [Chars | Buf]}
630    catch
631	C:T:S -> {{error, {C,T,S}}, Buf}
632    end;
633io_request({put_chars, _Enc, Chars}, Buf) ->
634    io_request({put_chars, Chars}, Buf);
635io_request({put_chars, _Enc, Mod, Func, Args}, Buf) ->
636    io_request({put_chars, Mod, Func, Args}, Buf);
637io_request({get_chars, _Enc, _Prompt, _N}, Buf) ->
638    {eof, Buf};
639io_request({get_chars, _Prompt, _N}, Buf) ->
640    {eof, Buf};
641io_request({get_line, _Prompt}, Buf) ->
642    {eof, Buf};
643io_request({get_line, _Enc, _Prompt}, Buf) ->
644    {eof, Buf};
645io_request({get_until, _Prompt, _M, _F, _As}, Buf) ->
646    {eof, Buf};
647io_request({get_until, _Enc, _Prompt, _M, _F, _As}, Buf) ->
648    {eof, Buf};
649io_request({setopts, _Opts}, Buf) ->
650    {ok, Buf};
651io_request(getopts, Buf) ->
652    {{error, enotsup}, Buf};
653io_request({get_geometry,columns}, Buf) ->
654    {{error, enotsup}, Buf};
655io_request({get_geometry,rows}, Buf) ->
656    {{error, enotsup}, Buf};
657io_request({requests, Reqs}, Buf) ->
658    io_requests(Reqs, {ok, Buf});
659io_request(_, Buf) ->
660    {{error, request}, Buf}.
661
662io_requests([R | Rs], {ok, Buf}) ->
663    io_requests(Rs, io_request(R, Buf));
664io_requests(_, Result) ->
665    Result.
666
667-ifdef(TEST).
668io_error_test_() ->
669    [?_assertMatch({error, enotsup}, io:getopts()),
670     ?_assertMatch({error, enotsup}, io:columns()),
671     ?_assertMatch({error, enotsup}, io:rows())].
672-endif.
673