unit TestJitClass;

{$mode objfpc}{$H+}
{$ModeSwitch typehelpers}
{$inline off}
{$WARN 4055 off : Conversion between ordinals and pointers is not portable}
interface

uses
  Classes, SysUtils, TypInfo, Math, fpcunit, testutils, testregistry,
  JitClass, JitTypes, LazLogger, Rtti;

type

  TInitProcedure = procedure of object;

  { TPropListTest }

  TPropListTest = class
  private
    FOwner: TTestCase;
    FObject: TObject;
    FPropCount: Integer;
    FPropList: PPropList;
  protected
    function GetPPropInfo(APropName: String): PPropInfo;
  public
    constructor Create(AnOwner: TTestCase; AnObject: TObject);
    destructor Destroy; override;

    procedure AssertPropCount(AName: String; AExpCount: Integer);
    procedure AssertPropOffsets(AName: String='');
    procedure AssertHasProp(AName, APropName: String; AExpType: TTypeKind);
    procedure AssertHasProp(AName, APropName: String; AExpType: TTypeKind; AValue: Int64);
    procedure AssertHasProp(AName, APropName: String; AExpType: TTypeKind; AValue: String);
  end;

  { TJitClassTest }

  TJitClassTest = class(TTestCase)
  private
    FFreedObjList, FFreedMemList: TList;

    procedure DoObjFreed(Sender: TObject);
    procedure AssertWasObjFreed(AName: String; AnObj: TObject);
    procedure AssertWasNotObjFreed(AName: String; AnObj: TObject);

    procedure StartMemMonitor;
    procedure StartAndClearMemMonitor;
    procedure StopMemMonitor;
    procedure ClearMemMonitor;
    procedure AssertWasMemFreed(AName: String; AMem: Pointer);
    procedure AssertWasNotMemFreed(AName: String; AMem: Pointer);

    procedure DumpPropInfo(AClass: TClass);
  private
    // Methods for circle ref testing
    FJitTypeLib: TJitTypeLibrary;
    FJitCreator: array [1..3] of TJitClassCreator;

    function GetCreator(ABase: TClass; AName: String; PropClass: String = '';
      ATakeCreatorOwnerShip: Boolean = False): TJitClassCreator;
    function GetCreator(ABase: TJitClassCreator; AName: String; PropClass: String = '';
      ATakeCreatorOwnerShip: Boolean = False): TJitClassCreator;
    procedure InitTwoClasses;
    procedure InitTwoClassesWithOneSelfRef;
    procedure InitTwoClassesWithDoubleLink;
    procedure InitTwoClassesAnchestor;
    procedure InitTwoClassesAnchestorWithAnchestorProp;
    procedure InitTwoClassesAnchestorWithAnchestorPropOneWay; // not for auto ref count tests // NOT a circle
    procedure InitThreeClasses;
    procedure InitThreeClassesWithOneSelfRef;
    procedure InitThreeClassesWithOneDoubleLink;
    procedure InitThreeClassesWithSubLoop;
    procedure InitThreeClassesWithSubLoopAndOneSelfRef;
    procedure InitThreeClassesWithTwoSubLoop;
    procedure InitThreeClassesChained;
    procedure InitThreeClassesChainedIndirect;
    procedure InitThreeClassesOneAnchestor;
    procedure InitThreeClassesOneAnchestorIndirect;
    procedure InitThreeClassesAnchestorParallel;
    procedure InitThreeClassesAnchestorParallelIndirect; // 5 classes
    procedure InitThreeClassesAnchestorParallelAndChildRef;
    procedure InitThreeClassesAnchestorParallelAndChildRefIndirect;
    procedure InitThreeClassesAnchestorParallelAndChildRefIndirect_2;
    procedure InitThreeClassesAnchestorParallelAndChildLoop;
    procedure InitThreeClassesAnchestorParallelAndChildLoopIndirect;
    procedure InitThreeClassesAnchestorParallelAndChildLoopIndirect_2;
    procedure InitThreeClassesTwoAnchestor;
    procedure InitThreeClassesTwoAnchestorIndirect;

    procedure TestTwoClassRefCount(AnInitProc: TInitProcedure);
    procedure TestThreeClassRefCount(AnInitProc: TInitProcedure);

  protected
    procedure DoStreamCopy(AJitSource, AJitDest: TComponent);
    procedure DoTestSimpleClass(AJitClass, AnExpParentClass: TComponentClass);

    procedure TearDown; override;
  published
    procedure TestSimpleClass;        // Test unmodified Jit
    procedure TestSimpleClassNested;  // Test unmodified Jit, with Jit as base
    procedure TestJitPropSimple;
    procedure TestJitParseClass;
    procedure TestJitPropCircularClassDef;
    procedure TestManagedJitProp;
    procedure TestRefCount;
    procedure TestRefCountProp;
    procedure TestRefCountClassCircle;
    procedure TestRefCountMethodCircle;
    procedure TestParseJitType;   // Parser errors / also run with valgrind
    procedure TestSetEnum;
    procedure TestMethods;
  end;

implementation

var
  MMgr: TMemoryManager;
  HeapState: TFPCHeapStatus;
  GlobFreedMemList: TList;
  InMyFreeMem: Integer;

function GetMemUsed: integer;
begin
  GetMemoryManager(MMgr);
  HeapState := MMgr.GetFPCHeapStatus();
  Result := HeapState.CurrHeapUsed;
end;

var
  TestVirt: Integer;
  OrigFreemem             : Function(p:pointer):ptruint;
  OrigFreememSize         : Function(p:pointer;Size:ptruint):ptruint;

Function MyFreemem(p:pointer):ptruint;
begin
  inc(InMyFreeMem);
  Result := OrigFreemem(p);
  if (InMyFreeMem = 1) and (GlobFreedMemList <> nil) then begin
    GlobFreedMemList.Add(p);
  end;
  dec(InMyFreeMem);
end;
Function MyFreememSize(p:pointer;Size:ptruint):ptruint;
begin
  inc(InMyFreeMem);
  Result := OrigFreememSize(p,Size);
  if (InMyFreeMem = 1) and (GlobFreedMemList <> nil) then begin
    GlobFreedMemList.Add(p);
  end;
  dec(InMyFreeMem);
end;


type

  { TMyBaseClass }

  TMyBaseClass = class(TComponent)
  private
    FMyBaseInt: Integer;
  public
    procedure MyVirt; virtual;
  published
    property MyBaseInt: Integer read FMyBaseInt write FMyBaseInt;
  end;

  { TMyClass }

  TMyClass = class(TMyBaseClass)
  private
    FMyLines: TStringList;
    FMyEvent: TNotifyEvent;
    FMyText: AnsiString;
    procedure SetMyLines(AValue: TStringList);
  protected
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  public
    FMyDynArray: Array of integer;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure MyVirt; override;
  published
    MyField: TMyBaseClass;
    procedure MyFoo(Sender: TObject);
    property MyLines: TStringList read FMyLines write SetMyLines;
    property MyText: AnsiString read FMyText write FMyText;
    property MyEvent: TNotifyEvent read FMyEvent write FMyEvent;
  end;

const
  MYCLASS_PROP_COUNT = 6;

{ TMyBaseClass }

procedure TMyBaseClass.MyVirt;
begin
  TestVirt := 1;
end;

{ TMyClass }

procedure TMyClass.SetMyLines(AValue: TStringList);
begin
  if FMyLines = AValue then Exit;
  FMyLines.Assign(AValue);
end;

procedure TMyClass.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
  inherited GetChildren(Proc, Root);
  if MyField <> nil then
    Proc(MyField);
end;

constructor TMyClass.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FMyLines := TStringList.Create;
end;

destructor TMyClass.Destroy;
begin
  FMyLines.Free;
  MyField.Free;
  inherited Destroy;
end;

procedure TMyClass.MyVirt;
begin
  TestVirt := 2;
end;

procedure TMyClass.MyFoo(Sender: TObject);
begin
//
end;

{ TPropListTest }

function TPropListTest.GetPPropInfo(APropName: String): PPropInfo;
var
  i: Integer;
begin
  Result := nil;
  for i := 0 to FPropCount-1 do
    if FPropList^[i]^.Name = APropName then
      exit(FPropList^[i]);
end;

constructor TPropListTest.Create(AnOwner: TTestCase; AnObject: TObject);
begin
  FOwner := AnOwner;
  FObject := AnObject;
  FPropCount := GetPropList(AnObject, FPropList);
end;

destructor TPropListTest.Destroy;
begin
  if FPropCount > 0 then
    Freemem(FPropList);
  inherited Destroy;
end;

procedure TPropListTest.AssertPropCount(AName: String; AExpCount: Integer);
begin
  FOwner.AssertEquals(AName+' (PropCount)', AExpCount, FPropCount);
end;

procedure TPropListTest.AssertPropOffsets(AName: String);
var
  CurPvmt, ParPvmt: pvmt;
  PropInf: PPropInfo;
  MaxOffs, MinOffs: SizeInt;
  ParMaxOffs: SmallInt;
  PropCount, i: Integer;
begin
  CurPvmt := PPVmt(FObject)^;
  PropCount := FPropCount;
  ParPvmt := CurPvmt^.vParent;
  MaxOffs := CurPvmt^.vInstanceSize;
  if ParPvmt <> nil then begin
    MinOffs := ParPvmt^.vInstanceSize;
    ParMaxOffs := GetTypeData(ParPvmt^.vTypeInfo)^.Propcount;
  end
  else begin
    ParMaxOffs := -1;
    MinOffs := 0
  end;

  FOwner.AssertTrue('', PropCount > 0);
  FOwner.AssertTrue('', PropCount >= ParMaxOffs);
  for i := 0 to PropCount-1 do begin
    PropInf := FPropList^[i];
    if i >= ParMaxOffs then
      FOwner.AssertTrue('', PropInf^.PropProcs = (ptConst shl 4));

    if PropInf^.PropProcs = 0 then begin
      //if PtrUInt(PropInf^.GetProc) <> 0 then
        FOwner.AssertTrue(AName+' (GetProc)', (PtrUInt(PropInf^.GetProc) <> 0));
        FOwner.AssertTrue(AName+' (GetProc)', (PtrUInt(PropInf^.GetProc) < MaxOffs));
      if PtrUInt(PropInf^.SetProc) <> 0 then
        FOwner.AssertTrue(AName+' (SetProc))', (PtrUInt(PropInf^.SetProc) < MaxOffs));

      if i >= ParMaxOffs then begin // belong to CurPvmt / must be bigger than MinOffs;
        //if PtrUInt(PropInf^.GetProc) <> 0 then
          FOwner.AssertTrue(AName+' (GetProc)', (PtrUInt(PropInf^.GetProc) >= MinOffs));
        if PtrUInt(PropInf^.SetProc) <> 0 then
          FOwner.AssertTrue(AName+' (SetProc))', (PtrUInt(PropInf^.SetProc) >= MinOffs));
      end;
    end;
  end;
end;

procedure TPropListTest.AssertHasProp(AName, APropName: String; AExpType: TTypeKind);
var
  PropInfo: PPropInfo;
begin
  PropInfo := GetPPropInfo(APropName);
  FOwner.AssertTrue(AName+' (HasProp '+APropName+')', PropInfo <> nil);
  FOwner.AssertTrue(AName+' (HasPropType '+APropName+')', PropInfo^.PropType <> nil);
  FOwner.AssertEquals(AName+' (HasPropType '+APropName+' match)', ord(AExpType), ord(PropInfo^.PropType^.Kind));
end;

procedure TPropListTest.AssertHasProp(AName, APropName: String; AExpType: TTypeKind; AValue: Int64);
var
  PropInfo: PPropInfo;
begin
  PropInfo := GetPPropInfo(APropName);
  FOwner.AssertTrue(AName+' (HasProp '+APropName+')', PropInfo <> nil);
  FOwner.AssertTrue(AName+' (HasPropType '+APropName+')', PropInfo^.PropType <> nil);
  FOwner.AssertEquals(AName+' (HasPropType '+APropName+' match)', ord(AExpType), ord(PropInfo^.PropType^.Kind));

  FOwner.AssertEquals(AName+' (Val for '+APropName+')', AValue, GetOrdProp(FObject, PropInfo));
end;

procedure TPropListTest.AssertHasProp(AName, APropName: String; AExpType: TTypeKind; AValue: String);
var
  PropInfo: PPropInfo;
begin
  PropInfo := GetPPropInfo(APropName);
  FOwner.AssertTrue(AName+' (HasProp '+APropName+')', PropInfo <> nil);
  FOwner.AssertTrue(AName+' (HasPropType '+APropName+')', PropInfo^.PropType <> nil);
  FOwner.AssertEquals(AName+' (HasPropType '+APropName+' match)', ord(AExpType), ord(PropInfo^.PropType^.Kind));

  FOwner.AssertEquals(AName+' (Val for '+APropName+')', AValue, GetStrProp(FObject, PropInfo));
end;

{ TJitClassTest }

procedure TJitClassTest.DoObjFreed(Sender: TObject);
begin
  if FFreedObjList = nil then
    FFreedObjList := TList.Create;
  FFreedObjList.Add(Pointer(Sender));
end;

procedure TJitClassTest.AssertWasObjFreed(AName: String; AnObj: TObject);
begin
  AssertTrue(AName, (FFreedObjList <> nil) and (FFreedObjList.IndexOf(Pointer(AnObj)) >= 0));
end;

procedure TJitClassTest.AssertWasNotObjFreed(AName: String; AnObj: TObject);
begin
  AssertFalse(AName, (FFreedObjList <> nil) and (FFreedObjList.IndexOf(Pointer(AnObj)) >= 0));
end;

procedure TJitClassTest.StartMemMonitor;
begin
  if FFreedMemList = nil then
    FFreedMemList := TList.Create;

  GlobFreedMemList := FFreedMemList;
end;

procedure TJitClassTest.StartAndClearMemMonitor;
begin
  ClearMemMonitor;
  StartMemMonitor;
end;

procedure TJitClassTest.StopMemMonitor;
begin
  GlobFreedMemList := nil;
end;

procedure TJitClassTest.ClearMemMonitor;
begin
  inc(InMyFreeMem);
  if FFreedMemList <> nil then
    FFreedMemList.Clear;
  dec(InMyFreeMem);
end;

procedure TJitClassTest.AssertWasMemFreed(AName: String; AMem: Pointer);
begin
  AssertTrue(AName, (FFreedMemList <> nil) and (FFreedMemList.IndexOf(AMem) >= 0));
end;

procedure TJitClassTest.AssertWasNotMemFreed(AName: String; AMem: Pointer);
begin
  AssertFalse(AName, (FFreedMemList <> nil) and (FFreedMemList.IndexOf(AMem) >= 0));
end;

procedure TJitClassTest.DumpPropInfo(AClass: TClass);
var
  PropCount, i: Integer;
  PropList: PPropList;
begin
  PropCount := GetPropList(AClass, PropList);
  if PropCount>0 then begin
    try
      DebugLn(['--- ', PropCount, '  InstSize ', PVmt(AClass)^.vInstanceSize]);
      for i := 0 to PropCount-1 do
        DebugLn('## %25s  %2d  // %2d %2d  // %10d     %d  %d  %d ', [
           PropList^[i]^.Name, PropList^[i]^.NameIndex,
           PropList^[i]^.Index, PropList^[i]^.Default,
           ptruint(PropList^[i]^.PropType),
           ptruint(PropList^[i]^.GetProc),
           ptruint(PropList^[i]^.SetProc),
           ptruint(PropList^[i]^.StoredProc)
        ]);
    finally
      Freemem(PropList);
    end;
  end;
end;

procedure TJitClassTest.DoStreamCopy(AJitSource, AJitDest: TComponent);
var
  strm: TMemoryStream;
  Driver: TAbstractObjectWriter;
  Writer: TWriter;
  Reader: TReader;
begin
  strm := TMemoryStream.Create;
  Driver := TBinaryObjectWriter.Create(strm,4096);
  Writer := TWriter.Create(Driver);
  Writer.WriteRootComponent(AJitSource);
  Driver.Free;
  Writer.Free;

  strm.Position := 0;
  Reader := TReader.Create(strm, 4096);
  Reader.ReadRootComponent(AJitDest);
  Reader.Free;

  strm.Free;
end;

procedure TJitClassTest.DoTestSimpleClass(AJitClass,
  AnExpParentClass: TComponentClass);
var
  JitObject, JitObject2: TMyClass;
  MemUsed, MemUsed2, PropCount, i: Integer;
  PropList: PPropList;
  TestProps: TPropListTest;
begin
  AssertEquals('Got class-name',    'TJitTestSimpleClass', AJitClass.ClassName);
  AssertEquals('Got unit-name',     'foo.pas',             AJitClass.UnitName);
  AssertEquals('Got class-parent',  AnExpParentClass,      AJitClass.ClassParent);
  AssertEquals('Got instance-size', TMyClass.InstanceSize, AJitClass.InstanceSize);

  MemUsed := GetMemUsed;

  // Call the virtual method
  JitObject := TMyClass(AJitClass.Create(nil));
  TMyBaseClass(JitObject).MyVirt;
  AssertEquals('virt meth call', 2, TestVirt);

  // check all memory is freed
  JitObject.Free;
  MemUsed2 := GetMemUsed;
  AssertEquals('Memory freed', MemUsed, MemUsed2);

  // check all memory is freed / including managed types
  JitObject := TMyClass(AJitClass.Create(nil));
  SetLength(JitObject.FMyDynArray, 100);
  JitObject.Free;
  MemUsed2 := GetMemUsed;
  AssertEquals('Memory freed (dyn array)', MemUsed, MemUsed2);

  // check RTTI properties
  JitObject := TMyClass(AJitClass.Create(nil));
  SetLength(JitObject.FMyDynArray, 1);
  JitObject.MyBaseInt := 88123;
  JitObject.MyText := 'SomeText';
  JitObject.MyLines.Text := 'Line123';
  JitObject.MyEvent := @JitObject.MyFoo;
  JitObject.MyField := TMyBaseClass.Create(JitObject);
  JitObject.MyField.Name := 'MyField';

  TestProps := TPropListTest.Create(Self, JitObject);
  try
    TestProps.AssertPropCount('', 6);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'MyBaseInt', tkInteger, 88123);
    TestProps.AssertHasProp('', 'MyText', tkAnsiString, 'SomeText');
    TestProps.AssertHasProp('', 'MyLines', tkClass);
    TestProps.AssertHasProp('', 'MyEvent', tkMethod);
  finally
    TestProps.Free;
  end;

  // stream and copy
  JitObject2 := TMyClass(AJitClass.Create(nil));
  DoStreamCopy(JitObject, JitObject2);

  AssertEquals('Stream-copied BaseInt', 88123,      JitObject2.MyBaseInt);
  AssertEquals('Stream-copied MyText',  'SomeText', JitObject2.MyText);
  AssertEquals('Stream-copied MyLines', 'Line123', JitObject2.MyLines[0]);
  AssertTrue  ('Stream-copied MyEvent', @JitObject.MyFoo = JitObject2.MyEvent);
  AssertTrue  ('Stream-copied MyField', JitObject2.MyField <> nil);
  AssertEquals('Stream-copied MyField', 'MyField', JitObject2.MyField.Name);

  TestProps := TPropListTest.Create(Self, JitObject2);
  try
    TestProps.AssertPropCount('', 6);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'MyBaseInt', tkInteger, 88123);
    TestProps.AssertHasProp('', 'MyText', tkAnsiString, 'SomeText');
    TestProps.AssertHasProp('', 'MyLines', tkClass);
    TestProps.AssertHasProp('', 'MyEvent', tkMethod);
  finally
    TestProps.Free;
  end;

  JitObject2.Free;



  PropCount := GetPropList(JitObject, PropList);
  if PropCount>0 then begin
    try
      for i := 0 to PropCount-1 do
        WriteLn('## ',
          ' / ', PropList^[i]^.Name,
          ' / ', PropList^[i]^.NameIndex,
          ' / ', PropList^[i]^.Index,
          ' / ', PropList^[i]^.Default,
          ' / ', ptruint(PropList^[i]^.PropType),
          ' / ', ptruint(PropList^[i]^.GetProc),
          ' / ', ptruint(PropList^[i]^.SetProc),
          ' / ', ptruint(PropList^[i]^.StoredProc)

        );
    finally
      Freemem(PropList);
    end;
  end;

  // RTTI methods
  AssertEquals('Func of Addr', PtrUint(@TMyClass.MyFoo), PtrUint(AJitClass.MethodAddress('MyFoo')));
  AssertEquals('Addr of Func', 'MyFoo', AJitClass.MethodName(@TMyClass.MyFoo));

  AssertEquals('Func of Addr', PtrUint(@TMyClass.MyFoo), PtrUint(JitObject.MethodAddress('MyFoo')));
  AssertEquals('Addr of Func', 'MyFoo', JitObject.MethodName(@TMyClass.MyFoo));

  AssertEquals('Func of Addr', PtrUint(@TMyClass.MyFoo), PtrUint(JitObject.ClassType.MethodAddress('MyFoo')));
  AssertEquals('Addr of Func', 'MyFoo', JitObject.ClassType.MethodName(@TMyClass.MyFoo));

  AssertEquals('Func of none Addr', 0, PtrUint(AJitClass.MethodAddress('NotHere')));
  AssertEquals('Addr of none Func', '', AJitClass.MethodName(codepointer($11002233)));

  // RTTI fields

  AssertEquals('Addr of Field', PtrUint(@JitObject.MyField), PtrUint(JitObject.FieldAddress('MyField')));
  AssertEquals('Addr of none Field', 0, PtrUint(JitObject.FieldAddress('NotHere')));

  JitObject.Free;
end;

procedure TJitClassTest.TearDown;
begin
  inherited TearDown;
  GlobFreedMemList := nil;
  FreeAndNil(FFreedObjList);
  FreeAndNil(FFreedMemList);
end;

procedure TJitClassTest.TestSimpleClass;
var
  JitCreator: TJitClassCreator;
type
  TTa= array of record a,b,c: cardinal; end; // vartype -1 // elSize 12
  TTb= array of record a,b,c: string; end; // vartype -1 // elSize 24
  TTc= array of byte; // vartype 17 // elSize 1
var a,b,c: PTypeInfo;
begin
a := PTypeInfo(TypeInfo(tta));
b := PTypeInfo(TypeInfo(ttb));
c := PTypeInfo(TypeInfo(ttc));
  JitCreator := TJitClassCreator.Create(TMyClass, 'TJitTestSimpleClass', 'foo.pas');
  DoTestSimpleClass(TComponentClass(JitCreator.JitClass), TMyClass);
  JitCreator.Free;
end;

procedure TJitClassTest.TestSimpleClassNested;
var
  JitCreator, JitCreatorNested: TJitClassCreator;
begin
  (* Include an empty JitClass as parent
     Test that a JitClass can be used as parent
  *)
  JitCreator       := TJitClassCreator.Create(TMyClass, 'TJitTestSimpleOuterClass', 'foo.pas');
  JitCreatorNested := TJitClassCreator.Create(JitCreator.JitClass, 'TJitTestSimpleClass', 'foo.pas');
  DoTestSimpleClass(TComponentClass(JitCreatorNested.JitClass), TComponentClass(JitCreator.JitClass));
  JitCreator.Free;
  JitCreatorNested.Free;
end;

procedure TJitClassTest.TestJitPropSimple;
var
  JitCreator: TJitClassCreator;
  JitClass: TComponentClass;
  JitObject, JitObject2: TComponent;
  TestProps: TPropListTest;
  PropList: PPropList;
  PropCount, i: Integer;
begin
  JitCreator       := TJitClassCreator.Create(TMyClass, 'TJitTestSimpleOuterClass', 'foo.pas');

  JitCreator.JitProperties.Add('JitInt32', 'longint');
  JitCreator.JitProperties.Add('JitInt64', 'int64');
  JitCreator.JitProperties.Add('JitWord1', 'word');
  JitCreator.JitProperties.Add('JitWord2', 'Word');
  JitCreator.JitProperties.Add('JitString', 'AnsiString');

  JitClass := TComponentClass(JitCreator.JitClass);
  JitObject := JitClass.Create(nil);


  PropCount := GetPropList(JitObject, PropList);
  if PropCount>0 then begin
    try
      for i := 0 to PropCount-1 do
        WriteLn(format('## %15s %2d / %2d %10d / %x   %d %d %d', [
          AnsiString(PropList^[i]^.Name),
          PropList^[i]^.NameIndex,
          PropList^[i]^.Index,
          PropList^[i]^.Default,
          ptruint(PropList^[i]^.PropType),
          ptruint(PropList^[i]^.GetProc),
          ptruint(PropList^[i]^.SetProc),
          ptruint(PropList^[i]^.StoredProc)
        ]));
    finally
      Freemem(PropList);
    end;
  end;

  TestProps := TPropListTest.Create(Self, JitObject);
  try
    TestProps.AssertPropCount('', 11);
    TestProps.AssertPropOffsets;
    //TestProps.AssertHasProp('', 'MyBaseInt', tkInteger, 88123);
    //TestProps.AssertHasProp('', 'MyText', tkAnsiString, 'SomeText');
    TestProps.AssertHasProp('', 'MyLines', tkClass);
    TestProps.AssertHasProp('', 'MyEvent', tkMethod);

    TestProps.AssertHasProp('', 'JitInt32', tkInteger);
    TestProps.AssertHasProp('', 'JitInt64', tkInt64);
    TestProps.AssertHasProp('', 'JitWord1', tkInteger);
    TestProps.AssertHasProp('', 'JitWord2', tkInteger);
    TestProps.AssertHasProp('', 'JitString', tkAString);
  finally
    TestProps.Free;
  end;

  SetOrdProp(JitObject, 'JitInt32', $66771122);
  SetOrdProp(JitObject, 'JitInt64', $7557444475574444);
  SetOrdProp(JitObject, 'JitWord1', $2332);
  SetOrdProp(JitObject, 'JitWord2', $4334);
  SetStrProp(JitObject, 'JitString', 'Hello World');

  AssertEquals(GetOrdProp(JitObject, 'JitInt32'), $66771122);
  AssertEquals(GetOrdProp(JitObject, 'JitInt64'), $7557444475574444);
  AssertEquals(GetOrdProp(JitObject, 'JitWord1'), $2332);
  AssertEquals(GetOrdProp(JitObject, 'JitWord2'), $4334);
  AssertEquals(GetStrProp(JitObject, 'JitString'), 'Hello World');

  // set in reverse order
  SetStrProp(JitObject, 'JitString', 'Hello World');
  SetOrdProp(JitObject, 'JitWord2', $4334);
  SetOrdProp(JitObject, 'JitWord1', $2332);
  SetOrdProp(JitObject, 'JitInt64', $7557444475574444);
  SetOrdProp(JitObject, 'JitInt32', $66771122);

  AssertEquals(GetOrdProp(JitObject, 'JitInt32'), $66771122);
  AssertEquals(GetOrdProp(JitObject, 'JitInt64'), $7557444475574444);
  AssertEquals(GetOrdProp(JitObject, 'JitWord1'), $2332);
  AssertEquals(GetOrdProp(JitObject, 'JitWord2'), $4334);
  AssertEquals(GetStrProp(JitObject, 'JitString'), 'Hello World');

  JitObject2 := JitClass.Create(nil);
  DoStreamCopy(JitObject, JitObject2);
  AssertEquals(GetOrdProp(JitObject2, 'JitInt32'), $66771122);
  AssertEquals(GetOrdProp(JitObject2, 'JitInt64'), $7557444475574444);
  AssertEquals(GetOrdProp(JitObject2, 'JitWord1'), $2332);
  AssertEquals(GetOrdProp(JitObject2, 'JitWord2'), $4334);
  AssertEquals(GetStrProp(JitObject2, 'JitString'), 'Hello World');

  JitObject2.Free;
  JitObject.Free;
  JitCreator.Free;
end;

procedure TJitClassTest.TestJitParseClass;
var
  JitCreator: TJitClassCreator;
  JitTypeLib: TJitTypeLibrary;
begin
  JitCreator := TJitClassCreator.Create(TMyClass, 'TJitTestClass', 'foo');
  JitCreator.JitProperties.ParseFromClassDeclaration(
    'class(foo)' +
    'published' +
    '  property TestProp1: int64 read foo write foo;' +
    '  property TestProp2: int64 read foo;' +
    'a: word;' +
    'function foo: boolean;' +
    '  property TestProp3: int64 read foo;' +
    'end'
  );

  AssertTrue(JitCreator.JitProperties.IndexOf('TestProp1') >= 0);
  AssertTrue(JitCreator.JitProperties.IndexOf('TestProp2') >= 0);
  AssertTrue(JitCreator.JitProperties.IndexOf('TestProp3') >= 0);
  AssertTrue(JitCreator.JitProperties.IndexOf('TestFoo') < 0);

  JitCreator.Free;



  JitCreator := TJitClassCreator.Create(TMyClass, 'TJitTestClass', 'foo');
  JitCreator.JitProperties.ParseFromClassDeclaration(
    '  property TestProp1: int64 read foo write foo;' +
    '  property TestProp2: int64 read foo;' +
    'a: word;' +
    'function foo: boolean;' +
    '  property TestProp3: int64 read foo;'
  );

  AssertTrue(JitCreator.JitProperties.IndexOf('TestProp1') >= 0);
  AssertTrue(JitCreator.JitProperties.IndexOf('TestProp2') >= 0);
  AssertTrue(JitCreator.JitProperties.IndexOf('TestProp3') >= 0);

  JitCreator.Free;


  JitTypeLib := TJitTypeLibrary.Create;
  JitTypeLib.AddAlias('string', 'ansistring');
  JitCreator := TJitClassCreator.Create(TMyClass, 'TJitTestClass', 'foo', JitTypeLib);
  JitCreator.JitProperties.ParseFromClassDeclaration(
    '  property TestProp1: string read foo write foo;' +
    '  property TestProp2: ansistring read foo;'
  );

  AssertTrue(JitCreator.JitProperties.IndexOf('TestProp1') >= 0);
  AssertTrue(JitCreator.JitProperties.IndexOf('TestProp2') >= 0);

  JitCreator.Free;
  JitTypeLib.Free;
end;

procedure TJitClassTest.TestJitPropCircularClassDef;
  procedure DoTestProps(AClass: TClass);
  var
    Obj: TComponent;
    TestProps: TPropListTest;
  begin
    Obj := TComponentClass(AClass).Create(nil);
    TestProps := TPropListTest.Create(Self, Obj);
    try
      //TestProps.AssertPropCount('', MYCLASS_PROP_COUNT + 4);
      TestProps.AssertPropOffsets;
    finally
      TestProps.Free;
      Obj.Free;
    end;
  end;
  procedure TestTwoClassProps(AnInitProc: TInitProcedure; NotACircle: Boolean = False);
  var
    i, MemUsed: Integer;
    cl: TClass;
    pi: PPropInfo;
    ti: PTypeInfo;
  begin
    for i := 1 to 2 do begin
      AnInitProc();

      case i of // which class to access first
        1: FJitCreator[1].JitClass;
        2: FJitCreator[2].JitClass;
      end;
      MemUsed := GetMemUsed;
      DoTestProps(FJitCreator[1].JitClass);
      DoTestProps(FJitCreator[2].JitClass);
      if not NotACircle then
        AssertEquals('No more Memory alloc', MemUsed, GetMemUsed);

      FJitTypeLib.Free;
      FJitCreator[1].Free;
      FJitCreator[2].Free;
    end;
    // only access one class
    for i := 1 to 2 do begin
      AnInitProc();

      case i of // which class to access first
        1: cl := FJitCreator[1].JitClass;
        2: cl := FJitCreator[2].JitClass;
      end;
      DoTestProps(cl);
      if cl.ClassParent.ClassName <> 'TMyClass' then
        DoTestProps(cl.ClassParent);
      pi := GetPropInfo(cl, 'a');
      if pi <> nil then begin
        ti := pi^.PropType;
        AssertTrue(ti <> nil);
        if ti^.Kind = tkClass then
          DoTestProps(GetTypeData(ti)^.ClassType);
      end;
      pi := GetPropInfo(cl, 'b');
      if pi <> nil then begin
        ti := pi^.PropType;
        AssertTrue(ti <> nil);
        if ti^.Kind = tkClass then
          DoTestProps(GetTypeData(ti)^.ClassType);
      end;
      pi := GetPropInfo(cl, 'par');
      if pi <> nil then begin
        ti := pi^.PropType;
        AssertTrue(ti <> nil);
        if ti^.Kind = tkClass then
          DoTestProps(GetTypeData(ti)^.ClassType);
      end;

      FJitTypeLib.Free;
      FJitCreator[1].Free;
      FJitCreator[2].Free;
    end;
  end;
  procedure TestThreeClassProps(AnInitProc: TInitProcedure);
  var
    i, MemUsed: Integer;
    cl: TClass;
    pi: PPropInfo;
    ti: PTypeInfo;
  begin
    for i := 1 to 3 do begin
      AnInitProc();

      case i of // which class to access first
        1: FJitCreator[1].JitClass;
        2: FJitCreator[2].JitClass;
        3: FJitCreator[3].JitClass;
      end;
      MemUsed := GetMemUsed;
      DoTestProps(FJitCreator[1].JitClass);
      DoTestProps(FJitCreator[2].JitClass);
      DoTestProps(FJitCreator[3].JitClass);
      AssertEquals('No more Memory alloc', MemUsed, GetMemUsed);

      FJitTypeLib.Free;
      FJitCreator[1].Free;
      FJitCreator[2].Free;
      FJitCreator[3].Free;
    end;
    // only access one class
    for i := 1 to 3 do begin
      AnInitProc();

      case i of // which class to access first
        1: cl := FJitCreator[1].JitClass;
        2: cl := FJitCreator[2].JitClass;
        3: cl := FJitCreator[3].JitClass;
      end;
      DoTestProps(cl);
      if cl.ClassParent.ClassName <> 'TMyClass' then
        DoTestProps(cl.ClassParent);
      pi := GetPropInfo(cl, 'a');
      if pi <> nil then begin
        ti := pi^.PropType;
        AssertTrue(ti <> nil);
        if ti^.Kind = tkClass then
          DoTestProps(GetTypeData(ti)^.ClassType);
      end;
      pi := GetPropInfo(cl, 'b');
      if pi <> nil then begin
        ti := pi^.PropType;
        AssertTrue(ti <> nil);
        if ti^.Kind = tkClass then
          DoTestProps(GetTypeData(ti)^.ClassType);
      end;
      pi := GetPropInfo(cl, 'par');
      if pi <> nil then begin
        ti := pi^.PropType;
        AssertTrue(ti <> nil);
        if ti^.Kind = tkClass then
          DoTestProps(GetTypeData(ti)^.ClassType);
      end;

      FJitTypeLib.Free;
      FJitCreator[1].Free;
      FJitCreator[2].Free;
      FJitCreator[3].Free;
    end;
  end;

var
  JitObject1, JitObject2: TComponent;
  TestProps: TPropListTest;
begin
  (* set up different circular scenarios *)

  //////////////////////////////////
  // 1 classes circle
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassOne');

  JitObject1 := TComponentClass(FJitCreator[1].JitClass).Create(nil);
  TestProps := TPropListTest.Create(Self, JitObject1);
  try
    TestProps.AssertPropCount('', MYCLASS_PROP_COUNT + 4);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'a', tkClass);
    TestProps.AssertHasProp('', 'prop1', tkInt64);
    TestProps.AssertHasProp('', 'prop2', tkInteger);
    TestProps.AssertHasProp('', 'prop3', tkInt64);
  finally
    TestProps.Free;
  end;

  JitObject1.Free;
  FJitCreator[1].Free;
  FJitTypeLib.Free;


  // 2 class Property Circle
  InitTwoClasses;
  JitObject1 := TComponentClass(FJitCreator[1].JitClass).Create(nil);
  JitObject2 := TComponentClass(FJitCreator[2].JitClass).Create(nil);

  TestProps := TPropListTest.Create(Self, JitObject1);
  try
    TestProps.AssertPropCount('', MYCLASS_PROP_COUNT + 4);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'a', tkClass);
    TestProps.AssertHasProp('', 'prop1', tkInt64);
    TestProps.AssertHasProp('', 'prop2', tkInteger);
    TestProps.AssertHasProp('', 'prop3', tkInt64);
  finally
    TestProps.Free;
  end;

  TestProps := TPropListTest.Create(Self, JitObject2);
  try
    TestProps.AssertPropCount('', MYCLASS_PROP_COUNT + 4);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'a', tkClass);
    TestProps.AssertHasProp('', 'prop1', tkInt64);
    TestProps.AssertHasProp('', 'prop2', tkInteger);
    TestProps.AssertHasProp('', 'prop3', tkInt64);
  finally
    TestProps.Free;
  end;

  JitObject1.Free;
  JitObject2.Free;
  FJitCreator[1].Free;
  FJitCreator[2].Free;
  FJitTypeLib.Free;


  // Anchestor Class, has child-class as property
  InitTwoClassesAnchestor;
  JitObject1 := TComponentClass(FJitCreator[1].JitClass).Create(nil);
  JitObject2 := TComponentClass(FJitCreator[2].JitClass).Create(nil);

  TestProps := TPropListTest.Create(Self, JitObject1);
  try
    TestProps.AssertPropCount('', MYCLASS_PROP_COUNT + 4);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'a', tkClass);
    TestProps.AssertHasProp('', 'prop1', tkInt64);
    TestProps.AssertHasProp('', 'prop2', tkInteger);
    TestProps.AssertHasProp('', 'prop3', tkInt64);
  finally
    TestProps.Free;
  end;

  TestProps := TPropListTest.Create(Self, JitObject2);
  try
    TestProps.AssertPropCount('', MYCLASS_PROP_COUNT + 6);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'a', tkClass);
    TestProps.AssertHasProp('', 'prop1', tkBool);
    TestProps.AssertHasProp('', 'prop2', tkInteger);
    TestProps.AssertHasProp('', 'prop3', tkInt64);
    TestProps.AssertHasProp('', 'bprop1', tkInt64);
    TestProps.AssertHasProp('', 'bprop2', tkInt64);
  finally
    TestProps.Free;
  end;

  JitObject1.Free;
  JitObject2.Free;
  FJitCreator[1].Free;
  FJitCreator[2].Free;
  FJitTypeLib.Free;


  // Child Class, has anchestor-class as property / not a circle
  InitTwoClassesAnchestorWithAnchestorPropOneWay;
  JitObject1 := TComponentClass(FJitCreator[1].JitClass).Create(nil);
  JitObject2 := TComponentClass(FJitCreator[2].JitClass).Create(nil);

  TestProps := TPropListTest.Create(Self, JitObject1);
  try
    TestProps.AssertPropCount('', MYCLASS_PROP_COUNT + 3);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'prop1', tkInt64);
    TestProps.AssertHasProp('', 'prop2', tkInteger);
    TestProps.AssertHasProp('', 'prop3', tkInt64);
  finally
    TestProps.Free;
  end;

  TestProps := TPropListTest.Create(Self, JitObject2);
  try
    TestProps.AssertPropCount('', MYCLASS_PROP_COUNT + 6);
    TestProps.AssertPropOffsets;
    TestProps.AssertHasProp('', 'prop1', tkBool);
    TestProps.AssertHasProp('', 'prop2', tkInteger);
    TestProps.AssertHasProp('', 'prop3', tkInt64);
    TestProps.AssertHasProp('', 'par', tkClass);
    TestProps.AssertHasProp('', 'bprop1', tkInt64);
    TestProps.AssertHasProp('', 'bprop2', tkInt64);
  finally
    TestProps.Free;
  end;

  JitObject1.Free;
  JitObject2.Free;
  FJitCreator[1].Free;
  FJitCreator[2].Free;
  FJitTypeLib.Free;

  /////////////////////

  TestTwoClassProps(@InitTwoClasses);
  TestTwoClassProps(@InitTwoClassesWithOneSelfRef);
  TestTwoClassProps(@InitTwoClassesWithDoubleLink);
  TestTwoClassProps(@InitTwoClassesAnchestor);
  TestTwoClassProps(@InitTwoClassesAnchestorWithAnchestorProp);
  TestTwoClassProps(@InitTwoClassesAnchestorWithAnchestorPropOneWay, True);

  TestThreeClassProps(@InitThreeClasses);
  TestThreeClassProps(@InitThreeClassesWithOneSelfRef);
  TestThreeClassProps(@InitThreeClassesWithOneDoubleLink);
  TestThreeClassProps(@InitThreeClassesWithSubLoop);
  TestThreeClassProps(@InitThreeClassesWithSubLoopAndOneSelfRef);
  TestThreeClassProps(@InitThreeClassesWithTwoSubLoop);
  TestThreeClassProps(@InitThreeClassesChained);
  TestThreeClassProps(@InitThreeClassesChainedIndirect);
  TestThreeClassProps(@InitThreeClassesOneAnchestor);
  TestThreeClassProps(@InitThreeClassesOneAnchestorIndirect);
  TestThreeClassProps(@InitThreeClassesAnchestorParallel);
  TestThreeClassProps(@InitThreeClassesAnchestorParallelIndirect); // 5 classes
  TestThreeClassProps(@InitThreeClassesAnchestorParallelAndChildRef);
  TestThreeClassProps(@InitThreeClassesAnchestorParallelAndChildRefIndirect);
  TestThreeClassProps(@InitThreeClassesAnchestorParallelAndChildRefIndirect_2);
  TestThreeClassProps(@InitThreeClassesAnchestorParallelAndChildLoop);
  TestThreeClassProps(@InitThreeClassesAnchestorParallelAndChildLoopIndirect);
  TestThreeClassProps(@InitThreeClassesAnchestorParallelAndChildLoopIndirect_2);
  TestThreeClassProps(@InitThreeClassesTwoAnchestor);
  TestThreeClassProps(@InitThreeClassesTwoAnchestorIndirect);
end;

procedure TJitClassTest.TestManagedJitProp;
var
  JitCreator: TJitClassCreator;
  JitClass: TComponentClass;
  JitObject: TComponent;
  a: ansistring;
  MemUsedBeforeCreate, MemUsedAfterCreate, MemUsedTmp: Integer;
begin
  a := '';
  JitCreator       := TJitClassCreator.Create(TMyClass, 'TJitClass', 'foo');
  JitCreator.JitProperties.Add('JitString', 'AnsiString');
  JitClass := TComponentClass(JitCreator.JitClass);

  MemUsedBeforeCreate := GetMemUsed;
  JitObject := JitClass.Create(nil);
  JitObject.Destroy;
  AssertEquals('Memory for object released', MemUsedBeforeCreate, GetMemUsed);


  MemUsedBeforeCreate := GetMemUsed;
  JitObject := JitClass.Create(nil);
  MemUsedAfterCreate := GetMemUsed;

  MemUsedTmp := GetMemUsed;
  SetStrProp(JitObject, 'JitString', a);
  AssertEquals('No Memory used by setting empty string', MemUsedTmp, GetMemUsed);

  SetLength(a, 100);

  MemUsedTmp := GetMemUsed;
  SetStrProp(JitObject, 'JitString', a);
  AssertEquals('No Memory used by setting string with data', MemUsedTmp, GetMemUsed);

  MemUsedTmp := GetMemUsed;
  a := '';
  AssertEquals('Memory for string hold by jitprop', MemUsedTmp, GetMemUsed);

  SetStrProp(JitObject, 'JitString', a); // empty again
  AssertEquals('Memory for string hold by jitprop freed (set to empty)', MemUsedAfterCreate, GetMemUsed);


  SetLength(a, 100);
  MemUsedTmp := GetMemUsed;
  SetStrProp(JitObject, 'JitString', a);
  a := '';
  AssertEquals('Memory for string hold by jitprop (2)', MemUsedTmp, GetMemUsed);


  JitObject.Destroy;
  AssertEquals('Memory for string released', MemUsedBeforeCreate, GetMemUsed);

  JitCreator.Free;
end;

procedure TJitClassTest.TestRefCount;
var
  JitTypeLib: TJitTypeLibrary;
  MyEnLock, OthEnLock, MySetLock: TRefCountedJitReference;
  MyEn, OthEn, MySet: TJitType;
  MyEnInfo, OthEnInfo, MySetInfo: PTypeInfo;
begin
  JitTypeLib := TJitTypeLibrary.Create;
  MyEn  := JitTypeLib.AddType('MyEnum', '(e1, e2, e3, e4, e5, e6)');
  OthEn := JitTypeLib.AddType('OtherEnum', '(o1, o2, o3, o4, o5)');

  // Get Locks
  MyEnLock := MyEn.LockReference;
  OthEnLock := OthEn.LockReference;
  AssertEquals('LockCount for MyEnum (used by set)', 2, MyEnLock.RefCount);
  AssertEquals('LockCount for OtherEnum',            2, OthEnLock.RefCount);

  // Get Lock for MySet => No TypeInfo call yet => no lock to nested MyEnum
  MySet := JitTypeLib.AddType('MySet', 'set of MyEnum');
  MySetLock := MySet.LockReference;
  AssertEquals('LockCount for MySet',                2, MySetLock.RefCount);
  AssertEquals('LockCount for MyEnum 2',             2, MyEnLock.RefCount);
  AssertEquals('LockCount for OtherEnum 2',          2, OthEnLock.RefCount);

  // Watch when Objects are freed
  MyEn.AddFreeNotification(@DoObjFreed);
  OthEn.AddFreeNotification(@DoObjFreed);
  MySet.AddFreeNotification(@DoObjFreed);

  // Access MySet TypeInfo => MySet will need MyEnum
  MySetInfo := MySet.TypeInfo;
  AssertEquals('LockCount for MyEn used',            3, MyEnLock.RefCount);
  AssertEquals('LockCount for OthEn not parsed',     2, OthEnLock.RefCount);
  AssertEquals('LockCount for MySet parsed',         2, MySetLock.RefCount);

  // Get all TypeInfo => start monitoring FreeMem calls
  MyEnInfo  := MyEn.TypeInfo;
  OthEnInfo := OthEn.TypeInfo;

  StartMemMonitor;
  AssertEquals('LockCount for MyEn parsed',           3, MyEnLock.RefCount);
  AssertEquals('LockCount for OthEn parsed',          2, OthEnLock.RefCount);

  // Still the same ref object? The increase by LockReference, should be visible in the earlier ref
  MyEn.LockReference;
  AssertEquals('LockCount for MyEn locked 2',      4, MyEnLock.RefCount);
  OthEn.LockReference;
  AssertEquals('LockCount for OthEn locked 2',     3, OthEnLock.RefCount);
  MySet.LockReference;
  AssertEquals('LockCount for MySet locked 2',     3, MySetLock.RefCount);

  // Free the lib => JitType objects should be freed too
  AssertWasNotObjFreed('', MySet);
  AssertWasNotObjFreed('', MyEn);
  AssertWasNotObjFreed('', OthEn);

  JitTypeLib.Free;
  AssertWasObjFreed('', MySet);
  AssertWasObjFreed('', MyEn);
  AssertWasObjFreed('', OthEn);
  // LockCounts went down by one (no longer locked by the JitType object
  AssertEquals('LockCount for MyEn kept',            3, MyEnLock.RefCount);
  AssertEquals('LockCount for OthEn kept',           2, OthEnLock.RefCount);
  AssertEquals('LockCount for MySet kept',           2, MySetLock.RefCount);

  // Release MySet, which gives up one ref to MyEnum
  MySetLock.ReleaseLock;
  AssertWasNotMemFreed('', MySetInfo);
  // Check FreeMem(TypeInfo) for MySet
  MySetLock.ReleaseLock;
  AssertWasMemFreed('', MySetInfo);

  AssertEquals('LockCount for MyEn kept',            2, MyEnLock.RefCount);
  AssertEquals('LockCount for OthEn kept',           2, OthEnLock.RefCount);

  MyEnLock.ReleaseLock;
  OthEnLock.ReleaseLock;
  AssertWasNotMemFreed('', MyEnInfo);
  AssertWasNotMemFreed('', OthEnInfo);

  // Check FreeMem(TypeInfo)
  MyEnLock.ReleaseLock;
  OthEnLock.ReleaseLock;
  AssertWasMemFreed('', MyEnInfo);
  AssertWasMemFreed('', OthEnInfo);

  ////////////////////////
  // Test Mem without external lock
  StartAndClearMemMonitor;
  JitTypeLib := TJitTypeLibrary.Create;
  MyEn  := JitTypeLib.AddType('MyEnum', '(e1, e2, e3, e4, e5, e6)');
  MyEnInfo  := MyEn.TypeInfo;
  StartAndClearMemMonitor;

  AssertWasNotMemFreed('', MyEnInfo);
  JitTypeLib.Free;
  AssertWasMemFreed('', MyEnInfo);


  StopMemMonitor;
  StartAndClearMemMonitor;
end;

procedure TJitClassTest.TestRefCountProp;
var
  JitCreator: TJitClassCreator;
  JitTypeLib: TJitTypeLibrary;
  MyEnLock, ClassLock: TRefCountedJitReference;
  MyEn: TJitType;
  MyEnInfo: PTypeInfo;
begin
  JitTypeLib := TJitTypeLibrary.Create;
  MyEn  := JitTypeLib.AddType('MyEnum', '(e1, e2, e3, e4, e5, e6)');
  MyEnInfo := MyEn.TypeInfo;
  StartMemMonitor;

  MyEnLock := MyEn.LockReference;
  AssertEquals('LockCount for MyEnum after typeinfo', 2, MyEnLock.RefCount);


  JitCreator := TJitClassCreator.Create(TMyClass, 'TJitTestSimpleOuterClass', 'foo.pas');
  JitCreator.TypeLibrary := JitTypeLib;
  JitCreator.JitProperties.Add('a', 'MyEnum');
  JitCreator.JitClass;

  AssertEquals('LockCount for MyEnum after jitclass', 3, MyEnLock.RefCount);

  JitTypeLib.Free;
  AssertEquals('LockCount for MyEnum after typelib free', 2, MyEnLock.RefCount);

  JitCreator.Free;
  AssertEquals('LockCount for MyEnum after creator free', 1, MyEnLock.RefCount);

  AssertWasNotMemFreed('', MyEnInfo);
  MyEnLock.ReleaseLock;
  AssertWasMemFreed('', MyEnInfo);


  ///////////////
  // Add as property
  // hold creator by lock
  JitTypeLib := TJitTypeLibrary.Create;
  MyEn  := JitTypeLib.AddType('MyEnum', '(e1, e2, e3, e4, e5, e6)');
  MyEnInfo := MyEn.TypeInfo;
  StartAndClearMemMonitor;

  MyEnLock := MyEn.LockReference;
  AssertEquals('LockCount for MyEnum after typeinfo', 2, MyEnLock.RefCount);


  JitCreator := TJitClassCreator.Create(TMyClass, 'TJitTestSimpleOuterClass', 'foo.pas');
  JitCreator.TypeLibrary := JitTypeLib;
  JitCreator.JitProperties.Add('a', 'MyEnum');
  ClassLock := JitCreator.LockReference;
  AssertEquals('LockCount for class ', 2, ClassLock.RefCount);

  JitCreator.JitClass;
  AssertEquals('LockCount for MyEnum after jitclass', 3, MyEnLock.RefCount);

  JitTypeLib.Free;
  AssertEquals('LockCount for MyEnum after typelib free', 2, MyEnLock.RefCount);

  JitCreator.Free;
  AssertEquals('LockCount for MyEnum after creator free (locked)', 2, MyEnLock.RefCount);
  AssertEquals('LockCount for class after creator free', 1, ClassLock.RefCount);

  ClassLock.ReleaseLock;
  AssertEquals('LockCount for MyEnum after creator free (unlocked)', 1, MyEnLock.RefCount);

  AssertWasNotMemFreed('', MyEnInfo);
  MyEnLock.ReleaseLock;
  AssertWasMemFreed('', MyEnInfo);
end;

function TJitClassTest.GetCreator(ABase: TClass; AName: String; PropClass: String = ''; ATakeCreatorOwnerShip: Boolean = False): TJitClassCreator;
begin
  Result := TJitClassCreator.Create(ABase, AName, 'foo');
  Result.TypeLibrary := FJitTypeLib;
  if PropClass <> '' then
    Result.JitProperties.Add('a', PropClass);
  FJitTypeLib.AddJitClass(Result.JitClassName, Result, ATakeCreatorOwnerShip);

  Result.JitProperties.Add('prop1', 'Int64');
  Result.JitProperties.Add('prop2', 'word');
  Result.JitProperties.Add('prop3', 'Int64');
end;

function TJitClassTest.GetCreator(ABase: TJitClassCreator; AName: String;
  PropClass: String; ATakeCreatorOwnerShip: Boolean): TJitClassCreator;
begin
  Result := TJitClassCreator.Create(ABase, AName, 'foo');
  Result.TypeLibrary := FJitTypeLib;
  if PropClass <> '' then
    Result.JitProperties.Add('b', PropClass);
  FJitTypeLib.AddJitClass(Result.JitClassName, Result, ATakeCreatorOwnerShip);

  Result.JitProperties.Add('bprop1', 'Int64');
  Result.JitProperties.Add('bprop2', 'Int64');
  Result.JitProperties.Add('prop1', 'boolean'); // replace prop
end;

procedure TJitClassTest.InitTwoClasses;
begin
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwo');
  FJitCreator[2] := GetCreator(TMyClass, 'TMyClassTwo', 'TMyClassOne');
end;

procedure TJitClassTest.InitTwoClassesWithOneSelfRef;
begin
  (* One -> Two
     One -> One
     Two -> One
  *)
  InitTwoClasses;
  FJitCreator[1].JitProperties.Add('b', 'TMyClassOne'); // self ref
end;

procedure TJitClassTest.InitTwoClassesWithDoubleLink;
begin
  (* One -> Two
     One -> Two (2nd property)
     Two -> One
  *)
  InitTwoClasses;
  FJitCreator[1].JitProperties.Add('b', 'TMyClassTwo'); // 2nd prop to TMyClassTwo;
end;

procedure TJitClassTest.InitTwoClassesAnchestor;
begin
  (* One -> Two
     Two >> One  (inherits)
  *)
  (* Anchestor Class, has child-class as property *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwo');
  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo');
end;

procedure TJitClassTest.InitTwoClassesAnchestorWithAnchestorProp;
begin
  (* One -> Two
     Two >> One  (inherits)
     Two -> One  (prop)
  *)
  (* Anchestor Class, has child-class as property
     AND Child has anchestor as prop
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwo');
  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo');
  FJitCreator[2].JitProperties.Add('par', 'TMyClassOne');
end;
procedure TJitClassTest.InitTwoClassesAnchestorWithAnchestorPropOneWay;
begin
  // NOT a circle
  (* Two >> One  (inherits)
     Two -> One  (prop)
  *)
  (* Child Class, has anchestor-class as property / not a circle *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne'); // anchestor does NOT refer to the child
  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo');
  FJitCreator[2].JitProperties.Add('par', 'TMyClassOne');
end;
procedure TJitClassTest.InitThreeClasses;
begin
  (* One   -> Two
     Two   -> Three
     Three -> One
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1]   := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwo');
  FJitCreator[2]   := GetCreator(TMyClass, 'TMyClassTwo', 'TMyClassThree');
  FJitCreator[3] := GetCreator(TMyClass, 'TMyClassThree', 'TMyClassOne');
end;

procedure TJitClassTest.InitThreeClassesWithOneSelfRef;
begin
  (* One   -> Two
     One   -> One
     Two   -> Three
     Three -> One
  *)
  InitThreeClasses;
  FJitCreator[1].JitProperties.Add('c', 'TMyClassOne');
end;

procedure TJitClassTest.InitThreeClassesWithOneDoubleLink;
begin
  (* One   -> Two
     One   -> Two  (2nd prop)
     Two   -> Three
     Three -> One
  *)
  InitThreeClasses;
  FJitCreator[1].JitProperties.Add('c', 'TMyClassTwo');
end;

procedure TJitClassTest.InitThreeClassesWithSubLoop;
begin
  (* One   -> Two
     One   -> Three
     Two   -> Three
     Three -> One
  *)
  InitThreeClasses;
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThree');
end;

procedure TJitClassTest.InitThreeClassesWithSubLoopAndOneSelfRef;
begin
  (* One   -> Two
     One   -> One
     One   -> Three
     Two   -> Three
     Three -> One
  *)
  InitThreeClasses;
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThree');
  FJitCreator[1].JitProperties.Add('c', 'TMyClassOne');
end;

procedure TJitClassTest.InitThreeClassesWithTwoSubLoop;
begin
  (* One   -> Two
     One   -> Three
     Two   -> Three
     Three -> One
     Three -> One  (2nd prop)
  *)
  InitThreeClasses;
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThree');
  FJitCreator[3].JitProperties.Add('c', 'TMyClassOne');
end;

procedure TJitClassTest.InitThreeClassesChained;
begin
  // 2 separate loops / only ONE is in both loops
  (* One   -> Two
     One   -> Three
     Two   -> One
     Three -> One
  *)
  // FJitCreator[1]  will be in 2 circles: One with FJitCreator[2] / the other one with FJitCreator[3]
  // FJitCreator[2] and FJitCreator[3] are anly connected to FJitCreator[1]
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1]   := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwo');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThree');
  FJitCreator[2]   := GetCreator(TMyClass, 'TMyClassTwo', 'TMyClassOne');
  FJitCreator[3] := GetCreator(TMyClass, 'TMyClassThree', 'TMyClassOne');
end;

procedure TJitClassTest.InitThreeClassesChainedIndirect;
begin
  // 2 separate loops / only ONE is in both loops
  (* One   -> TwoHolder   -> Two
     One   -> ThreeHolder -> Three
     Two   -> One
     Three -> One
  *)
  // FJitCreator[1]  will be in 2 circles: One with FJitCreator[2] / the other one with FJitCreator[3]
  // FJitCreator[2] and FJitCreator[3] are anly connected to FJitCreator[1]
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1]   := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwoHolder');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThreeHolder');

  GetCreator(TMyClass, 'TMyClassTwoHolder', 'TMyClassTwo', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder', 'TMyClassThree', True);

  FJitCreator[2] := GetCreator(TMyClass, 'TMyClassTwo', 'TMyClassOne');
  FJitCreator[3] := GetCreator(TMyClass, 'TMyClassThree', 'TMyClassOne');

end;

procedure TJitClassTest.InitThreeClassesOneAnchestor;
begin
  (* One   -> Three
     Three -> Two
     Two   >> One  (inherits)
  *)
  (* Anchestor Class, has child-class via 3rd obj as property *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassThree');
  FJitCreator[3] := GetCreator(TMyClass, 'TMyClassThree', 'TMyClassTwo');
  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo');
end;

procedure TJitClassTest.InitThreeClassesOneAnchestorIndirect;
begin
  (* One   -> ThreeHolder -> Three
     Three -> TwoHolder   -> Two
     Two   >> One  (inherits)
  *)
  (* Anchestor Class, has child-class via 3rd obj as property *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassThreeHolder');

  GetCreator(TMyClass, 'TMyClassTwoHolder', 'TMyClassTwo', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder', 'TMyClassThree', True);

  FJitCreator[3] := GetCreator(TMyClass, 'TMyClassThree', 'TMyClassTwoHolder');
  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo');
end;

procedure TJitClassTest.InitThreeClassesAnchestorParallel;
begin
  // 2 separate loops / only ONE is in both loops
  (* One   -> Two
     One   -> Three
     Two   >> One  (inherits)
     Three >> One  (inherits)
  *)
  (* Anchestor Class, has TWO child-classes, BOTH as property *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwo');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThree');

  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo');
  FJitCreator[3] := GetCreator(FJitCreator[1], 'TMyClassThree');
end;

procedure TJitClassTest.InitThreeClassesAnchestorParallelIndirect;
begin
  // 2 separate loops / only ONE is in both loops
  (* One   -> TwoHolder   -> Two
     One   -> ThreeHolder -> Three
     Two   >> One  (inherits)
     Three >> One  (inherits)
  *)
  (* Anchestor Class, has TWO child-classes,
     BOTH as property via indirection
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwoHolder');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThreeHolder');

  GetCreator(TMyClass, 'TMyClassTwoHolder', 'TMyClassTwo', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder', 'TMyClassThree', True);

  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo');
  FJitCreator[3] := GetCreator(FJitCreator[1], 'TMyClassThree');
end;

procedure TJitClassTest.InitThreeClassesAnchestorParallelAndChildRef;
begin
  (* One   -> Two
     One   -> Three
     Two   >> One  (inherits)
     Two   -> Three
     Three >> One  (inherits)
  *)
  (* Anchestor Class, has TWO child-classes, BOTH as property
     One child has the other child as prop
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwo');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThree');

  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo', 'TMyClassThree');
  FJitCreator[3] := GetCreator(FJitCreator[1], 'TMyClassThree');
end;

procedure TJitClassTest.InitThreeClassesAnchestorParallelAndChildRefIndirect;
begin
  (* One   -> TwoHolder   -> Two
     One   -> ThreeHolder -> Three
     Two   >> One  (inherits)
     Two   -> ThreeHolder -> Three
     Three >> One  (inherits)
  *)
  (* Anchestor Class, has TWO child-classes, BOTH as property
     One child has the other child as prop
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwoHolder');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThreeHolder');

  GetCreator(TMyClass, 'TMyClassTwoHolder', 'TMyClassTwo', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder', 'TMyClassThree', True);

  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo', 'TMyClassThreeHolder');
  FJitCreator[3] := GetCreator(FJitCreator[1], 'TMyClassThree');
end;

procedure TJitClassTest.InitThreeClassesAnchestorParallelAndChildRefIndirect_2;
begin
  (* One   -> TwoHolder   -> Two
     One   -> ThreeHolder -> Three
     Two   >> One  (inherits)
     Two   -> ThreeHolder_2 -> Three  // use different holder
     Three >> One  (inherits)
  *)
  (* Anchestor Class, has TWO child-classes, BOTH as property
     One child has the other child as prop
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwoHolder');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThreeHolder');

  GetCreator(TMyClass, 'TMyClassTwoHolder', 'TMyClassTwo', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder', 'TMyClassThree', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder_2', 'TMyClassThree', True);

  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo', 'TMyClassThreeHolder_2');
  FJitCreator[3] := GetCreator(FJitCreator[1], 'TMyClassThree');
end;

procedure TJitClassTest.InitThreeClassesAnchestorParallelAndChildLoop;
begin
  // 3 circles
  (* One   -> Two
     One   -> Three
     Two   >> One  (inherits)
     Two   -> Three
     Three >> One  (inherits)
     Three -> Two
  *)
  (* Anchestor Class, has TWO child-classes, BOTH as property
     Bothe children has the other child as prop (loop)
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwo');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThree');

  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo', 'TMyClassThree');
  FJitCreator[3] := GetCreator(FJitCreator[1], 'TMyClassThree', 'TMyClassTwo');
end;

procedure TJitClassTest.InitThreeClassesAnchestorParallelAndChildLoopIndirect;
begin
  // 3 circles
  (* One   -> TwoHolder   -> Two
     One   -> ThreeHolder -> Three
     Two   >> One  (inherits)
     Two   -> ThreeHolder -> Three
     Three >> One  (inherits)
     Three -> TwoHolder   -> Two
  *)
  (* Anchestor Class, has TWO child-classes, BOTH as property
     Bothe children has the other child as prop (loop)
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwoHolder');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThreeHolder');

  GetCreator(TMyClass, 'TMyClassTwoHolder', 'TMyClassTwo', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder', 'TMyClassThree', True);

  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo', 'TMyClassThreeHolder');
  FJitCreator[3] := GetCreator(FJitCreator[1], 'TMyClassThree', 'TMyClassTwoHolder');
end;

procedure TJitClassTest.InitThreeClassesAnchestorParallelAndChildLoopIndirect_2;
begin
  // 3 circles
  (* One   -> TwoHolder   -> Two
     One   -> ThreeHolder -> Three
     Two   >> One  (inherits)
     Two   -> ThreeHolder_2 -> Three
     Three >> One  (inherits)
     Three -> TwoHolder_2   -> Two
  *)
  (* Anchestor Class, has TWO child-classes, BOTH as property
     Bothe children has the other child as prop (loop)
  *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassTwoHolder');
  FJitCreator[1].JitProperties.Add('b', 'TMyClassThreeHolder');

  GetCreator(TMyClass, 'TMyClassTwoHolder', 'TMyClassTwo', True);
  GetCreator(TMyClass, 'TMyClassTwoHolder_2', 'TMyClassTwo', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder', 'TMyClassThree', True);
  GetCreator(TMyClass, 'TMyClassThreeHolder_2', 'TMyClassThree', True);

  FJitCreator[2] := GetCreator(FJitCreator[1], 'TMyClassTwo', 'TMyClassThreeHolder_2');
  FJitCreator[3] := GetCreator(FJitCreator[1], 'TMyClassThree', 'TMyClassTwoHolder_2');
end;

procedure TJitClassTest.InitThreeClassesTwoAnchestor;
begin
  (* One   -> Three
     Two   >> One  (inherits)
     Three >> Two  (inherits)
  *)
  (* Class, has grand child-class that has property to orig class *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassThree');

  // inherit TMyClassOne
  FJitCreator[2] := TJitClassCreator.Create(FJitCreator[1], 'TMyClassTwo', 'foo');
  FJitCreator[2].TypeLibrary := FJitTypeLib;
  FJitTypeLib.AddJitClass(FJitCreator[2].JitClassName, FJitCreator[2]);

  // inherit TMyClassTwo;
  FJitCreator[3] := GetCreator(FJitCreator[2], 'TMyClassThree');
end;

procedure TJitClassTest.InitThreeClassesTwoAnchestorIndirect;
begin
  (* One   -> ThreeHolder -> Three
     Two   >> One  (inherits)
     Three >> Two  (inherits)
  *)
  (* Class, has grand child-class that has property to orig class *)
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassThreeHolder');

  GetCreator(TMyClass, 'TMyClassThreeHolder', 'TMyClassThree', True);

  // inherit TMyClassOne
  FJitCreator[2] := TJitClassCreator.Create(FJitCreator[1], 'TMyClassTwo', 'foo');
  FJitCreator[2].TypeLibrary := FJitTypeLib;
  FJitTypeLib.AddJitClass(FJitCreator[2].JitClassName, FJitCreator[2]);

  // inherit TMyClassTwo;
  FJitCreator[3] := GetCreator(FJitCreator[2], 'TMyClassThree');
end;

procedure TJitClassTest.TestTwoClassRefCount(
  AnInitProc: TInitProcedure);
var
  i, j, MemUsed: Integer;
  JitClassOneInfo, JitClassTwoInfo: PTypeInfo;
  jc: TJitClassCreator;
begin
  for i := 1 to 2 do for j := 1 to 2 do begin
    AnInitProc();

    case i of // which class to access first
      1: JitClassOneInfo := FJitCreator[1].TypeInfo;
      2: JitClassTwoInfo := FJitCreator[2].TypeInfo;
    end;
    JitClassOneInfo := FJitCreator[1].TypeInfo; // access the other / double access to the first does not matter
    JitClassTwoInfo := FJitCreator[2].TypeInfo;
    StartAndClearMemMonitor;

    FJitTypeLib.Free;
    case j of // which class to destroy first
      1: FreeAndNil(FJitCreator[1]);
      2: FreeAndNil(FJitCreator[2]);
    end;

    AssertWasNotMemFreed('', JitClassOneInfo);
    AssertWasNotMemFreed('', JitClassTwoInfo);
    FreeAndNil(FJitCreator[1]);
    FreeAndNil(FJitCreator[2]);
    AssertWasMemFreed('', JitClassOneInfo);
    AssertWasMemFreed('', JitClassTwoInfo);
  end;

  // only access one typeinfo
  StopMemMonitor;
  for i := 1 to 2 do for j := 1 to 2 do begin
    MemUsed := GetMemUsed;

    AnInitProc();
    case i of // which class to access first
      1: jc := FJitCreator[1];
      2: jc := FJitCreator[2];
    end;
    JitClassOneInfo := jc.TypeInfo;

    FJitTypeLib.Free;
    case j of // which class to destroy first
      1: FreeAndNil(FJitCreator[1]);
      2: FreeAndNil(FJitCreator[2]);
    end;
    FreeAndNil(FJitCreator[1]);
    FreeAndNil(FJitCreator[2]);

    AssertEquals('mem used ', MemUsed, GetMemUsed);
  end;

end;

procedure TJitClassTest.TestThreeClassRefCount(AnInitProc: TInitProcedure);
var
  i, j, k, MemUsed: Integer;
  JitClassOneInfo, JitClassTwoInfo, JitClassThreeInfo: PTypeInfo;
  LastToFree, jc: TJitClassCreator;
begin
  for i := 1 to 3 do for j := 1 to 3 do for k := 1 to 3 do begin
    AnInitProc();
    case i of // which class to access first
      1: JitClassOneInfo   := FJitCreator[1].TypeInfo;
      2: JitClassTwoInfo   := FJitCreator[2].TypeInfo;
      3: JitClassThreeInfo := FJitCreator[3].TypeInfo;
    end;
    JitClassOneInfo   := FJitCreator[1].TypeInfo;
    JitClassTwoInfo   := FJitCreator[2].TypeInfo;
    JitClassThreeInfo := FJitCreator[3].TypeInfo;
    StartAndClearMemMonitor;
    FJitTypeLib.Free;
    case j of // which class to destroy last
      1: begin LastToFree := FJitCreator[1];   FJitCreator[1] := nil; end;
      2: begin LastToFree := FJitCreator[2];   FJitCreator[2] := nil; end;
      3: begin LastToFree := FJitCreator[3]; FJitCreator[3] := nil; end;
    end;
    case k of // which class to destroy first
      1: FreeAndNil(FJitCreator[1]);
      2: FreeAndNil(FJitCreator[2]);
      3: FreeAndNil(FJitCreator[3]);
    end;
    FJitCreator[1].Free;
    FJitCreator[2].Free;
    FJitCreator[3].Free;

    AssertWasNotMemFreed('', JitClassOneInfo);
    AssertWasNotMemFreed('', JitClassTwoInfo);
    AssertWasNotMemFreed('', JitClassThreeInfo);
    LastToFree.Free;
    AssertWasMemFreed('', JitClassOneInfo);
    AssertWasMemFreed('', JitClassTwoInfo);
    AssertWasMemFreed('', JitClassThreeInfo);
  end;


  // only access one typeinfo
  StopMemMonitor;
  for i := 1 to 3 do for j := 1 to 3 do for k := 1 to 3 do begin
    MemUsed := GetMemUsed;

    AnInitProc();
    case i of // which class to access first
      1: jc := FJitCreator[1];
      2: jc := FJitCreator[2];
      3: jc := FJitCreator[3];
    end;
    JitClassOneInfo := jc.TypeInfo;

    FJitTypeLib.Free;
    case j of // which class to destroy last
      1: FreeAndNil(FJitCreator[1]);
      2: FreeAndNil(FJitCreator[2]);
      3: FreeAndNil(FJitCreator[3]);
    end;
    case k of // which class to destroy first
      1: FreeAndNil(FJitCreator[1]);
      2: FreeAndNil(FJitCreator[2]);
      3: FreeAndNil(FJitCreator[3]);
    end;
    FJitCreator[1].Free;
    FJitCreator[2].Free;
    FJitCreator[3].Free;

    AssertEquals('mem used ', MemUsed, GetMemUsed);
  end;
end;


procedure TJitClassTest.TestRefCountClassCircle;
var
  JitClassOneInfo, JitClassTwoInfo, JitClassThreeInfo: PTypeInfo;
begin
  //////////////////////////////////
  // 1 classes circle
  FJitTypeLib := TJitTypeLibrary.Create;
  FJitCreator[1] := GetCreator(TMyClass, 'TMyClassOne', 'TMyClassOne');
  JitClassOneInfo := FJitCreator[1].TypeInfo;
  StartMemMonitor;

  FJitTypeLib.Free;

  AssertWasNotMemFreed('', JitClassOneInfo);
  FJitCreator[1].Free;
  AssertWasMemFreed('', JitClassOneInfo);

  TestTwoClassRefCount(@InitTwoClasses);
  TestTwoClassRefCount(@InitTwoClassesWithOneSelfRef);
  TestTwoClassRefCount(@InitTwoClassesWithDoubleLink);
  TestTwoClassRefCount(@InitTwoClassesAnchestor);
  TestTwoClassRefCount(@InitTwoClassesAnchestorWithAnchestorProp);

  TestThreeClassRefCount(@InitThreeClasses);
  TestThreeClassRefCount(@InitThreeClassesWithOneSelfRef);
  TestThreeClassRefCount(@InitThreeClassesWithOneDoubleLink);
  TestThreeClassRefCount(@InitThreeClassesWithSubLoop);
  TestThreeClassRefCount(@InitThreeClassesWithSubLoopAndOneSelfRef);
  TestThreeClassRefCount(@InitThreeClassesWithTwoSubLoop);
  TestThreeClassRefCount(@InitThreeClassesChained);
  TestThreeClassRefCount(@InitThreeClassesChainedIndirect);
  TestThreeClassRefCount(@InitThreeClassesOneAnchestor);
  TestThreeClassRefCount(@InitThreeClassesOneAnchestorIndirect);
  TestThreeClassRefCount(@InitThreeClassesAnchestorParallel);
  TestThreeClassRefCount(@InitThreeClassesAnchestorParallelIndirect); // 5 classes
  TestThreeClassRefCount(@InitThreeClassesAnchestorParallelAndChildRef);
  TestThreeClassRefCount(@InitThreeClassesAnchestorParallelAndChildRefIndirect);
  TestThreeClassRefCount(@InitThreeClassesAnchestorParallelAndChildRefIndirect_2);
  TestThreeClassRefCount(@InitThreeClassesAnchestorParallelAndChildLoop);
  TestThreeClassRefCount(@InitThreeClassesAnchestorParallelAndChildLoopIndirect);
  TestThreeClassRefCount(@InitThreeClassesAnchestorParallelAndChildLoopIndirect_2);
  TestThreeClassRefCount(@InitThreeClassesTwoAnchestor);
  TestThreeClassRefCount(@InitThreeClassesTwoAnchestorIndirect);

end;

procedure TJitClassTest.TestRefCountMethodCircle;
var
  JitTypeLib: TJitTypeLibrary;
  MyProc: TJitType;
  MyProcInfo: PTypeInfo;
begin
  JitTypeLib := TJitTypeLibrary.Create;
  MyProc := JitTypeLib.AddType('TMyProc', 'procedure (a: TMyProc)');
  MyProcInfo := MyProc.TypeInfo;
  StartMemMonitor;

  AssertWasNotMemFreed('', MyProcInfo);
  JitTypeLib.Free;
  AssertWasMemFreed('', MyProcInfo);


end;

procedure TJitClassTest.TestParseJitType;
var
  JitCreator: TJitClassCreator;
  Cnt: Integer;
  JitTypeLib: TJitTypeLibrary;
  t: PTypeInfo;

  function DoParseNoError(AName: String; ADecl: String): PTypeInfo;
  var
    jp: TJitProperty;
  begin
    AName := AName + ' ' + ADecl + ' ';
    inc(Cnt);
    try
      jp := JitCreator.JitProperties.Add('a'+IntToStr(Cnt), ADecl);
      Result := jp.TypeInfo;
      //AssertTrue(AName, jp.TypeInfo <> nil);
    except
      AssertTrue(AName + 'no except', False);
    end;
  end;

  function ParseNoError(AName: String; ADecl: String): PTypeInfo;
  var
    s: String;
  begin
    Result := DoParseNoError(AName, ADecl);
    DoParseNoError(AName+' IN record', 'record a: '+ADecl+' end');
    s := Trim(ADecl);
    if (s <> '') and (s[Length(s)] <> ';') then
      DoParseNoError(AName+' IN record ;', 'record a: '+ADecl+'; end');
  end;

  procedure ParseExpectError(AName: String; ADecl: String);
  var
    jp: TJitProperty;
    t: Boolean;
  begin
    AName := AName + ' ' + ADecl + ' ';
    inc(Cnt);
    t := True;
    try
      jp := JitCreator.JitProperties.Add('a'+IntToStr(Cnt), ADecl);
      jp.TypeInfo;
      t := False; // should skipped by exception
    except
      on E: Exception do
        if not (e is JitTypeParserException) then
          t := False; // wrong exception type
    end;
    AssertTrue(AName, t); // should not reach if above fails
  end;


begin
  Cnt := 0;
  JitCreator := TJitClassCreator.Create(TMyClass, 'TJitTestSimpleOuterClass', 'unitfoo');
  JitTypeLib := TJitTypeLibrary.Create;
  JitCreator.TypeLibrary := JitTypeLib;

  JitTypeLib.AddAlias('integer', 'longint');
  JitTypeLib.AddAlias('int32', 'integer');
  JitTypeLib.AddType('MyEnum', '(e1, e2, e3, e4, e5, e6)', 'unitfoo');
  JitTypeLib.AddType('OtherEnum',  '(zo1, o2, zo3, o4, zo5)', 'abc');
  JitTypeLib.AddType('OtherEnum',  '(o1, o2, o3, o4, o5)', 'unitfoo');
  JitTypeLib.AddType('OtherEnumX', '(xo1, o2, xo3, o4, xo5)', 'unitbar');

  ParseExpectError('', 'string');
  ParseNoError('', 'string[1]');

  JitTypeLib.AddAlias('string', 'AnsiString');
  ParseNoError('', 'longint');
  ParseNoError('', 'integer');
  ParseNoError('', 'int32');
  ParseNoError('', 'string');
  ParseNoError('', 'string[1]');
  ParseNoError('', 'string[255]');
  ParseNoError('', 'string[$FF]');
  ParseNoError('', 'string[%10]');
  ParseNoError('', 'string[&10]');

  ParseNoError('', 'procedure');
  ParseNoError('', 'procedure()');
  ParseNoError('', 'procedure of object');
  ParseNoError('', 'procedure () of object');

  ParseNoError('', 'function: int64 ');
  ParseNoError('', 'function(): int64');
  ParseNoError('', 'function: int64 of object');
  ParseNoError('', 'function(): int64 of object');
  ParseNoError('', 'function(): OtherEnum of object');
  ParseNoError('', 'function(): unitfoo.OtherEnum of object');
  ParseNoError('', 'function(): abc.OtherEnum of object');

  ParseNoError('', 'function(a,b: MyEnum): int64 ');
  ParseNoError('', 'function(a,b: unitfoo.MyEnum): int64 ');
  ParseNoError('', 'function(a,b: word; c: boolean=true; const x): int64 ');
  ParseNoError('', 'function(a,b: word; c: boolean=true; const x; d,e: array of int64): int64 ');

  ParseNoError('', 'record end;');
  ParseNoError('', 'record a,b: word end;');
  ParseNoError('', 'record a,b: word; end;');
  ParseNoError('', 'record a,b: word; c: procedure() end;');
  ParseNoError('', 'record a,b: word; c: procedure(); end;');
  ParseNoError('', 'record a,b: word; c: procedure(); x: record xx: boolean end; end;');
  ParseNoError('', 'record a,b: word; c: procedure(); x: record xx: boolean end end;');
  ParseNoError('', 'record b: MyEnum end;');
  ParseNoError('', 'record b: MyEnum; end;');
  ParseNoError('', 'record b: unitfoo.MyEnum end;');
  ParseNoError('', 'record b: unitfoo.MyEnum; end;');

  ParseNoError('', '1..3');
  ParseNoError('', '''a''..''c''');
  ParseNoError('', 'e2..e3');
  ParseNoError('', 'MyEnum.e2..e3');
  ParseNoError('', 'e2..MyEnum.e3');
  ParseNoError('', 'MyEnum.e2..MyEnum.e3');
  ParseNoError('', 'MyEnum(e2)..MyEnum.e3');
  ParseNoError('', 'MyEnum.e2..MyEnum(e3)');

  t := ParseNoError('', 'o2..o4');
  AssertEquals('has o3', GetEnumValue(t, 'o3'), 2);
  AssertEquals('has not xo3', GetEnumValue(t, 'xo3'), -1);
  AssertEquals('has not zo3', GetEnumValue(t, 'zo3'), -1);

  ParseNoError('', 'unitbar.OtherEnumX.o2..unitbar.OtherEnumX(o4)');
  ParseNoError('', 'OtherEnumX.o2..OtherEnumX(o4)');
  ParseNoError('', 'OtherEnumX(o2)..OtherEnumX(o4)');
  ParseNoError('', 'OtherEnumX.o2..OtherEnumX.o4');
  t := ParseNoError('', 'OtherEnumX.o2..o4');
  AssertEquals('has xo3', GetEnumValue(t, 'xo3'), 2);
  AssertEquals('has not o3',  GetEnumValue(t, 'o3'), -1);
  AssertEquals('has not zo3', GetEnumValue(t, 'zo3'), -1);

  t := ParseNoError('', 'abc.OtherEnum.o2..o4');
  AssertEquals('has zo3', GetEnumValue(t, 'zo3'), 2);
  AssertEquals('has not o3',  GetEnumValue(t, 'o3'), -1);
  AssertEquals('has not xo3', GetEnumValue(t, 'xo3'), -1);


  ParseNoError('', 'set of (a,b,c)');
  ParseNoError('', 'set of byte');
  ParseNoError('', 'set of 1..3');
  ParseNoError('', 'set of Byte(1)..Byte(3)');
  ParseNoError('', 'set of MyEnum');
  ParseNoError('', 'set of unitfoo.MyEnum');
  ParseNoError('', 'set of MyEnum(1)..e3');
  ParseNoError('', 'set of unitfoo.MyEnum(1)..e3');
  ParseNoError('', 'set of e2..e5'); // typelibrary with enum (en1..en7)

  ParseNoError('', 'array of int64');
  ParseNoError('', 'array of array of int64');
  ParseNoError('', 'array of string');
  ParseNoError('', 'array of string[2]');
  ParseNoError('', 'array of record a: word; end;');
  ParseNoError('', 'array of record a: (b,c) end;');
  ParseNoError('', 'record a: array of string[2]; b: word; end;');



  ParseExpectError('', 'foo');
  ParseExpectError('', 'procedure foo');
  ParseExpectError('', 'procedure: foo');
  ParseExpectError('', 'procedure of ');
  ParseExpectError('', 'procedure(a: foo)');
  ParseExpectError('', 'function:');
  ParseExpectError('', 'function: foo');
  ParseExpectError('', 'function(): unitbar.OtherEnum');
  ParseExpectError('', 'function: array of int64');
  ParseExpectError('', 'string[256]');
  ParseExpectError('', 'string[0]');
  ParseExpectError('', 'string[-1]');
  ParseExpectError('', 'OtherEnumX.o2..OtherEnum.o4');
  ParseExpectError('', 'abc.OtherEnumX.o2..unitfoo.OtherEnum.o4');
  ParseExpectError('', 'record b: unitbar.MyEnum; end;');
  ParseExpectError('', 'record b: what.MyEnum; end;');

  //ParseExpectError('', 'set of o2..e5'); // typelibrary with enum (en1..en7)


  // TEST & escaping
  JitTypeLib.AddAlias('&function', 'integer');

  ParseNoError('', '&Function');
  ParseNoError('', '&integer');
  ParseNoError('', '&longint');
  ParseExpectError('', 'Function');
  ParseExpectError('', '&foo');

  JitCreator.Free;
  JitTypeLib.Free;
end;

procedure TJitClassTest.TestSetEnum;
  procedure CheckEnum(TpInf: PTypeInfo; Names: array of string; UnitName: string; ExpMinVal: Integer = 0);
  var
    i: Integer;
    TpDat: PTypeData;
    MinVal: LongInt;
  begin
    AssertTrue('kind', TpInf^.Kind = tkEnumeration);
    TpDat := GetTypeData(TpInf);
    MinVal := TpDat^.MinValue;
    AssertEquals('minval', ExpMinVal, TpDat^.MinValue);
    AssertEquals('elem count', Length(Names), GetEnumNameCount(TpInf));
    AssertEquals('unitname', UnitName, GetEnumName(TpInf, Length(Names) + MinVal)); // unitname
    for i := 0 to length(Names) - 1 do begin
      AssertEquals('elem', Names[i], GetEnumName(TpInf, i+MinVal));
      AssertEquals('',   i+MinVal, GetEnumValue(TpInf, Names[i]));
    end;
    //AssertEquals('bad val',   -1, GetEnumValue(TpInf, UnitName));
    AssertEquals('bad val',   -1, GetEnumValue(TpInf, 'nevereverusethis'));
  end;
  procedure CheckSet(TpInf: PTypeInfo; Names: array of string);
  var
    i: Integer;
    CT: PTypeInfo;
    MinVal: LongInt;
  begin
    AssertTrue('kind', TpInf^.Kind = tkSet);
    CT := GetTypeData(TpInf)^.CompType;
    MinVal := GetTypeData(CT)^.MinValue;
    for i := 0 to length(Names) - 1 do begin
      AssertEquals('elem', Names[i], SetToString(TpInf, 1 << (i+MinVal), False));
    end;
  end;
var
  JitTypeLib: TJitTypeLibrary;
  jp, en1, en2: TJitType;
  ti, ti2: PTypeInfo;
begin
  JitTypeLib := TJitTypeLibrary.Create;

  jp := TJitTypeInfo.Create('x','(a,b,c)', 'unitfoo');
  ti := jp.TypeInfo;
  CheckEnum(ti, ['a', 'b', 'c'], 'unitfoo');
  jp.Free;

  en1 := JitTypeLib.AddType('En1','(a,b,c,d,e)', 'unitfoo');
  ti := en1.TypeInfo;
  CheckEnum(ti, ['a', 'b', 'c', 'd', 'e'], 'unitfoo');

  en2 := JitTypeLib.AddType('En2','b..d', 'unitbar');
  ti := en2.TypeInfo;
  CheckEnum(ti, ['b', 'c', 'd'], 'unitbar', 1);
  ti2 := GetTypeData(ti)^.BaseType;
  AssertTrue('', ti2 = en1.TypeInfo);
  CheckEnum(ti2, ['a', 'b', 'c', 'd', 'e'], 'unitfoo');

  jp := TJitTypeInfo.Create('x','en1.b..d', 'unitabc', JitTypeLib);
  ti := jp.TypeInfo;
  CheckEnum(ti, ['b', 'c', 'd'], 'unitabc', 1);
  ti2 := GetTypeData(ti)^.BaseType;
  AssertTrue('', ti2 = en1.TypeInfo);
  CheckEnum(ti2, ['a', 'b', 'c', 'd', 'e'], 'unitfoo');
  jp.Free;



  jp := TJitTypeInfo.Create('x','set of (a,b,c)', 'unitfoo');
  ti := jp.TypeInfo;
  CheckSet(ti, ['a', 'b', 'c']);
  ti2 := GetTypeData(ti)^.CompType;
  CheckEnum(ti2, ['a', 'b', 'c'], 'unitfoo');
  jp.Free;

  jp := TJitTypeInfo.Create('x','set of En1', 'unitsome', JitTypeLib);
  ti := jp.TypeInfo;
  CheckSet(ti, ['a', 'b', 'c', 'd', 'e']);
  ti2 := GetTypeData(ti)^.CompType;
  CheckEnum(ti2, ['a', 'b', 'c', 'd', 'e'], 'unitfoo');
  jp.Free;

  jp := TJitTypeInfo.Create('x','set of En2', 'unitsome', JitTypeLib);
  ti := jp.TypeInfo;
  CheckSet(ti, ['b', 'c', 'd']);
  ti2 := GetTypeData(ti)^.CompType;
  CheckEnum(ti2, ['b', 'c', 'd'], 'unitbar', 1);
  ti2 := GetTypeData(ti2)^.BaseType;
  CheckEnum(ti2, ['a', 'b', 'c', 'd', 'e'], 'unitfoo');
  jp.Free;

  jp := TJitTypeInfo.Create('x','set of EN1(b)..d', 'unitsome', JitTypeLib);
  ti := jp.TypeInfo;
  CheckSet(ti, ['b', 'c', 'd']);
  ti2 := GetTypeData(ti)^.CompType;
  CheckEnum(ti2, ['b', 'c', 'd'], 'unitsome', 1);
  ti2 := GetTypeData(ti2)^.BaseType;
  AssertTrue('', ti2 = en1.TypeInfo);
  CheckEnum(ti2, ['a', 'b', 'c', 'd', 'e'], 'unitfoo');
  jp.Free;

  jp := TJitTypeInfo.Create('x','set of EN2(b)..d', 'unitother', JitTypeLib);
  ti := jp.TypeInfo;
  CheckSet(ti, ['b', 'c', 'd']);
  ti2 := GetTypeData(ti)^.CompType;
  CheckEnum(ti2, ['b', 'c', 'd'], 'unitother', 1);
  ti2 := GetTypeData(ti2)^.BaseType;
  AssertTrue('', ti2 = en2.TypeInfo);
  jp.Free;


  jp := TJitTypeInfo.Create('x','set of EN1.b..d', 'unitsome', JitTypeLib);
  ti := jp.TypeInfo;
  CheckSet(ti, ['b', 'c', 'd']);
  ti2 := GetTypeData(ti)^.CompType;
  CheckEnum(ti2, ['b', 'c', 'd'], 'unitsome', 1);
  ti2 := GetTypeData(ti2)^.BaseType;
  AssertTrue('', ti2 = en1.TypeInfo);
  CheckEnum(ti2, ['a', 'b', 'c', 'd', 'e'], 'unitfoo');
  jp.Free;

  jp := TJitTypeInfo.Create('x','set of EN2.b..d', 'unitother', JitTypeLib);
  ti := jp.TypeInfo;
  CheckSet(ti, ['b', 'c', 'd']);
  ti2 := GetTypeData(ti)^.CompType;
  CheckEnum(ti2, ['b', 'c', 'd'], 'unitother', 1);
  ti2 := GetTypeData(ti2)^.BaseType;
  AssertTrue('', ti2 = en2.TypeInfo);
  jp.Free;

  jp := TJitTypeInfo.Create('x','set of EN2.b..EN2.d', 'unitother', JitTypeLib);
  ti := jp.TypeInfo;
  CheckSet(ti, ['b', 'c', 'd']);
  ti2 := GetTypeData(ti)^.CompType;
  CheckEnum(ti2, ['b', 'c', 'd'], 'unitother', 1);
  ti2 := GetTypeData(ti2)^.BaseType;
  AssertTrue('', ti2 = en2.TypeInfo);
  jp.Free;



  JitTypeLib.Free;
end;

procedure TJitClassTest.TestMethods;
var
  JitTypeLib: TJitTypeLibrary;

  procedure DoTest(Decl: String; ExpParam: array of string);
  var
    context: TRttiContext;
    jp: TJitTypeInfo;
    t: TRttiType;
  params: specialize TArray<TRttiParameter>;
  i: Integer;
  begin
    context := TRttiContext.Create;
    jp := TJitTypeInfo.Create('x', Decl, JitTypeLib);
    t := context.GetType(jp.TypeInfo);

    AssertTrue(t <> nil);
    AssertTrue(t is TRttiInvokableType);

    params := TRttiInvokableType(t).GetParameters;
    AssertEquals(Length(ExpParam), Length(params));

    for i := 0 to Length(ExpParam) - 1 do begin
      //debugln(params[i].tostring);
      AssertEquals(LowerCase(ExpParam[i]), LowerCase(params[i].ToString));
    end;
    jp.Free;
    context.Free;
  end;

begin
  JitTypeLib := TJitTypeLibrary.Create;
  JitTypeLib.AddType('TEnum1', '(a,b,c)');

  DoTest('function (const a: byte; b,c: word): boolean',
    ['const a: byte', 'b: word', 'c: word']
  );
  DoTest('function (const a: byte; var b,c: word): boolean of object',
    ['const a: byte', 'var b: word', 'var c: word']
  );
  DoTest('procedure (var a: TEnum1; b: array of Int64) of object',
    ['var a: TEnum1', {$IFDEF FooFIxed}'b: '+{$ENDIF}'array of Int64']
  );

  JitTypeLib.Free;
end;



initialization

  RegisterTest(TJitClassTest);

  GetMemoryManager(MMgr);
  OrigFreemem     := MMgr.Freemem;
  OrigFreememSize := MMgr.FreememSize;
  MMgr.Freemem     := @MyFreemem;
  MMgr.FreememSize := @MyFreememSize;
  SetMemoryManager(MMgr);
end.

