Quiz questions:
- Does the below unit test succeed or fail?
- Why?
procedure TestHResult.Test_EOleException; var OleException: EOleException; IResult: LongInt; // == HResult UResult: Cardinal; // == DWord begin IResult := $800A11FD; UResult := $800A11FD; try OleException := EOleException.Create('message', $800A11FD, 'source', 'helpfile', -1); raise OleException; except on E: EOleException do begin if E.ErrorCode = $800A11FD then Exit; Self.CheckEquals(E.ErrorCode, $800A11FD, 'E.ErrorCode should equal $800A11FD'); end; // E: EOleException end; end;
I partially gave away the answers in the title of the title of the post.
But first some background information:
The above code is inspired by code I see in a lot of applications at clients: tons of compiler warnings (and even more hints).
That is not the way you should program your applications (be it Delphi or any other): always make them Warning free. And try to make them Hint free too.
This particular test came of an application that tried to do handling of EOleException using the ErrorCode field which is of type HResult which – like for instance the .NET ExternalException.ErrorCode– is a 32-bit signed Integer (the 32-bit unsigned data type is Cardinal).
HResult is the Delphi equivalent of the Windows (and in the past OS/2) HRESULT. Most documentation will give you error codes in hexadecimal, is it is much easier to formulate the bit fields in HRESULT using hexadecimal values than decimal ones.
Now for the Warnings and why they are caused.
The assignment of the hexadecimal literal value to IResult will give the first warning: the literal is larger than the High(Integer) value (which is the same MaxInt). It won’t fit in a 4-byte Integer, but the compiler – despite the warning – will make it fit. If you ask the debugger for the hexadecimal value of IResult, it will happily return $800A11FD.
The signed decimal equivalent that gets assigned to UResult is 2148143613: no warning as it will fit.
// [DCC Warning] TestHResultUnit.pas(35): W1012 Constant expression violates subrange bounds IResult := $800A11FD; // Does not fit, but will cast 4-byte Cardinal into a 4-byte Integer with value -2146823683 ($800A11FD) UResult := $800A11FD; // Fits, will have value 2148143613 ($800A11FD)
The second warning is the same one as the first. Which means that OleException.ErrorCode will get the same value as IResult:
// [DCC Warning] TestHResultUnit.pas(41): W1012 Constant expression violates subrange bounds OleException := EOleException.Create('message', $800A11FD, 'source', 'helpfile', -1);
The difference btween IResult and UResult also explain the next two warnings: they basically come down to comparing -2146823683 (the stored value in E.ErrorCode) and 2148143613 (the signed equivalent of $800A11FD).
Since $800A11FD is bigger than MaxInt, the comparison will always be false.
// [DCC Warning] TestHResultUnit.pas(48): W1021 Comparison always evaluates to False // [DCC Warning] TestHResultUnit.pas(48): W1023 Comparing signed and unsigned types - widened both operands if E.ErrorCode = $800A11FD then // Integer can never match a Cardinal larger than High(Integer); Exit;
You can workaround these warnings in two ways – either cast to HResult or to Cardinal:
if E.ErrorCode = HResult($800A11FD) then Exit; // Succeed if Cardinal(E.ErrorCode) = $800A11FD then Exit; // Succeed
Finally no warning, but still a failure: both E.ErrorCode and $800A11FD is now passed as Int64 because there are no better overloads for TTestCase.CheckEquals in the TestFramework unit of DUnit.
Which again means that -2146823683 is compared to 2148143613. Which fails the test case.
// No warning, but both are passed as Int64, so comparison fails Self.CheckEquals(E.ErrorCode, $800A11FD, 'E.ErrorCode should equal $800A11FD');
To answer the questions:
- Does the below unit test succeed or fail?
Fail. - Why?
Because of compiler warnings, and theTTestCase.CheckEquals
overload chosen by the compiler.
–jeroen
unit TestHResultUnit; interface uses TestFramework, System.SysUtils; type TestHResult = class(TTestCase) public procedure SetUp; override; procedure TearDown; override; published procedure Test_EOleException; end; implementation uses System.Win.ComObj, Winapi.Windows; // 1 Fool the optimizer procedure Touch(var X); begin end; procedure TestHResult.Test_EOleException; var OleException: EOleException; IResult: LongInt; // == HResult UResult: Cardinal; // == DWord begin // [DCC Warning] TestHResultUnit.pas(35): W1012 Constant expression violates subrange bounds IResult := $800A11FD; // Does not fit, but will cast 4-byte Cardinal into a 4-byte Integer with value -2146823683 ($800A11FD) UResult := $800A11FD; // Fits, will have value 2148143613 ($800A11FD) Touch(IResult); Touch(UResult); try // [DCC Warning] TestHResultUnit.pas(41): W1012 Constant expression violates subrange bounds OleException := EOleException.Create('message', $800A11FD, 'source', 'helpfile', -1); raise OleException; except on E: EOleException do begin // [DCC Warning] TestHResultUnit.pas(48): W1021 Comparison always evaluates to False // [DCC Warning] TestHResultUnit.pas(48): W1023 Comparing signed and unsigned types - widened both operands if E.ErrorCode = $800A11FD then // Integer can never match a Cardinal larger than High(Integer); Exit; // Succeed // No warning, but both are passed as Int64, so comparison fails Self.CheckEquals(E.ErrorCode, $800A11FD, 'E.ErrorCode should equal $800A11FD'); end; // E: EOleException end; end; procedure TestHResult.SetUp; begin end; procedure TestHResult.TearDown; begin end; initialization RegisterTest(TestHResult.Suite); end.
Filed under: Delphi, Delphi 4, Delphi 5, Delphi 6, Delphi 7, Delphi XE, Delphi XE2, Delphi XE3, Delphi XE4, Delphi XE5, Development, Software Development
