Long time ago I wrote following code to retrieve emails from mailbox:
pop3 := TNMPOP3.Create(Self);
try
pop3.Host := FAppSettings.ServerName;
pop3.Port := FAppSettings.ServerPort;
pop3.UserID := FAppSettings.Login;
pop3.Password := FAppSettings.Password;
try
pop3.Connect;
except
on E:Exception do AddError(E.Message);
end;
if not pop3.Connected then Exit;
if pop3.MailCount > 0 then begin
pop3.DeleteOnRead := False;
pop3.AttachFilePath := GetTempDirectory;
ProcessMsgs(pop3);
end
else begin
TCommon.InfMsg('There are no messages in mailbox');
end;
pop3.Disconnect;
finally
pop3.Free;
end;
Now, when mail service provider switched entirely to SSL this code fails obviously, but in a strange way:
pop3.Connect line causes an exception but with an empty text in E.Message making the problem unclear to end user.
Investigation of the problem in Delphi debugger reveals that the first time the right exception is generated:
Project .... raised exception class Exception with message 'Authentication failed'.
but then, when I press F8 (Step Over) again, execution point remains in the same line and another exception is generated:
Project .... raised exception class Exception with message ''.
and only this exception is catched by try-except.
Why?
To answer your actual question of "why?", the sequence you describe means that TNMPOP3.Connect() is internally catching the original authentication exception and throwing a new exception without an error message. Whether that is a bug or intentional, there is no way to know without looking at the source code for TNMPOP3. Delphi did not ship with that source code, and NetMasters are no longer around so you can't ask them for it. TNMPOP3 does not support SSL anyway, so you will have to switch to another component/library to handle your POP3+SSL functionality moving forward.
Related
I'm trying to capture the debugger notification text, Firedac connection
//FDScript1.SQLScripts.Add.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
//FDScript1.ValidateAll;
//FDScript1.ExecuteAll;
//MSScript1.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
//MSScript1.Execute;
try
FDConnection1.Connected := True;
FDScript1.SQLScripts.Add.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
FDScript1.ExecuteAll;
FDScript1.ValidateAll;
except
//EMSSQLNativeException
on E: EMSSQLNativeException do
begin
//ShowMessage('Erro'+FDGUIxErrorDialog1.ErrorDialog.Caption);
//Memo1.Clear;
Memo1.Text := 'Erro de Sistema: '+#13#10+ E.Message;
end;
end;
It really would help if you would show us the scripts you are trying to execute,
or at least the script which does not execute correctly. In any case, your code is wrong
because the documentation
states
It is good practice to call the ValidateAll method before the ExecuteAll method.
Note the 'before'. Your ValidateAll is after ExecuteAll, not before. Both are Boolean
functions but you are not checking their results, which you should.
With some trivial experimenting I found that I can provoke a EMSSQLNativeException
using SqlServer 2014 with the code below:
procedure TForm2.Button1Click(Sender: TObject);
const
sScript = 'select m.*, d.* from master m join detail d on, m.masterid = d.masterid';
var
FDScript : TFDSqlScript;
begin
try
FDConnection1.Connected := True;
FDScript := FDScript1.SQLScripts.Add;
FDScript.SQL.Text := sScript;
//FDScript1.ExecuteAll;
//FDScript1.ValidateAll;
// FDScript1.ValidateAll then
//FDScript1.ExecuteAll;
FDQuery1.SQL.Text := sScript;
FDQuery1.Open();
except
//EMSSQLNativeException
on E: EMSSQLNativeException do
begin
//ShowMessage('Erro'+FDGUIxErrorDialog1.ErrorDialog.Caption);
//Memo1.Clear;
Memo1.Text := 'Erro de Sistema: '+#13#10+ E.Message;
end;
end;
end;
Note the blatantly wrong syntax in the Sql statement, namely the comma after the on.
When FDQuery1.Open is called, this exception is raised (and caught initially by the debugger)
---------------------------
Debugger Exception Notification
---------------------------
Project sqlerror.exe raised exception class EMSSQLNativeException with message '[FireDAC][Phys][ODBC][Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near 'd'.'.
---------------------------
Break Continue Help
---------------------------
When I click Continue, execution proceeds into your exception handler, exactly as #TomBrunberg described in a comment
and the exception's message text is inserted into Memo1.
So I cannot reproduce the behaviour you describe based on the information in your question. It
must be caused by something you are not telling us, possibly in code you have
not included in your q or some property setting of the components you are
using.
Hopefully, trying the code above, you will find the debugger behaving as I
have described and this may give you some clue as to why you are getting the
problem you've described. Note that it is very important that you try the code
above in a new project, not your existing one. The only property setting
you need to do before executing it is to set FDConnection1 so that it can connect
to your server.
FWIW, if I uncomment-out the lines
FDScript1.ValidateAll then
FDScript1.ExecuteAll;
they execute without complaint, and I get the exact same behaviour as i've described without them.
The below code works fine, the calc... generates an exception, comment it out or change calc... to not throw and exception and the test fails.
StartExpectingException(exception);
calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
StopExpectingException('calcMembersPIPEndDate - 1st after aDay');
My problem is that any checks I put in this test method after this do not execute.
so
checkEquals(1,0);
StartExpectingException(exception);
calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
StopExpectingException('calcMembersPIPEndDate - 1st after aDay');
fails on the 1st checkEquals
StartExpectingException(exception);
calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
StopExpectingException('calcMembersPIPEndDate - 1st after aDay');
checkEquals(1,0);
passes - why?
I have tried to work out what version of Dunit I am using:
testframework.pas has the following - which didn't seem to
rcs_id: string = '#(#)$Id: TestFramework.pas,v 1.117 2006/07/19 02:45:55
rcs_version : string = '$Revision: 1.117 $';
versioninfo.inc
ReleaseNo : array[1..3] of Integer
= (9,2,1);
ReleaseStr = '9.2.1';
ReleaseWhen : array[1..6] of Integer
= (2005,09,25,17,30,00);
These two methods, StartExpectingException and StopExpectingException are not meant to be called directly.
Instead you are supposed to use the ExpectedException property. When you set this property, StartExpectingException is called. Whilst you could call StartExpectingException I belive that the intended usage is that you assign to ExpectedException.
As for StopExpectingException, you don't call it. The framework calls it. It does so in TTestCase.RunTest, the framework code that executes your test method.
So your test case code might look like this:
ExpectedException := ESomeException;
raise ESomeException.Create(...);
When you state that you are expecting an exception, what you are saying is that your test method will raise that exception. Since raising an exception alters control flow, code that appears after the exception is raised will not execute. Exceptions propagate up the call stack until they are caught. The framework will catch the exception in TTestCase.RunTest. If you have indicated that the caught exception is expected then the test will pass, otherwise failure is recorded.
The net result of all this is that the ExpectedException mechanism can be used if the final act of the test method is to raise that expected exception. The ExpectedException mechanism is no use at all if you want to perform further tests after the exception is raised. If you wish to do that then you should either:
Write your own exception handling code, in your test method, that checks that exceptions are raised as designed.
Use CheckException.
StopExpectingException cannot work the way you expect. It's important to understand the flow of execution in an exception state to see why.
Consider the following code:
procedure InnerStep(ARaiseException);
begin
Writeln('Begin');
if ARaiseException then
raise Exception.Create('Watch what happens now');
Writeln('End');
end;
procedure OuterStep;
begin
try
InnerStep(False); //1
InnerStep(True); //2
InnerStep(False); //3
except
//Do something because of exception
raise;
end;
end;
When you call OuterStep above, line //2 will raise an exception inside InnerStep. Now whenever an exception is raised:
The instruction pointer jumps out of each method (a little like goto) to the first except or finally block found in the call-stack.
Writeln('End'); will not be called.
Line //3 will not be called.
Whatever code exists in the except block of OuterStep is executed next.
And finally when raise; is called, the exception is re-raised and the instruction pointer jumps to the next except or finally block.
Note also that like raise; any other exception within the except block will also jump out, (effectively hiding the first exception).
So when you write:
StartExpectingException(...);
DoSomething();
StopExpectingException(...);
There are 2 possibilities:
DoSomething raises an exception and StopExpectingException is never called.
DoSomething doesn't raise an exception and when StopExpectingException is called there is no exception.
David has explained that the DUnit framework calls StopExpectingException for you. But you may be wondering how to approach your test case checking multiple exception scenarios.
Option 1
Write smaller tests.
You know that's what everyone says you're supposed to do in any case right? :)
E.g.
procedure MyTests.TestBadCase1;
begin
ExpectedException := ESomethingBadHappened;
DoSomething('Bad1');
//Nothing to do. Exception should be raised, so any more code would
//be pointless.
//If exception is NOT raised, test will exit 'normally', and
//framework will fail the test when it detects that the expected
//exception was not raised.
end;
procedure MyTests.TestBadCase2;
begin
ExpectedException := ESomethingBadHappened;
DoSomething('Bad2');
end;
procedure MyTests.TestGoodCase;
begin
DoSomething('Good');
//Good case does not (or should not) raise an exception.
//So now you can check results or expected state change.
end;
Option 2
As David has suggested, you can write your own exception handling inside your test. But you'll note that it can get a little messy, and you'll probably prefer option 1 in most cases. Especially when you have the added benefit that distinctly named tests make it easier to identify exactly what went wrong.
procedure MyTests.TestMultipleBadCasesInTheSameTest;
begin
try
DoSomething('Bad1');
//This time, although you're expecting an exception and lines
//here shouldn't be executed:
//**You've taken on the responsibility** of checking that an
//exception is raised. So **if** the next line is called, the
//expected exception **DID NOT HAPPEN**!
Fail('Expected exception for case 1 not raised');
except
//Swallow the expected exception only!
on ESomethingBadHappened do;
//One of the few times doing nothing and simply swallowing an
//exception is the right thing to do.
//NOTE: Any other exception will escape the test and be reported
//as an error by DUnit
end;
try
DoSomething('Bad2');
Fail('Expected exception for case 2 not raised');
except
on E: ESomethingBadHappened do
CheckEquals('ExpectedErrorMessage', E.Message);
//One advantage of the manual checking is that you can check
//specific attributes of the exception object.
//You could also check objects used in the DoSomething method
//e.g. to ensure state is rolled back correctly as a result of
//the error.
end;
end;
NB! NB! Something very important to note in option 2. You need to be careful about what exception class you swallow. DUnit's Fail() method raises an ETestFailure exception to report to the framework that the test failed. And you wouldn't want to accidentally swallow the exception that's going to trigger the test failure for expected exception.
The subtle issues related exception testing make it important to: test first, ensure you have the correct failure, and only then implement the production code change to get a pass. The process will significantly reduce the chances of a dud test.
I have this method where i execute a sql statement and catch a error in a try except statement
AdoQuery := TAdoQuery.Create(self);
AdoQuery.connection := AdoConnection;
AdoQuery.SQL.Add(sqlStr);
AdoQuery.Prepared := true;
try
begin
AdoQuery.ExecSql;
AdoQuery.Active := false;
end;
except on e:eAdoError do
ShowMessage('Error while creating the table: ' + e.Message);
end;
I can catch the error like this and show it to the user but it's showing some useless info for the user. I Would like to show only the %msg part of the error, take a look at the pic:
I tought e.MEssage allow me to get only the %msg part but it give me the whole thing hardly understoodable by a random user. How do i get only the usefull info in this case
Table reftabtest.rPCE already exists
Thank you.
You can use the Errors property of the TADOConnection object, what you want is the Description member of the Error object.
In your case:
function ParseOBDCError(ErrorDescription : String) : String;
var
Ps : Integer;
Pattern : String;
begin
Pattern := '%msg:';
Ps := Pos(Pattern, ErrorDescription);
if Ps > 0 then
begin
Result := Copy(ErrorDescription, Ps+Length(Pattern)+1);
// if you want, you can clean out other parts like < and >
Result := StringReplace(Result, '<', , '', [rfReplaceAll]);
Result := StringReplace(Result, '>', , '', [rfReplaceAll]);
Result := Trim(Result);
end
else
Result := ErrorDescription;
end;
...
AdoQuery := TAdoQuery.Create(self);
AdoQuery.connection := AdoConnection;
AdoQuery.SQL.Add(sqlStr);
AdoQuery.Prepared := true;
try
AdoQuery.ExecSql;
AdoQuery.Active := false;
except on e : Exception do
begin
if AdoConnection.Errors.Count > 0 then
ShowMessageFmt('Error while creating the table: %s',
[ParseOBDCError(AdoConnection.Errors[0].Description)])
else
ShowMessageFmt('something went wrong here: %s', [e.Message]);
end;
end;
That message dialog wasn't shown by your ShowMessage() code.
First, the icon is wrong - that's not the ShowMessage icon.
Second, the text you added ('Error while creating the table: ') to the message is missing.
This means your exception swallower is not catching the exception, because it's not of the EADOError class. So what's happening is the application's default exception handler is showing the exception.
Before I explain how to fix it, I need to point out that your exception swallower is wrong (your's should not be misnamed an exception handler).
Because you're swallowing the exception: If another method calls yours it will incorrectly think you method succeeded, and possibly do something it shouldn't. You should never write code that makes an assumption that there isn't a significant call stack leading into your method.
Swallowing to show a message to the user doesn't help, because it hides the error from the rest of the program. Especially since, as you can see: there is already code in one place that tells the user about the error without hiding it from the rest of the program. (The problem you have is that you want a friendlier message.)
To fix it:
First find out what the actual exception class is, so you're able to catch the correct error.
Now you have a number of options, but the simplest is as follows:
First log the exception, preferably with call stack. You don't want to be stuck in the situation where the user gets a friendly message but you as developer lose critical information if you need to do some debugging.
To get the call stack you can consider third-party tools like Mad Except, Exceptional Magic, JCLDebug to name a few.
Now show the message to the user.
Finally call Abort. This raises an EAbort exception which by convention is a "silent exception". It tells the rest of the program that there was an error (so it doesn't do things assuming everything is fine). But by convention, any further exception handlers should not show another message to the user. This includes the default handler's dialog in your question.
If the default handler is incorrectly showing EAbort messages, then it should be fixed.
I would like my custom error message to show up rather than the one generated by the system. Specifically, in this case, if the user tries to open a file that is already in use, I would like to catch the error and display my message, but the system always beats me to it and generates the following message: "Project...raised exception class EFOpenError with message 'Cannot open file "File path and name". The process cannot access the file because it is being used by another process'." When I close this error message, that is when my message is displayed. I would like only my message to be displayed. I do not want the system message displayed. Is there a way to do this? My code does not work.
Here is my code:
begin
//check if input file can be opened
try
if OpenDialog1.Execute then
begin
Application.ProcessMessages;
Memo1.Clear;
try
Memo1.Lines.LoadFromFile(OpenDialog1.FileName);
InputFile := TStringList.Create;
InputFile.LoadFromFile(OpenDialog1.FileName);
ActiveFileName := ExtractFileName(OpenDialog1.FileName);
lblActiveFileName.Caption := 'Active File: ' + ActiveFileName;
mmuDisplayClear.Enabled := True;
mmuAnalysisOptions.Enabled := True;
except on
EFOpenError do //if file does not exist or is in use
MessageDlgPos('File either does not exist or may be ' +
'in use!', mtError, [mbOK], 0, 300, 300);
end;
end;
finally
mmuDispInstructions.Enabled := True;
mmuDispData.Enabled := False;
end;
end;
You are seeing the debugger catch the exception before your application does. That is perfectly normal behavior. Simply press F9 to pass the exception to your app for normal handling. If you don't want the debugger to display the exception, put the exception class type in the debugger's Ignore list, or wrap the code in breakpoints that disable/enable the debugger's exception handling.
When you are running an application through Delphi IDE (debuging it) and an exceptions is raised from within try..except..end clause the exception will first be detected by IDE and if you then press F9 code from the except block will fire.
So if you generate custom message there that message will be shown. But you don't need to show a message. You might wanna handle that exception silently.
Let's take a look at the next example: You are trying to establish a network connection to some server. In the call for establishing connection you specify certain timeout after which atempt to connect is considered as failed. After this timeout expires most network components raise a Etimeout exception so you know that establishing connection wa unsucsessfull. But due to the way how networks sometimes behave you might wanna go and retry the atempt to connect to the server again before showing an error to the user. You can do this by simply calling connect method again within the except block in order to initiate another atempt wihout rasing any errors. So code would look someting like this:
Timeout := 2000;
try
NetworkComponent.Connect(Timout);
except
try
NetworkComponent.Connect(Timeout);
except
MessageDlgPos('Connection could not be established!,
mtError, [mbOK], 0, 300, 300);
end;
end;
As you see in the example in first try..except..end block we handle the exception silently but in the next try..except..end block which is nested within the except block of the first try..except..end; block where we finally show message to the user.
While this is generaly considered as bad practice sometimes you might not even want to show any message abot such error. For instance if you are making some system service or a background process one thing that you definitly woul not want is to bother user with bunch of error messages. it is still good to athleast write the errors in some log for further debuging.
Now if you application is ran outside the Delphi IDE (not debuging) or if you turn of specific Exception detection in IDE then the code within the except block will fire directly. when specific exception is raised.
EDIT: Removed unrelated text.
There is an example which illustrates my question:
procedure Test;
begin
try
ShowMessage('1');
raise EWarning.Create(12345, 'Warning: something is happens!');
ShowMessage('2');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
E.SkipThisWarning // shows '1' then '2'
else
E.StopExecution; // shows '1'
end;
end;
function IWantToContinue(const ErrorCode: Integer): Boolean;
begin
//...
end;
I tried to use something like this:
asm
jmp ExceptAddr
end;
but it's wont' work...
Any ideas?
Thanks.
No, it is not possible:
There are two kinds of exceptions: logic exceptions that are raised by the programmer using the raise command, and external exceptions that are initiated by the CPU for various conditions: division by zero, stack overflow, access violation. For the first kind, the logic exceptions, there's nothing you can do because they're part of the application "flow". You can't mess with the flow of 3rd party code, you can't even mess with the flow of your own code.
External exceptions
Those are normally raised as a consequence of running a single CPU instruction, when that instruction fails. In Delphi those are made available as EExternal descendants. The list includes access violations, division by zero, stack overflow, privileged instruction and not-so-many others. Theoretically, for some of those exceptions, the causing condition of the exception could be removed and the single CPU instruction retried, allowing the original code to continue as if no error happened. For example SOME access violations might be "fixed" by actually mapping a page of RAM at the address where the error occurred.
There are provisions in the SEH (Structured Exception Handling) mechanism provided by Windows for dealing with such retry-able errors, and Delphi is using SEH under the hood. Unfortunately Delphi doesn't expose the required elements to make this easily accessible, so using them would be very difficult if not impossible. None the less, for particular types of EExternal errors, smart Delphinians might attempt writing custom SEH code and get things working. If that's the case, please ask a new question mentioning the particular type of error you're getting plus the steps you'd like to take to remove the error condition: you'll either get some working code or a customized explanation of why your idea would not work.
Logic exceptions initiated through the use of raise
Most exceptions fall into this category, because most code will check it's inputs before doing potentially dangerous low level stuff. For example, when trying to access an invalid index in a TList, the index would be checked and an invalid index exception raised before attempting to access the requested index. Without the check, accessing the invalid index would either return invalid data or raise an Access Violation. Both of those conditions would be very hard to track errors, so the invalid index exception is a very good thing. For the sake of this question, even if code were allowed to access an invalid index, causing an Access Violation, it would be impossible to "fix" the code and continue, because there's no way to guess what the correct index should be.
In other words, fixing "logic" exceptions doesn't work, shouldn't work, and it's insanely dangerous. If the code that raises the error is yours then you can simply restructure it to NOT raise exceptions for warnings. If that's not your code, then continuing the exception falls into the "insanely dangerous" category (not to mention it's technically not possible). When looking at already written code, ask yourself: would the code behave properly if the raise Exeption were replaced with ShowMessage? The answer should mostly be "NO, the code would fail anyway". For the very rare, very wrong case of 3rd party code that raises an exception for no good reason, you may ask for specific help on patching the code at run-time to NEVER raise the exception.
Here's what could be in some 3rd party code:
function ThirdPartyCode(A, B: Integer): Integer;
begin
if B = 0 then raise Exception.Create('Division by zero is not possible, you called ThirdPartyCode with B=0!');
Result := A div B;
end;
It should be obvious that continuing that code after the exception is not going to allow stuff to "self heal".
Third party code might also look like this:
procedure DoSomeStuff;
begin
if SomeCondition then
begin
// do useful stuff
end
else
raise Exception.Create('Ooops.');
end;
Where would that code "continue"? Quite obviously not the "do usefull stuff" part, unless the code is specifically designed that way.
Those were, of course, simple examples only scratching the surface. From a technical perspective, "continuing" after an exception as you're suggesting is a lot more difficult then jumping to the address of the error. Method calls use stack space to set up local variables. That space was released in the process of "rolling back" after the error, on the way to your exception handler. finally blocks were executed, possibly de-allocating resources where needed. Jumping back to the originating address would be very wrong, because the calling code no longer has what it expects on stack, it's local variables are no longer what they're ment to be.
If it's your code that's raising the exception
Your code can easily be fixed. Use something like this:
procedure Warning(const ErrorText:string);
begin
if not UserWantsToContinue(ErrorText) then
raise Exception.Create(ErrorText);
end;
// in your raising code, replace:
raise Exception.Create('Some Text');
// with:
Warning('Some Text');
AFAIK no. You have to restructure your code to something like
procedure Test;
begin
ShowMessage('1');
try
raise EWarning.Create(12345, 'Warning: something is happens!');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
// shows '1' then '2'
else
raise; // shows '1'
end;
ShowMessage('2');
end;
In C++, it is possible using an SEH __try/__except block whose __except expression evaluates to EXCEPTION_CONTINUE_EXECUTION.
In Delphi, it is not possible to use SEH directly, AFAIK.
BASICally, your code wont work because ExceptAddr is a function, not a variable. So, your code snippet changes as follows:
{$O-}
procedure TForm1.FormCreate(Sender: TObject);
begin
try
OutputDebugString('entering');
raise Exception.Create('Error Message');
OutputDebugString('leaving');
except on E: Exception do
begin
OutputDebugString(PChar(Format('ExceptAddr = %p', [ExceptAddr])));
asm
CALL ExceptAddr
JMP EAX
end;
end;
end;
end;
..you could nest multiple try-except and determine if continue inside each exception:
try
try
some code
except
ret := message('want to continue?');
if ret <> mrYes then
exit;
end;
some other code to perform when no exception or user choose to continue
except
etc..
end;