unit IdTestIOHandler;

interface

uses
  IdTest,
  IdIOHandler,
  IdObjs,
  IdStream,
  IdIntercept,
  IdExceptionCore,
  IdResourceStringsCore,
  IdGlobal,
  IdSys;

type

  //reads and writes to its own inputbuffer, makes easy to test
  TIdLoopbackIOHandler = class(TIdIOHandler)
  protected
    function ReadFromSource(
     ARaiseExceptionIfDisconnected: Boolean;
     ATimeout: Integer;
     ARaiseExceptionOnTimeout: Boolean
     ): Integer; override;
  public
    procedure CheckForDataOnSource(ATimeout:Integer); override;
    procedure CheckForDisconnect(
     ARaiseExceptionIfDisconnected: Boolean;
     AIgnoreBuffer: Boolean
     ); override;
    procedure WriteDirect(var ABuffer: TIdBytes); override;
  end;

  TIdExampleIntercept = class(TIdConnectionIntercept)
  public
    procedure Send(var ABuffer: TIdBytes); override;
  end;

  TIdTestIOHandler = class(TIdTest)
  private
    procedure RunStream(const io:TIdIOHandler;const aLarge:Boolean);
  published
    procedure TestReadWrite;
    procedure TestReadLn;
    procedure TestStreamSize;
    procedure TestIntercept;
    procedure TestWriteBuffered;
    procedure TestBuffered;
  end;

implementation

const
  cStr='abc123';
  //for intercept test
  cPlain='abcdef';
  cEncoded='123456';

procedure TIdLoopbackIOHandler.CheckForDataOnSource(ATimeout: Integer);
begin
 // inherited;
end;

procedure TIdLoopbackIOHandler.CheckForDisconnect;
begin
 // inherited;
end;

function TIdLoopbackIOHandler.ReadFromSource(
  ARaiseExceptionIfDisconnected: Boolean; ATimeout: Integer;
  ARaiseExceptionOnTimeout: Boolean): Integer;
begin
 //always report a timeout happened, ie there's no more data expected
 Result:=-1;
 EIdReadTimeout.IfTrue(ARaiseExceptionOnTimeout and (Result = -1), RSReadTimeout);

end;

procedure TIdLoopbackIOHandler.WriteDirect(var ABuffer: TIdBytes);
begin
  inherited;
  FInputBuffer.Write(abuffer);
end;

procedure TIdTestIOHandler.RunStream(const io: TIdIOHandler;const aLarge:Boolean);
//given iohandler in large/smallfile mode, check read/write ok
var
 aStream:TIdStringStream;
 aStr:string;
begin
 io.InputBuffer.Clear;
 io.LargeStream:=aLarge;
 aStream:=TIdStringStream.Create(cStr);
 try
 //write the stream to the iohandler
 io.Write(aStream,0,True);
 //buffer should contain length+data. stream unchanged.
 Assert(io.InputBuffer.Size>0);
 Assert(aStream.DataString=cStr);

 //reset and read
 aStream.Size:=0;
 io.ReadStream(aStream,-1);
 aStream.Position:=0;
 aStr:=aStream.DataString;
 Assert(aStr=cStr);

 finally
 Sys.FreeAndNil(aStream);
 end;

end;

procedure TIdTestIOHandler.TestBuffered;
var
  io:TIdLoopbackIOHandler;
begin
  io:=TIdLoopbackIOHandler.Create(nil);
  try
    io.Open;
    io.WriteBufferOpen(2);

    //check that writes less than buffer size arent sent
    io.Write('1');
    Assert(io.InputBufferAsString='');

    //write >1 buffersize of data, check all is sent
    io.Write('2345');
    Assert(io.InputBufferAsString='1234');
  finally
    Sys.FreeAndNil(io);
  end;

end;

procedure TIdTestIOHandler.TestIntercept;
var
  io:TIdLoopbackIOHandler;
  aStr:string;
  aIntercept:TIdExampleIntercept;
begin
  io:=TIdLoopbackIOHandler.Create(nil);
  aIntercept:=TIdExampleIntercept.Create;
  try
    io.Intercept:=aIntercept;
    io.Open;

    //check that the intercept actually changes data that gets written
    io.Write(cPlain);
    aStr:=io.InputBuffer.AsString;
    Assert(aStr=cEncoded,aStr);

    //todo test nested intercepts
  finally
    Sys.FreeAndNil(io);
    Sys.FreeAndNil(aIntercept);
  end;

end;

procedure TIdTestIOHandler.TestReadLn;
//tests that read timeout happens
var
  io:TIdLoopbackIOHandler;
  aStr:string;
const
  cPart='U';
begin
  io:=TIdLoopbackIOHandler.Create(nil);
  try
    io.Open;

    //check with no data in buffer
    aStr:=io.Readln;
    Assert(aStr = '');

    //check with some data in buffer
    io.Write(cPart);
    aStr:=io.Readln;
    Assert(aStr = '');

    //check that buffer still contains expected data
    Assert(io.FInputBuffer.Size = 1, Sys.IntToStr(io.FInputBuffer.Size));

    //complete the data line
    io.WriteLn('');
    aStr:=io.Readln;
    Assert(aStr = cPart);
    Assert(io.FInputBuffer.Size = 0);

  finally
    Sys.FreeAndNil(io);
  end;

end;

procedure TIdTestIOHandler.TestReadWrite;
//use specific cast variables in the write calls to ensure correct
//overload is called
var
 io:TIdLoopbackIOHandler;
 aInt:Integer;
 aInt64:Int64;
 aSmall:Smallint;
 aStr:string;
 aCardinal:Cardinal;
 aChar:Char;
const
 cInt=High(Integer)-1;
 cCard=Cardinal(4000000000);
 cInt64=Low(Int64)+1;
 cSmall=High(Smallint)-1;
 cChar='x';
begin
 io:=TIdLoopbackIOHandler.Create(nil);
 try
 io.Open;
 //should check with/without conversion parameter
 //should also check edge cases, eg high/low

 //test with no data in buffer
 //aChar:=io.ReadChar;
 //Assert(aChar=#0);

 //integer types
 Assert(io.FInputBuffer.Size=0);
 aSmall:=cSmall;
 io.Write(aSmall,True);
 Assert(io.FInputBuffer.Size>0);
 aSmall:=io.ReadSmallInt(True);
 Assert(aSmall=cSmall);
 Assert(io.FInputBuffer.Size=0);

 aInt:=cInt;
 io.Write(aInt,True);
 aInt:=io.ReadInteger(True);
 Assert(aInt=cInt);
 Assert(io.FInputBuffer.Size=0);

 aCardinal:=cCard;
 io.Write(aCardinal,True);
 aCardinal:=io.ReadCardinal(True);
 Assert(aCardinal=cCard);
 Assert(io.FInputBuffer.Size=0);

 aInt64:=cInt64;
 io.Write(aInt64,True);
 aInt64:=io.ReadInt64(True);
 Assert(aInt64=cInt64);
 Assert(io.FInputBuffer.Size=0);

 //string types

 aChar:=cChar;
 io.Write(aChar);
 aChar:=io.ReadChar;
 Assert(aChar=cChar);
 Assert(io.FInputBuffer.Size=0);

 //test normal readln
 aStr:=cStr;
 io.WriteLn(aStr);
 Assert(io.FInputBuffer.Size>0);
 aStr:=io.Readln;
 Assert(aStr=cStr,aStr);
 Assert(io.FInputBuffer.Size=0);

 finally
 Sys.FreeAndNil(io);
 end;
end;

procedure TIdTestIOHandler.TestStreamSize;
var
 io:TIdLoopbackIOHandler;
begin
 io:=TIdLoopbackIOHandler.Create(nil);
 try
 io.Open;

 //integer-size stream
 RunStream(io,False);
 //int64 streams
 RunStream(io,True);

 finally
 Sys.FreeAndNil(io);
 end;

end;

procedure TIdExampleIntercept.Send(var ABuffer: TIdBytes);
begin
  inherited;
  ABuffer:=ToBytes(cEncoded);
end;

procedure TIdTestIOHandler.TestWriteBuffered;
var
  io:TIdLoopbackIOHandler;
  aStr:string;
  aIntercept:TIdExampleIntercept;
begin
  io:=TIdLoopbackIOHandler.Create(nil);
  aIntercept:=TIdExampleIntercept.Create;
  try
    io.Open;

    Assert(not io.WriteBufferingActive);
    io.WriteBufferOpen;
    Assert(io.WriteBufferingActive);

    //write some data. it should not be sent
    io.Write(cStr);
    aStr:=io.InputBuffer.AsString;
    Assert(aStr='');

    //flush the buffer. data should now be sent
    io.WriteBufferFlush;
    aStr:=io.InputBuffer.AsString;
    Assert(aStr=cStr);

    io.WriteBufferClose;

    //todo test threshhold. currently not implemented

    //leave the writebuffer open, to check for memory leaks
    io.WriteBufferOpen;
    //if writebuffer has contents when iohandler closed, should it
    //be flushed by iohandler?
  finally
    Sys.FreeAndNil(io);
    Sys.FreeAndNil(aIntercept);
  end;
end;

initialization

  TIdTest.RegisterTest(TIdTestIOHandler);

end.
