Merge pull request #11152 from keynslug/test/ft-prop-aggressive

test(ft): make proptests even less aggressive
This commit is contained in:
Andrew Mayorov 2023-06-27 17:44:54 +02:00 committed by GitHub
commit 30d78aaac2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 57 deletions

View File

@ -52,12 +52,19 @@
%% Generic Types %% Generic Types
-export([ -export([
scaled/2 scaled/2,
fixedmap/1
]). ]).
%% Iterators %% Iterators
-export([nof/1]). -export([nof/1]).
%% Utilities
-export([
generate/2,
typegen/0
]).
-type proptype() :: proper_types:raw_type(). -type proptype() :: proper_types:raw_type().
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -679,6 +686,36 @@ limited_list(N, T) ->
scaled(F, T) when F > 0 -> scaled(F, T) when F > 0 ->
?SIZED(S, resize(round(S * F), T)). ?SIZED(S, resize(round(S * F), T)).
-spec fixedmap(#{_Key => proptype()}) -> proptype().
fixedmap(M) ->
?LET(PList, maps:to_list(M), maps:from_list(PList)).
%%--------------------------------------------------------------------
%% Utilities
%%--------------------------------------------------------------------
-type typegen() :: {typegen, proper_gen:size(), proper_gen:seed()}.
-spec typegen() -> proptype().
typegen() ->
Seed = {non_neg_integer(), non_neg_integer(), non_neg_integer()},
{typegen, ?SIZED(S, S), Seed}.
-spec generate(proptype(), typegen()) -> _Instance.
generate(T, {typegen, Size, Seed}) ->
% NOTE
% We need to run it in a separate process so that it won't erase
% any proper state in the current process allocated by the property
% being evaluated.
{Pid, MRef} = erlang:spawn_monitor(
fun() -> exit(proper_gen:pick(T, Size, Seed)) end
),
receive
{'DOWN', MRef, process, Pid, Result} ->
{ok, Instance} = Result,
Instance
end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -18,80 +18,92 @@
-include_lib("proper/include/proper.hrl"). -include_lib("proper/include/proper.hrl").
-import(emqx_proper_types, [scaled/2]). -import(emqx_proper_types, [scaled/2, fixedmap/1, typegen/0, generate/2]).
-define(COVERAGE_TIMEOUT, 5000). -define(COVERAGE_TIMEOUT, 10000).
prop_coverage() -> prop_coverage() ->
?FORALL( ?FORALL(
{Filesize, Segsizes}, #{filesize := Filesize, segsizes := Segsizes, typegen := TypeGen},
{filesize_t(), segsizes_t()}, noshrink(
?FORALL( fixedmap(#{
Fragments, filesize => filesize_t(),
noshrink(segments_t(Filesize, Segsizes)), segsizes => segsizes_t(),
?TIMEOUT( typegen => typegen()
?COVERAGE_TIMEOUT, })
begin ),
ASM1 = append_segments(mk_assembly(Filesize), Fragments), ?TIMEOUT(
{Time, ASM2} = timer:tc(emqx_ft_assembly, update, [ASM1]), ?COVERAGE_TIMEOUT,
measure( begin
#{"Fragments" => length(Fragments), "Time" => Time}, Segments = generate(segments_t(Filesize, Segsizes), TypeGen),
case emqx_ft_assembly:status(ASM2) of ASM1 = append_segments(mk_assembly(Filesize), Segments),
complete -> {Time, ASM2} = timer:tc(emqx_ft_assembly, update, [ASM1]),
Coverage = emqx_ft_assembly:coverage(ASM2), measure(
measure( #{"Segments" => length(Segments), "Time" => Time},
#{"CoverageLength" => length(Coverage)}, case emqx_ft_assembly:status(ASM2) of
is_coverage_complete(Coverage) complete ->
); Coverage = emqx_ft_assembly:coverage(ASM2),
{incomplete, {missing, {segment, _, _}}} -> measure(
measure("CoverageLength", 0, true) #{"CoverageLength" => length(Coverage)},
end is_coverage_complete(Coverage)
) );
end {incomplete, {missing, {segment, _, _}}} ->
) measure("CoverageLength", 0, true)
end
)
end
) )
). ).
prop_coverage_likely_incomplete() -> prop_coverage_likely_incomplete() ->
?FORALL( ?FORALL(
{Filesize, Segsizes, Hole}, #{filesize := Filesize, segsizes := Segsizes, hole := HoleIn, typegen := TypeGen},
{filesize_t(), segsizes_t(), filesize_t()}, noshrink(
?FORALL( fixedmap(#{
Fragments, filesize => filesize_t(),
noshrink(segments_t(Filesize, Segsizes, (Hole rem max(Filesize, 1)))), segsizes => segsizes_t(),
?TIMEOUT( hole => filesize_t(),
?COVERAGE_TIMEOUT, typegen => typegen()
begin })
ASM1 = append_segments(mk_assembly(Filesize), Fragments), ),
{Time, ASM2} = timer:tc(emqx_ft_assembly, update, [ASM1]), ?TIMEOUT(?COVERAGE_TIMEOUT, begin
measure( Hole = HoleIn rem max(Filesize, 1),
#{"Fragments" => length(Fragments), "Time" => Time}, Segments = generate(segments_t(Filesize, Segsizes, Hole), TypeGen),
case emqx_ft_assembly:status(ASM2) of ASM1 = append_segments(mk_assembly(Filesize), Segments),
complete -> {Time, ASM2} = timer:tc(emqx_ft_assembly, update, [ASM1]),
% NOTE: this is still possible due to the nature of `SUCHTHATMAYBE` measure(
IsComplete = emqx_ft_assembly:coverage(ASM2), #{"Segments" => length(Segments), "Time" => Time},
collect(complete, is_coverage_complete(IsComplete)); case emqx_ft_assembly:status(ASM2) of
{incomplete, {missing, {segment, _, _}}} -> complete ->
collect(incomplete, true) % NOTE: this is still possible due to the nature of `SUCHTHATMAYBE`
end IsComplete = emqx_ft_assembly:coverage(ASM2),
) collect(complete, is_coverage_complete(IsComplete));
{incomplete, {missing, {segment, _, _}}} ->
collect(incomplete, true)
end end
) )
) end)
). ).
prop_coverage_complete() -> prop_coverage_complete() ->
?FORALL( ?FORALL(
{Filesize, Segsizes}, #{filesize := Filesize, segsizes := Segsizes, node := RemoteNode, typegen := TypeGen},
{filesize_t(), ?SUCHTHAT([BaseSegsize | _], segsizes_t(), BaseSegsize > 0)}, noshrink(
?FORALL( fixedmap(#{
{Fragments, RemoteNode}, filesize => filesize_t(),
noshrink({segments_t(Filesize, Segsizes), remote_node_t()}), segsizes => ?SUCHTHAT([BaseSegsize | _], segsizes_t(), BaseSegsize > 0),
node => remote_node_t(),
typegen => typegen()
})
),
?TIMEOUT(
?COVERAGE_TIMEOUT,
begin begin
% Ensure that we have complete coverage % Ensure that we have complete coverage
Segments = generate(segments_t(Filesize, Segsizes), TypeGen),
ASM1 = mk_assembly(Filesize), ASM1 = mk_assembly(Filesize),
ASM2 = append_coverage(ASM1, RemoteNode, Filesize, Segsizes), ASM2 = append_coverage(ASM1, RemoteNode, Filesize, Segsizes),
ASM3 = append_segments(ASM2, Fragments), ASM3 = append_segments(ASM2, Segments),
{Time, ASM4} = timer:tc(emqx_ft_assembly, update, [ASM3]), {Time, ASM4} = timer:tc(emqx_ft_assembly, update, [ASM3]),
measure( measure(
#{"CoverageMax" => nsegs(Filesize, Segsizes), "Time" => Time}, #{"CoverageMax" => nsegs(Filesize, Segsizes), "Time" => Time},
@ -189,7 +201,7 @@ segment_t(Filesize, Segsizes) ->
). ).
filesize_t() -> filesize_t() ->
scaled(2500, non_neg_integer()). scaled(2000, non_neg_integer()).
segsizes_t() -> segsizes_t() ->
?LET( ?LET(