Skip to content

Adds ?ALWAYS and ?SOMETIMES macros #230

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions include/proper_common.hrl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%%% -*- coding: utf-8 -*-
%%% -*- erlang-indent-level: 2 -*-
%%% -------------------------------------------------------------------
%%% Copyright 2010-2017 Manolis Papadakis <[email protected]>,
%%% Copyright 2010-2020 Manolis Papadakis <[email protected]>,
%%% Eirini Arvaniti <[email protected]>,
%%% Kostis Sagonas <[email protected]>,
%%% and Andreas Löscher <[email protected]>
Expand All @@ -21,7 +21,7 @@
%%% You should have received a copy of the GNU General Public License
%%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.

%%% @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
Expand All @@ -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
Expand Down
45 changes: 44 additions & 1 deletion src/proper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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.</dd>
%%% <dt>`?ALWAYS(<N>, <Prop>)`</dt>
%%% <dd>Repeats the `<Prop>` test `<N>` times. It has to pass exactly `<N>`
%%% times or macro will fail as soon as any of the `<Prop>` 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, `<numtests/2>` is more suitable for
%%% that.</dd>
%%% <dt>`?SOMETIMES(<N>, <Prop>)`</dt>
%%% <dd>Repeats the `<Prop>` test `<N>` times. It has to fail exactly `<N>`
%%% times, for the macro to fail, and passes as soon as any of `<Prop>` tests
%%% passes - in other words, this macro ensures that `<Prop>` sometimes
%%% passes.</dd>
%%% <dt>`?WHENFAIL(<Action>, <Prop>)'</dt>
%%% <dd>The `<Action>' field should contain an expression or statement block
%%% that produces some side-effect (e.g. prints something to the screen).
Expand Down Expand Up @@ -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]).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) ->
Expand Down Expand Up @@ -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);
Expand Down
30 changes: 29 additions & 1 deletion test/proper_tests.erl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%%% -*- coding: utf-8 -*-
%%% -*- erlang-indent-level: 2 -*-
%%% -------------------------------------------------------------------
%%% Copyright 2010-2018 Manolis Papadakis <[email protected]>,
%%% Copyright 2010-2020 Manolis Papadakis <[email protected]>,
%%% Eirini Arvaniti <[email protected]>
%%% and Kostis Sagonas <[email protected]>
%%%
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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],
Expand Down