Search code examples
delphi-10.1-berlinspring4ddunitx

Memory issues when using spring.Nullable with DUnitX


Recently at my company we tried to use DUnitX with all it's blessings to test classes we wrote. Since those classes reflect entities in database all fields have to accept null values as well as specific type (e. g. Integer or string).

Since spring4d already have those we tried to use them:

INaleznosc = interface
  ['{D5D6C901-3DB9-4EC2-8070-EB0BEDBC7B06}']
  function DajPodstawaVAT(): TNullableCurrency;
  property PodstawaVAT: TNullableCurrency read DajPodstawaVAT;
end;

TNaleznosc = class(TInterfacedObject, INaleznosc)
strict private
  FId: TNullableInt64;
  FPodstawaVAT: Currency;
  function TNaleznosc.DajPodstawaVAT(): TNullableCurrency;
published
  property PodstawaVAT: TNullableCurrency read DajPodstawaVAT;
end; 

INaleznoscFunkcje = interface
  ['{509288AB-110A-4A52-BE93-3723E5725F4B}']
  function DajPodstawaVAT(pID: TNullableInt64): TNullableCurrency;
end;

function TNaleznosc.DajPodstawaVAT(): TNullableCurrency;
begin
  FPodstawaVAT := FFunkcje.DajPodstawaVAT(FId);
end;

procedure TTestNaleznosc.PodstawaVATGetterNieWywolujefunkcji();
var
  funkcjeNaleznosc: TMock<INaleznoscFunkcje>;
  klasa: INaleznosc;
  id: TNullableInteger;
begin
  //initialize tested elements
  funkcjeNaleznosc := TMock<INaleznoscFunkcje>.Create();
  id := 15;
  klasa := TNaleznosc.Create(funkcjeNaleznosc, id, zmienne);

  //setup expected behaviour from mock
  funkcjeNaleznosc.Setup.WillReturn(2).When.DajPodstawaVAT(id);
  funkcjeNaleznosc.Setup.Expect.Once.When.DajPodstawaVAT(id);

  //this triggers getter
  klasa.PodstawaVAT;
end;

When this code is executed we get AV exception First chance exception at $00000000. Exception class $C0000005 with message 'access violation at 0x00000000: access of address 0x00000000'. Process Tests.exe (6556).

Eventually we narrowed this issue down to Move procedure in System.Rtti unit, TValueDataImpl.ExtractRawDataNoCopy function: when Length(FData) is less or equal to 8 it works fine when Length(FData) is between 9 and 32 at line 5905 of System unit (FISTP QWORD PTR [EDX+8] {Save Second 8}) whole call stack disappears beside two lines (we are not sure whether it's relevant or not, but it doesn't look like good sign) and after getting to topmost function (according to call stack) we get error.

Call stack before "saving second 8"

Call stack after "saving second 8"

Is it our fault or is it some issue with system/spring/dunitx units? How can we use nullable types and tests at the same time?


Solution

  • I am not sure if Delphi Mocks has a generic type parameter on its WillReturn method but if so then pass TNullableCurrency there - otherwise the compiler will infer the type from the parameter 2 you are passing and obviously internally it fails to put that into the TNullableCurrency it should return.

    If it does not and only allows TValue then you need to pass one that contains a TNullableCurrency and not 2 which it would by using its implicit operator like so: TValue.From<TNullableCurrency>(2)

    Furthermore I am not sure if they did fix the code in the SameValue routine in Delphi Mocks when the value to be compared is a record (as TNullableCurrency is)

    Edit: no, they did not - see https://github.com/VSoftTechnologies/Delphi-Mocks/issues/39

    You might want to consider giving Spring4D mocks a try which should be able to handle nullables.