%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009-2013. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(io_proto_SUITE).
-compile(r12).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
-export([init_per_testcase/2, end_per_testcase/2]).
-export([setopts_getopts/1,unicode_options/1,unicode_options_gen/1,
binary_options/1, bc_with_r12/1,
bc_with_r12_gl/1, read_modes_gl/1,bc_with_r12_ogl/1,
read_modes_ogl/1, broken_unicode/1,eof_on_pipe/1,unicode_prompt/1]).
-export([io_server_proxy/1,start_io_server_proxy/0, proxy_getall/1,
proxy_setnext/2, proxy_quit/1]).
%% For spawn
-export([toerl_server/3,hold_the_line/3,answering_machine1/3,
answering_machine2/3]).
-export([uprompt/1]).
%-define(without_test_server, true).
-ifdef(without_test_server).
-define(line, put(line, ?LINE), ).
-define(config(X,Y), foo).
-define(t, test_server).
-define(privdir(_), "./io_SUITE_priv").
-else.
-include_lib("common_test/include/ct.hrl").
-define(privdir(Conf), ?config(priv_dir, Conf)).
-endif.
%%-define(debug, true).
-ifdef(debug).
-define(format(S, A), io:format(S, A)).
-define(dbg(Data),io:format(standard_error, "DBG: ~p\r\n",[Data])).
-define(RM_RF(Dir),begin io:format(standard_error, "Not Removed: ~p\r\n",[Dir]),
ok end).
-else.
-define(format(S, A), ok).
-define(dbg(Data),noop).
-define(RM_RF(Dir),rm_rf(Dir)).
-endif.
init_per_testcase(_Case, Config) ->
Term = os:getenv("TERM", "dumb"),
os:putenv("TERM","vt100"),
[{term, Term} | Config].
end_per_testcase(_Case, Config) ->
Term = ?config(term,Config),
os:putenv("TERM",Term),
ok.
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,20}}].
all() ->
[setopts_getopts, unicode_options, unicode_options_gen,
binary_options, bc_with_r12, bc_with_r12_gl,
bc_with_r12_ogl, read_modes_gl, read_modes_ogl,
broken_unicode, eof_on_pipe, unicode_prompt].
groups() ->
[].
init_per_suite(Config) ->
DefShell = get_default_shell(),
[{default_shell,DefShell}|Config].
end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
-record(state, {
q = [],
nxt = eof,
mode = list
}).
uprompt(_L) ->
[1050,1072,1082,1074,1086,32,1077,32,85,110,105,99,111,100,101,32,63].
%% Test that an Unicode prompt does not crash the shell.
unicode_prompt(Config) when is_list(Config) ->
?line PA = filename:dirname(code:which(?MODULE)),
case proplists:get_value(default_shell,Config) of
old ->
ok;
new ->
?line rtnode([{putline,""},
{putline, "2."},
{getline, "2"},
{putline, "shell:prompt_func({io_proto_SUITE,uprompt})."},
{getline, "default"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline, "\"hej\\n\""},
{putline, "io:setopts([{binary,true}])."},
{getline, "ok"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline, "<<\"hej\\n\">>"}
],[],[],"-pa \""++ PA++"\"")
end,
%% And one with oldshell
?line rtnode([{putline,""},
{putline, "2."},
{getline_re, ".*2$"},
{putline, "shell:prompt_func({io_proto_SUITE,uprompt})."},
{getline_re, ".*default"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline_re, ".*\"hej\\\\n\""},
{putline, "io:setopts([{binary,true}])."},
{getline_re, ".*ok"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline_re, ".*<<\"hej\\\\n\">>"}
],[],[],"-oldshell -pa \""++PA++"\""),
ok.
%% Check io:setopts and io:getopts functions.
setopts_getopts(Config) when is_list(Config) ->
?line FileName = filename:join([?config(priv_dir,Config),
"io_proto_SUITE_setopts_getopts.dat"]),
?line {ok,WFile} = file:open(FileName,[write]),
?line Server = start_io_server_proxy(),
?line [{binary, false}] = io:getopts(Server),
?line [getopts] = proxy_getall(Server),
?line [{binary,false},{encoding,latin1}] = lists:sort(io:getopts(WFile)),
?line proxy_setnext(Server,"Hej"),
?line "Hej" = io:get_line(Server,''),
?line proxy_setnext(Server,"Hej"++[532]),
?line [$H,$e,$j,532] = io:get_line(Server,''),
?line ok = io:setopts(Server,[{binary,true}]),
?line proxy_setnext(Server,"Hej"),
?line <<"Hej">> = io:get_line(Server,''),
?line proxy_setnext(Server,"Hej"++[532]),
?line <<72,101,106,200,148>> = io:get_line(Server,''),
?line [$H,$e,$j,532] = lists:flatten(io_lib:format("~ts",[<<72,101,106,200,148>>])),
?line file:write(WFile,<<"HejA">>),
?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,unicode)),
?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,{utf16,big})),
?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,{utf16,little})),
?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,{utf32,big})),
?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,{utf32,little})),
?line file:close(WFile),
?line {ok,RFile} = file:open(FileName,[read]),
?line [{binary,false},{encoding,latin1}] = lists:sort(io:getopts(RFile)),
?line [$H,$e,$j,$A] = io:get_chars(RFile,'',4),
?line io:setopts(RFile,[{encoding,unicode}]),
?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
?line [{binary,false},{encoding,unicode}] = lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,{utf16,big}}]),
?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
?line [{binary,false},{encoding,{utf16,big}}] =
lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,{utf16,little}}]),
?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
?line [{binary,false},{encoding,{utf16,little}}] =
lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,{utf32,big}}]),
?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
?line [{binary,false},{encoding,{utf32,big}}] =
lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,{utf32,little}}]),
?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
?line [{binary,false},{encoding,{utf32,little}}] =
lists:sort(io:getopts(RFile)),
?line eof = io:get_line(RFile,''),
?line file:position(RFile,0),
?line io:setopts(RFile,[{binary,true},{encoding,latin1}]),
?line <<$H,$e,$j,$A>> = io:get_chars(RFile,'',4),
?line [{binary,true},{encoding,latin1}] = lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,unicode}]),
?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
?line [{binary,true},{encoding,unicode}] = lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,{utf16,big}}]),
?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
?line [{binary,true},{encoding,{utf16,big}}] =
lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,{utf16,little}}]),
?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
?line [{binary,true},{encoding,{utf16,little}}] =
lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,{utf32,big}}]),
?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
?line [{binary,true},{encoding,{utf32,big}}] =
lists:sort(io:getopts(RFile)),
?line io:setopts(RFile,[{encoding,{utf32,little}}]),
?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
?line [{binary,true},{encoding,{utf32,little}}] =
lists:sort(io:getopts(RFile)),
?line eof = io:get_line(RFile,''),
?line file:close(RFile),
case proplists:get_value(default_shell,Config) of
old ->
ok;
new ->
%% So, lets test another node with new interactive shell
?line rtnode([{putline,""},
{putline, "2."},
{getline, "2"},
{putline, "lists:keyfind(binary,1,io:getopts())."},
{getline, "{binary,false}"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline, "\"hej\\n\""},
{putline, "io:setopts([{binary,true}])."},
{getline, "ok"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline, "<<\"hej\\n\">>"}
],[])
end,
%% And one with oldshell
?line rtnode([{putline,""},
{putline, "2."},
{getline_re, ".*2$"},
{putline, "lists:keyfind(binary,1,io:getopts())."},
{getline_re, ".*{binary,false}"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline_re, ".*\"hej\\\\n\""},
{putline, "io:setopts([{binary,true}])."},
{getline_re, ".*ok"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline_re, ".*<<\"hej\\\\n\">>"}
],[],[],"-oldshell"),
ok.
get_lc_ctype() ->
case {os:type(),os:version()} of
{{unix,sunos},{5,N,_}} when N =< 8 ->
"iso_8859_1";
_ ->
"ISO-8859-1"
end.
%% Test various unicode options.
unicode_options(Config) when is_list(Config) ->
DataDir = ?config(data_dir,Config),
PrivDir = ?config(priv_dir,Config),
%% A string in both russian and greek characters, which is present
%% in all the internal test files (but in different formats of course)...
TestData = [1090,1093,1077,32,1073,1080,1075,32,
1088,1077,1076,32,1092,1086,1100,32,1093,
1072,1089,32,1089,1086,1100,32,932,951,949,
32,946,953,947,32,961,949,948,32,
963,959,967,32,945,961,949,32,966,959,967,949,963],
%% Testdata from Chinese open source customer, that triggered OTP-7974
TestData2 = [46,46,46,12411,12370,12411,12370,44,12411,12370,12411,12370,44,
12411,12370,12411,12370,44,12411,12370,12411,12370,44,12411,12370,
12411,12370,44,44,44,12411,12370,12411,12370,44,44,12411,12370,12411,
12370,44,12411,12370,12411,12370,44,12411,12370,12411,12370,44,12411,
12370,12411,12370,44,12411,12370,12411,12370,44,44,44,10],
%% The external test files are generated with a BOM writing
%% text editor. A shorter line is written (with two characters
%% larger than 127).
ExternalTestData = [197,116,101,114,101,114,246,118,114,97],
InternalBomFiles = ["testdata_utf8_bom.dat",
"testdata_utf16_big_bom.dat",
"testdata_utf16_little_bom.dat",
"testdata_utf32_big_bom.dat",
"testdata_utf32_little_bom.dat"],
AllNoBom = [{utf8,"testdata_utf8.dat"},
{utf16,"testdata_utf16_big.dat"},
{{utf16,big},"testdata_utf16_big.dat"},
{{utf16,little},"testdata_utf16_little.dat"},
{utf32,"testdata_utf32_big.dat"},
{{utf32,big},"testdata_utf32_big.dat"},
{{utf32,little},"testdata_utf32_little.dat"}],
ExternalBomFiles = ["external_utf8_bom.dat",
"external_utf16_little_bom.dat",
"external_utf16_big_bom.dat"],
ReadBomFile = fun(File,Dir) ->
%io:format(standard_error,"~s\r\n",[filename:join([Dir,File])]),
{ok,F} = file:open(filename:join([Dir,File]),
[read,binary]),
{ok,Bin} = file:read(F,4),
{Type,Bytes} = unicode:bom_to_encoding(Bin),
%io:format(standard_error,"~p\r\n",[{Type,Bytes}]),
file:position(F,Bytes),
io:setopts(F,[{encoding,Type}]),
R = unicode:characters_to_list(
io:get_chars(F,'',length(TestData)),unicode),
file:close(F),
R
end,
ReadBomlessFile = fun({Type,File},DataLen,Dir) ->
{ok,F} = file:open(filename:join([Dir,File]),
[read,binary,
{encoding,Type}]),
R = unicode:characters_to_list(
io:get_chars(F,'',DataLen),unicode),
file:close(F),
R
end,
ReadBomlessFileList = fun({Type,File},DataLen,Dir) ->
{ok,F} = file:open(filename:join([Dir,File]),
[read,
{encoding,Type}]),
R = io:get_chars(F,'',DataLen),
file:close(F),
R
end,
ReadBomlessFileListLine = fun({Type,File},Dir) ->
{ok,F} = file:open(filename:join([Dir,File]),
[read,
{encoding,Type}]),
R = io:get_line(F,''),
file:close(F),
R
end,
?line [TestData = ReadBomFile(F,DataDir) || F <- InternalBomFiles ],
?line [ExternalTestData = ReadBomFile(F,DataDir) || F <- ExternalBomFiles ],
?line [TestData = ReadBomlessFile(F,length(TestData),DataDir) || F <- AllNoBom ],
?line [TestData = ReadBomlessFileList(F,length(TestData),DataDir) || F <- AllNoBom ],
?line [TestData = ReadBomlessFileListLine(F,DataDir) || F <- AllNoBom ],
BomDir = filename:join([PrivDir,"BOMDATA"]),
BomlessDir = filename:join([PrivDir,"BOMLESSDATA"]),
file:make_dir(BomDir),
file:make_dir(BomlessDir),
WriteBomFile = fun({Enc,File},Dir) ->
{ok,F} = file:open(filename:join([Dir,File]),
[write,binary]),
file:write(F,unicode:encoding_to_bom(Enc)),
io:setopts(F,[{encoding,Enc}]),
io:put_chars(F,TestData),
file:close(F),
ok
end,
?line [ ok = WriteBomFile(F,BomDir) || F <- AllNoBom ],
?line [TestData = ReadBomFile(F,BomDir) || {_,F} <- AllNoBom ],
WriteBomlessFile = fun({Enc,File},TData,Dir) ->
{ok,F} = file:open(
filename:join([Dir,File]),
[write,binary,{encoding,Enc}]),
io:put_chars(F,TData),
file:close(F),
ok
end,
?line [ ok = WriteBomlessFile(F,TestData,BomlessDir) || F <- AllNoBom ],
?line [TestData = ReadBomlessFile(F,length(TestData),BomlessDir) || F <- AllNoBom ],
?line [TestData = ReadBomlessFileList(F,length(TestData),BomlessDir) || F <- AllNoBom ],
?line [TestData = ReadBomlessFileListLine(F,BomlessDir) || F <- AllNoBom ],
CannotReadFile = fun({Enc,File},Dir) ->
%io:format(standard_error,"~s\r\n",[filename:join([Dir,File])]),
{ok,F} = file:open(
filename:join([Dir,File]),
[read,binary,{encoding,Enc}]),
Enc2 = case Enc of
utf8 ->
unicode;
Tpl when is_tuple(Tpl) ->
Tpl;
Atom when is_atom(Atom) ->
{Atom, big}
end,
{error, {no_translation,Enc2,latin1}} =
file:read(F,10),
{error,terminated} = io:get_chars(F,'',10),
ok
end,
?line [ ok = CannotReadFile(F,DataDir) || F <- AllNoBom ],
?line [ ok = CannotReadFile(F,BomlessDir) || F <- AllNoBom ],
?line [ ok = CannotReadFile(F,BomDir) || F <- AllNoBom ],
?line [ ok = WriteBomlessFile(F,TestData2,BomlessDir) || F <- AllNoBom ],
?line [TestData2 = ReadBomlessFile(F,length(TestData2),BomlessDir) || F <- AllNoBom ],
?line [TestData2 = ReadBomlessFileList(F,length(TestData2),BomlessDir) || F <- AllNoBom ],
?line [TestData2 = ReadBomlessFileListLine(F,BomlessDir) || F <- AllNoBom ],
FailDir = filename:join([PrivDir,"FAIL"]),
file:make_dir(FailDir),
CannotWriteFile = fun({_Enc,File},Dir) ->
{ok,F} = file:open(
filename:join([Dir,File]),
[write,binary]),
?line {'EXIT', {no_translation,_}} =
(catch io:put_chars(F,TestData)),
?line {'EXIT', {terminated,_}} = (catch io:put_chars(F,TestData)),
ok
end,
?line [ ok = CannotWriteFile(F,FailDir) || F <- AllNoBom ],
case proplists:get_value(default_shell,Config) of
old ->
ok;
new ->
%% OK, time for the group_leaders...
?line rtnode([{putline,""},
{putline, "2."},
{getline, "2"},
{putline, "lists:keyfind(encoding,1,io:getopts())."},
{getline, "{encoding,latin1}"},
{putline, "io:format(\"~ts~n\",[[1024]])."},
{getline, "\\x{400}"},
{putline, "io:setopts([unicode])."},
{getline, "ok"},
{putline, "io:format(\"~ts~n\",[[1024]])."},
{getline,
binary_to_list(unicode:characters_to_binary(
[1024],unicode,utf8))}
],[],"LC_CTYPE=\""++get_lc_ctype()++"\"; "
"export LC_CTYPE; ")
end,
?line rtnode([{putline,""},
{putline, "2."},
{getline_re, ".*2$"},
{putline, "lists:keyfind(encoding,1,io:getopts())."},
{getline_re, ".*{encoding,latin1}"},
{putline, "io:format(\"~ts~n\",[[1024]])."},
{getline_re, ".*\\\\x{400\\}"},
{putline, "io:setopts([{encoding,unicode}])."},
{getline_re, ".*ok"},
{putline, "io:format(\"~ts~n\",[[1024]])."},
{getline_re,
".*"++binary_to_list(unicode:characters_to_binary(
[1024],unicode,utf8))}
],[],"LC_CTYPE=\""++get_lc_ctype()++"\"; export LC_CTYPE; ",
" -oldshell "),
ok.
%% Tests various unicode options on random generated files.
unicode_options_gen(Config) when is_list(Config) ->
random:seed(1240, 900586, 553728),
PrivDir = ?config(priv_dir, Config),
AllModes = [utf8,utf16,{utf16,big},{utf16,little},
utf32,{utf32,big},{utf32,little}],
FSize = 9*1024,
NumItersRead = 2,
NumItersWrite = 2,
Dir = filename:join(PrivDir, "GENDATA1"),
file:make_dir(Dir),
DoOneFile1 =
fun(Encoding, N, M) ->
?dbg({Encoding,M,N}),
io:format("Read test: Encoding ~p, Chunk size ~p, Iteration ~p~n",[Encoding,M,N]),
io:format(standard_error,
"Read test: Encoding ~p, Chunk size ~p, Iteration ~p\r\n",[Encoding,M,N]),
Fname = filename:join(Dir,
"genfile_"++enc2str(Encoding)++
"_"++integer_to_list(N)),
Ulist = random_unicode(FSize),
Bin = unicode:characters_to_binary(Ulist, utf8, Encoding),
ok = file:write_file(Fname, Bin),
Read1 = fun(FD) -> io:get_line(FD, '') end,
Res1 = read_whole_file(Fname,
[read,read_ahead,{encoding,Encoding}],
Read1),
Read2 = fun(FD) -> io:get_chars(FD, '', M) end,
Res2 = read_whole_file(Fname,
[read,binary,
read_ahead,{encoding,Encoding}],
Read2),
Read3 = fun(FD) ->
case io:fread(FD, '', "~ts") of
{ok,D} -> D;
Other -> Other end
end,
Res3 = read_whole_file(Fname,
[read,binary,
read_ahead,{encoding,Encoding}],
Read3),
Read4 = fun(FD) ->
case io:fread(FD, '', "~ts") of
{ok,D} -> D;
Other -> Other end
end,
Res4 = read_whole_file(Fname,
[read,read_ahead,{encoding,Encoding}],
Read4),
Ulist2 = [X || X <- Ulist, X =/= $\n, X =/= $\s],
Ulist3 = [X || X <- Ulist, X =/= $\n],
Ulist = done(Res1),
Ulist = done(Res2),
Ulist2 = done(Res3),
Ulist3 = done(Res4),
file:delete(Fname)
end,
[ [ [ DoOneFile1(E, N, M) || E <- AllModes ] ||
M <- [10,1000,128,1024,8192,8193] ] ||
N <- lists:seq(1, NumItersRead) ],
DoOneFile2 =
fun(Encoding,N,M) ->
?dbg({Encoding,M,N}),
io:format("Write test: Encoding ~p, Chunk size ~p, Iteration ~p~n",[Encoding,M,N]),
io:format(standard_error,
"Write test: Encoding ~p, Chunk size ~p, Iteration ~p\r\n",[Encoding,M,N]),
Fname = filename:join(Dir,
"genfile_"++enc2str(Encoding)++
"_"++integer_to_list(N)),
Ulist = random_unicode(FSize),
Res1 = write_read_file(Fname, 1,
[write],
Encoding,
fun(FD) -> io:put_chars(FD, Ulist) end),
Res2 = write_read_file(Fname, 2,
[write,binary],
Encoding,
fun(FD) -> io:put_chars(FD, Ulist) end),
Fun3 = fun(FD) ->
_ = [io:format(FD, "~tc", [C]) || C <- Ulist],
ok
end,
Res3 = write_read_file(Fname, 3,
[write],
Encoding,
Fun3),
Fun4 = fun(FD) ->
io:put_chars(FD,
unicode:characters_to_binary(Ulist))
end,
Res4 = write_read_file(Fname, 4,
[write],
Encoding,
Fun4),
LL = string:tokens(Ulist, "\n"),
Fun5 = fun(FD) ->
_ = [io:format(FD, "~ts", [L]) || L <- LL],
ok
end,
Res5 = write_read_file(Fname, 5,
[write],
Encoding,
Fun5),
Ulist2 = lists:flatten(LL),
ResBin = done(Res1),
ResBin = done(Res2),
ResBin = done(Res3),
ResBin = done(Res4),
Ulist = unicode:characters_to_list(ResBin, Encoding),
ResBin2 = done(Res5),
Ulist2 = unicode:characters_to_list(ResBin2, Encoding),
ok
end,
[ [ [ DoOneFile2(E, N, M) || E <- AllModes ] ||
M <- [10,1000,128,1024,8192,8193] ] ||
N <- lists:seq(1, NumItersWrite) ],
ok.
read_whole_file(Fname, Options, Fun) ->
do(fun() ->
do_read_whole_file(Fname, Options, Fun)
end).
do_read_whole_file(Fname, Options, Fun) ->
{ok,F} = file:open(Fname, Options),
Res = do_read_whole_file_1(Fun, F),
ok = file:close(F),
unicode:characters_to_list(Res, unicode).
do_read_whole_file_1(Fun, F) ->
case Fun(F) of
eof ->
[];
{error,Error} ->
receive after 10000 -> ok end,
exit(Error);
Other ->
[Other|do_read_whole_file_1(Fun, F)]
end.
write_read_file(Fname0, N, Options, Enc, Writer) ->
Fname = Fname0 ++ "_" ++ integer_to_list(N),
do(fun() ->
do_write_read_file(Fname, Options, Enc, Writer)
end).
do_write_read_file(Fname, Options, Encoding, Writer) ->
{ok,F} = file:open(Fname, [{encoding,Encoding}|Options]),
Writer(F),
ok = file:close(F),
{ok,Bin} = file:read_file(Fname),
ok = file:delete(Fname),
Bin.
enc2str(Atom) when is_atom(Atom) ->
atom_to_list(Atom);
enc2str({A1,A2}) when is_atom(A1), is_atom(A2) ->
atom_to_list(A1)++"_"++atom_to_list(A2).
random_unicode(0) ->
[];
random_unicode(N) ->
% Favour large unicode and make linebreaks
X = case random:uniform(20) of
A when A =< 1 -> $\n;
A0 when A0 =< 3 -> random:uniform(16#10FFFF);
A1 when A1 =< 6 -> random:uniform(16#10FFFF - 16#7F) + 16#7F;
A2 when A2 =< 12 -> random:uniform(16#10FFFF - 16#7FF) + 16#7FF;
_ -> random:uniform(16#10FFFF - 16#FFFF) + 16#FFFF
end,
case X of
Inv1 when Inv1 >= 16#D800, Inv1 =< 16#DFFF;
Inv1 =:= 16#FFFE;
Inv1 =:= 16#FFFF ->
random_unicode(N);
_ ->
[X | random_unicode(N-1)]
end.
%% Test variants with binary option.
binary_options(Config) when is_list(Config) ->
DataDir = ?config(data_dir,Config),
PrivDir = ?config(priv_dir,Config),
TestData = unicode:characters_to_binary(
[1090,1093,1077,32,1073,1080,1075,32,
1088,1077,1076,32,1092,1086,1100,32,1093,
1072,1089,32,1089,1086,1100,32,932,951,949,
32,946,953,947,32,961,949,948,32,
963,959,967,32,945,961,949,32,966,959,967,949,963]),
<<First10:10/binary,Second10:10/binary,_/binary>> = TestData,
First10List = binary_to_list(First10),
Second10List = binary_to_list(Second10),
TestFile = filename:join([DataDir, "testdata_utf8.dat"]),
?line {ok, F} = file:open(TestFile,[read]),
?line {ok, First10List} = file:read(F,10),
?line io:setopts(F,[binary]),
?line {ok, Second10} = file:read(F,10),
?line file:close(F),
?line {ok, F2} = file:open(TestFile,[read,binary]),
?line {ok, First10} = file:read(F2,10),
?line io:setopts(F2,[list]),
?line {ok, Second10List} = file:read(F2,10),
?line file:position(F2,0),
%dbg:tracer(),dbg:p(F2,call),dbg:tpl(file_io_server,x),
?line First10List = io:get_chars(F2,'',10),
?line io:setopts(F2,[binary]),
?line Second10 = unicode:characters_to_binary(io:get_chars(F2,'',10),unicode,latin1),
?line file:close(F2),
?line LineBreakFileName = filename:join([PrivDir, "testdata.dat"]),
?line LineBreakTestData = <<TestData/binary,$\n>>,
?line LineBreakTestDataList = binary_to_list(LineBreakTestData),
?line file:write_file(LineBreakFileName,[LineBreakTestData,LineBreakTestData,LineBreakTestData,TestData]),
?line {ok, F3} = file:open(LineBreakFileName,[read]),
?line LineBreakTestDataList = io:get_line(F3,''),
?line io:setopts(F3,[binary]),
?line LineBreakTestData = unicode:characters_to_binary(io:get_line(F3,''),unicode,latin1),
?line io:setopts(F3,[list]),
?line LineBreakTestDataList = io:get_line(F3,''),
?line io:setopts(F3,[binary]),
%ok = io:format(standard_error,"TestData = ~w~n",[TestData]),
?line TestData = unicode:characters_to_binary(io:get_line(F3,''),unicode,latin1),
?line eof = io:get_line(F3,''),
?line file:close(F3),
%% OK, time for the group_leaders...
%% io:format(standard_error,"Hmmm:~w~n",["<<\""++binary_to_list(<<"\345\344\366"/utf8>>)++"\\n\">>"]),
case proplists:get_value(default_shell,Config) of
old ->
ok;
new ->
?line rtnode([{putline, "2."},
{getline, "2"},
{putline, "lists:keyfind(binary,1,io:getopts())."},
{getline, "{binary,false}"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline, "\"hej\\n\""},
{putline, "io:setopts([{binary,true},unicode])."},
{getline, "ok"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline, "<<\"hej\\n\">>"},
{putline, "io:get_line('')."},
{putline, binary_to_list(<<"\345\344\366"/utf8>>)},
{getline, "<<\""++binary_to_list(<<"\345\344\366"/utf8>>)++"\\n\"/utf8>>"}
],[])
end,
%% And one with oldshell
?line rtnode([{putline, "2."},
{getline_re, ".*2$"},
{putline, "lists:keyfind(binary,1,io:getopts())."},
{getline_re, ".*{binary,false}"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline_re, ".*\"hej\\\\n\""},
{putline, "io:setopts([{binary,true},unicode])."},
{getline_re, ".*ok"},
{putline, "io:get_line('')."},
{putline, "hej"},
{getline_re, ".*<<\"hej\\\\n\">>"},
{putline, "io:get_line('')."},
{putline, binary_to_list(<<"\345\344\366"/utf8>>)},
{getline_re, ".*<<\""++binary_to_list(<<"\345\344\366"/utf8>>)++"\\\\n\"/utf8>>"}
],[],[],"-oldshell"),
ok.
%% Test io protocol compatibility with R12 nodes.
bc_with_r12(Config) when is_list(Config) ->
case test_server:is_release_available("r12b") of
true -> bc_with_r12_1(Config);
false -> {skip,"No R12B found"}
end.
bc_with_r12_1(Config) ->
PA = filename:dirname(code:which(?MODULE)),
Name1 = io_proto_r12_1,
?line N1 = list_to_atom(atom_to_list(Name1) ++ "@" ++ hostname()),
test_server:start_node(Name1, peer, [{args, "-pz \""++PA++"\""},
{erl,[{release,"r12b"}]}]),
DataDir = ?config(data_dir,Config),
%PrivDir = ?config(priv_dir,Config),
FileName1 = filename:join([DataDir,"testdata_latin1.dat"]),
TestDataLine1 = [229,228,246],
TestDataLine2 = [197,196,214],
?line SPid1 = rpc:call(N1,erlang,spawn,[?MODULE,hold_the_line,[self(),FileName1,[read]]]),
?line {ok,F1} = receive
{SPid1,Res1} ->
Res1
after 5000 ->
exit(timeout)
end,
?line TestDataLine1 = chomp(io:get_line(F1,'')),
?line SPid1 ! die,
receive after 1000 -> ok end,
?line SPid2 = rpc:call(N1,erlang,spawn,[?MODULE,hold_the_line,[self(),FileName1,[read,binary]]]),
?line {ok,F2} = receive
{SPid2,Res2} ->
Res2
after 5000 ->
exit(timeout)
end,
TestDataLine1BinUtf = unicode:characters_to_binary(TestDataLine1),
TestDataLine1BinLatin = list_to_binary(TestDataLine1),
TestDataLine2BinUtf = unicode:characters_to_binary(TestDataLine2),
TestDataLine2BinLatin = list_to_binary(TestDataLine2),
?line TestDataLine1BinUtf = chomp(io:get_line(F2,'')),
?line TestDataLine2BinUtf = chomp(io:get_line(F2,'')),
%io:format(standard_error,"Exec:~s\r\n",[rpc:call(N1,os,find_executable,["erl"])]),
%io:format(standard_error,"Io:~s\r\n",[rpc:call(N1,code,which,[io])]),
%io:format(standard_error,"File_io_server:~s\r\n",[rpc:call(N1,code,which,[file_io_server])]),
?line file:position(F2,0),
?line TestDataLine1BinLatin = chomp(rpc:call(N1,io,get_line,[F2,''])),
?line TestDataLine2BinUtf = chomp(io:get_line(F2,'')),
?line file:position(F2,0),
?line TestDataLine1BinUtf = chomp(io:get_line(F2,'')),
?line TestDataLine2BinLatin = chomp(rpc:call(N1,io,get_line,[F2,''])),
?line eof = chomp(rpc:call(N1,io,get_line,[F2,''])),
?line file:position(F2,0),
?line TestDataLine1BinLatin = rpc:call(N1,io,get_chars,[F2,'',3]),
io:get_chars(F2,'',1),
?line TestDataLine2BinLatin = chomp(rpc:call(N1,io,get_line,[F2,''])),
?line file:position(F2,0),
?line {ok,[TestDataLine1]} = io:fread(F2,'',"~s"),
?line {ok,[TestDataLine2]} = rpc:call(N1,io,fread,[F2,'',"~s"]),
?line DataLen1 = length(TestDataLine1),
?line DataLen2 = length(TestDataLine2),
?line file:position(F2,0),
?line {ok,TestDataLine1BinLatin} = file:read(F2,DataLen1),
?line {ok,_} = file:read(F2,1),
?line {ok,TestDataLine2BinLatin} = rpc:call(N1,file,read,[F2,DataLen2]),
?line {ok,_} = file:read(F2,1),
?line eof = rpc:call(N1,file,read,[F2,1]),
%% As r12 has a bug when setting options with setopts, we need
%% to reopen the file...
?line SPid2 ! die,
receive after 1000 -> ok end,
?line SPid3 = rpc:call(N1,erlang,spawn,[?MODULE,hold_the_line,[self(),FileName1,[read]]]),
?line {ok,F3} = receive
{SPid3,Res3} ->
Res3
after 5000 ->
exit(timeout)
end,
?line file:position(F3,0),
?line {ok,[TestDataLine1]} = io:fread(F3,'',"~s"),
?line {ok,[TestDataLine2]} = rpc:call(N1,io,fread,[F3,'',"~s"]),
?line file:position(F3,0),
?line {ok,TestDataLine1} = file:read(F3,DataLen1),
?line {ok,_} = file:read(F3,1),
?line {ok,TestDataLine2} = rpc:call(N1,file,read,[F3,DataLen2]),
?line {ok,_} = file:read(F3,1),
?line eof = rpc:call(N1,file,read,[F3,1]),
%% So, lets do it all again, but the other way around
{ok,F4} = file:open(FileName1,[read]),
?line TestDataLine1 = chomp(io:get_line(F4,'')),
?line file:position(F4,0),
?line io:setopts(F4,[binary]),
?line TestDataLine1BinUtf = chomp(io:get_line(F4,'')),
?line TestDataLine2BinUtf = chomp(io:get_line(F4,'')),
?line file:position(F4,0),
?line TestDataLine1BinUtf = chomp(io:get_line(F4,'')),
?line TestDataLine2BinUtf = chomp(io:get_line(F4,'')),
?line file:position(F4,0),
%dbg:tracer(),dbg:p(F4,[call,m]),dbg:tpl(file_io_server,x),dbg:tpl(io_lib,x),
?line TestDataLine1BinUtf = chomp(io:get_line(F4,'')),
?line TestDataLine2BinLatin = chomp(rpc:call(N1,io,get_line,[F4,''])),
?line file:position(F4,0),
?line TestDataLine1BinLatin = chomp(rpc:call(N1,io,get_line,[F4,''])),
?line TestDataLine2BinUtf = chomp(io:get_line(F4,'')),
?line eof = chomp(rpc:call(N1,io,get_line,[F4,''])),
?line file:position(F4,0),
?line TestDataLine1BinLatin = rpc:call(N1,io,get_chars,[F4,'',3]),
io:get_chars(F4,'',1),
?line TestDataLine2BinLatin = chomp(rpc:call(N1,io,get_line,[F4,''])),
?line file:position(F4,0),
?line {ok,[TestDataLine1]} = io:fread(F4,'',"~s"),
?line {ok,[TestDataLine2]} = rpc:call(N1,io,fread,[F4,'',"~s"]),
?line file:position(F4,0),
?line {ok,TestDataLine1BinLatin} = file:read(F4,DataLen1),
?line {ok,_} = file:read(F4,1),
?line {ok,TestDataLine2BinLatin} = rpc:call(N1,file,read,[F4,DataLen2]),
?line {ok,_} = file:read(F4,1),
?line eof = rpc:call(N1,file,read,[F4,1]),
?line io:setopts(F4,[list]),
?line file:position(F4,0),
?line {ok,[TestDataLine1]} = io:fread(F4,'',"~s"),
?line {ok,[TestDataLine2]} = rpc:call(N1,io,fread,[F4,'',"~s"]),
?line file:position(F4,0),
?line {ok,TestDataLine1} = file:read(F4,DataLen1),
?line {ok,_} = file:read(F4,1),
?line {ok,TestDataLine2} = rpc:call(N1,file,read,[F4,DataLen2]),
?line {ok,_} = file:read(F4,1),
?line eof = rpc:call(N1,file,read,[F4,1]),
file:close(F4),
test_server:stop_node(N1),
ok.
hold_the_line(Parent,Filename,Options) ->
Parent ! {self(), file:open(Filename,Options)},
receive
die ->
ok
end.
%% Test io protocol compatibility with R12 nodes (terminals).
bc_with_r12_gl(Config) when is_list(Config) ->
case test_server:is_release_available("r12b") of
true ->
case get_progs() of
{error,Reason} ->
{skip, Reason};
_ ->
bc_with_r12_gl_1(Config,answering_machine1)
end;
false ->
{skip,"No R12B found"}
end.
%% Test io protocol compatibility with R12 nodes (oldshell).
bc_with_r12_ogl(Config) when is_list(Config) ->
case test_server:is_release_available("r12b") of
true ->
case get_progs() of
{error,Reason} ->
{skip, Reason};
_ ->
bc_with_r12_gl_1(Config,answering_machine2)
end;
false ->
{skip,"No R12B found"}
end.
bc_with_r12_gl_1(_Config,Machine) ->
PA = filename:dirname(code:which(?MODULE)),
Name1 = io_proto_r12_gl_1,
?line N1 = list_to_atom(atom_to_list(Name1) ++ "@" ++ hostname()),
test_server:start_node(Name1, peer, [{args, "-pz \""++PA++"\""},
{erl,[{release,"r12b"}]}]),
TestDataLine1 = [229,228,246],
TestDataLine1BinUtf = unicode:characters_to_binary(TestDataLine1),
TestDataLine1BinLatin = list_to_binary(TestDataLine1),
{ok,N2List} = create_nodename(),
MyNodeList = atom2list(node()),
register(io_proto_suite,self()),
AM1 = spawn(?MODULE,Machine,
[MyNodeList, "io_proto_suite", N2List]),
?line GL = receive X when is_pid(X) -> X end,
%% get_line
?line "Hej\n" = rpc:call(N1,io,get_line,[GL,"Prompt\n"]),
?line io:setopts(GL,[binary]),
?line io:format(GL,"Okej~n",[]),
?line <<"Hej\n">> = rpc:call(N1,io,get_line,[GL,"Prompt\n"]),
?line io:setopts(GL,[{encoding,latin1}]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinLatin = chomp(rpc:call(N1,io,get_line,[GL,"Prompt\n"])),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinUtf = chomp(io:get_line(GL,"Prompt\n")),
?line io:setopts(GL,[{encoding,unicode}]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinLatin = chomp(rpc:call(N1,io,get_line,[GL,"Prompt\n"])),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinUtf = chomp(io:get_line(GL,"Prompt\n")),
?line io:setopts(GL,[list]),
?line io:format(GL,"Okej~n",[]),
%%get_chars
?line "Hej" = rpc:call(N1,io,get_chars,[GL,"Prompt\n",3]),
?line io:setopts(GL,[binary]),
?line io:format(GL,"Okej~n",[]),
?line <<"Hej">> = rpc:call(N1,io,get_chars,[GL,"Prompt\n",3]),
?line io:setopts(GL,[{encoding,latin1}]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinLatin = rpc:call(N1,io,get_chars,[GL,"Prompt\n",3]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinUtf = io:get_chars(GL,"Prompt\n",3),
?line io:setopts(GL,[{encoding,unicode}]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinLatin = rpc:call(N1,io,get_chars,[GL,"Prompt\n",3]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinUtf = io:get_chars(GL,"Prompt\n",3),
?line io:setopts(GL,[list]),
?line io:format(GL,"Okej~n",[]),
%%fread
?line {ok,["Hej"]} = rpc:call(N1,io,fread,[GL,"Prompt\n","~s"]),
?line io:setopts(GL,[binary]),
?line io:format(GL,"Okej~n",[]),
?line {ok,["Hej"]} = rpc:call(N1,io,fread,[GL,"Prompt\n","~s"]),
?line io:setopts(GL,[{encoding,latin1}]),
?line io:format(GL,"Okej~n",[]),
?line {ok,[TestDataLine1]} = rpc:call(N1,io,fread,[GL,"Prompt\n","~s"]),
?line io:format(GL,"Okej~n",[]),
?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
?line io:setopts(GL,[{encoding,unicode}]),
?line io:format(GL,"Okej~n",[]),
?line {ok,[TestDataLine1]} = rpc:call(N1,io,fread,[GL,"Prompt\n","~s"]),
?line io:format(GL,"Okej~n",[]),
?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
?line io:setopts(GL,[list]),
?line io:format(GL,"Okej~n",[]),
?line receive
{AM1,done} ->
ok
after 5000 ->
exit(timeout)
end,
test_server:stop_node(N1),
ok.
answering_machine1(OthNode,OthReg,Me) ->
TestDataLine1 = [229,228,246],
TestDataUtf = binary_to_list(unicode:characters_to_binary(TestDataLine1)),
?line rtnode([{putline,""},
{putline, "2."},
{getline, "2"},
{putline, "{"++OthReg++","++OthNode++"} ! group_leader()."},
{getline, "<"},
% get_line
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
% get_chars
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
% fread
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"}
],Me,"LC_CTYPE=\""++get_lc_ctype()++"\"; export LC_CTYPE; "),
O = list_to_atom(OthReg),
O ! {self(),done},
ok.
answering_machine2(OthNode,OthReg,Me) ->
TestDataLine1 = [229,228,246],
TestDataUtf = binary_to_list(unicode:characters_to_binary(TestDataLine1)),
?line rtnode([{putline,""},
{putline, "2."},
{getline, "2"},
{putline, "{"++OthReg++","++OthNode++"} ! group_leader()."},
{getline_re, ".*<[0-9].*"},
% get_line
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
% get_chars
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
% fread
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, "Hej"},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataLine1},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"},
{getline_re, ".*Prompt"},
{putline, TestDataUtf},
{getline_re, ".*Okej"}
],Me,"LC_CTYPE=\""++get_lc_ctype()++"\"; export LC_CTYPE; "," -oldshell "),
O = list_to_atom(OthReg),
O ! {self(),done},
ok.
%% Test various modes when reading from the group leade from another machine.
read_modes_ogl(Config) when is_list(Config) ->
case get_progs() of
{error,Reason} ->
{skipped,Reason};
_ ->
read_modes_gl_1(Config,answering_machine2)
end.
%% Test various modes when reading from the group leade from another machine.
read_modes_gl(Config) when is_list(Config) ->
case {get_progs(),proplists:get_value(default_shell,Config)} of
{{error,Reason},_} ->
{skipped,Reason};
{_,old} ->
{skipper,"No new shell"};
_ ->
read_modes_gl_1(Config,answering_machine1)
end.
read_modes_gl_1(_Config,Machine) ->
TestDataLine1 = [229,228,246],
TestDataLine1BinUtf = unicode:characters_to_binary(TestDataLine1),
TestDataLine1BinLatin = list_to_binary(TestDataLine1),
{ok,N2List} = create_nodename(),
MyNodeList = atom2list(node()),
register(io_proto_suite,self()),
AM1 = spawn(?MODULE,Machine,
[MyNodeList, "io_proto_suite", N2List]),
?line GL = receive X when is_pid(X) -> X end,
?dbg({group_leader,X}),
%% get_line
?line receive after 500 -> ok end, % Dont clash with the new shell...
?line "Hej\n" = io:get_line(GL,"Prompt\n"),
?line io:setopts(GL,[binary]),
?line io:format(GL,"Okej~n",[]),
?line <<"Hej\n">> = io:get_line(GL,"Prompt\n"),
?line io:setopts(GL,[{encoding,latin1}]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinLatin = chomp(io:request(GL,{get_line,latin1,"Prompt\n"})),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinUtf = chomp(io:get_line(GL,"Prompt\n")),
?line io:setopts(GL,[{encoding,unicode}]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinLatin = chomp(io:request(GL,{get_line,latin1,"Prompt\n"})),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinUtf = chomp(io:get_line(GL,"Prompt\n")),
?line io:setopts(GL,[list]),
?line io:format(GL,"Okej~n",[]),
%%get_chars
?line "Hej" = io:get_chars(GL,"Prompt\n",3),
?line io:setopts(GL,[binary]),
?line io:format(GL,"Okej~n",[]),
?line <<"Hej">> = io:get_chars(GL,"Prompt\n",3),
?line io:setopts(GL,[{encoding,latin1}]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinLatin = io:request(GL,{get_chars,latin1,"Prompt\n",3}),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinUtf = io:get_chars(GL,"Prompt\n",3),
?line io:setopts(GL,[{encoding,unicode}]),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinLatin = io:request(GL,{get_chars,latin1,"Prompt\n",3}),
?line io:format(GL,"Okej~n",[]),
?line TestDataLine1BinUtf = io:get_chars(GL,"Prompt\n",3),
?line io:setopts(GL,[list]),
?line io:format(GL,"Okej~n",[]),
%%fread
?line {ok,["Hej"]} = io:fread(GL,"Prompt\n","~s"),
?line io:setopts(GL,[binary]),
?line io:format(GL,"Okej~n",[]),
?line {ok,["Hej"]} = io:fread(GL,"Prompt\n","~s"),
?line io:setopts(GL,[{encoding,latin1}]),
?line io:format(GL,"Okej~n",[]),
?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
?line io:format(GL,"Okej~n",[]),
?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
?line io:setopts(GL,[{encoding,unicode}]),
?line io:format(GL,"Okej~n",[]),
?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
?line io:format(GL,"Okej~n",[]),
?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
?line io:setopts(GL,[list]),
?line io:format(GL,"Okej~n",[]),
?line receive
{AM1,done} ->
ok
after 5000 ->
exit(timeout)
end,
ok.
%% Test behaviour when reading broken Unicode files
broken_unicode(Config) when is_list(Config) ->
Dir = ?config(priv_dir,Config),
Latin1Name = filename:join([Dir,"latin1_data_file.dat"]),
Utf8Name = filename:join([Dir,"utf8_data_file.dat"]),
Latin1Data = iolist_to_binary(lists:duplicate(10,lists:seq(0,255)++[255,255,255])),
Utf8Data = unicode:characters_to_binary(
lists:duplicate(10,lists:seq(0,255))),
file:write_file(Latin1Name,Latin1Data),
file:write_file(Utf8Name,Utf8Data),
?line [ latin1 = heuristic_encoding_file2(Latin1Name,N,utf8) || N <- lists:seq(1,100)++[1024,2048,10000]],
?line [ utf8 = heuristic_encoding_file2(Utf8Name,N,utf8) || N <- lists:seq(1,100)++[1024,2048,10000]],
?line [ latin1 = heuristic_encoding_file2(Latin1Name,N,utf16) || N <- lists:seq(1,100)++[1024,2048,10000]],
?line [ latin1 = heuristic_encoding_file2(Latin1Name,N,utf32) || N <- lists:seq(1,100)++[1024,2048,10000]],
ok.
%%
%% From the cookbook, more or less
heuristic_encoding_file2(FileName,Chunk,Enc) ->
{ok,F} = file:open(FileName,[read,binary,{encoding,Enc}]),
loop_through_file2(F,io:get_chars(F,'',Chunk),Chunk,Enc).
loop_through_file2(_,eof,_,Enc) ->
Enc;
loop_through_file2(_,{error,_Err},_,_) ->
latin1;
loop_through_file2(F,Bin,Chunk,Enc) when is_binary(Bin) ->
loop_through_file2(F,io:get_chars(F,'',Chunk),Chunk,Enc).
%% Test eof before newline on stdin when erlang is in pipe.
eof_on_pipe(Config) when is_list(Config) ->
case {get_progs(),os:type()} of
{{error,Reason},_} ->
{skipped,Reason};
{{_,_,Erl},{unix,linux}} ->
%% Not even Linux is reliable - echo can be both styles
try
EchoLine = case os:cmd("echo -ne \"test\\ntest\"") of
"test\ntest" ->
"echo -ne \"a\\nbu\" | ";
_ ->
case os:cmd("echo \"test\\ntest\\c\"") of
"test\ntest" ->
"echo \"a\\nbu\\c\" | ";
_ ->
throw(skip)
end
end,
CommandLine1 = EchoLine ++
"\""++Erl++"\" -noshell -eval "
"'io:format(\"~p\",[io:get_line(\"\")]),"
"io:format(\"~p\",[io:get_line(\"\")]),"
"io:format(\"~p\",[io:get_line(\"\")]).' -run init stop",
case os:cmd(CommandLine1) of
"\"a\\n\"\"bu\"eof" ->
ok;
Other1 ->
exit({unexpected1,Other1})
end,
CommandLine2 = EchoLine ++
"\""++Erl++"\" -noshell -eval "
"'io:setopts([binary]),io:format(\"~p\",[io:get_line(\"\")]),"
"io:format(\"~p\",[io:get_line(\"\")]),"
"io:format(\"~p\",[io:get_line(\"\")]).' -run init stop",
case os:cmd(CommandLine2) of
"<<\"a\\n\">><<\"bu\">>eof" ->
ok;
Other2 ->
exit({unexpected2,Other2})
end
catch
throw:skip ->
{skipped,"unsupported echo program"}
end;
{_,_} ->
{skipped,"Only on linux"}
end.
%%
%% Tool for running interactive shell (stolen from the kernel
%% test suite interactive_shell_SUITE)
%%
-undef(line).
-define(line,).
rtnode(C,N) ->
rtnode(C,N,[]).
rtnode(Commands,Nodename,ErlPrefix) ->
rtnode(Commands,Nodename,ErlPrefix,[]).
rtnode(Commands,Nodename,ErlPrefix,Extra) ->
case get_progs() of
{error,_Reason} ->
{skip,"No runerl present"};
{RunErl,ToErl,Erl} ->
case create_tempdir() of
{error, Reason2} ->
{skip, Reason2};
Tempdir ->
SPid = start_runerl_node(RunErl, ErlPrefix++
"\\\""++Erl++"\\\"",
Tempdir, Nodename, Extra),
CPid = start_toerl_server(ToErl, Tempdir),
put(getline_skipped, []),
Res = (catch get_and_put(CPid, Commands, 1)),
case stop_runerl_node(CPid) of
{error,_} ->
CPid2 = start_toerl_server(ToErl, Tempdir),
put(getline_skipped, []),
ok = get_and_put
(CPid2,
[{putline,[7]},
{sleep,
timeout(short)},
{putline,""},
{getline," -->"},
{putline,"s"},
{putline,"c"},
{putline,""}], 1),
stop_runerl_node(CPid2);
_ ->
ok
end,
wait_for_runerl_server(SPid),
ok = ?RM_RF(Tempdir),
ok = Res
end
end.
timeout(long) ->
2 * timeout(normal);
timeout(short) ->
timeout(normal) div 10;
timeout(normal) ->
10000 * test_server:timetrap_scale_factor().
%% start_noshell_node(Name) ->
%% PADir = filename:dirname(code:which(?MODULE)),
%% {ok, Node} = test_server:start_node(Name,slave,[{args," -noshell -pa "++
%% PADir++" "}]),
%% Node.
%% stop_noshell_node(Node) ->
%% test_server:stop_node(Node).
-ifndef(debug).
rm_rf(Dir) ->
try
{ok,List} = file:list_dir(Dir),
Files = [filename:join([Dir,X]) || X <- List],
[case file:list_dir(Y) of
{error, enotdir} ->
ok = file:delete(Y);
_ ->
ok = rm_rf(Y)
end || Y <- Files],
ok = file:del_dir(Dir),
ok
catch
_:Exception -> {error, {Exception,Dir}}
end.
-endif.
get_and_put(_CPid,[],_) ->
ok;
get_and_put(CPid, [{sleep, X}|T],N) ->
?dbg({sleep, X}),
receive
after X ->
get_and_put(CPid,T,N+1)
end;
get_and_put(CPid, [{getline_pred,Pred,Msg}|T]=T0, N)
when is_function(Pred) ->
?dbg({getline, Match}),
CPid ! {self(), {get_line, timeout(normal)}},
receive
{get_line, timeout} ->
error_logger:error_msg("~p: getline timeout waiting for \"~s\" "
"(command number ~p, skipped: ~p)~n",
[?MODULE,Msg,N,get(getline_skipped)]),
{error, timeout};
{get_line, Data} ->
?dbg({data,Data}),
case Pred(Data) of
yes ->
put(getline_skipped, []),
get_and_put(CPid, T,N+1);
no ->
error_logger:error_msg("~p: getline match failure "
"\"~s\" "
"(command number ~p)\n",
[?MODULE,Msg,N]),
{error, no_match};
maybe ->
List = get(getline_skipped),
put(getline_skipped, List ++ [Data]),
get_and_put(CPid, T0, N)
end
end;
get_and_put(CPid, [{getline, Match}|T],N) ->
?dbg({getline, Match}),
F = fun(Data) ->
case lists:prefix(Match, Data) of
true -> yes;
false -> maybe
end
end,
get_and_put(CPid, [{getline_pred,F,Match}|T], N);
get_and_put(CPid, [{getline_re, Match}|T],N) ->
F = fun(Data) ->
case re:run(Data, Match, [{capture,none}]) of
match -> yes;
_ -> maybe
end
end,
get_and_put(CPid, [{getline_pred,F,Match}|T], N);
get_and_put(CPid, [{putline_raw, Line}|T],N) ->
?dbg({putline_raw, Line}),
CPid ! {self(), {send_line, Line}},
Timeout = timeout(normal),
receive
{send_line, ok} ->
get_and_put(CPid, T,N+1)
after Timeout ->
error_logger:error_msg("~p: putline_raw timeout (~p) sending "
"\"~s\" (command number ~p)~n",
[?MODULE, Timeout, Line, N]),
{error, timeout}
end;
get_and_put(CPid, [{putline, Line}|T],N) ->
?dbg({putline, Line}),
CPid ! {self(), {send_line, Line}},
Timeout = timeout(normal),
receive
{send_line, ok} ->
get_and_put(CPid, [{getline, []}|T],N)
after Timeout ->
error_logger:error_msg("~p: putline timeout (~p) sending "
"\"~s\" (command number ~p)~n[~p]~n",
[?MODULE, Timeout, Line, N,get()]),
{error, timeout}
end.
wait_for_runerl_server(SPid) ->
Ref = erlang:monitor(process, SPid),
Timeout = timeout(long),
receive
{'DOWN', Ref, process, SPid, _} ->
ok
after Timeout ->
{error, timeout}
end.
stop_runerl_node(CPid) ->
Ref = erlang:monitor(process, CPid),
CPid ! {self(), kill_emulator},
Timeout = timeout(long),
receive
{'DOWN', Ref, process, CPid, noproc} ->
ok;
{'DOWN', Ref, process, CPid, normal} ->
ok;
{'DOWN', Ref, process, CPid, {error, Reason}} ->
{error, Reason}
after Timeout ->
{error, timeout}
end.
get_progs() ->
case os:type() of
{unix,freebsd} ->
{error,"cant use run_erl on freebsd"};
{unix,openbsd} ->
{error,"cant use run_erl on openbsd"};
{unix,_} ->
case os:find_executable("run_erl") of
RE when is_list(RE) ->
case os:find_executable("to_erl") of
TE when is_list(TE) ->
case os:find_executable("erl") of
E when is_list(E) ->
{RE,TE,E};
_ ->
{error, "Could not find erl command"}
end;
_ ->
{error, "Could not find to_erl command"}
end;
_ ->
{error, "Could not find run_erl command"}
end;
_ ->
{error, "Not a unix OS"}
end.
create_tempdir() ->
create_tempdir(filename:join(["/tmp","rtnode"++os:getpid()]),$A).
create_tempdir(Dir,X) when X > $Z, X < $a ->
create_tempdir(Dir,$a);
create_tempdir(Dir,X) when X > $z ->
Estr = lists:flatten(
io_lib:format("Unable to create ~s, reason eexist",
[Dir++[$z]])),
{error, Estr};
create_tempdir(Dir0, Ch) ->
% Expect fairly standard unix.
Dir = Dir0++[Ch],
case file:make_dir(Dir) of
{error, eexist} ->
create_tempdir(Dir0, Ch+1);
{error, Reason} ->
Estr = lists:flatten(
io_lib:format("Unable to create ~s, reason ~p",
[Dir,Reason])),
{error,Estr};
ok ->
Dir
end.
create_nodename() ->
create_nodename($A).
create_nodename(X) when X > $Z, X < $a ->
create_nodename($a);
create_nodename(X) when X > $z ->
{error,out_of_nodenames};
create_nodename(X) ->
NN = "rtnode"++os:getpid()++[X],
case file:read_file_info(filename:join(["/tmp",NN])) of
{error,enoent} ->
Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")),
{ok,NN++"@"++Host};
_ ->
create_nodename(X+1)
end.
start_runerl_node(RunErl,Erl,Tempdir,Nodename,Extra) ->
XArg = case Nodename of
[] ->
[];
_ ->
" -sname "++(if is_atom(Nodename) -> atom_to_list(Nodename);
true -> Nodename
end)++
" -setcookie "++atom_to_list(erlang:get_cookie())
end,
XXArg = case Extra of
[] ->
[];
_ ->
" "++Extra
end,
spawn(fun() ->
?dbg("\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++
" \""++Erl++XArg++XXArg++"\""),
os:cmd("\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++
" \""++Erl++XArg++XXArg++"\"")
end).
start_toerl_server(ToErl,Tempdir) ->
Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir]),
receive
{Pid,started} ->
Pid;
{Pid,error,Reason} ->
{error,Reason}
end.
try_to_erl(_Command, 0) ->
{error, cannot_to_erl};
try_to_erl(Command, N) ->
?dbg({?LINE,N}),
Port = open_port({spawn, Command},[eof,{line,1000}]),
Timeout = timeout(normal) div 2,
receive
{Port, eof} ->
receive after Timeout ->
ok
end,
try_to_erl(Command, N-1)
after Timeout ->
?dbg(Port),
Port
end.
toerl_server(Parent,ToErl,Tempdir) ->
Port = try_to_erl("\""++ToErl++"\" "++Tempdir++"/ 2>/dev/null",8),
case Port of
P when is_port(P) ->
Parent ! {self(),started};
{error,Other} ->
Parent ! {self(),error,Other},
exit(Other)
end,
case toerl_loop(Port,[]) of
normal ->
ok;
{error, Reason} ->
error_logger:error_msg("toerl_server exit with reason ~p~n",
[Reason]),
exit(Reason)
end.
toerl_loop(Port,Acc) ->
?dbg({toerl_loop, Port, Acc}),
receive
{Port,{data,{Tag0,Data}}} when is_port(Port) ->
?dbg({?LINE,Port,{data,{Tag0,Data}}}),
case Acc of
[{noeol,Data0}|T0] ->
toerl_loop(Port,[{Tag0, Data0++Data}|T0]);
_ ->
toerl_loop(Port,[{Tag0,Data}|Acc])
end;
{Pid,{get_line,Timeout}} ->
case Acc of
[] ->
case get_data_within(Port,Timeout,[]) of
timeout ->
Pid ! {get_line, timeout},
toerl_loop(Port,[]);
{noeol,Data1} ->
Pid ! {get_line, timeout},
toerl_loop(Port,[{noeol,Data1}]);
{eol,Data2} ->
Pid ! {get_line, Data2},
toerl_loop(Port,[])
end;
[{noeol,Data3}] ->
case get_data_within(Port,Timeout,Data3) of
timeout ->
Pid ! {get_line, timeout},
toerl_loop(Port,Acc);
{noeol,Data4} ->
Pid ! {get_line, timeout},
toerl_loop(Port,[{noeol,Data4}]);
{eol,Data5} ->
Pid ! {get_line, Data5},
toerl_loop(Port,[])
end;
List ->
{NewAcc,[{eol,Data6}]} = lists:split(length(List)-1,List),
Pid ! {get_line,Data6},
toerl_loop(Port,NewAcc)
end;
{Pid, {send_line, Data7}} ->
Port ! {self(),{command, Data7++"\n"}},
Pid ! {send_line, ok},
toerl_loop(Port,Acc);
{_Pid, kill_emulator} ->
Port ! {self(),{command, "init:stop().\n"}},
Timeout1 = timeout(long),
receive
{Port,eof} ->
normal
after Timeout1 ->
{error, kill_timeout}
end;
{Port, eof} ->
{error, unexpected_eof};
Other ->
{error, {unexpected, Other}}
end.
millistamp() ->
erlang:monotonic_time(milli_seconds).
get_data_within(Port, X, Acc) when X =< 0 ->
?dbg({get_data_within, X, Acc, ?LINE}),
receive
{Port,{data,{Tag0,Data}}} ->
?dbg({?LINE,Port,{data,{Tag0,Data}}}),
{Tag0, Acc++Data}
after 0 ->
case Acc of
[] ->
timeout;
Noeol ->
{noeol,Noeol}
end
end;
get_data_within(Port, Timeout, Acc) ->
?dbg({get_data_within, Timeout, Acc, ?LINE}),
T1 = millistamp(),
receive
{Port,{data,{noeol,Data}}} ->
?dbg({?LINE,Port,{data,{noeol,Data}}}),
Elapsed = millistamp() - T1 + 1,
get_data_within(Port, Timeout - Elapsed, Acc ++ Data);
{Port,{data,{eol,Data1}}} ->
?dbg({?LINE,Port,{data,{eol,Data1}}}),
{eol, Acc ++ Data1}
after Timeout ->
timeout
end.
get_default_shell() ->
Match = fun(Data) ->
case lists:prefix("undefined", Data) of
true ->
yes;
false ->
case re:run(Data, "<\\d+[.]\\d+[.]\\d+>",
[{capture,none}]) of
match -> no;
_ -> maybe
end
end
end,
try
rtnode([{putline,""},
{putline, "whereis(user_drv)."},
{getline_pred, Match, "matching of user_drv pid"}], []),
old
catch _E:_R ->
?dbg({_E,_R}),
new
end.
%%
%% Test I/O-server
%%
start_io_server_proxy() ->
spawn_link(?MODULE,io_server_proxy,[#state{}]).
proxy_getall(Pid) ->
req(Pid,{self(),getall}).
proxy_setnext(Pid,Data) when is_list(Data) ->
req(Pid,{self(),next,Data}).
proxy_quit(Pid) ->
req(Pid,{self(),quit}).
req(Pid,Mess) ->
Pid ! Mess,
receive
{Pid, Answer} ->
Answer
after 5000 ->
exit(timeout)
end.
io_server_proxy(State) ->
receive
{io_request, From, ReplyAs, Request} ->
case request(Request,State) of
{Tag, Reply, NewState} when Tag =:= ok; Tag =:= error ->
reply(From, ReplyAs, Reply),
io_server_proxy(NewState);
{stop, Reply, _NewState} ->
reply(From, ReplyAs, Reply),
exit(Reply)
end;
%% Private message
{From, next, Data} ->
From ! {self(), ok},
io_server_proxy(State#state{nxt = Data});
{From, getall} ->
From ! {self(), lists:reverse(State#state.q)},
io_server_proxy(State#state{q=[]});
{From, quit} ->
From ! {self(), lists:reverse(State#state.q)},
ok;
_Unknown ->
io_server_proxy(State)
end.
reply(From, ReplyAs, Reply) ->
From ! {io_reply, ReplyAs, Reply}.
request({put_chars, Encoding, Chars}, State) ->
{ok, ok, State#state{q=[{put_chars, Encoding, Chars} | State#state.q ]}};
request({put_chars, Encoding, Module, Function, Args}, State) ->
{ok, ok, State#state{q=[{put_chars, Encoding, Module, Function, Args} |
State#state.q ]}};
request({put_chars,Chars}, State) ->
{ok, ok, State#state{q=[{put_chars, Chars} | State#state.q ]}};
request({put_chars,M,F,As}, State) ->
{ok, ok, State#state{q=[{put_chars, M,F,As} | State#state.q ]}};
request({get_until, Encoding, Prompt, M, F, As}, State) ->
{ok, convert(State#state.nxt, Encoding, State#state.mode), State#state{nxt = eof, q = [{get_until, Encoding, Prompt, M, F, As} | State#state.q]}};
request({get_chars, Encoding, Prompt, N}, State) ->
{ok, convert(State#state.nxt, Encoding, State#state.mode), State#state{nxt = eof,
q = [{get_chars, Encoding, Prompt, N} |
State#state.q]}};
request({get_line, Encoding, Prompt}, State) ->
{ok, convert(State#state.nxt, Encoding, State#state.mode),
State#state{nxt = eof,
q = [{get_line, Encoding, Prompt} |
State#state.q]}};
request({get_until, Prompt, M, F, As}, State) ->
{ok, convert(State#state.nxt, latin1, State#state.mode),
State#state{nxt = eof,
q = [{get_until, Prompt, M, F, As} | State#state.q]}};
request({get_chars, Prompt, N}, State) ->
{ok, convert(State#state.nxt, latin1, State#state.mode),
State#state{nxt = eof,
q = [{get_chars, Prompt, N} |
State#state.q]}};
request({get_line, Prompt}, State) ->
{ok, convert(State#state.nxt, latin1, State#state.mode),
State#state{nxt = eof,
q = [{get_line, Prompt} |
State#state.q]}};
request({get_geomentry,_}, State) ->
{error, {error,enotsup}, State};
request({setopts, Opts}, State) when Opts =:= [{binary, false}]; Opts =:= [list] ->
{ok, ok, State#state{q=[{setopts, Opts} | State#state.q ], mode = list}};
request({setopts, Opts}, State) when Opts =:= [{binary, true}]; Opts =:= [binary] ->
{ok, ok, State#state{q=[{setopts, Opts} | State#state.q ], mode = binary}};
request(getopts, State) ->
{ok, case State#state.mode of
list -> [{binary,false}];
binary -> [{binary, true}]
end, State#state{q=[getopts | State#state.q ]}};
request({requests, Reqs}, State) ->
multi_request(Reqs, {ok, ok, State}).
multi_request([R|Rs], {ok, _Res, State}) ->
multi_request(Rs, request(R, State));
multi_request([_|_], Error) ->
Error;
multi_request([], State) ->
State.
convert(Atom,_,_) when is_atom(Atom) ->
Atom;
convert(Data, unicode, list) ->
unicode:characters_to_list(Data,unicode);
convert(Data, latin1, list) ->
try
L = unicode:characters_to_list(Data, unicode),
[ true = Ch =< 255 || Ch <- L ],
L
catch
_:_ ->
{error, {cannot_convert, unicode, latin1}}
end;
convert(Data, unicode, binary) ->
unicode:characters_to_binary(Data,unicode,unicode);
convert(Data, latin1, binary) ->
case unicode:characters_to_binary(Data, unicode, latin1) of
Bin when is_binary(Bin) ->
Bin;
_ ->
{error, {cannot_convert, unicode, latin1}}
end.
hostname() ->
from($@, atom_to_list(node())).
from(H, [H | T]) -> T;
from(H, [_ | T]) -> from(H, T);
from(_, []) -> [].
atom2list(A) ->
lists:flatten(io_lib:format("~w", [A])).
chomp([]) ->
[];
chomp([$\n]) ->
[];
chomp([H|T]) ->
[H|chomp(T)];
chomp(<<>>) ->
<<>>;
chomp(<<$\n>>) ->
<<>>;
chomp(<<Ch,Rest/binary>>) ->
X = chomp(Rest),
<<Ch,X/binary>>;
chomp(Atom) ->
Atom.
do(Fun) ->
{_,Ref} = spawn_monitor(fun() ->
exit(Fun())
end),
Ref.
done(Ref) ->
receive
{'DOWN',Ref,process,_,Result} ->
Result
end.