From a71c6b976fa79fa3bcd0e61850a1a57071159b28 Mon Sep 17 00:00:00 2001
From: Lukas Larsson <lukas@erlang-solutions.com>
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

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 @@
 	<v> Func = atom()</v>
 	<v> Args = list()</v>
 	<v> Fun = fun()</v>
-	<v> Required = Key | {Key,SubKeys}</v>
+	<v> Required = Key | {Key,SubKeys} | {Key,SubKey} | {Key,SubKey,SubKeys}</v>
 	<v> Key = atom()</v>
 	<v> SubKeys = SubKey | [SubKey]</v>
 	<v> SubKey = atom()</v>
@@ -289,7 +289,7 @@
 	<v> Func = atom()</v>
 	<v> Args = list()</v>
 	<v> Fun = fun()</v>
-	<v> Required = Key | {Key,SubKeys}</v>
+	<v> Required = Key | {Key,SubKeys} | {Key,Subkey} | {Key,Subkey,SubKeys}</v>
 	<v> Key = atom()</v>
 	<v> SubKeys = SubKey | [SubKey]</v>
 	<v> SubKey = atom()</v>
@@ -476,7 +476,7 @@
 	<v> Func = atom()</v>
 	<v> Args = list()</v>
 	<v> Fun = fun()</v>
-	<v> Required = Key | {Key,SubKeys}</v>
+	<v> Required = Key | {Key,SubKeys} | {Key,Subkey} | {Key,Subkey,SubKeys}</v>
 	<v> Key = atom()</v>
 	<v> SubKeys = SubKey | [SubKey]</v>
 	<v> SubKey = atom()</v>
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 @@
 
       <pre>
         [{ftp_host, [{ftp, "targethost"}, {username, "tester"}, {password, "letmein"}]},
-        {lm_directory, "/test/loadmodules"}]</pre>
+         {lm_directory, "/test/loadmodules"}]</pre>
 
     </section>
 
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}}</c>, reason for failure.</p>	
-        <p><c>RequireInfo = {not_available,atom()}</c>, why require has failed.</p>
+        <p><c>RequireInfo = {not_available,atom() | tuple()}</c>, why require has failed.</p>
         <p><c>FailInfo = {timetrap_timeout,integer()} | 
 	                 {RunTimeError,StackTrace} | 
 			 UserTerm</c>, 
@@ -233,7 +233,7 @@
 	      reason for auto skipping <c>Func</c>.</p>
         <p><c>FailReason = {Suite,ConfigFunc,FailInfo}} | 
 	                   {Suite,FailedCaseInSequence}</c>, reason for failure.</p>	
-        <p><c>RequireInfo = {not_available,atom()}</c>, why require has failed.</p>
+        <p><c>RequireInfo = {not_available,atom() | tuple()}</c>, why require has failed.</p>
         <p><c>ConfigFunc = init_per_suite | init_per_group</c></p>
         <p><c>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 @@
 
 	  <pre>
 	    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"}]}}].</pre>
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 <c>Required</c>. Note that it is
+%%% only the last element of the tuple which can be a list of <c>SubKey</c>s.
 %%%
-%%% <p>Example: require the variable <code>myvar</code>:<br/>
-%%% <code>ok = ct:require(myvar)</code></p>
+%%% <p>Example 1: require the variable <code>myvar</code>:</p>
+%%% <pre>ok = ct:require(myvar).</pre>
 %%%
 %%% <p>In this case the config file must at least contain:</p>
-%%% <pre>
-%%% {myvar,Value}.</pre>
+%%% <pre>{myvar,Value}.</pre>
 %%% 
-%%% <p>Example: require the variable <code>myvar</code> with
-%%% subvariable <code>sub1</code>:<br/>
-%%% <code>ok = ct:require({myvar,sub1})</code></p>
+%%% <p>Example 2: require the key <code>myvar</code> with
+%%% subkeys <code>sub1</code> and <code>sub2</code>:</p>
+%%% <pre>ok = ct:require({myvar,[sub1,sub2]}).</pre>
 %%%
 %%% <p>In this case the config file must at least contain:</p>
-%%% <pre>
-%%% {myvar,[{sub1,Value}]}.</pre>
+%%% <pre>{myvar,[{sub1,Value},{sub2,Value}]}.</pre>
+%%%
+%%% <p>Example 3: require the key <code>myvar</code> with
+%%% subkey <code>sub1</code> with <code>subsub1</code>:</p>
+%%% <pre>ok = ct:require({myvar,sub1,sub2}).</pre>
+%%%
+%%% <p>In this case the config file must at least contain:</p>
+%%% <pre>{myvar,[{sub1,[{sub2,Value}]}]}.</pre>
 %%%
 %%% @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 <c>Required</c> is the same as in
+%%% <c>required/1</c> except that it is not possible to specify a list
+%%% of <c>SubKey</c>s.
 %%%
-%%% <p>If the requested data is available, the main entry will be
+%%% <p>If the requested data is available, the sub entry will be
 %%% associated with <code>Name</code> so that the value of the element
 %%% can be read with <code>get_config/1,2</code> provided
-%%% <code>Name</code> instead of the <code>Key</code>.</p>
+%%% <code>Name</code> instead of the whole <code>Required</code> term.</p>
 %%%
 %%% <p>Example: Require one node with a telnet connection and an
-%%% ftp connection. Name the node <code>a</code>:<br/> <code>ok =
-%%% ct:require(a,{node,[telnet,ftp]}).</code><br/> All references
-%%% to this node may then use the node name. E.g. you can fetch a
-%%% file over ftp like this:<br/>
-%%% <code>ok = ct:ftp_get(a,RemoteFile,LocalFile).</code></p>
+%%% ftp connection. Name the node <code>a</code>:
+%%% <pre>ok = ct:require(a,{machine,node}).</pre>
+%%% All references to this node may then use the node name.
+%%% E.g. you can fetch a file over ftp like this:</p>
+%%% <pre>ok = ct:ftp_get(a,RemoteFile,LocalFile).</pre>
 %%%
 %%% <p>For this to work, the config file must at least contain:</p>
-%%% <pre>
-%%% {node,[{telnet,IpAddr},
-%%%        {ftp,IpAddr}]}.</pre>
+%%% <pre>{machine,[{node,[{telnet,IpAddr},{ftp,IpAddr}]}]}.</pre>
+%%%
+%%% <note>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: <br/><c>ct:require(a,{node,[telnet,ftp]}).</c><br/>
+%%% This will associate the name <c>a</c> with the top level <c>node</c> entry.
+%%% For this to work, the config file must at least contain:<br/>
+%%% <c>{node,[{telnet,IpAddr},{ftp,IpAddr}]}.</c></note>
 %%%
 %%% @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) ->
 %%% <p>Example, given the following config file:</p>
 %%% <pre>
 %%% {unix,[{telnet,IpAddr},
-%%%        {username,Username},
-%%%        {password,Password}]}.</pre>
-%%% <p><code>get_config(unix,Default) -> 
+%%%        {user,[{username,Username},
+%%%               {password,Password}]}]}.</pre>
+%%% <p><code>ct:get_config(unix,Default) ->
 %%%                          [{telnet,IpAddr},
-%%%                           {username,Username},
-%%%                           {password,Password}]</code><br/>
-%%% <code>get_config({unix,telnet},Default) -> IpAddr</code><br/>
-%%% <code>get_config({unix,ftp},Default) -> Default</code><br/>
-%%% <code>get_config(unknownkey,Default) -> Default</code></p>
+%%%                           {user, [{username,Username},
+%%%                                   {password,Password}]}]</code><br/>
+%%% <code>ct:get_config({unix,telnet},Default) -> IpAddr</code><br/>
+%%% <code>ct:get_config({unix,user,username},Default) -> Username</code><br/>
+%%% <code>ct:get_config({unix,ftp},Default) -> Default</code><br/>
+%%% <code>ct:get_config(unknownkey,Default) -> Default</code></p>
 %%%
 %%% <p>If a config variable key has been associated with a name (by
 %%% means of <code>require/2</code> or a require statement), the name 
 %%% may be used instead of the key to read the value:</p>
 %%%
-%%% <p><code>require(myhost,unix) -> ok</code><br/>
-%%% <code>get_config(myhost,Default) -> 
-%%%                          [{telnet,IpAddr},
-%%%                           {username,Username},
-%%%                           {password,Password}]</code></p>
+%%% <p><code>ct:require(myuser,{unix,user}) -> ok.</code><br/>
+%%% <code>ct:get_config(myuser,Default) ->
+%%%          [{username,Username},
+%%%           {password,Password}]</code></p>
 %%%
 %%% <p>If a config variable is defined in multiple files and you want to
 %%% access all possible values, use the <code>all</code> option. The
@@ -390,9 +403,7 @@ get_config(Required,Default) ->
 %%%
 %%% <p>If you want config elements (key-value tuples) returned as result 
 %%% instead of values, use the <code>element</code> option. 
-%%% The returned elements will then be on the form <code>{KeyOrName,Value}</code>, 
-%%% or (in case a subkey has been specified)
-%%% <code>{{KeyOrName,SubKey},Value}</code></p>
+%%% The returned elements will then be on the form <code>{Required,Value}</code></p>
 %%%
 %%% @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}]}.</pre>
+%%% @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) ->
 %%%
 %%% <p>The config file must be as for put/3.</p>
 %%% @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 <code>Key</code>, 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.</p>
+%%%
+%%% <p>See <c>ct:require/3</c> for how to create a new <c>Name</c></p>
+%%%
+%%% @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 <code>Name</code>. If <code>Key</code> is 
 %%%      used, the returned handle must be used for subsequent calls
 %%%      (multiple connections may be opened using the config
-%%%      data specified by <code>Key</code>).</p>
+%%%      data specified by <code>Key</code>). See <c>ct:require/3</c>
+%%%      for how to create a new <c>Name</c></p>
 %%%
 %%%      <p><code>ConnType</code> 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.</p>
+%%%
+%%% @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) ->
 %%% <p><code>TargetMod</code> is a module which exports the functions
 %%% <code>connect(Ip,Port,KeepAlive,Extra)</code> and <code>get_prompt_regexp()</code>
 %%% for the given <code>TargetType</code> (e.g. <code>unix_telnet</code>).</p>
+%%%
+%%% @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 @@
 	<gen_cfg>
 		<a>a_value</a>
 		<b>b_value</b>
+		<c><d>d_value</d></c>
 	</gen_cfg>
 	<gen_cfg2>
 		<c>"Hello, world!"</c>
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