dosbox_wrapper.pas 15 KB

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