mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-07 06:08:22 +02:00
541 lines
14 KiB
ObjectPascal
541 lines
14 KiB
ObjectPascal
{$mode objfpc}
|
|
{$H+}
|
|
|
|
unit dbtests;
|
|
|
|
Interface
|
|
|
|
Uses
|
|
sqldb, testu;
|
|
|
|
{ ---------------------------------------------------------------------
|
|
High-level access
|
|
---------------------------------------------------------------------}
|
|
|
|
Function GetTestID(Name : string) : Integer;
|
|
Function GetOSID(Name : String) : Integer;
|
|
Function GetCPUID(Name : String) : Integer;
|
|
Function GetCategoryID(Name : String) : Integer;
|
|
Function GetVersionID(Name : String) : Integer;
|
|
Function GetRunID(OSID, CPUID, VERSIONID : Integer; Date : TDateTime) : Integer;
|
|
Function AddRun(OSID, CPUID, VERSIONID, CATEGORYID : Integer; Date : TDateTime) : Integer;
|
|
Function AddTest(Name : String; AddSource : Boolean) : Integer;
|
|
Function UpdateTest(ID : Integer; Info : TConfig; Source : String) : Boolean;
|
|
Function AddTestResult(TestID,RunID,TestRes : Integer;
|
|
OK, Skipped : Boolean;
|
|
Log : String;var count_it : boolean) : Integer;
|
|
Function RequireTestID(Name : String): Integer;
|
|
Function CleanTestRun(ID : Integer) : Boolean;
|
|
function GetTestPreviousRunHistoryID(TestRunID : Integer) : Integer;
|
|
function GetTestNextRunHistoryID(TestRunID : Integer) : Integer;
|
|
function AddTestHistoryEntry(TestRunID,TestPreviousID : Integer) : boolean;
|
|
|
|
{ ---------------------------------------------------------------------
|
|
Low-level DB access.
|
|
---------------------------------------------------------------------}
|
|
|
|
Function ConnectToDatabase(DatabaseName,Host,User,Password,Port : String) : Boolean;
|
|
Procedure DisconnectDatabase;
|
|
Function InsertQuery(const Query : string) : Integer;
|
|
Function ExecuteQuery (Qry : String; Silent : Boolean) : Boolean ;
|
|
Function OpenQuery (Qry : String; Out Res : TSQLQuery; Silent : Boolean) : Boolean ;
|
|
Procedure FreeQueryResult (Var Res : TSQLQuery);
|
|
Function GetResultField (Res : TSQLQuery; Id : Integer) : String;
|
|
Function IDQuery(Qry : String) : Integer;
|
|
Function StringQuery(Qry : String) : String;
|
|
Function EscapeSQL( S : String) : String;
|
|
Function SQLDate(D : TDateTime) : String;
|
|
|
|
|
|
var
|
|
RelSrcDir,
|
|
TestSrcDir : string;
|
|
|
|
Implementation
|
|
|
|
Uses
|
|
SysUtils, pqconnection;
|
|
|
|
Var
|
|
Connection : TPQConnection;
|
|
|
|
{ ---------------------------------------------------------------------
|
|
Low-level DB access.
|
|
---------------------------------------------------------------------}
|
|
|
|
Function ConnectToDatabase(DatabaseName,Host,User,Password,Port : String) : Boolean;
|
|
|
|
begin
|
|
Result:=False;
|
|
Verbose(V_SQL,'Connection params : '+DatabaseName+' '+Host+' '+User+' '+Port);
|
|
Connection:=TPQConnection.Create(Nil);
|
|
try
|
|
Connection.Hostname:=Host;
|
|
Connection.DatabaseName:=DatabaseName;
|
|
Connection.Username:=User;
|
|
Connection.Password:=Password;
|
|
Connection.Connected:=true;
|
|
Connection.Transaction:=TSQLTransaction.Create(Connection);
|
|
if (Port<>'') then
|
|
Connection.Params.Values['Port']:=Port;
|
|
except
|
|
On E : Exception do
|
|
begin
|
|
Verbose(V_ERROR,'Failed to connect to database : '+E.Message);
|
|
FreeAndNil(Connection);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
Procedure DisconnectDatabase;
|
|
|
|
begin
|
|
FreeAndNil(Connection);
|
|
end;
|
|
|
|
Function CreateQuery(Const ASQL : String) : TSQLQuery;
|
|
|
|
begin
|
|
Result:=TSQLQuery.Create(Connection);
|
|
Result.Database:=Connection;
|
|
Result.Transaction:=Connection.Transaction;
|
|
Result.SQL.Text:=ASQL;
|
|
end;
|
|
|
|
|
|
|
|
Function ExecuteQuery (Qry : String; Silent : Boolean) : Boolean ;
|
|
|
|
begin
|
|
Verbose(V_SQL,'Executing query:'+Qry);
|
|
Result:=False;
|
|
try
|
|
With CreateQuery(Qry) do
|
|
try
|
|
ExecSQL;
|
|
Result:=True;
|
|
(Transaction as TSQLTransaction).Commit;
|
|
finally
|
|
Free;
|
|
end;
|
|
except
|
|
On E : exception do
|
|
begin
|
|
Connection.Transaction.RollBack;
|
|
if not Silent then
|
|
Verbose(V_WARNING,'Query : '+Qry+'Failed : '+E.Message);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
Function OpenQuery (Qry : String; Out res : TSQLQuery; Silent : Boolean) : Boolean ;
|
|
|
|
begin
|
|
Result:=False;
|
|
Verbose(V_SQL,'Running query:'+Qry);
|
|
Res:=CreateQuery(Qry);
|
|
try
|
|
Res.Open;
|
|
Result:=True;
|
|
except
|
|
On E : exception do
|
|
begin
|
|
FreeAndNil(Res);
|
|
Try
|
|
Connection.Transaction.RollBack;
|
|
except
|
|
end;
|
|
if not Silent then
|
|
Verbose(V_WARNING,'Query : '+Qry+'Failed : '+E.Message);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
Function GetResultField (Res : TSQLQuery; Id : Integer) : String;
|
|
|
|
|
|
begin
|
|
If (Res=Nil) or (ID>=Res.Fields.Count) then
|
|
Result:=''
|
|
else
|
|
Result:=Res.Fields[ID].AsString;
|
|
Verbose(V_SQL,'Field value '+Result);
|
|
end;
|
|
|
|
Procedure FreeQueryResult(var Res : TSQLQuery);
|
|
|
|
begin
|
|
if Assigned(Res) and Assigned(Res.Transaction) then
|
|
(Res.Transaction as TSQLTransaction).Commit;
|
|
FreeAndNil(Res);
|
|
end;
|
|
|
|
Function IDQuery(Qry : String) : Integer;
|
|
|
|
Var
|
|
Res : TSQLQuery;
|
|
|
|
begin
|
|
Result:=-1;
|
|
If OpenQuery(Qry,Res,False) then
|
|
try
|
|
Result:=StrToIntDef(GetResultField(Res,0),-1);
|
|
finally
|
|
FreeQueryResult(Res);
|
|
end;
|
|
end;
|
|
|
|
Function StringQuery(Qry : String) : String;
|
|
|
|
Var
|
|
Res : TSQLQuery;
|
|
|
|
begin
|
|
Result:='';
|
|
If OpenQuery(Qry,Res,False) then
|
|
try
|
|
Result:=GetResultField(Res,0);
|
|
finally
|
|
FreeQueryResult(Res);
|
|
end;
|
|
end;
|
|
|
|
Function EscapeSQL( S : String) : String;
|
|
|
|
begin
|
|
// Result:=StringReplace(S,'\','\\',[rfReplaceAll]);
|
|
Result:=StringReplace(S,'''','''''',[rfReplaceAll]);
|
|
Verbose(V_SQL,'EscapeSQL : "'+S+'" -> "'+Result+'"');
|
|
end;
|
|
|
|
|
|
Function SQLDate(D : TDateTime) : String;
|
|
|
|
begin
|
|
Result:=FormatDateTime('YYYY/MM/DD hh:nn:ss',D);
|
|
end;
|
|
|
|
{ ---------------------------------------------------------------------
|
|
High-level access
|
|
---------------------------------------------------------------------}
|
|
|
|
|
|
Function GetTestID(Name : string) : Integer;
|
|
|
|
Const
|
|
SFromName = 'SELECT T_ID FROM TESTS WHERE (T_NAME=''%s'')';
|
|
|
|
begin
|
|
Result:=IDQuery(Format(SFromName,[Name]));
|
|
end;
|
|
|
|
Function GetOSID(Name : String) : Integer;
|
|
|
|
Const
|
|
SFromName = 'SELECT TO_ID FROM TESTOS WHERE (TO_NAME=''%s'')';
|
|
|
|
begin
|
|
Result:=IDQuery(Format(SFromName,[Name]));
|
|
end;
|
|
|
|
Function GetVersionID(Name : String) : Integer;
|
|
|
|
Const
|
|
SFromName = 'SELECT TV_ID FROM TESTVERSION WHERE (TV_VERSION=''%s'')';
|
|
|
|
begin
|
|
Result:=IDQuery(Format(SFromName,[Name]));
|
|
end;
|
|
|
|
Function GetCPUID(Name : String) : Integer;
|
|
|
|
Const
|
|
SFromName = 'SELECT TC_ID FROM TESTCPU WHERE (TC_NAME=''%s'')';
|
|
|
|
begin
|
|
Result:=IDQuery(Format(SFromName,[Name]));
|
|
end;
|
|
|
|
Function GetCategoryID(Name : String) : Integer;
|
|
|
|
Const
|
|
SFromName = 'SELECT TCAT_ID FROM TESTCATEGORY WHERE (TCAT_NAME=''%s'')';
|
|
|
|
begin
|
|
Result:=IDQuery(Format(SFromName,[Name]));
|
|
end;
|
|
|
|
Function GetRunID(OSID, CPUID, VERSIONID : Integer; Date : TDateTime) : Integer;
|
|
|
|
|
|
Const
|
|
SFromIDS = 'SELECT TU_ID FROM TESTRUN WHERE '+
|
|
' (TU_OS_FK=%d) '+
|
|
' AND (TU_CPU_FK=%d) '+
|
|
' AND (TU_VERSION_FK=%d) '+
|
|
' AND (TU_DATE=''%s'')';
|
|
|
|
begin
|
|
Result:=IDQuery(Format(SFromIDS,[OSID,CPUID,VERSIONID,SQLDate(Date)]));
|
|
end;
|
|
|
|
Function InsertQuery(const Query : string) : Integer;
|
|
|
|
begin
|
|
Result:=IDQuery(Query);
|
|
end;
|
|
|
|
Function AddRun(OSID, CPUID, VERSIONID, CATEGORYID : Integer; Date : TDateTime) : Integer;
|
|
|
|
Const
|
|
SInsertRun = 'INSERT INTO TESTRUN '+
|
|
'(TU_OS_FK,TU_CPU_FK,TU_VERSION_FK,TU_CATEGORY_FK,TU_DATE)'+
|
|
' VALUES '+
|
|
'(%d,%d,%d,%d,''%s'') RETURNING TU_ID';
|
|
var
|
|
Qry : string;
|
|
begin
|
|
qry:=Format(SInsertRun,[OSID,CPUID,VERSIONID,CATEGORYID,SQLDate(Date)]);
|
|
Result:=IDQuery(Qry);
|
|
end;
|
|
|
|
function posr(c : Char; const s : AnsiString) : integer;
|
|
var
|
|
i : integer;
|
|
begin
|
|
i := length(s);
|
|
while (i>0) and (s[i] <> c) do dec(i);
|
|
Result := i;
|
|
end;
|
|
|
|
function GetUnitTestConfig(const fn : string; var r : TConfig) : Boolean;
|
|
var
|
|
Path : string;
|
|
ClassName : string;
|
|
MethodName : string;
|
|
slashpos : integer;
|
|
FileName : string;
|
|
s : string;
|
|
t : text;
|
|
begin
|
|
Result := False;
|
|
FillChar(r,sizeof(r),0);
|
|
if pos('.',fn) > 0 then exit; // This is normally not a unit-test
|
|
slashpos := posr('/',fn);
|
|
if slashpos < 1 then exit;
|
|
MethodName := copy(fn,slashpos+1,length(fn));
|
|
Path := copy(fn,1,slashpos-1);
|
|
slashpos := posr('/',Path);
|
|
if slashpos > 0 then
|
|
begin
|
|
ClassName := copy(Path,slashpos+1,length(Path));
|
|
Path := copy(Path,1,slashpos-1);
|
|
end
|
|
else
|
|
begin
|
|
ClassName := Path;
|
|
path := '.';
|
|
end;
|
|
if upper(ClassName[1])<>'T' then exit;
|
|
FileName := TestSrcDir+RelSrcDir+Path+DirectorySeparator+copy(lowercase(ClassName),2,length(classname));
|
|
if FileExists(FileName+'.pas') then
|
|
FileName := FileName + '.pas'
|
|
else if FileExists(FileName+'.pp') then
|
|
FileName := FileName + '.pp'
|
|
else exit;
|
|
|
|
Verbose(V_Debug,'Reading: '+FileName);
|
|
assign(t,FileName);
|
|
{$I-}
|
|
reset(t);
|
|
{$I+}
|
|
if ioresult<>0 then
|
|
begin
|
|
Verbose(V_Error,'Can''t open '+FileName);
|
|
exit;
|
|
end;
|
|
while not eof(t) do
|
|
begin
|
|
readln(t,s);
|
|
|
|
if s<>'' then
|
|
begin
|
|
TrimB(s);
|
|
if SameText(copy(s,1,9),'PROCEDURE') then
|
|
begin
|
|
if pos(';',s)>11 then
|
|
begin
|
|
s := copy(s,11,pos(';',s)-11);
|
|
TrimB(s);
|
|
if SameText(s,ClassName+'.'+MethodName) then
|
|
begin
|
|
Result := True;
|
|
r.Note:= 'unittest';
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
close(t);
|
|
end;
|
|
|
|
Function AddTest(Name : String; AddSource : Boolean) : Integer;
|
|
|
|
Const
|
|
SInsertTest = 'INSERT INTO TESTS (T_NAME,T_ADDDATE)'+
|
|
' VALUES (''%s'',NOW())';
|
|
|
|
Var
|
|
Info : TConfig;
|
|
|
|
begin
|
|
Result:=-1;
|
|
If (FileExists(TestSrcDir+RelSrcDir+Name) and
|
|
GetConfig(TestSrcDir+RelSrcDir+Name,Info)) or
|
|
GetUnitTestConfig(Name,Info) then
|
|
begin
|
|
If ExecuteQuery(Format(SInsertTest,[Name]),False) then
|
|
begin
|
|
Result:=GetTestID(Name);
|
|
If Result=-1 then
|
|
Verbose(V_WARNING,'Could not find newly added test!')
|
|
else
|
|
If AddSource then
|
|
UpdateTest(Result,Info,testu.GetFileContents(Name))
|
|
else
|
|
UpdateTest(Result,Info,'');
|
|
end
|
|
end
|
|
else
|
|
Verbose(V_ERROR,'Could not find test "'+Name+'" or info about this test.');
|
|
end;
|
|
|
|
Const
|
|
B : Array[Boolean] of String = ('f','t');
|
|
|
|
Function UpdateTest(ID : Integer; Info : TConfig; Source : String) : Boolean;
|
|
|
|
Const
|
|
SUpdateTest = 'Update TESTS SET '+
|
|
' T_CPU=''%s'', T_OS=''%s'', T_VERSION=''%s'','+
|
|
' T_GRAPH=''%s'', T_INTERACTIVE=''%s'', T_RESULT=%d,'+
|
|
' T_FAIL=''%s'', T_RECOMPILE=''%s'', T_NORUN=''%s'','+
|
|
' T_NEEDLIBRARY=''%s'', T_KNOWNRUNERROR=%d,'+
|
|
' T_KNOWN=''%s'', T_NOTE=''%s'', T_OPTS = ''%s'''+
|
|
' %s '+
|
|
'WHERE'+
|
|
' T_ID=%d';
|
|
|
|
|
|
Var
|
|
Qry : String;
|
|
|
|
begin
|
|
If Source<>'' then
|
|
begin
|
|
Source:=EscapeSQL(Source);
|
|
Source:=', T_SOURCE='''+Source+'''';
|
|
end;
|
|
With Info do
|
|
Qry:=Format(SUpdateTest,[EscapeSQL(NeedCPU),'',EscapeSQL(MinVersion),
|
|
B[usesGraph],B[IsInteractive],ResultCode,
|
|
B[ShouldFail],B[NeedRecompile],B[NoRun],
|
|
B[NeedLibrary],KnownRunError,
|
|
B[IsKnownCompileError],EscapeSQL(Note),EscapeSQL(NeedOptions),
|
|
Source,
|
|
ID
|
|
]);
|
|
Result:=ExecuteQuery(Qry,False);
|
|
end;
|
|
|
|
Function AddTestResult(TestID,RunID,TestRes : Integer;
|
|
OK, Skipped : Boolean;
|
|
Log : String;var count_it : boolean) : Integer;
|
|
|
|
Const
|
|
SInsertRes='Insert into TESTRESULTS '+
|
|
'(TR_TEST_FK,TR_TESTRUN_FK,TR_OK,TR_SKIP,TR_RESULT) '+
|
|
' VALUES '+
|
|
'(%d,%d,''%s'',''%s'',%d) RETURNING TR_ID';
|
|
SSelectId='SELECT TR_ID FROM TESTRESULTS WHERE (TR_TEST_FK=%d) '+
|
|
' AND (TR_TESTRUN_FK=%d)';
|
|
SSelectTestResult='SELECT TR_RESULT FROM TESTRESULTS WHERE (TR_TEST_FK=%d) '+
|
|
' AND (TR_TESTRUN_FK=%d)';
|
|
SInsertLog='Update TESTRESULTS SET TR_LOG=''%s'''+
|
|
',TR_OK=''%s'',TR_SKIP=''%s'',TR_RESULT=%d WHERE (TR_ID=%d)';
|
|
Var
|
|
Qry : String;
|
|
updateValues : boolean;
|
|
prevTestResult : integer;
|
|
begin
|
|
updateValues:=false;
|
|
Result:=-1;
|
|
prevTestResult:=-1;
|
|
Qry:=Format(SInsertRes,
|
|
[TestID,RunID,B[OK],B[Skipped],TestRes]);
|
|
Result:=IDQuery(Qry);
|
|
if (Result=-1) then
|
|
begin
|
|
Qry:=format(SSelectId,[TestId,RunId]);
|
|
Result:=IDQuery(Qry);
|
|
if Result<>-1 then
|
|
begin
|
|
UpdateValues:=true;
|
|
Qry:=format(SSelectTestResult,[TestId,RunId]);
|
|
prevTestResult:=IDQuery(Qry);
|
|
end;
|
|
end;
|
|
if (Result<>-1) and ((Log<>'') or updateValues) then
|
|
begin
|
|
Qry:=Format(SInsertLog,[EscapeSQL(Log),B[OK],B[Skipped],TestRes,Result]);
|
|
if Not ExecuteQuery(Qry,False) then
|
|
Verbose(V_Warning,'Insert Log failed');
|
|
end;
|
|
{ If test already existed, return false for count_it to avoid double counting }
|
|
count_it:=not updateValues or (prevTestResult<>TestRes);
|
|
end;
|
|
|
|
Function RequireTestID(Name : String): Integer;
|
|
|
|
begin
|
|
Result:=GetTestID(Name);
|
|
If Result=-1 then
|
|
Result:=AddTest(Name,FileExists(Name));
|
|
If Result=-1 then
|
|
Verbose(V_WARNING,'Could not find or create entry for test '+Name);
|
|
end;
|
|
|
|
Function CleanTestRun(ID : Integer) : Boolean;
|
|
|
|
Const
|
|
SDeleteRun = 'DELETE FROM TESTRESULTS WHERE TR_TESTRUN_FK=%d';
|
|
|
|
begin
|
|
Result:=ExecuteQuery(Format(SDeleteRun,[ID]),False);
|
|
end;
|
|
|
|
function GetTestPreviousRunHistoryID(TestRunID : Integer) : Integer;
|
|
begin
|
|
GetTestPreviousRunHistoryID:=IDQuery(
|
|
format('SELECT TH_PREVIOUS_FK FROM TESTRUNHISTORY WHERE TH_ID_FK=%d',[TestRunID]));
|
|
end;
|
|
|
|
function GetTestNextRunHistoryID(TestRunID : Integer) : Integer;
|
|
begin
|
|
GetTestNextRunHistoryID:=IDQuery(
|
|
format('SELECT TH_ID_FK FROM TESTRUNHISTORY WHERE TH_PREVIOUS_FK=%d',[TestRunID]));
|
|
end;
|
|
|
|
function AddTestHistoryEntry(TestRunID,TestPreviousID : Integer) : boolean;
|
|
|
|
var
|
|
qry : string;
|
|
|
|
begin
|
|
Qry:=format('INSERT INTO TESTRUNHISTORY (TH_ID_FK,TH_PREVIOUS_FK) '+
|
|
' VALUES (%d,%d)',[TestRunID,TestPreviousID]);
|
|
Result:=ExecuteQuery(Qry,False);
|
|
end;
|
|
|
|
end.
|