aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src')
-rw-r--r--lib/common_test/src/Makefile3
-rw-r--r--lib/common_test/src/common_test.app.src4
-rw-r--r--lib/common_test/src/ct_cover.erl18
-rw-r--r--lib/common_test/src/ct_netconfc.erl49
-rw-r--r--lib/common_test/src/ct_property_test.erl186
5 files changed, 242 insertions, 18 deletions
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile
index 4600c0ad78..8d74546880 100644
--- a/lib/common_test/src/Makefile
+++ b/lib/common_test/src/Makefile
@@ -74,7 +74,8 @@ MODULES= \
ct_netconfc \
ct_conn_log_h \
cth_conn_log \
- ct_groups
+ ct_groups \
+ ct_property_test
TARGET_MODULES= $(MODULES:%=$(EBIN)/%)
BEAM_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src
index e28751fb59..580d5dbd7b 100644
--- a/lib/common_test/src/common_test.app.src
+++ b/lib/common_test/src/common_test.app.src
@@ -1,7 +1,7 @@
% This is an -*- erlang -*- file.
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2014. 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
@@ -64,7 +64,7 @@
{applications, [kernel,stdlib]},
{env, []},
{runtime_dependencies,["xmerl-1.3.7","webtool-0.8.10","tools-2.6.14",
- "test_server-3.7","stdlib-2.0","ssh-3.0.1",
+ "test_server-3.7.1","stdlib-2.0","ssh-3.0.1",
"snmp-4.25.1","sasl-2.4","runtime_tools-1.8.14",
"kernel-3.0","inets-5.10","erts-6.0",
"debugger-4.0","crypto-3.3","compiler-5.0"]}]}.
diff --git a/lib/common_test/src/ct_cover.erl b/lib/common_test/src/ct_cover.erl
index cf2860ae25..c7f446dee9 100644
--- a/lib/common_test/src/ct_cover.erl
+++ b/lib/common_test/src/ct_cover.erl
@@ -128,20 +128,20 @@ get_spec(File) ->
catch get_spec_test(File).
get_spec_test(File) ->
- FullName = filename:absname(File),
- case filelib:is_file(FullName) of
+ Dir = filename:dirname(File), % always abs path in here, set in ct_run
+ case filelib:is_file(File) of
true ->
- case file:consult(FullName) of
+ case file:consult(File) of
{ok,Terms} ->
Import =
case lists:keysearch(import, 1, Terms) of
{value,{_,Imps=[S|_]}} when is_list(S) ->
ImpsFN = lists:map(fun(F) ->
- filename:absname(F)
+ filename:absname(F,Dir)
end, Imps),
test_files(ImpsFN, ImpsFN);
{value,{_,Imp=[IC|_]}} when is_integer(IC) ->
- ImpFN = filename:absname(Imp),
+ ImpFN = filename:absname(Imp,Dir),
test_files([ImpFN], [ImpFN]);
_ ->
[]
@@ -149,9 +149,9 @@ get_spec_test(File) ->
Export =
case lists:keysearch(export, 1, Terms) of
{value,{_,Exp=[EC|_]}} when is_integer(EC) ->
- filename:absname(Exp);
+ filename:absname(Exp,Dir);
{value,{_,[Exp]}} ->
- filename:absname(Exp);
+ filename:absname(Exp,Dir);
_ ->
undefined
end,
@@ -179,7 +179,7 @@ get_spec_test(File) ->
E;
[CoverSpec] ->
CoverSpec1 = remove_excludes_and_dups(CoverSpec),
- {FullName,Nodes,Import,Export,CoverSpec1};
+ {File,Nodes,Import,Export,CoverSpec1};
_ ->
{error,multiple_apps_in_cover_spec}
end;
@@ -190,7 +190,7 @@ get_spec_test(File) ->
{error,{invalid_cover_spec,Error}}
end;
false ->
- {error,{cant_read_cover_spec_file,FullName}}
+ {error,{cant_read_cover_spec_file,File}}
end.
collect_apps([{level,Level}|Ts], Apps) ->
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index a3861dc745..2f66c7613c 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -190,6 +190,7 @@
get_config/4,
edit_config/3,
edit_config/4,
+ edit_config/5,
delete_config/2,
delete_config/3,
copy_config/3,
@@ -678,15 +679,39 @@ get_config(Client, Source, Filter, Timeout) ->
%%----------------------------------------------------------------------
%% @spec edit_config(Client, Target, Config) -> Result
-%% @equiv edit_config(Client, Target, Config, infinity)
+%% @equiv edit_config(Client, Target, Config, [], infinity)
edit_config(Client, Target, Config) ->
edit_config(Client, Target, Config, ?DEFAULT_TIMEOUT).
%%----------------------------------------------------------------------
--spec edit_config(Client, Target, Config, Timeout) -> Result when
+-spec edit_config(Client, Target, Config, OptParamsOrTimeout) -> Result when
Client :: client(),
Target :: netconf_db(),
Config :: simple_xml(),
+ OptParamsOrTimeout :: [simple_xml()] | timeout(),
+ Result :: ok | {error,error_reason()}.
+%% @doc
+%%
+%% If `OptParamsOrTimeout' is a timeout value, then this is
+%% equivalent to {@link edit_config/5. edit_config(Client, Target,
+%% Config, [], Timeout)}.
+%%
+%% If `OptParamsOrTimeout' is a list of simple XML, then this is
+%% equivalent to {@link edit_config/5. edit_config(Client, Target,
+%% Config, OptParams, infinity)}.
+%%
+%% @end
+edit_config(Client, Target, Config, Timeout) when ?is_timeout(Timeout) ->
+ edit_config(Client, Target, Config, [], Timeout);
+edit_config(Client, Target, Config, OptParams) when is_list(OptParams) ->
+ edit_config(Client, Target, Config, OptParams, ?DEFAULT_TIMEOUT).
+
+%%----------------------------------------------------------------------
+-spec edit_config(Client, Target, Config, OptParams, Timeout) -> Result when
+ Client :: client(),
+ Target :: netconf_db(),
+ Config :: simple_xml(),
+ OptParams :: [simple_xml()],
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
%% @doc Edit configuration data.
@@ -695,10 +720,20 @@ edit_config(Client, Target, Config) ->
%% include `:candidate' or `:startup' in its list of
%% capabilities.
%%
+%% `OptParams' can be used for specifying optional parameters
+%% (`default-operation', `test-option' or `error-option') that will be
+%% added to the `edit-config' request. The value must be a list
+%% containing valid simple XML, for example
+%%
+%% ```
+%% [{'default-operation', ["none"]},
+%% {'error-option', ["rollback-on-error"]}]
+%%'''
+%%
%% @end
%%----------------------------------------------------------------------
-edit_config(Client, Target, Config, Timeout) ->
- call(Client, {send_rpc_op, edit_config, [Target,Config], Timeout}).
+edit_config(Client, Target, Config, OptParams, Timeout) ->
+ call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
%%----------------------------------------------------------------------
@@ -1086,6 +1121,7 @@ handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
SimpleXml = encode_rpc_operation(get,[Filter]),
do_send_rpc(Op, SimpleXml, Timeout, From, State).
+%% @private
handle_msg({ssh_cm, CM, {data, Ch, _Type, Data}}, State) ->
ssh_connection:adjust_window(CM,Ch,size(Data)),
handle_data(Data, State);
@@ -1234,8 +1270,8 @@ encode_rpc_operation(get,[Filter]) ->
{get,filter(Filter)};
encode_rpc_operation(get_config,[Source,Filter]) ->
{'get-config',[{source,[Source]}] ++ filter(Filter)};
-encode_rpc_operation(edit_config,[Target,Config]) ->
- {'edit-config',[{target,[Target]},{config,[Config]}]};
+encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
+ {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,[Config]}]};
encode_rpc_operation(delete_config,[Target]) ->
{'delete-config',[{target,[Target]}]};
encode_rpc_operation(copy_config,[Target,Source]) ->
@@ -1707,6 +1743,7 @@ log(#connection{host=Host,port=Port,name=Name},Action,Data) ->
%% Log callback - called from the error handler process
+%% @private
format_data(How,Data) ->
%% Assuming that the data is encoded as UTF-8. If it is not, then
%% the printout might be wrong, but the format function will not
diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl
new file mode 100644
index 0000000000..52acda5388
--- /dev/null
+++ b/lib/common_test/src/ct_property_test.erl
@@ -0,0 +1,186 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2014. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% %%%
+%%% WARNING %%%
+%%% %%%
+%%% This is experimental code which may be changed or removed %%%
+%%% anytime without any warning. %%%
+%%% %%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+%%% @doc EXPERIMENTAL support in common-test for calling property based tests.
+%%%
+%%% <p>This module is a first step towards running Property Based testing in the
+%%% Common Test framework. A property testing tool like QuickCheck or PropEr is
+%%% assumed to be installed.</p>
+%%%
+%%% <p>The idea is to have a common_test testsuite calling a property testing
+%%% tool with special property test suites as defined by that tool. In this manual
+%%% we assume the usual Erlang Application directory structure. The tests are
+%%% collected in the application's <c>test</c> directory. The test directory
+%%% has a sub-directory called <c>property_test</c> where everything needed for
+%%% the property tests are collected.</p>
+%%%
+%%% <p>A typical ct test suite using <c>ct_property_test</c> is organized as follows:
+%%% </p>
+%%% ```
+%%% -include_lib("common_test/include/ct.hrl").
+%%%
+%%% all() -> [prop_ftp_case].
+%%%
+%%% init_per_suite(Config) ->
+%%% ct_property_test:init_per_suite(Config).
+%%%
+%%% %%%---- test case
+%%% prop_ftp_case(Config) ->
+%%% ct_property_test:quickcheck(
+%%% ftp_simple_client_server:prop_ftp(Config),
+%%% Config
+%%% ).
+%%% '''
+%%%
+%%% <warning>
+%%% <p>
+%%% This is experimental code which may be changed or removed
+%%% anytime without any warning.
+%%% </p>
+%%% </warning>
+%%%
+%%% @end
+
+-module(ct_property_test).
+
+%% API
+-export([init_per_suite/1,
+ quickcheck/2]).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%%-----------------------------------------------------------------
+%%% @spec init_per_suite(Config) -> Config | {skip,Reason}
+%%%
+%%% @doc Initializes Config for property testing.
+%%%
+%%% <p>The function investigates if support is available for either Quickcheck, PropEr,
+%%% or Triq.
+%%% The options <c>{property_dir,AbsPath}</c> and
+%%% <c>{property_test_tool,Tool}</c> is set in the Config returned.</p>
+%%% <p>The function is intended to be called in the init_per_suite in the test suite.</p>
+%%% <p>The property tests are assumed to be in the subdirectory <c>property_test</c>.</p>
+%%% @end
+
+init_per_suite(Config) ->
+ case which_module_exists([eqc,proper,triq]) of
+ {ok,ToolModule} ->
+ ct:pal("Found property tester ~p",[ToolModule]),
+ Path = property_tests_path("property_test", Config),
+ case compile_tests(Path,ToolModule) of
+ error ->
+ {fail, "Property test compilation failed in "++Path};
+ up_to_date ->
+ add_code_pathz(Path),
+ [{property_dir,Path},
+ {property_test_tool,ToolModule} | Config]
+ end;
+
+ not_found ->
+ ct:pal("No property tester found",[]),
+ {skip, "No property testing tool found"}
+ end.
+
+%%%-----------------------------------------------------------------
+%%% @spec quickcheck(Property, Config) -> true | {fail,Reason}
+%%%
+%%% @doc Call quickcheck and return the result in a form suitable for common_test.
+%%%
+%%% <p>The function is intended to be called in the test cases in the test suite.</p>
+%%% @end
+
+quickcheck(Property, Config) ->
+ Tool = proplists:get_value(property_test_tool,Config),
+ F = function_name(quickcheck, Tool),
+ mk_ct_return( Tool:F(Property), Tool ).
+
+
+%%%================================================================
+%%%
+%%% Local functions
+%%%
+
+%%% Make return values back to the calling Common Test suite
+mk_ct_return(true, _Tool) ->
+ true;
+mk_ct_return(Other, Tool) ->
+ try lists:last(hd(Tool:counterexample()))
+ of
+ {set,{var,_},{call,M,F,Args}} ->
+ {fail, io_lib:format("~p:~p/~p returned bad result",[M,F,length(Args)])}
+ catch
+ _:_ ->
+ {fail, Other}
+ end.
+
+%%% Check if a property testing tool is found
+which_module_exists([Module|Modules]) ->
+ case module_exists(Module) of
+ true -> {ok,Module};
+ false -> which_module_exists(Modules)
+ end;
+which_module_exists(_) ->
+ not_found.
+
+module_exists(Module) ->
+ is_list(catch Module:module_info()).
+
+%%% The path to the property tests
+property_tests_path(Dir, Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ filename:join(lists:droplast(filename:split(DataDir))++[Dir]).
+
+%%% Extend the code path with Dir if it not already present
+add_code_pathz(Dir) ->
+ case lists:member(Dir, code:get_path()) of
+ true -> ok;
+ false -> code:add_pathz(Dir)
+ end.
+
+compile_tests(Path, ToolModule) ->
+ MacroDefs = macro_def(ToolModule),
+ {ok,Cwd} = file:get_cwd(),
+ ok = file:set_cwd(Path),
+ {ok,FileNames} = file:list_dir("."),
+ BeamFiles = [F || F<-FileNames,
+ filename:extension(F) == ".beam"],
+ [file:delete(F) || F<-BeamFiles],
+ ct:pal("Compiling in ~p:~n Deleted ~p~n MacroDefs=~p",[Path,BeamFiles,MacroDefs]),
+ Result = make:all([load|MacroDefs]),
+ file:set_cwd(Cwd),
+ Result.
+
+
+macro_def(eqc) -> [{d, 'EQC'}];
+macro_def(proper) -> [{d, 'PROPER'}];
+macro_def(triq) -> [{d, 'TRIQ'}].
+
+function_name(quickcheck, triq) -> check;
+function_name(F, _) -> F.
+