Packages

Property-based testing and metamorphic testing combinator library for Gleam

Current section

Files

Jump to
metamon src metamon@internal@runner.erl
Raw

src/metamon@internal@runner.erl

-module(metamon@internal@runner).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/metamon/internal/runner.gleam").
-export([plain/3, equivariant/4, morph_name/1, run_assert_morph/4, run_forall_morph_n/6, run_forall_morphs/5, run_forall/4, run_forall_morph/5]).
-export_type([morph_spec/2, step/1, outcome/0, evaluation/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(false).
-opaque morph_spec(GWN, GWO) :: {plain_spec,
binary(),
metamon@transform:transform(GWN),
metamon@relation:relation(GWO)} |
{equivariant_spec,
binary(),
metamon@transform:transform(GWN),
metamon@transform:transform(GWO),
metamon@relation:relation(GWO)}.
-type step(GWP) :: continue | {stop, binary()} | {gleam_phantom, GWP}.
-type outcome() :: done | {halted, binary()}.
-type evaluation(GWQ) :: {evaluation, binary(), GWQ, GWQ, boolean()}.
-file("src/metamon/internal/runner.gleam", 48).
?DOC(false).
-spec plain(
binary(),
metamon@transform:transform(GWR),
metamon@relation:relation(GWT)
) -> morph_spec(GWR, GWT).
plain(Name, Transform, Relation) ->
{plain_spec, Name, Transform, Relation}.
-file("src/metamon/internal/runner.gleam", 58).
?DOC(false).
-spec equivariant(
binary(),
metamon@transform:transform(GWX),
metamon@transform:transform(GWZ),
metamon@relation:relation(GWZ)
) -> morph_spec(GWX, GWZ).
equivariant(Name, Input_transform, Output_transform, Relation) ->
{equivariant_spec, Name, Input_transform, Output_transform, Relation}.
-file("src/metamon/internal/runner.gleam", 216).
?DOC(false).
-spec render_for(
metamon@config:config(),
metamon@internal@report:failure_report()
) -> binary().
render_for(Cfg, Report) ->
case metamon@config:output_format(Cfg) of
text ->
metamon@internal@report:render(Report);
json ->
metamon@internal@report:render_json(Report)
end.
-file("src/metamon/internal/runner.gleam", 266).
?DOC(false).
-spec compute_n_outputs(
list(metamon@transform:transform(GYP)),
fun((GYP) -> GYS),
GYP
) -> list(GYS).
compute_n_outputs(Transforms, F, Input) ->
Source_output = F(Input),
Followups = gleam@list:map(
Transforms,
fun(T) -> F((erlang:element(3, T))(Input)) end
),
[Source_output | Followups].
-file("src/metamon/internal/runner.gleam", 277).
?DOC(false).
-spec morph_n_failure(
metamon@config:config(),
binary(),
list(metamon@transform:transform(GYU)),
metamon@relation:relation_n(GYX),
integer(),
GYU,
metamon@internal@report:input_source(),
list(GYX)
) -> binary().
morph_n_failure(
Cfg,
Test_name,
Transforms,
Rel,
Run_index,
Input,
Source,
Outputs
) ->
Transform_names = begin
_pipe = gleam@list:map(Transforms, fun(T) -> erlang:element(2, T) end),
gleam@string:join(_pipe, <<", "/utf8>>)
end,
Outputs_rendered = begin
_pipe@1 = gleam@list:index_map(
Outputs,
fun(Out, I) ->
<<<<<<" ["/utf8, (erlang:integer_to_binary(I))/binary>>/binary,
"] "/utf8>>/binary,
(gleam@string:inspect(Out))/binary>>
end
),
gleam@string:join(_pipe@1, <<"\n"/utf8>>)
end,
erlang:list_to_binary(
[<<"× n-ary metamorphic relation `"/utf8>>,
erlang:element(2, Rel),
<<"` failed\n test: "/utf8>>,
Test_name,
<<"\n config seed: "/utf8>>,
metamon@internal@report:format_config_seed(
metamon@generator@seed:state(metamon@config:seed(Cfg)),
metamon@generator@seed:original_input(metamon@config:seed(Cfg))
),
<<"\n runs: "/utf8>>,
erlang:integer_to_binary(Run_index),
<<" / "/utf8>>,
erlang:integer_to_binary(metamon@config:runs(Cfg)),
<<"\n source: "/utf8>>,
case Source of
{edge_source, I@1} ->
<<<<"edge("/utf8, (erlang:integer_to_binary(I@1))/binary>>/binary,
")"/utf8>>;
{random_source, S, Sz} ->
<<<<<<<<"random(seed="/utf8,
(erlang:integer_to_binary(S))/binary>>/binary,
", size="/utf8>>/binary,
(erlang:integer_to_binary(Sz))/binary>>/binary,
")"/utf8>>
end,
<<"\n transforms: "/utf8>>,
Transform_names,
<<"\n source input:\n "/utf8>>,
gleam@string:inspect(Input),
<<"\n outputs (source first, then each follow-up):\n"/utf8>>,
Outputs_rendered]
).
-file("src/metamon/internal/runner.gleam", 415).
?DOC(false).
-spec run_edges(
list(GZJ),
integer(),
fun((GZJ, metamon@internal@report:input_source(), integer()) -> step(GZJ))
) -> outcome().
run_edges(Edges, Index, Body) ->
case Edges of
[] ->
done;
[First | Rest] ->
case Body(First, {edge_source, Index}, Index) of
continue ->
run_edges(Rest, Index + 1, Body);
{stop, Text} ->
{halted, Text}
end
end.
-file("src/metamon/internal/runner.gleam", 456).
?DOC(false).
-spec scaled_size(integer(), integer(), integer()) -> integer().
scaled_size(Index, Total, Max_size) ->
case Total =< 1 of
true ->
Max_size;
false ->
case (Total - 1) of
0 -> 0;
Gleam@denominator -> Index * Max_size div Gleam@denominator
end
end.
-file("src/metamon/internal/runner.gleam", 430).
?DOC(false).
-spec run_random(
metamon@config:config(),
metamon@generator:generator(GZM),
integer(),
integer(),
metamon@generator@seed:seed(),
fun((GZM, metamon@internal@report:input_source(), integer()) -> step(GZM))
) -> outcome().
run_random(Cfg, Gen, Start_index, Remaining, S, Body) ->
case Remaining =< 0 of
true ->
done;
false ->
{Here, Rest_seed} = metamon@generator@seed:split(S),
Size = scaled_size(
Start_index,
metamon@config:runs(Cfg),
metamon@config:max_size(Cfg)
),
Value = erlang:element(
2,
metamon@generator:generate(Gen, Here, Size)
),
case Body(
Value,
{random_source, metamon@generator@seed:state(Here), Size},
Start_index
) of
continue ->
run_random(
Cfg,
Gen,
Start_index + 1,
Remaining - 1,
Rest_seed,
Body
);
{stop, Text} ->
{halted, Text}
end
end.
-file("src/metamon/internal/runner.gleam", 465).
?DOC(false).
-spec forall_failure(
metamon@config:config(),
binary(),
metamon@generator:generator(GZP),
fun((GZP) -> boolean()),
integer(),
GZP,
metamon@internal@report:input_source()
) -> binary().
forall_failure(Cfg, Test_name, _, _, Run_index, Input, Source) ->
Report = {failure_report,
<<"(plain property)"/utf8>>,
Test_name,
metamon@generator@seed:state(metamon@config:seed(Cfg)),
metamon@generator@seed:original_input(metamon@config:seed(Cfg)),
Run_index,
metamon@config:runs(Cfg),
0,
false,
none,
<<""/utf8>>,
gleam@string:inspect(Input),
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
Source,
false,
metamon@annotate:current_annotations(),
metamon@annotate:current_footnotes(),
{some, metamon@coverage:snapshot()}},
render_for(Cfg, Report).
-file("src/metamon/internal/runner.gleam", 503).
?DOC(false).
-spec evaluate_spec(morph_spec(GZR, GZS), fun((GZR) -> GZS), GZR) -> evaluation(GZS).
evaluate_spec(Spec, F, Input) ->
case Spec of
{plain_spec, _, T, R} ->
Fol_in = (erlang:element(3, T))(Input),
Src_out = F(Input),
Fol_out = F(Fol_in),
{evaluation,
gleam@string:inspect(Fol_in),
Src_out,
Fol_out,
(erlang:element(3, R))(Src_out, Fol_out)};
{equivariant_spec, _, Ti, To, R@1} ->
Fol_in@1 = (erlang:element(3, Ti))(Input),
Raw_src_out = F(Input),
Src_out@1 = (erlang:element(3, To))(Raw_src_out),
Fol_out@1 = F(Fol_in@1),
{evaluation,
gleam@string:inspect(Fol_in@1),
Src_out@1,
Fol_out@1,
(erlang:element(3, R@1))(Src_out@1, Fol_out@1)}
end.
-file("src/metamon/internal/runner.gleam", 535).
?DOC(false).
-spec morph_mode_of(morph_spec(any(), any())) -> metamon@internal@report:morph_mode().
morph_mode_of(Spec) ->
case Spec of
{plain_spec, _, T, _} ->
{plain, erlang:element(2, T)};
{equivariant_spec, _, Ti, To, _} ->
{equivariant, erlang:element(2, Ti), erlang:element(2, To)}
end.
-file("src/metamon/internal/runner.gleam", 543).
?DOC(false).
-spec relation_name_of(morph_spec(any(), any())) -> binary().
relation_name_of(Spec) ->
case Spec of
{plain_spec, _, _, R} ->
erlang:element(2, R);
{equivariant_spec, _, _, _, R@1} ->
erlang:element(2, R@1)
end.
-file("src/metamon/internal/runner.gleam", 550).
?DOC(false).
-spec spec_name(morph_spec(any(), any())) -> binary().
spec_name(Spec) ->
case Spec of
{plain_spec, Name, _, _} ->
Name;
{equivariant_spec, Name@1, _, _, _} ->
Name@1
end.
-file("src/metamon/internal/runner.gleam", 73).
?DOC(false).
-spec morph_name(morph_spec(any(), any())) -> binary().
morph_name(Spec) ->
spec_name(Spec).
-file("src/metamon/internal/runner.gleam", 600).
?DOC(false).
-spec morph_failure_static(
binary(),
morph_spec(HAO, HAP),
HAO,
evaluation(HAP),
integer(),
integer()
) -> metamon@internal@report:failure_report().
morph_failure_static(
Test_name,
Spec,
Input,
Evaluation,
Run_index,
Config_seed_value
) ->
{failure_report,
spec_name(Spec),
Test_name,
Config_seed_value,
none,
Run_index,
1,
0,
false,
{some, morph_mode_of(Spec)},
relation_name_of(Spec),
gleam@string:inspect(Input),
erlang:element(2, Evaluation),
gleam@string:inspect(erlang:element(3, Evaluation)),
gleam@string:inspect(erlang:element(4, Evaluation)),
{edge_source, 0},
true,
metamon@annotate:current_annotations(),
metamon@annotate:current_footnotes(),
none}.
-file("src/metamon/internal/runner.gleam", 651).
?DOC(false).
-spec shrink_loop(
metamon@generator@tree:tree(HAY),
morph_spec(HAY, HBA),
fun((HAY) -> HBA),
HAY,
integer(),
integer()
) -> {HAY, integer(), boolean()}.
shrink_loop(Current_tree, Spec, F, Best, Budget, Done) ->
case Done >= Budget of
true ->
{Best, Done, true};
false ->
case metamon@generator@tree:shrinks_find(
erlang:element(3, Current_tree),
fun(Child) ->
Evaluation = evaluate_spec(
Spec,
F,
erlang:element(2, Child)
),
not erlang:element(5, Evaluation)
end
) of
{error, _} ->
{Best, Done, false};
{ok, Child@1} ->
shrink_loop(
Child@1,
Spec,
F,
erlang:element(2, Child@1),
Budget,
Done + 1
)
end
end.
-file("src/metamon/internal/runner.gleam", 633).
?DOC(false).
-spec shrink_morph_input(
metamon@config:config(),
metamon@generator:generator(HAT),
morph_spec(HAT, HAV),
fun((HAT) -> HAV),
HAT,
metamon@internal@report:input_source()
) -> {HAT, integer(), boolean()}.
shrink_morph_input(Cfg, Gen, Spec, F, Current, Source) ->
case Source of
{edge_source, _} ->
{Current, 0, false};
{random_source, Seed_value, Size} ->
Initial_tree = metamon@generator:generate(
Gen,
metamon@generator@seed:seed(Seed_value),
Size
),
shrink_loop(
Initial_tree,
Spec,
F,
Current,
metamon@config:shrink_limit(Cfg),
0
)
end.
-file("src/metamon/internal/runner.gleam", 557).
?DOC(false).
-spec morph_failure(
metamon@config:config(),
binary(),
metamon@generator:generator(HAI),
morph_spec(HAI, HAK),
fun((HAI) -> HAK),
integer(),
HAI,
metamon@internal@report:input_source(),
evaluation(HAK)
) -> binary().
morph_failure(
Cfg,
Test_name,
Gen,
Spec,
F,
Run_index,
Input,
Source,
Evaluation
) ->
{Shrunk_input, Shrinks_count, Capped} = shrink_morph_input(
Cfg,
Gen,
Spec,
F,
Input,
Source
),
Final_eval = evaluate_spec(Spec, F, Shrunk_input),
Report = {failure_report,
spec_name(Spec),
Test_name,
metamon@generator@seed:state(metamon@config:seed(Cfg)),
metamon@generator@seed:original_input(metamon@config:seed(Cfg)),
Run_index,
metamon@config:runs(Cfg),
Shrinks_count,
Capped,
{some, morph_mode_of(Spec)},
relation_name_of(Spec),
gleam@string:inspect(Shrunk_input),
erlang:element(2, Final_eval),
gleam@string:inspect(erlang:element(3, Final_eval)),
gleam@string:inspect(erlang:element(4, Final_eval)),
Source,
metamon@config:diff_enabled(Cfg),
metamon@annotate:current_annotations(),
metamon@annotate:current_footnotes(),
{some, metamon@coverage:snapshot()}},
_ = Evaluation,
render_for(Cfg, Report).
-file("src/metamon/internal/runner.gleam", 676).
?DOC(false).
-spec reset_state() -> nil.
reset_state() ->
metamon@annotate:reset(),
metamon@coverage:reset().
-file("src/metamon/internal/runner.gleam", 713).
?DOC(false).
-spec panic_with(binary()) -> nil.
panic_with(Message) ->
erlang:error(#{gleam_error => panic,
message => Message,
file => <<?FILEPATH/utf8>>,
module => <<"metamon/internal/runner"/utf8>>,
function => <<"panic_with"/utf8>>,
line => 714}).
-file("src/metamon/internal/runner.gleam", 198).
?DOC(false).
-spec run_assert_morph(binary(), morph_spec(GYF, GYG), fun((GYF) -> GYG), GYF) -> nil.
run_assert_morph(Test_name, Spec, F, Input) ->
reset_state(),
Evaluation = evaluate_spec(Spec, F, Input),
case erlang:element(5, Evaluation) of
true ->
nil;
false ->
Report = morph_failure_static(
Test_name,
Spec,
Input,
Evaluation,
0,
0
),
panic_with(render_for(metamon@config:default_config(), Report))
end.
-file("src/metamon/internal/runner.gleam", 681).
?DOC(false).
-spec finish_with_coverage_check(
metamon@config:config(),
binary(),
gleam@option:option(binary())
) -> nil.
finish_with_coverage_check(_, Test_name, Morph_name) ->
Snap = metamon@coverage:snapshot(),
case metamon@coverage:first_shortfall(Snap) of
none ->
nil;
{some, Req} ->
Prefix = case Morph_name of
none ->
<<<<"× property `"/utf8, Test_name/binary>>/binary,
"`"/utf8>>;
{some, Name} ->
<<<<<<<<"× metamorphic relation `"/utf8, Name/binary>>/binary,
"` ("/utf8>>/binary,
Test_name/binary>>/binary,
")"/utf8>>
end,
Message = <<<<<<<<<<<<<<<<<<<<<<Prefix/binary,
" passed but coverage shortfall: label `"/utf8>>/binary,
(erlang:element(2, Req))/binary>>/binary,
"` hit "/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(4, Req)
))/binary>>/binary,
"/"/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(2, Snap)
))/binary>>/binary,
" ("/utf8>>/binary,
(gleam_stdlib:float_to_string(
metamon@coverage:actual_pct(
erlang:element(4, Req),
erlang:element(2, Snap)
)
))/binary>>/binary,
"%, target≥"/utf8>>/binary,
(gleam_stdlib:float_to_string(
metamon@coverage:target_pct_of(
Req,
erlang:element(2, Snap)
)
))/binary>>/binary,
"%)"/utf8>>,
panic_with(Message)
end.
-file("src/metamon/internal/runner.gleam", 717).
?DOC(false).
-spec take_first(list(HBE), integer()) -> list(HBE).
take_first(Items, N) ->
case {N, Items} of
{N@1, _} when N@1 =< 0 ->
[];
{_, []} ->
[];
{N@2, [First | Rest]} ->
[First | take_first(Rest, N@2 - 1)]
end.
-file("src/metamon/internal/runner.gleam", 394).
?DOC(false).
-spec iterate_inputs(
metamon@config:config(),
metamon@generator:generator(GZG),
fun((GZG, metamon@internal@report:input_source(), integer()) -> step(GZG))
) -> outcome().
iterate_inputs(Cfg, Gen, Body) ->
Edges = begin
_pipe = metamon@generator:edges_of(Gen),
take_first(_pipe, metamon@config:max_edges(Cfg))
end,
Edge_count = erlang:length(Edges),
Run_total = metamon@config:runs(Cfg),
case run_edges(Edges, 0, Body) of
{halted, Text} ->
{halted, Text};
done ->
Remaining = Run_total - Edge_count,
case Remaining =< 0 of
true ->
done;
false ->
run_random(
Cfg,
Gen,
Edge_count,
Remaining,
metamon@config:seed(Cfg),
Body
)
end
end.
-file("src/metamon/internal/runner.gleam", 226).
?DOC(false).
-spec run_forall_morph_n(
metamon@config:config(),
binary(),
metamon@generator:generator(GYJ),
list(metamon@transform:transform(GYJ)),
metamon@relation:relation_n(GYN),
fun((GYJ) -> GYN)
) -> nil.
run_forall_morph_n(Cfg, Test_name, Gen, Transforms, Rel, F) ->
case Transforms of
[] ->
panic_with(
<<"metamon.forall_morph_n: empty transforms list (vacuous test). Pass at least one Transform or use forall(...) for a single-input property."/utf8>>
);
_ ->
nil
end,
reset_state(),
case iterate_inputs(
Cfg,
Gen,
fun(Input, Source, Run_index) ->
Outputs = compute_n_outputs(Transforms, F, Input),
case (erlang:element(3, Rel))(Outputs) of
true ->
continue;
false ->
{stop,
morph_n_failure(
Cfg,
Test_name,
Transforms,
Rel,
Run_index,
Input,
Source,
Outputs
)}
end
end
) of
done ->
finish_with_coverage_check(
Cfg,
Test_name,
{some, erlang:element(2, Rel)}
);
{halted, Text} ->
panic_with(Text)
end.
-file("src/metamon/internal/runner.gleam", 331).
?DOC(false).
-spec run_forall_morphs(
metamon@config:config(),
binary(),
metamon@generator:generator(GZA),
list(morph_spec(GZA, GZC)),
fun((GZA) -> GZC)
) -> nil.
run_forall_morphs(Cfg, Test_name, Gen, Specs, F) ->
case Specs of
[] ->
panic_with(
<<"metamon.forall_morphs: empty MR list (vacuous test). Pass at least one MR or use forall(...) for a single-input property."/utf8>>
);
_ ->
nil
end,
Collected = gleam@list:fold(
Specs,
[],
fun(Reports, Spec) ->
reset_state(),
Outcome = iterate_inputs(
Cfg,
Gen,
fun(Input, Source, Run_index) ->
Evaluation = evaluate_spec(Spec, F, Input),
case erlang:element(5, Evaluation) of
true ->
continue;
false ->
{stop,
morph_failure(
Cfg,
Test_name,
Gen,
Spec,
F,
Run_index,
Input,
Source,
Evaluation
)}
end
end
),
case Outcome of
done ->
Reports;
{halted, Text} ->
[Text | Reports]
end
end
),
case Collected of
[] ->
nil;
Failures ->
panic_with(
<<"× one or more metamorphic relations failed\n\n"/utf8,
(gleam@string:join(
lists:reverse(Failures),
<<"\n\n---\n\n"/utf8>>
))/binary>>
)
end.
-file("src/metamon/internal/runner.gleam", 727).
?DOC(false).
-spec load_regression_for(metamon@config:config(), binary()) -> list(metamon@internal@regression:entry()).
load_regression_for(Cfg, Mr_name) ->
case metamon@config:regression_file(Cfg) of
none ->
[];
{some, Path} ->
case simplifile:read(Path) of
{ok, Content} ->
_pipe = metamon@internal@regression:parse(Content),
gleam@list:filter(
_pipe,
fun(Entry) -> erlang:element(2, Entry) =:= Mr_name end
);
{error, _} ->
[]
end
end.
-file("src/metamon/internal/runner.gleam", 846).
?DOC(false).
-spec list_at(list(HBU), integer()) -> gleam@option:option(HBU).
list_at(Items, Index) ->
case {Items, Index} of
{[], _} ->
none;
{[First | _], 0} ->
{some, First};
{[_ | Rest], I} ->
list_at(Rest, I - 1)
end.
-file("src/metamon/internal/runner.gleam", 824).
?DOC(false).
-spec reproduce_entry_value(
metamon@generator:generator(HBS),
metamon@internal@regression:entry()
) -> HBS.
reproduce_entry_value(Gen, Entry) ->
case erlang:element(6, Entry) of
{some, I} ->
case list_at(metamon@generator:edges_of(Gen), I) of
{some, Value} ->
Value;
none ->
erlang:element(
2,
metamon@generator:generate(
Gen,
metamon@generator@seed:seed(
erlang:element(3, Entry)
),
erlang:element(5, Entry)
)
)
end;
none ->
erlang:element(
2,
metamon@generator:generate(
Gen,
metamon@generator@seed:seed(erlang:element(3, Entry)),
erlang:element(5, Entry)
)
)
end.
-file("src/metamon/internal/runner.gleam", 854).
?DOC(false).
-spec replay_failure_message(
binary(),
metamon@internal@regression:entry(),
any()
) -> binary().
replay_failure_message(Mr_name, Entry, Value) ->
erlang:list_to_binary(
[<<"× regression replay for `"/utf8>>,
Mr_name,
<<"` failed\n recorded: "/utf8>>,
erlang:element(8, Entry),
<<"\n config_seed: "/utf8>>,
erlang:integer_to_binary(erlang:element(3, Entry)),
<<"\n run_index: "/utf8>>,
erlang:integer_to_binary(erlang:element(4, Entry)),
<<"\n size: "/utf8>>,
erlang:integer_to_binary(erlang:element(5, Entry)),
<<"\n reproduced input:\n "/utf8>>,
gleam@string:inspect(Value),
case erlang:element(7, Entry) of
{some, N} ->
<<"\n note:\n "/utf8, N/binary>>;
none ->
<<""/utf8>>
end]
).
-file("src/metamon/internal/runner.gleam", 787).
?DOC(false).
-spec replay_regression_for_forall(
metamon@generator:generator(HBJ),
list(metamon@internal@regression:entry()),
fun((HBJ) -> boolean())
) -> outcome().
replay_regression_for_forall(Gen, Entries, Property) ->
case Entries of
[] ->
done;
[Entry | Rest] ->
Value = reproduce_entry_value(Gen, Entry),
case Property(Value) of
true ->
replay_regression_for_forall(Gen, Rest, Property);
false ->
{halted,
replay_failure_message(
<<"(plain property)"/utf8>>,
Entry,
Value
)}
end
end.
-file("src/metamon/internal/runner.gleam", 805).
?DOC(false).
-spec replay_regression_for_morph(
metamon@generator:generator(HBM),
morph_spec(HBM, HBO),
fun((HBM) -> HBO),
list(metamon@internal@regression:entry())
) -> outcome().
replay_regression_for_morph(Gen, Spec, F, Entries) ->
case Entries of
[] ->
done;
[Entry | Rest] ->
Value = reproduce_entry_value(Gen, Entry),
Evaluation = evaluate_spec(Spec, F, Value),
case erlang:element(5, Evaluation) of
true ->
replay_regression_for_morph(Gen, Spec, F, Rest);
false ->
{halted,
replay_failure_message(spec_name(Spec), Entry, Value)}
end
end.
-file("src/metamon/internal/runner.gleam", 742).
?DOC(false).
-spec record_regression_for(
metamon@config:config(),
binary(),
metamon@internal@report:input_source(),
any()
) -> nil.
record_regression_for(Cfg, Mr_name, Source, Input) ->
case metamon@config:regression_file(Cfg) of
none ->
nil;
{some, Path} ->
Edge_index = case Source of
{edge_source, I} ->
{some, I};
{random_source, _, _} ->
none
end,
{Seed_value, Size} = case Source of
{edge_source, _} ->
{metamon@generator@seed:state(metamon@config:seed(Cfg)), 0};
{random_source, S, Sz} ->
{S, Sz}
end,
Run_index = case Source of
{edge_source, I@1} ->
I@1;
{random_source, _, _} ->
0
end,
Entry = {entry,
Mr_name,
Seed_value,
Run_index,
Size,
Edge_index,
{some, gleam@string:inspect(Input)},
erlang:integer_to_binary(metamon_ffi:now_microseconds())},
Existing = case simplifile:read(Path) of
{ok, C} ->
C;
{error, _} ->
<<""/utf8>>
end,
Prefix = case Existing of
<<""/utf8>> ->
<<(metamon@internal@regression:version_header())/binary,
"\n\n"/utf8>>;
_ ->
<<Existing/binary, "\n"/utf8>>
end,
_ = simplifile:write(
Path,
<<Prefix/binary,
(metamon@internal@regression:render(Entry))/binary>>
),
nil
end.
-file("src/metamon/internal/runner.gleam", 108).
?DOC(false).
-spec forall_step(
metamon@config:config(),
binary(),
metamon@generator:generator(GXM),
fun((GXM) -> boolean()),
GXM,
metamon@internal@report:input_source(),
integer()
) -> step(GXM).
forall_step(Cfg, Test_name, Gen, Property, Input, Source, Run_index) ->
case Property(Input) of
true ->
continue;
false ->
record_regression_for(
Cfg,
<<"(plain property)"/utf8>>,
Source,
Input
),
{stop,
forall_failure(
Cfg,
Test_name,
Gen,
Property,
Run_index,
Input,
Source
)}
end.
-file("src/metamon/internal/runner.gleam", 92).
?DOC(false).
-spec drive_forall(
metamon@config:config(),
binary(),
metamon@generator:generator(GXK),
fun((GXK) -> boolean())
) -> nil.
drive_forall(Cfg, Test_name, Gen, Property) ->
Outcome = iterate_inputs(
Cfg,
Gen,
fun(Input, Source, Run_index) ->
forall_step(Cfg, Test_name, Gen, Property, Input, Source, Run_index)
end
),
case Outcome of
done ->
finish_with_coverage_check(Cfg, Test_name, none);
{halted, Report_text} ->
panic_with(Report_text)
end.
-file("src/metamon/internal/runner.gleam", 78).
?DOC(false).
-spec run_forall(
metamon@config:config(),
binary(),
metamon@generator:generator(GXI),
fun((GXI) -> boolean())
) -> nil.
run_forall(Cfg, Test_name, Gen, Property) ->
reset_state(),
Regression_entries = load_regression_for(Cfg, <<"(plain property)"/utf8>>),
case replay_regression_for_forall(Gen, Regression_entries, Property) of
{halted, Text} ->
panic_with(Text);
done ->
drive_forall(Cfg, Test_name, Gen, Property)
end.
-file("src/metamon/internal/runner.gleam", 167).
?DOC(false).
-spec morph_step(
metamon@config:config(),
binary(),
metamon@generator:generator(GXZ),
morph_spec(GXZ, GYB),
fun((GXZ) -> GYB),
GXZ,
metamon@internal@report:input_source(),
integer()
) -> step(GXZ).
morph_step(Cfg, Test_name, Gen, Spec, F, Input, Source, Run_index) ->
Evaluation = evaluate_spec(Spec, F, Input),
case erlang:element(5, Evaluation) of
true ->
continue;
false ->
record_regression_for(Cfg, spec_name(Spec), Source, Input),
{stop,
morph_failure(
Cfg,
Test_name,
Gen,
Spec,
F,
Run_index,
Input,
Source,
Evaluation
)}
end.
-file("src/metamon/internal/runner.gleam", 150).
?DOC(false).
-spec drive_morph(
metamon@config:config(),
binary(),
metamon@generator:generator(GXU),
morph_spec(GXU, GXW),
fun((GXU) -> GXW)
) -> nil.
drive_morph(Cfg, Test_name, Gen, Spec, F) ->
Outcome = iterate_inputs(
Cfg,
Gen,
fun(Input, Source, Run_index) ->
morph_step(Cfg, Test_name, Gen, Spec, F, Input, Source, Run_index)
end
),
case Outcome of
done ->
finish_with_coverage_check(Cfg, Test_name, {some, spec_name(Spec)});
{halted, Report_text} ->
panic_with(Report_text)
end.
-file("src/metamon/internal/runner.gleam", 135).
?DOC(false).
-spec run_forall_morph(
metamon@config:config(),
binary(),
metamon@generator:generator(GXP),
morph_spec(GXP, GXR),
fun((GXP) -> GXR)
) -> nil.
run_forall_morph(Cfg, Test_name, Gen, Spec, F) ->
reset_state(),
Entries = load_regression_for(Cfg, spec_name(Spec)),
case replay_regression_for_morph(Gen, Spec, F, Entries) of
{halted, Text} ->
panic_with(Text);
done ->
drive_morph(Cfg, Test_name, Gen, Spec, F)
end.