Add support for @setup_driver and @setup_feature annotations

This commit is contained in:
Emmanuel Briot 2015-06-15 11:24:02 +02:00
parent 31973ca3df
commit b1f55e8e5a
7 changed files with 215 additions and 19 deletions

View File

@ -158,6 +158,13 @@ The comments should start with one of '@given', '@then' or '@when'.
There is no semantic difference, they only act as a way to help
introduce the regexp.
It is recommended that regular expressions always be surrounded with '^' and
'$', to indicate they should match the whole step definition, and not just part
of it.
Parameter types
===============
The regular expressions are matched with the step as found in the
:file:`*.feature` file. The parenthesis groups found in the regexp will be
passed as parameters to the procedure. By default, all parameters are passed as
@ -165,9 +172,22 @@ strings. If you use another scalar type for the parameter, GNATbdd will use a
`Type'Value (...)` before passing the parameter, and raise an error if
the type is incorrect.
It is recommended that regular expressions always be surrounded with '^' and
'$', to indicate they should match the whole step definition, and not just part
of it.
GNATbdd provides specific handling for a few parameter types. Note that the
type must be written exactly as in the table below (case-insensitive), since
GNATbdd does not contain a semantic analyzer to resolve names.
+-------------------+-----------------------------------------------------+
| Type | Conversion from regexp match |
+===================+=====================================================+
| String | The parenthesis group as matched by the regexp |
+-------------------+-----------------------------------------------------+
| Ada.Calendar.Time | Converts a date with GNATCOLL.Utils.Time_Value. |
| | This supports a number of date and time formats, for|
| | instance '2015-06-15T12:00:00Z' or '2015-06-15' or |
| | '15 jun 2015' or 'jun 15, 2015'. |
+-------------------+-----------------------------------------------------+
| others | Use others'Value to convert from string |
+-------------------+-----------------------------------------------------+
.. _using_tables_in_step_definitions:
@ -274,7 +294,6 @@ percent sign, as in::
The predefined regexps are automatically included in a parenthesis group,
so you should not add parenthesis yourself.
Here is the full list of predefined regular expressions:
+---------+----------------------------+-----------------------------+
@ -289,6 +308,11 @@ Here is the full list of predefined regular expressions:
| date | Feb 04, 2014; 2014-02-04 | String or Ada.Calendar.Time |
+---------+----------------------------+-----------------------------+
You can use the percent symbol twice ("%%") when you need to ignore a
string matching one of the predefined regular expressions. For instance,
"%%integer" would match the string "%integer" in the feature file. When
you only when to insert a percent sign before any other string, you do not
need to duplicate it though (so "%test" can be written exactly as is).
Predefined steps
================
@ -343,6 +367,36 @@ As usual, any python file found in the :file:`features/step_definitions`
directory or the one set through :option:`--steps` will be analyzed,
and those that use the `@step_regexp` decorator.
Set up
------
Often, the code for the step definitions will need some initialization
(connecting to a database, opening a web browser,...). It is possible
to declare any number of such initialization subprograms.
Some of them will be run once per execution of the driver, others will
be executed one per feature file.
These subprograms are also declared in the step definitions files, as
in::
package My_Steps is
@setup_driver
procedure Init_Driver;
@setup_feature
procedure Init_Feature;
end My_Steps;
The subprograms never take any parameter. Since they might be called
multiple times, they also need to clean up any initialization they might
have done before.
Most of the time, it is better for these procedures to be written directly
as specific steps in the tests, so `@setup_feature` should in general be
replaced with a `Background` section in the feature file.
Asynchronous tests
==================

View File

@ -34,7 +34,8 @@ package body BDD.Main is
-- Main --
----------
procedure Main (Self : in out BDD.Runner.Feature_Runner) is
procedure Main (Self : in out BDD.Runner.Feature_Runner'Class)
is
Parser : BDD.Parser.Feature_Parser;
Format : access BDD.Formatters.Formatter'Class;
Media : Media_Writer_Access;

View File

@ -27,8 +27,8 @@ with BDD.Runner;
package BDD.Main is
procedure Main (Self : in out BDD.Runner.Feature_Runner);
procedure Main (Self : in out BDD.Runner.Feature_Runner'Class);
-- The main loop, which discovers and then runs all the tests
-- Self should have been initialized with Add_Step_Runner first
-- Self should have been initialized with Add_Step_Runner first.
end BDD.Main;

View File

@ -60,7 +60,7 @@ package BDD.Parser is
Feature : BDD.Features.Feature) is null;
-- Called when the last line of a feature has been seen.
type Feature_Parser is tagged private;
type Feature_Parser is tagged null record;
procedure Parse
(Self : in out Feature_Parser;
@ -72,7 +72,4 @@ package BDD.Parser is
--
-- Raises Syntax_Error when the file does not contain valid syntax.
private
type Feature_Parser is tagged null record;
end BDD.Parser;

View File

@ -78,14 +78,14 @@ package body BDD.Runner is
is
begin
if Self.Files /= null then
Self.Run_Start;
Feature_Runner'Class (Self).Run_Start;
Self.Format := Format;
Sort (Self.Files.all);
for F in Self.Files'Range loop
Parser.Parse (Self.Files (F), Self);
end loop;
Self.Run_End;
Feature_Runner'Class (Self).Run_End;
Self.Format := null;
Set_Exit_Status

View File

@ -52,9 +52,9 @@ package BDD.Runner is
-- Register one or more features file explicitly.
procedure Run
(Self : in out Feature_Runner;
Format : not null access BDD.Formatters.Formatter'Class;
Parser : in out BDD.Parser.Feature_Parser'Class);
(Self : in out Feature_Runner;
Format : not null access BDD.Formatters.Formatter'Class;
Parser : in out BDD.Parser.Feature_Parser'Class);
-- Run all features and their scenarios.
--
-- Each of the features file is parsed through Parser. This allows you to

View File

@ -47,8 +47,14 @@ package body Gnatbdd.Codegen is
Case_Insensitive or Single_Line);
Cst_Package_Re : constant Pattern_Matcher :=
Compile ("^package ([_\.\w]+)", Case_Insensitive or Multiple_Lines);
Cst_Comment_Re : constant Pattern_Matcher :=
Compile ("--\s*@(given|then|when)\s+");
-- Matches the special comments before step definitions
Cst_Setup_Re : constant Pattern_Matcher :=
Compile ("--\s*@setup_(driver|feature)\s*$", Multiple_Lines);
-- Matches the special comments before initialization procedures
Predefined_Regexps : constant Substitution_Array :=
(1 => (new String'("integer"),
@ -59,7 +65,7 @@ package body Gnatbdd.Codegen is
new String'("(\+?\d+)")),
4 => (new String'("date"),
new String'(
"((?:" -- date part
"((?:" -- date part
& "\d{4}/\d{2}/\d{2}" -- 2014/01/02
& "|"
& "\d{2}/\d{2}/\d{4}" -- 01/02/2014
@ -70,7 +76,8 @@ package body Gnatbdd.Codegen is
& "\d{2}:\d{2}:\d{2}" -- hh:mm:ss
& "(?:\s*[+-]\d{2})" -- optional time zone within time
& "))"
))
)),
5 => (new String'("%"), new String'("%"))
);
type Generated_Data is record
@ -78,6 +85,9 @@ package body Gnatbdd.Codegen is
-- Code extract that, for a given step, checks all known step definition
-- and execute the corresponding subprogram if needed.
Setup_Driver, Setup_Feature : Unbounded_String;
-- Code extract that calls all the initializers defined by the user.
Regexps : Unbounded_String;
-- Code extract that declares all the regexps used by the step
-- definitions.
@ -124,6 +134,18 @@ package body Gnatbdd.Codegen is
-- Found is set to True if at least one step definition was found, and left
-- unchanged otherwise
procedure Parse_Setup_Subprogram_Def
(Contents : String;
Package_Name : String;
Setup_Type : String;
Found : in out Boolean;
Pos : in out Integer;
Data : in out Generated_Data);
-- Parse the definition of an initialization subprogram.
-- Pos is left after the declaration (if the latter could
-- be parsed).
-- Found is set to True if an initialization subprogram was found.
type Param_Description is record
Name : GNAT.Strings.String_Access;
Of_Type : GNAT.Strings.String_Access;
@ -470,6 +492,68 @@ package body Gnatbdd.Codegen is
Free (List);
end Parse_Subprogram_Def;
--------------------------------
-- Parse_Setup_Subprogram_Def --
--------------------------------
procedure Parse_Setup_Subprogram_Def
(Contents : String;
Package_Name : String;
Setup_Type : String;
Found : in out Boolean;
Pos : in out Integer;
Data : in out Generated_Data)
is
Name : constant String := "@setup_" & Setup_Type;
Matches : Match_Array (0 .. 3);
Subprogram : GNAT.Strings.String_Access;
begin
Skip_Blanks (Contents, Pos);
if Pos > Contents'Last
or else not Starts_With
(Contents (Pos .. Contents'Last), Cst_Procedure)
then
Put_Line
(Standard_Error,
"Error: The step definition for '" & Name & "' must be"
& " followed immediately by its subprogram");
Ada.Command_Line.Set_Exit_Status (Failure);
return;
end if;
Match (Cst_Procedure_Re, Contents, Matches, Data_First => Pos);
if Matches (1) = No_Match then
Put_Line
(Standard_Error,
"Could not find name of subprogram for '" & Name & "'");
Ada.Command_Line.Set_Exit_Status (Failure);
return;
end if;
Pos := Matches (0).Last;
Subprogram := new String'
(Package_Name & '.'
& Contents (Matches (1).First .. Matches (1).Last));
if Matches (3) /= No_Match then
Put_Line
(Standard_Error,
"Subprogram for " & Name & " should take no parameter");
Ada.Command_Line.Set_Exit_Status (Failure);
return;
end if;
if Setup_Type = "driver" then
Append (Data.Setup_Driver, " " & Subprogram.all & ";");
elsif Setup_Type = "feature" then
Append (Data.Setup_Feature, " " & Subprogram.all & ";");
end if;
Found := True;
Free (Subprogram);
end Parse_Setup_Subprogram_Def;
-----------------
-- Check_Steps --
-----------------
@ -524,6 +608,27 @@ package body Gnatbdd.Codegen is
Pos => Pos);
end loop;
-- Parse the initialization functions
Pos := Contents'First;
while Pos <= Contents'Last loop
Match (Cst_Setup_Re, Contents.all, Matches, Data_First => Pos);
exit when Matches (0) = No_Match;
Pos := Matches (0).Last;
Skip_Blanks (Contents.all, Pos);
Last := EOL (Contents (Pos .. Contents'Last));
Start := Pos;
Pos := Last + 1; -- After ASCII.LF
Parse_Setup_Subprogram_Def
(Contents.all,
Package_Name => Contents (Pack_Start .. Pack_End),
Setup_Type => Contents (Matches (1).First .. Matches (1).Last),
Found => Found,
Data => Data,
Pos => Pos);
end loop;
if Found then
Append (Data.Withs,
"with " & Contents (Pack_Start .. Pack_End)
@ -637,7 +742,46 @@ package body Gnatbdd.Codegen is
Put_Line (F, " end Run_Steps;");
New_Line (F);
Put_Line (F, " Runner : Feature_Runner;");
-- Feature_Runner
Put_Line (F, " type Auto_Feature_Runner is new Feature_Runner");
Put_Line (F, " with null record;");
if Data.Setup_Driver /= "" then
Put_Line (F, " overriding procedure Run_Start");
Put_Line (F, " (Self : in out Auto_Feature_Runner);");
end if;
if Data.Setup_Feature /= "" then
Put_Line (F, " overriding procedure Feature_Start");
Put_Line (F, " (Self : in out Auto_Feature_Runner;");
Put_Line (F, " Feature : BDD.Features.Feature);");
end if;
New_Line (F);
if Data.Setup_Driver /= "" then
Put_Line (F, " overriding procedure Run_Start");
Put_Line (F, " (Self : in out Auto_Feature_Runner) is");
Put_Line (F, " begin");
Put_Line (F, To_String (Data.Setup_Driver));
Put_Line (F, " Run_Start (Feature_Runner (Self)); -- inherited");
Put_Line (F, " end Run_Start;");
New_Line (F);
end if;
if Data.Setup_Feature /= "" then
Put_Line (F, " overriding procedure Feature_Start");
Put_Line (F, " (Self : in out Auto_Feature_Runner;");
Put_Line (F, " Feature : BDD.Features.Feature) is");
Put_Line (F, " begin");
Put_Line (F, To_String (Data.Setup_Feature));
Put_Line (F, " Feature_Start (Feature_Runner (Self), Feature);");
Put_Line (F, " end Feature_Start;");
New_Line (F);
end if;
Put_Line (F, " Runner : Auto_Feature_Runner;");
Put_Line (F, "begin");
Put_Line
(F, " Runner.Add_Step_Runner (Run_Steps'Unrestricted_Access);");