dosbox_wrapper.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. {$MODE objfpc}{$H+}
  2. uses
  3. SysUtils, StrUtils,
  4. {$ifdef UseSignals}
  5. signals,
  6. {$endif def UseSignals}
  7. testu, classes,
  8. Process;
  9. const
  10. use_temp_dir : boolean = true;
  11. need_cwsdpmi : boolean = false;
  12. cwsdpmi_file : string = '';
  13. hide_execution : boolean = true;
  14. do_exit : boolean = true;
  15. verbose : boolean = false;
  16. DosBoxProcess: TProcess = nil;
  17. dosbox_timeout : integer = 400; { default timeout in seconds }
  18. var
  19. OutputFileName : String;
  20. SourceFileName : String;
  21. StartDir, DosBoxDir: string;
  22. ExitCode: Integer = 255;
  23. DosBoxBinaryPath: string;
  24. TmpFileList : TStringList;
  25. function GenerateTempDir: string;
  26. var
  27. FileName: string;
  28. TempDir: string;
  29. Done: Boolean = False;
  30. begin
  31. TempDir := GetTempDir(False);
  32. repeat
  33. try
  34. FileName := TempDir + 'dosboxwrappertmp_' + IntToStr(Random(100000));
  35. if verbose then
  36. writeln('Trying to create directory ',Filename);
  37. MkDir(FileName);
  38. Done := True;
  39. except
  40. on E: EInOutError do
  41. begin
  42. { 5 = Access Denied, returned when a file is duplicated }
  43. if E.ErrorCode <> 5 then
  44. begin
  45. Writeln('Directory creation failed');
  46. raise;
  47. end;
  48. end;
  49. end;
  50. until Done;
  51. Result := FileName + DirectorySeparator;
  52. end;
  53. procedure GenerateDosBoxConf(const ADosBoxDir: string);
  54. var
  55. SourceConfFileName, TargetConfFileName: string;
  56. SourceFile, TargetFile: TextFile;
  57. OrigS, S: string;
  58. begin
  59. SourceConfFileName := ExtractFilePath(ParamStr(0)) + 'dosbox.conf';
  60. TargetConfFileName := ADosBoxDir + 'dosbox.conf';
  61. OutputFileName := ADosBoxDir + 'dosbox.out';
  62. if verbose then
  63. Writeln('Using target dosbox.conf ',TargetConfFileName);
  64. AssignFile(SourceFile, SourceConfFileName);
  65. AssignFile(TargetFile, TargetConfFileName);
  66. Reset(SourceFile);
  67. try
  68. Rewrite(TargetFile);
  69. try
  70. while not EoF(SourceFile) do
  71. begin
  72. Readln(SourceFile, S);
  73. OrigS:=S;
  74. S := AnsiReplaceStr(S, '$DosBoxDir', ADosBoxDir);
  75. S := AnsiReplaceStr(S, '$wrapper_output', OutputFileName);
  76. if do_exit then
  77. S := AnsiReplaceStr(S, '$exit', 'exit')
  78. else
  79. S := AnsiReplaceStr(S, '$exit', '');
  80. If verbose and (OrigS <> S) then
  81. Writeln('"',OrigS,'" transformed into "',S,'"');
  82. Writeln(TargetFile, S);
  83. end;
  84. finally
  85. CloseFile(TargetFile);
  86. end;
  87. finally
  88. CloseFile(SourceFile);
  89. end;
  90. end;
  91. procedure CopyFile(ASrcFileName, ADestFileName: string);
  92. var
  93. SrcF, DestF: File;
  94. OldFileMode: Integer;
  95. Buf: array [0..4095] of Byte;
  96. BytesRead: Integer;
  97. begin
  98. if verbose then
  99. Writeln('CopyFile ', ASrcFileName, '->', ADestFileName);
  100. if not AnsiEndsText('.exe', ASrcFileName) and AnsiEndsText('.EXE',ADestFileName) then
  101. ASrcFileName := ASrcFileName + '.exe';
  102. OldFileMode := FileMode;
  103. try
  104. AssignFile(SrcF, ASrcFileName);
  105. AssignFile(DestF, ADestFileName);
  106. FileMode := fmOpenRead;
  107. Reset(SrcF, 1);
  108. try
  109. FileMode := fmOpenWrite;
  110. try
  111. Rewrite(DestF, 1);
  112. repeat
  113. BlockRead(SrcF, Buf, SizeOf(Buf), BytesRead);
  114. BlockWrite(DestF, Buf, BytesRead);
  115. until BytesRead < SizeOf(Buf);
  116. finally
  117. CloseFile(DestF);
  118. end;
  119. finally
  120. CloseFile(SrcF);
  121. end;
  122. finally
  123. FileMode := OldFileMode;
  124. end;
  125. end;
  126. function ForceExtension(Const HStr,ext:String):String;
  127. {
  128. Return a filename which certainly has the extension ext
  129. }
  130. var
  131. j : longint;
  132. begin
  133. j:=length(Hstr);
  134. while (j>0) and (Hstr[j]<>'.') do
  135. dec(j);
  136. if j=0 then
  137. j:=length(Hstr)+1;
  138. if Ext<>'' then
  139. begin
  140. if Ext[1]='.' then
  141. ForceExtension:=Copy(Hstr,1,j-1)+Ext
  142. else
  143. ForceExtension:=Copy(Hstr,1,j-1)+'.'+Ext
  144. end
  145. else
  146. ForceExtension:=Copy(Hstr,1,j-1);
  147. end;
  148. procedure CopyNeededFiles;
  149. var
  150. Config : TConfig;
  151. LocalFile, RemoteFile, s: string;
  152. LocalPath: string;
  153. i : integer;
  154. FileList : TStringList;
  155. RelativeToConfigMarker : TObject;
  156. function SplitPath(const s:string):string;
  157. var
  158. i : longint;
  159. begin
  160. i:=Length(s);
  161. while (i>0) and not(s[i] in ['/','\'{$IFDEF MACOS},':'{$ENDIF}]) do
  162. dec(i);
  163. SplitPath:=Copy(s,1,i);
  164. end;
  165. function BuildFileList: TStringList;
  166. var
  167. dfl, fl : string;
  168. begin
  169. fl:=Trim(Config.Files);
  170. dfl:=Trim(Config.DelFiles);
  171. if (fl='') and (dfl='') and (Config.ConfigFileSrc='') then
  172. begin
  173. Result:=nil;
  174. exit;
  175. end;
  176. Result:=TStringList.Create;
  177. while fl<>'' do
  178. begin
  179. LocalFile:=Trim(GetToken(fl, [' ',',',';']));
  180. Result.Add(LocalFile);
  181. end;
  182. if Config.ConfigFileSrc<>'' then
  183. begin
  184. if Config.ConfigFileSrc=Config.ConfigFileDst then
  185. Result.AddObject(Config.ConfigFileSrc,RelativeToConfigMarker)
  186. else
  187. Result.AddObject(Config.ConfigFileSrc+'='+Config.ConfigFileDst,RelativeToConfigMarker);
  188. end;
  189. while dfl <> '' do
  190. begin
  191. LocalFile:=Trim(GetToken(dfl, [' ',',',';']));
  192. Result.Add(LocalFile);
  193. end;
  194. end;
  195. var
  196. ddir : string;
  197. begin
  198. if not IsAbsolute(SourceFileName) and not FileExists(SourceFileName) then
  199. begin
  200. ddir:=GetEnvironmentVariable('BASEDIR');
  201. if ddir='' then
  202. GetDir(0,ddir);
  203. // writeln('Start ddir=',ddir);
  204. while (ddir<>'') do
  205. begin
  206. if FileExists(ddir+DirectorySeparator+SourceFileName) then
  207. begin
  208. SourceFileName:=ddir+DirectorySeparator+SourceFileName;
  209. break;
  210. end
  211. else
  212. begin
  213. if ddir=splitpath(ddir) then
  214. break
  215. else
  216. ddir:=splitpath(ddir);
  217. if ddir[length(ddir)]=DirectorySeparator then
  218. ddir:=copy(ddir,1,length(ddir)-1);
  219. // writeln('Next ddir=',ddir);
  220. end;
  221. end;
  222. end;
  223. if not FileExists(SourceFileName) then
  224. begin
  225. writeln('File ',SourceFileName,' not found');
  226. exit;
  227. end
  228. else if verbose then
  229. writeln('Analyzing source file ',SourceFileName);
  230. if not GetConfig(SourceFileName,config) then
  231. exit;
  232. RelativeToConfigMarker:=TObject.Create;
  233. FileList:=BuildFileList;
  234. TmpFileList:=TStringList.Create;
  235. if assigned(FileList) then
  236. begin
  237. LocalPath:=SplitPath(SourceFileName);
  238. if (Length(LocalPath) > 0) and (LocalPath[Length(LocalPath)]<>DirectorySeparator) then
  239. LocalPath:=LocalPath+DirectorySeparator;
  240. for i:=0 to FileList.count-1 do
  241. begin
  242. if FileList.Names[i]<>'' then
  243. begin
  244. LocalFile:=FileList.Names[i];
  245. RemoteFile:=FileList.ValueFromIndex[i];
  246. end
  247. else
  248. begin
  249. LocalFile:=FileList[i];
  250. RemoteFile:=LocalFile;
  251. end;
  252. if FileList.Objects[i]=RelativeToConfigMarker then
  253. s:='config/'+LocalFile
  254. else
  255. s:=LocalPath+LocalFile;
  256. CopyFile(s,DosBoxDir+DirectorySeparator+RemoteFile);
  257. TmpFileList.Add(RemoteFile);
  258. end;
  259. FileList.Free;
  260. end;
  261. RelativeToConfigMarker.Free;
  262. end;
  263. { On modified dosbox executable it is possible to get
  264. a copy of all output to CON into a file, simply write it
  265. back to output, so it ends up into testname.elg file.
  266. Skip all until line beginning with 'Drive C is mounted as' }
  267. procedure EchoOutput;
  268. const
  269. SkipUntilText = 'Drive C is mounted as ';
  270. var
  271. StdText : TextFile;
  272. st : string;
  273. line : longint;
  274. SkipUntilSeen : boolean;
  275. begin
  276. if FileExists(OutputFileName) then
  277. begin
  278. if verbose then
  279. Writeln('Trying to open ',OutputFileName);
  280. try
  281. AssignFile(StdText, OutputFileName);
  282. Reset(StdText);
  283. if verbose then
  284. Writeln('Successfully opened ',OutputFileName,', copying content to output');
  285. try
  286. line:=0;
  287. SkipUntilSeen:=false;
  288. while not eof(StdText) do
  289. begin
  290. Readln(StdText,st);
  291. inc(line);
  292. if not SkipUntilSeen then
  293. SkipUntilSeen:=pos(SkipUntilText,st)>0;
  294. if SkipUntilSeen then
  295. Writeln(line,': ',st);
  296. end;
  297. finally
  298. if not SkipUntilSeen then
  299. Writeln('Could not find "',SkipUntilText,'" in file ',OutputFilename);
  300. Flush(output);
  301. CloseFile(StdText);
  302. end;
  303. finally
  304. if use_temp_dir then
  305. DeleteFile(OutputFileName);
  306. end;
  307. end;
  308. end;
  309. function ReadExitCode(const ADosBoxDir: string): Integer;
  310. var
  311. F: TextFile;
  312. begin
  313. AssignFile(F, ADosBoxDir + 'EXITCODE.TXT');
  314. try
  315. Reset(F);
  316. Readln(F, Result);
  317. if Result <> 0 then
  318. Writeln('ExitCode=',Result);
  319. CloseFile(F);
  320. except
  321. Writeln('Unable to read exitcode value');
  322. ReadExitCode:=127*256;
  323. end;
  324. end;
  325. procedure ExecuteDosBox(const ADosBoxBinaryPath, ADosBoxDir: string);
  326. var
  327. Time: Integer = 0;
  328. begin
  329. DosBoxProcess := TProcess.Create(nil);
  330. try
  331. DosBoxProcess.Executable := ADosBoxBinaryPath;
  332. DosBoxProcess.Parameters.Add('-conf');
  333. DosBoxProcess.Parameters.Add(ADosBoxDir + 'dosbox.conf');
  334. if hide_execution then
  335. DosBoxProcess.ShowWindow := swoHIDE;
  336. DosBoxProcess.Execute;
  337. repeat
  338. Inc(Time);
  339. if (Time > 10*dosbox_timeout) and do_exit then
  340. break;
  341. Sleep(100);
  342. until not DosBoxProcess.Running;
  343. if DosBoxProcess.Running then
  344. begin
  345. Writeln('Timeout exceeded. Killing dosbox...');
  346. DosBoxProcess.Terminate(254);
  347. Sleep(100);
  348. end;
  349. finally
  350. DosBoxProcess.Free;
  351. DosBoxProcess:=nil;
  352. EchoOutput;
  353. end;
  354. end;
  355. procedure DeleteIfExists(const AFileName: string);
  356. begin
  357. if FileExists(AFileName) then
  358. DeleteFile(AFileName);
  359. end;
  360. procedure Cleanup(const ADosBoxDir: string);
  361. var
  362. i : longint;
  363. begin
  364. DeleteIfExists(ADosBoxDir + 'dosbox.conf');
  365. DeleteIfExists(ADosBoxDir + 'EXITCODE.TXT');
  366. DeleteIfExists(ADosBoxDir + 'EXITCODE.EXE');
  367. DeleteIfExists(ADosBoxDir + 'CWSDPMI.EXE');
  368. DeleteIfExists(ADosBoxDir + 'TEST.EXE');
  369. if Assigned(TmpFileList) then
  370. begin
  371. for i:=0 to TmpFileList.count-1 do
  372. if TmpFileList[i]<>'' then
  373. DeleteIfExists(ADosBoxDir + TmpFileList[i]);
  374. end;
  375. TmpFileList.Free;
  376. ChDir(StartDir);
  377. RmDir(ADosBoxDir);
  378. end;
  379. {$ifdef UseSignals}
  380. const
  381. SignalCalled : boolean = false;
  382. SignalNb : longint = 0;
  383. function DosBoxSignal(signal:longint):longint; cdecl;
  384. begin
  385. SignalCalled:=true;
  386. SignalNb:=signal;
  387. end;
  388. {$endif def UseSignals}
  389. procedure ExitProc;
  390. var
  391. count : longint;
  392. begin
  393. if assigned(DosBoxProcess) and (DosBoxProcess.Running) then
  394. begin
  395. Writeln('In ExitProc. Killing dosbox...');
  396. DosBoxProcess.Terminate(254*1024);
  397. Sleep(100);
  398. count:=1;
  399. while (DosBoxProcess.Running) do
  400. begin
  401. Sleep(100);
  402. inc(count);
  403. if (count mod 20=0) then
  404. DosBoxProcess.Terminate(254*1024+count);
  405. end;
  406. if count>1 then
  407. Writeln('In ExitProc. Wait for termination dosbox..., time=',count/10);
  408. EchoOutput;
  409. end;
  410. end;
  411. begin
  412. Randomize;
  413. if GetEnvironmentVariable('DOSBOX_NO_TEMPDIR')<>'' then
  414. begin
  415. use_temp_dir:=false;
  416. Writeln('use_temp_dir set to false');
  417. end;
  418. if GetEnvironmentVariable('DOSBOX_NO_HIDE')<>'' then
  419. begin
  420. hide_execution:=false;
  421. Writeln('hide_execution set to false');
  422. end;
  423. if GetEnvironmentVariable('DOSBOX_NO_EXIT')<>'' then
  424. begin
  425. do_exit:=false;
  426. Writeln('do_exit set to false');
  427. end;
  428. if GetEnvironmentVariable('DOSBOX_VERBOSE')<>'' then
  429. begin
  430. verbose:=true;
  431. Writeln('verbose set to true');
  432. end;
  433. if (GetEnvironmentVariable('DOSBOX_NEEDS_CWSDPMI')<>'') or
  434. (GetEnvironmentVariable('TEST_OS_TARGET')='go32v2') then
  435. begin
  436. need_cwsdpmi:=true;
  437. Writeln('need_cwsdpmi set to true');
  438. end;
  439. if GetEnvironmentVariable('DOSBOX_TIMEOUT')<>'' then
  440. begin
  441. dosbox_timeout:=StrToInt(GetEnvironmentVariable('DOSBOX_TIMEOUT'));
  442. Writeln('dosbox_timeout set to ', dosbox_timeout, ' seconds');
  443. end;
  444. if ParamCount = 0 then
  445. begin
  446. Writeln('Usage: ' + ParamStr(0) + ' <executable> (-Ssourcename)');
  447. Writeln('Set DOSBOX_NO_TEMPDIR env variable to 1 to avoid using a temporary directory');
  448. Writeln('Set DOSBOX_NO_HIDE to avoid running dosbox in an hidden window');
  449. Writeln('Set DOSBOX_NO_EXIT to avoid exiting dosbox after test has been run');
  450. Writeln('Set DOSBOX_TIMEOUT to set the timeout in seconds before killing the dosbox process, assuming the test has hanged');
  451. halt(1);
  452. end;
  453. DosBoxBinaryPath := GetEnvironmentVariable('DOSBOX');
  454. if DosBoxBinaryPath = '' then
  455. begin
  456. Writeln('Please set the DOSBOX environment variable to the dosbox executable');
  457. halt(1);
  458. end
  459. else
  460. begin
  461. Writeln('Using DOSBOX executable: ',DosBoxBinaryPath);
  462. end;
  463. { DosBoxDir is used inside dosbox.conf as a MOUNT parameter }
  464. if use_temp_dir then
  465. begin
  466. GetDir(0,StartDir);
  467. DosBoxDir := GenerateTempDir;
  468. { All executable test have t.*.pp pattern }
  469. if (paramcount>1) and (copy(paramstr(2),1,2)='-S') then
  470. SourceFileName:=copy(paramstr(2),3,length(paramstr(2)))
  471. else
  472. SourceFileName:=ForceExtension(Paramstr(1),'.pp');
  473. CopyNeededFiles;
  474. end
  475. else
  476. begin
  477. Writeln('Using ',ParamStr(1));
  478. DosBoxDir:=ExtractFilePath(ParamStr(1));
  479. if DosBoxDir='' then
  480. DosBoxDir:=GetCurrentDir+DirectorySeparator;
  481. Writeln('Using DosBoxDir=',DosBoxDir);
  482. { Get rid of previous exicode.txt file }
  483. DeleteIfExists(DosBoxDir + 'EXITCODE.TXT');
  484. end;
  485. try
  486. {$ifdef UseSignals}
  487. Signal(SIGINT,@DosBoxSignal);
  488. Signal(SIGQUIT,@DosBoxSignal);
  489. Signal(SIGTERM,@DosBoxSignal);
  490. {$endif def UseSignals}
  491. GenerateDosBoxConf(DosBoxDir);
  492. CopyFile(ExtractFilePath(ParamStr(0)) + 'exitcode.exe', DosBoxDir + 'EXITCODE.EXE');
  493. CopyFile(ParamStr(1), DosBoxDir + 'TEST.EXE');
  494. if need_cwsdpmi then
  495. begin
  496. cwsdpmi_file:=FileSearch('cwsdpmi.exe',GetEnvironmentVariable('PATH'));
  497. if cwsdpmi_file<>'' then
  498. CopyFile(cwsdpmi_file, DosBoxDir + 'CWSDPMI.EXE')
  499. else if verbose then
  500. writeln('cwsdpmi executable missing');
  501. end;
  502. ExecuteDosBox(DosBoxBinaryPath, DosBoxDir);
  503. finally
  504. ExitProc;
  505. end;
  506. {$ifdef UseSignals}
  507. if SignalCalled then
  508. begin
  509. Writeln('Signal ',SignalNb,' called');
  510. end;
  511. {$endif def UseSignals}
  512. ExitProc;
  513. ExitCode:=ReadExitCode(DosBoxDir);
  514. if use_temp_dir then
  515. Cleanup(DosBoxDir);
  516. halt(ExitCode);
  517. end.