utcprocess.pp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. unit utcprocess;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils, fpcunit, testutils, testregistry, pipes, process;
  6. type
  7. { TTestProcess }
  8. TTestProcess= class(TTestCase)
  9. private
  10. FProc: TProcess;
  11. FProc2: TProcess;
  12. FProc3: TProcess;
  13. procedure AssertFileContent(const aFileName, aContent: String);
  14. procedure AssertFileContent(const aFileName: String; aContent: array of string);
  15. procedure AssertGenOutLines(const S: String; aCount: integer);
  16. procedure AssertGenOutLinesFile(const aFileName : string; aCount : Integer);
  17. procedure CreateInputLinesFile(const aFileName : string; aCount : Integer);
  18. function GetHelper(const aHelper: string): String;
  19. function GetTestFile(const aName: string): String;
  20. function ReadProcessOutput(aProc: TProcess; ReadStdErr : Boolean = False): string;
  21. procedure WaitForFile(const aFileName: String);
  22. protected
  23. procedure CheckHelper(const aHelper : string);
  24. procedure SetUp; override;
  25. procedure TearDown; override;
  26. property Proc : TProcess read FProc;
  27. property Proc2 : TProcess read FProc2;
  28. property Proc3 : TProcess read FProc3;
  29. published
  30. procedure TestHookUp;
  31. procedure TestSimple;
  32. procedure TestSimpleParam;
  33. Procedure TestExitStatus;
  34. Procedure TestWaitFor;
  35. Procedure TestOptionWaitOnExit;
  36. Procedure TestTerminate;
  37. Procedure TestPipes;
  38. Procedure TestWritePipes;
  39. Procedure TestStdErr;
  40. Procedure TestStdErrToOutput;
  41. Procedure TestInputFile;
  42. Procedure TestOutputFile;
  43. Procedure TestStdErrFile;
  44. Procedure TestInputNull;
  45. Procedure TestOutputFileExistingAppend;
  46. Procedure TestOutputFileExistingTruncate;
  47. Procedure TestOutputFileExistingAtStart;
  48. Procedure TestPipeOut;
  49. Procedure TestPipeOutToFile;
  50. Procedure TestPipeInOutToFile;
  51. Procedure TestPipeRestart;
  52. end;
  53. implementation
  54. uses dateutils;
  55. const
  56. dotouch = 'dotouch';
  57. docat = 'docat';
  58. doexit = 'doexit';
  59. genout = 'genout';
  60. fntouch = 'touch.txt';
  61. fntestoutput = 'output.txt';
  62. fntestinput = 'input.txt';
  63. var
  64. TestDir : String;
  65. TmpDir : String;
  66. procedure TTestProcess.AssertFileContent(const aFileName,aContent : String);
  67. begin
  68. AssertFileContent(aFileName,[aContent]);
  69. end;
  70. procedure TTestProcess.AssertFileContent(const aFileName : String; aContent : Array of string);
  71. var
  72. L : TStrings;
  73. I : integer;
  74. begin
  75. L:=TStringList.Create;
  76. try
  77. L.LoadFromFile(aFileName);
  78. AssertEquals('Line count',Length(aContent),L.Count);
  79. for I:=0 to L.Count-1 do
  80. AssertEquals('Line '+Inttostr(i)+'content',aContent[I],L[i]);
  81. finally
  82. L.Free;
  83. end;
  84. end;
  85. Procedure TTestProcess.WaitForFile(const aFileName : String);
  86. var
  87. aCount : Integer;
  88. FN : String;
  89. Exists : boolean;
  90. begin
  91. FN:=aFileName;
  92. aCount:=0;
  93. Repeat
  94. Sleep(20);
  95. Inc(aCount);
  96. Exists:=FileExists(FN);
  97. Until (aCount>=50) or Exists;
  98. AssertTrue('File did not appear: '+FN,Exists);
  99. Sleep(20);
  100. end;
  101. procedure TTestProcess.TestHookUp;
  102. procedure AssertNoFile(const FN :string);
  103. begin
  104. AssertFalse('File '+FN+' does not exist',FileExists(FN));
  105. end;
  106. begin
  107. AssertNotNull('Have process 1',Proc);
  108. AssertNotNull('Have process 2',Proc2);
  109. AssertNotNull('Have process 3',Proc3);
  110. AssertNoFile(fntouch);
  111. AssertNoFile(GetTestFile(fnTouch));
  112. AssertNoFile(GetTestFile(fntestoutput));
  113. end;
  114. procedure TTestProcess.TestSimple;
  115. begin
  116. Proc.Executable:=GetHelper(dotouch);
  117. Proc.Execute;
  118. AssertNull('no input stream',Proc.Input);
  119. AssertNull('no output stream',Proc.Output);
  120. AssertNull('no error stream',Proc.Stderr);
  121. WaitForFile(fntouch);
  122. AssertFileContent(fntouch,fntouch);
  123. end;
  124. procedure TTestProcess.TestSimpleParam;
  125. var
  126. FN : String;
  127. begin
  128. FN:=GetTestFile(fntouch);
  129. Proc.Executable:=GetHelper(dotouch);
  130. Proc.Parameters.Add(FN);
  131. Proc.Execute;
  132. WaitForFile(FN);
  133. AssertFileContent(FN,FN);
  134. end;
  135. procedure TTestProcess.TestExitStatus;
  136. // Test that halt(23) results in 23...
  137. begin
  138. Proc.Executable:=GetHelper(doexit);
  139. Proc.Parameters.Add('23');
  140. Proc.Execute;
  141. Proc.WaitOnExit;
  142. AssertEquals('Exit code',23,Proc.ExitStatus);
  143. end;
  144. procedure TTestProcess.TestWaitFor;
  145. var
  146. N : TDateTime;
  147. ms : Int64;
  148. begin
  149. Proc.Executable:=GetHelper(doexit);
  150. Proc.Parameters.Add('0');
  151. Proc.Parameters.Add('1000');
  152. N:=Now;
  153. Proc.Execute;
  154. Proc.WaitOnExit;
  155. ms:=MilliSecondsBetween(Now,N);
  156. AssertEquals('Exit code',0,Proc.ExitStatus);
  157. AssertTrue('Wait time',ms>900);
  158. end;
  159. procedure TTestProcess.TestOptionWaitOnExit;
  160. var
  161. N : TDateTime;
  162. ms : Int64;
  163. begin
  164. Proc.Executable:=GetHelper(doexit);
  165. Proc.Parameters.Add('0');
  166. Proc.Parameters.Add('1000');
  167. N:=Now;
  168. Proc.Options:=Proc.Options+[poWaitOnExit];
  169. Proc.Execute;
  170. ms:=MilliSecondsBetween(Now,N);
  171. AssertEquals('Exit code',0,Proc.ExitStatus);
  172. AssertTrue('Wait time',ms>900);
  173. end;
  174. procedure TTestProcess.TestTerminate;
  175. var
  176. N : TDateTime;
  177. ms : Int64;
  178. begin
  179. Proc.Executable:=GetHelper(doexit);
  180. Proc.Parameters.Add('0');
  181. Proc.Parameters.Add('2000');
  182. N:=Now;
  183. Proc.Execute;
  184. Sleep(500);
  185. Proc.Terminate(23);
  186. ms:=MilliSecondsBetween(Now,N);
  187. AssertTrue('Process exits at once',ms<1000);
  188. {$IFDEF UNIX}
  189. // Also check Kill if term will not work
  190. AssertTrue('Exit status',(15=Proc.ExitStatus) or (9=Proc.ExitStatus));
  191. {$ENDIF}
  192. {$IFDEF WINDOWS}
  193. // Check exit status provided to terminate.
  194. AssertTrue('Exit status',(23=Proc.ExitCode));
  195. {$ENDIF}
  196. end;
  197. procedure TTestProcess.AssertGenOutLines(const S : String; aCount : integer);
  198. var
  199. L : TStrings;
  200. I : Integer;
  201. begin
  202. sleep(100);
  203. // Writeln('Testing >>',S,'<<');
  204. L:=TStringList.Create;
  205. try
  206. L.Text:=S;
  207. AssertEquals('Count',aCount,L.Count);
  208. For I:=1 to aCount do
  209. AssertEquals('Content Line '+IntToStr(I),'Line '+IntToStr(I),L[I-1]);
  210. finally
  211. L.Free;
  212. end;
  213. end;
  214. procedure TTestProcess.AssertGenOutLinesFile(const aFileName: string; aCount: Integer);
  215. var
  216. L : TStrings;
  217. I : Integer;
  218. begin
  219. sleep(100);
  220. // Writeln('Testing file >>',aFileName,'<<');
  221. L:=TStringList.Create;
  222. try
  223. L.LoadFromFile(aFileName);
  224. AssertEquals('Count',aCount,L.Count);
  225. For I:=1 to aCount do
  226. AssertEquals('Content Line '+IntToStr(I),'Line '+IntToStr(I),L[I-1]);
  227. finally
  228. L.Free;
  229. end;
  230. end;
  231. procedure TTestProcess.CreateInputLinesFile(const aFileName: string; aCount: Integer);
  232. var
  233. L : TStrings;
  234. I : Integer;
  235. begin
  236. // Writeln('Creating Test file >>',aFileName,'<<');
  237. L:=TStringList.Create;
  238. try
  239. For I:=1 to aCount do
  240. L.Add('Line '+IntToStr(I));
  241. L.SaveToFile(aFileName);
  242. finally
  243. L.Free;
  244. end;
  245. end;
  246. function TTestProcess.ReadProcessOutput(aProc: TProcess; ReadStdErr: Boolean): string;
  247. var
  248. aRead,aLen: Integer;
  249. S : String;
  250. St : TInputPipeStream;
  251. begin
  252. aRead:=0;
  253. aLen:=0;
  254. S:='';
  255. Sleep(100);
  256. if ReadStdErr then
  257. st:=aProc.StdErr
  258. else
  259. st:=aProc.Output;
  260. AssertNotNull('Have stream to read output from',St);
  261. AssertTrue('Read input',aProc.ReadInputStream(St,aRead,aLen,S,100));
  262. SetLength(S,aRead);
  263. // Writeln('>>>',S,'<<<');
  264. Result:=S;
  265. end;
  266. procedure TTestProcess.TestPipes;
  267. var
  268. S : String;
  269. begin
  270. Proc.Executable:=GetHelper(genout);
  271. Proc.Options:=[poUsePipes];
  272. Proc.Execute;
  273. AssertNotNull('input stream',Proc.Input);
  274. AssertNotNull('output stream',Proc.Output);
  275. AssertNotNull('error stream',Proc.Stderr);
  276. S:=ReadProcessOutput(Proc);
  277. AssertGenOutLines(S,3);
  278. end;
  279. procedure TTestProcess.TestWritePipes;
  280. var
  281. Sin,Sout : String;
  282. begin
  283. Proc.Executable:=GetHelper(docat);
  284. Proc.Options:=[poUsePipes];
  285. Proc.Execute;
  286. // Note: this test will only work for small amounts of data, less than pipe buffer size.
  287. Sin:='this is some text'+sLineBreak+'And some more text'+sLineBreak;
  288. Proc.Input.Write(Sin[1],Length(Sin));
  289. Proc.CloseInput;
  290. SOut:=ReadProcessOutput(Proc);
  291. AssertEquals('Out equals in',SIn,Sout);
  292. end;
  293. procedure TTestProcess.TestStdErr;
  294. var
  295. S : String;
  296. begin
  297. Proc.Executable:=GetHelper(genout);
  298. Proc.Parameters.Add('-3');
  299. Proc.Options:=[poUsePipes];
  300. Proc.Execute;
  301. S:=ReadProcessOutput(Proc,true);
  302. AssertGenOutLines(S,3);
  303. end;
  304. procedure TTestProcess.TestStdErrToOutput;
  305. var
  306. S : String;
  307. begin
  308. Proc.Executable:=GetHelper(genout);
  309. Proc.Parameters.Add('-3');
  310. Proc.Options:=[poUsePipes,poStderrToOutPut];
  311. Proc.Execute;
  312. S:=ReadProcessOutput(Proc);
  313. AssertGenOutLines(S,3);
  314. end;
  315. procedure TTestProcess.TestInputFile;
  316. var
  317. S : String;
  318. begin
  319. CreateInputLinesFile(GetTestFile(fntestinput),3);
  320. Proc.Executable:=GetHelper(docat);
  321. Proc.InputDescriptor.FileName:=GetTestFile(fntestinput);
  322. AssertTrue('Descriptor IOType', Proc.InputDescriptor.IOType=iotFile);
  323. Proc.OutputDescriptor.IOType:=iotPipe;
  324. Proc.Execute;
  325. AssertNull('input stream',Proc.Input);
  326. AssertNotNull('output stream',Proc.Output);
  327. AssertNull('error stream',Proc.Stderr);
  328. S:=ReadProcessOutput(Proc);
  329. AssertGenOutLines(S,3);
  330. end;
  331. procedure TTestProcess.TestOutputFile;
  332. begin
  333. Proc.Executable:=GetHelper(genout);
  334. Proc.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
  335. Proc.Execute;
  336. AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
  337. end;
  338. procedure TTestProcess.TestStdErrFile;
  339. begin
  340. Proc.Executable:=GetHelper(genout);
  341. Proc.Parameters.Add('-3');
  342. Proc.ErrorDescriptor.FileName:=GetTestFile(fntestoutput);
  343. Proc.Execute;
  344. AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
  345. end;
  346. procedure TTestProcess.TestInputNull;
  347. var
  348. B : TBytes;
  349. begin
  350. Proc.Executable:=GetHelper(docat);
  351. Proc.InputDescriptor.IOType:=iotNull;
  352. Proc.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
  353. Proc.Execute;
  354. Sleep(100);
  355. B:=Sysutils.GetFileContents(GetTestFile(fntestoutput));
  356. AssertEquals('Empty file',0,Length(B));
  357. end;
  358. procedure TTestProcess.TestOutputFileExistingAppend;
  359. // Check that we actually append
  360. begin
  361. CreateInputLinesFile(GetTestFile(fntestoutput),3);
  362. Proc.Executable:=GetHelper(genout);
  363. Proc.Parameters.add('3');
  364. Proc.Parameters.add('3');
  365. Proc.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
  366. Proc.OutputDescriptor.FileWriteMode:=fwmAppend;
  367. Proc.Execute;
  368. AssertGenOutLinesFile(GetTestFile(fntestoutput),6);
  369. end;
  370. procedure TTestProcess.TestOutputFileExistingTruncate;
  371. // Check that we actually rewrite
  372. begin
  373. CreateInputLinesFile(GetTestFile(fntestoutput),6);
  374. AssertGenOutLinesFile(GetTestFile(fntestoutput),6);
  375. Proc.Executable:=GetHelper(genout);
  376. Proc.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
  377. Proc.OutputDescriptor.FileWriteMode:=fwmTruncate;
  378. Proc.Execute;
  379. AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
  380. end;
  381. procedure TTestProcess.TestOutputFileExistingAtStart;
  382. // Check that we actually write at start of file...
  383. // Write file with 6 lines (1-6), overwrite files with first 3 lines 7-9
  384. // Result has 7 - 8 - 9 - 4 - 5 -6
  385. var
  386. L : TStrings;
  387. I : Integer;
  388. begin
  389. CreateInputLinesFile(GetTestFile(fntestoutput),6);
  390. Proc.Executable:=GetHelper(genout);
  391. Proc.Parameters.add('3');
  392. Proc.Parameters.add('6'); // Offset 6, so first output line is 7
  393. Proc.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
  394. Proc.OutputDescriptor.FileWriteMode:=fwmAtStart;
  395. Proc.Execute;
  396. sleep(100);
  397. // Writeln('Testing file >>',aFileName,'<<');
  398. L:=TStringList.Create;
  399. try
  400. L.LoadFromFile(GetTestFile(fntestoutput));
  401. AssertEquals('Count',6,L.Count);
  402. For I:=1 to 3 do
  403. AssertEquals('Content Line '+IntToStr(I),'Line '+IntToStr(I+6),L[I-1]);
  404. For I:=4 to 6 do
  405. AssertEquals('Content Line '+IntToStr(I),'Line '+IntToStr(I),L[I-1]);
  406. finally
  407. L.Free;
  408. end;
  409. end;
  410. procedure TTestProcess.TestPipeOut;
  411. { Simulate
  412. genout | docat
  413. we read output of docat.
  414. }
  415. var
  416. S : String;
  417. begin
  418. Proc.Executable:=GetHelper(genout);
  419. Proc2.Executable:=GetHelper(docat);
  420. Proc2.OutputDescriptor.IOType:=iotPipe;
  421. Proc.OutputDescriptor.Process:=Proc2;
  422. AssertTrue('Proc2 input is pipe',Proc2.InputDescriptor.IOType=iotPipe);
  423. Proc2.Execute;
  424. Proc.execute;
  425. S:=ReadProcessOutput(Proc2);
  426. AssertGenOutLines(S,3);
  427. end;
  428. procedure TTestProcess.TestPipeOutToFile;
  429. { Simulate
  430. genout | docat > file
  431. we read output from file
  432. }
  433. var
  434. S : String;
  435. begin
  436. Proc.Executable:=GetHelper(genout);
  437. Proc2.Executable:=GetHelper(docat);
  438. Proc2.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
  439. Proc.OutputDescriptor.Process:=Proc2;
  440. AssertTrue('Proc2 input is pipe',Proc2.InputDescriptor.IOType=iotPipe);
  441. Proc2.Execute;
  442. Proc.execute;
  443. AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
  444. end;
  445. procedure TTestProcess.TestPipeInOutToFile;
  446. { Simulate
  447. docat <input | docat > file
  448. we read output from file
  449. }
  450. var
  451. S : String;
  452. begin
  453. CreateInputLinesFile(GetTestFile(fntestinput),3);
  454. Proc.Executable:=GetHelper(docat);
  455. Proc.InputDescriptor.FileName:=GetTestFile(fntestinput);
  456. Proc2.Executable:=GetHelper(docat);
  457. Proc2.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
  458. Proc.OutputDescriptor.Process:=Proc2;
  459. AssertTrue('Proc2 input is pipe',Proc2.InputDescriptor.IOType=iotPipe);
  460. Proc2.Execute;
  461. Proc.execute;
  462. AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
  463. end;
  464. procedure TTestProcess.TestPipeRestart;
  465. begin
  466. end;
  467. function TTestProcess.GetTestFile(const aName: string) : String;
  468. begin
  469. if TmpDir='' then
  470. TmpDir:=GetTempDir(False);
  471. Result:=IncludeTrailingPathDelimiter(TmpDir)+aName;
  472. end;
  473. function TTestProcess.GetHelper(const aHelper: string) : String;
  474. begin
  475. if TestDir='' then
  476. TestDir:=ExtractFilePath(ParamStr(0));
  477. Result:=IncludeTrailingPathDelimiter(TestDir)+aHelper;
  478. {$IFDEF WINDOWS}
  479. Result:=Result+'.exe';
  480. {$ENDIF}
  481. end;
  482. procedure TTestProcess.CheckHelper(const aHelper: string);
  483. var
  484. F : String;
  485. begin
  486. F:=GetHelper(aHelper);
  487. AssertTrue('No helper '+F+' please compile '+aHelper+'.pp',FileExists(F));
  488. end;
  489. procedure TTestProcess.SetUp;
  490. begin
  491. FProc:=TProcess.Create(Nil);
  492. FProc2:=TProcess.Create(Nil);
  493. FProc3:=TProcess.Create(Nil);
  494. // CheckHelper(dols);
  495. CheckHelper(genout);
  496. CheckHelper(docat);
  497. CheckHelper(dotouch);
  498. CheckHelper(doexit);
  499. DeleteFile(fntouch);
  500. DeleteFile(GetTestFile(fntouch));
  501. DeleteFile(GetTestFile(fntestoutput));
  502. end;
  503. procedure TTestProcess.TearDown;
  504. begin
  505. FreeAndNil(FProc);
  506. end;
  507. initialization
  508. RegisterTest(TTestProcess);
  509. end.