aboutsummaryrefslogblamecommitdiffstats
path: root/lib/common_test/test/ct_testspec_2_SUITE.erl
blob: 1bab80942aeed947e89c61b760ee2fce48592fe2 (plain) (tree)
1
2
3
4
5


                   
                                                        
  










                                                                           

































































                                                                      



                         







                           
                    









































































































                                                                                
                                             



















                                                                                

















                                                         








































































































                                                                                      
                                             


















                                                                          


















                                                       



























































































                                                                                 





















                                                                      







































                                                                       
                                                                             









                                                                                      
                                          








                                                                                     


                                                                        
                                              


                                                                            

                                                                         


















                                                                




                                                                    
                           











                                                          


                                          

                                      
                              










                                                 


                                                






















                                                                                
                                                                          










                                                    
                                            







                                                                  


                                                                                  























































































                                                                                       
                                                       

                                             
                                                    



























                                                                                    

                                                  




































                                                         
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%%-------------------------------------------------------------------
%%% File: ct_testspec_2_SUITE
%%%
%%% Description:
%%% Test test specifications
%%%
%%% The suites used for the test are located in the data directory.
%%%-------------------------------------------------------------------
-module(ct_testspec_2_SUITE).

-compile(export_all).

-include_lib("common_test/include/ct.hrl").
-include_lib("common_test/src/ct_util.hrl").

%%--------------------------------------------------------------------
%% TEST SERVER CALLBACK FUNCTIONS
%%--------------------------------------------------------------------

init_per_suite(Config) ->
    Config.

end_per_suite(_Config) ->
    ok.

init_per_testcase(_TestCase, Config) ->
    Config.

end_per_testcase(_TestCase, _Config) ->
    ok.

%% suite() -> [{ct_hooks,[ts_install_cth]}].

all() -> 
    [basic_compatible_no_nodes,
     basic_compatible_nodes,
     unknown_terms,
     no_merging,
     multiple_specs,
     misc_config_terms,
     define_names_1].


%%--------------------------------------------------------------------
%% VALID TEST SPEC TERMS (R15B02):
%%
%% {node,3}
%% {cover,2}
%% {cover,3}
%% {config,2}
%% {config,3}
%% {config,4}
%% {userconfig,2}
%% {userconfig,3}
%% {alias,3}
%% {merge_tests,2}
%% {logdir,2}
%% {logdir,3}
%% {logopts,2}
%% {logopts,3}
%% {basic_html,2}
%% {basic_html,3}
%% {verbosity,2}
%% {verbosity,3}
%% {silent_connections,2}
%% {silent_connections,3}
%% {label,2}
%% {label,3}
%% {event_handler,2}
%% {event_handler,3}
%% {event_handler,4}
%% {ct_hooks,2}
%% {ct_hooks,3}
%% {enable_builtin_hooks,2}
%% {release_shell,2}
%% {multiply_timetraps,2}
%% {multiply_timetraps,3}
%% {scale_timetraps,2}
%% {scale_timetraps,3}
%% {include,2}
%% {include,3}
%% {auto_compile,2}
%% {auto_compile,3}
%% {stylesheet,2}
%% {stylesheet,3}
%% {suites,3}
%% {suites,4}
%% {groups,4}
%% {groups,5}
%% {groups,6}
%% {cases,4}
%% {cases,5}
%% {skip_suites,4}
%% {skip_suites,5}
%% {skip_groups,5}
%% {skip_groups,6}
%% {skip_groups,7}
%% {skip_cases,5}
%% {skip_cases,6}
%% {create_priv_dir,2}
%%
%%--------------------------------------------------------------------

%%--------------------------------------------------------------------
%% TEST CASES
%%--------------------------------------------------------------------

%%%-----------------------------------------------------------------
%%%
basic_compatible_no_nodes(_Config) ->

    AliasDir1 = "../tests/to1",
    AliasDir2 = "../tests/to2",
    CfgDir1 = "../cfgs/to1/x.cfg",
    CfgDir2 = ["../cfgs/to2/x.cfg","../cfgs/to2/y.cfg"],
    LogDir = "../logs",
    IncludeDir1 = "../../include",
    IncludeDir2 = ["../tests/to1/include","../tests/to2/include"],

    Spec =
	[
	 {label,"basic_compatible_no_nodes"},
	 {alias,to1,AliasDir1},
	 {alias,to2,AliasDir2},
	 {config,CfgDir1},
	 {config,CfgDir2},
	 {userconfig,{?MODULE,"cfg_str1"}},
	 {userconfig,{?MODULE,"cfg_str2"}},
	 {logdir,LogDir},
	 {logopts,[no_nl]},
	 {event_handler,evh1,[1]},
	 {event_handler,[evh2,evh3],[[2,3]]},
	 {ct_hooks,[{cth_mod1,[]}]},
	 {ct_hooks,[{cth_mod2,[]}]},
	 {multiply_timetraps,2},
	 {include,IncludeDir1},
	 {include,IncludeDir2},
	 {suites,to1,[x_SUITE]},
	 {groups,to1,y_SUITE,[g1,g2]},
	 {cases,to1,y_SUITE,[tc1,tc2]},
	 {skip_suites,to1,z_SUITE,"skipped"},
	 {suites,to2,[x_SUITE,y_SUITE]},
	 {skip_groups,to2,x_SUITE,[g1,g2],"skipped"},
	 {skip_cases,to2,y_SUITE,[tc1,tc2],"skipped"}
	],
    
    {ok,SpecDir} = file:get_cwd(),
    
    ListResult = ct_testspec:collect_tests_from_list(Spec, false),
    ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),	
    SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
					      "basic_compatible_no_nodes.spec"),
    FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
    ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
    
    Node = node(),
    LogDirV = get_absdir(filename:join(SpecDir,"../logs")),
    Alias1V = get_absdir(filename:join(SpecDir,AliasDir1)),
    Alias2V = get_absdir(filename:join(SpecDir,AliasDir2)),
    CFGs = [{Node,get_absdir(filename:join(SpecDir,CfgDir))} ||
	       CfgDir <- [CfgDir1 | CfgDir2]],
    Incls = [{Node,get_absdir(filename:join(SpecDir,IncludeDir))} ||
		IncludeDir <- [IncludeDir1 | IncludeDir2]],
    
    Verify = #testspec{spec_dir = SpecDir,
		       nodes = [{undefined,Node}],
		       init = [],
		       label = [{Node,"basic_compatible_no_nodes"}],
		       logdir = [{Node,LogDirV},"."],
		       logopts = [{Node,[no_nl]}],
		       basic_html = [],
		       cover = [],
		       config = CFGs,
		       userconfig = [{Node,{?MODULE,"cfg_str1"}},
				     {Node,{?MODULE,"cfg_str2"}}],
		       event_handler = [{Node,evh1,[1]},
					{Node,evh2,[[2,3]]},
					{Node,evh3,[[2,3]]}],
		       ct_hooks = [{Node,{cth_mod1,[]}},
				   {Node,{cth_mod2,[]}}],
		       enable_builtin_hooks = true,
		       release_shell = false,
		       include = Incls,
		       auto_compile = [],
		       stylesheet = [],
		       multiply_timetraps = [{Node,2}],
		       scale_timetraps = [],
		       create_priv_dir = [],
		       alias = [{to1,Alias1V},{to2,Alias2V}],			
		       tests = [{{Node,Alias1V},
				 [{x_SUITE,[all]},
				  {y_SUITE,[{g1,all},{g2,all},tc1,tc2]},
				  {z_SUITE,[{all,{skip,"skipped"}}]}]},
				{{Node,Alias2V},
				 [{x_SUITE,[all,
					    {{g1,all},{skip,"skipped"}},
					    {{g2,all},{skip,"skipped"}}]},
				  {y_SUITE,[all,
					    {tc1,{skip,"skipped"}},
					    {tc2,{skip,"skipped"}}]}]}],
		       merge_tests = true},

    verify_result(Verify,ListResult,FileResult),

    {ok,Tests} = ct_testspec:get_tests([SpecFile]),
    ct:pal("ct_testspec:get_tests/1:~n~p~n", [Tests]),
    [{[SpecFile],[{Node,Run,Skip}]}] = Tests,
    [{Alias1V,x_SUITE,all},
     {Alias1V,y_SUITE,[{g1,all},{g2,all},tc1,tc2]},
     {Alias1V,z_SUITE,all},
     {Alias2V,x_SUITE,all},
     {Alias2V,y_SUITE,all}] = lists:sort(Run),
    [{Alias1V,z_SUITE,"skipped"},
     {Alias2V,x_SUITE,{g1,all},"skipped"},
     {Alias2V,x_SUITE,{g2,all},"skipped"},
     {Alias2V,y_SUITE,tc1,"skipped"},
     {Alias2V,y_SUITE,tc2,"skipped"}] = lists:sort(Skip),

    ok.


%%%-----------------------------------------------------------------
%%%
basic_compatible_nodes(_Config) ->

    Node1 = node1@host1,
    Node2 = node2@host2,
    TODir1 = "../tests/to1",
    TODir2 = "../tests/to2",
    CfgDir1 = "../cfgs/to1/x.cfg",
    CfgDir2 = ["../cfgs/to2/x.cfg","../cfgs/to2/y.cfg"],
    LogDir = "../logs",
    MasterLogDir = "../master_logs",
    IncludeDir1 = "../../include",
    IncludeDir2 = ["../tests/to1/include","../tests/to2/include"],

    Spec =
	[
	 {node,n1,Node1},
	 {node,n2,Node2},
	 {init,[n1],[{node_start,[{callback_module,cbm}]}]},
	 {init,n2,[{node_start,[]}]},
	 {init,all_nodes,{eval,{mod,func,[]}}},
	 {label,"basic_compatible_nodes"},
	 {label,n1,basic_compatible_nodes_1},
	 {config,n1,CfgDir1},
	 {config,n2,CfgDir2},
	 {userconfig,{?MODULE,"cfg_str1"}},
	 {userconfig,{?MODULE,"cfg_str2"}},
	 {logdir,all_nodes,LogDir},
	 {logdir,master,MasterLogDir},
	 {logopts,node2@host2,[no_nl]},
	 {event_handler,master,evh1,[1]},
	 {event_handler,[n1,n2],[evh2,evh3],[[2,3]]},
	 {ct_hooks,all_nodes,[{cth_mod1,[]}]},
	 {ct_hooks,[{cth_mod2,[]}]},
	 {multiply_timetraps,node1@host1,2},
	 {include,n1,IncludeDir1},
	 {include,[n1,n2],IncludeDir2},
	 {suites,n1,TODir1,[x_SUITE]},
	 {groups,n1,TODir1,y_SUITE,[g1,g2]},
	 {cases,n1,TODir1,y_SUITE,[tc1,tc2]},
	 {skip_suites,n1,TODir1,z_SUITE,"skipped"},
	 {suites,n2,TODir2,[x_SUITE,y_SUITE]},
	 {skip_groups,n2,TODir2,x_SUITE,[g1,g2],"skipped"},
	 {skip_cases,n2,TODir2,y_SUITE,[tc1,tc2],"skipped"}
	],
    
    {ok,SpecDir} = file:get_cwd(),
    
    ListResult = ct_testspec:collect_tests_from_list(Spec, false),
    ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),	
    SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
					      "basic_compatible_nodes.spec"),
    FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
    ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
    
    Node = node(),
    LogDirV = get_absdir(filename:join(SpecDir,"../logs")),
    MasterLogDirV = get_absdir(filename:join(SpecDir,"../master_logs")),
    TO1V = get_absdir(filename:join(SpecDir,TODir1)),
    TO2V = get_absdir(filename:join(SpecDir,TODir2)),
    CFGs = [{Node1,get_absdir(filename:join(SpecDir,CfgDir1))} |
	    [{Node2,get_absdir(filename:join(SpecDir,CfgDir))} || CfgDir <- CfgDir2]],
    Incls = [{Node1,get_absdir(filename:join(SpecDir,IncludeDir1))} |
	     [{Node1,get_absdir(filename:join(SpecDir,IncludeDir))} ||
		 IncludeDir <- IncludeDir2] ++
	     [{Node2,get_absdir(filename:join(SpecDir,IncludeDir))} ||
		 IncludeDir <- IncludeDir2]],
    
    Verify = #testspec{spec_dir = SpecDir,
		       nodes = [{undefined,Node},{n1,Node1},{n2,Node2}],
		       init = [{Node1,[{node_start,[{callback_module,cbm}]},
				       {eval,[{mod,func,[]}]}]},
			       {Node2,[{node_start,[{callback_module,ct_slave}]},
				       {eval,[{mod,func,[]}]}]},
			       {Node,[{node_start,[]},
				      {eval,[{mod,func,[]}]}]}],
		       label = [{Node,"basic_compatible_nodes"},
				{Node2,"basic_compatible_nodes"},
				{Node1,basic_compatible_nodes_1}],
		       logdir = [{Node,LogDirV},{Node1,LogDirV},{Node2,LogDirV},
				 {master,MasterLogDirV},"."],
		       logopts = [{Node2,[no_nl]}],
		       basic_html = [],
		       cover = [],
		       config = CFGs,
		       userconfig = [{Node,{?MODULE,"cfg_str1"}},
				     {Node1,{?MODULE,"cfg_str1"}},
				     {Node2,{?MODULE,"cfg_str1"}},
				     {Node,{?MODULE,"cfg_str2"}},
				     {Node1,{?MODULE,"cfg_str2"}},
				     {Node2,{?MODULE,"cfg_str2"}}],
		       event_handler = [{master,evh1,[1]},
					{Node1,evh2,[[2,3]]},
					{Node1,evh3,[[2,3]]},
					{Node2,evh2,[[2,3]]},
					{Node2,evh3,[[2,3]]}],
		       ct_hooks = [{Node,{cth_mod1,[]}},
				   {Node1,{cth_mod1,[]}},
				   {Node2,{cth_mod1,[]}},
				   {Node,{cth_mod2,[]}},
				   {Node1,{cth_mod2,[]}},
				   {Node2,{cth_mod2,[]}}],
		       enable_builtin_hooks = true,
		       release_shell = false,
		       include = Incls,
		       auto_compile = [],
		       stylesheet = [],
		       multiply_timetraps = [{Node1,2}],
		       scale_timetraps = [],
		       create_priv_dir = [],
		       tests = [{{Node1,TO1V},
				 [{x_SUITE,[all]},
				  {y_SUITE,[{g1,all},{g2,all},tc1,tc2]},
				  {z_SUITE,[{all,{skip,"skipped"}}]}]},
				{{Node2,TO2V},
				 [{x_SUITE,[all,
					    {{g1,all},{skip,"skipped"}},
					    {{g2,all},{skip,"skipped"}}]},
				  {y_SUITE,[all,
					    {tc1,{skip,"skipped"}},
					    {tc2,{skip,"skipped"}}]}]}],
		       merge_tests = true},

    verify_result(Verify,ListResult,FileResult),

    {ok,Tests} = ct_testspec:get_tests([SpecFile]),
    ct:pal("ct_testspec:get_tests/1:~n~p~n", [Tests]),
    [{[SpecFile],[{Node,[],[]},
                  {Node1,Run1,Skip1},
                  {Node2,Run2,Skip2}]}] = Tests,
    [{TO1V,x_SUITE,all},
     {TO1V,y_SUITE,[{g1,all},{g2,all},tc1,tc2]},
     {TO1V,z_SUITE,all}] = lists:sort(Run1),
    [{TO2V,x_SUITE,all},
     {TO2V,y_SUITE,all}] = lists:sort(Run2),
    [{TO1V,z_SUITE,"skipped"}] = lists:sort(Skip1),
    [{TO2V,x_SUITE,{g1,all},"skipped"},
     {TO2V,x_SUITE,{g2,all},"skipped"},
     {TO2V,y_SUITE,tc1,"skipped"},
     {TO2V,y_SUITE,tc2,"skipped"}] = lists:sort(Skip2),

    ok.

%%%-----------------------------------------------------------------
%%%
unknown_terms(Config) ->
    PrivDir = ?config(priv_dir, Config),

    Spec1 = [{suites,PrivDir,all},
	     {userdata,"I've got news for you"}],
    {error,{undefined_term_in_spec,{userdata,_}}} =
	(catch ct_testspec:collect_tests_from_list(Spec1, false)),
    true = is_record(ct_testspec:collect_tests_from_list(Spec1, true),
		      testspec),
    
    Spec2 = [{logdir,{logdir,PrivDir}}],
    {error,{invalid_directory_name,_}} =
	(catch ct_testspec:collect_tests_from_list(Spec2, false)),
    
    Spec3 = [{suite,PrivDir,all}],
    {error,{undefined_term_in_spec,{suite,_,_}}} =
	(catch ct_testspec:collect_tests_from_list(Spec3, false)),
    true = is_record(ct_testspec:collect_tests_from_list(Spec3, true), testspec),
    
    Spec4 = [{suites,PrivDir,all},
	     {skip_suites,PrivDir,x_SUITE}],
    {error,{bad_term_in_spec,{skip_suites,_,_}}} =
	(catch ct_testspec:collect_tests_from_list(Spec4, false)),
    {error,{bad_term_in_spec,{skip_suites,_,_}}} =
	(catch ct_testspec:collect_tests_from_list(Spec4, true)),

    Spec5 = [{configs,all_nodes,PrivDir}],
    {error,{undefined_term_in_spec,{configs,_,_}}} =
	(catch ct_testspec:collect_tests_from_list(Spec5, false)),
    true = is_record(ct_testspec:collect_tests_from_list(Spec5, true), testspec),
  
    ok.

%%%-----------------------------------------------------------------
%%%
no_merging(_Config) ->    
    Node1 = node1@host1,
    Node2 = node2@host2,
    TODir1 = "../tests/to1",
    TODir2 = "../tests/to2",
    Spec =
	[
	 {merge_tests,false},
	 {node,n1,Node1},
	 {node,n2,Node2},
	 {suites,n1,TODir1,[x_SUITE]},
	 {groups,n1,TODir1,y_SUITE,[g1,g2]},
	 {cases,n1,TODir1,y_SUITE,[tc1,tc2]},
	 {skip_suites,n1,TODir1,z_SUITE,"skipped"},
	 {suites,n2,TODir2,[x_SUITE,y_SUITE]},
	 {skip_groups,n2,TODir2,x_SUITE,[g1,g2],"skipped"},
	 {skip_cases,n2,TODir2,y_SUITE,[tc1,tc2],"skipped"}
	],
    
    {ok,SpecDir} = file:get_cwd(),
    
    ListResult = ct_testspec:collect_tests_from_list(Spec, false),
    ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),	
    SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
					      "no_merging.spec"),
    FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
    ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
    
    Node = node(),
    TO1V = get_absdir(filename:join(SpecDir,TODir1)),
    TO2V = get_absdir(filename:join(SpecDir,TODir2)),
    
    Verify = #testspec{merge_tests = false,
		       spec_dir = SpecDir,
		       nodes = [{undefined,Node},{n1,Node1},{n2,Node2}],
		       tests = [{{Node1,TO1V},
				 [{x_SUITE,[all]}]},
				{{Node1,TO1V},
				 [{y_SUITE,[{g1,all},{g2,all}]}]},
				{{Node1,TO1V},
				 [{y_SUITE,[tc1,tc2]}]},
				{{Node1,TO1V},
				 [{z_SUITE,[{all,{skip,"skipped"}}]}]},
				{{Node2,TO2V},
				 [{x_SUITE,[all]}]},
				{{Node2,TO2V},
				 [{y_SUITE,[all]}]},
				{{Node2,TO2V},
				 [{x_SUITE,[{{g1,all},{skip,"skipped"}},
					    {{g2,all},{skip,"skipped"}}]}]},
				{{Node2,TO2V},
				 [{y_SUITE,[{tc1,{skip,"skipped"}},
					    {tc2,{skip,"skipped"}}]}]}]},
		       
    verify_result(Verify,ListResult,FileResult),

    {ok,Tests} = ct_testspec:get_tests([SpecFile]),
    ct:pal("ct_testspec:get_tests/1:~n~p~n", [Tests]),
    [{[SpecFile],[{Node,[],[]},
                  {Node1,Run1,Skip1},
                  {Node2,Run2,Skip2}]}] = Tests,
    [{TO1V,x_SUITE,all},
     {TO1V,y_SUITE,[tc1,tc2]},
     {TO1V,y_SUITE,[{g1,all},{g2,all}]},
     {TO1V,z_SUITE,all}] = lists:sort(Run1),
    [{TO2V,x_SUITE,all},
     {TO2V,x_SUITE,[{skipped,g1,all},{skipped,g2,all}]},
     {TO2V,y_SUITE,all},
     {TO2V,y_SUITE,[{skipped,tc1},{skipped,tc2}]}] = lists:sort(Run2),
    [{TO1V,z_SUITE,"skipped"}] = lists:sort(Skip1),
    [{TO2V,x_SUITE,{g1,all},"skipped"},
     {TO2V,x_SUITE,{g2,all},"skipped"},
     {TO2V,y_SUITE,tc1,"skipped"},
     {TO2V,y_SUITE,tc2,"skipped"}] = lists:sort(Skip2),

    ok.

%%%-----------------------------------------------------------------
%%%
multiple_specs(_Config) ->
    Node1 = node1@host1,
    Node2 = node2@host2,
    TODir1 = "../tests/to1",
    TODir2 = "../tests/to2",
    CfgDir1 = "../cfgs/to1/x.cfg",
    CfgDir2 = ["../cfgs/to2/x.cfg","../cfgs/to2/y.cfg"],
    LogDir = "../logs",
    Spec1 =
	[
	 {node,n1,Node1},
	 {node,n2,Node2},
	 {alias,to1,TODir1},
	 {alias,to2,TODir2},
	 {label,"multiple_specs1"},
	 {config,n1,CfgDir1},
	 {config,n2,CfgDir2},
	 {logdir,all_nodes,LogDir}
	],
    Spec2 =
	[
	 {merge_tests,false},
	 {label,"multiple_specs2"},
	 {suites,n1,TODir1,[x_SUITE]},
	 {groups,n1,TODir1,y_SUITE,[g1,g2]},
	 {cases,n1,TODir1,y_SUITE,[tc1,tc2]},
	 {skip_suites,n1,TODir1,z_SUITE,"skipped"},
	 {suites,n2,TODir2,[x_SUITE,y_SUITE]},
	 {skip_groups,n2,TODir2,x_SUITE,[g1,g2],"skipped"},
	 {skip_cases,n2,TODir2,y_SUITE,[tc1,tc2],"skipped"}
	],
    
    {ok,SpecDir} = file:get_cwd(),    
    SpecFile1 = ct_test_support:write_testspec(Spec1,SpecDir,
					      "multiple_specs.1.spec"),
    SpecFile2 = ct_test_support:write_testspec(Spec2,SpecDir,
					      "multiple_specs.2.spec"),
    FileResult = ct_testspec:collect_tests_from_file([[SpecFile1,SpecFile2]],
						     false),
    ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
    
    Node = node(),
    TO1V = get_absdir(filename:join(SpecDir,TODir1)),
    TO2V = get_absdir(filename:join(SpecDir,TODir2)),
    CFGs = [{Node1,get_absdir(filename:join(SpecDir,CfgDir1))} |
	    [{Node2,get_absdir(filename:join(SpecDir,CfgDir))} || CfgDir <- CfgDir2]],
    LogDirV = get_absdir(filename:join(SpecDir,"../logs")),

    Verify = #testspec{merge_tests = true,
		       spec_dir = SpecDir,
		       nodes = [{undefined,Node},{n1,Node1},{n2,Node2}],
		       alias = [{to1,TO1V},{to2,TO2V}],
		       label = [{Node,"multiple_specs1"},
				{Node1,"multiple_specs1"},
				{Node2,"multiple_specs1"}],
		       logdir = [{Node,LogDirV},{Node1,LogDirV},{Node2,LogDirV},"."],
		       config = CFGs,
		       tests = [{{Node1,TO1V},
				 [{x_SUITE,[all]},
				  {y_SUITE,[{g1,all},{g2,all},tc1,tc2]},
				  {z_SUITE,[{all,{skip,"skipped"}}]}]},
				{{Node2,TO2V},
				 [{x_SUITE,[all,{{g1,all},{skip,"skipped"}},
					    {{g2,all},{skip,"skipped"}}]},
				  {y_SUITE,[all,{tc1,{skip,"skipped"}},
					    {tc2,{skip,"skipped"}}]}]}]},
		       
    verify_result(Verify,FileResult,FileResult),

    {ok,Tests} = ct_testspec:get_tests([[SpecFile1,SpecFile2]]),
    ct:pal("ct_testspec:get_tests/1:~n~p~n", [Tests]),
    [{[SpecFile1,SpecFile2],[{Node,[],[]},
                             {Node1,Run1,Skip1},
                             {Node2,Run2,Skip2}]}] = Tests,
    [{TO1V,x_SUITE,all},
     {TO1V,y_SUITE,[{g1,all},{g2,all},tc1,tc2]},
     {TO1V,z_SUITE,all}] = lists:sort(Run1),
    [{TO2V,x_SUITE,all},
     {TO2V,y_SUITE,all}] = lists:sort(Run2),
    [{TO1V,z_SUITE,"skipped"}] = lists:sort(Skip1),
    [{TO2V,x_SUITE,{g1,all},"skipped"},
     {TO2V,x_SUITE,{g2,all},"skipped"},
     {TO2V,y_SUITE,tc1,"skipped"},
     {TO2V,y_SUITE,tc2,"skipped"}] = lists:sort(Skip2),

    ok.

%%%-----------------------------------------------------------------
%%% 
misc_config_terms(_Config) ->
    CfgDir = "../cfgs/to1",
    TODir = "../tests/to1",
    Spec =
	[{node,x,n1@h1},{node,y,n2@h2},

	 {config,CfgDir,"a.cfg"},
	 {config,n1@h1,CfgDir,"b.cfg"},
	 {config,all_nodes,CfgDir,"c.cfg"},
	 {config,all_nodes,filename:join(CfgDir,"d.cfg")},

	 {basic_html,true},
	 {basic_html,n1@h1,false},
	 {basic_html,n2@h2,true},
	 
	 {silent_connections,n1@h1,all},
	 {silent_connections,n2@h2,[ssh]},

	 {enable_builtin_hooks,false},

	 {release_shell,true},

	 {auto_compile,false},
	 {auto_compile,n1@h1,true},
	 {auto_compile,n2@h2,false},

	 {stylesheet,"../css"},
	 {stylesheet,n1@h1,"./n1/css"},
	 {stylesheet,n2@h2,"./n2/css"},

	 {create_priv_dir,[auto_per_tc]},
	 {create_priv_dir,n1@h1,[manual_per_tc]},
	 {create_priv_dir,n2@h2,[auto_per_run]},

	 {suites,n1@h1,TODir,[x_SUITE]}
	],
    
    {ok,SpecDir} = file:get_cwd(),
    
    ListResult = ct_testspec:collect_tests_from_list(Spec, false),
    ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),	
    SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
					      "misc_config_terms.spec"),
    FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
    ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
    
    Node = node(),
    CfgA = get_absdir(filename:join(filename:join(SpecDir,CfgDir), "a.cfg")),
    CfgB = get_absdir(filename:join(filename:join(SpecDir,CfgDir), "b.cfg")),
    CfgC = get_absdir(filename:join(filename:join(SpecDir,CfgDir), "c.cfg")),
    CfgD = get_absdir(filename:join(filename:join(SpecDir,CfgDir), "d.cfg")),
    CSS = get_absdir(filename:join(SpecDir,"../css")),
    CSS1 = get_absdir(filename:join(SpecDir,"./n1/css")),
    CSS2 = get_absdir(filename:join(SpecDir,"./n2/css")),
    
    Verify = #testspec{spec_dir = SpecDir,
		       nodes = [{undefined,Node},{x,n1@h1},{y,n2@h2}],
		       basic_html = [{Node,true},{n1@h1,false},{n2@h2,true}],
		       silent_connections = [{n1@h1,[all]},{n2@h2,[ssh]}],
		       config = [{Node,CfgA},
				 {n1@h1,CfgA},
				 {n2@h2,CfgA},
				 {n1@h1,CfgB},
				 {Node,CfgC},
				 {n1@h1,CfgC},
				 {n2@h2,CfgC},
				 {Node,CfgD},
				 {n1@h1,CfgD},
				 {n2@h2,CfgD}],
		       enable_builtin_hooks = false,
		       release_shell = true,
		       auto_compile = [{Node,false},
				       {n1@h1,true},
				       {n2@h2,false}],
		       stylesheet = [{Node,CSS},
				     {n1@h1,CSS1},
				     {n2@h2,CSS2}],	       
		       create_priv_dir = [{Node,[auto_per_tc]},
					  {n1@h1,[manual_per_tc]},
					  {n2@h2,[auto_per_run]}],
		       tests = [{{n1@h1,get_absdir(filename:join(SpecDir,TODir))},
				 [{x_SUITE,[all]}]}]
		      },
    
    verify_result(Verify,ListResult,FileResult).

%%%-----------------------------------------------------------------
%%% 
define_names_1(_Config) ->
    Spec =
	[
	 {define,'HOST','eniac'},
	 {define,'NODE1',testnode1},
	 {define,'NODE2',testnode2},
	 {define,'NODES',['NODE1@HOST',
			  'NODE2@HOST']},
	 {define,'TOPDIR',".."},
	 {define,'TO1',"to1"},
	 {define,'TO2',"to2"},
	 {define,'LOGDIR',"'TOPDIR'/logdir"},
	 {define,'LOGDIR1',"'TOPDIR'/logdir1"},
	 {define,'LOGDIR2',"'TOPDIR'/logdir2"},
	 {define,'CFGDIR',"'TOPDIR'/cfgs"},
	 {define,'CFGFILES',["cfgX","cfgY"]},
	 {define,'TESTDIR',"'TOPDIR'/test"},
	 {define,'TO1DIR',"'TESTDIR'/'TO1'"},
	 {define,'TO2DIR',"'TESTDIR'/'TO2'"},
	 {define,'EXSUITE',ex_SUITE},
	 {define,'EXGRS',[g1,g2]},

	 {logdir,'LOGDIR'},
	 {logdir,'NODE1@HOST','LOGDIR1'},
	 {logdir,'NODE2@HOST','LOGDIR2'},

	 {config,["a.cfg","b.cfg"]},
	 {config,'NODES',"./'CFGDIR'/c.cfg"},
	 {config,'CFGDIR',["d.cfg","e.cfg"]},
	 {config,'NODE2@HOST','CFGDIR','CFGFILES'},

	 {suites,'NODE1@HOST','TO1DIR',all},
	 {suites,'NODES','TO2DIR',all},

	 {groups,'TO1DIR','EXSUITE','EXGRS'}
	],
    
    {ok,SpecDir} = file:get_cwd(),
    
    ListResult = ct_testspec:collect_tests_from_list(Spec, false),
    ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),	
    SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
					      "define_names_1.spec"),
    FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
    ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
    
    N = node(),
    N1 = testnode1@eniac,
    N2 = testnode2@eniac,
    Join = fun(Dir) -> shorten_path(filename:join(SpecDir,Dir),SpecDir) end,

    Verify = #testspec{spec_dir = SpecDir,
		       nodes = [{undefined,N2},
				{undefined,N1},
				{undefined,N}],
		       config = [{N2,Join("a.cfg")},{N2,Join("b.cfg")},
				 {N1,Join("a.cfg")},{N1,Join("b.cfg")},
				 {N,Join("a.cfg")},{N,Join("b.cfg")},
				 {N1,Join("../cfgs/c.cfg")},{N2,Join("../cfgs/c.cfg")},
				 {N2,Join("../cfgs/d.cfg")},{N2,Join("../cfgs/e.cfg")},
				 {N1,Join("../cfgs/d.cfg")},{N1,Join("../cfgs/e.cfg")},
				 {N,Join("../cfgs/d.cfg")},{N,Join("../cfgs/e.cfg")},
				 {N2,Join("../cfgs/cfgX")},{N2,Join("../cfgs/cfgY")}],
		       logdir = [{N,Join("../logdir")},
				 {N1,Join("../logdir1")},
				 {N2,Join("../logdir2")},
				 "."],
		       tests = [{{N1,Join("../test/to1")},[{all,[all]}]},
				{{N1,Join("../test/to2")},[{all,[all]}]},
				{{N2,Join("../test/to2")},[{all,[all]}]},
				{{N2,Join("../test/to1")},
				 [{ex_SUITE,[{g1,all},{g2,all}]}]},
				{{N,Join("../test/to1")},
				 [{ex_SUITE,[{g1,all},{g2,all}]}]}]
		      },    
    verify_result(Verify,ListResult,FileResult).


%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
%%%-----------------------------------------------------------------

verify_result(VerificationRec,ListResult,FileResult) ->
    {_,TSLTuples} = rec2proplist(ListResult),
    {_,TSFTuples} = rec2proplist(FileResult),
    {_,VTuples} = rec2proplist(VerificationRec),    
    VResult =
	(catch lists:foldl(fun({Tag,Val},{[{Tag,Val}|TSL],[{Tag,Val}|TSF]}) ->
				   {TSL,TSF};
			      ({Tag,Val},{[{_Tag,TSLVal}|_TSL],[{Tag,Val}|_TSF]}) ->
				   exit({ts_list_mismatch,Tag,Val,TSLVal});
			      ({Tag,Val},{[{Tag,Val}|_TSL],[{_Tag,TSFVal}|_TSF]}) ->
				   exit({ts_file_mismatch,Tag,Val,TSFVal});
			      ({Tag,Val},{_,[{_Tag,TSFVal}|_TSF]}) ->
				   exit({ts_mismatch,Tag,Val,TSFVal})
			   end, {TSLTuples,TSFTuples}, VTuples)),
	       case VResult of
		  {'EXIT',Reason} ->
		      ct:fail(Reason);
		  _ ->
		      ok
	       end,
    ok.


check_parameter(S="cfg_str1") ->
    {ok,{config,S}};
check_parameter(S="cfg_str2") ->
    {ok,{config,S}}.
read_config(S) ->
    {ok,[{cfg,S}]}.

rec2proplist(E={error,_What}) ->
    exit({invalid_testspec_record,E});
rec2proplist([{Specs,Rec}]) when is_list(Specs) ->
    rec2proplist(Rec);
rec2proplist(Rec) ->
    [RecName|RecList] = tuple_to_list(Rec),
    FieldNames = 
	if RecName == testspec ->
		record_info(fields, testspec);
	   true ->
		undefined
	end,
    {RecName,combine_names_and_vals(FieldNames,RecList)}.

combine_names_and_vals([FN|FNs], [V|Vs]) ->
    [{FN,V} | combine_names_and_vals(FNs, Vs)];
combine_names_and_vals([], []) ->
    [];
combine_names_and_vals(_, _) ->
    [].

get_absdir(Dir) ->
    shorten_path(filename:absname(Dir),Dir).

shorten_path(Path,SpecDir) ->
    case shorten_split_path(filename:split(Path),[]) of
	[] ->
	    [Root|_] = filename:split(SpecDir),
	    Root;
	Short ->	    
	    filename:join(Short)
    end.

shorten_split_path([".."|Path],SoFar) ->
    shorten_split_path(Path,tl(SoFar));
shorten_split_path(["."|Path],SoFar) ->
    shorten_split_path(Path,SoFar);
shorten_split_path([Dir|Path],SoFar) ->
    shorten_split_path(Path,[Dir|SoFar]);
shorten_split_path([],SoFar) ->
    lists:reverse(SoFar).