lazarus/components/lazdebuggergdbmi/test/gdbmitestutils/testbase.pas

522 lines
14 KiB
ObjectPascal

unit TestBase;
{$mode objfpc}{$H+}
{$ModeSwitch typehelpers}
interface
uses
Classes, SysUtils, FileUtil, fpcunit, testregistry, LCLProc, LazLogger,
LazFileUtils, DbgIntfDebuggerBase, Dialogs, Forms, RegExpr, GDBMIDebugger,
TestDbgConfig, TestDbgTestSuites, TTestDbgExecuteables, TTestDebuggerClasses,
TestDbgCompilerProcess, TestDbgControl, TestOutputLogger, LazDebuggerIntf,
LazDebuggerIntfBaseTypes; // , FpGdbmiDebugger;
// EnvironmentOpts, ExtToolDialog, TransferMacros,
const
stDwarf2All = [stDwarf, stDwarfSet];
stDwarfAll = [stDwarf, stDwarfSet, stDwarf3];
stSymAll = [stStabs, stDwarf, stDwarfSet, stDwarf3];
TWatchDisplayFormatNames: array [TWatchDisplayFormat] of string =
('wdfDefault',
'wdfStructure',
'wdfChar', 'wdfString',
'wdfDecimal', 'wdfUnsigned', 'wdfFloat', 'wdfHex',
'wdfPointer',
'wdfMemDump', 'wdfBinary'
);
type
TGDBMIDebuggerClass = class of TGDBMIDebugger;
{ TTestDebuggerHelper }
TTestDebuggerHelper = class helper for TDebuggerIntf
procedure AddTestBreakPoint(AFilename: String; ALine: Integer; AEnabled: Boolean = True);
end;
TDebuggerInfo = TExternalExeInfo;
TCompilerInfo = TExternalExeInfo;
{ TCompilerList }
TCompilerList = TBaseList;
TCompilerListHelper = class helper for TBaseList
private
function GetCompilerInfo(Index: Integer): TCompilerInfo;
public
property CompilerInfo[Index: Integer]: TCompilerInfo read GetCompilerInfo;
end;
{ TDebuggerList }
TDebuggerList = TBaseList;
TDebuggerListHelper = class helper for TBaseList
private
function GetDebuggerInfo(Index: Integer): TDebuggerInfo;
public
property DebuggerInfo[Index: Integer]: TDebuggerInfo read GetDebuggerInfo;
end;
{ TCompilerSuite }
TCompilerSuite = class(TDBGTestsuite)
private
FSymbolSwitch: String;
FFileNameExt: String;
FCompileProcess: TCompilerProcess;
function GetCompilerInfo: TExternalExeInfo;
function GetDebuggerInfo: TExternalExeInfo;
function GetSymbolType: TSymbolType;
protected
public
constructor Create; reintroduce; overload; override;
public
property SymbolType: TSymbolType read GetSymbolType;
property SymbolSwitch: String read FSymbolSwitch;
property CompilerInfo: TExternalExeInfo read GetCompilerInfo;
property DebuggerInfo: TExternalExeInfo read GetDebuggerInfo;
end;
{ TGDBMIDebuggerForTest }
var
FEvalDone: Boolean;
FEvalRes: String;
FEvalResType: TDBGType;
type
TGDBMIDebuggerForTest = class helper for TGDBMIDebugger
private
procedure EvalCallBack(Sender: TObject; ASuccess: Boolean;
ResultText: String; ResultDBGType: TDBGType);
public
function EvaluateWait(const AExpression: String; var ARes: String;
var AResType: TDBGType; EvalFlags: TWatcheEvaluateFlags = []; ATimeOut: Integer = -1): Boolean;
end;
{ TGDBTestCase }
TGDBTestCase = class(TDBGTestCase)
private
FTotalGDBInternalErrorCnt, FTotalDsErrorCrash: Integer;
FTotalClassVsRecord: Integer;
FStartTime: QWord;
FLogDebuglnCount: Integer;
function GetCompilerInfo: TCompilerInfo;
function GetSymbolType: TSymbolType;
function GetWatches: TTestWatchesMonitor;
protected
procedure DoDbgOut(Sender: TObject; S: string; var Handled: Boolean); override;
procedure DoDebugln(Sender: TObject; S: string; var Handled: Boolean); override;
procedure SetUp; override;
function GetFinalLogFileName: String; override;
procedure DoDbgOutPut(Sender: TObject; const AText: String); virtual;
procedure InternalDbgOutPut(Sender: TObject; const AText: String);
function InternalFeedBack(Sender: TObject; const AText, AInfo: String;
AType: TDBGFeedbackType; AButtons: TDBGFeedbackResults): TDBGFeedbackResult;
procedure InternalDbgEvent(Sender: TObject;
const ACategory: TDBGEventCategory; const AEventType: TDBGEventType;
const AText: String);
function GdbClass: TGDBMIDebuggerClass; virtual;
function StartGDB(AppDir, TestExeName: String): TGDBMIDebugger;
procedure CleanGdb;
function GetDebuggerInfo: TDebuggerInfo;
property TotalClassVsRecord: Integer read FTotalClassVsRecord write FTotalClassVsRecord;
property TotalDsErrorCrash: Integer read FTotalDsErrorCrash write FTotalDsErrorCrash;
public
procedure LogToFile(const s: string);
public
property DebuggerInfo: TDebuggerInfo read GetDebuggerInfo;
property SymbolType: TSymbolType read GetSymbolType;
property CompilerInfo: TCompilerInfo read GetCompilerInfo;
public
property Watches: TTestWatchesMonitor read GetWatches;
end;
function GetCompilers: TCompilerList;
function GetDebuggers: TDebuggerList;
var
TestGdbClass: TGDBMIDebuggerClass = TGDBMIDebugger;
// TestGdbClass: TGDBMIDebuggerClass = TFPGDBMIDebugger;
implementation
var
Compilers: TCompilerList = nil;
Debuggers: TDebuggerList = nil;
function GetCompilers: TCompilerList;
begin
if Compilers <> nil then exit(Compilers);
Compilers := TCompilerList(LoadConfig(ConfDir + 'fpclist.txt'));
Result := Compilers;
end;
function GetDebuggers: TDebuggerList;
begin
if Debuggers <> nil then exit(Debuggers);
Debuggers := TDebuggerList(LoadConfig(ConfDir + 'gdblist.txt'));
Result := Debuggers;
end;
{ TTestDebuggerHelper }
procedure TTestDebuggerHelper.AddTestBreakPoint(AFilename: String;
ALine: Integer; AEnabled: Boolean);
begin
with BreakPoints.Add(AFilename, ALine, True) do begin
Enabled := AEnabled;
InitialEnabled := AEnabled;
EndUpdate;
end;
end;
{ TGDBMIDebuggerForTest }
procedure TGDBMIDebuggerForTest.EvalCallBack(Sender: TObject;
ASuccess: Boolean; ResultText: String; ResultDBGType: TDBGType);
begin
FEvalRes := ResultText;
FEvalResType := ResultDBGType;
FEvalDone := true;
end;
function TGDBMIDebuggerForTest.EvaluateWait(const AExpression: String;
var ARes: String; var AResType: TDBGType; EvalFlags: TWatcheEvaluateFlags;
ATimeOut: Integer): Boolean;
var
t: QWord;
begin
FEvalResType := nil;
FEvalDone := false;
t := GetTickCount64;
inherited Evaluate(AExpression, @EvalCallBack, EvalFlags);
while not FEvalDone do begin
Application.ProcessMessages;
sleep(5);
if ATimeOut > 0 then begin
if GetTickCount64 - t > ATimeOut then
break;
end;
end;
ARes := FEvalRes;
AResType := FEvalResType;
end;
{ TGDBTestCase }
procedure TGDBTestCase.DoDbgOutPut(Sender: TObject; const AText: String);
begin
//
end;
procedure TGDBTestCase.InternalDbgOutPut(Sender: TObject; const AText: String);
begin
//LogToFile(AText);
DoDbgOutPut(Sender, AText);
end;
function TGDBTestCase.GdbClass: TGDBMIDebuggerClass;
begin
Result := TestGdbClass;
end;
procedure TGDBTestCase.DoDbgOut(Sender: TObject; S: string; var Handled: Boolean);
begin
DoDebugln(Sender, '| '+S, Handled);
end;
procedure TGDBTestCase.DoDebugln(Sender: TObject; S: string; var Handled: Boolean);
begin
inherited DoDebugln(Sender, S, Handled);
if pos('(gdb)', s) > 0 then begin
inc(FLogDebuglnCount);
if FLogDebuglnCount mod 10 = 0 then begin
TestLogger.DebugLn([FLogDebuglnCount]);
end;
end;
end;
function TGDBTestCase.GetWatches: TTestWatchesMonitor;
begin
Result := Debugger.Watches;
end;
function TGDBTestCase.InternalFeedBack(Sender: TObject; const AText, AInfo: String;
AType: TDBGFeedbackType; AButtons: TDBGFeedbackResults): TDBGFeedbackResult;
begin
Result := frOk;
DebugLn(['**** Feedback requested ****: ', AText]);
DebugLn(['**** ', AInfo]);
end;
procedure TGDBTestCase.InternalDbgEvent(Sender: TObject;
const ACategory: TDBGEventCategory; const AEventType: TDBGEventType;
const AText: String);
begin
case ACategory of
ecBreakpoint: ;
ecProcess: ;
ecThread: ;
ecModule: ;
ecOutput: ;
ecWindows: ;
ecDebugger: begin
case AEventType of
etDefault: begin
// maybe crash / internal error? Text from IDE not GDB (po file)
if (Pos('internal error:', LowerCase(AText)) > 0) then
inc(FTotalGDBInternalErrorCnt);
end;
end;
end;
end;
end;
function TGDBTestCase.GetCompilerInfo: TCompilerInfo;
begin
Result := TCompilerSuite(Parent).CompilerInfo;
end;
function TGDBTestCase.GetDebuggerInfo: TDebuggerInfo;
begin
Result := TCompilerSuite(Parent).DebuggerInfo;
end;
function TGDBTestCase.GetSymbolType: TSymbolType;
begin
Result := TCompilerSuite(Parent).SymbolType;
end;
procedure TGDBTestCase.SetUp;
begin
FLogDebuglnCount := 0;
FTotalGDBInternalErrorCnt := 0;
FTotalDsErrorCrash := 0;
FTotalClassVsRecord := 0;
FStartTime := GetTickCount64;
inherited SetUp;
end;
function TGDBTestCase.GetFinalLogFileName: String;
var
i: QWord;
begin
Result := inherited GetFinalLogFileName;
i := GetTickCount64;
if i >= FStartTime then
i := i - FStartTime
else
i := high(QWord) - FStartTime + 1 + i;
if FTotalGDBInternalErrorCnt > 0
then Result := Result + '___gdb_intern.'+IntToStr(FTotalGDBInternalErrorCnt);
if FTotalDsErrorCrash > 0
then Result := Result + '___gdb_crash.'+IntToStr(FTotalDsErrorCrash);
if FTotalClassVsRecord > 0
then Result := Result + '___class_re._'+IntToStr(FTotalClassVsRecord);
// Result := Result + '___time.'+ IntToStr(i div 1000);
end;
function TGDBTestCase.StartGDB(AppDir, TestExeName: String): TGDBMIDebugger;
begin
Result := GdbClass.Create(DebuggerInfo.ExeName);
try
Debugger.LazDebugger := Result;
Result.OnDbgOutput := @InternalDbgOutPut;
Result.OnFeedback := @InternalFeedBack;
Result.OnDbgEvent:=@InternalDbgEvent;
Debugger.InitDebuggerMonitors(Result);
Result.Init;
if Result.State = dsError then
Fail(' Failed Init');
Result.WorkingDir := AppDir;
Result.FileName := TestExeName;
Result.Arguments := '';
Result.ShowConsole := True;
except
on e: Exception do
Fail('INIT Exception: '+E.Message);
end;
end;
procedure TGDBTestCase.CleanGdb;
begin
Debugger.ClearDebuggerMonitors;
end;
procedure TGDBTestCase.LogToFile(const s: string);
begin
LogText('## '+s);
end;
{ TCompilerListHelper }
function TCompilerListHelper.GetCompilerInfo(Index: Integer): TCompilerInfo;
begin
Result := FullInfo[Index];
end;
{ TCompilerListHelper }
function TDebuggerListHelper.GetDebuggerInfo(Index: Integer): TDebuggerInfo;
begin
Result := FullInfo[Index];
end;
{ TCompilerSuite }
function TCompilerSuite.GetCompilerInfo: TExternalExeInfo;
begin
Result := Compiler.FullInfo;
end;
function TCompilerSuite.GetDebuggerInfo: TExternalExeInfo;
begin
Result := Debugger.FullInfo;
end;
function TCompilerSuite.GetSymbolType: TSymbolType;
begin
Result := Compiler.SymbolType;
end;
constructor TCompilerSuite.Create;
begin
inherited Create;
FSymbolSwitch := SymbolTypeSwitches[SymbolType];
FFileNameExt := SymbolTypeNames[SymbolType] + '_' + NameToFileName(CompilerInfo.Name);
end;
{ --- }
procedure BuildTestSuites;
var
FpcList: TCompilerList;
GdbList: TDebuggerList;
begin
FpcList := GetCompilers;
GdbList := GetDebuggers;
CreateCompilerList(FpcList, TTestDbgCompiler);
CreateDebuggerList(GdbList, TTestDbgDebugger);
CreateTestSuites(TestDbgCompilerList, TestDbgDebuggerList, TCompilerSuite);
TestControlRegisterCompilers(FpcList);
TestControlRegisterDebuggers(GdbList);
end;
function CheckAppDir(var AppDir: string): Boolean;
begin
Result := DirectoryExistsUTF8(AppDir + 'TestApps');
end;
function CheckAppDirLib(var AppDir: string): Boolean;
var
s: string;
begin
Result := False;
if RightStr(AppDir, length('lib' + DirectorySeparator)) = 'lib' + DirectorySeparator
then begin
s := copy(AppDir, 1, length(AppDir) - length('lib' + DirectorySeparator));
Result := DirectoryExistsUTF8(s + 'TestApps');
if Result then
AppDir := s;
end;
end;
function AppDirStripAppBundle(AppDir: string): String;
var
p: LongInt;
begin
Result := AppDir;
p := pos('.app' + DirectorySeparator, AppDir);
while (p > 1) and (AppDir[p-1] <> DirectorySeparator) do
dec(p);
if p > 1 then
Result := Copy(AppDir, 1, p - 1);
end;
initialization
// GDBMIDebugger is un uses
DebugLogger.FindOrRegisterLogGroup('DBG_CMD_ECHO' , True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('DBGMI_QUEUE_DEBUG' , True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('DBGMI_STRUCT_PARSER' , True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' , True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS', True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('DBG_DISASSEMBLER', True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('DBGMI_TYPE_INFO', True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('DBGMI_TIMEOUT_DEBUG', True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('DBG_THREAD_AND_FRAME', True )^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('FPDBG_DWARF_ERRORS', True);
DebugLogger.FindOrRegisterLogGroup('FPDBG_DWARF_SEARCH', True)^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('FPDBG_DWARF_WARNINGS', True)^.Enabled := True;
DebugLogger.FindOrRegisterLogGroup('FPDBG_DWARF_VERBOSE', True);
DebugLogger.FindOrRegisterLogGroup('FPDBG_DWARF_DATA_WARNINGS', True);
AppDir := AppendPathDelim(ExtractFilePath(Paramstr(0)));
if not(CheckAppDir(AppDir))
and not(CheckAppDirLib(AppDir))
then begin
AppDir := AppDirStripAppBundle(AppDir);
if not(CheckAppDir(AppDir))
and not(CheckAppDirLib(AppDir))
then
with TSelectDirectoryDialog.Create(nil) do begin
if Execute then AppDir := AppendPathDelim(FileName);
Free;
end;
end;
ConfDir := AppDir;
AppDir := AppendPathDelim(AppDir + 'TestApps');
if DirectoryExistsUTF8(ConfDir+'logs') then
TestControlSetLogPath(ConfDir+'logs'+DirectorySeparator)
else if DirectoryExistsUTF8(ConfDir+'log') then
TestControlSetLogPath(ConfDir+'log'+DirectorySeparator)
else
TestControlSetLogPath(ConfDir);
//EnvironmentOptions := TEnvironmentOptions.Create;
//with EnvironmentOptions do
//begin
// CreateConfig;
// Load(false);
//end;
//GlobalMacroList:=TTransferMacroList.Create;
BuildTestSuites;
finalization
FreeAndNil(Compilers);
FreeAndNil(Debuggers);
//FreeAndNil(EnvironmentOptions);
end.