Browse Source

* Fixes and tests

Michaël Van Canneyt 1 year ago
parent
commit
1fdc6ef6a7

+ 40 - 27
packages/fcl-process/src/processbody.inc

@@ -40,7 +40,7 @@ Type
    TProcessStringList = TStringList;
    {$endif}
 
-   TFileWriteMode = (fwmAtstart, fwmTruncate, fwmAppend);
+   TFileWriteMode = (fwmTruncate, fwmAppend, fwmAtstart);
 
    TIODescriptor = class(TPersistent)
    private
@@ -74,17 +74,20 @@ Type
      Function CreateProcessHandle : THandle;
      Function ResolveProcessHandle : THandle;
      Function ResolveStream : THandleStream;
-     Function OurHandle : THandle;
      Procedure CloseOurHandle;
      Procedure CloseTheirHandle;
      Procedure PrepareHandles;
+     Procedure ResetHandles;
      Property OwnerProcess : TProcess Read FOwnerProcess;
      Property PipeBufferSize : cardinal read FPipeBufferSize write FPipeBufferSize;
+     Property OurHandle: THandle Read FOurHandle;
+     Property HandleValid : Boolean Read FHandleValid;
    public
      Constructor Create(aOwnerProcess : TProcess; aType : TProcessHandleType);
      Destructor Destroy; override;
      Property ProcessHandleType : TProcessHandleType Read FHandleType;
      Property CustomHandle : THandle Read FCustomHandle Write FCustomHandle;
+
    Published
      Property IOType : TIOType Read FIOType Write SetIOType;
      Property FileName : TFileName Read FFileName Write SetFileName;
@@ -162,6 +165,7 @@ Type
     FThreadHandle : Thandle;
     procedure CloseProcessHandles; virtual;
     procedure Loaded; override;
+    Procedure SysExecute; virtual;
   Public
     Constructor Create (AOwner : TComponent);override;
     Destructor Destroy; override;
@@ -404,6 +408,18 @@ begin
     ConvertCommandLine;
 end;
 
+Procedure TProcess.Execute;
+
+Var
+  HT : TProcessHandleType;
+
+begin
+  for HT in TProcessHandleType do
+    FDescriptors[HT].ResetHandles;
+  SysExecute;
+end;
+
+
 procedure TProcess.CloseInput;
 begin
   FDescriptors[phtInput].CloseOurHandle;
@@ -896,6 +912,10 @@ begin
   FProcess:=AValue;
   if Assigned(FOwnerProcess) and Assigned(FProcess) then
     FProcess.RemoveComponent(FOwnerProcess);
+  if Self.ProcessHandleType=phtInput then
+    FProcess.OutputDescriptor.IOType:=iotPipe
+  else
+    FProcess.InputDescriptor.IOType:=iotPipe;
 end;
 
 
@@ -973,31 +993,20 @@ Function TIODescriptor.CreateProcessHandle : THandle;
 begin
   if Not Assigned(Process) then
     Raise EProcess.Create('Cannot get handle. No process assigned');
-  if not Process.Running then
-    Raise EProcess.Create('Cannot get handle. Process not active');
   case ProcessHandleType of
     phtInput:  Result:=Process.OutputDescriptor.OurHandle;
     phtOutput:  Result:=Process.InputDescriptor.OurHandle;
     phtError:  Result:=Process.InputDescriptor.OurHandle;
   end;
-end;
-
-function TIODescriptor.OurHandle: THandle;
-begin
-  Case IOType of
-    iotNone : Result:=CreateStandardHandle;
-    iotProcess : Result:=Process.ProcessHandle;
-    iotPipe : Result:=FOurHandle;
-    iotFile : Result:=CreateProcessHandle;
-    iotHandle : Result:=CreateProcessHandle;
-  end;
+  if Result=THandle(INVALID_HANDLE_VALUE) then
+    Raise EProcess.Create('Cannot get handle. Process not active');
 end;
 
 function TIODescriptor.ResolveStream: THandleStream;
 begin
   if (FStream=Nil) and (FHandleValid) and (IOType=iotPipe) then
     begin
-    Writeln(ProcessHandleType,' creating stream for stream ',IOType,': ',OurHandle);
+    // Writeln(ProcessHandleType,' creating stream for stream ',IOType,': ',OurHandle);
     Case FHandleType of
       phtInput : FStream:=TOutputPipeStream.Create(OurHandle);
       phtError,
@@ -1007,6 +1016,7 @@ begin
   Result:=FStream;
 end;
 
+
 procedure TIODescriptor.CloseOurHandle;
 
 var
@@ -1015,15 +1025,8 @@ var
 begin
   if Not FHandleValid then
      exit;
-  H:=THandle(INVALID_HANDLE_VALUE);
-  Writeln(StdErr, GetProcessID ,' : ',ProcessHandleType,' closing our handle ',IOType,': ',FOurHandle);
-  Case IOType of
-    iotNone : ;
-    iotProcess : H:=OurHandle;
-    iotPipe : H:=OurHandle;
-    iotFile : H:=FTheirHandle;
-    iotHandle : H:=FTheirHandle;
-  end;
+  H:=OurHandle;
+  // Writeln(StdErr, GetProcessID ,' : ',ProcessHandleType,' closing our handle ',IOType,': ',FOurHandle);
   if H<>THandle(INVALID_HANDLE_VALUE) then
     FileClose(H);
   FOurHandle:=THandle(INVALID_HANDLE_VALUE) ;
@@ -1037,7 +1040,7 @@ begin
   if (IOType=iotNone) or Not FHandleValid then
      exit;
   H:=ResolveProcessHandle;
-  Writeln(StdErr,GetProcessID,' : ',ProcessHandleType,' closing their handle ',IOType,': ',H);
+  // Writeln(StdErr,GetProcessID,' : ',ProcessHandleType,' closing their handle ',IOType,': ',H);
   if H<>THandle(INVALID_HANDLE_VALUE) then
     FileClose(H);
 end;
@@ -1051,11 +1054,20 @@ var
 begin
   WriteStr(S,IOType);
   H:=ResolveProcessHandle;
-  Writeln('PReparing handle ',S,' : ',H,' (ours: ',OurHandle,')');
+  // Writeln('PReparing handle ',S,' : ',H,' (ours: ',OurHandle,')');
   if H=THandle(INVALID_HANDLE_VALUE) then
      Raise EProcess.CreateFmt('Failed to prepare process handle for %s',[S]);
 end;
 
+procedure TIODescriptor.ResetHandles;
+
+begin
+  CloseOurHandle;
+  CloseTheirHandle;
+  FreeAndNil(FStream);
+  FHandleValid:=False;
+end;
+
 
 
 function TIODescriptor.ResolveProcessHandle: THandle;
@@ -1066,6 +1078,7 @@ var
 begin
   if not FHandleValid then
     begin
+    FOurHandle:=THAndle(INVALID_HANDLE_VALUE);
     Case IOType of
       iotNone : H:=CreateStandardHandle;
       iotPipe : H:=CreatePipeHandle;

+ 3 - 2
packages/fcl-process/src/unix/process.inc

@@ -335,7 +335,7 @@ begin
   until (safefpdup2<>-1) or (fpgeterrno<>ESysEINTR);
 end;
 
-Procedure TProcess.Execute;
+Procedure TProcess.SysExecute;
 
 Var
   PID      : Longint;
@@ -459,7 +459,7 @@ begin
         FreePCharList(FEnv);
     end;
   Finally
-    Writeln(system.StdErr,'fork closing our handles');
+    // Writeln(system.StdErr,'fork closing our handles');
     FDescriptors[phtInput].CloseTheirHandle;
     FDescriptors[phtOutput].CloseTheirHandle;
     FDescriptors[phtError].CloseTheirHandle;
@@ -529,6 +529,7 @@ begin
     Result:=1;
 end;
 
+
 Function TProcess.Resume : LongInt;
 
 begin

+ 1 - 1
packages/fcl-process/src/win/process.inc

@@ -231,7 +231,7 @@ begin
 end;
 
 
-Procedure TProcess.Execute;
+Procedure TProcess.SysExecute;
 Var
   i : Integer;
   WName,WDir,WCommandLine : UnicodeString;

+ 61 - 0
packages/fcl-process/tests/docat.lpi

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="docat"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="docat.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="docat"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ConfigFile>
+        <WriteConfigFilePath Value=""/>
+      </ConfigFile>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 29 - 0
packages/fcl-process/tests/docat.pp

@@ -0,0 +1,29 @@
+program docat;
+
+Procedure ReadAndWrite(var O : Text);
+
+var
+  S : AnsiString;
+
+begin
+  While not EOF(O) do
+    begin
+    Readln(O,S);
+    Writeln(S);
+    end;
+end;
+
+var
+  F : Text;
+
+begin
+  if ParamStr(1)<>'' then
+    begin
+    Assign(F,ParamStr(1));
+    Reset(F);
+    ReadAndWrite(F);
+    end
+  else
+    ReadAndWrite(INput);
+end.
+

+ 61 - 0
packages/fcl-process/tests/dols.lpi

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="dols"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="dols.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="dols"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ConfigFile>
+        <WriteConfigFilePath Value=""/>
+      </ConfigFile>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 38 - 0
packages/fcl-process/tests/dols.pp

@@ -0,0 +1,38 @@
+program dols;
+
+uses sysutils;
+
+var
+  Idx,Count : integer;
+  Dir : String;
+  Info : TSearchRec;
+  Long : Boolean;
+
+
+begin
+  Dir:=GetCurrentDir;
+  Idx:=1;
+  if ParamStr(Idx)='-l' then
+    begin
+    Inc(Idx);
+    Long:=True;
+    end;
+  if ParamStr(Idx)<>'' then
+    Dir:=ParamStr(Idx);
+  Dir:=IncludeTrailingPathDelimiter(Dir);
+  Count:=0;
+  If FindFirst(Dir+AllFilesMask,faAnyFile,Info)=0 then
+    try
+      Repeat
+        if Long then
+          Write(Info.Size:14,' ',DateTimeToStr(Info.TimeStamp));
+        Writeln(Info.Name);
+        Inc(Count);
+      Until FindNext(Info)<>0;
+
+    finally
+      FindClose(Info);
+    end;
+  Writeln('Total: ',Count);
+end.
+

+ 61 - 0
packages/fcl-process/tests/dotouch.lpi

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="dotouch"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="dotouch.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="dotouch"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ConfigFile>
+        <WriteConfigFilePath Value=""/>
+      </ConfigFile>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 17 - 0
packages/fcl-process/tests/dotouch.pp

@@ -0,0 +1,17 @@
+program dotouch;
+
+
+var
+  F : Text;
+  N : String;
+
+begin
+  N:=ParamStr(1);
+  if N='' then
+    N:='touch.txt';
+  Assign(F,N);
+  Rewrite(F);
+  Writeln(F,N);
+  Close(F);
+end.
+

+ 61 - 0
packages/fcl-process/tests/genout.lpi

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="genout"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="genout.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="genout"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ConfigFile>
+        <WriteConfigFilePath Value=""/>
+      </ConfigFile>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 24 - 0
packages/fcl-process/tests/genout.pp

@@ -0,0 +1,24 @@
+program genout;
+
+uses sysutils;
+
+var
+  I,aOffset, aCount : Integer;
+  UseStdErr : Boolean;
+
+begin
+  // number of lines to emit. If negative, use stderr
+  aCount:=StrToIntDef(ParamStr(1),3);
+  // Offset : start at 1+Offset
+  aOffset:=StrToIntDef(ParamStr(2),0);
+  UseStdErr:=aCount<0;
+  aCount:=Abs(aCount);
+  aCount:=aCount+aOffset;
+  Inc(aOffset);
+  For I:=aOffset to aCount do
+    if UseStdErr then
+      Writeln(StdErr,'Line ',IntToStr(I))
+    else
+      Writeln('Line ',IntToStr(I));
+end.
+

+ 76 - 0
packages/fcl-process/tests/testprocess.lpi

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="testprocess"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <RequiredPackages>
+      <Item>
+        <PackageName Value="FCL"/>
+      </Item>
+    </RequiredPackages>
+    <Units>
+      <Unit>
+        <Filename Value="testprocess.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="utcprocess.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="testprocess"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir);../src/unix"/>
+      <OtherUnitFiles Value="../src"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsDwarf3"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <ConfigFile>
+        <WriteConfigFilePath Value=""/>
+      </ConfigFile>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 28 - 0
packages/fcl-process/tests/testprocess.pp

@@ -0,0 +1,28 @@
+program testprocess;
+
+{$mode objfpc}{$H+}
+
+uses
+  Classes, consoletestrunner, utcprocess;
+
+type
+
+  { TMyTestRunner }
+
+  TMyTestRunner = class(TTestRunner)
+  protected
+  // override the protected methods of TTestRunner to customize its behavior
+  end;
+
+var
+  Application: TMyTestRunner;
+
+begin
+  DefaultRunAllTests:=True;
+  DefaultFormat:=fPlain;
+  Application := TMyTestRunner.Create(nil);
+  Application.Initialize;
+  Application.Title := 'FPCUnit Console test runner';
+  Application.Run;
+  Application.Free;
+end.

+ 432 - 0
packages/fcl-process/tests/utcprocess.pp

@@ -0,0 +1,432 @@
+unit utcprocess;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testutils, testregistry, pipes, process;
+
+type
+
+  { TTestProcess }
+
+  TTestProcess= class(TTestCase)
+  private
+    FProc: TProcess;
+    FProc2: TProcess;
+    FProc3: TProcess;
+    procedure AssertFileContent(const aFileName, aContent: String);
+    procedure AssertFileContent(const aFileName: String; aContent: array of string);
+    procedure AssertGenOutLines(const S: String; aCount: integer);
+    procedure AssertGenOutLinesFile(const aFileName : string; aCount : Integer);
+    procedure CreateInputLinesFile(const aFileName : string; aCount : Integer);
+    function GetHelper(const aHelper: string): String;
+    function GetTestFile(const aName: string): String;
+    function ReadProcessOutput(aProc: TProcess; ReadStdErr : Boolean = False): string;
+    procedure WaitForFile(const aFileName: String);
+  protected
+    procedure CheckHelper(const aHelper : string);
+    procedure SetUp; override;
+    procedure TearDown; override;
+    property Proc : TProcess read FProc;
+    property Proc2 : TProcess read FProc2;
+    property Proc3 : TProcess read FProc3;
+  published
+    procedure TestHookUp;
+    procedure TestSimple;
+    procedure TestSimpleParam;
+    Procedure TestPipes;
+    Procedure TestWritePipes;
+    Procedure TestStdErr;
+    Procedure TestStdErrToOutput;
+    Procedure TestInputFile;
+    Procedure TestOutputFile;
+    Procedure TestStdErrFile;
+    Procedure TestPipeOut;
+    Procedure TestPipeOutToFile;
+    Procedure TestPipeInOutToFile;
+  end;
+
+implementation
+
+const
+  dotouch = 'dotouch';
+  docat = 'docat';
+  dols = 'dols';
+  genout = 'genout';
+  fntouch = 'touch.txt';
+  fntestoutput = 'output.txt';
+  fntestinput = 'input.txt';
+
+var
+  TestDir : String;
+  TmpDir : String;
+
+procedure TTestProcess.AssertFileContent(const aFileName,aContent : String);
+begin
+  AssertFileContent(aFileName,[aContent]);
+end;
+
+procedure TTestProcess.AssertFileContent(const aFileName : String; aContent : Array of string);
+
+var
+  L : TStrings;
+  I : integer;
+
+begin
+  L:=TStringList.Create;
+  try
+    L.LoadFromFile(aFileName);
+    AssertEquals('Line count',Length(aContent),L.Count);
+    for I:=0 to L.Count-1 do
+      AssertEquals('Line '+Inttostr(i)+'content',aContent[I],L[i]);
+  finally
+    L.Free;
+  end;
+
+end;
+
+Procedure TTestProcess.WaitForFile(const aFileName : String);
+
+var
+  aCount : Integer;
+  FN : String;
+  Exists : boolean;
+
+begin
+  FN:=aFileName;
+  aCount:=0;
+  Repeat
+    Sleep(20);
+    Inc(aCount);
+    Exists:=FileExists(FN);
+  Until (aCount>=50) or Exists;
+  AssertTrue('File did not appear: '+FN,Exists);
+  Sleep(20);
+end;
+
+procedure TTestProcess.TestHookUp;
+
+  procedure AssertNoFile(const FN :string);
+  begin
+    AssertFalse('File '+FN+' does not exist',FileExists(FN));
+  end;
+
+begin
+  AssertNotNull('Have process 1',Proc);
+  AssertNotNull('Have process 2',Proc2);
+  AssertNotNull('Have process 3',Proc3);
+  AssertNoFile(fntouch);
+  AssertNoFile(GetTestFile(fnTouch));
+  AssertNoFile(GetTestFile(fntestoutput));
+end;
+
+procedure TTestProcess.TestSimple;
+
+begin
+  Proc.Executable:=GetHelper(dotouch);
+  Proc.Execute;
+  AssertNull('no input stream',Proc.Input);
+  AssertNull('no output stream',Proc.Output);
+  AssertNull('no error stream',Proc.Stderr);
+  WaitForFile(fntouch);
+  AssertFileContent(fntouch,fntouch);
+end;
+
+procedure TTestProcess.TestSimpleParam;
+
+var
+  FN : String;
+begin
+  FN:=GetTestFile(fntouch);
+  Proc.Executable:=GetHelper(dotouch);
+  Proc.Parameters.Add(FN);
+  Proc.Execute;
+  WaitForFile(FN);
+  AssertFileContent(FN,FN);
+end;
+
+procedure TTestProcess.AssertGenOutLines(const S : String; aCount : integer);
+
+var
+  L : TStrings;
+  I : Integer;
+
+begin
+  sleep(100);
+//  Writeln('Testing >>',S,'<<');
+  L:=TStringList.Create;
+  try
+    L.Text:=S;
+    AssertEquals('Count',aCount,L.Count);
+    For I:=1 to aCount do
+      AssertEquals('Content Line '+IntToStr(I),'Line '+IntToStr(I),L[I-1]);
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestProcess.AssertGenOutLinesFile(const aFileName: string; aCount: Integer);
+var
+  L : TStrings;
+  I : Integer;
+
+begin
+  sleep(100);
+  // Writeln('Testing file >>',aFileName,'<<');
+  L:=TStringList.Create;
+  try
+    L.LoadFromFile(aFileName);
+    AssertEquals('Count',aCount,L.Count);
+    For I:=1 to aCount do
+      AssertEquals('Content Line '+IntToStr(I),'Line '+IntToStr(I),L[I-1]);
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestProcess.CreateInputLinesFile(const aFileName: string; aCount: Integer);
+var
+  L : TStrings;
+  I : Integer;
+
+begin
+  // Writeln('Creating Test file >>',aFileName,'<<');
+  L:=TStringList.Create;
+  try
+    For I:=1 to aCount do
+      L.Add('Line '+IntToStr(I));
+    L.SaveToFile(aFileName);
+  finally
+    L.Free;
+  end;
+end;
+
+function TTestProcess.ReadProcessOutput(aProc: TProcess; ReadStdErr: Boolean): string;
+
+var
+  aRead,aLen: Integer;
+  S : String;
+  St : TInputPipeStream;
+begin
+  aRead:=0;
+  aLen:=0;
+  S:='';
+  Sleep(100);
+  if ReadStdErr then
+    st:=aProc.StdErr
+  else
+    st:=aProc.Output;
+  AssertNotNull('Have stream to read output from',St);
+  AssertTrue('Read input',aProc.ReadInputStream(St,aRead,aLen,S,100));
+  SetLength(S,aRead);
+//  Writeln('>>>',S,'<<<');
+  Result:=S;
+end;
+
+procedure TTestProcess.TestPipes;
+
+var
+  S : String;
+
+begin
+  Proc.Executable:=GetHelper(genout);
+  Proc.Options:=[poUsePipes];
+  Proc.Execute;
+  AssertNotNull('input stream',Proc.Input);
+  AssertNotNull('output stream',Proc.Output);
+  AssertNotNull('error stream',Proc.Stderr);
+  S:=ReadProcessOutput(Proc);
+  AssertGenOutLines(S,3);
+end;
+
+procedure TTestProcess.TestWritePipes;
+var
+  Sin,Sout : String;
+
+begin
+  Proc.Executable:=GetHelper(docat);
+  Proc.Options:=[poUsePipes];
+  Proc.Execute;
+  // Note: this test will only work for small amounts of data, less than pipe buffer size.
+  Sin:='this is some text'+sLineBreak+'And some more text'+sLineBreak;
+  Proc.Input.Write(Sin[1],Length(Sin));
+  Proc.CloseInput;
+  SOut:=ReadProcessOutput(Proc);
+  AssertEquals('Out equals in',SIn,Sout);
+end;
+
+procedure TTestProcess.TestStdErr;
+var
+  S : String;
+
+begin
+  Proc.Executable:=GetHelper(genout);
+  Proc.Parameters.Add('-3');
+  Proc.Options:=[poUsePipes];
+  Proc.Execute;
+  S:=ReadProcessOutput(Proc,true);
+  AssertGenOutLines(S,3);
+end;
+
+procedure TTestProcess.TestStdErrToOutput;
+var
+  S : String;
+
+begin
+  Proc.Executable:=GetHelper(genout);
+  Proc.Parameters.Add('-3');
+  Proc.Options:=[poUsePipes,poStderrToOutPut];
+  Proc.Execute;
+  S:=ReadProcessOutput(Proc);
+  AssertGenOutLines(S,3);
+end;
+
+procedure TTestProcess.TestInputFile;
+
+var
+  S : String;
+
+begin
+  CreateInputLinesFile(GetTestFile(fntestinput),3);
+  Proc.Executable:=GetHelper(docat);
+  Proc.InputDescriptor.FileName:=GetTestFile(fntestinput);
+  AssertTrue('Descriptor IOType', Proc.InputDescriptor.IOType=iotFile);
+  Proc.OutputDescriptor.IOType:=iotPipe;
+  Proc.Execute;
+  AssertNull('input stream',Proc.Input);
+  AssertNotNull('output stream',Proc.Output);
+  AssertNull('error stream',Proc.Stderr);
+  S:=ReadProcessOutput(Proc);
+  AssertGenOutLines(S,3);
+end;
+
+procedure TTestProcess.TestOutputFile;
+
+begin
+  Proc.Executable:=GetHelper(genout);
+  Proc.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
+  Proc.Execute;
+  AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
+end;
+
+procedure TTestProcess.TestStdErrFile;
+begin
+  Proc.Executable:=GetHelper(genout);
+  Proc.Parameters.Add('-3');
+  Proc.ErrorDescriptor.FileName:=GetTestFile(fntestoutput);
+  Proc.Execute;
+  AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
+end;
+
+procedure TTestProcess.TestPipeOut;
+{ Simulate
+  genout | docat
+  we read output of docat.
+}
+var
+  S : String;
+
+begin
+  Proc.Executable:=GetHelper(genout);
+  Proc2.Executable:=GetHelper(docat);
+  Proc2.OutputDescriptor.IOType:=iotPipe;
+  Proc.OutputDescriptor.Process:=Proc2;
+  AssertTrue('Proc2 input is pipe',Proc2.InputDescriptor.IOType=iotPipe);
+  Proc2.Execute;
+  Proc.execute;
+  S:=ReadProcessOutput(Proc2);
+  AssertGenOutLines(S,3);
+end;
+
+procedure TTestProcess.TestPipeOutToFile;
+
+{ Simulate
+  genout | docat > file
+  we read output from file
+}
+var
+  S : String;
+
+begin
+  Proc.Executable:=GetHelper(genout);
+  Proc2.Executable:=GetHelper(docat);
+  Proc2.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
+  Proc.OutputDescriptor.Process:=Proc2;
+  AssertTrue('Proc2 input is pipe',Proc2.InputDescriptor.IOType=iotPipe);
+  Proc2.Execute;
+  Proc.execute;
+  AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
+end;
+
+procedure TTestProcess.TestPipeInOutToFile;
+{ Simulate
+  docat <input | docat > file
+  we read output from file
+}
+var
+  S : String;
+
+begin
+  CreateInputLinesFile(GetTestFile(fntestinput),3);
+  Proc.Executable:=GetHelper(docat);
+  Proc.InputDescriptor.FileName:=GetTestFile(fntestinput);
+  Proc2.Executable:=GetHelper(docat);
+  Proc2.OutputDescriptor.FileName:=GetTestFile(fntestoutput);
+  Proc.OutputDescriptor.Process:=Proc2;
+  AssertTrue('Proc2 input is pipe',Proc2.InputDescriptor.IOType=iotPipe);
+  Proc2.Execute;
+  Proc.execute;
+  AssertGenOutLinesFile(GetTestFile(fntestoutput),3);
+end;
+
+function TTestProcess.GetTestFile(const aName: string) : String;
+
+begin
+  if TmpDir='' then
+    TmpDir:=GetTempDir(False);
+  Result:=IncludeTrailingPathDelimiter(TmpDir)+aName;
+end;
+
+function TTestProcess.GetHelper(const aHelper: string) : String;
+begin
+  if TestDir='' then
+    TestDir:=ExtractFilePath(ParamStr(0));
+  Result:=IncludeTrailingPathDelimiter(TestDir)+aHelper;
+  {$IFDEF WINDOWS}
+  Result:=Result+'.exe';
+  {$ENDIF}
+end;
+
+procedure TTestProcess.CheckHelper(const aHelper: string);
+var
+  F : String;
+begin
+  F:=GetHelper(aHelper);
+  AssertTrue('No helper '+F+' please compile '+aHelper+'.pp',FileExists(F));
+end;
+
+procedure TTestProcess.SetUp;
+begin
+  FProc:=TProcess.Create(Nil);
+  FProc2:=TProcess.Create(Nil);
+  FProc3:=TProcess.Create(Nil);
+  // CheckHelper(dols);
+  CheckHelper(genout);
+  CheckHelper(docat);
+  CheckHelper(dotouch);
+  DeleteFile(fntouch);
+  DeleteFile(GetTestFile(fntouch));
+  DeleteFile(GetTestFile(fntestoutput));
+end;
+
+procedure TTestProcess.TearDown;
+begin
+  FreeAndNil(FProc);
+end;
+
+initialization
+  RegisterTest(TTestProcess);
+end.
+