As it appears there are no viable code coverage tools for Lazarus, I have thought of the following stop-gap solution: set breakpoints on lines I want to be covered, and disable breakpoints which are hit. Once the program finishes, I'll go through the list of all breakpoints and report line numbers of those that are still active. Unfortunately, I can't get this approach to work.
My test program (hello.lpr
) looks like this:
program hello;
uses
Interfaces, Forms;
begin
Application.Initialize;
Application.MessageBox('Hello, world', 'Hello'); // Line 6
end.
This program (hello.exe
) is then build with debug info.
Next, I have made a test program which is expected to check for coverage. It's quite big because of the required boilerplate code borrowed from Lazarus CodeCoverage Helper, the important part is in TCovRunner.Run
and TCovRunner.OnBreakPointHit
methods:
program lazcov;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
Classes, Interfaces, SysUtils,
FpDebugDebugger, GDBMIDebugger, DbgIntfDebuggerBase, DbgIntfBaseTypes;
type
TCovRunner = class(TObject)
protected
Dbg: TDebuggerIntf;
procedure OnBreakPointHit(ADbg: TDebuggerIntf; ADbgBp: TBaseBreakPoint; var CanContinue: boolean);
public
procedure Run;
end;
TCovCallStackList = class(TCallStackList)
protected
function NewEntryForThread(const AThreadId: integer): TCallStackBase; override;
end;
TCovCallStackMonitor = class(TCallStackMonitor)
protected
function CreateCallStackList: TCallStackList; override;
end;
TCovThreadsMonitor = class;
TCovThreads = class(TThreads)
private
FMonitor: TCovThreadsMonitor;
FDataValidity: TDebuggerDataState;
public
constructor Create;
function Count: integer; override;
procedure Clear; override;
procedure SetValidity(AValidity: TDebuggerDataState); override;
end;
TCovThreadsMonitor = class(TThreadsMonitor)
protected
procedure DoStateEnterPause; override;
function CreateThreads: TThreads; override;
procedure RequestData;
end;
procedure TCovThreadsMonitor.DoStateEnterPause;
begin
inherited DoStateEnterPause;
TCovThreads(Threads).SetValidity(ddsUnknown);
end;
function TCovThreadsMonitor.CreateThreads: TThreads;
begin
Result := TCovThreads.Create;
TCovThreads(Result).FMonitor := Self;
end;
procedure TCovThreadsMonitor.RequestData;
begin
if Supplier <> nil then
Supplier.RequestMasterData;
end;
constructor TCovThreads.Create;
begin
inherited Create;
FDataValidity := ddsUnknown;
end;
function TCovThreads.Count: integer;
begin
if (FDataValidity = ddsUnknown) then begin
FDataValidity := ddsRequested;
FMonitor.RequestData;
end;
Result := inherited Count;
end;
procedure TCovThreads.Clear;
begin
FDataValidity := ddsUnknown;
inherited Clear;
end;
procedure TCovThreads.SetValidity(AValidity: TDebuggerDataState);
begin
if FDataValidity = AValidity then begin
exit;
end;
FDataValidity := AValidity;
if FDataValidity = ddsUnknown then begin
Clear;
end;
end;
function TCovCallStackMonitor.CreateCallStackList: TCallStackList;
begin
Result := TCovCallStackList.Create;
end;
function TCovCallStackList.NewEntryForThread(const AThreadId: integer): TCallStackBase;
begin
Result := TCallStackBase.Create;
Result.ThreadId := AThreadId;
add(Result);
end;
procedure TCovRunner.Run;
var
DbgBp: TDBGBreakPoint;
i: Integer;
begin
// Create a debugger instance
Dbg := TGDBMIDebugger.Create('hello.exe');
Dbg.OnBreakPointHit := @OnBreakPointHit;
Dbg.Threads.Monitor := TCovThreadsMonitor.Create;
Dbg.CallStack.Monitor := TCovCallStackMonitor.Create;
// Set a breakpoint on line 6
DbgBp := Dbg.BreakPoints.Add('hello.lpr', 6);
DbgBp.Enabled := True;
try
Dbg.Init;
Dbg.Run;
finally
Dbg.Done;
// Check breakpoint number 0
DbgBp := Dbg.BreakPoints[0];
if DbgBp.Enabled then begin
WriteLn('no coverage on line '+ IntToStr(DbgBp.Line));
end;
Dbg.Free;
end;
end;
procedure TCovRunner.OnBreakPointHit(ADbg: TDebuggerIntf; ADbgBp: TBaseBreakPoint; var CanContinue: boolean);
begin
ADbgBp.Enabled := False;
CanContinue := True;
end;
var
CovRunner: TCovRunner;
begin
CovRunner := TCovRunner.Create;
CovRunner.Run;
end.
When I run lazcov.exe
, it initially starts hello.exe
under the debugger:
However, once I click on the OK button (thus terminating hello.exe
), the debugger reports an error "No process running" and claims that the breakpoints on line 6 was not hit:
Why is my breakpoint not working? Did I go wrong somewhere in the initialization? Any insights on how I can debug the issue?