* Rework to be able to use in compiler testsuite

* Refactor for better code readability
  * Set exit status based on bitmask: 
    bit 0 set -> there were failures
    bit 1 set -> there were errors 
  * Allow use of testdefaults.ini file
  * Location of testdefaults.ini file can be set in environment variable FPCUNITCONFIG.
  * Improved usage message.

git-svn-id: trunk@36816 -
This commit is contained in:
michael 2017-08-02 10:02:24 +00:00
parent 19087d04da
commit 1729d6a848

View File

@ -33,6 +33,7 @@ const
type type
TFormat = (fPlain, fLatex, fXML, fPlainNoTiming); TFormat = (fPlain, fLatex, fXML, fPlainNoTiming);
TRunMode = (rmUnknown,rmList,rmSuite,rmAll);
var var
DefaultFormat : TFormat = fXML; DefaultFormat : TFormat = fXML;
@ -48,7 +49,18 @@ type
FStyleSheet: string; FStyleSheet: string;
FLongOpts: TStrings; FLongOpts: TStrings;
FFormatParam: TFormat; FFormatParam: TFormat;
FSkipTiming : Boolean;
FSParse: Boolean;
FSkipAddressInfo : Boolean;
FSuite: String;
FRunMode : TRunMode;
protected protected
Class function StrToFormat(S: String): TFormat;
function DefaultsFileName: String;
procedure RunSuite; virtual;
procedure ShowTestList; virtual;
procedure ReadDefaults; virtual;
procedure Usage; virtual;
property FileName: string read FFileName write FFileName; property FileName: string read FFileName write FFileName;
property LongOpts: TStrings read FLongOpts write FLongOpts; property LongOpts: TStrings read FLongOpts write FLongOpts;
property ShowProgress: boolean read FShowProgress write FShowProgress; property ShowProgress: boolean read FShowProgress write FShowProgress;
@ -59,7 +71,7 @@ type
function GetShortOpts: string; virtual; function GetShortOpts: string; virtual;
procedure AppendLongOpts; virtual; procedure AppendLongOpts; virtual;
procedure WriteCustomHelp; virtual; procedure WriteCustomHelp; virtual;
procedure ParseOptions; virtual; function ParseOptions: Boolean; virtual;
procedure ExtendXmlDocument(Doc: TXMLDocument); virtual; procedure ExtendXmlDocument(Doc: TXMLDocument); virtual;
function GetResultsWriter: TCustomResultsWriter; virtual; function GetResultsWriter: TCustomResultsWriter; virtual;
public public
@ -69,7 +81,7 @@ type
implementation implementation
uses testdecorator; uses inifiles, testdecorator;
const const
ShortOpts = 'alhp'; ShortOpts = 'alhp';
@ -77,15 +89,31 @@ const
('all', 'list', 'progress', 'help', 'skiptiming', ('all', 'list', 'progress', 'help', 'skiptiming',
'suite:', 'format:', 'file:', 'stylesheet:','sparse','no-addresses'); 'suite:', 'format:', 'file:', 'stylesheet:','sparse','no-addresses');
Type
TTestDecoratorClass = Class of TTestDecorator;
{ TDecoratorTestSuite }
TDecoratorTestSuite = Class(TTestSuite)
public
Destructor Destroy; override;
end;
{ TProgressWriter } { TProgressWriter }
type
TProgressWriter= class(TNoRefCountObject, ITestListener) TProgressWriter= class(TNoRefCountObject, ITestListener)
private private
FSuccess: boolean; FTotal : Integer;
FFailed: Integer;
FIgnored : Integer;
FErrors : Integer;
FQuiet : Boolean;
FSuccess : Boolean;
procedure WriteChar(c: char); procedure WriteChar(c: char);
public public
Constructor Create(AQuiet : Boolean);
destructor Destroy; override; destructor Destroy; override;
Function GetExitCode : Integer;
{ ITestListener interface requirements } { ITestListener interface requirements }
procedure AddFailure(ATest: TTest; AFailure: TTestFailure); procedure AddFailure(ATest: TTest; AFailure: TTestFailure);
procedure AddError(ATest: TTest; AError: TTestFailure); procedure AddError(ATest: TTest; AError: TTestFailure);
@ -93,8 +121,17 @@ type
procedure EndTest(ATest: TTest); procedure EndTest(ATest: TTest);
procedure StartTestSuite(ATestSuite: TTestSuite); procedure StartTestSuite(ATestSuite: TTestSuite);
procedure EndTestSuite(ATestSuite: TTestSuite); procedure EndTestSuite(ATestSuite: TTestSuite);
Property Total : Integer Read FTotal;
Property Failed : Integer Read FFailed;
Property Errors : Integer Read FErrors;
Property Ignored : Integer Read FIgnored;
Property Quiet : Boolean Read FQuiet;
end; end;
{ ---------------------------------------------------------------------
TProgressWriter
---------------------------------------------------------------------}
procedure TProgressWriter.WriteChar(c: char); procedure TProgressWriter.WriteChar(c: char);
begin begin
write(c); write(c);
@ -102,6 +139,12 @@ begin
Flush(output); Flush(output);
end; end;
constructor TProgressWriter.Create(AQuiet: Boolean);
begin
FQuiet:=AQuiet;
end;
destructor TProgressWriter.Destroy; destructor TProgressWriter.Destroy;
begin begin
// on descruction, just write the missing line ending // on descruction, just write the missing line ending
@ -109,16 +152,31 @@ begin
inherited Destroy; inherited Destroy;
end; end;
function TProgressWriter.GetExitCode: Integer;
begin
Result:=Ord(Failed<>0); // Bit 0 indicates fails
if Errors<>0 then
Result:=Result or 2; // Bit 1 indicates errors.
end;
procedure TProgressWriter.AddFailure(ATest: TTest; AFailure: TTestFailure); procedure TProgressWriter.AddFailure(ATest: TTest; AFailure: TTestFailure);
begin begin
FSuccess := false; FSuccess:=False;
writechar('F'); If AFailure.IsIgnoredTest then
Inc(FIgnored)
else
Inc(FFailed);
If Not Quiet then
writechar('F');
end; end;
procedure TProgressWriter.AddError(ATest: TTest; AError: TTestFailure); procedure TProgressWriter.AddError(ATest: TTest; AError: TTestFailure);
begin begin
FSuccess := false; FSuccess:=False;
writechar('E'); Inc(FErrors);
if not Quiet then
writechar('E');
end; end;
procedure TProgressWriter.StartTest(ATest: TTest); procedure TProgressWriter.StartTest(ATest: TTest);
@ -128,7 +186,7 @@ end;
procedure TProgressWriter.EndTest(ATest: TTest); procedure TProgressWriter.EndTest(ATest: TTest);
begin begin
if FSuccess then if FSuccess and not Quiet then
writechar('.'); writechar('.');
end; end;
@ -142,43 +200,87 @@ begin
// do nothing // do nothing
end; end;
{ ---------------------------------------------------------------------
TDecoratorTestSuite
---------------------------------------------------------------------}
destructor TDecoratorTestSuite.Destroy;
begin
OwnsTests:=False;
inherited Destroy;
end;
{ ---------------------------------------------------------------------
TTestRunner
---------------------------------------------------------------------}
constructor TTestRunner.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FLongOpts := TStringList.Create;
AppendLongOpts;
StopOnException:=True;
end;
destructor TTestRunner.Destroy;
begin
FLongOpts.Free;
inherited Destroy;
end;
class function TTestRunner.StrToFormat(S: String): TFormat;
begin
Case lowercase(S) of
'latex': Result:=fLatex;
'plain': Result:=fPlain;
'plainnotiming': Result:=fPlainNoTiming;
'xml': Result:=fXML;
else
Raise EConvertError.CreateFmt('Not a valid output format : "%s"',[S]);
end;
end;
function TTestRunner.GetResultsWriter: TCustomResultsWriter; function TTestRunner.GetResultsWriter: TCustomResultsWriter;
begin begin
case FormatParam of case FormatParam of
fLatex: Result := TLatexResultsWriter.Create(nil); fLatex: Result := TLatexResultsWriter.Create(nil);
fPlain: Result := TPlainResultsWriter.Create(nil); fPlain: Result := TPlainResultsWriter.Create(nil);
fPlainNotiming: Result := TPlainResultsWriter.Create(nil);
else else
begin begin
Result := TXmlResultsWriter.Create(nil); Result := TXmlResultsWriter.Create(nil);
ExtendXmlDocument(TXMLResultsWriter(Result).Document); ExtendXmlDocument(TXMLResultsWriter(Result).Document);
end; end;
end; end;
Result.SkipTiming:=HasOption('skiptiming'); Result.SkipTiming:=FSkipTiming or (formatParam=fPlainNoTiming);
Result.Sparse:=HasOption('sparse'); Result.Sparse:=FSparse;
Result.SkipAddressInfo:=HasOption('no-addresses'); Result.SkipAddressInfo:=FSkipAddressInfo;
end; end;
procedure TTestRunner.DoTestRun(ATest: TTest); procedure TTestRunner.DoTestRun(ATest: TTest);
var var
ResultsWriter: TCustomResultsWriter; ResultsWriter: TCustomResultsWriter;
ProgressWriter: TProgressWriter; ProgressWriter: TProgressWriter;
TestResult: TTestResult; TestResult: TTestResult;
begin begin
ResultsWriter := GetResultsWriter; ProgressWriter:=Nil;
ResultsWriter.Filename := FileName; ResultsWriter:=Nil;
TestResult := TTestResult.Create; TestResult := TTestResult.Create;
try try
if ShowProgress then ProgressWriter:=TProgressWriter.Create(Not ShowProgress);
begin TestResult.AddListener(ProgressWriter);
ProgressWriter := TProgressWriter.Create; ResultsWriter:=GetResultsWriter;
TestResult.AddListener(ProgressWriter); ResultsWriter.Filename := FileName;
end
else
ProgressWriter := nil;
TestResult.AddListener(ResultsWriter); TestResult.AddListener(ResultsWriter);
ATest.Run(TestResult); ATest.Run(TestResult);
ResultsWriter.WriteResult(TestResult); ResultsWriter.WriteResult(TestResult);
finally finally
if Assigned(ProgressWriter) then
ExitCode:=ProgressWriter.GetExitCode;
TestResult.Free; TestResult.Free;
ResultsWriter.Free; ResultsWriter.Free;
ProgressWriter.Free; ProgressWriter.Free;
@ -203,17 +305,18 @@ begin
// no custom help options in base class; // no custom help options in base class;
end; end;
procedure TTestRunner.ParseOptions; procedure TTestRunner.Usage;
begin begin
if HasOption('h', 'help') or ((ParamCount = 0) and not DefaultRunAllTests) then
begin
writeln(Title); writeln(Title);
writeln(Version); writeln(Version);
writeln; writeln;
writeln('Usage: '); writeln('Usage: ');
writeln(' --format=latex output as latex source (only list implemented)'); writeln(' --format=FMT Select output format. FMT is one of:');
writeln(' --format=plain output as plain ASCII source'); writeln(' latex output as latex');
writeln(' --format=xml output as XML source (default)'); writeln(' plain output as plain ASCII source');
writeln(' plainnotiming output as plain ASCII source, skip timings');
writeln(' xml output as XML source (default)');
writeln(' --skiptiming Do not output timings (useful for diffs of testruns)'); writeln(' --skiptiming Do not output timings (useful for diffs of testruns)');
writeln(' --sparse Produce Less output (errors/failures only)'); writeln(' --sparse Produce Less output (errors/failures only)');
writeln(' --no-addresses Do not display address info'); writeln(' --no-addresses Do not display address info');
@ -226,35 +329,98 @@ begin
writeln(' --suite=MyTestSuiteName run single test suite class'); writeln(' --suite=MyTestSuiteName run single test suite class');
WriteCustomHelp; WriteCustomHelp;
writeln; writeln;
writeln('The results can be redirected to an xml file,'); Writeln('Defaults for long options will be read from ini file ',DefaultsFileName);
writeln('The results can be redirected to a file,');
writeln('for example: ', ParamStr(0),' --all > results.xml'); writeln('for example: ', ParamStr(0),' --all > results.xml');
end; end;
Function TTestRunner.DefaultsFileName : String;
begin
Result:=GetEnvironmentVariable('FPCUNITCONFIG');
if (Result='') then
Result:=Location+'testdefaults.ini';
end;
procedure TTestRunner.ReadDefaults;
Const
S = 'defaults';
Var
Ini : TMemIniFile;
FN,F : String;
begin
FN:=DefaultsFileName;
if FileExists(FN) then
begin
Ini:=TMemIniFile.Create(FN);
try
F:=Ini.ReadString(S,'format','');
if (F<>'') then
FormatParam:=StrToFormat(F);
FileName:=Ini.ReadString(S,'file',FileName);
StyleSheet:=Ini.ReadString(S,'stylesheet',StyleSheet);
ShowProgress:=Ini.ReadBool(S,'progress',ShowProgress);
FSkipTiming:=Ini.ReadBool(S,'skiptiming',FSKipTiming);
FSparse:=Ini.ReadBool(S,'sparse',FSparse);
FSkipAddressInfo:=Ini.ReadBool(S,'no-addresses',FSkipAddressInfo);
// Determine runmode
FSuite:=Ini.ReadString(S,'suite','');
if (FSuite<>'') then
FRunMode:=rmSuite
else if Ini.ReadBool(S,'all', false) then
FRunMode:=rmAll
else if Ini.ReadBool(S,'list',False) then
FRunMode:=rmList;
finally
Ini.Free;
end;
end;
end;
Function TTestRunner.ParseOptions : Boolean;
begin
Result:=True;
if HasOption('h', 'help') or ((ParamCount = 0) and not DefaultRunAllTests) then
begin
Usage;
Exit(False);
end;
//get the format parameter //get the format parameter
FormatParam := DefaultFormat;
if HasOption('format') then if HasOption('format') then
begin FormatParam:=StrToFormat(GetOptionValue('format'));
if CompareText(GetOptionValue('format'),'latex')=0 then
FormatParam := fLatex
else if CompareText(GetOptionValue('format'),'plain')=0 then
FormatParam := fPlain
else if CompareText(GetOptionValue('format'),'plainnotiming')=0 then
FormatParam := fPlainNoTiming
else if CompareText(GetOptionValue('format'),'xml')=0 then
FormatParam := fXML;
end;
ShowProgress := HasOption('p', 'progress');
if HasOption('file') then if HasOption('file') then
FileName := GetOptionValue('file'); FileName:=GetOptionValue('file');
if HasOption('stylesheet') then if HasOption('stylesheet') then
StyleSheet := GetOptionValue('stylesheet'); StyleSheet:=GetOptionValue('stylesheet');
if HasOption('p', 'progress') then
ShowProgress:=True;
if HasOption('skiptiming') then
FSkipTiming:=True;
if HasOption('sparse') then
FSparse:=True;
If HasOption('no-addresses') then
FSkipAddressInfo:=True;
// Determine runmode
if HasOption('suite') then
begin
FSuite:=GetOptionValue('suite');
FRunMode:=rmSuite;
end
else If HasOption('a','all') then
FRunMode:=rmAll
else if HasOption('l','list') then
FRunMode:=rmList;
end; end;
procedure TTestRunner.ExtendXmlDocument(Doc: TXMLDocument); procedure TTestRunner.ExtendXmlDocument(Doc: TXMLDocument);
var var
n: TDOMElement; n: TDOMElement;
begin begin
if StyleSheet<>'' then begin if StyleSheet<>'' then begin
Doc.StylesheetType := 'text/xsl'; Doc.StylesheetType := 'text/xsl';
@ -265,105 +431,87 @@ begin
Doc.FirstChild.AppendChild(n); Doc.FirstChild.AppendChild(n);
end; end;
constructor TTestRunner.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FLongOpts := TStringList.Create;
AppendLongOpts;
end;
destructor TTestRunner.Destroy;
begin
FLongOpts.Free;
inherited Destroy;
end;
Type
TTestDecoratorClass = Class of TTestDecorator;
{ TDecoratorTestSuite }
TDecoratorTestSuite = Class(TTestSuite)
public
Destructor Destroy; override;
end;
{ TDecoratorTestSuite }
destructor TDecoratorTestSuite.Destroy;
begin
OwnsTests:=False;
inherited Destroy;
end;
procedure TTestRunner.DoRun;
procedure TTestRunner.RunSuite;
var var
I,P : integer; I,P : integer;
S,TN : string; S,TN : string;
TS : TDecoratorTestSuite; TS : TDecoratorTestSuite;
T : TTest; T : TTest;
begin begin
S := FSuite;
if S = '' then
for I := 0 to GetTestRegistry.ChildTestCount - 1 do
writeln(GetTestRegistry[i].TestName)
else
begin
TS:=TDecoratorTestSuite.Create('SuiteList');
try
while Not(S = '') Do
begin
P:=Pos(',',S);
If P=0 then
P:=Length(S)+1;
TN:=Copy(S,1,P-1);
Delete(S,1,P);
if (TN<>'') then
begin
T:=GetTestRegistry.FindTest(TN);
if Assigned(T) then
TS.AddTest(T);
end;
end;
if (TS.CountTestCases>1) then
DoTestRun(TS)
else if TS.CountTestCases=1 then
DoTestRun(TS[0])
else
Writeln('No tests selected.');
finally
TS.Free;
end;
end;
end;
procedure TTestRunner.ShowTestList;
begin
case FormatParam of
fLatex: Write(GetSuiteAsLatex(GetTestRegistry));
fPlain: Write(GetSuiteAsPlain(GetTestRegistry));
fPlainNoTiming: Write(GetSuiteAsPlain(GetTestRegistry));
else
Write(GetSuiteAsXml(GetTestRegistry));
end
end;
procedure TTestRunner.DoRun;
var
S : string;
begin
Terminate;
FormatParam := DefaultFormat;
S := CheckOptions(GetShortOpts, LongOpts); S := CheckOptions(GetShortOpts, LongOpts);
if (S <> '') then if (S <> '') then
begin
Writeln(S); Writeln(S);
Exit;
ParseOptions;
//get a list of all registed tests
if HasOption('l', 'list') then
case FormatParam of
fLatex: Write(GetSuiteAsLatex(GetTestRegistry));
fPlain: Write(GetSuiteAsPlain(GetTestRegistry));
fPlainNoTiming: Write(GetSuiteAsPlain(GetTestRegistry));
else
Write(GetSuiteAsXml(GetTestRegistry));
end; end;
ReadDefaults;
//run the tests if Not ParseOptions then
if HasOption('suite') then exit;
begin //get a list of all registed tests
S := ''; Case FRunMode of
S := GetOptionValue('suite'); rmList: ShowTestList;
if S = '' then rmSuite: RunSuite;
for I := 0 to GetTestRegistry.ChildTestCount - 1 do rmAll: DoTestRun(GetTestRegistry);
writeln(GetTestRegistry[i].TestName) else
else Usage
begin end;
TS:=TDecoratorTestSuite.Create('SuiteList');
try
while Not(S = '') Do
begin
P:=Pos(',',S);
If P=0 then
P:=Length(S)+1;
TN:=Copy(S,1,P-1);
Delete(S,1,P);
if (TN<>'') then
begin
T:=GetTestRegistry.FindTest(TN);
if Assigned(T) then
TS.AddTest(T);
end;
end;
if (TS.CountTestCases>1) then
DoTestRun(TS)
else if TS.CountTestCases=1 then
DoTestRun(TS[0])
else
Writeln('No tests selected.');
finally
TS.Free;
end;
end;
end
else if HasOption('a', 'all') or (DefaultRunAllTests and Not HasOption('l','list')) then
DoTestRun(GetTestRegistry) ;
Terminate;
end; end;
end. end.