aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test')
-rw-r--r--lib/common_test/src/ct_config.erl10
-rw-r--r--lib/common_test/src/ct_telnet.erl80
-rw-r--r--lib/common_test/src/ct_telnet_client.erl8
-rw-r--r--lib/common_test/src/ct_util.erl2
-rw-r--r--lib/common_test/src/unix_telnet.erl36
-rw-r--r--lib/common_test/test/Makefile1
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl70
-rw-r--r--lib/common_test/test/ct_telnet_SUITE.erl80
-rw-r--r--lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl184
-rw-r--r--lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_timetrap_SUITE.erl76
-rw-r--r--lib/common_test/test/telnet_server.erl228
11 files changed, 656 insertions, 119 deletions
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index c35cbd3c08..9bb9817001 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -599,7 +599,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) ->
encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
crypto:start(),
- {K1,K2,K3,IVec} = make_crypto_key(Key),
+ {Key,IVec} = make_crypto_key(Key),
case file:read_file(SrcFileName) of
{ok,Bin0} ->
Bin1 = term_to_binary({SrcFileName,Bin0}),
@@ -607,7 +607,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
0 -> Bin1;
N -> list_to_binary([Bin1,random_bytes(8-N)])
end,
- EncBin = crypto:des3_cbc_encrypt(K1, K2, K3, IVec, Bin2),
+ EncBin = crypto:block_encrypt(des3_cbc, Key, IVec, Bin2),
case file:write_file(EncryptFileName, EncBin) of
ok ->
io:format("~ts --(encrypt)--> ~ts~n",
@@ -638,10 +638,10 @@ decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) ->
decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
crypto:start(),
- {K1,K2,K3,IVec} = make_crypto_key(Key),
+ {Key,IVec} = make_crypto_key(Key),
case file:read_file(EncryptFileName) of
{ok,Bin} ->
- DecBin = crypto:des3_cbc_decrypt(K1, K2, K3, IVec, Bin),
+ DecBin = crypto:block_decrypt(des3_cbc, Key, IVec, Bin),
case catch binary_to_term(DecBin) of
{'EXIT',_} ->
{error,bad_file};
@@ -713,7 +713,7 @@ get_crypt_key_from_file() ->
make_crypto_key(String) ->
<<K1:8/binary,K2:8/binary>> = First = erlang:md5(String),
<<K3:8/binary,IVec:8/binary>> = erlang:md5([First|lists:reverse(String)]),
- {K1,K2,K3,IVec}.
+ {[K1,K2,K3],IVec}.
random_bytes(N) ->
{A,B,C} = now(),
diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl
index bd74991859..4755d939e0 100644
--- a/lib/common_test/src/ct_telnet.erl
+++ b/lib/common_test/src/ct_telnet.erl
@@ -29,9 +29,7 @@
%%% Command timeout = 10 sec (time to wait for a command to return)
%%% Max no of reconnection attempts = 3
%%% Reconnection interval = 5 sek (time to wait in between reconnection attempts)
-%%% Keep alive = true (will send NOP to the server every 10 sec if connection is idle)
-%%% Wait for linebreak = true (Will expect answer from server to end with linebreak when
-%%% using ct_telnet:expect)</pre>
+%%% Keep alive = true (will send NOP to the server every 10 sec if connection is idle)</pre>
%%% <p>These parameters can be altered by the user with the following
%%% configuration term:</p>
%%% <pre>
@@ -39,8 +37,7 @@
%%% {command_timeout,Millisec},
%%% {reconnection_attempts,N},
%%% {reconnection_interval,Millisec},
-%%% {keep_alive,Bool},
-%% {wait_for_linebreak, Bool}]}.</pre>
+%%% {keep_alive,Bool}]}.</pre>
%%% <p><code>Millisec = integer(), N = integer()</code></p>
%%% <p>Enter the <code>telnet_settings</code> term in a configuration
%%% file included in the test and ct_telnet will retrieve the information
@@ -312,7 +309,7 @@ expect(Connection,Patterns) ->
%%% Tag = term()
%%% Opts = [Opt]
%%% Opt = {timeout,Timeout} | repeat | {repeat,N} | sequence |
-%%% {halt,HaltPatterns} | ignore_prompt
+%%% {halt,HaltPatterns} | ignore_prompt | no_prompt_check
%%% Timeout = integer()
%%% N = integer()
%%% HaltPatterns = Patterns
@@ -339,14 +336,28 @@ expect(Connection,Patterns) ->
%%% will also include the matched <code>Tag</code>. Else, only
%%% <code>RxMatch</code> is returned.</p>
%%%
-%%% <p>The function will always return when a prompt is found, unless
-%%% the <code>ignore_prompt</code> options is used.</p>
-%%%
%%% <p>The <code>timeout</code> option indicates that the function
%%% shall return if the telnet client is idle (i.e. if no data is
%%% received) for more than <code>Timeout</code> milliseconds. Default
%%% timeout is 10 seconds.</p>
%%%
+%%% <p>The function will always return when a prompt is found, unless
+%%% any of the <code>ignore_prompt</code> or
+%%% <code>no_prompt_check</code> options are used, in which case it
+%%% will return when a match is found or after a timeout.</p>
+%%%
+%%% <p>If the <code>ignore_prompt</code> option is used,
+%%% <code>ct_telnet</code> will ignore any prompt found. This option
+%%% is useful if data sent by the server could include a pattern that
+%%% would match the prompt regexp (as returned by
+%%% <code>TargedMod:get_prompt_regexp/0</code>), but which should not
+%%% cause the function to return.</p>
+%%%
+%%% <p>If the <code>no_prompt_check</code> option is used,
+%%% <code>ct_telnet</code> will not search for a prompt at all. This
+%%% is useful if, for instance, the <code>Pattern</code> itself
+%%% matches the prompt.</p>
+%%%
%%% <p>The <code>repeat</code> option indicates that the pattern(s)
%%% shall be matched multiple times. If <code>N</code> is given, the
%%% pattern(s) will be matched <code>N</code> times, and the function
@@ -732,7 +743,7 @@ teln_get_all_data(Pid,Prx,Data,Acc,LastLine) ->
seq=false,
repeat=false,
found_prompt=false,
- wait_for_linebreak=true}).
+ prompt_check=true}).
%% @hidden
%% @doc Externally the silent_teln_expect function shall only be used
@@ -758,25 +769,27 @@ silent_teln_expect(Pid,Data,Pattern,Prx,Opts) ->
%% condition is fullfilled.
%% 3b) Repeat (sequence): 2) is repeated either N times or until a
%% halt condition is fullfilled.
-teln_expect(Pid,Data,Pattern0,Prx,Opts) ->
- HaltPatterns = case get_ignore_prompt(Opts) of
- true ->
- get_haltpatterns(Opts);
- false ->
- [prompt | get_haltpatterns(Opts)]
- end,
- WaitForLineBreak = get_line_break_opt(Opts),
+teln_expect(Pid,Data,Pattern0,Prx,Opts) ->
+ HaltPatterns =
+ case get_ignore_prompt(Opts) of
+ true ->
+ get_haltpatterns(Opts);
+ false ->
+ [prompt | get_haltpatterns(Opts)]
+ end,
+
+ PromptCheck = get_prompt_check(Opts),
Seq = get_seq(Opts),
Pattern = convert_pattern(Pattern0,Seq),
-
+
Timeout = get_timeout(Opts),
-
+
EO = #eo{teln_pid=Pid,
prx=Prx,
timeout=Timeout,
seq=Seq,
haltpatterns=HaltPatterns,
- wait_for_linebreak=WaitForLineBreak},
+ prompt_check=PromptCheck},
case get_repeat(Opts) of
false ->
@@ -817,11 +830,6 @@ get_timeout(Opts) ->
{value,{timeout,T}} -> T;
false -> ?DEFAULT_TIMEOUT
end.
-get_line_break_opt(Opts) ->
- case lists:keysearch(wait_for_linebreak,1,Opts) of
- {value,{wait_for_linebreak,false}} -> false;
- _ -> true
- end.
get_repeat(Opts) ->
case lists:keysearch(repeat,1,Opts) of
{value,{repeat,N}} when is_integer(N) ->
@@ -845,7 +853,9 @@ get_haltpatterns(Opts) ->
end.
get_ignore_prompt(Opts) ->
lists:member(ignore_prompt,Opts).
-
+get_prompt_check(Opts) ->
+ not lists:member(no_prompt_check,Opts).
+
%% Repeat either single or sequence. All match results are accumulated
%% and returned when a halt condition is fulllfilled.
repeat_expect(Rest,_Pattern,Acc,#eo{repeat=0}) ->
@@ -906,6 +916,9 @@ get_data1(Pid) ->
%% lines and each line is matched against each pattern.
%% one_expect: split data chunk at prompts
+one_expect(Data,Pattern,EO) when EO#eo.prompt_check==false ->
+% io:format("Raw Data ~p Pattern ~p EO ~p ",[Data,Pattern,EO]),
+ one_expect1(Data,Pattern,[],EO#eo{found_prompt=false});
one_expect(Data,Pattern,EO) ->
case match_prompt(Data,EO#eo.prx) of
{prompt,UptoPrompt,PromptType,Rest} ->
@@ -964,6 +977,8 @@ seq_expect(Data,[],Acc,_EO) ->
{match,lists:reverse(Acc),Data};
seq_expect([],Patterns,Acc,_EO) ->
{continue,Patterns,lists:reverse(Acc),[]};
+seq_expect(Data,Patterns,Acc,EO) when EO#eo.prompt_check==false ->
+ seq_expect1(Data,Patterns,Acc,[],EO#eo{found_prompt=false});
seq_expect(Data,Patterns,Acc,EO) ->
case match_prompt(Data,EO#eo.prx) of
{prompt,UptoPrompt,PromptType,Rest} ->
@@ -1018,9 +1033,8 @@ seq_expect1(Data,[],Acc,Rest,_EO) ->
%% Split prompt-chunk at lines
match_lines(Data,Patterns,EO) ->
FoundPrompt = EO#eo.found_prompt,
- NeedLineBreak = EO#eo.wait_for_linebreak,
case one_line(Data,[]) of
- {noline,Rest} when FoundPrompt=/=false, NeedLineBreak =:= true ->
+ {noline,Rest} when FoundPrompt=/=false ->
%% This is the line including the prompt
case match_line(Rest,Patterns,FoundPrompt,EO) of
nomatch ->
@@ -1028,14 +1042,14 @@ match_lines(Data,Patterns,EO) ->
{Tag,Match} ->
{Tag,Match,[]}
end;
- {noline,Rest} when NeedLineBreak =:= false ->
- case match_line(Rest,Patterns,FoundPrompt,EO) of
+ {noline,Rest} when EO#eo.prompt_check==false ->
+ case match_line(Rest,Patterns,false,EO) of
nomatch ->
- {nomatch,prompt};
+ {nomatch,Rest};
{Tag,Match} ->
{Tag,Match,[]}
end;
- {noline, Rest} ->
+ {noline,Rest} ->
{nomatch,Rest};
{Line,Rest} ->
case match_line(Line,Patterns,false,EO) of
diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl
index 7329498ed6..2cbcba9c77 100644
--- a/lib/common_test/src/ct_telnet_client.erl
+++ b/lib/common_test/src/ct_telnet_client.erl
@@ -143,7 +143,9 @@ loop(State, Sock, Acc) ->
State
end;
_ ->
- Pid ! {data,lists:reverse(lists:append(Acc))},
+ Data = lists:reverse(lists:append(Acc)),
+ dbg("get_data ~p\n",[Data]),
+ Pid ! {data,Data},
State
end,
loop(NewState, Sock, []);
@@ -161,7 +163,9 @@ loop(State, Sock, Acc) ->
NewAcc =
case erlang:is_process_alive(Pid) of
true ->
- Pid ! {data,lists:reverse(lists:append(Acc))},
+ Data = lists:reverse(lists:append(Acc)),
+ dbg("get_data_delayed ~p\n",[Data]),
+ Pid ! {data,Data},
[];
false ->
Acc
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index 6a8b37bf3b..b77845eb5b 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -421,6 +421,8 @@ loop(Mode,TestData,StartDir) ->
"Reason: ~p\n\n",
[Pid,A,CB,Reason]),
catch CB:close(Pid),
+ %% in case CB:close failed to do this:
+ unregister_connection(Pid),
loop(Mode,TestData,StartDir);
_ ->
%% Let process crash in case of error, this shouldn't happen!
diff --git a/lib/common_test/src/unix_telnet.erl b/lib/common_test/src/unix_telnet.erl
index 71df2ab44e..88199b07d0 100644
--- a/lib/common_test/src/unix_telnet.erl
+++ b/lib/common_test/src/unix_telnet.erl
@@ -94,16 +94,11 @@ connect(Ip,Port,Timeout,KeepAlive,Extra) ->
{Username,Password} ->
connect1(Ip,Port,Timeout,KeepAlive,Username,Password);
Name ->
- case not_require_user_and_pass(Name) of
- true ->
- connect_without_username_and_pass(Ip,Port,Timeout,KeepAlive);
- _ ->
- case get_username_and_password(Name) of
- {ok,{Username,Password}} ->
- connect1(Ip,Port,Timeout,KeepAlive,Username,Password);
- Error ->
- Error
- end
+ case get_username_and_password(Name) of
+ {ok,{Username,Password}} ->
+ connect1(Ip,Port,Timeout,KeepAlive,Username,Password);
+ Error ->
+ Error
end
end.
@@ -149,27 +144,6 @@ connect1(Ip,Port,Timeout,KeepAlive,Username,Password) ->
end_log(),
Result.
-connect_without_username_and_pass(Ip,Port,Timeout,KeepAlive) ->
- start_log("unix_telnet:connect"),
- Result =
- case ct_telnet_client:open(Ip,Port,Timeout,KeepAlive) of
- {ok,Pid} ->
- {ok, Pid};
- Error ->
- cont_log("Could not open telnet connection\n~p\n",[Error]),
- Error
- end,
- end_log(),
- Result.
-
-not_require_user_and_pass(Name) ->
- case ct:get_config({Name, not_require_user_and_pass}) of
- undefined ->
- false;
- _ ->
- true
- end.
-
get_username_and_password(Name) ->
case ct:get_config({Name,username}) of
undefined ->
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index 94569fa87f..31ab28c41d 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -28,6 +28,7 @@ MODULES= \
ct_test_support \
ct_test_support_eh \
ct_userconfig_callback \
+ telnet_server \
ct_smoke_test_SUITE \
ct_priv_dir_SUITE \
ct_event_handler_SUITE \
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
index 54526e8e83..0535eb924b 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
@@ -1,7 +1,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012. All Rights Reserved.
+%% Copyright Ericsson AB 2013. 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
@@ -1035,10 +1035,12 @@ make_dsa_files(Config, Type) ->
file:write_file(DSAPrivateFile, PemBin),
ok.
+
%%--------------------------------------------------------------------
-%% Creates a dsa key (OBS: for testing only)
+%% @doc Creates a dsa key (OBS: for testing only)
%% the sizes are in bytes
-%% gen_dsa(::integer()) -> {::atom(), ::binary(), ::opaque()}
+%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()}
+%% @end
%%--------------------------------------------------------------------
gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) ->
Key = gen_dsa2(LSize, NSize),
@@ -1048,7 +1050,6 @@ encode_key(Key = #'DSAPrivateKey'{}) ->
Der = public_key:der_encode('DSAPrivateKey', Key),
{'DSAPrivateKey', Der, not_encrypted}.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% DSA key generation (OBS: for testing only)
%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm
@@ -1058,67 +1059,70 @@ gen_dsa2(LSize, NSize) ->
Q = prime(NSize), %% Choose N-bit prime Q
X0 = prime(LSize),
P0 = prime((LSize div 2) +1),
-
+
%% Choose L-bit prime modulus P such that p-1 is a multiple of q.
case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of
- error ->
+ error ->
gen_dsa2(LSize, NSize);
- P ->
- G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q.
+ P ->
+ G = crypto:mod_pow(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q.
%% such that This may be done by setting g = h^(p-1)/q mod p, commonly h=2 is used.
-
+
X = prime(20), %% Choose x by some random method, where 0 < x < q.
- Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p.
-
- #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X}
+ Y = crypto:mod_pow(G, X, P), %% Calculate y = g^x mod p.
+
+ #'DSAPrivateKey'{version=0, p = P, q = Q,
+ g = crypto:bytes_to_integer(G), y = crypto:bytes_to_integer(Y), x = X}
end.
-
+
%% See fips_186-3.pdf
dsa_search(T, P0, Q, Iter) when Iter > 0 ->
P = 2*T*Q*P0 + 1,
- case is_prime(crypto:mpint(P), 50) of
+ case is_prime(P, 50) of
true -> P;
false -> dsa_search(T+1, P0, Q, Iter-1)
end;
-dsa_search(_,_,_,_) ->
+dsa_search(_,_,_,_) ->
error.
%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
prime(ByteSize) ->
Rand = odd_rand(ByteSize),
- crypto:erlint(prime_odd(Rand, 0)).
+ prime_odd(Rand, 0).
prime_odd(Rand, N) ->
case is_prime(Rand, 50) of
- true ->
+ true ->
Rand;
- false ->
- NotPrime = crypto:erlint(Rand),
- prime_odd(crypto:mpint(NotPrime+2), N+1)
+ false ->
+ prime_odd(Rand+2, N+1)
end.
%% see http://en.wikipedia.org/wiki/Fermat_primality_test
is_prime(_, 0) -> true;
-is_prime(Candidate, Test) ->
- CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate),
- case crypto:mod_exp(CoPrime, Candidate, Candidate) of
- CoPrime -> is_prime(Candidate, Test-1);
- _ -> false
- end.
+is_prime(Candidate, Test) ->
+ CoPrime = odd_rand(10000, Candidate),
+ Result = crypto:mod_pow(CoPrime, Candidate, Candidate) ,
+ is_prime(CoPrime, crypto:bytes_to_integer(Result), Candidate, Test).
+
+is_prime(CoPrime, CoPrime, Candidate, Test) ->
+ is_prime(Candidate, Test-1);
+is_prime(_,_,_,_) ->
+ false.
odd_rand(Size) ->
Min = 1 bsl (Size*8-1),
Max = (1 bsl (Size*8))-1,
- odd_rand(crypto:mpint(Min), crypto:mpint(Max)).
+ odd_rand(Min, Max).
odd_rand(Min,Max) ->
- Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max),
- BitSkip = (Sz+4)*8-1,
- case Rand of
- Odd = <<_:BitSkip, 1:1>> -> Odd;
- Even = <<_:BitSkip, 0:1>> ->
- crypto:mpint(crypto:erlint(Even)+1)
+ Rand = crypto:rand_uniform(Min,Max),
+ case Rand rem 2 of
+ 0 ->
+ Rand + 1;
+ _ ->
+ Rand
end.
copyfile(SrcDir, DstDir, Fn) ->
diff --git a/lib/common_test/test/ct_telnet_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE.erl
index b4f24baa0c..e2ee207754 100644
--- a/lib/common_test/test/ct_telnet_SUITE.erl
+++ b/lib/common_test/test/ct_telnet_SUITE.erl
@@ -33,6 +33,10 @@
-define(eh, ct_test_support_eh).
+-define(erl_telnet_server_port,1234).
+-define(erl_telnet_server_user,"telnuser").
+-define(erl_telnet_server_pwd,"telnpwd").
+
%%--------------------------------------------------------------------
%% TEST SERVER CALLBACK FUNCTIONS
%%--------------------------------------------------------------------
@@ -48,17 +52,28 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
ct_test_support:end_per_suite(Config).
+init_per_testcase(TestCase, Config) when TestCase=/=unix_telnet->
+ TS = telnet_server:start([{port,?erl_telnet_server_port},
+ {users,[{?erl_telnet_server_user,
+ ?erl_telnet_server_pwd}]}]),
+ ct_test_support:init_per_testcase(TestCase, [{telnet_server,TS}|Config]);
init_per_testcase(TestCase, Config) ->
ct_test_support:init_per_testcase(TestCase, Config).
end_per_testcase(TestCase, Config) ->
+ case ?config(telnet_server,Config) of
+ undefined -> ok;
+ TS -> telnet_server:stop(TS)
+ end,
ct_test_support:end_per_testcase(TestCase, Config).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[
- default
+ unix_telnet,
+ own_server,
+ timetrap
].
%%--------------------------------------------------------------------
@@ -67,19 +82,33 @@ all() ->
%%%-----------------------------------------------------------------
%%%
-default(Config) when is_list(Config) ->
- DataDir = ?config(data_dir, Config),
- Suite = filename:join(DataDir, "ct_telnet_basic_SUITE"),
- Cfg = {unix, ct:get_config(unix)},
- ok = file:write_file(filename:join(DataDir, "telnet.cfg"), io_lib:write(Cfg) ++ "."),
- CfgFile = filename:join(DataDir, "telnet.cfg"),
- {Opts,ERPid} = setup([{suite,Suite},{label,default}, {config, CfgFile}], Config),
- ok = execute(default, Opts, ERPid, Config).
+unix_telnet(Config) when is_list(Config) ->
+ all_tests_in_suite(unix_telnet,"ct_telnet_basic_SUITE","telnet.cfg",Config).
+
+own_server(Config) ->
+ all_tests_in_suite(own_server,"ct_telnet_own_server_SUITE",
+ "telnet2.cfg",Config).
+
+timetrap(Config) ->
+ all_tests_in_suite(timetrap,"ct_telnet_timetrap_SUITE",
+ "telnet3.cfg",Config).
%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
%%%-----------------------------------------------------------------
+all_tests_in_suite(TestCase, SuiteName, CfgFileName, Config) ->
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, SuiteName),
+ CfgFile = filename:join(DataDir, CfgFileName),
+ Cfg = telnet_config(TestCase),
+ ok = file:write_file(CfgFile, io_lib:write(Cfg) ++ "."),
+ {Opts,ERPid} = setup([{suite,Suite},
+ {label,TestCase},
+ {config,CfgFile}],
+ Config),
+ ok = execute(TestCase, Opts, ERPid, Config).
+
setup(Test, Config) ->
Opts0 = ct_test_support:get_opts(Config),
Level = ?config(trace_level, Config),
@@ -103,19 +132,40 @@ execute(Name, Opts, ERPid, Config) ->
reformat(Events, EH) ->
ct_test_support:reformat(Events, EH).
+
+telnet_config(unix_telnet) ->
+ {unix, ct:get_config(unix)};
+telnet_config(_) ->
+ {unix,[{telnet,"localhost"},
+ {port, ?erl_telnet_server_port},
+ {username,?erl_telnet_server_user},
+ {password,?erl_telnet_server_pwd},
+ {keep_alive,true}]}.
+
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
-events_to_check(default,Config) ->
+events_to_check(unix_telnet,Config) ->
+ all_cases(ct_telnet_basic_SUITE,Config);
+events_to_check(own_server,Config) ->
+ all_cases(ct_telnet_own_server_SUITE,Config);
+events_to_check(timetrap,_Config) ->
+ [{?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,tc_done,{ct_telnet_timetrap_SUITE,expect_timetrap,
+ {failed,{timetrap_timeout,7000}}}},
+ {?eh,tc_done,{ct_telnet_timetrap_SUITE,expect_success,ok}},
+ {?eh,stop_logging,[]}].
+
+all_cases(Suite,Config) ->
{module,_} = code:load_abs(filename:join(?config(data_dir,Config),
- ct_telnet_basic_SUITE)),
- TCs = ct_telnet_basic_SUITE:all(),
- code:purge(ct_telnet_basic_SUITE),
- code:delete(ct_telnet_basic_SUITE),
+ Suite)),
+ TCs = Suite:all(),
+ code:purge(Suite),
+ code:delete(Suite),
OneTest =
[{?eh,start_logging,{'DEF','RUNDIR'}}] ++
- [{?eh,tc_done,{ct_telnet_basic_SUITE,TC,ok}} || TC <- TCs] ++
+ [{?eh,tc_done,{Suite,TC,ok}} || TC <- TCs] ++
[{?eh,stop_logging,[]}],
%% 2 tests (ct:run_test + script_start) is default
diff --git a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
new file mode 100644
index 0000000000..3f7c0d68bf
--- /dev/null
+++ b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
@@ -0,0 +1,184 @@
+-module(ct_telnet_own_server_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+
+suite() -> [{require,erl_telnet_server,{unix,[telnet]}}].
+
+all() ->
+ [expect,
+ expect_repeat,
+ expect_sequence,
+ expect_error_prompt,
+ expect_error_timeout,
+ no_prompt_check,
+ no_prompt_check_repeat,
+ no_prompt_check_sequence,
+ no_prompt_check_timeout,
+ ignore_prompt,
+ ignore_prompt_repeat,
+ ignore_prompt_sequence,
+ ignore_prompt_timeout].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+%% Simple expect
+expect(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo ayt"),
+ {ok,["ayt"]} = ct_telnet:expect(Handle, ["ayt"]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% Expect with repeat option
+expect_repeat(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo_ml xy xy"),
+ {ok,[["xy"],["xy"]],done} = ct_telnet:expect(Handle, ["xy"],[{repeat,2}]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% Expect with sequence option
+expect_sequence(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo_ml ab cd ef"),
+ {ok,[["ab"],["cd"],["ef"]]} = ct_telnet:expect(Handle,
+ [["ab"],["cd"],["ef"]],
+ [sequence]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% Check that expect returns when a prompt is found, even if pattern
+%% is not matched.
+expect_error_prompt(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo xxx> yyy"),
+ {error,{prompt,"> "}} = ct_telnet:expect(Handle, ["yyy"]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% Check that expect returns after idle timeout, and even if the
+%% expected pattern is received - as long as not newline or prompt is
+%% received it will not match.
+expect_error_timeout(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo_no_prompt xxx"),
+ {error,timeout} = ct_telnet:expect(Handle, ["xxx"], [{timeout,1000}]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% expect with ignore_prompt option should not return even if a prompt
+%% is found. The pattern after the prompt (here "> ") can be matched.
+ignore_prompt(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo xxx> yyy"),
+ {ok,["yyy"]} = ct_telnet:expect(Handle, ["yyy"], [ignore_prompt]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% expect with ignore_prompt and repeat options.
+ignore_prompt_repeat(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo_ml yyy> yyy>"),
+ {ok,[["yyy"],["yyy"]],done} = ct_telnet:expect(Handle, ["yyy"],
+ [{repeat,2},
+ ignore_prompt]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% expect with ignore_prompt and sequence options.
+ignore_prompt_sequence(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo_ml xxx> yyy> zzz> "),
+ {ok,[["xxx"],["yyy"],["zzz"]]} = ct_telnet:expect(Handle,
+ [["xxx"],["yyy"],["zzz"]],
+ [sequence,
+ ignore_prompt]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% Check that expect returns after idle timeout when ignore_prompt
+%% option is used.
+%% As for expect without the ignore_prompt option, it a newline or a
+%% prompt is required in order for the pattern to match.
+ignore_prompt_timeout(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo xxx"),
+ {error,timeout} = ct_telnet:expect(Handle, ["yyy"], [ignore_prompt,
+ {timeout,1000}]),
+ ok = ct_telnet:send(Handle, "echo xxx"), % sends prompt and newline
+ {ok,["xxx"]} = ct_telnet:expect(Handle, ["xxx"], [ignore_prompt,
+ {timeout,1000}]),
+ ok = ct_telnet:send(Handle, "echo_no_prompt xxx\n"), % no prompt, but newline
+ {ok,["xxx"]} = ct_telnet:expect(Handle, ["xxx"], [ignore_prompt,
+ {timeout,1000}]),
+ ok = ct_telnet:send(Handle, "echo_no_prompt xxx"), % no prompt, no newline
+ {error,timeout} = ct_telnet:expect(Handle, ["xxx"], [ignore_prompt,
+ {timeout,1000}]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% no_prompt_check option shall match pattern both when prompt is sent
+%% and when it is not.
+no_prompt_check(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo xxx"),
+ {ok,["xxx"]} = ct_telnet:expect(Handle, ["xxx"], [no_prompt_check]),
+ ok = ct_telnet:send(Handle, "echo_no_prompt yyy"),
+ {ok,["yyy"]} = ct_telnet:expect(Handle, ["yyy"], [no_prompt_check]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% no_prompt_check and repeat options
+no_prompt_check_repeat(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo_ml xxx xxx"),
+ {ok,[["xxx"],["xxx"]],done} = ct_telnet:expect(Handle,["xxx"],
+ [{repeat,2},
+ no_prompt_check]),
+ ok = ct_telnet:send(Handle, "echo_ml_no_prompt yyy yyy"),
+ {ok,[["yyy"],["yyy"]],done} = ct_telnet:expect(Handle,["yyy"],
+ [{repeat,2},
+ no_prompt_check]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% no_prompt_check and sequence options
+no_prompt_check_sequence(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo_ml_no_prompt ab cd ef"),
+ {ok,[["ab"],["cd"],["ef"]]} = ct_telnet:expect(Handle,
+ [["ab"],["cd"],["ef"]],
+ [sequence,
+ no_prompt_check]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% Check that expect returns after idle timeout when no_prompt_check
+%% option is used.
+no_prompt_check_timeout(_) ->
+ {ok, Handle} = ct_telnet:open(erl_telnet_server),
+ ok = ct_telnet:send(Handle, "echo xxx"),
+ {error,timeout} = ct_telnet:expect(Handle, ["yyy"], [no_prompt_check,
+ {timeout,1000}]),
+ ok = ct_telnet:close(Handle),
+ ok.
diff --git a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_timetrap_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_timetrap_SUITE.erl
new file mode 100644
index 0000000000..f274fb9112
--- /dev/null
+++ b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_timetrap_SUITE.erl
@@ -0,0 +1,76 @@
+-module(ct_telnet_timetrap_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+-define(name,erl_telnet_server).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+suite() -> [{require,?name,{unix,[telnet]}},
+ {timetrap,{seconds,7}}].
+
+all() ->
+ [expect_timetrap,
+ expect_success].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(_,Config) ->
+ ct:log("init_per_testcase: opening telnet connection...",[]),
+ {ok,_} = ct_telnet:open(?name),
+ ct:log("...done",[]),
+ Config.
+
+end_per_testcase(_,_Config) ->
+ ct:log("end_per_testcase: closing telnet connection...",[]),
+ _ = ct_telnet:close(?name),
+ ct:log("...done",[]),
+ ok.
+
+
+%% OTP-10648
+%% This test case should fail with timetrap timeout.
+%%
+%% The long timetrap timeout and timeout option in the expect call
+%% also causes the telnet client to hang so long that the attempt at
+%% closing it (in end_per_testcase) will time out (close timeout is 5
+%% sec) without a timetrap timeout occuring in end_per_testcase.
+%%
+%% The point is to see that the connection is thoroughly removed and
+%% unregistered anyway so that the next test case can successfully
+%% open a connection with the same name.
+%%
+%% Note!!! that if end_per_testcase reaches a timetrap timeout before
+%% the connection is closed, then the connection will survive until
+%% the hanging expect times out, after which it will be closed in a
+%% seemingly normal way due to the close message which was sent by the
+%% close attempt. This could happen any time during the subsequent
+%% test cases and cause confusion... There is however not much to do
+%% about this, except writing test cases with timetrap timeout longer
+%% than the close timeout (as this test case does)
+expect_timetrap(_) ->
+ {error,timeout} = ct_telnet:expect(?name, ["ayt"], [{timeout,20000}]),
+ ok.
+
+%% This should succeed
+expect_success(_) ->
+ ok = ct_telnet:send(?name, "echo ayt"),
+ {ok,["ayt"]} = ct_telnet:expect(?name, ["ayt"]),
+ ok.
diff --git a/lib/common_test/test/telnet_server.erl b/lib/common_test/test/telnet_server.erl
new file mode 100644
index 0000000000..31884aa182
--- /dev/null
+++ b/lib/common_test/test/telnet_server.erl
@@ -0,0 +1,228 @@
+-module(telnet_server).
+-compile(export_all).
+
+%% telnet control characters
+-define(SE, 240).
+-define(NOP, 241).
+-define(DM, 242).
+-define(BRK, 243).
+-define(IP, 244).
+-define(AO, 245).
+-define(AYT, 246).
+-define(EC, 247).
+-define(EL, 248).
+-define(GA, 249).
+-define(SB, 250).
+-define(WILL, 251).
+-define(WONT, 252).
+-define(DO, 253).
+-define(DONT, 254).
+-define(IAC, 255).
+
+%% telnet options
+-define(BINARY, 0).
+-define(ECHO, 1).
+-define(SUPPRESS_GO_AHEAD, 3).
+-define(TERMINAL_TYPE, 24).
+
+-record(state,
+ {listen,
+ client,
+ users,
+ authorized=false,
+ suppress_go_ahead=false,
+ buffer=[]}).
+
+-type options() :: [{port,pos_integer()} | {users,users()}].
+-type users() :: [{user(),password()}].
+-type user() :: string().
+-type password() :: string().
+
+-spec start(Opts::options()) -> Pid::pid().
+start(Opts) when is_list(Opts) ->
+ spawn_link(fun() -> init(Opts) end).
+
+-spec stop(Pid::pid()) -> ok.
+stop(Pid) ->
+ Ref = erlang:monitor(process,Pid),
+ Pid ! stop,
+ receive {'DOWN',Ref,_,_,_} -> ok end.
+
+init(Opts) ->
+ Port = proplists:get_value(port,Opts),
+ Users = proplists:get_value(users,Opts,[]),
+ {ok, LSock} = gen_tcp:listen(Port, [list, {packet, 0},
+ {active, true}]),
+ State = #state{listen=LSock,users=Users},
+ accept(State),
+ ok = gen_tcp:close(LSock).
+
+accept(#state{listen=LSock}=State) ->
+ Server = self(),
+ Acceptor = spawn_link(fun() -> do_accept(LSock,Server) end),
+ receive
+ {Acceptor,Sock} when is_port(Sock) ->
+ case init_client(State#state{client=Sock}) of
+ stopped ->
+ io:format("telnet_server stopped\n"),
+ ok;
+ R ->
+ io:format("connection to client closed with reason ~p~n",[R]),
+ accept(State)
+ end;
+ {Acceptor,closed} ->
+ io:format("listen socket closed unexpectedly, "
+ "terminating telnet_server\n"),
+ ok;
+ stop ->
+ io:format("telnet_server stopped\n"),
+ ok
+ end.
+
+do_accept(LSock,Server) ->
+ case gen_tcp:accept(LSock) of
+ {ok, Sock} ->
+ ok = gen_tcp:controlling_process(Sock,Server),
+ Server ! {self(),Sock};
+ {error,closed} ->
+ %% This will happen when stop/1 is called, since listen
+ %% socket is closed. Then the server is probably already
+ %% dead by now, but to be 100% sure not to hang, we send a
+ %% message to say what happened.
+ Server ! {self(),closed}
+ end.
+
+init_client(#state{client=Sock}=State) ->
+ dbg("Server sending: ~p~n",["login: "]),
+ R = case gen_tcp:send(Sock,"login: ") of
+ ok ->
+ loop(State);
+ Error ->
+ Error
+ end,
+ _ = gen_tcp:close(Sock),
+ R.
+
+loop(State) ->
+ receive
+ {tcp,_,Data} ->
+ try handle_data(Data,State) of
+ {ok,State1} ->
+ loop(State1)
+ catch
+ throw:Error ->
+ Error
+ end;
+ {tcp_closed, _} ->
+ closed;
+ {tcp_error,_,Error} ->
+ {error,tcp,Error};
+ stop ->
+ stopped
+ end.
+
+handle_data([?IAC|Cmd],State) ->
+ dbg("Server got cmd: ~p~n",[Cmd]),
+ handle_cmd(Cmd,State);
+handle_data(Data,State) ->
+ dbg("Server got data: ~p~n",[Data]),
+ case get_line(Data,[]) of
+ {Line,Rest} ->
+ WholeLine = lists:flatten(lists:reverse(State#state.buffer,Line)),
+ {ok,State1} = do_handle_data(WholeLine,State),
+ case Rest of
+ [] -> {ok,State1};
+ _ -> handle_data(Rest,State1)
+ end;
+ false ->
+ {ok,State#state{buffer=[Data|State#state.buffer]}}
+ end.
+
+%% Add function clause below to handle new telnet commands (sent with
+%% ?IAC from client - this is not possible to do from ct_telnet API,
+%% but ct_telnet sends DONT SUPPRESS_GO_AHEAD)
+handle_cmd([?DO,?SUPPRESS_GO_AHEAD|T],State) ->
+ send([?IAC,?WILL,?SUPPRESS_GO_AHEAD],State),
+ handle_cmd(T,State#state{suppress_go_ahead=true});
+handle_cmd([?DONT,?SUPPRESS_GO_AHEAD|T],State) ->
+ send([?IAC,?WONT,?SUPPRESS_GO_AHEAD],State),
+ handle_cmd(T,State#state{suppress_go_ahead=false});
+handle_cmd([?IAC|T],State) ->
+ %% Multiple commands in one packet
+ handle_cmd(T,State);
+handle_cmd([_H|T],State) ->
+ %% Not responding to this command
+ handle_cmd(T,State);
+handle_cmd([],State) ->
+ {ok,State}.
+
+%% Add function clause below to handle new text command (text entered
+%% from the telnet prompt)
+do_handle_data(Data,#state{authorized=false}=State) ->
+ check_user(Data,State);
+do_handle_data(Data,#state{authorized={user,_}}=State) ->
+ check_pwd(Data,State);
+do_handle_data("echo "++ Data,State) ->
+ send(Data++"\r\n> ",State),
+ {ok,State};
+do_handle_data("echo_no_prompt "++ Data,State) ->
+ send(Data,State),
+ {ok,State};
+do_handle_data("echo_ml "++ Data,State) ->
+ Lines = string:tokens(Data," "),
+ ReturnData = string:join(Lines,"\n"),
+ send(ReturnData++"\r\n> ",State),
+ {ok,State};
+do_handle_data("echo_ml_no_prompt "++ Data,State) ->
+ Lines = string:tokens(Data," "),
+ ReturnData = string:join(Lines,"\n"),
+ send(ReturnData,State),
+ {ok,State};
+do_handle_data([],State) ->
+ send("> ",State),
+ {ok,State};
+do_handle_data(_Data,State) ->
+ send("error: unknown command\r\n> ",State),
+ {ok,State}.
+
+check_user(User,State) ->
+ case lists:keyfind(User,1,State#state.users) of
+ {User,Pwd} ->
+ dbg("user ok\n"),
+ send("Password: ",State),
+ {ok,State#state{authorized={user,Pwd}}};
+ false ->
+ throw({error,unknown_user})
+ end.
+
+check_pwd(Pwd,#state{authorized={user,Pwd}}=State) ->
+ dbg("password ok\n"),
+ send("Welcome to the ultimate telnet server!\r\n> ",State),
+ {ok,State#state{authorized=true}};
+check_pwd(_,_State) ->
+ throw({error,authentication}).
+
+send(Data,State) ->
+ dbg("Server sending: ~p~n",[Data]),
+ case gen_tcp:send(State#state.client,Data) of
+ ok ->
+ ok;
+ {error,Error} ->
+ throw({error,send,Error})
+ end.
+
+get_line([$\r,$\n|Rest],Acc) ->
+ {lists:reverse(Acc),Rest};
+get_line([$\r,0|Rest],Acc) ->
+ {lists:reverse(Acc),Rest};
+get_line([$\n|Rest],Acc) ->
+ {lists:reverse(Acc),Rest};
+get_line([H|T],Acc) ->
+ get_line(T,[H|Acc]);
+get_line([],_) ->
+ false.
+
+dbg(_F) ->
+ io:format(_F).
+dbg(_F,_A) ->
+ io:format(_F,_A).