dosbox_wrapper.pas 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. {$MODE objfpc}{$H+}
  2. uses
  3. SysUtils, StrUtils, Process;
  4. const
  5. use_temp_dir : boolean = true;
  6. hide_execution : boolean = true;
  7. do_exit : boolean = true;
  8. verbose : boolean = false;
  9. dosbox_timeout : integer = 100; { default timeout in seconds }
  10. var
  11. OutputFileName : String;
  12. function GenerateTempDir: string;
  13. var
  14. FileName: string;
  15. TempDir: string;
  16. Done: Boolean = False;
  17. begin
  18. TempDir := GetTempDir(False);
  19. repeat
  20. try
  21. FileName := TempDir + 'dosboxwrappertmp_' + IntToStr(Random(100000));
  22. if verbose then
  23. writeln('Trying to create directory ',Filename);
  24. MkDir(FileName);
  25. Done := True;
  26. except
  27. on E: EInOutError do
  28. begin
  29. { 5 = Access Denied, returned when a file is duplicated }
  30. if E.ErrorCode <> 5 then
  31. begin
  32. Writeln('Directory creation failed');
  33. raise;
  34. end;
  35. end;
  36. end;
  37. until Done;
  38. Result := FileName + DirectorySeparator;
  39. end;
  40. procedure GenerateDosBoxConf(const ADosBoxDir: string);
  41. var
  42. SourceConfFileName, TargetConfFileName: string;
  43. SourceFile, TargetFile: TextFile;
  44. S: string;
  45. begin
  46. SourceConfFileName := ExtractFilePath(ParamStr(0)) + 'dosbox.conf';
  47. TargetConfFileName := ADosBoxDir + 'dosbox.conf';
  48. OutputFileName := ADosBoxDir + 'dosbox.out';
  49. if verbose then
  50. Writeln('Using target dosbox.conf ',TargetConfFileName);
  51. AssignFile(SourceFile, SourceConfFileName);
  52. AssignFile(TargetFile, TargetConfFileName);
  53. Reset(SourceFile);
  54. try
  55. Rewrite(TargetFile);
  56. try
  57. while not EoF(SourceFile) do
  58. begin
  59. Readln(SourceFile, S);
  60. S := AnsiReplaceStr(S, '$DosBoxDir', ADosBoxDir);
  61. S := AnsiReplaceStr(S, '$wrapper_output', OutputFileName);
  62. if do_exit then
  63. S := AnsiReplaceStr(S, '$exit', 'exit')
  64. else
  65. S := AnsiReplaceStr(S, '$exit', '');
  66. Writeln(TargetFile, S);
  67. end;
  68. finally
  69. CloseFile(TargetFile);
  70. end;
  71. finally
  72. CloseFile(SourceFile);
  73. end;
  74. end;
  75. procedure CopyFile(ASrcFileName, ADestFileName: string);
  76. var
  77. SrcF, DestF: File;
  78. OldFileMode: Integer;
  79. Buf: array [0..4095] of Byte;
  80. BytesRead: Integer;
  81. begin
  82. if verbose then
  83. Writeln('CopyFile ', ASrcFileName, '->', ADestFileName);
  84. if not AnsiEndsText('.exe', ASrcFileName) then
  85. ASrcFileName := ASrcFileName + '.exe';
  86. OldFileMode := FileMode;
  87. try
  88. AssignFile(SrcF, ASrcFileName);
  89. AssignFile(DestF, ADestFileName);
  90. FileMode := fmOpenRead;
  91. Reset(SrcF, 1);
  92. try
  93. FileMode := fmOpenWrite;
  94. try
  95. Rewrite(DestF, 1);
  96. repeat
  97. BlockRead(SrcF, Buf, SizeOf(Buf), BytesRead);
  98. BlockWrite(DestF, Buf, BytesRead);
  99. until BytesRead < SizeOf(Buf);
  100. finally
  101. CloseFile(DestF);
  102. end;
  103. finally
  104. CloseFile(SrcF);
  105. end;
  106. finally
  107. FileMode := OldFileMode;
  108. end;
  109. end;
  110. { On modified dosbox executable it is possible to get
  111. a copy of all output to CON into a file, simply write it
  112. back to output, so it ends up into testname.elg file.
  113. Skip all until line beginning with 'Drive C is mounted as' }
  114. procedure EchoOutput;
  115. const
  116. SkipUntilText = 'Drive C is mounted as ';
  117. var
  118. StdText : TextFile;
  119. st : string;
  120. line : longint;
  121. SkipUntilSeen : boolean;
  122. begin
  123. if FileExists(OutputFileName) then
  124. begin
  125. if verbose then
  126. Writeln('Trying to open ',OutputFileName);
  127. try
  128. AssignFile(StdText, OutputFileName);
  129. Reset(StdText);
  130. if verbose then
  131. Writeln('Successfully opened ',OutputFileName,', copying content to output');
  132. try
  133. line:=0;
  134. SkipUntilSeen:=false;
  135. while not eof(StdText) do
  136. begin
  137. Readln(StdText,st);
  138. inc(line);
  139. if not SkipUntilSeen then
  140. SkipUntilSeen:=pos(SkipUntilText,st)>0;
  141. if SkipUntilSeen then
  142. Writeln(line,': ',st);
  143. end;
  144. finally
  145. if not SkipUntilSeen then
  146. Writeln('Could not find "',SkipUntilText,'" in file ',OutputFilename);
  147. Flush(output);
  148. CloseFile(StdText);
  149. end;
  150. finally
  151. if use_temp_dir then
  152. DeleteFile(OutputFileName);
  153. end;
  154. end;
  155. end;
  156. function ReadExitCode(const ADosBoxDir: string): Integer;
  157. var
  158. F: TextFile;
  159. begin
  160. AssignFile(F, ADosBoxDir + 'EXITCODE.TXT');
  161. try
  162. Reset(F);
  163. Readln(F, Result);
  164. if Result <> 0 then
  165. Writeln('ExitCode=',Result);
  166. CloseFile(F);
  167. except
  168. Writeln('Unable to read exitcode value');
  169. ReadExitCode:=127*256;
  170. end;
  171. end;
  172. procedure ExecuteDosBox(const ADosBoxBinaryPath, ADosBoxDir: string);
  173. var
  174. Process: TProcess;
  175. Time: Integer = 0;
  176. begin
  177. Process := TProcess.Create(nil);
  178. try
  179. Process.Executable := ADosBoxBinaryPath;
  180. Process.Parameters.Add('-conf');
  181. Process.Parameters.Add(ADosBoxDir + 'dosbox.conf');
  182. if hide_execution then
  183. Process.ShowWindow := swoHIDE;
  184. Process.Execute;
  185. repeat
  186. Inc(Time);
  187. if (Time > 10*dosbox_timeout) and do_exit then
  188. break;
  189. Sleep(100);
  190. until not Process.Running;
  191. if Process.Running then
  192. begin
  193. Writeln('Timeout exceeded. Killing dosbox...');
  194. Process.Terminate(254);
  195. end;
  196. finally
  197. Process.Free;
  198. EchoOutput;
  199. end;
  200. end;
  201. procedure Cleanup(const ADosBoxDir: string);
  202. procedure DeleteIfExists(const AFileName: string);
  203. begin
  204. if FileExists(AFileName) then
  205. DeleteFile(AFileName);
  206. end;
  207. begin
  208. DeleteIfExists(ADosBoxDir + 'dosbox.conf');
  209. DeleteIfExists(ADosBoxDir + 'EXITCODE.TXT');
  210. DeleteIfExists(ADosBoxDir + 'EXITCODE.EXE');
  211. DeleteIfExists(ADosBoxDir + 'TEST.EXE');
  212. RmDir(ADosBoxDir);
  213. end;
  214. var
  215. DosBoxDir: string;
  216. ExitCode: Integer = 255;
  217. DosBoxBinaryPath: string;
  218. begin
  219. Randomize;
  220. if GetEnvironmentVariable('DOSBOX_NO_TEMPDIR')<>'' then
  221. begin
  222. use_temp_dir:=false;
  223. Writeln('use_temp_dir set to false');
  224. end;
  225. if GetEnvironmentVariable('DOSBOX_NO_HIDE')<>'' then
  226. begin
  227. hide_execution:=false;
  228. Writeln('hide_execution set to false');
  229. end;
  230. if GetEnvironmentVariable('DOSBOX_NO_EXIT')<>'' then
  231. begin
  232. do_exit:=false;
  233. Writeln('do_exit set to false');
  234. end;
  235. if GetEnvironmentVariable('DOSBOX_VERBOSE')<>'' then
  236. begin
  237. verbose:=true;
  238. Writeln('verbose set to true');
  239. end;
  240. if GetEnvironmentVariable('DOSBOX_TIMEOUT')<>'' then
  241. begin
  242. dosbox_timeout:=StrToInt(GetEnvironmentVariable('DOSBOX_TIMEOUT'));
  243. Writeln('dosbox_timeout set to ', dosbox_timeout, ' seconds');
  244. end;
  245. if ParamCount = 0 then
  246. begin
  247. Writeln('Usage: ' + ParamStr(0) + ' <executable>');
  248. Writeln('Set DOSBOX_NO_TEMPDIR env variable to 1 to avoid using a temporary directory');
  249. Writeln('Set DOSBOX_NO_HIDE to avoid running dosbox in an hidden window');
  250. Writeln('Set DOSBOX_NO_EXIT to avoid exiting dosbox after test has been run');
  251. Writeln('Set DOSBOX_TIMEOUT to set the timeout in seconds before killing the dosbox process, assuming the test has hanged');
  252. halt(1);
  253. end;
  254. DosBoxBinaryPath := GetEnvironmentVariable('DOSBOX');
  255. if DosBoxBinaryPath = '' then
  256. begin
  257. Writeln('Please set the DOSBOX environment variable to the dosbox executable');
  258. halt(1);
  259. end
  260. else
  261. begin
  262. Writeln('Using DOSBOX executable: ',DosBoxBinaryPath);
  263. end;
  264. { DosBoxDir is used inside dosbox.conf as a MOUNT parameter }
  265. if use_temp_dir then
  266. DosBoxDir := GenerateTempDir
  267. else
  268. begin
  269. Writeln('Using ',ParamStr(1));
  270. DosBoxDir:=ExtractFilePath(ParamStr(1));
  271. if DosBoxDir='' then
  272. DosBoxDir:=GetCurrentDir+DirectorySeparator;
  273. Writeln('Using DosBoxDir=',DosBoxDir);
  274. end;
  275. try
  276. GenerateDosBoxConf(DosBoxDir);
  277. CopyFile(ExtractFilePath(ParamStr(0)) + 'exitcode.exe', DosBoxDir + 'EXITCODE.EXE');
  278. CopyFile(ParamStr(1), DosBoxDir + 'TEST.EXE');
  279. ExecuteDosBox(DosBoxBinaryPath, DosBoxDir);
  280. ExitCode := ReadExitCode(DosBoxDir);
  281. finally
  282. if use_temp_dir then
  283. Cleanup(DosBoxDir);
  284. end;
  285. halt(ExitCode);
  286. end.