我正在使用DUnit和FastMM来捕获未完成的内存块,但似乎有一个Bug。我不知道如果它在FastMM,DUnit或Delphi本身,但这里是:
当我的测试用例有内部字符串时,测试失败,内存泄漏。如果我在没有关闭DUnit GUI的情况下再次运行相同的测试,则测试通过OK。使用DUnit GUI Testing也是如此,我相信出于同样的原因。我的应用程序中没有泄漏,证明FastMM在这些情况下不会生成泄漏报告。
问题1:如果没有设置AllowedMemoryLeakSize
问题2:我正在使用Delphi 7,如果在Delphi XE中修复此任何消息?
我的实际测试配置:
以下是示例代码(仅限实现)
procedure TTest.Setup;
begin
A := 'test';
end;
procedure TTest.TearDown;
begin
// nothing here :)
end;
procedure TTest.Test;
begin
CheckTrue(True);
end;
感谢!!!!
更新:我面临的问题记录在http://members.optusnet.com.au/mcnabp/Projects/HIDUnit/HIDUnit.html#memoryleakdetection中 但是,除了再次运行相同的测试之外,相同的链接不会提供解决方案。
答案 0 :(得分:1)
我会首先从Subversion尝试当前版本的(但是这个版本不适用于Delphi 7,只有2007和更新版本):
在commit log中,有一个版本对区域中的修复有评论
Revision 40 Modified Fri Apr 15 23:21:27 2011 UTC(14 months ago)
移出JclStartExcetionTracking和JclStopExceptionTracking DUnit递归以防止无效的内存泄漏报告
答案 1 :(得分:1)
我找到了一种减轻问题的方法:我没有使用Strings,而是在Test Classes中使用了ShortStrings和WideStrings。没有任何泄漏。
这不是解决方案,顺便说一句,这似乎是在最新的Delphi版本中解决的。
答案 2 :(得分:1)
实际上,严格来说,您的测试 会在第一次运行时泄漏内存 。
它是不 FastMM,DUnit或Delphi中的错误,这个错误在你的测试中。
让我们首先清除误解,并解释一些内部运作:
这里的问题是,如果没有检测到泄漏,FastMM会给你一种错误的安全感。原因是任何类型的泄漏检测都必须从检查点寻找泄漏。如果在Start检查点之后完成的所有分配都由End checkpoint恢复 - 一切都很酷。
因此,如果您创建一个全局对象Bin,并将所有对象发送到Bin而不销毁它们,那么执行会导致内存泄漏。继续运行,你的应用程序将耗尽内存。但是,如果Bin在FastMM End检查点之前销毁其所有对象,则FastMM不会发现任何不实用的内容。
您的测试中发生的事情是FastMM的检查点范围比DUnit泄漏检测更广泛。您的测试会泄漏内存,但该内存稍后会在FastMM检查时恢复。
DUnit为每个测试用例创建一个单独的测试类实例。但是,这些实例将在每次测试运行时重复使用。简化的事件顺序如下:
因此,如果这三种方法之间存在泄漏 - 即使泄漏仅发生在实例上,并且一旦对象被销毁就会恢复 - 将报告泄漏。在您的情况下,当对象被销毁时泄漏被恢复。因此,如果DUnit创造了&每次运行都会破坏测试类,不会报告任何泄漏。
注意这是设计使然,所以你不能把它称为错误。
基本上,对于您的测试必须100%自包含的原则,DUnit非常严格。从SetUp到TearDown,必须恢复您(直接/间接)分配的任何内存。
每当您对StringVar := 'SomeLiteralString'
或StringVar := SomeConstString
或StringVar := SomeResourceString
进行编码时,都会复制该常量的值(是,已复制 - 未计算引用次数)
同样,这是设计的。目的是如果从库中检索字符串,则不会删除该字符串如果库已卸载。所以这不是一个真正的错误,只是一个“不方便”的设计。
因此,第一次运行时测试代码泄漏内存的原因是A := 'test'
正在为“test”的副本分配内存。在随后的运行中,进行了另一个“test”副本,并且前一个副本被销毁 - 但是内存分配是相同的。
在这种特殊情况下的解决方案是微不足道的。
procedure TTest.TearDown;
begin
A := ''; //Remove the last reference to the copy of "test" and presto leak is gone :)
end;
总的来说,你不应该做更多的事情。如果您的测试创建引用常量字符串副本的子对象,则在销毁子对象时将销毁这些副本。
但是,如果你的任何测试将对字符串的引用传递给任何全局对象/单例(顽皮,顽皮,你知道你不应该这样做),那么你就会泄露一个引用,因此也有一些记忆 - 即使它稍后恢复。
回到关于DUnit如何运行测试的讨论。相同测试的单独运行可能相互干扰。 E.g。
procedure TTestLeaks.SetUp;
begin
FSwitch := not FSwitch;
if FSwitch then Fail('This test fails every second run.');
end;
扩展这个想法,你可以让你的测试在第一次和每一次(偶数)运行时“泄漏”内存。
procedure TTestLeaks.SetUp;
begin
FSwitch := not FSwitch;
case FSwitch of
True : FString := 'Short';
False : FString := 'This is a long string';
end;
end;
procedure TTestLeaks.TearDown;
begin
// nothing here :( <-- note the **correct** form for the smiley
end;
这并不会导致内存的总体消耗增加,因为每次备用运行都会恢复每次运行时泄漏的相同内存量。
字符串复制会导致一些有趣的(也许是意外的)行为。
var
S1, S2: string;
begin
S1 := 'Some very very long string literal';
S2 := S1; { A pointer copy and increased ref count }
if (S1 = S2) then { Very quick comparison because both vars point to the same address, therefore they're obviously equal. }
end;
然而....
const
CLongStr = 'Some very very long string literal';
var
S1, S2: string;
begin
S1 := CLongStr;
S2 := CLongStr; { A second **copy** of the same constant is allocated }
if (S1 = S2) then { A full comparison has to be done because there is no shortcut to guarantee they're the same. }
end;
这确实提出了一个有趣的,虽然极端且可能是不明智的解决方法,仅仅是由于这种方法的荒谬:
const
CLongStr = 'Some very very long string literal';
var
GlobalLongStr: string;
initialization
GlobalLongStr := CLongStr; { Creates a copy that is safely on the heap so it will be allowed to be reference counted }
//Elsewhere in a test
procedure TTest.SetUp;
begin
FString1 := GlobalLongStr; { A pointer copy and increased ref count }
FString2 := GlobalLongStr; { A pointer copy and increased ref count }
if (FString1 = FString2) then { Very efficient compare }
end;
procedure TTest.TearDown;
begin
{... and no memory leak even though we aren't clearing the strings. }
end;
是的,显然这篇冗长的帖子即将结束。
非常感谢您提出这个问题 它给了我一个关于我记得经历过一段时间的相关问题的线索。在我有机会证实我的理论之后,我会发布一个Q&amp;一个;其他人也可能觉得它很有用。
答案 3 :(得分:0)
底线是检测到的泄漏可能与正在执行的测试用例无关,但在检测到它时是合法的泄漏。在进入SetUp过程之前,字符串的内存未分配,并且在退出TearDown
过程之前未释放。因此,在重新分配字符串变量或销毁测试用例之前,它是内存泄漏。
对于字符串和动态数组,您可以在SetLength(<VarName>, 0)
过程中使用TearDown
。