DUnit with FastMM: Detecting memory leaks in individual tests.
Posted by jpluimers on 2021/08/05
Steps:
- Add the conditional define
FASTMM
to your project - Ensure you have
$(BDS)\source\DUnit\src
in your search path in your project (as otherwise Delphi will pick the pre-builtTestFramework.dcu
file which was compiled without theFASTMM
conditional define) - Inside a test method, or the
SetUp
method of a class, setFailsOnMemoryLeak
toTrue
- If the
SetUp
method of the class allocates memory, ensure theTearDown
de-allocates it. Otherwise you will have leaks:- DUnit will check memory differences from the start of the
SetUp
until the end of theTearDown
- DUnit will not take into account what is freed in the
destructor
or by automatic finalization after thedestructor
!
- DUnit will check memory differences from the start of the
- Re-build your application (otherwise the DUnit
TestFramework
unit will not take into account theFASTMM
conditional define)
Depending in your test framework, FailsOnMemoryLeak
might be by default be False
or True
:
- TestInsight by default has
FailsIfMemoryLeaked
set to True for the root test suite (which is then applied toFailsOnMemoryLeak
of any test method).
procedure RunRegisteredTests(const baseUrl: string); var suite: ITestSuite; result: TTestResult; listener: ITestListener; begin suite := RegisteredTests; if not Assigned(suite) then Exit; result := TTestResult.Create; result.FailsIfNoChecksExecuted := True; result.FailsIfMemoryLeaked := True; listener := TTestInsightListener.Create(baseUrl, suite.CountEnabledTestCases); result.AddListener(listener); try suite.Run(result); finally result.Free; end; end;
- Console DUnit runners (Text, or XML) by default have
FailsIfMemoryLeaked
set toFalse
. - GUI DUnit runner has
FailsIfMemoryLeaked
depending on the options:
DUnit source differences
Note that recent Delphi versions (I think XE and up) ship with almost the same sources as https://sourceforge.net/code-snapshots/svn/d/du/dunit/svn/dunit-svn-r44-trunk.zip, with these Embarcadero changes:
- all SVN timestamps are based on time zone -0400 instead of +0000:
$Date: 2008-04-24 07:59:47 -0400 (Thu, 24 Apr 2008) $
$Date: 2008-04-24 11:59:47 +0000 (Thu, 24 Apr 2008) $
- Embarcadero removed:
- all comment lines having
TODO
in them - all files of types
.dof
,.cfg
, and.wmz
- the files
NGUITestRunner.nfm
andNGUITestRunner.pas
- the file
/etc/usermap
- the directory trees
/private
and/projects
- all comment lines having
- Embarcadero changed
- file
/src/versioninfo.res
from9.3.0
to9.2.1
(which is odd, as all files are from9.3.0
) - unit
TextTestRunner
to support:- CLR (used last in Delphi 2007)
FailureCount
ErrorCount
IndentLevel
PrefixChars
- conditional defines
ADDITIONAL_INFO
,BASIC_INFO
- output of
UnitName
- unit
TestExtensions
to support:- CLR (used last in Delphi 2007)
- conditional defines
ANDROID_FIXME
andLINUX
- compiler directive
LEGACYIFEND
GUITestRunner.dfm
to haveResultsView: TListView
propertyHeight
value45
instead of39
- the below methods to use
ReturnAddress
in stead ofCallerAddr
:TGUITestCase.FindControl
TGUITestCase.Click
TGUITestCase.EnterKeyInto
TGUITestCase.EnterTextInto
TGUITestCase.Show
TGUITestCase.CheckTabTo
overloadsTGUITestCase.CheckFocused
overloadsTGUITestCase.CheckEnabled
overloadsTGUITestCase.SetFocus
overloadsTGUITestCase.CheckVisible
overloads
- method
TGUITestRunner.RunTest
- from
class procedure TGUITestRunner.RunTest(test: ITest); begin with TGUITestRunner.Create(nil) do begin try suite := test; ShowModal; finally Free; end; end; end;
- to
class procedure TGUITestRunner.RunTest(test: ITest); var GUI :TGUITestRunner; begin Application.CreateForm(TGUITestRunner, GUI); with GUI do begin try suite := test; ShowModal; finally Free; end; end; end;
- from
- unit
GUITestRunner
:- from
procedure RunTest(test: ITest); begin with TGUITestRunner.Create(nil) do begin try Suite := test; ShowModal; finally Free; end; end; end;
- to
procedure RunTest(test: ITest); var GUI :TGUITestRunner; begin Application.CreateForm(TGUITestRunner, GUI); with GUI do begin try Suite := test; if Suite <> nil then ShowModal; finally Free; end; end; end;
- method
TGUITestRunner.SetUp
not to setSubItems[0] := '';
when there is no test suite. - method
TGUITestRunner.FormCreate
to useFormatSettings.TimeSeparator
instead ofTimeSeparator
.
- from
- file
- unit TestFramework to support:
HPPEMIT
LEGACYIFEND
CLR
and_USE_SYSDEBUG_
AUTOREFCOUNT
NEXTGEN
ANDROID
- the
RunCountAttribute
andRunCount
support - a
CheckEquals
overload foruint64
- a
CheckNotEquals
overload forTCharArray
CheckCircularRef
CompareFloatRelative
NotSameErrorMessage
withWideString
arguments instead of string- a
TestDataDir
function ReturnAddress
for older compilers
- a new unit
DUnitTestRunner
- a new
Makefile
file - a new UnitTests.log file
–jeroen
sglienke said
This only reports that there is a leak and how many bytes were leaked which is not very useful after all.
The better alternative is to use LeakCheck which gives an exact summary and even is able to provide a dependency graph in case a large number of objects were leaked which makes is easy to find the root or even see that its some circular reference.