[fpc-pascal] FPCUnit test + raise E;

Sven Barth pascaldragon at googlemail.com
Sun Sep 15 21:25:50 CEST 2013


On 15.09.2013 13:05, Marcos Douglas wrote:
> On Sun, Sep 15, 2013 at 6:56 AM, Sven Barth <pascaldragon at googlemail.com> wrote:
>> On 15.09.2013 03:21, Marcos Douglas wrote:
>>>
>>> Hi,
>>>
>>> 1) I have a code like that:
>>>
>>> procedure TghSQLConnector.Connect;
>>> begin
>>>     try
>>>       FLib.Connect;
>>>     except
>>>       on E: Exception do
>>>         DoOnException(E);
>>>     end;
>>> end;
>>>
>>>    https://github.com/mdbs99/Greyhound/blob/0.1.8/src/ghsql.pas#L1565
>>>
>>>
>>> 2) DoOnException was implemented so:
>>>
>>> procedure TghSQLHandler.DoOnException(E: Exception);
>>> begin
>>>     if Assigned(FOnException) then
>>>       FOnException(Self, E)
>>>     else
>>>       raise E;
>>> end;
>>>
>>> https://github.com/mdbs99/Greyhound/blob/0.1.8/src/ghsql.pas#L443
>>>
>>>
>>> So, as you see, if occurs a Exception the code checks if the event
>>> (OnException) was setted. If yes, call user implementation; if no,
>>> call raise E;
>>> It works... but not when I uses in FPCUnit tests.
>>>
>>> See a simple test (this works):
>>>
>>> procedure TghSQLConnectorTest.TestOnException;
>>> begin
>>>     // catch
>>>     FConn.OnException := @DoOnException;
>>>     FConn.Script.Text := 'foo';
>>>     FConn.Execute;
>>> end;
>>>
>>> https://github.com/mdbs99/Greyhound/blob/0.1.8/test/ghsqltest.pas#L246
>>>
>>>
>>> The code DoOnException is:
>>>
>>> procedure TghSQLTest.DoOnException(Sender: TObject; E: Exception);
>>> begin
>>>     AssertTrue(Assigned(E));
>>> end;
>>>
>>> https://github.com/mdbs99/Greyhound/blob/0.1.8/test/ghsqltest.pas#L141
>>>
>>> So, if I change the test like bellow... BUM! SIGSEGV!!
>>>
>>> procedure TghSQLConnectorTest.TestOnException;
>>> begin
>>>     // removed >>> FConn.OnException := @DoOnException;
>>>     FConn.Script.Text := 'foo';
>>>     FConn.Execute;
>>> end;
>>>
>>>
>>> The ERROR is:
>>> [Content]
>>> Project test raised exception class 'External: SIGSEGV'.
>>>
>>>    At address 40B758
>>
>>
>> Could you as a test replace the
>>
>> raise E
>>
>> with
>>
>> raise E at get_caller_addr(get_frame), get_caller_frame(get_frame);
>>
>> and check whether this makes a difference?
>
> I tried. No makes difference.
> (could you explain which the difference to call "raise" using "raise E
> at [...]"?)

The raise-at statement allows you to raise an exception for a specific 
address. This allows you to have e.g. a error handling procedure (to 
wrap a more compilated generation of a exception class) but have the 
exception address still point to the original call site. E.g.:

=== code begin ===

procedure Error(aArg: Boolean);
begin
   if aArg then
     raise Exception.Create('Test')
   else
     raise Exception.Create('Test') at get_caller_addr(get_frame), 
get_caller_frame(get_frame);
end;

procedure Test1;
begin
   Error(True);
end;

procedure Test2;
begin
   Error(False);
end;

begin
   try
     Test1;
   except
     DumpExceptionBackTrace(Output);
   end;
   try
     Test2;
   except
     DumpExceptionBackTrace(Output);
   end;
end;

=== code end ===

In case one the stack trace will contain Test1 and Error in case two the 
stack trace will be only Test2.

> I implemented -- but not up to Git yet -- some like that:
> procedure TghSQLHandler.DoOnException(E: Exception);
> var
>    NewEx: EghSQLError;
> begin
>    if Assigned(FOnException) then
>      FOnException(Self, E)
>    else
>    begin
>      NewEx := EghSQLError.Create(Self, E.Message);   // <<<
>      NewEx.InnerException := E;                                  // <<<
>      raise NewEx;
>    end;
> end;
>
> So, if I recreate the Exception, it works in any cases. As you see, I
> created a new property (InnerException) to save the original
> exception to know what the real Exception happened...
> But I think this introduces much more overhead to processing.

Can you access fields of the InnerException? Maybe it's a problem of 
reraising an existing exception...

> In my code I have classes that inherited from TghSQLHandler. This
> class have the DoOnException method.
> So DoOnException can be call many times in override methods. Maybe the
> program "broke" the stack because "raise E" can be call and raise an
> exception; the next "level" raise another; and next call again.  :/

Could possibly be. If you could reproduce it in an as simple example as 
possible that would help.

Another thing you could try (just for testing): change your exception 
handler procedure to a function that returns bool and use it like this:

=== code begin ===

procedure TghSQLConnector.Connect;
begin
   try
     FLib.Connect;
   except
     on E: Exception do
       if not DoOnException(E) then
         raise;
   end;
end;

=== code end ===

Regards,
Sven



More information about the fpc-pascal mailing list