DUnit with FastMM: Detecting memory leaks in individual tests.
Posted by jpluimers on 2021/08/05
Steps:
- Add the conditional define
FASTMMto your project - Ensure you have
$(BDS)\source\DUnit\srcin your search path in your project (as otherwise Delphi will pick the pre-builtTestFramework.dcufile which was compiled without theFASTMMconditional define) - Inside a test method, or the
SetUpmethod of a class, setFailsOnMemoryLeaktoTrue - If the
SetUpmethod of the class allocates memory, ensure theTearDownde-allocates it. Otherwise you will have leaks:- DUnit will check memory differences from the start of the
SetUpuntil the end of theTearDown - DUnit will not take into account what is freed in the
destructoror by automatic finalization after thedestructor!
- DUnit will check memory differences from the start of the
- Re-build your application (otherwise the DUnit
TestFrameworkunit will not take into account theFASTMMconditional define)
Depending in your test framework, FailsOnMemoryLeak might be by default be False or True:
- TestInsight by default has
FailsIfMemoryLeakedset to True for the root test suite (which is then applied toFailsOnMemoryLeakof 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
FailsIfMemoryLeakedset toFalse. - GUI DUnit runner has
FailsIfMemoryLeakeddepending 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
TODOin them - all files of types
.dof,.cfg, and.wmz - the files
NGUITestRunner.nfmandNGUITestRunner.pas - the file
/etc/usermap - the directory trees
/privateand/projects
- all comment lines having
- Embarcadero changed
- file
/src/versioninfo.resfrom9.3.0to9.2.1(which is odd, as all files are from9.3.0) - unit
TextTestRunnerto support:- CLR (used last in Delphi 2007)
FailureCountErrorCountIndentLevelPrefixChars- conditional defines
ADDITIONAL_INFO,BASIC_INFO - output of
UnitName
- unit
TestExtensionsto support:- CLR (used last in Delphi 2007)
- conditional defines
ANDROID_FIXMEandLINUX - compiler directive
LEGACYIFEND
GUITestRunner.dfmto haveResultsView: TListViewpropertyHeightvalue45instead of39- the below methods to use
ReturnAddressin stead ofCallerAddr:TGUITestCase.FindControlTGUITestCase.ClickTGUITestCase.EnterKeyIntoTGUITestCase.EnterTextIntoTGUITestCase.ShowTGUITestCase.CheckTabTooverloadsTGUITestCase.CheckFocusedoverloadsTGUITestCase.CheckEnabledoverloadsTGUITestCase.SetFocusoverloadsTGUITestCase.CheckVisibleoverloads
- 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.SetUpnot to setSubItems[0] := '';when there is no test suite. - method
TGUITestRunner.FormCreateto useFormatSettings.TimeSeparatorinstead ofTimeSeparator.
- from
- file
- unit TestFramework to support:
HPPEMITLEGACYIFENDCLRand_USE_SYSDEBUG_AUTOREFCOUNTNEXTGENANDROID- the
RunCountAttributeandRunCountsupport - a
CheckEqualsoverload foruint64 - a
CheckNotEqualsoverload forTCharArray CheckCircularRefCompareFloatRelativeNotSameErrorMessagewithWideStringarguments instead of string- a
TestDataDirfunction ReturnAddressfor older compilers
- a new unit
DUnitTestRunner - a new
Makefilefile - 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.