Current section
Files
Jump to
Current section
Files
src/gurka.erl
-module(gurka).
-include("gurka.hrl").
-export([run/1, run/2, run/3]).
run(File) ->
run(File, []).
run(File, Options) ->
Module = list_to_atom("feature_" ++ filename:basename(File, ".feature")),
run(File, Module, Options).
run(File, Module, Options) ->
case gurka_parser:parse(File) of
{ok, Feature} ->
Result = run(Module, Options, undefined, undefined, undefined, Feature),
Passed = has_passed(Result),
if
Passed ->
{ok, Result};
true ->
{fail, Result}
end;
{error, Reason} ->
{error, Reason}
end.
run(Module, Options, FeatureState, _ScenarioState, _PreviousPhase, []) ->
apply(Module, Options, teardown_feature, [FeatureState]),
[];
run(DefaultModule, Options, FeatureState, ScenarioState, PreviousPhase, [Step = #step{phase = feature, action = start, tokens = Tokens} | Steps]) ->
Module = resolve_module(DefaultModule, Tokens),
case PreviousPhase of
feature ->
apply(Module, Options, teardown_feature, [FeatureState]);
background ->
apply(Module, Options, teardown_feature, [FeatureState]);
scenario ->
apply(Module, Options, teardown_scenario, [ScenarioState]),
apply(Module, Options, teardown_feature, [FeatureState]);
_ ->
ok
end,
case apply(Module, Options, setup_feature, [Tokens]) of
{ok, State} ->
[{ok, Step} | run(Module, Options, State, ScenarioState, feature, Steps)];
{error, undef, _Stack} ->
[{ok, Step} | run(Module, Options, undefined, ScenarioState, feature, Steps)];
Term ->
[{Term, Step}]
end;
run(Module, Options, FeatureState, ScenarioState, _PreviousPhase, [Step = #step{phase = background, action = given, tokens = Tokens} | Steps]) ->
case apply(Module, Options, given, [Tokens, FeatureState]) of
Flag when Flag == ok; Flag == true ->
[{ok, Step} | run(Module, Options, FeatureState, ScenarioState, background, Steps)];
{Flag, State} when Flag == ok; Flag == true ->
[{ok, Step} | run(Module, Options, State, ScenarioState, background, Steps)];
Term ->
apply(Module, Options, teardown_feature, [FeatureState]),
[{Term, Step}]
end;
run(Module, Options, FeatureState, ScenarioState, PreviousPhase, [Step = #step{phase = scenario, action = start, tokens = Tokens} | Steps]) ->
case do_tags_match(Options, Step) of
true ->
case PreviousPhase of
scenario ->
apply(Module, Options, teardown_scenario, [ScenarioState]);
_ ->
ok
end,
case apply(Module, Options, setup_scenario, [Tokens, FeatureState]) of
{ok, State} ->
[{ok, Step} | run(Module, Options, FeatureState, State, scenario, Steps)];
{error, undef, _Stack} ->
[{ok, Step} | run(Module, Options, FeatureState, FeatureState, scenario, Steps)];
Term ->
apply(Module, Options, teardown_feature, [FeatureState]),
[{Term, Step}]
end;
_ ->
[{ok, Step#step{action = skip}} | skip_scenario(Module, Options, FeatureState, Steps)]
end;
run(Module, Options, FeatureState, ScenarioState, _PreviousPhase, [Step = #step{phase = scenario, action = Action, tokens = Tokens} | Steps]) when Action == given; Action == 'when'; Action == then ->
case apply(Module, Options, Action, [Tokens, ScenarioState]) of
Flag when Flag == ok; Flag == true ->
[{ok, Step} | run(Module, Options, FeatureState, ScenarioState, scenario, Steps)];
{Flag, State} when Flag == ok; Flag == true ->
[{ok, Step} | run(Module, Options, FeatureState, State, scenario, Steps)];
Term ->
apply(Module, Options, teardown_scenario, [ScenarioState]),
apply(Module, Options, teardown_feature, [FeatureState]),
[{Term, Step}]
end;
run(Module, Options, _FeatureState, ScenarioState, _PreviousPhase, [#step{phase = scenario_outline, action = 'end'}]) ->
apply(Module, Options, teardown_scenario, [ScenarioState]),
[];
run(Module, Options, FeatureState, ScenarioState, PreviousPhase, [Step = #step{phase = scenario_outline, action = start} | Steps]) ->
case PreviousPhase of
scenario ->
apply(Module, Options, teardown_scenario, [ScenarioState]);
_ ->
ok
end,
run_outline(Module, Options, FeatureState, ScenarioState, PreviousPhase, Steps, [Step]);
run(Module, Options, FeatureState, ScenarioState, PreviousPhase, [Step = #step{phase = scenario_outline} | Steps]) ->
run_outline(Module, Options, FeatureState, ScenarioState, PreviousPhase, Steps, [Step]);
run(Module, Options, FeatureState, ScenarioState, _PreviousPhase, [Step = #step{phase = Phase} | Steps]) ->
[{ok, Step} | run(Module, Options, FeatureState, ScenarioState, Phase, Steps)].
run_outline(Module, Options, FeatureState, ScenarioState, _PreviousPhase, [ExamplesStep = #step{phase = scenario_outline, action = examples, row = ExampleRow, tokens = [Headers | Rows]} | Steps], ScenarioOutline) ->
TaggedHeaders = [<<"<", Header/binary, ">">> || Header <- Headers],
{Results, _} = lists:mapfoldl(fun(Row, Count) ->
Example = lists:zip(TaggedHeaders, Row),
Scenario = lists:foldl(fun(Step, ScenarioAcc) ->
Tokens = lists:map(fun(Token) ->
lists:foldr(fun replace_tags/2, Token, Example)
end, Step#step.tokens),
case Step#step.action of
start ->
MergedTags = proplists:get_value(merged_tags, ExamplesStep#step.meta, []),
Meta = [{example_row, {ExampleRow, Count}} | [{merged_tags, MergedTags} | proplists:delete(merged_tags, Step#step.meta)]],
[Step#step{phase = scenario, tokens = Tokens, meta = Meta} | ScenarioAcc];
_ ->
[Step#step{phase = scenario, tokens = Tokens} | ScenarioAcc]
end
end, [#step{phase = scenario_outline, action = 'end'}], ScenarioOutline),
{run(Module, Options, FeatureState, ScenarioState, scenario_outline, Scenario), Count + 1}
end, 1, Rows),
[Results | run_outline(Module, Options, FeatureState, ScenarioState, scenario_outline, Steps, ScenarioOutline)];
run_outline(Module, Options, FeatureState, ScenarioState, PreviousPhase, [Step = #step{phase = scenario_outline} | Steps], ScenarioOutline) ->
run_outline(Module, Options, FeatureState, ScenarioState, PreviousPhase, Steps, [Step | ScenarioOutline]);
run_outline(Module, Options, FeatureState, ScenarioState, PreviousPhase, Steps, _ScenarioOutline) ->
run(Module, Options, FeatureState, ScenarioState, PreviousPhase, Steps).
replace_tags({Pattern, Replacement}, {Term, Subject}) ->
{Term, replace_tags({Pattern, Replacement}, Subject)};
replace_tags({Pattern, Replacement}, Subject) when is_binary(Subject) ->
binary:replace(Subject, Pattern, Replacement, [global]);
replace_tags({Pattern, Replacement}, Subject) when is_list(Subject) ->
[replace_tags({Pattern, Replacement}, Token) || Token <- Subject];
replace_tags(_, Subject) ->
Subject.
do_tags_match(Options, Step) ->
OptionsTags = proplists:get_value(tags, Options, []),
StepTags = proplists:get_value(merged_tags, Step#step.meta, []),
Flag = lists:foldl(
fun(<<$!, OptionTag/binary>>, undefined) -> not lists:member(OptionTag, StepTags);
(<<$+, $!, OptionTag/binary>>, undefined) -> not lists:member(OptionTag, StepTags);
(<<$+, OptionTag/binary>>, undefined) -> lists:member(OptionTag, StepTags);
(true, undefined) -> true;
(OptionTag, undefined) -> lists:member(OptionTag, StepTags);
(<<$!, OptionTag/binary>>, AccIn) -> AccIn or (not lists:member(OptionTag, StepTags));
(<<$+, OptionTag/binary>>, AccIn) -> AccIn and lists:member(OptionTag, StepTags);
(<<$+, $!, OptionTag/binary>>, AccIn) -> AccIn and (not lists:member(OptionTag, StepTags));
(OptionTag, AccIn) -> AccIn or lists:member(OptionTag, StepTags)
end, undefined, OptionsTags),
if
Flag == undefined -> true;
true -> Flag
end.
apply(Module, _Options, Function, Args) ->
case catch erlang:apply(Module, Function, Args) of
{'EXIT', {Reason, Stack}} ->
{error, Reason, Stack};
{'EXIT', Term} ->
{error, Term};
Term ->
Term
end.
has_passed([]) ->
true;
has_passed([Step | Steps]) when is_list(Step) ->
has_passed(Step) and has_passed(Steps);
has_passed([{ok, _} | Steps]) ->
has_passed(Steps);
has_passed(_) ->
false.
resolve_module(Module, []) ->
Module;
resolve_module(_Module, [<<"(module:", Term/binary>> | Pattern]) ->
NewModule = hd(binary:split(Term, <<")">>)),
resolve_module(binary_to_atom(NewModule, utf8), Pattern);
resolve_module(Module, [_ | Pattern]) ->
resolve_module(Module, Pattern).
skip_scenario(Module, Options, FeatureState, []) ->
apply(Module, Options, teardown_feature, [FeatureState]),
[];
skip_scenario(Module, Options, FeatureState, Feature = [#step{action = start} | _Steps]) ->
run(Module, Options, FeatureState, undefined, undefined, Feature);
skip_scenario(Module, Options, FeatureState, [_Step | Steps]) ->
skip_scenario(Module, Options, FeatureState, Steps).