From 12db9a2c150edd41bafaf58b119ee88fe75b8814 Mon Sep 17 00:00:00 2001
From: Peti Gomori
Date: Wed, 13 Jun 2012 15:53:23 +0200
Subject: Fix Table Viewer crash after a 'Found' -> 'Not found' search sequence
Start position was lost after a 'Found' -> 'Not found' search sequence
leading an undefined position in the next search
---
lib/observer/src/observer_tv_table.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'lib')
diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl
index 3930f9ee26..d339a853cb 100644
--- a/lib/observer/src/observer_tv_table.erl
+++ b/lib/observer/src/observer_tv_table.erl
@@ -321,7 +321,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdS
wxStatusBar:setStatusText(SB, "Not found"),
Pid ! {mark_search_hit, Find#find.start},
wxListCtrl:refreshItem(Grid, Find#find.start),
- {noreply, State#state{search=Search#search{find=#find{found=false}}}};
+ {noreply, State#state{search=Search#search{find=Find#find{found=false}}}};
Row ->
wxListCtrl:ensureVisible(Grid, Row),
wxListCtrl:refreshItem(Grid, Row),
--
cgit v1.2.3
From f530bdb22a3a59ef97b35d7be11f9c06b0c34356 Mon Sep 17 00:00:00 2001
From: Peti Gomori
Date: Wed, 13 Jun 2012 18:35:02 +0200
Subject: Escape control characters in Table Viewer
Similar behaviour to old tv.
Objects in tables supposed to be printed in a single line
and it looks ugly when a [...,10,...] integer list
creates a new-line.
---
lib/observer/src/observer_tv_table.erl | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
(limited to 'lib')
diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl
index d339a853cb..97d18ec03c 100644
--- a/lib/observer/src/observer_tv_table.erl
+++ b/lib/observer/src/observer_tv_table.erl
@@ -762,7 +762,7 @@ format_tuple(_Tuple, 1, 0) ->
format_list([]) -> "[]";
format_list(List) ->
case printable_list(List) of
- true -> io_lib:format("\"~ts\"", [List]);
+ true -> io_lib:format("\"~ts\"", [map_printable_list(List)]);
false -> [$[ | make_list(List)]
end.
@@ -771,6 +771,24 @@ make_list([Last]) ->
make_list([Head|Tail]) ->
[format(Head), $,|make_list(Tail)].
+map_printable_list([$\n|Cs]) ->
+ [$\\, $n|map_printable_list(Cs)];
+map_printable_list([$\r|Cs]) ->
+ [$\\, $r|map_printable_list(Cs)];
+map_printable_list([$\t|Cs]) ->
+ [$\\, $t|map_printable_list(Cs)];
+map_printable_list([$\v|Cs]) ->
+ [$\\, $v|map_printable_list(Cs)];
+map_printable_list([$\b|Cs]) ->
+ [$\\, $b|map_printable_list(Cs)];
+map_printable_list([$\f|Cs]) ->
+ [$\\, $f|map_printable_list(Cs)];
+map_printable_list([$\e|Cs]) ->
+ [$\\, $e|map_printable_list(Cs)];
+map_printable_list([]) -> [];
+map_printable_list([C|Cs]) ->
+ [C|map_printable_list(Cs)].
+
%% printable_list([Char]) -> bool()
%% Return true if CharList is a list of printable characters, else
%% false.
--
cgit v1.2.3
From 5d299de9e24c79c3029e7d0d0d75bf6ebb774665 Mon Sep 17 00:00:00 2001
From: Peti Gomori
Date: Thu, 14 Jun 2012 16:47:03 +0200
Subject: Fix Table Viewer search crash on new|changed|deleted rows
---
lib/observer/src/observer_tv_table.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'lib')
diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl
index 97d18ec03c..786d1a5576 100644
--- a/lib/observer/src/observer_tv_table.erl
+++ b/lib/observer/src/observer_tv_table.erl
@@ -613,7 +613,7 @@ search([Str, Row, Dir0, CaseSens],
search(Row, Dir, Re, Table) ->
Res = try lists:nth(Row+1, Table) of
- Term ->
+ [Term|_] ->
Str = format(Term),
re:run(Str, Re)
catch _:_ -> no_more
--
cgit v1.2.3
From 8490cb6328da880119dbc1eb92d3b439a2c5f34a Mon Sep 17 00:00:00 2001
From: Bartlomiej Puzon
Date: Mon, 18 Jun 2012 15:58:08 -0700
Subject: Add tests showing that trying to delete non-existing object may
corrupt the table index
In case of bag tables, trying to delete a non-existing object leads to
the index becoming corrupt. This happens if the non-existing object we
try to delete happens to share its key and index field value with a single
existing object in the table.
Result: The index entry corresponding to the existing object is
removed.
---
lib/mnesia/test/mnesia_dirty_access_test.erl | 3 +++
lib/mnesia/test/mnesia_trans_access_test.erl | 4 ++++
2 files changed, 7 insertions(+)
(limited to 'lib')
diff --git a/lib/mnesia/test/mnesia_dirty_access_test.erl b/lib/mnesia/test/mnesia_dirty_access_test.erl
index abbdab48c0..a57adefbac 100644
--- a/lib/mnesia/test/mnesia_dirty_access_test.erl
+++ b/lib/mnesia/test/mnesia_dirty_access_test.erl
@@ -527,6 +527,9 @@ dirty_index_update_bag(Config, Storage) ->
?match(ok, mnesia:dirty_write(Rec1)),
?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ ?match(ok, mnesia:dirty_delete_object(Rec5)),
+ ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)),
+
?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec2) end)),
R1 = mnesia:dirty_index_read(Tab, 2, ValPos),
?match([Rec1, Rec2], lists:sort(R1)),
diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl
index c040d0ca3f..73c3fe0362 100644
--- a/lib/mnesia/test/mnesia_trans_access_test.erl
+++ b/lib/mnesia/test/mnesia_trans_access_test.erl
@@ -896,6 +896,10 @@ index_update_bag(Config)when is_list(Config) ->
?match({atomic, [Rec1]},
mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)),
+ ?match({atomic, [Rec1]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+
?match({atomic, ok},
mnesia:transaction(fun() -> mnesia:write(Rec2) end)),
{atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
--
cgit v1.2.3
From 0fcdb6fdd38291072dd130cf4362ea5e8f3d1717 Mon Sep 17 00:00:00 2001
From: Bartlomiej Puzon
Date: Mon, 18 Jun 2012 16:16:00 -0700
Subject: Prevent index from being corrupted if a nonexistent item is deleted
We have to ensure that we actually delete the last object with a
given (key, index) pair before removing the index.
---
lib/mnesia/src/mnesia_index.erl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
(limited to 'lib')
diff --git a/lib/mnesia/src/mnesia_index.erl b/lib/mnesia/src/mnesia_index.erl
index 61210d7e55..37989a1958 100644
--- a/lib/mnesia/src/mnesia_index.erl
+++ b/lib/mnesia/src/mnesia_index.erl
@@ -120,9 +120,9 @@ del_object_bag(Tab, Key, Obj, Pos, Ixt, undefined) ->
IxKey = element(Pos, Obj),
Old = [X || X <- mnesia_lib:db_get(Tab, Key), element(Pos, X) =:= IxKey],
del_object_bag(Tab, Key, Obj, Pos, Ixt, Old);
-%% If Tab type is bag we need remove index identifier if Tab
-%% contains less than 2 elements.
-del_object_bag(_Tab, Key, Obj, Pos, Ixt, Old) when length(Old) < 2 ->
+%% If Tab type is bag we need remove index identifier if the object being
+%% deleted was the last one
+del_object_bag(_Tab, Key, Obj, Pos, Ixt, Old) when Old =:= [Obj] ->
del_ixes(Ixt, [Obj], Pos, Key);
del_object_bag(_Tab, _Key, _Obj, _Pos, _Ixt, _Old) -> ok.
--
cgit v1.2.3
From a33b8c3f1ff1ddcc9dca9b1da955a9cda5136636 Mon Sep 17 00:00:00 2001
From: Kenji Rikitake
Date: Mon, 25 Jun 2012 21:06:23 +0900
Subject: Fix lib/src/test/ssh_basic_SUITE.erl to fix IPv6 option typos
Fixed incorrect option "ipv6_disable" to "ipv6_disabled"
as documented in the ssh manual.
---
lib/ssh/test/ssh_basic_SUITE.erl | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
(limited to 'lib')
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index c5019425cd..2ceaa9daa5 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -194,10 +194,10 @@ misc_ssh_options(Config) when is_list(Config) ->
SystemDir = filename:join(?config(priv_dir, Config), system),
UserDir = ?config(priv_dir, Config),
- CMiscOpt0 = [{connecect_timeout, 1000}, {ip_v6_disable, false}, {user_dir, UserDir}],
- CMiscOpt1 = [{connecect_timeout, infinity}, {ip_v6_disable, true}, {user_dir, UserDir}],
- SMiscOpt0 = [{ip_v6_disable, false}, {user_dir, UserDir}, {system_dir, SystemDir}],
- SMiscOpt1 = [{ip_v6_disable, true}, {user_dir, UserDir}, {system_dir, SystemDir}],
+ CMiscOpt0 = [{connecect_timeout, 1000}, {ip_v6_disabled, false}, {user_dir, UserDir}],
+ CMiscOpt1 = [{connecect_timeout, infinity}, {ip_v6_disabled, true}, {user_dir, UserDir}],
+ SMiscOpt0 = [{ip_v6_disabled, false}, {user_dir, UserDir}, {system_dir, SystemDir}],
+ SMiscOpt1 = [{ip_v6_disabled, true}, {user_dir, UserDir}, {system_dir, SystemDir}],
ClientOpts = ?config(client_opts, Config),
ServerOpts = ?config(server_opts, Config),
--
cgit v1.2.3
From d763d5864ea785098233fb35649ffb8913c431ab Mon Sep 17 00:00:00 2001
From: Tuncer Ayaz
Date: Tue, 3 Jul 2012 11:18:28 +0200
Subject: observer: fix app file (Noticed-by: Motiejus Jakstys)
Add missing observer modules to observer.app.src.
---
lib/observer/src/observer.app.src | 12 ++++++++++++
1 file changed, 12 insertions(+)
(limited to 'lib')
diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src
index 5c65ea5c8f..d3aaf351dd 100644
--- a/lib/observer/src/observer.app.src
+++ b/lib/observer/src/observer.app.src
@@ -25,6 +25,18 @@
etop_gui,
etop_tr,
etop_txt,
+ observer,
+ observer_app_wx,
+ observer_lib,
+ observer_perf_wx,
+ observer_pro_wx,
+ observer_procinfo,
+ observer_sys_wx,
+ observer_trace_wx,
+ observer_traceoptions_wx,
+ observer_tv_table,
+ observer_tv_wx,
+ observer_wx,
ttb,
ttb_et]},
{registered, []},
--
cgit v1.2.3
From 9d3bb79a1bec07706de46a67a001269dbbada293 Mon Sep 17 00:00:00 2001
From: Tomas Abrahamsson
Date: Mon, 7 May 2012 00:26:37 +0200
Subject: Fix SCTP multihoming
Setting several ip addresses for an SCTP socket worked only for IPv4
on Linux. For IPv6 and for other for instance Solaris and FreeBSD, it
failed with badarg for both IPv4 and IPv6.
For the first address specified to gen_sctp:open, bind is now called,
while for any following addresses, sctp_bindx is called, repeatedly,
with one address at a time. Previously, sctp_bindx was called for all
addresses in one go, with the addresses in reverse order, and bind was
not called at all if more than one address was specified. Both
Solaris and FreeBSD requires bind to have been called before calling
sctp_bindx, and FreeBSD additionally allows at most one address at a
time in the call to sctp_bindx.
For some versions of Linux, for instance SuSE 10, the port can be 0
only for the call to bind but not for subsequent calls to sctp_bindx,
so replace with the port number assigned by the operating system.
---
lib/kernel/src/inet.erl | 42 +++++++++++++++---
lib/kernel/test/gen_sctp_SUITE.erl | 91 +++++++++++++++++++++++++++++++++++++-
2 files changed, 124 insertions(+), 9 deletions(-)
(limited to 'lib')
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index 0bb5444dbb..1a03424f88 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -763,8 +763,12 @@ sctp_opt([Opt|Opts], Mod, R, As) ->
{Name,Val} -> sctp_opt (Opts, Mod, R, As, Name, Val);
_ -> {error,badarg}
end;
-sctp_opt([], _Mod, R, _SockOpts) ->
- {ok, R}.
+sctp_opt([], _Mod, #sctp_opts{ifaddr=IfAddr}=R, _SockOpts) ->
+ if is_list(IfAddr) ->
+ {ok, R#sctp_opts{ifaddr=lists:reverse(IfAddr)}};
+ true ->
+ {ok, R}
+ end.
sctp_opt(Opts, Mod, R, As, Name, Val) ->
case add_opt(Name, Val, R#sctp_opts.opts, As) of
@@ -1015,11 +1019,7 @@ open(Fd, Addr, Port, Opts, Protocol, Family, Type, Module) when Fd < 0 ->
case prim_inet:setopts(S, Opts) of
ok ->
case if is_list(Addr) ->
- prim_inet:bind(S, add,
- [case A of
- {_,_} -> A;
- _ -> {A,Port}
- end || A <- Addr]);
+ bindx(S, Addr, Port);
true ->
prim_inet:bind(S, Addr, Port)
end of
@@ -1040,6 +1040,34 @@ open(Fd, Addr, Port, Opts, Protocol, Family, Type, Module) when Fd < 0 ->
open(Fd, _Addr, _Port, Opts, Protocol, Family, Type, Module) ->
fdopen(Fd, Opts, Protocol, Family, Type, Module).
+bindx(S, [Addr], Port0) ->
+ {IP, Port} = set_bindx_port(Addr, Port0),
+ prim_inet:bind(S, IP, Port);
+bindx(S, Addrs, Port0) ->
+ [{IP, Port} | Rest] = [set_bindx_port(Addr, Port0) || Addr <- Addrs],
+ case prim_inet:bind(S, IP, Port) of
+ {ok, AssignedPort} when Port =:= 0 ->
+ %% On newer Linux kernels, Solaris and FreeBSD, calling
+ %% bindx with port 0 is ok, but on SuSE 10, it results in einval
+ Rest2 = [change_bindx_0_port(Addr, AssignedPort) || Addr <- Rest],
+ prim_inet:bind(S, add, Rest2);
+ {ok, _} ->
+ prim_inet:bind(S, add, Rest);
+ Error ->
+ Error
+ end.
+
+set_bindx_port({_IP, _Port}=Addr, _OtherPort) ->
+ Addr;
+set_bindx_port(IP, Port) ->
+ {IP, Port}.
+
+change_bindx_0_port({IP, 0}, AssignedPort) ->
+ {IP, AssignedPort};
+change_bindx_0_port({_IP, _Port}=Addr, _AssignedPort) ->
+ Addr.
+
+
-spec fdopen(Fd :: non_neg_integer(),
Opts :: [socket_setopt()],
Protocol :: socket_protocol(),
diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl
index 8f490b6643..209b95a99f 100644
--- a/lib/kernel/test/gen_sctp_SUITE.erl
+++ b/lib/kernel/test/gen_sctp_SUITE.erl
@@ -31,14 +31,16 @@
[basic/1,
api_open_close/1,api_listen/1,api_connect_init/1,api_opts/1,
xfer_min/1,xfer_active/1,def_sndrcvinfo/1,implicit_inet6/1,
- basic_stream/1, xfer_stream_min/1, peeloff/1, buffers/1]).
+ basic_stream/1, xfer_stream_min/1, peeloff/1, buffers/1,
+ open_multihoming_ipv4_socket/1, open_multihoming_ipv6_socket/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[basic, api_open_close, api_listen, api_connect_init,
api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6,
- basic_stream, xfer_stream_min, peeloff, buffers].
+ basic_stream, xfer_stream_min, peeloff, buffers,
+ open_multihoming_ipv4_socket, open_multihoming_ipv6_socket].
groups() ->
[].
@@ -1105,6 +1107,91 @@ mk_data(N, Bytes, Bin) when N < Bytes ->
mk_data(_, _, Bin) ->
Bin.
+
+
+open_multihoming_ipv4_socket(doc) ->
+ "Test opening a multihoming ipv4 socket";
+open_multihoming_ipv4_socket(suite) ->
+ [];
+open_multihoming_ipv4_socket(Config) when is_list(Config) ->
+ ?line IfAddrs = ok(inet:getifaddrs()),
+ ?line
+ case filter_addrs_by_family(IfAddrs, inet) of
+ [Addr1, Addr2 | _] ->
+ ?line io:format("using ipv4 addresses ~p and ~p~n",
+ [Addr1, Addr2]),
+ ?line S = ok(gen_sctp:open(0, [{ip,Addr1},{ip,Addr2},inet])),
+ ?line ok = gen_sctp:listen(S, true),
+ ?line setup_connection(S, Addr1, inet),
+ ?line ok = gen_sctp:close(S);
+ X ->
+ {skip, f("Need 2 IPv4 addresses, found only ~p", [X])}
+ end.
+
+open_multihoming_ipv6_socket(doc) ->
+ "Test opening a multihoming ipv6 socket";
+open_multihoming_ipv6_socket(suite) ->
+ [];
+open_multihoming_ipv6_socket(Config) when is_list(Config) ->
+ ?line IfAddrs = ok(inet:getifaddrs()),
+ ?line
+ case inet:getaddr(localhost, inet6) of
+ {error,eafnosupport} ->
+ {skip, "No IPv6 support"};
+ {ok, _} ->
+ ?line
+ case filter_addrs_by_family(IfAddrs, inet6) of
+ [Addr1, Addr2 | _] ->
+ ?line io:format("using ipv6 addresses ~p and ~p~n",
+ [Addr1, Addr2]),
+ ?line S = ok(gen_sctp:open(
+ 0, [{ip,Addr1},{ip,Addr2}, inet6])),
+ ?line ok = gen_sctp:listen(S, true),
+ ?line setup_connection(S, Addr1, inet6),
+ ?line ok = gen_sctp:close(S);
+ X ->
+ {skip, f("Need 2 IPv6 addresses, found ~p", [X])}
+ end
+ end.
+
+filter_addrs_by_family(IfAddrs, Family) ->
+ lists:flatten([[Addr || {addr, Addr} <- Info,
+ is_good_addr(Addr, Family)]
+ || {_IfName, Info} <- IfAddrs]).
+
+is_good_addr(Addr, inet) when tuple_size(Addr) =:= 4 ->
+ true;
+is_good_addr({0,0,0,0,0,16#ffff,_,_}, inet6) ->
+ false; %% ipv4 mapped
+is_good_addr({16#fe80,_,_,_,_,_,_,_}, inet6) ->
+ false; %% link-local
+is_good_addr(Addr, inet6) when tuple_size(Addr) =:= 8 ->
+ true;
+is_good_addr(_Addr, _Family) ->
+ false.
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+setup_connection(S1, Addr, IpFamily) ->
+ ?line P1 = ok(inet:port(S1)),
+ ?line S2 = ok(gen_sctp:open(0, [IpFamily])),
+ ?line P2 = ok(inet:port(S2)),
+ ?line #sctp_assoc_change{state=comm_up} =
+ ok(gen_sctp:connect(S2, Addr, P1, [])),
+ ?line case ok(gen_sctp:recv(S1)) of
+ {Addr,P2,_,#sctp_assoc_change{state=comm_up}} ->
+ ok;
+ {Addr,P2,_,#sctp_paddr_change{state=addr_confirmed,
+ addr={Addr,P2}}} ->
+ ?line case ok(gen_sctp:recv(S1)) of
+ {Addr,P2,_,#sctp_assoc_change{state=comm_up}} ->
+ ok
+ end
+ end,
+ ?line ok = gen_sctp:close(S2).
+
+
%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% socket gen_server ultra light
--
cgit v1.2.3
From 87570500f821c4aaeafa18705b3a4a479f5d9baa Mon Sep 17 00:00:00 2001
From: Tomas Abrahamsson
Date: Thu, 10 May 2012 18:52:18 +0200
Subject: Allow mixed IPv4 and IPv6 addresses to sctp_bindx
Also allow mixed address families to bind, since the first address on
a multihomed sctp socket must be bound with bind, while the rest are
to be bound using sctp_bindx.
At least Linux supports adding address of mixing families.
Make inet_set_faddress function available also when HAVE_SCTP is not
defined, since we use it to find an address for bind to be able to mix
ipv4 and ipv6 addresses.
---
lib/kernel/test/gen_sctp_SUITE.erl | 201 ++++++++++++++++++++++++++++---------
1 file changed, 154 insertions(+), 47 deletions(-)
(limited to 'lib')
diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl
index 209b95a99f..f4bf6e719e 100644
--- a/lib/kernel/test/gen_sctp_SUITE.erl
+++ b/lib/kernel/test/gen_sctp_SUITE.erl
@@ -32,7 +32,10 @@
api_open_close/1,api_listen/1,api_connect_init/1,api_opts/1,
xfer_min/1,xfer_active/1,def_sndrcvinfo/1,implicit_inet6/1,
basic_stream/1, xfer_stream_min/1, peeloff/1, buffers/1,
- open_multihoming_ipv4_socket/1, open_multihoming_ipv6_socket/1]).
+ open_multihoming_ipv4_socket/1,
+ open_unihoming_ipv6_socket/1,
+ open_multihoming_ipv6_socket/1,
+ open_multihoming_ipv4_and_ipv6_socket/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -40,7 +43,10 @@ all() ->
[basic, api_open_close, api_listen, api_connect_init,
api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6,
basic_stream, xfer_stream_min, peeloff, buffers,
- open_multihoming_ipv4_socket, open_multihoming_ipv6_socket].
+ open_multihoming_ipv4_socket,
+ open_unihoming_ipv6_socket,
+ open_multihoming_ipv6_socket,
+ open_multihoming_ipv4_and_ipv6_socket].
groups() ->
[].
@@ -1114,45 +1120,119 @@ open_multihoming_ipv4_socket(doc) ->
open_multihoming_ipv4_socket(suite) ->
[];
open_multihoming_ipv4_socket(Config) when is_list(Config) ->
- ?line IfAddrs = ok(inet:getifaddrs()),
- ?line
- case filter_addrs_by_family(IfAddrs, inet) of
- [Addr1, Addr2 | _] ->
- ?line io:format("using ipv4 addresses ~p and ~p~n",
- [Addr1, Addr2]),
- ?line S = ok(gen_sctp:open(0, [{ip,Addr1},{ip,Addr2},inet])),
- ?line ok = gen_sctp:listen(S, true),
- ?line setup_connection(S, Addr1, inet),
- ?line ok = gen_sctp:close(S);
- X ->
- {skip, f("Need 2 IPv4 addresses, found only ~p", [X])}
- end.
+ ?line case get_addrs_by_family(inet, 2) of
+ {ok, [Addr1, Addr2]} ->
+ ?line do_open_and_connect([Addr1, Addr2], Addr1);
+ {error, Reason} ->
+ {skip, Reason}
+ end.
+
+open_unihoming_ipv6_socket(doc) ->
+ %% This test is mostly aimed to indicate
+ %% whether host has a non-working ipv6 setup
+ "Test opening a unihoming (non-multihoming) ipv6 socket";
+open_unihoming_ipv6_socket(suite) ->
+ [];
+open_unihoming_ipv6_socket(Config) when is_list(Config) ->
+ ?line case get_addrs_by_family(inet6, 1) of
+ {ok, [Addr]} ->
+ ?line do_open_and_connect([Addr], Addr);
+ {error, Reason} ->
+ {skip, Reason}
+ end.
+
open_multihoming_ipv6_socket(doc) ->
"Test opening a multihoming ipv6 socket";
open_multihoming_ipv6_socket(suite) ->
[];
open_multihoming_ipv6_socket(Config) when is_list(Config) ->
- ?line IfAddrs = ok(inet:getifaddrs()),
+ ?line case get_addrs_by_family(inet6, 2) of
+ {ok, [Addr1, Addr2]} ->
+ ?line do_open_and_connect([Addr1, Addr2], Addr1);
+ {error, Reason} ->
+ {skip, Reason}
+ end.
+
+open_multihoming_ipv4_and_ipv6_socket(doc) ->
+ "Test opening a multihoming ipv6 socket with ipv4 and ipv6 addresses";
+open_multihoming_ipv4_and_ipv6_socket(suite) ->
+ [];
+open_multihoming_ipv4_and_ipv6_socket(Config) when is_list(Config) ->
+ ?line case get_addrs_by_family(inet_and_inet6, 2) of
+ {ok, [[InetAddr1, InetAddr2], [Inet6Addr1, Inet6Addr2]]} ->
+ %% Connect to the first address to test bind
+ ?line do_open_and_connect([InetAddr1, Inet6Addr1, InetAddr2],
+ InetAddr1),
+ ?line do_open_and_connect([Inet6Addr1, InetAddr1],
+ Inet6Addr1),
+
+ %% Connect an address, not the first,
+ %% to test sctp_bindx
+ ?line do_open_and_connect([Inet6Addr1, Inet6Addr2, InetAddr1],
+ Inet6Addr2),
+ ?line do_open_and_connect([Inet6Addr1, Inet6Addr2, InetAddr1],
+ InetAddr1);
+ {error, Reason} ->
+ {skip, Reason}
+ end.
+
+
+get_addrs_by_family(Family, NumAddrs) ->
+ case os:type() of
+ {unix,linux} ->
+ get_addrs_by_family_aux(Family, NumAddrs);
+ {unix,freebsd} ->
+ get_addrs_by_family_aux(Family, NumAddrs);
+ {unix,sunos} ->
+ case get_addrs_by_family_aux(Family, NumAddrs) of
+ {ok, [InetAddrs, Inet6Addrs]} when Family =:= inet_and_inet6 ->
+ %% Man page for sctp_bindx on Solaris says: "If sock is an
+ %% Internet Protocol Version 6 (IPv6) socket, addrs should
+ %% be an array of sockaddr_in6 structures containing IPv6
+ %% or IPv4-mapped IPv6 addresses."
+ {ok, [ipv4_map_addrs(InetAddrs), Inet6Addrs]};
+ {ok, Addrs} ->
+ {ok, Addrs};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ Os ->
+ Reason = if Family =:= inet_and_inet6 ->
+ f("Mixing ipv4 and ipv6 addresses for multihoming "
+ " has not been verified on ~p", [Os]);
+ true ->
+ f("Multihoming for ~p has not been verified on ~p",
+ [Family, Os])
+ end,
+ {error, Reason}
+ end.
+
+get_addrs_by_family_aux(Family, NumAddrs) when Family =:= inet;
+ Family =:= inet6 ->
?line
- case inet:getaddr(localhost, inet6) of
+ case inet:getaddr(localhost, Family) of
{error,eafnosupport} ->
- {skip, "No IPv6 support"};
+ {skip, f("No support for ~p", Family)};
{ok, _} ->
- ?line
- case filter_addrs_by_family(IfAddrs, inet6) of
- [Addr1, Addr2 | _] ->
- ?line io:format("using ipv6 addresses ~p and ~p~n",
- [Addr1, Addr2]),
- ?line S = ok(gen_sctp:open(
- 0, [{ip,Addr1},{ip,Addr2}, inet6])),
- ?line ok = gen_sctp:listen(S, true),
- ?line setup_connection(S, Addr1, inet6),
- ?line ok = gen_sctp:close(S);
- X ->
- {skip, f("Need 2 IPv6 addresses, found ~p", [X])}
- end
- end.
+ ?line IfAddrs = ok(inet:getifaddrs()),
+ ?line case filter_addrs_by_family(IfAddrs, Family) of
+ Addrs when length(Addrs) >= NumAddrs ->
+ {ok, lists:sublist(Addrs, NumAddrs)};
+ [] ->
+ {error, f("Need ~p ~p address(es) found none~n",
+ [NumAddrs, Family])};
+ Addrs ->
+ {error,
+ f("Need ~p ~p address(es) found only ~p: ~p~n",
+ [NumAddrs, Family, length(Addrs), Addrs])}
+ end
+ end;
+get_addrs_by_family_aux(inet_and_inet6, NumAddrs) ->
+ ?line catch {ok, [case get_addrs_by_family_aux(Family, NumAddrs) of
+ {ok, Addrs} -> Addrs;
+ {error, Reason} -> throw({error, Reason})
+ end || Family <- [inet, inet6]]}.
filter_addrs_by_family(IfAddrs, Family) ->
lists:flatten([[Addr || {addr, Addr} <- Info,
@@ -1170,27 +1250,54 @@ is_good_addr(Addr, inet6) when tuple_size(Addr) =:= 8 ->
is_good_addr(_Addr, _Family) ->
false.
+ipv4_map_addrs(InetAddrs) ->
+ [begin
+ <> = <>,
+ <> = <>,
+ {0, 0, 0, 0, 0, 16#ffff, AB, CD}
+ end || {A,B,C,D} <- InetAddrs].
+
f(F, A) ->
lists:flatten(io_lib:format(F, A)).
-setup_connection(S1, Addr, IpFamily) ->
+do_open_and_connect(ServerAddresses, AddressToConnectTo) ->
+ ?line ServerFamily = get_family_by_addrs(ServerAddresses),
+ ?line io:format("Serving ~p addresses: ~p~n",
+ [ServerFamily, ServerAddresses]),
+ ?line S1 = ok(gen_sctp:open(0, [{ip,Addr} || Addr <- ServerAddresses] ++
+ [ServerFamily])),
+ ?line ok = gen_sctp:listen(S1, true),
?line P1 = ok(inet:port(S1)),
- ?line S2 = ok(gen_sctp:open(0, [IpFamily])),
- ?line P2 = ok(inet:port(S2)),
+ ?line ClientFamily = get_family_by_addr(AddressToConnectTo),
+ ?line io:format("Connecting to ~p ~p~n",
+ [ClientFamily, AddressToConnectTo]),
+ ?line S2 = ok(gen_sctp:open(0, [ClientFamily])),
+ %% Verify client can connect
?line #sctp_assoc_change{state=comm_up} =
- ok(gen_sctp:connect(S2, Addr, P1, [])),
- ?line case ok(gen_sctp:recv(S1)) of
- {Addr,P2,_,#sctp_assoc_change{state=comm_up}} ->
- ok;
- {Addr,P2,_,#sctp_paddr_change{state=addr_confirmed,
- addr={Addr,P2}}} ->
- ?line case ok(gen_sctp:recv(S1)) of
- {Addr,P2,_,#sctp_assoc_change{state=comm_up}} ->
- ok
- end
- end,
- ?line ok = gen_sctp:close(S2).
+ ok(gen_sctp:connect(S2, AddressToConnectTo, P1, [])),
+ %% verify server side also receives comm_up from client
+ ?line recv_comm_up_eventually(S1),
+ ?line ok = gen_sctp:close(S2),
+ ?line ok = gen_sctp:close(S1).
+
+%% If at least one of the addresses is an ipv6 address, return inet6, else inet.
+get_family_by_addrs(Addresses) ->
+ ?line case lists:usort([get_family_by_addr(Addr) || Addr <- Addresses]) of
+ [inet, inet6] -> inet6;
+ [inet] -> inet;
+ [inet6] -> inet6
+ end.
+get_family_by_addr(Addr) when tuple_size(Addr) =:= 4 -> inet;
+get_family_by_addr(Addr) when tuple_size(Addr) =:= 8 -> inet6.
+
+recv_comm_up_eventually(S) ->
+ ?line case ok(gen_sctp:recv(S)) of
+ {_Addr, _Port, _Info, #sctp_assoc_change{state=comm_up}} ->
+ ok;
+ {_Addr, _Port, _Info, _OtherSctpMsg} ->
+ ?line recv_comm_up_eventually(S)
+ end.
%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% socket gen_server ultra light
--
cgit v1.2.3
From a71c6b976fa79fa3bcd0e61850a1a57071159b28 Mon Sep 17 00:00:00 2001
From: Lukas Larsson
Date: Thu, 9 Aug 2012 18:00:13 +0200
Subject: Update user config to use nested tuple keys
ct:get_config and ct:require can now use nested tuples
to fetch data from user configuration. E.g.
ct:get_config({localhost,ip,v4}).
This introduces a backwards incompatability with how names
are associated with keys when using require/2. E.g.
ct:require(a_name,{localhost,ip}) will associate a_name with ip
instead of localhost.
---
lib/common_test/doc/src/common_test_app.xml | 6 +-
lib/common_test/doc/src/config_file_chapter.xml | 2 +-
lib/common_test/doc/src/event_handler_chapter.xml | 4 +-
lib/common_test/doc/src/write_test_chapter.xml | 3 +-
lib/common_test/src/ct.erl | 97 ++++----
lib/common_test/src/ct_config.erl | 250 +++++++++++----------
lib/common_test/src/ct_ftp.erl | 6 +
lib/common_test/src/ct_netconfc.erl | 1 +
lib/common_test/src/ct_ssh.erl | 5 +-
lib/common_test/src/ct_telnet.erl | 2 +
lib/common_test/test/ct_config_SUITE.erl | 112 ++++-----
.../test/ct_config_SUITE_data/config/config.txt | 3 +-
.../test/ct_config_SUITE_data/config/config.xml | 1 +
.../test/ct_config_SUITE_data/config/shadow.txt | 12 +
.../config/test/config_static_SUITE.erl | 90 +++++++-
15 files changed, 358 insertions(+), 236 deletions(-)
create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/shadow.txt
(limited to 'lib')
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index 6babdb93af..addeed002a 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -162,7 +162,7 @@
Func = atom()
Args = list()
Fun = fun()
- Required = Key | {Key,SubKeys}
+ Required = Key | {Key,SubKeys} | {Key,SubKey} | {Key,SubKey,SubKeys}
Key = atom()
SubKeys = SubKey | [SubKey]
SubKey = atom()
@@ -289,7 +289,7 @@
Func = atom()
Args = list()
Fun = fun()
- Required = Key | {Key,SubKeys}
+ Required = Key | {Key,SubKeys} | {Key,Subkey} | {Key,Subkey,SubKeys}
Key = atom()
SubKeys = SubKey | [SubKey]
SubKey = atom()
@@ -476,7 +476,7 @@
Func = atom()
Args = list()
Fun = fun()
- Required = Key | {Key,SubKeys}
+ Required = Key | {Key,SubKeys} | {Key,Subkey} | {Key,Subkey,SubKeys}
Key = atom()
SubKeys = SubKey | [SubKey]
SubKey = atom()
diff --git a/lib/common_test/doc/src/config_file_chapter.xml b/lib/common_test/doc/src/config_file_chapter.xml
index 6a860bb58b..706d0d5f4e 100644
--- a/lib/common_test/doc/src/config_file_chapter.xml
+++ b/lib/common_test/doc/src/config_file_chapter.xml
@@ -295,7 +295,7 @@
[{ftp_host, [{ftp, "targethost"}, {username, "tester"}, {password, "letmein"}]},
- {lm_directory, "/test/loadmodules"}]
+ {lm_directory, "/test/loadmodules"}]
diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml
index a5886b9687..2f796b91ab 100644
--- a/lib/common_test/doc/src/event_handler_chapter.xml
+++ b/lib/common_test/doc/src/event_handler_chapter.xml
@@ -205,7 +205,7 @@
{error,{RunTimeError,StackTrace}} |
{timetrap_timeout,integer()} |
{failed,{Suite,end_per_testcase,FailInfo}}, reason for failure.
- RequireInfo = {not_available,atom()}, why require has failed.
+ RequireInfo = {not_available,atom() | tuple()}, why require has failed.
FailInfo = {timetrap_timeout,integer()} |
{RunTimeError,StackTrace} |
UserTerm,
@@ -233,7 +233,7 @@
reason for auto skipping Func.
FailReason = {Suite,ConfigFunc,FailInfo}} |
{Suite,FailedCaseInSequence}, reason for failure.
- RequireInfo = {not_available,atom()}, why require has failed.
+ RequireInfo = {not_available,atom() | tuple()}, why require has failed.
ConfigFunc = init_per_suite | init_per_group
FailInfo = {timetrap_timeout,integer()} |
{RunTimeError,StackTrace} |
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 7b7e7af8ea..d545c9e432 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -338,7 +338,8 @@
testcase2() ->
- [{require, unix_telnet, {unix, [telnet, username, password]}},
+ [{require, unix_telnet, unix},
+ {require, {unix, [telnet, username, password]}},
{default_config, unix, [{telnet, "my_telnet_host"},
{username, "aladdin"},
{password, "sesame"}]}}].
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 6373634812..922f794395 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -266,27 +266,34 @@ stop_interactive() ->
%%%-----------------------------------------------------------------
%%% @spec require(Required) -> ok | {error,Reason}
-%%% Required = Key | {Key,SubKeys}
+%%% Required = Key | {Key,SubKeys} | {Key,SubKey,SubKeys}
%%% Key = atom()
%%% SubKeys = SubKey | [SubKey]
%%% SubKey = atom()
%%%
-%%% @doc Check if the required configuration is available.
+%%% @doc Check if the required configuration is available. It is possible
+%%% to specify arbitrarily deep tuples as Required. Note that it is
+%%% only the last element of the tuple which can be a list of SubKeys.
%%%
-%%% Example: require the variable myvar
:
-%%% ok = ct:require(myvar)
+%%% Example 1: require the variable myvar
:
+%%% ok = ct:require(myvar).
%%%
%%% In this case the config file must at least contain:
-%%%
-%%% {myvar,Value}.
+%%% {myvar,Value}.
%%%
-%%% Example: require the variable myvar
with
-%%% subvariable sub1
:
-%%% ok = ct:require({myvar,sub1})
+%%% Example 2: require the key myvar
with
+%%% subkeys sub1
and sub2
:
+%%% ok = ct:require({myvar,[sub1,sub2]}).
%%%
%%% In this case the config file must at least contain:
-%%%
-%%% {myvar,[{sub1,Value}]}.
+%%% {myvar,[{sub1,Value},{sub2,Value}]}.
+%%%
+%%% Example 3: require the key myvar
with
+%%% subkey sub1
with subsub1
:
+%%% ok = ct:require({myvar,sub1,sub2}).
+%%%
+%%% In this case the config file must at least contain:
+%%% {myvar,[{sub1,[{sub2,Value}]}]}.
%%%
%%% @see require/2
%%% @see get_config/1
@@ -298,30 +305,36 @@ require(Required) ->
%%%-----------------------------------------------------------------
%%% @spec require(Name,Required) -> ok | {error,Reason}
%%% Name = atom()
-%%% Required = Key | {Key,SubKeys}
+%%% Required = Key | {Key,SubKey} | {Key,SubKey,SubKey}
+%%% SubKey = Key
%%% Key = atom()
-%%% SubKeys = SubKey | [SubKey]
-%%% SubKey = atom()
%%%
%%% @doc Check if the required configuration is available, and give it
-%%% a name.
+%%% a name. The semantics for Required is the same as in
+%%% required/1 except that it is not possible to specify a list
+%%% of SubKeys.
%%%
-%%% If the requested data is available, the main entry will be
+%%%
If the requested data is available, the sub entry will be
%%% associated with Name
so that the value of the element
%%% can be read with get_config/1,2
provided
-%%% Name
instead of the Key
.
+%%% Name
instead of the whole Required
term.
%%%
%%% Example: Require one node with a telnet connection and an
-%%% ftp connection. Name the node a
:
ok =
-%%% ct:require(a,{node,[telnet,ftp]}).
All references
-%%% to this node may then use the node name. E.g. you can fetch a
-%%% file over ftp like this:
-%%% ok = ct:ftp_get(a,RemoteFile,LocalFile).
+%%% ftp connection. Name the node a
:
+%%% ok = ct:require(a,{machine,node}).
+%%% All references to this node may then use the node name.
+%%% E.g. you can fetch a file over ftp like this:
+%%% ok = ct:ftp_get(a,RemoteFile,LocalFile).
%%%
%%% For this to work, the config file must at least contain:
-%%%
-%%% {node,[{telnet,IpAddr},
-%%% {ftp,IpAddr}]}.
+%%% {machine,[{node,[{telnet,IpAddr},{ftp,IpAddr}]}]}.
+%%%
+%%% The behaviour of this function changed radically in common_test
+%%% 1.6.2. In order too keep some backwards compatability it is still possible
+%%% to do:
ct:require(a,{node,[telnet,ftp]}).
+%%% This will associate the name a with the top level node entry.
+%%% For this to work, the config file must at least contain:
+%%% {node,[{telnet,IpAddr},{ftp,IpAddr}]}.
%%%
%%% @see require/1
%%% @see get_config/1
@@ -344,7 +357,7 @@ get_config(Required,Default) ->
%%%-----------------------------------------------------------------
%%% @spec get_config(Required,Default,Opts) -> ValueOrElement
-%%% Required = KeyOrName | {KeyOrName,SubKey}
+%%% Required = KeyOrName | {KeyOrName,SubKey} | {KeyOrName,SubKey,SubKey}
%%% KeyOrName = atom()
%%% SubKey = atom()
%%% Default = term()
@@ -362,25 +375,25 @@ get_config(Required,Default) ->
%%% Example, given the following config file:
%%%
%%% {unix,[{telnet,IpAddr},
-%%% {username,Username},
-%%% {password,Password}]}.
-%%% get_config(unix,Default) ->
+%%% {user,[{username,Username},
+%%% {password,Password}]}]}.
+%%% ct:get_config(unix,Default) ->
%%% [{telnet,IpAddr},
-%%% {username,Username},
-%%% {password,Password}]
-%%% get_config({unix,telnet},Default) -> IpAddr
-%%% get_config({unix,ftp},Default) -> Default
-%%% get_config(unknownkey,Default) -> Default
+%%% {user, [{username,Username},
+%%% {password,Password}]}]
+%%% ct:get_config({unix,telnet},Default) -> IpAddr
+%%% ct:get_config({unix,user,username},Default) -> Username
+%%% ct:get_config({unix,ftp},Default) -> Default
+%%% ct:get_config(unknownkey,Default) -> Default
%%%
%%% If a config variable key has been associated with a name (by
%%% means of require/2
or a require statement), the name
%%% may be used instead of the key to read the value:
%%%
-%%% require(myhost,unix) -> ok
-%%% get_config(myhost,Default) ->
-%%% [{telnet,IpAddr},
-%%% {username,Username},
-%%% {password,Password}]
+%%% ct:require(myuser,{unix,user}) -> ok.
+%%% ct:get_config(myuser,Default) ->
+%%% [{username,Username},
+%%% {password,Password}]
%%%
%%% If a config variable is defined in multiple files and you want to
%%% access all possible values, use the all
option. The
@@ -390,9 +403,7 @@ get_config(Required,Default) ->
%%%
%%%
If you want config elements (key-value tuples) returned as result
%%% instead of values, use the element
option.
-%%% The returned elements will then be on the form {KeyOrName,Value}
,
-%%% or (in case a subkey has been specified)
-%%% {{KeyOrName,SubKey},Value}
+%%% The returned elements will then be on the form {Required,Value}
%%%
%%% @see get_config/1
%%% @see get_config/2
@@ -403,7 +414,7 @@ get_config(Required,Default,Opts) ->
%%%-----------------------------------------------------------------
%%% @spec reload_config(Required) -> ValueOrElement
-%%% Required = KeyOrName | {KeyOrName,SubKey}
+%%% Required = KeyOrName | {KeyOrName,SubKey} | {KeyOrName,SubKey,SubKey}
%%% KeyOrName = atom()
%%% SubKey = atom()
%%% ValueOrElement = term()
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index 9277af5bc1..5b7ed0f7fb 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -122,8 +122,8 @@ return({To,Ref},Result) ->
loop(StartDir) ->
receive
- {{require,Name,Tag,SubTags},From} ->
- Result = do_require(Name,Tag,SubTags),
+ {{require,Name,Key},From} ->
+ Result = do_require(Name,Key),
return(From,Result),
loop(StartDir);
{{set_default_config,{Config,Scope}},From} ->
@@ -168,16 +168,19 @@ reload_config(KeyOrName) ->
call({reload_config, KeyOrName}).
process_default_configs(Opts) ->
- case lists:keysearch(config, 1, Opts) of
- {value,{_,Files=[File|_]}} when is_list(File) ->
- Files;
- {value,{_,File=[C|_]}} when is_integer(C) ->
- [File];
- {value,{_,[]}} ->
- [];
- false ->
- []
- end.
+ lists:flatmap(fun({config,[_|_] = FileOrFiles}) ->
+ case {io_lib:printable_list(FileOrFiles),
+ io_lib:printable_list(hd(FileOrFiles))} of
+ {true,true} ->
+ FileOrFiles;
+ {true,false} ->
+ [FileOrFiles];
+ _ ->
+ []
+ end;
+ (_) ->
+ []
+ end,Opts).
process_user_configs(Opts, Acc) ->
case lists:keytake(userconfig, 1, Opts) of
@@ -319,75 +322,58 @@ get_config(KeyOrName,Default) ->
get_config(KeyOrName,Default,[]).
get_config(KeyOrName,Default,Opts) when is_atom(KeyOrName) ->
- case lookup_config(KeyOrName) of
- [] ->
- Default;
- [{_Ref,Val}|_] = Vals ->
- case {lists:member(all,Opts),lists:member(element,Opts)} of
- {true,true} ->
- [{KeyOrName,V} || {_R,V} <- lists:sort(Vals)];
- {true,false} ->
- [V || {_R,V} <- lists:sort(Vals)];
- {false,true} ->
- {KeyOrName,Val};
- {false,false} ->
- Val
- end
+ case get_config({KeyOrName}, Default, Opts) of
+ %% If only an atom is given, then we need to unwrap the
+ %% key if it is returned
+ {{KeyOrName}, Val} ->
+ {KeyOrName, Val};
+ [{{KeyOrName}, _Val}|_] = Res ->
+ [{K, Val} || {{K},Val} <- Res, K == KeyOrName];
+ Else ->
+ Else
end;
-get_config({KeyOrName,SubKey},Default,Opts) ->
- case lookup_config(KeyOrName) of
+%% This useage of get_config is only used by internal ct functions
+%% and may change at any time
+get_config({DeepKey,SubKey}, Default, Opts) when is_tuple(DeepKey) ->
+ get_config(erlang:append_element(DeepKey, SubKey), Default, Opts);
+get_config(KeyOrName,Default,Opts) when is_tuple(KeyOrName) ->
+ case lookup_config(element(1,KeyOrName)) of
[] ->
- Default;
+ format_value([Default],KeyOrName,Opts);
Vals ->
- Vals1 = case [Val || {_Ref,Val} <- lists:sort(Vals)] of
- Result=[L|_] when is_list(L) ->
- case L of
- [{_,_}|_] ->
- Result;
- _ ->
- []
- end;
- _ ->
- []
- end,
- case get_subconfig([SubKey],Vals1,[],Opts) of
- {ok,[{_,SubVal}|_]=SubVals} ->
- case {lists:member(all,Opts),lists:member(element,Opts)} of
- {true,true} ->
- [{{KeyOrName,SubKey},Val} || {_,Val} <- SubVals];
- {true,false} ->
- [Val || {_SubKey,Val} <- SubVals];
- {false,true} ->
- {{KeyOrName,SubKey},SubVal};
- {false,false} ->
- SubVal
- end;
- _ ->
- Default
- end
+ NewVals =
+ lists:map(
+ fun({Val}) ->
+ get_config(tl(tuple_to_list(KeyOrName)),
+ Val,Default,Opts)
+ end,Vals),
+ format_value(NewVals,KeyOrName,Opts)
end.
-get_subconfig(SubKeys,Values) ->
- get_subconfig(SubKeys,Values,[],[]).
-
-get_subconfig(SubKeys,[Value|Rest],Mapped,Opts) ->
- case do_get_config(SubKeys,Value,[]) of
- {ok,SubMapped} ->
- case lists:member(all,Opts) of
- true ->
- get_subconfig(SubKeys,Rest,Mapped++SubMapped,Opts);
- false ->
- {ok,SubMapped}
- end;
- _Error ->
- get_subconfig(SubKeys,Rest,Mapped,Opts)
+get_config([],Vals,_Default,_Opts) ->
+ Vals;
+get_config([[]],Vals,Default,Opts) ->
+ get_config([],Vals,Default,Opts);
+%% This case is used by {require,{unix,[port,host]}} functionality
+get_config([SubKeys], Vals, Default, _Opts) when is_list(SubKeys) ->
+ case do_get_config(SubKeys, Vals, []) of
+ {ok, SubVals} ->
+ [SubVal || {_,SubVal} <- SubVals];
+
+ _ ->
+ Default
end;
-get_subconfig(SubKeys,[],[],_) ->
- {error,{not_available,SubKeys}};
-get_subconfig(_SubKeys,[],Mapped,_) ->
- {ok,Mapped}.
+get_config([Key|Rest], Vals, Default, Opts) ->
+ case do_get_config([Key], Vals, []) of
+ {ok, [{Key,NewVals}]} ->
+ get_config(Rest, NewVals, Default, Opts);
+ _ ->
+ Default
+ end.
+do_get_config([Key|_], Available, _Mapped) when not is_list(Available) ->
+ {error,{not_available,Key}};
do_get_config([Key|Required],Available,Mapped) ->
case lists:keysearch(Key,1,Available) of
{value,{Key,Value}} ->
@@ -403,8 +389,7 @@ do_get_config([],_Available,Mapped) ->
get_all_config() ->
ets:select(?attr_table,[{#ct_conf{name='$1',key='$2',value='$3',
default='$4',_='_'},
- [],
- [{{'$1','$2','$3','$4'}}]}]).
+ [],[{{'$1','$2','$3','$4'}}]}]).
lookup_config(KeyOrName) ->
case lookup_name(KeyOrName) of
@@ -415,13 +400,23 @@ lookup_config(KeyOrName) ->
end.
lookup_name(Name) ->
- ets:select(?attr_table,[{#ct_conf{ref='$1',value='$2',name=Name,_='_'},
- [],
- [{{'$1','$2'}}]}]).
+ ets:select(?attr_table,[{#ct_conf{value='$1',name=Name,_='_'},
+ [],[{{'$1'}}]}]).
lookup_key(Key) ->
- ets:select(?attr_table,[{#ct_conf{key=Key,ref='$1',value='$2',name='_UNDEF',_='_'},
- [],
- [{{'$1','$2'}}]}]).
+ ets:select(?attr_table,[{#ct_conf{key=Key,value='$1',name='_UNDEF',_='_'},
+ [],[{{'$1'}}]}]).
+
+format_value([SubVal|_] = SubVals, KeyOrName, Opts) ->
+ case {lists:member(all,Opts),lists:member(element,Opts)} of
+ {true,true} ->
+ [{KeyOrName,Val} || Val <- SubVals];
+ {true,false} ->
+ [Val || Val <- SubVals];
+ {false,true} ->
+ {KeyOrName,SubVal};
+ {false,false} ->
+ SubVal
+ end.
lookup_handler_for_config({Key, _Subkey}) ->
lookup_handler_for_config(Key);
@@ -475,65 +470,78 @@ release_allocated([H|T]) ->
release_allocated([]) ->
ok.
-allocate(Name,Key,SubKeys) ->
- case ets:match_object(?attr_table,#ct_conf{key=Key,name='_UNDEF',_='_'}) of
- [] ->
+allocate(Name,Key) ->
+ Ref = make_ref(),
+ case get_config(Key,Ref,[all,element]) of
+ [{_,Ref}] ->
{error,{not_available,Key}};
- Available ->
- case allocate_subconfig(Name,SubKeys,Available,false) of
- ok ->
- ok;
- Error ->
- Error
- end
+ Configs ->
+ associate(Name,Key,Configs),
+ ok
end.
-allocate_subconfig(Name,SubKeys,[C=#ct_conf{value=Value}|Rest],Found) ->
- case do_get_config(SubKeys,Value,[]) of
- {ok,_SubMapped} ->
- ets:insert(?attr_table,C#ct_conf{name=Name}),
- allocate_subconfig(Name,SubKeys,Rest,true);
- _Error ->
- allocate_subconfig(Name,SubKeys,Rest,Found)
- end;
-allocate_subconfig(_Name,_SubKeys,[],true) ->
+
+associate('_UNDEF',_Key,_Configs) ->
ok;
-allocate_subconfig(_Name,SubKeys,[],false) ->
- {error,{not_available,SubKeys}}.
+associate(Name,{Key,SubKeys},Configs) when is_atom(Key), is_list(SubKeys) ->
+ associate_int(Name,Configs,"true");
+associate(Name,_Key,Configs) ->
+ associate_int(Name,Configs,os:getenv("COMMON_TEST_ALIAS_TOP")).
+
+associate_int(Name,Configs,"true") ->
+ lists:map(fun({K,_Config}) ->
+ Cs = ets:match_object(
+ ?attr_table,
+ #ct_conf{key=element(1,K),
+ name='_UNDEF',_='_'}),
+ [ets:insert(?attr_table,C#ct_conf{name=Name})
+ || C <- Cs]
+ end,Configs);
+associate_int(Name,Configs,_) ->
+ lists:map(fun({K,Config}) ->
+ Key = if is_tuple(K) -> element(1,K);
+ is_atom(K) -> K
+ end,
+
+ Cs = ets:match_object(
+ ?attr_table,
+ #ct_conf{key=Key,
+ name='_UNDEF',_='_'}),
+ [ets:insert(?attr_table,C#ct_conf{name=Name,
+ value=Config})
+ || C <- Cs]
+ end,Configs).
+
+
delete_config(Default) ->
ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}),
ok.
-require(Key) when is_atom(Key) ->
- require({Key,[]});
-require({Key,SubKeys}) when is_atom(Key) ->
- allocate('_UNDEF',Key,to_list(SubKeys));
+require(Key) when is_atom(Key); is_tuple(Key) ->
+ allocate('_UNDEF',Key);
require(Key) ->
{error,{invalid,Key}}.
-require(Name,Key) when is_atom(Key) ->
- require(Name,{Key,[]});
-require(Name,{Key,SubKeys}) when is_atom(Name), is_atom(Key) ->
- call({require,Name,Key,to_list(SubKeys)});
+require(Name,Key) when is_atom(Name),is_atom(Key) orelse is_tuple(Key) ->
+ call({require,Name,Key});
require(Name,Keys) ->
{error,{invalid,{Name,Keys}}}.
-to_list(X) when is_list(X) -> X;
-to_list(X) -> [X].
-
-do_require(Name,Key,SubKeys) when is_list(SubKeys) ->
+do_require(Name,Key) ->
case get_key_from_name(Name) of
{error,_} ->
- allocate(Name,Key,SubKeys);
+ allocate(Name,Key);
{ok,Key} ->
%% already allocated - check that it has all required subkeys
- Vals = [Val || {_Ref,Val} <- lookup_name(Name)],
- case get_subconfig(SubKeys,Vals) of
- {ok,_SubMapped} ->
- ok;
- Error ->
- Error
+ R = make_ref(),
+ case get_config(Key,R,[]) of
+ R ->
+ {error,{not_available,Key}};
+ {error,_} = Error ->
+ Error;
+ _Error ->
+ ok
end;
{ok,OtherKey} ->
{error,{name_in_use,Name,OtherKey}}
diff --git a/lib/common_test/src/ct_ftp.erl b/lib/common_test/src/ct_ftp.erl
index 5db73066a3..d1d511f137 100644
--- a/lib/common_test/src/ct_ftp.erl
+++ b/lib/common_test/src/ct_ftp.erl
@@ -66,6 +66,7 @@
%%% {unix,[{ftp,IpAddr},
%%% {username,Username},
%%% {password,Password}]}.
+%%% @see ct:require/3
put(KeyOrName,LocalFile,RemoteFile) ->
Fun = fun(Ftp) -> send(Ftp,LocalFile,RemoteFile) end,
open_and_do(KeyOrName,Fun).
@@ -85,6 +86,7 @@ put(KeyOrName,LocalFile,RemoteFile) ->
%%%
%%% The config file must be as for put/3.
%%% @see put/3
+%%% @see ct:require/3
get(KeyOrName,RemoteFile,LocalFile) ->
Fun = fun(Ftp) -> recv(Ftp,RemoteFile,LocalFile) end,
open_and_do(KeyOrName,Fun).
@@ -105,6 +107,10 @@ get(KeyOrName,RemoteFile,LocalFile) ->
%%% simply use Key
, the configuration variable name, to
%%% specify the target. Note that a connection that has no associated target
%%% name can only be closed with the handle value.
+%%%
+%%% See ct:require/3 for how to create a new Name
+%%%
+%%% @see ct:require/3
open(KeyOrName) ->
case ct_util:get_key_from_name(KeyOrName) of
{ok,node} ->
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index d9c4a962dc..8d2e25b946 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -408,6 +408,7 @@ open(Options) ->
%% server. It is not used for any other purposes during the lifetime
%% of the connection.
%%
+%% @see ct:require/3
%% @end
%%----------------------------------------------------------------------
open(KeyOrName, ExtraOpts) ->
diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl
index aebb28bc42..21553f5551 100644
--- a/lib/common_test/src/ct_ssh.erl
+++ b/lib/common_test/src/ct_ssh.erl
@@ -136,7 +136,8 @@ connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) ->
%%% associated with Name
. If Key
is
%%% used, the returned handle must be used for subsequent calls
%%% (multiple connections may be opened using the config
-%%% data specified by Key
).
+%%% data specified by Key
). See ct:require/3
+%%% for how to create a new Name
%%%
%%% ConnType
will always override the type
%%% specified in the address tuple in the configuration data (and
@@ -152,6 +153,8 @@ connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) ->
%%% The extra options will override any existing options with the
%%% same key in the config data. For details on valid SSH
%%% options, see the documentation for the OTP ssh application.
+%%%
+%%% @see ct:require/3
connect(KeyOrName, ConnType, ExtraOpts) ->
case ct:get_config(KeyOrName) of
undefined ->
diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl
index f4a551e3ff..e37a657617 100644
--- a/lib/common_test/src/ct_telnet.erl
+++ b/lib/common_test/src/ct_telnet.erl
@@ -155,6 +155,8 @@ open(KeyOrName,ConnType,TargetMod) ->
%%% TargetMod
is a module which exports the functions
%%% connect(Ip,Port,KeepAlive,Extra)
and get_prompt_regexp()
%%% for the given TargetType
(e.g. unix_telnet
).
+%%%
+%%% @see ct:require/2
open(KeyOrName,ConnType,TargetMod,Extra) ->
case ct:get_config({KeyOrName,ConnType}) of
undefined ->
diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl
index 18218bee47..83b8c00458 100644
--- a/lib/common_test/test/ct_config_SUITE.erl
+++ b/lib/common_test/test/ct_config_SUITE.erl
@@ -88,7 +88,8 @@ require(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
run_test(config_static_SUITE,
Config,
- {config, filename:join(DataDir, "config/config.txt")},
+ [{config, filename:join(DataDir, "config/shadow.txt")},
+ {config, filename:join(DataDir, "config/config.txt")}],
["config_static_SUITE"]).
install_config(Config) when is_list(Config) ->
@@ -106,7 +107,8 @@ userconfig_static(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
run_test(config_static_SUITE,
Config,
- {userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}},
+ [{userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}},
+ {config, filename:join(DataDir, "config/shadow.txt")}],
["config_static_SUITE"]).
userconfig_dynamic(Config) when is_list(Config) ->
@@ -121,7 +123,8 @@ testspec_legacy(Config) when is_list(Config) ->
make_spec(DataDir, ConfigDir,
"spec_legacy.spec",
[config_static_SUITE],
- [{config, filename:join(DataDir, "config/config.txt")}]),
+ [{config, filename:join(DataDir, "config/shadow.txt")},
+ {config, filename:join(DataDir, "config/config.txt")}]),
run_test(config_static_SUITE,
Config,
{spec, filename:join(ConfigDir, "spec_legacy.spec")},
@@ -134,7 +137,8 @@ testspec_static(Config) when is_list(Config) ->
make_spec(DataDir, ConfigDir,
"spec_static.spec",
[config_static_SUITE],
- [{userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}}]),
+ [{userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}},
+ {config, filename:join(DataDir, "config/shadow.txt")}]),
run_test(config_static_SUITE,
Config,
{spec, filename:join(ConfigDir, "spec_static.spec")},
@@ -179,13 +183,15 @@ run_test(Name, Config, CTConfig, SuiteNames)->
ExpEvents = events_to_check(Name),
ok = ct_test_support:verify_events(ExpEvents, TestEvents, Config).
-setup_env(Test, Config, CTConfig) ->
+setup_env(Test, Config, CTConfig) when is_list(CTConfig) ->
Opts0 = ct_test_support:get_opts(Config),
Level = ?config(trace_level, Config),
EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
- Opts = Opts0 ++ [Test,{event_handler,{?eh,EvHArgs}}, CTConfig],
+ Opts = Opts0 ++ [Test,{event_handler,{?eh,EvHArgs}} | CTConfig],
ERPid = ct_test_support:start_event_receiver(Config),
- {Opts,ERPid}.
+ {Opts,ERPid};
+setup_env(Test, Config, CTConfig) ->
+ setup_env(Test, Config, [CTConfig]).
reformat_events(Events, EH) ->
ct_test_support:reformat(Events, EH).
@@ -202,40 +208,49 @@ events_to_check(_, 0) ->
events_to_check(Test, N) ->
expected_events(Test) ++ events_to_check(Test, N-1).
+-define(ok(Name,Suite,Stat),{?eh,tc_start,{Suite,Name}},
+ {?eh,tc_done,{Suite,Name,ok}},
+ {?eh,test_stats,Stat}).
+-define(nok(Name,Suite,Reason,Stat),{?eh,tc_start,{Suite,Name}},
+ {?eh,tc_done,{Suite,Name,Reason}},
+ {?eh,test_stats,Stat}).
+
+-define(sok(Name,Stat),?ok(Name,config_static_SUITE,Stat)).
+-define(snok(Name,Reason,Stat),?nok(Name,config_static_SUITE,Reason,Stat)).
+
+-define(dok(Name,Stat),?ok(Name,config_dynamic_SUITE,Stat)).
+-define(dnok(Name,Reason,Stat),?nok(Name,config_dynamic_SUITE,Reason,Stat)).
+
expected_events(config_static_SUITE)->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
- {?eh,start_info,{1,1,8}},
+ {?eh,start_info,{1,1,'_'}},
{?eh,tc_start,{config_static_SUITE,init_per_suite}},
{?eh,tc_done,{config_static_SUITE,init_per_suite,ok}},
- {?eh,tc_start,{config_static_SUITE,test_get_config_simple}},
- {?eh,tc_done,{config_static_SUITE,test_get_config_simple,ok}},
- {?eh,test_stats,{1,0,{0,0}}},
- {?eh,tc_start,{config_static_SUITE,test_get_config_nested}},
- {?eh,tc_done,{config_static_SUITE,test_get_config_nested,ok}},
- {?eh,test_stats,{2,0,{0,0}}},
- {?eh,tc_start,{config_static_SUITE,test_default_suitewide}},
- {?eh,tc_done,{config_static_SUITE,test_default_suitewide,ok}},
- {?eh,test_stats,{3,0,{0,0}}},
- {?eh,tc_start,{config_static_SUITE,test_config_name_already_in_use1}},
- {?eh,tc_done,
- {config_static_SUITE,test_config_name_already_in_use1,{skipped,{config_name_already_in_use,[x1]}}}},
- {?eh,test_stats,{3,0,{1,0}}},
- {?eh,tc_start,{config_static_SUITE,test_default_tclocal}},
- {?eh,tc_done,{config_static_SUITE,test_default_tclocal,ok}},
- {?eh,test_stats,{4,0,{1,0}}},
- {?eh,tc_start,{config_static_SUITE,test_config_name_already_in_use2}},
- {?eh,tc_done,
- {config_static_SUITE,test_config_name_already_in_use2,
- {skipped,{config_name_already_in_use,[alias,x1]}}}},
- {?eh,test_stats,{4,0,{2,0}}},
- {?eh,tc_start,{config_static_SUITE,test_alias_tclocal}},
- {?eh,tc_done,{config_static_SUITE,test_alias_tclocal,ok}},
- {?eh,test_stats,{5,0,{2,0}}},
- {?eh,tc_start,{config_static_SUITE,test_get_config_undefined}},
- {?eh,tc_done,{config_static_SUITE,test_get_config_undefined,ok}},
- {?eh,test_stats,{6,0,{2,0}}},
+ ?sok(test_get_config_simple,{1,0,{0,0}}),
+ ?sok(test_get_config_nested,{2,0,{0,0}}),
+ ?sok(test_get_config_deep_nested,{3,0,{0,0}}),
+ ?sok(test_default_suitewide,{4,0,{0,0}}),
+ ?snok(test_config_name_already_in_use1,
+ {skipped,{config_name_already_in_use,[x1]}},{4,0,{1,0}}),
+ ?sok(test_default_tclocal,{5,0,{1,0}}),
+ ?snok(test_config_name_already_in_use2,
+ {skipped,{config_name_already_in_use,[alias,x1]}},{5,0,{2,0}}),
+ ?sok(test_alias_tclocal,{6,0,{2,0}}),
+ ?sok(test_get_config_undefined,{7,0,{2,0}}),
+ ?sok(test_require_subvals,{8,0,{2,0}}),
+ ?snok(test_require_subvals2,
+ {skipped,{require_failed,
+ {not_available,{gen_cfg,[a,b,c,d]}}}},{8,0,{2,1}}),
+ ?sok(test_require_deep_config,{9,0,{2,1}}),
+ ?sok(test_shadow_all,{10,0,{2,1}}),
+ ?sok(test_element,{11,0,{2,1}}),
+ ?sok(test_shadow_all_element,{12,0,{2,1}}),
+ ?sok(test_internal_deep,{13,0,{2,1}}),
+ ?sok(test_alias_tclocal_nested,{14,0,{2,1}}),
+ ?sok(test_alias_tclocal_nested_backward_compat,{15,0,{2,1}}),
+ ?sok(test_alias_tclocal_nested_backward_compat_subvals,{16,0,{2,1}}),
{?eh,tc_start,{config_static_SUITE,end_per_suite}},
{?eh,tc_done,{config_static_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
@@ -246,29 +261,14 @@ expected_events(config_dynamic_SUITE)->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
- {?eh,start_info,{1,1,5}},
+ {?eh,start_info,{1,1,'_'}},
{?eh,tc_start,{config_dynamic_SUITE,init_per_suite}},
{?eh,tc_done,{config_dynamic_SUITE,init_per_suite,ok}},
- {?eh,tc_start,{config_dynamic_SUITE,test_get_known_variable}},
- {?eh,tc_done,
- {config_dynamic_SUITE,test_get_known_variable,ok}},
- {?eh,test_stats,{1,0,{0,0}}},
- {?eh,tc_start,{config_dynamic_SUITE,test_localtime_update}},
- {?eh,tc_done,{config_dynamic_SUITE,test_localtime_update,ok}},
- {?eh,test_stats,{2,0,{0,0}}},
- {?eh,tc_start,{config_dynamic_SUITE,test_server_pid}},
- {?eh,tc_done,{config_dynamic_SUITE,test_server_pid,ok}},
- {?eh,test_stats,{3,0,{0,0}}},
- {?eh,tc_start,
- {config_dynamic_SUITE,test_disappearable_variable}},
- {?eh,tc_done,
- {config_dynamic_SUITE,test_disappearable_variable,ok}},
- {?eh,test_stats,{4,0,{0,0}}},
- {?eh,tc_start,
- {config_dynamic_SUITE,test_disappearable_variable_alias}},
- {?eh,tc_done,
- {config_dynamic_SUITE,test_disappearable_variable_alias,ok}},
- {?eh,test_stats,{5,0,{0,0}}},
+ ?dok(test_get_known_variable,{1,0,{0,0}}),
+ ?dok(test_localtime_update,{2,0,{0,0}}),
+ ?dok(test_server_pid,{3,0,{0,0}}),
+ ?dok(test_disappearable_variable,{4,0,{0,0}}),
+ ?dok(test_disappearable_variable_alias,{5,0,{0,0}}),
{?eh,tc_start,{config_dynamic_SUITE,end_per_suite}},
{?eh,tc_done,{config_dynamic_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/config.txt b/lib/common_test/test/ct_config_SUITE_data/config/config.txt
index fcbffcd7f3..e4bcc5ba6b 100644
--- a/lib/common_test/test/ct_config_SUITE_data/config/config.txt
+++ b/lib/common_test/test/ct_config_SUITE_data/config/config.txt
@@ -2,7 +2,8 @@
{gen_cfg,
[
{a,a_value},
- {b,b_value}
+ {b,b_value},
+ {c,[{d,d_value}]}
]}.
{gen_cfg2,
[
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/config.xml b/lib/common_test/test/ct_config_SUITE_data/config/config.xml
index 0a3e5f2e31..8eeff1482f 100644
--- a/lib/common_test/test/ct_config_SUITE_data/config/config.xml
+++ b/lib/common_test/test/ct_config_SUITE_data/config/config.xml
@@ -3,6 +3,7 @@
a_value
b_value
+ d_value
"Hello, world!"
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/shadow.txt b/lib/common_test/test/ct_config_SUITE_data/config/shadow.txt
new file mode 100644
index 0000000000..865bf9255a
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/shadow.txt
@@ -0,0 +1,12 @@
+{x, suite}.
+{gen_cfg3,
+ [
+ {l,
+ [
+ {m,
+ [
+ {n, "n"},
+ {o, 'o'}
+ ]}
+ ]}
+ ]}.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
index 8751a2e8f3..d7119d7fde 100644
--- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
@@ -46,7 +46,7 @@ suite() ->
{require, gen_cfg3},
{require, alias, gen_cfg},
%% x1 default value
- {x1, {x,suite}}
+ {default_config, x1, {x,suite}}
].
init_per_suite(Config) ->
@@ -55,14 +55,24 @@ init_per_suite(Config) ->
end_per_suite(_) ->
ok.
-all() -> [test_get_config_simple, test_get_config_nested, test_default_suitewide,
+all() -> [test_get_config_simple, test_get_config_nested,
+ test_get_config_deep_nested, test_default_suitewide,
test_config_name_already_in_use1, test_default_tclocal,
test_config_name_already_in_use2, test_alias_tclocal,
- test_get_config_undefined].
-
-init_per_testcase(_, Config) ->
+ test_get_config_undefined,
+ test_require_subvals,test_require_subvals2,test_require_deep_config,
+ test_shadow_all,test_element,test_shadow_all_element,
+ test_internal_deep, test_alias_tclocal_nested,
+ test_alias_tclocal_nested_backward_compat,
+ test_alias_tclocal_nested_backward_compat_subvals
+].
+
+init_per_testcase(_,Config) ->
Config.
+end_per_testcase(test_alias_tclocal_nested_backward_compat, _) ->
+ os:putenv("COMMON_TEST_ALIAS_TOP",""),
+ ok;
end_per_testcase(_, _) ->
ok.
@@ -76,6 +86,11 @@ test_get_config_nested(_)->
a_value = ct:get_config({gen_cfg, a}),
ok.
+%% test getting a deep nested value
+test_get_config_deep_nested(_)->
+ d_value = ct:get_config({gen_cfg, c, d}),
+ ok.
+
%% test suite-wide default value
test_default_suitewide(_)->
suite = ct:get_config(x1),
@@ -112,12 +127,73 @@ test_config_name_already_in_use2(_) ->
%% test aliases
test_alias_tclocal() ->
[{require,newalias,gen_cfg}].
-test_alias_tclocal(_) ->
- A = [{a,a_value},{b,b_value}] = ct:get_config(newalias),
+test_alias_tclocal(C) when is_list(C) ->
+ test_alias_tclocal(newalias);
+test_alias_tclocal(Alias) when is_atom(Alias) ->
+ A = [{a,a_value},{b,b_value},{c,[{d,d_value}]}] = ct:get_config(Alias),
A = ct:get_config(gen_cfg),
+ B = b_value = ct:get_config({Alias,b}),
+ B = ct:get_config({gen_cfg,b}),
+ ok.
+
+%% test nested aliases
+test_alias_tclocal_nested() ->
+ [{require,newalias2,{gen_cfg,c}}].
+test_alias_tclocal_nested(_) ->
+ A = [{d,d_value}] = ct:get_config(newalias2),
+ A = ct:get_config({gen_cfg,c}),
+ B = d_value = ct:get_config({newalias2,d}),
+ B = ct:get_config({gen_cfg,c,d}),
ok.
+%% test nested aliases backward compat option
+test_alias_tclocal_nested_backward_compat() ->
+ os:putenv("COMMON_TEST_ALIAS_TOP","true"),
+ [{require,newalias3,{gen_cfg,c}}].
+test_alias_tclocal_nested_backward_compat(_) ->
+ test_alias_tclocal(newalias3).
+
+%% test nested aliases backward compat option
+test_alias_tclocal_nested_backward_compat_subvals() ->
+ [{require,newalias4,{gen_cfg,[c]}}].
+test_alias_tclocal_nested_backward_compat_subvals(_) ->
+ test_alias_tclocal(newalias4).
+
%% test for getting undefined variables
test_get_config_undefined(_) ->
undefined = ct:get_config(y1),
ok.
+
+test_require_subvals() ->
+ [{require, {gen_cfg,[a,b,c]}}].
+test_require_subvals(_) ->
+ ok.
+
+test_require_subvals2() ->
+ [{require, {gen_cfg,[a,b,c,d]}}].
+test_require_subvals2(_) ->
+ ct:fail("Test should've been skipped, you shouldn't see this!"),
+ ok.
+
+test_require_deep_config() ->
+ [{require, {gen_cfg3, m, n}}].
+test_require_deep_config(_) ->
+ ok.
+
+
+test_shadow_all(_) ->
+ ["n","N"] = ct:get_config({gen_cfg3,l, m, n}, [], [all]).
+
+test_element(_) ->
+ {{gen_cfg3,l, m, n},"n"} = ct:get_config({gen_cfg3,l, m, n}, [], [element]).
+
+test_shadow_all_element(_) ->
+ [{{gen_cfg3,l, m, n},"n"},{{gen_cfg3,l, m, n},"N"}] =
+ ct:get_config({gen_cfg3,l, m, n}, [], [all,element]).
+
+%% The tests below are needed to verify that things like ct:telnet can use
+%% nested configs
+test_internal_deep(_) ->
+ "n" = ct:get_config({{gen_cfg3,l,m},n}),
+ a_value = ct:get_config({{gen_cfg},a}),
+ undefined = ct:get_config({{gen_cfg3,l,m},p}).
--
cgit v1.2.3
From a62b355f2122385564f5ea2080d9705e2ef45efa Mon Sep 17 00:00:00 2001
From: Lukas Larsson
Date: Mon, 20 Aug 2012 11:29:34 +0200
Subject: Remove config option from common_test args
This is needed because it is no longer ignored by common_test
---
lib/test_server/src/ts_run.erl | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
(limited to 'lib')
diff --git a/lib/test_server/src/ts_run.erl b/lib/test_server/src/ts_run.erl
index a61028e4bc..2e8c092400 100644
--- a/lib/test_server/src/ts_run.erl
+++ b/lib/test_server/src/ts_run.erl
@@ -334,9 +334,9 @@ path_separator() ->
end.
-make_common_test_args(Args0, Options, _Vars) ->
+make_common_test_args(Args0, Options0, _Vars) ->
Trace =
- case lists:keysearch(trace,1,Options) of
+ case lists:keysearch(trace,1,Options0) of
{value,{trace,TI}} when is_tuple(TI); is_tuple(hd(TI)) ->
ok = file:write_file(?tracefile,io_lib:format("~p.~n",[TI])),
[{ct_trace,?tracefile}];
@@ -348,7 +348,7 @@ make_common_test_args(Args0, Options, _Vars) ->
[]
end,
Cover =
- case lists:keysearch(cover,1,Options) of
+ case lists:keysearch(cover,1,Options0) of
{value,{cover, App, none, _Analyse}} ->
io:format("No cover file found for ~p~n",[App]),
[];
@@ -358,7 +358,7 @@ make_common_test_args(Args0, Options, _Vars) ->
[]
end,
- Logdir = case lists:keysearch(logdir, 1, Options) of
+ Logdir = case lists:keysearch(logdir, 1, Options0) of
{value,{logdir, _}} ->
[];
false ->
@@ -373,15 +373,16 @@ make_common_test_args(Args0, Options, _Vars) ->
{scale_timetraps, true}]
end,
- ConfigPath = case {os:getenv("TEST_CONFIG_PATH"),
- lists:keysearch(config, 1, Options)} of
- {false,{value, {config, Path}}} ->
- Path;
- {false,false} ->
- "../test_server";
- {Path,_} ->
- Path
- end,
+ {ConfigPath,
+ Options} = case {os:getenv("TEST_CONFIG_PATH"),
+ lists:keysearch(config, 1, Options0)} of
+ {_,{value, {config, Path}}} ->
+ {Path,lists:keydelete(config, 1, Options0)};
+ {false,false} ->
+ {"../test_server",Options0};
+ {Path,_} ->
+ {Path,Options0}
+ end,
ConfigFiles = [{config,[filename:join(ConfigPath,File)
|| File <- get_config_files()]}],
io_lib:format("~100000p",[Args0++Trace++Cover++Logdir++
--
cgit v1.2.3
From a95a238719369feb1002daeacf4e1485439a38d2 Mon Sep 17 00:00:00 2001
From: Lukas Larsson
Date: Tue, 21 Aug 2012 17:44:49 +0200
Subject: Add more cross reference links to ct docs
---
lib/common_test/doc/src/common_test_app.xml | 53 +++++++++++-----------
lib/common_test/doc/src/config_file_chapter.xml | 17 +++----
lib/common_test/doc/src/cover_chapter.xml | 2 +-
lib/common_test/doc/src/ct_hooks_chapter.xml | 4 +-
lib/common_test/doc/src/ct_run.xml | 2 +-
lib/common_test/doc/src/event_handler_chapter.xml | 6 +--
.../doc/src/getting_started_chapter.xml | 2 +-
lib/common_test/doc/src/run_test_chapter.xml | 23 +++++-----
lib/common_test/doc/src/write_test_chapter.xml | 20 ++++----
lib/common_test/src/ct_ftp.erl | 8 ++--
lib/common_test/src/ct_netconfc.erl | 2 +-
lib/common_test/src/ct_ssh.erl | 6 +--
12 files changed, 75 insertions(+), 70 deletions(-)
(limited to 'lib')
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index addeed002a..a0fa45c71f 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -99,11 +99,11 @@
be executed by Common Test. A test case is represented by an atom,
the name of the test case function. A test case group is
represented by a group tuple, where GroupName,
- an atom, is the name of the group (defined in groups/0).
+ an atom, is the name of the group (defined in groups/0).
Execution properties for groups may also be specified, both
for a top level group and for any of its sub-groups.
Group execution properties specified here, will override
- properties in the group definition (see groups/0).
+ properties in the group definition (see groups/0).
(With value default, the group definition properties
will be used).
@@ -184,8 +184,8 @@
test cases in the suite).
The timetrap tag sets the maximum time each
- test case is allowed to execute (including init_per_testcase/2
- and end_per_testcase/2). If the timetrap time is
+ test case is allowed to execute (including init_per_testcase/2
+ and end_per_testcase/2). If the timetrap time is
exceeded, the test case fails with reason
timetrap_timeout. A TimeFunc function can be used to
set a new timetrap by returning a TimeVal. It may also be
@@ -201,11 +201,11 @@
in any of the configuration files, all test cases are skipped. For more
information about the 'require' functionality, see the
reference manual for the function
- ct:require/[1,2].
+ ct:require/1/2.
With userdata, it is possible for the user to
specify arbitrary test suite related information which can be
- read by calling ct:userdata/2.
+ read by calling ct:userdata/2.
The ct_hooks tag specifies which
Common Test Hooks
@@ -264,7 +264,7 @@
This function is called as the last test case in the
suite. It is meant to be used for cleaning up after
- init_per_suite/1.
+ init_per_suite/1.
For information on save_config, please see
Dependencies
between Test Cases and Suites in the User's Guide.
@@ -309,13 +309,14 @@
This is the test case group info function. It is supposed to
return a list of tagged tuples that specify various properties
related to the execution of a test case group (i.e. its test cases
- and sub-groups). Properties set by groups/1 override
+ and sub-groups). Properties set by
+ group/1 override
properties with the same key that have been previously set by
- suite/0.
+ suite/0.
The timetrap tag sets the maximum time each
- test case is allowed to execute (including init_per_testcase/2
- and end_per_testcase/2). If the timetrap time is
+ test case is allowed to execute (including init_per_testcase/2
+ and end_per_testcase/2). If the timetrap time is
exceeded, the test case fails with reason
timetrap_timeout. A TimeFunc function can be used to
set a new timetrap by returning a TimeVal. It may also be
@@ -330,11 +331,11 @@
in any of the configuration files, all test cases in this group are skipped.
For more information about the 'require' functionality, see the
reference manual for the function
- ct:require/[1,2].
+ ct:require/1/2.
With userdata, it is possible for the user to
specify arbitrary test case group related information which can be
- read by calling ct:userdata/2.
+ read by calling ct:userdata/2.
The ct_hooks tag specifies which
Common Test Hooks
@@ -367,7 +368,7 @@
test case group. It typically contains initializations which are
common for all test cases and sub-groups in the group, and which
shall only be performed once. GroupName is the name of the
- group, as specified in the group definition (see groups/0). The
+ group, as specified in the group definition (see groups/0). The
Config parameter is the configuration data which can be modified
here. The return value of this function is given as Config
to all test cases and sub-groups in the group. If {skip,Reason}
@@ -396,10 +397,10 @@
OPTIONAL
This function is called after the execution of a test case group is finished.
- It is meant to be used for cleaning up after init_per_group/2.
+ It is meant to be used for cleaning up after init_per_group/2.
By means of {return_group_result,Status}, it is possible to return a
status value for a nested sub-group. The status can be retrieved in
- end_per_group/2 for the group on the level above. The status will also
+ end_per_group/2 for the group on the level above. The status will also
be used by Common Test for deciding if execution of a group should proceed in
case the property sequence or repeat_until_* is set.
@@ -450,7 +451,7 @@
OPTIONAL
This function is called after each test case, and can be used
- to clean up after init_per_testcase/2 and the test case.
+ to clean up after init_per_testcase/2 and the test case.
Any return value (besides {fail,Reason} and {save_config,SaveConfig})
is ignored. By returning {fail,Reason}, TestCase will be marked as
failed (even though it was actually successful in the sense that it returned
@@ -492,15 +493,15 @@
This is the test case info function. It is supposed to
return a list of tagged tuples that specify various properties
related to the execution of this particular test case.
- Properties set by Testcase/0 override
+ Properties set by Testcase/0 override
properties that have been previously set for the test case
- by group/1 or suite/0.
+ by group/1 or suite/0.
The timetrap tag sets the maximum time the
test case is allowed to execute. If the timetrap time is
exceeded, the test case fails with reason
- timetrap_timeout. init_per_testcase/2
- and end_per_testcase/2 are included in the
+ timetrap_timeout. init_per_testcase/2
+ and end_per_testcase/2 are included in the
timetrap time. A TimeFunc function can be used to
set a new timetrap by returning a TimeVal. It may also be
used to trigger a timetrap timeout by, at some point, returning a
@@ -514,15 +515,15 @@
configuration files, the test case is skipped. For more
information about the 'require' functionality, see the
reference manual for the function
- ct:require/[1,2].
+ ct:require/1/2.
If timetrap and/or require is not set, the
- default values specified by suite/0 (or
- group/1) will be used.
+ default values specified by suite/0 (or
+ group/1) will be used.
With userdata, it is possible for the user to
specify arbitrary test case related information which can be
- read by calling ct:userdata/3.
+ read by calling ct:userdata/3.
Other tuples than the ones defined will simply be ignored.
@@ -550,7 +551,7 @@
This is the implementation of a test case. Here you must
call the functions you want to test, and do whatever you
need to check the result. If something fails, make sure the
- function causes a runtime error, or call ct:fail/1/2
+ function causes a runtime error, or call ct:fail/1/2
(which also causes the test case process to terminate).
Elements from the Config list can e.g. be read
diff --git a/lib/common_test/doc/src/config_file_chapter.xml b/lib/common_test/doc/src/config_file_chapter.xml
index 706d0d5f4e..e843ed3ba4 100644
--- a/lib/common_test/doc/src/config_file_chapter.xml
+++ b/lib/common_test/doc/src/config_file_chapter.xml
@@ -78,7 +78,7 @@
test is skipped (unless a default value has been specified, see the
test case info
function chapter for details). There is also a function
- ct:require/[1,2] which can be called from a test case
+ ct:require/1/2 which can be called from a test case
in order to check if a specific variable is available. The return
value from this function must be checked explicitly and appropriate
action be taken depending on the result (e.g. to skip the test case
@@ -88,7 +88,7 @@
info-list should look like this:
{require,CfgVarName} or {require,AliasName,CfgVarName}.
The arguments AliasName and CfgVarName are the same as the
- arguments to ct:require/[1,2] which are described in the
+ arguments to ct:require/1/2 which are described in the
reference manual for ct.
AliasName becomes an alias for the configuration variable,
and can be used as reference to the configuration data value.
@@ -101,7 +101,8 @@
(or test case) and improve readability.
To read the value of a config variable, use the function
- get_config/[1,2,3] which is also described in the reference
+ get_config/1/2/3
+ which is also described in the reference
manual for ct.
Example:
@@ -118,7 +119,7 @@
Using configuration variables defined in multiple files
If a configuration variable is defined in multiple files and you
- want to access all possible values, you may use the ct:get_config/3
+ want to access all possible values, you may use the ct:get_config/3
function and specify all in the options list. The values will then
be returned in a list and the order of the elements corresponds to the order
that the config files were specified at startup. Please see
@@ -130,7 +131,7 @@
It is possible to encrypt configuration files containing sensitive data
if these files must be stored in open and shared directories.
- Call ct:encrypt_config_file/[2,3] to have Common Test encrypt a
+
Call ct:encrypt_config_file/2/3 to have Common Test encrypt a
specified file using the DES3 function in the OTP crypto application.
The encrypted file can then be used as a regular configuration file,
in combination with other encrypted files or normal text files. The key
@@ -139,7 +140,7 @@
decrypt_file flag/option, or a key file in a predefined location.
Common Test also provides decryption functions,
- ct:decrypt_config_file/[2,3], for recreating the original text
+ ct:decrypt_config_file/2/3, for recreating the original text
files.
Please see the ct reference manual for
@@ -149,8 +150,8 @@
Opening connections by using configuration data
There are two different methods for opening a connection
- by means of the support functions in e.g. ct_ssh, ct_ftp,
- and ct_telnet:
+ by means of the support functions in e.g. ct_ssh, ct_ftp,
+ and ct_telnet:
- Using a configuration target name (an alias) as reference.
- Using the configuration variable as reference.
diff --git a/lib/common_test/doc/src/cover_chapter.xml b/lib/common_test/doc/src/cover_chapter.xml
index b7162cb542..fc609ee137 100644
--- a/lib/common_test/doc/src/cover_chapter.xml
+++ b/lib/common_test/doc/src/cover_chapter.xml
@@ -100,7 +100,7 @@
$ ct_run -dir $TESTOBJS/db -cover $TESTOBJS/db/config/db.coverspec
You may also pass the cover specification file name in a
- call to ct:run_test/1, by adding a {cover,CoverSpec}
+ call to ct:run_test/1, by adding a {cover,CoverSpec}
tuple to the Opts argument. Also, you can of course
enable code coverage in your test specifications (read
more in the chapter about
diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml
index 014507c886..c938851e0e 100644
--- a/lib/common_test/doc/src/ct_hooks_chapter.xml
+++ b/lib/common_test/doc/src/ct_hooks_chapter.xml
@@ -192,12 +192,12 @@
External configuration data and Logging
It's possible in the CTH to read configuration data values
- by calling ct:get_config/1/2/3 (as explained in the
+ by calling ct:get_config/1/2/3 (as explained in the
External configuration data
chapter). The config variables in question must, as always, first have been
required by means of a suite-, group-, or test case info function,
- or the ct:require/1/2 function. Note that the latter can also be used
+ or the ct:require/1/2 function. Note that the latter can also be used
in CT hook functions.
The CT hook functions may call any of the logging functions available
in the ct interface to print information to the log files, or to
diff --git a/lib/common_test/doc/src/ct_run.xml b/lib/common_test/doc/src/ct_run.xml
index 078b9b958c..434cb7da7c 100644
--- a/lib/common_test/doc/src/ct_run.xml
+++ b/lib/common_test/doc/src/ct_run.xml
@@ -46,7 +46,7 @@
particular mode.
There is an interface function that corresponds to this program,
- called ct:run_test/1, for starting Common Test from the Erlang
+ called ct:run_test/1, for starting Common Test from the Erlang
shell (or an Erlang program). Please see the ct man page for
details.
diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml
index 2f796b91ab..b95a18e47e 100644
--- a/lib/common_test/doc/src/event_handler_chapter.xml
+++ b/lib/common_test/doc/src/event_handler_chapter.xml
@@ -64,7 +64,7 @@
Usage
Event handlers may be installed by means of an event_handler
- start flag (ct_run) or option (ct:run_test/1), where the
+ start flag (ct_run) or option (ct:run_test/1), where the
argument specifies the names of one or more event handler modules.
Example:
$ ct_run -suite test/my_SUITE -event_handler handlers/my_evh1
@@ -78,7 +78,7 @@
example).
An event_handler tuple in the argument Opts has the following
- definition (see also ct:run_test/1 in the reference manual):
+ definition (see also ct:run_test/1 in the reference manual):
{event_handler,EventHandlers}
@@ -308,7 +308,7 @@
manager can look like.
To ensure that printouts to standard out (or printouts made with
- ct:log/2/3 or ct:pal/2/3) get written to the test case log
+ ct:log/2/3 or ct:pal/2/3) get written to the test case log
file, and not to the Common Test framework log, you can syncronize
with the Common Test server by matching on the tc_start and tc_done
events. In the period between these events, all IO gets directed to the
diff --git a/lib/common_test/doc/src/getting_started_chapter.xml b/lib/common_test/doc/src/getting_started_chapter.xml
index 039578dd2e..891cbc49f3 100644
--- a/lib/common_test/doc/src/getting_started_chapter.xml
+++ b/lib/common_test/doc/src/getting_started_chapter.xml
@@ -90,7 +90,7 @@
As you can understand from the illustration above, Common Test requires
that a test case generates a runtime error to indicate failure (e.g.
by causing a bad match error or by calling exit/1, preferrably
- through the ct:fail/1,2 help function). A succesful execution is
+ through the ct:fail/1,2 help function). A succesful execution is
indicated by means of a normal return from the test case function.
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index 30486d3eec..c1f68becc3 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -242,12 +242,12 @@
Common Test provides an Erlang API for running tests. The main (and most
flexible) function for specifying and executing tests is called
- ct:run_test/1. This function takes the same start parameters as
+ ct:run_test/1. This function takes the same start parameters as
the ct_run program described above, only the flags are instead
given as options in a list of key-value tuples. E.g. a test specified
with ct_run like:
$ ct_run -suite ./my_SUITE -logdir ./results
- is with ct:run_test/1 specified as:
+ is with ct:run_test/1 specified as:
1> ct:run_test([{suite,"./my_SUITE"},{logdir,"./results"}]).
For detailed documentation, please see the ct manual page.
@@ -266,9 +266,9 @@
for trying out various operations during test suite development.
To invoke the interactive shell mode, you can start an Erlang shell
- manually and call ct:install/1 to install any configuration
+ manually and call ct:install/1 to install any configuration
data you might need (use [] as argument otherwise), then
- call ct:start_interactive/0 to start Common Test. If you use
+ call ct:start_interactive/0 to start Common Test. If you use
the ct_run program, you may start the Erlang shell and Common Test
in the same go by using the -shell and, optionally, the -config
and/or -userconfig flag. Examples:
@@ -287,7 +287,8 @@
If any functions using "required config data" (e.g. ct_telnet or
ct_ftp functions) are to be called from the erlang shell, config
- data must first be required with ct:require/[1,2]. This is
+ data must first be required with
+ ct:require/1/2. This is
equivalent to a require statement in the Test Suite Info
Function or in the
If you wish to exit the interactive mode (e.g. to start an
- automated test run with ct:run_test/1), call the function
- ct:stop_interactive/0. This shuts down the
+ automated test run with ct:run_test/1), call the function
+ ct:stop_interactive/0. This shuts down the
running ct application. Associations between
configuration names and data created with require are
- consequently deleted. ct:start_interactive/0 will get you
+ consequently deleted. ct:start_interactive/0 will get you
back into interactive mode, but the previous state is not restored.
@@ -326,7 +327,7 @@
Step by step execution of test cases with the Erlang Debugger
By means of ct_run -step [opts], or by passing the
- {step,Opts} option to ct:run_test/1, it is possible
+ {step,Opts} option to ct:run_test/1, it is possible
to get the Erlang Debugger started automatically and use its
graphical interface to investigate the state of the current test
case and to execute it step by step and/or set execution breakpoints.
@@ -586,7 +587,7 @@
ct_run. This forces Common Test to ignore unrecognizable terms.
Note that in this mode, Common Test is not able to check the specification
for errors as efficiently as if the scanner runs in default mode.
- If ct:run_test/1 is used for starting the tests, the relaxed scanner
+ If ct:run_test/1 is used for starting the tests, the relaxed scanner
mode is enabled by means of the tuple: {allow_user_terms,true}
@@ -943,7 +944,7 @@
The -silent_connections tag (or
silent_connections tagged tuple in the call to
- ct:run_test/1) overrides any settings in the test
+ ct:run_test/1) overrides any settings in the test
suite.
Note that in the current Common Test version, the
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index d545c9e432..1fae50577e 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -173,7 +173,7 @@
The end_per_testcase/2 function is called even after a
- test case terminates due to a call to ct:abort_current_testcase/1,
+ test case terminates due to a call to ct:abort_current_testcase/1,
or after a timetrap timeout. However, end_per_testcase
will then execute on a different process than the test case
function, and in this situation, end_per_testcase will
@@ -243,7 +243,8 @@
The test case function argument Config should not be
confused with the information that can be retrieved from
- configuration files (using ct:get_config/[1,2]). The Config argument
+ configuration files (using
+ ct:get_config/1/2). The Config argument
should be used for runtime configuration of the test suite and the
test cases, while configuration files should typically contain data
related to the SUT. These two types of configuration data are handled
@@ -302,7 +303,7 @@
-
Use this to specify arbitrary data related to the testcase. This
- data can be retrieved at any time using the ct:userdata/3
+ data can be retrieved at any time using the ct:userdata/3
utility function.
@@ -347,7 +348,8 @@
See the Config files
- chapter and the ct:require/[1,2] function in the
+ chapter and the
+ ct:require/1/2 function in the
ct reference manual for more information about
require.
@@ -824,7 +826,7 @@
Common Test to create one dedicated private directory per
test case and execution instead. This is accomplished by means of
the flag/option: create_priv_dir (to be used with the
- ct_run program, the ct:run_test/1 function, or
+ ct_run program, the ct:run_test/1 function, or
as test specification term). There are three possible values
for this option:
@@ -840,7 +842,7 @@
become very inefficient for test runs with many test cases and/or
repetitions. Therefore, in case the manual version is instead used, the
test case must tell Common Test to create priv_dir when it needs it.
- It does this by calling the function ct:make_priv_dir/0.
+ It does this by calling the function ct:make_priv_dir/0.
You should not depend on current working directory for
@@ -888,7 +890,7 @@
It is also possible to dynamically set/reset a timetrap during the
excution of a test case, or configuration function. This is done by calling
- ct:timetrap/1. This function cancels the current timetrap
+ ct:timetrap/1. This function cancels the current timetrap
and starts a new one (that stays active until timeout, or end of the
current function).
@@ -901,12 +903,12 @@
If a test case needs to suspend itself for a time that also gets
multipled by multiply_timetraps (and possibly also scaled up if
- scale_timetraps is enabled), the function ct:sleep/1
+ scale_timetraps is enabled), the function ct:sleep/1
may be used (instead of e.g. timer:sleep/1).
A function (fun/0 or MFA) may be specified as
timetrap value in the suite-, group- and test case info function, as
- well as argument to the ct:timetrap/1 function. Examples:
+ well as argument to the ct:timetrap/1 function. Examples:
{timetrap,{my_test_utils,timetrap,[?MODULE,system_start]}}
ct:timetrap(fun() -> my_timetrap(TestCaseName, Config) end)
diff --git a/lib/common_test/src/ct_ftp.erl b/lib/common_test/src/ct_ftp.erl
index d1d511f137..723715c986 100644
--- a/lib/common_test/src/ct_ftp.erl
+++ b/lib/common_test/src/ct_ftp.erl
@@ -66,7 +66,7 @@
%%% {unix,[{ftp,IpAddr},
%%% {username,Username},
%%% {password,Password}]}.
-%%% @see ct:require/3
+%%% @see ct:require/2
put(KeyOrName,LocalFile,RemoteFile) ->
Fun = fun(Ftp) -> send(Ftp,LocalFile,RemoteFile) end,
open_and_do(KeyOrName,Fun).
@@ -86,7 +86,7 @@ put(KeyOrName,LocalFile,RemoteFile) ->
%%%
%%% The config file must be as for put/3.
%%% @see put/3
-%%% @see ct:require/3
+%%% @see ct:require/2
get(KeyOrName,RemoteFile,LocalFile) ->
Fun = fun(Ftp) -> recv(Ftp,RemoteFile,LocalFile) end,
open_and_do(KeyOrName,Fun).
@@ -108,9 +108,9 @@ get(KeyOrName,RemoteFile,LocalFile) ->
%%% specify the target. Note that a connection that has no associated target
%%% name can only be closed with the handle value.
%%%
-%%% See ct:require/3 for how to create a new Name
+%%% See ct:require/2 for how to create a new Name
%%%
-%%% @see ct:require/3
+%%% @see ct:require/2
open(KeyOrName) ->
case ct_util:get_key_from_name(KeyOrName) of
{ok,node} ->
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index 8d2e25b946..a6b170f07e 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -408,7 +408,7 @@ open(Options) ->
%% server. It is not used for any other purposes during the lifetime
%% of the connection.
%%
-%% @see ct:require/3
+%% @see ct:require/2
%% @end
%%----------------------------------------------------------------------
open(KeyOrName, ExtraOpts) ->
diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl
index 21553f5551..81a25849db 100644
--- a/lib/common_test/src/ct_ssh.erl
+++ b/lib/common_test/src/ct_ssh.erl
@@ -133,10 +133,10 @@ connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) ->
%%% is used to identify the connection, this name may
%%% be used as connection reference for subsequent calls.
%%% It's only possible to have one open connection at a time
-%%% associated with Name
. If Key
is
+%%% associated with Name
. If Key
is
%%% used, the returned handle must be used for subsequent calls
%%% (multiple connections may be opened using the config
-%%% data specified by Key
). See ct:require/3
+%%% data specified by Key
). See ct:require/2
%%% for how to create a new Name
%%%
%%% ConnType
will always override the type
@@ -154,7 +154,7 @@ connect(KeyOrName, ExtraOpts) when is_list(ExtraOpts) ->
%%% same key in the config data. For details on valid SSH
%%% options, see the documentation for the OTP ssh application.
%%%
-%%% @see ct:require/3
+%%% @see ct:require/2
connect(KeyOrName, ConnType, ExtraOpts) ->
case ct:get_config(KeyOrName) of
undefined ->
--
cgit v1.2.3
From 69d4a56d9f7ade14fd0496ffbf56d96bf9184aef Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin
Date: Thu, 23 Aug 2012 16:51:00 +0200
Subject: ssl & public_key: Workaround that some certificates encode
countryname as utf8 and close down gracefully if other ASN-1 errors occur.
The reason certificate_unknown that is used as ALERT for ASN-1 encoding failure is described as:
Some other (unspecified) issue arose in processing the
certificate, rendering it unacceptable.
---
lib/public_key/asn1/OTP-PKIX.asn1 | 12 +++++++++-
lib/public_key/src/pubkey_cert_records.erl | 11 +++++++++-
lib/public_key/test/public_key_SUITE.erl | 35 ++++++++++++++++++++++++++++--
lib/ssl/src/ssl_handshake.erl | 29 +++++++++++++++----------
4 files changed, 71 insertions(+), 16 deletions(-)
(limited to 'lib')
diff --git a/lib/public_key/asn1/OTP-PKIX.asn1 b/lib/public_key/asn1/OTP-PKIX.asn1
index fbf531df40..e94a77a3e7 100644
--- a/lib/public_key/asn1/OTP-PKIX.asn1
+++ b/lib/public_key/asn1/OTP-PKIX.asn1
@@ -225,7 +225,17 @@ dnQualifier ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
countryName ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
ID id-at-countryName
- TYPE X520countryName }
+ TYPE X520countryName } -- this is currently not used when decoding
+ -- The decoding and mapping between ID and Type is done in the code
+ -- in module publickey_cert_records via the function attribute_type
+ -- To be more forgiving and compatible with other SSL implementations
+ -- regarding how to handle and sometimes accept incorrect certificates
+ -- we define and use the type below instead of X520countryName
+
+ OTP-X520countryname ::= CHOICE {
+ printableString PrintableString (SIZE (2)),
+ utf8String UTF8String (SIZE (2))
+}
serialNumber ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
ID id-at-serialNumber
diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl
index b86d7a1f0c..33fe940ea2 100644
--- a/lib/public_key/src/pubkey_cert_records.erl
+++ b/lib/public_key/src/pubkey_cert_records.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2012. 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
@@ -57,6 +57,15 @@ transform(#'OTPTBSCertificate'{}= TBS, decode) ->
transform(#'AttributeTypeAndValue'{type=Id,value=Value0} = ATAV, Func) ->
{ok, Value} =
case attribute_type(Id) of
+ 'X520countryName'when Func == decode ->
+ %% Workaround that some certificates break the ASN-1 spec
+ %% and encode countryname as utf8
+ case 'OTP-PUB-KEY':Func('OTP-X520countryname', Value0) of
+ {ok, {utf8String, Utf8Value}} ->
+ {ok, unicode:characters_to_list(Utf8Value)};
+ {ok, {printableString, ASCCI}} ->
+ {ok, ASCCI}
+ end;
Type when is_atom(Type) -> 'OTP-PUB-KEY':Func(Type, Value0);
_UnknownType -> {ok, Value0}
end,
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index a91dcfa029..6a879867e1 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2012. 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
@@ -107,7 +107,7 @@ all() ->
{group, ssh_public_key_decode_encode},
encrypt_decrypt,
{group, sign_verify},
- pkix, pkix_path_validation].
+ pkix, pkix_countryname, pkix_path_validation].
groups() ->
[{pem_decode_encode, [], [dsa_pem, rsa_pem, encrypted_pem,
@@ -626,6 +626,34 @@ pkix(Config) when is_list(Config) ->
VerifyStr = public_key:pkix_normalize_name(TestStr),
ok.
+
+%%--------------------------------------------------------------------
+pkix_countryname(doc) ->
+ "Test workaround for certs that code x509countryname as utf8";
+pkix_countryname(suite) ->
+ [];
+pkix_countryname(Config) when is_list(Config) ->
+ Cert = incorrect_pkix_cert(),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBSCert = OTPCert#'OTPCertificate'.tbsCertificate,
+ Issuer = TBSCert#'OTPTBSCertificate'.issuer,
+ Subj = TBSCert#'OTPTBSCertificate'.subject,
+ check_countryname(Issuer),
+ check_countryname(Subj).
+
+check_countryname({rdnSequence,DirName}) ->
+ do_check_countryname(DirName).
+do_check_countryname([]) ->
+ ok;
+do_check_countryname([#'AttributeTypeAndValue'{type = ?'id-at-countryName',
+ value = "US"}|_]) ->
+ ok;
+do_check_countryname([#'AttributeTypeAndValue'{type = ?'id-at-countryName',
+ value = Value}|_]) ->
+ test_server:fail({incorrect_cuntry_name, Value});
+do_check_countryname([_| Rest]) ->
+ do_check_countryname(Rest).
+
%%--------------------------------------------------------------------
pkix_path_validation(doc) ->
"Misc pkix tests not covered elsewhere";
@@ -716,3 +744,6 @@ check_entry_type(_,_) ->
strip_ending_newlines(Bin) ->
string:strip(binary_to_list(Bin), right, 10).
+
+incorrect_pkix_cert() ->
+ <<48,130,5,186,48,130,4,162,160,3,2,1,2,2,7,7,250,61,63,6,140,137,48,13,6,9,42, 134,72,134,247,13,1,1,5,5,0,48,129,220,49,11,48,9,6,3,85,4,6,19,2,85,83,49, 16,48,14,6,3,85,4,8,19,7,65,114,105,122,111,110,97,49,19,48,17,6,3,85,4,7,19, 10,83,99,111,116,116,115,100,97,108,101,49,37,48,35,6,3,85,4,10,19,28,83,116, 97,114,102,105,101,108,100,32,84,101,99,104,110,111,108,111,103,105,101,115, 44,32,73,110,99,46,49,57,48,55,6,3,85,4,11,19,48,104,116,116,112,58,47,47,99, 101,114,116,105,102,105,99,97,116,101,115,46,115,116,97,114,102,105,101,108, 100,116,101,99,104,46,99,111,109,47,114,101,112,111,115,105,116,111,114,121, 49,49,48,47,6,3,85,4,3,19,40,83,116,97,114,102,105,101,108,100,32,83,101,99, 117,114,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117, 116,104,111,114,105,116,121,49,17,48,15,6,3,85,4,5,19,8,49,48,54,56,56,52,51, 53,48,30,23,13,49,48,49,48,50,51,48,49,51,50,48,53,90,23,13,49,50,49,48,50, 51,48,49,51,50,48,53,90,48,122,49,11,48,9,6,3,85,4,6,12,2,85,83,49,11,48,9,6, 3,85,4,8,12,2,65,90,49,19,48,17,6,3,85,4,7,12,10,83,99,111,116,116,115,100, 97,108,101,49,38,48,36,6,3,85,4,10,12,29,83,112,101,99,105,97,108,32,68,111, 109,97,105,110,32,83,101,114,118,105,99,101,115,44,32,73,110,99,46,49,33,48, 31,6,3,85,4,3,12,24,42,46,108,111,103,105,110,46,115,101,99,117,114,101,115, 101,114,118,101,114,46,110,101,116,48,130,1,34,48,13,6,9,42,134,72,134,247, 13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,185,136,240,80,141,36,124, 245,182,130,73,19,188,74,166,117,72,228,185,209,43,129,244,40,44,193,231,11, 209,12,234,88,43,142,1,162,48,122,17,95,230,105,171,131,12,147,46,204,36,80, 250,171,33,253,35,62,83,22,71,212,186,141,14,198,89,89,121,204,224,122,246, 127,110,188,229,162,67,95,6,74,231,127,99,131,7,240,85,102,203,251,50,58,58, 104,245,103,181,183,134,32,203,121,232,54,32,188,139,136,112,166,126,14,91, 223,153,172,164,14,61,38,163,208,215,186,210,136,213,143,70,147,173,109,217, 250,169,108,31,211,104,238,103,93,182,59,165,43,196,189,218,241,30,148,240, 109,90,69,176,194,52,116,173,151,135,239,10,209,179,129,192,102,75,11,25,168, 223,32,174,84,223,134,70,167,55,172,143,27,130,123,226,226,7,34,142,166,39, 48,246,96,231,150,84,220,106,133,193,55,95,159,227,24,249,64,36,1,142,171,16, 202,55,126,7,156,15,194,22,116,53,113,174,104,239,203,120,45,131,57,87,84, 163,184,27,83,57,199,91,200,34,43,98,61,180,144,76,65,170,177,2,3,1,0,1,163, 130,1,224,48,130,1,220,48,15,6,3,85,29,19,1,1,255,4,5,48,3,1,1,0,48,29,6,3, 85,29,37,4,22,48,20,6,8,43,6,1,5,5,7,3,1,6,8,43,6,1,5,5,7,3,2,48,14,6,3,85, 29,15,1,1,255,4,4,3,2,5,160,48,56,6,3,85,29,31,4,49,48,47,48,45,160,43,160, 41,134,39,104,116,116,112,58,47,47,99,114,108,46,115,116,97,114,102,105,101, 108,100,116,101,99,104,46,99,111,109,47,115,102,115,50,45,48,46,99,114,108, 48,83,6,3,85,29,32,4,76,48,74,48,72,6,11,96,134,72,1,134,253,110,1,7,23,2,48, 57,48,55,6,8,43,6,1,5,5,7,2,1,22,43,104,116,116,112,115,58,47,47,99,101,114, 116,115,46,115,116,97,114,102,105,101,108,100,116,101,99,104,46,99,111,109, 47,114,101,112,111,115,105,116,111,114,121,47,48,129,141,6,8,43,6,1,5,5,7,1, 1,4,129,128,48,126,48,42,6,8,43,6,1,5,5,7,48,1,134,30,104,116,116,112,58,47, 47,111,99,115,112,46,115,116,97,114,102,105,101,108,100,116,101,99,104,46,99, 111,109,47,48,80,6,8,43,6,1,5,5,7,48,2,134,68,104,116,116,112,58,47,47,99, 101,114,116,105,102,105,99,97,116,101,115,46,115,116,97,114,102,105,101,108, 100,116,101,99,104,46,99,111,109,47,114,101,112,111,115,105,116,111,114,121, 47,115,102,95,105,110,116,101,114,109,101,100,105,97,116,101,46,99,114,116, 48,31,6,3,85,29,35,4,24,48,22,128,20,73,75,82,39,209,27,188,242,161,33,106, 98,123,81,66,122,138,215,213,86,48,59,6,3,85,29,17,4,52,48,50,130,24,42,46, 108,111,103,105,110,46,115,101,99,117,114,101,115,101,114,118,101,114,46,110, 101,116,130,22,108,111,103,105,110,46,115,101,99,117,114,101,115,101,114,118, 101,114,46,110,101,116,48,29,6,3,85,29,14,4,22,4,20,138,233,191,208,157,203, 249,85,242,239,20,195,48,10,148,49,144,101,255,116,48,13,6,9,42,134,72,134, 247,13,1,1,5,5,0,3,130,1,1,0,82,31,121,162,49,50,143,26,167,202,143,61,71, 189,201,199,57,81,122,116,90,192,88,24,102,194,174,48,157,74,27,87,210,223, 253,93,3,91,150,109,120,1,110,27,11,200,198,141,222,246,14,200,71,105,41,138, 13,114,122,106,63,17,197,181,234,121,61,89,74,65,41,231,248,219,129,83,176, 219,55,107,55,211,112,98,38,49,69,77,96,221,108,123,152,12,210,159,157,141, 43,226,55,187,129,3,82,49,136,66,81,196,91,234,196,10,82,48,6,80,163,83,71, 127,102,177,93,209,129,26,104,2,84,24,255,248,161,3,244,169,234,92,122,110, 43,4,17,113,185,235,108,219,210,236,132,216,177,227,17,169,58,162,159,182, 162,93,160,229,200,9,163,229,110,121,240,168,232,14,91,214,188,196,109,210, 164,222,0,109,139,132,113,91,16,118,173,178,176,80,132,34,41,199,51,206,250, 224,132,60,115,192,94,107,163,219,212,226,225,65,169,148,108,213,46,174,173, 103,110,189,229,166,149,254,31,51,44,144,108,187,182,11,251,201,206,86,138, 208,59,51,86,132,235,81,225,88,34,190,8,184>>.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 28469dfa5f..bb26302fff 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -220,18 +220,23 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
end, {Role, UserState0}}
end,
- {TrustedErlCert, CertPath} =
- ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef),
-
- case public_key:pkix_path_validation(TrustedErlCert,
- CertPath,
- [{max_path_length,
- MaxPathLen},
- {verify_fun, ValidationFunAndState}]) of
- {ok, {PublicKeyInfo,_}} ->
- {PeerCert, PublicKeyInfo};
- {error, Reason} ->
- path_validation_alert(Reason)
+ try
+ {TrustedErlCert, CertPath} =
+ ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef),
+ case public_key:pkix_path_validation(TrustedErlCert,
+ CertPath,
+ [{max_path_length,
+ MaxPathLen},
+ {verify_fun, ValidationFunAndState}]) of
+ {ok, {PublicKeyInfo,_}} ->
+ {PeerCert, PublicKeyInfo};
+ {error, Reason} ->
+ path_validation_alert(Reason)
+ end
+ catch
+ error:_ ->
+ %% ASN-1 decode of certificate somehow failed
+ ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN)
end.
%%--------------------------------------------------------------------
--
cgit v1.2.3
From fade3d414f2a052fc810eb4aa9b681c70fc94575 Mon Sep 17 00:00:00 2001
From: Fredrik Gustafsson
Date: Mon, 27 Aug 2012 11:29:09 +0200
Subject: Bumped version nr
---
lib/mnesia/src/mnesia.appup.src | 6 ++++++
1 file changed, 6 insertions(+)
(limited to 'lib')
diff --git a/lib/mnesia/src/mnesia.appup.src b/lib/mnesia/src/mnesia.appup.src
index 304a15242f..355aafb215 100644
--- a/lib/mnesia/src/mnesia.appup.src
+++ b/lib/mnesia/src/mnesia.appup.src
@@ -1,6 +1,9 @@
%% -*- erlang -*-
{"%VSN%",
[
+ {"4.7.1", [{restart_application, mnesia}]},
+ {"4.7", [{restart_application, mnesia}]},
+ {"4.6", [{restart_application, mnesia}]},
{"4.5.1", [{restart_application, mnesia}]},
{"4.5", [{restart_application, mnesia}]},
{"4.4.19", [{restart_application, mnesia}]},
@@ -9,6 +12,9 @@
{"4.4.16", [{restart_application, mnesia}]}
],
[
+ {"4.7.1", [{restart_application, mnesia}]},
+ {"4.7", [{restart_application, mnesia}]},
+ {"4.6", [{restart_application, mnesia}]},
{"4.5.1", [{restart_application, mnesia}]},
{"4.5", [{restart_application, mnesia}]},
{"4.4.19", [{restart_application, mnesia}]},
--
cgit v1.2.3