mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-10 00:52:42 +02:00
522 lines
14 KiB
ObjectPascal
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.
|
|
|