{ --------------------------------------------------------------------- utility functions, shared by several programs of the test suite ---------------------------------------------------------------------} {$mode objfpc} {$modeswitch advancedrecords} {$h+} unit tsutils; Interface uses classes, sysutils, tstypes; Type TOnVerboseEvent = procedure(lvl:TVerboseLevel; const aMsg : String) of object; var OnVerbose : TOnVerboseEvent = Nil; IsCGI : boolean = false; DoVerbose : boolean = false; DoSQL : boolean = false; MaxLogSize : LongInt = 50000; procedure TrimB(var s:string); procedure TrimE(var s:string); function upper(const s : string) : string; procedure Verbose(lvl:TVerboseLevel; const s:string); function GetConfig(const logprefix,fn:string;out aConfig:TConfig):boolean; function GetUnitTestConfig(const logprefix,fn,SrcDir: string; out aConfig : TConfig) : Boolean; Function GetFileContents (FN : String) : String; const { Constants used in IsAbsolute function } TargetHasDosStyleDirectories : boolean = false; TargetAmigaLike : boolean = false; TargetIsMacOS : boolean = false; TargetIsUnix : boolean = false; { File path helper functions } function SplitPath(const s:string):string; function SplitBasePath(const s:string): string; Function SplitFileName(const s:string):string; Function SplitFileBase(const s:string):string; Function SplitFileExt(const s:string):string; Function FileExists (Const F : String) : Boolean; Function PathExists (Const F : String) : Boolean; Function IsAbsolute (Const F : String) : boolean; function GetToken(var s: string; Delims: TCharSet = [' ']):string; Implementation 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 GetToken(var s: string; Delims: TCharSet = [' ']):string; var i : longint; p: PChar; begin p:=PChar(s); i:=0; while (p^ <> #0) and not (p^ in Delims) do begin Inc(p); Inc(i); end; GetToken:=Copy(s,1,i); Delete(s,1,i+1); end; function SplitPath(const s:string):string; var i : longint; begin i:=Length(s); while (i>0) and not(s[i] in ['/','\'{$IFDEF MACOS},':'{$ENDIF}]) do dec(i); SplitPath:=Copy(s,1,i); end; function SplitBasePath(const s:string): string; var i : longint; begin i:=1; while (i<length(s)) and not(s[i] in ['/','\'{$IFDEF MACOS},':'{$ENDIF}]) do inc(i); if s[i] in ['/','\'{$IFDEF MACOS},':'{$ENDIF}] then dec(i); SplitBasePath:=Copy(s,1,i); end; Function SplitFileName(const s:string):string; begin Result:=ExtractFileName(S); end; Function SplitFileBase(const s:string):string; begin Result:=ChangeFileExt(ExtractFileName(S),''); end; Function SplitFileExt(const s:string):string; begin Result:=ExtractFileExt(S); end; Function FileExists (Const F : String) : Boolean; begin Result:=SysUtils.FileExists(F); end; Function PathExists (Const F : String) : Boolean; { Returns True if the file exists, False if not. } begin Result:=DirectoryExists(F); end; { extracted from rtl/macos/macutils.inc } function IsMacFullPath (const path: string): Boolean; begin if Pos(':', path) = 0 then {its partial} IsMacFullPath := false else if path[1] = ':' then IsMacFullPath := false else IsMacFullPath := true end; Function IsAbsolute (Const F : String) : boolean; { Returns True if the name F is a absolute file name } begin IsAbsolute:=false; if TargetHasDosStyleDirectories then begin if (F[1]='/') or (F[1]='\') then IsAbsolute:=true; if (Length(F)>2) and (F[2]=':') and ((F[3]='\') or (F[3]='/')) then IsAbsolute:=true; end else if TargetAmigaLike then begin if (length(F)>0) and (Pos(':',F) <> 0) then IsAbsolute:=true; end else if TargetIsMacOS then begin IsAbsolute:=IsMacFullPath(F); end { generic case } else if (F[1]='/') then IsAbsolute:=true; end; procedure Verbose(lvl:TVerboseLevel;const s:string); const lPrefixes : Array[TVerboseLevel] of string = ('Abort','Error','Warning','Info','Debug','SQL'); var lOutput : String; Procedure DoOutput; begin if not IsCGI then begin Writeln(output,lOutput); Flush(output); end else begin Writeln(stderr,lOutput); Flush(stderr); end; if Assigned(OnVerbose) then OnVerbose(lvl,lOutput); end; begin lOutput:=lPrefixes[lvl]+': '+S; case lvl of V_Normal : DoOutput; V_Debug : if DoVerbose then DoOutput; V_SQL : if DoSQL then DoOutput; V_Warning : DoOutput; V_Error : begin DoOutput; if not IsCGI then halt(1); end; V_Abort : begin DoOutput; if not IsCGI then halt(0); end; end; end; procedure TrimB(var s:string); begin S:=TrimLeft(S); end; procedure TrimE(var s:string); begin S:=TrimRight(S); end; function upper(const s : string) : string; var i,l : longint; begin Result:=''; L:=Length(S); SetLength(Result,l); for i:=1 to l do if s[i] in ['a'..'z'] then Result[i]:=char(byte(s[i])-32) else Result[i]:=s[i]; end; function GetConfig(const logprefix,fn:string;out aConfig:TConfig):boolean; Procedure ExtractCodeAndNote(s : String; out aCode : Integer; out aNote : String); var i : Integer; begin aCode:=0; aNote:=''; if S='' then exit; I:=1; While (i<=Length(s)) and (S[I] in ['0'..'9']) do Inc(i); if I>1 then aCode:=StrToIntDef(Copy(S,1,i-1),0); aNote:=Copy(S,I,Length(S)-I+1); end; function GetEntry(S : String; Out entry, Res :string):boolean; var i : longint; begin Result:=False; Entry:=''; Res:=''; S:=TrimLeft(s); if (s='') or (S[1]<>'{') then exit(False); Delete(S,1,1); S:=TrimLeft(s); if (s='') or (S[1]<>'%') then exit(False); Delete(S,1,1); S:=TrimLeft(s); i:=Pos('}',S); if I=0 then exit(False); S:=Copy(S,1,I-1); i:=Pos('=',S); if I=0 then Entry:=Trim(S) else begin Entry:=Trim(Copy(S,1,I-1)); Res:=Trim(Copy(S,I+1,Length(S)-I)); end; Result:=True; Verbose(V_Debug,'Config: '+Entry+' = "'+Res+'"'); end; Procedure AnalyseEntry(aEntry,aValue : string); var l,p,code : Integer; begin case UpperCase(aEntry) of 'OPT': aConfig.NeedOptions:=aValue; 'DELOPT': aConfig.DelOptions:=aValue; 'TARGET': aConfig.NeedTarget:=aValue; 'SKIPTARGET': aConfig.SkipTarget:=aValue; 'CPU': aConfig.NeedCPU:=aValue; 'SKIPCPU': aConfig.SkipCPU:=aValue; 'SKIPEMU': aConfig.SkipEmu:=aValue; 'VERSION': aConfig.MinVersion:=aValue; 'MAXVERSION': aConfig.MaxVersion:=aValue; 'RESULT' : aConfig.ResultCode:=StrToIntDef(aValue,0); 'GRAPH' : aConfig.UsesGraph:=true; 'FAIL' : aConfig.ShouldFail:=true; 'NORUN': aConfig.NoRun:=true; 'NEEDLIBRARY': aConfig.NeedLibrary:=true; 'NEEDEDAFTER': aConfig.NeededAfter:=true; 'TIMEOUT': aConfig.Timeout:=StrToIntDef(aValue,0); 'FILES': aConfig.Files:=aValue; 'WPOPARAS': aConfig.wpoparas:=aValue; 'WPOPASSES': aConfig.wpopasses:=StrToIntDef(aValue,0); 'DELFILES': aConfig.DelFiles:=aValue; 'INTERACTIVE': aConfig.IsInteractive:=true; 'RECOMPILE': begin aConfig.NeedRecompile:=true; aConfig.RecompileOpt:=aValue; end; 'KNOWNRUNERROR': begin aConfig.IsKnownRunError:=true; ExtractCodeAndNote(aValue,aConfig.KnownRunError,aConfig.KnownRunNote); end; 'KNOWNCOMPILEERROR': begin aConfig.IsKnownCompileError:=true; ExtractCodeAndNote(aValue,aConfig.KnownCompileError,aConfig.KnownCompileNote); end; 'NOTE': begin aConfig.Note:=aValue; Verbose(V_Normal,LogPrefix+aConfig.Note); end; 'CONFIGFILE': begin l:=Pos(' ',aValue); if l>0 then begin aConfig.ConfigFileSrc:=Trim(Copy(aValue,1,l-1)); aConfig.ConfigFileDst:=Trim(Copy(aValue,l+1,Length(aValue)-l+1)); if aConfig.ConfigFileSrc='' then Verbose(V_Error,LogPrefix+' File '+fn+' Config file source is empty'); if aConfig.ConfigFileDst='' then Verbose(V_Error,LogPrefix+' File '+fn+' Config file destination is empty'); end else begin aConfig.ConfigFileSrc:=aValue; aConfig.ConfigFileDst:=aValue; end; end; 'EXPECTMSGS': begin p:=Pos(',',aValue); while p>0 do begin val(Copy(aValue,1,p-1),l,code); if code<>0 then Verbose(V_Error,LogPrefix+' File '+fn+' Invalid value in EXPECTMSGS list: '+Copy(aValue,1,p-1)); Insert(l,aConfig.ExpectMsgs,Length(aConfig.ExpectMsgs)); Delete(aValue,1,p); p:=Pos(',',aValue); end; Val(aValue,l,code); if code<>0 then Verbose(V_Error,LogPrefix+' File '+fn+' Invalid value in EXPECTMSGS list: '+aValue); Insert(l,aConfig.ExpectMsgs,Length(aConfig.ExpectMsgs)); end; else Verbose(V_Error,LogPrefix+' File '+fn+' Unknown entry: '+aEntry+' with value: '+aValue); end; end; var l : TStringList; lErr : longint; s,aEntry,aValue: string; begin Result:=False; aConfig:=Default(TConfig); GetConfig:=false; Verbose(V_Debug,LogPrefix+'Reading '+fn); lErr:=0; L:=TStringList.Create; try try L.LoadFromFile(FN); except on E : Exception do begin Verbose(V_WARNING,'Error '+E.ClassName+' loading '+fn+': '+E.Message); exit; end; end; For S in L do begin if GetEntry(S,aEntry,aValue) then AnalyseEntry(aEntry,aValue) else Inc(lErr); if lErr>2 then Break; end; finally L.Free; end; Result:=true; end; Function GetFileContents (FN : String) : String; begin Result:=Sysutils.GetFileAsString(FN); end; function GetUnitTestConfig(const logprefix,fn,SrcDir : string; out aConfig : TConfig) : Boolean; var Path : string; lClassName : string; lMethodName : string; slashpos : integer; FileName : string; s,line : string; Src : TStrings; begin Result := False; aConfig:=Default(TConfig); if pos('.',fn) > 0 then exit; // This is normally not a unit-test slashpos := posr('/',fn); if slashpos < 1 then exit; lMethodName := copy(fn,slashpos+1,length(fn)); Path := copy(fn,1,slashpos-1); slashpos := posr('/',Path); if slashpos > 0 then begin lClassName := copy(Path,slashpos+1,length(Path)); Path := copy(Path,1,slashpos-1); end else begin lClassName := Path; path := '.'; end; if upper(lClassName[1])<>'T' then exit; FileName := SrcDir+Path+DirectorySeparator+copy(lowercase(lClassName),2,length(lClassName)); if FileExists(FileName+'.pas') then FileName := FileName + '.pas' else if FileExists(FileName+'.pp') then FileName := FileName + '.pp' else exit; Src:=TStringList.Create; try Verbose(V_Debug,logprefix+'Reading: '+FileName); Src.LoadFromFile(FileName); for Line in Src do if Line<>'' then begin s:=Line; 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,lClassName+'.'+lMethodName) then begin Result := True; aConfig.Note:= 'unittest'; end; end; end; end; finally Src.Free end; end; end.