unit IdTest;

{
minimal/example test runner for .net/mono
feel free to improve this code
for example, better format for the output
}

interface

uses
  System.Reflection,
  System.Threading,
  IdObjs,
  IdBaseComponent;

type

  TIdTest = class;
  TIdTestClass = class of TIdTest;

  TOutputStringProcedure = procedure(const AString: string);

  TIdTest = class(TIdBaseComponent)
  private
    FOnOutputString: TOutputStringProcedure;
  protected
    procedure SetUp; virtual;
    procedure TearDown; virtual;
  public
    procedure OutputLn(const ALine: string);
    class procedure RegisterTest(const aClass:TIdTestClass);
    class function TestList:TIdList;
    property OnOutputString: TOutputStringProcedure read FOnOutputString write FOnOutputString;
  end;

  TIdBasicRunner = class(TObject)
  private
    FLockObj: &Object;
    FDebugInfo: Boolean;
    procedure WriteLn(const aStr:string);
    procedure WriteOutput(const AString: string);
    procedure RecordPass(const aTest:TIdTest;const aMethod:string);
    procedure RecordFail(const aTest:TIdTest;const aMethod:string;const e:exception);
    procedure WriteString(const AString: string);
  public
    PassCount:integer;
    FailCount:integer;
    constructor Create;
    procedure Execute;
  end;

implementation

var
 // this should really be a classlist
 FRegisterList:TIdList;

class procedure TIdTest.RegisterTest(const aClass: TIdTestClass);
begin
 TestList.Add(aClass.Create);
end;

class function TIdTest.TestList: TIdList;
begin
  if FRegisterList=nil then
  begin
    FRegisterList:=TIdList.Create;
  end;
  Result:=FRegisterList;
end;

procedure TIdTest.OutputLn(const ALine: string);
begin
  if Assigned(FOnOutputString) then
  begin
    FOnOutputString(ALine + Environment.NewLine);
  end;
end;

{ TIdBasicRunner }

constructor TIdBasicRunner.Create;
  function ShouldOutputDebuggingInfo: Boolean;
  var
    I: Integer;
  begin
    Result := False;
    for I := 0 to High(Environment.GetCommandLineArgs) do
      Result := Result or (Environment.GetCommandLineArgs[i].ToLower = '/debug');
  end;
begin
  inherited;
  FLockObj := &Object.Create;
  FDebugInfo := ShouldOutputDebuggingInfo;
end;

procedure TIdBasicRunner.WriteOutput(const AString: string);
begin
  WriteString('INFO: ' + AString);
end;

procedure TIdBasicRunner.WriteString(const AString: string);
begin
  if FDebugInfo then
  begin
    Monitor.Enter(FLockObj);
    try
      Console.Write('    ' + AString);
    finally
      Monitor.Exit(FLockObj);
    end;
  end;
end;

procedure TIdBasicRunner.Execute;
var
  aMethods:array of methodinfo;
  aMethodCount:integer;
  aTestCount:integer;
  aMethod:methodinfo;
  aTest:TIdTest;
begin

  PassCount:=0;
  FailCount:=0;

  for aTestCount:=0 to TIdTest.TestList.Count-1 do
  begin
    aTest:=TIdTest.TestList[aTestCount] as TIdTest; //aClass.Create();
    aTest.OnOutputString := WriteOutput;
    aMethods:=aTest.GetType.GetMethods;

    WriteLn('Test:'+aTest.classname);

    for aMethodCount:=low(aMethods) to high(aMethods) do
    begin
      aMethod:=aMethods[aMethodCount];
      if not aMethod.Name.StartsWith('Test') then continue;

      try
        WriteLn('  ' + aMethod.Name);
        aTest.SetUp;
        try
        aMethod.Invoke(aTest,[]);
        finally
        aTest.TearDown;
        end;
        //commented out, makes easier to see the fails
        RecordPass(aTest,aMethod.name);
      except
        on e:exception do
        begin
          RecordFail(aTest,aMethod.name,e);
        end;
      end;

    end; //methods

  end; //tests

  WriteLn('Results: Pass='+PassCount.ToString+', Fail='+FailCount.ToString);

end;

procedure TIdBasicRunner.RecordPass(const aTest: TIdTest;
  const aMethod: string);
begin
  inc(PassCount);
  WriteString('Passed' + Environment.NewLine);
end;

procedure TIdBasicRunner.RecordFail(const aTest: TIdTest; const aMethod: string;
  const e: exception);
var
  ie:TargetInvocationException;
begin
  inc(failcount);
  WriteLn('  Fail:');

  //this exception is raised as we are calling methods using reflection
  if e is TargetInvocationException then
  begin
    ie:=e as TargetInvocationException;
    WriteLn('    '+ie.InnerException.classname+':'+ie.InnerException.Message);
    WriteLn(ie.InnerException.StackTrace);
  end else begin
    WriteLn('    '+e.classname);
  end;
end;

procedure TIdBasicRunner.WriteLn(const aStr: string);
begin
  Monitor.Enter(FLockObj);
  try
    Console.WriteLine(AStr);
  finally
    Monitor.Exit(FLockObj);
  end;
end;

procedure TIdTest.SetUp;
begin
end;

procedure TIdTest.TearDown;
begin
end;

end.
