123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- {$MODE objfpc}{$H+}
- uses
- SysUtils, StrUtils,
- {$ifdef UseSignals}
- signals,
- {$endif def UseSignals}
- tsutils, tstypes, classes,
- Process;
- const
- use_temp_dir : boolean = true;
- temp_dir_generated : boolean = false;
- need_cwsdpmi : boolean = false;
- cwsdpmi_file : string = '';
- hide_execution : boolean = true;
- do_exit : boolean = true;
- verbose : boolean = false;
- DosBoxProcess: TProcess = nil;
- dosbox_timeout : integer = 400; { default timeout in seconds }
- DosBoxExitStatus : integer = -1;
- no_temp_dir_generated = '/no/temp/dir/generated/';
- var
- OutputFileName : String;
- SourceFileName : String;
- StartDir, DosBoxDir: string;
- TempDir: String;
- ExitCode: Integer = 255;
- DosBoxBinaryPath: string;
- TmpFileList : TStringList;
- function GenerateTempDir: string;
- const
- max_attempts = 10;
- var
- TempDirName: string;
- BaseTempDir: string;
- Done: Boolean = False;
- attempt: longint;
- begin
- BaseTempDir := GetTempDir(False);
- Result := no_temp_dir_generated;
- attempt := 0;
- repeat
- inc(attempt);
- try
- TempDirName := BaseTempDir + 'dosboxwrappertmp_' + IntToStr(Random(100000));
- if verbose then
- writeln('Trying to create directory ',TempDirName);
- MkDir(TempDirName);
- Done := True;
- temp_dir_generated := True;
- TempDir := TempDirName + DirectorySeparator;
- except
- on E: EInOutError do
- begin
- { 5 = Access Denied, returned when a file is duplicated }
- if E.ErrorCode <> 5 then
- begin
- Writeln('Directory creation failed');
- raise;
- end;
- end;
- on E: Exception do
- begin
- Writeln('Exception ',E.Message);
- Sleep(1000);
- end;
- end;
- until Done or (attempt > max_attempts);
- Result := TempDirName + DirectorySeparator;
- end;
- procedure GenerateDosBoxConf(const ADosBoxDir: string);
- var
- SourceConfFileName, TargetConfFileName: string;
- SourceFile, TargetFile: TextFile;
- OrigS, S: string;
- begin
- SourceConfFileName := ExtractFilePath(ParamStr(0)) + 'dosbox.conf';
- TargetConfFileName := ADosBoxDir + 'dosbox.conf';
- OutputFileName := ADosBoxDir + 'dosbox.out';
- if verbose then
- Writeln('Using target dosbox.conf ',TargetConfFileName);
- AssignFile(SourceFile, SourceConfFileName);
- AssignFile(TargetFile, TargetConfFileName);
- Reset(SourceFile);
- try
- Rewrite(TargetFile);
- try
- while not EoF(SourceFile) do
- begin
- Readln(SourceFile, S);
- OrigS:=S;
- S := AnsiReplaceStr(S, '$DosBoxDir', ADosBoxDir);
- S := AnsiReplaceStr(S, '$wrapper_output', OutputFileName);
- if do_exit then
- S := AnsiReplaceStr(S, '$exit', 'exit')
- else
- S := AnsiReplaceStr(S, '$exit', '');
- If verbose and (OrigS <> S) then
- Writeln('"',OrigS,'" transformed into "',S,'"');
- Writeln(TargetFile, S);
- end;
- finally
- CloseFile(TargetFile);
- end;
- finally
- CloseFile(SourceFile);
- end;
- end;
- { File names in Config entries assume that
- executables have no suffix }
- function TargetFileExists(AName : string) : boolean;
- begin
- result:=SysUtils.FileExists(AName);
- if not result then
- result:=SysUtils.FileExists(AName+'.exe');
- if not result then
- result:=SysUtils.FileExists(AName+'.EXE');
- end;
- procedure CopyFile(ASrcFileName, ADestFileName: string);
- var
- SrcF, DestF: File;
- OldFileMode: Integer;
- Buf: array [0..4095] of Byte;
- BytesRead: Integer;
- begin
- if not AnsiEndsText('.exe', ASrcFileName) and AnsiEndsText('.EXE',ADestFileName) then
- ASrcFileName := ASrcFileName + '.exe';
- if not FileExists(ASrcFileName) then
- begin
- ASrcFileName:=ASrcFileName+'.exe';
- ADestFileName:=ADestFileName+'.exe';
- end;
- if verbose then
- Writeln('CopyFile "', ASrcFileName, '" -> "', ADestFileName,'"');
- OldFileMode := FileMode;
- try
- try
- AssignFile(SrcF, ASrcFileName);
- AssignFile(DestF, ADestFileName);
- FileMode := fmOpenRead;
- Reset(SrcF, 1);
- try
- FileMode := fmOpenWrite;
- try
- Rewrite(DestF, 1);
- repeat
- BlockRead(SrcF, Buf, SizeOf(Buf), BytesRead);
- BlockWrite(DestF, Buf, BytesRead);
- until BytesRead < SizeOf(Buf);
- finally
- CloseFile(DestF);
- end;
- finally
- CloseFile(SrcF);
- end;
- finally
- FileMode := OldFileMode;
- end;
- except
- on E : Exception do
- writeln('Error: '+ E.ClassName + #13#10 + E.Message );
- end;
- end;
- function ForceExtension(Const HStr,ext:String):String;
- {
- Return a filename which certainly has the extension ext
- }
- var
- j : longint;
- begin
- j:=length(Hstr);
- while (j>0) and (Hstr[j]<>'.') do
- dec(j);
- if j=0 then
- j:=length(Hstr)+1;
- if Ext<>'' then
- begin
- if Ext[1]='.' then
- ForceExtension:=Copy(Hstr,1,j-1)+Ext
- else
- ForceExtension:=Copy(Hstr,1,j-1)+'.'+Ext
- end
- else
- ForceExtension:=Copy(Hstr,1,j-1);
- end;
- procedure CopyNeededFiles;
- var
- Config : TConfig;
- LocalFile, RemoteFile, s: string;
- LocalPath: string;
- i : integer;
- FileList : TStringList;
- RelativeToConfigMarker : TObject;
- 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 BuildFileList: TStringList;
- var
- dfl, fl : string;
- begin
- fl:=Trim(Config.Files);
- dfl:=Trim(Config.DelFiles);
- if (fl='') and (dfl='') and (Config.ConfigFileSrc='') then
- begin
- Result:=nil;
- exit;
- end;
- Result:=TStringList.Create;
- while fl<>'' do
- begin
- LocalFile:=Trim(GetToken(fl, [' ',',',';']));
- Result.Add(LocalFile);
- if verbose then
- writeln('Adding file ',LocalFile,' from Config.Files');
- end;
- if Config.ConfigFileSrc<>'' then
- begin
- if Config.ConfigFileSrc=Config.ConfigFileDst then
- Result.AddObject(Config.ConfigFileSrc,RelativeToConfigMarker)
- else
- Result.AddObject(Config.ConfigFileSrc+'='+Config.ConfigFileDst,RelativeToConfigMarker);
- if verbose then
- writeln('Adding config file Src=',Config.ConfigFileSrc,' Dst=',Config.ConfigFileDst);
- end;
- while dfl <> '' do
- begin
- LocalFile:=Trim(GetToken(dfl, [' ',',',';']));
- Result.Add(LocalFile);
- if verbose then
- writeln('Adding file ',LocalFile,' from Config.DelFiles');
- end;
- end;
- var
- ddir : string;
- param1_dir : string;
- begin
- param1_dir:=ExtractFilePath(ParamStr(1));
- if not IsAbsolute(SourceFileName) and not TargetFileExists(SourceFileName) then
- begin
- ddir:=GetEnvironmentVariable('BASEDIR');
- if ddir='' then
- GetDir(0,ddir);
- // writeln('Start ddir=',ddir);
- while (ddir<>'') do
- begin
- if TargetFileExists(ddir+DirectorySeparator+SourceFileName) then
- begin
- SourceFileName:=ddir+DirectorySeparator+SourceFileName;
- break;
- end
- else
- begin
- if ddir=splitpath(ddir) then
- break
- else
- ddir:=splitpath(ddir);
- if ddir[length(ddir)]=DirectorySeparator then
- ddir:=copy(ddir,1,length(ddir)-1);
- // writeln('Next ddir=',ddir);
- end;
- end;
- end;
- if not TargetFileExists(SourceFileName) then
- begin
- writeln('File ',SourceFileName,' not found');
- exit;
- end
- else if verbose then
- writeln('Analyzing source file ',SourceFileName);
- if not GetConfig('',SourceFileName,config) then
- exit;
- RelativeToConfigMarker:=TObject.Create;
- FileList:=BuildFileList;
- TmpFileList:=TStringList.Create;
- if assigned(FileList) then
- begin
- LocalPath:=SplitPath(SourceFileName);
- if (Length(LocalPath) > 0) and (LocalPath[Length(LocalPath)]<>DirectorySeparator) then
- LocalPath:=LocalPath+DirectorySeparator;
- for i:=0 to FileList.count-1 do
- begin
- if FileList.Names[i]<>'' then
- begin
- LocalFile:=FileList.Names[i];
- RemoteFile:=FileList.ValueFromIndex[i];
- end
- else
- begin
- LocalFile:=FileList[i];
- RemoteFile:=LocalFile;
- end;
- if FileList.Objects[i]=RelativeToConfigMarker then
- s:='config/'+LocalFile
- else
- s:=LocalPath+LocalFile;
- if not TargetFileExists(s) then
- if TargetFileExists(param1_dir+DirectorySeparator+LocalFile) then
- s:=param1_dir+DirectorySeparator+LocalFile;
- CopyFile(s,DosBoxDir+RemoteFile);
- TmpFileList.Add(RemoteFile);
- end;
- FileList.Free;
- end;
- RelativeToConfigMarker.Free;
- end;
- { On modified dosbox executable it is possible to get
- a copy of all output to CON into a file, simply write it
- back to output, so it ends up into testname.elg file.
- Skip all until line beginning with 'Drive C is mounted as' }
- procedure EchoOutput;
- const
- SkipUntilText = 'Drive C is mounted as ';
- var
- StdText : TextFile;
- st : string;
- line : longint;
- SkipUntilSeen : boolean;
- begin
- if FileExists(OutputFileName) then
- begin
- if verbose then
- Writeln('Trying to open ',OutputFileName);
- try
- AssignFile(StdText, OutputFileName);
- Reset(StdText);
- if verbose then
- Writeln('Successfully opened ',OutputFileName,', copying content to output');
- try
- line:=0;
- SkipUntilSeen:=false;
- while not eof(StdText) do
- begin
- Readln(StdText,st);
- inc(line);
- if not SkipUntilSeen then
- SkipUntilSeen:=pos(SkipUntilText,st)>0;
- if SkipUntilSeen then
- Writeln(line,': ',st);
- end;
- finally
- if not SkipUntilSeen then
- Writeln('Could not find "',SkipUntilText,'" in file ',OutputFilename);
- Flush(output);
- CloseFile(StdText);
- end;
- finally
- if use_temp_dir and SkipUntilSeen then
- DeleteFile(OutputFileName);
- if use_temp_dir and not SkipUntilSeen then
- begin
- writeln('Setting temp_dir_generated to false');
- temp_dir_generated:=false;
- end;
- end;
- end;
- end;
- function ReadExitCode(const ADosBoxDir: string): Integer;
- var
- F: TextFile;
- S : ShortString;
- value : Integer;
- errpos : Word;
- begin
- AssignFile(F, ADosBoxDir + 'EXITCODE.TXT');
- if verbose and not FileExists(ADosBoxDir + 'EXITCODE.TXT') then
- writeln('ReadExitCode: '+ADosBoxDir + 'EXITCODE.TXT does not exist');
- try
- Reset(F);
- if verbose then
- begin
- Readln(F, S);
- system.Val(S,Value,errpos);
- if errpos=0 then
- Result:=value
- else
- begin
- writeln('ReadExitCode: First line "'+S+'" generated error at pos=',errpos);
- ReadExitCode:=126*256;
- exit;
- end;
- end
- else
- Readln(F, Result);
- if Result <> 0 then
- Writeln('ExitCode=',Result);
- CloseFile(F);
- except
- Writeln('Unable to read exitcode value');
- if (DosBoxExitStatus <> 0) then
- Writeln('DosBox exit status = ',DosBoxExitStatus);
- temp_dir_generated:=false;
- ReadExitCode:=127*256;
- end;
- if verbose then
- writeln('Test finished with ExitCode=',ReadExitCode);
- end;
- function ExecuteDosBox(const ADosBoxBinaryPath, ADosBoxDir: string) : Integer;
- var
- Time: Integer = 0;
- begin
- DosBoxProcess := TProcess.Create(nil);
- result:=-1;
- try
- DosBoxProcess.Executable := ADosBoxBinaryPath;
- DosBoxProcess.Parameters.Add('-conf');
- DosBoxProcess.Parameters.Add(ADosBoxDir + 'dosbox.conf');
- if hide_execution then
- DosBoxProcess.ShowWindow := swoHIDE;
- if verbose then
- writeln('Starting ',ADosBoxBinaryPath+' -conf dosbox.conf');
- DosBoxProcess.Execute;
- repeat
- Inc(Time);
- if (Time > 10*dosbox_timeout) and do_exit then
- break;
- Sleep(100);
- until not DosBoxProcess.Running;
- if DosBoxProcess.Running then
- begin
- Writeln('Timeout exceeded. Killing dosbox...');
- DosBoxProcess.Terminate(254);
- Sleep(100);
- end;
- finally
- result:=DosBoxProcess.ExitStatus;
- DosBoxProcess.Free;
- DosBoxProcess:=nil;
- EchoOutput;
- end;
- end;
- function DeleteIfExists(const AFileName: string) : boolean;
- begin
- result:=false;
- if FileExists(AFileName) then
- result:=DeleteFile(AFileName);
- if not result and FileExists(AFileName+'.exe') then
- result:=DeleteFile(AFileName+'.exe');
- if not result and FileExists(AFileName+'.EXE') then
- result:=DeleteFile(AFileName+'.EXE');
- end;
- { RemoveDir, with removal of files or subdirectories inside first.
- ADirName is supposed to finish with DirectorySeparator }
- function RemoveDir(const ADirName: string) : boolean;
- var
- Info : TSearchRec;
- begin
- Result:=true;
- If FindFirst (AdirName+'*',faAnyFile and faDirectory,Info)=0 then
- begin
- repeat
- with Info do
- begin
- If (Attr and faDirectory) = faDirectory then
- begin
- { Skip present and parent directory }
- if (Name<>'..') and (Name<>'.') then
- if not RemoveDir(ADirName+Name+DirectorySeparator) then
- begin
- writeln('Failed to remove dir '+ADirName+Name+DirectorySeparator);
- result:=false;
- FindClose(Info);
- exit;
- end;
- end
- else
- if not DeleteFile(ADirName+Name) then
- begin
- writeln('Failed to remove file '+ADirName+Name);
- result:=false;
- FindClose(Info);
- exit;
- end;
- end;
- Until FindNext(info)<>0;
- end;
- FindClose(Info);
- RemoveDir:=SysUtils.RemoveDir(ADirName);
- end;
- procedure Cleanup(const ADosBoxDir: string);
- var
- i : longint;
- begin
- if verbose then
- writeln('Cleanup '+ADosBoxDir);
- DeleteIfExists(ADosBoxDir + 'dosbox.conf');
- DeleteIfExists(ADosBoxDir + 'EXITCODE.TXT');
- DeleteIfExists(ADosBoxDir + 'EXITCODE.EXE');
- DeleteIfExists(ADosBoxDir + 'CWSDPMI.EXE');
- DeleteIfExists(ADosBoxDir + 'TEST.EXE');
- if Assigned(TmpFileList) then
- begin
- for i:=0 to TmpFileList.count-1 do
- if TmpFileList[i]<>'' then
- DeleteIfExists(ADosBoxDir + TmpFileList[i]);
- end;
- TmpFileList.Free;
- ChDir(StartDir);
- if not RemoveDir(ADosBoxDir) then
- writeln('Failed to remove dir ',ADosBoxDir);
- end;
- {$ifdef UseSignals}
- const
- SignalCalled : boolean = false;
- SignalNb : longint = 0;
- function DosBoxSignal(signal:longint):longint; cdecl;
- begin
- SignalCalled:=true;
- SignalNb:=signal;
- end;
- {$endif def UseSignals}
- procedure ExitProc;
- var
- count : longint;
- begin
- if assigned(DosBoxProcess) and (DosBoxProcess.Running) then
- begin
- Writeln('In ExitProc. Killing dosbox...');
- DosBoxProcess.Terminate(254*1024);
- Sleep(100);
- count:=1;
- while (DosBoxProcess.Running) do
- begin
- Sleep(100);
- inc(count);
- if (count mod 20=0) then
- DosBoxProcess.Terminate(254*1024+count);
- end;
- if count>1 then
- Writeln('In ExitProc. Wait for termination dosbox..., time=',count/10);
- EchoOutput;
- end;
- end;
- begin
- Randomize;
- if GetEnvironmentVariable('DOSBOX_NO_TEMPDIR')<>'' then
- begin
- use_temp_dir:=false;
- Writeln('use_temp_dir set to false');
- end;
- if GetEnvironmentVariable('DOSBOX_NO_HIDE')<>'' then
- begin
- hide_execution:=false;
- Writeln('hide_execution set to false');
- end;
- if GetEnvironmentVariable('DOSBOX_NO_EXIT')<>'' then
- begin
- do_exit:=false;
- Writeln('do_exit set to false');
- end;
- if GetEnvironmentVariable('DOSBOX_VERBOSE')<>'' then
- begin
- verbose:=true;
- Writeln('verbose set to true');
- end;
- if (GetEnvironmentVariable('DOSBOX_NEEDS_CWSDPMI')<>'') or
- (GetEnvironmentVariable('TEST_OS_TARGET')='go32v2') then
- begin
- need_cwsdpmi:=true;
- Writeln('need_cwsdpmi set to true');
- end;
- if GetEnvironmentVariable('DOSBOX_TIMEOUT')<>'' then
- begin
- dosbox_timeout:=StrToInt(GetEnvironmentVariable('DOSBOX_TIMEOUT'));
- Writeln('dosbox_timeout set to ', dosbox_timeout, ' seconds');
- end;
- if ParamCount = 0 then
- begin
- Writeln('Usage: ' + ParamStr(0) + ' <executable> (-Ssourcename)');
- Writeln('Set DOSBOX_NO_TEMPDIR env variable to 1 to avoid using a temporary directory');
- Writeln('Set DOSBOX_NO_HIDE to avoid running dosbox in an hidden window');
- Writeln('Set DOSBOX_NO_EXIT to avoid exiting dosbox after test has been run');
- Writeln('Set DOSBOX_TIMEOUT to set the timeout in seconds before killing the dosbox process, assuming the test has hanged');
- halt(1);
- end;
- DosBoxBinaryPath := GetEnvironmentVariable('DOSBOX');
- if DosBoxBinaryPath = '' then
- begin
- Writeln('Please set the DOSBOX environment variable to the dosbox executable');
- halt(1);
- end
- else
- begin
- Writeln('Using DOSBOX executable: ',DosBoxBinaryPath);
- end;
- { DosBoxDir is used inside dosbox.conf as a MOUNT parameter }
- if use_temp_dir then
- begin
- GetDir(0,StartDir);
- Try
- DosBoxDir := GenerateTempDir;
- Except
- Writeln('GenerateTempDir call failed');
- halt(1);
- end;
- { All executable test have t.*.pp pattern }
- if (paramcount>1) and (copy(paramstr(2),1,2)='-S') then
- SourceFileName:=copy(paramstr(2),3,length(paramstr(2)))
- else
- SourceFileName:=ForceExtension(Paramstr(1),'.pp');
- CopyNeededFiles;
- end
- else
- begin
- Writeln('Using ',ParamStr(1));
- DosBoxDir:=ExtractFilePath(ParamStr(1));
- if DosBoxDir='' then
- DosBoxDir:=GetCurrentDir+DirectorySeparator;
- Writeln('Using DosBoxDir=',DosBoxDir);
- { Get rid of previous exicode.txt file }
- DeleteIfExists(DosBoxDir + 'EXITCODE.TXT');
- end;
- try
- {$ifdef UseSignals}
- Signal(SIGINT,@DosBoxSignal);
- Signal(SIGQUIT,@DosBoxSignal);
- Signal(SIGTERM,@DosBoxSignal);
- {$endif def UseSignals}
- GenerateDosBoxConf(DosBoxDir);
- CopyFile(ExtractFilePath(ParamStr(0)) + 'exitcode.exe', DosBoxDir + 'EXITCODE.EXE');
- CopyFile(ParamStr(1), DosBoxDir + 'TEST.EXE');
- if need_cwsdpmi then
- begin
- cwsdpmi_file:=FileSearch('cwsdpmi.exe',GetEnvironmentVariable('PATH'));
- if cwsdpmi_file<>'' then
- CopyFile(cwsdpmi_file, DosBoxDir + 'CWSDPMI.EXE')
- else if verbose then
- writeln('cwsdpmi executable missing');
- end;
- DosBoxExitStatus:=ExecuteDosBox(DosBoxBinaryPath, DosBoxDir);
- finally
- ExitProc;
- end;
- {$ifdef UseSignals}
- if SignalCalled then
- begin
- Writeln('Signal ',SignalNb,' called');
- end;
- {$endif def UseSignals}
- ExitProc;
- ExitCode:=ReadExitCode(DosBoxDir);
- if use_temp_dir and temp_dir_generated then
- Cleanup(DosBoxDir);
- halt(ExitCode);
- end.
|