diff --git a/include/proper_common.hrl b/include/proper_common.hrl index aa7d7b0a..1e456f34 100644 --- a/include/proper_common.hrl +++ b/include/proper_common.hrl @@ -1,7 +1,7 @@ %%% -*- coding: utf-8 -*- %%% -*- erlang-indent-level: 2 -*- %%% ------------------------------------------------------------------- -%%% Copyright 2010-2017 Manolis Papadakis , +%%% Copyright 2010-2020 Manolis Papadakis , %%% Eirini Arvaniti , %%% Kostis Sagonas , %%% and Andreas Löscher @@ -21,7 +21,7 @@ %%% You should have received a copy of the GNU General Public License %%% along with PropEr. If not, see . -%%% @copyright 2010-2017 Manolis Papadakis, Eirini Arvaniti, Kostis Sagonas and Andreas Löscher +%%% @copyright 2010-2020 Manolis Papadakis, Eirini Arvaniti, Kostis Sagonas and Andreas Löscher %%% @version {@version} %%% @author Manolis Papadakis %%% @doc Common parts of user and internal header files @@ -41,8 +41,8 @@ -define(TRAPEXIT(Prop), proper:trapexit(?DELAY(Prop))). -define(TIMEOUT(Limit,Prop), proper:timeout(Limit,?DELAY(Prop))). -define(SETUP(SetupFun,Prop), proper:setup(SetupFun,Prop)). -%% TODO: -define(ALWAYS(Tests,Prop), proper:always(Tests,?DELAY(Prop))). -%% TODO: -define(SOMETIMES(Tests,Prop), proper:sometimes(Tests,?DELAY(Prop))). +-define(ALWAYS(N,Prop), proper:always(N,?DELAY(Prop))). +-define(SOMETIMES(N,Prop), proper:sometimes(N,?DELAY(Prop))). %%------------------------------------------------------------------------------ %% Generator macros diff --git a/src/proper.erl b/src/proper.erl index 315c89c9..d30c448d 100644 --- a/src/proper.erl +++ b/src/proper.erl @@ -64,6 +64,20 @@ %%% wrappers, the test case is rejected (it doesn't count as a failing test %%% case), and PropEr starts over with a new random test case. Also, in %%% verbose mode, an `x' is printed on screen. +%%%
`?ALWAYS(, )`
+%%%
Repeats the `` test `` times. It has to pass exactly `` +%%% times or macro will fail as soon as any of the `` tests fails. It's +%%% useful when shrinking `X`, but `Y` generated based on `X`, makes the test +%%% pass in most cases, because as `X` is getting smaller, `Y` is getting more +%%% likely to pass. Or when hunting for a race condition, which needs several +%%% executions to provoke it during shrinking. +%%% This should not be used at top-level, `` is more suitable for +%%% that.
+%%%
`?SOMETIMES(, )`
+%%%
Repeats the `` test `` times. It has to fail exactly `` +%%% times, for the macro to fail, and passes as soon as any of `` tests +%%% passes - in other words, this macro ensures that `` sometimes +%%% passes.
%%%
`?WHENFAIL(, )'
%%%
The `' field should contain an expression or statement block %%% that produces some side-effect (e.g. prints something to the screen). @@ -387,7 +401,8 @@ -export([get_size/1, global_state_init_size/1, global_state_init_size_seed/2, report_error/2]). -export([pure_check/1, pure_check/2]). --export([forall/2, targeted/2, exists/3, implies/2, whenfail/2, trapexit/1, timeout/2, setup/2]). +-export([forall/2, targeted/2, exists/3, implies/2, whenfail/2, trapexit/1, + always/2, sometimes/2, timeout/2, setup/2]). -export_type([test/0, outer_test/0, counterexample/0, exception/0, false_positive_mfas/0, setup_opts/0]). @@ -1020,6 +1035,16 @@ conjunction(SubProps) -> implies(Pre, DTest) -> {implies, Pre, DTest}. +%% @private +-spec always(pos_integer(), delayed_test()) -> test(). +always(N, DTest) -> + {always, N, DTest}. + +%% @private +-spec sometimes(pos_integer(), delayed_test()) -> test(). +sometimes(N, DTest) -> + {sometimes, N, DTest}. + %% @doc Specifies that test cases produced by this property should be %% categorized under the term `Category'. This field can be an expression or %% statement block that evaluates to any term. All produced categories are @@ -1483,6 +1508,20 @@ run({conjunction, SubProps}, #ctx{mode = try_cexm, bound = Bound} = Ctx, Opts) - [SubTCs] -> run_all(SubProps, SubTCs, Ctx#ctx{bound = []}, Opts); _ -> {error, too_many_instances} end; +run({always, 1, Prop}, Ctx, Opts) -> + force(Prop, Ctx, Opts); +run({always, N, Prop}, Ctx, Opts) -> + case force(Prop, Ctx, Opts) of + #pass{} -> run({always, N-1, Prop}, Ctx, Opts); + #fail{} = Res -> Res + end; +run({sometimes, 1, Prop}, Ctx, Opts) -> + force(Prop, Ctx, Opts); +run({sometimes, N, Prop}, Ctx, Opts) -> + case force(Prop, Ctx, Opts) of + #pass{} = Res -> Res; + #fail{} -> run({sometimes, N-1, Prop}, Ctx, Opts) + end; run({implies, true, Prop}, Ctx, Opts) -> force(Prop, Ctx, Opts); run({implies, false, _Prop}, _Ctx, _Opts) -> @@ -1905,6 +1944,10 @@ skip_to_next({forall,RawType,Prop}) -> {Type, Prop}; skip_to_next({conjunction,SubProps}) -> SubProps; +skip_to_next({always,_N,Prop}) -> + force_skip(Prop); +skip_to_next({sometimes,_N,Prop}) -> + force_skip(Prop); skip_to_next({implies,Pre,Prop}) -> case Pre of true -> force_skip(Prop); diff --git a/test/proper_tests.erl b/test/proper_tests.erl index 44429f95..22818b33 100644 --- a/test/proper_tests.erl +++ b/test/proper_tests.erl @@ -1,7 +1,7 @@ %%% -*- coding: utf-8 -*- %%% -*- erlang-indent-level: 2 -*- %%% ------------------------------------------------------------------- -%%% Copyright 2010-2018 Manolis Papadakis , +%%% Copyright 2010-2020 Manolis Papadakis , %%% Eirini Arvaniti %%% and Kostis Sagonas %%% @@ -928,6 +928,20 @@ true_props_test_() -> ?FORALL(X, ?SIZED(Size,Size), begin inc_temp(X),true end), [{numtests,3},{start_size,4},{max_size,4}]), + ?_assertTempBecomesN(10, true, + ?FORALL(_, 1, + ?ALWAYS(10, begin inc_temp(), true end)), + [{numtests,1}, noshrink]), + ?_assertTempBecomesN(1, true, + ?FORALL(_, 1, + ?SOMETIMES(10, + begin inc_temp(), true end)), + [{numtests,1}, noshrink]), + ?_assertTempBecomesN(5, true, + ?FORALL(_, 1, + ?SOMETIMES(10, + begin inc_temp(), get_temp() == 5 end)), + [{numtests,1}, noshrink]), ?_passes(?FORALL(X, integer(), ?IMPLIES(abs(X) > 1, X * X > X))), ?_passes(?FORALL(X, integer(), ?IMPLIES(X >= 0, true))), ?_passes(?FORALL({X,Lim}, {int(),?SIZED(Size,Size)}, abs(X) =< Lim)), @@ -987,6 +1001,20 @@ false_props_test_() -> ?FORALL(S, ?SIZED(Size,Size), begin inc_temp(), S =< 20 end), [{numtests,3},{max_size,40},noshrink]), + ?_assertTempBecomesN(1, false, + ?FORALL(_, 1, + ?ALWAYS(10, + begin inc_temp(), false end)), + [{numtests,1}, noshrink]), + ?_assertTempBecomesN(5, false, + ?FORALL(_, 1, + ?ALWAYS(10, + begin inc_temp(), get_temp() < 5 end)), + [{numtests,1}, noshrink]), + ?_assertTempBecomesN(10, false, + ?FORALL(_, 1, + ?SOMETIMES(10, begin inc_temp(), false end)), + [{numtests, 1}, noshrink]), ?_failsWithOneOf([[{true,false}],[{false,true}]], ?FORALL({B1,B2}, {boolean(),boolean()}, equals(B1,B2))), ?_failsWith([2,1],