1--
2-- Copyright (c) 2007-2011 Tero Koskinen <tero.koskinen@iki.fi>
3--
4-- Permission to use, copy, modify, and distribute this software for any
5-- purpose with or without fee is hereby granted, provided that the above
6-- copyright notice and this permission notice appear in all copies.
7--
8-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15--
16
17with GNAT.Traceback.Symbolic;
18with GNAT.Regpat;
19with Ada.Strings;
20with Ada.Unchecked_Deallocation;
21with Ada.Exceptions;
22with Ahven.Long_AStrings;
23
24package body Ahven.Framework is
25   use Ahven.AStrings;
26
27   -- Convert an exception into a traceback, but truncate it at the first
28   -- line matching "0x.* in ahven.framework" as this is unwanted trace.
29   function Filtered_Traceback (E : in Ada.Exceptions.Exception_Occurrence)
30   return String is
31      ftb : constant String := GNAT.Traceback.Symbolic.Symbolic_Traceback (E);
32      pat : constant String := "(0x[0-9a-f]* in ahven\.framework)";
33      reg : constant GNAT.Regpat.Pattern_Matcher := GNAT.Regpat.Compile (pat);
34      result : GNAT.Regpat.Match_Array (0 .. 1);
35      use type GNAT.Regpat.Match_Location;
36   begin
37      GNAT.Regpat.Match (reg, ftb, result);
38      if result (0) = GNAT.Regpat.No_Match then
39         return ftb;
40      else
41         return ftb (1 .. result (1).First - 2);
42      end if;
43   end Filtered_Traceback;
44
45   -- A few local procedures, so we do not need to duplicate code.
46   procedure Free_Test is
47      new Ada.Unchecked_Deallocation (Object => Test'Class,
48                                      Name   => Test_Class_Access);
49
50   generic
51      with procedure Action is <>;
52   procedure Execute_Internal
53     (Test_Object     : in out Test'Class;
54      Listener_Object : in out Listeners.Result_Listener'Class);
55   -- Logic for Execute procedures. Action is specified by the caller.
56
57   -- Helper function to reduce some typing.
58   function To_Bounded (Source : String) return AStrings.Bounded_String is
59   begin
60      return To_Bounded_String (Source => Source,
61                                Drop   => Ada.Strings.Right);
62   end To_Bounded;
63
64   function Name_In_List (Name : AStrings.Bounded_String;
65                          List_Of_Names : Name_List.List)
66     return Boolean is
67      use type Name_List.Cursor;
68
69      Pos : Name_List.Cursor := Name_List.First (List_Of_Names);
70   begin
71      loop
72         exit when not Name_List.Is_Valid (Pos);
73
74         if Name_List.Data (Pos) = Name then
75            return True;
76         end if;
77
78         Pos := Name_List.Next (Pos);
79      end loop;
80      return False;
81   end Name_In_List;
82
83   function Name_In_List (Name : String; List_Of_Names : Name_List.List)
84     return Boolean is
85   begin
86      return Name_In_List (To_Bounded (Name), List_Of_Names);
87   end Name_In_List;
88
89
90   ----------- Indefinite_Test_List -------------------
91
92
93   package body Indefinite_Test_List is
94      procedure Remove (Ptr : Node_Access) is
95         procedure Free is
96           new Ada.Unchecked_Deallocation (Object => Node,
97                                           Name   => Node_Access);
98         My_Ptr : Node_Access := Ptr;
99      begin
100         Ptr.Next := null;
101         Free_Test (My_Ptr.Data);
102         My_Ptr.Data := null;
103         Free (My_Ptr);
104      end Remove;
105
106      procedure Append (Target : in out List;
107                        Node_Data : Test'Class) is
108         New_Node : constant Node_Access :=
109           new Node'(Data => new Test'Class'(Node_Data), Next => null);
110      begin
111         if Target.Last = null then
112            Target.Last := New_Node;
113            Target.First := New_Node;
114         else
115            Target.Last.Next := New_Node;
116            Target.Last := New_Node;
117         end if;
118      end Append;
119
120      procedure Clear (Target : in out List) is
121         Current_Node : Node_Access := Target.First;
122         Next_Node    : Node_Access := null;
123      begin
124         while Current_Node /= null loop
125            Next_Node := Current_Node.Next;
126            Remove (Current_Node);
127            Current_Node := Next_Node;
128         end loop;
129
130         Target.First := null;
131         Target.Last := null;
132      end Clear;
133
134      procedure For_Each (Target : List) is
135         Current_Node : Node_Access := Target.First;
136      begin
137         while Current_Node /= null loop
138            Action (Current_Node.Data.all);
139            Current_Node := Current_Node.Next;
140         end loop;
141      end For_Each;
142
143      procedure Initialize (Target : in out List) is
144      begin
145         Target.Last := null;
146         Target.First := null;
147      end Initialize;
148
149      procedure Finalize (Target : in out List) is
150      begin
151         Clear (Target);
152      end Finalize;
153
154      procedure Adjust (Target : in out List) is
155         Target_Last  : Node_Access := null;
156         Target_First : Node_Access := null;
157         Current      : Node_Access := Target.First;
158         New_Node     : Node_Access;
159      begin
160         while Current /= null loop
161            New_Node := new Node'(Data => new Test'Class'(Current.Data.all),
162                                  Next => null);
163
164            if Target_Last = null then
165               Target_First := New_Node;
166            else
167               Target_Last.Next := New_Node;
168            end if;
169            Target_Last := New_Node;
170
171            Current := Current.Next;
172         end loop;
173         Target.First := Target_First;
174         Target.Last := Target_Last;
175      end Adjust;
176   end Indefinite_Test_List;
177
178
179   ----------- Test type -------------------
180
181
182   procedure Set_Up (T : in out Test) is
183   begin
184      null; -- empty by default
185   end Set_Up;
186
187   procedure Tear_Down (T : in out Test) is
188   begin
189      null; -- empty by default
190   end Tear_Down;
191
192   procedure Run (T         : in out Test;
193                  Listener  : in out Listeners.Result_Listener'Class) is
194   begin
195      Run (T => Test'Class (T), Listener => Listener, Timeout => 0.0);
196   end Run;
197
198   procedure Run (T          : in out Test;
199                  Test_Names :        Name_List.List;
200                  Listener   : in out Listeners.Result_Listener'Class) is
201   begin
202      Run (T          => Test'Class (T),
203           Test_Names => Test_Names,
204           Listener   => Listener,
205           Timeout    => 0.0);
206   end Run;
207
208   procedure Execute_Internal
209     (Test_Object     : in out Test'Class;
210      Listener_Object : in out Listeners.Result_Listener'Class)
211   is
212      use Ahven.Listeners;
213   begin
214      -- This Start_Test here is called for Test_Suites and Test_Cases.
215      -- Info includes only the name of the test suite/case.
216      --
217      -- There is a separate Start_Test/End_Test pair for test routines
218      -- in the Run (T : in out Test_Case; ...) procedure.
219      Listeners.Start_Test
220        (Listener_Object,
221         (Phase     => TEST_BEGIN,
222          Test_Name => To_Bounded_String (Get_Name (Test_Object)),
223          Test_Kind => CONTAINER));
224
225      Action;
226
227      -- Like Start_Test, only for Test_Suites and Test_Cases.
228      Listeners.End_Test
229        (Listener_Object,
230         (Phase     => TEST_END,
231          Test_Name => To_Bounded_String (Get_Name (Test_Object)),
232          Test_Kind => CONTAINER));
233   end Execute_Internal;
234
235   function Test_Count (T : Test; Test_Name : String) return Test_Count_Type
236   is
237      A_List : Name_List.List := Name_List.Empty_List;
238   begin
239      Name_List.Append (A_List, To_Bounded (Test_Name));
240
241      return Test_Count (Test'Class (T), A_List);
242   end Test_Count;
243
244   procedure Execute (T        : in out Test'Class;
245                      Listener : in out Listeners.Result_Listener'Class;
246                      Timeout  :        Test_Duration) is
247      procedure Run_Impl is
248      begin
249         Run (T, Listener, Timeout);
250      end Run_Impl;
251
252      procedure Execute_Impl is new Execute_Internal (Action => Run_Impl);
253   begin
254      Execute_Impl (Test_Object => T, Listener_Object => Listener);
255   end Execute;
256
257   procedure Execute (T          : in out Test'Class;
258                      Test_Names :        Name_List.List;
259                      Listener   : in out Listeners.Result_Listener'Class;
260                      Timeout    :        Test_Duration) is
261      procedure Run_Impl is
262      begin
263         Run (T          => T,
264              Test_Names => Test_Names,
265              Listener   => Listener,
266              Timeout    => Timeout);
267      end Run_Impl;
268
269      procedure Execute_Impl is new Execute_Internal (Action => Run_Impl);
270   begin
271      Execute_Impl (Test_Object => T, Listener_Object => Listener);
272   end Execute;
273
274
275   ----------- Test_Case ------------------------------
276
277
278   -- Wrap an "object" routine inside a Test_Command record
279   -- and add it to the test command list.
280   --
281   -- Name of the test will be silently cut if it does not
282   -- fit completely into AStrings.Bounded_String.
283   procedure Add_Test_Routine (T       : in out Test_Case'Class;
284                               Routine :        Object_Test_Routine_Access;
285                               Name    :        String)
286   is
287      Command : constant Test_Command :=
288        (Command_Kind   => OBJECT,
289         Name           => To_Bounded (Source => Name),
290         Object_Routine => Routine);
291   begin
292      Test_Command_List.Append (T.Routines, Command);
293   end Add_Test_Routine;
294
295   -- Wrap a "simple" routine inside a Test_Command record
296   -- and add it to the test command list.
297   --
298   -- Name of the test will be silently cut if it does not
299   -- fit completely into AStrings.Bounded_String.
300   procedure Add_Test_Routine (T       : in out Test_Case'Class;
301                               Routine :        Simple_Test_Routine_Access;
302                               Name    :        String)
303   is
304      Command : constant Test_Command :=
305        (Command_Kind   => SIMPLE,
306         Name           => To_Bounded (Source => Name),
307         Simple_Routine => Routine);
308   begin
309      Test_Command_List.Append (T.Routines, Command);
310   end Add_Test_Routine;
311
312   -- The heart of the package.
313   -- Run one test routine (well, Command at this point) and
314   -- notify listeners about the result.
315   procedure Run_Command (Command  :        Test_Command;
316                          Info     :        Listeners.Context;
317                          Timeout  :        Test_Duration;
318                          Listener : in out Listeners.Result_Listener'Class;
319                          T        : in out Test_Case'Class) is
320      use Ahven.Listeners;
321      use Ahven.Long_AStrings;
322
323      type Test_Status is
324        (TEST_PASS, TEST_FAIL, TEST_ERROR, TEST_TIMEOUT, TEST_SKIP);
325
326      protected type Test_Results is
327         function Get_Status return Test_Status;
328         procedure Set_Status (Value : Test_Status);
329
330         function Get_Message return AStrings.Bounded_String;
331         procedure Set_Message (Value : AStrings.Bounded_String);
332
333         function Get_Long_Message return Long_AStrings.Bounded_String;
334         procedure Set_Long_Message (Value : Long_AStrings.Bounded_String);
335      private
336         Status : Test_Status := TEST_ERROR;
337         Message : AStrings.Bounded_String;
338         Long_Message : Long_AStrings.Bounded_String;
339      end Test_Results;
340
341      protected body Test_Results is
342         function Get_Status return Test_Status is
343         begin
344            return Status;
345         end Get_Status;
346
347         procedure Set_Status (Value : Test_Status) is
348         begin
349            Status := Value;
350         end Set_Status;
351
352         function Get_Message return AStrings.Bounded_String is
353         begin
354            return Message;
355         end Get_Message;
356
357         procedure Set_Message (Value : AStrings.Bounded_String) is
358         begin
359            Message := Value;
360         end Set_Message;
361
362         function Get_Long_Message return Long_AStrings.Bounded_String is
363         begin
364            return Long_Message;
365         end Get_Long_Message;
366
367         procedure Set_Long_Message (Value : Long_AStrings.Bounded_String) is
368         begin
369            Long_Message := Value;
370         end Set_Long_Message;
371      end Test_Results;
372
373      Result : Test_Results;
374
375      task type Command_Task is
376         entry Start_Command;
377         entry End_Command;
378      end Command_Task;
379
380      procedure Run_A_Command is
381         procedure Set_Status (S            :        Test_Status;
382                               Message      :        String;
383                               Long_Message :        String;
384                               R            : in out Test_Results)
385         is
386         begin
387            R.Set_Status (S);
388            R.Set_Message (To_Bounded (Source => Message));
389            R.Set_Long_Message (To_Bounded_String
390              (Source => Long_Message,
391               Drop   => Ada.Strings.Right));
392         end Set_Status;
393      begin
394         begin
395            Run (Command, T);
396            Result.Set_Status (TEST_PASS);
397         exception
398            when E : Assertion_Error =>
399               Set_Status
400                 (S            => TEST_FAIL,
401                  Message      => Ada.Exceptions.Exception_Message (E),
402                  Long_Message => Filtered_Traceback (E),
403                  R            => Result);
404            when E : Test_Skipped_Error =>
405               Set_Status
406                 (S            => TEST_SKIP,
407                  Message      => Ada.Exceptions.Exception_Message (E),
408                  Long_Message => Filtered_Traceback (E),
409                  R            => Result);
410            when E : others =>
411               Set_Status
412                 (S            => TEST_ERROR,
413                  Message      => Ada.Exceptions.Exception_Message (E),
414                  Long_Message => Filtered_Traceback (E),
415                  R            => Result);
416         end;
417      end Run_A_Command;
418
419      task body Command_Task is
420      begin
421         accept Start_Command;
422         Run_A_Command;
423         accept End_Command;
424      end Command_Task;
425
426      Status : Test_Status;
427
428   begin
429      if Timeout > 0.0 then
430         declare
431            Command_Runner : Command_Task;
432         begin
433            Command_Runner.Start_Command;
434            select
435               Command_Runner.End_Command;
436            or
437               delay Duration (Timeout);
438               abort Command_Runner;
439               Result.Set_Status (TEST_TIMEOUT);
440            end select;
441         end;
442      else
443         Run_A_Command;
444      end if;
445      Status := Result.Get_Status;
446
447      case Status is
448         when TEST_PASS =>
449            Listeners.Add_Pass (Listener, Info);
450         when TEST_FAIL =>
451            Listeners.Add_Failure
452              (Listener,
453               (Phase        => TEST_RUN,
454                Test_Name    => Info.Test_Name,
455                Test_Kind    => CONTAINER,
456                Routine_Name => Info.Routine_Name,
457                Message      => Result.Get_Message,
458                Long_Message => Long_AStrings.Null_Bounded_String));
459         when TEST_ERROR =>
460            Listeners.Add_Error
461              (Listener,
462               (Phase        => Listeners.TEST_RUN,
463                Test_Name    => Info.Test_Name,
464                Test_Kind    => CONTAINER,
465                Routine_Name => Info.Routine_Name,
466                Message      => Result.Get_Message,
467                Long_Message => Result.Get_Long_Message));
468         when TEST_TIMEOUT =>
469            Listeners.Add_Error
470              (Listener,
471               (Phase        => Listeners.TEST_RUN,
472                Test_Name    => Info.Test_Name,
473                Test_Kind    => CONTAINER,
474                Routine_Name => Info.Routine_Name,
475                Message      => To_Bounded_String ("TIMEOUT"),
476                Long_Message => Long_AStrings.Null_Bounded_String));
477         when TEST_SKIP =>
478            Listeners.Add_Skipped
479              (Listener,
480               (Phase        => TEST_RUN,
481                Test_Name    => Info.Test_Name,
482                Test_Kind    => CONTAINER,
483                Routine_Name => Info.Routine_Name,
484                Message      => Result.Get_Message,
485                Long_Message => Long_AStrings.Null_Bounded_String));
486      end case;
487   end Run_Command;
488
489   function Get_Name (T : Test_Case) return String is
490   begin
491      return To_String (T.Name);
492   end Get_Name;
493
494   procedure Run_Internal
495     (T            : in out Test_Case;
496      Listener     : in out Listeners.Result_Listener'Class;
497      Command      :        Test_Command;
498      Test_Name    :        String;
499      Routine_Name :        String;
500      Timeout      :        Test_Duration)
501   is
502      use Ahven.Listeners;
503   begin
504      Listeners.Start_Test
505        (Listener,
506         (Phase     => Ahven.Listeners.TEST_BEGIN,
507          Test_Name => To_Bounded_String (Test_Name),
508          Test_Kind => ROUTINE));
509      Run_Command (Command  => Command,
510                   Info     =>
511                     (Phase        => Listeners.TEST_RUN,
512                      Test_Name    => To_Bounded_String (Test_Name),
513                      Test_Kind    => ROUTINE,
514                      Routine_Name =>
515                        To_Bounded_String (Routine_Name),
516                      Message      => AStrings.Null_Bounded_String,
517                      Long_Message => Long_AStrings.Null_Bounded_String),
518                   Timeout  => Timeout,
519                   Listener => Listener,
520                   T        => T);
521      Listeners.End_Test
522        (Listener,
523         (Phase          => Ahven.Listeners.TEST_END,
524          Test_Name      => To_Bounded_String (Test_Name),
525          Test_Kind      => ROUTINE));
526   end Run_Internal;
527
528   -- Run procedure for Test_Case.
529   --
530   -- Loops over the test routine list and executes the routines.
531   procedure Run (T        : in out Test_Case;
532                  Listener : in out Listeners.Result_Listener'Class;
533                  Timeout  :        Test_Duration)
534   is
535      procedure Exec (Cmd : in out Test_Command) is
536      begin
537         Run_Internal (T            => T,
538                       Listener     => Listener,
539                       Command      => Cmd,
540                       Timeout      => Timeout,
541                       Test_Name    => Get_Name (T),
542                       Routine_Name => To_String (Cmd.Name));
543      end Exec;
544
545      procedure Run_All is new Test_Command_List.For_Each
546        (Action => Exec);
547   begin
548      Run_All (T.Routines);
549   end Run;
550
551   -- Purpose of the procedure is to run all
552   -- test routines with name Test_Name.
553   procedure Run (T          : in out Test_Case;
554                  Test_Names :        Name_List.List;
555                  Listener   : in out Listeners.Result_Listener'Class;
556                  Timeout    :        Test_Duration)
557   is
558      procedure Exec (Cmd : in out Test_Command) is
559      begin
560         if Name_In_List (Cmd.Name, Test_Names) then
561            Run_Internal (T            => T,
562                          Listener     => Listener,
563                          Command      => Cmd,
564                          Timeout      => Timeout,
565                          Test_Name    => Get_Name (T),
566                          Routine_Name => To_String (Cmd.Name));
567         end if;
568      end Exec;
569
570      procedure Run_All is new Test_Command_List.For_Each (Action => Exec);
571   begin
572      Run_All (T.Routines);
573   end Run;
574
575   function Test_Count (T : Test_Case) return Test_Count_Type is
576   begin
577      return Test_Count_Type (Test_Command_List.Length (T.Routines));
578   end Test_Count;
579
580   function Test_Count (T : Test_Case; Test_Names : Name_List.List)
581     return Test_Count_Type
582   is
583      use Test_Command_List;
584
585      Counter  : Test_Count_Type := 0;
586
587      procedure Increase (Cmd : in out Test_Command) is
588      begin
589         if Name_In_List (Cmd.Name, Test_Names) then
590            Counter := Counter + 1;
591         end if;
592      end Increase;
593
594      procedure Count_Commands is new
595        Test_Command_List.For_Each (Action => Increase);
596   begin
597      Count_Commands (T.Routines);
598
599      return Counter;
600   end Test_Count;
601
602   procedure Finalize (T : in out Test_Case) is
603   begin
604      Test_Command_List.Clear (T.Routines);
605   end Finalize;
606
607   procedure Set_Name (T : in out Test_Case; Name : String) is
608   begin
609      T.Name := To_Bounded (Source => Name);
610   end Set_Name;
611
612
613   ----------- Test_Suite -----------------------------
614
615
616   function Create_Suite (Suite_Name : String)
617     return Test_Suite_Access is
618   begin
619      return
620        new Test_Suite'
621          (Ada.Finalization.Controlled with
622           Suite_Name        => To_Bounded (Source => Suite_Name),
623           Test_Cases        => Test_List.Empty_List,
624           Static_Test_Cases => Indefinite_Test_List.Empty_List);
625   end Create_Suite;
626
627   function Create_Suite (Suite_Name : String)
628     return Test_Suite is
629   begin
630      return (Ada.Finalization.Controlled with
631              Suite_Name        => To_Bounded (Source => Suite_Name),
632              Test_Cases        => Test_List.Empty_List,
633              Static_Test_Cases => Indefinite_Test_List.Empty_List);
634   end Create_Suite;
635
636   procedure Add_Test (Suite : in out Test_Suite; T : Test_Class_Access) is
637   begin
638      Test_List.Append (Suite.Test_Cases, (Ptr => T));
639   end Add_Test;
640
641   procedure Add_Test (Suite : in out Test_Suite; T : Test_Suite_Access) is
642   begin
643      Add_Test (Suite, Test_Class_Access (T));
644   end Add_Test;
645
646   procedure Add_Static_Test
647     (Suite : in out Test_Suite; T : Test'Class) is
648   begin
649      Indefinite_Test_List.Append (Suite.Static_Test_Cases, T);
650   end Add_Static_Test;
651
652   function Get_Name (T : Test_Suite) return String is
653   begin
654      return To_String (T.Suite_Name);
655   end Get_Name;
656
657   procedure Run (T        : in out Test_Suite;
658                  Listener : in out Listeners.Result_Listener'Class;
659                  Timeout  :        Test_Duration)
660   is
661      -- Some nested procedure exercises here.
662      --
663      -- Execute_Cases is for normal test list
664      -- and Execute_Static_Cases is for indefinite test list.
665
666      -- A helper procedure which runs Execute for the given test.
667      procedure Execute_Test (Current : in out Test'Class) is
668      begin
669         Execute (Current, Listener, Timeout);
670      end Execute_Test;
671
672      procedure Execute_Test_Ptr (Current : in out Test_Class_Wrapper) is
673      begin
674         Execute (Current.Ptr.all, Listener, Timeout);
675      end Execute_Test_Ptr;
676
677      procedure Execute_Static_Cases is
678        new Indefinite_Test_List.For_Each (Action => Execute_Test);
679      procedure Execute_Cases is
680        new Test_List.For_Each (Action => Execute_Test_Ptr);
681   begin
682      Execute_Cases (T.Test_Cases);
683      Execute_Static_Cases (T.Static_Test_Cases);
684   end Run;
685
686   procedure Run (T          : in out Test_Suite;
687                  Test_Names :        Name_List.List;
688                  Listener   : in out Listeners.Result_Listener'Class;
689                  Timeout    :        Test_Duration)
690   is
691      procedure Execute_Test (Current : in out Test'Class) is
692      begin
693         if Name_In_List (Get_Name (Current), Test_Names) then
694            Execute (T => Current, Listener => Listener, Timeout => Timeout);
695         else
696            Execute (T          => Current,
697                     Test_Names => Test_Names,
698                     Listener   => Listener,
699                     Timeout    => Timeout);
700         end if;
701      end Execute_Test;
702
703      procedure Execute_Test_Ptr (Current : in out Test_Class_Wrapper) is
704      begin
705         Execute_Test (Current.Ptr.all);
706      end Execute_Test_Ptr;
707
708      procedure Execute_Cases is
709        new Test_List.For_Each (Action => Execute_Test_Ptr);
710
711      procedure Execute_Static_Cases is
712        new Indefinite_Test_List.For_Each (Action => Execute_Test);
713   begin
714      if Name_In_List (T.Suite_Name, Test_Names) then
715         Run (T, Listener, Timeout);
716      else
717         Execute_Cases (T.Test_Cases);
718         Execute_Static_Cases (T.Static_Test_Cases);
719      end if;
720   end Run;
721
722   function Test_Count (T : Test_Suite) return Test_Count_Type is
723      Counter : Test_Count_Type := 0;
724
725      procedure Inc_Counter (Test_Obj : in out Test'Class) is
726      begin
727         Counter := Counter + Test_Count (Test_Obj);
728      end Inc_Counter;
729
730      procedure Inc_Counter_Ptr (Wrapper : in out Test_Class_Wrapper) is
731      begin
732         Inc_Counter (Wrapper.Ptr.all);
733      end Inc_Counter_Ptr;
734   begin
735      declare
736         use Test_List;
737         procedure Count_All is new For_Each (Action => Inc_Counter_Ptr);
738      begin
739         Count_All (T.Test_Cases);
740      end;
741
742      declare
743         use Indefinite_Test_List;
744         procedure Count_All is new For_Each (Action => Inc_Counter);
745      begin
746         Count_All (T.Static_Test_Cases);
747      end;
748
749      return Counter;
750   end Test_Count;
751
752   function Test_Count (T : Test_Suite; Test_Names : Name_List.List)
753     return Test_Count_Type is
754      Counter : Test_Count_Type := 0;
755
756      procedure Handle_Test (Test_Object : in out Test'Class) is
757      begin
758         if Name_In_List (Get_Name (Test_Object), Test_Names) then
759            Counter := Counter + Test_Count (Test_Object);
760         else
761            Counter := Counter + Test_Count (Test_Object, Test_Names);
762         end if;
763      end Handle_Test;
764
765      procedure Handle_Test_Ptr (Obj : in out Test_Class_Wrapper) is
766      begin
767         Handle_Test (Obj.Ptr.all);
768      end Handle_Test_Ptr;
769
770      procedure Count_Static is
771        new Indefinite_Test_List.For_Each (Action => Handle_Test);
772      procedure Count_Tests is
773        new Test_List.For_Each (Action => Handle_Test_Ptr);
774   begin
775      if Name_In_List (T.Suite_Name, Test_Names) then
776         return Test_Count (T);
777      end if;
778
779      Count_Tests (T.Test_Cases);
780      Count_Static (T.Static_Test_Cases);
781
782      return Counter;
783   end Test_Count;
784
785   procedure Adjust (T : in out Test_Suite) is
786      use Test_List;
787
788      New_List : List := Empty_List;
789
790      procedure Create_Copy (Item : in out Test_Class_Wrapper) is
791      begin
792         Append (New_List, (Ptr => new Test'Class'(Item.Ptr.all)));
793      end Create_Copy;
794
795      procedure Copy_All is new For_Each (Action => Create_Copy);
796   begin
797      Copy_All (T.Test_Cases);
798
799      T.Test_Cases := New_List;
800   end Adjust;
801
802   procedure Finalize  (T : in out Test_Suite) is
803      use Test_List;
804
805      procedure Free_Item (Item : in out Test_Class_Wrapper) is
806      begin
807         Free_Test (Item.Ptr);
808      end Free_Item;
809
810      procedure Free_All is new For_Each (Action => Free_Item);
811
812   begin
813      Free_All (T.Test_Cases);
814      Clear (T.Test_Cases);
815   end Finalize;
816
817   procedure Release_Suite (T : Test_Suite_Access) is
818      procedure Free is
819        new Ada.Unchecked_Deallocation (Object => Test_Suite,
820                                        Name   => Test_Suite_Access);
821      Ptr : Test_Suite_Access := T;
822   begin
823      Free (Ptr);
824   end Release_Suite;
825
826   procedure Run (Command : Test_Command; T : in out Test_Case'Class) is
827   begin
828      case Command.Command_Kind is
829         when SIMPLE =>
830            Command.Simple_Routine.all;
831         when OBJECT =>
832            Set_Up (T);
833            Command.Object_Routine.all (T);
834            Tear_Down (T);
835      end case;
836   end Run;
837end Ahven.Framework;
838