0

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:

enter image description here

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:

enter image description here

Why is my breakpoint not working? Did I go wrong somewhere in the initialization? Any insights on how I can debug the issue?

Dmitry Grigoryev
  • 3,156
  • 1
  • 25
  • 53
  • Could my previous [answer](https://stackoverflow.com/a/33108233/5043424) help you? – asd-tm Jun 23 '23 at 19:39
  • @asd-tm Thanks, but I'm 100% sure the "MessageBox" line is executed. The message box itself is displayed, and I don't see how it could appear without executing that line. – Dmitry Grigoryev Jun 26 '23 at 14:57
  • @asd-tm And just make this clear, I'm not trying to debug hello.exe with the IDE. I'm trying to debug it from another program. – Dmitry Grigoryev Jun 26 '23 at 15:00

0 Answers0