{
 Test with:
     ./runtests --format=plain --suite=TTestCTStdCodetools
     ./runtests --format=plain --suite=TestCTStdFindBlockStart
     ./runtests --format=plain --suite=TestCTRemoveUnitFromAllUsesSections
}
unit TestStdCodetools;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LCLProc, testglobals, fpcunit,
  CodeToolManager, StdCodeTools, CodeCache, LinkScanner;

type

  { TTestCTStdCodetools }

  TTestCTStdCodetools = class(TTestCase)
  private
    function GetCTMarker(Code: TCodeBuffer; Comment: string; out Position: TPoint): boolean;
  published
    procedure TestCTStdFindBlockStart;
    procedure TestCTRemoveUnitFromAllUsesSections;
  end;

implementation

{ TTestCTStdCodetools }

function TTestCTStdCodetools.GetCTMarker(Code: TCodeBuffer; Comment: string;
  out Position: TPoint): boolean;
var
  p: SizeInt;
begin
  Result:=false;
  Position:=Point(0,0);
  if Comment[1]<>'{' then
    Comment:='{'+Comment+'}';
  p:=System.Pos(Comment,Code.Source);
  if p<1 then
    AssertEquals('searching marker: '+Comment,true,p>=1);
  Code.AbsoluteToLineCol(p+length(Comment),Position.Y,Position.X);
  if Position.Y<1 then
    AssertEquals('Code.AbsoluteToLineCol: '+Comment,true,Position.Y>=1)
  else
    Result:=true;
end;

procedure TTestCTStdCodetools.TestCTStdFindBlockStart;
var
  Code: TCodeBuffer;

  function GetSource: string;
  begin
    Result:=
     'program TestStdCodeTools;'+LineEnding
    +'begin'+LineEnding
    +'  if true then {begin1}begin'+LineEnding
    +'    {try1}try'+LineEnding
    +'      writeln;'+LineEnding
    +'    {try1finally}finally'+LineEnding
    +'      writeln;'+LineEnding
    +'    {try1end}end;'+LineEnding
    +'    writeln;'+LineEnding
    +'  {begin1end}end;'+LineEnding
    +'end.'+LineEnding;
  end;

  function GetInfo(XY: TPoint): string;
  var
    Line: String;
  begin
    Line:=Code.GetLine(XY.Y-1);
    Result:=dbgs(XY)+': '+copy(Line,1,XY.X-1)+'|'+copy(Line,XY.X,length(Line));
  end;

  procedure Test(aTitle, StartMarker,EndMarker: string);
  var
    BlockStart: TPoint;
    BlockEnd: TPoint;
    NewCode: TCodeBuffer;
    NewX: integer;
    NewY: integer;
    NewTopline: integer;
  begin
    if not GetCTMarker(Code,StartMarker,BlockStart) then exit;
    if not GetCTMarker(Code,EndMarker,BlockEnd) then exit;
    //debugln(['TTestCTStdCodetools.TestCTStdFindBlockStart BlockStart=',GetInfo(BlockStart),' BlockEnd=',GetInfo(BlockEnd)]);
    if not CodeToolBoss.FindBlockStart(Code,BlockEnd.X,BlockEnd.Y,NewCode,NewX,NewY,NewTopline)
    then
      AssertEquals(aTitle+': '+CodeToolBoss.ErrorMessage,true,false)
    else
      AssertEquals(aTitle,GetInfo(BlockStart),GetInfo(Point(NewX,NewY)))
  end;

begin
  Code:=CodeToolBoss.CreateFile('TestStdCodeTools.pas');
  Code.Source:=GetSource();

  Test('begin,try,finally,end|end','begin1','begin1end');
  Test('begin,try,finally,|end,end','try1finally','try1end');
  Test('begin,try,finally,|end,end','try1','try1finally');
end;

procedure TTestCTStdCodetools.TestCTRemoveUnitFromAllUsesSections;

  function GetSource(UsesSrc: string): string;
  begin
    Result:='program TestStdCodeTools;'+LineEnding
      +UsesSrc
  end;

  procedure Test(RemoveUnit, UsesSrc, ExpectedUsesSrc: string);
  var
    Header: String;
    Footer: String;
    Code: TCodeBuffer;
    Src: String;
  begin
    Header:='program TestStdCodeTools;'+LineEnding;
    Footer:=LineEnding
      +'begin'+LineEnding
      +'end.'+LineEnding;
    Code:=CodeToolBoss.CreateFile('TestStdCodeTools.pas');
    Code.Source:=Header+UsesSrc+Footer;
    if not CodeToolBoss.RemoveUnitFromAllUsesSections(Code,RemoveUnit) then
    begin
      AssertEquals('RemoveUnitFromAllUsesSections failed: '+CodeToolBoss.ErrorMessage,true,false);
    end else begin
      Src:=Code.Source;
      AssertEquals('RemoveUnitFromAllUsesSections altered header: ',Header,LeftStr(Src,length(Header)));
      System.Delete(Src,1,length(Header));
      AssertEquals('RemoveUnitFromAllUsesSections altered footer: ',Footer,RightStr(Src,length(Footer)));
      System.Delete(Src,length(Src)-length(Footer)+1,length(Footer));
      AssertEquals('RemoveUnitFromAllUsesSections: ',ExpectedUsesSrc,Src);
    end;
  end;

begin
  // remove first unit
  Test('windows',
   'uses'+LineEnding
  +'   Windows, Messages, Forms,'+LineEnding
  +'   Dialogs, inifiles;'+LineEnding
  ,
   'uses'+LineEnding
  +'   Messages, Forms,'+LineEnding
  +'   Dialogs, inifiles;'+LineEnding
  );

  // remove middle unit
  Test('shellapi',
   'uses'+LineEnding
  +'   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,'+LineEnding
  +'   Dialogs, shellAPI, StdCtrls, ExtCtrls, ComCtrls, strutils, Buttons, inifiles;'+LineEnding
  ,
   'uses'+LineEnding
  +'   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,'+LineEnding
  +'   Dialogs, StdCtrls, ExtCtrls, ComCtrls, strutils, Buttons, inifiles;'+LineEnding
  );

  // remove first unit in second line
  Test('shellapi',
   'uses'+LineEnding
  +'   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,'+LineEnding
  +'   shellAPI, StdCtrls, ExtCtrls, ComCtrls, strutils, Buttons, inifiles;'+LineEnding
  ,
   'uses'+LineEnding
  +'   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,'+LineEnding
  +'   StdCtrls, ExtCtrls, ComCtrls, strutils, Buttons, inifiles;'+LineEnding
  );

  // remove last unit in first line
  Test('forms',
   'uses'+LineEnding
  +'   Windows, Messages, Forms,'+LineEnding
  +'   Dialogs, inifiles;'+LineEnding
  ,
   'uses'+LineEnding
  +'   Windows, Messages,'+LineEnding
  +'   Dialogs, inifiles;'+LineEnding
  );

  // remove last unit
  Test('inifiles',
   'uses'+LineEnding
  +'   Windows, Messages, Forms,'+LineEnding
  +'   Dialogs, inifiles;'+LineEnding
  ,
   'uses'+LineEnding
  +'   Windows, Messages, Forms,'+LineEnding
  +'   Dialogs;'+LineEnding
  );
end;

initialization
  AddToPascalTestSuite(TTestCTStdCodetools);

end.

