views:

343

answers:

5

I am writing a unit test infrastructure for a large Delphi code base. I would like to link calls to pure functions in SysUtils.FileExists for example to a "MockSysUtils.FileExists" instead.

Creating a SysUtils unit with the same interface is not appreciated by the compiler.

What I am thinking of is to hook in my mock function at runtime. Is this possible nowadays?

Any other suggestions?

Regards,

Peter

+7  A: 

Replacing a function at runtime is difficult but usually technically possible. "All" you need to do is:

  • take the address of the function in question
  • disassemble the first 5 bytes or so (to check for a RET instruction - very small routines may abut another routine, preventing you from replacing it)
  • change its page protection (with VirtualProtect) to be writable
  • rewrite the first 5 bytes with a JMP rel32 instruction (i.e. E9 <offset-to-your-func>)
  • implement your version function as normal, making sure it has the same arguments and calling convention as the function you are mocking

An easier approach would be to link against a different version of SysUtils.pas. That will require you to also recompile all the units in the RTL and VCL that depend on SysUtils.pas, but it is likely quite a bit easier than the function intrumentation approach described above.

The easiest approach is the language-level one, where either you don't directly rely on SysUtils at all (and so can switch at a higher level), or you modify the uses declaration to conditionally refer to a different unit.

Barry Kelly
"or you modify the uses declaration to conditionally refer to a different unit." - strikes me that this must be the easiest for this. Make sure your special unit is last, and then you can redefine any function.
mj2008
It assumes that the unit source is available. If it is, then it's a no-brainer; however, the questioner did specifically ask about runtime!
Barry Kelly
I like irony :) But you're completely right. +1
furtelwart
A: 

Thanks,

yes, it would be great to have TSysUtils class for example instead that I could inherit with my MockSysUtils. But, that is not the case and the code base huge. It will be replaced bit by bit, but I wondered if there was a quick-start solution.

The first approach is ok for one function perhaps, but not in this case I guess.

I will go for the second approach.

+6  A: 

You can do it with MadCodeHook. Use the HookCode function, give it the address of the function you want to replace and the address of the function you want to be called instead. It will give you back a function pointer that you can use for calling the original and for unhooking afterward. In essence, it implements the middle three steps of Barry's description.

I think MadCodeHook is free for personal use. If you're looking for something freer than that, you can try to find an old version of the Tnt Unicode controls. It used the same hooking technique to inject Unicode support into some of the VCL's code. You'll need an old version because more recent releases aren't free anymore. Find the OverwriteProcedure function in TntSystem.pas, which is also where you'll find examples of how to use it.

Code-hooking is nice because it doesn't require you to recompile the RTL and VCL, and it doesn't involve conditional compilation to control which functions are in scope. You can hook the code from your unit-test setup procedure, and the original code will never know the difference. It will think it's calling the original FileExists function (because it is), but when it gets there, it will immediately jump to your mocked version instead.

Rob Kennedy
A: 

This is slightly way out there but here is another alternative.

When building your unit tests and your main codebase to go with it, you could grep all the functions you wish to replace and specify the unit to use

Instead of

fileexists(MyFilename);

you could grep fileexists and replace with

MockTests.fileexists(MyFileName);

If you did this at build time (using automated build tools) it could easily be done and would provide you with the greatest flexibility. You could simply have a config file that listed all the functions to be replaced.

Toby Allen
+1  A: 

You could also just add a unit that only contains the functions you want to mock to the test unit's uses clause. Delphi will always use the function from the unit that is listed last. Unfortunately this would require you to change the unit you want to test.

Your Mock-Sysutils unit:

unit MockSysutils;

interface

function FileExists(...) ...
...
end.

Your unit, you want to test:

unit UnitTotest;

interface

uses
  Sysutils,
  MockSysUtils;

...

  if FileExists(...) then

FileExists will now call the version from MockSysutils rather than from Sysutils.

dummzeuch