diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/test_server/src/things | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/test_server/src/things')
-rw-r--r-- | lib/test_server/src/things/distr_startup_SUITE.erl | 238 | ||||
-rw-r--r-- | lib/test_server/src/things/mnesia_power_SUITE.erl | 125 | ||||
-rw-r--r-- | lib/test_server/src/things/random_kill_SUITE.erl | 81 | ||||
-rw-r--r-- | lib/test_server/src/things/soft.gs.txt | 16 | ||||
-rw-r--r-- | lib/test_server/src/things/verify.erl | 199 |
5 files changed, 659 insertions, 0 deletions
diff --git a/lib/test_server/src/things/distr_startup_SUITE.erl b/lib/test_server/src/things/distr_startup_SUITE.erl new file mode 100644 index 0000000000..0d4e467570 --- /dev/null +++ b/lib/test_server/src/things/distr_startup_SUITE.erl @@ -0,0 +1,238 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(distr_startup_SUITE). +-compile([export_all]). +%%-define(line_trace,1). +-include("test_server.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(suite) -> [reads,writes]. + +-define(iterations,10000). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +app1() -> + {application, app1, + [{description, "ERTS CXC 138 10"}, + {vsn, "2.0"}, + {applications, [kernel, stdlib]}, + {mod, {ch_sup, {app1, 1, 3}}}]}. + +app3() -> + {application, app3, + [{description, "ERTS CXC 138 10"}, + {vsn, "2.0"}, + {applications, [kernel, stdlib]}, + {mod, {ch_sup, {app3, 7, 9}}}]}. + + +config(Fd,C1,C2,C3) -> + io:format(Fd, + "[{kernel, [{sync_nodes_optional, ['~s','~s','~s']}," + "{sync_nodes_timeout, 1}," + "{distributed, [{app1, ['~s', '~s', '~s']}," + "{app2, 10000, ['~s', '~s', '~s']}," + "{app3, 5000, [{'~s', '~s'}, '~s']}]}]}].~n", + [C1,C2,C3, C1,C2,C3, C1,C2,C3, C1,C2,C3]). + +from(H, [H | T]) -> T; +from(H, [_ | T]) -> from(H, T); +from(H, []) -> []. + +%%----------------------------------------------------------------- +%% Test suite for distributed applications, tests start, load +%% etc indirectly. +%% Should be started in a CC view with: +%% erl -sname master -rsh ctrsh +%%----------------------------------------------------------------- +start_nodes(Conf) -> + % Write a config file + ?line Nodes = ?config(nodes,Conf), + ?line [C1,C2,C3|_] = Nodes, %% Need at least 3 nodes + ?line Dir = ?config(priv_dir,Conf), + ?line {ok, Fd} = file:open(Dir ++ "sys.config", write), + ?line config(Fd,C1,C2,C3), + ?line file:close(Fd), + ?line Config = Dir ++ "sys", + + % Test [cp1, cp2, cp3] + ?line {ok, Cp1} = start_node(lists:nth(1,Nodes), Config), + ?line {ok, Cp2} = start_node(lists:nth(2,Nodes), Config), + ?line {ok, Cp3} = start_node(lists:nth(3,Nodes), Config), + % Start app1 and make sure cp1 starts it + %%?line rpc:multicall([Cp1, Cp2, Cp3], application, load, [app1()]), + %%?line rpc:multicall([Cp1, Cp2, Cp3], application, start,[app1,permanent]), + ?line test_server:sleep(1000), + {Cp1,Cp2,Cp3}. + +stop_nodes({Cp1,Cp2,Cp3}) -> + ?line stop_node(Cp1), + ?line stop_node(Cp2), + ?line stop_node(Cp3). + +start_node(NodeAtHost, Config) -> + ?line NodeAtHostStr = atom_to_list(NodeAtHost), + ?line HostStr = from($@,NodeAtHostStr), + ?line NodeStr = lists:reverse(from($@,lists:reverse(NodeAtHostStr))), + ?line Host = list_to_atom(HostStr), + ?line Node = list_to_atom(NodeStr), + ?line io:format("Launching slave node ~p@~p ~p",[Node,Host,Config]), + ?line slave:start(Host, Node, lists:concat(["-config ", Config])). + +stop_node(Node) -> + ?line rpc:cast(Node, erlang, halt, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_client_process(Cp,Mode,NodeNum) -> + io:format("Starting client process at ~p in mode ~p",[Cp,Mode]), + ?line case rpc:call(Cp, erlang, spawn, + [?MODULE, client, + [Mode,NodeNum,self(),random:uniform(1000)]]) of + {badrpc,Reason} -> + ?line exit({badrpc,{Cp,Reason}}); + Client -> + ?line Client + end. + +start_clients(Mode,Conf) -> + ?line random:seed(4711,0,0), + ?line {Cp1,Cp2,Cp3} = start_nodes(Conf), + ?line Client1 = start_client_process(Cp1,Mode,1), + ?line Client2 = start_client_process(Cp2,Mode,2), + ?line Client3 = start_client_process(Cp3,Mode,3), + test_server:format(1,"All 3 nodes started, " + "power off client(s) any time...",[]), + Client1 ! go, + Client2 ! go, + Client3 ! go, + {{Cp1,Cp2,Cp3},{Client1,Client2,Client3}}. + +stop_clients(Cps) -> + test_server:format(1,"Test completed.",[]), + ?line stop_nodes(Cps). + +data() -> + {{self(),foo,bar,[1,2,3,4,5,6,7],{{{{}}}}, + "We need pretty long packages, so that there is a big risk " + "of cutting it in the middle when suddenly turning off " + "the power or breaking the connection. " + "We don't check the contents of the data very much, but " + "at least there is a magic cookie at the end (123456)." + "If that one arrives correctly, the link is ok as far " + "as we are concerned."}, + 123456}. + +reads(suite) -> []; +reads(Conf) -> + ?line {Cps,_} = start_clients(w,Conf), + ?line read_loop(?iterations,0), + ?line stop_clients(Cps), + ok. + +read_loop(0,M) -> + ok; +read_loop(N,M) -> + ?line Dog = test_server:timetrap(test_server:seconds(0.5)), + M2 = + receive + {Node,Count,{_,123456}} -> + ?line setelement(Node,M,element(Node,M)+1); + {Node,Count,Data} -> + ?line exit({network_transmission_error,Data}); + {nodedown,Node} -> + ?line test_server:format(1,"Node ~s went down",[Node]), + ?line M; + Other -> + ?line M + after test_server:seconds(0.1) -> + ?line io:format("No message!"), + ?line M + end, + ?line test_server:timetrap_cancel(Dog), + ?line M3 = + case N rem 100 of + 0 -> io:format("~p reads to go (~w msgs)",[N,M2]), + {0,0,0}; + _ -> M2 + end, + ?line read_loop(N-1,M3). + +client(w,NodeNum,Pid,Seed) -> + random:seed(Seed,0,0), + receive + go -> ok + end, + client_write_loop(Pid,0,NodeNum,data()); +client(r,NodeNum,Pid,Seed) -> + random:seed(Seed,0,0), + receive + go -> ok + end, + client_read_loop(0). + +client_write_loop(Pid,N,NodeNum,Data) -> + test_server:sleep(random:uniform(20)), + Pid ! {NodeNum,N,Data}, + client_write_loop(Pid,N+1,NodeNum,Data). + +writes(suite) -> []; +writes(Conf) -> + ?line {Cps,{C1,C2,C3}} = start_clients(r,Conf), + ?line write_loop(2*?iterations,{C1,C2,C3},data()), + ?line stop_clients(Cps), + ok. + +write_loop(0,_,_) -> + ok; +write_loop(N,Clients,Data) -> + ?line Dog = test_server:timetrap(test_server:seconds(0.5)), + ?line Client = element(random:uniform(size(Clients)),Clients), + ?line Client ! {node(),N,Data}, + ?line test_server:timetrap_cancel(Dog), + receive + {nodedown,Node} -> + ?line test_server:format(1,"Node ~s went down",[Node]) + after 0 -> + ?line ok + end, + ?line case N rem 100 of + 0 -> io:format("~p writes to go",[N]); + _ -> ok + end, + ?line write_loop(N-1,Clients,Data). + +client_read_loop(N) -> + receive + {Node,Count,{_,123456}} -> + ?line ok; + {Node,Count,Data} -> + ?line io:format("~p(~p): transmission error from node ~p(~p): ~p", + [node(),N,Node,Count,Data]); + Other -> + ?line io:format("~p(~p): got a strange message: ~p", + [node(),N,Other]) + end, + client_read_loop(N+1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + diff --git a/lib/test_server/src/things/mnesia_power_SUITE.erl b/lib/test_server/src/things/mnesia_power_SUITE.erl new file mode 100644 index 0000000000..281dac7742 --- /dev/null +++ b/lib/test_server/src/things/mnesia_power_SUITE.erl @@ -0,0 +1,125 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(mnesia_power_SUITE). +-compile([export_all]). +%%-define(line_trace,1). +-include("test_server.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(suite) -> [run]. + +-define(iterations,3). %% nof power-off cycles to do before acceptance +-define(rows,8). %% nof database rows to use (not too big, please) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(sum_table_1,{row,a,b,c,s}). + +run(suite) -> []; +run(Config) -> + ?line mnesia:create_schema([node()]), + ?line mnesia:start(), + ?line mnesia:create_table([{name, sum_table_1}, {disc_copies,[node()]}, + {attributes,record_info(fields,sum_table_1)}]), + ?line run_test(Config,?iterations). + +run(Config,N) -> + ?line mnesia:start(), + ?line check_consistency(sum_table_1), + case N of + 0 -> ?line ok; + N -> ?line run_test(Config,N) + end. + +run_test(Config,N) -> + ?line Pid1a = start_manipulator(sum_table_1), + ?line Pid1b = start_manipulator(sum_table_1), + ?line Pid1c = start_manipulator(sum_table_1), + ?line test_server:resume_point(?MODULE,run,[Config,N-1]), + ?line test_server:format(1,"Manipulating data like crazy now, " + "power off any time..."), + ?line test_server:sleep(infinity). + +start_manipulator(Table) -> + ?line spawn_link(?MODULE,manipulator_init,[Table]). + +manipulator_init(Table) -> + random:seed(4711,0,0), + manipulator(0,Table). + +manipulator(N,Table) -> + ?line Fun = + fun() -> + ?line Row = random:uniform(?rows), + ?line A = random:uniform(100000), + ?line B = random:uniform(100000), + ?line C = random:uniform(100000), + ?line Sum = A+B+C, + ?line case mnesia:write(#sum_table_1 + {row=Row,a=A,b=B,c=C,s=Sum}) of + ok -> ok; + Other -> + ?line io:format("Trans failed: ~p\n",[Other]) + end + end, + ?line mnesia:transaction(Fun), + case mnesia:table_info(sum_table_1,size) of + 0 -> exit(still_empty); + _ -> ok + end, + case N rem 2000 of + 0 -> io:format("~p did ~p operations",[self(),N]), + check_consistency(sum_table_1); + _ -> ok + end, + ?line manipulator(N+1,Table). + +check_consistency(Table) -> + io:format("Checking consistency of table ~p\n",[Table]), + All = mnesia:table_info(Table,wild_pattern), + ?line Fun = + fun() -> + mnesia:match_object(All) + end, + ?line case mnesia:transaction(Fun) of + {atomic,Val} -> + check_consistency_rows(Val,0); + Other -> + io:format("Trans failed: ~p\n",[Other]), + exit(failed), + check_consistency(Table) + end. + +check_consistency_rows([#sum_table_1{a=A,b=B,c=C,s=Sum}|Rows],N) -> + ?line Sum=A+B+C, + ?line check_consistency_rows(Rows,N+1); +check_consistency_rows([],N) -> + io:format("All ~p rows were consistent\n",[N]), + {ok,N}; +check_consistency_rows(Thing,N) -> + io:format("Mnesia transaction returned:\n~p\n",[Thing]), + exit({bad_format,Thing}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + + + diff --git a/lib/test_server/src/things/random_kill_SUITE.erl b/lib/test_server/src/things/random_kill_SUITE.erl new file mode 100644 index 0000000000..4fcd63e3af --- /dev/null +++ b/lib/test_server/src/things/random_kill_SUITE.erl @@ -0,0 +1,81 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(random_kill_SUITE). +-compile([export_all]). +%%-define(line_trace,1). +-include("test_server.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +all(suite) -> [run]. + +-define(iterations,25). %% Kill this many processes, + %% possibly with reboots in between + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +run(suite) -> []; +run(Config) -> + registered(?iterations). + +registered(0) -> + ok; +registered(N) -> + random:seed(3461*N,1159*N,351*N), + Pid = select_victim(registered), + test_server:resume_point(?MODULE,registered,[N-1]), + test_server:format("About to kill pid ~p (~p)\n~p", + [Pid,process_info(Pid,registered_name),info(Pid)]), + %%exit(Pid,kill), + registered(N-1). + +info(Pid) -> + Rest0 = tl(pid_to_list(Pid)), + {P1,Rest1} = get_until($.,Rest0), + {P2,Rest2} = get_until($.,Rest1), + {P3,_} = get_until($>,Rest2), + c:i(list_to_integer(P1),list_to_integer(P2),list_to_integer(P3)). + +get_until(Ch,L) -> + get_until(Ch,L,[]). +get_until(Ch,[],Acc) -> + {lists:reverse(Acc),[]}; +get_until(Ch,[Ch|T],Acc) -> + {lists:reverse(Acc),T}; +get_until(Ch,[H|T],Acc) -> + get_until(Ch,T,[H|Acc]). + +select_victim(registered) -> + Pids = + lists:map(fun(Server)-> whereis(Server) end,registered()), + ImmunePids = + [self()|lists:map(fun(Job)-> element(2,Job) end,test_server:jobs())], + SuitablePids = + lists:filter(fun(Pid)-> case lists:member(Pid,ImmunePids) of + true -> false; + false -> true + end + end, Pids), + Selected = random:uniform(length(SuitablePids)), + io:format("Selected ~p if ~p",[Selected,length(SuitablePids)]), + lists:nth(Selected,SuitablePids). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + diff --git a/lib/test_server/src/things/soft.gs.txt b/lib/test_server/src/things/soft.gs.txt new file mode 100644 index 0000000000..ec57884997 --- /dev/null +++ b/lib/test_server/src/things/soft.gs.txt @@ -0,0 +1,16 @@ +6> gs:start(). +RealTimeViolation, 478ms (after 1164 good) +{1,<0.65.0>} +RealTimeViolation, 352ms (after 0 good) +RealTimeViolation, 492ms (after 0 good) +RealTimeViolation, 166ms (after 0 good) +RealTimeInfo, 18ms (after 7 good) +RealTimeViolation, 115ms (after 13 good) +7> application-specific initialization failed: couldn't connect to display ":0.0" +RealTimeViolation, 20340ms (after 0 good) +gs error: user backend died reason {port_handler,#Port,normal} + +RealTimeInfo, 31ms (after 21 good) +RealTimeInfo, 21ms (after 69 good) +RealTimeInfo, 21ms (after 119 good) +RealTimeInfo, 21ms (after 169 good) diff --git a/lib/test_server/src/things/verify.erl b/lib/test_server/src/things/verify.erl new file mode 100644 index 0000000000..eac20c013e --- /dev/null +++ b/lib/test_server/src/things/verify.erl @@ -0,0 +1,199 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(verify). + +-export([dir/0, dir/1]). + +%% usage verify:dir() +%% or verify:dir(Dir) +%% +%% runs tests on all files with the extension ".t1" +%% creates an error log file verify.log in the directory where the +%% tests were run + +-import(lists, [reverse/1, foldl/3, map/2]). + +dir() -> + dir("."). + +dir(Dir) -> + case file:list_dir(Dir) of + {ok, Files} -> + VFiles = collect_vers(Files, []), + VFiles1 = map(fun(F) -> Dir ++ "/" ++ F end, VFiles), + Nerrs = foldl(fun(F, Sum) -> + case file(F) of + {file,_,had,N,errors} -> + Sum + N; + no_errors -> + Sum; + Other -> + Sum + 1 + end + end, 0, VFiles1), + case Nerrs of + 0 -> no_errors; + _ -> {dir,Dir,had,Nerrs,errors} + end; + _ -> + {error, cannot,list_dir, Dir} + end. + +collect_vers([H|T], L) -> + case reverse(H) of + [$1,$t,$.|T1] -> collect_vers(T, [reverse(T1)|L]); + _ -> collect_vers(T, L) + end; +collect_vers([], L) -> + L. + +file(File) -> + case file:open(File ++ ".t1", read) of + {ok, S} -> + io:format("Verifying: ~s\n", [File]), + ErrFile = File ++ ".errs", + {ok, E} = file:open(ErrFile, write), + Bind0 = erl_eval:new_bindings(), + NErrs = do(S, {E, File, Bind0, 0}, 1), + file:close(S), + file:close(E), + case NErrs of + 0 -> + file:delete(ErrFile), + no_errors; + _ -> + {file,File,had,NErrs,errors} + end; + _ -> + error_in_opening_file + end. + +do(S, Env, Line) -> + R = io:scan_erl_exprs(S, '', Line), + do1(R, S, Env). + +do1({eof,_}, _, {_,_,_,NErrs}) -> + NErrs; +do1({ok,Toks,Next}, S, Env0) -> + E1 = handle_toks(Toks, Next, Env0), + do(S, E1, Next); +do1({error, {Line,Mod,Args}, Next}, S, E) -> + io:format("*** ~w ~p~n", [Line,Mod:format_error(Args)]), + E1 = add_error(E), + do(S, E1, Next). + +add_error({Stream, File, Bindings, N}) -> {Stream, File, Bindings, N+1}. + +handle_toks(Toks, Line, Env0) -> + %% io:format("Toks:~p\n", [Toks]). + case erl_parse:parse_exprs(Toks) of + {ok, Exprs} -> + %% io:format("Got:~p\n", [Exprs]), + eval(Exprs, Line, Env0); + {error, {LineNo, Mod, What}} -> + Str = apply(Mod, format_error, [What]), + io:format("*** Line:~w ***~s\n", [LineNo, Str]), + add_error(Env0); + Parse_error -> + io:format("Parse Error:~p\n",[Parse_error]), + add_error(Env0) + end. + +forget([{var,_,Name}], B0) -> erl_eval:del_binding(Name, B0); +forget([], _) -> erl_eval:new_bindings(). + +eval([{call,_,{atom,_,f}, Args}], _, {Stream, Bind0, Errs}) -> + Bind1 = forget(Args, Bind0), + {Stream, Bind1, Errs}; +eval(Exprs, Line, {Stream, File, Bind0, NErrs}) -> + %% io:format("Bindings >> ~p\n", [Bind0]), + %% io:format("Exprs >> ~p\n", [Exprs]), + case catch erl_eval:exprs(Exprs, Bind0) of + {'EXIT', Reason} -> + out_both(Stream, "----------------------------------~n", []), + out_both(Stream, "File:~s Error in:~s~n", [File, pp(Exprs)]), + print_bindings(Stream, Exprs, Bind0), + print_lhs(Stream, Exprs), + out_both(Stream, '*** Rhs evaluated to:~p~n',[rhs(Exprs, Bind0)]), + {Stream, File, Bind0, NErrs+1}; + {value, _, Bind1} -> + {Stream, File, Bind1, NErrs} + end. + +pp([H]) -> erl_pp:expr(H); +pp([H|T]) -> [erl_pp:expr(H),$,|pp(T)]; +pp([]) -> []. + +print_bindings(E, Form, Bindings) -> + case varsin(Form) of + [] -> + true; + Vars -> + print_vars(E, Vars, Bindings) + end. + +print_vars(E, [Var|T], Bindings) -> + case erl_eval:binding(Var, Bindings) of + {value, Val} -> + out_both(E, '~s = ~p\n',[Var, Val]); + unbound -> + out_both(E, '~s *is unbound*\n', [Var]) + end, + print_vars(E, T, Bindings); +print_vars(_, [], _) -> + true. + + +out_both(E, Format, Data) -> + io:format(Format, Data), + io:format(E, Format, Data). + +print_lhs(E, [{match, _, Lhs, Rhs}]) -> + %% io:format(">>>> here:~w\n",[Lhs]), + out_both(E, '*** Lhs was:~s\n',[erl_pp:expr(Lhs)]); +print_lhs(E, _) -> + out_both(E, '** UNDEFINED **', []). + + +rhs([{match, _, Lhs, Rhs}], Bindings) -> + case catch erl_eval:exprs([Rhs], Bindings) of + {value, Val, _} -> Val; + Other -> undefined() + end; +rhs(_, _) -> + undefined(). + +varsin(X) -> varsin(X, []). + +varsin({var,_,'_'}, L) -> + L; +varsin({var,_,V}, L) -> + case lists:member(V, L) of + true -> L; + false -> [V|L] + end; +varsin([H|T], L) -> + varsin(T, varsin(H, L)); +varsin(T, L) when tuple(T) -> + varsin(tuple_to_list(T), L); +varsin(_, L) -> + L. + +undefined() -> + '** UNDEFINED **'. |